AI
A2A vs MCP: Which AI Agent Protocol Do You Actually Need?
October 202612 min read

MCP (Model Context Protocol) standardises how an AI agent connects to tools, APIs and data sources with defined inputs and outputs. A2A (Agent2Agent) standardises how independent, opaque agents discover each other, delegate tasks and exchange results as peers. In short, MCP is agent-to-tool and A2A is agent-to-agent.
Often, yes. A typical design uses MCP below an orchestrator agent to reach tools you control, such as ERP read and draft tools, and A2A sideways to delegate work to agents owned by another team or company. The A2A specification itself describes the two as complementary rather than competing.
No. The 2026-07-28 release removed the initialize handshake and the Mcp-Session-Id header, so every request carries its protocol version, client info and capabilities in _meta. Any request can be served by any instance behind a round-robin load balancer. Servers that need continuity are told to return an explicit handle from a tool that the model passes back as an argument.
MCP Tasks are an extension, io.modelcontextprotocol/tasks, that lets a server return a durable handle for a slow tool call, which the client polls with tasks/get and answers with tasks/update. A2A tasks are a core protocol object for delegated work, with states such as TASK_STATE_INPUT_REQUIRED and TASK_STATE_AUTH_REQUIRED, plus streaming and push-notification webhooks. Use MCP Tasks for a slow but well-defined operation and A2A tasks when another agent must reason, ask questions or seek approval.
Both are projects of the Agentic AI Foundation at the Linux Foundation. MCP was a founding project when the foundation was announced in December 2025, and A2A became a hosted project on 17 August 2026 after moving to the Linux Foundation in June 2025. Each keeps its own maintainers and specification process.

Key Takeaway
MCP connects one AI agent to tools and data; A2A connects independent agents to each other. Since the 2026-07-28 spec, MCP requests are stateless and long jobs use the Tasks extension, while A2A 1.0 keeps server-owned tasks, Agent Cards and an AUTH_REQUIRED state. Most production systems need both, at different layers.
A2A vs MCP is the question I now hear in almost every agent design review, usually phrased as a choice: which one should we standardise on? It is the wrong question. MCP, the Model Context Protocol, is how an agent reaches tools and data it is allowed to use. A2A, the Agent2Agent protocol, is how one agent hands work to another agent it cannot see inside. A procurement agent reading stock levels from your ERP is an MCP problem. The same agent asking a supplier's agent for a quote is an A2A problem, and it is common for one system to need both in the same afternoon.
What changed this year is that the old shorthand stopped being accurate. MCP used to be the stateful one, with a handshake and a session; the 2026-07-28 specification removed both. A2A reached a stable 1.0 and, on 17 August 2026, moved into the Agentic AI Foundation alongside MCP. This post compares the two on what actually decides an architecture: who is on the other end, discovery, state, long-running work, auth and governance. Every method name, field and date below comes from the two specifications, the MCP release post and the foundation's announcement, as of 1 October 2026. The earlier post on MCP versus function calling compared MCP with in-process tools; this one is about the protocol layer above it.
The A2A specification puts the line in its own appendix: MCP standardises how agents connect to tools, APIs, data sources and other resources, while A2A standardises how independent, often opaque agents communicate as peers. The word that matters is opaque. With MCP you see a tool's name, its input schema and its result. With A2A you see an Agent Card listing skills, and nothing about the prompts, tools or memory behind them. Three questions settle which side of the line a capability sits on.
That third question is the one teams skip. A finance team's credit-check agent inside your own company is still a separately owned agent with its own release cycle and its own access to data. Wrapping it as an MCP tool means freezing its behaviour into one schema and borrowing its credentials, which is exactly the coupling A2A exists to avoid.
This is the table I wanted when I started, filled in from MCP 2026-07-28 and A2A 1.0. The rows that changed most this year are state and long-running work, so read those two before trusting any comparison written in 2025.
| Dimension | MCP (2026-07-28) | A2A (1.0) |
|---|---|---|
| What is on the other end | A server exposing tools, resources and prompts with defined inputs and structured outputs | Another agent, deliberately opaque: you see its skills, not its prompts, tools or memory |
| Direction | Vertical: one agent reaching down into systems it may use | Horizontal: peers delegating work, often across a company boundary |
| Discovery | Optional server/discover call, then tools/list; list results carry ttlMs and cacheScope hints | Agent Card at /.well-known/agent-card.json, plus an authenticated extended card when capabilities.extendedAgentCard is true |
| Protocol state | None: no initialize handshake and no Mcp-Session-Id; each request carries version, client info and capabilities in _meta | Server-owned tasks, with a contextId that groups related tasks and messages |
| Application state | An explicit handle returned by one tool and passed back by the model as an argument | Built in: a follow-up message references the taskId it continues |
| Long-running work | io.modelcontextprotocol/tasks extension: tasks/get to poll, tasks/update to answer, tasks/cancel | Core: GetTask, ListTasks, CancelTask, SubscribeToTask streaming and push-notification webhooks |
| Needing input mid-call | Multi Round-Trip Requests: resultType input_required, client retries with inputResponses | TASK_STATE_INPUT_REQUIRED; the client replies with a new message carrying the taskId |
| Wire format | JSON-RPC over stdio or Streamable HTTP; HTTP requests must send Mcp-Method and Mcp-Name headers | Three bindings, JSON-RPC, gRPC and HTTP+JSON/REST, declared in supportedInterfaces |
| Auth | Optional; over HTTP the server is an OAuth 2.1 resource server with Protected Resource Metadata and audience-bound tokens | Card declares securitySchemes: API key, HTTP auth, OAuth 2.0, OpenID Connect or mutual TLS; plus TASK_STATE_AUTH_REQUIRED |
| Governance | Founding project of the Agentic AI Foundation, announced December 2025 | Linux Foundation project from June 2025; Agentic AI Foundation hosted project since 17 August 2026 |
Read across the rows and a pattern appears. MCP has spent this year removing anything that ties a request to a connection, so a tool server behaves like any other stateless HTTP service. A2A went the other way on purpose: the task is a durable object the remote agent owns, because delegating to an agent is a conversation, not a function call. Neither design is better. They fit different jobs.
The two discovery mechanisms tell you a lot about the intended relationship. An MCP client asks a server what it can call and gets typed tool definitions it can hand straight to a model. An A2A client fetches a public JSON document that describes an agent's identity, endpoints, auth requirements and skills, and then decides whether to trust it.
# MCP 2026-07-28 — no initialize, no Mcp-Session-Id.
# Every request carries its own version, client info and capabilities,
# so it can land on any instance behind a plain round-robin balancer.
POST /mcp HTTP/1.1
Host: erp-tools.internal.example
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call # gateways route and meter on these two
Mcp-Name: get_stock_level # headers without parsing the JSON body
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"get_stock_level","arguments":{"sku":"BRG-0042","warehouse":"SBY"},
"_meta":{"io.modelcontextprotocol/clientInfo":{"name":"procurement-agent","version":"3.1"}}}}
# A2A 1.0 — you are not calling a function, you are reading a résumé.
GET /.well-known/agent-card.json HTTP/1.1
Host: agents.supplier.example
{
"name": "Supplier Quote Agent",
"description": "Quotes price and lead time for bulk orders",
"version": "2.0.0",
"supportedInterfaces": [
{"url": "https://agents.supplier.example/a2a/v1",
"protocolBinding": "JSONRPC", "protocolVersion": "1.0"}
],
"capabilities": {"streaming": true, "pushNotifications": true,
"extendedAgentCard": true},
"securitySchemes": {"partnerOAuth": {"oauth2SecurityScheme": {"...": "flows"}}},
"securityRequirements": [{"schemes": {"partnerOAuth": {"list": ["quotes"]}}}],
"defaultInputModes": ["text/plain", "application/json"],
"defaultOutputModes": ["application/json"],
"skills": [{"id": "bulk-quote", "name": "Bulk order quote",
"description": "Negotiates a quote for 100+ units",
"tags": ["quote", "procurement"]}]
}
# Note what is missing: no tool list, no input schema per call.
# The partner's tools stay private — that opacity is the point of A2A.Two details from the specifications matter in practice. On the MCP side, tools/list, prompts/list, resources/list and resources/read responses now carry ttlMs and cacheScope, and list order is deterministic, so a client can cache the tool catalogue and keep an upstream prompt cache stable across reconnects. On the A2A side, Agent Cards may be signed with JSON Web Signature, and a public card can advertise an extended card that only authenticated clients receive. That is how a partner can show a general skill list to the world and a fuller one to you.
The consequence for design: an MCP tool list is a contract you program against, so changing a tool's schema is a breaking change for every client. An Agent Card is closer to a résumé. Skills carry descriptions, tags and examples rather than strict schemas, and the remote agent is expected to cope with varied requests and ask when it needs more.
The 2026-07-28 release retired the initialize and initialized exchange and the Mcp-Session-Id header. Each request now carries its protocol version, client identity and capabilities in _meta, and the release post says plainly why: any request can land on any server instance behind a plain round-robin load balancer, without shared storage. Server-initiated requests such as elicitation and sampling are replaced by Multi Round-Trip Requests, where the server returns input_required and the client retries the original call with the answers attached. Roots, Sampling and Logging are deprecated with at least twelve months of support, and so is the legacy HTTP+SSE transport.
Stateless protocol does not mean stateless application. The release post recommends that a server needing continuity mint an explicit handle from a tool and have the model pass it back as an argument. A2A never had that problem to solve, because state is its subject matter. A task has an id, a status and artifacts, and the contextId lets the remote agent keep a multi-turn thread together across several tasks. If you catch yourself building conversation memory on top of MCP handles so that two agents can talk, that is the moment to switch protocols.
A quick test for an existing design: list everything your MCP server stores between calls. If it is a cache, a draft document id or a cursor, keep MCP and pass it as a handle. If it is the other party's half of a conversation, you have built an agent behind a tool interface, and A2A will model it more honestly.
Both protocols now have something called a task, which is where most confusion starts. In MCP it is an extension, io.modelcontextprotocol/tasks, that the client opts into per request and the server applies per request: when it judges a call will be slow, it returns a durable task handle instead of the result. In A2A the task is the core object every non-trivial interaction produces. Here is the same shape of job in each.
# ── MCP: tools/call with the io.modelcontextprotocol/tasks extension ──
# Client opted in via _meta clientCapabilities.extensions. The SERVER decides
# per request whether to return a task instead of the result.
-> tools/call {"name": "rebuild_stock_valuation", "arguments": {"period": "2026-09"}}
<- {"resultType": "task", "taskId": "t-81f2", "status": "working",
"ttlMs": 86400000, "pollIntervalMs": 5000} # abbreviated
-> tasks/get {"taskId": "t-81f2"} # store taskId durably:
<- {"status": "input_required", "inputRequests": {...}} # a restart resumes polling
-> tasks/update {"taskId": "t-81f2", "inputResponses": {...}}
-> tasks/get {"taskId": "t-81f2"}
<- {"status": "completed", "result": {...}} # what tools/call would
# have returned inline
# ── A2A: the task IS the protocol, not an extension ──
-> SendMessage {"message": {"role": "ROLE_USER", "messageId": "m-1",
"parts": [{"text": "Quote 400 units of BRG-0042, delivery Surabaya"}]}}
<- {"task": {"id": "q-77", "contextId": "ctx-9",
"status": {"state": "TASK_STATE_INPUT_REQUIRED",
"message": {"role": "ROLE_AGENT",
"parts": [{"text": "Incoterm? FOB or delivered?"}]}}}}
-> SendMessage {"message": {"taskId": "q-77", "role": "ROLE_USER",
"messageId": "m-2", "parts": [{"text": "Delivered"}]}}
# Then poll GetTask, hold a SubscribeToTask stream, or register a webhook
# with CreateTaskPushNotificationConfig and stop holding connections at all.The state names line up more closely than the designs do, and the gaps are instructive.
| MCP Tasks status | A2A TaskState | What the difference means |
|---|---|---|
| working | TASK_STATE_SUBMITTED, TASK_STATE_WORKING | A2A separates accepted from started, which matters when the remote side queues work |
| input_required | TASK_STATE_INPUT_REQUIRED | MCP answers through tasks/update with inputResponses; A2A answers with a new message that references the taskId |
| no equivalent | TASK_STATE_AUTH_REQUIRED | MCP handles missing permission at the HTTP layer with 401 or 403 and step-up authorization, not as a task state |
| completed | TASK_STATE_COMPLETED | In MCP the result is exactly what the original call would have returned inline |
| failed | TASK_STATE_FAILED, TASK_STATE_REJECTED | A2A distinguishes an agent that refused the work from one that broke while doing it |
| cancelled | TASK_STATE_CANCELED | MCP documents cancellation as cooperative: the server may still finish the work |
The rule I use: MCP Tasks are for a tool that is slow, such as a stock revaluation, a report export or a CI run. The caller still knows exactly what it asked for and only needs to wait. A2A tasks are for work that is delegated, where the remote agent may ask questions, need approval and produce several artifacts along the way. MCP polls by default and can push through an opt-in subscriptions/listen stream; A2A offers polling, streaming over Server-Sent Events and webhooks, which suits a partner that may take hours to answer.
MCP's authorization is optional, and for stdio servers the spec says to take credentials from the environment instead. Over HTTP it is precise: the MCP server is an OAuth 2.1 resource server, it must publish Protected Resource Metadata, clients must send a resource indicator naming the server, and the server must reject any token not issued for it as the audience. The 2026-07-28 revision adds RFC 9207 issuer validation, binds client credentials to the issuer that minted them and formally deprecates Dynamic Client Registration in favour of Client ID Metadata Documents.
A2A treats an agent as an ordinary enterprise application. The Agent Card declares securitySchemes and securityRequirements, credentials are obtained out of band, and the server must authenticate every request. The interesting part is in-task authorization. When a remote agent needs a new permission halfway through, it moves the task to TASK_STATE_AUTH_REQUIRED and the client resolves it, by delegating up its own chain if necessary. The spec is explicit that this state by itself authorises nothing; what the resulting credential covers is for the implementation to define.
The expensive mistake in a multi-agent chain is forwarding the user's token. MCP forbids it outright: a server must not accept or pass on tokens issued for anything else. A2A warns that in-band credentials can be exposed to every agent in a chain, and recommends binding them to the requesting agent and encrypting them. Give each hop its own audience-bound credential.
Governance is the one dimension where the two have converged. MCP was a founding project of the Agentic AI Foundation, announced by the Linux Foundation in December 2025 with Anthropic contributing MCP, Block contributing goose and OpenAI contributing AGENTS.md. A2A started at Google in April 2025, went to the Linux Foundation in June 2025, reached its stable 1.0 in March 2026 and became an AAIF hosted project on 17 August 2026. Each keeps its own maintainers and specification process, but choosing one is no longer a bet on a single vendor.
Here is how I lay out one system that needs both, using an ERP procurement assistant as the example. Each arrow is labelled with the protocol it uses, and the point of the drawing is that the protocols never compete for the same arrow.
Layer 4 Surfaces web chat · WhatsApp · ERP side panel
│ (your own app API — neither A2A nor MCP)
▼
Layer 3 Orchestrator procurement agent: owns the conversation and the plan
│ │ │
│ MCP │ A2A │ A2A
│ tools/call, stateless │ SendMessage, task │ (internal, but
▼ ▼ ▼ separately owned)
Layer 2 MCP gateway partner endpoint finance team's agent
routes on supplier quote agent credit-limit check
Mcp-Method/Mcp-Name (its tools are invisible (it keeps its own MCP
│ to you; it may use MCP) servers behind it)
▼
MCP tool servers
erp-read: stock, price, PO status ← safe, cacheable lists
erp-draft: create draft PO, draft GRN ← writes, idempotency key
erp-post: NOT exposed to any agent ← humans post documents
│
▼
Layer 1 Systems of record ERP database · document store · approval queueRead it from the middle. The orchestrator owns the user's conversation and the plan. Everything it can call with known arguments goes down through MCP to tool servers you run: read tools for stock, price and purchase order status, and draft tools that create documents a human then reviews. Posting tools are not exposed to any agent at all. Everything that needs another party's judgement goes sideways over A2A: the supplier's quote agent outside the company, and the finance team's credit-limit agent inside it, which keeps its own MCP servers behind it.
Two operational benefits come from keeping the layers clean. Because MCP requests now carry Mcp-Method and Mcp-Name headers, the gateway in layer 2 can rate-limit, log and authorise per tool without parsing bodies, and scale horizontally with no sticky sessions. Because A2A sits only at ownership boundaries, every A2A endpoint corresponds to a real contract with another team or company, which is the right granularity for audit and for deciding whose credentials are used where.
In practice the decision falls out of four questions, asked in this order for every integration in the design.
The two failure modes mirror each other. Wrapping a remote agent as one MCP tool loses INPUT_REQUIRED, AUTH_REQUIRED and multi-turn context, so the agent either guesses or fails silently. Wrapping every database query as an A2A agent adds an extra model call, latency and cost to work a typed function would do deterministically.
The useful rule is about boundaries, not brands: MCP below the agent, A2A between agents. MCP's move to a stateless core made it an even cleaner tool layer, and A2A's server-owned tasks are what delegation needs. Since August 2026 both sit in the same foundation, so the only real decision left is which arrow in your design is which.