From 4717a0801fa25efc7ed53f7d9bf8f4a820f75ba2 Mon Sep 17 00:00:00 2001 From: ennsharma Date: Thu, 11 Jun 2026 16:25:27 -0700 Subject: [PATCH] Serve OAuth protected resource metadata on the MCP server The bearer-auth middleware already advertises a resource_metadata URL in 401 WWW-Authenticate headers, but no route served it, so spec-compliant clients following the pointer got a 404 (#22). In EXTERNAL mode there was no way to discover the authorization server at all besides the legacy RFC 8414 shim. - Serve RFC 9728 protected resource metadata at /.well-known/oauth-protected-resource (the URL advertised in 401s) and /.well-known/oauth-protected-resource/mcp (RFC 9728 path insertion), listing the mode-appropriate authorization server - Keep the legacy /.well-known/oauth-authorization-server shim for clients of the 2025-03-26 spec revision, and label it as such Fixes #22 Co-Authored-By: Claude Fable 5 --- src/index.ts | 45 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index d587065..4b69e47 100644 --- a/src/index.ts +++ b/src/index.ts @@ -70,9 +70,44 @@ async function main() { } } - // OAuth metadata discovery endpoint + // OAuth metadata discovery endpoints // Only served by MCP servers (not standalone auth servers) if (config.auth.mode !== 'auth_server') { + // Determine the auth server URL based on mode + const authServerUrl = config.auth.mode === 'internal' + ? config.baseUri // Internal mode: auth is in same process + : config.auth.externalUrl!; // External mode: separate auth server + + // OAuth 2.0 Protected Resource Metadata (RFC 9728) + // This is what the MCP spec requires the *resource server* to expose: the + // bearer-auth middleware advertises this URL in the WWW-Authenticate header + // of 401 responses, and clients follow it to discover the authorization + // server. Served at the root well-known path (advertised in 401s) and at + // the path-suffixed variant for the /mcp endpoint (RFC 9728 ยง3.1 path + // insertion, for clients that derive the URL from the endpoint themselves). + const protectedResourceHandler = (resource: string) => (req: express.Request, res: express.Response) => { + logger.info('OAuth protected resource metadata discovery', { + userAgent: req.get('user-agent'), + authMode: config.auth.mode, + ip: req.ip + }); + + res.json({ + resource, + resource_name: 'MCP Feature Reference Server', + authorization_servers: [authServerUrl], + bearer_methods_supported: ['header'], + service_documentation: 'https://modelcontextprotocol.io' + }); + }; + app.get('/.well-known/oauth-protected-resource', protectedResourceHandler(config.baseUri)); + app.get('/.well-known/oauth-protected-resource/mcp', protectedResourceHandler(`${config.baseUri}/mcp`)); + + // OAuth 2.0 Authorization Server Metadata (RFC 8414), kept on the MCP + // server for backwards compatibility: clients of the 2025-03-26 revision + // of the MCP spec discover auth by querying this path on the MCP server + // directly. In external mode it proxies the metadata shape of the + // external auth server's endpoints. app.get('/.well-known/oauth-authorization-server', (req, res) => { // Log the metadata discovery request logger.info('OAuth metadata discovery', { @@ -81,11 +116,6 @@ async function main() { ip: req.ip }); - // Determine the auth server URL based on mode - const authServerUrl = config.auth.mode === 'internal' - ? config.baseUri // Internal mode: auth is in same process - : config.auth.externalUrl!; // External mode: separate auth server - res.json({ issuer: authServerUrl, authorization_endpoint: `${authServerUrl}/authorize`, @@ -181,7 +211,8 @@ async function main() { console.log('MCP Endpoints:'); console.log(` Streamable HTTP: ${config.baseUri}/mcp`); console.log(` SSE (legacy): ${config.baseUri}/sse`); - console.log(` OAuth Metadata: ${config.baseUri}/.well-known/oauth-authorization-server`); + console.log(` Protected Resource Metadata: ${config.baseUri}/.well-known/oauth-protected-resource`); + console.log(` OAuth Metadata (legacy): ${config.baseUri}/.well-known/oauth-authorization-server`); console.log(''); console.log('MCP App Example Servers:'); for (const slug of AVAILABLE_EXAMPLES) {