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

McpAgent

McpAgent создаёт устаревший сервер MCP с сохранением состояния на основе Durable Object.

import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export class MyMCP extends McpAgent {
	server = new McpServer({ name: "Demo", version: "1.0.0" });

	async init() {
		this.server.tool(
			"add",
			{ a: z.number(), b: z.number() },
			async ({ a, b }) => ({
				content: [{ type: "text", text: String(a + b) }],
			}),
		);
	}
}
src/index.ts
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export class MyMCP extends McpAgent {
	server = new McpServer({ name: "Demo", version: "1.0.0" });

	async init() {
		this.server.tool(
			"add",
			{ a: z.number(), b: z.number() },
			async ({ a, b }) => ({
				content: [{ type: "text", text: String(a + b) }],
			}),
		);
	}
}

Это означает, что каждый экземпляр вашего MCP-сервера имеет собственное устойчивое состояние, подкреплённое Durable Object, со своим собственным База данных SQL.

Сервер без сохранения состояния может определить tools с @modelcontextprotocol/server и предоставляет их через createMcpHandler.

Но если вы хотите, чтобы ваш MCP-сервер:

Для этого вы можете использовать приведённые ниже API.

Обзор API

Свойство/Метод Описание
state Объект текущего состояния (сохраняется)
initialState Состояние по умолчанию при запуске экземпляра
setState(state) Обновляйте и сохраняйте состояние
onStateChanged(state) Вызывается при изменении состояния
sql Выполнять SQL-запросы к встроенной базе данных
server McpServer экземпляр для регистрации инструментов
props Идентификационные данные пользователя и токены OAuth-аутентификации
elicitInput(options, context) Запросить структурированный ввод у пользователя
McpAgent.serve(path, options) Статический метод для создания обработчика Worker

Развёртывание с помощью McpAgent.serve()

McpAgent.serve() статический метод создаёт обработчик Worker, который перенаправляет запросы на ваш MCP-сервер:

import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export class MyMCP extends McpAgent {
	server = new McpServer({ name: "my-server", version: "1.0.0" });

	async init() {
		this.server.tool("square", { n: z.number() }, async ({ n }) => ({
			content: [{ type: "text", text: String(n * n) }],
		}));
	}
}

// Export the Worker handler
export default MyMCP.serve("/mcp");
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export class MyMCP extends McpAgent {
	server = new McpServer({ name: "my-server", version: "1.0.0" });

	async init() {
		this.server.tool("square", { n: z.number() }, async ({ n }) => ({
			content: [{ type: "text", text: String(n * n) }],
		}));
	}
}

// Export the Worker handler
export default MyMCP.serve("/mcp");

Это самый простой способ развернуть MCP-сервер: около 15 строк кода. Этот serve() метод автоматически обрабатывает транспорт Streamable HTTP.

С аутентификацией через OAuth

При использовании OAuth Provider Library ↗, передайте свой сервер MCP в apiHandlers:

import { OAuthProvider } from "@cloudflare/workers-oauth-provider";

export default new OAuthProvider({
	apiHandlers: { "/mcp": MyMCP.serve("/mcp") },
	authorizeEndpoint: "/authorize",
	tokenEndpoint: "/token",
	clientRegistrationEndpoint: "/register",
	defaultHandler: AuthHandler,
});
import { OAuthProvider } from "@cloudflare/workers-oauth-provider";

export default new OAuthProvider({
	apiHandlers: { "/mcp": MyMCP.serve("/mcp") },
	authorizeEndpoint: "/authorize",
	tokenEndpoint: "/token",
	clientRegistrationEndpoint: "/register",
	defaultHandler: AuthHandler,
});

Юрисдикция данных

Для соответствия GDPR и требованиям к размещению данных укажите юрисдикцию, чтобы инстансы вашего MCP-сервера работали в определённых регионах:

// EU jurisdiction for GDPR compliance
export default MyMCP.serve("/mcp", { jurisdiction: "eu" });
// EU jurisdiction for GDPR compliance
export default MyMCP.serve("/mcp", { jurisdiction: "eu" });

С OAuth:

export default new OAuthProvider({
	apiHandlers: {
		"/mcp": MyMCP.serve("/mcp", { jurisdiction: "eu" }),
	},
	// ... other OAuth config
});
export default new OAuthProvider({
	apiHandlers: {
		"/mcp": MyMCP.serve("/mcp", { jurisdiction: "eu" }),
	},
	// ... other OAuth config
});

Если указать jurisdiction: "eu":

Доступные юрисдикции включают "eu" (Европейский союз) и "fedramp" (регионы, соответствующие FedRAMP). См. Расположение данных Durable Objects для дополнительных опций.

Поддержка гибернации

McpAgent экземпляры автоматически поддерживают WebSockets Hibernation, что позволяет MCP серверам с сохранением состояния «засыпать» в периоды бездействия, не теряя при этом своё состояние. Это значит, что ваши агенты расходуют вычислительные ресурсы только тогда, когда активно обрабатывают запросы, что снижает затраты и одновременно сохраняет полный контекст и историю разговора.

Гибернация включена по умолчанию и не требует дополнительной настройки.

Возобновляемость потока

McpAgent: транспорт Streamable HTTP переживает примерно пятиминутный watchdog простаивающего потока на границе сети Cloudflare, поэтому выполняющиеся вызовы инструментов не теряются при нестабильном соединении:

DurableObjectEventStore экспортируется из agents/mcp для сохраняющего состояние WorkerTransport вызывающего кода, который встраивает транспорт внутрь Agent или Durable Object:

import { DurableObjectEventStore } from "agents/mcp";

const eventStore = new DurableObjectEventStore(this.ctx.storage);
import { DurableObjectEventStore } from "agents/mcp";

const eventStore = new DurableObjectEventStore(this.ctx.storage);

См. MCP-транспорт для настройки транспорта.

Аутентификация и авторизация

Класс McpAgent обеспечивает бесшовную интеграцию с OAuth Provider Library ↗ для аутентификация и авторизация.

Когда пользователь проходит аутентификацию на вашем MCP-сервере, информация о его личности и токены становятся доступны через props параметр, позволяющий:

API синхронизации состояния

McpAgent класс предоставляет полный доступ к API состояния Agent:

Например, следующий код реализует сервер MCP, который запоминает значение счётчика и обновляет его, когда add инструмент вызывается:

import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export class MyMCP extends McpAgent {
	server = new McpServer({
		name: "Demo",
		version: "1.0.0",
	});

	initialState = {
		counter: 1,
	};

	async init() {
		this.server.resource(`counter`, `mcp://resource/counter`, (uri) => {
			return {
				contents: [{ uri: uri.href, text: String(this.state.counter) }],
			};
		});

		this.server.tool(
			"add",
			"Add to the counter, stored in the MCP",
			{ a: z.number() },
			async ({ a }) => {
				this.setState({ ...this.state, counter: this.state.counter + a });

				return {
					content: [
						{
							type: "text",
							text: String(`Added ${a}, total is now ${this.state.counter}`),
						},
					],
				};
			},
		);
	}

	onStateChanged(state) {
		console.log({ stateUpdate: state });
	}
}
src/index.ts
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

type State = { counter: number };

export class MyMCP extends McpAgent<Env, State, {}> {
	server = new McpServer({
		name: "Demo",
		version: "1.0.0",
	});

	initialState: State = {
		counter: 1,
	};

	async init() {
		this.server.resource(`counter`, `mcp://resource/counter`, (uri) => {
			return {
				contents: [{ uri: uri.href, text: String(this.state.counter) }],
			};
		});

		this.server.tool(
			"add",
			"Add to the counter, stored in the MCP",
			{ a: z.number() },
			async ({ a }) => {
				this.setState({ ...this.state, counter: this.state.counter + a });

				return {
					content: [
						{
							type: "text",
							text: String(`Added ${a}, total is now ${this.state.counter}`),
						},
					],
				};
			},
		);
	}

	onStateChanged(state: State) {
		console.log({ stateUpdate: state });
	}
}

Elicitation на устаревших серверах

MCP elicitation ↗ позволяет серверу запрашивать ввод у пользователя во время обработки другого запроса, например вызова инструмента. Устаревший путь определяет два режима:

Клиент должен объявить поддержку режима, прежде чем сервер начнёт его отправлять.

Режим формы

Вызовите this.server.server.elicitInput() в обработчике инструмента. Передайте extra.requestId как relatedRequestId чтобы ответ возвращался в потоке для исходного вызова инструмента:

const result = await this.server.server.elicitInput(
	{
		mode: "form",
		message: "By how much do you want to increase the counter?",
		requestedSchema: {
			type: "object",
			properties: {
				amount: {
					type: "number",
					title: "Amount",
					minimum: 1,
					maximum: 100,
				},
			},
			required: ["amount"],
		},
	},
	{ relatedRequestId: extra.requestId },
);

if (result.action !== "accept" || !result.content) {
	return { content: [{ type: "text", text: "Counter unchanged." }] };
}

const amount = Number(result.content.amount);
const result = await this.server.server.elicitInput(
	{
		mode: "form",
		message: "By how much do you want to increase the counter?",
		requestedSchema: {
			type: "object",
			properties: {
				amount: {
					type: "number",
					title: "Amount",
					minimum: 1,
					maximum: 100,
				},
			},
			required: ["amount"],
		},
	},
	{ relatedRequestId: extra.requestId },
);

if (result.action !== "accept" || !result.content) {
	return { content: [{ type: "text", text: "Counter unchanged." }] };
}

const amount = Number(result.content.amount);

Для обратной совместимости в form-запросах можно опустить mode: "form". Схема поддерживает плоские объекты с примитивными полями. Не используйте режим формы для запроса паролей, API-ключей, токенов доступа, платёжных данных и других секретов.

Режим URL

Используйте режим URL для взаимодействий, которые должны происходить вне MCP-клиента. Запрос включает сообщение, URL и уникальный elicitationId:

const elicitationId = crypto.randomUUID();
const result = await this.server.server.elicitInput(
	{
		mode: "url",
		message: "Connect your account to continue.",
		url: `https://example.com/connect?elicitationId=${elicitationId}`,
		elicitationId,
	},
	{ relatedRequestId: extra.requestId },
);

if (result.action !== "accept") {
	return { content: [{ type: "text", text: "Connection cancelled." }] };
}

return {
	content: [
		{
			type: "text",
			text: "Connection page opened. Complete it in your browser.",
		},
	],
};
const elicitationId = crypto.randomUUID();
const result = await this.server.server.elicitInput(
	{
		mode: "url",
		message: "Connect your account to continue.",
		url: `https://example.com/connect?elicitationId=${elicitationId}`,
		elicitationId,
	},
	{ relatedRequestId: extra.requestId },
);

if (result.action !== "accept") {
	return { content: [{ type: "text", text: "Connection cancelled." }] };
}

return {
	content: [
		{
			type: "text",
			text: "Connection page opened. Complete it in your browser.",
		},
	],
};

Для режима URL accept означает, что пользователь согласился открыть URL. Это не значит, что внешнее взаимодействие завершилось. Сервер может позже отправить notifications/elicitation/complete с тем же elicitationId.

Не помещайте секреты, персональные данные или предварительно авторизованный URL защищённого ресурса в url. Рабочие серверы должны использовать HTTPS. Привязывайте каждый запрос к аутентифицированному пользователю и проверяйте, что тот же пользователь завершает внешний процесс.

Обрабатывайте ответы

Оба режима возвращают одно из трёх действий:

Действие Значение
accept Пользователь отправил форму или согласился открыть URL.
decline Пользователь явно отклонил запрос.
cancel Пользователь закрыл запрос, не сделав явного выбора.

Принятые ответы формы включают content который соответствует requestedSchema. Ответы URL не включают content. Ответы об отклонении и отмене обычно его не включают.

switch (result.action) {
	case "accept":
		// For form mode, validate and process result.content.
		break;
	case "decline":
		return { content: [{ type: "text", text: "Request declined." }] };
	case "cancel":
		return { content: [{ type: "text", text: "Request dismissed." }] };
}
switch (result.action) {
	case "accept":
		// For form mode, validate and process result.content.
		break;
	case "decline":
		return { content: [{ type: "text", text: "Request declined." }] };
	case "cancel":
		return { content: [{ type: "text", text: "Request dismissed." }] };
}

Другие паттерны с участием человека см. в Паттерны с участием человека.

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

Создайте удалённый MCP-сервер

Начало работы с MCP-серверами на Cloudflare.

MCP-инструменты

Спроектируйте и добавьте инструменты в свой MCP-сервер.

Авторизация

Настройте OAuth-аутентификацию.

Защита серверов MCP

Рекомендации по безопасности для продакшена.

createMcpHandler

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