Skip to content

fix: validate A2A version and extensions on all gRPC handler methods - #1042

Merged
kabir merged 4 commits into
a2aproject:mainfrom
ez-lbz:fix/grpc-version-extension-validation
Aug 12, 2026
Merged

fix: validate A2A version and extensions on all gRPC handler methods#1042
kabir merged 4 commits into
a2aproject:mainfrom
ez-lbz:fix/grpc-version-extension-validation

Conversation

@ez-lbz

@ez-lbz ez-lbz commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What changed

1. Enforce A2A version and extension validation on all gRPC handler methods

Problem: In GrpcHandler, only sendMessage and sendStreamingMessage called A2AVersionValidator.validateProtocolVersion + A2AExtensions.validateRequiredExtensions. The remaining methods — getTask, listTasks, cancelTask, createTaskPushNotificationConfig, getTaskPushNotificationConfig, listTaskPushNotificationConfigs, subscribeToTask, deleteTaskPushNotificationConfig, getExtendedAgentCard — skipped validation entirely, so clients using an incompatible protocol version (or missing required extensions) were served instead of being rejected.

Fix (transport/grpc/src/main/java/org/a2aproject/sdk/transport/grpc/handler/GrpcHandler.java):

  • Added A2AVersionValidator.validateProtocolVersion(getAgentCardInternal(), context) and A2AExtensions.validateRequiredExtensions(getAgentCardInternal(), context) immediately after createCallContext(...) in all nine previously-unvalidated methods, mirroring the existing sendMessage/sendStreamingMessage pattern. For subscribeToTask the checks are placed after installForkedContextWrapper, matching sendStreamingMessage.

Fix (transport/grpc/src/test/java/org/a2aproject/sdk/transport/grpc/handler/GrpcHandlerTest.java):

  • Added testVersionNotSupportedErrorOnGetTask: a handler with an incompatible requested protocol version ("2.0" vs the card's "1.0") now fails getTask with gRPC UNIMPLEMENTED (mapped from VersionNotSupportedError). Previously getTask skipped validation and would have returned the task.

Behavior change: gRPC requests with an incompatible A2A-Version header or missing required extensions are now rejected on all methods instead of only the two streaming/unary message methods. Requests carrying no version header are unaffected (they default to "0.3" compatibility per spec, and validation passes when the card supports a compatible version).

Testing

  • mvn -pl transport/grpc test41 tests run, 0 failures, 0 errors, 0 skipped (BUILD SUCCESS), including the new regression test testVersionNotSupportedErrorOnGetTask.

@kabir kabir left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ez-lbz thanks it looks good! Just a small suggestion to centralize the code.

The main question there is whether installForkedContextWrapper() can happen after doing the validation for the streaming calls which is what will happen if we go with my suggestion. sendStreamingMessage() currently creates this wrapper before its validation.

If the wrapper needs to be created before validation we can probably massage createCallContext() a bit to conditionally install the wrapper before validation

try {
ServerCallContext context = createCallContext(responseObserver);
A2AVersionValidator.validateProtocolVersion(getAgentCardInternal(), context);
A2AExtensions.validateRequiredExtensions(getAgentCardInternal(), context);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could just put these two lines inside createCallContext()?

…ntext

Move validateProtocolVersion + validateRequiredExtensions into
createCallContext() so every gRPC handler method validates through one
path, per review suggestion. The streaming methods still install the
forked context wrapper after createCallContext() returns; the wrapper is
independent of the validation (it only attaches a forked gRPC Context
around agent-executor runnables), so validating first is safe — and a
failed validation now skips the fork entirely.
@ez-lbz

ez-lbz commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Good suggestion — done. validateProtocolVersion and validateRequiredExtensions now live inside createCallContext(), so all gRPC handler methods (getTask, listTasks, cancelTask, push-config methods, subscribe, streaming) validate through a single path.

Regarding your question on ordering: installForkedContextWrapper() is independent of the validation — it only attaches a forked gRPC Context around agent-executor runnables, and validation doesn't touch that. Moving validation before the wrapper install is safe, and a failed validation now skips the fork entirely. GrpcHandlerTest passes (41 tests, 0 failures).

@kabir
kabir merged commit b9d8f73 into a2aproject:main Aug 12, 2026
14 of 15 checks passed
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.

3 participants