DEV Community

Cover image for A2A vs MCP: What Agent-to-Agent Actually Adds Over Tool Calling
Andrii B.
Andrii B.

Posted on

A2A vs MCP: What Agent-to-Agent Actually Adds Over Tool Calling

The question "should I use A2A or MCP" is the wrong question, and asking it out loud is a decent early sign you don't need A2A yet. They aren't two options on the same shelf. One connects your agent down to its tools; the other connects your agent sideways to other agents. You can run both, neither, or one without the other, and for most teams the honest answer is "MCP, and not A2A, at least not this quarter."

But that's a boring answer, and it hides the interesting part. Because when A2A does earn its place, it's not adding "collaboration" in some fuzzy marketing sense. It's adding three specific, mechanical things that a tool call cannot give you, and all three come from the same root: A2A is built to talk to an agent you don't own and can't see inside. Let's pin down what that actually buys you, and when it's worth the protocol.

Two different axes, not two options

Start with where each one sits, because the whole confusion is a category error.

MCP (the Model Context Protocol) is Anthropic's standard, released in late 2024, for how a single agent reaches its tools and context: databases, APIs, files, a search index. It defines a few primitives (tools, resources, prompts), speaks JSON-RPC, and its mental model is vertical. Your agent is at the top; the things it can call are underneath it. Crucially, your agent can enumerate those tools. It knows their names, their input schemas, their output shapes. A tool is, in A2A's own words, a "primitive with well-defined, structured inputs and outputs" doing a "specific, often stateless" job.

A2A (Agent2Agent) is a protocol Google introduced in April 2025 and handed to the Linux Foundation a couple of months later, where it now lives as a vendor-neutral project. Its mental model is horizontal. Your agent talks to other agents as peers. And the defining word in A2A's own framing is "opaque": the other agent is an autonomous system that "reasons, plans, uses multiple tools, maintains state over longer interactions," and it does all of that without showing you any of it. You don't see its tools. You don't see its prompt. You don't see its memory. You hand it a task and deal with it as a black box.

So the picture isn't "A2A vs MCP." It's an agent with a vertical line down to its own tools (MCP) and horizontal lines out to other agents (A2A). A serious multi-agent deployment often runs both: A2A to reach other agents, MCP inside each agent to reach its own tools.

Two axes, not two options: your agent connects down via MCP to tools you own and can enumerate (Postgres, REST API, search index, each with in/out ports) and sideways via A2A to opaque peers you delegate to (another team's agent, a vendor's agent), each padlocked

The baseline: what a tool call already gives you

Before naming what A2A adds, be honest about how much a plain tool call already does, because it's a lot, and it's the thing you should reach for first.

When your agent calls a tool (whether you wired it by hand or through MCP), you get a clean contract. There's a name and a JSON schema. The call is usually synchronous: you ask, you block, you get a structured result or an error. You own both sides, so you can change the schema, add a field, fix a bug, and redeploy. The type system is right there. If the tool returns the wrong shape, you find out immediately, in your own codebase.

// An MCP-style tool: a contract you own and can enumerate
{
  "name": "get_order",
  "description": "Fetch an order by id",
  "inputSchema": {
    "type": "object",
    "properties": { "order_id": { "type": "string" } },
    "required": ["order_id"]
  }
}
Enter fullscreen mode Exit fullscreen mode

That's the baseline. It's simple, it's typed, it's synchronous, and it's yours. Ninety percent of "let my agent do X" is exactly this, and adding a second agent protocol on top would be pure overhead. So the bar for A2A isn't "does it help agents work together." The bar is "does my problem have a shape that a tool call genuinely can't hold." Here's when it does.

What A2A actually adds

Three things, and they're a package deal because they all fall out of the same design goal: talking to an agent you can't see into.

1. Opacity, on purpose. With a tool, you see the whole contract. With A2A, you don't, and that's the feature, not a gap. The remote agent advertises what it can do through an Agent Card, a small JSON document describing its identity, skills, endpoint, and auth, but nothing about how. You never learn its internal tools or reasoning. Why would you want less visibility? Because the other agent belongs to another team, another company, or another trust boundary, and coupling to its internals would be exactly the mistake you spent years learning not to make with microservices. A2A makes the black box the unit of integration.

// An A2A Agent Card: capability advertisement, no internals leaked
{
  "name": "Fraud Review Agent",
  "description": "Reviews a transaction and returns a risk decision",
  "url": "https://risk.acme.com/a2a",
  "version": "1.4.0",
  "capabilities": { "streaming": true, "pushNotifications": true },
  "skills": [
    { "id": "review_transaction", "description": "Assess a transaction for fraud risk" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

2. Long-running, stateful tasks instead of a blocking call. A tool call is a function: it returns, and it's over. A lot of real agent work isn't shaped like that. It runs for minutes, hours, or across a human approval step. A2A models this as a Task with a lifecycle: it moves through states like submitted, working, input-required, and finally completed or failed. You send a message, the call returns immediately with a Task that's still working, and the agent keeps going in the background. You get progress three ways: synchronous polling, a Server-Sent Events stream for live updates, or push notifications to a webhook when the whole thing is disconnected and long. Try expressing "start this, it'll take an hour, ping my webhook when the human approves" as a single tool call. You can't, cleanly. That's a Task.

// message/send returns a Task that is still running, not a final answer
{
  "jsonrpc": "2.0", "id": 1, "method": "message/send",
  "params": {
    "message": {
      "role": "user",
      "parts": [{ "kind": "text", "text": "Review transaction tx_88213" }]
    }
  }
}
// -> Task { "id": "t_9f2", "status": { "state": "working" } }
// later: poll tasks/get, or receive a push notification, or read the stream
Enter fullscreen mode Exit fullscreen mode

3. Discovery, so you don't hardwire the other side. With tools, you know at build time exactly what's available; you wrote them in. A2A agents publish Agent Cards, so a client can discover a capable peer and negotiate with it, including which content types and modalities both sides speak. It's the difference between calling a function you imported and finding a service that advertises it can do the job. That only matters across a boundary where you don't control, or don't want to control, the full catalogue up front.

The A2A Task lifecycle as a state machine that a synchronous tool call cannot express: submitted, then working (where the call returns immediately), branching to input-required (needs human input) then completed, or to failed; results reach the client later via polling (tasks/get), an SSE stream, or a push webhook

The boundary is the whole point

Read those three back and notice they're the same idea wearing three hats. Opacity, long-running tasks, and discovery are all answers to one situation: the other agent is not yours. It's another team's service, a vendor's product, a partner's system. You can't import its functions, you shouldn't see its internals, and it doesn't finish on your request's timeline.

That's the real test, and it's a lot sharper than "do I have multiple agents." You can absolutely have five agents in one codebase, all yours, and the right way to connect them is still boring function calls or MCP, because you own them, you can enumerate them, and a typed call beats a network protocol every time you can have one. Putting A2A between two agents you control is the classic over-engineering move: you've traded a compile-time-checked call for an opaque JSON-RPC round trip and gotten nothing back, because the opacity you're paying for is protecting you from a boundary that doesn't exist.

A2A earns its keep exactly at the org chart's edges. Your agent needs a risk decision from the fraud team's agent, and that team ships on their own cadence and won't hand you their internals. You integrate a vendor's research agent you literally cannot see inside. A partner exposes a booking agent and you negotiate over A2A. In every case the thing you actually needed wasn't "collaboration." It was a stable seam between systems that don't, and shouldn't, know each other's guts.

So which do you reach for?

Here's the decision, stripped down.

Default to a tool call, and use MCP to standardize it when you have more than a couple of tools or want them reusable across agents. This covers the overwhelming majority of "make my agent able to do X." It's typed, synchronous, and yours.

Reach for A2A when the other side is an agent you don't own or can't see into, when the work is long-running or needs a human in the loop across time, or when you need to discover and negotiate with a capable peer rather than hardwire it. If none of those is true, you're adding a protocol to solve a problem you don't have.

And know that they compose, because that's the setup you'll actually run once you're past one agent. A2A's own docs use an auto-repair-shop example: the customer talks to the shop's manager agent over A2A, the manager delegates to a mechanic agent over A2A, and the mechanic reaches its diagnostic tools over MCP. Outward across boundaries, A2A. Inward to your own tools, MCP. The agent is the same thing on both sides; only the axis changes.

A decision diagram: is the thing you're calling an agent you own and can see inside? Yes leads to a tool call or MCP (typed, synchronous, enumerable); no leads to A2A (opaque peer, long-running Task, discovery), with a note that long-running or human-in-the-loop work also fits an async Task. Caption: the number of agents doesn't decide this, the boundary does

The one question

Strip away the protocol names and the layer diagrams and you're left with a single question that decides everything: is the agent I'm calling mine?

If it is, keep it simple. Call the function, wire the tool, standardize with MCP when the toolset grows. You keep your types, your synchronous flow, and the ability to fix both sides at once. If it isn't, that's the moment A2A stops being overhead and starts being the thing that lets two systems you don't jointly control actually work together, without either one reaching into the other.

"A2A vs MCP" was never the real question. The real one is where your boundaries are, and whether the agent on the other side of one is a black box by accident or by design. Get that right and the protocol picks itself.


PS: English is not my native language, so I used AI to help with proofreading and phrasing. All ideas and technical content are my own.
Originally published at andriiboyko.com.

Top comments (1)

Collapse
 
unitbuilds profile image
UnitBuilds

Hey Andrii, just a heads up, I've been experimenting in Optimized branch, worth having a look, I swapped out HTTP with VCTP to drop the persistence latency by 96%, needed some work to get it to properly match HTTP's features, but sofar it seems to be working well, currently doing some benchmarking to see if it's stable at that performance.