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:
- A2A-native buyer authors have a reference to learn against, not just MCP-native ones.
- 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.
- 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
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:
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
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