The gRPC binding is the proto itself: the eleven rpcs of
A2AServicein packagelf.a2a.v1, carried over HTTP/2 with TLS, with the service parameters in metadata.
The planner reads three cards and finds only JSONRPC and HTTP+JSON interfaces, so no call in 19-planner.http uses gRPC. The kit is standard library only, and the standard library has no gRPC, so no capture in this manual shows a gRPC call. The reference SDK's sample agent lists a third interface, protocol_binding='GRPC' at a plain host and port {{sdk samples/hello_world_agent.py}}.
When you finish this section, you can read a GRPC interface, call A2AService from a generated stub, and read its errors and streams.
The service
The binding has four requirements: "gRPC over HTTP/2 with TLS", the normative definition in specification/a2a.proto, "Protocol Buffers version 3", and the A2AService service {{spec §10.1}}. The file declares package lf.a2a.v1, so the full service name is lf.a2a.v1.A2AService {{proto A2AService}}. Its eleven rpcs carry the same PascalCase names as the JSON-RPC methods, and operations by binding lists them with their messages.
Two rpcs return stream StreamResponse: SendStreamingMessage and SubscribeToTask {{proto A2AService}} {{spec §10.7}}. The other nine are unary, and none takes a client stream. Each rpc also carries the google.api.http annotation that defines its HTTP+JSON route, as HTTP+JSON shows:
// Sends a streaming message to an agent, allowing for real-time interaction and status updates.
// Streaming version of `SendMessage`
rpc SendStreamingMessage(SendMessageRequest) returns (stream StreamResponse) {
option (google.api.http) = {
post: "/message:stream"
body: "*"
additional_bindings: {
post: "/{tenant}/message:stream"
body: "*"
}
};
}Original source
title: the streaming send, as the proto defines it
source: research/sources/a2a.proto
lang: text
note: The rpc with its HTTP annotation, complete. The additional_bindings block is the tenant route.
---
// Sends a streaming message to an agent, allowing for real-time interaction and status updates.
// Streaming version of `SendMessage`
rpc SendStreamingMessage(SendMessageRequest) returns (stream StreamResponse) {
option (google.api.http) = {
post: "/message:stream"
body: "*"
additional_bindings: {
post: "/{tenant}/message:stream"
body: "*"
}
};
}A card names this binding with protocolBinding set to GRPC {{proto AgentInterface}}. At the tag the proto wants an absolute HTTPS URL for every interface, while a gRPC client dials a host and port (conflict D34). AgentCard fields notes the same gap. PR #1997 changed the comment on main to the form hostname:port, the one change queued for release 1.0.2, not yet released.
Metadata, errors, and streams
The two service parameters must travel as gRPC metadata {{spec §10.2}}. gRPC lowercases metadata keys, so the keys are a2a-version and a2a-extensions. A server should validate required service parameters, such as A2A-Version, from the metadata {{spec §10.2}}. The §10.2 sample sends a2a-version with the old example value 0.3 {{spec §10.2}}, so send 1.0 from a 1.0 client, as A2A-Version and A2A-Extensions explains.
An error is a google.rpc.Status with status.code, status.message, and status.details {{spec §10.6}}. For an A2A error, the details must include a google.rpc.ErrorInfo message {{spec §10.6}}. Its reason is the error name in upper snake case without Error, such as TASK_NOT_FOUND, and its domain is a2a-protocol.org {{spec §10.6}}. The table in errors gives the status of each error, and FAILED_PRECONDITION covers six of the nine, so read the reason.
A stream is a server-streaming rpc, and each message is one StreamResponse with one of task, message, status_update, or artifact_update {{spec §10.7}} {{proto StreamResponse}}. The stream ends when the rpc completes with its status, and the Go server returns OK when its event sequence ends {{sdk-go a2agrpc/v1/handler.go at v2.6.0}}. Figure follows one streaming call from the request to that end.
Original source
id: fig-grpc-call
kind: sequence
title: one SendStreamingMessage call over gRPC
claim: One rpc sends the request with its metadata, the server streams StreamResponse messages until the task ends, and the rpc status closes the stream.
caption: Read top to bottom. The client sends one SendMessageRequest with a2a-version in its metadata, and each reply is one StreamResponse payload. Names are the proto's, because the kit has no gRPC capture, so no values are shown. From research/sources/a2a.proto.SDK support
| SDK | gRPC server | gRPC client | How to enable it |
|---|---|---|---|
| a2a-python 1.2.2 | GrpcHandler {{sdk src/a2a/server/request_handlers/grpc_handler.py}} |
GrpcTransport {{sdk src/a2a/client/transports/grpc.py}} |
pip install a2a-sdk[grpc], register the handler as the servicer, and list GRPC in the client factory {{sdk src/a2a/client/client_factory.py}} |
| a2a-js 1.3.0 | grpcService, Node only {{sdk-js src/server/grpc/grpc_service.ts at v1.3.0}} |
GrpcTransport |
add GrpcTransportFactory, which the default factory omits {{sdk-js src/client/factory.ts at v1.3.0}} |
| a2a-go 2.6.0 | a2agrpc/v1.NewHandler {{sdk-go a2agrpc/v1/handler.go at v2.6.0}} |
a2agrpc/v1.WithGRPCTransport |
add the transport option, which the default client omits {{sdk-go a2aclient/factory.go at v2.6.0}} |
| a2a-java 1.4.0.Final | transport/grpc and the Quarkus reference/grpc server |
client/transport/grpc |
add the Maven modules {{sdk-java README.md at v1.4.0.Final}} |
| a2a-dotnet 1.0.0-preview2 | none, only the name constant {{sdk-dotnet src/A2A/Client/ProtocolBindingNames.cs at v1.0.0-preview2}} | none | A2A.Grpc.AspNetCore and A2A.Grpc exist on main, not yet released |
| a2a-rs, a2a-grpc 0.3.9 | GrpcHandler in the a2a-grpc crate |
GrpcTransport in the same crate |
register it yourself, because the client factory lists JSON-RPC and HTTP+JSON only {{sdk-rust a2a-client/src/factory.rs at a2a-server-lf-v0.5.1}} |
The servers differ on the version check: the Python, Go, and Rust gRPC servers never read a2a-version, as the table in A2A-Version and A2A-Extensions shows.
One custom binding exists in the project today, SLIMRPC: Protocol Buffers RPC over SLIM instead of HTTP/2. A card names it https://a2a-protocol.org/bindings/experimental-slimrpc/v1, and its repository marks it experimental. A custom binding should have a URI as its identifier {{spec §5.8}}, and the project's guide gives the rules for one {{docs custom-protocol-bindings}}.
Original source
- Generate the stub from `specification/a2a.proto`, package `lf.a2a.v1`, and keep its rpc names.
- Send `a2a-version` and `a2a-extensions` as lowercase metadata keys on every call.
- Read `ErrorInfo.reason` from `status.details`, because `FAILED_PRECONDITION` covers six A2A errors.
- Register the gRPC transport yourself in the JS, Go, and Rust client factories.Sources: spec §5.8, §10, §10.1, §10.2, §10.6, §10.7 (research/sources/specification.md); proto A2AService, AgentInterface, StreamResponse (research/sources/a2a.proto); docs custom-protocol-bindings (research/sources/docs.md); sdk samples/hello_world_agent.py, src/a2a/server/request_handlers/grpc_handler.py, src/a2a/client/transports/grpc.py, src/a2a/client/client_factory.py (a2a-python 1.2.2); sdk-go a2agrpc/v1/handler.go, a2aclient/factory.go at v2.6.0; sdk-js src/server/grpc/grpc_service.ts, src/client/factory.ts at v1.3.0; sdk-java README.md at v1.4.0.Final; sdk-dotnet src/A2A/Client/ProtocolBindingNames.cs at v1.0.0-preview2; sdk-rust a2a-client/src/factory.rs at a2a-server-lf-v0.5.1; capture/out/01-agent-cards.http, 19-planner.http; capture/README.md