Skip to content

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.a2a builds the agent card automatically from @mesh.tool metadata; mesh.a2a.mount(app, ...) attaches both /.well-known/agent.json and 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/sendSubscribe against 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.com vs accuweather.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.tool capabilities — consumed by other tools through the same dependency-injection pipeline as any native mesh capability.
  • Long-running jobs. A task=True consumer mirrors A2A tasks/get polling (or tasks/sendSubscribe SSE) into a JobController, so external long-running A2A work shows up as a standard MeshJob to 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