What you will learn
What the Agent2Agent (A2A) protocol is for, how it differs from MCP, and how to call another team's agent without handing it more trust than it needs.
MCP and A2A solve different problems
- MCP connects an application or agent to tools and context: a ticket lookup, a file, a search function. The calling agent stays in control and decides what to do with each result.
- A2A connects an agent to another agent that does its own reasoning and may run for a while: "research this vendor", "reconcile these invoices". You delegate a task and receive progress and results.
A2A is an open protocol originally introduced by Google and now developed as an open-source project under the Linux Foundation. Read the version of the specification you implement at a2a-protocol.org, because details change between versions.
The core ideas
- Agent Card: a JSON document an agent publishes to describe itself — its name, endpoint, the skills it offers, and the authentication it requires. Clients use it to discover what the agent can do.
- Task: a unit of delegated work with a lifecycle. States include working, input required, completed, failed, and cancelled.
- Messages and artifacts: the client and remote agent exchange messages made of parts (text, files, structured data), and the remote agent returns its outputs as artifacts.
- Transport: requests use JSON-RPC over HTTPS, with streaming and push notifications for long-running tasks.
An Agent Card is a claim, not a permission
A card says what an agent offers. It does not mean your agent is allowed to use it, or that the remote agent is trustworthy. Apply the same rules you used for MCP servers: decide in advance which remote agents and skills your application may call, pin what you approved, and treat everything returned as untrusted data.
# academy-check
APPROVED = {
"https://research.example.internal/a2a": {"skills": {"vendor-research"}, "auth": "oauth2"},
}
def allowed_call(card, skill_id):
policy = APPROVED.get(card.get("url"))
if policy is None:
return False, "agent not approved"
offered = {skill.get("id") for skill in card.get("skills", [])}
if skill_id not in offered:
return False, "skill not offered"
if skill_id not in policy["skills"]:
return False, "skill not approved for this application"
return True, "ok"
card = {"name": "Research agent", "url": "https://research.example.internal/a2a",
"skills": [{"id": "vendor-research"}, {"id": "send-email"}]}
assert allowed_call(card, "vendor-research") == (True, "ok")
assert allowed_call(card, "send-email") == (False, "skill not approved for this application")
assert allowed_call({**card, "url": "https://unknown.example/a2a"}, "vendor-research")[0] is False
The remote agent advertising send-email does not give your application permission to request it. A new skill appearing in a card later needs an explicit integration decision, just like a new MCP tool.
Delegating safely
- Authenticate both ways. Use the authentication scheme the card declares, with credentials scoped to that agent. Never let a model choose where a credential is sent.
- Send the minimum. Delegate a task description and the data needed for it — not your whole context, and never another user's data.
- Bound the task. Set your own deadline and cancel the remote task when you no longer need it. A remote agent's "working" state does not extend your budget.
- Validate results. Treat returned artifacts like retrieved documents: check their structure, keep their source, and never let instructions inside them change your agent's permissions.
- Keep approvals local. If the result leads to an action — paying an invoice, updating a ticket — your application's exact-action approval still applies. A remote agent's recommendation is not an approval.
- Trace across the boundary. Record the remote task ID alongside your own task ID so a failure can be followed end to end.
When to use A2A
Use it when another team or vendor runs an agent you want to delegate to, and that agent needs to own its own reasoning, tools, and data. If you only need a function — "look up this order" — an MCP tool or a plain API is simpler, cheaper, and easier to test.
Exercise
Write an Agent Card for a read-only research agent with one skill. Build a client policy that approves only that skill, then test four cases: an approved call, a card that adds a new skill, an unknown agent URL, and a returned artifact containing instructions to call another tool.
Check: only approved agent and skill pairs can be called; a changed card cannot widen access; returned content cannot change your agent's permissions; your trace links your task to the remote task. If you implement against a real A2A SDK, record the protocol and SDK versions.