🔥
HonoでMCPサーバーを動かす
はじめに
こちらのブログを参考にStreamable HTTP transport MCPサーバーをexpressで動かすことができた。
これをHonoでやってみた。
変更点
やってみる。といっても、ちょっと実装変えるだけ。
- ルーティングの実装
// Express
app.post("/mcp", async (req, res) => { ... });
// Hono
app.post("/mcp", async (c) => { ... });
Honoでは、コンテキストcを通じてリクエストとレスポンスにアクセスします。これはExpressのreqとresパラメータと異なる方式です。
- リクエストとレスポンスの変換
import { toFetchResponse, toReqRes } from "fetch-to-node";
// リクエスト処理部分
const { req, res } = toReqRes(c.req.raw);
await transport.handleRequest(req, res, body);
HonoはHonoRequestクラスを使用してますが、MCPサーバーのSDKにパラメータとして渡すために、fetch-to-nodeライブラリを使用して変換を行っています。
- return時の変換
// Hono
// 明示的にレスポンスを返す必要がある
return toFetchResponse(res);
MCPサーバーSDKの処理後は、toFetchResponse(res)がこの変換を行い、Honoがクライアントに適切にレスポンスを送信できるようにします。
全体
hono-mcp-server.js
+ import { Hono } from "hono";
+ import { handle } from 'hono/aws-lambda';
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
+ import { toFetchResponse, toReqRes } from "fetch-to-node";
const getServer = () => {
// MCP サーバーを作成
const server = new McpServer(
{
name: "my-mcp-server",
version: "0.0.1",
}
);
// Add an addition tool
server.tool(
...
);
return server;
};
+ const app = new Hono();
// MCPエンドポイント (POST)
+ app.post("/mcp", async (c) => {
// Fetch APIのリクエスト/レスポンスをNode.jsのリクエスト/レスポンスに変換
+ const { req, res } = toReqRes(c.req.raw);
const server = getServer();
try {
const body = await c.req.json();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
transport.onerror = console.error.bind(console);
// MCPサーバーをトランスポートに接続
await server.connect(transport);
// MCPリクエストを処理
await transport.handleRequest(req, res, body);
// リクエストが終了したらリソースをクリーンアップ
res.on("close", () => {
console.log("Request closed");
transport.close();
server.close();
});
console.log(res);
// Node.jsのレスポンスをFetch APIのレスポンスに変換して返す
+ return toFetchResponse(res);
} catch (e) {
console.error("MCP request error:", e);
return c.json(
{
jsonrpc: "2.0",
error: {
code: -32603,
message: "Internal server error",
},
id: null,
},
{ status: 500 }
);
}
});
// GET リクエスト(MCP)
+app.get("/mcp", (c) => {
console.log("Received GET MCP request");
return c.json(
{
jsonrpc: "2.0",
error: {
code: -32000,
message: "Method not allowed.",
},
id: null,
},
{ status: 405 }
);
});
// DELETE リクエスト(MCP)
+app.delete("/mcp", (c) => {
console.log("Received DELETE MCP request");
return c.json(
{
jsonrpc: "2.0",
error: {
code: -32000,
message: "Method not allowed.",
},
id: null,
},
{ status: 405 }
);
});
export const handler = handle(app);
終わりに
次はこれをLambdaで動かしてみたい。
Discussion