{"slug": "beyond-protocol-translation-building-a-stateless-mcp-a2a-bridge", "title": "Beyond Protocol Translation: Building a Stateless MCP-A2A Bridge", "summary": "Developer Aarushi Tandon built a2a-mcp-bridge, an open-source project that connects the Model Context Protocol (MCP) and Agent2Agent (A2A) protocols so an MCP client can resume a multi-step A2A task even after the bridge instance that started it is killed. The bridge avoids an in-memory lookup table by encoding task identity in sealed task handles, letting any instance behind a load balancer pick up the task; Tandon tested this by terminating the originating instance and confirming another instance completed the task without losing context. The work addresses a failure mode in agent interoperability where a crashed or load-balanced bridge loses the mapping between MCP task identifiers and the remote A2A agent's task.", "body_md": "I killed the bridge instance that started an A2A task. Another instance resumed it, and the task completed without losing its context.\n\nThe original instance was gone. Its in-memory state was gone. But the task was still accessible, and the client could continue interacting with it.\n\nThat was the exact failure scenario I wanted to test when I started building [a2a-mcp-bridge](https://github.com/aarushitandon0/a2a-mcp-bridge).\n\nThe project connects two protocols used in agent systems: the Model Context Protocol (MCP), which allows applications to discover and invoke tools, and Agent2Agent (A2A), which allows independent agents to communicate and manage tasks.\n\nMaking an A2A agent available as an MCP tool sounds like a straightforward translation problem. In practice, the difficult part begins when a task cannot be completed in a single request.\n\n**How does an MCP client resume a task if the bridge that started it is no longer running?**\n\nMy approach was to build a bridge that does not need to remember the task at all.\n\nConsider a simple example.\n\nAn MCP client wants a documentation agent to summarize a technical report. The documentation agent is an A2A service, so the MCP client cannot invoke its skills directly through an ordinary MCP tool call without an adapter.\n\nNow imagine the agent needs clarification before completing the summary.\n\nIt asks, “Would you prefer a concise overview or a detailed technical summary?”\n\nThe task is not finished. It is waiting for additional input.\n\nThe MCP client must be able to receive that question, provide an answer, and continue the **same task**, preserving its identity and context.\n\nA basic bridge can translate the first request. But what happens after the first response?\n\nThe bridge has to know which A2A agent owns the task, which task identifier to use, which conversation context belongs to it, and how to translate the agent’s current state into something the MCP client understands.\n\nOne approach is to maintain a lookup table in memory, associating each MCP task identifier with its corresponding A2A task.\n\nThat works while a single bridge process stays alive.\n\nNow consider a deployment with three bridge instances behind a load balancer. The first request reaches instance A. The next request reaches instance B.\n\nIf only instance A knows which A2A task belongs to the MCP task identifier, instance B cannot resume it. If instance A crashes, the mapping disappears entirely.\n\nThe remote agent may still be holding the task. The problem is that the bridge has lost the information needed to reach it.\n\nThis is the failure mode I wanted to eliminate.\n\nImagine leaving a package at a warehouse.\n\nThe package remains safely inside the warehouse, but the reception desk keeps the only record of which package belongs to you. If that desk loses its records, another employee cannot find your package, even though it is still there.\n\nNow imagine receiving a sealed claim ticket that securely identifies the warehouse and your package.\n\nAny authorized reception desk that can interpret the ticket can locate the package. It does not need the original employee’s notebook.\n\nThat is the idea behind the task handles in this bridge.\n\nThe remote A2A agent is the warehouse. The MCP bridge is the reception desk. The sealed task identifier is the claim ticket.\n\nThe analogy has an important boundary: the ticket identifies the task, but it does not perform the work or replace authorization. The remote agent still owns the task, and the system still needs appropriate access controls.\n\nMCP and A2A address complementary parts of agent interoperability.\n\n**MCP** standardizes how model-facing applications discover and invoke tools. **A2A** standardizes how independent agents exchange messages, identify capabilities, and manage tasks that may continue across multiple interactions.\n\nThe distinction is particularly important for asynchronous work.\n\nAn MCP client may need to invoke a tool, while an A2A agent may respond by creating a task that remains active, pauses for clarification, requires authentication, or eventually produces artifacts.\n\nThe [MCP 2026–07–28 specification](https://blog.modelcontextprotocol.io/posts/2026-07-28/) made stateless operation a core protocol feature. Individual requests became self-describing, and long-running operations moved into the [Tasks extension](https://modelcontextprotocol.github.io/ext-tasks/specification/2026-07-28/tasks.html).\n\nOn the other side, [A2A 1.0](https://a2a-protocol.org/v1.0.0/specification/) provides a task-oriented interface with operations such as SendMessage, GetTask, and CancelTask.\n\nThese developments make it practical to connect the two protocols through a request-oriented bridge.\n\nBut a stateless MCP protocol does not automatically make the entire integration stateless.\n\nA bridge can still introduce its own stateful dependency by storing task mappings in memory or a shared database.\n\nI built a2a-mcp-bridge around a specific requirement: **a task should remain accessible through another bridge instance without requiring a bridge-side task store.**\n\nThe project is a protocol adapter, not a new agent framework or a replacement for either specification.\n\nThe bridge exposes configured A2A agents as MCP tools.\n\nIt discovers each agent through its Agent Card, translates advertised skills into MCP tool definitions, and forwards tool invocations through the A2A JSON-RPC interface.\n\nA typical interaction has four stages.\n\n**1. Discover the agent’s capabilities**\n\nWhen the MCP client requests tools/list, the bridge retrieves each configured A2A agent's Agent Card from /.well-known/agent-card.json.\n\nEach advertised skill becomes a namespaced MCP tool.\n\nFor example, an agent configured as docs with a skill named summarize becomes the MCP tool docs__summarize.\n\n**2. Invoke the agent**\n\nThe MCP client calls tools/call, and the bridge translates the invocation into an A2A SendMessage request.\n\nThe agent may return an immediate message or create a task that continues beyond the initial request.\n\nIf a task is created, the bridge produces an opaque task identifier containing the information needed to find it again.\n\n**3. Resume an interrupted task**\n\nThe client uses tasks/get to retrieve the current status.\n\nIf the agent requires additional input, the bridge translates that interruption into the MCP Tasks extension’s input mechanism.\n\nThe client supplies the response through tasks/update, and the bridge sends it to the original A2A task.\n\n**4. Retrieve the result**\n\nWhen the agent completes its work, the client can retrieve the terminal status and a summary of the returned artifacts.\n\nThe important distinction is that the bridge does not own the underlying work. It translates the interaction while the A2A agent remains responsible for task execution and persistence.\n\nThe central design decision was to avoid a server-side mapping between MCP task identifiers and A2A tasks.\n\nInstead of returning a random identifier that requires a lookup, the bridge returns an encrypted, authenticated handle that carries the information needed to contact the original agent.\n\nEach handle contains five fields:\n\nThe fields are packed into a compact CBOR array, encrypted and authenticated using AES-256-GCM with a fresh nonce, and encoded as unpadded base64url text.\n\nThe client receives this opaque value as its MCP taskId.\n\nWhen a later request arrives, the receiving bridge instance unseals the handle, recovers the remote identifiers, and contacts the appropriate A2A agent.\n\nIt does not need the original request or an entry in a shared task database.\n\nThis is what makes task resumption independent of the instance that initiated it.\n\nIt also introduces some deliberate trade-offs. Handles are longer than ordinary UUIDs, they expire, and all bridge instances must have compatible encryption keys and agent configuration.\n\nThe bridge also does not make the underlying A2A service stateless. The remote agent must continue retaining its own task.\n\nI wanted to verify the architecture under the failure it was designed to handle.\n\nThe test environment consisted of three bridge instances behind a round-robin load balancer, with no session affinity. All three communicated with the same fake A2A agent.\n\nThe test initiated a task and allowed it to reach input_required. It then terminated the specific bridge instance that had received the original request.\n\nThe client submitted the outstanding answer through the load balancer. A different instance processed it, and another retrieved the completed task.\n\nThe recorded execution showed:\n\n```\ntools/call      bridge2    task openedtasks/get       bridge3    input_requiredkill bridge2               original instance terminatedtasks/update    bridge1    answer submittedtasks/get       bridge3    completed\nPASS\n```\n\nThe Bridge-Instance-Id response header made it possible to identify the instance serving each request.\n\nThe result demonstrated the property I was testing: a bridge instance that had never received the original invocation could still continue the same task.\n\nI also tested restarting the entire pool while a task was paused. After all three instances were restarted, the task could still be resumed through its sealed handle.\n\nThese tests demonstrate recovery from bridge-instance failures in the supplied environment. They do not imply that the remote A2A agent itself can fail without losing its task.\n\nThe sealed-handle design creates an additional operational concern.\n\nIf every handle is encrypted with a key, replacing that key can make existing handles unreadable.\n\nThe bridge supports a two-key transition. BRIDGE_STATE_KEY is used to create new handles, while BRIDGE_STATE_KEY_PREVIOUS allows instances to read handles issued under the previous key.\n\nIn the recorded demonstration, the bridge pool was rotated one instance at a time. A handle issued under the old key remained usable, and a newly created handle used the replacement key.\n\nThe previous key must remain available until the handles that depend on it no longer need to be accepted. Removing it prematurely can still prevent tasks from being resumed through the bridge.\n\nA protocol bridge must do more than forward messages.\n\nIt must also translate the meaning of a task’s state.\n\nA2A 1.0 defines eight task states, while MCP’s Tasks extension defines five statuses. Some A2A states therefore share the same MCP representation.\n\nFor example, A2A SUBMITTED and WORKING both map to MCP working. A2A FAILED and REJECTED map to MCP failed.\n\nThe bridge implements the mapping as an exhaustive transformation in mapping.py.\n\nAn unrecognized A2A state raises an error rather than silently falling back to a default status.\n\nThis matters because silently interpreting an unexpected state as working could make a task appear active indefinitely, even when the agent has entered an unsupported or terminal state.\n\nThe mapping functions contain no network I/O. They operate on already-fetched protocol data, allowing the translation behavior to be tested independently from the HTTP server and the remote agent.\n\nOne of the more subtle integration problems appears when an A2A task pauses.\n\nA2A distinguishes between TASK_STATE_INPUT_REQUIRED, where an agent needs additional information, and TASK_STATE_AUTH_REQUIRED, where it needs authentication.\n\nBoth correspond to MCP’s input_required status, but treating them identically could create a security problem.\n\nConsider the difference between asking a user which document format they prefer and asking them to authenticate to an external service.\n\nThe first can be answered through the ordinary task input mechanism.\n\nThe second should not automatically become a password or API-key field inside a model-facing conversation.\n\nThe bridge separates these paths.\n\nFor ordinary input requests, it derives an input schema from the agent’s structured data when available, or falls back to a free-text field. The answer is then forwarded to the same A2A task.\n\nFor authentication interruptions, the bridge exposes a different request shape. When an authentication URL is available, it uses URL-mode elicitation without a credential-entry schema. Otherwise, it provides a message with no fillable properties.\n\nMost importantly, acknowledging an authentication interruption through tasks/update does not forward credential content to the remote task.\n\nAuthentication is expected to occur out of band, and the MCP client can subsequently check whether the agent has resumed.\n\nThis is a deliberate security boundary rather than complete support for every A2A authentication workflow. Agents that require in-band negotiation during AUTH_REQUIRED are not currently supported through that path.\n\nThe bridge is implemented in Python and organized into eight modules with separate responsibilities.\n\nserver.py handles Streamable HTTP requests, JSON-RPC routing, and validation. discover.py retrieves Agent Cards and projects their skills into MCP tools.\n\ntasks.py manages invocation, polling, input responses, and cancellation. mapping.py handles protocol translations, while state.py implements the sealed task-handle format.\n\na2a_client.py communicates with remote A2A services through the official SDK. artifacts.py summarizes the returned results, and config.py loads agent definitions and encryption keys from environment variables.\n\nThe architecture intentionally avoids a bridge-side task database, shared cache, and in-memory task registry.\n\nThe bridge currently supports A2A agents advertising a JSON-RPC interface. It does not provide every A2A transport binding, full artifact retrieval, or a general-purpose multi-tenant authorization layer.\n\nThose boundaries are important to the current implementation.\n\nThe repository documents a suite of 102 tests covering the task lifecycle, handle encoding and rejection, protocol translation, discovery, HTTP validation, and cross-instance task behavior.\n\nI also ran targeted scenarios from the official [MCP conformance suite](https://github.com/modelcontextprotocol/conformance), including stateless server behavior, HTTP header validation, and tool listing.\n\nThese checks helped validate specific aspects of the implementation, but they should not be interpreted as proof of complete conformance across every protocol scenario.\n\nThe protocol decisions and implementation references are documented in [SPEC-NOTES.md](https://github.com/aarushitandon0/a2a-mcp-bridge/blob/main/SPEC-NOTES.md).\n\nThe repository includes a local demonstration that starts three bridge processes, a fake A2A agent, and a round-robin proxy.\n\nAfter installing the project with Python 3.12 or later, the demonstration can be run from the repository root:\n\n```\n.venv/bin/python demo/run_local.py\n```\n\nThe script exercises instance termination, a full bridge-pool restart, and encryption key rotation.\n\nThe corresponding code is available in the [demo directory](https://github.com/aarushitandon0/a2a-mcp-bridge/tree/main/demo).\n\nThe next area I want to address is full artifact retrieval.\n\nThe bridge currently summarizes large artifacts rather than returning every part of their content. A future resources/read interface could use sealed resource identifiers to make those artifacts retrievable without introducing a server-side lookup table.\n\nOther potential improvements include more accurate task-creation timestamps, opt-in authentication negotiation, and identity propagation across agent chains.\n\nFor now, the most meaningful result is the behavior demonstrated by the multi-instance tests.\n\nThe bridge instance that started a task did not have to remain alive for the task to finish.\n\nThat is the property I set out to build: **an MCP-to-A2A integration where task continuity does not depend on the memory or lifetime of the bridge process that initiated it.**\n\nThe complete implementation, tests, and demonstrations are available in [a2a-mcp-bridge on GitHub](https://github.com/aarushitandon0/a2a-mcp-bridge).\n\nIf you are working with MCP, A2A, or long-running agent integrations, I would welcome feedback, bug reports, and contributions, particularly from developers testing the bridge with real A2A services.\n\n[Beyond Protocol Translation: Building a Stateless MCP-A2A Bridge](https://pub.towardsai.net/beyond-protocol-translation-building-a-stateless-mcp-a2a-bridge-750a0f0a7804) was originally published in [Towards AI](https://pub.towardsai.net) on Medium, where people are continuing the conversation by highlighting and responding to this story.", "url": "https://wpnews.pro/news/beyond-protocol-translation-building-a-stateless-mcp-a2a-bridge", "canonical_source": "https://pub.towardsai.net/beyond-protocol-translation-building-a-stateless-mcp-a2a-bridge-750a0f0a7804?source=rss----98111c9905da---4", "published_at": "2026-11-11 22:31:01+00:00", "updated_at": "2026-11-11 22:51:40.175795+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "developer-tools"], "entities": ["a2a-mcp-bridge", "Aarushi Tandon", "Model Context Protocol", "Agent2Agent", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/beyond-protocol-translation-building-a-stateless-mcp-a2a-bridge", "markdown": "https://wpnews.pro/news/beyond-protocol-translation-building-a-stateless-mcp-a2a-bridge.md", "text": "https://wpnews.pro/news/beyond-protocol-translation-building-a-stateless-mcp-a2a-bridge.txt", "jsonld": "https://wpnews.pro/news/beyond-protocol-translation-building-a-stateless-mcp-a2a-bridge.jsonld"}}