fix(mcp): return 405 (not 401) on GET /mcp to stop client re-auth storm (#747)
The Streamable HTTP GET handler returned 401 unconditionally. This server is stateless and offers no server-initiated SSE stream, for which the MCP Streamable HTTP spec requires 405 Method Not Allowed. Returning 401 made spec-compliant clients (Claude connector, Claude Desktop, Cursor) treat the SSE GET as an auth failure and enter a refresh-token → re-open-GET → 401 retry loop. Across the active connector base this storms /api/extensions/ext/mcp-server/mcp (observed ~steady GET→401 traffic on app.gnubok.se) and churns OAuth API-key rotation — and tripped a Vercel usage anomaly (edge requests + function invocations spiking ~16x). OAuth discovery remains bootstrapped on the POST 401 (WWW-Authenticate + .well-known/oauth-protected-resource); the POST JSON-RPC channel and the POST-only npm bridge are unaffected. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ca3ae65b12
commit
7aa37fd3b8
@@ -36,14 +36,18 @@ export const mcpServerExtension: Extension = {
|
||||
method: 'GET',
|
||||
path: '/mcp',
|
||||
skipAuth: true,
|
||||
// This server is stateless and offers no server-initiated SSE stream, so
|
||||
// the Streamable HTTP spec requires 405 Method Not Allowed here. Returning
|
||||
// 401 (as we previously did) makes spec-compliant clients (Claude
|
||||
// connector, Claude Desktop, Cursor) treat the SSE GET as an auth failure
|
||||
// and retry-loop — refresh token → re-open GET → 401 → … — which storms
|
||||
// the endpoint and churns OAuth key rotation. OAuth discovery is
|
||||
// bootstrapped on the POST 401 (WWW-Authenticate), not here.
|
||||
handler: async (request: Request) => {
|
||||
if (isForbiddenOrigin(request)) return forbiddenOriginResponse()
|
||||
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
|
||||
return new Response('Authorization required', {
|
||||
status: 401,
|
||||
headers: {
|
||||
'WWW-Authenticate': `Bearer resource_metadata="${appUrl}/.well-known/oauth-protected-resource"`,
|
||||
},
|
||||
return new Response('Method Not Allowed', {
|
||||
status: 405,
|
||||
headers: { Allow: 'POST, DELETE' },
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user