McpAgent·续
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.tsts
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 on legacy servers
MCP elicitation ↗︎ lets a server request user input while handling another request, such as a tool call. The legacy path defines two modes:
- Form mode collects structured, non-sensitive data through the client.
- URL mode sends the user to an out-of-band interaction, such as third-party authorization or payment.
The client must advertise support for a mode before the server sends it.
Form mode
Call this.server.server.elicitInput() in a tool handler. Pass extra.requestId as relatedRequestId so the response returns on the stream for the originating tool call:
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);
For backwards compatibility, form requests may omit mode: "form". The schema supports flat objects with primitive fields. Do not use form mode to request passwords, API keys, access tokens, payment credentials, or other secrets.
URL mode
Use URL mode for interactions that must happen outside the MCP client. The request includes a message, the URL, and a unique 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.",
},
],
};
For URL mode, accept means the user consented to open the URL. It does not mean the external interaction finished. The server may later send notifications/elicitation/complete with the same elicitationId.
Do not put secrets, personal information, or a pre-authenticated protected-resource URL in url. Production servers should use HTTPS. Bind each request to the authenticated user, and verify that the same user completes the external flow.
Handle responses
Both modes return one of three actions:
| Action | Meaning |
|---|---|
accept |
The user submitted the form or consented to open the URL. |
decline |
The user explicitly rejected the request. |
cancel |
The user dismissed the request without making an explicit choice. |
Accepted form responses include content that matches requestedSchema. URL responses omit content. Decline and cancel responses typically omit it.
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 client support
Not all MCP clients implement legacy elicitation. Check the client before depending on it and provide a fallback when appropriate. Agents acting as MCP clients can handle both modes through MCP client elicitation handlers.
For more human-in-the-loop patterns, refer to Human-in-the-loop patterns.
Next steps
Build a Remote MCP server
Get started with MCP servers on Cloudflare.
MCP Tools
Design and add tools to your MCP server.
Authorization
Set up OAuth authentication.
Securing MCP servers
Security best practices for production.
createMcpHandler
Build stateless MCP servers.
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/agents/model-context-protocol/apis/agent-api/#page","headline":"McpAgent","description":"Reference the deprecated, feature-frozen McpAgent class while migrating existing stateful MCP servers to stateless handlers.","url":"https://developers.cloudflare.com/agents/model-context-protocol/apis/agent-api/","inLanguage":"en","image":"https://developers.cloudflare.com/agents/model-context-protocol/apis/agent-api/og.png?v=d322518ac23269ff","dateModified":"2026-07-27","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"},"keywords":["MCP"]}
description: Connect Agents to external MCP servers to use their tools, resources, and prompts over the Model Context Protocol. title: McpClient image: https://developers.cloudflare.com/agents/model-context-protocol/apis/client-api/og.png?v=d2c89cb40edacc61
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/agents/llms.txt
Use this file to discover all available pages before exploring further.
Docs