Skip to content

feat: A2A server support in serve()#179

Merged
bokelley merged 4 commits into
mainfrom
bokelley/issue-175
Apr 16, 2026
Merged

feat: A2A server support in serve()#179
bokelley merged 4 commits into
mainfrom
bokelley/issue-175

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Summary

  • Adds serve(handler, transport="a2a") to serve an ADCPHandler as an A2A agent
  • ADCPAgentExecutor bridges ADCPHandler methods to the a2a-sdk AgentExecutor interface — parses DataPart skill invocations, dispatches to handler methods, returns results as A2A Task events
  • create_a2a_server() builds a Starlette app with agent card + JSON-RPC endpoints
  • TestControllerStore works over A2A (same _handle_test_controller dispatch as MCP), so storyboards will work over both transports
  • Transport validation rejects unknown transport strings with helpful error
  • Exception details not leaked to callers (security review finding)

Test plan

  • 15 unit tests: executor dispatch (DataPart, TextPart JSON fallback), unknown/missing skill errors, handler exception handling, cancel, test controller (list_scenarios, force_account_status, errors), agent card generation, server creation
  • Integration tested: started A2A server with test_controller, exercised agent card, get_products, sync_accounts + force_account_status, list_scenarios, unknown skill — all 5/5 passing
  • Full test suite: 1178 passed, 0 failures
  • ruff clean, mypy clean (506 files)
  • Code review and security review addressed

Closes #175

🤖 Generated with Claude Code

bokelley and others added 4 commits April 16, 2026 12:25
Add A2A transport support so the same ADCPHandler can be served over
both MCP and A2A protocols with a single flag:

  serve(MyHandler(), name="my-agent", transport="a2a")

Implementation:
- ADCPAgentExecutor bridges ADCPHandler to a2a-sdk's AgentExecutor
  interface, parsing DataPart skill invocations and dispatching to
  handler methods
- create_a2a_server() builds a Starlette app with A2A endpoints
  (agent card at /.well-known/agent.json, JSON-RPC at /)
- TestControllerStore wired into A2A via the same _handle_test_controller
  dispatch logic MCP uses, so storyboards work over both transports
- Transport validation rejects unknown transport strings
- Exception details not leaked to callers (security review)

15 tests covering executor dispatch, error handling, test controller
integration, agent card generation, and server creation.

Closes #175

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Skip create_a2a_server tests on Python <3.11 (a2a-sdk starlette
  compat issue: Cannot subclass typing.Any)
- Don't leak exception details to A2A callers (security review)
- Add transport validation with helpful error message
- Restore specific security warning about exposed endpoints
- Add handler exception test
- Move Task import to top-level (cleanup)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ADCP requires that if a request contains a context field, the response
must echo it back unchanged. Previously handlers had to manually call
inject_context() — easy to forget, causing storyboard failures.

Now create_tool_caller() automatically injects context after the handler
returns, for both MCP and A2A transports. Handlers no longer need to
call inject_context() manually (though it's still safe to do so — it
won't overwrite if context is already set).

Validated: capability_discovery storyboard passes 2/2 over A2A,
media_buy_seller passes 7/9 (remaining 2 are test handler issues).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tested skill-doc DX by having agents build seller/signals/creative
agents. Seller doc forced iteration on undocumented schema fields;
other docs were shipping-quality.

- Seller product example now includes required reporting_capabilities
  and delivery_measurement fields so agents pass 9/9 first try
- Added Common Mistakes entries: reporting_capabilities sub-fields,
  valid enum values (hourly|daily|monthly), delivery_measurement.provider
- Removed inject_context calls from code examples — context passthrough
  is now automatic in create_tool_caller, so teaching it is wrong
- Updated serve() reference to document transport="a2a" and port= kwargs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit d0c3015 into main Apr 16, 2026
8 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.

feat: A2A server support in serve()

1 participant