← Blog

August 5, 2026 · 7 min read

How to Scale an MCP Server: What the 2026 Stateless Protocol Makes Possible

The MCP 2026-07-28 spec landed with a lot of changes — deprecation policies, MCP Apps, the Tasks extension. But the most consequential change for anyone running an MCP server in production is the one that gets the least attention: the protocol is now stateless.

If you've ever tried to scale an MCP server horizontally, you know why this matters. The old protocol required a stateful session — you couldn't route a request to a different server instance than the one that handled the initialize call. The new spec removes that constraint entirely.

Why the old protocol couldn't scale horizontally

Original MCP (2024-11-05) requires every connection to begin with an initialize request and a matching InitializeResult. This isn't just a handshake — the server uses it to track what capabilities and protocol version that client supports. The result is implicit state: the server has to remember something about you for subsequent calls to make sense.

This is fine when you have one server instance. It becomes a problem when you add a second. A round-robin load balancer that routes your tools/call to instance B can't work correctly if instance B has no record of the initialize that happened on instance A. The workarounds — sticky sessions, shared session stores, single-instance deployments — all add complexity and limit where you can deploy.

Serverless platforms have the same problem compounded. A Vercel Edge Function or a Cloudflare Worker has no memory between requests. If each function invocation is independent, a stateful protocol doesn't fit without an external store.

What stateless MCP actually means

The 2026-07-28 spec makes the core protocol stateless. Each request carries enough information to be handled without prior context. The initialize method is deprecated — clients call server/discover once (or never, if they cache the result), and then make tool calls directly.

A tools/call request in the new spec is just an HTTP POST. Any server instance can handle it. No session lookup required.

The spec also adds two headers that make stateless routing practical at the infrastructure layer:

Mcp-Method — the JSON-RPC method being called (e.g. tools/call)

Mcp-Name — the tool name (e.g. ask_site)

A load balancer or API gateway can read these headers without parsing JSON, enabling routing decisions at the infrastructure layer before the request even hits your application code.

Deployment patterns the stateless spec enables

1. Plain round-robin load balancing

The simplest scaling pattern now works correctly. Deploy two or more identical server instances, put nginx or a cloud load balancer in front, and requests can go to any instance without sticky sessions. No shared state store required.

# nginx.conf — stateless MCP works with round-robin by default
upstream mcp_servers {
  server mcp-1.internal:3000;
  server mcp-2.internal:3000;
  server mcp-3.internal:3000;
  # no ip_hash needed; each request is self-contained
}

server {
  location /api/mcp {
    proxy_pass http://mcp_servers;
    proxy_set_header Mcp-Method $http_mcp_method;
    proxy_set_header Mcp-Name $http_mcp_name;
  }
}

2. Route by tool type

The Mcp-Method and Mcp-Name headers let you route intelligently without parsing JSON. You can separate read-heavy discovery calls from compute-heavy tool calls, or route long-running tools to a separate fleet with a longer timeout.

# Cloudflare Worker — route MCP calls by method header
export default {
  async fetch(request: Request): Promise<Response> {
    const method = request.headers.get('Mcp-Method')
    const toolName = request.headers.get('Mcp-Name')

    // Discovery calls go to a cached edge response
    if (method === 'server/discover' || method === 'tools/list') {
      return fetch('https://mcp-discovery.example.com/api/mcp', request)
    }

    // Long-running tools go to workers with longer CPU limits
    if (toolName === 'submit_site' || toolName === 'refresh_site') {
      return fetch('https://mcp-heavy.example.com/api/mcp', request)
    }

    // Fast reads go to the main fleet
    return fetch('https://mcp-primary.example.com/api/mcp', request)
  }
}

3. Cache the tools list at the CDN

The stateless spec lets tools/list responses carry a ttlMs field indicating how long the tool list is valid. A CDN or edge cache can respect this TTL and serve tool discovery from cache, eliminating the round-trip entirely for frequently-connecting clients.

// Return tools/list with a TTL so clients and CDNs can cache it
return Response.json({
  jsonrpc: '2.0',
  id: req.id,
  result: {
    tools: TOOL_DEFINITIONS,
    ttlMs: 60_000,          // cache for 60 seconds
    cacheScope: 'public',   // shared cache ok (no user-specific tools)
  }
}, {
  headers: {
    'Cache-Control': 'public, max-age=60, s-maxage=60',
    'Mcp-Cache-Scope': 'public',
  }
})

For a server with stable, infrequently-changing tools, caching the tool list at the CDN can eliminate a significant fraction of origin requests — every new client connection would hit cache instead of your application.

4. True serverless deployment

Without session state, MCP servers map cleanly to serverless platforms. Each request is independent, so cold starts are the only concern. A Vercel Function, AWS Lambda, or Cloudflare Worker can handle MCP tool calls correctly without any session management code.

// Next.js App Router route — pure stateless MCP handler
export async function POST(req: Request) {
  const body = await req.json()
  const { method, id, params } = body

  // No session state to look up — each request is self-contained
  switch (method) {
    case 'server/discover':
      return Response.json({ jsonrpc: '2.0', id, result: SERVER_MANIFEST })
    case 'tools/list':
      return Response.json({ jsonrpc: '2.0', id, result: { tools: TOOLS, ttlMs: 60_000 } })
    case 'tools/call':
      return Response.json({ jsonrpc: '2.0', id, result: await callTool(params) })
    default:
      return Response.json({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' } })
  }
}

Backward compatibility: supporting old clients

Not every MCP client has updated to the 2026 spec. Old clients will still send initialize before making tool calls. A production server should handle both gracefully.

The cleanest approach is to respond to initialize as if nothing changed — return a valid InitializeResult — but don't store any session state when you do. The response satisfies the old client without actually creating statefulness in your server.

case 'initialize':
  // Respond to old clients without storing session state.
  // The response tells them we're 2026-spec, and they adapt.
  return Response.json({
    jsonrpc: '2.0',
    id,
    result: {
      protocolVersion: '2026-07-28',
      capabilities: { tools: {} },
      serverInfo: { name: 'your-server', version: '1.0.0' },
    }
  })

What the stateless spec doesn't solve

Stateless requests handle read-heavy tool calls well. They're less helpful for operations that have inherent duration — like crawling a site, running an embedding job, or waiting on an external API. Those need the Tasks extension, which adds async tool calls with polling or webhooks.

Tasks require a queue and a background worker — state lives in the job queue rather than in the server process. This is a good separation of concerns, but it means "stateless MCP" and "Tasks" together require more infrastructure than a simple stateless HTTP server. If your tools are fast and deterministic, you don't need Tasks. If they're slow, plan for a queue.

CORS is also worth flagging. The new headers need to be in your Access-Control-Allow-Headers preflight response if you're serving browser clients via WebMCP:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, Mcp-Method, Mcp-Name

The practical upshot

If you're building an MCP server that needs to handle real traffic, the 2026 spec removes the main architectural constraint that was forcing stateful deployments. You can now deploy on any HTTP platform you're already familiar with — no sticky sessions, no session stores, no single-instance bottlenecks.

The migration path is straightforward: add server/discover, drop any session state you're tracking after initialize, add Mcp-Method and Mcp-Name to your CORS headers, and add a ttlMs to your tools/list response. The protocol does the rest.

AgentReady's MCP endpoint is stateless, deployed on Vercel, and supports both the 2024 and 2026 spec versions. Connect it to Claude, Cursor, or any MCP client to query any indexed site.

Connect AgentReady to your AI client →