From 6f05df96f5f14ecb0eef32d4af1b73a6b9692faa Mon Sep 17 00:00:00 2001 From: Brendan Kellam Date: Tue, 31 Mar 2026 15:43:25 -0700 Subject: [PATCH 1/2] fix(web): return 405 for GET /api/mcp instead of hanging connection The GET SSE stream is only used for server-initiated messages, which Sourcebot does not send. Per the MCP Streamable HTTP spec, servers that do not offer a GET SSE stream MUST return 405 Method Not Allowed. Previously the handler opened an SSE stream that never flushed its HTTP headers, causing clients to hang until timeout. Co-Authored-By: Claude Sonnet 4.6 --- .../web/src/app/api/(server)/mcp/route.ts | 43 ++++--------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/packages/web/src/app/api/(server)/mcp/route.ts b/packages/web/src/app/api/(server)/mcp/route.ts index 5db39707c..bf820d1b7 100644 --- a/packages/web/src/app/api/(server)/mcp/route.ts +++ b/packages/web/src/app/api/(server)/mcp/route.ts @@ -129,38 +129,13 @@ export const DELETE = apiHandler(async (request: NextRequest) => { return result; }); -export const GET = apiHandler(async (request: NextRequest) => { - const result = await sew(() => - withOptionalAuthV2(async ({ user }) => { - if (env.EXPERIMENT_ASK_GH_ENABLED === 'true' && !user) { - return notAuthenticated(); - } - const ownerId = user?.id ?? null; - const sessionId = request.headers.get(MCP_SESSION_ID_HEADER); - if (!sessionId || !sessions.has(sessionId)) { - return { - statusCode: StatusCodes.NOT_FOUND, - errorCode: ErrorCode.NOT_FOUND, - message: 'Session not found.', - } satisfies ServiceError; - } - - const session = sessions.get(sessionId)!; - if (session.ownerId !== ownerId) { - return { - statusCode: StatusCodes.FORBIDDEN, - errorCode: ErrorCode.INSUFFICIENT_PERMISSIONS, - message: 'Session does not belong to the authenticated user.', - } satisfies ServiceError; - } - - return session.transport.handleRequest(request); - }) - ); - - if (isServiceError(result)) { - return mcpErrorResponse(result); - } - - return result; +// Sourcebot does not send server-initiated messages, so the GET SSE stream is not +// supported. Per the MCP Streamable HTTP spec, servers that do not offer a GET SSE +// stream MUST return 405 Method Not Allowed. +// @see: https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#listening-for-messages-from-the-server +export const GET = apiHandler(async (_request: NextRequest) => { + return new Response(null, { + status: StatusCodes.METHOD_NOT_ALLOWED, + headers: { Allow: 'POST, DELETE' }, + }); }); From 4920101652b904f5658ffbedf1de24cd511d7dcb Mon Sep 17 00:00:00 2001 From: Brendan Kellam Date: Tue, 31 Mar 2026 15:44:03 -0700 Subject: [PATCH 2/2] chore: update CHANGELOG for #1064 Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 309ef90d5..3bf1aa885 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added `GET /api/diff` endpoint for retrieving structured diffs between two git refs ([#1063](https://github.com/sourcebot-dev/sourcebot/pull/1063)) +### Fixed +- Fixed `GET /api/mcp` hanging with zero bytes by returning `405 Method Not Allowed` per the MCP Streamable HTTP spec ([#1064](https://github.com/sourcebot-dev/sourcebot/pull/1064)) + ## [4.16.3] - 2026-03-27 ### Added