DEV Community

Cover image for A2A in Plain English: Read an Agent Card, Then Follow One Task
PromptMaster
PromptMaster

Posted on AI-assisted

A2A in Plain English: Read an Agent Card, Then Follow One Task

MCP connects your agent to tools. A2A connects it to other agents. Easy to repeat, harder to use when you're about to wire two systems together.

The official A2A docs give a practical test: look at what's on the other end. If it's a tool with structured inputs and outputs, like a database query or a weather lookup, that's MCP territory. If it's another agent that reasons, plans, keeps state and may need a back-and-forth to finish the job, that's what A2A is for.

Here's that test made concrete. I ran the official A2A "Hello World" sample locally, read its Agent Card, sent it a task, and then tried a few things you'll run into on day one. All the JSON below is real output from that run (IDs shortened), using the a2a-sdk Python package, version 1.3.0, against protocol version 1.0.

What A2A is, in two paragraphs

The Agent2Agent (A2A) Protocol is an open standard for communication between AI agents, including agents built with different frameworks or by different vendors. It was originally developed by Google and donated to the Linux Foundation. A Technical Steering Committee with representatives from AWS, Cisco, Google, IBM Research, Microsoft, Salesforce, SAP and ServiceNow maintains it. The latest released version of the specification is 1.0.0.

The docs are just as clear about what A2A is not. It's not a framework for building agents (that's LangGraph, CrewAI, ADK and the like). It's not how an agent calls its own sub-agents or tools. And it's not a replacement for MCP. The docs put it like this: "MCP is vertical. It deepens a single agent." A2A is horizontal: it connects your agent to agents that may belong to "another team, another department, or a partner organization."

Tool or agent? A quick decision test

Before choosing, answer four questions about the thing you want to connect to:

  1. Is the work one call, or a conversation? A weather lookup is one call. "Find me a supplier for this part and negotiate delivery" may take several exchanges.
  2. Does it keep state between requests? Tools are often stateless. Agents keep context over longer interactions.
  3. Might it need to ask you something before it can finish? A2A has a task state for exactly that.
  4. Do you control its internals? A2A is designed for "opaque" agents. You see what the agent offers, not its prompts, tools or memory.

Mostly "one call, stateless, no questions, I own it" points to MCP. Mostly the opposite points to A2A. The docs' own example uses both: an auto repair shop where a mechanic agent calls a diagnostic scanner through MCP, then asks a parts-supplier agent for a part through A2A.

That's my rule of thumb, not the spec's, and the line isn't always sharp: the docs note an A2A agent can expose well-defined, stateless skills as MCP-style tools.

Step 1: read the Agent Card

An A2A agent describes itself in a JSON document called the Agent Card. For public agents, the recommended place to publish it is a well-known URL: https://{agent-domain}/.well-known/agent-card.json.

Here's the card the sample agent served on my machine:

{
  "name": "Hello World Agent",
  "description": "Just a hello world agent",
  "supportedInterfaces": [
    {
      "url": "http://127.0.0.1:9999",
      "protocolBinding": "JSONRPC",
      "protocolVersion": "1.0"
    }
  ],
  "version": "0.0.1",
  "capabilities": { "streaming": true, "extendedAgentCard": true },
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"],
  "skills": [
    {
      "id": "echo_bot",
      "name": "Echo Bot",
      "description": "An example agent that acknowledges client request and responds with a \"Hello World\" message.",
      "tags": ["a2a", "echo-example"],
      "examples": ["hi", "how are you"],
      "inputModes": ["text/plain"],
      "outputModes": ["text/plain"]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Read it top to bottom like a business card:

  • supportedInterfaces tells you where to send requests and how. It's an ordered list, and the first entry is the preferred one. This agent speaks JSON-RPC over HTTP. The spec also defines gRPC and a plain REST binding.
  • capabilities says what's optional and turned on. This one supports streaming, plus an extended card for authenticated clients.
  • skills is what the agent says it's good at, with examples. Treat this as a description, not a guarantee. The spec calls skills "largely a descriptive concept."
  • What's missing: no securitySchemes, so this agent asks for no authentication. Fine for a localhost demo; for anything real, check it first.

The sample also defines an extended card with an extra skill, "Echo Bot (Super Mode)," that the public card doesn't show. The spec says the extended card must require authentication, so expect the public card to show less than the agent offers once you're signed in.

Step 2: send a message, get a Task back

A2A methods have plain names: SendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask and SubscribeToTask, plus methods for push notifications and the extended card. I sent the simplest request with curl, including the A2A-Version: 1.0 header:

curl -s -X POST http://127.0.0.1:9999/ \
  -H 'Content-Type: application/json' \
  -H 'A2A-Version: 1.0' \
  -d '{"jsonrpc":"2.0","id":"req-1","method":"SendMessage",
       "params":{"message":{"role":"ROLE_USER","messageId":"msg-1",
       "parts":[{"text":"hi"}]}}}'
Enter fullscreen mode Exit fullscreen mode

The agent didn't just answer. It created a Task and returned it already finished:

{
  "result": {
    "task": {
      "id": "0c0faba9-…",
      "contextId": "91d6e4d5-…",
      "status": {
        "state": "TASK_STATE_COMPLETED",
        "message": { "role": "ROLE_AGENT", "parts": [{ "text": "Request is completed!" }] }
      },
      "artifacts": [
        {
          "artifactId": "5b355243-…",
          "parts": [{ "text": "Hello, World! I have received your request (hi)", "mediaType": "text/plain" }]
        }
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

(Trimmed: the history array and timestamps.)

Three ideas cover most of what you'll read in A2A responses:

  • Task: a unit of work with an ID and a state.
  • Artifact: the output. The answer lives here, not in the status message.
  • contextId: groups related tasks and messages into one conversation.

An agent can also reply with a plain Message instead of a Task, for quick exchanges that need no tracking. This sample always creates a Task, which the docs describe as a "task-generating agent."

Step 3: know the states, especially the two that pause

The spec defines these task states:

  • TASK_STATE_SUBMITTED and TASK_STATE_WORKING: in progress.
  • TASK_STATE_INPUT_REQUIRED: the agent needs more from you to continue.
  • TASK_STATE_AUTH_REQUIRED: the agent needs authorization to continue.
  • TASK_STATE_COMPLETED, TASK_STATE_FAILED, TASK_STATE_CANCELED, TASK_STATE_REJECTED: terminal. The task is over.

The two "interrupted" states are the easy ones to miss. A client that only waits for "completed or failed" sees a task waiting on input or authorization as a hang. Handle both: show the status message to a person, or answer it from your own agent.

One detail worth knowing: the spec says an agent must not treat the AUTH_REQUIRED transition, by itself, as permission for anything. Answering that state isn't a blanket yes.

Step 4: the rule that saves you a debugging session

What happens if you send a follow-up to a finished task? I sent a second message with the completed task's taskId:

{
  "error": {
    "code": -32004,
    "message": "Task 0c0faba9-… is in terminal state: TASK_STATE_COMPLETED"
  }
}
Enter fullscreen mode Exit fullscreen mode

That's by design. The docs say a task in a terminal state "cannot restart." Any follow-up has to start a new task in the same context. So I sent the follow-up again, this time with the same contextId and the old task in referenceTaskIds. I got a new task ID, the same contextId, and a completed result.

GetTask on the old ID still returned TASK_STATE_COMPLETED, and CancelTask returned "Task cannot be canceled." Finished tasks stay finished, which makes them easy to trace.

Before your agent talks to a remote agent

The spec's security section reads like a checklist, so here's a short one built from it:

  • HTTPS only in production. The spec says production deployments must use encrypted transport. My http://127.0.0.1 demo is not a production setup.
  • Read securitySchemes on the card. That's how a client learns which authentication the agent expects. You get the credentials out of band, not from the card.
  • Expect scoped results. The server must check authorization on every request and scope ListTasks and GetTask to what the caller is allowed to see.
  • Treat push-notification tokens as secrets. If you register a webhook, the spec recommends unique, single-purpose tokens, HTTPS endpoints, and processing notifications idempotently, because duplicates can arrive.
  • Don't trust skills because they're listed. A card is a self-description. Decide what your agent may delegate the same way you'd vet any third-party service.

If you're also connecting MCP servers, Before You Connect an MCP Server: A 5-Question Trust Check covers that side. If you're still deciding whether you need more than one agent at all, read Single Agent, Subagents or an Agent Team? first.

Try it yourself

The Hello World agent is at samples/python/agents/helloworld in a2aproject/a2a-samples. Install a2a-sdk[http-server] and uvicorn, run the sample (it serves on port 9999), fetch /.well-known/agent-card.json and send the curl request above.

Quick checklist

  • [ ] The other side is an agent (stateful, multi-turn), not a single tool call
  • [ ] Agent Card fetched and read: interface, capabilities, skills, securitySchemes
  • [ ] Client handles INPUT_REQUIRED and AUTH_REQUIRED, not only completed/failed
  • [ ] Results read from artifacts, not from the status message
  • [ ] Follow-ups start a new task with the same contextId
  • [ ] HTTPS and authentication in place before anything leaves localhost
  • [ ] Webhook tokens unique, and notifications handled idempotently

Where to go next

This article was written with the assistance of AI and reviewed by the author. The code samples and JSON outputs come from a real local run of the official A2A sample.

Top comments (0)