Safeguard
Supply Chain Security•

A2A vs MCP: How the Agent2Agent Protocol Works on the Wire

A2A lets one AI agent hand a task to another over HTTPS or gRPC; MCP connects an agent to its tools. How A2A tasks, Agent Cards and streams work on the wire.

Safeguard Security Team
39 min read

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:

LayerWhat carries A2A
Application semanticsA2A operations and data model (Task, Message, Part, Artifact, AgentCard)
EncodingJSON-RPC 2.0, Protocol Buffers 3 (gRPC), or JSON over REST-style URLs
Session and streamingHTTP request and response, Server-Sent Events, gRPC server streaming
SecurityTLS (HTTPS for HTTP bindings, TLS for gRPC); production use MUST be encrypted
TransportTCP, 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:

  1. 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.
  2. 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.
  3. IANA registrations. Section 14 contains registration templates for the application/a2a+json media type, the A2A-Version and A2A-Extensions HTTP header fields, and the agent-card.json well-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 was agent.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:

FieldRequiredWhat it declares
name, descriptionYesHuman readable identity and purpose
supportedInterfacesYesOrdered list of URL, protocolBinding (JSONRPC, GRPC or HTTP+JSON) and protocolVersion; the first entry is preferred
versionYesThe agent's own version, not the protocol version
capabilitiesYesBooleans for streaming, pushNotifications and extendedAgentCard, plus declared extensions
securitySchemesNoNamed schemes: API key, HTTP auth, OAuth 2.0, OpenID Connect or mutual TLS
securityRequirementsNoWhich schemes (and scopes) a client must satisfy
defaultInputModes, defaultOutputModesYesMedia types the agent accepts and produces
skillsYesEach skill has an id, name, description, tags, optional examples, optional input and output modes, and optional per-skill security requirements
provider, documentationUrl, iconUrlNoWho runs the agent and where to read more
signaturesNoOne 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:

ObjectPurposeKey fields
MessageOne turn of communication, from client (ROLE_USER) or agent (ROLE_AGENT)messageId, role, parts, optional contextId, taskId, referenceTaskIds, extensions
PartThe smallest unit of contentExactly one of text, raw (bytes, base64 in JSON), url (a file reference) or data (any JSON value), plus optional mediaType and filename
TaskThe stateful unit of work, with a server-generated idid, contextId, status, artifacts, history
ArtifactA task output, such as a document or structured resultartifactId (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:

StateKindMeaning
TASK_STATE_SUBMITTEDActiveReceived and acknowledged
TASK_STATE_WORKINGActiveBeing processed
TASK_STATE_INPUT_REQUIREDInterruptedThe agent needs more input from the client
TASK_STATE_AUTH_REQUIREDInterruptedThe agent needs an authorization step to continue
TASK_STATE_COMPLETEDTerminalFinished successfully
TASK_STATE_FAILEDTerminalFinished with an error
TASK_STATE_CANCELEDTerminalCanceled before completion
TASK_STATE_REJECTEDTerminalThe agent decided not to do it
TASK_STATE_UNSPECIFIEDUnknownIndeterminate 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 errorJSON-RPCgRPCHTTP
TaskNotFoundError-32001NOT_FOUND404
TaskNotCancelableError-32002FAILED_PRECONDITION400
PushNotificationNotSupportedError-32003FAILED_PRECONDITION400
UnsupportedOperationError-32004FAILED_PRECONDITION400
ContentTypeNotSupportedError-32005INVALID_ARGUMENT400
InvalidAgentResponseError-32006INTERNAL500
ExtendedAgentCardNotConfiguredError-32007FAILED_PRECONDITION400
ExtensionSupportRequiredError-32008FAILED_PRECONDITION400
VersionNotSupportedError-32009FAILED_PRECONDITION400

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

BindingCarrierContent typeStreamingService parameters
JSON-RPC (JSONRPC)JSON-RPC 2.0 over HTTP(S) POSTapplication/jsonServer-Sent Events (text/event-stream)HTTP headers
gRPC (GRPC)gRPC over HTTP/2 with TLS, Protocol Buffers 3, service A2AServiceProtobufgRPC server streaminggRPC metadata
HTTP+JSON (HTTP+JSON)REST-style URLs and verbsapplication/a2a+json SHOULD be usedServer-Sent EventsHTTP 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.

AspectA2A 1.0MCP 2026-07-28
Who talksClient agent and remote agent, as peers that stay opaqueHost application, its client connectors, and servers offering tools, resources and prompts
Unit of workTask with server id, lifecycle and historyA request such as tools/call; long-running tasks moved to an optional extension, io.modelcontextprotocol/tasks
DiscoveryAgent Card at /.well-known/agent-card.json, registries or configurationConfigured by the host; servers MUST implement server/discover to report versions, capabilities and identity
EncodingJSON-RPC 2.0, gRPC with Protobuf, or HTTP+JSONJSON-RPC 2.0 only
TransportsHTTPS (JSON-RPC, HTTP+JSON), gRPC over HTTP/2 with TLSstdio (newline-delimited JSON-RPC to a subprocess) and Streamable HTTP (each message a POST to one endpoint)
StateStateful tasks and contexts by designStateless: the 2026-07-28 revision removed the initialize handshake and protocol-level sessions
VersioningA2A-Version header, Major.Minor; per-interface version in the cardDate-based revision; every request carries its version in _meta; Streamable HTTP mirrors it in MCP-Protocol-Version
Server to clientStreams, plus webhooks that the agent POSTs to the clientServers 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
AuthenticationSchemes declared in the card: API key, HTTP auth, OAuth 2.0 (authorization code with PKCE flag, client credentials, device code), OIDC, mTLSOptional; 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 loopINPUT_REQUIRED and AUTH_REQUIRED task statesElicitation, 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:

VersionRelease date (GitHub)What changed
0.1.0 and 0.2.x2025 (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.030 July 2025Breaking: 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.012 March 2026Breaking: 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.1May 2026Patch: 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-Version value 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

PropertyWhat the spec providesWhat it leaves to the implementer
Confidentiality in transitProduction MUST use HTTPS or TLS; TLS 1.3 or later recommended; SSLv3, TLS 1.0 and 1.1 SHOULD be disabledEndpoint and certificate management
Server authenticationClients SHOULD validate the server's certificate against trusted CAsPinning, mTLS, private PKI choices
Client authenticationServers MUST authenticate every request using a declared schemeChoosing the scheme, issuing and rotating credentials
AuthorizationServers MUST check authorization on every operation and scope results to the callerThe entire authorization model; the spec does not prescribe one
Card integrityOptional JWS signatures over a canonical formWhether to sign, key distribution, whether clients verify
Replay and duplicatesAgents may use messageId to detect duplicate messages; webhook receivers should be idempotentActual replay protection
AvailabilityRate limiting, size limits and monitoring recommendedAll 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:

  1. HTTPS or gRPC with TLS everywhere; prefer TLS 1.3, disable SSLv3, TLS 1.0 and 1.1, and send HSTS.
  2. Keep internal URLs and sensitive skills out of the public card; use the authenticated extended card.
  3. Sign the card, publish keys over HTTPS, and make clients verify.
  4. Declare the narrowest scheme that fits; require PKCE for authorization code flows; prefer client credentials or mTLS between services.
  5. Authorise every operation, including ListTasks, Subscribe and push config calls.
  6. Allow-list webhook URLs on the agent; authenticate and rate limit webhook receivers on the client.
  7. Set size limits, reject unexpected media types, and log auth failures and denials without logging credentials.
  8. 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 caseA2AMCPDirect API call
Delegate an open-ended job to an agent another team or company runsBest fit: tasks, states, streaming, webhooksPoor fit: tools are not peersPossible, but you rebuild task tracking yourself
Give your agent access to a database, file store or SaaS APIOverkillBest fit: tools, resources, prompts with schemasFine for one fixed integration with no model in the loop
Long-running work with human approval mid-wayBuilt in (INPUT_REQUIRED, AUTH_REQUIRED, push notifications)Possible with the tasks extension and elicitationCustom design
Discovering capabilities at runtimeAgent Card, signed if you chooseHost configuration plus server/discoverAPI documentation, out of band
Deterministic, audited business transactionAdds a model where you may not want oneAdds a model where you may not want oneBest fit: fixed contract, simplest to test
Security propertiesTLS, declared auth schemes, optional card signing; authorization is yoursOptional OAuth 2.1 profile for HTTP; local stdio servers rely on the hostWhatever your API gateway enforces
Operational costHighest: card publishing, auth, webhooks, task storageModerate: one server per tool surface, host configurationLowest

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 tenant isolates 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_REQUIRED as 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 situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull 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

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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.