From 24a30c0e6eaa59b01ecf8c6fa95c05e541b3bb47 Mon Sep 17 00:00:00 2001 From: DDT <141296842+Hugo-DDT@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:31:32 +0800 Subject: [PATCH 1/2] fix(server): set Allow: POST on stateless legacy 405 responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit createMcpHandler's legacy stateless fallback answered non-POST requests with 405 without an Allow header. RFC 9110 §15.5.6 requires Allow on 405, and WebStandardStreamableHTTPServerTransport.handleUnsupportedRequest already sets it. Stateless serving only accepts POST, so advertise that. Closes #2970 Assisted-by: AI (Grok Bot) --- packages/server/src/server/createMcpHandler.ts | 18 +++++++++++++++++- .../test/server/createMcpHandler.test.ts | 1 + .../server/legacyStatelessFallback.test.ts | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) 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.'); From 0a2a5d916d00ee6b86d4bd6377cdd189e7ec7f3c Mon Sep 17 00:00:00 2001 From: DDT <141296842+Hugo-DDT@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:28:31 +0800 Subject: [PATCH 2/2] chore: add patch changeset for Allow header on stateless 405 --- .changeset/stateless-405-allow-header.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/stateless-405-allow-header.md 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).