Last month, I was building an AI assistant for an e-commerce system. The idea was simple: let users ask natural-language questions about orders and products through a chat interface. I used the Model Context Protocol (MCP) to connect my AI agent to internal tools -- inventory lookup, order tracking, customer history.
It worked. Until the WebSocket connection dropped at 2 AM, the server ran out of file descriptors under load, and I realized my stateful SSE transport could not scale across multiple instances behind a load balancer.
If that sounds familiar, the news from July 28, 2026 is exactly what you have been waiting for. MCP just went stateless.
I am a Software Engineer from Bangladesh. I work with Spring Boot daily and run my own AI agent infrastructure on the side. Here is what the MCP 2026-07-28 spec means for Java developers, and how to build a production-ready stateless MCP server with Spring AI 2.0.
What MCP Is (And Why the Change Matters)
The Model Context Protocol is an open standard from Anthropic that defines how AI models discover and call external tools. Think of it as a universal adapter between your LLM and your databases, APIs, and file systems. Since its launch in late 2024, MCP has seen tremendous adoption. The Tier 1 SDKs alone have crossed close to half a billion downloads per month, with both TypeScript and Python SDKs passing 1 billion total downloads.
Before this release, MCP used a bidirectional stateful protocol over SSE (Server-Sent Events). Every client needed a persistent connection to a specific server instance carrying session state. A handshake established a session, and all subsequent communication happened over that session. This worked for demos and small-scale deployments, but in production it meant a cascade of problems:
- Sticky sessions required on load balancers. You could not distribute requests freely.
- No serverless deployment. Lambda, Cloud Run, and Azure Functions cannot hold open SSE connections indefinitely.
- Harder debugging. Session state lived on a specific instance. Tracing a request across instances required session affinity.
- Resource bloat. Every open connection consumed a file descriptor and a memory buffer. Under load, servers hit OS limits faster than expected.
- Scaling pain. Adding instances required rethinking session distribution. Zero-to-one scaling was impractical.
The MCP maintainers -- David Soria Parra and Den Delimarsky -- described the stateless shift as the most requested feature from the developer community. The 2026-07-28 spec delivers it comprehensively. Here is what changed:
No handshake or sessions. Every HTTP request is self-describing. An optional discovery call lets clients request capabilities upfront, but it is not required. Any request can land on any instance behind a plain round-robin load balancer.
Header-based routing. Method and tool names travel in Mcp-Method and Mcp-Name HTTP headers. Gateways can route and authorize on headers directly without inspecting request bodies. This makes API gateways, service meshes, and edge functions much simpler to configure.
Multi Round-Trip Requests (MRTR). Server-to-client requests (sampling for asking the LLM to generate additional content, elicitation for requesting clarification) no longer need always-open bidirectional streams. They use a callback request mechanism over regular HTTP. The server sends a request, the client responds. No WebSocket, no long-lived connection.
Cacheable list responses. Tool catalogs and resource lists carry cache hints with deterministic ordering. Clients can cache tool listings and avoid redundant discovery calls on every interaction. This cuts latency significantly for multi-turn conversations.
Authorization hardening. RFC 9207 issuer validation, client metadata documents (CIMD) replacing Dynamic Client Registration. If you have ever worried about MCP security in production, this is the release that addresses it.
Extensions framework. The spec now formally locks in on a proper extensions model. Tasks join other extensions like MCP Apps and Enterprise Managed Authorization (EMA). The protocol is no longer a single monolithic spec -- it is a platform.
Formal deprecation policy. Twelve-month minimum window for any breaking change. You can plan upgrades instead of reacting to midnight failures.
All four Tier 1 SDKs -- TypeScript, Python, Go, and C# -- updated alongside the spec. And Spring AI 2.0, released alongside Spring Boot 4, already ships with full support for the new stateless transport. Spring Boot Starters for MCP server and client are available on start.spring.io from day one.
Spring AI 2.0 and the Stateless MCP Server
Spring AI 2.0 (released alongside Spring Boot 4) includes MCP support as one of its headline features. The Spring team contributed to the official MCP Java SDK and built Boot Starters around it.
You get three MCP server options, each suited for a different scenario:
- STDIO -- local dev, single-process apps. Sessions are in-process. Not serverless-compatible. No server-to-client communication.
- Streamable HTTP -- full-featured servers needing bidirectional notifications. Stateful sessions. Partially serverless-compatible. Supports server-to-client communication.
- Stateless HTTP -- production deployments, microservices, serverless. No sessions. Fully serverless-compatible. No server-to-client (by design, as it is not needed for tool calling).
Stateless Streamable HTTP is the new default for production. Use it when:
- You deploy behind a load balancer and do not want sticky sessions
- You run on serverless platforms (AWS Lambda, Google Cloud Run)
- Your agent only needs tool calling, resource access, and prompt templates
- You want to scale to zero when idle
Use Streamable HTTP (stateful) when:
- Your server needs to send notifications, sampling requests, or elicitation to the client
- You need real-time tool registration changes pushed to clients
- You are migrating an existing SSE-based MCP server and cannot drop features yet
For most AI agent use cases, stateless is the right choice. Tool calling is inherently request-response: the LLM asks, the tool answers. You do not need a persistent channel for that.
What You Get With Spring AI's MCP Support
The Spring AI MCP integration is not a thin wrapper. It provides:
- Auto-configuration through Spring Boot starters. Add a dependency, set one property, done.
-
Annotation-based tool development with
@McpTool,@McpResource,@McpPrompt. Automatic JSON schema generation from method signatures and parameter annotations. -
Client auto-discovery of server tools. Wire
ToolCallbackProviderinto yourChatClientand tools are automatically registered. - Request context injection for advanced use cases like logging, progress tracking, and client pinging.
- Named client connections supporting multiple MCP servers simultaneously with tool filtering and prefix-based naming to avoid conflicts.
- Sync and async support for both server and client.
- Annotation-based client handlers for receiving server-initiated messages (logging, sampling, tool list changes).
Tutorial: Building a Stateless MCP Server
Let me walk through building an MCP server that exposes product search and order lookup tools. These are tools my e-commerce AI agent actually uses.
Step 1: Add the dependency
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
For reactive stacks, use spring-ai-starter-mcp-server-webflux instead.
Step 2: Configure the protocol
In application.properties:
spring.ai.mcp.server.protocol=STATELESS
spring.ai.mcp.server.name=product-agent-server
spring.ai.mcp.server.version=1.0.0
Or in application.yml:
spring:
ai:
mcp:
server:
protocol: STATELESS
name: product-agent-server
version: 1.0.0
That is the entire configuration. Spring Boot auto-configuration handles the rest.
Step 3: Declare tools with @McpTool
Spring AI provides annotation-based MCP tool development. Here is a product search tool:
@Service
public class ProductTools {
private final ProductRepository productRepository;
public ProductTools(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@McpTool(name = "search_products",
description = "Search products by name, category, or keyword")
public List<Product> searchProducts(
@McpToolParam(description = "Search query string", required = true) String query,
@McpToolParam(description = "Maximum results to return", required = false) Integer limit) {
return productRepository.search(query, limit != null ? limit : 10);
}
@McpTool(name = "get_order_status",
description = "Get the current status of an order by ID")
public String getOrderStatus(
@McpToolParam(description = "The order ID", required = true) String orderId) {
Order order = orderRepository.findById(orderId);
if (order == null) {
return "Order not found: " + orderId;
}
return String.format("Order %s: %s (placed %s, total $%.2f)",
order.id(), order.status(), order.placedAt(), order.total());
}
}
The @McpTool annotation automatically generates JSON schema for the tool parameters. @McpToolParam provides descriptions that the LLM uses to decide when to call each tool.
You can also add hints:
@McpTool(name = "cancel_order",
description = "Cancel an order by ID",
annotations = @McpTool.McpAnnotations(
destructiveAction = true,
title = "Cancel Order",
readOnlyHint = false
))
public String cancelOrder(
@McpToolParam(description = "The order ID to cancel", required = true) String orderId) {
// ...
}
Step 4: Access request context
Tools can access the request context for advanced operations like client-side logging or progress reporting:
@McpTool(name = "process_refund",
description = "Process a refund for an order")
public String processRefund(
McpSyncRequestContext context,
@McpToolParam(description = "Order ID", required = true) String orderId,
@McpToolParam(description = "Refund amount", required = true) double amount) {
context.getLogger().info("Processing refund for order " + orderId);
// ... refund logic
return "Refund of $" + amount + " processed for order " + orderId;
}
Step 5: Run it
./mvnw spring-boot:run
Your stateless MCP server is now running on port 8080. It exposes tools through a standard HTTP endpoint. Any MCP client -- including Spring AI's own client starter -- can discover and call these tools without a handshake or session setup.
Building the Client Side
On the client side, Spring AI provides spring-ai-starter-mcp-client:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
Configure the connection:
spring:
ai:
mcp:
client:
streamable-http:
connections:
product-server:
url: http://localhost:8080
Then wire it into a ChatClient:
@Bean
public CommandLineRunner demo(ChatClient chatClient, ToolCallbackProvider mcpTools) {
return args -> {
String response = chatClient
.prompt("What products are available for under $50?")
.tools(mcpTools)
.call()
.content();
System.out.println(response);
};
}
The client discovers the server's tools automatically. No manual tool registration required.
Before and After: Why Stateless Wins
I rebuilt my e-commerce agent using the stateless transport. Here is what changed:
Before (SSE/stateful): Four server instances behind an ALB, each with sticky sessions enabled. Connection count grew linearly with users. A deployment required draining existing connections gracefully. Memory usage per instance averaged 800 MB because the server held session state for every connected client.
After (Stateless HTTP): Same four instances, no sticky sessions. The load balancer distributes requests freely. I can deploy new versions without connection draining because there are no sessions to preserve. Memory per instance dropped to 450 MB. More importantly, I can now run the same server on AWS Lambda for low-traffic periods and scale to zero when nobody is using it.
What You Lose (And Whether It Matters)
The stateless transport drops support for server-to-client requests. That means no:
- Elicitation -- the server cannot ask the LLM for clarification
- Sampling -- the server cannot request the LLM to generate additional content
- Ping -- the server cannot initiate health checks
For most tool-calling use cases, none of these are needed. Tools are request-response by nature: the LLM calls a tool, the tool returns a result. If you need server-initiated communication, use the Streamable HTTP transport instead. It keeps the bidirectional channel but uses proper HTTP with optional SSE, not the old bespoke SSE protocol.
Key Takeaways
- MCP 2026-07-28 introduces a stateless protocol core that eliminates sticky sessions and enables serverless deployment
- Spring AI 2.0 ships with full stateless MCP server support through
spring-ai-starter-mcp-server-webmvcorspring-ai-starter-mcp-server-webflux - Tools are declared with
@McpToolannotations -- automatic JSON schema generation, no boilerplate - Client auto-discovers server tools via
spring-ai-starter-mcp-client - The stateless transport is ideal for tool-calling agents. Use Streamable HTTP if you need server-to-client communication
I write about Java, Spring Boot, and AI every week. Subscribe -- it is free.
Have you tried building MCP servers with Spring AI? What transport are you using in production? I would love to hear what works (or breaks) in your setup.
Top comments (0)