Two service parameters travel as HTTP headers or gRPC metadata on every request, and a missing
A2A-Versionmeans 0.3, which a 1.0 agent must refuse.
The kit sends test-runner a SendMessage with only Host and Content-Type headers, as a client that forgot the version would. The agent answers -32009 and reports the request as 0.3 (no header), in exchange 2 of 11-errors.http. Every other request in the captures carries A2A-Version: 1.0.
When you finish this section, you can set both headers in any binding and predict what each SDK does without a version.
Service parameters
Service parameter: an entry in a key-value map that carries context or parameters that apply to every operation. The keys are case-insensitive strings, and the values are case-sensitive strings {{spec §3.2.6}}. The specification defines two, A2A-Version and A2A-Extensions, and reserves the prefix a2a- for its own {{spec §3.2.6}}. Each binding says how they travel: as HTTP request headers in JSON-RPC and HTTP+JSON {{spec §9.2}} {{spec §11.2}}, and as metadata keys in gRPC {{spec §10.2}}. A custom binding must document how it carries service parameters {{spec §12.3}}.
The IANA templates for both headers cite section 3.2.5 of the specification {{spec §14.2.1}} {{spec §14.2.2}}. That is the metadata section, where the parameters are §3.2.6 (conflict D17). Issue #2305 tracks it, still open.
A2A-Version
A version is Major.Minor, such as 1.0, and requests, responses, and Agent Cards should not carry a patch number {{spec §3.6}}. A client must send the A2A-Version header with every request {{spec §3.6.1}}, and a 1.0 client sends 1.0. A server has two duties: process the request with the semantics of that version, and return VersionNotSupportedError when the interface does not support it {{spec §3.6.2}}. The empty case has a fixed meaning: an agent must read an empty value as version 0.3 {{spec §3.6.2}}. So a client that forgets the header gets 0.3 semantics from an agent that still serves 0.3, and this error from one that does not:
POST /a2a/jsonrpc HTTP/1.1
Host: localhost:41241
Content-Type: application/json
…
HTTP/1.1 200 OK
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32009,
"message": "Protocol version not supported",
…
"requested": "0.3 (no header)",
"supported": "1.0"Original source
title: no A2A-Version header, read as 0.3 and refused
source: capture/out/11-errors.http
lang: http
note: The request body and the error details are cut down. The request carries only Host and Content-Type, and the kit speaks only 1.0.
---
POST /a2a/jsonrpc HTTP/1.1
Host: localhost:41241
Content-Type: application/json
…
HTTP/1.1 200 OK
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32009,
"message": "Protocol version not supported",
…
"requested": "0.3 (no header)",
"supported": "1.0"VersionNotSupportedError is -32009 in JSON-RPC, 400 in HTTP+JSON, and FAILED_PRECONDITION in gRPC, with the reason VERSION_NOT_SUPPORTED {{spec §5.4}}, as errors lists. The version to send comes from the card: each supportedInterfaces entry declares its own protocolVersion. An agent can expose several interfaces on the same transport with different versions {{spec §3.6.2}}, as AgentCard fields shows.
Three samples in the specification send A2A-Version: 0.3 {{spec §9.2}} {{spec §11.2}} {{spec §14.2.1}}, and the gRPC sample in §10.2 sends the same value as metadata {{spec §10.2}}. Never copy a sample's value into a 1.0 client (conflict D39). §3.6.1 also allows a client to send A2A-Version as a request parameter instead of a header {{spec §3.6.1}}. Yet both JSON bindings require the header (conflict D31). The reference SDK reads only the header {{sdk src/a2a/utils/version_validator.py}}, and the kit reads both, so send the header.
What each SDK does without the header
| Implementation | Missing or empty header | Accepted values | Where it checks |
|---|---|---|---|
kit, a2a_ref.py |
refused as 0.3 (no header) |
exactly 1.0, from the header or the query parameter |
JSON-RPC and HTTP+JSON |
| a2a-python 1.2.2 | read as 0.3, then refused |
1, 1.0, 1.0.0, and 1.5, because only the major must be 1 {{sdk src/a2a/utils/version_validator.py}} |
JSON-RPC and HTTP+JSON, never gRPC {{sdk src/a2a/server/request_handlers/grpc_handler.py}} |
| a2a-js 1.3.0 | read as 0.3, refused unless the card lists it |
only the strings the card declares for that binding, so 1.0.0 is refused {{sdk-js src/server/version.ts at v1.3.0}} |
all three bindings |
| a2a-java 1.4.0.Final | read as 0.3, then refused |
the same major, in major.minor form, so 1 is refused {{sdk-java server-common/src/main/java/org/a2aproject/sdk/server/version/A2AVersionValidator.java at v1.4.0.Final}} |
all three, with gRPC UNIMPLEMENTED instead of FAILED_PRECONDITION |
| a2a-go 2.6.0 | accepted | anything, because no server code reads the header {{sdk-go a2a/svcparams.go at v2.6.0}} | nowhere |
| a2a-dotnet 1.0.0-preview2 | accepted as 1.0 | 1.0 or 0.3, with 0.3 processed as 1.0 {{sdk-dotnet src/A2A.AspNetCore/A2AJsonRpcProcessor.cs at v1.0.0-preview2}} |
JSON-RPC only |
| a2a-rs, a2a-server 0.5.1 | read as 0.3, then refused |
major 1 {{sdk-rust a2a-server/src/jsonrpc.rs at a2a-server-lf-v0.5.1}} | JSON-RPC only |
Every client in the list sends 1.0 on each call, and the Python and JS clients send it on the card fetch too. So send the literal string 1.0, never a patch number, and never rely on a server to refuse a missing header.
A2A-Extensions
A2A-Extensions is a comma-separated list of the extension URIs that the client wants for the request {{spec §3.2.6}}. It is one header field in both JSON bindings {{spec §9.2}} {{spec §11.2}} and one metadata entry in gRPC {{spec §10.2}}. §4.6.1 also allows JSON-RPC request parameters as the mechanism {{spec §4.6.1}}, while §9.2 requires the header, so send the header (conflict D30). The reference SDK's with_a2a_extensions helper joins the URIs with commas on the client {{sdk src/a2a/client/service_parameters.py}}, and its server reads them into the call context {{sdk src/a2a/server/routes/common.py}}. What the server does with them, the payload keyed by URI, and ExtensionSupportRequiredError are in extensions.
Original source
- Send `A2A-Version: 1.0` as a header or metadata on every request, with no patch number.
- Refuse a request without `A2A-Version` with `VersionNotSupportedError` when you serve only 1.0.
- Never rely on the query parameter or on a server to reject a missing version.
- Send `A2A-Extensions` as one comma-separated header field, never as a request parameter.Sources: spec §3.2.6, §3.6, §3.6.1, §3.6.2, §4.6.1, §5.4, §9.2, §10.2, §11.2, §12.3, §14.2.1, §14.2.2 (research/sources/specification.md); sdk src/a2a/utils/version_validator.py, src/a2a/server/request_handlers/grpc_handler.py, src/a2a/client/service_parameters.py, src/a2a/server/routes/common.py (a2a-python 1.2.2); sdk-js src/server/version.ts at v1.3.0; sdk-java server-common/src/main/java/org/a2aproject/sdk/server/version/A2AVersionValidator.java at v1.4.0.Final; sdk-go a2a/svcparams.go at v2.6.0; sdk-dotnet src/A2A.AspNetCore/A2AJsonRpcProcessor.cs at v1.0.0-preview2; sdk-rust a2a-server/src/jsonrpc.rs at a2a-server-lf-v0.5.1; capture/out/11-errors.http, 01-agent-cards.http; capture/a2a_ref.py