Skip to content

Commit 4717a08

Browse files
ennsharmaclaude
andcommitted
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 <noreply@anthropic.com>
1 parent 0813d97 commit 4717a08

1 file changed

Lines changed: 38 additions & 7 deletions

File tree

src/index.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,44 @@ async function main() {
7070
}
7171
}
7272

73-
// OAuth metadata discovery endpoint
73+
// OAuth metadata discovery endpoints
7474
// Only served by MCP servers (not standalone auth servers)
7575
if (config.auth.mode !== 'auth_server') {
76+
// Determine the auth server URL based on mode
77+
const authServerUrl = config.auth.mode === 'internal'
78+
? config.baseUri // Internal mode: auth is in same process
79+
: config.auth.externalUrl!; // External mode: separate auth server
80+
81+
// OAuth 2.0 Protected Resource Metadata (RFC 9728)
82+
// This is what the MCP spec requires the *resource server* to expose: the
83+
// bearer-auth middleware advertises this URL in the WWW-Authenticate header
84+
// of 401 responses, and clients follow it to discover the authorization
85+
// server. Served at the root well-known path (advertised in 401s) and at
86+
// the path-suffixed variant for the /mcp endpoint (RFC 9728 §3.1 path
87+
// insertion, for clients that derive the URL from the endpoint themselves).
88+
const protectedResourceHandler = (resource: string) => (req: express.Request, res: express.Response) => {
89+
logger.info('OAuth protected resource metadata discovery', {
90+
userAgent: req.get('user-agent'),
91+
authMode: config.auth.mode,
92+
ip: req.ip
93+
});
94+
95+
res.json({
96+
resource,
97+
resource_name: 'MCP Feature Reference Server',
98+
authorization_servers: [authServerUrl],
99+
bearer_methods_supported: ['header'],
100+
service_documentation: 'https://modelcontextprotocol.io'
101+
});
102+
};
103+
app.get('/.well-known/oauth-protected-resource', protectedResourceHandler(config.baseUri));
104+
app.get('/.well-known/oauth-protected-resource/mcp', protectedResourceHandler(`${config.baseUri}/mcp`));
105+
106+
// OAuth 2.0 Authorization Server Metadata (RFC 8414), kept on the MCP
107+
// server for backwards compatibility: clients of the 2025-03-26 revision
108+
// of the MCP spec discover auth by querying this path on the MCP server
109+
// directly. In external mode it proxies the metadata shape of the
110+
// external auth server's endpoints.
76111
app.get('/.well-known/oauth-authorization-server', (req, res) => {
77112
// Log the metadata discovery request
78113
logger.info('OAuth metadata discovery', {
@@ -81,11 +116,6 @@ async function main() {
81116
ip: req.ip
82117
});
83118

84-
// Determine the auth server URL based on mode
85-
const authServerUrl = config.auth.mode === 'internal'
86-
? config.baseUri // Internal mode: auth is in same process
87-
: config.auth.externalUrl!; // External mode: separate auth server
88-
89119
res.json({
90120
issuer: authServerUrl,
91121
authorization_endpoint: `${authServerUrl}/authorize`,
@@ -181,7 +211,8 @@ async function main() {
181211
console.log('MCP Endpoints:');
182212
console.log(` Streamable HTTP: ${config.baseUri}/mcp`);
183213
console.log(` SSE (legacy): ${config.baseUri}/sse`);
184-
console.log(` OAuth Metadata: ${config.baseUri}/.well-known/oauth-authorization-server`);
214+
console.log(` Protected Resource Metadata: ${config.baseUri}/.well-known/oauth-protected-resource`);
215+
console.log(` OAuth Metadata (legacy): ${config.baseUri}/.well-known/oauth-authorization-server`);
185216
console.log('');
186217
console.log('MCP App Example Servers:');
187218
for (const slug of AVAILABLE_EXAMPLES) {

0 commit comments

Comments
 (0)