Skip to main content
Glama
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`を重ねないでください。