Documentation Index Fetch the complete documentation index at: https://mintlify.com/cloudflare/agents/llms.txt
Use this file to discover all available pages before exploring further.
A stateful MCP server using McpAgent backed by a Durable Object. State persists across requests - the built-in UI lets you call tools and read resources to see it in action.
What it demonstrates
McpAgent - the Agents SDK class for building MCP servers with persistent state
Tools - registering an add tool that modifies the counter
Resources - exposing the counter value as an MCP resource
State management - setState and onStateChanged for durable state
Streamable HTTP transport - the default transport for McpAgent
Server Implementation
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ;
import { McpAgent } from "agents/mcp" ;
import { z } from "zod" ;
import icon from "./mcp-icon.svg" ;
type State = { counter : number };
export class MyMCP extends McpAgent < Env , State , {}> {
server = new McpServer ({
name: "Demo" ,
version: "1.0.0" ,
icons: [
{
src: icon ,
sizes: [ "any" ],
mimeType: "image/svg+xml"
}
],
websiteUrl: "https://github.com/cloudflare/agents"
});
initialState : State = {
counter: 1
};
async init () {
// Register a resource that exposes the counter value
this . server . resource ( "counter" , "mcp://resource/counter" , ( uri ) => {
return {
contents: [{ text: String ( this . state . counter ), uri: uri . href }]
};
});
// Register a tool that modifies the counter
this . server . registerTool (
"add" ,
{
description: "Add to the counter, stored in the MCP" ,
inputSchema: { a: z . number () }
},
async ({ a }) => {
this . setState ({ ... this . state , counter: this . state . counter + a });
return {
content: [
{
text: String ( `Added ${ a } , total is now ${ this . state . counter } ` ),
type: "text"
}
]
};
}
);
}
}
export default MyMCP . serve ( "/mcp" , { binding: "MyMCP" }) ;
How It Works
Extend McpAgent
McpAgent extends the base Agent class with MCP protocol support. Each instance is backed by a Durable Object.
Define state
The initialState property sets the default state. This state persists across hibernation and restarts.
Register tools and resources
In the init() method, register MCP tools and resources. Tools can read and modify state via this.state and this.setState().
Serve the MCP
MyMCP.serve() creates a Worker handler that routes requests to the MCP agent.
Testing with the Built-in UI
Run the example locally:
Open http://localhost:5173 to see the built-in tool tester. You can:
Call the add tool with different numbers
Read the counter resource to see the current value
Watch state persist across requests
Testing with MCP Inspector
You can also connect with the MCP Inspector :
Install the inspector: npm install -g @modelcontextprotocol/inspector
Run your MCP server: npm run dev
Open the inspector
Set transport to Streamable HTTP
Set URL to http://localhost:5173/mcp
Key Features
Persistent State
State is stored in Durable Objects and survives:
Worker restarts
Hibernation (when idle)
Redeployments
this . setState ({ counter: this . state . counter + 1 });
// State is now persisted and available in all tools/resources
MCP tools are functions that can be called by MCP clients:
this . server . registerTool (
"add" ,
{
description: "Add to the counter" ,
inputSchema: { a: z . number () }
},
async ({ a }) => {
// Tool implementation
this . setState ({ counter: this . state . counter + a });
return {
content: [{ type: "text" , text: `Counter is now ${ this . state . counter } ` }]
};
}
);
Resources
MCP resources are read-only data exposed to clients:
this . server . resource ( "counter" , "mcp://resource/counter" , ( uri ) => {
return {
contents: [{ text: String ( this . state . counter ), uri: uri . href }]
};
});
Resources can dynamically read from agent state, SQLite, KV, or any other source.
Prompts
MCP prompts are reusable message templates:
this . server . registerPrompt (
"counter-status" ,
{
name: "counter-status" ,
description: "Get a formatted status message about the counter"
},
async () => {
return {
messages: [
{
role: "user" ,
content: {
type: "text" ,
text: `The counter is currently at ${ this . state . counter } .`
}
}
]
};
}
);
Advanced: Using SQLite
export class MyMCP extends McpAgent < Env , State , {}> {
async init () {
// Create tables
this . sql `
CREATE TABLE IF NOT EXISTS items (
id TEXT PRIMARY KEY,
name TEXT,
created_at TEXT
)
` ;
// Register tool that uses SQLite
this . server . registerTool (
"list_items" ,
{ description: "List all items" , inputSchema: {} },
async () => {
const items = [ ... this . sql `SELECT * FROM items` ];
return {
content: [{
type: "text" ,
text: JSON . stringify ( items , null , 2 )
}]
};
}
);
}
}
Deployment
Deploy to Cloudflare Workers:
Your MCP server will be available at:
https://your-worker.workers.dev/mcp
Clients can connect using the Streamable HTTP transport.
MCP Client Connect to MCP servers as a client
MCP Worker Simplest stateless MCP server
MCP Authenticated Adding OAuth to an MCP server
MCP Guide In-depth guide to Model Context Protocol
Further Reading