Skip to main content
This guide covers the different ways to create MCP servers with the Agents SDK and helps you choose the right approach.

Choosing an Approach

  • createMcpHandler() is the fastest way to get a stateless MCP server running. Use it when your tools do not need per-session state.
  • McpAgent gives you a Durable Object per session with built-in state management, elicitation support, and both SSE and Streamable HTTP transports.
  • Raw transport gives you full control if you want to use the @modelcontextprotocol/sdk directly without the Agents SDK helpers.

Stateless MCP Server with createMcpHandler()

The simplest way to create an MCP server. No Durable Objects or bindings required:
Create a new McpServer instance per request. The MCP SDK does not allow connecting an already-connected server to a new transport.

createMcpHandler Options

Accessing Authenticated User Context

When your MCP server is wrapped with OAuthProvider from @cloudflare/workers-oauth-provider, authenticated user information is available inside tools via getMcpAuthContext():
The OAuthProvider sets ctx.props on the execution context, which createMcpHandler automatically picks up and makes available via getMcpAuthContext().

Stateful MCP Server with McpAgent

McpAgent gives each client session its own Durable Object with persistent state. Use this when your tools need to track per-session data.

Writing TinyMCP

Prototyping is very easy! If you want to quickly deploy an MCP, it only takes ~20 lines of code:
Your wrangler.jsonc would look something like:

What is going on here?

McpAgent requires us to define 2 bits, server and init(). init() is the initialization logic that runs every time our MCP server is started (each client session goes to a different Agent instance).
In there you will normally setup all your tools/resources and anything else you might need. In this case, we are only setting the tool square.
That was just the McpAgent, but we still need a Worker to route requests to our MCP server. McpAgent exports a static method that deals with that for you. That is what TinyMcp.serve(...) is for.
It returns an object with a fetch handler that can act as our Worker entrypoint and deal with the Streamable HTTP transport for us, so we can deploy our MCP directly!

Putting it to the test

It is a very simple MCP indeed, but you can get a feel of how fast you can get a server up and running. You can deploy this worker and test your MCP with any client.

Password-protected StorageMCP with OAuth

To get a feel of what a more realistic MCP might look like, let’s deploy an MCP that lets anyone that knows our secret password access a shared R2 bucket.
This is an example of a custom authorization flow. Do not use this in production.
1

Define the McpAgent

Create your McpAgent with tools that interact with R2:
2

Create the OAuth flow

Build a simple password-based authentication flow:
3

Wire it all together

Combine the MCP server with OAuth:
Add these to your wrangler.jsonc:

Data Jurisdiction for Compliance

McpAgent supports specifying a data jurisdiction for your MCP server, which is particularly useful for satisfying GDPR and other data residency regulations.

Using the EU Jurisdiction for GDPR

To comply with GDPR requirements, you can specify the "eu" jurisdiction to ensure that all data processed by your MCP server remains within the European Union:
Or with the OAuth-protected example:
When you specify jurisdiction: "eu", Cloudflare will create the Durable Object instances in EU data centers, ensuring that:
  • All MCP session data stays within the EU
  • User data processed by your tools remains in the EU
  • State stored in the Durable Object’s storage API stays in the EU
This helps you comply with GDPR’s data localization requirements without any additional configuration.

Available Jurisdictions

The jurisdiction option accepts any value supported by Cloudflare’s Durable Objects jurisdiction API, including:
  • "eu" - European Union
  • "fedramp" - FedRAMP compliant locations

Elicitation (Human-in-the-Loop)

MCP servers can request additional input from the user during a tool call using elicitation. This is useful for confirmation dialogs, requesting amounts, or any interactive tool flow. Elicitation is supported via McpAgent (which manages the request/response lifecycle through Durable Object storage) or via WorkerTransport (for stateful non-McpAgent setups).
See the examples/mcp-elicitation example for a full working demo.

WorkerTransport

WorkerTransport is a server-side transport for running MCP servers in stateless Workers while optionally persisting session state. It is used internally by createMcpHandler() but can also be used directly for advanced scenarios like stateful sessions without McpAgent.

Key Options

Next Steps

Connecting Clients

Connect your agent to external MCP servers

Transports

Learn about different MCP transport options

Securing Servers

Implement OAuth and security best practices

Examples

View example implementations on GitHub