← Vytvářejte agenty na Cloudflare / agents / model-context-protocol / apis
McpAgent
McpAgent vytvoří stavový starší server MCP podložený 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) }],
}),
);
}
}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) }],
}),
);
}
}To znamená, že každá instance vašeho serveru MCP má vlastní trvalý stav, který je podpořen Durable Object, s vlastním Databáze SQL.
Bezstavový server může definovat nástroje hodnotou @modelcontextprotocol/server a poskytovat je prostřednictvím createMcpHandler.
Pokud ale chcete, aby váš server MCP:
- pamatovat si předchozí volání nástrojů a odpovědi, které poskytl
- poskytovat klientovi MCP hru a pamatovat si přitom stav hracího plánu, předchozí tahy a skóre
- ukládat do mezipaměti stav předchozího volání externího API, aby jej mohla následující volání nástrojů znovu použít
- dělat vše, co dokáže Agent, ale zároveň umožnit klientům MCP s ním komunikovat
K tomu můžete použít níže uvedená API.
Přehled API
| Vlastnost/metoda | Popis |
|---|---|
state |
Aktuální objekt stavu (uložený) |
initialState |
Výchozí stav při spuštění instance |
setState(state) |
Aktualizace a zachování stavu |
onStateChanged(state) |
Volá se při změně stavu |
sql |
Spouští SQL dotazy ve vestavěné databázi |
server |
McpServer instanci pro registraci nástrojů |
props |
Identita uživatele a tokeny z autentizace OAuth |
elicitInput(options, context) |
Vyžádat od uživatele strukturovaný vstup |
McpAgent.serve(path, options) |
Statická metoda pro vytvoření handleru Workeru |
Nasazování pomocí McpAgent.serve()
McpAgent.serve() statická metoda vytvoří handler Workeru, který směruje požadavky na váš server 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");Toto je nejjednodušší způsob nasazení serveru MCP, zabere zhruba 15 řádků kódu. Tento serve() metoda automaticky zpracovává transport Streamable HTTP.
S ověřováním OAuth
Při použití OAuth Provider Library ↗, předejte svůj server MCP do 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,
});Jurisdikce dat
Kvůli souladu s GDPR a požadavky na umístění dat určete jurisdikci, aby instance vašeho serveru MCP běžely ve specifických regionech:
// EU jurisdiction for GDPR compliance
export default MyMCP.serve("/mcp", { jurisdiction: "eu" });// EU jurisdiction for GDPR compliance
export default MyMCP.serve("/mcp", { jurisdiction: "eu" });S 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
});Když zadáte jurisdiction: "eu":
- Všechna data relací MCP zůstávají v rámci EU
- Data uživatele zpracovaná vašimi nástroji zůstávají v EU
- Stav uložený v Durable Object zůstává v EU
Dostupné jurisdikce zahrnují "eu" (Evropská unie) a "fedramp" (lokality vyhovující FedRAMP). Viz Umístění dat Durable Objects pro další možnosti.
Podpora hibernace
McpAgent instance automaticky podporují Hibernace WebSockets, což stavovým MCP serverům umožňuje během neaktivních period usnout, aniž by přišly o svůj stav. Vaši agenti tak spotřebovávají výpočetní prostředky jen tehdy, když aktivně zpracovávají požadavky, což snižuje náklady a zároveň zachovává celý kontext a historii konverzace.
Hibernace je ve výchozím nastavení povolená a nevyžaduje žádnou další konfiguraci.
Obnovitelnost streamu
McpAgent: transport Streamable HTTP přežije přibližně pětiminutový watchdog nečinného streamu na Cloudflare edge, takže probíhající volání nástrojů se při nestabilním připojení neztratí:
- GET (samostatný naslouchací stream) : když
EventStoreje nakonfigurován, výpadky při nečinnosti klienti obnoví opětovným připojením sLast-Event-IDhlavička (keepalive není potřeba). BezEventStore, udržovací signál keepalive ve formě komentářového rámce (: keepalive, každých 25 sekund) udržuje dlouhotrvající posluchače aktivní. - POST (stream odpovědi nástroje) : Vždy keepalive, aby probíhající volání nástrojů přežila hlídací mechanismus pro nečinnost (watchdog). Streamy POST lze navíc obnovit pomocí
Last-Event-IDkdyžEventStoreje nakonfigurován, klient, který se znovu připojí, přehraje všechny události, které zmeškal, až po závěrečnou odpověď včetně. Události každého POST streamu se vymažou ve chvíli, kdy se zapíše jeho uzavírací rámec.
DurableObjectEventStore se exportuje z agents/mcp pro stavový WorkerTransport volajících, kteří vkládají transport dovnitř Agentu nebo 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);Viz Transport MCP pro konfiguraci transportu.
Ověřování a autorizace
Třída McpAgent poskytuje bezproblémovou integraci s OAuth Provider Library ↗ pro autentizace a autorizace.
Když se uživatel ověří u vašeho serveru MCP, informace o jeho identitě a tokeny jsou zpřístupněny přes props parametr, který umožňuje:
- přístup k datům specifickým pro uživatele
- kontrolovat oprávnění uživatele před prováděním operací
- přizpůsobit odpovědi podle atributů uživatele
- používat autentizační tokeny k vytváření požadavků na externí služby jménem uživatele
API pro synchronizaci stavu
McpAgent třída poskytuje plný přístup k API pro stav agenta:
state: Aktuální uložený stavinitialState: Výchozí stav při spuštění instancesetState: Aktualizujte a ukládejte stavonStateChanged: Reagujte na změny stavusql: Spouštění SQL dotazů na vestavěné databázi
Následující kód například implementuje server MCP, který si pamatuje hodnotu čítače a aktualizuje ho, když se add nástroj je volán:
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 });
}
}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 });
}
}Elicitace na starších serverech
Elicitace MCP ↗ umožňuje serveru vyžádat si vstup od uživatele během zpracování jiného požadavku, například volání nástroje. Starší postup definuje dva režimy:
- Režim formuláře shromažďuje strukturovaná, necitlivá data prostřednictvím klienta.
- Režim URL přesměruje uživatele na interakci mimo standardní tok, například autorizaci třetí strany nebo platbu.
Klient musí podporu daného režimu oznámit dřív, než mu ho server odešle.
Režim formuláře
Zavolejte this.server.server.elicitInput() v handleru nástroje. Předejte extra.requestId jako relatedRequestId aby se odpověď vrátila na streamu původního volání nástroje:
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);Kvůli zpětné kompatibilitě mohou formulářové požadavky vynechat mode: "form". Schéma podporuje ploché objekty s primitivními poli. Režim formuláře nepoužívejte k vyžádání hesel, klíčů API, přístupových tokenů, platebních údajů ani jiných tajných údajů.
Režim URL
Použijte režim URL pro interakce, které musí probíhat mimo klienta MCP. Požadavek obsahuje zprávu, URL a jedinečné 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.",
},
],
};Pro režim URL, accept znamená, že uživatel souhlasil s otevřením URL. Neznamená to, že externí interakce skončila. Server může později odeslat notifications/elicitation/complete se stejným elicitationId.
Nevkládejte tajné údaje, osobní údaje ani předautorizovanou adresu URL chráněného prostředku do url. Produkční servery by měly používat HTTPS. Každý požadavek svažte s ověřeným uživatelem a ověřte, že externí postup dokončí tentýž uživatel.
Zpracování odpovědí
Oba režimy vracejí jednu ze tří akcí:
| Akce | Význam |
|---|---|
accept |
Uživatel odeslal formulář nebo souhlasil s otevřením URL. |
decline |
Uživatel žádost výslovně odmítl. |
cancel |
Uživatel žádost zavřel, aniž by provedl explicitní volbu. |
Přijaté odpovědi formuláře zahrnují content která odpovídá requestedSchema. Odpovědi URL vynechávají content. Odpovědi typu decline a cancel ho obvykle vynechávají.
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." }] };
}Další vzory s lidským zásahem najdete v Vzory s lidským zásahem.