Skip to main content

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

src/server.ts
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

1

Extend McpAgent

McpAgent extends the base Agent class with MCP protocol support. Each instance is backed by a Durable Object.
2

Define state

The initialState property sets the default state. This state persists across hibernation and restarts.
3

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().
4

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:
npm install
npm run dev
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:
  1. Install the inspector: npm install -g @modelcontextprotocol/inspector
  2. Run your MCP server: npm run dev
  3. Open the inspector
  4. Set transport to Streamable HTTP
  5. 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

Tools

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:
npm run deploy
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

Build docs developers (and LLMs) love