How to build and deploy an MCP server to production (2026-07-28 spec) The maintainers of the Model Context Protocol (MCP) shipped the 2026-07-28 spec, which removes the handshake and session requirements, making MCP servers stateless HTTP services that can be load-balanced and autoscaled. A developer demonstrates building and deploying an MCP server to Cloudflare Workers using the new TypeScript SDK v2, highlighting features like MRTR for interactive tools and caching with ttlMs. MCP just had its biggest release since launch. On July 28, the maintainers shipped the 2026-07-28 spec , and it changes how MCP servers work at a pretty fundamental level. The handshake is gone. Sessions are gone. Three long-standing features are deprecated. The maintainers themselves called it the most substantial change since authorization was added. Their words, not mine. Sounds scary. But it actually makes MCP servers much easier to deploy. And what am I here for? I'm here to help you build and deploy one. Your MCP server is now just a regular stateless HTTP service. Round-robin load balancing, autoscaling, and caching all work. No sticky sessions or shared session state. In this guide, we'll build a small MCP server on the new spec, connect a client to it, see every headline feature actually running, and then deploy it to Cloudflare Workers. For free. ℹ️ All the code here uses the new TypeScript SDK v2, released alongside the spec. If you're on the old @modelcontextprotocol/sdk package, that's v1 now. ttlMs Quick rundown of what's new. If you want the full changelog, it's on the official spec site https://modelcontextprotocol.io/specification/2026-07-28/changelog . The initialize / initialized exchange and the Mcp-Session-Id header are officially retired. Every request is now self-describing . It carries its own protocol version, client identity, and capabilities in meta . Any request can land on any server instance behind a plain load balancer. Such a relief There's an optional server/discover RPC if a client wants capabilities up front. But it's optional. One bare POST is a complete conversation now. This one is my favorite. Before, if a tool needed something from the user mid-call, such as confirmation or a missing parameter, the server had to push an elicitation/create request back over a held-open stream. That meant you needed a held-open stream, which was bad for stateless deployments. MRTR flips it. The server returns resultType: "input required" with the questions it needs answered, and closes the connection. The client collects the answers and retries the original call with them attached, plus an opaque requestState token so the server knows where it left off. No open streams. No sessions. Interactive tools on fully stateless infra. Requests now carry Mcp-Method and Mcp-Name HTTP headers. Your gateway, rate limiter, or WAF can route and meter on headers without parsing JSON bodies. tools/list , prompts/list , resources/list , and resources/read responses now carry ttlMs and cacheScope fields, modeled on HTTP's Cache-Control. Clients cache your tool catalog instead of re-fetching it every time they connect. Tasks moved out of the experimental core into an official extension io.modelcontextprotocol/tasks . MCP Apps and Enterprise Managed Authorization live there too. You can build your own extensions as well. And the deprecations: There's also a formal deprecation policy now: a 12-month minimum window for anything marked deprecated. So you get to plan upgrades, which is noicee One more thing before we build: the TypeScript SDK is no longer one package. v2 splits it into @modelcontextprotocol/server , @modelcontextprotocol/client , and thin framework adapters @modelcontextprotocol/hono , express , fastify , node . Finally, we're onto the build. We will build a quick tiny deploy bot over MCP. It has three tools: deploy asks the user for confirmation before deploying MRTR in action . list deployments reads back the deployment history server stats proves a fresh server instance handled every requestHere's the trick that pays off at deploy time: all the MCP logic lives in one platform-neutral file bot.ts , and each platform gets a tiny entry file. Node gets server.ts . Cloudflare gets worker.ts . Both are about ten lines. An MCP server on the new spec is just a fetch handler; the platform is a serving shim. You'll understand everything along the way. Run the following command: mkdir updated-mcp-spec-bot && cd updated-mcp-spec-bot npm init -y && npm pkg set type=module npm install @modelcontextprotocol/server @modelcontextprotocol/client \ @modelcontextprotocol/hono @hono/node-server hono zod tsx ℹ️ On TypeScript 6+, add "types": "node" to your tsconfig compilerOptions after installing @types/node . TS 6 no longer auto-includes @types/ , and you'll get Cannot find name 'process' errors without it. Ask me how I know. 😴 Create bot.ts . This is the whole MCP server, with zero platform code in it: python // 👇 bot.ts import type { CallToolResult, InputRequiredResult, } from "@modelcontextprotocol/server"; import { acceptedContent, CLIENT CAPABILITIES META KEY, createRequestStateCodec, inputRequired, McpServer, } from "@modelcontextprotocol/server"; import as z from "zod/v4"; const deployments: { env: string; at: string } = ; let requestsServed = 0; type DeployState = { step: "confirm"; env: string }; // set STATE KEY in production so all instances share the secret // lazy init: Workers forbids generating random values at module scope let codec: ReturnType