mcp-examples
README.md
# MCP Examples
Cloudflare WorkersとHonoで、REST APIとModel Context Protocol(MCP)のツールを実装するサンプルです。ユーザー・投稿データの取得、Zodによる入力検証、OpenAPI仕様とSwagger UIの生成を含みます。
## セットアップ
依存関係をインストールし、開発サーバーを起動します。
```bash
pnpm install
pnpm run dev
```
開発サーバーの起動後、次のURLを利用できます。
| URL | 用途 |
| --- | --- |
| `/api/users` | ユーザーAPI |
| `/api/posts` | 投稿API |
| `/api/openapi` | OpenAPI JSON |
| `/api/docs` | Swagger UI |
| `/mcp` | MCPエンドポイント |
本番用ビルドとCloudflare Workersへのデプロイには、次のコマンドを使用します。
```bash
pnpm run build
pnpm run deploy
```
Wranglerの設定から`CloudflareBindings`型を生成する場合は、次のコマンドを実行します。
```bash
pnpm run cf-typegen
```
生成した型は、Honoインスタンスの`Bindings`に指定します。
```ts
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()`は変更しません。
```diff
-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`も同じ要領で移行します。
```ts
validator("param", getUserByIdParamsSchema, (result, c) => {
if (!result.success) {
return c.json(
{
success: false,
message:
"ユーザーIDの指定が正しくありません(1以上の整数を指定してください)",
errors: result.error,
},
400,
);
}
});
```
両validatorでは、検証失敗時の`result.error`の型が異なります。
| validator | `result.error` | クライアントへIssue一覧を返す指定 |
| --- | --- | --- |
| `@hono/zod-validator`の`zValidator` | `ZodError` | `result.error.issues` |
| `hono-openapi`の`validator` | Standard SchemaのIssue配列 | `result.error` |
`hono-openapi`の`validator`にZodスキーマを直接渡せるため、入力検証では`resolver()`によるラップは不要です。
### `validator`と`describeRoute`の役割
`describeRoute`はリクエスト検証に必須ではありません。2つのミドルウェアは、次のように役割を分担します。
| API | 役割 |
| --- | --- |
| `validator` | リクエストを実行時に検証し、検証済みの値を`c.req.valid()`へ格納する。検証対象のスキーマをOpenAPIのparameterまたはrequest bodyへ反映する。 |
| `describeRoute` | `summary`、`tags`、`operationId`、レスポンスなど、OpenAPI operationの追加情報を定義する。 |
`validator`だけでもルートと入力スキーマはOpenAPI仕様へ出力されますが、レスポンスはスキーマや説明を持たない`200`として生成されます。このプロジェクトでは、全RESTルートでレスポンスやタグも明示するため、`describeRoute`を`validator`と併用します。
```diff
+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アプリへマウントされています。
```text
userRoutes の "/"
→ apiRoot の "/users"
→ app の "/api"
→ 実際のルート "/api/users"
```
外側の`src/index.tsx`は、`apiRoot`を`/api`へマウントします。
```ts
app.route("/api", apiRoot);
```
一方、OpenAPI仕様は内側の`apiRoot`から生成します。
```ts
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`を含まない内側のパスが記録されます。
```json
{
"servers": [{ "url": "/api" }],
"paths": {
"/users": {},
"/users/{id}": {},
"/posts": {},
"/posts/{id}": {}
}
}
```
Swagger UIは、OpenAPIのserver URLとpathを組み合わせてリクエスト先を決定します。
```text
server "/api" + path "/users" = "/api/users"
```
`servers`を設定しない場合、Swagger UIは通常、仕様にある`/users`をそのままホスト直下へ送信します。実ルートの`/api/users`を呼び出すには、現在の実装のように`documentation.servers`へ`{ url: "/api" }`を設定します。`apiRoot`側のルートを`/api/users`へ変更すると、外側のマウントと重なって実ルートが`/api/api/users`になるため、ルート定義側へ`/api`を重ねないでください。
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues