Skip to content

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

@bookernath

Description

@bookernath

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions