modelcontextprotocol / modelcontextprotocol/typescript-sdk
StreamableHTTPServerTransport rejects JSON-only Accept with 406 even when enableJsonResponse: true
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.4k
- Forks
- 2.2k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 4
Description
Summary
StreamableHTTPServerTransport returns 406 Not Acceptable when a client sends Accept: application/json without also including text/event-stream, even when the transport is constructed with enableJsonResponse: true. In that mode the transport is operating in pure request/response — there is no SSE stream to negotiate — so requiring text/event-stream in Accept is incorrect.
This blocks buyer agents and validators that probe MCP servers with a plain JSON Accept (a reasonable default for JSON-RPC over HTTP). Every MCP seller implementation in the AdCP ecosystem hits this; we currently ship a tiny Express middleware that rewrites the Accept header before the transport sees it, which is an escape hatch we'd rather not need.
Reproduction
import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
const app = express();
app.use(express.json());
app.post('/mcp', async (req, res) => {
const server = new McpServer({ name: 'repro', version: '0.0.0' });
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true, // pure request/response, no SSE
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.listen(3000);
# Fails with 406 Not Acceptable
curl -sS -X POST http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"c","version":"0"}}}'
# Succeeds when both are advertised
curl -sS -X POST http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"c","version":"0"}}}'
Expected behavior
When enableJsonResponse: true and the response will be a plain JSON body (no SSE), the Accept check should accept application/json alone. text/event-stream should only be required when the transport is about to open a stream.
Proposed fix
In the POST handler's Accept validation:
- If
enableJsonResponseistrueand the request is not one the transport would stream for, require onlyapplication/json(or a wildcard that matches it) inAccept. - Keep the existing stricter check for the streaming path: when the response will be SSE, continue to require
text/event-stream. - GET (SSE subscription) is unaffected — that path genuinely needs
text/event-stream.
Happy to send a PR if the approach looks right.
Context
Filed from the AdCP client SDK (@adcp/client), which ships a temporary middleware that normalizes Accept before the transport sees it. We'd like to drop that once this is fixed upstream.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at StreamableHTTPServerTransport.handleRequest, focusing on the POST handler's Accept validation and how enableJsonResponse distinguishes JSON responses from SSE streams. Run the provided curl reproduction, then verify that JSON-only Accept succeeds for non-streaming responses while SSE POST and GET negotiation retain their existing requirements.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend-api-design
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100