Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Builds remote MCP (Model Context Protocol) servers on Cloudflare Workers with tools, OAuth authentication, and production deployment. Generates server code, configures auth providers, and deploys to Workers. Use when: user wants to \"build MCP server\", \"create MCP tools\", \"remote MCP\", \"deploy MCP\", add \"OAuth to MCP\", or mentions Model Context Protocol on Cloudflare. Also triggers on \"
.claude/skills/kunanonj-cursor-plugin-cf-building-mcp-server-on-cloudflare/SKILL.md| Model | Eval pass | Runs |
|---|---|---|
| gemini-3.6-flashlowest | 93% | 15 |
| gemini-3.1-pro-preview | 100% | 2 |
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | 23% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 19% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 39% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 73% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 6% | 0% |
Creates production-ready Model Context Protocol servers on Cloudflare Workers with tools, authentication, and deployment.
npm install -g wrangler)bashnpm create cloudflare@latest -- my-mcp-server \ --template=cloudflare/ai/demos/remote-mcp-authless cd my-mcp-server npm start
Server runs at http://localhost:8788/mcp
bashnpm create cloudflare@latest -- my-mcp-server \ --template=cloudflare/ai/demos/remote-mcp-github-oauth cd my-mcp-server
Requires OAuth app setup. See references/oauth-setup.md.
Tools are functions MCP clients can call. Define them using server.tool():
typescriptimport { McpAgent } from "agents/mcp"; import { z } from "zod"; export class MyMCP extends McpAgent { server = new Server({ name: "my-mcp", version: "1.0.0" }); async init() { // Simple tool with parameters this.server.tool( "add", { a: z.number(), b: z.number() }, async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }], }) ); // Tool that calls external API this.server.tool( "get_weather", { city: z.string() }, async ({ city }) => { const response = await fetch(`https://api.weather.com/${city}`); const data = await response.json(); return { content: [{ type: "text", text: JSON.stringify(data) }], }; } ); } }
Public server (src/index.ts):
typescriptimport { MyMCP } from "./mcp"; export default { fetch(request: Request, env: Env, ctx: ExecutionContext) { const url = new URL(request.url); if (url.pathname === "/mcp") { return MyMCP.serveSSE("/mcp").fetch(request, env, ctx); } return new Response("MCP Server", { status: 200 }); }, }; export { MyMCP };
Authenticated server — See references/oauth-setup.md.
bash# Start server npm start # In another terminal, test with MCP Inspector npx @modelcontextprotocol/inspector@latest # Open http://localhost:5173, enter http://localhost:8788/mcp
bashnpx wrangler deploy
Server accessible at https://[worker-name].[account].workers.dev/mcp
Claude Desktop (claude_desktop_config.json):
json{ "mcpServers": { "my-server": { "command": "npx", "args": ["mcp-remote", "https://my-mcp.workers.dev/mcp"] } } }
Restart Claude Desktop after updating config.
typescript// Text response return { content: [{ type: "text", text: "result" }] }; // Multiple content items return { content: [ { type: "text", text: "Here's the data:" }, { type: "text", text: JSON.stringify(data, null, 2) }, ], };
typescriptthis.server.tool( "create_user", { email: z.string().email(), name: z.string().min(1).max(100), role: z.enum(["admin", "user", "guest"]), age: z.number().int().min(0).optional(), }, async (params) => { // params are fully typed and validated } );
typescriptexport class MyMCP extends McpAgent<Env> { async init() { this.server.tool("query_db", { sql: z.string() }, async ({ sql }) => { // Access D1 binding const result = await this.env.DB.prepare(sql).all(); return { content: [{ type: "text", text: JSON.stringify(result) }] }; }); } }
For OAuth-protected servers, see references/oauth-setup.md.
Supported providers:
Minimal wrangler.toml:
tomlname = "my-mcp-server" main = "src/index.ts" compatibility_date = "2024-12-01" [durable_objects] bindings = [{ name = "MCP", class_name = "MyMCP" }] [[migrations]] tag = "v1" new_classes = ["MyMCP"]
With bindings (D1, KV, etc.):
toml[[d1_databases]] binding = "DB" database_name = "my-db" database_id = "xxx" [[kv_namespaces]] binding = "KV" id = "xxx"
init() registers tools before connectionswrangler tail/mcpwrangler deployments listGITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET are sethttp://localhost:8788/callback| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | fail→pass | 10,721 | 2,573 | -76% | 1 | 1 | 0% | 1,681 | 2,074 | +23% | 0 | 0 | — |
case-02 | fail→pass | 13,720 | 6,498 | -53% | 1 | 1 | 0% | 2,505 | 2,990 | +19% | 0 | 0 | — |
case-03 | fail→pass | 13,521 | 10,019 | -26% | 1 | 1 | 0% | 2,512 | 3,504 | +39% | 0 | 0 | — |
case-04 | fail→pass | 6,972 | 2,212 | -68% | 1 | 1 | 0% | 1,171 | 2,027 | +73% | 0 | 0 | — |
case-01 | fail→pass | 21,805 | 13,189 | -40% | 1 | 1 | 0% | 4,250 | 4,500 | +6% | 0 | 0 | — |
case-06 | fail→pass | 6,434 | 2,350 | -63% | 1 | 1 | 0% | 1,034 | 2,045 | +98% | 0 | 0 | — |
case-07 | fail→pass | 16,490 | 7,729 | -53% | 1 | 1 | 0% | 3,041 | 3,160 | +4% | 0 | 0 | — |
case-08 | pass→pass | 9,962 | 4,774 | -52% | 1 | 1 | 0% | 1,753 | 2,558 | +46% | 0 | 0 | — |
case-09 | fail→pass | 13,613 | 7,511 | -45% | 1 | 1 | 0% | 2,390 | 3,117 | +30% | 0 | 0 | — |
case-10 | pass→pass | 9,449 | 4,215 | -55% | 1 | 1 | 0% | 1,646 | 2,431 | +48% | 0 | 0 | — |
case-11 | pass→pass | 10,493 | 6,451 | -39% | 1 | 1 | 0% | 1,819 | 2,836 | +56% | 0 | 0 | — |
case-12 | fail→pass | 5,654 | 4,866 | -14% | 1 | 1 | 0% | 1,100 | 2,625 | +139% | 0 | 0 | — |
case-13 | pass→pass | 7,234 | 4,436 | -39% | 1 | 1 | 0% | 1,296 | 2,411 | +86% | 0 | 0 | — |
case-14 | fail→pass | 5,285 | 2,103 | -60% | 1 | 1 | 0% | 847 | 2,070 | +144% | 0 | 0 | — |
case-15 | fail→pass | 13,105 | 2,600 | -80% | 1 | 1 | 0% | 1,990 | 2,070 | +4% | 0 | 0 | — |
case-16 | fail→pass | 3,791 | 2,407 | -37% | 1 | 1 | 0% | 667 | 2,048 | +207% | 0 | 0 | — |
case-17 | fail→pass | 13,796 | 6,350 | -54% | 1 | 1 | 0% | 2,073 | 2,691 | +30% | 0 | 0 | — |
case-18 | pass→pass | 6,405 | 3,128 | -51% | 1 | 1 | 0% | 955 | 2,240 | +135% | 0 | 0 | — |
case-19 | pass→pass | 9,765 | 4,980 | -49% | 1 | 1 | 0% | 1,750 | 2,615 | +49% | 0 | 0 | — |
case-20 | pass→pass | 9,633 | 8,010 | -17% | 1 | 1 | 0% | 1,931 | 3,377 | +75% | 0 | 0 | — |
case-21 | fail→pass | 10,780 | 7,059 | -35% | 1 | 1 | 0% | 2,128 | 3,078 | +45% | 0 | 0 | — |
case-22 | pass→pass | 18,899 | 10,723 | -43% | 1 | 1 | 0% | 3,504 | 4,120 | +18% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +64 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.