Skip to content

feat(mcp): relax Accept header validation with opt-in strict mode#1774

Open
bookernath wants to merge 1 commit intohonojs:mainfrom
bookernath:feat/mcp-relax-accept-header
Open

feat(mcp): relax Accept header validation with opt-in strict mode#1774
bookernath wants to merge 1 commit intohonojs:mainfrom
bookernath:feat/mcp-relax-accept-header

Conversation

@bookernath
Copy link

Summary

  • Default to permissive Accept header validation in the Streamable HTTP transport — accept application/json, text/event-stream, or */* individually (and treat missing Accept as */*)
  • Add strictAcceptHeader option to opt in to the original strict behavior (requiring both application/json and text/event-stream)
  • Also accept */* in the GET handler for consistency

Motivation

The current strict requirement for both application/json and text/event-stream in the Accept header 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 on Accept is unnecessarily strict.

This matches the approach taken in the upstream MCP TypeScript SDK (modelcontextprotocol/typescript-sdk#551).

Closes #1773

Test plan

  • POST with Accept: application/json only → 200
  • POST with Accept: text/event-stream only → 200
  • POST with Accept: */* → 200
  • POST with no Accept header → 200 (defaults to */*)
  • POST with Accept: text/html only → 406
  • POST with Accept: application/json, text/event-stream → 200 (fully compliant)
  • POST with strictAcceptHeader: true and Accept: application/json only → 406
  • GET with Accept: */* → 200 (SSE stream)

🤖 Generated with Claude Code

@changeset-bot
Copy link

changeset-bot bot commented Mar 2, 2026

🦋 Changeset detected

Latest commit: 467851c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@hono/mcp Patch

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>
@bookernath bookernath force-pushed the feat/mcp-relax-accept-header branch from b5c4379 to 467851c Compare March 2, 2026 02:09
@bookernath
Copy link
Author

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
Copy link

codecov bot commented Mar 2, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.73%. Comparing base (e549c57) to head (467851c).
⚠️ Report is 15 commits behind head on main.

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              
Flag Coverage Δ
mcp 90.63% <100.00%> (+0.11%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@yusukebe
Copy link
Member

yusukebe commented Mar 5, 2026

@bookernath Thank you for the PR.

Hey @MathurAditya724 Can you review this?

Copy link
Contributor

@MathurAditya724 MathurAditya724 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some JSDocs around this new prop

})
const acceptHeader = ctx.req.header('Accept') || '*/*'

if (this.#strictAcceptHeader) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@MathurAditya724
Copy link
Contributor

Also, can you resolve the CI issues?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@hono/mcp: Relax Accept header validation to accommodate common MCP clients

3 participants