Google open-sourced the Agent2Agent protocol in April 2025, handed it to the Linux Foundation two months later, and by its first anniversary in April 2026 the numbers looked like this: 150+ supporting organizations (up from just over 50 a year earlier), 22,000+ stars on the core repo, and SDKs shipping in five languages: Python, JavaScript, Java, Go, and .NET. It's baked into Azure AI Foundry, Amazon Bedrock AgentCore, and Google Cloud's Agent Development Kit.
Those are real numbers, but "150 companies signed a support letter" and "developers actually reach for this instead of a REST call" are two different claims. If you've been building agents this year, you've probably heard A2A mentioned in the same breath as MCP and weren't sure if it's a protocol you need or a press-release fixture. This post covers what A2A actually does, where it stops overlapping with MCP, and a working Python example so you can decide for yourself.
What A2A Actually Is
MCP connects an agent to tools and data. A2A connects an agent to other agents, ones that might run on a different vendor's stack, in a different language, owned by a different team entirely. The core problem it solves: agent A doesn't need to know how agent B is built, only what it can do and how to hand it a task.
Four concepts do most of the work:
| Concept | What it is |
|---|---|
| Agent Card | A JSON document, served at /.well-known/agent-card.json, describing an agent's skills, supported input/output modes, and auth requirements |
| Task | A unit of work with a lifecycle: submitted → working → (input-required if it needs more from the caller) → completed / failed / canceled |
| Message | One turn in the exchange between a client agent and a remote agent |
| Artifact | The actual output a task produces, made of one or more typed Parts (text, file, or structured data) |
Transport is JSON-RPC 2.0 over HTTP by default, with Server-Sent Events for streaming and push notifications for tasks that outlive a single request. Version 0.3, released in August 2025, added gRPC as an alternate transport and signed Agent Cards (JWS) so a client can verify a card wasn't tampered with in transit, which matters more than it sounds like the first time someone points out an Agent Card is just an unauthenticated JSON file sitting at a well-known URL.
A2A vs MCP: Different Layers, Not Competitors
The framing that actually holds up: MCP is the tool-integration layer, A2A is the agent-collaboration layer. MCP handles "give my agent a function to call." A2A handles "hand this task to an agent I don't control and get a result back." A serious multi-agent system usually needs both, not one instead of the other.
| MCP | A2A | |
|---|---|---|
| Connects | Agent → tool/data source | Agent → agent |
| Unit of work | Tool call | Task (stateful, can span multiple turns) |
| Discovery | Server lists its tools | Agent Card lists its skills |
| Typical owner | You, for your own tools | Often a different team or vendor |
If you're building an MCP server yourself, our Python MCP server guide and the OAuth 2.1 security walkthrough cover that side. A2A picks up where those leave off: once your agent has tools, how does it delegate a sub-task to someone else's agent instead of reimplementing what they already built?
When You Actually Need It (and When You Don't)
The honest criticism circulating this year is fair: a lot of A2A demos show three agents doing what three function calls would do just as well. Standing up an Agent Card, a task store, and JSON-RPC handling for a single in-process call is pure overhead. Reach for it when:
- The other agent is genuinely out of your control - a different team, a different company, or built on a framework you're not going to rewrite.
- The interaction is a real task, not a function call - it needs multiple turns, can ask for more input mid-flight, or takes long enough that you want streaming/push updates instead of blocking on one HTTP response.
- You need to discover capabilities at runtime rather than hardcoding what the other side can do.
Skip it when a direct API call, a shared MCP server, or (if you're using something like LangGraph, CrewAI, or AutoGen) in-process orchestration already does the job. And treat "150 organizations support this" as an interoperability signal, not proof that it's load-bearing in production yet. What actually determines that is whether teams keep it after the first authentication failure and compliance review, which is a much smaller number than 150.
Building a Minimal A2A Agent in Python
The reference implementation is a2a-sdk (compatible with both the 1.0 and 0.3 protocol versions):
pip install "a2a-sdk[http-server]"
First, describe what the agent can do with an AgentSkill, then wrap it in an AgentCard:
Pythonfrom a2a.types import AgentCard, AgentCapabilities, AgentSkill skill = AgentSkill( id="summarize_ticket", name="Summarize Support Ticket", description="Reads a support ticket thread and returns a one-paragraph summary.", input_modes=["text/plain"], output_modes=["text/plain"], tags=["support", "summarization"], examples=["Summarize ticket #4021"], ) agent_card = AgentCard( name="Ticket Summarizer Agent", description="Summarizes support tickets on request.", url="http://localhost:9999", version="1.0.0", default_input_modes=["text/plain"], default_output_modes=["text/plain"], capabilities=AgentCapabilities(streaming=True), skills=[skill], )
Then implement the executor, the part that actually does the work when a task comes in:
Pythonfrom a2a.server.agent_execution import AgentExecutor, RequestContext from a2a.server.events import EventQueue from a2a.server.tasks import TaskUpdater from a2a.types import TaskState from a2a.utils import new_task_from_user_message, new_text_message, new_text_part class TicketSummarizerExecutor(AgentExecutor): async def execute(self, context: RequestContext, event_queue: EventQueue) -> None: task = context.current_task or new_task_from_user_message(context.message) updater = TaskUpdater(event_queue, task_id=task.id, context_id=task.context_id) await updater.update_status( state=TaskState.working, message=new_text_message("Reading ticket thread..."), ) summary = summarize(context.message) # your own logic / LLM call goes here await updater.add_artifact(parts=[new_text_part(text=summary)]) await updater.update_status(state=TaskState.completed) async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None: raise NotImplementedError("This agent doesn't support cancellation yet.")
Finally, wire it up to a server and run it:
Pythonfrom a2a.server.apps import A2AStarletteApplication from a2a.server.request_handlers import DefaultRequestHandler from a2a.server.tasks import InMemoryTaskStore import uvicorn handler = DefaultRequestHandler( agent_executor=TicketSummarizerExecutor(), task_store=InMemoryTaskStore(), ) app = A2AStarletteApplication(agent_card=agent_card, http_handler=handler).build() if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=9999)
Confirm the card is being served correctly before wiring up a real client:
Bashcurl -s http://localhost:9999/.well-known/agent-card.json
Run that JSON through the JSON validator or the JSON Schema validator while you're wiring up a new agent, a malformed Agent Card is a surprisingly common reason a client silently fails to discover skills. For the actual task-submission call, the cURL command generator saves you from hand-typing the JSON-RPC envelope every time you tweak a parameter, and a UUID generator is the easiest way to hand out unique task and context IDs if your framework doesn't do it for you.
Security: The Part the Demos Skip
Every serious writeup of A2A this year lands on the same open question: an Agent Card answers "what can this agent do," not "who owns it, and what should it be allowed to know." That's a real gap, not a nitpick. A few things worth doing from day one instead of retrofitting later:
- Verify signed cards. v0.3's JWS signing means you can check a card wasn't swapped in transit, don't skip verification just because the request came back over HTTPS.
- Scope what a remote agent can request, the same way you would with an OAuth scope on an MCP server, rather than trusting every skill listed in a card.
- Decode and check tokens carried between agents with the JWT decoder before assuming an
audorsubclaim means what you think it does. - Don't forward a caller's token to a third-party agent unmodified. The same confused-deputy risk we covered in the MCP OAuth guide applies here: if agent A hands its own credential to agent B, B can now act with A's authority on systems it was never meant to touch.
Conclusion
A2A isn't hype in the sense of "doesn't work," the spec is real, the SDKs work, and the adoption numbers are genuine. It's hype in the narrower sense that most projects reaching for it this year don't need it yet. Use MCP to give your agent tools. Reach for A2A only once you actually have a second agent, one you don't control, that needs to accept a task, work on it over multiple turns, and hand back a result. If your current use case is "agent A calls agent B once and gets a string back," you don't have an A2A problem, you have a function call.
