Skip to main content
Glama
AayushCharde

mcp-worker-template

by AayushCharde

mcp-worker-template

An MCP server on Cloudflare Workers with zero runtime dependencies. No SDK, no framework — a hand-rolled Streamable HTTP transport in ~190 lines you can read in one sitting, plus one file where your tools live.

git clone https://github.com/AayushCharde/mcp-worker-template my-mcp-server
cd my-mcp-server && npm install && cp .dev.vars.example .dev.vars
npm run dev   # → http://localhost:8787/mcp

Test it:

curl -s http://localhost:8787/mcp \
  -H 'Authorization: Bearer dev-token-change-me' \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"echo","arguments":{"message":"hello"}}}'

Connect it to Claude Code:

claude mcp add --transport http my-server http://localhost:8787/mcp \
  --header "Authorization: Bearer dev-token-change-me"

Why no SDK?

Most MCP server examples pull in the official SDK, an HTTP framework, and a session layer. For a tools-only server, the protocol surface is four methods: initialize, tools/list, tools/call, ping. That's small and stable enough to own outright:

  • Zero runtime dependencies — nothing to update, nothing to audit, no bundle bloat. The Worker is two source files.

  • Nothing is magic — when a client misbehaves, you read your own 190-line transport, not a stack trace through someone else's abstraction.

  • Stateless by design — no sessions, no SSE stream, no Durable Objects. Every request is self-contained, which is exactly the shape Cloudflare Workers want.

The trade-off: no server→client notifications, resources, or prompts. If you need those, use the official SDK — this template is for the (very common) case where you just want tools.

Related MCP server: Remote MCP Server (Authless)

Adding a tool

Everything you touch is in src/tools.ts. A tool is a name, a description, a JSON Schema for its input, and a run function:

{
  name: 'get_weather',
  description: 'Current weather for a city.',
  inputSchema: {
    type: 'object',
    properties: { city: { type: 'string' } },
    required: ['city'],
    additionalProperties: false
  },
  async run(args, { env }) {
    const res = await fetch(`https://api.example.com/weather?q=${args.city}`);
    if (!res.ok) throw new ToolError(`Weather API returned ${res.status}`);
    return res.json();
  }
}

Throw ToolError for failures the client should see verbatim; any other exception is masked as a generic internal error so implementation details never leak. Need bindings (KV, D1, a database URL)? Add them to Environment in src/index.ts — they arrive in every tool via ctx.env.

Auth model — read this before deploying

Auth is a single static bearer token (MCP_BEARER_TOKEN), compared in constant time. That token is the entire trust boundary: anyone who has it can call every tool. This is the right shape for a personal server or an internal integration; it is not multi-tenant. If different users need different permissions, you need OAuth (see the MCP authorization spec) — at which point the official SDK starts earning its keep.

openssl rand -hex 32                      # generate a real token
npx wrangler secret put MCP_BEARER_TOKEN  # set it in production

Deploy

npm run check     # wrangler types + tsc --noEmit
npm run deploy    # → https://my-mcp-server.<your-subdomain>.workers.dev/mcp

Rename the worker in wrangler.jsonc first.

Transport details (for the curious)

The spec-relevant behavior, all in src/index.ts:

Behavior

Implementation

Protocol versions

Negotiates 2025-06-18 / 2025-03-26 / 2024-11-05 — echoes the client's version if supported, else offers the newest

Notifications

No response body; HTTP 202 Accepted

JSON-RPC batches

Accepted; responses filtered of notification slots

GET /mcp

405 Method Not Allowed — stateless server, no SSE stream to offer

Unknown method

-32601; unknown tool -32602; parse failure -32700

CORS

Permissive by default (*) — tighten Access-Control-Allow-Origin if a browser client will hold your token

Real-world example

This transport was extracted from Junto, a keyboard-first task tracker whose MCP server lets Claude list, create, and update tasks in a live Postgres database — see apps/mcp there for what a production instance of this pattern looks like (per-request DB clients, workspace scoping, activity logging).

License

MIT © Aayush Charde

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    A template for deploying authentication-free MCP servers on Cloudflare Workers. Enables quick deployment of custom tools accessible via SSE from MCP clients like Claude Desktop or Cloudflare AI Playground.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    A template for deploying an authentication-free remote MCP server on Cloudflare Workers. Enables quick deployment of custom tools accessible from MCP clients like Claude Desktop or the Cloudflare AI Playground via SSE endpoints.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    A template for deploying an authentication-free MCP server on Cloudflare Workers. Provides a foundation for building custom tools accessible via SSE from MCP clients like Claude Desktop or the Cloudflare AI Playground.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    A template for deploying an authentication-free MCP server on Cloudflare Workers. Enables custom tool creation and connection from MCP clients like Claude Desktop or the Cloudflare AI Playground.
    -