Skip to content
API Reference
Guides
MCP Servers

Deploy an MCP Server on Cloudflare Workers

Deploy a Keycard-protected MCP server on Cloudflare Workers with JWT verification, token exchange, and isolate-safe caching

Cloudflare Workers give you edge-deployed MCP servers with zero infrastructure management. But Workers have a unique constraint: isolates are reused across requests, which means naive module-level caches leak tokens between users.

The @keycardai/cloudflare package handles this. It adapts Keycard’s auth primitives to Workers’ fetch(request, env) model — real JWT verification, OAuth metadata endpoints, and per-user token caching that’s isolate-safe by design.

This guide walks through deploying a Worker with delegated API access. The full working example is in the TypeScript SDK.

flowchart LR
    A[AI Agent] -->|"Bearer JWT"| B[Your Worker]
    B -->|"Verify JWT"| C[Keycard JWKS]
    B -->|"Exchange token"| D[Keycard STS]
    D -->|"Upstream token"| B
    B -->|"Bearer upstream"| E[External API]
  1. AI agent sends a request with a Keycard JWT
  2. Worker verifies the JWT against Keycard’s JWKS endpoint
  3. Worker exchanges the JWT for an upstream API token via Keycard STS
  4. Worker calls the external API with the exchanged token

The createKeycardWorker() wrapper handles steps 1-2 automatically. You handle 3-4 using IsolateSafeTokenCache.

  • Cloudflare account with wrangler CLI installed
  • Keycard account with a configured zone and identity provider
  • Completed the Build an MCP Server guide (for Keycard Console concepts)
  1. Register the upstream API

    If using the Resource Catalog (GitHub, Google, etc.), add it there. Otherwise create a provider and resource manually for your API.

  2. Register your Worker as a resource

    Navigate to ResourcesCreate Resource:

    FieldValue
    Resource NameMy Worker MCP Server
    Resource Identifierhttps://your-worker.your-subdomain.workers.dev
    Credential ProviderZone Provider
    Scopesmcp:tools
  3. Add the upstream API as a dependency

    Go to the resource details → Dependencies tab → Connect Resource → select your upstream API resource.

  4. Create application credentials

    Navigate to ApplicationsCreate Application:

    FieldValue
    Provided ResourceYour Worker MCP Server
    DependencyYour upstream API resource

    Generate client credentials and save the Client ID and Client Secret.

Install dependencies:

Terminal window
npm install @keycardai/cloudflare @keycardai/oauth @modelcontextprotocol/sdk
npm install -D @cloudflare/workers-types typescript wrangler

createKeycardWorker() handles OAuth metadata, CORS, and JWT verification. Your handler only runs for authenticated requests:

src/index.ts
import { createKeycardWorker, IsolateSafeTokenCache, resolveCredential } from "@keycardai/cloudflare";
import { TokenExchangeClient } from "@keycardai/oauth/tokenExchange";
interface Env {
KEYCARD_ISSUER: string;
KEYCARD_CLIENT_ID?: string;
KEYCARD_CLIENT_SECRET?: string;
KEYCARD_PRIVATE_KEY?: string;
KEYCARD_RESOURCE_URL: string;
}
// Module-level cache is safe — keyed by user identity, not shared
let tokenCache: IsolateSafeTokenCache;
function getCache(env: Env) {
if (!tokenCache) {
const credential = resolveCredential(env);
const client = new TokenExchangeClient(env.KEYCARD_ISSUER, credential.getAuth() ?? undefined);
tokenCache = new IsolateSafeTokenCache(client, { credential });
}
return tokenCache;
}
export default createKeycardWorker<Env>({
resourceName: "My Worker MCP Server",
scopesSupported: ["mcp:tools"],
requiredScopes: ["mcp:tools"],
async fetch(request, env, ctx, auth) {
// auth.subject is the verified user identity
// auth.token is the raw JWT for token exchange
const cache = getCache(env);
const upstream = await cache.getToken(auth.subject!, auth.token, env.KEYCARD_RESOURCE_URL);
// Use upstream.accessToken to call your API
// Register MCP tools, handle routes, etc.
// ...
},
});
wrangler.jsonc
{
"name": "my-mcp-worker",
"main": "src/index.ts",
"compatibility_date": "2025-04-01",
"compatibility_flags": ["nodejs_compat"],
"vars": {
"KEYCARD_ISSUER": "https://your-zone-id.keycard.cloud",
"KEYCARD_RESOURCE_URL": "https://api.github.com"
}
}

You can authenticate your Worker with Keycard using either method:

Store the client ID and secret from Keycard Console:

Terminal window
wrangler secret put KEYCARD_CLIENT_ID
wrangler secret put KEYCARD_CLIENT_SECRET

Generate a private key — the Worker serves its public key automatically at /.well-known/jwks.json:

Terminal window
openssl genrsa 2048 | wrangler secret put KEYCARD_PRIVATE_KEY

Register the Worker’s JWKS URL (https://your-worker.workers.dev/.well-known/jwks.json) in Keycard Console as the application’s public key endpoint.

createKeycardWorker auto-detects which mode to use from env.

  1. Deploy

    Terminal window
    wrangler deploy
  2. Set secrets (Option A or B from above)

  3. Connect from Cursor/Claude Desktop

    Add to your MCP settings:

    {
    "mcpServers": {
    "my-worker": {
    "url": "https://your-worker.your-subdomain.workers.dev/mcp"
    }
    }
    }
  4. Verify in Audit Logs

    Check Keycard Console Audit Logs for credentials:issue events showing the identity chain (user + application).

The complete working example — with MCP tool registration, token exchange, and both credential modes — is in the TypeScript SDK:

examples/cloudflare-worker

  • Verify KEYCARD_ISSUER matches your zone URL exactly (including https://)
  • Check that your Worker’s URL matches the resource identifier in Keycard Console
  • Verify the upstream API resource is added as a dependency on your Application
  • Check that client credentials (or private key) are set correctly as Worker secrets
  • Ensure the user completed the OAuth consent flow for the upstream API
  • Set either KEYCARD_CLIENT_ID + KEYCARD_CLIENT_SECRET or KEYCARD_PRIVATE_KEY via wrangler secret put