Model Context Protocol(MCP)仕様は、クライアントとサーバー間の通信向けに、次の 2 つの標準 トランスポート機構 ↗ を定義しています。
- stdio — 標準入力と標準出力での通信です。ローカル MCP 接続向けです。
- Streamable HTTP — リモート MCP 接続の標準トランスポートです。2025 年 3 月に 導入 ↗ されました。双方向メッセージングに、単一の HTTP エンドポイントを使います。
Agents SDK で構築した MCP サーバーは、Streamable HTTP トランスポートの処理に createMcpHandler を使います。
createMcpHandler で、Streamable HTTP トランスポートを扱う MCP サーバーを作成します。新規 MCP サーバーでは、この方法を推奨します。
「Cloudflare にデプロイ」ボタンで、リモート MCP サーバーを作成できます。
createMcpHandler で MCP サーバーを作成します。GitHub の完全な例 ↗ を参照してください。
import { createMcpHandler } from "agents/mcp/server";
import { McpServer } from "@modelcontextprotocol/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({
name: "My MCP Server",
version: "1.0.0",
});
server.registerTool(
"hello",
{
description: "Returns a greeting message",
inputSchema: { name: z.string().optional() },
},
async ({ name }) => {
return {
content: [{ text: `Hello, ${name ?? "World"}!`, type: "text" }],
};
},
);
return server;
}
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
};import { createMcpHandler } from "agents/mcp/server";
import { McpServer } from "@modelcontextprotocol/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({
name: "My MCP Server",
version: "1.0.0",
});
server.registerTool(
"hello",
{
description: "Returns a greeting message",
inputSchema: { name: z.string().optional() },
},
async ({ name }) => {
return {
content: [{ text: `Hello, ${name ?? "World"}!`, type: "text" }],
};
},
);
return server;
}
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
} satisfies ExportedHandler;MCP サーバーが Workers OAuth Provider ↗ ライブラリで認証と認可を実装している場合は、apiRoute と apiHandler を指定して createMcpHandler を使います。GitHub の完全な例 ↗ を参照してください。
export default new OAuthProvider({
apiRoute: "/mcp",
apiHandler: createMcpHandler(createServer),
// ... other OAuth configuration
});export default new OAuthProvider({
apiRoute: "/mcp",
apiHandler: createMcpHandler(createServer),
// ... other OAuth configuration
});MCP のステートレス経路には、プロトコルレベルのセッションはありません。アプリケーションは、別のストレージ境界の向こうに、耐久性のある業務データを置けます。
レガシーセッションの移行中は、既存サーバーが、新しいステートレスルートの横に、WorkerTransport または McpAgent ルート付きの一時的な createLegacyMcpHandler を残せます。これらの API は、トランスポート状態、イベントの再送、プッシュ型 elicitation、sampling、roots リクエストをサポートします。McpAgent は非推奨で、機能は凍結されています。
クライアントを移す前にステートレスルートを追加し、既存セッションが排出されるまで両方のレーンを維持します。段階的な移行は MCP SDK v2 へ移行する を参照してください。既存のストリーム動作は McpAgent: ストリームの再開可能性 を参照してください。
RPC トランスポートは、MCP サーバーとエージェントの両方が Cloudflare 上で動く内部アプリケーション向けです。同じ Worker 内でも動かせます。公開インターネットを経由せず、Cloudflare の RPC バインディング 上で JSON-RPC メッセージを直接送ります。
- 高速 — ネットワークオーバーヘッドがなく、Durable Objects 間の直接的な関数呼び出しです
- 単純 — HTTP エンドポイントも接続管理も不要です
- 内部専用 — 同じ Worker 内でエージェントが MCP サーバーを呼び出す用途に適します
RPC トランスポートは認証をサポートしません。OAuth が必要な外部接続には、Streamable HTTP を使います。
公開したいツールを持つ McpAgent を作成します。
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export class MyMCP extends McpAgent {
server = new McpServer({ name: "MyMCP", version: "1.0.0" });
initialState = { counter: 0 };
async init() {
this.server.tool(
"add",
"Add to the counter",
{ amount: z.number() },
async ({ amount }) => {
this.setState({ counter: this.state.counter + amount });
return {
content: [
{
type: "text",
text: `Added ${amount}, total is now ${this.state.counter}`,
},
],
};
},
);
}
}import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
type State = { counter: number };
export class MyMCP extends McpAgent<Env, State> {
server = new McpServer({ name: "MyMCP", version: "1.0.0" });
initialState: State = { counter: 0 };
async init() {
this.server.tool(
"add",
"Add to the counter",
{ amount: z.number() },
async ({ amount }) => {
this.setState({ counter: this.state.counter + amount });
return {
content: [
{
type: "text",
text: `Added ${amount}, total is now ${this.state.counter}`,
},
],
};
},
);
}
}Agent の onStart() で、Durable Object バインディングを渡して addMcpServer() を呼び出します。
import { AIChatAgent } from "@cloudflare/ai-chat";
export class Chat extends AIChatAgent {
async onStart() {
// Pass the DO namespace binding directly
await this.addMcpServer("my-mcp", this.env.MyMCP);
}
async onChatMessage(onFinish) {
const allTools = this.mcp.getAITools();
const result = streamText({
model,
tools: allTools,
// ...
});
return createUIMessageStreamResponse({ stream: result });
}
}import { AIChatAgent } from "@cloudflare/ai-chat";
export class Chat extends AIChatAgent<Env> {
async onStart(): Promise<void> {
// Pass the DO namespace binding directly
await this.addMcpServer("my-mcp", this.env.MyMCP);
}
async onChatMessage(onFinish) {
const allTools = this.mcp.getAITools();
const result = streamText({
model,
tools: allTools,
// ...
});
return createUIMessageStreamResponse({ stream: result });
}
}RPC 接続は、HTTP 接続と同様に、Durable Object のハイバネーション後に自動で復元されます。バインディング名と props はストレージに永続化されるため、追加コードなしで接続を再確立できます。
RPC トランスポートでは、すでにアクティブな接続がある名前で addMcpServer を呼ぶと、重複を作らず既存接続を返します。HTTP トランスポートでは、サーバー名と URL の両方で重複排除します(詳細は MCP Client API を参照)。そのため onStart() から呼んでも安全です。
wrangler.jsonc で、両方の Durable Objects のバインディングを定義します。
{
"durable_objects": {
"bindings": [
{ "name": "Chat", "class_name": "Chat" },
{ "name": "MyMCP", "class_name": "MyMCP" },
],
},
"migrations": [
{
"new_sqlite_classes": ["MyMCP", "Chat"],
"tag": "v1",
},
],
}リクエストを Chat エージェントへルーティングします。
import { routeAgentRequest } from "agents";
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// Optionally expose the MCP server via HTTP as well
if (url.pathname.startsWith("/mcp")) {
return MyMCP.serve("/mcp").fetch(request, env, ctx);
}
const response = await routeAgentRequest(request, env);
if (response) return response;
return new Response("Not found", { status: 404 });
},
};import { routeAgentRequest } from "agents";
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url);
// Optionally expose the MCP server via HTTP as well
if (url.pathname.startsWith("/mcp")) {
return MyMCP.serve("/mcp").fetch(request, env, ctx);
}
const response = await routeAgentRequest(request, env);
if (response) return response;
return new Response("Not found", { status: 404 });
},
} satisfies ExportedHandler<Env>;RPC トランスポートには OAuth フローがないため、ユーザーコンテキストを props として直接渡せます。
await this.addMcpServer("my-mcp", this.env.MyMCP, {
props: { userId: "user-123", role: "admin" },
});await this.addMcpServer("my-mcp", this.env.MyMCP, {
props: { userId: "user-123", role: "admin" },
});McpAgent 側では、次のように props にアクセスできます。
export class MyMCP extends McpAgent {
async init() {
this.server.tool("whoami", "Get current user info", {}, async () => {
const userId = this.props?.userId || "anonymous";
const role = this.props?.role || "guest";
return {
content: [{ type: "text", text: `User ID: ${userId}, Role: ${role}` }],
};
});
}
}export class MyMCP extends McpAgent<
Env,
State,
{ userId?: string; role?: string }
> {
async init() {
this.server.tool("whoami", "Get current user info", {}, async () => {
const userId = this.props?.userId || "anonymous";
const role = this.props?.role || "guest";
return {
content: [{ type: "text", text: `User ID: ${userId}, Role: ${role}` }],
};
});
}
}props は型安全です(TypeScript が McpAgent のジェネリックから Props 型を抽出します)。永続的で(Durable Object ストレージに保存されます)、ツール呼び出しの前にすぐ使えます。
RPC トランスポートは、ツール応答を待つタイムアウトを設定できます。デフォルトでは、サーバーはツールハンドラーの応答を 60 秒 待ちます。McpAgent で getRpcTransportOptions() をオーバーライドすると、変更できます。
export class MyMCP extends McpAgent {
server = new McpServer({ name: "MyMCP", version: "1.0.0" });
getRpcTransportOptions() {
return { timeout: 120000 }; // 2 minutes
}
async init() {
this.server.tool(
"long-running-task",
"A tool that takes a while",
{ input: z.string() },
async ({ input }) => {
await longRunningOperation(input);
return {
content: [{ type: "text", text: "Task completed" }],
};
},
);
}
}export class MyMCP extends McpAgent<Env, State> {
server = new McpServer({ name: "MyMCP", version: "1.0.0" });
protected getRpcTransportOptions() {
return { timeout: 120000 }; // 2 minutes
}
async init() {
this.server.tool(
"long-running-task",
"A tool that takes a while",
{ input: z.string() },
async ({ input }) => {
await longRunningOperation(input);
return {
content: [{ type: "text", text: "Task completed" }],
};
},
);
}
}| トランスポート | 使う場面 | 利点 | 欠点 |
|---|---|---|---|
| Streamable HTTP | 外部 MCP サーバー、本番アプリ | 標準プロトコル、セキュア、認証をサポート | わずかなネットワークオーバーヘッド |
| RPC | Cloudflare 上の内部エージェント | 最速で、セットアップが最も単純 | 認証なし、Durable Object バインディングのみ |
| SSE | 古いクライアントとの互換性 | 後方互換 | 非推奨。Streamable HTTP を使います |
エンドポイントがレガシーのステートフル機能を使っていない場合は、@modelcontextprotocol/server のステートレスサーバーファクトリへ直接移行し、createMcpHandler に渡します。
MCP セッション状態、RPC、サーバーからクライアントへのプッシュリクエスト、スタンドアロンストリーム、再送に依存している場合は、まずステートレスな同等機能を設計します。たとえば、業務状態を明示的なアプリケーションストレージへ移し、プッシュ型の入力リクエストをステートレス elicitation に置き換えます。クライアントが移行し、既存セッションが排出されるまで、ステートレスレーンとレガシーレーンを並行提供します。
機能対応、デュアル時代のルーティング、ロールアウト手順は MCP SDK v2 へ移行する を参照してください。
リモート接続をサポートする MCP クライアントで MCP サーバーをテストできます。ローカル接続だけをサポートする MCP クライアントを、リモート MCP サーバーで動かすアダプター mcp-remote ↗ も使えます。
Claude Desktop、Cursor、Windsurf、その他の MCP クライアントからリモート MCP サーバーへ接続する手順は、このガイド に従ってください。