The Agent2Agent (A2A) protocol is an open standard that lets one AI agent discover another, hand it a task, and get results back, even when the two were built by different teams on different frameworks. The Model Context Protocol (MCP) solves a different problem: it connects one agent to its own tools and data. A2A is agent to agent; MCP is agent to tool.
This post goes below the overview. It walks through what A2A version 1.0 actually puts on the wire: the Agent Card a client fetches first, the JSON-RPC, gRPC and HTTP+JSON bindings, the task state machine, streaming over Server-Sent Events, and push notification webhooks. It then lines A2A up against the current MCP revision (2026-07-28) message by message, explains the weakness classes the A2A specification itself warns about, and gives defender-side checks you can run against agents you operate. If you want the shorter security overview first, read Agent2Agent (A2A) protocol explained for security teams.
Key takeaways
- A2A connects agents to other agents as peers that stay opaque to each other. MCP connects an agent to tools, resources and prompts. The A2A specification describes the two as complementary: a remote A2A agent may use MCP internally to do the work it was asked to do.
- The latest released A2A specification is version 1.0.0, released on 12 March 2026, with a 1.0.1 patch release in May 2026. The normative source is the Protocol Buffers file
a2a.proto, not the prose. - Discovery starts with an Agent Card, usually at
/.well-known/agent-card.json(registered with IANA on 1 August 2025). It declares interfaces, capabilities, skills and auth schemes, and can be signed with JWS. - Every A2A interaction is a Message that may create a Task. Tasks move through nine defined states, four of them terminal (completed, failed, canceled, rejected) and two of them interrupted (input required, auth required). Outputs come back as Artifacts made of Parts.
- Three standard bindings carry the same operations: JSON-RPC 2.0 over HTTPS, gRPC over HTTP/2 with TLS, and HTTP+JSON (REST style). Streaming uses Server-Sent Events or gRPC server streaming; long-running work can use push notifications to a client webhook.
- A2A payloads carry no identity. Credentials travel in HTTP headers or gRPC metadata, using a scheme the card declares.
- Design against Agent Card spoofing, push notification abuse including SSRF, over-broad delegation and credential relay, cross-tenant task access, and untrusted content passed between agents.
What A2A is for, and how agent to agent differs from agent to tool
The A2A specification states its aim plainly: let independent, "potentially opaque" agent systems discover each other's capabilities, negotiate how they will exchange content (text, files, structured data), manage collaborative tasks, and exchange information "without needing access to each other's internal state, memory, or tools."
That clause is the core difference from MCP. An MCP server exposes tools with schemas; the model calls one and gets a structured result. The A2A project's comparison page calls MCP "vertical" because it deepens one agent, and A2A "horizontal" because it connects agents across team or company boundaries.
In A2A the remote side is another agent. It may plan, ask questions, wait for a human and run for hours, so A2A needs a durable task identifier, interrupted states, streamed partial results and callbacks. Appendix B of the specification gives the canonical example: an A2A client agent asks an A2A server agent to do a complex task, and the server agent in turn uses MCP to reach the tools and data sources it needs.
Where A2A sits in the stack
A2A is an application layer protocol. It does not define its own framing on TCP and has no assigned port. Its three standard bindings ride on existing protocols:
| Layer | What carries A2A |
|---|---|
| Application semantics | A2A operations and data model (Task, Message, Part, Artifact, AgentCard) |
| Encoding | JSON-RPC 2.0, Protocol Buffers 3 (gRPC), or JSON over REST-style URLs |
| Session and streaming | HTTP request and response, Server-Sent Events, gRPC server streaming |
| Security | TLS (HTTPS for HTTP bindings, TLS for gRPC); production use MUST be encrypted |
| Transport | TCP, usually on whatever port your HTTPS or gRPC service listens on |
The defining specification
The specification lives at a2a-protocol.org and in the a2aproject/A2A repository on GitHub, which is published under the Apache 2.0 licence. Three facts about it matter for anyone reading it closely:
- The proto file is normative. Section 1.4 calls
spec/a2a.proto"the single authoritative normative definition" of every protocol object. JSON schemas and SDK types are derived from it. - The spec is layered. A canonical data model, a set of abstract operations, and concrete bindings. Section 5.1 requires every binding an agent offers to behave identically, including errors and authentication.
- IANA registrations. Section 14 contains registration templates for the
application/a2a+jsonmedia type, theA2A-VersionandA2A-ExtensionsHTTP header fields, and theagent-card.jsonwell-known URI. When we checked IANA on 23 October 2026, the well-known URI was registered (permanent status, Linux Foundation as change controller, dated 2025-08-01). The media type did not appear in IANA's application media types registry.
The Agent Card: how agents find each other
Section 8.1 says every A2A server MUST make an Agent Card available. The card is a JSON document that describes the agent's identity, the interfaces it offers, its optional capabilities, its skills and how a client must authenticate.
Where the Agent Card is published
Section 8.2 lists three discovery mechanisms:
- Well-known URI. The client requests
https://plus the server's domain plus/.well-known/agent-card.json. Before version 0.3.0 the path wasagent.json; the 0.3.0 release notes record the rename as a breaking change. - Registries and catalogues. A central service holds cards and lets clients search by skill or tag. The A2A discovery guide notes that the specification "does not prescribe a standard API for curated registries."
- Direct configuration. The client is given the card URL or the card content in configuration.
What an Agent Card declares
These are the main fields of the AgentCard object in version 1.0, taken from section 4.4.1:
| Field | Required | What it declares |
|---|---|---|
name, description | Yes | Human readable identity and purpose |
supportedInterfaces | Yes | Ordered list of URL, protocolBinding (JSONRPC, GRPC or HTTP+JSON) and protocolVersion; the first entry is preferred |
version | Yes | The agent's own version, not the protocol version |
capabilities | Yes | Booleans for streaming, pushNotifications and extendedAgentCard, plus declared extensions |
securitySchemes | No | Named schemes: API key, HTTP auth, OAuth 2.0, OpenID Connect or mutual TLS |
securityRequirements | No | Which schemes (and scopes) a client must satisfy |
defaultInputModes, defaultOutputModes | Yes | Media types the agent accepts and produces |
skills | Yes | Each skill has an id, name, description, tags, optional examples, optional input and output modes, and optional per-skill security requirements |
provider, documentationUrl, iconUrl | No | Who runs the agent and where to read more |
signatures | No | One or more JWS signatures over the card |
A trimmed card in the shape the specification's sample uses looks like this:
{
"name": "Invoice Reconciliation Agent",
"description": "Matches supplier invoices against purchase orders.",
"supportedInterfaces": [
{"url": "https://agents.example.com/a2a/v1", "protocolBinding": "JSONRPC", "protocolVersion": "1.0"},
{"url": "https://agents.example.com/a2a/json", "protocolBinding": "HTTP+JSON", "protocolVersion": "1.0"}
],
"version": "2.3.0",
"capabilities": {"streaming": true, "pushNotifications": true, "extendedAgentCard": true},
"securitySchemes": {
"corp-oidc": {"openIdConnectSecurityScheme": {"openIdConnectUrl": "https://login.example.com/.well-known/openid-configuration"}}
},
"securityRequirements": [{"schemes": {"corp-oidc": {"list": ["openid", "invoices.read"]}}}],
"defaultInputModes": ["application/json", "text/plain"],
"defaultOutputModes": ["application/json"],
"skills": [
{"id": "reconcile", "name": "Reconcile invoices", "description": "Matches invoices to POs and flags mismatches.", "tags": ["finance", "invoices"]}
]
}
There are no credentials in the card; the discovery guide recommends out-of-band dynamic credentials instead.
The extended Agent Card
If capabilities.extendedAgentCard is true, an authenticated client can fetch a fuller card with extra skills or tenant settings. Section 13.3 says this operation MUST require authentication, and even extended cards SHOULD NOT contain internal service URLs or unmasked credentials.
Signing the Agent Card
Section 8.4 lets a provider sign the card with a JSON Web Signature (RFC 7515). Before signing, the card is canonicalised with the JSON Canonicalization Scheme (RFC 8785) so that two JSON libraries produce the same bytes. The AgentCardSignature object carries a base64url protected header, a base64url signature, and an optional unprotected header.
To verify, a client fetches the key named by kid and jku (or uses a trusted key store), strips default-valued properties and the signatures field, canonicalises the rest and checks the signature. Clients SHOULD verify at least one signature, fetch keys over HTTPS, and MUST NOT use expired or revoked keys.
Signing is optional (MAY). That single word matters for the threat model later in this post.
Caching
Section 8.6 asks servers to send Cache-Control with a max-age and an ETag, and clients to honour RFC 9111 caching.
How A2A works on the wire
Here is a complete exchange using the JSON-RPC binding with streaming. The arrows show HTTP requests and the events that come back.
Client agent Remote agent (A2A server)
| |
| GET /.well-known/agent-card.json |
|------------------------------------------------------->|
| 200 OK, AgentCard (interfaces, skills, securitySchemes)
|<-------------------------------------------------------|
| |
| (out of band: obtain OAuth token / OIDC token / mTLS cert)
| |
| POST /a2a/v1 method=SendStreamingMessage |
| Authorization: Bearer ... A2A-Version: 1.0 |
|------------------------------------------------------->|
| 200 OK Content-Type: text/event-stream |
| data: Task (TASK_STATE_SUBMITTED) |
|<-------------------------------------------------------|
| data: statusUpdate (TASK_STATE_WORKING) |
|<-------------------------------------------------------|
| data: statusUpdate (TASK_STATE_INPUT_REQUIRED) |
|<-------------------------------------------------------| stream closes
| |
| POST /a2a/v1 method=SendStreamingMessage |
| message.taskId = same task, extra input |
|------------------------------------------------------->|
| data: statusUpdate (TASK_STATE_WORKING) |
| data: artifactUpdate (append, lastChunk=false) |
| data: artifactUpdate (append, lastChunk=true) |
| data: statusUpdate (TASK_STATE_COMPLETED) |
|<-------------------------------------------------------| stream closes
The eleven operations
Section 3.1 defines eleven abstract operations. Section 5.3 maps each one onto the three bindings:
Operation JSON-RPC / gRPC method HTTP+JSON endpoint
Send message SendMessage POST /message:send
Send streaming message SendStreamingMessage POST /message:stream
Get task GetTask GET /tasks/{id}
List tasks ListTasks GET /tasks
Cancel task CancelTask POST /tasks/{id}:cancel
Subscribe to task SubscribeToTask POST /tasks/{id}:subscribe
Create push notification config CreateTaskPushNotificationConfig POST /tasks/{id}/pushNotificationConfigs
Get push notification config GetTaskPushNotificationConfig GET /tasks/{id}/pushNotificationConfigs/{configId}
List push notification configs ListTaskPushNotificationConfigs GET /tasks/{id}/pushNotificationConfigs
Delete push notification config DeleteTaskPushNotificationConfig DELETE /tasks/{id}/pushNotificationConfigs/{configId}
Get extended Agent Card GetExtendedAgentCard GET /extendedAgentCard
The JSON-RPC and gRPC method names are identical, in PascalCase. Version 0.3 used slash-style JSON-RPC names such as message/send and tasks/get; version 1.0 renamed all of them.
A JSON-RPC request and its response
JSON-RPC 2.0, defined at jsonrpc.org, gives every request a jsonrpc version string, an id, a method and params. A request without an id is a notification and gets no reply. A2A sends JSON-RPC over HTTP POST with Content-Type: application/json.
POST /a2a/v1 HTTP/1.1
Host: agents.example.com
Content-Type: application/json
Authorization: Bearer eyJ...
A2A-Version: 1.0
{
"jsonrpc": "2.0",
"id": 1,
"method": "SendMessage",
"params": {
"message": {
"messageId": "6f1c2a1e-4b5d-4c2e-9d0e-1a2b3c4d5e6f",
"role": "ROLE_USER",
"parts": [{"text": "Reconcile the attached October invoices."}]
},
"configuration": {
"acceptedOutputModes": ["application/json"],
"returnImmediately": true
}
}
}
The response carries either a Task or a Message in result:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"task": {
"id": "b2f7c0d4-0c4a-4f4e-8a1e-2d9a6e5f7b10",
"contextId": "c9a8e7d6-5b4a-4c3b-2a19-08f7e6d5c4b3",
"status": {"state": "TASK_STATE_WORKING", "timestamp": "2026-10-23T09:14:02.118Z"}
}
}
}
Send Message blocks by default until the task is terminal or interrupted. With returnImmediately set, the server returns once the task exists and the client polls, subscribes or waits for a webhook.
Tasks, messages, parts and artifacts
Four objects carry almost all of the content:
| Object | Purpose | Key fields |
|---|---|---|
| Message | One turn of communication, from client (ROLE_USER) or agent (ROLE_AGENT) | messageId, role, parts, optional contextId, taskId, referenceTaskIds, extensions |
| Part | The smallest unit of content | Exactly one of text, raw (bytes, base64 in JSON), url (a file reference) or data (any JSON value), plus optional mediaType and filename |
| Task | The stateful unit of work, with a server-generated id | id, contextId, status, artifacts, history |
| Artifact | A task output, such as a document or structured result | artifactId (unique within the task), name, parts (at least one) |
Section 3.7 draws a firm line: messages are for communication, artifacts are for results. Messages "SHOULD NOT be used to deliver task outputs," and clients MUST NOT treat messages as a reliable delivery channel, because a client that disconnects and reconnects may miss status messages. Anything important belongs in an artifact or in persisted task history.
Task states
Section 4.1.3 defines the full set:
| State | Kind | Meaning |
|---|---|---|
TASK_STATE_SUBMITTED | Active | Received and acknowledged |
TASK_STATE_WORKING | Active | Being processed |
TASK_STATE_INPUT_REQUIRED | Interrupted | The agent needs more input from the client |
TASK_STATE_AUTH_REQUIRED | Interrupted | The agent needs an authorization step to continue |
TASK_STATE_COMPLETED | Terminal | Finished successfully |
TASK_STATE_FAILED | Terminal | Finished with an error |
TASK_STATE_CANCELED | Terminal | Canceled before completion |
TASK_STATE_REJECTED | Terminal | The agent decided not to do it |
TASK_STATE_UNSPECIFIED | Unknown | Indeterminate state |
As a state machine:
+-------------+
message --> | SUBMITTED |------------------------+
+-------------+ |
| v
v +-----------+
+-------------+ needs input | REJECTED | (terminal)
| WORKING |----------------> +-----------+
+-------------+ --+
| | | ^ | INPUT_REQUIRED or AUTH_REQUIRED
| | | +-----+ (client sends more input or the
| | | credential arrives out of band)
| | v
| | +-----------+
| | | CANCELED | (terminal, after CancelTask)
| | +-----------+
| v
| +-----------+
| | FAILED | (terminal)
| +-----------+
v
+-----------+
| COMPLETED | (terminal)
+-----------+
Two rules follow from terminal states. A message sent to a terminal task returns UnsupportedOperationError, and so does an attempt to subscribe to one. A finished task is never reopened; follow-up work starts a new task in the same context.
Context, task ids and multi-turn work
A contextId groups related tasks into one conversation. A taskId is always server-generated; clients cannot choose ids for new tasks. A message whose taskId and contextId do not match MUST be rejected. To answer an input-required task, the client replies with the same ids.
Errors
Each A2A-specific error has a fixed code per binding (section 5.4):
| A2A error | JSON-RPC | gRPC | HTTP |
|---|---|---|---|
| TaskNotFoundError | -32001 | NOT_FOUND | 404 |
| TaskNotCancelableError | -32002 | FAILED_PRECONDITION | 400 |
| PushNotificationNotSupportedError | -32003 | FAILED_PRECONDITION | 400 |
| UnsupportedOperationError | -32004 | FAILED_PRECONDITION | 400 |
| ContentTypeNotSupportedError | -32005 | INVALID_ARGUMENT | 400 |
| InvalidAgentResponseError | -32006 | INTERNAL | 500 |
| ExtendedAgentCardNotConfiguredError | -32007 | FAILED_PRECONDITION | 400 |
| ExtensionSupportRequiredError | -32008 | FAILED_PRECONDITION | 400 |
| VersionNotSupportedError | -32009 | FAILED_PRECONDITION | 400 |
The codes sit in JSON-RPC 2.0's implementation-defined server error range (-32000 to -32099). Servers SHOULD NOT distinguish "does not exist" from "not authorised," so errors do not reveal which tasks exist.
Bindings: JSON-RPC, gRPC and HTTP+JSON
| Binding | Carrier | Content type | Streaming | Service parameters |
|---|---|---|---|---|
JSON-RPC (JSONRPC) | JSON-RPC 2.0 over HTTP(S) POST | application/json | Server-Sent Events (text/event-stream) | HTTP headers |
gRPC (GRPC) | gRPC over HTTP/2 with TLS, Protocol Buffers 3, service A2AService | Protobuf | gRPC server streaming | gRPC metadata |
HTTP+JSON (HTTP+JSON) | REST-style URLs and verbs | application/a2a+json SHOULD be used | Server-Sent Events | HTTP headers |
A client picks the first interface in supportedInterfaces that it supports. Each interface carries its own protocolVersion, so one agent can serve 0.3 and 1.0 clients side by side. Custom bindings are allowed if they meet the same equivalence rules.
Streaming
Send Streaming Message and Subscribe to Task both need capabilities.streaming to be true. A stream follows one of two patterns. If the agent answers with a Message, the stream carries exactly one Message and closes. If the agent creates a Task, the stream starts with the Task object, then carries any number of status and artifact update events, and closes when the task reaches a terminal or interrupted state. Each event is a StreamResponse holding exactly one of task, message, statusUpdate or artifactUpdate.
With the JSON-RPC binding, each SSE data: line holds a full JSON-RPC response object with the same id as the request:
HTTP/1.1 200 OK
Content-Type: text/event-stream
data: {"jsonrpc":"2.0","id":7,"result":{"task":{"id":"b2f7...","contextId":"c9a8...","status":{"state":"TASK_STATE_SUBMITTED"}}}}
data: {"jsonrpc":"2.0","id":7,"result":{"statusUpdate":{"taskId":"b2f7...","contextId":"c9a8...","status":{"state":"TASK_STATE_WORKING"}}}}
data: {"jsonrpc":"2.0","id":7,"result":{"artifactUpdate":{"taskId":"b2f7...","contextId":"c9a8...","artifact":{"artifactId":"report","parts":[{"text":"..."}]},"append":true,"lastChunk":true}}}
data: {"jsonrpc":"2.0","id":7,"result":{"statusUpdate":{"taskId":"b2f7...","contextId":"c9a8...","status":{"state":"TASK_STATE_COMPLETED"}}}}
Artifacts can arrive in chunks marked by append and lastChunk. Events MUST arrive in order, and every concurrent stream for a task gets the same events. Subscribe to Task starts with a Task snapshot so nothing is missed after a Get Task call. Version 1.0 removed the final flag; the stream closing is the signal.
Push notifications
For work that runs longer than anyone wants to hold a connection open, the client can register a webhook. It does so either inside the Send Message configuration or with Create Push Notification Config. The config holds a url, an optional token, and an optional authentication object naming an HTTP scheme (Bearer, Basic and so on) and its credentials.
On each change the agent POSTs a StreamResponse to that URL with Content-Type: application/a2a+json and an Authorization header from the config, whatever binding the agent otherwise uses.
Section 4.3.3 keeps delivery guarantees modest: at least one attempt, optional retries with backoff, a recommended 10 to 30 second timeout, and permission to give up. Clients must return 2xx, check the task id and tolerate duplicates. Treat a webhook as a hint to call Get Task, not as the record of truth.
A2A vs MCP on the wire
Both protocols use JSON-RPC 2.0 and both can stream over HTTP, so they look alike at a glance. Underneath they make opposite choices. The MCP column below describes the current MCP revision, 2026-07-28, which modelcontextprotocol.io lists as current on 23 October 2026.
| Aspect | A2A 1.0 | MCP 2026-07-28 |
|---|---|---|
| Who talks | Client agent and remote agent, as peers that stay opaque | Host application, its client connectors, and servers offering tools, resources and prompts |
| Unit of work | Task with server id, lifecycle and history | A request such as tools/call; long-running tasks moved to an optional extension, io.modelcontextprotocol/tasks |
| Discovery | Agent Card at /.well-known/agent-card.json, registries or configuration | Configured by the host; servers MUST implement server/discover to report versions, capabilities and identity |
| Encoding | JSON-RPC 2.0, gRPC with Protobuf, or HTTP+JSON | JSON-RPC 2.0 only |
| Transports | HTTPS (JSON-RPC, HTTP+JSON), gRPC over HTTP/2 with TLS | stdio (newline-delimited JSON-RPC to a subprocess) and Streamable HTTP (each message a POST to one endpoint) |
| State | Stateful tasks and contexts by design | Stateless: the 2026-07-28 revision removed the initialize handshake and protocol-level sessions |
| Versioning | A2A-Version header, Major.Minor; per-interface version in the card | Date-based revision; every request carries its version in _meta; Streamable HTTP mirrors it in MCP-Protocol-Version |
| Server to client | Streams, plus webhooks that the agent POSTs to the client | Servers do not send JSON-RPC requests; Multi Round-Trip Requests return input_required results and the client retries with answers; subscriptions/listen for change notifications |
| Authentication | Schemes declared in the card: API key, HTTP auth, OAuth 2.0 (authorization code with PKCE flag, client credentials, device code), OIDC, mTLS | Optional; for HTTP transports, an OAuth 2.1 based profile using Protected Resource Metadata (RFC 9728) and Resource Indicators (RFC 8707). stdio servers take credentials from the environment instead |
| Human in the loop | INPUT_REQUIRED and AUTH_REQUIRED task states | Elicitation, carried through the Multi Round-Trip Request pattern |
A few of these differences are worth spelling out.
Discovery is public in A2A and private in MCP. An A2A agent advertises itself on the web; an MCP server is added to a host's configuration. That gives A2A card integrity real weight.
A2A is stateful; MCP has moved the other way. MCP 2026-07-28 removed sessions, and servers needing state mint handles passed as tool arguments. Either way, possession of an identifier is not proof of identity; the MCP security page calls that failure "state handle hijacking."
Callbacks differ. In current MCP, all traffic is client initiated, even when the server needs more input. A2A lets the agent call the client's webhook, which helps long jobs and opens a new outbound path from the agent's network.
For more on the MCP side, see how to choose an MCP transport, what the MCP 2026-07-28 spec changes for security, and the MCP permissions model.
Versions and history
The specification page lists 1.0.0 as the latest released version, with 0.3.0, 0.2.6 and 0.1.0 as previous versions. The GitHub release history adds the dates:
| Version | Release date (GitHub) | What changed |
|---|---|---|
| 0.1.0 and 0.2.x | 2025 (0.2.1 dated 27 May 2025; 0.2.6 dated 17 July 2025) | Early public drafts with the core task, message and Agent Card model |
| 0.3.0 | 30 July 2025 | Breaking: well-known path renamed from agent.json to agent-card.json; mutual TLS added to security schemes, OAuth metadata URL field, per-skill security; a method to fetch the extended card; signatures added to the Agent Card; a Transport enum |
| 1.0.0 | 12 March 2026 | Breaking: a2a.proto made the normative source; JSON-RPC methods renamed to PascalCase; enum values changed to SCREAMING_SNAKE_CASE (TASK_STATE_WORKING, ROLE_USER); kind discriminators removed and Part unified; final removed from status events; supportedInterfaces replaced preferredTransport and additionalInterfaces, with protocol version per interface; extendedAgentCard moved into capabilities; /v1 prefix dropped from HTTP+JSON paths; simple ids instead of compound resource names; new ListTasks with cursor pagination; tenant field; OAuth implicit and password flows removed, device code flow and a PKCE requirement flag added; RFC 8785 canonicalisation for card signatures |
| 1.0.1 | May 2026 | Patch: prefer application/a2a+json in the HTTP binding, error transcoding fixes, corrected TaskStatus values in the text |
Only Major.Minor is negotiated, so 1.0.1 still speaks A2A-Version: 1.0.
Two compatibility rules shape what you will meet in practice:
- An empty version means 0.3. Section 3.6.2 says agents MUST interpret an empty
A2A-Versionvalue as 0.3, so that older clients keep working. Clients MUST send the header on every request otherwise. - Avoid silent fallback. Section 3.6.3 says clients that need the newest features "should be configured to request specific versions and avoid automatic fallback to older versions." A client that quietly drops to 0.3 also drops 1.0 behaviour such as the PKCE flag in the OAuth flow declarations.
Security properties and weakness classes
This section stays at the level of concepts: what the protocol guarantees, what it leaves to you, and where designs go wrong. Most of it comes straight from section 13 (Security Considerations) and section 7 (Authentication and Authorization) of the specification.
What A2A guarantees, and what it does not
| Property | What the spec provides | What it leaves to the implementer |
|---|---|---|
| Confidentiality in transit | Production MUST use HTTPS or TLS; TLS 1.3 or later recommended; SSLv3, TLS 1.0 and 1.1 SHOULD be disabled | Endpoint and certificate management |
| Server authentication | Clients SHOULD validate the server's certificate against trusted CAs | Pinning, mTLS, private PKI choices |
| Client authentication | Servers MUST authenticate every request using a declared scheme | Choosing the scheme, issuing and rotating credentials |
| Authorization | Servers MUST check authorization on every operation and scope results to the caller | The entire authorization model; the spec does not prescribe one |
| Card integrity | Optional JWS signatures over a canonical form | Whether to sign, key distribution, whether clients verify |
| Replay and duplicates | Agents may use messageId to detect duplicate messages; webhook receivers should be idempotent | Actual replay protection |
| Availability | Rate limiting, size limits and monitoring recommended | All of it |
Section 7 explains the split: A2A "treats agents as standard enterprise applications," so teams reuse the OAuth servers, gateways and logging they already run. It also means a weak deployment gets no help from the protocol itself.
Agent Card spoofing and tampering
What goes wrong. A client trusts a card that is forged or was altered after publication. Because the card names the endpoint, the identity provider and the scopes, a bad card can send real requests, and possibly real tokens, to the wrong place.
Why the design allows it. Signing is optional and only helps if clients verify against a key they trust. Registries are not standardised, and caching lets a bad card outlive its removal.
What the spec asks. Serve cards over HTTPS, sign them with JWS over the RFC 8785 canonical form, have clients verify at least one signature, fetch keys over HTTPS, and never accept expired or revoked keys. For anything sensitive, keep it in the authenticated extended card rather than the public one.
Push notification endpoint abuse
Push notifications create two separate risks, one on each side of the webhook.
On the agent side: server-side request forgery. The client chooses the URL the agent will POST to. Unchecked, that lets a client make the agent reach addresses only the agent can reach, such as internal services. Section 13.2 says agents SHOULD validate webhook URLs to prevent SSRF, reject private ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), reject localhost and link-local addresses, and use allow lists where appropriate. The same concern applies to Parts that carry a url: the media type registration says file references "MUST be validated to prevent server-side request forgery."
On the client side: spoofed and flooded webhooks. Anyone who finds the receiver can POST something shaped like a task update. The spec says clients MUST validate webhook authenticity using the configured credentials, SHOULD check the task id is one they created, SHOULD rate limit, and SHOULD use unique single-purpose tokens per configuration. Treat the payload as a prompt to fetch the task with Get Task over an authenticated channel, not as data to act on directly.
Over-broad delegation and credential relay through chains of agents
What goes wrong. An agent holds far wider permissions than its task needs and passes them along. When A delegates to B and B to C, a credential that travels with the request can be read and replayed by every agent in the chain.
Why the design allows it. An agent can ask its client for authorization mid-task with TASK_STATE_AUTH_REQUIRED, and a client that is itself an agent can pass that request further up the chain. Section 7.6.3 notes that in-band credential exchange "can allow credentials to be passed across chains of multiple A2A agents, exposing those credentials to each agent participating in the chain."
What the spec asks. Receive in-task credentials out of band over a secure channel. If credentials must travel in band, bind them to the agent that asked, so no other agent can use them, and encrypt sensitive ones so only that agent can read them. Section 7.6.4 adds an important rule: entering AUTH_REQUIRED is not itself authorization for anything, and a credential obtained for one step MUST NOT be assumed to cover later messages on the task unless the implementation explicitly defines that. For practical patterns on scoping what an agent may do, see agent authorization: how to limit what an AI agent can do.
Prompt injection across agents
The A2A specification does not use the phrase "prompt injection." It does say, in the media type registration, that implementations "MUST sanitize user-provided content to prevent injection attacks," and that content must be validated against the schema before processing. The underlying problem is easy to state: a remote agent's messages and artifacts are untrusted input, and if the receiving agent feeds them straight into a model's context with tools attached, text crafted by one agent can steer another. Skill descriptions in a card are also text a client model may read when choosing an agent.
MCP's specification is more direct: tool descriptions should be "considered untrusted, unless obtained from a trusted server," and hosts must get user consent before invoking tools. Apply the same discipline to A2A: label remote agent content as untrusted, never let it widen the receiving agent's permissions, and require human approval for high-impact actions that follow from it. Our A2A security review and MCP threat model cover these patterns in more depth.
Cross-tenant task access and enumeration
What goes wrong. One caller reads, lists, cancels or subscribes to another caller's tasks, or learns that a task exists.
Why the design allows it. Task ids are plain strings, ListTasks spans contexts, and one endpoint can serve many tenants. Version 1.0's simple ids "map directly to database keys," which makes a skipped authorization check more damaging.
What the spec asks. Section 13.1 is strict: servers MUST authorise every operation, MUST scope results to the caller even when no filter is given, and MUST run authorization checks before any query that could reveal whether a resource exists. The tenant value is a routing hint, not a security boundary, and authorization must come from the authenticated identity.
Secure configuration and how to check it
Start with a short configuration baseline, drawn from sections 7, 8 and 13 of the specification:
- HTTPS or gRPC with TLS everywhere; prefer TLS 1.3, disable SSLv3, TLS 1.0 and 1.1, and send HSTS.
- Keep internal URLs and sensitive skills out of the public card; use the authenticated extended card.
- Sign the card, publish keys over HTTPS, and make clients verify.
- Declare the narrowest scheme that fits; require PKCE for authorization code flows; prefer client credentials or mTLS between services.
- Authorise every operation, including ListTasks, Subscribe and push config calls.
- Allow-list webhook URLs on the agent; authenticate and rate limit webhook receivers on the client.
- Set size limits, reject unexpected media types, and log auth failures and denials without logging credentials.
- Pin the protocol version clients send and disable fallback to 0.3.
The commands below come from the official curl manual, the OpenSSL s_client manual and the jq manual. Only run them against systems you own or are authorised to test. Replace the host names with your own agent's.
Fetch your own Agent Card and look at the caching headers. -i includes response headers, -sS hides the progress meter but still shows errors.
curl -sS -i https://agents.example.com/.well-known/agent-card.json
Look for Content-Type, Cache-Control with a sensible max-age, and an ETag.
List the interfaces, capabilities and declared security schemes.
curl -sS https://agents.example.com/.well-known/agent-card.json \
| jq '.supportedInterfaces[] | {url, protocolBinding, protocolVersion}'
curl -sS https://agents.example.com/.well-known/agent-card.json \
| jq '{capabilities, schemes: (.securitySchemes | keys), requirements: .securityRequirements}'
Look for forgotten 0.3 interfaces and schemes your identity team does not expect.
Confirm every HTTP interface uses HTTPS. jq -e sets the exit status from the result, so this fails a CI step when any interface URL is not HTTPS. gRPC addresses use the hostname and port form, so filter by binding first.
curl -sS https://agents.example.com/.well-known/agent-card.json \
| jq -e '[.supportedInterfaces[] | select(.protocolBinding != "GRPC") | .url | startswith("https://")] | all'
Check whether the card is signed and what it exposes in skills.
curl -sS https://agents.example.com/.well-known/agent-card.json \
| jq '{signed: has("signatures"), skills: [.skills[] | {id, name, tags}]}'
If a skill description names an internal system, move it to the extended card.
Confirm the extended card refuses unauthenticated callers. On the HTTP+JSON binding the path is /extendedAgentCard under the interface URL. Expect 401 or 403 without credentials.
curl -sS -o /dev/null -w '%{http_code}\n' \
-H 'A2A-Version: 1.0' \
https://agents.example.com/a2a/json/extendedAgentCard
Check the TLS version and certificate chain. The first command should complete a TLS 1.3 handshake and verify the chain; the second should fail if TLS 1.1 is disabled.
openssl s_client -connect agents.example.com:443 -servername agents.example.com \
-tls1_3 -brief -verify_return_error < /dev/null
openssl s_client -connect agents.example.com:443 -servername agents.example.com \
-tls1_1 -brief < /dev/null
On the MCP side, see which servers your agent host has configured. If the same agent also acts as an MCP client through Claude Code, Anthropic's documentation gives these commands. claude mcp list shows each server with a health status such as connected, needs authentication or pending approval; claude mcp get shows one server's details.
claude mcp list
claude mcp get my-server
Which A2A and MCP libraries are inside your software
Most teams build on an SDK, a JSON-RPC or gRPC stack and an OAuth library, each with its own dependencies. A flaw in how one of them parses JSON, validates a JWT or handles an SSE stream becomes a flaw in your agent.
That is where Safeguard's generally available software composition analysis helps: it builds a full transitive dependency inventory for each repository and container image, so you can see every agent SDK, gRPC, HTTP and JSON library in use and at which version. SBOM generation in SPDX and CycloneDX (generally available) gives you a record you can hand to the teams that run your agents, and vulnerability management with EPSS and CISA KEV enrichment plus reachability analysis (all generally available) helps you rank which library findings sit on code paths your agent actually calls. Safeguard's generally available DAST can test verified HTTP and API targets you own, which includes the HTTPS endpoints an A2A agent exposes. None of them inspect A2A traffic in flight.
When to use A2A, MCP or a direct API call
| Use case | A2A | MCP | Direct API call |
|---|---|---|---|
| Delegate an open-ended job to an agent another team or company runs | Best fit: tasks, states, streaming, webhooks | Poor fit: tools are not peers | Possible, but you rebuild task tracking yourself |
| Give your agent access to a database, file store or SaaS API | Overkill | Best fit: tools, resources, prompts with schemas | Fine for one fixed integration with no model in the loop |
| Long-running work with human approval mid-way | Built in (INPUT_REQUIRED, AUTH_REQUIRED, push notifications) | Possible with the tasks extension and elicitation | Custom design |
| Discovering capabilities at runtime | Agent Card, signed if you choose | Host configuration plus server/discover | API documentation, out of band |
| Deterministic, audited business transaction | Adds a model where you may not want one | Adds a model where you may not want one | Best fit: fixed contract, simplest to test |
| Security properties | TLS, declared auth schemes, optional card signing; authorization is yours | Optional OAuth 2.1 profile for HTTP; local stdio servers rely on the host | Whatever your API gateway enforces |
| Operational cost | Highest: card publishing, auth, webhooks, task storage | Moderate: one server per tool surface, host configuration | Lowest |
Use A2A when the other end makes decisions, MCP when it does what it is told, and a plain API when no model is needed. For MCP versus REST, see MCP vs REST API for security tools.
Common mistakes
- Treating the Agent Card as configuration you can trust. It is a document someone published on the internet. Verify its signature, or obtain it through a channel you control.
- Putting secrets or internal URLs in the public card. The specification says the card SHOULD NOT include sensitive credentials or internal implementation details. Use the extended card behind authentication.
- Assuming
tenantisolates customers. It is an opaque routing value. Isolation comes from authorization checks tied to the authenticated identity. - Trusting webhook bodies. A push notification can be spoofed or repeated. Authenticate it, check the task id, and fetch the task over an authenticated channel before acting.
- Reading
AUTH_REQUIREDas permission. The specification says the state transition alone authorises nothing, and one credential does not automatically cover later steps. - Forgetting the version header. Without
A2A-Version, an agent treats the request as 0.3. Pin the version in your client.
FAQ
What is the A2A protocol used for?
It lets one AI agent discover another, hand it a task, follow progress and get results, while each agent keeps its memory and tools private.
What is the difference between A2A and MCP?
MCP connects an agent to tools, resources and prompts, so it extends what one agent can do. A2A connects an agent to other agents so they can delegate and collaborate on tasks. The A2A specification describes them as complementary: a remote A2A agent can use MCP internally.
What port does A2A use?
None of its own. A2A runs over HTTPS for the JSON-RPC and HTTP+JSON bindings and over gRPC with TLS for the gRPC binding, so it uses whatever port those services listen on, commonly 443.
Where is the A2A Agent Card published?
Usually at /.well-known/agent-card.json on the agent's domain. IANA registered that well-known URI in August 2025. Agents can also be found through registries or direct configuration, and authenticated clients may fetch a fuller extended card.
What are the A2A task states?
Submitted, working, input required, auth required, completed, failed, canceled and rejected, plus an unspecified value. Completed, failed, canceled and rejected are terminal. Input required and auth required are interrupted states that wait for the client.
What is the latest version of A2A?
The specification site lists 1.0.0 as the latest released version, released on 12 March 2026. A 1.0.1 patch followed in May 2026. On the wire both are version 1.0, because only Major.Minor is negotiated.
How does A2A authenticate agents?
The Agent Card declares schemes (API key, HTTP auth, OAuth 2.0, OpenID Connect or mutual TLS). The client gets credentials out of band and sends them in headers or gRPC metadata, never in the payload.
Is A2A secure?
The baseline is sound: mandatory TLS, declared authentication, strict authorization scoping and optional card signing. The rest depends on your implementation.
Fix it without waiting for upstream
Protocol bugs in A2A and MCP deployments rarely live in the specification. They live in the libraries your agents depend on: the agent SDK, the JSON-RPC and gRPC stacks, the HTTP server that streams SSE, and the JWT and OAuth libraries that check every token.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
None of the demos below shows A2A traffic. They show the controls this post keeps returning to: least privilege, human approval and fast library fixes. Safeguard's agent identity feature (generally available) tracks which agent holds what access and at what level, which is the inventory question A2A delegation raises. The Safeguard MCP server at mcp.safeguard.sh, listed in the Claude connector directory, is generally available. MCP Guardrail, which gives you proxy URLs for MCP servers, is in early access, and so is the AI Gateway, Safeguard's LLM firewall. To try the CLI, download it from https://safeguard.sh/download and read the setup guide at https://docs.safeguard.sh/docs/cli .
The first demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. That choice is the same least-privilege decision you make when an agent publishes skills in an Agent Card or delegates work to another agent: grant the narrow set the task needs, and see it in one place.
Click through it at your own pace, or open it full screen.
The second demo is a real Safeguard CLI session. Asked to fix the lodash CVE in a repository, it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with approval, prints a summary and pushes nothing, then shows /permissions, /model and /scan. The same flow applies when the vulnerable package is the JSON-RPC, gRPC or JWT library underneath your A2A agent.
Click through it at your own pace, or open it full screen.
The third demo shows the desktop app sending work to a computer running the CLI, with each command approved before it runs. That is the human approval step A2A models with its input required and auth required states, applied to an assistant on your own machine.
Click through it at your own pace, or open it full screen.
Quick check
An A2A task has moved to TASK_STATE_AUTH_REQUIRED and the client supplies a token. Does that token now cover every later message on the task?
No. Section 7.6.4 of the specification says the state change alone authorises nothing, and a credential obtained in that state MUST NOT be assumed to authorise later messages unless the implementation, the credential issuer or an extension explicitly defines that.
A client registers a push notification URL that points at a private 10.x address. What should the agent do?
Reject it. Section 13.2 says agents SHOULD validate webhook URLs to prevent server-side request forgery, including rejecting private ranges, localhost and link-local addresses, and using allow lists where appropriate.
Sources
- A2A Protocol Specification (latest, version 1.0.0)
- A2A: What's new in v1.0
- A2A: A2A and MCP, detailed comparison
- A2A: Agent discovery
- A2A: Enterprise implementation of A2A
- A2A: Streaming and asynchronous operations
- A2A: Life of a task
- A2A: What is A2A?
- a2aproject/A2A on GitHub, releases
- IANA: Well-Known URIs registry
- IANA: Media types registry
- JSON-RPC 2.0 Specification
- MCP specification, revision 2026-07-28
- MCP: Key changes in 2026-07-28
- MCP: Versioning
- MCP: Transports overview
- MCP: Streamable HTTP transport
- MCP: Authorization
- MCP: Security best practices
- Claude Code docs: Connect Claude Code to tools via MCP
- curl manual page
- OpenSSL 3.5: openssl-s_client manual
- jq manual
Last reviewed: 23 October 2026. A2A and MCP both revise their specifications regularly; check the linked pages for the current version before you build on a detail here.