I killed the bridge instance that started an A2A task. Another instance resumed it, and the task completed without losing its context.
The original instance was gone. Its in-memory state was gone. But the task was still accessible, and the client could continue interacting with it.
That was the exact failure scenario I wanted to test when I started building a2a-mcp-bridge.
The 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.
Making 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.
How does an MCP client resume a task if the bridge that started it is no longer running?
My approach was to build a bridge that does not need to remember the task at all.
Consider a simple example.
An 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.
Now imagine the agent needs clarification before completing the summary.
It asks, “Would you prefer a concise overview or a detailed technical summary?”
The task is not finished. It is waiting for additional input.
The MCP client must be able to receive that question, provide an answer, and continue the same task, preserving its identity and context.
A basic bridge can translate the first request. But what happens after the first response?
The 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.
One approach is to maintain a lookup table in memory, associating each MCP task identifier with its corresponding A2A task.
That works while a single bridge process stays alive.
Now consider a deployment with three bridge instances behind a load balancer. The first request reaches instance A. The next request reaches instance B.
If 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.
The remote agent may still be holding the task. The problem is that the bridge has lost the information needed to reach it.
This is the failure mode I wanted to eliminate.
Imagine leaving a package at a warehouse.
The 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.
Now imagine receiving a sealed claim ticket that securely identifies the warehouse and your package.
Any authorized reception desk that can interpret the ticket can locate the package. It does not need the original employee’s notebook.
That is the idea behind the task handles in this bridge.
The remote A2A agent is the warehouse. The MCP bridge is the reception desk. The sealed task identifier is the claim ticket.
The 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.
MCP and A2A address complementary parts of agent interoperability.
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.
The distinction is particularly important for asynchronous work.
An MCP client may need to invoke a tool, while an A2A agent may respond by creating a task that remains active, s for clarification, requires authentication, or eventually produces artifacts.
The MCP 2026–07–28 specification made stateless operation a core protocol feature. Individual requests became self-describing, and long-running operations moved into the Tasks extension.
On the other side, A2A 1.0 provides a task-oriented interface with operations such as SendMessage, GetTask, and CancelTask.
These developments make it practical to connect the two protocols through a request-oriented bridge.
But a stateless MCP protocol does not automatically make the entire integration stateless.
A bridge can still introduce its own stateful dependency by storing task mappings in memory or a shared database.
I built a2a-mcp-bridge around a specific requirement: a task should remain accessible through another bridge instance without requiring a bridge-side task store.
The project is a protocol adapter, not a new agent framework or a replacement for either specification.
The bridge exposes configured A2A agents as MCP tools.
It discovers each agent through its Agent Card, translates advertised skills into MCP tool definitions, and forwards tool invocations through the A2A JSON-RPC interface.
A typical interaction has four stages.
1. Discover the agent’s capabilities
When the MCP client requests tools/list, the bridge retrieves each configured A2A agent's Agent Card from /.well-known/agent-card.json.
Each advertised skill becomes a namespaced MCP tool.
For example, an agent configured as docs with a skill named summarize becomes the MCP tool docs__summarize.
2. Invoke the agent
The MCP client calls tools/call, and the bridge translates the invocation into an A2A SendMessage request.
The agent may return an immediate message or create a task that continues beyond the initial request.
If a task is created, the bridge produces an opaque task identifier containing the information needed to find it again.
3. Resume an interrupted task
The client uses tasks/get to retrieve the current status.
If the agent requires additional input, the bridge translates that interruption into the MCP Tasks extension’s input mechanism.
The client supplies the response through tasks/update, and the bridge sends it to the original A2A task.
4. Retrieve the result
When the agent completes its work, the client can retrieve the terminal status and a summary of the returned artifacts.
The 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.
The central design decision was to avoid a server-side mapping between MCP task identifiers and A2A tasks.
Instead 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.
Each handle contains five fields:
The 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.
The client receives this opaque value as its MCP taskId.
When a later request arrives, the receiving bridge instance unseals the handle, recovers the remote identifiers, and contacts the appropriate A2A agent.
It does not need the original request or an entry in a shared task database.
This is what makes task resumption independent of the instance that initiated it.
It 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.
The bridge also does not make the underlying A2A service stateless. The remote agent must continue retaining its own task.
I wanted to verify the architecture under the failure it was designed to handle.
The 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.
The test initiated a task and allowed it to reach input_required. It then terminated the specific bridge instance that had received the original request.
The client submitted the outstanding answer through the load balancer. A different instance processed it, and another retrieved the completed task.
The recorded execution showed:
tools/call bridge2 task openedtasks/get bridge3 input_requiredkill bridge2 original instance terminatedtasks/update bridge1 answer submittedtasks/get bridge3 completed
PASS
The Bridge-Instance-Id response header made it possible to identify the instance serving each request.
The result demonstrated the property I was testing: a bridge instance that had never received the original invocation could still continue the same task.
I also tested restarting the entire pool while a task was d. After all three instances were restarted, the task could still be resumed through its sealed handle.
These 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.
The sealed-handle design creates an additional operational concern.
If every handle is encrypted with a key, replacing that key can make existing handles unreadable.
The 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.
In 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.
The 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.
A protocol bridge must do more than forward messages.
It must also translate the meaning of a task’s state.
A2A 1.0 defines eight task states, while MCP’s Tasks extension defines five statuses. Some A2A states therefore share the same MCP representation.
For example, A2A SUBMITTED and WORKING both map to MCP working. A2A FAILED and REJECTED map to MCP failed.
The bridge implements the mapping as an exhaustive transformation in mapping.py.
An unrecognized A2A state raises an error rather than silently falling back to a default status.
This 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.
The 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.
One of the more subtle integration problems appears when an A2A task s.
A2A distinguishes between TASK_STATE_INPUT_REQUIRED, where an agent needs additional information, and TASK_STATE_AUTH_REQUIRED, where it needs authentication.
Both correspond to MCP’s input_required status, but treating them identically could create a security problem.
Consider the difference between asking a user which document format they prefer and asking them to authenticate to an external service.
The first can be answered through the ordinary task input mechanism.
The second should not automatically become a password or API-key field inside a model-facing conversation.
The bridge separates these paths.
For 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.
For 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.
Most importantly, acknowledging an authentication interruption through tasks/update does not forward credential content to the remote task.
Authentication is expected to occur out of band, and the MCP client can subsequently check whether the agent has resumed.
This 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.
The bridge is implemented in Python and organized into eight modules with separate responsibilities.
server.py handles Streamable HTTP requests, JSON-RPC routing, and validation. discover.py retrieves Agent Cards and projects their skills into MCP tools.
tasks.py manages invocation, polling, input responses, and cancellation. mapping.py handles protocol translations, while state.py implements the sealed task-handle format.
a2a_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.
The architecture intentionally avoids a bridge-side task database, shared cache, and in-memory task registry.
The 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.
Those boundaries are important to the current implementation.
The 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.
I also ran targeted scenarios from the official MCP conformance suite, including stateless server behavior, HTTP header validation, and tool listing.
These checks helped validate specific aspects of the implementation, but they should not be interpreted as proof of complete conformance across every protocol scenario.
The protocol decisions and implementation references are documented in SPEC-NOTES.md.
The repository includes a local demonstration that starts three bridge processes, a fake A2A agent, and a round-robin proxy.
After installing the project with Python 3.12 or later, the demonstration can be run from the repository root:
.venv/bin/python demo/run_local.py
The script exercises instance termination, a full bridge-pool restart, and encryption key rotation.
The corresponding code is available in the demo directory.
The next area I want to address is full artifact retrieval.
The 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.
Other potential improvements include more accurate task-creation timestamps, opt-in authentication negotiation, and identity propagation across agent chains.
For now, the most meaningful result is the behavior demonstrated by the multi-instance tests.
The bridge instance that started a task did not have to remain alive for the task to finish.
That 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.
The complete implementation, tests, and demonstrations are available in a2a-mcp-bridge on GitHub.
If 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.
Beyond Protocol Translation: Building a Stateless MCP-A2A Bridge was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.