Hono MCP Server-Sent Events Transport
raw JSON →This package provides a Server-Sent Events (SSE) transport layer specifically designed for Hono applications to interface with a Model Context Protocol (MCP) server. As of version `0.0.7`, it offers a working solution to a known incompatibility: the official `@modelcontextprotocol/sdk` is primarily built for Express.js, leading to issues with Hono's response handling for SSE. This library acts as an interim solution, enabling developers to integrate MCP servers with Hono by correctly managing SSE connections and message routing. Releases are currently rapid patch/minor versions, indicating active development in its early stages. Its primary differentiator is bridging this specific framework gap, ensuring proper header and stream management for Hono-based MCP server implementations, including robust handling of multiple simultaneous connections via session IDs.
Common errors
error No transport found for sessionId ↓
sessionId that corresponds to an established SSE connection. Ensure the server-side transports object correctly tracks and cleans up connections. error TypeError: Cannot find module '@modelcontextprotocol/sdk' ↓
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; error TypeError: streamSSE is not a function ↓
streamSSE from the correct Hono submodule: import { streamSSE } from 'hono/streaming'; error ReferenceError: SSETransport is not defined ↓
import { SSETransport } from 'hono-mcp-server-sse-transport'; at the top of your TypeScript/ESM file. Warnings
gotcha This package is an interim solution designed to bridge an incompatibility between Hono and the official `@modelcontextprotocol/sdk`. It may become deprecated or obsolete once official Hono support is integrated into the SDK. ↓
breaking Version `0.0.5` introduced a fix for 'export destinations'. If you were relying on previously incorrect or internal module paths, this change might break your imports or module resolution. ↓
gotcha The management of `sessionId` and the `transports` object is manual in the quickstart example. Incorrect handling of session IDs or failure to clean up abandoned transports can lead to memory leaks or messages being routed to the wrong client. ↓
gotcha As of v0.0.7, a 'ping event' is sent before the 'endpoint event'. This changes the event order in the SSE stream, which could potentially affect clients expecting a different sequence of initial events. ↓
Install
npm install hono-mcp-server-sse-transport yarn add hono-mcp-server-sse-transport pnpm add hono-mcp-server-sse-transport Imports
- SSETransport wrong
const { SSETransport } = require('hono-mcp-server-sse-transport');correctimport { SSETransport } from 'hono-mcp-server-sse-transport'; - McpServer wrong
import { McpServer } from '@modelcontextprotocol/sdk';correctimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; - streamSSE wrong
import { streamSSE } from 'hono';correctimport { streamSSE } from 'hono/streaming'; - Hono wrong
const Hono = require('hono').Hono;correctimport { Hono } from 'hono';
Quickstart
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import { streamSSE } from 'hono/streaming';
import { SSETransport } from 'hono-mcp-server-sse-transport';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
const mcpServer = new McpServer(
{
name: 'your-mcp-server-name',
version: '1.0.0'
},
{
capabilities: {
tools: {}
}
}
);
// here you add your tools
// ...
const app = new Hono();
// to support multiple simultaneous connections we have a lookup object from
// sessionId to transport
const transports: { [sessionId: string]: SSETransport } = {};
app.get('/sse', (c) => {
return streamSSE(c, async (stream) => {
const transport = new SSETransport('/messages', stream);
transports[transport.sessionId] = transport;
stream.onAbort(() => {
delete transports[transport.sessionId];
});
await mcpServer.connect(transport);
while (true) {
// This will keep the connection alive
// You can also await for a promise that never resolves
await stream.sleep(60_000);
}
});
});
app.post('/messages', async (c) => {
const sessionId = c.req.query('sessionId');
const transport = transports[sessionId];
if (transport == null) {
return c.text('No transport found for sessionId', 400);
}
return await transport.handlePostMessage(c);
});
serve(
{
fetch: app.fetch,
port: 3000
}
);