Too Long? Read This First
- MCP allows concurrent clients by design. The protocol has no session limit and doesn't serialize requests.
- The 2026-07-28 MCP spec removed protocol-level sessions and the Mcp-Session-Id header, so any request can go to any server instance.
- Removing sessions doesn't make your app stateless. A multi-step workflow still needs an explicit handle, like a job_id, stored in a shared database instead of process memory.
- A session ID was never a lock. Two agents can hold different sessions and still overwrite the same record.
- Optimistic concurrency, a conditional update that fails if the record changed since it was read, is what the C# SDK's task docs recommend for this.
Two independent MCP clients can call the same server at the same time. That covers two separate agents or a single agent running two sessions, and the protocol doesn't serialize those calls or assume a single client.
What MCP doesn't do is protect a shared record for you.
If two agents update the same row, file, or contact at once, nothing stops one write from silently overwriting the other.
This is the exact scenario you'll run into with a Wati MCP server handling live conversations and contacts.
In this guide, we cover what changed in the July 2026 MCP spec, the race condition it leaves open, and five patterns that keep concurrent agents from overwriting each other's work.
MCP Sessions vs. Stateless Servers: What Changed?
MCP's approach to sessions has changed, but the underlying need for application state hasn't. Understanding that distinction is important when you're building a server that needs to handle multiple clients and keep track of work across calls.
Earlier MCP Revisions Used Protocol Sessions
Earlier MCP revisions were session-based. A client sentinitialize, the server returned its negotiated capabilities, and the client then sent initialized. With Streamable HTTP, the server could also return an Mcp-Session-Id that the client had to include on every later request.
Two clients performing that handshake independently received different session IDs, allowing a session-aware server to maintain separate per-client state, such as browser contexts or carts.
The 2026-07-28 Specification Removed Protocol Sessions
The 2026-07-28 specification removed protocol-level sessions and the mandatory initialize/initialized handshake.
Version and capability metadata now travel with each request, so requests can be routed to any server instance without sticky sessions or a shared protocol-session store.
This simplifies horizontal scaling, but it doesn't eliminate application state. If a workflow spans several calls, the server still needs to track that state explicitly rather than relying on an implicit session object.
The C# SDK documents both modes: stateless mode treats each request independently, while stateful mode maintains an in-memory session per client when session-scoped state, unsolicited server-to-client messages, or subscriptions are needed. The current SDK defaults to stateless HTTP, with stateful mode available when required.
MCP Race Conditions: Why Shared Records Conflict?
Here's the simplest way to see the problem. Agent A reads a record at version 10. Agent B reads that same record before A's update, so it also sees version 10. A writes its change first. B then writes its own update using the old version 10, quietly overwriting A's change. Neither agent knows a conflict occurred.
Separate sessions don't prevent this when both agents are working with the same database row, file, or API object. Statelessness isn't what creates the race, either.
The real issue is that MCP doesn't manage concurrency for shared application data. Your server needs its own safeguards to decide what happens when two agents try to change the same thing at once.
Here's the full rewritten section, ready to paste in:
5 Patterns for Safe Concurrent MCP Operations
Five patterns protect a shared record from concurrent writes. Each one fits a different kind of MCP server.
Pattern | How it works | Best for |
|---|---|---|
Optimistic concurrency | Conditional update: | Most record updates where conflicts are occasional |
Database transactions and row locks | Wrap the full read-modify-write in one transaction. Keep lock duration short and define a deadlock-retry policy. | Tightly coupled operations within a single database |
Per-record distributed locks | Acquire a lock keyed to the resource, such a | Multi-step operations spanning several calls or external systems |
Queue or single-writer serialization | Enqueue commands keyed by record or tenant so one worker processes each key at a time. Return a | Hot, inherently sequential resources |
Idempotency keys | An application-level argument, distinct from the JSON-RPC request id, persisted with a uniqueness constraint like | Any mutating call where a retry could otherwise duplicate a side effect |
MCP's JSON-RPC request ID correlates a response to a request. It is not a business idempotency key.
Reusing it for deduplication doesn't work, because it's scoped to the transport, not your application's mutation history.
Build a separate idempotency argument, bind it to the operation and its parameters, and persist it at the actual side-effect boundary.
How to Build a Safe Concurrent MCP Server?
When building a new MCP server, start with these assumptions rather than discovering them under load:
- Expect concurrent requests: Multiple clients and tool calls can arrive at the same time.
- Keep security in the request: Authentication, tenant identity, and authorization belong in each request's security context, not in a session object.
- Give workflows an explicit handle: If a workflow spans multiple calls, use an explicit, authorized handle rather than relying on an implicit session.
- Make mutations safe to retry: Use an idempotency key whenever repeating an operation could cause a duplicate side effect.
- Use the simplest concurrency control that works: Start with database transactions and optimistic version checks for ordinary updates. Add distributed locks or keyed queues only when operations genuinely can't run concurrently.
- Return actionable statuses: Use clear conflict, duplicate, or busy statuses instead of silently overwriting data, so the calling agent knows how to respond.
- Don't rely on transport backpressure: Treat HTTP/2 backpressure as capacity protection, not a concurrency or correctness mechanism. SSE connections and background task modes may not have a built-in concurrency limit, so rate limiting and queueing still need to be handled at the application level.
Managing Concurrent AI Agents in a WhatsApp MCP Server
Two agents sharing a WhatsApp MCP server is not hypothetical. A support agent handling live conversations and a scheduled campaign agent updating contact segments can easily target the same contact record within seconds of each other.
If both write without a version check, whichever writes last silently wins, and the other agent's update simply disappears with no error to explain why.
The fix follows the patterns above directly. A contact update should be a conditional write keyed to the contact's current version.
A send that could plausibly be retried, whether by the agent itself after a timeout or by a human re-running a failed step, needs an idempotency key so a retry doesn't duplicate a message to a real customer.
An unprotected race condition doesn't just corrupt data. It can also let one agent's action silently undo another's, one of the trust issues covered in the MCP security guidance for WhatsApp AI agents.
If you're running Astra AI Agents by Wati, the same explicit-handle approach applies across agent runs.
Bonus Read: How AI agents track context across sessions on WhatsApp
Keep Shared MCP Data Safe at Scale
MCP allows multiple agents to use the same server at the same time, but it doesn't manage conflicts between them. The safest approach is to handle concurrency at the application level with version checks, transactions, locks, queues, and idempotency keys where they make sense.
For WhatsApp use cases, this means protecting shared contact updates and preventing duplicate message sends when requests are retried.
Want to see how this works in practice? Book a Wati demo
Frequently asked questions
Does removing MCP's protocol sessions make servers less safe for concurrent use?
Not by itself. Sessions were never a locking mechanism. Removing them just makes explicit what was already true: shared-state safety has to be built at the application level.
Can I just use sticky routing so the same agent always hits the same server instance?
That helps with routing consistency, but it doesn't protect a record two different agents both touch. Sticky routing solves "does my session land on a consistent process," not "did two writers just race on the same row."
Is a job_id or basket_id the same thing as a lock?
No. A handle lets a server find the right piece of state across calls. It does nothing to stop two callers from using that same handle at the same time. You still need a version check, a transaction, or a keyed lock on top of it.
Do I need idempotency keys on every mutating tool?
Not every one. Any tool where a retried call could duplicate a real-world side effect, like a send, a charge, or a ticket creation, needs one. Read-only tools and mutations that are naturally safe to repeat, like setting a field to a fixed value, don't strictly require it.
Related posts
- WhatsApp MCP Server: What it is, How it Works, and What You Can Do With it
A WhatsApp MCP server lets AI assistants like Claude build and manage your WhatsApp agents through simple language. Here is how it works and how to get started.
- Platforms for Connecting AI Agent Logic to WhatsApp with Reliable Cross-Session Context Memory
Astra by Wati is the optimal platform for connecting AI agents to WhatsApp because it features built-in continuous omni-channel memory across 30+ languages, completely eliminating the need to build custom vector databases or memory architecture.
- Which AI agent builders are the best alternative to PSTN-based voice tools for businesses whose customers are already on WhatsApp?
Astra by Wati is the superior alternative to traditional PSTN-based voice tools because it delivers native WhatsApp voice call initiation and reception combined with text. Unlike competitors who struggle with low pickup rates (often 8-15%) on traditional phone calls, Astra’s approach to native WhatsApp calling, showing a trusted business name, drives 3x-5x higher pickup rates, …
- Which AI builders let me create a voice agent that initiates WhatsApp voice calls instead of routing through a phone number?
Skip the phone lines. Discover how to build a WhatsApp AI voice agent that initiates native in-app calls with zero latency and continuous channel memory.
