From a6534f82e158bb08024012aa69b70ca51bbf28be Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 18 May 2026 13:01:51 -0700 Subject: [PATCH] fix(serve): return 405 on GET /mcp instead of 404 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MCP Streamable HTTP spec says GET /mcp opens an optional SSE backchannel for server-initiated messages. gbrain's transport is stateless and doesn't push server-initiated messages, so per spec we MUST return 405 with Allow: POST, DELETE — not 404. Probing clients (claude.ai, etc.) distinguish "endpoint exists, no SSE channel" from "endpoint missing" on this status code; 404 makes them give up. Cherry-picked from PR #1076. Co-Authored-By: lukejduncan --- src/commands/serve-http.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/commands/serve-http.ts b/src/commands/serve-http.ts index db00d3540..1cda88239 100644 --- a/src/commands/serve-http.ts +++ b/src/commands/serve-http.ts @@ -795,6 +795,17 @@ export async function runServeHttp(engine: BrainEngine, options: ServeHttpOption // --------------------------------------------------------------------------- const mcpOperations = operations.filter(op => !op.localOnly); + // v0.36.x #1076: MCP Streamable HTTP spec — GET /mcp opens an optional SSE + // backchannel for server-initiated messages. gbrain's transport is stateless + // and doesn't push server-initiated messages, so per spec we MUST return 405 + // (not 404) so probing clients (claude.ai, etc.) recognize this as an MCP + // endpoint, not a missing route. Without this, clients display "endpoint not + // found" instead of "endpoint exists but no SSE channel." + app.get('/mcp', (_req: Request, res: Response) => { + res.set('Allow', 'POST, DELETE'); + res.status(405).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Method not allowed' }, id: null }); + }); + app.post('/mcp', requireBearerAuth({ verifier: oauthProvider }), async (req: Request, res: Response) => { const startTime = Date.now(); const authInfo = (req as any).auth as AuthInfo;