MCP got the headlines, and deservedly so: it gave agents a standard way to call
tools — search the docs, query the database, hit an API. But tools are only
half of a multi-agent system. The other half is agents talking to each
other: a support agent handing off to a billing agent, a research agent
delegating to a specialist, a long-running task that streams progress back.
That's the job of the Agent2Agent (A2A) protocol. And the moment you try to
debug an A2A call, you discover it's nothing like pointing curl at a REST route:
- there's a version split where the two wire formats are not compatible;
- requests ride inside a JSON-RPC envelope;
- work is asynchronous — you send a message and then poll or stream a task;
- discovery goes through an Agent Card;
- signed requests introduce a key-trust trap that's easy to get backwards.
This article is the debugging field guide I wish existed — and how to inspect an
A2A conversation the same way you inspect an HTTP request.
MCP vs A2A in one line
| MCP | A2A | |
|---|---|---|
| Connects | an agent to tools and resources | an agent to another agent |
| Unit of work | a tool call | a message that starts a task |
| Typical shape | request/response, short-lived | long-lived, stateful, often streaming |
| Discovery | tool list | an Agent Card describing skills and auth |
If MCP is "call this function," A2A is "delegate this to a capable counterpart
and track the job."
1. Pin the version before you debug anything else
The first thing that looks like a server bug is often just a version mismatch.
A2A 1.0 and 0.3 are explicitly not wire-compatible — the JSON-RPC
method names differ:
| Capability | A2A 1.0 | A2A 0.3 |
|---|---|---|
| Send a message | SendMessage |
message/send |
| Stream a message | SendStreamingMessage |
message/stream |
| Get a task | GetTask |
tasks/get |
| Cancel a task | CancelTask |
tasks/cancel |
| Subscribe to updates | SubscribeToTask |
tasks/resubscribe |
| Authenticated card | GetExtendedAgentCard |
agent/getAuthenticatedExtendedCard |
A 1.0 client sending message/send will be rejected by a 1.0 server, and vice
versa. Before you inspect payloads, confirm both sides agree on the version.
A2A 1.0 also defines three bindings — JSON-RPC, HTTP+JSON (REST) and
gRPC — while 0.3 here is JSON-RPC only. Switching bindings changes the
envelope: JSON-RPC wraps everything; REST and gRPC take the bare params
object with no jsonrpc/id wrapper. If you paste a JSON-RPC body into a REST
binding, that's a malformed request, not an agent failure.
2. Discover the agent with its Agent Card
You don't guess an agent's endpoint or capabilities. You fetch its public Agent
Card, conventionally served at:
{endpoint}/.well-known/agent-card.json
The card tells you what the agent can do (its skills), where it lives, which
transport it supports, and what authentication it expects. In a debugging tool
this is a one-click "fetch the public card" step — inspect the card JSON first,
because half of "the agent ignored my request" bugs are really "I sent a method
this agent never advertised."
3. A message starts a task; it doesn't return an answer
This is the conceptual shift from REST. You don't call an endpoint and get the
result synchronously. You send a message, and the agent creates a task
that moves through states like working, input-required, completed and
failed. You then GetTask, ListTasks, CancelTask, or subscribe to
updates.
A minimal A2A 1.0 SendMessage over JSON-RPC looks like this:
{
"jsonrpc": "2.0",
"id": "a2a-0001",
"method": "SendMessage",
"params": {
"message": {
"messageId": "a2a-msg-0001",
"role": "ROLE_USER",
"parts": [{ "text": "Where is order A-10293?" }]
}
}
}
The same message on 0.3 is a different shape — lowercase role, a typed
part, and an explicit message kind:
{
"jsonrpc": "2.0",
"id": "a2a-0001",
"method": "message/send",
"params": {
"message": {
"messageId": "a2a-msg-0001",
"role": "user",
"kind": "message",
"parts": [{ "kind": "text", "text": "Where is order A-10293?" }]
}
}
}
Task follow-ups take the task id:
{
"jsonrpc": "2.0",
"id": "a2a-0002",
"method": "GetTask",
"params": { "id": "<task-id-from-the-send-response>" }
}
When you're debugging, send the message, capture the returned task id, and then
walk the lifecycle explicitly. Treating A2A like a single synchronous RPC is the
most common source of "it sometimes returns nothing."
4. Long work streams — know which methods open a stream
For anything that takes time, the agent doesn't block on one response; it
streams task updates (over SSE in the JSON-RPC binding). The streaming methods
are SendStreamingMessage and SubscribeToTask on 1.0, and message/stream
and tasks/resubscribe on 0.3.
Debugging these means inspecting the event sequence, not just the final
frame: did the task go working → artifact/progress events → completed, or
did it stall in input-required waiting on something you never sent? A stream
that ends without a terminal state is a different bug from one that returns an
error event, and a REST client that only reads the first chunk will miss both.
5. The signature trap: don't trust a key URL the card hands you
Authenticated A2A uses signed requests verified against a JWKS (a JSON Web
Key Set). Here's the subtle failure: an Agent Card can advertise where to fetch
keys, but blindly fetching keys from a URL the remote card itself provides means
the remote party is handing you both the lock and the key. A spoofed or
compromised card can then point you at attacker-controlled keys.
The safe pattern:
- obtain the agent's public JWKS through a trusted out-of-band channel and pin it, rather than following a key URL embedded in the card;
- understand the verification scope — the standard A2A fields are authenticated, while arbitrary custom fields are not, so don't treat an unverified custom claim as provenance;
- verify the signature before you trust any task instruction.
This is the same principle as TLS pinning and webhook-signature verification:
the trust anchor has to arrive independently of the signed payload.
6. Debug it like the HTTP request it ultimately is
Once you know the version, binding, card and task lifecycle, an A2A call is
debuggable with the same discipline you already use for REST — you just need a
client that speaks the envelope. In Powerduck, A2A sits alongside HTTP as a
first-class debug protocol: create a new A2A operation, pick the version
(1.0 / 0.3) and binding (JSON-RPC / HTTP+JSON / gRPC), paste the Agent Card URL
and fetch the public card, choose the method, edit the request from a valid
template, send it, and inspect the returned task or the streamed events.
That workflow enforces the rules above in the right order — unknown method or a
JSON-RPC envelope pasted into a REST binding is rejected up front, so you fix
the request shape before you go chasing agent behavior.
7. Exposing your own service as an agent is a separate step
Debugging an existing agent needs no server. If you want your business service
callable over A2A, you can download an A2A 1.0 adapter template, wire it to
your own HTTP handler, and run it yourself. It receives A2A requests and returns
your business results over JSON-RPC, REST or gRPC.
Two expectations to set correctly: the adapter is a thin protocol bridge — it
does not ship an AI model or a persistent task engine, and you bring the
business logic and auth. That's deliberate: A2A is the transport between
capable agents, not a replacement for what your agent actually does.
One contract, three ways to reach an API
What makes this manageable long-term is describing the underlying service once
and reaching it through the right surface for the caller: humans read the docs,
agents call tools over MCP, and other agents delegate over A2A. The protocol
differs; the contract shouldn't.
If you're integrating agents today, do the boring debugging first: pin the
version, fetch the card, send one message, capture the task id, walk the
lifecycle, pin the JWKS out of band. Most "agent interoperability" problems
dissolve into one of those five steps.
You can create and send A2A 1.0/0.3 requests (JSON-RPC, REST and gRPC), fetch
Agent Cards, inspect task/stream responses, and verify signatures in the free
web app at powerduck.com/app.
Have you hit an A2A integration yet — version mismatch, streaming, or the
signature/JWKS trust step? Tell me which one burned the most time in the
comments; I'm collecting the sharp edges as the protocol settles.
Top comments (0)