← Создание агентов на Cloudflare / agents / model-context-protocol / apis
McpClient
Подключите агента к внешним Model Context Protocol (MCP) серверов, чтобы использовать их инструменты, ресурсы и промпты. Agents SDK версии 0.20.0 использует @modelcontextprotocol/client и автоматически согласовывает поведение без сохранения состояния или устаревшее поведение.
См. Мигрировать на MCP SDK v2 для изменений пакета, типа, OAuth-провайдера и поэтапного развёртывания.
Обзор
Возможности MCP-клиента позволяют вашему агенту:
- Подключение к внешним MCP-серверам - GitHub, Slack, базы данных, сервисы ИИ
- Используйте их инструменты - вызывайте функции, предоставляемые MCP-серверами
- Ресурсы Access - Чтение данных с 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() чтобы подключиться к MCP-серверу. Для серверов без 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 для интеграций в стиле коннекторов, чтобы инструменты отображались как читаемые ключи, а не непрозрачные идентификаторы соединений.
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 заменяет сгенерированное значение как ID сервера при хранении, восстановлении, listServers(), listTools(), getAITools(), и состояние OAuth. Переданный ID нормализуется с помощью экспортированной normalizeServerId вспомогательная функция, поэтому такие значения, как "GitHub MCP!" становятся "github-mcp" : гарантирует, что ID безопасно встраивать в имена инструментов AI SDK и ключи хранилища.
Устойчивые ID полностью аддитивны: существующий код не ломается. Если вы добавите { id: "github" } к addMcpServer вызов для сервера, уже зарегистрированного под автоматически сгенерированным ID, 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
URL-адреса MCP-серверов проверяются перед подключением, чтобы предотвратить подделку запроса на стороне сервера (SSRF). Блокируются следующие целевые URL-адреса:
- Частные и внутренние диапазоны IP-адресов (RFC 1918:
10.x,172.16-31.x,192.168.x) - Неуказанные адреса (
0.0.0.0,[::]) - Link-local адреса (
169.254.x,fe80::) - уникальные локальные адреса IPv6 (
fc00::/7) - IPv4-сопоставленные адреса IPv6, которые указывают на частные диапазоны (например,
[::ffff:10.0.0.1]) - Эндпойнты облачных метаданных (
metadata.google.internal)
Loopback-адреса (localhost, 127.x.x.x, [::1]) являются разрешено для локальной разработки.
Для продакшен-подключений к внутренним сервисам используйте RPC-транспорт с привязкой Durable Object вместо HTTP.
Возвращаемое значение
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-колбэках
При использовании sendIdentityOnConnect: false чтобы скрыть чувствительные имена экземпляров (например, идентификаторы сессий или пользователей), URL колбэка OAuth по умолчанию раскрывал бы имя экземпляра. Чтобы избежать этой проблемы безопасности, необходимо указать пользовательский 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
После подключения получите доступ к возможностям сервера:
Получить доступные инструменты
Используйте listTools() чтобы просмотреть необработанный каталог MCP без подготовки инструментов для вызова модели AI SDK:
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. Ни один из API не преобразует схемы инструментов.
Интеграция с AI SDK
Чтобы использовать инструменты MCP с AI SDK, используйте this.mcp.getAITools() который преобразует инструменты MCP в формат 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);
}
}Resources и prompts
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. Запросы на stateless-пути передают их вместе с возможностями запроса. Обработчик только для форм заявляет режим формы. Обработчик только для 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 значение имеет приоритет над режимами, определяемыми обработчиком, и сохраняется вместе с регистрацией сервера. Не объявляйте режим, для которого нет соответствующего обработчика. Если сервер отправит этот режим, подключение вернёт ошибку, поскольку не сможет обработать запрос.
Режим формы
Режим формы собирает структурированные несекретные данные на стороне клиента. Запрос включает ограниченную JSON Schema в requestedSchema. Если пользователь отправляет форму, верните action: "accept" с совпадающим content:
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-ключей, токенов доступа, платёжных данных или других секретов.
Режим URL
Режим URL просит пользователя открыть внешнюю страницу. Используйте его для внеполосных взаимодействий, которые могут собирать секреты, например авторизацию у стороннего сервиса или оплату. Держите URL в отдельном пути elicitation и не допускайте его попадания в сообщения, видимые модели, и текст результатов инструментов.
Обработчик URL должен:
- Покажите, какой MCP-сервер отправил запрос.
- Покажите сообщение запроса, целевой хост и полный URL.
- Запрашивайте согласие перед открытием URL.
- Открывайте страницу в контексте браузера, который Agent и модель не могут проверить.
- Верните
action: "accept"безcontentпосле согласия. - Предусмотрите отдельные элементы управления для отклонения и отмены.
Не загружайте заранее URL или его метаданные. Считайте URL ненадёжным входным значением. Продакшн-серверы должны отправлять адреса HTTPS.
Для режима URL accept означает, что пользователь согласился открыть URL. Это не значит, что внешнее взаимодействие завершилось. Сервер может позже отправить notifications/elicitation/complete с запросом elicitationId.
Ответные действия
Оба режима поддерживают три действия:
| Действие | Значение |
|---|---|
accept |
Пользователь отправил форму или согласился открыть URL. |
decline |
Пользователь явно отклонил запрос. |
cancel |
Пользователь закрыл запрос, не сделав явного выбора. |
Подключите content только для принятого ответа формы. Для ответов URL, отклонения и отмены его следует опустить.
Пересылка elicitation в интерфейс
Обработчик возвращает promise, но ответ часто приходит от браузера. Разошлите запрос подключённым клиентам, а затем разрешите promise через @callable метод:
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);Это отключается от сервера и удаляет его из хранилища.
Интеграция на стороне клиента
Подключённые клиенты получают обновления MCP в реальном времени через WebSocket:
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() не беспокоясь о дублирующихся подключениях при перезапуске.
Если вы вызовете addMcpServer с тем же именем, но другой URL создаётся новое соединение. Оба соединения остаются активными, а их инструменты объединяются в 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" }>Параметры (транспорт HTTP)
serverName(строка, обязательно): отображаемое имя MCP-сервераurl(строка, обязательно): URL эндпойнта MCP-сервераoptions(объект, необязательно): конфигурация соединения:id: необязательный устойчивый идентификатор сервера, задаваемый вызывающей стороной, для интеграций коннекторного типа. Если он указан, то заменяет сгенерированныйnanoid(8)в хранилище,listServers(),listTools(),getAITools()(чтобы ключи инструментов стали читаемыми, напримерtool_github_create_pull_request), и состояние OAuth. См. Устойчивые идентификаторы сервераcallbackHost: хост для URL обратного вызова OAuth. Требуется только для серверов с аутентификацией через OAuth. Если параметр не указан, он автоматически определяется по входящему запросу или URI соединения WebSocket: как правило, задавать его нужно только при использовании собственного домена, который отличается от имени хоста WorkercallbackPath: Пользовательский путь URL для колбэка, который обходит стандартный/agents/{class}/{name}/callbackконструирование. Обязательно, еслиsendIdentityOnConnectэтоfalseчтобы предотвратить утечку имени экземпляра. При установке URL колбэка становится{callbackHost}/{callbackPath}. Вы должны направить этот путь к экземпляру агента черезgetAgentByNameagentsPrefix: префикс URL для пути обратного вызова OAuth. По умолчанию:"agents". Игнорируется, когдаcallbackPathзаданclient: поддерживаемый AgentsMcpClientOptionsподмножество из@modelcontextprotocol/client. Валидатор по умолчанию поддерживает JSON Schema 2020-12 и устаревшие схемы draft-07 в Workerstransport: конфигурация транспортного уровня:headers: Пользовательские HTTP-заголовки для аутентификацииtype: тип транспорта:"auto"(по умолчанию),"streamable-http", или"sse"
retry: параметры повторных попыток для подключения и переподключения. Сохраняются и используются при восстановлении соединений после гибернации или завершения OAuth. По умолчанию: 3 попытки, базовая задержка 500 мс, максимальная задержка 5 с. См. Повторы для подробностей оRetryOptions.
Параметры (транспорт RPC)
serverName(строка, обязательно): отображаемое имя MCP-сервераbinding(DurableObjectNamespace, обязательно): привязка Durable Object дляMcpAgentклассoptions(объект, необязательно): конфигурация соединения:id: необязательный устойчивый идентификатор сервера, задаваемый вызывающей стороной. См. Устойчивые идентификаторы сервераprops: данные инициализации, передаваемые вMcpAgent:onStart(props). Используйте это, чтобы передать пользовательский контекст, конфигурацию или другие данные экземпляру MCP-сервераclient: параметры конфигурации MCP-клиентаretry: параметры повторных попыток для соединения
RPC-транспорт напрямую подключает ваш Agent к McpAgent через привязки Durable Object без накладных расходов на HTTP. См. MCP-транспорт для подробностей о настройке RPC-транспорта.
Возвращает
Promise, который разрешается в размеченное объединение на основе состояния соединения:
-
Когда
stateэто"authenticating":id(строка): уникальный идентификатор этого соединения сервераstate("authenticating"): сервер ожидает авторизации OAuthauthUrl(строка): URL авторизации OAuth для аутентификации пользователя
-
Когда
stateэто"ready":id(строка): уникальный идентификатор этого соединения сервераstate("ready"): сервер полностью подключён и работоспособен
removeMcpServer()
Отключитесь от MCP-сервера и освободите его ресурсы.
async removeMcpServer(id: string): Promise<void>Параметры
id(строка, обязательно): идентификатор соединения сервера, возвращённыйaddMcpServer()
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: ожидание завершения авторизации OAuthconnecting: установление транспортного соединенияconnected: транспортное соединение установленоdiscovering: обнаружение возможностей сервера (инструментов, ресурсов, промптов)ready: полностью подключён и работаетfailed: Не удалось установить соединение (см.errorполе, где приведены подробности)
error поле содержит сообщение об ошибке, когда state это "failed". Сообщения об ошибках от внешних провайдеров OAuth автоматически экранируются для предотвращения XSS-атак, что позволяет безопасно выводить их напрямую в вашем интерфейсе.
configureOAuthCallback()
Настройте поведение обратного вызова OAuth для MCP-серверов, которым требуется аутентификация. Этот метод позволяет задать, что происходит после завершения пользователем авторизации OAuth.
this.mcp.configureOAuthCallback(options: {
successRedirect?: string;
errorRedirect?: string;
customHandler?: () => Response | Promise<Response>;
}): voidПараметры
options(объект, обязательно): конфигурация колбэка OAuth:successRedirect(строка, необязательно): URL для перенаправления после успешной аутентификацииerrorRedirect(строка, необязательно): URL для перенаправления после неудачной аутентификации. Сообщение об ошибке добавляется как?error=<message>параметр запросаcustomHandler(функция, необязательно): пользовательский обработчик для полного контроля над ответом колбэка. Должен возвращать Response
Поведение по умолчанию
Если конфигурация не задана:
- Успешно: Перенаправляет на источник вашего приложения
- Сбой: Отображает HTML-страницу ошибки с текстом ошибки
Если OAuth завершается ошибкой, состояние подключения становится "failed" и сообщение об ошибке сохраняется в server.error поле для отображения в вашем интерфейсе.
Использование
Настройте в onStart() до начала любых потоков OAuth:
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 запросов. Добавьте обработчик для каждого режима elicitation, поддерживаемого вашим Agent.
this.mcp.configureElicitationHandlers(handlers?: {
form?: (
request: ElicitRequest,
serverId: string,
signal?: AbortSignal,
) => Promise<ElicitResult>;
url?: (
request: ElicitRequest,
serverId: string,
signal?: AbortSignal,
) => Promise<ElicitResult>;
}): voidПараметры
handlers(объект, необязательно): обработчики elicitation по режимам:form(функция, необязательно): обрабатывает запросы в режиме формы для структурированного ввода, не содержащего конфиденциальных данных.url(функция, необязательно): обрабатывает запросы в режиме URL для взаимодействий out-of-band.
request(ElicitRequest): запрос MCP elicitation. Изучитеrequest.params.modeдля полей, специфичных для режима.serverId(строка): идентификатор соединения MCP-сервера, отправившего запрос.signal(AbortSignal, необязательно): прерывается, когда исходная операция MCP отменяется.
Каждый обработчик возвращает promise, содержащий ElicitResult. Верните accept, decline, или cancel. Принятые ответы формы включают content который соответствует requestedSchema. Ответы 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-провайдер
Переопределите провайдера OAuth по умолчанию, который используется при подключении к MCP-серверам, реализовав createMcpOAuthProvider() в вашем классе Agent. Это позволяет использовать собственные стратегии аутентификации, такие как заранее зарегистрированные учётные данные клиента или 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,
};
},
};
}
}Если вы не переопределите этот метод, агент использует провайдера по умолчанию, который выполняет OAuth 2.0 Dynamic Client Registration ↗ с MCP-сервером.
Пользовательский бэкенд хранилища
Чтобы сохранить встроенную логику OAuth (состояние CSRF, 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. Это полезно, когда вам нужно 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()
Получить необработанные записи инструментов MCP без преобразования их схем в Zod.
listTools(filter?: MCPServerFilter): Array<Tool & { serverId: string }>Используйте этот метод для обнаружения и просмотра каталога. Передайте MCPServerFilter чтобы ограничить возвращаемые инструменты конкретными подключениями.
this.mcp.getAITools()
Получить все обнаруженные инструменты MCP в формате, совместимом с AI SDK.
getAITools(filter?: MCPServerFilter): ToolSetИнструменты автоматически распределяются по пространствам имён на основе ID сервера, чтобы избежать конфликтов, когда несколько MCP-серверов предоставляют инструменты с одинаковым именем.
getAITools() повторно использует преобразованные схемы для текущего catalog при каждом live connection. Он преобразует схемы заново после того, как discovery заменит catalog, или при изменении live connection. Каждый вызов возвращает новые записи инструментов и функции 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;
}
}
}