@hono/mcp で Streamable HTTP の MCP Server を動かしてみる
はじめに
本記事では、@hono/mcp で、Streamable HTTP の MCP Server を動かす方法についてメモを残します。
具体的には、これまでに観た映画情報を返す MCP Server を作成します。
なお、映画の情報は、Cloudflare D1 に格納し、MCP Server は、Cloudflare Workers にデプロイします。
ローカルで MCP Server を動かす
以降は、サンプルコードを用いて環境構築を実施します。
ダミーデータの作成
まず、以下のコマンドを実行し、ローカルの D1 に映画情報を用意します。
git clone https://github.com/0machi/mcp-sample
cd mcp-sample
bun install
bun run d1:local:migrate
bun run d1:local:seed
bun run d1:local:studio
その後、https://local.drizzle.studio/ にアクセスすると、以下のテーブルに格納された映画情報を確認することができます。
MCP Server の作成
次に、@modelcontextprotocol/sdk を利用して、MCP Server が提供する Tool を登録します。
今回は、getMcpServer
という関数に D1 から映画情報を取得する Tool を3つ作成しました。
- 映画情報を全件取得する Tool
- 映画の製作年を指定して映画情報を取得する Tool
- 鑑賞した映画の本数を製作年ごとに集計する Tool
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { asc, between, count, desc } from "drizzle-orm";
import { drizzle } from "drizzle-orm/d1";
import type { Context } from "hono";
import z from "zod";
import { movieTable } from "../drizzle/schema";
import type { Env } from ".";
export const getMcpServer = async (c: Context<Env>) => {
const server = new McpServer({
name: "my-mcp-server",
version: "1.0.0",
});
const db = drizzle(c.env.MOVIE_DB);
server.registerTool(
"get-all-movies",
{
title: "Get All Movies",
description: "Get all list of movies",
},
async () => {
const result = await db
.select()
.from(movieTable)
.orderBy(desc(movieTable.productionYear), asc(movieTable.movieId));
return { content: [{ type: "text", text: JSON.stringify(result) }] };
},
);
server.registerTool(
"get-movies-by-production-year",
{
title: "Get Movies By Production Year",
description: "Get a list of movies produced between the specified years",
inputSchema: {
fromYear: z.number(),
toYear: z.number(),
},
},
async (params) => {
const result = await db
.select()
.from(movieTable)
.where(
between(movieTable.productionYear, params.fromYear, params.toYear),
)
.orderBy(desc(movieTable.productionYear), asc(movieTable.movieId));
return { content: [{ type: "text", text: JSON.stringify(result) }] };
},
);
server.registerTool(
"get-watched-movies-count-by-production-year",
{
title: "Get Watched Movies Count By Production Year",
description: "Get the count of watched movies grouped by production year",
},
async () => {
const result = await db
.select({
productionYear: movieTable.productionYear,
watchedCount: count(movieTable.movieId),
})
.from(movieTable)
.groupBy(movieTable.productionYear)
.orderBy(desc(movieTable.productionYear));
return { content: [{ type: "text", text: JSON.stringify(result) }] };
},
);
return server;
};
@hono/mcp で Streamable HTTP の MCP Server を動かす
作成した getMcpServer
という関数を import し、@hono/mcp で Streamable HTTP の MCP Server を /mcp
で受け付けるようにします。
ここでは、MCP Server には、Bearer Auth Middlewareで Bearer 認証を適用しています。
import { StreamableHTTPTransport } from "@hono/mcp";
import { Hono } from "hono";
import { env } from "hono/adapter";
import { bearerAuth } from "hono/bearer-auth";
import { getMcpServer } from "./mcp";
export type Env = {
Bindings: {
MOVIE_DB: D1Database;
};
};
const app = new Hono<Env>();
const transport = new StreamableHTTPTransport();
let isConnected = false;
app.use(
"/mcp",
bearerAuth({
verifyToken: async (token, c) => {
const { TOKEN } = env<{ TOKEN: string }>(c);
return token === TOKEN;
},
}),
);
app.all("/mcp", async (c) => {
const mcpServer = await getMcpServer(c);
const connectedToServer = mcpServer.connect(transport).then(() => {
isConnected = true;
});
if (!isConnected) {
await connectedToServer;
}
return transport.handleRequest(c);
});
export default app;
MCP Inspector による動作確認
最後に、@modelcontextprotocol/inspector を使用して、作成した MCP Server の動作を確認します。
cp .example.vars .dev.vars
bun run dev
bunx @modelcontextprotocol/inspector
MCP Inspector の起動後、以下の設定を実施して、MCP Server に接続します。
- Transport Type: Streamable HTTP
- URL: http://localhost:8787/mcp
- Custom Headers: Authorization: Bearer TOKEN
以下のように Tool が実行できることを確認します。
Cloudflare Workers で MCP Server を動かす
- 必要に応じて作成した MCP Server を Cloudflare Workers にデプロイし、D1 にデータを格納します。
Claude Desktop から接続
claude_desktop_config.json
に以下のように設定を追加して、MCP Server の動作を確認します。
{
"mcpServers": {
"movie-api": {
"command": "npx",
"args": [
"mcp-remote",
"<URL>",
"--header",
"Authorization: Bearer <TOKEN>"
]
}
}
}
Workers AI LLM Playground から接続
まず、CORS Middleware を、https://playground.ai.cloudflare.com/ に対して適用します。
app.use("/mcp", cors({ origin: "https://playground.ai.cloudflare.com" }));
その後、https://playground.ai.cloudflare.com/ にアクセスし、MCP Server の動作を確認します。
おわりに
本記事では、@hono/mcp で、Streamable HTTP の MCP Server を動かす方法についてメモを残しました。MCP Server を動かしてみたい方の参考になれば幸いです。
最近リピめしというサービスを公開したので、良ければ利用してみてください。
Discussion