Skip to content

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

McpClient

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

エージェントを外部の Model Context Protocol (MCP) サーバーに接続し、そのツール、リソース、プロンプトを使います。Agents SDK v0.20.0 は @modelcontextprotocol/client を使い、ステートレスまたはレガシー動作を自動交渉します。

パッケージ、型、OAuth プロバイダー、ロールアウトの変更は MCP SDK v2 への移行 を参照してください。

概要

MCP クライアント機能で、エージェントは次ができます。

  • 外部 MCP サーバーへ接続する — GitHub、Slack、データベース、AI サービス
  • ツールを使う — MCP サーバーが公開する関数を呼び出す
  • リソースへアクセスする — MCP サーバーからデータを読む
  • プロンプトを使う — 事前構築されたプロンプトテンプレートを活用する

クイックスタート

import { Agent } from "agents";

export class MyAgent extends Agent {
	async onRequest(request) {
		// Add an MCP server
		const result = await this.addMcpServer(
			"github",
			"https://mcp.github.com/mcp",
		);

		if (result.state === "authenticating") {
			// Server requires OAuth - redirect user to authorize
			return Response.redirect(result.authUrl);
		}

		// Server is ready - tools are now available
		const state = this.getMcpServers();
		console.log(`Connected! ${state.tools.length} tools available`);

		return new Response("MCP server connected");
	}
}
import { Agent } from "agents";

export class MyAgent extends Agent {
	async onRequest(request: Request) {
		// Add an MCP server
		const result = await this.addMcpServer(
			"github",
			"https://mcp.github.com/mcp",
		);

		if (result.state === "authenticating") {
			// Server requires OAuth - redirect user to authorize
			return Response.redirect(result.authUrl);
		}

		// Server is ready - tools are now available
		const state = this.getMcpServers();
		console.log(`Connected! ${state.tools.length} tools available`);

		return new Response("MCP server connected");
	}
}

接続はエージェントの SQL ストレージ に残り、エージェントが MCP サーバーに接続すると、そのサーバーの全ツールが自動で使えるようになります。

MCP サーバーの追加

MCP サーバーへ接続するには addMcpServer() を使います。非 OAuth サーバーではオプションは不要です。

// Non-OAuth server — no options required
await this.addMcpServer("notion", "https://mcp.notion.so/mcp");

// OAuth server — callbackHost is auto-derived from the incoming request,
// but you can set it explicitly if needed (e.g. custom domains)
await this.addMcpServer("github", "https://mcp.github.com/mcp", {
	callbackHost: "https://my-worker.workers.dev",
});
// Non-OAuth server — no options required
await this.addMcpServer("notion", "https://mcp.notion.so/mcp");

// OAuth server — callbackHost is auto-derived from the incoming request,
// but you can set it explicitly if needed (e.g. custom domains)
await this.addMcpServer("github", "https://mcp.github.com/mcp", {
	callbackHost: "https://my-worker.workers.dev",
});

安定したサーバー ID

デフォルトでは、各接続に生成された nanoid(8) ID が割り当てられます。コネクタ型の統合では id を渡し、ツールを不透明な接続 ID ではなく読みやすいキーとして表面化します。

await this.addMcpServer("GitHub", env.MCP_SESSION, {
	id: "github",
	props: { token: "..." },
});
// tools surface as `tool_github_<name>`
await this.addMcpServer("GitHub", env.MCP_SESSION, {
	id: "github",
	props: { token: "..." },
});
// tools surface as `tool_github_<name>`

指定した場合、この id は生成値の代わりに、ストレージ、復元、listServers()listTools()getAITools()、OAuth 状態におけるサーバー ID になります。渡した ID はエクスポート済みの normalizeServerId ヘルパーで正規化されるので、"GitHub MCP!" のような値は "github-mcp" になります。AI SDK のツール名とストレージキーに埋め込んでも安全な ID になります。

安定 ID は完全に追加的で、既存コードは壊れません。自動生成 ID で登録済みのサーバーに対して addMcpServer{ id: "github" } を足すと、SDK は既存のストレージ行、メモリ上の接続、OAuth 関連のストレージキーを新しい安定 ID へ透過的に移行します。removeMcpServer は不要です。addMcpServer が例外を投げるのは、本当に曖昧な衝突があるときだけです。同じ安定 ID がすでに別の (name, url) サーバーに属している場合です。

トランスポートオプション

MCP は複数のトランスポート種別をサポートします。

await this.addMcpServer("server", "https://mcp.example.com/mcp", {
	transport: {
		type: "streamable-http",
	},
});
await this.addMcpServer("server", "https://mcp.example.com/mcp", {
	transport: {
		type: "streamable-http",
	},
});
トランスポート 説明
auto サーバー応答に基づく自動検出(デフォルト)
streamable-http ストリーミング付き HTTP
sse Server-Sent Events — レガシー / 互換トランスポート

カスタムヘッダー

認証の後ろにあるサーバー(Cloudflare Access など)や Bearer トークンを使うサーバー向けです。

await this.addMcpServer("internal", "https://internal-mcp.example.com/mcp", {
	transport: {
		headers: {
			Authorization: "Bearer my-token",
			"CF-Access-Client-Id": "...",
			"CF-Access-Client-Secret": "...",
		},
	},
});
await this.addMcpServer("internal", "https://internal-mcp.example.com/mcp", {
	transport: {
		headers: {
			Authorization: "Bearer my-token",
			"CF-Access-Client-Id": "...",
			"CF-Access-Client-Secret": "...",
		},
	},
});

URL のセキュリティ

SSRF(Server-Side Request Forgery)を防ぐため、MCP サーバー URL は接続前に検証されます。次の URL 先はブロックされます。

  • プライベート / 内部 IP 範囲(RFC 1918: 10.x172.16-31.x192.168.x
  • 未指定アドレス(0.0.0.0[::]
  • リンクローカルアドレス(169.254.xfe80::
  • IPv6 unique-local アドレス(fc00::/7
  • プライベート範囲に解決する IPv4 マップ済み IPv6 アドレス(例: [::ffff:10.0.0.1]
  • クラウドメタデータエンドポイント(metadata.google.internal

ループバックアドレス(localhost127.x.x.x[::1])はローカル開発向けに許可されます。

本番で内部サービスへ接続する場合は、HTTP ではなく Durable Object バインディング付きの RPC トランスポート を使います。

戻り値

addMcpServer() は接続状態を返します。

  • ready — サーバー接続済みで、ツールを発見済み
  • authenticating — サーバーが OAuth を要求。ユーザーを authUrl へリダイレクトします

OAuth 認証

多くの MCP サーバーは OAuth 認証を要求します。エージェントは OAuth フローを自動で扱います。

仕組み

sequenceDiagram
    participant Client
    participant Agent
    participant MCPServer

    Client->>Agent: addMcpServer(name, url)
    Agent->>MCPServer: Connect
    MCPServer-->>Agent: Requires OAuth
    Agent-->>Client: state: authenticating, authUrl
    Client->>MCPServer: User authorizes
    MCPServer->>Agent: Callback with code
    Agent->>MCPServer: Exchange for token
    Agent-->>Client: onMcpUpdate (ready)

エージェント内での OAuth の扱い

class MyAgent extends Agent {
	async onRequest(request) {
		const result = await this.addMcpServer(
			"github",
			"https://mcp.github.com/mcp",
		);

		if (result.state === "authenticating") {
			// Redirect the user to the OAuth authorization page
			return Response.redirect(result.authUrl);
		}

		return Response.json({ status: "connected", id: result.id });
	}
}
class MyAgent extends Agent {
	async onRequest(request: Request) {
		const result = await this.addMcpServer(
			"github",
			"https://mcp.github.com/mcp",
		);

		if (result.state === "authenticating") {
			// Redirect the user to the OAuth authorization page
			return Response.redirect(result.authUrl);
		}

		return Response.json({ status: "connected", id: result.id });
	}
}

OAuth コールバック

コールバック URL は自動で組み立てられます。

https://{host}/{agentsPrefix}/{agent-name}/{instance-name}/callback

例: https://my-worker.workers.dev/agents/my-agent/default/callback

OAuth トークンは SQLite に安全に保存され、エージェント再起動をまたいで残ります。

OAuth コールバックでのインスタンス名の保護

機密のインスタンス名(セッション ID やユーザー ID など)を隠すために sendIdentityOnConnect: false を使うと、デフォルトの OAuth コールバック URL がインスタンス名を露出します。このセキュリティ問題を防ぐには、カスタム callbackPath を渡す必要があります。

import { Agent, routeAgentRequest, getAgentByName } from "agents";

export class SecureAgent extends Agent {
	static options = { sendIdentityOnConnect: false };

	async onRequest(request) {
		// callbackPath is required when sendIdentityOnConnect is false
		const result = await this.addMcpServer(
			"github",
			"https://mcp.github.com/mcp",
			{
				callbackPath: "mcp-oauth-callback", // Custom path without instance name
			},
		);

		if (result.state === "authenticating") {
			return Response.redirect(result.authUrl);
		}

		return new Response("Connected!");
	}
}

// Route the custom callback path to the agent
export default {
	async fetch(request, env) {
		const url = new URL(request.url);

		// Route custom MCP OAuth callback to agent instance
		if (url.pathname.startsWith("/mcp-oauth-callback")) {
			// Implement this to extract the instance name from your session/auth mechanism
			const instanceName = await getInstanceNameFromSession(request);

			const agent = await getAgentByName(env.SecureAgent, instanceName);
			return agent.fetch(request);
		}

		// Standard agent routing
		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
};
import { Agent, routeAgentRequest, getAgentByName } from "agents";

export class SecureAgent extends Agent {
	static options = { sendIdentityOnConnect: false };

	async onRequest(request: Request) {
		// callbackPath is required when sendIdentityOnConnect is false
		const result = await this.addMcpServer(
			"github",
			"https://mcp.github.com/mcp",
			{
				callbackPath: "mcp-oauth-callback", // Custom path without instance name
			},
		);

		if (result.state === "authenticating") {
			return Response.redirect(result.authUrl);
		}

		return new Response("Connected!");
	}
}

// Route the custom callback path to the agent
export default {
	async fetch(request: Request, env: Env) {
		const url = new URL(request.url);

		// Route custom MCP OAuth callback to agent instance
		if (url.pathname.startsWith("/mcp-oauth-callback")) {
			// Implement this to extract the instance name from your session/auth mechanism
			const instanceName = await getInstanceNameFromSession(request);

			const agent = await getAgentByName(env.SecureAgent, instanceName);
			return agent.fetch(request);
		}

		// Standard agent routing
		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;

カスタム OAuth コールバック処理

OAuth 完了時の扱いを設定します。デフォルトでは、認証成功はアプリケーション origin へリダイレクトし、失敗は HTML エラーページを表示します。

export class MyAgent extends Agent {
	onStart() {
		this.mcp.configureOAuthCallback({
			// Redirect after successful auth
			successRedirect: "https://myapp.com/success",

			// Redirect on error with error message in query string
			errorRedirect: "https://myapp.com/error",

			// Or use a custom handler
			customHandler: () => {
				// Close popup window after auth completes
				return new Response("<script>window.close();</script>", {
					headers: { "content-type": "text/html" },
				});
			},
		});
	}
}
export class MyAgent extends Agent {
	onStart() {
		this.mcp.configureOAuthCallback({
			// Redirect after successful auth
			successRedirect: "https://myapp.com/success",

			// Redirect on error with error message in query string
			errorRedirect: "https://myapp.com/error",

			// Or use a custom handler
			customHandler: () => {
				// Close popup window after auth completes
				return new Response("<script>window.close();</script>", {
					headers: { "content-type": "text/html" },
				});
			},
		});
	}
}

MCP 機能の利用

接続後、サーバーの機能にアクセスします。

利用可能なツールの取得

AI SDK モデル呼び出し向けにツールを準備せず、生の MCP カタログを確認するには listTools() を使います。

const tools = this.mcp.listTools();

for (const tool of tools) {
	console.log(`Tool: ${tool.name}`);
	console.log(`  From server: ${tool.serverId}`);
	console.log(`  Title: ${tool.title ?? tool.annotations?.title ?? tool.name}`);
	console.log(`  Description: ${tool.description}`);
}
const tools = this.mcp.listTools();

for (const tool of tools) {
	console.log(`Tool: ${tool.name}`);
	console.log(`  From server: ${tool.serverId}`);
	console.log(`  Title: ${tool.title ?? tool.annotations?.title ?? tool.name}`);
	console.log(`  Description: ${tool.description}`);
}

getMcpServers().tools は、MCP クライアント状態全体の一部として、同じ生のツールレコードを返します。どちらの API もツールスキーマは変換しません。

AI SDK との統合

MCP ツールを AI SDK と使うには、MCP ツールを AI SDK 形式に変換する this.mcp.getAITools() を使います。

import { generateText } from "ai";
import { createWorkersAI } from "workers-ai-provider";

export class MyAgent extends Agent {
	async onRequest(request) {
		const workersai = createWorkersAI({ binding: this.env.AI });
		const response = await generateText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			prompt: "What's the weather in San Francisco?",
			tools: this.mcp.getAITools(),
		});

		return new Response(response.text);
	}
}
import { generateText } from "ai";
import { createWorkersAI } from "workers-ai-provider";

export class MyAgent extends Agent<Env> {
	async onRequest(request: Request) {
		const workersai = createWorkersAI({ binding: this.env.AI });
		const response = await generateText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			prompt: "What's the weather in San Francisco?",
			tools: this.mcp.getAITools(),
		});

		return new Response(response.text);
	}
}

リソースとプロンプト

const state = this.getMcpServers();

// Available resources
for (const resource of state.resources) {
	console.log(`Resource: ${resource.name} (${resource.uri})`);
}

// Available prompts
for (const prompt of state.prompts) {
	console.log(`Prompt: ${prompt.name}`);
}
const state = this.getMcpServers();

// Available resources
for (const resource of state.resources) {
	console.log(`Resource: ${resource.name} (${resource.uri})`);
}

// Available prompts
for (const prompt of state.prompts) {
	console.log(`Prompt: ${prompt.name}`);
}

Elicitation

MCP サーバーは、別の操作の処理中にユーザー入力を要求できます。ステートレスパスでは、elicitation は input_required を返し、複数往復リクエスト(MRTR)で完了します。レガシーパスでは、サーバーがプッシュした elicitation/create リクエストを送ります。どちらも form と URL モードを使います。

Agent がサポートする各モードのハンドラーを onStart() で登録します。同じハンドラーが両レーンに使われます。

import { Agent } from "agents";

class MyAgent extends Agent {
	onStart() {
		this.mcp.configureElicitationHandlers({
			form: (request, serverId) =>
				this.forwardElicitationToBrowser(request, serverId),
			url: (request, serverId) =>
				this.forwardElicitationToBrowser(request, serverId),
		});
	}

	forwardElicitationToBrowser(request, serverId) {
		// Forward the request to your UI and resolve after the user responds.
		// A complete implementation appears in Forward elicitation to a UI.
		throw new Error(
			`Implement elicitation for ${serverId}: ${request.params.message}`,
		);
	}
}
import { Agent } from "agents";
import type { ElicitRequest, ElicitResult } from "agents/mcp/client";

class MyAgent extends Agent<Env> {
	onStart() {
		this.mcp.configureElicitationHandlers({
			form: (request, serverId) =>
				this.forwardElicitationToBrowser(request, serverId),
			url: (request, serverId) =>
				this.forwardElicitationToBrowser(request, serverId),
		});
	}

	private forwardElicitationToBrowser(
		request: ElicitRequest,
		serverId: string,
	): Promise<ElicitResult> {
		// Forward the request to your UI and resolve after the user responds.
		// A complete implementation appears in Forward elicitation to a UI.
		throw new Error(
			`Implement elicitation for ${serverId}: ${request.params.message}`,
		);
	}
}

serverId はリクエストを送った接続を識別します。どのサーバーが入力を求めているかをユーザーに伝え、サーバー固有のポリシーを適用するために使います。

能力交渉とハイバネーション

SDK は、設定済みハンドラーがあるモードだけを広告します。レガシーパスの接続は initialize 中に広告します。ステートレスパスのリクエストは、リクエスト能力と一緒に運びます。form のみのハンドラーは form モードを広告します。URL のみのハンドラーは URL モードを広告します。ハンドラーのない接続は elicitation 能力を広告せず、サーバーはフォールバックを使えます。

SDK は広告したモードを各サーバー登録と一緒に保存します。そのため Durable Object ハイバネーション後に復元された接続は、再接続時に同じモードを広告できます。コールバック関数はメモリ上に残り、onStart() 実行時に再接続されます。

サーバー追加時に、広告するモードを明示的に狭められます。

await this.addMcpServer("portal", "https://portal.example.com/mcp", {
	client: {
		capabilities: {
			elicitation: { form: {} },
		},
	},
});
await this.addMcpServer("portal", "https://portal.example.com/mcp", {
	client: {
		capabilities: {
			elicitation: { form: {} },
		},
	},
});

明示的な client.capabilities.elicitation 値は、ハンドラー由来のモードより優先され、サーバー登録と一緒に残ります。対応するハンドラーなしでモードを広告しないでください。サーバーがそのモードを送ると、接続はリクエストを扱えずエラーを返します。

Form モード

Form モードは、クライアント内で構造化された非機密データを集めます。リクエストには、requestedSchema 内の制限付き JSON Schema が含まれます。ユーザーがフォームを送信したら、一致する content 付きで action: "accept" を返します。

this.mcp.configureElicitationHandlers({
	form: async (request) => {
		const content = await showFormToUser(request.params.requestedSchema);
		return content ? { action: "accept", content } : { action: "cancel" };
	},
});
this.mcp.configureElicitationHandlers({
	form: async (request) => {
		const content = await showFormToUser(request.params.requestedSchema);
		return content ? { action: "accept", content } : { action: "cancel" };
	},
});

送信前に、ユーザーが値を確認・編集できるようにします。受け入れた内容は requestedSchema に対して検証します。パスワード、API キー、アクセストークン、支払い認証情報、その他の秘密の要求に form モードを使わないでください。

URL モード

URL モードは、外部ページを開くようユーザーに求めます。サードパーティ認可や支払いなど、秘密を集めうる帯域外のやり取りに使います。URL は専用の elicitation パスに留め、モデルに見えるメッセージやツール結果テキストから外します。

URL ハンドラーは次を行います。

  1. どの MCP サーバーがリクエストを送ったかを示す。
  2. リクエストメッセージ、対象ホスト、完全な URL を示す。
  3. URL を開く前に同意を求める。
  4. Agent とモデルが検査できないブラウザコンテキストでページを開く。
  5. 同意後、content なしで action: "accept" を返す。
  6. 拒否とキャンセルを別コントロールとして用意する。

URL やそのメタデータをプリフェッチしないでください。URL は信頼できない入力として扱います。本番サーバーは HTTPS URL を送る必要があります。

URL モードでは、accept はユーザーが URL を開くことに同意したことを意味します。外部やり取りが終わったことではありません。サーバーはあとで、リクエストの elicitationId 付きで notifications/elicitation/complete を送ることがあります。

応答アクション

両モードは 3 つのアクションをサポートします。

アクション 意味
accept ユーザーがフォームを送信したか、URL を開くことに同意しました。
decline ユーザーがリクエストを明示的に拒否しました。
cancel ユーザーが明示的な選択をせずにリクエストを閉じました。

content を含めるのは、受け入れた form 応答だけです。URL、decline、cancel 応答では省略します。

Elicitation を UI へ転送する

ハンドラーは Promise を返しますが、応答は多くの場合ブラウザから来ます。接続中のクライアントへリクエストをブロードキャストし、@callable メソッド経由で Promise を解決します。

import { Agent, callable } from "agents";

class MyAgent extends Agent {
	pendingElicitations = new Map();

	onStart() {
		this.mcp.configureElicitationHandlers({
			form: (request, serverId) => this.forward(request, serverId),
			url: (request, serverId) => this.forward(request, serverId),
		});
	}

	forward(request, serverId) {
		const id = crypto.randomUUID();

		const result = new Promise((resolve) => {
			const timeout = setTimeout(() => {
				if (this.pendingElicitations.delete(id)) {
					resolve({ action: "cancel" });
				}
			}, 55_000);
			this.pendingElicitations.set(id, { resolve, timeout });
		});

		this.broadcast(
			JSON.stringify({
				type: "mcp-elicitation",
				id,
				serverId,
				params: request.params,
			}),
		);

		return result;
	}

	@callable()
	respondToElicitation(id, result) {
		const pending = this.pendingElicitations.get(id);
		if (!pending) return;

		this.pendingElicitations.delete(id);
		clearTimeout(pending.timeout);
		pending.resolve(result);
	}
}
import { Agent, callable } from "agents";
import type { ElicitRequest, ElicitResult } from "agents/mcp/client";

type PendingResolver = {
	resolve: (result: ElicitResult) => void;
	timeout: ReturnType<typeof setTimeout>;
};

class MyAgent extends Agent<Env> {
	private pendingElicitations = new Map<string, PendingResolver>();

	onStart() {
		this.mcp.configureElicitationHandlers({
			form: (request, serverId) => this.forward(request, serverId),
			url: (request, serverId) => this.forward(request, serverId),
		});
	}

	private forward(
		request: ElicitRequest,
		serverId: string,
	): Promise<ElicitResult> {
		const id = crypto.randomUUID();

		const result = new Promise<ElicitResult>((resolve) => {
			const timeout = setTimeout(() => {
				if (this.pendingElicitations.delete(id)) {
					resolve({ action: "cancel" });
				}
			}, 55_000);
			this.pendingElicitations.set(id, { resolve, timeout });
		});

		this.broadcast(
			JSON.stringify({
				type: "mcp-elicitation",
				id,
				serverId,
				params: request.params,
			}),
		);

		return result;
	}

	@callable()
	respondToElicitation(id: string, result: ElicitResult) {
		const pending = this.pendingElicitations.get(id);
		if (!pending) return;

		this.pendingElicitations.delete(id);
		clearTimeout(pending.timeout);
		pending.resolve(result);
	}
}

この例は 55 秒タイムアウトです。MCP SDK リクエストのデフォルトが 60 秒だからです。クライアント呼び出しでより長いリクエストタイムアウトを設定する場合は、こちらが先に終わるよう調整します。

ブラウザ実装は mcp-client の例 を参照してください。mcp-elicitation-mrtr はステートレス elicitation を示します。mcp-elicitation はレガシー elicitation を示します。

サーバー側のパターンは ステートレスハンドラーでの Elicitationレガシーサーバーでの Elicitation を参照してください。

サーバーの管理

MCP サーバー登録は Agent 再起動をまたいで残ります。SDK はサーバー設定を SQLite に保存し、OAuth トークンを安全に保存し、Agent が起きたときに接続を復元します。

全サーバーの一覧

const state = this.getMcpServers();

for (const [id, server] of Object.entries(state.servers)) {
	console.log(`${id}: ${server.name} (${server.server_url})`);
}
const state = this.getMcpServers();

for (const [id, server] of Object.entries(state.servers)) {
	console.log(`${id}: ${server.name} (${server.server_url})`);
}

サーバー状態の取得

個別接続を確認するにはサーバー ID を使います。

const state = this.getMcpServers();
const server = state.servers[serverId];

if (server) {
	console.log(`${server.name}: ${server.state}`);
	// state: "ready" | "authenticating" | "connecting" | "connected" | "discovering" | "failed"
}
const state = this.getMcpServers();
const server = state.servers[serverId];

if (server) {
	console.log(`${server.name}: ${server.state}`);
	// state: "ready" | "authenticating" | "connecting" | "connected" | "discovering" | "failed"
}

サーバーの削除

await this.removeMcpServer(serverId);
await this.removeMcpServer(serverId);

サーバーから切断し、ストレージからも削除します。

クライアント側の統合

接続中のクライアントは、WebSocket 経由でリアルタイムの MCP 更新を受け取ります。

import { useAgent } from "agents/react";
import { useState } from "react";

function Dashboard() {
	const [tools, setTools] = useState([]);
	const [servers, setServers] = useState({});

	const agent = useAgent({
		agent: "MyAgent",
		onMcpUpdate: (mcpState) => {
			setTools(mcpState.tools);
			setServers(mcpState.servers);
		},
	});

	return (
		<div>
			<h2>Connected Servers</h2>
			{Object.entries(servers).map(([id, server]) => (
				<div key={id}>
					{server.name}: {server.state}
				</div>
			))}

			<h2>Available Tools ({tools.length})</h2>
			{tools.map((tool) => (
				<div key={`${tool.serverId}-${tool.name}`}>{tool.name}</div>
			))}
		</div>
	);
}
import { useAgent } from "agents/react";
import { useState } from "react";

function Dashboard() {
	const [tools, setTools] = useState([]);
	const [servers, setServers] = useState({});

	const agent = useAgent({
		agent: "MyAgent",
		onMcpUpdate: (mcpState) => {
			setTools(mcpState.tools);
			setServers(mcpState.servers);
		},
	});

	return (
		<div>
			<h2>Connected Servers</h2>
			{Object.entries(servers).map(([id, server]) => (
				<div key={id}>
					{server.name}: {server.state}
				</div>
			))}

			<h2>Available Tools ({tools.length})</h2>
			{tools.map((tool) => (
				<div key={`${tool.serverId}-${tool.name}`}>{tool.name}</div>
			))}
		</div>
	);
}

API リファレンス

addMcpServer()

MCP サーバーへの接続を追加し、そのツールをエージェントで使えるようにします。

addMcpServer の呼び出しは、サーバー名 URL の両方が既存のアクティブ接続と一致するときべき等です。既存接続が返され、重複は作られません。再起動時の重複接続を気にせず onStart() で呼べます。

同じ名前で異なる URL を渡して addMcpServer を呼ぶと、新しい接続が作られます。両方の接続がアクティブのまま、ツールは getAITools() でマージされます。サーバーを置き換えるには、先に removeMcpServer(oldId) を呼びます。

比較前に URL は正規化されます(末尾スラッシュ、デフォルトポート、ホスト名の大文字小文字)。そのため https://MCP.Example.comhttps://mcp.example.com/ は同じ URL として扱われます。

// HTTP transport (Streamable HTTP, SSE)
async addMcpServer(
  serverName: string,
  url: string,
  options?: {
    id?: string;
    callbackHost?: string;
    callbackPath?: string;
    agentsPrefix?: string;
    client?: McpClientOptions;
    transport?: {
      headers?: HeadersInit;
      type?: "sse" | "streamable-http" | "auto";
    };
    retry?: RetryOptions;
  }
): Promise<
  | { id: string; state: "authenticating"; authUrl: string }
  | { id: string; state: "ready" }
>

// RPC transport (Durable Object binding — no HTTP overhead)
async addMcpServer(
  serverName: string,
  binding: DurableObjectNamespace,
  options?: {
    id?: string;
    props?: Record<string, unknown>;
    client?: McpClientOptions;
    retry?: RetryOptions;
  }
): Promise<{ id: string; state: "ready" }>

パラメータ(HTTP トランスポート)

  • serverName (string, 必須) — MCP サーバーの表示名
  • url (string, 必須) — MCP サーバーエンドポイントの URL
  • options (object, 任意) — 接続設定:
    • id — コネクタ型統合向けの、呼び出し元が渡す任意の安定サーバー ID。指定すると、生成された nanoid(8) をストレージ、listServers()listTools()getAITools()(ツールキーが読みやすくなります。例: tool_github_create_pull_request)、OAuth 状態で置き換えます。安定したサーバー ID を参照してください
    • callbackHost — OAuth コールバック URL のホスト。OAuth 認証サーバーでのみ必要です。省略すると、受信リクエストまたは WebSocket 接続 URI から自動導出されます。Worker のホスト名と異なるカスタムドメインを使う場合以外、通常は設定不要です
    • callbackPath — デフォルトの /agents/{class}/{name}/callback 組み立てを迂回するカスタムコールバック URL パス。インスタンス名の漏洩を防ぐため、sendIdentityOnConnectfalse のときは必須です。設定すると、コールバック URL は {callbackHost}/{callbackPath} になります。このパスは getAgentByName 経由でエージェントインスタンスへルーティングする必要があります
    • agentsPrefix — OAuth コールバックパスの URL プレフィックス。デフォルト: "agents"callbackPath があるときは無視されます
    • client@modelcontextprotocol/client の、Agents がサポートする McpClientOptions サブセット。デフォルトバリデータは Workers で JSON Schema 2020-12 とレガシー draft-07 スキーマをサポートします
    • transport — トランスポート層の設定:
      • headers — 認証用のカスタム HTTP ヘッダー
      • type — トランスポート種別: "auto"(デフォルト)、"streamable-http"、または "sse"
    • retry — 接続と再接続の再試行オプション。ハイバネーション後や OAuth 完了後の接続復元時にも保存して使われます。デフォルト: 3 回、ベース遅延 500ms、最大遅延 5s。RetryOptions の詳細は 再試行 を参照してください。

パラメータ(RPC トランスポート)

  • serverName (string, 必須) — MCP サーバーの表示名
  • binding (DurableObjectNamespace, 必須) — McpAgent クラスの Durable Object バインディング
  • options (object, 任意) — 接続設定:
    • id — 任意の安定した、呼び出し元指定のサーバー ID。安定したサーバー ID を参照してください
    • propsMcpAgentonStart(props) に渡す初期化データ。ユーザーコンテキスト、設定、その他のデータを MCP サーバーインスタンスへ渡すときに使います
    • client — MCP クライアントの設定オプション
    • retry — 接続の再試行オプション

RPC トランスポートは、HTTP オーバーヘッドなしで Durable Object バインディング経由で Agent を McpAgent に直接接続します。RPC トランスポートの設定は MCP Transport を参照してください。

戻り値

接続状態に基づく判別共用体に解決する Promise です。

  • state"authenticating" のとき:

    • id (string) — このサーバー接続の一意な識別子
    • state ("authenticating") — サーバーが OAuth 認可を待っています
    • authUrl (string) — ユーザー認証用の OAuth 認可 URL
  • state"ready" のとき:

    • id (string) — このサーバー接続の一意な識別子
    • state ("ready") — サーバーは完全に接続され、稼働中です

removeMcpServer()

MCP サーバーから切断し、リソースをクリーンアップします。

async removeMcpServer(id: string): Promise<void>

パラメータ

  • id (string, 必須) — addMcpServer() が返したサーバー接続 ID

getMcpServers()

全 MCP サーバー接続の現在の状態を取得します。

getMcpServers(): MCPServersState

戻り値

type MCPServersState = {
	servers: Record<
		string,
		{
			name: string;
			server_url: string;
			auth_url: string | null;
			state:
				| "authenticating"
				| "connecting"
				| "connected"
				| "discovering"
				| "ready"
				| "failed";
			capabilities: ServerCapabilities | null;
			instructions: string | null;
			error: string | null;
		}
	>;
	tools: Array<Tool & { serverId: string }>;
	prompts: Array<Prompt & { serverId: string }>;
	resources: Array<Resource & { serverId: string }>;
	resourceTemplates: Array<ResourceTemplate & { serverId: string }>;
};

state フィールドは接続ライフサイクルを示します。

  • authenticating — OAuth 認可の完了を待っています
  • connecting — トランスポート接続を確立中です
  • connected — トランスポート接続が確立されました
  • discovering — サーバー能力(ツール、リソース、プロンプト)を発見中です
  • ready — 完全に接続され、稼働中です
  • failed — 接続に失敗しました(詳細は error フィールド)

error フィールドは、state"failed" のときにエラーメッセージを持ちます。外部 OAuth プロバイダーからのエラーメッセージは XSS 攻撃を防ぐため自動でエスケープされ、UI に直接表示しても安全です。

configureOAuthCallback()

認証が必要な MCP サーバー向けに、OAuth コールバックの動作を設定します。ユーザーが OAuth 認可を完了したあとの振る舞いをカスタマイズできます。

this.mcp.configureOAuthCallback(options: {
  successRedirect?: string;
  errorRedirect?: string;
  customHandler?: () => Response | Promise<Response>;
}): void

パラメータ

  • options (object, 必須) — OAuth コールバック設定:
    • successRedirect (string, 任意) — 認証成功後のリダイレクト先 URL
    • errorRedirect (string, 任意) — 認証失敗後のリダイレクト先 URL。エラーメッセージは ?error=<message> クエリパラメータとして付きます
    • customHandler (function, 任意) — コールバック応答を完全に制御するカスタムハンドラー。Response を返す必要があります

デフォルト動作

設定がない場合:

  • 成功: アプリケーション origin へリダイレクトします
  • 失敗: エラーメッセージ付きの HTML エラーページを表示します

OAuth が失敗すると、接続状態は "failed" になり、エラーメッセージは UI 表示用に server.error フィールドへ保存されます。

使い方

OAuth フローが始まる前に onStart() で設定します。

export class MyAgent extends Agent {
	onStart() {
		// Option 1: Simple redirects
		this.mcp.configureOAuthCallback({
			successRedirect: "/dashboard",
			errorRedirect: "/auth-error",
		});

		// Option 2: Custom handler (e.g., for popup windows)
		this.mcp.configureOAuthCallback({
			customHandler: () => {
				return new Response("<script>window.close();</script>", {
					headers: { "content-type": "text/html" },
				});
			},
		});
	}
}
export class MyAgent extends Agent {
	onStart() {
		// Option 1: Simple redirects
		this.mcp.configureOAuthCallback({
			successRedirect: "/dashboard",
			errorRedirect: "/auth-error",
		});

		// Option 2: Custom handler (e.g., for popup windows)
		this.mcp.configureOAuthCallback({
			customHandler: () => {
				return new Response("<script>window.close();</script>", {
					headers: { "content-type": "text/html" },
				});
			},
		});
	}
}

configureElicitationHandlers()

ステートレス elicitation とレガシー elicitation/create リクエスト向けのハンドラーを設定します。Agent がサポートする各 elicitation モードにハンドラーを追加します。

this.mcp.configureElicitationHandlers(handlers?: {
  form?: (
    request: ElicitRequest,
    serverId: string,
    signal?: AbortSignal,
  ) => Promise<ElicitResult>;
  url?: (
    request: ElicitRequest,
    serverId: string,
    signal?: AbortSignal,
  ) => Promise<ElicitResult>;
}): void

パラメータ

  • handlers (object, 任意) — モードをキーにした elicitation ハンドラー:
    • form (function, 任意) — 構造化された非機密入力向けの form モードリクエストを扱います。
    • url (function, 任意) — 帯域外やり取り向けの URL モードリクエストを扱います。
  • request (ElicitRequest) — MCP elicitation リクエスト。モード固有フィールドは request.params.mode を確認します。
  • serverId (string) — リクエストを送った MCP サーバー接続の ID。
  • signal (AbortSignal, 任意) — 元の MCP 操作がキャンセルされると中断します。

各ハンドラーは ElicitResult を含む Promise を返します。acceptdeclinecancel を返します。受け入れた form 応答には、requestedSchema に一致する content を含めます。URL 応答では content を省略します。

undefined を渡すと、設定済みハンドラーをすべてクリアします。

能力の動作

クライアントは、レガシー交渉中とステートレスリクエスト時に、設定済みハンドラーがあるモードだけを広告します。ハンドラー変更はライブ接続にすぐ適用されますが、サーバーが更新された広告モードを受け取るのは、それらの接続が再接続したあとです。

SDK はハンドラー由来のモードを各 MCP サーバー登録と一緒に保存します。復元された接続は Durable Object ハイバネーション後にそれらのモードを広告し、コールバックは onStart() 実行時に再接続されます。

使い方

ハンドラーは onStart() で設定します。

import { Agent } from "agents";

export class MyAgent extends Agent {
	onStart() {
		this.mcp.configureElicitationHandlers({
			form: (request, serverId) =>
				this.forwardElicitationToBrowser(request, serverId),
			url: (request, serverId) =>
				this.forwardElicitationToBrowser(request, serverId),
		});
	}

	forwardElicitationToBrowser(request, serverId) {
		// Forward the request to your UI and resolve after the user responds.
		throw new Error(
			`Implement elicitation for ${serverId}: ${request.params.message}`,
		);
	}
}
import { Agent } from "agents";
import type { ElicitRequest, ElicitResult } from "agents/mcp/client";

export class MyAgent extends Agent<Env> {
	onStart() {
		this.mcp.configureElicitationHandlers({
			form: (request, serverId) =>
				this.forwardElicitationToBrowser(request, serverId),
			url: (request, serverId) =>
				this.forwardElicitationToBrowser(request, serverId),
		});
	}

	private forwardElicitationToBrowser(
		request: ElicitRequest,
		serverId: string,
	): Promise<ElicitResult> {
		// Forward the request to your UI and resolve after the user responds.
		throw new Error(
			`Implement elicitation for ${serverId}: ${request.params.message}`,
		);
	}
}

ブラウザ転送パターンとモード固有の要件の全体は Elicitation を参照してください。

カスタム OAuth プロバイダー

Agent クラスで createMcpOAuthProvider() を実装し、MCP サーバー接続時のデフォルト OAuth プロバイダーを上書きします。組み込みの動的クライアント登録を超えて、事前登録済みクライアント認証情報や mTLS などのカスタム認証戦略が使えます。

この上書きは、新規接続(addMcpServer)と Durable Object 再起動後の復元接続の両方で使われます。

import { Agent } from "agents";

export class MyAgent extends Agent {
	createMcpOAuthProvider(callbackUrl) {
		const env = this.env;
		return {
			get redirectUrl() {
				return callbackUrl;
			},
			get clientMetadata() {
				return {
					client_id: env.MCP_CLIENT_ID,
					client_secret: env.MCP_CLIENT_SECRET,
					redirect_uris: [callbackUrl],
				};
			},
			clientInformation() {
				return {
					client_id: env.MCP_CLIENT_ID,
					client_secret: env.MCP_CLIENT_SECRET,
				};
			},
		};
	}
}
import { Agent } from "agents";
import type { AgentMcpOAuthProvider } from "agents";

export class MyAgent extends Agent<Env> {
	createMcpOAuthProvider(callbackUrl: string): AgentMcpOAuthProvider {
		const env = this.env;
		return {
			get redirectUrl() {
				return callbackUrl;
			},
			get clientMetadata() {
				return {
					client_id: env.MCP_CLIENT_ID,
					client_secret: env.MCP_CLIENT_SECRET,
					redirect_uris: [callbackUrl],
				};
			},
			clientInformation() {
				return {
					client_id: env.MCP_CLIENT_ID,
					client_secret: env.MCP_CLIENT_SECRET,
				};
			},
		};
	}
}

このメソッドをオーバーライドしない場合、エージェントは MCP サーバーに対して OAuth 2.0 Dynamic Client Registration を行うデフォルトプロバイダーを使います。

カスタムストレージバックエンド

組み込みの OAuth ロジック(CSRF state、PKCE、nonce 生成、トークン管理)は残し、トークン保存だけ別バックエンドへ向ける場合は、DurableObjectOAuthClientProvider をインポートし、独自のストレージアダプターを渡します。

import { Agent, DurableObjectOAuthClientProvider } from "agents";

export class MyAgent extends Agent {
	createMcpOAuthProvider(callbackUrl) {
		return new DurableObjectOAuthClientProvider(
			myCustomStorage, // any DurableObjectStorage-compatible adapter
			this.name,
			callbackUrl,
		);
	}
}
import { Agent, DurableObjectOAuthClientProvider } from "agents";
import type { AgentMcpOAuthProvider } from "agents";

export class MyAgent extends Agent {
	createMcpOAuthProvider(callbackUrl: string): AgentMcpOAuthProvider {
		return new DurableObjectOAuthClientProvider(
			myCustomStorage, // any DurableObjectStorage-compatible adapter
			this.name,
			callbackUrl,
		);
	}
}

上級: MCPClientManager

細かい制御には、this.mcp を直接使います。

ステップごとの接続

// 1. Register the server (saves to storage and creates in-memory connection)
const id = "my-server";
await this.mcp.registerServer(id, {
	url: "https://mcp.example.com/mcp",
	name: "My Server",
	callbackUrl: "https://my-worker.workers.dev/agents/my-agent/default/callback",
	transport: { type: "auto" },
});

// 2. Connect (initializes transport, handles OAuth if needed)
const connectResult = await this.mcp.connectToServer(id);

if (connectResult.state === "failed") {
	console.error("Connection failed:", connectResult.error);
	return;
}

if (connectResult.state === "authenticating") {
	console.log("OAuth required:", connectResult.authUrl);
	return;
}

// 3. Discover capabilities (transitions from "connected" to "ready")
if (connectResult.state === "connected") {
	const discoverResult = await this.mcp.discoverIfConnected(id);

	if (!discoverResult?.success) {
		console.error("Discovery failed:", discoverResult?.error);
	}
}
// 1. Register the server (saves to storage and creates in-memory connection)
const id = "my-server";
await this.mcp.registerServer(id, {
	url: "https://mcp.example.com/mcp",
	name: "My Server",
	callbackUrl: "https://my-worker.workers.dev/agents/my-agent/default/callback",
	transport: { type: "auto" },
});

// 2. Connect (initializes transport, handles OAuth if needed)
const connectResult = await this.mcp.connectToServer(id);

if (connectResult.state === "failed") {
	console.error("Connection failed:", connectResult.error);
	return;
}

if (connectResult.state === "authenticating") {
	console.log("OAuth required:", connectResult.authUrl);
	return;
}

// 3. Discover capabilities (transitions from "connected" to "ready")
if (connectResult.state === "connected") {
	const discoverResult = await this.mcp.discoverIfConnected(id);

	if (!discoverResult?.success) {
		console.error("Discovery failed:", discoverResult?.error);
	}
}

イベント購読

// Listen for state changes (onServerStateChanged is an Event<void>)
const disposable = this.mcp.onServerStateChanged(() => {
	console.log("MCP server state changed");
	this.broadcastMcpServers(); // Notify connected clients
});

// Clean up the subscription when no longer needed
// disposable.dispose();
// Listen for state changes (onServerStateChanged is an Event<void>)
const disposable = this.mcp.onServerStateChanged(() => {
	console.log("MCP server state changed");
	this.broadcastMcpServers(); // Notify connected clients
});

// Clean up the subscription when no longer needed
// disposable.dispose();

ライフサイクルメソッド

this.mcp.registerServer()

すぐ接続せずにサーバーを登録します。

async registerServer(
  id: string,
  options: {
    url: string;
    name: string;
    callbackUrl: string;
    clientOptions?: ClientOptions;
    transportOptions?: TransportOptions;
  }
): Promise<string>

this.mcp.connectToServer()

以前登録したサーバーへ接続を確立します。

async connectToServer(id: string): Promise<MCPConnectionResult>

type MCPConnectionResult =
  | { state: "failed"; error: string }
  | { state: "authenticating"; authUrl: string }
  | { state: "connected" }

this.mcp.discoverIfConnected()

接続がアクティブなら、サーバー能力を確認します。

async discoverIfConnected(
  serverId: string,
  options?: { timeoutMs?: number }
): Promise<MCPDiscoverResult | undefined>

type MCPDiscoverResult = {
  success: boolean;
  state: MCPConnectionState;
  error?: string;
}

this.mcp.waitForConnections()

進行中の MCP 接続と発見操作がすべて落ち着くのを待ちます。Agent がハイバネーションから起きた直後に、this.mcp.getAITools() がツール一式をすぐ返す必要があるときに便利です。

// Wait indefinitely
await this.mcp.waitForConnections();

// Wait with a timeout (milliseconds)
await this.mcp.waitForConnections({ timeout: 10_000 });

this.mcp.closeConnection()

登録は残したまま、特定サーバーへの接続を閉じます。

async closeConnection(id: string): Promise<void>

this.mcp.closeAllConnections()

登録は残したまま、すべてのアクティブなサーバー接続を閉じます。

async closeAllConnections(): Promise<void>

this.mcp.listTools()

スキーマを Zod に変換せず、生の MCP ツールレコードを取得します。

listTools(filter?: MCPServerFilter): Array<Tool & { serverId: string }>

カタログの発見と確認に使います。返すツールを特定の接続に限定するには MCPServerFilter を渡します。

this.mcp.getAITools()

発見済みの全 MCP ツールを、AI SDK 互換形式で取得します。

getAITools(filter?: MCPServerFilter): ToolSet

複数の MCP サーバーが同名ツールを公開しても衝突しないよう、ツールはサーバー ID で自動的に名前空間分けされます。

getAITools() は、各ライブ接続の現在のカタログ向けに変換済みスキーマを再利用します。発見がカタログを置き換えるか、ライブ接続が変わると、スキーマを再変換します。各呼び出しは新しいツールレコードと execute 関数を返します。生のカタログだけが必要なら this.mcp.listTools() を使います。

返すツールを接続済みサーバーの一部に限定するには MCPServerFilter を渡します。

// Tools from a specific server only
const githubTools = this.mcp.getAITools({ serverId: "github" });

// Tools from multiple servers
const tools = this.mcp.getAITools({ serverId: ["github", "notion"] });

// Tools from servers matching a name
const tools = this.mcp.getAITools({ serverName: "GitHub" });

// Only tools from servers that are ready
const tools = this.mcp.getAITools({ state: "ready" });
// Tools from a specific server only
const githubTools = this.mcp.getAITools({ serverId: "github" });

// Tools from multiple servers
const tools = this.mcp.getAITools({ serverId: ["github", "notion"] });

// Tools from servers matching a name
const tools = this.mcp.getAITools({ serverName: "GitHub" });

// Only tools from servers that are ready
const tools = this.mcp.getAITools({ state: "ready" });

フィルター型は agents/mcp/client から利用できます。

import type { MCPServerFilter } from "agents/mcp/client";

type MCPServerFilter = {
	serverId?: string | string[];
	serverName?: string | string[];
	state?: MCPConnectionState | MCPConnectionState[];
};

指定したフィルター条件はすべて AND されます。同じフィルターパラメータは listTools()listPrompts()listResources()listResourceTemplates() でも受け付けます。

エラー処理

接続エラーの扱いに、エラー検出ユーティリティを使います。

import { isUnauthorized, isTransportNotImplemented } from "agents";

export class MyAgent extends Agent {
	async onRequest(request) {
		try {
			await this.addMcpServer("Server", "https://mcp.example.com/mcp");
		} catch (error) {
			if (isUnauthorized(error)) {
				return new Response("Authentication required", { status: 401 });
			} else if (isTransportNotImplemented(error)) {
				return new Response("Transport not supported", { status: 400 });
			}
			throw error;
		}
	}
}
import { isUnauthorized, isTransportNotImplemented } from "agents";

export class MyAgent extends Agent {
	async onRequest(request: Request) {
		try {
			await this.addMcpServer("Server", "https://mcp.example.com/mcp");
		} catch (error) {
			if (isUnauthorized(error)) {
				return new Response("Authentication required", { status: 401 });
			} else if (isTransportNotImplemented(error)) {
				return new Response("Transport not supported", { status: 400 });
			}
			throw error;
		}
	}
}

次のステップ

Client SDK

onMcpUpdate でブラウザから接続します。

役に立ちましたか?