Skip to content

feat(hono): add mcp() middleware to serve an McpServer in one call - #2595

Open
MathurAditya724 wants to merge 1 commit into
modelcontextprotocol:mainfrom
MathurAditya724:feat/hono-mcp-middleware
Open

feat(hono): add mcp() middleware to serve an McpServer in one call#2595
MathurAditya724 wants to merge 1 commit into
modelcontextprotocol:mainfrom
MathurAditya724:feat/hono-mcp-middleware

Conversation

@MathurAditya724

@MathurAditya724 MathurAditya724 commented Jul 31, 2026

Copy link
Copy Markdown

What

Adds mcp(factory, options?) to @modelcontextprotocol/hono — a single Hono MiddlewareHandler that serves MCP over Streamable HTTP in one call:

import { mcp } from '@modelcontextprotocol/hono';
import { McpServer } from '@modelcontextprotocol/server';
import { Hono } from 'hono';

const app = new Hono();
app.all(
    '/mcp',
    mcp(() => new McpServer({ name: 'my-server', version: '1.0.0' }))
);

It builds on createMcpHandler (not a raw transport), so the endpoint serves the modern 2026-07-28 protocol and falls back to stateless 2025-era serving, with a fresh server per request. JSON body parsing + localhost DNS-rebinding / origin protection are wired in front via createMcpHonoApp. createMcpHonoApp + createMcpHandler stay as the own-your-routing form.

Why

Mounting MCP on a Hono app you already own means building an app via createMcpHonoApp, wiring the handler, and adding a /mcp route by hand. mcp(factory) collapses that to one line while composing the exact same pieces, so it stays consistent with the rest of the SDK and dual-era by default.

Changes

  • packages/middleware/hono/src/hono.ts: mcp() + McpMiddlewareOptions (host/origin options plus handler forwarded to createMcpHandler). The inner app is self-contained, so only the raw request is forwarded to it — this also avoids c.executionCtx, which throws off Cloudflare Workers.
  • packages/middleware/hono/test/mcp.test.ts: driven by a real Client over both the legacy (2025) and modern (2026-07-28) paths, plus Host/Origin protection tests.
  • packages/middleware/hono/README.md and docs/serving/hono.md: feature mcp() on top; keep createMcpHonoApp + createMcpHandler documented as the own-your-routing form. Docs snippets are synced from the runnable, type-checked examples/guides/serving/hono.examples.ts harness.

Credit

Feature and API shape inspired by @yusukebe's mcp-server-hono-middleware. Discussion: https://x.com/yusukebe/status/2083147644592128080

Checklist

  • pnpm --filter @modelcontextprotocol/hono check (typecheck + lint)
  • pnpm --filter @modelcontextprotocol/hono test (20 passing)
  • pnpm sync:snippets --check (docs snippets in sync)
  • Discussed in an issue first (per CONTRIBUTING.md — opening as draft for maintainer alignment)

@changeset-bot

changeset-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 8405014

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 31, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2595

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/@modelcontextprotocol/codemod@2595

@modelcontextprotocol/core

npm i https://pkg.pr.new/@modelcontextprotocol/core@2595

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2595

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/@modelcontextprotocol/server-legacy@2595

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2595

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2595

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2595

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2595

commit: 8405014

@MathurAditya724
MathurAditya724 force-pushed the feat/hono-mcp-middleware branch from 0fb9328 to aaa3985 Compare July 31, 2026 12:47
@MathurAditya724
MathurAditya724 marked this pull request as ready for review July 31, 2026 13:00
@MathurAditya724
MathurAditya724 requested a review from a team as a code owner July 31, 2026 13:00
@cameronapak

Copy link
Copy Markdown

LGTM. Take my money! 🚀🚀🚀

Comment thread packages/middleware/hono/src/hono.ts Outdated
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, ...transportOptions });
const app = createMcpHonoApp(appOptions);
app.all('*', (c: Context<{ Variables: { parsedBody: unknown } }>) =>
transport.handleRequest(c.req.raw, { parsedBody: c.get('parsedBody') })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why are you calling the transport directly here rather than going through createMcpHandler?

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/packages/server/src/server/createMcpHandler.ts#L604

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good call — you're right. Switched mcp() to wrap createMcpHandler instead of the raw transport, so it's dual-era and uses a per-request factory now. Much less code too.

}

describe('@modelcontextprotocol/hono mcp() middleware', () => {
test('serves an McpServer on the mounted route (initialize succeeds)', async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this looks very targetted to the previous spec.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yep. Rewrote these to run through a real Client — one test over the legacy 2025 path and one pinned to the modern 2026-07-28 path — so both eras are covered now.

Adds mcp(factory, options?) — a single Hono MiddlewareHandler that serves
MCP over Streamable HTTP with JSON body parsing and localhost
DNS-rebinding / origin protection applied (the same defaults as
createMcpHonoApp), so it can be mounted on a route you already own:

  app.all('/mcp', mcp(() => new McpServer({ name: '...', version: '...' })))

It builds on createMcpHandler rather than a raw transport, so the endpoint
serves the modern 2026-07-28 protocol and falls back to stateless 2025-era
serving, with a fresh server per request. The inner app is self-contained,
so only the raw request is forwarded to it — this also avoids
c.executionCtx, which throws off Cloudflare Workers.

Feature and API shape inspired by yusukebe's mcp-server-hono-middleware.

- packages/middleware/hono/src/hono.ts: mcp() + McpMiddlewareOptions
- packages/middleware/hono/test/mcp.test.ts: real-client tests over both
  the legacy (2025) and modern (2026-07-28) paths, plus protection tests
- README + docs/serving/hono.md: feature mcp() as the preferred path,
  keep createMcpHonoApp + createMcpHandler as the own-your-routing form
@MathurAditya724
MathurAditya724 force-pushed the feat/hono-mcp-middleware branch from aaa3985 to 8405014 Compare July 31, 2026 19:33
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.

3 participants