Agents SDK は、次の 2 つのサーバーハンドラー経路を提供します。
| API | Import パス | MCP サーバーパッケージ | 動作 |
|---|---|---|---|
createMcpHandler |
agents/mcp/server |
@modelcontextprotocol/server |
既定はレガシー互換付きのステートレス |
createLegacyMcpHandler |
agents/mcp |
@modelcontextprotocol/sdk |
WorkerTransport 経由のレガシーセッション |
McpAgent は非推奨で、機能は凍結されています。既存の McpAgent サーバーは、ステートレスハンドラーへ移行してください。セッション付き機能で段階的なロールアウトが必要な場合は、移行ガイド を参照してください。
ステートレスサーバーの場合:
npm i agents @modelcontextprotocol/[email protected] zodyarn add agents @modelcontextprotocol/[email protected] zodpnpm add agents @modelcontextprotocol/[email protected] zodbun add agents @modelcontextprotocol/[email protected] zod明示的なレガシーサーバーの場合:
npm i agents @modelcontextprotocol/[email protected] zodyarn add agents @modelcontextprotocol/[email protected] zodpnpm add agents @modelcontextprotocol/[email protected] zodbun add agents @modelcontextprotocol/[email protected] zodインストール済みの Agents リリースが要求する、正確な MCP バージョンを使ってください。
createMcpHandler は、MCP SDK v2 のサーバーファクトリから、呼び出し可能なステートレス MCP リクエストハンドラーを作成します。Worker オブジェクトの fetch() エクスポートから呼び出すか、別のハンドラー内で合成します。
import {
createMcpHandler,
type CreateMcpHandlerOptions,
type StatelessMcpHandler,
} from "agents/mcp/server";
import type { McpServerFactory } from "@modelcontextprotocol/server";
function createMcpHandler(
factory: McpServerFactory,
options?: CreateMcpHandlerOptions,
): StatelessMcpHandler;factoryは、@modelcontextprotocol/serverから新しいMcpServerまたはServerを作成します。同期でも非同期でも構いません。optionsは、Agents Worker のオプションと、対応するアップストリーム SDK v2 ハンドラーオプションを組み合わせます。
ファクトリが受け取るリクエストコンテキストは次です。
interface McpRequestContext {
era: "modern" | "legacy";
authInfo?: AuthInfo;
requestInfo?: Request;
}引数なしのファクトリも有効です。
import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({
name: "hello-server",
version: "1.0.0",
});
server.registerTool(
"hello",
{
description: "Return a greeting",
inputSchema: { name: z.string().optional() },
},
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name ?? "World"}!` }],
}),
);
return server;
}
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
};import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({
name: "hello-server",
version: "1.0.0",
});
server.registerTool(
"hello",
{
description: "Return a greeting",
inputSchema: { name: z.string().optional() },
},
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name ?? "World"}!` }],
}),
);
return server;
}
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
} satisfies ExportedHandler;ファクトリ自体を渡してください。グローバルなサーバーインスタンスを 1 つ作ったり、構築済みの SDK v2 サーバーを直接渡したりしないでください。
次のオプションが使えます。
| オプション | 型 | デフォルト | 説明 |
|---|---|---|---|
route |
string |
"/mcp" |
Worker ラッパーが扱う正確なパス |
corsOptions |
CORSOptions | false |
ワイルドカード CORS | CORS 応答ヘッダー。削除するには false |
allowedHostnames |
string[] |
Localhost または workers.dev ルート |
カスタムドメイン向けの省略可能な Host 制限 |
allowedOriginHostnames |
string[] | "*" |
Localhost、workers.dev、または具体的な CORS Origin |
ブラウザー Origin の制限、または明示的なミドルウェア委譲 |
authContext |
McpAuthContext |
Execution context の props | getMcpAuthContext() が返すアプリケーション props |
legacy |
"stateless" | "reject" |
"stateless" |
レガシー互換、またはステートレス専用の拒否 |
responseMode |
"auto" | "json" | "sse" |
"auto" |
ステートレスリクエストの応答整形 |
onerror |
(error: Error) => void |
なし | 帯域外のエラー報告 |
maxSubscriptions |
number |
1,024 |
同時 listen ストリームの最大数 |
keepAliveMs |
number |
15,000 |
listen ストリームの keepalive 間隔 |
SDK v1 のトランスポートオプションは、このハンドラーには適用されません。transport、storage、sessionIdGenerator、eventStore、enableJsonResponse などのオプションは拒否します。
enableJsonResponse: true の代わりに responseMode: "json" を使ってください。JSON モードは、最終結果より前に発行された通知を落とします。
ハンドラーはリクエストごとに 1 つの MCP サーバーを作成します。これはドラフトプロトコルモデルに従います。バージョン、識別子、能力は、プロトコルセッションではなく、各リクエストに付きます。
アプリケーションデータは、それでも耐久的にできます。リクエストをまたぐデータは、MCP セッション ID ではなく、Durable Object、D1、KV、R2 の認証済みハンドルの後ろに保存してください。
ステートレスハンドラー経由の elicitation は input_required を返し、複数往復リクエスト (MRTR) で完了します。各再試行で SDK は最新の requestState をエコーし、直前の入力ラウンドへの応答を送ります。それより前の inputResponses は蓄積しません。ユーザーが応答しているあいだ、Worker は停止したままになりません。
入力を要求するには inputRequired(...) を使います。そのラウンドで受理したフォーム内容は、acceptedContent(...) で context.mcpReq.inputResponses から読みます。後のラウンドで必要な信頼できる中間値は、完全性保護された requestState に封入します。
2 ラウンドのツールフローは ステートレス elicitation の例 ↗ を参照してください。ステートフルなプッシュリクエストは レガシーサーバーでの elicitation を参照してください。
Workers ラッパーは、存在するすべてのブラウザー Origin を検証します。不正、opaque、非 HTTP の Origin は 403 で拒否します。Origin のない非ブラウザー MCP クライアントは有効なままです。
既定の許可リストには、localhost 系の Origin、エンドポイントの workers.dev ホスト名、corsOptions.origin からの具体的なホスト名が含まれます。ハンドラーは localhost と workers.dev エンドポイントに、対応する Host チェックも適用します。一般的な Workers ルートでは、別の Origin リストなしで、ローカルの DNS リバインディング対策を維持します。
ワイルドカード CORS のカスタムドメインでは、allowedHostnames と allowedOriginHostnames を明示的に設定します。corsOptions.origin が具体的な URL なら、ハンドラーはその Origin ホスト名を自動で導出します。
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer, {
allowedHostnames: ["mcp.example.com"],
corsOptions: {
origin: "https://app.example.com",
},
})(request, env, ctx);
},
};export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer, {
allowedHostnames: ["mcp.example.com"],
corsOptions: {
origin: "https://app.example.com",
},
})(request, env, ctx);
},
} satisfies ExportedHandler;許可リストの値は、スキームとポートのないホスト名です。Origin の照合はスキームとポートを無視します。
allowedOriginHostnames: "*" は、信頼できるミドルウェアがハンドラー呼び出し前に Origin を検証する場合にだけ設定します。この値は、不正および opaque Origin の拒否を含む、ハンドラーの Origin チェックをオフにします。MCP HTTP サーバーはブラウザー Origin を検証する必要があります。
CORS 応答ヘッダーは認証ではありません。MCP エンドポイントは OAuth または別の認証層で保護してください。
ハンドラーは request.url から Host 許可リストを推測しません。デプロイが任意の Host 値を受け入れる場合は、ハンドラー呼び出し前に検証してください。Cloudflare Workers 外のローカルサーバーは、アップストリーム SDK の DNS リバインディングガイダンスに従ってください。
既定の legacy: "stateless" 設定は、通常のレガシーツール、プロンプト、リソースを受け付けます。この経路は SDK v2 の Web 標準トランスポートを使い、WorkerTransport はインポートしません。
この互換経路は、完全なセッショントランスポートを提供しません。
- 各 POST が新しいサーバーとトランスポートを作成します。
- HTTP GET と DELETE は
405を返します。 - MCP セッション ID は永続化しません。
- プッシュされた elicitation、sampling、roots リクエストは即座に失敗します。
- スタンドアロンストリーム、再開可能性、リプレイ、セッション削除は使えません。
- 公開済みの実験的タスクは、この経路ではサポートされません。
ステートレス専用エンドポイントには legacy: "reject" を設定します。移行中、プロトコルセッションをまだ必要とするレガシークライアントは、一時的な createLegacyMcpHandler または McpAgent 経路へルーティングしてください。
createMcpHandler は StatelessMcpHandler を返します。呼び出し可能で、リクエストと通知の制御を公開します。
interface StatelessMcpHandler {
(request: Request, env: unknown, ctx: ExecutionContext): Promise<Response>;
fetch(
request: Request,
options?: McpHandlerRequestOptions,
): Promise<Response>;
notify: {
toolsChanged(): void;
promptsChanged(): void;
resourcesChanged(): void;
resourceUpdated(uri: string): void;
};
}
type McpHandlerRequestOptions = {
authInfo?: AuthInfo;
parsedBody?: unknown;
};Worker オブジェクトの fetch() エクスポートからハンドラーを呼び出します。
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
} satisfies ExportedHandler;呼び出し可能なハンドラーを、Worker の default export として直接エクスポートしないでください。Wrangler は関数の default export を WorkerEntrypoint クラスとして扱います。
別のフレームワークや認証層が、すでにリクエストデータをパースまたは検証している場合は fetch() を使います。
const response = await handler.fetch(request, {
authInfo,
parsedBody,
});authInfo はサーバーファクトリとリクエストハンドラーへ渡されます。ハンドラーはリクエストヘッダーから導出せず、アクセストークンも検証しません。parsedBody は、上流ミドルウェアがすでに消費した JSON ボディの再パースを避けます。
notify メソッドは、一致する開いている subscriptions/listen ストリームへ、型付きの変更イベントを公開します。
| メソッド | MCP 通知 |
|---|---|
notify.toolsChanged() |
notifications/tools/list_changed |
notify.promptsChanged() |
notifications/prompts/list_changed |
notify.resourcesChanged() |
notifications/resources/list_changed |
notify.resourceUpdated(uri) |
notifications/resources/updated |
一致するサブスクリプションが開いていないときに notifier を呼んでも、何もしません。
通知のルーティングはハンドラーインスタンスに属します。Worker の毎回の fetch() 呼び出しで新しいハンドラーを作るのは、通常のツール、プロンプト、リソース、MRTR elicitation には適します。以前のハンドラーインスタンスが所有する subscriptions/listen ストリームには通知できません。
notify または subscriptions/listen を使うときは、モジュールスコープでハンドラーを 1 回作成し、Worker オブジェクトのエクスポートから呼び出します。
const handler = createMcpHandler(createServer);
export default {
fetch(request, env, ctx) {
return handler(request, env, ctx);
},
} satisfies ExportedHandler;通知は isolate ローカルです。ある Worker isolate で公開した通知は、別 isolate で動いているサブスクリプションストリームには届きません。
createLegacyMcpHandler は、WorkerTransport 経由で SDK v1 サーバーを提供します。
import {
createLegacyMcpHandler,
type CreateLegacyMcpHandlerOptions,
type LegacyMcpHandler,
} from "agents/mcp";
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
function createLegacyMcpHandler(
server: McpServer | Server,
options?: CreateLegacyMcpHandlerOptions,
): LegacyMcpHandler;既存の SDK v1 エンドポイントが、レガシーセッション、トランスポートストレージ、イベントリプレイ、サーバーからクライアントへのプッシュリクエストをまだ必要とする場合にだけ、一時的な移行ブリッジとして使います。
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createLegacyMcpHandler } from "agents/mcp";
function createServer() {
return new McpServer({ name: "legacy-server", version: "1.0.0" });
}
export default {
async fetch(request, env, ctx) {
return createLegacyMcpHandler(createServer())(request, env, ctx);
},
};import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createLegacyMcpHandler } from "agents/mcp";
function createServer() {
return new McpServer({ name: "legacy-server", version: "1.0.0" });
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
return createLegacyMcpHandler(createServer())(request, env, ctx);
},
} satisfies ExportedHandler<Env>;SDK v1 サーバーを createMcpHandler に渡しても動きますが、非推奨の警告が出ます。サーバーを SDK v2 ファクトリへ移し、そのファクトリを createMcpHandler へ渡してください。セッション付き動作が即時移行を妨げる場合は、一時的なレガシー経路でのみ createLegacyMcpHandler を使います。
experimental_createMcpHandler も非推奨です。その SDK v1 サーバーを SDK v2 ファクトリへ移してください。セッション付き動作の一時ブリッジとしてだけ createLegacyMcpHandler を使います。
CreateLegacyMcpHandlerOptions は WorkerTransportOptions を拡張し、次のフィールドを追加します。
| オプション | 型 | デフォルト | 説明 |
|---|---|---|---|
route |
string |
"/mcp" |
ハンドラーが扱う正確なパス |
authContext |
McpAuthContext |
Execution context の props | ツールハンドラー向けのアプリケーション props |
transport |
WorkerTransport |
新しいトランスポート | 永続的または事前設定済みのトランスポート |
よく使う WorkerTransportOptions は次です。
| オプション | 説明 |
|---|---|
sessionIdGenerator |
プロトコルセッション ID を作成します |
enableJsonResponse |
対応する場合、SSE ではなく JSON を返します |
storage |
{ get, set } アダプター経由でトランスポート状態を永続化します |
eventStore |
リプレイとストリーム復旧のためにイベントを永続化します |
corsOptions |
CORS 応答とプリフライトヘッダーを追加します |
onsessioninitialized、onsessionclosed |
セッションライフサイクルの変化を観測します |
永続トランスポートをそのサーバーへすでに接続済みで渡す場合を除き、リクエストごとに新しい SDK v1 サーバーを作成してください。1 つのサーバーは、複数のトランスポートへ再接続できません。
互換のある @cloudflare/workers-oauth-provider は、検証済みの標準 AuthInfo を SDK v2 コールバックの context.http.authInfo に供給します。
既存の getMcpAuthContext() ヘルパーは、引き続きアプリケーション props を返します。
interface McpAuthContext {
props: Record<string, unknown>;
}import { getMcpAuthContext } from "agents/mcp/server";
server.registerTool(
"whoami",
{ description: "Return the current identity", inputSchema: {} },
async (_args, context) => {
const auth = getMcpAuthContext();
return {
content: [
{
type: "text",
text: JSON.stringify({
clientId: context.http?.authInfo?.clientId,
scopes: context.http?.authInfo?.scopes,
userId: auth?.props.userId,
}),
},
],
};
},
);import { getMcpAuthContext } from "agents/mcp/server";
server.registerTool(
"whoami",
{ description: "Return the current identity", inputSchema: {} },
async (_args, context) => {
const auth = getMcpAuthContext();
return {
content: [
{
type: "text",
text: JSON.stringify({
clientId: context.http?.authInfo?.clientId,
scopes: context.http?.authInfo?.scopes,
userId: auth?.props.userId,
}),
},
],
};
},
);authInfo.token や authInfo.extra.props をログに出したり返したりしないでください。
既存サーバーを変更する前に MCP SDK v2 へ移行する を参照してください。移行ガイドは、デュアル era ルーティング、ステートフルサーバー、クライアント変更、ロールアウト確認を扱います。