Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

MCP ハンドラー API

最終更新 Markdown で表示Agent セットアップ

Agents SDK は、次の 2 つのサーバーハンドラー経路を提供します。

API Import パス MCP サーバーパッケージ 動作
createMcpHandler agents/mcp/server @modelcontextprotocol/server 既定はレガシー互換付きのステートレス
createLegacyMcpHandler agents/mcp @modelcontextprotocol/sdk WorkerTransport 経由のレガシーセッション

McpAgent は非推奨で、機能は凍結されています。既存の McpAgent サーバーは、ステートレスハンドラーへ移行してください。セッション付き機能で段階的なロールアウトが必要な場合は、移行ガイド を参照してください。

依存関係をインストールする

ステートレスサーバーの場合:

npm i agents @modelcontextprotocol/server@2.0.0 zod

明示的なレガシーサーバーの場合:

npm i agents @modelcontextprotocol/sdk@1.30.0 zod

インストール済みの Agents リリースが要求する、正確な MCP バージョンを使ってください。

createMcpHandler

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);
	},
};
src/index.tsts
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 サーバーを直接渡したりしないでください。

CreateMcpHandlerOptions

次のオプションが使えます。

オプション デフォルト 説明
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 のトランスポートオプションは、このハンドラーには適用されません。transportstoragesessionIdGeneratoreventStoreenableJsonResponse などのオプションは拒否します。

enableJsonResponse: true の代わりに responseMode: "json" を使ってください。JSON モードは、最終結果より前に発行された通知を落とします。

ファクトリのライフサイクル

ハンドラーはリクエストごとに 1 つの MCP サーバーを作成します。これはドラフトプロトコルモデルに従います。バージョン、識別子、能力は、プロトコルセッションではなく、各リクエストに付きます。

アプリケーションデータは、それでも耐久的にできます。リクエストをまたぐデータは、MCP セッション ID ではなく、Durable Object、D1、KV、R2 の認証済みハンドルの後ろに保存してください。

ステートレスハンドラーでの elicitation

ステートレスハンドラー経由の elicitation は input_required を返し、複数往復リクエスト (MRTR) で完了します。各再試行で SDK は最新の requestState をエコーし、直前の入力ラウンドへの応答を送ります。それより前の inputResponses は蓄積しません。ユーザーが応答しているあいだ、Worker は停止したままになりません。

入力を要求するには inputRequired(...) を使います。そのラウンドで受理したフォーム内容は、acceptedContent(...)context.mcpReq.inputResponses から読みます。後のラウンドで必要な信頼できる中間値は、完全性保護された requestState に封入します。

2 ラウンドのツールフローは ステートレス elicitation の例 を参照してください。ステートフルなプッシュリクエストは レガシーサーバーでの elicitation を参照してください。

Origin 検証と CORS

Workers ラッパーは、存在するすべてのブラウザー Origin を検証します。不正、opaque、非 HTTP の Origin は 403 で拒否します。Origin のない非ブラウザー MCP クライアントは有効なままです。

既定の許可リストには、localhost 系の Origin、エンドポイントの workers.dev ホスト名、corsOptions.origin からの具体的なホスト名が含まれます。ハンドラーは localhost と workers.dev エンドポイントに、対応する Host チェックも適用します。一般的な Workers ルートでは、別の Origin リストなしで、ローカルの DNS リバインディング対策を維持します。

ワイルドカード CORS のカスタムドメインでは、allowedHostnamesallowedOriginHostnames を明示的に設定します。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 経路へルーティングしてください。

戻り値

createMcpHandlerStatelessMcpHandler を返します。呼び出し可能で、リクエストと通知の制御を公開します。

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 を呼んでも、何もしません。

通知には 1 つのハンドラーを保つ

通知のルーティングはハンドラーインスタンスに属します。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

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 エンドポイントが、レガシーセッション、トランスポートストレージ、イベントリプレイ、サーバーからクライアントへのプッシュリクエストをまだ必要とする場合にだけ、一時的な移行ブリッジとして使います。

src/index.jsjs
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);
	},
};
src/index.tsts
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

CreateLegacyMcpHandlerOptionsWorkerTransportOptions を拡張し、次のフィールドを追加します。

オプション デフォルト 説明
route string "/mcp" ハンドラーが扱う正確なパス
authContext McpAuthContext Execution context の props ツールハンドラー向けのアプリケーション props
transport WorkerTransport 新しいトランスポート 永続的または事前設定済みのトランスポート

よく使う WorkerTransportOptions は次です。

オプション 説明
sessionIdGenerator プロトコルセッション ID を作成します
enableJsonResponse 対応する場合、SSE ではなく JSON を返します
storage { get, set } アダプター経由でトランスポート状態を永続化します
eventStore リプレイとストリーム復旧のためにイベントを永続化します
corsOptions CORS 応答とプリフライトヘッダーを追加します
onsessioninitializedonsessionclosed セッションライフサイクルの変化を観測します

永続トランスポートをそのサーバーへすでに接続済みで渡す場合を除き、リクエストごとに新しい 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.tokenauthInfo.extra.props をログに出したり返したりしないでください。

移行

既存サーバーを変更する前に MCP SDK v2 へ移行する を参照してください。移行ガイドは、デュアル era ルーティング、ステートフルサーバー、クライアント変更、ロールアウト確認を扱います。

関連リソース

McpAgent API

移行中の、非推奨で機能凍結されたステートフルサーバー経路のリファレンスです。

役に立ちましたか?