A2A in MCP Mesh¶
MCP Mesh implements the A2A v1.0 protocol on both sides of the wire. A mesh agent can call OUT to an external A2A endpoint (consumer side) AND expose its own mesh tools as A2A skills (producer side). The shared transport between mesh agents stays MCP; A2A is the bridge to anything outside the mesh that speaks the protocol.
Why A2A in mesh¶
The A2A v1.0 spec defines an HTTP + JSON-RPC envelope for cross-vendor agent calls. It deliberately stops at the protocol — capability discovery is per-card, there is no resolver, no failover, no DDDI. MCP Mesh adds those layers around A2A:
- Producer (Python only today):
@mesh.a2abuilds the agent card automatically from@mesh.toolmetadata;mesh.a2a.mount(app, ...)attaches both/.well-known/agent.jsonand the JSON-RPC entrypoint to a user-owned FastAPI app. Sync, long-running (task=True), and SSE handlers are all supported. - Consumer (Python, Java, TypeScript): a mesh capability whose body issues outbound
tasks/send/tasks/sendSubscribeagainst a foreign A2A backend. The bridge re-publishes the upstream skill as a normal mesh capability — downstream callers do not need to know they are talking to A2A.
The strategic value-add¶
Once a foreign A2A backend is bridged into the mesh as a capability, every mesh feature applies on top — without any changes to the protocol:
- Capability+tag failover. Two consumers bridging the same logical capability against different vendors (
weather.comvsaccuweather.com) auto-tag with their agent name. Downstream callers either pin a specific provider via tags or let the resolver pick a healthy one. - Health-driven rewiring. When a consumer dies, the registry's orphan-reset transparently routes new calls to a peer consumer in seconds.
- DDDI. A2A consumers are first-class
@mesh.toolcapabilities — consumed by other tools through the same dependency-injection pipeline as any native mesh capability. - Long-running jobs. A
task=Trueconsumer mirrors A2Atasks/getpolling (ortasks/sendSubscribeSSE) into aJobController, so external long-running A2A work shows up as a standardMeshJobto the caller.
Producer vs consumer¶
| Aspect | Producer | Consumer |
|---|---|---|
| Direction | Mesh tool exposed AS A2A | External A2A skill bridged INTO mesh |
| Decorator/marker | @mesh.a2a + mesh.a2a.mount(app, ...) (Python) | @mesh.a2a_consumer (Py) / @A2AConsumer (Java) / a2aConfig (TS) |
| Runtime support | Python | Python, Java, TypeScript |
| Card / discovery | Auto-generated at /.well-known/agent.json | Card fetched once at scaffold time (or --offline) |
| Long-running | Return JobProxy; framework parks the task | task=True body submits + bridge(JobController) |
| SSE | tasks/sendSubscribe handler returns JobProxy | A2AClient.subscribe(...) + stream.bridge(JobController) |
| Auth | Bearer enforcement on the JSON-RPC route | A2ABearer / authBearerEnv / tokenEnv |
A one-liner consumer¶
This is the simplest possible bridge — re-publish an external A2A get-date skill as a regular mesh current-date capability:
import json
import mesh
@app.tool()
@mesh.a2a_consumer(
capability="current-date",
a2a_url="http://upstream.example.com/agents/date",
a2a_skill_id="get-date",
)
async def current_date(_a2a: mesh.A2AClient = None) -> dict:
response = await _a2a.send(
message={"role": "user", "parts": [{"type": "text", "text": "now"}]},
)
return json.loads(response.artifact_text)
A downstream mesh tool then depends on current-date like any other capability — no awareness that the work is happening over A2A. Full multi-runtime walkthrough in the Consumer Quick Start.
Data flow¶
flowchart LR
EXT[External A2A backend<br/>e.g. weather.com] -->|tasks/send| BRIDGE
BRIDGE[Consumer mesh agent<br/>the bridge] -->|register capability| REG[Mesh Registry]
REG -->|resolve dependency| CALLER[Downstream caller<br/>any mesh agent]
CALLER -->|tools/call| BRIDGE
BRIDGE -->|outbound A2A| EXT The consumer agent is a regular @mesh.tool capability whose body happens to wrap an outbound A2A call. From the registry's perspective there is no special path — capability discovery, tag matching, and health propagation all work as they do for any native tool.
What's NOT in scope here¶
- A2A v1.0 protocol semantics. Wire format, JSON-RPC envelopes, task lifecycle states — see the A2A v1.0 spec and JSON-RPC 2.0.
- Authentication schemes beyond bearer. OAuth / mTLS are future work — Phase 1 ships bearer only (Authentication).
- Cross-runtime producer. Java and TypeScript producer support is future work (Producer (Python) header).
See also¶
- Consumer Quick Start — Python, TypeScript, and Java side-by-side
- Failover & Federation — the strategic differentiator
- Architecture & Decisions — why
bridge(JobController)and the cancel propagation chain