McpAgent
Last updated Jul 27, 2026
McpAgent creates a stateful legacy MCP server backed by a Durable Object.
Deprecated
McpAgent remains available only for existing legacy servers while they migrate. It is deprecated and feature-frozen. Migrate to createMcpHandler at your earliest convenience.
A server that depends on MCP session state, RPC, pushed server-to-client requests, standalone streams, or replay needs a staged migration. Design stateless equivalents, add a stateless route, and serve both lanes until clients migrate and existing sessions drain.
Keep importing the legacy server McpServer from @modelcontextprotocol/sdk. An SDK v2 server from @modelcontextprotocol/server cannot run inside McpAgent.
Refer to Migrate to MCP SDK v2 for the feature mapping and dual-era rollout.
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.tsts
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) }],
}),
);
}
}
This means that each instance of your MCP server has its own durable state, backed by a Durable Object, with its own SQL database.
A stateless server can define tools with @modelcontextprotocol/server and serve them through createMcpHandler.
But if you want your MCP server to:
- remember previous tool calls, and responses it provided
- provide a game to the MCP client, remembering the state of the game board, previous moves, and the score
- cache the state of a previous external API call, so that subsequent tool calls can reuse it
- do anything that an Agent can do, but allow MCP clients to communicate with it
You can use the APIs below in order to do so.
API overview
| Property/Method | Description |
|---|---|
state |
Current state object (persisted) |
initialState |
Default state when instance starts |
setState(state) |
Update and persist state |
onStateChanged(state) |
Called when state changes |
sql |
Execute SQL queries on embedded database |
server |
The McpServer instance for registering tools |
props |
User identity and tokens from OAuth authentication |
elicitInput(options, context) |
Request structured input from user |
McpAgent.serve(path, options) |
Static method to create a Worker handler |
Deploying with McpAgent.serve()
The McpAgent.serve() static method creates a Worker handler that routes requests to your MCP server:
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");
This is the simplest way to deploy an MCP server — about 15 lines of code. The serve() method handles Streamable HTTP transport automatically.
With OAuth authentication
When using the OAuth Provider Library ↗︎, pass your MCP server to 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,
});
Data jurisdiction
For GDPR and data residency compliance, specify a jurisdiction to ensure your MCP server instances run in specific regions:
// EU jurisdiction for GDPR compliance
export default MyMCP.serve("/mcp", { jurisdiction: "eu" });
// EU jurisdiction for GDPR compliance
export default MyMCP.serve("/mcp", { jurisdiction: "eu" });
With 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
});
When you specify jurisdiction: "eu":
- All MCP session data stays within the EU
- User data processed by your tools remains in the EU
- State stored in the Durable Object stays in the EU
Available jurisdictions include "eu" (European Union) and "fedramp" (FedRAMP compliant locations). Refer to Durable Objects data location for more options.
Hibernation support
McpAgent instances automatically support WebSockets Hibernation, allowing stateful MCP servers to sleep during inactive periods while preserving their state. This means your agents only consume compute resources when actively processing requests, optimizing costs while maintaining the full context and conversation history.
Hibernation is enabled by default and requires no additional configuration.
Stream resumability
McpAgent's Streamable HTTP transport survives the roughly 5-minute Cloudflare edge idle-stream watchdog so in-flight tool calls are not lost on a flaky connection:
- GET (standalone listen stream) — when an
EventStoreis configured, idle drops are recovered by clients reconnecting with aLast-Event-IDheader (no keepalive needed). Without anEventStore, a comment-frame keepalive (: keepalive, every 25 seconds) keeps long-lived listeners alive. - POST (tool response stream) — always keepalive, so in-flight tool calls survive the idle watchdog. POST streams can additionally be resumed via
Last-Event-IDwhen anEventStoreis configured; a reconnecting client replays any events it missed up to and including the final response. Each POST stream's events are cleared when its close frame is written.
DurableObjectEventStore is exported from agents/mcp for stateful WorkerTransport callers that embed the transport inside an Agent or 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);
Refer to MCP Transport for transport configuration.
Authentication and authorization
The McpAgent class provides seamless integration with the OAuth Provider Library ↗︎ for authentication and authorization.
When a user authenticates to your MCP server, their identity information and tokens are made available through the props parameter, allowing you to:
- access user-specific data
- check user permissions before performing operations
- customize responses based on user attributes
- use authentication tokens to make requests to external services on behalf of the user
State synchronization APIs
The McpAgent class provides full access to the Agent state APIs:
state— Current persisted stateinitialState— Default state when instance startssetState— Update and persist stateonStateChanged— React to state changessql— Execute SQL queries on embedded database
State resets after the session ends
Currently, each client session is backed by an instance of the McpAgent class. This is handled automatically for you, as shown in the getting started guide. This means that when the same client reconnects, they will start a new session, and the state will be reset.
For example, the following code implements an MCP server that remembers a counter value, and updates the counter when the add tool is called:
