Skip to content

Expose the training agent over A2A alongside MCP #3053

Description

@bokelley

Context

`server/src/training-agent/` exposes the reference training agent at `/api/training-agent/mcp` today via `StreamableHTTPServerTransport`. It's the anchor example buyer-agent authors reach for when learning AdCP — every tool, every specialism, every async/idempotency edge case.

With adcontextprotocol/adcp-client#899 landing, the SDK now ships a peer A2A transport adapter that mounts the same `AdcpServer` over A2A JSON-RPC. Running the training agent on BOTH transports from one process means:

  1. A2A-native buyer authors have a reference to learn against, not just MCP-native ones.
  2. Every conformance check available via MCP (fuzz, storyboards, grade) can be run via `--protocol a2a` against the same process — catching cross-transport divergences that only surface when both are exposed.
  3. The training agent's auth / signing / idempotency / capability wiring gets exercised over A2A, which is where most of the subtle interactions live.

Proposed change

Add an A2A mount to `server/src/training-agent/index.ts` using `createA2AAdapter` from `@adcp/client/server` (ships in adcp-client#899). Shape:

```ts
import { createA2AAdapter } from '@adcp/client/server';

const a2a = createA2AAdapter({
server: adcpServer, // same AdcpServer the MCP path uses
agentCard: {
name: 'AdCP Training Agent',
description: '...',
url: `${publicOrigin}/api/training-agent/a2a`,
version: PKG_VERSION,
provider: { organization: 'Ad Context Protocol', url: '...' },
securitySchemes: { bearer: { type: 'http', scheme: 'bearer' } },
},
async authenticate(req) {
// reuse the existing bearer-token / request-signing authenticator
},
});

a2a.mount(app); // wires JSON-RPC at /api/training-agent/a2a
// + agent card at both /api/training-agent/a2a/.well-known/agent-card.json
// and /.well-known/agent-card.json
```

Scope

  • Same handlers, same state store, same idempotency store — A2A and MCP share one `AdcpServer`. Zero handler changes; wiring only.
  • Same auth posture — `authenticate` on the A2A adapter calls into the existing bearer/signing authenticator. A request-signing buyer should authenticate identically over either transport.
  • Dual-hosted agent card — `adapter.mount()` handles this; sellers following the training-agent example then inherit the correct mount pattern for free.
  • CORS / rate limiting — the existing Express middleware stack applies; no new middleware needed.

Follow-up calls for testing / adoption

Once live, the training agent + CI can run:

```bash

same storyboard over both transports, diff the results

adcp storyboard run https://test.agent.example/api/training-agent/mcp --protocol mcp --auth $TOKEN
adcp storyboard run https://test.agent.example/api/training-agent/a2a --protocol a2a --auth $TOKEN
```

That's the first place the ecosystem will observe cross-transport parity (or divergences) empirically.

Rough effort

Ballpark 100-200 LOC in `server/src/training-agent/index.ts` + one or two config touches. The heavy lifting (DataPart shape, `Task.state` semantics, `adcp_task_id` on artifact metadata, dual-path agent card) is already handled inside `createA2AAdapter`.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions