mcpField
C
cloudflare-mcp-server

cloudflare-mcp-server

github.com/jezweb開発1.1k328 回インストール3 週間前に更新
リポジトリ

cloudflare-mcp-server について

TypeScriptでCloudflare Workers上にリモートMCPサーバーを構築・デプロイするための開発者向けスキルです。トランスポートのパス、認証、デプロイ、接続エラーの調査を扱います。

主な機能

  1. 01WorkersでのリモートMCPサーバー構築
  2. 02SSEとStreamable HTTPの設定
  3. 03認証テンプレートとOAuth設定
  4. 04パスとデプロイのトラブル対応

活用シーン

  1. 01テンプレートからリモートMCPサーバーを作成
  2. 02デプロイ後の404応答を調査
  3. 03MCPサーバーにOAuth認証を設定

MCP 対応

リモートMCPサーバーの構築方法を扱い、SSEとStreamable HTTP、クライアントURL設定、OAuthについて説明します。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jezweb/claude-skills --skill cloudflare-mcp-server

インストール後、Claude Code で/cloudflare-mcp-serverを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
.claude-plugin/
plugin.json
commands/
init.md
references/
authentication.md
common-issues.md
debugging-guide.md
http-transport-fundamentals.md
oauth-providers.md
official-examples.md
transport-comparison.md
transport.md
rules/
cloudflare-mcp-server.md
templates/
basic-mcp-server.ts
claude_desktop_config.json
mcp-bearer-auth.ts
mcp-http-fundamentals.ts
mcp-oauth-proxy.ts
mcp-stateful-do.ts
mcp-with-d1.ts
mcp-with-workers-ai.ts
package.json
wrangler-basic.jsonc
wrangler-oauth.jsonc

詳細

Cloudflare MCP サーバースキル

TypeScript を使用して、Cloudflare Workers 上に Model Context Protocol(MCP)サーバーを構築し、デプロイします。

ステータス:本番環境で利用可能 ✅ 最終更新日:2026-01-21 最新バージョン:@modelcontextprotocol/[email protected]、@cloudflare/[email protected]、[email protected]

最近の更新(2025 年):

  • 2025 年 9 月:コードモード(ツールを呼び出す代わりにエージェントがコードを作成、スキーマから TypeScript API を自動生成)
  • 2025 年 8 月:MCP エリシテーション(対話型ワークフロー、実行中のユーザー入力)、タスクキュー、メール連携
  • 2025 年 7 月:MCPClientManager(接続管理、OAuth フロー、ハイバネーション)
  • 2025 年 4 月:HTTP ストリーマブルトランスポート(単一エンドポイント、SSE より推奨)、Python MCP サポート
  • 2025 年 5 月:Claude.ai のリモート MCP サポート、use-mcp React ライブラリ、大手企業との提携

このスキルについて

このスキルでは、リモート MCP を公式にサポートする唯一のプラットフォームである Cloudflare 上に、リモート MCP サーバーを構築する方法を学びます。

使用する場面:MCP と Cloudflare に関する 24 件以上のよくあるエラー(特に、最も多い失敗原因である URL パスの不一致)を回避するとき


🚀 クイックスタート(5 分)

Cloudflare の公式テンプレートから始めます:

npm create cloudflare@latest -- my-mcp-server \
  --template=cloudflare/ai/demos/remote-mcp-authless
cd my-mcp-server && npm install && npm run dev

認証要件に応じてテンプレートを選択してください:

  • remote-mcp-authless - 認証なし(ほとんどの場合に推奨)
  • remote-mcp-github-oauth - GitHub OAuth
  • remote-mcp-google-oauth - Google OAuth
  • remote-mcp-auth0 / remote-mcp-authkit - エンタープライズ SSO
  • mcp-server-bearer-auth - カスタム認証

すべてのテンプレート:https://github.com/cloudflare/ai/tree/main/demos

本番環境での例:https://github.com/cloudflare/mcp-server-cloudflare (実際の連携機能を備えた 15 個のサーバー)


デプロイのワークフロー

# 1. Create from template
npm create cloudflare@latest -- my-mcp --template=cloudflare/ai/demos/remote-mcp-authless
cd my-mcp && npm install && npm run dev

# 2. Deploy
npx wrangler deploy
# Note the output URL: https://my-mcp.YOUR_ACCOUNT.workers.dev

# 3. Test (PREVENTS 80% OF ERRORS!)
curl https://my-mcp.YOUR_ACCOUNT.workers.dev/sse
# Expected: {"name":"My MCP Server","version":"1.0.0","transports":["/sse","/mcp"]}
# Got 404? See "HTTP Transport Fundamentals" below

# 4. Configure client (~/.config/claude/claude_desktop_config.json)
{
  "mcpServers": {
    "my-mcp": {
      "url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse"  // Must match curl URL!
    }
  }
}

# 5. Restart Claude Desktop (config only loads at startup)

デプロイ後のチェックリスト:

  • curl がサーバー情報を返す(404 ではない)
  • クライアントの URL が curl の URL と完全に一致している
  • Claude Desktop を再起動した
  • Claude Desktop にツールが表示されている
  • ツール呼び出しのテストが成功する

⚠️ 重要:HTTP トランスポートの基本

MCP サーバーが接続に失敗する最大の原因は、URL パスの設定ミスです。

URL パス設定の詳細

特定のパスで MCP サーバーを提供する場合、クライアントの URL は完全に一致している必要があります。

例 1:/sse で提供する場合

// src/index.ts
export default {
  fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const { pathname } = new URL(request.url);

    if (pathname.startsWith("/sse")) {
      return MyMCP.serveSSE("/sse").fetch(request, env, ctx);  // ← Base path is "/sse"
    }

    return new Response("Not Found", { status: 404 });
  }
};

クライアント設定には /sse を必ず含めてください:

{
  "mcpServers": {
    "my-mcp": {
      "url": "https://my-mcp.workers.dev/sse"  // ✅ Correct
    }
  }
}

❌ 間違ったクライアント設定:

"url": "https://my-mcp.workers.dev"      // Missing /sse → 404
"url": "https://my-mcp.workers.dev/"     // Missing /sse → 404
"url": "http://localhost:8788"           // Wrong after deploy

例 2:/(ルート)で提供する場合

export default {
  fetch(request: Request, env: Env, ctx: ExecutionContext) {
    return MyMCP.serveSSE("/").fetch(request, env, ctx);  // ← Base path is "/"
  }
};

クライアント設定:

{
  "mcpServers": {
    "my-mcp": {
      "url": "https://my-mcp.workers.dev"  // ✅ Correct (no /sse)
    }
  }
}

ベースパスがツールの URL に与える影響

serveSSE("/sse") を呼び出すと、MCP ツールは次の場所で提供されます:

https://my-mcp.workers.dev/sse/tools/list
https://my-mcp.workers.dev/sse/tools/call
https://my-mcp.workers.dev/sse/resources/list

serveSSE("/") を呼び出すと、MCP ツールは次の場所で提供されます:

https://my-mcp.workers.dev/tools/list
https://my-mcp.workers.dev/tools/call
https://my-mcp.workers.dev/resources/list

ベースパスはすべての MCP エンドポイントの先頭に自動的に付加されます。


リクエスト/レスポンスのライフサイクル
1. Client connects to: https://my-mcp.workers.dev/sse
                                ↓
2. Worker receives request: { url: "https://my-mcp.workers.dev/sse", ... }
                                ↓
3. Your fetch handler: const { pathname } = new URL(request.url)
                                ↓
4. pathname === "/sse" → Check passes
                                ↓
5. MyMCP.serveSSE("/sse").fetch() → MCP server handles request
                                ↓
6. Tool calls routed to: /sse/tools/call

クライアントが https://my-mcp.workers.dev に接続した場合(/sse がない場合):

pathname === "/" → Check fails → 404 Not Found

URL 設定のテスト

手順 1:MCP サーバーをデプロイする

npx wrangler deploy
# Output: Deployed to https://my-mcp.YOUR_ACCOUNT.workers.dev

手順 2:curl でベースパスをテストする

# If serving at /sse, test this URL:
curl https://my-mcp.YOUR_ACCOUNT.workers.dev/sse

# Should return MCP server info (not 404)

手順 3:テストした URL と完全に一致するようにクライアント設定を更新する

{
  "mcpServers": {
    "my-mcp": {
      "url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse"  // Match curl URL
    }
  }
}

手順 4:Claude Desktop を再起動する


デプロイ後のチェックリスト

デプロイ後、次の項目を確認します:

  • curl https://worker.dev/sse が MCP サーバー情報を返す(404 ではない)
  • クライアント設定の URL がデプロイ先 URL と完全に一致している
  • URL に誤字がない(よくある例:workers.dev ではなく workes.dev)
  • デプロイ済み Workers には http:// ではなく https:// を使用している
  • OAuth を使用する場合、リダイレクト URI も更新されている

トランスポートの選択

利用できるトランスポートは 2 種類です:

  1. SSE(Server-Sent Events) - レガシー方式、幅広い互換性

    MyMCP.serveSSE("/sse").fetch(request, env, ctx)
    
  2. ストリーマブル HTTP - 2025 年の標準(推奨)、単一エンドポイント

    MyMCP.serve("/mcp").fetch(request, env, ctx)
    

互換性を最大限に高めるには、両方をサポートしてください:

export default {
  fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const { pathname } = new URL(request.url);

    if (pathname.startsWith("/sse")) {
      return MyMCP.serveSSE("/sse").fetch(request, env, ctx);
    }
    if (pathname.startsWith("/mcp")) {
      return MyMCP.serve("/mcp").fetch(request, env, ctx);
    }

    return new Response("Not Found", { status: 404 });
  }
};

重要:パスを正しく照合するには pathname.startsWith() を使用してください!


2025 年の知識ギャップ

MCP エリシテーション(2025 年 8 月)

MCP サーバーは、ツールの実行中にユーザー入力を求められるようになりました:

// Request user input during tool execution
const result = await this.elicit({
  prompt: "Enter your API key:",
  type: "password"
});

// Interactive workflows with Durable Objects state
await this.state.storage.put("api_key", result);

用途:確認、フォーム、複数ステップのワークフロー 状態:エージェントのハイバネーション中も保持されます

コードモード(2025年9月)

Agents SDK は MCP スキーマを TypeScript API に変換します。

// Old: Direct tool calls
await server.callTool("get_user", { id: "123" });

// New: Type-safe generated API
const user = await api.getUser("123");

メリット:ドキュメントコメントの自動生成、型安全性、コード補完

MCPClientManager(2025年7月)

MCP クライアント機能を提供する新しいクラスです。

import { MCPClientManager } from "agents/mcp";

const manager = new MCPClientManager(env);
await manager.connect("https://external-mcp.com/sse");
// Auto-discovers tools, resources, prompts
// Handles reconnection, OAuth flow, hibernation
タスクキューとメール(2025年8月)
// Task queues for background jobs
await this.queue.send({ task: "process_data", data });

// Email integration
async onEmail(message: Email) {
  // Process incoming email
  const response = await this.generateReply(message);
  await this.sendEmail(response);
}
HTTP Streamable Transport の詳細(2025年4月)

接続用とメッセージング用に分かれていたエンドポイントを、単一のエンドポイントに置き換えます。

// Old: Separate endpoints
/connect  // Initialize connection
/message  // Send/receive messages

// New: Single streamable endpoint
/mcp      // All communication via HTTP streaming

メリット:アーキテクチャの簡素化、パフォーマンスの向上


セキュリティに関する考慮事項

PKCE バイパスの脆弱性(重大)

CVE:GHSA-qgp8-v765-qxx9 深刻度:重大 修正バージョン:@cloudflare/[email protected]

問題:OAuth プロバイダーライブラリの以前のバージョンには、PKCE 保護を完全に回避し、攻撃者が認可コードを傍受できる可能性のある重大な脆弱性がありました。

必要な対応:

# Check current version
npm list @cloudflare/workers-oauth-provider

# Update if < 0.0.5
npm install @cloudflare/workers-oauth-provider@latest

安全な最小バージョン:@cloudflare/[email protected] 以降

トークンストレージのベストプラクティス

OAuth トークンには必ず暗号化ストレージを使用してください。

// ✅ GOOD: workers-oauth-provider handles encryption automatically
export default new OAuthProvider({
  kv: (env) => env.OAUTH_KV,  // Tokens stored encrypted
  // ...
});

// ❌ BAD: Storing tokens in plain text
await env.KV.put("access_token", token);  // Security risk!

ユーザーごとにスコープ設定した KV キーにより、ユーザー間のデータ漏えいを防ぎます。

// ✅ GOOD: Namespace by user ID
await env.KV.put(`user:${userId}:todos`, data);

// ❌ BAD: Global namespace
await env.KV.put(`todos`, data);  // Data visible to all users!

認証パターン

ユースケースに応じて認証方式を選択してください。

  1. 認証なし - 内部ツール、開発(テンプレート:remote-mcp-authless)

  2. ベアラートークン - カスタム認証(テンプレート:mcp-server-bearer-auth)

    // Validate Authorization: Bearer <token>
    const token = request.headers.get("Authorization")?.replace("Bearer ", "");
    if (!await validateToken(token, env)) {
      return new Response("Unauthorized", { status: 401 });
    }
    
  3. OAuth プロキシ - GitHub/Google(テンプレート:remote-mcp-github-oauth)

    import { OAuthProvider, GitHubHandler } from "@cloudflare/workers-oauth-provider";
    
    export default new OAuthProvider({
      authorizeEndpoint: "/authorize",
      tokenEndpoint: "/token",
      defaultHandler: new GitHubHandler({
        clientId: (env) => env.GITHUB_CLIENT_ID,
        clientSecret: (env) => env.GITHUB_CLIENT_SECRET,
        scopes: ["repo", "user:email"]
      }),
      kv: (env) => env.OAUTH_KV,
      apiHandlers: { "/sse": MyMCP.serveSSE("/sse") }
    });
    

    ⚠️ 重大:すべての OAuth URL(url、authorizationUrl、tokenUrl)は同じドメインを使用する必要があります。

  4. DCR を使用したリモート OAuth - フル機能の OAuth プロバイダー(テンプレート:remote-mcp-authkit)

セキュリティレベル:認証なし(⚠️)< ベアラー(✅)< OAuth プロキシ(✅✅)< リモート OAuth(✅✅✅)


ステートフル MCP サーバー(Durable Objects)

McpAgent は Durable Objects を拡張し、セッションごとの状態を管理します。

// Storage API
await this.state.storage.put("key", "value");
const value = await this.state.storage.get<string>("key");

// Required wrangler.jsonc
{
  "durable_objects": {
    "bindings": [{ "name": "MY_MCP", "class_name": "MyMCP" }]
  },
  "migrations": [{ "tag": "v1", "new_classes": ["MyMCP"] }]  // Required on first deploy!
}

重要:初回デプロイ時にマイグレーションが必要です。

料金:Durable Objects は無料プランに含まれるようになりました(2025年)。


アーキテクチャ:内部トランスポートと外部トランスポート

重要:McpAgent は、クライアント向け通信と内部通信で異なるトランスポートを使用します。

出典:GitHub Issue #172

トランスポートアーキテクチャ
Client --- (SSE or HTTP) --> Worker --- (WebSocket) --> Durable Object

クライアント → Worker(外部):

  • SSE トランスポート:/sse エンドポイント
  • HTTP Streamable:/mcp エンドポイント
  • クライアントがトランスポートを選択

Worker → Durable Object(内部):

  • 常に WebSocket
  • PartyServer(McpAgent の内部依存関係)で必須
  • 自動的にアップグレードされ、クライアントからは見えません
これが意味すること
  1. SSE クライアントは完全にサポートされています - 外部インターフェースに SSE を使用できます
  2. DO には WebSocket が必須です - 内部の Worker と DO の通信には常に WebSocket が使用されます
  3. これは制限ではありません - McpAgent のアーキテクチャ上の実装詳細です
例
export default {
  fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const { pathname } = new URL(request.url);

    // Client uses SSE
    if (pathname.startsWith("/sse")) {
      // ✅ Client → Worker: SSE
      // ✅ Worker → DO: WebSocket (automatic)
      return MyMCP.serveSSE("/sse").fetch(request, env, ctx);
    }

    return new Response("Not Found", { status: 404 });
  }
};

要点:内部で WebSocket が必須であっても、クライアントには SSE を提供できます。


よくあるパターン

ツールの戻り値の形式(重大)

出典:Stytch Blog - OAuth を使用した MCP サーバーの構築

すべての MCP ツールは、次の形式で返す必要があります。

this.server.tool(
  "my_tool",
  { /* schema */ },
  async (params) => {
    // ✅ CORRECT: Return object with content array
    return {
      content: [
        { type: "text", text: "Your result here" }
      ]
    };

    // ❌ WRONG: Raw string
    return "Your result here";

    // ❌ WRONG: Plain object
    return { result: "Your result here" };
  }
);

よくある間違い:適切な MCP コンテンツ形式ではなく、生の文字列やプレーンオブジェクトを返すことです。これにより、クライアントの解析エラーが発生します。

条件に応じたツール登録

出典:Cloudflare Blog - AI エージェントの構築

認証済みユーザーに応じてツールを動的に追加します。

export class MyMCP extends McpAgent<Env> {
  async init() {
    this.server = new McpServer({ name: "My MCP" });

    // Base tools for all users
    this.server.tool("public_tool", { /* schema */ }, async (params) => {
      // Available to everyone
    });

    // Conditional tools based on user
    const userId = this.props?.userId;
    if (await this.isAdmin(userId)) {
      this.server.tool("admin_tool", { /* schema */ }, async (params) => {
        // Only available to admins
      });
    }

    // Premium features
    if (await this.isPremiumUser(userId)) {
      this.server.tool("premium_feature", { /* schema */ }, async (params) => {
        // Only for premium users
      });
    }
  }

  private async isAdmin(userId?: string): Promise<boolean> {
    if (!userId) return false;
    const userRole = await this.state.storage.get<string>(`user:${userId}:role`);
    return userRole === "admin";
  }
}

ユースケース:

  • ユーザーごとの機能フラグ
  • プレミアムプランと無料プランのツール
  • ロールベースのアクセス制御(RBAC)
  • 新しいツールの A/B テスト
DO ストレージを使用したキャッシュ
async getCached<T>(key: string, ttlMs: number, fetchFn: () => Promise<T>): Promise<T> {
  const cached = await this.state.storage.get<{ data: T, timestamp: number }>(key);
  if (cached && Date.now() - cached.timestamp < ttlMs) {
    return cached.data;
  }
  const data = await fetchFn();
  await this.state.storage.put(key, { data, timestamp: Date.now() });
  return data;
}
レート制限
async rateLimit(key: string, maxRequests: number, windowMs: number): Promise<boolean> {
  const requests = await this.state.storage.get<number[]>(`ratelimit:${key}`) || [];
  const recentRequests = requests.filter(ts => Date.now() - ts < windowMs);
  if (recentRequests.length >= maxRequests) return false;
  recentRequests.push(Date.now());
  await this.state.storage.put(`ratelimit:${key}`, recentRequests);
  return true;
}

既知のエラー24件(解決策付き)

1. McpAgent クラスがエクスポートされていない

エラー:TypeError: Cannot read properties of undefined (reading 'serve')

原因:McpAgent クラスのエクスポートを忘れています。

解決策:

export class MyMCP extends McpAgent { ... }  // ✅ Must export
export default { fetch() { ... } }

2. ベースパスの設定が一致しない(最もよくある問題です!)

エラー:404 Not Found または Connection failed

原因:serveSSE("/sse") を使用している一方で、クライアントは https://worker.dev(/sse がありません)に接続するよう設定されています。

解決策:ベースパスを正確に一致させます。

// Server serves at /sse
MyMCP.serveSSE("/sse").fetch(...)

// Client MUST include /sse
{ "url": "https://worker.dev/sse" }  // ✅ Correct
{ "url": "https://worker.dev" }      // ❌ Wrong - 404

デバッグ手順:

  1. サーバーが使用するパスを確認します:serveSSE("/sse") と serveSSE("/") のどちらか
  2. curl でテストします:curl https://worker.dev/sse
  3. curl の URL に一致するようクライアント設定を更新します

3. トランスポートタイプの混同

エラー:Connection failed: Unexpected response format

原因:クライアントが SSE を想定しているのに HTTP エンドポイントに接続している(またはその逆です)。

解決策:トランスポートタイプを一致させます。

// SSE transport
MyMCP.serveSSE("/sse")  // Client URL: https://worker.dev/sse

// HTTP transport
MyMCP.serve("/mcp")     // Client URL: https://worker.dev/mcp

ベストプラクティス:両方のトランスポートをサポートします(トランスポート選択ガイドを参照)。


4. pathname.startsWith() のロジックエラー

エラー:/sse と /mcp の両方のルートが失敗するか、競合します。

原因:パスマッチングのロジックが正しくありません。

解決策:startsWith() を正しく使用します。

// ✅ CORRECT
if (pathname.startsWith("/sse")) {
  return MyMCP.serveSSE("/sse").fetch(...);
}
if (pathname.startsWith("/mcp")) {
  return MyMCP.serve("/mcp").fetch(...);
}

// ❌ WRONG: Exact match breaks sub-paths
if (pathname === "/sse") {  // Breaks /sse/tools/list
  return MyMCP.serveSSE("/sse").fetch(...);
}

5. ローカル URL とデプロイ後の URL の不一致

エラー:開発環境では動作しますが、デプロイ後に失敗します。

原因:クライアントが引き続き localhost の URL を使用するよう設定されています。

解決策:デプロイ後にクライアント設定を更新します。

// Development
{ "url": "http://localhost:8788/sse" }

// ⚠️ MUST UPDATE after npx wrangler deploy
{ "url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse" }

デプロイ後のチェックリスト:

  • npx wrangler deploy を実行し、出力された URL を控えます
  • デプロイ後の URL でクライアント設定を更新します
  • curl でテストします
  • Claude Desktop を再起動します

6. OAuth リダイレクト URI の不一致

エラー:OAuth error: redirect_uri does not match

原因:OAuth リダイレクト URI がデプロイ後の URL と一致していません。

解決策:デプロイ後にすべての OAuth URL を更新します。

{
  "url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse",
  "auth": {
    "type": "oauth",
    "authorizationUrl": "https://my-mcp.YOUR_ACCOUNT.workers.dev/authorize",  // Must match deployed domain
    "tokenUrl": "https://my-mcp.YOUR_ACCOUNT.workers.dev/token"
  }
}

重要:すべての URL で同じプロトコルとドメインを使用する必要があります!


7. OPTIONS ハンドラーがない(CORS プリフライト)

エラー:Access to fetch at '...' blocked by CORS policy または Method Not Allowed

原因:ブラウザのクライアントは CORS プリフライトのために OPTIONS リクエストを送信しますが、サーバーが処理していません。

解決策:OPTIONS ハンドラーを追加します。

export default {
  fetch(request: Request, env: Env, ctx: ExecutionContext) {
    // Handle CORS preflight
    if (request.method === "OPTIONS") {
      return new Response(null, {
        status: 204,
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
          "Access-Control-Allow-Headers": "Content-Type, Authorization",
          "Access-Control-Max-Age": "86400"
        }
      });
    }

    // ... rest of your fetch handler
  }
};

必要な場合:ブラウザベースの MCP クライアント(ブラウザ内の MCP Inspector など)


8. リクエストボディの検証がない

エラー:TypeError: Cannot read properties of undefined または JSON の解析中に Unexpected token

原因:クライアントが不正な形式の JSON を送信し、サーバーが解析前に検証していません。

解決策:リクエスト処理を try/catch で囲みます。

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    try {
      // Your MCP server logic
      return await MyMCP.serveSSE("/sse").fetch(request, env, ctx);
    } catch (error) {
      console.error("Request handling error:", error);
      return new Response(
        JSON.stringify({
          error: "Invalid request",
          details: error.message
        }),
        {
          status: 400,
          headers: { "Content-Type": "application/json" }
        }
      );
    }
  }
};

9. 環境変数の検証がない

エラー:TypeError: env.API_KEY is undefined または、エラーを表示せずに失敗します(ツールが空のデータを返します)。

原因:必須の環境変数が設定されていないか、実行時に見つかりません。

解決策:起動時の検証を追加します。

export class MyMCP extends McpAgent<Env> {
  async init() {
    // Validate required environment variables
    if (!this.env.API_KEY) {
      throw new Error("API_KEY environment variable not configured");
    }
    if (!this.env.DATABASE_URL) {
      throw new Error("DATABASE_URL environment variable not configured");
    }

    // Continue with tool registration
    this.server.tool(...);
  }
}

設定チェックリスト:

  • 開発環境:.dev.vars に追加します(ローカルのみ、gitignore 対象)
  • 本番環境:wrangler.jsonc の vars に追加する(公開情報の場合)か、wrangler secret を使用します(機密情報の場合)

ベストプラクティス:

# .dev.vars (local development, gitignored)
API_KEY=dev-key-123
DATABASE_URL=http://localhost:3000

# wrangler.jsonc (public config)
{
  "vars": {
    "ENVIRONMENT": "production",
    "LOG_LEVEL": "info"
  }
}

# wrangler secret (production secrets)
npx wrangler secret put API_KEY
npx wrangler secret put DATABASE_URL

10. McpAgent と McpServer の混同

エラー:TypeError: server.registerTool is not a function または this.server is undefined

原因:McpAgent クラスで、単独 SDK のパターンを使おうとしています。

解決策:McpAgent の this.server.tool() パターンを使用します。

// ❌ WRONG: Mixing standalone SDK with McpAgent
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

const server = new McpServer({ name: "My Server" });
server.registerTool(...);  // Not compatible with McpAgent!

export class MyMCP extends McpAgent { /* no server property */ }

// ✅ CORRECT: McpAgent pattern
export class MyMCP extends McpAgent<Env> {
  server = new McpServer({
    name: "My MCP Server",
    version: "1.0.0"
  });

  async init() {
    this.server.tool("tool_name", ...);  // Use this.server
  }
}

主な違い:McpAgent には this.server プロパティがありますが、単独 SDK にはありません。


11. WebSocket の休止による状態の消失

エラー:再接続後、状態が見つからないというメッセージが表示され、ツール呼び出しが失敗します。

原因:休止時にメモリ内の状態がクリアされます。

解決策:インスタンスのプロパティではなく、this.state.storage を使用します。

// ❌ DON'T: Lost on hibernation
this.userId = "123";

// ✅ DO: Persists through hibernation
await this.state.storage.put("userId", "123");

12. Durable Objects のバインディングがない

エラー:TypeError: Cannot read properties of undefined (reading 'idFromName')

原因:wrangler.jsonc で DO のバインディングを忘れています。

解決策:バインディングを追加します(ステートフル MCP サーバーのセクションを参照)。

{
  "durable_objects": {
    "bindings": [
      {
        "name": "MY_MCP",
        "class_name": "MyMCP",
        "script_name": "my-mcp-server"
      }
    ]
  }
}

13. マイグレーションが定義されていない

エラー:Error: Durable Object class MyMCP has no migration defined

原因:最初の DO のデプロイにはマイグレーションが必要です。

解決策:

{
  "migrations": [
    { "tag": "v1", "new_classes": ["MyMCP"] }
  ]
}

14. serializeAttachment() が使用されていない

エラー:休止状態から復帰したときに WebSocket のメタデータが失われます

原因:接続メタデータの保持に serializeAttachment() を使用していません

解決策:WebSocket の休止状態セクションを参照してください


15. OAuth 同意画面が無効になっている

セキュリティリスク:ユーザーは付与する権限を確認できません

原因:本番環境で allowConsentScreen: false に設定されています

解決策:本番環境では常に有効にしてください

export default new OAuthProvider({
  allowConsentScreen: true,  // ✅ Always true in production
  // ...
});

16. JWT 署名キーがありません

エラー:Error: JWT_SIGNING_KEY environment variable not set

原因:OAuth Provider では、トークンに署名するためのキーが必要です

解決策:

# Generate secure key
openssl rand -base64 32

# Add to wrangler secret
npx wrangler secret put JWT_SIGNING_KEY

17. ツールスキーマの検証エラー

エラー:ZodError: Invalid input type

原因:クライアントは文字列を送信していますが、スキーマでは数値を想定しています(またはその逆です)

解決策:Zod の変換機能を使用してください

// Accept string, convert to number
param: z.string().transform(val => parseInt(val, 10))

// Or: Accept both types
param: z.union([z.string(), z.number()]).transform(val =>
  typeof val === "string" ? parseInt(val, 10) : val
)

18. 複数のトランスポートエンドポイントが競合しています

エラー:/mcp の追加後、/sse が 404 を返します

原因:パスのマッチングが正しくありません(startsWith() がありません)

解決策:startsWith() または完全一致を正しく使用してください(エラー #4 を参照)


19. Miniflare の制限によるローカルテストの問題

エラー:ローカル開発環境で OAuth フローが失敗する、または Durable Objects の動作が異なります

原因:Miniflare はすべての DO 機能に対応していません

解決策:DO の全機能を使用するには npx wrangler dev --remote を使用してください

# Local simulation (faster but limited)
npm run dev

# Remote DOs (slower but accurate)
npx wrangler dev --remote

20. クライアント設定の形式エラー

エラー:Claude Desktop がサーバーを認識しません

原因:claude_desktop_config.json の JSON 形式が正しくありません

解決策:正しい形式については「Claude Desktop の接続」セクションを参照してください

よくある間違い:

// ❌ WRONG: Missing "mcpServers" wrapper
{
  "my-mcp": {
    "url": "https://worker.dev/sse"
  }
}

// ❌ WRONG: Trailing comma
{
  "mcpServers": {
    "my-mcp": {
      "url": "https://worker.dev/sse",  // ← Remove comma
    }
  }
}

// ✅ CORRECT
{
  "mcpServers": {
    "my-mcp": {
      "url": "https://worker.dev/sse"
    }
  }
}

21. ヘルスチェックエンドポイントがありません

問題:Worker が実行中か、URL が正しいかを確認できません

影響:接続問題のデバッグに時間がかかります

解決策:ヘルスチェックエンドポイントを追加してください(トランスポート選択ガイドを参照)

テスト:

curl https://my-mcp.workers.dev/health
# Should return: {"status":"ok","transports":{...}}

22. CORS ヘッダーがありません

エラー:Access to fetch at '...' blocked by CORS policy

原因:MCP サーバーがクロスオリジンリクエストに対して CORS ヘッダーを返しません

解決策:すべてのレスポンスに CORS ヘッダーを追加してください

// Manual CORS (if not using OAuthProvider)
const corsHeaders = {
  "Access-Control-Allow-Origin": "*",  // Or specific origin
  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type, Authorization"
};

// Add to responses
return new Response(body, {
  headers: {
    ...corsHeaders,
    "Content-Type": "application/json"
  }
});

注:OAuthProvider は CORS を自動的に処理します!


23. MCP 初期化中の IoContext タイムアウト

エラー:IoContext timed out due to inactivity, waitUntil tasks were cancelled

情報源:GitHub Issue #640

原因:カスタム Bearer 認証で McpAgent を使用して MCP サーバーを実装すると、MCP プロトコルの初期化ハンドシェイク中(ツールが呼び出される前)に IoContext がタイムアウトします。

症状:

  • ツールが呼び出される前にタイムアウトが発生します
  • 最初のリクエストからエージェントの初期化まで約 2 分の間隔があります
  • 内部メソッドは動作します(setInitializeRequest、getInitializeRequest、updateProps)
  • /mcp への GET と POST の両方がキャンセルされます
  • エラー:「IoContext timed out due to inactivity, waitUntil tasks were cancelled」

影響を受けるコードパターン:

// Custom Bearer auth without OAuthProvider wrapper
export default {
  fetch: async (req, env, ctx) => {
    const authHeader = req.headers.get("Authorization");
    if (!authHeader?.startsWith("Bearer ")) {
      return new Response("Unauthorized", { status: 401 });
    }

    if (url.pathname === "/sse") {
      return MyMCP.serveSSE("/sse")(req, env, ctx);  // ← Timeout here
    }
    return new Response("Not found", { status: 404 });
  }
};

根本原因の仮説:

  • カスタム Bearer 認証であっても OAuthProvider ラッパーが必要な可能性があります
  • Durable Object IoContext のタイムアウト設定が不足している可能性があります
  • 標準の McpServer ではなく CloudflareMCPServer が必要な可能性があります

回避策:カスタム Bearer 認証ではなく、OAuthProvider パターンを使用した公式テンプレートを利用してください。

// Use OAuthProvider wrapper (recommended)
import { OAuthProvider } from "@cloudflare/workers-oauth-provider";

export default new OAuthProvider({
  authorizeEndpoint: "/authorize",
  tokenEndpoint: "/token",
  // ... OAuth config
  apiHandlers: { "/sse": MyMCP.serveSSE("/sse") }
});

ステータス:調査中(2026-01-21 時点で Issue は未解決)


24. OAuth リモート接続の失敗

エラー:OAuth を使用すると、リモート MCP サーバーへの接続に失敗します(ローカルでは動作しますが、デプロイ後に失敗します)

出典:GitHub Issue #444

原因:Cloudflare Agents リポジトリから Workers に MCP クライアントをデプロイすると、OAuth で保護された MCP サーバーへの接続に失敗します。

症状:

  • ローカル開発では問題なく動作します
  • Workers へのデプロイ後に失敗します
  • OAuth ハンドシェイクが完了しません
  • クライアントが接続を確立できません

トラブルシューティングの手順:

  1. リモート接続の試行中に OAuth トークンが正しく処理されていることを確認します

    // Check token is being passed to remote server
    console.log("Connecting with token:", token ? "present" : "missing");
    
  2. OAuth プロバイダーへのアクセスに必要なネットワーク権限を確認します

    // Ensure Worker can reach OAuth endpoints
    const response = await fetch("https://oauth-provider.com/token");
    
  3. OAuth プロバイダーの CORS 設定を確認します

    // OAuth provider must allow Worker origin
    headers: {
      "Access-Control-Allow-Origin": "https://your-worker.workers.dev",
      "Access-Control-Allow-Methods": "POST, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type, Authorization"
    }
    
  4. リダイレクト URI がデプロイ済みの URL と一致していることを確認します

    {
      "url": "https://mcp.workers.dev/sse",
      "auth": {
        "authorizationUrl": "https://mcp.workers.dev/authorize",  // Must match deployed domain
        "tokenUrl": "https://mcp.workers.dev/token"
      }
    }
    

デプロイチェックリスト:

  • すべての OAuth URL でデプロイ済みのドメインを使用している(localhost ではない)
  • OAuth プロバイダーで CORS ヘッダーが設定されている
  • Worker で OAuth プロバイダーへのネットワークリクエストが許可されている
  • リダイレクト URI が OAuth プロバイダーに登録されている
  • 本番環境で環境変数が設定されている(wrangler secret)

関連:Issue #640(どちらもリモートデプロイにおける OAuth/認証に関するものです)


テストとデプロイ

# Local dev
npm run dev                    # Miniflare (fast)
npx wrangler dev --remote      # Remote DOs (accurate)

# Test with MCP Inspector
npx @modelcontextprotocol/inspector@latest
# Open http://localhost:5173, enter http://localhost:8788/sse

# Deploy
npx wrangler login  # First time only
npx wrangler deploy
# ⚠️ CRITICAL: Update client config with deployed URL!

# Monitor logs
npx wrangler tail

公式ドキュメント


パッケージバージョン:@modelcontextprotocol/[email protected]、@cloudflare/[email protected]、[email protected] 最終確認日:2026-01-21 防止したエラー:記録済みの問題 24 件(防止率 100%) スキルバージョン:3.1.0 | 変更点:IoContext のタイムアウト(#23)、OAuth リモート接続の失敗(#24)、セキュリティセクション(PKCE の脆弱性)、アーキテクチャの明確化(内部 WebSocket)、ツールの戻り値形式のパターン、条件付きツール登録を追加

コメント