diff --git a/.changeset/stateless-405-allow-header.md b/.changeset/stateless-405-allow-header.md new file mode 100644 index 0000000000..7aec9d00d4 --- /dev/null +++ b/.changeset/stateless-405-allow-header.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/server': patch +--- + +fix(server): set `Allow: POST` on legacy stateless 405 responses for non-POST requests (RFC 9110). diff --git a/packages/server/src/server/createMcpHandler.ts b/packages/server/src/server/createMcpHandler.ts index b7d27662c9..6ecfe116b0 100644 --- a/packages/server/src/server/createMcpHandler.ts +++ b/packages/server/src/server/createMcpHandler.ts @@ -335,7 +335,23 @@ function createLegacyStatelessFallback( ): LegacyHttpHandler { return async (request, options) => { if (request.method.toUpperCase() !== 'POST') { - return jsonRpcErrorResponse(405, -32_000, 'Method not allowed.'); + // RFC 9110 §15.5.6 requires Allow on 405. Stateless serving only accepts POST. + return Response.json( + { + jsonrpc: '2.0', + error: { + code: -32_000, + message: 'Method not allowed.' + }, + id: null + }, + { + status: 405, + headers: { + Allow: 'POST' + } + } + ); } try { const product = await factory({ diff --git a/packages/server/test/server/createMcpHandler.test.ts b/packages/server/test/server/createMcpHandler.test.ts index a1b9ce7986..1069faba2e 100644 --- a/packages/server/test/server/createMcpHandler.test.ts +++ b/packages/server/test/server/createMcpHandler.test.ts @@ -546,6 +546,7 @@ describe('createMcpHandler — stateless legacy fallback (the default)', () => { for (const method of ['GET', 'DELETE']) { const response = await handler.fetch(new Request('http://localhost/mcp', { method })); expect(response.status).toBe(405); + expect(response.headers.get('Allow')).toBe('POST'); const body = (await response.json()) as JSONRPCErrorBody; expect(body.error.code).toBe(-32_000); expect(body.error.message).toBe('Method not allowed.'); diff --git a/packages/server/test/server/legacyStatelessFallback.test.ts b/packages/server/test/server/legacyStatelessFallback.test.ts index 50247ae6ab..efd1177cff 100644 --- a/packages/server/test/server/legacyStatelessFallback.test.ts +++ b/packages/server/test/server/legacyStatelessFallback.test.ts @@ -87,6 +87,7 @@ describe('legacyStatelessFallback', () => { for (const method of ['GET', 'DELETE']) { const response = await handler(new Request('http://localhost/mcp', { method })); expect(response.status).toBe(405); + expect(response.headers.get('Allow')).toBe('POST'); const body = (await response.json()) as JSONRPCErrorBody; expect(body.error.code).toBe(-32_000); expect(body.error.message).toBe('Method not allowed.');