INTEGRITY Документация

McpClient

Подключите агента к внешним Model Context Protocol (MCP) серверов, чтобы использовать их инструменты, ресурсы и промпты. Agents SDK версии 0.20.0 использует @modelcontextprotocol/client и автоматически согласовывает поведение без сохранения состояния или устаревшее поведение.

См. Мигрировать на MCP SDK v2 для изменений пакета, типа, OAuth-провайдера и поэтапного развёртывания.

Обзор

Возможности 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-адреса:

Loopback-адреса (localhost, 127.x.x.x, [::1]) являются разрешено для локальной разработки.

Для продакшен-подключений к внутренним сервисам используйте RPC-транспорт с привязкой Durable Object вместо HTTP.

Возвращаемое значение

addMcpServer() возвращает состояние подключения:

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 должен:

  1. Покажите, какой MCP-сервер отправил запрос.
  2. Покажите сообщение запроса, целевой хост и полный URL.
  3. Запрашивайте согласие перед открытием URL.
  4. Открывайте страницу в контексте браузера, который Agent и модель не могут проверить.
  5. Верните action: "accept" без content после согласия.
  6. Предусмотрите отдельные элементы управления для отклонения и отмены.

Не загружайте заранее 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)

Параметры (транспорт RPC)

RPC-транспорт напрямую подключает ваш Agent к McpAgent через привязки Durable Object без накладных расходов на HTTP. См. MCP-транспорт для подробностей о настройке RPC-транспорта.

Возвращает

Promise, который разрешается в размеченное объединение на основе состояния соединения:

removeMcpServer()

Отключитесь от MCP-сервера и освободите его ресурсы.

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

Параметры

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 поле указывает на жизненный цикл соединения:

error поле содержит сообщение об ошибке, когда state это "failed". Сообщения об ошибках от внешних провайдеров OAuth автоматически экранируются для предотвращения XSS-атак, что позволяет безопасно выводить их напрямую в вашем интерфейсе.

configureOAuthCallback()

Настройте поведение обратного вызова OAuth для MCP-серверов, которым требуется аутентификация. Этот метод позволяет задать, что происходит после завершения пользователем авторизации OAuth.

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

Параметры

Поведение по умолчанию

Если конфигурация не задана:

Если 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

Параметры

Каждый обработчик возвращает 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;
		}
	}
}

Следующие шаги

Создание MCP-серверов

Создайте собственный MCP-сервер.

Клиентский SDK

Подключение из браузеров с помощью onMcpUpdate.

Хранение и синхронизация состояния

Узнайте о сохранении состояния агента.