modelcontextprotocol / modelcontextprotocol/typescript-sdk
Streamable HTTP server accepts invalid media types by Accept-header substring
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.4k
- Forks
- 2.2k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 4
Description
Summary
WebStandardStreamableHTTPServerTransport validates the Accept header with raw substring checks. As a result, invalid media types such as application/jsonx and text/event-stream-bogus satisfy the transport requirements and are accepted as though the client had listed application/json and text/event-stream.
This affects both POST negotiation and the GET SSE check.
Reproduction
On current main (24be4040), send an initialize request with:
Accept: application/jsonx, text/event-stream-bogus
Content-Type: application/json
Equivalent unit-level reproduction:
const request = new Request('http://localhost/mcp', {
method: 'POST',
headers: {
Accept: 'application/jsonx, text/event-stream-bogus',
'Content-Type': 'application/json',
},
body: JSON.stringify(initializeRequest),
});
const response = await transport.handleRequest(request);
console.log(response.status); // currently 200; expected 406
For GET, Accept: text/event-stream-bogus likewise passes the current check instead of returning 406.
Root cause
packages/server/src/server/streamableHttp.ts currently uses:
acceptHeader?.includes('text/event-stream')
acceptHeader?.includes('application/json')
Those checks match substrings inside other type/subtype tokens and parameters; they do not parse the comma-separated media ranges.
This is the same class of bug recently fixed for Content-Type in #2441 / #2444, where the server switched from substring matching to parsed media-type comparison. The Accept checks still use the older substring behavior.
Expected behavior
The transport should require the exact concrete media types named by the MCP Streamable HTTP requirements:
- GET:
text/event-stream - POST: both
application/jsonandtext/event-stream
Parameters and case variants should remain valid, for example:
Accept: Application/JSON; q=0.9, text/event-stream; charset=utf-8
Values that only contain the required strings as substrings should return 406.
Suggested fix
Parse the comma-separated Accept values and compare each media-type essence (type/subtype, case-insensitive, without parameters) to the required concrete values. Add regression coverage for POST and GET substring false positives plus valid parameterized media types.
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 in packages/server/src/server/streamableHttp.ts and trace the Accept-header checks used by POST negotiation and the GET SSE response. Compare the existing Content-Type parsing fix referenced in #2441 and #2444, then add regression coverage for substring false positives and valid parameterized, case-insensitive media types. Done means invalid values return 406 while valid concrete media types continue to negotiate successfully.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100