Skip to main content
Glama

hono-mcp-bridge

Turn your existing Hono API into an MCP server for Claude, Cursor and other AI agents. Change one import, keep your routes.

npm CI License: MIT

- import { zValidator } from '@hono/zod-validator'
+ import { zValidator, mountMcp } from 'hono-mcp-bridge'

  app.get('/tours', zValidator('query', z.object({ month: z.coerce.number() })), (c) => ...)
  app.post('/bookings', zValidator('json', BookingSchema), (c) => ...)

+ mountMcp(app)   // → POST /mcp: every route is now a tool

Your zod schemas become the tools' input schemas. Calls go through your real routes, so validation, auth middleware and logging work exactly as they do for HTTP clients.

Claude Code finds and books a tour through hono-mcp-bridge

Install

npm i hono-mcp-bridge     # or: bun add / pnpm add

Peer dependencies: hono >= 4.6, zod >= 4, @hono/zod-validator. Runs anywhere Hono runs: Bun, Node, Deno, Cloudflare Workers, Vercel.

Related MCP server: hono-mcp-server

Example

import { Hono } from 'hono'
import { z } from 'zod'
import { describeTool, mountMcp, zValidator } from 'hono-mcp-bridge'

const app = new Hono()

app.get(
  '/tours',
  describeTool('Search tours by departure month and maximum price in USD.'),
  zValidator('query', z.object({
    month: z.coerce.number().min(1).max(12).describe('Departure month, 1-12'),
    maxPrice: z.coerce.number().optional(),
  })),
  (c) => c.json(searchTours(c.req.valid('query'))),
)

app.get('/admin/stats', describeTool({ hidden: true }), (c) => ...)  // never shown to agents

mountMcp(app, { name: 'Tours API' })

export default app

The agent sees:

Tool

Input

get_tours

{ query: { month: number, maxPrice?: number } }

Full runnable version: examples/tours.ts (bun run example).

Connect an agent

Claude Code

claude mcp add --transport http tours http://localhost:3000/mcp

Cursor (.cursor/mcp.json)

{ "mcpServers": { "tours": { "url": "http://localhost:3000/mcp" } } }

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "tours": { "command": "npx", "args": ["mcp-remote", "http://localhost:3000/mcp"] }
  }
}

Debug with the MCP Inspector: npx @modelcontextprotocol/inspector.

How routes become tools

Route

Tool

Name

describeTool({ name }), otherwise GET /tours/:id → get_tours_by_id

Description

describeTool('...'), otherwise GET /tours/:id, and a startup warning lists these routes. See Writing descriptions for agents

Input

{ params, query, body } from zValidator('param' | 'query' | 'json', schema). Path params without a validator are derived from the path

Annotations

GET → readOnlyHint; DELETE → destructiveHint

Result

Response body as text, plus structuredContent for JSON objects. Non-2xx responses are returned with isError: true, so the agent can read a validation error and retry

Writing descriptions for agents

The schemas come for free. The description does not, and it decides whether the tool gets used at all: an agent picks a tool from its name and description only, before it sees any parameters. A route described as GET /tours/:id is a tool the agent will rarely call, or will call at the wrong time.

On startup the bridge lists every exposed route without a description:

[hono-mcp-bridge] 2 tool(s) have no description and are exposed with only "METHOD /path" as a description, so agents will rarely pick them:
  - get_tours_by_id (GET /tours/:id)
  - delete_bookings_by_id (DELETE /bookings/:id)
Add describeTool('What it does and when to use it') to these routes.

Set requireDescription: true to hide such routes from agents entirely, so an undocumented route never becomes a tool.

A good description says what the tool does, what it returns and when to use it instead of a similar tool:

// ✗ the agent can't tell this apart from search
describeTool('Get tour')

// ✓
describeTool('Get full details of one tour (dates, hotel, price, seats left) by id. Use after get_tours, when the user asks about a specific tour.')

Field-level .describe() on the zod schema matters next: it tells the agent how to fill each argument once it has picked the tool (.describe('Full name exactly as in the passport, in Latin letters')).

Options

mountMcp(app, {
  path: "/mcp", // endpoint path
  name: "Tours API",
  version: "1.0.0",
  instructions: "Prices are in USD.", // sent to the agent on connect
  include: ["/tours*", "/bookings*"], // path globs to expose (default: all)
  exclude: ["/admin/*"], // path globs to hide
  methods: ["GET"], // read-only API for agents
  forwardHeaders: ["authorization", "cookie"], // copied onto each tool call
  allowedOrigins: ["https://app.example.com"], // reject other Origin headers
  requireDescription: true, // hide routes without describeTool()
  warnUndescribed: (msg) => logger.warn(msg), // or false to silence the startup warning
});

Lower-level pieces: createMcpHandler(app, options) returns a plain Hono handler to mount yourself; collectTools(app, options) returns the tool list.

Security

Agents act with the credentials of whoever connected them. Before exposing an API:

  • Expose only what an agent should do. Prefer include over exclude, and consider methods: ['GET'] to start.

  • Keep auth on your routes. The bridge forwards Authorization and Cookie from the MCP request, so your existing middleware decides.

  • When serving on localhost, set allowedOrigins to block DNS-rebinding attacks from web pages.

How it compares

  • hono-mcp-server: you wrap each route in registerTool() with a separate input schema. Here the schema you already validate with is reused.

  • hono-gql-mcp: exposes one GraphQL tool derived from @hono/zod-openapi. Here every route is its own tool and plain Hono works.

  • @hono/mcp: MCP transport only; you define tools by hand.

Limitations

  • Transport is Streamable HTTP in stateless JSON mode: no server-initiated messages, SSE streams or sessions. Tool calls are request/response, which is what a REST route is.

  • Only zod v4 schemas are converted. form, header and cookie validators are not bridged yet.

  • If a sub-app with its own onError is mounted via app.route(), Hono wraps its handlers and the schemas can't be read. Mount the bridge on that sub-app or use describeTool + path params.

Roadmap

  • @hono/zod-openapi (OpenAPIHono) and hono-openapi support

  • Valibot / ArkType via Standard Schema

  • outputSchema from response schemas

  • stdio transport for local servers

Contributions welcome. See CONTRIBUTING.md.

License

MIT

Test

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Exposes Hono API endpoints as Model Context Protocol tools, allowing LLMs to interact with your API routes through a dedicated MCP endpoint. It provides helpers to describe routes and includes a codemode for dynamic API interaction via search and execute tools.
    56 npm
    86
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Easily expose your Hono API endpoints as MCP tools with minimal configuration, supporting type-safe input handling and tool registration.
    56 npm
    2
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Turn your typed TypeScript functions into an MCP server — tool, resource, and prompt schemas inferred from your types and JSDoc. No schema library, no decorators, no boilerplate.
    0
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides an MCP server that automatically exposes API routes as MCP tools with input validation and authentication, enabling AI agents to call the same handlers as HTTP RPC.
    226 npm
    3
    MIT