You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(hono): add mcp() middleware to serve an McpServer in one call
Adds mcp(server, options?) — a single Hono MiddlewareHandler that serves
an McpServer 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(server))
The server is connected lazily on the first 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: end-to-end + protection tests
- README + docs/serving/hono.md: feature mcp() as the preferred path,
keep createMcpHonoApp as the bring-your-own-app/factory escape hatch
`createMcpHandler` turns a server factory into a web-standard HTTP handler, and `handler.fetch` takes the `Request` a Hono route already holds as `c.req.raw` — no Node adapter. `createMcpHonoApp` is `new Hono()` with JSON body parsing and DNS rebinding protection already applied.
12
+
`mcp(server)` is the shortest path: it returns a single Hono middleware that serves your `McpServer` over Streamable HTTP, with JSON body parsing and DNS rebinding protection already applied. Mount it on a route you already own — it connects the server on the first request.
`app` is an ordinary Hono app, and `export default app` is the `{ fetch }` object Cloudflare Workers, Deno, and Bun serve directly; on Node, pass `app` to `serve` from `@hono/node-server`.
32
+
33
+
### Bring your own app and factory
34
+
35
+
When you want a fresh `McpServer` per request or full control over routing, drop down to `createMcpHandler` + `createMcpHonoApp`. `createMcpHandler` turns a server factory into a web-standard HTTP handler, and `handler.fetch` takes the `Request` a Hono route already holds as `c.req.raw` — no Node adapter. `createMcpHonoApp` is `new Hono()` with the same JSON body parsing and DNS rebinding protection applied.
`app` is an ordinary Hono app, and `export default app` is the `{ fetch }` object Cloudflare Workers, Deno, and Bun serve directly; on Node, pass `app` to `serve` from `@hono/node-server`. The factory runs once per request, so a fresh `McpServer` serves every call: [Serve over HTTP](./http.md#understand-the-per-request-factory) covers that model.
58
+
The factory runs once per request, so a fresh `McpServer` serves every call: [Serve over HTTP](./http.md#understand-the-per-request-factory) covers that model.
35
59
36
60
::: tip
37
61
Keep the explicit `c: Context` annotation: on an inferred callback context `c.get`'s key parameter narrows to `never` and `c.get('parsedBody')` does not compile.
38
62
:::
39
63
40
64
## Protect against DNS rebinding
41
65
42
-
A malicious page can DNS-rebind its own domain to `127.0.0.1` and reach a localhost server as if it were same-origin. `createMcpHonoApp`validates the `Host` and `Origin` headers against that: with the default `127.0.0.1` bind (and `localhost` / `::1`), a request carrying a non-localhost value gets `403` before your handler runs.
66
+
A malicious page can DNS-rebind its own domain to `127.0.0.1` and reach a localhost server as if it were same-origin. `mcp()` and `createMcpHonoApp`both validate the `Host` and `Origin` headers against that: with the default `127.0.0.1` bind (and `localhost` / `::1`), a request carrying a non-localhost value gets `403` before your handler runs.
43
67
44
-
Binding to all interfaces drops that default — name the hosts you serve instead.
68
+
Binding to all interfaces drops that default — name the hosts you serve instead.`mcp()` takes the same `host` / `allowedHosts` / `allowedOrigins` options.
@@ -82,8 +106,8 @@ data: {"result":{"tools":[{"name":"add-note","description":"Append a note","inpu
82
106
83
107
## Recap
84
108
85
-
- One install line, one file: `createMcpHonoApp()` plus one `app.all('/mcp', …)` route over `createMcpHandler(factory).fetch`.
109
+
-`mcp(server)` is one Hono middleware — `app.all('/mcp', mcp(server))` serves the whole endpoint with body parsing and Host/Origin validation applied.
110
+
- Need a fresh server per request or custom routing? Use `createMcpHonoApp()` plus one `app.all('/mcp', …)` route over `createMcpHandler(factory).fetch`.
86
111
- Hono hands `c.req.raw` straight to `handler.fetch` — no Node adapter.
87
-
- A fresh server instance from your factory serves every request.
88
112
- The default `127.0.0.1` bind validates `Host` and `Origin`; pass `allowedHosts` when binding to `0.0.0.0`.
89
113
-`authInfo` and `parsedBody` travel in `handler.fetch`'s second argument; handlers read auth as `ctx.http.authInfo`.
0 commit comments