Part 2 of 6 · 6 min read
A2A and MCP: the honest comparison
Covers: A2A and MCP: the honest comparison; The Agent Card: a claim, not a fact.
A2A and MCP: the honest comparison
The usual line is that MCP connects an agent to tools and A2A connects an agent to other agents. That is a fair first sketch.
It needs two corrections.
- The gap has narrowed. MCP now has a Tasks extension for long-running calls. So "MCP calls are one request and one answer" is no longer a clean distinction. I read the page on 24 October 2026; check it, because it is an extension and may change.
- The real difference is who the callee is. A tool is a piece of code with a schema, which the caller can inspect and the operator can test. An agent is opaque: the caller knows what it says it does (its card), not how. That changes what you need: a way to discover it and see what it claims, a task that can pause for a question or a credential, an identity for the user on whose behalf you call, and a message and artifact model for results that are not one JSON blob.
| MCP tool call | A2A message to an agent | |
|---|---|---|
| Caller reads | a JSON schema, fills arguments | a card with skills in prose, sends a message |
| Result | one result (or a task, with the extension) | a message, or a task with state, history and artifacts |
| Can ask you back | only by an elicitation, outside the call | yes, as a state of the task (INPUT_REQUIRED) |
| Can need a credential | through the transport's authorization | yes, as a state of the task (AUTH_REQUIRED) |
| Callee's inner workings | usually visible to its operator | deliberately hidden |
| What can go wrong in a new way | a bad description | a card that lies, a delegation chain, a loop |
The answer I would give
"They overlap less than the slogan says and more than people think. I would use MCP when the other side is a capability I can describe with a schema and test as code, and A2A when it is a separate agent run by someone else, which may take long, ask questions, need permission, and which I should not have to understand. In practice the same system uses both: an agent calls tools over MCP and calls other agents over A2A, and the hard part is carrying the user's identity and the task's state across the boundary. I would not claim A2A is required for long-running work now that MCP has a Tasks extension."
The Agent Card: a claim, not a fact
An agent publishes a JSON Agent Card at /.well-known/agent-card.json. The one in the example is for a venue-finding agent:
{ "name": "Venue Finder", "version": "1.0.0",
"supportedInterfaces": [
{"url": "https://venue.example/a2a/v1", "protocolBinding": "JSONRPC", "protocolVersion": "1.0"},
{"url": "https://venue.example/rest", "protocolBinding": "HTTP+JSON", "protocolVersion": "1.0"} ],
"capabilities": {"streaming": true, "pushNotifications": true, "extendedAgentCard": true},
"securitySchemes": {"bearer": {"httpAuthSecurityScheme": {"scheme": "Bearer"}}},
"skills": [{"id": "find-venue", "name": "Find a venue", "description": "...", "examples": ["..."]}] }
Three things to take from it.
Discovery. The client fetches the card, checks that required fields are present (problems() lists what is missing), and picks the first interface in the card that it can speak (the specification gives the order to the agent, not the client). My choice: a card with no usable interface is an error that says so, not a silent fall-back to a default URL.
Caching. A client that fetches the card before every call spends one request in two on something that rarely changes. With a cache and a conditional request (ETag, If-None-Match, answer 304), 20 calls cost 2 card requests, one full and one empty, against 20 full ones. The card is public and needs no token.
Trust. Anyone can publish a card that says anything. The specification offers signed cards (JWS over a canonical form of the JSON, section 8.4), and the suite checks that a changed field breaks the signature, that a key the client does not trust is refused, and that alg: none is refused. A signature proves who published the card, not that the agent is good. My code uses HS256 with a shared key, because the standard library has no public-key signatures; a real deployment uses asymmetric keys, such as the specification's ES256 example, so that the verifier cannot also forge.
What a client can do with 'trust'
I built a routing step, which picks the agent whose card best matches a request (words weighted by how rare they are across cards). Then I added a rogue agent whose card claims to do everything.
| Routing policy | Requests that went to the rogue | Requests routed correctly (of 6) |
|---|---|---|
| Trust any card | 6 | 0 |
| Only cards signed with a key I provisioned | 0 | 6 |
| The same, but one honest agent's key was never provisioned | 0 | 4 (2 had nowhere to go) |
Two honest notes. My first rogue card, which only listed topics, won nothing: the router preferred the real agents. It won only after I copied the honest agents' vocabulary into its card, which costs an attacker nothing, so the result is what you should expect. And the third row is the price of the defence: refusing unsigned cards also refuses real agents whose keys you have not collected. An attacker can sign a card too, with a key of their own; the client then reports "verified: False" for that key because it is not in the trusted set.
Treat card changes as events
A card's description is text that a language model may read. A card that gains a skill, or whose skill description is edited, can change what an agent is sent. The example's card_diff flags a new skill, a changed skill, a new endpoint or a changed security scheme, and a client should hold for review on any of them instead of following silently.
I wrote a client from that description and ran it against the 1.0 server:
| What the old description says | What a 1.0 server did |
|---|---|
Card at /.well-known/agent.json |
404 |
Method sendTask |
-32601 (method not found) |
| (no version header at all) | -32009 VersionNotSupported |
upgrade_book_card converts an old card and returns notes about what it changed or could not carry over. This is the reason the module's date is in the title page: protocols that are this new drift, and a book is a snapshot.