Agent-to-Agent (A2A) Protocol
A2A is an open protocol for inter-agent collaboration: independent, often opaque agents discover one another through Agent Cards, then exchange Messages and stateful Tasks over JSON-RPC 2.0 (with optional SSE streaming and webhook push notifications). This page covers Agent Card discovery, the Task lifecycle, Messages and Parts, Artifacts, the RPC method surface, and how A2A complements MCP.
The A2A (Agent2Agent) protocol is an open standard for how independent AI agents — built by
different teams, vendors, and frameworks — discover one another, negotiate, and collaborate on
tasks. Where the Model Context Protocol (see protocols-mcp.html) lets a single agent reach
down to its own tools and data, A2A lets an agent reach across to a peer it does not
control and cannot see inside. The remote agent is treated as an autonomous, opaque system
that "reasons, plans, uses multiple tools, and maintains state over longer interactions," rather
than a stateless function call.
A2A was introduced by Google in April 2025 and donated to the Linux Foundation, which launched the vendor-neutral Agent2Agent Protocol project on June 23, 2025 with founding support from Amazon Web Services, Cisco, Google, Microsoft, Salesforce, SAP, and ServiceNow. The specification is published under the Apache 2.0 license.
A2A is deliberately built on widely deployed web standards so it drops into existing infrastructure: communication occurs over HTTP(S); JSON-RPC 2.0 is the payload format for all requests and responses; and Server-Sent Events (SSE) carry streaming updates.
The three actors
A2A defines a client/server relationship that is itself between agents:
- A2A Client (client agent) — an application, service, or agent acting on behalf of a user that initiates requests to a remote agent.
- A2A Server (remote agent) — an agent or agentic system that exposes an HTTP endpoint implementing A2A.
- User — the human or system whose goal is ultimately being served.
A single deployment is frequently both: an orchestrator agent is a server to its caller and a client to the specialist agents it delegates to.
Agent Card discovery
Before any work happens, a client must find a server and learn what it can do. A server advertises itself with an agent-card: a JSON metadata document describing the agent's identity, service endpoint, capabilities, skills, and how to authenticate. The canonical discovery mechanism is the well-known URI — the card is hosted at:
https://{agent-server-domain}/.well-known/agent-card.json
(This path was /.well-known/agent.json through v0.2.x and was renamed to agent-card.json
in v0.3.0, released in late July 2025; the Agent Card examples on this page pin
protocolVersion: "0.2.5" but adopt the newer path name.) Two other discovery mechanisms are
defined: curated registries (a catalog service that holds many Agent Cards) and direct
configuration (hardcoded URLs, config files, or environment variables).
A representative Agent Card. Field names below are spec-accurate:
{
"protocolVersion": "0.2.5", // A2A version the server implements
"name": "Revenue Analyst Agent", // human-readable agent name
"description": "Analyzes financial data and produces revenue summaries.",
"url": "https://agent.example.com/a2a/v1", // the A2A service endpoint
"preferredTransport": "JSONRPC", // transport at `url` (default JSON-RPC)
"version": "1.4.0", // the agent implementation's own version
"provider": { // optional: who operates the agent
"organization": "Example Corp",
"url": "https://example.com"
},
"capabilities": { // optional protocol features the agent supports
"streaming": true, // supports message/stream over SSE
"pushNotifications": true, // supports webhook push notifications
"stateTransitionHistory": false
},
"defaultInputModes": ["text/plain", "application/json"], // accepted media types
"defaultOutputModes": ["text/plain", "application/json"], // produced media types
"securitySchemes": { // OpenAPI-style auth scheme declarations
"bearer": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" }
},
"security": [{ "bearer": [] }], // which scheme(s) apply
"skills": [ // discrete capabilities the agent offers
{
"id": "summarize-revenue",
"name": "Summarize revenue",
"description": "Summarize quarterly revenue from supplied figures.",
"tags": ["finance", "summarization"],
"inputModes": ["text/plain"],
"outputModes": ["text/plain"]
}
],
"supportsAuthenticatedExtendedCard": true // a fuller card may exist behind auth
}
Authentication in A2A is transport-level, not a bespoke scheme: the card declares
securitySchemes using OpenAPI security-scheme objects (e.g. HTTP bearer/JWT, API key, OAuth 2,
OpenID Connect), and the client presents the corresponding credentials on its HTTP requests.
Capability discovery is just reading the card: the capabilities, skills, and the
defaultInputModes/defaultOutputModes fields tell a client what the agent can do and in what
formats before it ever sends a request. If supportsAuthenticatedExtendedCard is true, a client
can fetch a richer card after authenticating — but how depends on the protocol version. In the
v0.2.5 spec this page pins its examples to, the extended card is an HTTP GET endpoint at
{AgentCard.url}/../agent/authenticatedExtendedCard and is explicitly "not a JSON-RPC method." A
dedicated JSON-RPC method, agent/getAuthenticatedExtendedCard, was only added in v0.3.0.
Messages and Parts
A a2a-message is a single turn of communication. Each message carries a role —
"user" for content from the client/user side, "agent" for content from the remote agent —
and an array of parts. A2A makes the multi-modal unit of content explicit as a a2a-part,
a discriminated union with a kind field:
- TextPart —
{ "kind": "text", "text": "..." } - FilePart —
{ "kind": "file", "file": { ... } }, where the file is either aFileWithBytes(bytes, base64-encoded, plus optionalname/mimeType) or aFileWithUri(uriplus optionalname/mimeType). - DataPart —
{ "kind": "data", "data": { ... } }for structured JSON (forms, parameters, machine-readable results).
A message also has a required messageId, an optional taskId and contextId to bind it to an
existing task and grouping, optional referenceTaskIds, and the literal discriminator
"kind": "message".
Tasks and the lifecycle
When a request requires stateful, possibly long-running processing, the server creates a
a2a-task: a unit of work with a unique id, a server-generated contextId (which logically
groups related tasks), a status, and accumulated history (messages) and artifacts. A task
carries the literal discriminator "kind": "task".
The task's status is a TaskStatus object holding a task-state (state), an optional
message, and an optional timestamp. The spec defines exactly these TaskState string
values:
| State | Meaning |
|---|---|
submitted |
Acknowledged, not yet started |
working |
Actively being processed |
input-required |
Paused; needs more input from the client (interrupted, not terminal) |
auth-required |
Paused; needs additional authentication/credentials |
completed |
Finished successfully (terminal) |
canceled |
Canceled by request (terminal) |
failed |
Ended in error (terminal) |
rejected |
Refused by the agent (terminal) |
unknown |
State could not be determined |
completed, canceled, failed, and rejected are terminal; input-required and
auth-required are interrupted states that hand control back to the client mid-task without
ending it.
Artifacts
An a2a-artifact is a tangible output the agent produces during a task — a document, image,
or structured result. Like a message, an artifact is composed of parts, and it has a stable
artifactId plus optional name, description, and metadata. Artifacts are distinct from
messages: messages are conversational turns, while artifacts are the durable results
accumulated on the task and can be streamed incrementally in chunks.
The RPC method surface
A2A's JSON-RPC binding defines a small, stable set of method strings:
| Method | Purpose |
|---|---|
message/send |
Send a message; start or continue a task (request/response) |
message/stream |
Send a message and subscribe to streamed updates over SSE |
tasks/get |
Poll the current state of a task by id |
tasks/cancel |
Request cancellation of a task |
tasks/resubscribe |
Re-attach an SSE stream to an existing task (e.g. after a drop) |
tasks/pushNotificationConfig/set |
Register a webhook for push notifications on a task |
tasks/pushNotificationConfig/get |
Retrieve a task's push-notification config |
tasks/pushNotificationConfig/list |
List configured push configs for a task |
tasks/pushNotificationConfig/delete |
Remove a push-notification config |
Fetching the authenticated extended Agent Card is not a JSON-RPC method in v0.2.5 (the version
these examples pin to) — there it is an HTTP GET endpoint (see Agent Card discovery above). A
JSON-RPC method for it, agent/getAuthenticatedExtendedCard, was added in v0.3.0.
A message/send request — the delegating agent speaks as the "user":
{
"jsonrpc": "2.0", // JSON-RPC 2.0 envelope
"id": 1, // request id; echoed in the response
"method": "message/send", // A2A method to hand off work
"params": {
"message": {
"role": "user", // content from the client side
"parts": [
{ "kind": "text", "text": "tell me a joke" } // a TextPart
],
"messageId": "9229e770-767c-417b-a0b0-f0741243c589" // client-generated id
},
"metadata": {}
}
}
If the work completes quickly, the server can answer the same request with a finished Task:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"id": "363422be-b0f9-4692-a24d-278670e7c7f1", // the task id
"contextId": "c295ea44-7543-4f78-b524-7a38915ad6e4", // groups related tasks
"status": { "state": "completed" }, // terminal state
"artifacts": [
{
"artifactId": "9b6934dd-37e3-4eb1-8766-962efaab63a1",
"name": "joke",
"parts": [
{ "kind": "text",
"text": "Why did the chicken cross the road? To get to the other side!" }
]
}
],
"kind": "task" // literal discriminator
}
}
A server may instead reply with a Message directly (a one-shot answer with no task to track),
or with a Task in a non-terminal state that the client then polls via tasks/get.
Streaming with SSE
When the Agent Card advertises capabilities.streaming: true, a client can call
message/stream instead of message/send. The HTTP response is a Server-Sent Events stream;
each SSE data: line carries a JSON-RPC response whose result is one of:
- the initial
Taskobject, - a
TaskStatusUpdateEvent—kind: "status-update"— carrying a newstatus, with afinalboolean that istrueon the last event, - a
TaskArtifactUpdateEvent—kind: "artifact-update"— carrying anartifactchunk, withappend(concatenate to a prior chunk vs. replace) andlastChunkflags.
The stream MUST begin with the Task and MUST close once the task reaches a terminal state. A
status-update event mid-task:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"taskId": "363422be-b0f9-4692-a24d-278670e7c7f1",
"contextId": "c295ea44-7543-4f78-b524-7a38915ad6e4",
"kind": "status-update", // TaskStatusUpdateEvent discriminator
"status": { "state": "working" },
"final": false // not the last event in the stream
}
}
sequenceDiagram
participant C as Client Agent
participant S as Remote Agent (A2A Server)
C->>S: GET /.well-known/agent-card.json
S-->>C: Agent Card (skills, capabilities, auth)
C->>S: POST message/stream (role=user, parts)
S-->>C: SSE: Task (state=submitted)
S-->>C: SSE: status-update (state=working, final=false)
S-->>C: SSE: artifact-update (artifact chunk, lastChunk=false)
S-->>C: SSE: artifact-update (artifact chunk, lastChunk=true)
S-->>C: SSE: status-update (state=completed, final=true)
Note over C,S: Stream closes on terminal state
Push notifications
For long-running work where holding a connection open is impractical, A2A supports
push-notifications: the client registers a webhook via
tasks/pushNotificationConfig/set, and the server POSTs asynchronous updates to that URL as the
task progresses. The PushNotificationConfig carries the target url, an optional token that
the server includes so the client can validate the callback belongs to the right task/session,
and an authentication object describing how the server should authenticate to the webhook.
This lets a client disconnect entirely and be notified when state changes — the asynchronous
counterpart to SSE.
How A2A complements MCP
A2A and the Model Context Protocol are not competitors; they operate at different layers and are explicitly designed to compose:
- MCP is agent-to-tool. It connects an agent to tools and resources — "primitives with well-defined, structured inputs and outputs" that perform "specific, often stateless, functions." MCP focuses on agents using capabilities.
- A2A is agent-to-agent. It lets "independent, often opaque" agents collaborate as peers — discovering each other, negotiating interactions, and managing shared, stateful tasks. A2A focuses on agents partnering on tasks.
The canonical framing in the spec: an agentic application "might primarily use A2A to
communicate with other agents," while "each individual agent internally uses MCP to interact
with its specific tools and resources." In practice an orchestrator delegates a task to a
specialist agent over A2A, and that specialist reaches for a database or API over MCP — the two
protocols stack rather than overlap. For a side-by-side of the protocol landscape, see
protocols-comparison.html.