Skip to main content
xmcp supports deployment to Cloudflare Workers with a specialized build process that creates a fully-bundled, edge-ready worker.

Quick Start

1. Build for Cloudflare

xmcp build --cf
This generates:
  • worker.js - Bundled Cloudflare Worker in your project root
  • wrangler.jsonc - Wrangler configuration (if not already present)

2. Test Locally

npx wrangler dev

3. Deploy

npx wrangler deploy

How It Works

The --cf flag triggers specialized Cloudflare bundling:
// From: packages/xmcp/src/platforms/build-cloudflare-output.ts

async function buildCloudflareOutput(options = {}) {
  const outputDir = rootDir;
  const buildDir = path.join(rootDir, ".xmcp", "cloudflare");

  // Check if Cloudflare worker build exists
  const sourceFile = path.join(buildDir, "worker.js");
  if (!fs.existsSync(sourceFile)) {
    throw new Error("Cloudflare build output not found. Run: xmcp build --cf");
  }

  // Copy the bundled worker
  const targetFile = path.join(outputDir, "worker.js");
  fs.copyFileSync(sourceFile, targetFile);

  // Generate wrangler config (only if the user doesn't have one already)
  const wranglerTomlPath = path.join(outputDir, "wrangler.toml");
  const wranglerJsoncPath = path.join(outputDir, "wrangler.jsonc");
  if (!fs.existsSync(wranglerTomlPath) && !fs.existsSync(wranglerJsoncPath)) {
    const projectName = getProjectName();
    const wranglerConfig = generateWranglerConfig(projectName);
    fs.writeFileSync(wranglerJsoncPath, wranglerConfig);
  }
}

Generated wrangler.jsonc

wrangler.jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  // Wrangler config generated by: xmcp build --cf
  // Docs: https://developers.cloudflare.com/workers/wrangler/configuration/
  "name": "your-project-name",
  "main": "worker.js",
  "compatibility_date": "2026-02-28",
  "compatibility_flags": ["nodejs_compat"],

  // Observability (Workers Logs)
  "observability": {
    "enabled": true
  }

  // Uncomment to add environment variables:
  // "vars": {
  //   "MY_VAR": "my-value"
  // },

  // Uncomment to add KV namespaces:
  // "kv_namespaces": [
  //   { "binding": "MY_KV", "id": "your-kv-namespace-id" }
  // ],

  // Uncomment to add D1 databases:
  // "d1_databases": [
  //   { "binding": "MY_DB", "database_name": "my-database", "database_id": "your-database-id" }
  // ]
}

Development Workflow

For rapid development, use the watch mode:
xmcp dev --cf
This rebuilds worker.js whenever your source files change. Run this alongside wrangler dev:
# Terminal 1: Watch and rebuild
xmcp dev --cf

# Terminal 2: Run local worker
npx wrangler dev
Or use concurrently to run both commands together:
package.json
{
  "scripts": {
    "dev": "concurrently \"xmcp dev --cf\" \"npx wrangler dev\"",
    "build": "xmcp build --cf",
    "deploy": "npx wrangler deploy"
  },
  "devDependencies": {
    "concurrently": "^9.2.0",
    "wrangler": "^4.62.0"
  }
}

Complete Example

Here’s a real working example from the xmcp repository:
import { XmcpConfig } from "xmcp";

const config: XmcpConfig = {
  http: {
    debug: true,
  },
  paths: {
    tools: "./src/tools",
    prompts: false,
    resources: false,
  },
};

export default config;

Testing Your Worker

Once wrangler dev is running (default port: 8787), test your endpoints:

Health Check

curl http://localhost:8787/health

List Tools

curl -X POST http://localhost:8787/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

Call a Tool

curl -X POST http://localhost:8787/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"hello","arguments":{"name":"World"}},"id":2}'

Cloudflare-Specific Features

xmcp exports Cloudflare-optimized middleware through the xmcp/cloudflare package.

API Key Authentication

xmcp.config.ts
import { XmcpConfig } from "xmcp";
import { apiKeyAuthMiddleware } from "xmcp/cloudflare";

const config: XmcpConfig = {
  http: {
    middlewares: [
      apiKeyAuthMiddleware({
        apiKey: "your-secret-key",
        headerName: "x-api-key", // optional, defaults to 'x-api-key'
      }),
    ],
  },
};

export default config;
// From: packages/xmcp/src/runtime/platforms/cloudflare/middlewares/api-key.ts

export function cloudflareApiKeyAuthMiddleware(
  config: { apiKey: string; headerName?: string }
): WebMiddleware {
  const headerName = config.headerName ?? "x-api-key";
  
  return async (request) => {
    const apiKeyHeader = request.headers.get(headerName);
    if (!apiKeyHeader || apiKeyHeader !== config.apiKey) {
      return new Response(
        JSON.stringify({ error: "Unauthorized: Missing or invalid API key" }),
        { status: 401, headers: { "Content-Type": "application/json" } }
      );
    }
    return; // Allow request to continue
  };
}

JWT Authentication

xmcp.config.ts
import { XmcpConfig } from "xmcp";
import { jwtAuthMiddleware } from "xmcp/cloudflare";

const config: XmcpConfig = {
  http: {
    middlewares: [
      jwtAuthMiddleware({
        secret: "your-jwt-secret",
        algorithms: ["HS256"],
        issuer: "your-issuer",
        audience: "your-audience",
      }),
    ],
  },
};

export default config;
// From: packages/xmcp/src/runtime/platforms/cloudflare/middlewares/jwt.ts

import { jwtVerify, importJWK, importSPKI, importX509 } from "jose";

export function cloudflareJwtAuthMiddleware(
  config: CloudflareJWTAuthMiddlewareConfig
): WebMiddleware {
  return async (request) => {
    const authHeader = request.headers.get("authorization");
    const token = extractBearerToken(authHeader);
    
    if (!token) {
      return jsonUnauthorized(
        "Unauthorized: Missing or malformed Authorization header"
      );
    }

    try {
      await verifyJwt(token, config);
      return; // Valid token
    } catch {
      return jsonUnauthorized("Unauthorized: Invalid or expired token");
    }
  };
}
The JWT middleware supports multiple secret formats:
  • Plain string: HMAC secrets (HS256, HS384, HS512)
  • JWK: JSON Web Key format
  • PEM: Public keys (RS256, ES256, etc.)
  • X.509: Certificate format

Environment Variables

Configure secrets and environment variables in wrangler.jsonc:
wrangler.jsonc
{
  "name": "my-xmcp-worker",
  "main": "worker.js",
  "compatibility_date": "2026-02-28",
  "compatibility_flags": ["nodejs_compat"],
  
  "vars": {
    "ENVIRONMENT": "production"
  },
  
  // For secrets, use: wrangler secret put API_KEY
  // Then access via: env.API_KEY in your middleware
}
Set secrets using Wrangler CLI:
wrangler secret put API_KEY
wrangler secret put JWT_SECRET
Never commit secrets to wrangler.jsonc. Use wrangler secret put or environment-specific configurations.

Cloudflare Bindings

Access Cloudflare platform features:

KV Storage

wrangler.jsonc
{
  "kv_namespaces": [
    { "binding": "MY_KV", "id": "your-namespace-id" }
  ]
}

D1 Database

wrangler.jsonc
{
  "d1_databases": [
    { 
      "binding": "DB",
      "database_name": "my-database",
      "database_id": "your-database-id"
    }
  ]
}

R2 Storage

wrangler.jsonc
{
  "r2_buckets": [
    { "binding": "MY_BUCKET", "bucket_name": "my-bucket" }
  ]
}

Limitations

Cloudflare Workers have some constraints:
  • CPU Time: 50ms on free plan, 30s on paid
  • Memory: 128 MB
  • Script Size: 1 MB compressed, 10 MB uncompressed
  • No Filesystem: Use KV, R2, or D1 for persistence
The --cf build process handles these by:
  • Bundling all dependencies into a single file
  • Inlining React component bundles at compile time
  • Using only Web APIs (no Node.js runtime dependencies)
  • Polyfilling necessary Node.js APIs

Troubleshooting

”nodejs_compat” Required

If you see errors about Node.js APIs, ensure your wrangler.jsonc includes:
"compatibility_flags": ["nodejs_compat"]

Build Output Not Found

If you see:
Cloudflare build output not found. Run: xmcp build --cf
Ensure you’re running xmcp build --cf, not just xmcp build.

Worker Too Large

If your worker exceeds size limits:
  1. Review dependencies—remove unused packages
  2. Use Cloudflare bindings (KV, D1) instead of bundling data
  3. Enable code splitting if possible

Next Steps

Vercel Deployment

Deploy to Vercel instead

Self-Hosting

Run on your own infrastructure

Middleware

Add custom middleware

Configuration

Configure your xmcp server

Build docs developers (and LLMs) love