cloudflare-mcp-server について
TypeScriptでCloudflare Workers上にリモートMCPサーバーを構築・デプロイするための開発者向けスキルです。トランスポートのパス、認証、デプロイ、接続エラーの調査を扱います。
主な機能
- 01WorkersでのリモートMCPサーバー構築
- 02SSEとStreamable HTTPの設定
- 03認証テンプレートとOAuth設定
- 04パスとデプロイのトラブル対応
活用シーン
- 01テンプレートからリモートMCPサーバーを作成
- 02デプロイ後の404応答を調査
- 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.jsoncommands/init.mdreferences/authentication.mdcommon-issues.mddebugging-guide.mdhttp-transport-fundamentals.mdoauth-providers.mdofficial-examples.mdtransport-comparison.mdtransport.mdrules/cloudflare-mcp-server.mdtemplates/basic-mcp-server.tsclaude_desktop_config.jsonmcp-bearer-auth.tsmcp-http-fundamentals.tsmcp-oauth-proxy.tsmcp-stateful-do.tsmcp-with-d1.tsmcp-with-workers-ai.tspackage.jsonwrangler-basic.jsoncwrangler-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 OAuthremote-mcp-google-oauth- Google OAuthremote-mcp-auth0/remote-mcp-authkit- エンタープライズ SSOmcp-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 種類です:
-
SSE(Server-Sent Events) - レガシー方式、幅広い互換性
MyMCP.serveSSE("/sse").fetch(request, env, ctx) -
ストリーマブル 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!
認証パターン
ユースケースに応じて認証方式を選択してください。
-
認証なし - 内部ツール、開発(テンプレート:
remote-mcp-authless) -
ベアラートークン - カスタム認証(テンプレート:
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 }); } -
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)は同じドメインを使用する必要があります。
-
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 は、クライアント向け通信と内部通信で異なるトランスポートを使用します。
トランスポートアーキテクチャ
Client --- (SSE or HTTP) --> Worker --- (WebSocket) --> Durable Object
クライアント → Worker(外部):
- SSE トランスポート:
/sseエンドポイント - HTTP Streamable:
/mcpエンドポイント - クライアントがトランスポートを選択
Worker → Durable Object(内部):
- 常に WebSocket
- PartyServer(McpAgent の内部依存関係)で必須
- 自動的にアップグレードされ、クライアントからは見えません
これが意味すること
- SSE クライアントは完全にサポートされています - 外部インターフェースに SSE を使用できます
- DO には WebSocket が必須です - 内部の Worker と DO の通信には常に WebSocket が使用されます
- これは制限ではありません - 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
デバッグ手順:
- サーバーが使用するパスを確認します:
serveSSE("/sse")とserveSSE("/")のどちらか - curl でテストします:
curl https://worker.dev/sse - 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
原因:カスタム 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 サーバーへの接続に失敗します(ローカルでは動作しますが、デプロイ後に失敗します)
原因:Cloudflare Agents リポジトリから Workers に MCP クライアントをデプロイすると、OAuth で保護された MCP サーバーへの接続に失敗します。
症状:
- ローカル開発では問題なく動作します
- Workers へのデプロイ後に失敗します
- OAuth ハンドシェイクが完了しません
- クライアントが接続を確立できません
トラブルシューティングの手順:
-
リモート接続の試行中に OAuth トークンが正しく処理されていることを確認します
// Check token is being passed to remote server console.log("Connecting with token:", token ? "present" : "missing"); -
OAuth プロバイダーへのアクセスに必要なネットワーク権限を確認します
// Ensure Worker can reach OAuth endpoints const response = await fetch("https://oauth-provider.com/token"); -
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" } -
リダイレクト 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
公式ドキュメント
- Cloudflare Agents:https://developers.cloudflare.com/agents/
- MCP Specification:https://modelcontextprotocol.io/
- 公式テンプレート:https://github.com/cloudflare/ai/tree/main/demos
- 本番環境向けサーバー:https://github.com/cloudflare/mcp-server-cloudflare
- workers-oauth-provider:https://github.com/cloudflare/workers-oauth-provider
パッケージバージョン:@modelcontextprotocol/[email protected]、@cloudflare/[email protected]、[email protected] 最終確認日:2026-01-21 防止したエラー:記録済みの問題 24 件(防止率 100%) スキルバージョン:3.1.0 | 変更点:IoContext のタイムアウト(#23)、OAuth リモート接続の失敗(#24)、セキュリティセクション(PKCE の脆弱性)、アーキテクチャの明確化(内部 WebSocket)、ツールの戻り値形式のパターン、条件付きツール登録を追加