modelcontextprotocol / modelcontextprotocol/typescript-sdk

Streamable HTTP server accepts invalid media types by Accept-header substring

Open
#2,480 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

v1 v2
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/json and text/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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.