
This tutorial is part of Build an MCP Server, within The MCP Guide. Want it built for you? See MCP development.
To deploy an MCP server to Cloudflare Workers, start from Cloudflare's remote-mcp-authless template, test it locally with wrangler dev and the MCP Inspector, then run npx wrangler@latest deploy. Your server goes live at https://<name>.<account>.workers.dev/mcp, and any client that speaks Streamable HTTP can connect.
The method changed in 2026. Cloudflare's docs now build servers with createMcpHandler and the official MCP TypeScript SDK v2. The older McpAgent class, used in most tutorials from 2025, is deprecated and feature-frozen. This guide follows the current path and explains how to move off the old one.
Checked against: Cloudflare's Build a Remote MCP server guide and MCP Server (createMcpHandler) reference, both last updated 27 October 2026, and npm on 2 November 2026. The template ran locally with wrangler dev on 2 November 2026 (Node.js 24, wrangler 4.156.2, agents 0.29.0, @modelcontextprotocol/server 2.3.1). Nothing was deployed to a Cloudflare account.
Key Takeaways
- Current Cloudflare MCP servers use
createMcpHandlerfromagents/mcp/serverplus anMcpServerfrom@modelcontextprotocol/server(SDK v2). - The handler is stateless: it builds a fresh server per request, so you need no Durable Object or session.
McpAgentis deprecated and feature-frozen. Cloudflare says to migrate "at your earliest convenience."@modelcontextprotocol/serveris an optional peer dependency ofagents; install the exact version youragentsrelease expects.npx wrangler@latest deploypublishes to yourworkers.devsubdomain at the/mcppath.- The template has no authentication. Add Workers OAuth Provider before you expose anything private.
Prerequisites
- A Cloudflare account for the deploy step. Local development works without signing in.
- Node.js and npm. The local run for this guide used Node.js 24.
- Basic TypeScript. The server code is a single
src/index.tsfile.
New to remote servers? Lesson 21 of our MCP course explains what changes when a server moves from your laptop to a URL, and the full course starts from the basics.
Step 1: Create the project from the template
Cloudflare's guide gives two routes. The dashboard route uses a "Deploy to Workers" button that also creates a GitHub or GitLab repository and redeploys on every push to main. The CLI route gives you a local project first:
npm create cloudflare@latest -- remote-mcp-server-authless --template=cloudflare/ai/demos/remote-mcp-authless
cd remote-mcp-server-authless
The guide suggests answering "No" to the AGENTS.md, git and deploy prompts so you can test before deploying.
Step 2: Read the server code
The template's src/index.ts, as published in the cloudflare/ai repository on 2 November 2026, has two calculator tools. Here it is with one tool kept:
import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({
name: "Authless Calculator",
version: "1.0.0",
});
server.registerTool(
"add",
{ inputSchema: z.object({ a: z.number(), b: z.number() }) },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
}),
);
return server;
}
const handler = createMcpHandler(createServer);
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
return handler(request, env, ctx);
},
} satisfies ExportedHandler<Env>;
What matters here:
createServer()is a factory. The handler reference says to "pass the factory itself" and not to build one global server. Each request gets a freshMcpServer.registerTool(name, config, handler)is the SDK v2 way to add a tool. TheinputSchemais a fullz.object(...). Our registerTool guide covers the options in depth.- The default route is
/mcp. Change it with therouteoption. - State lives elsewhere. Because the handler is stateless, Cloudflare's guide says to keep data that must outlive a request in KV, D1 or a Durable Object.
The template's wrangler.jsonc is short: a name, main: "src/index.ts", a compatibility date, the nodejs_compat flag and observability turned on.
Step 3: Check your package versions
This is the step most likely to bite you. The handler reference says @modelcontextprotocol/server "is an optional peer dependency of agents, so your package manager does not install it for you. Use the exact version required by your installed Agents release."
On 2 November 2026 the versions did not all line up:
| Source | agents | @modelcontextprotocol/server |
|---|---|---|
Template package.json | ^0.29.0 | 2.3.1 |
| Cloudflare install command in docs | latest | 2.3.1 |
npm latest (npm view) | 0.32.0 | 2.4.0 |
| Peer dependency declared by agents 0.32.0 | n/a | 2.4.0 (exact) |
The template install resolved to agents 0.29.0 with server 2.3.1, and that pair worked. If you upgrade agents, run npm view agents peerDependencies and match the @modelcontextprotocol/server version exactly.
Step 4: Run and test it locally
Start the local server:
npm start
This runs wrangler dev. Cloudflare's guide shows it ready on port 8788, so the MCP endpoint is http://localhost:8788/mcp. Opening that URL in a browser does nothing useful; the guide notes the endpoint expects MCP messages. A plain GET returned HTTP 405 in the local run.
Test with the MCP Inspector instead. The browser UI runs with npx @modelcontextprotocol/inspector@latest; paste the URL and select Connect. The Inspector CLI is faster for a quick check:
npx @modelcontextprotocol/inspector --cli http://localhost:8788/mcp \
--method tools/call --tool-name calculate \
--tool-args-json '{"operation":"divide","a":84,"b":2}' --format json
In the local run on 2 November 2026 (Inspector 2.13.0), that returned {"result":{"content":[{"type":"text","text":"42"}]}}. An initialize call with --protocol-era modern reported "protocolVersion":"2026-07-28", and tools/list with --protocol-era legacy returned the same tools. So one endpoint served both protocol eras.
Our Inspector lesson explains the rest of the Inspector's views.
Step 5: Deploy
From the project folder:
npx wrangler@latest deploy
Wrangler asks you to sign in the first time. Per Cloudflare's guide, the server then lives at https://remote-mcp-server-authless.your-account.workers.dev/mcp. If you connected a Git repository to the Worker, pushing to main deploys instead.
Point the Inspector at the deployed URL to confirm it works, then connect a real client.
Step 6: Connect a client
Clients with Streamable HTTP support connect straight to the URL. The guide's Claude Code example:
claude mcp add --transport http math https://remote-mcp-server-authless.your-account.workers.dev/mcp
Run claude mcp list to confirm the connection. The Test a Remote MCP Server page gives the Cursor form ("url" under mcpServers in .cursor/mcp.json) and also lists Cloudflare's Workers AI Playground as a test client. Our MCP config generator writes these blocks for other clients.
Step 7: Add authentication
The template accepts any caller. Cloudflare's guide describes a two-Worker setup built with Workers OAuth Provider (npm @cloudflare/workers-oauth-provider, 1.2.4 on 2 November 2026):
- An authorization server Worker built with
OAuthAuthorizationServer. Its/authorizepage signs users in directly or through GitHub, Google, Cloudflare Access or another identity provider. - Your MCP Worker wraps
createMcpHandlerin anOAuthResourceServer, which checks each request's token with the authorization server over a Service Binding.
Clients find the authorization server from your server's 401 response, sign the user in and retry with a token. Inside a tool, getMcpAuthContext() returns the signed-in user. Cloudflare's Authorization page has both Workers in full.
Two more settings from the handler reference apply to custom domains. CORS headers are "not authentication." And for a custom domain with wildcard CORS, set allowedHostnames and allowedOriginHostnames explicitly, because the default allowlist covers only localhost and your workers.dev hostname.
Migrating from McpAgent
If your server extends McpAgent and calls MyMCP.serve("/mcp"), it still runs, but the McpAgent reference says the class "remains available only for existing legacy servers while they migrate." It also notes an SDK v2 server cannot run inside McpAgent.
The Migrate to MCP SDK v2 guide splits the work by what your server uses:
| Current server | Path |
|---|---|
| McpAgent without stateful features | Move to an SDK v2 factory and createMcpHandler directly |
| McpAgent with stateful features | Build stateless equivalents, serve both lanes, then drain the legacy one |
| SDK v1 server, no session features | Move the definition to an SDK v2 factory |
| SDK v1 server with session features | Add an SDK v2 route; keep createLegacyMcpHandler only temporarily |
"Stateful features" means protocol sessions, transport storage or replay, standalone GET streams, pushed elicitation, sampling or roots requests, and session deletion. If you use none of them, the move is mostly a rewrite of the tool registration.
Common pitfalls
- Copying 2025 tutorials. Code with
extends McpAgent,this.server.tool(...)or an/sseroute is the old pattern. New servers usecreateMcpHandlerandregisterTool. - Passing a server instead of a factory.
createMcpHandler(new McpServer(...))is wrong. PasscreateServer. - Unpinned MCP versions. Keep
@modelcontextprotocol/serverat the exact version youragentsrelease declares. - Expecting sessions. In legacy compatibility mode, the reference says each POST creates a new server, GET and DELETE return 405, and pushed elicitation or sampling requests fail. Elicitation works through the stateless input-required flow instead.
- Shipping without auth. Fine for a calculator demo, not for anything that touches company data.
For a side-by-side look at hosting options, read Cloudflare Workers vs Vercel for MCP Hosting. If you want to use Cloudflare's own managed servers rather than build one, see our Cloudflare MCP server profile.
Next step
Once the authless version works, add OAuth and a real data source, then put it in front of a few internal users. If you would like help planning tools, scopes and hosting for a production server, our MCP development team builds remote servers for businesses.
More in the MCP Guide
Free tools: server.json Validator · MCP Error Code Lookup
Latest research: State of MCP: October 2026
Frequently asked questions
Is McpAgent still supported on Cloudflare?
It still runs, but Cloudflare marks it deprecated and feature-frozen as of Agents SDK v0.20.0. No removal version is announced. New servers should use createMcpHandler with MCP SDK v2.
Do I need a Durable Object for an MCP server on Cloudflare?
Not with createMcpHandler. Cloudflare's reference says the handler builds a fresh server for every request, so no Durable Object, protocol session or WebSocket is needed. Use KV, D1 or a Durable Object only for application data that must persist.
Does a Cloudflare MCP server work with older MCP clients?
By default, yes. The handler's legacy: "stateless" setting accepts ordinary legacy tools, prompts and resources, without session features. Set legacy: "reject" if you want a modern-only endpoint.
Does the SSE transport still work on Cloudflare?
The current template serves Streamable HTTP at /mcp only. The MCP spec reclassified HTTP+SSE as deprecated in 2026-07-28, so build new servers on Streamable HTTP.
What does it cost to host an MCP server on Workers?
It depends on your Workers plan and traffic. Check Cloudflare's Workers pricing page for current limits before you launch; this guide does not cover pricing.











