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
enableJsonResponse is true and the request is not one the transport would stream for, require only application/json (or a wildcard that matches it) in Accept.
- 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.
Summary
StreamableHTTPServerTransportreturns406 Not Acceptablewhen a client sendsAccept: application/jsonwithout also includingtext/event-stream, even when the transport is constructed withenableJsonResponse: true. In that mode the transport is operating in pure request/response — there is no SSE stream to negotiate — so requiringtext/event-streaminAcceptis 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 theAcceptheader before the transport sees it, which is an escape hatch we'd rather not need.Reproduction
Expected behavior
When
enableJsonResponse: trueand the response will be a plain JSON body (no SSE), theAcceptcheck should acceptapplication/jsonalone.text/event-streamshould only be required when the transport is about to open a stream.Proposed fix
In the POST handler's
Acceptvalidation:enableJsonResponseistrueand the request is not one the transport would stream for, require onlyapplication/json(or a wildcard that matches it) inAccept.text/event-stream.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 normalizesAcceptbefore the transport sees it. We'd like to drop that once this is fixed upstream.