DEV Community

Marwa Sahli
Marwa Sahli

Posted on Originally published at marwashli.github.io

The A2A protocol: how it works, step by step, and how it compares with MCP

AI systems are no longer just a single model answering questions. We now build specialised agents (an HR agent, a booking agent, a code-review agent), often developed by different teams, on different frameworks (LangGraph, CrewAI, ADK, OpenAI Agents SDK…), and hosted by different providers. That raises a new question: how do these agents talk to each other?

That is the question A2A (Agent2Agent) answers. This article introduces the protocol, walks through how it works step by step, and then compares it with MCP (Model Context Protocol), with which it is often confused.


Contents

  1. What is A2A?
  2. History and governance
  3. Key concepts
  4. A three-layer architecture
  5. How it works, step by step
  6. Task lifecycle
  7. Security
  8. A2A vs MCP
  9. Using A2A and MCP together
  10. Current limitations and best practices
  11. Conclusion

1. What is A2A?

A2A is an open specification that defines how independent AI agents:

  • discover each other (who can do what, and how to reach them),
  • delegate tasks to one another, including long-running, multi-step ones,
  • exchange data (text, JSON, files) securely,
  • track progress on work in flight (streaming, notifications).

The core idea: every agent is treated as a black box. A client agent does not need to know the remote agent's model, prompt, tools or memory. It hands over a task, the way you would hand a file to a colleague in another department, and gets a result back.

In one sentence: MCP connects an agent to its tools; A2A connects an agent to other agents.


2. History and governance

Date Milestone
April 2025 Protocol announced by Google, with around fifty partners
June 2025 Project donated to the Linux Foundation, which now provides neutral governance
Summer 2025 Version 0.3: gRPC binding added, Agent Card signatures
March 2026 Stable version 1.0
2026 Production support in several platforms (Microsoft Copilot Studio, Azure AI Foundry, Amazon Bedrock AgentCore, ServiceNow, Salesforce…)

Version 1.0 introduced several breaking changes compared with 0.x (method names, state names, removed fields). If you are reading tutorials from 2025, check which version they target.


3. Key concepts

A2A relies on a small vocabulary:

Concept Role
Client agent The agent that initiates the request (often an orchestrator).
Remote agent (A2A server) The agent that receives and executes the task. Roles are fluid: an agent can be the client in one interaction and the server in another.
Agent Card The agent's public JSON identity card: name, description, version, skills, endpoints, authentication methods.
Skill A capability declared in the Agent Card (e.g. book_flight), with a description, tags, examples and accepted formats.
Task A unit of work with an identifier and a state, generated by the server. It can last a few milliseconds or several days.
Message One turn of conversation, carried by a role (user or agent).
Part A piece of content inside a message: text, structured JSON data, a URL to a file, or raw bytes.
Artifact The deliverable produced by the task (a report, a JSON itinerary, a PDF…), also made of Parts.
Context An identifier used to group several tasks and messages belonging to the same conversation.

4. A three-layer architecture

The v1.0 specification is split into three layers, so you can change transport without rewriting the agent's logic:

┌─────────────────────────────────────────────────────────┐
│ Layer 3 — Protocol bindings                             │
│   JSON-RPC 2.0 over HTTPS  │  gRPC  │  HTTP+JSON (REST) │
├─────────────────────────────────────────────────────────┤
│ Layer 2 — Abstract operations                           │
│   SendMessage, SendStreamingMessage, GetTask,           │
│   ListTasks, CancelTask, SubscribeToTask,               │
│   push notification config, Agent Card retrieval        │
├─────────────────────────────────────────────────────────┤
│ Layer 1 — Canonical data model                          │
│   AgentCard, AgentSkill, Task, Message, Part,           │
│   Artifact, Extension (Protocol Buffers + JSON Schema)  │
└─────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Each agent advertises the binding(s) it supports in its Agent Card (the supportedInterfaces field). JSON-RPC is the most common for publicly exposed agents; gRPC is attractive for low-latency internal traffic.


5. How it works, step by step

Let's take a concrete scenario: an HR agent needs to organise an interview and delegates the booking to a travel agent run by another team.

![A2A task delegation sequence: discovery, authentication, sending, tracking, multi-turn exchange and result]

Step 1 — Discovery via the Agent Card

The remote agent publishes its card at a standard URL: https://<domain>/.well-known/agent-card.json (an RFC 8615 convention). A simplified, v1.0-compliant example:

{
  "name": "Travel booking agent",
  "description": "Books flights and hotels for employees.",
  "version": "1.2.0",
  "provider": {
    "organization": "MyCompany",
    "url": "https://mycompany.example"
  },
  "supportedInterfaces": [
    {
      "url": "https://travel.mycompany.example/a2a",
      "protocolBinding": "JSONRPC",
      "protocolVersion": "1.0"
    }
  ],
  "capabilities": {
    "streaming": true,
    "pushNotifications": true,
    "extendedAgentCard": false
  },
  "defaultInputModes": ["text/plain", "application/json"],
  "defaultOutputModes": ["application/json"],
  "skills": [
    {
      "id": "book_flight",
      "name": "Book a flight",
      "description": "Books a flight given an origin, a destination and dates.",
      "tags": ["travel", "booking"],
      "examples": [
        "Book a Paris–Lyon flight on 12 November for employee 4432"
      ]
    }
  ],
  "securitySchemes": {
    "corp_oauth": {
      "type": "oauth2",
      "flows": {
        "clientCredentials": {
          "tokenUrl": "https://auth.mycompany.example/token",
          "scopes": { "flights:write": "Book flights" }
        }
      }
    }
  },
  "security": [{ "corp_oauth": ["flights:write"] }]
}
Enter fullscreen mode Exit fullscreen mode

The client reads the card, checks that the book_flight skill exists, picks an interface and identifies the expected authentication method.

Discovery can also go through an internal registry (a catalogue of vetted agents) or direct configuration (a hard-coded URL) for tightly coupled systems.

Step 2 — Authentication

Credentials never travel in the body of the A2A message: they are sent in HTTP headers (Authorization: Bearer …). Here, the client runs an OAuth2 client credentials flow against the declared tokenUrl.

Step 3 — Sending the task (SendMessage)

POST /a2a HTTP/1.1
Host: travel.mycompany.example
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
Accept: application/json, text/event-stream
Enter fullscreen mode Exit fullscreen mode
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "SendMessage",
  "params": {
    "message": {
      "role": "ROLE_USER",
      "parts": [
        {
          "text": "Book a direct Paris–Lyon flight on 12 November for employee 4432."
        }
      ]
    },
    "configuration": { "returnImmediately": true }
  }
}
Enter fullscreen mode Exit fullscreen mode

Things to note:

  • The client does not supply a task identifier: the server generates it and returns it.
  • For a trivial answer, the server can return a Message directly instead of creating a Task.
  • returnImmediately: true asks the server to hand back the newly created task right away, so the client can follow progress in parallel (in v1.0 the default behaviour is blocking).

Step 4 — Tracking progress

Three mechanisms, depending on duration and topology:

Mechanism When to use it Operations
Polling Short tasks, simple client GetTask at regular intervals
SSE streaming The client stays connected and wants real-time updates SendStreamingMessage (send + stream) or SubscribeToTask (subscribe to an existing task)
Push notifications Very long tasks, disconnected client (mobile, serverless) The client registers a webhook; the server POSTs updates to it, authenticated (signed JWT, HMAC or mTLS)

Example of a received SSE stream:

data: {"task": {"id": "task-9876", "status": {"state": "TASK_STATE_SUBMITTED"}}}

data: {"statusUpdate": {"taskId": "task-9876", "status": {"state": "TASK_STATE_WORKING"}}}

data: {"artifactUpdate": {"taskId": "task-9876", "artifact": {"artifactId": "a1", "name": "itinerary", "parts": [ ... ]}}}

data: {"statusUpdate": {"taskId": "task-9876", "status": {"state": "TASK_STATE_COMPLETED"}}}
Enter fullscreen mode Exit fullscreen mode

The stream closes automatically when the task reaches a terminal state.

Step 5 — Multi-turn exchange (if needed)

If the remote agent needs clarification ("economy or business class?"), the task moves to INPUT_REQUIRED. The client replies with a new SendMessage carrying the same task identifier, and work resumes.

Step 6 — Retrieving the result

Artifacts hold the final deliverable. A client that missed the stream can always call GetTask to fetch the full task, with its history and artifacts.

Bonus: a minimal Python client

import requests

BASE = "https://travel.mycompany.example"

# 1. Discovery
card = requests.get(f"{BASE}/.well-known/agent-card.json", timeout=10).json()
if "book_flight" not in {s["id"] for s in card.get("skills", [])}:
    raise RuntimeError("The agent does not offer the book_flight skill")

endpoint = card["supportedInterfaces"][0]["url"]
headers = {"Authorization": "Bearer <token>", "Content-Type": "application/json"}

# 2. Send the task
payload = {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "SendMessage",
    "params": {
        "message": {
            "role": "ROLE_USER",
            "parts": [{"text": "Book a Paris–Lyon flight on 12/11 for employee 4432."}],
        },
        "configuration": {"returnImmediately": True},
    },
}
task = requests.post(endpoint, json=payload, headers=headers, timeout=30).json()["result"]

# 3. Read the state (simple polling)
status = requests.post(
    endpoint,
    json={"jsonrpc": "2.0", "id": 2, "method": "GetTask",
          "params": {"taskId": task["task"]["id"]}},
    headers=headers, timeout=30,
).json()
print(status)
Enter fullscreen mode Exit fullscreen mode

In production, prefer the official SDKs (Python, JavaScript, Java, Go, .NET) published by the A2A project over hand-written HTTP calls. The exact shape of result depends on the server's response (task or message).


6. Task lifecycle

![A2A task lifecycle: in-progress, interrupted and terminal states]

State (v1.0) Category Meaning
TASK_STATE_SUBMITTED in progress Accepted, not yet started
TASK_STATE_WORKING in progress Actively being processed
TASK_STATE_INPUT_REQUIRED interrupted Waiting for information from the client
TASK_STATE_AUTH_REQUIRED interrupted The client must provide additional credentials
TASK_STATE_COMPLETED terminal Finished successfully
TASK_STATE_FAILED terminal Unrecoverable error
TASK_STATE_CANCELED terminal Cancelled (often via CancelTask)
TASK_STATE_REJECTED terminal Refused by the agent (policy, quota, capability)

7. Security

A2A builds on web standards rather than inventing its own:

  • Encrypted transport: HTTPS (TLS 1.2 minimum) is mandatory in production.
  • Authentication schemes aligned with OpenAPI: OAuth 2.0, OpenID Connect, API key, mTLS. Declared in securitySchemes and required via security.
  • Signed Agent Cards: a JWS signature (RFC 7515) computed over a canonical form of the card (RFC 8785) makes any tampering detectable. Otherwise, a modified card could redirect calls or inject malicious content into the client agent's prompt.
  • Extended Agent Card: an agent can expose a minimal public card and a more detailed one, available only after authentication.
  • Traceability: the specification recommends OpenTelemetry and propagating W3C Trace Context headers (traceparent) to follow a request across the whole chain of agents.

For high assurance: combine mTLS and OAuth2, or use sender-constrained tokens (DPoP, RFC 9449), so that a stolen token is useless from another machine.


8. A2A vs MCP

Reminder: what is MCP?

MCP (Model Context Protocol), launched by Anthropic in late 2024 and now widely adopted, standardises the connection between an AI application (the client/host) and servers that expose tools, resources (data) and prompts. It runs on JSON-RPC 2.0, locally over stdio or remotely over HTTP. Its specifications are named by date; the 2026-07-28 revision notably made the protocol stateless (sessions and the initialisation handshake were removed), which makes horizontal scaling of servers easier.

Comparison table

Criterion A2A MCP
Purpose Make autonomous agents collaborate Give an agent access to tools and data
Analogy A colleague you delegate a file to A toolbox you use yourself
Nature of the counterpart Opaque, autonomous, reasons on its own A deterministic function or exposed data source
Unit of exchange A task (Task) with a state and a lifecycle A tool call / resource read
Typical duration Seconds to days, multi-turn Usually short, request/response
Interaction Negotiation possible (INPUT_REQUIRED, AUTH_REQUIRED) The model calls, the tool answers
Discovery Public Agent Card (/.well-known/agent-card.json), registries Listing tools/resources on the connected server
Transports JSON-RPC 2.0, gRPC, HTTP+JSON JSON-RPC 2.0 over stdio or HTTP
Async / long-running Native: SSE, webhook push notifications Possible but less central
Content Multimodal via Parts (text, JSON, files, bytes) Structured results, text, images, resources
Topology Peer-to-peer, interchangeable client/server roles Host → client → server
Origin / governance Google → Linux Foundation Anthropic → open governance by the MCP community
Maturity (Oct. 2026) v1.0 stable (March 2026) 2026-07-28 revision stable

The right question to ask

"Do I want to perform a precise action, or hand over a goal to someone who will figure out how to reach it?"

  • A precise, deterministic action (an SQL query, reading a file, calling a weather API) → MCP.
  • A goal to reach by a system that reasons, can ask questions, and whose implementation you don't want to know about → A2A.

There is a grey area: you can expose a whole agent as an MCP "tool". That works for simple cases, but you lose what makes A2A strong: state tracking, multi-turn exchanges, long-running tasks and cross-organisation discovery.


9. Using A2A and MCP together

The two protocols are complementary and together form the communication stack of multi-agent systems:

An orchestrator delegates via A2A to two agents, each of which uses its own tools via MCP

Scenario:

  1. The user asks the orchestrator: "Organise Marie's interview in Lyon on Thursday."
  2. The orchestrator delegates via A2A to the HR agent ("schedule the interview") and to the travel agent ("book the trip").
  3. Each of these agents uses MCP internally to query its own tools: calendar, HRIS, booking API.
  4. The agents return their artifacts (confirmed slot, ticket) to the orchestrator via A2A.

Rule of thumb: A2A between agents, MCP between an agent and its tools.


10. Current limitations and best practices

Limitations to be aware of:

  • Per-skill data schema: a skill declares that it accepts application/json, but the specification does not standardise the exact schema expected. Workaround: publish a JSON Schema in the skill description or through an Extension declared in the Agent Card.
  • Downscoping across delegation chains: A2A says how to pass credentials, not how to narrow them when a highly privileged orchestrator delegates to a less privileged agent. Workaround: OAuth 2.0 Token Exchange (RFC 8693) to issue short-lived, narrowly scoped tokens.
  • No standard registry: discovery through a central catalogue is still something you build yourself.
  • Migrating from 0.x to 1.0: method names (message/send → SendMessage), prefixed state names, removal of the kind and final fields.

Best practices:

  • Treat the Agent Card as a contract: any change to a skill, URL or capability must bump the version.
  • Sign Agent Cards and verify signatures on the client side.
  • Serve the card with Content-Type: application/json.
  • Put an API gateway in front of agents to centralise authentication, quotas, logging and long-lived connection handling.
  • Instrument with OpenTelemetry from day one: debugging a chain of five agents without distributed traces is painful.
  • Never blindly trust content received from another agent: it is data, not instructions (prompt-injection risk).

11. Conclusion

A2A does for agents what HTTP did for documents or SMTP for email: it provides a common, neutral language that lets heterogeneous systems cooperate without bespoke integration. With a stable v1.0, Linux Foundation governance and adoption by the major platforms, it is now ready for production use.

MCP and A2A are not competitors: MCP gives an agent hands; A2A gives it colleagues. A modern multi-agent architecture uses both.


Further reading


Originally published on my blog. Une version française est disponible ici.

Top comments (0)