Stateless MCP 2.0: Deploying Serverless Tools on Cloudflare & Railway

Complete implementation guide to the MCP 2.0 stateless transport: deploy serverless tool endpoints on Cloudflare Workers and Railway with zero idle cost.

August 18, 2026
mcpmodel-context-protocolserverlesscloudflare-workersrailwaymcp-2.0

The original Model Context Protocol (MCP 1.0) was designed around stateful local subprocesses running over stdio or persistent HTTP + Server-Sent Events (SSE) connections. While this model worked well for local developer tools like Cursor, Claude Desktop, and VS Code, it created severe bottlenecks for production cloud infrastructure: persistent idle connection costs, resource-heavy daemon management, fragile reconnect cycles, and severe multi-tenant scaling constraints.

MCP 2.0 introduces standardized Stateless HTTP Transport, enabling developers to deploy Model Context Protocol endpoints onto serverless edge platforms like Cloudflare Workers and containerized serverless runtimes like Railway. With stateless MCP, every tool call or discovery request is an isolated, authenticated HTTP POST transaction with zero idle compute cost, sub-15ms edge routing, and instant horizontal scalability.

This comprehensive engineering guide walks through the architectural shift, complete production code implementations for Cloudflare Workers and Railway, cryptographic authentication, security sandboxing, edge caching, and real-world tool integrations.


1. Architecture Shift: Stateful Stdio vs. MCP 2.0 Stateless Transport

The Problem with Stateful MCP

Traditional MCP setups require an always-on process maintaining a persistent duplex channel with the host client:

  1. Memory & Idle Costs: A cluster of 100 idle stateful MCP servers (e.g., Node.js or Python containers) continuously consumes RAM and CPU cycles waiting for agent invocations.
  2. Connection Fragility: Network blips, container restarts, or load balancer timeouts sever active SSE streams or stdio pipes, crashing agent loops mid-execution.
  3. Multi-Tenant Complexity: Managing dedicated long-lived server processes per user or workspace creates orchestration overhead across Kubernetes or virtual machines.

The MCP 2.0 Stateless Paradigm

Under MCP 2.0 Stateless HTTP Transport, the client-server interaction shifts to an atomic Request/Response lifecycle adhering to standard JSON-RPC 2.0 over HTTP POST:

sequenceDiagram
    autonumber
    participant Agent as AI Agent / LLM Client
    participant Gateway as Edge CDN / Gateway (Cloudflare / Railway)
    participant Worker as Stateless MCP Serverless Worker
    participant Ext as Upstream API / DB / Firecrawl

    Note over Agent, Worker: 1. Tool Discovery (Cached / One-shot)
    Agent->>Gateway: POST /mcp (method: "tools/list", Bearer Token)
    Gateway->>Worker: Spin isolate (<5ms cold start)
    Worker-->>Agent: JSON-RPC Response (Tool definitions schema)
    Note over Worker: Isolate halts immediately (0 idle cost)

    Note over Agent, Worker: 2. Stateless Tool Invocation
    Agent->>Gateway: POST /mcp (method: "tools/call", args: {...})
    Gateway->>Worker: Route request + verify HMAC / JWT
    Worker->>Ext: Fetch external API / Execute isolated task
    Ext-->>Worker: Return raw data
    Worker-->>Agent: JSON-RPC Response (Content block / Tool output)
    Note over Worker: Isolate terminates execution context

Protocol Comparison Matrix

Architectural FeatureMCP 1.0 Stdio (Local)MCP 1.1 HTTP + SSEMCP 2.0 Stateless HTTP (Serverless)
Transport MediumLocal OS Pipes (stdin/stdout)Dual-Endpoint (/sse + /message)Single-Endpoint (POST /mcp)
Connection StatePersistent long-lived processLong-lived SSE stream + stateful session100% Stateless (Request/Response)
Idle Infrastructure CostLocal machine RAMFixed VPS / Container hourly cost$0.00 (Pure scale-to-zero)
Cold Start Latency300ms – 1.5s (Process spawn)N/A (Always-on daemon)< 15ms (V8 Isolate / Edge)
Horizontal ScalabilitySingle-machine onlySticky sessions / Redis pub-subInfinite instant concurrency
Authentication StandardNone (OS user context)Ad-hoc headers or basic authOAuth 2.1 / Bearer JWT / HMAC
Edge CompatibilityIncompatibleDifficult (requires durable streams)Native (Cloudflare, Vercel, Fastly, Railway)

[!IMPORTANT] Stateless Protocol Invariance: In MCP 2.0 Stateless mode, the server does not store client session IDs in local memory between calls. All context required to execute a tool must be passed in the params payload or derived securely from the authenticated Authorization token header.


2. Cloudflare Workers: Zero-Cold-Start MCP 2.0 Endpoint

Cloudflare Workers provides the ideal execution environment for stateless MCP tools: globally distributed V8 isolates, sub-10ms cold starts, and built-in KV caching.

Step 1: Project Setup & Dependencies

Initialize a TypeScript Worker project using wrangler:

npm create cloudflare@latest stateless-mcp-worker -- --type hello-world-ts
cd stateless-mcp-worker
npm install @modelcontextprotocol/sdk zod
npm install -D @cloudflare/workers-types wrangler typescript

Configure wrangler.jsonc:

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "stateless-mcp-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-08-01",
  "compatibility_flags": ["nodejs_compat"],
  "vars": {
    "ENVIRONMENT": "production"
  },
  "kv_namespaces": [
    {
      "binding": "TOOL_CACHE",
      "id": "tool_cache_kv_id"
    }
  ]
}

Step 2: Stateless Server Implementation (src/index.ts)

Here is the complete production-grade stateless MCP 2.0 dispatcher with JSON-RPC error handling, tool schemas, and execution sandboxing:

import { z } from "zod";

export interface Env {
  ENVIRONMENT: string;
  MCP_AUTH_TOKEN: string;
  TOOL_CACHE?: KVNamespace;
}

// JSON-RPC 2.0 Specification Schemas
const JsonRpcRequestSchema = z.object({
  jsonrpc: z.literal("2.0"),
  id: z.union([z.string(), z.number()]),
  method: z.string(),
  params: z.record(z.unknown()).optional(),
});

// Tool Definitions & Schemas
const TOOLS = [
  {
    name: "calculate_compound_interest",
    description: "Calculate compound interest with regular monthly contributions.",
    inputSchema: {
      type: "object",
      properties: {
        principal: { type: "number", description: "Initial investment amount in USD" },
        annualRate: { type: "number", description: "Annual interest rate as percentage (e.g. 7.5 for 7.5%)" },
        years: { type: "integer", description: "Investment duration in years" },
        monthlyContribution: { type: "number", description: "Additional monthly contribution", default: 0 }
      },
      required: ["principal", "annualRate", "years"]
    }
  },
  {
    name: "http_health_ping",
    description: "Perform an edge latency ping to an external HTTPS URL.",
    inputSchema: {
      type: "object",
      properties: {
        url: { type: "string", format: "uri", description: "The HTTPS URL to ping" }
      },
      required: ["url"]
    }
  }
];

// Tool Argument Validators
const InterestArgsSchema = z.object({
  principal: z.number().positive(),
  annualRate: z.number().min(0).max(100),
  years: z.number().int().positive().max(100),
  monthlyContribution: z.number().min(0).default(0),
});

const PingArgsSchema = z.object({
  url: z.string().url().refine((val) => val.startsWith("https://"), {
    message: "Only secure HTTPS URLs are permitted"
  }),
});

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // 1. Handle CORS Preflight
    if (request.method === "OPTIONS") {
      return new Response(null, {
        status: 204,
        headers: getCorsHeaders(),
      });
    }

    // 2. Enforce HTTP POST on /mcp
    const url = new URL(request.url);
    if (url.pathname !== "/mcp" || request.method !== "POST") {
      return new Response(JSON.stringify({ error: "Not Found. Point MCP clients to POST /mcp" }), {
        status: 404,
        headers: { "Content-Type": "application/json", ...getCorsHeaders() },
      });
    }

    // 3. Authenticate Bearer Token
    const authHeader = request.headers.get("Authorization");
    const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : null;
    
    if (env.MCP_AUTH_TOKEN && token !== env.MCP_AUTH_TOKEN) {
      return new Response(
        JSON.stringify({
          jsonrpc: "2.0",
          error: { code: -32000, message: "Unauthorized: Invalid or missing Bearer token" },
          id: null,
        }),
        { status: 401, headers: { "Content-Type": "application/json", ...getCorsHeaders() } }
      );
    }

    // 4. Parse & Validate JSON-RPC Payload
    let body: unknown;
    try {
      body = await request.json();
    } catch {
      return jsonRpcError(null, -32700, "Parse error: Invalid JSON");
    }

    const parsed = JsonRpcRequestSchema.safeParse(body);
    if (!parsed.success) {
      return jsonRpcError(null, -32600, "Invalid Request: Malformed JSON-RPC 2.0 object");
    }

    const { id, method, params } = parsed.data;

    // 5. Route MCP Methods Statelessly
    try {
      switch (method) {
        case "initialize": {
          return jsonRpcSuccess(id, {
            protocolVersion: "2026-08-19",
            capabilities: {
              tools: { listChanged: false },
              logging: {},
            },
            serverInfo: {
              name: "promptgenius-stateless-edge",
              version: "2.0.0",
            },
          });
        }

        case "tools/list": {
          return jsonRpcSuccess(id, { tools: TOOLS });
        }

        case "tools/call": {
          const toolCall = params as { name: string; arguments?: Record<string, unknown> };
          if (!toolCall?.name) {
            return jsonRpcError(id, -32602, "Invalid params: Missing tool name");
          }

          const result = await executeTool(toolCall.name, toolCall.arguments || {}, env);
          return jsonRpcSuccess(id, {
            content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
            isError: false,
          });
        }

        case "ping": {
          return jsonRpcSuccess(id, {});
        }

        default:
          return jsonRpcError(id, -32601, `Method '${method}' not found`);
      }
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : "Internal tool execution error";
      return jsonRpcSuccess(id, {
        content: [{ type: "text", text: `Error: ${message}` }],
        isError: true,
      });
    }
  },
};

// Stateless Tool Execution Logic
async function executeTool(name: string, args: Record<string, unknown>, env: Env): Promise<unknown> {
  switch (name) {
    case "calculate_compound_interest": {
      const valid = InterestArgsSchema.parse(args);
      const r = valid.annualRate / 100 / 12;
      const n = valid.years * 12;
      
      const futureValuePrincipal = valid.principal * Math.pow(1 + r, n);
      const futureValueContributions = valid.monthlyContribution * ((Math.pow(1 + r, n) - 1) / r);
      const totalBalance = futureValuePrincipal + (valid.monthlyContribution > 0 ? futureValueContributions : 0);
      const totalContributed = valid.principal + (valid.monthlyContribution * n);
      const totalInterest = totalBalance - totalContributed;

      return {
        principal: valid.principal,
        totalContributions: totalContributed,
        totalInterestEarned: Math.round(totalInterest * 100) / 100,
        finalPortfolioValue: Math.round(totalBalance * 100) / 100,
        durationMonths: n,
      };
    }

    case "http_health_ping": {
      const valid = PingArgsSchema.parse(args);
      const startTime = performance.now();
      const res = await fetch(valid.url, { method: "HEAD", redirect: "follow" });
      const latencyMs = Math.round(performance.now() - startTime);

      return {
        url: valid.url,
        status: res.status,
        statusText: res.statusText,
        latencyMs,
        edgeLocation: "cloudflare-v8-isolate",
        timestamp: new Date().toISOString(),
      };
    }

    default:
      throw new Error(`Unknown tool: ${name}`);
  }
}

// Helpers for JSON-RPC 2.0 Response Formatting
function jsonRpcSuccess(id: string | number, result: unknown): Response {
  return new Response(
    JSON.stringify({ jsonrpc: "2.0", id, result }),
    { status: 200, headers: { "Content-Type": "application/json", ...getCorsHeaders() } }
  );
}

function jsonRpcError(id: string | number | null, code: number, message: string): Response {
  return new Response(
    JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }),
    { status: 200, headers: { "Content-Type": "application/json", ...getCorsHeaders() } }
  );
}

function getCorsHeaders(): Record<string, string> {
  return {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "POST, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type, Authorization, X-MCP-Signature",
  };
}

Step 3: Local Testing with curl

Test the stateless flow instantly using wrangler dev:

# Start the local edge runtime
npx wrangler dev

# 1. Initialize MCP Handshake
curl -X POST http://localhost:8787/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer dev-secret-token" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'

# 2. List Available Tools
curl -X POST http://localhost:8787/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer dev-secret-token" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# 3. Invoke Tool Statelessly
curl -X POST http://localhost:8787/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer dev-secret-token" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "calculate_compound_interest",
      "arguments": {
        "principal": 10000,
        "annualRate": 8.5,
        "years": 10,
        "monthlyContribution": 500
      }
    }
  }'

3. Containerized Stateless Server on Railway

For workloads requiring native system binaries, Python machine learning runtimes, headless browsers, or heavier execution sandboxes, Railway provides containerized serverless scaling with automated GitHub deployments and sub-50ms warm invocations.

Step 1: Python FastMCP Stateless Wrapper

Using Python's FastMCP alongside Starlette / Uvicorn, we wrap tool declarations into a stateless HTTP POST controller.

Create server.py:

import os
import time
from typing import Any, Dict, Optional
from fastapi import FastAPI, Header, HTTPException, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field

app = FastAPI(title="Railway Stateless MCP 2.0 Server", version="2.0.0")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["POST", "OPTIONS", "GET"],
    allow_headers=["*"],
)

AUTH_SECRET = os.getenv("MCP_AUTH_TOKEN", "railway-production-token")

# Tool Registry Declarations
TOOLS_REGISTRY = [
    {
        "name": "extract_dns_records",
        "description": "Statelessly query DNS A, AAAA, MX, and TXT records for a domain.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "domain": {"type": "string", "description": "Target hostname (e.g. promptgenius.net)"}
            },
            "required": ["domain"]
        }
    }
]

class JsonRpcRequest(BaseModel):
    jsonrpc: str = "2.0"
    id: Optional[Any] = None
    method: str
    params: Optional[Dict[str, Any]] = None

@app.get("/healthz")
async def health_check():
    return {"status": "ok", "runtime": "railway-container", "timestamp": time.time()}

@app.post("/mcp")
async def handle_mcp(
    request: JsonRpcRequest,
    authorization: Optional[str] = Header(None)
):
    # 1. Bearer Token Auth Validation
    if AUTH_SECRET:
        token = authorization.replace("Bearer ", "") if authorization else None
        if token != AUTH_SECRET:
            return {
                "jsonrpc": "2.0",
                "id": request.id,
                "error": {"code": -32000, "message": "Unauthorized access"}
            }

    # 2. Method Dispatcher
    if request.method == "initialize":
        return {
            "jsonrpc": "2.0",
            "id": request.id,
            "result": {
                "protocolVersion": "2026-08-19",
                "capabilities": {"tools": {}},
                "serverInfo": {"name": "railway-mcp-python", "version": "2.0.0"}
            }
        }

    elif request.method == "tools/list":
        return {
            "jsonrpc": "2.0",
            "id": request.id,
            "result": {"tools": TOOLS_REGISTRY}
        }

    elif request.method == "tools/call":
        tool_name = request.params.get("name") if request.params else None
        tool_args = request.params.get("arguments", {}) if request.params else {}
        
        if tool_name == "extract_dns_records":
            domain = tool_args.get("domain", "")
            # Simple mock DNS resolution or use dnspython
            result_data = {
                "domain": domain,
                "records": {
                    "A": ["104.21.45.12", "172.67.182.90"],
                    "MX": ["10 mail.protonmail.ch"],
                    "TXT": ["v=spf1 include:_spf.google.com ~all"]
                },
                "resolvedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ")
            }
            return {
                "jsonrpc": "2.0",
                "id": request.id,
                "result": {
                    "content": [{"type": "text", "text": str(result_data)}],
                    "isError": False
                }
            }
        else:
            return {
                "jsonrpc": "2.0",
                "id": request.id,
                "error": {"code": -32601, "message": f"Tool '{tool_name}' not recognized"}
            }

    return {
        "jsonrpc": "2.0",
        "id": request.id,
        "error": {"code": -32601, "message": f"Method '{request.method}' not implemented"}
    }

Step 2: Multi-Stage Production Dockerfile

Optimized for Alpine with minimal layers and rapid boot times:

# Multi-stage build for ultra-lightweight Railway container
FROM python:3.12-alpine AS builder

WORKDIR /app
RUN apk add --no-cache gcc musl-dev libffi-dev

COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt

# Final Production Stage
FROM python:3.12-alpine AS runner

WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY server.py .

ENV PATH=/root/.local/bin:$PATH \
    PYTHONUNBUFFERED=1 \
    PORT=8080

EXPOSE 8080

# Run Uvicorn with single worker for pure stateless per-request handling
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8080", "--workers", "2", "--no-access-log"]

requirements.txt:

fastapi>=0.115.0
uvicorn>=0.30.0
pydantic>=2.8.0

Step 3: Railway Environment & 1-Click Deployment

Configure Railway environment variables in the project dashboard:

VariableRecommended ValuePurpose
PORT8080Listening port for Railway router
MCP_AUTH_TOKENgenerate-uuid-v4-secretBearer token for client authentication
ENVIRONMENTproductionActive environment tag
RAILWAY_DOCKERFILE_PATHDockerfileRelative path to container file
// railway.json configuration
{
  "$schema": "https://railway.app/railway.schema.json",
  "build": {
    "builder": "DOCKERFILE",
    "dockerfilePath": "Dockerfile"
  },
  "deploy": {
    "numReplicas": 1,
    "sleepApplication": true,
    "restartPolicyType": "ON_FAILURE",
    "restartPolicyMaxRetries": 5
  }
}

[!TIP] Railway Sleep Mode: Enable sleepApplication: true in your railway.json. Railway will automatically spin the container down to 0 instances when idle, incurring $0 charge until an incoming agent HTTP POST request triggers an automatic wake-up in <100ms.


4. Authentication, Security & Tool Sandboxing

Deploying MCP servers as public web endpoints exposes them to internet-scale attack vectors. You must enforce strict token verification, signature checks, and input sanitization.

Cryptographic HMAC-SHA256 Request Signing

In high-security environments, prevent replay attacks and man-in-the-middle tampering by verifying request signatures:

// Edge middleware: HMAC-SHA256 signature verification
export async function verifyMcpSignature(
  rawBody: string,
  signatureHeader: string | null,
  timestampHeader: string | null,
  secretKey: string
): Promise<boolean> {
  if (!signatureHeader || !timestampHeader) return false;

  // 1. Prevent replay attacks: Reject payloads older than 5 minutes
  const requestTime = parseInt(timestampHeader, 10);
  const currentTime = Math.floor(Date.now() / 1000);
  if (Math.abs(currentTime - requestTime) > 300) {
    return false;
  }

  // 2. Compute HMAC
  const encoder = new TextEncoder();
  const key = await crypto.subtle.importKey(
    "raw",
    encoder.encode(secretKey),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["verify"]
  );

  const signedPayload = `${timestampHeader}.${rawBody}`;
  const signatureBytes = hexToUint8Array(signatureHeader);

  return await crypto.subtle.verify(
    "HMAC",
    key,
    signatureBytes,
    encoder.encode(signedPayload)
  );
}

function hexToUint8Array(hex: string): Uint8Array {
  const match = hex.match(/.{1,2}/g) || [];
  return new Uint8Array(match.map((byte) => parseInt(byte, 16)));
}

Defense-in-Depth Security Rules

[!WARNING] Never Execute Raw System Commands: Any tool that shells out to child_process.exec or Python os.system using LLM-supplied arguments creates immediate Remote Code Execution (RCE) vulnerabilities. Always use parameterized execution, strict Zod schemas, and isolated edge sandboxes.

  1. SSRF Guardrails: When writing fetch/scraping tools, restrict outbound requests to public IPv4/IPv6 addresses. Explicitly reject private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.169.254, localhost).
  2. Schema Whitelisting: Enforce strict type checking with Zod or Pydantic. Use .strict() to reject unexpected object properties.
  3. Execution Timeouts: Enforce an aggressive timeout (e.g. AbortSignal.timeout(10000)) on all external API requests to prevent thread starvation.

5. Production Integrations: Firecrawl MCP on Edge Workers

A common pattern for stateless MCP is bridging external data APIs into standardized tool formats. Below is a real-world edge worker tool integrating Firecrawl for LLM-ready markdown web scraping:

// Integration: Stateless Firecrawl Scraper Tool for Cloudflare Workers
import { z } from "zod";

const ScrapeSchema = z.object({
  url: z.string().url(),
  formats: z.array(z.enum(["markdown", "html", "rawHtml"])).default(["markdown"]),
  onlyMainContent: z.boolean().default(true),
});

export async function handleFirecrawlScrape(args: unknown, apiKey: string) {
  const params = ScrapeSchema.parse(args);

  const response = await fetch("https://api.firecrawl.dev/v1/scrape", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      url: params.url,
      formats: params.formats,
      onlyMainContent: params.onlyMainContent,
    }),
    signal: AbortSignal.timeout(15000), // 15s timeout
  });

  if (!response.ok) {
    const errorBody = await response.text();
    throw new Error(`Firecrawl API error [${response.status}]: ${errorBody}`);
  }

  const data = (await response.json()) as { success: boolean; data: { markdown: string; metadata: Record<string, unknown> } };
  return {
    markdown: data.data.markdown,
    title: data.data.metadata?.title || "Untitled",
    url: params.url,
    scrapedAt: new Date().toISOString(),
  };
}

6. Edge Caching & Performance Optimization

Since tools/list returns static schema definitions and certain tool calls are idempotent (e.g. calculating formulas or querying static data), we can leverage Cloudflare KV or Cache API to eliminate redundant compute:

// Edge Cache Middleware for MCP Tools List
async function getCachedToolsList(env: Env): Promise<Response> {
  const CACHE_KEY = "mcp_tools_list_v2";
  
  if (env.TOOL_CACHE) {
    const cached = await env.TOOL_CACHE.get(CACHE_KEY);
    if (cached) {
      return new Response(cached, {
        headers: { "Content-Type": "application/json", "X-Cache-Hit": "true" },
      });
    }
  }

  const responsePayload = JSON.stringify({
    jsonrpc: "2.0",
    id: "cached-discovery",
    result: { tools: TOOLS }
  });

  if (env.TOOL_CACHE) {
    // Cache schemas for 24 hours at the edge
    await env.TOOL_CACHE.put(CACHE_KEY, responsePayload, { expirationTtl: 86400 });
  }

  return new Response(responsePayload, {
    headers: { "Content-Type": "application/json", "X-Cache-Hit": "false" },
  });
}

7. Configuring AI Clients for Stateless Remote MCP

To connect client interfaces (Cursor, Antigravity CLI, Claude Code, or VS Code) to your newly deployed serverless endpoint, configure the remote URL in your client settings.

Antigravity CLI / Cursor Configuration

Add the server to your ~/.gemini/antigravity/settings.json or project-level .cursor/mcp.json:

{
  "mcpServers": {
    "stateless-edge-tools": {
      "url": "https://stateless-mcp-worker.yourname.workers.dev/mcp",
      "headers": {
        "Authorization": "Bearer your-production-secret-token"
      }
    }
  }
}

8. Troubleshooting & FAQ