Skip to content

A2A on a2a-sdk 1.x: build the routes through ADK's compat layer - #224

Merged
antiv merged 1 commit into
mainfrom
a2a-sdk-1x
Oct 11, 2026
Merged

antiv merged 1 commit into
mainfrom
a2a-sdk-1x

Conversation

@antiv

@antiv antiv commented Oct 11, 2026

Copy link
Copy Markdown
Owner

Fixes #223.

What was wrong

requirements.lock pins a2a-sdk==1.2.1, but adk_main.py used the 0.3 API (a2a.server.apps.A2AStarletteApplication, a pydantic AgentCard). On 1.x the import fails. --a2a, which the dashboard always passes, printed "A2A dependencies not available" and mounted no /a2a/* routes. A2A calls failed, and so did agents exposed over MCP: the bridge calls /a2a/{agent}.

It went unnoticed locally because a venv that wasn't reinstalled from the lock still had a2a-sdk 0.3.22.

The fix

  • shared/utils/a2a_routes.py builds each agent's card and routes through google.adk.a2a._compat (parse_agent_card, attach_a2a_routes_to_app), the same way ADK's own --a2a does. It works on 0.3.x and 1.x. adk_main.py keeps its agent discovery (files, database, runners) and calls it. A missing dependency still gets the "A2A dependencies not available" message.
  • Methods: on 1.x the JSON-RPC route serves the 1.x methods and, through the v0.3 compat layer, the 0.3 ones. Existing callers and the MCP bridge (message/send) keep working.
  • The default card:
    • url is now http://localhost:8000/a2a/{agent}, the agent's endpoint, instead of the server root. A client resolving the card would have posted to the wrong URL. The base address is still fixed; MATE has no public-URL setting.
    • The modes are text/plain, what the executor reads and writes.
    • The metadata block is dropped; 1.x cards have no such field.
    • agents/<name>/agent.json is still merged over the default, and 0.3-form files are converted by parse_agent_card.
  • The MCP bridge (agent_mcp_protocol_handler.py) now takes the reply text from the task's artifacts, or a failed task's status message. It used to return the whole task as a Python dict string, since ADK's executor answers with a task rather than a message.

Checks

  • Venv installed from requirements.lock (a2a-sdk 1.2.1, google-adk 2.11.0). The server started through auth_server.py, which starts ADK with --a2a. Through the auth server:

    • GET /a2a/creative_agent/.well-known/agent-card.json → 200. The card is in 1.x form, with supportedInterfaces[0].url = http://localhost:8000/a2a/creative_agent.
    • message/send (0.3) to pisac_root → task completed, reply "Zdravo".
    • SendMessage (1.x, A2A-Version: 1.0) to pisac_root → TASK_STATE_COMPLETED, with a reply.
    • MCP tools/call on chess_mate_root (/agents/chess_mate_root/mcp) → [{"type": "text", "text": "chess"}]. Before the bridge change, the same call returned the whole task as a dict string.
  • shared/test/test_a2a_routes.py, 11 tests. It mounts the routes on a FastAPI app with an echo executor and checks:

    • the routes and the card for each agent;
    • that the card names /a2a/{agent};
    • the agent.json merge;
    • message/send and SendMessage both answered;
    • that a failing agent doesn't stop the others;
    • the bridge's text extraction.

    A dependency bump that breaks this now fails CI.

  • Full suite: 1467 OK. gen_docs.py --check: OK.

Docs

gen_docs.py drift flags docs/dev/a2a.md and docs/dev/architecture.md.

  • architecture.md only says the agent server starts with --a2a, which still holds.
  • a2a.md is being rewritten in docs(dev): verify a2a guide against code and remove migrated status (#206) #217, so I left it out of this PR to avoid a conflict. What it needs to say after this:
    • Methods: both the 1.x methods (SendMessage, SendStreamingMessage, GetTask, CancelTask, GetExtendedAgentCard) and the 0.3 ones (message/send, message/stream, tasks/get, ...) are accepted.
    • Card: it is served in the 1.x form (supportedInterfaces, not url). agent.json may be in either form.
    • Card address: the default card points at http://localhost:8000/a2a/{agent}.

adk_main.py was written for a2a-sdk 0.3. On the pinned 1.2.1,
a2a.server.apps is gone, so --a2a printed "A2A dependencies not
available" and mounted no /a2a routes, which broke A2A and agents
exposed over MCP (the bridge calls /a2a/{agent}).

The routes and cards are now built in shared/utils/a2a_routes.py through
google.adk.a2a._compat, which supports 0.3 and 1.x; on 1.x the 0.3 method
names stay accepted. The default card names the agent's own endpoint,
and agent.json files in the 0.3 form are still read. The MCP bridge reads
the reply from the task's artifacts instead of returning the whole task.
A test mounts the routes and calls both method names, so a breaking
dependency bump fails CI.

Fixes #223
@antiv
antiv merged commit f65dd77 into main Oct 11, 2026
2 checks passed
@antiv
antiv deleted the a2a-sdk-1x branch October 11, 2026 09:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A2A is disabled on the pinned a2a-sdk 1.2.1: adk_main.py uses the 0.3.x API

1 participant