feat(mcp): relax Accept header validation with opt-in strict mode#1774
feat(mcp): relax Accept header validation with opt-in strict mode#1774bookernath wants to merge 1 commit intohonojs:mainfrom
Conversation
🦋 Changeset detectedLatest commit: 467851c The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
The strict requirement for both application/json and text/event-stream in the Accept header breaks Gemini CLI, Java MCP SDK, Open WebUI, curl, and other standard HTTP clients. The server already knows its response format, so pre-rejecting based on Accept is unnecessarily strict. Default to permissive validation that accepts application/json, text/event-stream, or */* individually. Add strictAcceptHeader option for users who need strict MCP spec compliance. Also accept */* in GET handler for consistency. Closes honojs#1773 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
b5c4379 to
467851c
Compare
|
Hey @yusukebe @BarryThePenguin @MathurAditya724 👋 Would appreciate your eyes on this. We hit this while building an MCP server on Cloudflare Workers with @hono/mcp — the strict Accept header check breaks curl, Gemini CLI, the Java MCP SDK, and basically any client that isn't using the official TypeScript SDK. Companion issue at #1773 with the full context. This is the same problem showing up across the MCP ecosystem (Open WebUI, Dify, Agent Gateway, fastapi_mcp have all filed similar issues against their servers). The fix makes the default permissive (accept /, application/json alone, or missing header) while adding a strictAcceptHeader option for anyone who needs spec compliance. Happy to adjust anything, thanks! |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1774 +/- ##
==========================================
+ Coverage 91.71% 91.73% +0.01%
==========================================
Files 113 113
Lines 3779 3787 +8
Branches 956 965 +9
==========================================
+ Hits 3466 3474 +8
Misses 281 281
Partials 32 32
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@bookernath Thank you for the PR. Hey @MathurAditya724 Can you review this? |
MathurAditya724
left a comment
There was a problem hiding this comment.
Sorry for the delay on this. 2 small changes but the rest looks good.
Thank you @bookernath for the PR
|
|
||
| constructor(options?: StreamableHTTPServerTransportOptions) { | ||
| constructor( | ||
| options?: StreamableHTTPServerTransportOptions & { strictAcceptHeader?: boolean } |
There was a problem hiding this comment.
Can we add some JSDocs around this new prop
| }) | ||
| const acceptHeader = ctx.req.header('Accept') || '*/*' | ||
|
|
||
| if (this.#strictAcceptHeader) { |
There was a problem hiding this comment.
This whole block can be like -
const isAcceptable = this.#strictAcceptHeader
? acceptHeader.includes('application/json') && acceptHeader.includes('text/event-stream')
: acceptHeader.includes('application/json') ||
acceptHeader.includes('text/event-stream') ||
acceptHeader.includes('*/*')
if (!isAcceptable) {}Just to keep things clean
|
Also, can you resolve the CI issues? |
Summary
Acceptheader validation in the Streamable HTTP transport — acceptapplication/json,text/event-stream, or*/*individually (and treat missingAcceptas*/*)strictAcceptHeaderoption to opt in to the original strict behavior (requiring bothapplication/jsonandtext/event-stream)*/*in the GET handler for consistencyMotivation
The current strict requirement for both
application/jsonandtext/event-streamin theAcceptheader breaks compatibility with Gemini CLI, Java MCP SDK, Open WebUI, curl, and any standard HTTP client that doesn't send both. The server already knows whether it's responding with JSON or SSE — pre-rejecting based onAcceptis unnecessarily strict.This matches the approach taken in the upstream MCP TypeScript SDK (modelcontextprotocol/typescript-sdk#551).
Closes #1773
Test plan
Accept: application/jsononly → 200Accept: text/event-streamonly → 200Accept: */*→ 200Acceptheader → 200 (defaults to*/*)Accept: text/htmlonly → 406Accept: application/json, text/event-stream→ 200 (fully compliant)strictAcceptHeader: trueandAccept: application/jsononly → 406Accept: */*→ 200 (SSE stream)🤖 Generated with Claude Code