MCP Is Going Stateless: What Changed and How I Migrated My Currency Converter Server The Model Context Protocol (MCP) is moving toward a stateless protocol model, allowing requests to reach any server instance without sticky routing or shared session state. A developer recently migrated their MCP currency converter server to align with this change and the new split TypeScript SDK packages, highlighting the benefits for horizontally scalable HTTP services. The developer notes that stateless MCP does not mean the entire application must be stateless, as application state can be represented explicitly via data like cart IDs. The Model Context Protocol MCP has been evolving quickly. One of the most interesting changes in the latest MCP specification is the move toward a stateless protocol model . I recently updated my MCP currency converter server to work with the newer stateless behavior and the new split TypeScript SDK packages, particularly @modelcontextprotocol/server . In this article, I'll explain: If you're new to MCP, the Model Context Protocol is a standard for connecting AI applications to external tools, resources, and data. Instead of building custom integrations between every AI application and every external service, MCP provides a common protocol. For example, an AI assistant can use an MCP server exposing a tool like: convert currency The model can then request: Convert 100 USD to EUR. The MCP client communicates with the MCP server, which performs the actual operation and returns the result. MCP servers can expose several primitives, including tools, resources, and prompts. For my example, the server is intentionally simple: it exposes currency-conversion functionality. Before the stateless changes, Streamable HTTP could maintain a protocol-level session. Conceptually, the flow looked something like this: Client | | POST /mcp | initialize v MCP Server | | Mcp-Session-Id v Client | | POST /mcp | Mcp-Session-Id: abc123 v MCP Server The server creates a session during initialization. Subsequent requests contain the session identifier. That means the server can associate requests with the session that was established earlier. This isn't necessarily bad. Session state can be useful when an application genuinely needs conversational or connection-level state. But it creates an architectural problem when we want MCP servers to behave like ordinary horizontally scalable HTTP services. Imagine deploying three MCP server instances: ┌─────────────┐ │ Load │ │ Balancer │ └──────┬──────┘ | ┌─────────────┼─────────────┐ | | | v v v Server A Server B Server C Suppose the initialization request reaches Server A. The session now belongs to Server A. What happens when the next request reaches Server B? Server B doesn't necessarily know anything about that session. You now need mechanisms such as: That adds complexity to the deployment architecture. The latest MCP direction removes this protocol-level session dependency. The MCP team describes the goal as allowing requests to reach any server instance without requiring sticky routing or a shared protocol session store. Model Context Protocol Blog https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/?utm source=chatgpt.com The key idea is surprisingly simple: Each request should be understandable independently. Instead of relying on a protocol-level session established earlier, the request contains the information necessary for the server to process it. Conceptually: Request 1 | v Server A Request 2 | v Server C Request 3 | v Server B There is no requirement that all three requests reach the same instance. This is a much more natural fit for cloud-native architectures. This is an important distinction. Stateless MCP does not mean your entire application must be stateless. Suppose you have a shopping-cart tool: create cart The server can return: { "cart id": "cart 123" } The next tool call can explicitly provide: { "cart id": "cart 123", "product id": "product 456" } The application still has state. But that state is represented explicitly by application data rather than being hidden inside an MCP protocol session. The MCP team explicitly recommends this type of application-level state when state needs to persist between calls. Model Context Protocol Blog https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/?utm source=chatgpt.com That's a subtle but important architectural improvement. Streamable HTTP is the important transport for remote MCP servers. The 2026-07-28 specification changed its behavior significantly. The new model removes: Mcp-Session-Id Instead, the server exposes a single MCP endpoint that accepts POST requests. Each JSON-RPC request is sent as its own HTTP request. The server can return either: application/json or an SSE stream associated with that request. GitHub https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2026-07-28/basic/transports/streamable-http.mdx?utm source=chatgpt.com So the mental model becomes: POST /mcp | v +----------------+ | MCP Server | | | | Process request| | | | Return result | +----------------+ Rather than: Initialize session | v Maintain session | v Process requests | v Eventually close session To understand the change practically, I used my currency converter MCP server. The server exposes currency conversion as an MCP tool. The architecture is roughly: AI Application | | MCP v ┌───────────────┐ │ Currency MCP │ │ Server │ └───────┬───────┘ | v Exchange Rate API The server doesn't need conversational state. For example: Convert 100 USD to EUR is completely independent from: Convert 500 GBP to USD There is no reason for the server to maintain a protocol session between these requests. That makes the currency converter a good candidate for a stateless MCP server. Another change I encountered while updating the project was the move to the split TypeScript SDK packages. Instead of relying on the older monolithic package structure, the SDK now provides separate packages such as: @modelcontextprotocol/server @modelcontextprotocol/client The server package is responsible for building MCP servers, while the client package provides client functionality. GitHub https://github.com/modelcontextprotocol/typescript-sdk/blob/main/README.md?utm source=chatgpt.com For my server, I use: npm install @modelcontextprotocol/server along with the required schema library. This separation makes the dependency boundaries much clearer. One of the important concepts when working with Streamable HTTP is the absence of a session ID generator. In the earlier SDK transport model, a stateful server could configure something like: js const transport = new StreamableHTTPServerTransport { sessionIdGenerator: = randomUUID , } ; The transport then generates and manages a session identifier. For stateless operation, the session ID generator is omitted: js const transport = new StreamableHTTPServerTransport { sessionIdGenerator: undefined, } ; The SDK documentation describes this as stateless mode: no session ID is returned and no session validation is performed. GitHub https://github.com/modelcontextprotocol/typescript-sdk/blob/main/packages/middleware/node/src/streamableHttp.ts?utm source=chatgpt.com However, there is an important nuance here. Don't confuse this older SDK configuration with the new 2026 protocol itself. The 2026 specification removes protocol-level sessions altogether. The SDK is evolving to provide higher-level APIs around the new protocol behavior as well. GitHub https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/migration/support-2026-07-28.md?utm source=chatgpt.com This is probably the biggest reason I find this change interesting. Consider a Kubernetes deployment: Load Balancer | ┌──────────────┼──────────────┐ | | | v v v Pod 1 Pod 2 Pod 3 With protocol-level sessions, routing could become: Client A ─────────────── Pod 1 Client A ─────────────── Pod 1 Client A ─────────────── Pod 1 because the session belongs to Pod 1. With a stateless protocol, requests can be distributed naturally: Client A ─────────────── Pod 1 Client A ─────────────── Pod 3 Client A ─────────────── Pod 2 Client A ─────────────── Pod 1 The load balancer doesn't need to understand MCP session ownership. This makes horizontal scaling significantly simpler. This is another area where the change becomes interesting. Consider AWS Lambda. A typical architecture might look like: MCP Client | v API Gateway | v Lambda | v Exchange Rate API With stateless requests, each invocation can independently process an MCP request. There is less reason to keep a warm process specifically because it owns a particular MCP session. This aligns nicely with the serverless execution model. Of course, long-running streaming operations and application-specific state can still introduce additional considerations. Stateless doesn't magically eliminate all stateful requirements. Suppose I later extend my currency server with something like: create conversion watch A user might create a watch: create conversion watch from = USD, to = EUR, threshold = 0.95 The server could return: { "watch id": "watch 123" } The watch itself could be persisted in: PostgreSQL or: Redis or another durable store. Later: get conversion watch watch id = "watch 123" The MCP protocol doesn't need to maintain a session. The application owns the state. That's a much cleaner separation of responsibilities: MCP | | Protocol communication | v Application | | Business state | v Database This is another misconception worth addressing. An MCP request can still contain metadata and application parameters. The difference is that we're no longer relying on an implicit protocol session to reconstruct the context. The newer protocol explicitly moves toward per-request information. The TypeScript SDK documentation describes the 2026 protocol as carrying request-level information such as protocol version and client information rather than relying on an initialization-scoped identity. GitHub https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/protocol-versions.md?utm source=chatgpt.com This makes each request more self-contained. After migrating my currency converter server, the biggest architectural benefit I see is simplification . Instead of thinking: Where does this client's MCP session live? I can think: Can this request be processed independently? For many tools, the answer is yes. That makes MCP much easier to reason about in distributed environments. It also fits familiar backend architecture principles: In other words, MCP starts feeling much more like a conventional cloud-native protocol. Stateless doesn't remove architectural responsibilities. You still need to consider: Every request must still be authenticated and authorized appropriately. If a tool performs a side effect, you may need idempotency protection. For example: create payment should not accidentally execute twice because a request was retried. If your application needs state, store it explicitly. For example: MCP request | v Application | v Redis / PostgreSQL / DynamoDB Streamable HTTP servers need appropriate security controls. The MCP specification specifically calls out validating the Origin header to mitigate DNS rebinding attacks and recommends authentication for connections. Model Context Protocol https://modelcontextprotocol.io/specification/2025-06-18/basic/transports?utm source=chatgpt.com Stateless does not mean "everything must be a simple JSON response." The newer Streamable HTTP transport can still use SSE for streaming data associated with a request. GitHub https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2026-07-28/basic/transports/streamable-http.mdx?utm source=chatgpt.com The easiest way to summarize the architectural difference is: Initialize | v ┌─────────────────┐ │ MCP Session │ │ │ │ Session ID │ │ Session State │ └────────┬────────┘ | ┌────────┼────────┐ v v v Req 1 Req 2 Req 3 The protocol session is part of the architecture. Request 1 ─────── Server Request 2 ─────── Server Request 3 ─────── Server Request 4 ─────── Server Each request can be independently routed and processed. If state is required: php Request | v MCP Server | +---- Application State | v Database The state belongs to the application rather than the MCP transport. The move toward a stateless MCP protocol might initially look like a relatively small protocol change. I think its implications are much bigger. Removing protocol-level sessions makes MCP easier to deploy in environments where stateless HTTP is already the default architectural model. It reduces the need for: And it encourages a cleaner separation: MCP ↓ Communication protocol Application ↓ Business logic Database ↓ Application state My currency converter server was a relatively simple project, but it was a useful way to understand this change in practice. If you're building remote MCP servers today, I think it's worth understanding the new stateless model—not just from an MCP perspective, but from a distributed systems and deployment architecture perspective . The interesting question is no longer: "How do I maintain an MCP session?" It becomes: "Can I design my MCP server so that every request can be independently processed?" For many MCP tools, the answer is yes. And when it is, the resulting architecture can be considerably simpler. I implemented the stateless behavior in my currency converter MCP server as a practical example of the concepts discussed in this article. The project is available on GitHub: In NPM repository: If you're learning MCP, I recommend starting with a small tool server like this and then experimenting with: That's where the protocol changes become much more interesting from an architecture perspective.