mcp-examples
MCP Examples
Cloudflare WorkersとHonoで、REST APIとModel Context Protocol(MCP)のツールを実装するサンプルです。ユーザー・投稿データの取得、Zodによる入力検証、OpenAPI仕様とSwagger UIの生成を含みます。
セットアップ
依存関係をインストールし、開発サーバーを起動します。
pnpm install
pnpm run dev開発サーバーの起動後、次のURLを利用できます。
URL | 用途 |
| ユーザーAPI |
| 投稿API |
| OpenAPI JSON |
| Swagger UI |
| MCPエンドポイント |
本番用ビルドとCloudflare Workersへのデプロイには、次のコマンドを使用します。
pnpm run build
pnpm run deployWranglerの設定からCloudflareBindings型を生成する場合は、次のコマンドを実行します。
pnpm run cf-typegen生成した型は、HonoインスタンスのBindingsに指定します。
const app = new Hono<{ Bindings: CloudflareBindings }>();リクエスト検証とOpenAPI
このプロジェクトはhono-openapi@1.3.1とZod 4を使用します。src/features/users/routes.tsでは、hono-openapiのvalidatorがリクエストを検証すると同時に、query・path parameterのスキーマをOpenAPI仕様へ反映します。
zValidatorからvalidatorへ移行する
@hono/zod-validatorのzValidatorを使用しているルートは、importとミドルウェア名を次のように変更します。Zodスキーマ、および検証後の値を取得するc.req.valid()は変更しません。
-import { zValidator } from "@hono/zod-validator";
+import { validator } from "hono-openapi";
userRoutes.get(
"/",
- zValidator("query", getUsersQuerySchema, (result, c) => {
+ validator("query", getUsersQuerySchema, (result, c) => {
if (!result.success) {
return c.json(
{
success: false,
message: "クエリパラメータの形式が正しくありません",
- errors: result.error.issues,
+ errors: result.error,
},
400,
);
}
}),
async (c) => {
const query = c.req.valid("query");
// 検証済みのqueryを使用する
},
);paramを検証するGET /users/:idも同じ要領で移行します。
validator("param", getUserByIdParamsSchema, (result, c) => {
if (!result.success) {
return c.json(
{
success: false,
message:
"ユーザーIDの指定が正しくありません(1以上の整数を指定してください)",
errors: result.error,
},
400,
);
}
});両validatorでは、検証失敗時のresult.errorの型が異なります。
validator |
| クライアントへIssue一覧を返す指定 |
|
|
|
| Standard SchemaのIssue配列 |
|
hono-openapiのvalidatorにZodスキーマを直接渡せるため、入力検証ではresolver()によるラップは不要です。
validatorとdescribeRouteの役割
describeRouteはリクエスト検証に必須ではありません。2つのミドルウェアは、次のように役割を分担します。
API | 役割 |
| リクエストを実行時に検証し、検証済みの値を |
|
|
validatorだけでもルートと入力スキーマはOpenAPI仕様へ出力されますが、レスポンスはスキーマや説明を持たない200として生成されます。このプロジェクトでは、全RESTルートでレスポンスやタグも明示するため、describeRouteをvalidatorと併用します。
+import { COMMON_ERROR_RESPONSES, OPENAPI_TAGS } from "@/api/openapi";
+import { describeRoute, validator } from "hono-openapi";
userRoutes.get(
"/",
+ describeRoute({
+ tags: [OPENAPI_TAGS.USERS],
+ summary: "ユーザー一覧を取得する",
+ responses: {
+ 200: { description: "ユーザー一覧の取得成功" },
+ 400: { description: "クエリパラメータが不正" },
+ ...COMMON_ERROR_RESPONSES,
+ },
+ }),
validator("query", getUsersQuerySchema, (result, c) => {
// 検証エラーの応答
}),
async (c) => {
// ユーザー一覧を返す既存のハンドラー
},
);OPENAPI_TAGSとCOMMON_ERROR_RESPONSESはsrc/api/openapi.tsで共有します。各ルート固有のsummary、成功・400・404の説明はルート側に残し、全ルートで同一となる500・502の説明だけを共通化します。
レスポンス本文のスキーマを定義するときは、describeRouteのresponses内でresolver()を使用できます。実際の成功レスポンスは{ success: true, data: users }というラッパーを持つため、dataの配列だけではなく、ラッパー全体に対応するZodスキーマを指定してください。
Swagger UIから実ルートを呼び出す仕組み
REST APIは、2段階でHonoアプリへマウントされています。
userRoutes の "/"
→ apiRoot の "/users"
→ app の "/api"
→ 実際のルート "/api/users"外側のsrc/index.tsxは、apiRootを/apiへマウントします。
app.route("/api", apiRoot);一方、OpenAPI仕様は内側のapiRootから生成します。
apiRoot.route("/users", usersRoutes);
apiRoot.get(
"/openapi",
openAPIRouteHandler(apiRoot, {
documentation: {
info: {
title: "Post App API",
version: "1.0.0",
description: "Cloudflare with Hono Examples",
},
servers: [{ url: "/api" }],
},
}),
);openAPIRouteHandler()へ渡しているのは外側のappではなくapiRootです。そのため、生成されるOpenAPI仕様のpathsには、/apiを含まない内側のパスが記録されます。
{
"servers": [{ "url": "/api" }],
"paths": {
"/users": {},
"/users/{id}": {},
"/posts": {},
"/posts/{id}": {}
}
}Swagger UIは、OpenAPIのserver URLとpathを組み合わせてリクエスト先を決定します。
server "/api" + path "/users" = "/api/users"serversを設定しない場合、Swagger UIは通常、仕様にある/usersをそのままホスト直下へ送信します。実ルートの/api/usersを呼び出すには、現在の実装のようにdocumentation.serversへ{ url: "/api" }を設定します。apiRoot側のルートを/api/usersへ変更すると、外側のマウントと重なって実ルートが/api/api/usersになるため、ルート定義側へ/apiを重ねないでください。