A2A and MCP for Agent Interoperability: Stop Building Islands
In 2024, my team spent four weeks wiring a Slack bot to a document search agent. Then another two weeks connecting that to a CRM agent. By the time we had a working demo, the integration code was bigger than the agents themselves.
That's the problem. That's the whole problem.
Agent interoperability isn't a protocol debate. It's a survival question for anyone building production AI systems in 2026. If your agents can't talk to each other, you don't have a system. You have a collection of expensive, isolated demos.
This guide covers a2a and mcp for agent interoperability — what these protocols actually do, when to use which, and how to wire them together without losing your mind.
What Actually Happened: The Protocol Split
Here's the thing most people miss. MCP and A2A solve different problems.
MCP (Model Context Protocol) is about tools. It lets an LLM call a function, query a database, or hit an API. Introduced by Anthropic in late 2024, it standardized how models access external capabilities.
A2A (Agent2Agent) is about agents. It lets one agent delegate work to another agent. Announced by Google in April 2025, it handles discovery, task negotiation, and result delivery between autonomous systems.
At first I thought this was a branding problem. Turns out it was a fundamental architecture difference.
Think of it this way. MCP is the nervous system — it lets a brain control muscles. A2A is the vocal cords — it lets one brain talk to another brain.
You need both.
The Layer Model That Actually Works
We've settled on a three-layer architecture at SIVARO. It's not fancy. It's pragmatic.
Layer 1: LLM + Tools (single agent)
↓ uses MCP
Layer 2: Agent Mesh (multiple agents)
↓ uses A2A
Layer 3: Orchestration (routing, retries, governance)
Each layer has its own protocols because each layer has its own failure modes.
MCP failures are usually deterministic — the tool returned a 500, the schema changed, the auth token expired. You can retry, you can cache, you can mock.
A2A failures are semantic — Agent B understood "analyze this dataset" as "delete this dataset." You can't retry your way out of a misunderstanding.
That distinction drives everything about how you design your integration layer.
MCP: The Tool Protocol
Let's get practical. MCP defines three primitives: tools, resources, and prompts.
Tools are functions the model can call. Resources are data the model can read. Prompts are templates that structure interactions.
Here's a minimal MCP server in Python using the official SDK:
python
from mcp.server import Server, stdio_server
from mcp.types import Tool, TextContent
app = Server("data-lookup")
@app.list_tools()
async def list_tools():
return [
Tool(
name="get_customer_events",
description="Fetch customer interaction events",
inputSchema={
"type": "object",
"properties": {
"customer_id": {"type": "string"},
"days": {"type": "integer", "default": 30}
}
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "get_customer_events":
# Your actual logic here
events = fetch_events(arguments["customer_id"], arguments.get("days", 30))
return [TextContent(type="text", text=json.dumps(events))]
async def main():
async with stdio_server() as (read, write):
await app.run(read, write)
if __name__ == "__main__":
asyncio.run(main())
That's it. That's the whole server. Register it with your LLM client and the model can now call get_customer_events naturally.
But here's what the tutorials don't tell you.
What MCP Gets Wrong
MCP was designed for a single agent talking to tools. It assumes context is shared. It assumes the tool doesn't have opinions.
In production, those assumptions break.
At SIVARO we had an MCP tool that sent email campaigns. The model thought it was sending a test to one address. The tool interpreted it as a blast to 50,000 subscribers. As StackOne's analysis notes, MCP doesn't have a native concept of authorization granularity — the server declares what tools exist, but not who can call them how.
That's not a protocol flaw. That's a design gap. MCP assumes you've solved authorization elsewhere. If you haven't, you will eventually send a mass email by accident.
Second problem: MCP servers are stateless by default. Your tool needs conversation history, session state, or user context — you're maintaining that externally. The Redis team's breakdown makes this explicit: MCP connects a model to a capability, but it does not manage the context of that connection.
And context is where production systems go to die.
A2A: The Agent Protocol
Now for the interesting part.
A2A treats agents as first-class citizens. It defines an Agent Card — a JSON document that describes what an agent can do, its capabilities, and how to reach it.
Here's what an Agent Card looks like:
json
{
"name": "doc-analyzer",
"description": "Analyzes documents and extracts structured data",
"url": "https://agents.sivaro.dev/doc-analyzer",
"capabilities": {
"skills": [
{
"id": "invoice_extraction",
"name": "Extract invoice data",
"inputModes": ["text/plain", "application/json"],
"outputModes": ["application/json"]
},
{
"id": "contract_review",
"name": "Review contract clauses",
"inputModes": ["text/plain", "application/pdf"],
"outputModes": ["text/markdown"]
}
]
},
"security": {
"auth": "oauth2",
"scopes": ["documents:read"]
}
}
A2A servers expose this card at a well-known endpoint. Client agents fetch it, discover what's available, and then initiate a Task.
The task lifecycle is where A2A shines. Unlike a simple tool call, A2A tasks have states:
submitted → working → input-required → completed
↘ cancelled
↘ failed
That input-required state matters. It handles the case where Agent B needs more information from Agent A. The task pauses, the requester provides context, the task resumes.
Here's a minimal A2A client in TypeScript:
typescript
import { A2AClient, TaskStatus } from 'a2a-sdk';
const client = new A2AClient('https://agents.sivaro.dev/doc-analyzer');
// Fetch the Agent Card
const card = await client.getAgentCard();
console.log(`Capabilities: ${card.capabilities.skills.map(s => s.id).join(', ')}`);
// Create a task
const task = await client.createTask({
skillId: 'invoice_extraction',
input: {
text: 'Extract all line items from invoice #12345',
documentRef: 's3://invoices/2026/08/12345.pdf'
}
});
// Poll or subscribe to task updates
while (task.status === TaskStatus.WORKING) {
await new Promise(r => setTimeout(r, 2000));
const status = await client.getTask(task.id);
if (status.status !== task.status) {
task = status;
console.log(`Status: ${task.status}`);
}
}
if (task.status === TaskStatus.COMPLETED) {
console.log('Extracted data:', task.artifacts);
}
Notice the polling loop. A2A doesn't assume instant response — agents take time. Some tasks run for hours. The protocol accommodates that with async status checks and webhook subscriptions.
Where A2A Gets Uncomfortable
A2A is young. It hasn't had years of production hardening like HTTP or AMQP.
Three issues we've hit:
Discovery is presumptuous. The Agent Card assumes you know the agent's URL. In large enterprises, that URL moves. We built a service registry overlay that caches cards and handles re-discovery. That's not in the spec.
No standardized auth flows. The security field in the Agent Card documents the auth type, but the handshake is still ad-hoc. Most implementations use Bearer tokens. Some use mTLS. None of them interop cleanly. Auth0's comparison highlights this — A2A delegates security to the transport layer, which means "standard" is whatever your infrastructure already does.
Error semantics are thin. When a task fails, the protocol gives you a message string. No error codes, no retry metadata, no structured diagnostics. For debugging production agent failures, this is painful. We log task IDs and correlate them to our tracing system — but we built that ourselves.
The Orchestration Layer
Protocols handle the transport of interoperability. But production systems need decisions.
Who routes which task to which agent? What if the primary agent is down? How do you handle partial failures where one of three sub-agents completed its work?
We built a router that uses MCP to discover tool availability and A2A to delegate agent work. The routing logic itself is simple — a YAML config with fallback rules:
yaml
routes:
- pattern: "invoice_*"
primary: doc-analyzer
fallback: legacy-invoice-agent
timeout: 30s
- pattern: "customer_*"
primary: crm-agent
fallback: data-lookup-tool
timeout: 15s
The trick is the semantic layer. MCP tells us which agent has the right tool. A2A tells us whether that agent will accept the task. The router combines both signals before delegating.
Elastic's writeup on this nails the mental model: MCP is for "how does this agent use tools" and A2A is for "how do agents discover and talk to each other." They're complementary, and the order matters. You check MCP compatibility first, then initiate A2A.
Memory: The Missing Protocol
Here's the uncomfortable truth. Orca Security's analysis gets at something neither MCP nor A2A solves: context persistence.
Your agent calls a tool via MCP. It gets a result. That result needs to inform the next tool call. Then the next agent. Then the next task.
Where does that state live?
In our systems, we maintain a shared context store — Redis with JSON payloads, TTL'd per user session. Both MCP tool executions and A2A task delegations read from and write to this store.
Neither protocol manages this for you. That's by design, but it's a gap you'll feel in production.
Here's the pattern that works:
python
class AgentContext:
def __init__(self, session_id):
self.session_id = session_id
self.store = redis_client
def get(self, key):
return json.loads(self.store.get(f"{self.session_id}:{key}"))
def set(self, key, value):
self.store.set(f"{self.session_id}:{key}", json.dumps(value), ex=3600)
You pass a session_id through every MCP call and every A2A task. Each agent retrieves and updates the shared context. It's not elegant. It's necessary.
Practical Integration: A Walkthrough
Let's build something real. A customer support system with two agents:
- Triage Agent — classifies incoming support tickets
- Resolution Agent — looks up product docs and suggests fixes
The Triage Agent uses MCP to call a sentiment-analysis tool. When it detects high urgency, it delegates to the Resolution Agent via A2A.
Setting up the MCP tool:
python
# mcp_server.py
from mcp.server import Server, stdio_server
app = Server("triage")
@app.call_tool()
async def call_tool(name, arguments):
if name == "classify_urgency":
text = arguments["text"]
score = sentiment_model.predict(text) # Returns 0-1
return [TextContent(
type="text",
text=json.dumps({
"urgency": "high" if score > 0.8 else "low",
"score": score
})
)]
Then the A2A client that delegates:
python
# a2a_client.py
from a2a_client import A2AClient
async def escalate_to_resolution(ticket_text):
client = A2AClient("https://agents.sivaro.dev/resolution-agent")
task = await client.create_task({
"input": {
"text": ticket_text,
"priority": "high",
"source": "triage-agent"
}
})
return task
Now the orchestration:
python
# orchestrator.py
async def handle_ticket(ticket):
# Step 1: Triage via MCP tool
urgency = await mcp_call(
server="triage-server",
tool="classify_urgency",
args={"text": ticket.text}
)
if urgency["urgency"] == "high":
# Step 2: Escalate via A2A
task = await escalate_to_resolution(ticket.text)
result = await poll_task(client, task.id)
# Step 3: Sync result back to context
context.set("ticket_resolution", result)
return result
else:
# Low urgency: handle with internal logic
return simple_autoresponse(ticket)
At no point does the Triage Agent need to know how the Resolution Agent works internally. It has the Agent Card. It knows the input format. It delegates.
That's the whole point.
When NOT to Use These Protocols
I'm going to give you the contrarian take. Most protocol comparisons frame this as "MCP for single-agent, A2A for multi-agent." And that's true, but it's reductive.
Don't use MCP when your "tools" are just REST endpoints. If you're calling a service that already has a well-defined API, skip the MCP wrapper. You're adding a translation layer that obscures errors and complicates debugging.
Don't use A2A for simple service-to-service calls. If your "agent" is a Lambda function that returns a JSON blob, you don't need task lifecycle management. Just call it directly.
A2A earns its keep when you have:
- Agents that run for minutes or hours, not milliseconds
- Agents that need to ask clarifying questions (
input-requiredstate) - Agents whose capabilities change over time (discovery matters)
- Teams that build agents independently (decoupled contracts matter)
If none of those apply, you're adding complexity for no benefit.
The Security Reality
Let's talk about what nobody wants to admit. Agent-to-agent authentication is a mess.
MCP has a clear security story: the model authenticates to the tool server, usually with API keys or OAuth. That's solvable.
A2A is harder. When Agent A asks Agent B to do work, you must verify:
- Agent A has permission to delegate tasks
- Agent B trusts Agent A's caller (the end user)
- The user's authorization scope carries through the delegation chain
That's delegated authorization. It's the same problem enterprise SSO solved two decades ago, but now the "client" is a language model with token-context limits and no real concept of user impersonation.
We've hacked around it with JWT propagation — each MCP call and A2A request carries a token that encodes the original user. But this isn't in either spec.
Until the protocols address delegation security natively, you're building custom middleware. Budget for it.
Future Trends
The interoperability story is evolving fast.
Context-aware protocols are emerging — memGPT's research line and several startup efforts are tackling the memory problem that MCP and A2A ignore. By early 2027, I expect context persistence to become a first-class concern.
Gateways are consolidating. Instead of running separate MCP and A2A infrastructure, teams are adopting unified gateways that translate between protocols. You present an MCP interface to your models, an A2A interface to other agents, and the gateway handles the translation.
One thing I'm confident about: the ecosystem is moving toward fewer protocols, not more. MCP and A2A won the mindshare battle. Rather than betting on a single winner, we're seeing the two settle into complementary roles. As the Elastic team observed, you don't choose between them. You choose how they work together.
The SIVARO Rule
Here's what we've settled on after two years of building agent systems:
MCP is for control. A2A is for delegation.
If you're giving an agent capability, use MCP. If you're giving an agent responsibility, use A2A.
That distinction has saved us from some genuinely bad architecture decisions. We moved three services from A2A to MCP when we realized they were just tools, not agents. And we moved two integrations from MCP to A2A when we realized they needed task negotiation and human-in-the-loop interactions.
When people ask me "what's the right protocol for agent interoperability," the answer is almost always "both, but not the way you think."
The common failure mode isn't choosing wrong. It's choosing one, then trying to stretch it to cover the other's job. Don't do that.
Your production agents need tools (MCP) and peers (A2A). Build both layers, keep them separate, and let the orchestration layer translate between them.
That's a2a and mcp for agent interoperability understood as an engineering practice, not a marketing category.
FAQ
Q: Is A2A ready for production?
A: Yes, with caveats. We've run it in production for six months. The core protocol is stable. Discovery and security are still rough edges — you'll need to build infrastructure around those.
Q: Do I need both MCP and A2A?
A: If you're building a single agent that calls tools, MCP alone suffices. If you're building multiple agents that collaborate, you need both. MCP handles tool access; A2A handles agent-to-agent work.
Q: Can I use MCP to connect two agents?
A: Technically yes, but it's a bad fit. MCP frames everything as tool calls. Agent-to-agent work involves task lifecycle, long-running operations, and clarification loops — none of which MCP models well.
Q: How do I authenticate between agents?
A: Pragmatically, use JWT with user-context propagation. The A2A spec supports Bearer tokens, but delegation security is still immature. Plan to build middleware for this.
Q: What about Model Context Protocol's new versions?
A: MCP is iterating rapidly. The recent spec updates improved resource handling and added more robust error metadata. Watch for version changes — breaking changes in MCP are rare but real.
Q: Performance impact of running both protocols?
A: Negligible for most workloads. The overhead is a few milliseconds per request on the wire. The real cost is engineering time: you'll spend more time debugging semantic mismatches than protocol overhead.
Q: Which protocol wins for agent interoperability?
A: Neither. The interop layer is where your engineering judgment lives. Protocols are transport — context, security, and orchestration are your architecture.
Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.