Skip to main content
Glama

MCP Examples

Cloudflare WorkersとHonoで、REST APIとModel Context Protocol(MCP)のツールを実装するサンプルです。ユーザー・投稿データの取得、Zodによる入力検証、OpenAPI仕様とSwagger UIの生成を含みます。

セットアップ

依存関係をインストールし、開発サーバーを起動します。

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へのデプロイには、次のコマンドを使用します。

pnpm run build
pnpm run deploy

Wranglerの設定から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-openapivalidatorがリクエストを検証すると同時に、query・path parameterのスキーマをOpenAPI仕様へ反映します。

zValidatorからvalidatorへ移行する

@hono/zod-validatorzValidatorを使用しているルートは、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

result.error

クライアントへIssue一覧を返す指定

@hono/zod-validatorzValidator

ZodError

result.error.issues

hono-openapivalidator

Standard SchemaのIssue配列

result.error

hono-openapivalidatorにZodスキーマを直接渡せるため、入力検証ではresolver()によるラップは不要です。

validatordescribeRouteの役割

describeRouteはリクエスト検証に必須ではありません。2つのミドルウェアは、次のように役割を分担します。

API

役割

validator

リクエストを実行時に検証し、検証済みの値をc.req.valid()へ格納する。検証対象のスキーマをOpenAPIのparameterまたはrequest bodyへ反映する。

describeRoute

summarytagsoperationId、レスポンスなど、OpenAPI operationの追加情報を定義する。

validatorだけでもルートと入力スキーマはOpenAPI仕様へ出力されますが、レスポンスはスキーマや説明を持たない200として生成されます。このプロジェクトでは、全RESTルートでレスポンスやタグも明示するため、describeRoutevalidatorと併用します。

+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_TAGSCOMMON_ERROR_RESPONSESsrc/api/openapi.tsで共有します。各ルート固有のsummary、成功・400・404の説明はルート側に残し、全ルートで同一となる500・502の説明だけを共通化します。

レスポンス本文のスキーマを定義するときは、describeRouteresponses内で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を重ねないでください。