The Model Context Protocol's Tasks extension (SEP-2663, now Final) fixes a real operational problem: a long-running tool call should not hold a connection open for minutes. Instead of blocking, the server returns a task handle, and the client either polls tasks/get or subscribes to notifications/tasks. What the specification does not define is where the task's state lives between working and completed. That omission is the interesting part, because it decides what breaks at the second replica.
Alibaba Cloud Native Community's write-up walks through the contradiction directly. SEP-2575, "Make MCP Stateless", requires any replica to serve any request, and explicitly pushes persistence onto Tasks. But the obvious implementation of Tasks keeps state in the memory of whichever process accepted the call. One replica plus one process works. A deployment with autoscaling does not.
The post names three failure modes, and they are worth restating precisely because they are ordinary distributed-systems failures wearing protocol clothing. First, the client's second poll may land on a replica that never saw the task, so the task appears not to exist. The protocol's answer is the Mcp-Name header, which asks the gateway for routing affinity. Affinity is a gateway behaviour, not a server guarantee: a scale-in, a restart, or a gateway that ignores the header all break it. Second, if the replica holding the record dies, the state dies with it, even though the work may still be running or may already have produced a result. Third, the client cannot tell a task that genuinely does not exist from a request that simply reached the wrong machine, so its only rational move is to stop polling and abandon work it already paid for.
The proposed answer, called Task as LiteTopic, gives every task its own LiteTopic channel keyed by the task ID, created on demand and reclaimed when a TTL expires. Submission writes the first record (working, plus a creation timestamp) and only then returns the handle. The execution pool appends the terminal state and the result to the same channel. Any replica asked about the task reads the channel and takes the last record as current state.
That last sentence is the design's load-bearing claim: the messages are the state, and the broker is the state store. It works because a channel maps to a single queue on a single broker, so write order equals read order and no reduction or priority-based reconciliation is required. The protocol permits a task to move from waiting-for-input back to in-progress; in an append-only ledger that is simply another record rather than a state-machine transition that has to be allowed for in advance. It is a defensible simplification, and it moves the hard part into delivery semantics.
The implementation detail that deserves more attention than the post gives it is the two consumption planes. A LiteTopic message is indexed twice in the broker core, once on the parent topic's queue and once on the task's channel. The execution pool consumes the parent topic as a shared consumer group with TAG filtering that accepts only submission commands and blocks result writes; without that filter the pool would treat its own output as a new task and feed itself indefinitely. Replica-side state following consumes the channel plane with an independent consumer group per replica, because a shared group would make replicas compete for messages and reset each other's offsets, producing intermittent missing reads. Both are the kind of defect that appears only under concurrency, and both are configuration rather than code.
Delivery is at-least-once, with failed executions redelivered automatically, which the post correctly pairs with an idempotency requirement. For a pure computation, rerunning appends an equivalent record. For a tool with external side effects — moving funds, sending mail, mutating a ticketing system — idempotency has to be enforced by the application, because the ledger cannot know whether the effect already happened. That is not a flaw in the design so much as a boundary the design makes visible, but it means the durability guarantee transfers responsibility to whoever writes the tool.
The open-source artefact is concrete: mcp-tasks-rocketmq/ in apache/rocketmq-ai, a Python implementation of SEP-2663 over LiteTopic with a RocketMQ execution pool and a runnable example. LiteTopic itself has been merged into the RocketMQ main branch. So the mechanism can be inspected rather than taken on faith.
What cannot be inspected is the cost side, and this is where the post is thinnest. It asserts that a single broker can host millions of channels, but gives no measurement of channel-creation churn, no retention cost per task, no replay latency for a cold read on a replica that has never seen the channel, and no failover test. Those numbers decide whether the design survives production. A channel-per-task model also doubles the index footprint of every task message, and the post does not quantify that overhead. Nor does it compare against the obvious alternatives — a compacted Kafka topic, a Redis hash with expiry, or a row in Postgres — each of which has a different latency and blast-radius profile.
There is also an unresolved tension inside the description. Results are said to remain in the channel so any replica can answer, while channels are said to be reclaimed at TTL expiry. Both can be true only if retention is set longer than the longest task lifetime plus the longest plausible re-query window, and that is a policy decision, not a property of the system. Choosing it wrong produces exactly the failure the design set out to remove: a valid handle that resolves to nothing.
Finally, the write path assumes a mostly linear lifecycle. Appending records gives last-writer-wins ordering, which is fine while one execution owns a task. It is less obviously fine when a retry, a user-input resumption, and a cancellation can interleave, because nothing in an append-only channel arbitrates between them the way a compare-and-set on a state column would. The ordering guarantee is per channel on one broker; what happens to that guarantee during broker failover or partition migration is precisely the question a durability claim has to answer.
None of this makes the design wrong. Moving task state from process memory into messaging infrastructure is the right direction, and treating the protocol's silence as a deliberate boundary between semantics and infrastructure is a mature reading of the spec. The correction worth making is to the framing: replicas become stateless, but the system does not. Durability, ordering and query latency all migrate to the broker, which stops being transport and becomes a correctness dependency. That is a reasonable trade, but it is a trade, and it should be measured before it is called a foundation.
The practical checklist for anyone adopting this shape is short. Pin retention above maximum task lifetime plus re-query window. Make every tool idempotent or refuse it. Test cold reads, broker failover, and concurrent resumption, not just the happy path. Measure channel churn and index overhead under realistic task volume. And keep the notifications path in scope: the post details the polling read well, but push fan-out to thousands of subscribed clients is a separate scaling problem that a per-task channel does not by itself solve.
Sources
Alibaba Cloud Native Community, "MCP Servers Are Stateless—Where Does Task State Live? RocketMQ LiteTopic Keeps a Ledger for Every Task" (26 October 2026): https://www.alibabacloud.com/blog/mcp-servers-are-stateless%E2%80%94where-does-task-state-live-rocketmq-litetopic-keeps-a-ledger-for-every-task_603670
Model Context Protocol specification references cited therein: Tasks extension (SEP-2663, Final) and "Make MCP Stateless" (SEP-2575).