Why
AdCP defines MCP and A2A as first-class transports. adcp-go is currently MCP-only — no /.well-known/agent-card.json, no message/send / tasks/get handlers. That leaves half the protocol undemonstrated and gives Go buyers nothing to hit when we ship an A2A client (companion to adcontextprotocol/adcp-client-python#251).
What
Add A2A transport to at least one reference agent (start with reference/seller-agent), sharing tool-handler logic with the existing MCP surface so both transports produce identical output.
Endpoints
GET /.well-known/agent-card.json — advertise skills, transports, auth
POST /a2a (JSON-RPC 2.0):
message/send
tasks/get
tasks/cancel
Envelope contract (server-side of the PR 251 story)
- Always return
contextId on every response.
- Echo caller-provided
contextId when present; mint when absent. Default to pass-through (no ADK-style rewriting) unless we have a reason otherwise — document the choice.
- Preserve
taskId across responses while status is non-terminal (submitted, working, input-required, auth-required).
- Terminal-state semantics for
taskId (completed, failed, canceled, rejected) documented on the response type.
- Error responses still carry
contextId/taskId when they belong to a task.
Shared handler plumbing
Refactor adcp.Register() so the tool handler closure is transport-agnostic — both mcp.CallToolRequest and an A2A message/send payload dispatch into the same typed (ctx, input) -> (output, error) function. No duplicated business logic per transport.
Schema nit (opportunistic)
CreateMediaBuySubmitted is missing ContextID while MCPWebhookPayload has it. Both are async-shaped envelopes; align them.
Tests
- HTTP-level E2E: A2A buyer against the agent, assert
contextId persists across two sends.
- HITL test: tool returns
input-required; follow-up message/send with same contextId + taskId resumes the same server task.
- Cross-transport conformance: same scenario over MCP and A2A produces identical payloads modulo envelope fields.
Scope
- Sync
message/send only. Streaming (SSE) and push-notification config are out of scope for v1.
- Reference agent(s) only — production agents opt in later.
- No breaking changes to existing MCP surface.
Non-goals
- Extensions API, batch, or any A2A feature not required for AdCP tool flows.
- New auth mechanism — inherit existing AdCP signing.
Sequence
- Transport-agnostic handler refactor (
adcp/seller.go, adcp/addtool.go).
- A2A server package (
adcp/a2a).
- Wire into
reference/seller-agent.
- Companion: Go A2A client (tracked separately).
Why
AdCP defines MCP and A2A as first-class transports. adcp-go is currently MCP-only — no
/.well-known/agent-card.json, nomessage/send/tasks/gethandlers. That leaves half the protocol undemonstrated and gives Go buyers nothing to hit when we ship an A2A client (companion to adcontextprotocol/adcp-client-python#251).What
Add A2A transport to at least one reference agent (start with
reference/seller-agent), sharing tool-handler logic with the existing MCP surface so both transports produce identical output.Endpoints
GET /.well-known/agent-card.json— advertise skills, transports, authPOST /a2a(JSON-RPC 2.0):message/sendtasks/gettasks/cancelEnvelope contract (server-side of the PR 251 story)
contextIdon every response.contextIdwhen present; mint when absent. Default to pass-through (no ADK-style rewriting) unless we have a reason otherwise — document the choice.taskIdacross responses while status is non-terminal (submitted,working,input-required,auth-required).taskId(completed,failed,canceled,rejected) documented on the response type.contextId/taskIdwhen they belong to a task.Shared handler plumbing
Refactor
adcp.Register()so the tool handler closure is transport-agnostic — bothmcp.CallToolRequestand an A2Amessage/sendpayload dispatch into the same typed(ctx, input) -> (output, error)function. No duplicated business logic per transport.Schema nit (opportunistic)
CreateMediaBuySubmittedis missingContextIDwhileMCPWebhookPayloadhas it. Both are async-shaped envelopes; align them.Tests
contextIdpersists across two sends.input-required; follow-upmessage/sendwith samecontextId+taskIdresumes the same server task.Scope
message/sendonly. Streaming (SSE) and push-notification config are out of scope for v1.Non-goals
Sequence
adcp/seller.go,adcp/addtool.go).adcp/a2a).reference/seller-agent.