跳转到内容
搜索文档

McpClient

最后更新 查看 MarkdownAgent 设置

将 Agent 连接到外部 Model Context Protocol (MCP) server,以使用其 tool、resource 与 prompt。这使 Agent 能通过标准化协议与 GitHub、Slack、数据库及其他服务交互。

概览

MCP 客户端能力让你的 Agent 可以:

  • 连接外部 MCP server — GitHub、Slack、数据库、AI 服务等
  • 使用其 tool — 调用 MCP server 暴露的函数
  • 访问 resource — 从 MCP server 读取数据
  • 使用 prompt — 利用预构建的 prompt 模板

快速入门

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");
	}
}

连接持久化在 Agent 的 SQL 存储 中;Agent 连接到 MCP server 后,该 server 的所有 tool 会自动可用。

添加 MCP server

使用 addMcpServer() 连接 MCP server。非 OAuth server 无需选项:

// 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",
});

稳定的 server ID

默认情况下,每个连接会分配生成的 nanoid(8) ID。对于 connector 式集成,传入 id 使 tool 以可读 key 而非 opaque 连接 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 会替换 storage、restore、listServers()listTools()getAITools() 与 OAuth state 中的生成值,作为 server ID。提供的 ID 会通过导出的 normalizeServerId helper 规范化,因此 "GitHub MCP!" 等值会变成 "github-mcp" — 保证 ID 可安全嵌入 AI SDK tool 名称与 storage key。

Stable ID 完全向后兼容 — 现有代码不会破坏。若对已在 auto-generated ID 下注册的 server 的 addMcpServer 调用添加 { id: "github" },SDK 会透明地将现有 storage 行、内存连接与 OAuth 相关 storage key 迁移到新 stable ID。无需先调用 removeMcpServeraddMcpServer 仅在相同 stable ID 已属于不同 (name, url) server 时抛出真正歧义的冲突。

Transport 选项

MCP 支持多种 transport 类型:

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",
	},
});
Transport 描述
auto 根据 server 响应自动检测(默认)
streamable-http 带流式传输的 HTTP
sse Server-Sent Events — 旧版/兼容 transport

自定义 header

对于需要身份验证(如 Cloudflare Access)或使用 bearer token 的 server:

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 安全

连接前会验证 MCP server URL,以防止服务端请求伪造(SSRF)。以下 URL 目标会被阻止:

  • 私有/内部 IP 范围(RFC 1918:10.x172.16-31.x192.168.x
  • 未指定地址(0.0.0.0[::]
  • 链路本地地址(169.254.xfe80::
  • IPv6 唯一本地地址(fc00::/7
  • 解析为私有范围的 IPv4 映射 IPv6 地址(例如 [::ffff:10.0.0.1]
  • Cloud metadata 端点(metadata.google.internal

回环地址(localhost127.x.x.x[::1])在本地开发中允许

生产环境中连接内部服务时,请使用带 Durable Object binding 的 RPC transport,而非 HTTP。

返回值

addMcpServer() 返回连接 state:

  • ready — server 已连接且 tool 已发现
  • authenticating — server 需要 OAuth;将用户重定向到 authUrl

OAuth 身份验证

许多 MCP server 需要 OAuth 认证。Agent 会自动处理 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)

在 Agent 中处理 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 token 安全存储在 SQLite 中,并在 Agent 重启后保留。

在 OAuth 回调中保护实例名称

使用 sendIdentityOnConnect: false 隐藏敏感实例名称(如会话 ID 或用户 ID)时,默认 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 能力

连接后,访问 server 的能力:

获取可用 tool

使用 listTools() 检查原始 MCP 目录,而无需为 AI SDK model 调用准备 tool:

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 client state 的一部分返回相同的原始 tool 记录。两个 API 都不会转换 tool schema。

与 AI SDK 集成

要在 AI SDK 中使用 MCP tool,请使用 this.mcp.getAITools(),它将 MCP tool 转换为 AI SDK 格式:

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);
	}
}

Resource 与 prompt

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 允许 server 在处理其他请求(如 tool call)时向用户请求输入。当前稳定 MCP 规范定义 form 与 URL 模式。

onStart() 中为 Agent 支持的每种模式注册 handler:

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";

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 标识发送请求的连接。用它告知用户哪个服务器在请求输入,并应用服务器特定策略。

能力协商与休眠

在 MCP initialize 握手时,连接仅通告已配置处理程序的模式。仅表单处理程序时通告表单模式;仅 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 模式在 client 内收集结构化、非敏感数据。请求在 requestedSchema 中包含受限 JSON Schema。若用户提交表单,返回带匹配 contentaction: "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 验证已接受 content。不要使用 form 模式请求密码、API 密钥、access token、支付凭证或其他机密。

URL 模式

URL 模式要求用户打开外部页面。用于可能收集机密的带外交互,如第三方授权或支付。将 URL 保留在专用 elicitation 路径中,不要放入 model 可见消息或 tool result 文本。

URL handler 应:

  1. 显示哪个 MCP server 发送了请求。
  2. 显示请求 message、目标 host 与完整 URL。
  3. 打开 URL 前请求同意。
  4. 在 Agent 与 model 无法检查的 browser 上下文中打开页面。
  5. 同意后返回不带 contentaction: "accept"
  6. 提供独立的 decline 与 cancel 控件。

不要 prefetch URL 或其 metadata。将 URL 视为不可信输入。生产 server 应发送 HTTPS URL。

URL 模式下,accept 表示用户同意打开 URL,不表示带外交互已完成。server 之后可发送带请求 elicitationIdnotifications/elicitation/complete

响应 action

两种模式均支持三种 action:

Action 含义
accept 用户提交了表单或同意打开 URL。
decline 用户明确拒绝请求。
cancel 用户关闭请求但未明确选择。

仅对已接受的 form 响应包含 content。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";

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 秒 timeout,因为 MCP SDK 请求默认 60 秒。若 client 调用设置了更长 request timeout,请调整此 timeout 使其先完成。

browser 实现请参阅 mcp-client 示例mcp-elicitation 示例 是发送两种模式的 server。

从 MCP server 发送 elicitation 请求请参阅 elicitInput

管理 server

MCP server 注册在 Agent 重启后保留。SDK 在 SQLite 中存储 server 配置、安全存储 OAuth token,并在 Agent 唤醒时恢复连接。

列出所有 server

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})`);
}

获取 server 状态

使用 server 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"
}

移除 server

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

这会断开与 server 的连接并将其从 storage 中移除。

客户端集成

已连接 client 通过 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 server 的连接,使其 tool 对 agent 可用。

当 server 名称 URL 均匹配现有活跃连接时,调用 addMcpServer 是幂等的 — 返回现有连接而不创建重复项。因此在 onStart() 中调用是安全的,重启时无需担心重复连接。

若以相同名称但不同 URL 调用 addMcpServer,会创建新连接。两个连接保持活跃,其 tool 在 getAITools() 中合并。要替换 server,先调用 removeMcpServer(oldId)

比较前 URL 会规范化(尾随斜杠、默认端口与 hostname 大小写),因此 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?: ClientOptions;
    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?: ClientOptions;
    retry?: RetryOptions;
  }
): Promise<{ id: string; state: "ready" }>

参数(HTTP transport)

  • serverName(string,必需)— MCP server 的显示名称
  • url(string,必需)— MCP server 端点 URL
  • options(object,可选)— 连接配置:
    • id — 可选的稳定、调用方提供的 server ID,用于 connector 式集成。提供后,会替换 storage、listServers()listTools()getAITools()(tool key 变为可读,例如 tool_github_create_pull_request)与 OAuth state 中生成的 nanoid(8)。请参阅稳定的 server ID
    • callbackHost — OAuth 回调 URL 的主机。仅 OAuth 认证的服务器需要。省略时自动从入站请求或 WebSocket 连接 URI 推导 — 通常无需设置,除非使用与 Worker 主机名不同的自定义域
    • callbackPath — 绕过默认 /agents/{class}/{name}/callback 构造的自定义回调 URL 路径。sendIdentityOnConnectfalse 时必需,以防泄漏实例名称。设置后回调 URL 为 {callbackHost}/{callbackPath}。必须通过 getAgentByName 将此路径路由到 agent 实例
    • agentsPrefix — OAuth 回调路径的 URL 前缀。默认:"agents"。提供 callbackPath 时忽略
    • client — MCP 客户端配置选项(传给 @modelcontextprotocol/sdk Client 构造函数)。默认包含 CfWorkerJsonSchemaValidator,用于根据 JSON schema 验证工具参数
    • transport — 传输层配置:
      • headers — 用于身份验证的自定义 HTTP 标头
      • type — 传输类型:"auto"(默认)、"streamable-http""sse"
    • retry — 连接与重连尝试的重试选项。持久化并在休眠或 OAuth 完成后恢复连接时使用。默认:3 次尝试,500ms 基础延迟,5s 最大延迟。RetryOptions 详情请参阅 重试

参数(RPC transport)

  • serverName(string,必需)— MCP server 的显示名称
  • bindingDurableObjectNamespace,必需)— McpAgent 类的 Durable Object 绑定
  • options(object,可选)— 连接配置:
    • id — 可选的稳定、调用方提供的 server ID。请参阅稳定的 server ID
    • props — 传给 McpAgentonStart(props) 的初始化数据。用于向 MCP server instance 传递用户 context、配置或其他数据
    • client — MCP client 配置选项
    • retry — 连接的重试选项

RPC transport 通过 Durable Object 绑定将 Agent 直接连接到 McpAgent,无 HTTP 开销。RPC transport 配置详情请参阅 MCP Transport

返回值

根据连接 state 解析为 discriminated union 的 Promise:

  • state"authenticating" 时:

    • id(string)— 此 server 连接的唯一标识符
    • state"authenticating")— server 等待 OAuth 授权
    • authUrl(string)— 用户身份验证的 OAuth 授权 URL
  • state"ready" 时:

    • id(string)— 此 server 连接的唯一标识符
    • state"ready")— server 已完全连接并可运行

removeMcpServer()

断开与 MCP server 的连接并清理其资源。

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

参数

  • id(string,必需)— addMcpServer() 返回的 server 连接 ID

getMcpServers()

获取所有 MCP server 连接的当前 state。

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 — 建立 transport 连接
  • connected — transport 连接已建立
  • discovering — 发现 server 能力(tool、resource、prompt)
  • ready — 已完全连接并可运行
  • failed — 连接失败(详情见 error 字段)

state"failed" 时,error 字段包含错误消息。外部 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 失败时,连接 state 变为 "failed",错误消息存储在 server.error 字段,供 UI 显示。

用法

在任何 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()

为 server 发起的 elicitation/create 请求配置 handler。为 Agent 支持的每种 elicitation 模式添加 handler。

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

参数

  • handlers(object,可选)— 按模式键控的 elicitation handler:
    • form(function,可选)— 处理 form 模式请求,用于结构化、非敏感输入。
    • url(function,可选)— 处理 URL 模式请求,用于带外交互。
  • requestElicitRequest)— MCP elicitation 请求。检查 request.params.mode 获取模式特定字段。
  • serverId(string)— 发送请求的 MCP server 连接 ID。

每个 handler 返回包含 ElicitResult 的 promise。返回 acceptdeclinecancel。已接受的 form 响应包含与 requestedSchema 匹配的 content。URL 响应省略 content

传入 undefined 会清除所有已配置的 handler。

能力行为

客户端在 MCP initialize 握手期间仅通告已配置处理程序的模式。处理程序变更立即应用于实时连接,但服务器在连接重连后才会收到更新的通告模式。

SDK 将处理程序推导的模式与每个 MCP 服务器注册一并存储。Durable Object 休眠后恢复的连接会通告这些模式,回调在 onStart() 运行时重新挂接。

用法

onStart() 中配置 handler:

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";

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}`,
		);
	}
}

完整 browser 转发模式与模式特定要求请参阅 Elicitation(征询输入)

自定义 OAuth provider

通过在 Agent 类上实现 createMcpOAuthProvider() 覆盖连接 MCP server 时使用的默认 OAuth provider。这支持内置动态 client 注册之外的自定义身份验证策略,如预注册 client 凭证或 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,
				};
			},
		};
	}
}

若不覆盖此方法,agent 使用默认 provider,与 MCP server 执行 OAuth 2.0 Dynamic Client Registration

自定义 storage 后端

保留内置 OAuth 逻辑(CSRF state、PKCE、nonce 生成、token 管理),但将 token storage 路由到不同后端时,导入 DurableObjectOAuthClientProvider 并传入自有 storage adapter:

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()

注册 server 但不立即连接。

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

this.mcp.connectToServer()

建立与先前已注册 server 的连接。

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

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

this.mcp.discoverIfConnected()

若连接处于活跃状态,检查 server 能力。

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()

关闭与特定 server 的连接,但保留其注册。

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

this.mcp.closeAllConnections()

关闭所有活跃 server 连接,但保留注册。

async closeAllConnections(): Promise<void>

获取原始 MCP tool 记录,不将其 schema 转换为 Zod。

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

使用此方法进行目录发现与检查。传入 MCPServerFilter 将返回的 tool 限制到特定连接。

this.mcp.getAITools()

以 AI SDK 兼容格式获取所有已发现的 MCP tool。

getAITools(filter?: MCPServerFilter): ToolSet

多个 MCP 服务器暴露同名工具时,工具会按服务器 ID 自动命名空间化,以防冲突。

getAITools() 在每个实时连接上复用当前目录的已转换 schema。发现替换目录或实时连接变更后会再次转换 schema。每次调用返回新的工具记录与 execute 函数。仅需原始目录时使用 this.mcp.listTools()

传入 MCPServerFilter 将返回的 tool 限定到已连接 server 的子集:

// 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" });

filter 类型可从 agents/mcp/client 获取:

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

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

所有指定的 filter 条件以 AND 组合。listTools()listPrompts()listResources()listResourceTemplates() 接受相同的 filter 参数。

错误处理

使用错误检测工具处理连接错误:

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 从 browser 连接。

这篇文档对您有帮助吗?