A2A Protocol for MCP Developers: Code Walkthrough

Your MCP server started as one focused tool. Then you added another capability, then another — and now it’s the agent equivalent of a Swiss Army knife that does everything slowly. If you’re hitting that ceiling, A2A protocol is the missing piece your MCP agent stack needs.

This isn’t a “throw everything out” moment. A2A (Agent-to-Agent) doesn’t replace MCP — it sits alongside it and solves a completely different problem. By the end of this post, you’ll understand exactly how the two protocols relate, have a working mental model for when to use each, and walk away with copy-paste-ready Python — using either Google ADK or Pydantic AI — to expose your first A2A agent and call a remote one from your existing orchestrator.

The Two-Layer Mental Model: MCP Does Vertical, A2A Does Horizontal

Think of your current MCP setup as a vertical stack: one agent reaches down to grab tools, query databases, and call APIs. That’s exactly what MCP was designed for — and it handles it well. The problem only surfaces when your agent needs to delegate an entire job to something else, not just fetch a resource.

A2A is horizontal. It’s the protocol that lets one agent hand work to another agent — as a peer, not as a tool call. The two protocols don’t compete because they operate on different axes:

  • MCP: your agent ↔ tools, databases, APIs, files
  • A2A: your agent ↔ other agents

In practice, a production multi-agent system needs both. Specialist agents each keep their MCP servers for tool access. An A2A orchestrator coordinates across them.

The numbers back up why this matters now. Multi-agent architectures already account for 66.4% of the AI agent market, and Gartner forecasts that 40% of enterprise applications will embed task-specific AI agents by 2026, up from less than 5% in 2025. The pressure to get agents collaborating — not just acting independently — is what’s driving A2A adoption so quickly.

What Changed in A2A v1.0 (and Why It’s Now Production-Ready)

A2A launched in April 2025 as a Google initiative with roughly 50 partner organizations. By the time it hit v1.0 in early 2026, that number had grown to 150+ — including AWS, Microsoft, Salesforce, SAP, and ServiceNow. Google Cloud donated the specification, SDKs, and tooling to the Linux Foundation in June 2025, making it a neutral, community-governed standard.

The most significant consolidation came in August 2025: IBM’s Agent Communication Protocol (ACP), which was A2A’s largest potential competitor, voluntarily merged into A2A under LF AI & Data. The protocol fragmentation debate is effectively over — A2A is the de facto standard.

What v1.0 specifically added that makes it ready for production:

  • Signed Agent Cards — cryptographic signatures on capability documents prevent card forgery and make decentralized agent discovery trustworthy
  • Stable transport semantics — the JSON-RPC 2.0 + SSE transport layer is finalized with no more breaking changes anticipated
  • Formalized task lifecycle — the state machine is fully specified, including terminal state behavior and its operational implications

If you were waiting for A2A to stabilize before investing time in it, v1.0 is that signal.

The Three Primitives You Need to Know: Agent Cards, Task Lifecycle, and Transport

Before touching code, three concepts will save you a lot of confusion.

Agent cards

Every A2A agent publishes an Agent Card at /.well-known/agent-card.json. This is how other agents discover what you can do, how to reach you, and what authentication you require. Think of it as a machine-readable API spec combined with a service advertisement.

A minimal Agent Card includes:
name and description — what this agent does
url — where to send tasks
capabilities — whether you support streaming, push notifications, etc.
skills — the specific tasks this agent handles, tagged by ID so orchestrators can route intelligently

In v1.0, cards can be cryptographically signed. An orchestrator that verifies signatures before connecting gets real protection against a malicious agent advertising itself as a trusted service. Most existing blog posts still describe cards as plain JSON and skip the signature entirely — don’t skip it in production.

Task lifecycle

A2A is task-oriented. When your orchestrator sends work to a remote agent, that work becomes a Task with a well-defined state machine:

submitted → working → [input-required] → completed
                                       → failed
                                       → canceled

The critical operational detail: terminal states (completed, failed, canceled) cannot be restarted. If a task fails, you create a new one — you don’t retry the same task object. Retry logic and idempotency belong in your orchestrator, not inside individual agents.

The input-required state is particularly useful: a remote agent can pause a task and ask your orchestrator for clarification before continuing. This enables multi-turn conversations within a single logical work unit.

Transport

A2A runs JSON-RPC 2.0 over HTTPS. For long-running tasks, agents stream updates back using Server-Sent Events (SSE). Your orchestrator can either poll for task status synchronously or subscribe to the SSE stream for real-time updates.

Choosing between them matters operationally. SSE is lower latency but requires managing persistent connections and handling reconnects. For short tasks — under a few seconds — synchronous polling is simpler and avoids connection overhead. For anything involving document processing, multi-step reasoning, or external API calls, SSE is worth the added complexity.

When to Promote an MCP Tool to a Standalone A2A Agent (Decision Framework)

This is the question that has no good answer anywhere online, so here’s a practical framework.

Keep it as an MCP tool when:
– It’s a simple read/write operation — database query, file read, API call
– It runs in under a second and requires no internal state
– Only one agent ever calls it
– There’s no concept of “progress” — it either works or it doesn’t

Promote it to a standalone A2A agent when:
– The task involves multiple steps that take real time (seconds to minutes)
– The logic is complex enough you’d want to version and deploy it independently
– Multiple orchestrators need to call the same capability
– You need input-required — the capability needs to ask clarifying questions mid-task
– You want separate scaling, monitoring, or error boundaries

A concrete example: a “fetch webpage” capability stays as an MCP tool. A “research a topic and write a summary” capability becomes an A2A agent — it’s long-running, stateful, and multiple orchestrators will want it.

The pattern that works well in practice: keep all tool and resource access on your MCP servers exactly as-is, then build an A2A orchestrator that delegates to specialist sub-agents.

Exposing Your First A2A Agent with Google ADK — Code Walkthrough

Google’s Agent Development Kit (ADK) has the most concise path to an A2A server. If you already have an agent defined in ADK, exposing it via A2A takes three lines:

import uvicorn
from google.adk.agents import Agent
from google.adk.a2a import to_a2a

research_agent = Agent(
    name="research_agent",
    model="gemini-2.0-flash",
    description="Researches topics and returns structured summaries",
    instruction="You are a research specialist. Given a topic, search for "
                "information and return a concise, factual summary with sources.",
)

# Wrap the agent as an A2A server
app = to_a2a(research_agent)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8001)

to_a2a() automatically generates the Agent Card endpoint at /.well-known/agent-card.json, wires up the task management endpoints, and handles SSE streaming. You configure none of that manually.

To consume this agent from your existing orchestrator:

from google.adk.a2a import RemoteA2aAgent

# Drop this into your orchestrator's sub-agent list
research_peer = RemoteA2aAgent(
    name="research_agent",
    description="Remote research specialist — delegates topic research",
    agent_card_url="http://localhost:8001/.well-known/agent-card.json",
)

orchestrator = Agent(
    name="orchestrator",
    model="gemini-2.0-flash",
    description="Coordinates research and writing tasks",
    agents=[research_peer],  # A2A peer treated as a sub-agent
)

Your orchestrator sees research_peer the same way it sees any local sub-agent. ADK handles capability discovery, task dispatch, and response streaming — none of that is visible at the call site.

The Same Thing with Pydantic AI — FastA2A in Under 10 Lines

If you’re not on ADK, Pydantic AI ships native A2A support and integrates cleanly with any ASGI server. The standalone FastA2A library handles the server side:

from pydantic_ai import Agent
from pydantic_ai.a2a import FastA2A
import uvicorn

agent = Agent(
    "openai:gpt-4o",
    system_prompt="You are a research specialist. Given a topic, "
                  "return a factual summary with sources.",
)

app = FastA2A(agent)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8001)

FastA2A generates the Agent Card, handles task routing, and supports SSE streaming out of the box.

Calling a remote A2A agent from Pydantic AI is equally direct:

from pydantic_ai.a2a import A2AClient

async def run_with_remote_agent(topic: str) -> str:
    client = A2AClient("http://localhost:8001/.well-known/agent-card.json")
    result = await client.run(f"Research this topic: {topic}")
    return result.output

ADK vs. Pydantic AI — which should you pick? ADK is the better fit if you’re already in the Google Cloud ecosystem or using Gemini models — the operational tooling (tracing, evaluation) is more mature there. Pydantic AI is the better fit if you want model-agnostic code, type-safety throughout your agent logic, or you’re already using Pydantic for data validation elsewhere in your stack. Both are production-ready as of early 2026.

Calling a Remote A2A Agent from Your Existing MCP Orchestrator

If your current orchestrator is MCP-based and you’re not ready to move to ADK or Pydantic AI, you can still consume A2A agents. The cleanest pattern wraps the A2A call as an MCP tool inside your orchestrator server:

from mcp.server.fastmcp import FastMCP
import httpx

mcp = FastMCP("orchestrator")

@mcp.tool()
async def delegate_research(topic: str) -> str:
    """Delegate research tasks to the specialist research agent."""
    payload = {
        "jsonrpc": "2.0",
        "id": "1",
        "method": "tasks/send",
        "params": {
            "message": {
                "role": "user",
                "parts": [{"type": "text", "text": f"Research: {topic}"}]
            }
        }
    }

    async with httpx.AsyncClient() as client:
        response = await client.post(
            "http://localhost:8001/",
            json=payload,
            headers={"Content-Type": "application/json"}
        )
        task_id = response.json()["result"]["id"]

        # Poll for completion (add exponential backoff in production)
        while True:
            status = await client.post("http://localhost:8001/", json={
                "jsonrpc": "2.0", "id": "2",
                "method": "tasks/get",
                "params": {"id": task_id}
            })
            task = status.json()["result"]
            if task["status"]["state"] in ("completed", "failed", "canceled"):
                break

    if task["status"]["state"] != "completed":
        raise RuntimeError(f"Task failed: {task['status'].get('message')}")

    return task["artifacts"][0]["parts"][0]["text"]

This pattern preserves your entire MCP orchestrator as-is. The A2A complexity is encapsulated in one tool function. Specialist agents can run anywhere — on-prem, in different cloud accounts, or managed by another team — and your orchestrator treats them like any other tool call.

What to Watch Out For: Signed Agent Cards, Terminal Task States, and Auth

Three things catch developers off guard in production.

Signed Agent Cards. Agent Card forgery is a real attack surface in decentralized multi-agent systems. If your orchestrator blindly trusts any card it fetches, a compromised DNS record or SSRF vulnerability could route tasks to a malicious agent. In v1.0, cards include a signature field with a cryptographic signature over the card body. Verify it against the agent’s public key before connecting — the official A2A SDKs handle this, but you have to opt in.

Terminal task states don’t restart. When a task enters failed or canceled, it’s done permanently. There’s no tasks/retry method. Your orchestrator needs to detect these states, create a new task, and handle idempotency at the application layer. A clean pattern: tag tasks with an idempotency key in your orchestrator’s database, then check for an existing completed task before creating a new one.

Authentication isn’t optional in production. The A2A spec supports OAuth 2.0, API keys, and service account tokens — the Agent Card declares which schemes the agent accepts. Many tutorial examples skip auth entirely. In any deployment where agents cross trust boundaries — different teams, different clouds, external vendors — treat A2A endpoints like any other external API. Landbase’s analysis of agentic AI pilots found that lack of interoperability is the second most common reason agentic AI pilots fail, and many of those failures trace back to auth and trust boundary issues rather than protocol mismatches.

Putting It Together

MCP vs. A2A is a false choice. MCP handles the vertical — your agent reaching down to tools, data, and APIs. A2A handles the horizontal — your agents reaching across to each other. Both layers are present in any serious multi-agent architecture, and they compose cleanly.

With 150+ organizations behind A2A, IBM’s ACP merged in, and v1.0 now production-stable, the fragmentation risk that might have made you hesitant is gone. The entry cost is genuinely low — three lines with ADK, ten with Pydantic AI — and your existing MCP infrastructure stays completely intact.

Start small: pick the capability in your current MCP server that’s most self-contained and slowest, expose it as an A2A agent using the snippets above, and call it from your orchestrator. That’s your proof of concept. Scale from there once you see how cleanly the two protocols compose.

Leave a Reply

Your email address will not be published. Required fields are marked *