Which middleware is the feature for?
@hono/mcp
What is the feature you are proposing?
Summary
The Streamable HTTP transport in @hono/mcp strictly requires POST requests to include both application/json and text/event-stream in the Accept header. While this matches the spec language, it breaks compatibility with several widely-used MCP clients that don't send both values.
Current behavior
In streamable-http.ts, handlePostRequest:
const acceptHeader = ctx.req.header('Accept')
if (
!acceptHeader?.includes('application/json') ||
!acceptHeader.includes('text/event-stream')
) {
throw new HTTPException(406, { ... })
}
This rejects requests from:
- Gemini CLI (native HTTP transport) — sends only
Accept: application/json
- Java MCP SDK (
io.modelcontextprotocol) — sends only Accept: text/event-stream
- Open WebUI — missing
text/event-stream in health check/validation requests
- Standard HTTP clients (curl, httpx, fetch) — send
*/* or no Accept header
- Dify MCP plugin — same issue
Impact
This is becoming a common pain point across the MCP ecosystem. The same strict check in the official TypeScript SDK and other implementations has generated issues in multiple projects:
We ran into this ourselves building an MCP Code Mode surface on Cloudflare Workers using @hono/mcp. Our server works perfectly with spec-compliant clients (Claude Desktop, Cursor, etc.) but rejects everything else — including simple curl calls during development and testing.
Proposed fix
Apply Postel's Law: be liberal in what you accept. The server already knows whether it will respond with JSON or SSE, so it can make the right choice regardless of what the client advertises.
Something like:
const acceptHeader = ctx.req.header('Accept') || '*/*'
const acceptsJson = acceptHeader.includes('application/json') || acceptHeader.includes('*/*')
const acceptsSse = acceptHeader.includes('text/event-stream') || acceptHeader.includes('*/*')
if (!acceptsJson && !acceptsSse) {
// Reject only if the client explicitly accepts neither
throw new HTTPException(406, { ... })
}
This would:
- ✅ Continue working with fully spec-compliant clients
- ✅ Accept
*/* (standard default for curl, fetch, httpx)
- ✅ Accept
application/json alone (Gemini CLI, standard JSON-RPC clients)
- ✅ Accept
text/event-stream alone (Java MCP SDK)
- ✅ Accept missing Accept header (treat as
*/* per HTTP semantics)
- ❌ Still reject explicitly incompatible Accept headers (e.g.,
Accept: text/html)
Alternative: make it configurable
If maintaining strict spec compliance by default is important, a strictAcceptHeader option on StreamableHTTPTransport (defaulting to false) would let users opt into strict mode while keeping the default permissive.
Environment
@hono/mcp latest
- Deployed on Cloudflare Workers
- Tested against: curl, Gemini CLI,
@modelcontextprotocol/sdk client, custom HTTP clients
Which middleware is the feature for?
@hono/mcp
What is the feature you are proposing?
Summary
The Streamable HTTP transport in
@hono/mcpstrictly requires POST requests to include bothapplication/jsonandtext/event-streamin the Accept header. While this matches the spec language, it breaks compatibility with several widely-used MCP clients that don't send both values.Current behavior
In
streamable-http.ts,handlePostRequest:This rejects requests from:
Accept: application/jsonio.modelcontextprotocol) — sends onlyAccept: text/event-streamtext/event-streamin health check/validation requests*/*or no Accept headerImpact
This is becoming a common pain point across the MCP ecosystem. The same strict check in the official TypeScript SDK and other implementations has generated issues in multiple projects:
We ran into this ourselves building an MCP Code Mode surface on Cloudflare Workers using
@hono/mcp. Our server works perfectly with spec-compliant clients (Claude Desktop, Cursor, etc.) but rejects everything else — including simplecurlcalls during development and testing.Proposed fix
Apply Postel's Law: be liberal in what you accept. The server already knows whether it will respond with JSON or SSE, so it can make the right choice regardless of what the client advertises.
Something like:
This would:
*/*(standard default for curl, fetch, httpx)application/jsonalone (Gemini CLI, standard JSON-RPC clients)text/event-streamalone (Java MCP SDK)*/*per HTTP semantics)Accept: text/html)Alternative: make it configurable
If maintaining strict spec compliance by default is important, a
strictAcceptHeaderoption onStreamableHTTPTransport(defaulting tofalse) would let users opt into strict mode while keeping the default permissive.Environment
@hono/mcplatest@modelcontextprotocol/sdkclient, custom HTTP clients