エージェントを外部の 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 サーバーへ接続するには 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",
});デフォルトでは、各接続に生成された 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": "...",
},
},
});SSRF(Server-Side Request Forgery)を防ぐため、MCP サーバー URL は接続前に検証されます。次の URL 先はブロックされます。
- プライベート / 内部 IP 範囲(RFC 1918:
10.x、172.16-31.x、192.168.x) - 未指定アドレス(
0.0.0.0、[::]) - リンクローカルアドレス(
169.254.x、fe80::) - IPv6 unique-local アドレス(
fc00::/7) - プライベート範囲に解決する IPv4 マップ済み IPv6 アドレス(例:
[::ffff:10.0.0.1]) - クラウドメタデータエンドポイント(
metadata.google.internal)
ループバックアドレス(localhost、127.x.x.x、[::1])はローカル開発向けに許可されます。
本番で内部サービスへ接続する場合は、HTTP ではなく Durable Object バインディング付きの RPC トランスポート を使います。
addMcpServer() は接続状態を返します。
ready— サーバー接続済みで、ツールを発見済みauthenticating— サーバーが OAuth を要求。ユーザーをauthUrlへリダイレクトします
多くの 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)
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 });
}
}コールバック URL は自動で組み立てられます。
https://{host}/{agentsPrefix}/{agent-name}/{instance-name}/callback例: https://my-worker.workers.dev/agents/my-agent/default/callback
OAuth トークンは SQLite に安全に保存され、エージェント再起動をまたいで残ります。
機密のインスタンス名(セッション 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 完了時の扱いを設定します。デフォルトでは、認証成功はアプリケーション 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" },
});
},
});
}
}接続後、サーバーの機能にアクセスします。
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 もツールスキーマは変換しません。
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}`);
}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 モードは、クライアント内で構造化された非機密データを集めます。リクエストには、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 は専用の elicitation パスに留め、モデルに見えるメッセージやツール結果テキストから外します。
URL ハンドラーは次を行います。
- どの MCP サーバーがリクエストを送ったかを示す。
- リクエストメッセージ、対象ホスト、完全な URL を示す。
- URL を開く前に同意を求める。
- Agent とモデルが検査できないブラウザコンテキストでページを開く。
- 同意後、
contentなしでaction: "accept"を返す。 - 拒否とキャンセルを別コントロールとして用意する。
URL やそのメタデータをプリフェッチしないでください。URL は信頼できない入力として扱います。本番サーバーは HTTPS URL を送る必要があります。
URL モードでは、accept はユーザーが URL を開くことに同意したことを意味します。外部やり取りが終わったことではありません。サーバーはあとで、リクエストの elicitationId 付きで notifications/elicitation/complete を送ることがあります。
両モードは 3 つのアクションをサポートします。
| アクション | 意味 |
|---|---|
accept |
ユーザーがフォームを送信したか、URL を開くことに同意しました。 |
decline |
ユーザーがリクエストを明示的に拒否しました。 |
cancel |
ユーザーが明示的な選択をせずにリクエストを閉じました。 |
content を含めるのは、受け入れた form 応答だけです。URL、decline、cancel 応答では省略します。
ハンドラーは 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>
);
}MCP サーバーへの接続を追加し、そのツールをエージェントで使えるようにします。
addMcpServer の呼び出しは、サーバー名と URL の両方が既存のアクティブ接続と一致するときべき等です。既存接続が返され、重複は作られません。再起動時の重複接続を気にせず onStart() で呼べます。
同じ名前で異なる URL を渡して addMcpServer を呼ぶと、新しい接続が作られます。両方の接続がアクティブのまま、ツールは getAITools() でマージされます。サーバーを置き換えるには、先に removeMcpServer(oldId) を呼びます。
比較前に URL は正規化されます(末尾スラッシュ、デフォルトポート、ホスト名の大文字小文字)。そのため https://MCP.Example.com と https://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" }>serverName(string, 必須) — MCP サーバーの表示名url(string, 必須) — MCP サーバーエンドポイントの URLoptions(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 パス。インスタンス名の漏洩を防ぐため、sendIdentityOnConnectがfalseのときは必須です。設定すると、コールバック 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の詳細は 再試行 を参照してください。
serverName(string, 必須) — MCP サーバーの表示名binding(DurableObjectNamespace, 必須) —McpAgentクラスの Durable Object バインディングoptions(object, 任意) — 接続設定:id— 任意の安定した、呼び出し元指定のサーバー ID。安定したサーバー ID を参照してくださいprops—McpAgentのonStart(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") — サーバーは完全に接続され、稼働中です
MCP サーバーから切断し、リソースをクリーンアップします。
async removeMcpServer(id: string): Promise<void>id(string, 必須) —addMcpServer()が返したサーバー接続 ID
全 MCP サーバー接続の現在の状態を取得します。
getMcpServers(): MCPServersStatetype 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 に直接表示しても安全です。
認証が必要な MCP サーバー向けに、OAuth コールバックの動作を設定します。ユーザーが OAuth 認可を完了したあとの振る舞いをカスタマイズできます。
this.mcp.configureOAuthCallback(options: {
successRedirect?: string;
errorRedirect?: string;
customHandler?: () => Response | Promise<Response>;
}): voidoptions(object, 必須) — OAuth コールバック設定:successRedirect(string, 任意) — 認証成功後のリダイレクト先 URLerrorRedirect(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" },
});
},
});
}
}ステートレス 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>;
}): voidhandlers(object, 任意) — モードをキーにした elicitation ハンドラー:form(function, 任意) — 構造化された非機密入力向けの form モードリクエストを扱います。url(function, 任意) — 帯域外やり取り向けの URL モードリクエストを扱います。
request(ElicitRequest) — MCP elicitation リクエスト。モード固有フィールドはrequest.params.modeを確認します。serverId(string) — リクエストを送った MCP サーバー接続の ID。signal(AbortSignal, 任意) — 元の MCP 操作がキャンセルされると中断します。
各ハンドラーは ElicitResult を含む Promise を返します。accept、decline、cancel を返します。受け入れた 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 を参照してください。
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,
);
}
}細かい制御には、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();すぐ接続せずにサーバーを登録します。
async registerServer(
id: string,
options: {
url: string;
name: string;
callbackUrl: string;
clientOptions?: ClientOptions;
transportOptions?: TransportOptions;
}
): Promise<string>以前登録したサーバーへ接続を確立します。
async connectToServer(id: string): Promise<MCPConnectionResult>
type MCPConnectionResult =
| { state: "failed"; error: string }
| { state: "authenticating"; authUrl: string }
| { state: "connected" }接続がアクティブなら、サーバー能力を確認します。
async discoverIfConnected(
serverId: string,
options?: { timeoutMs?: number }
): Promise<MCPDiscoverResult | undefined>
type MCPDiscoverResult = {
success: boolean;
state: MCPConnectionState;
error?: string;
}進行中の MCP 接続と発見操作がすべて落ち着くのを待ちます。Agent がハイバネーションから起きた直後に、this.mcp.getAITools() がツール一式をすぐ返す必要があるときに便利です。
// Wait indefinitely
await this.mcp.waitForConnections();
// Wait with a timeout (milliseconds)
await this.mcp.waitForConnections({ timeout: 10_000 });登録は残したまま、特定サーバーへの接続を閉じます。
async closeConnection(id: string): Promise<void>登録は残したまま、すべてのアクティブなサーバー接続を閉じます。
async closeAllConnections(): Promise<void>スキーマを Zod に変換せず、生の MCP ツールレコードを取得します。
listTools(filter?: MCPServerFilter): Array<Tool & { serverId: string }>カタログの発見と確認に使います。返すツールを特定の接続に限定するには MCPServerFilter を渡します。
発見済みの全 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;
}
}
}