Summary
The Python SDK's A2A server registers a Starlette route for the canonical /.well-known/agent-card.json (via a2a-sdk's create_agent_card_routes) but does not register a route for the 0.3-compat alias /.well-known/agent.json. Any healthy adcp-client-python agent therefore returns 404 (or 503 at edge proxies that don't return application 404 from no-route conditions, e.g. Fly.io) on the alias.
This breaks @adcp/sdk (JS) CLI auto-detection of A2A transport — the JS SDK probes both well-known paths and uses the alias as a positive A2A signal. Reported by users running npx @adcp/sdk@6.15.0 storyboard run <agent> against a Python-SDK-built seller — A2A transport not detected, despite the agent being fully functional on the canonical path.
Repro
Any agent built on adcp-client-python with serve(transport=\"a2a\", ...) or serve(transport=\"both\", ...):
```bash
curl -i 'https:///.well-known/agent-card.json' # 200 OK (canonical, works)
curl -i 'https:///.well-known/agent.json' # 404 (or 503 at edge — route never registered)
```
Live example: https://wonderstruck.sales-agent.scope3.com/.well-known/agent.json → HTTP 503 from Fly's edge.
Root cause
adcp/server/a2a_server.py:833:
```python
routes = list(create_agent_card_routes(agent_card=agent_card)) + list(
create_jsonrpc_routes(**jsonrpc_kwargs)
)
```
create_agent_card_routes (from a2a-sdk) registers exactly one route at AGENT_CARD_WELL_KNOWN_PATH = '/.well-known/agent-card.json'. The 0.3 alias path is mentioned only defensively elsewhere — adcp/server/auth.py:603-604 exempts both paths from auth ("Legacy 0.3 alias retained by enable_v0_3_compat=True"), but the auth list doesn't create routes, it only exempts them.
enable_v0_3_compat=True (line 829) is passed to create_jsonrpc_routes (which dual-serves 0.3/1.0 RPC formats on /) but is not plumbed through to the agent-card route construction — the flag's promise of "0.3-compat retention" is incomplete for discovery.
Why it matters
The 0.3 alias is documented in your code as "retained" by enable_v0_3_compat=True. Auth was wired for it. Buyer SDKs still probe it. But the route was never registered. This makes enable_v0_3_compat partially load-bearing in a way that surprises adopters: their 0.3 RPC traffic works, their 0.3 discovery silently breaks. SDK auto-detection logic that uses the alias as a transport hint (as @adcp/sdk's does) treats Python-SDK agents as if they don't speak A2A.
Suggested fix
In adcp/server/a2a_server.py:833, when enable_v0_3_compat=True is in effect, append a redirect or duplicate route at /.well-known/agent.json:
```python
from starlette.responses import RedirectResponse
from starlette.routing import Route
... after create_agent_card_routes(...) ...
if enable_v0_3_compat:
async def _redirect_v0_3_alias(request):
return RedirectResponse('/.well-known/agent-card.json', status_code=308)
routes.append(Route(
'/.well-known/agent.json',
endpoint=_redirect_v0_3_alias,
methods=['GET'],
))
```
308 (vs 301/302) preserves request method and is the modern correct status for permanent redirects to a different URL where the original is deprecated. Buyer SDKs that follow redirects (which both @adcp/sdk and most HTTP clients do by default) get the canonical agent-card content transparently.
Alternative: register a duplicate route handler that returns the same body. Slightly more bytes per request, no redirect round-trip. Either is a valid resolution.
Workaround (already shipped on our agent)
We shipped an ASGI redirect middleware on our agent as the immediate workaround: bokelley/salesagent#269 (closes bokelley/salesagent#267). After this lands upstream, we'd remove our middleware as cleanup.
Tests
A regression test in tests/server/test_a2a_server.py (or wherever the agent-card route tests live) that asserts:
- GET
/.well-known/agent.json returns 308 (or 200 if you choose the duplicate-handler path).
- Following the redirect resolves to a 200 with valid agent-card JSON matching the canonical path.
Environment
adcp-client-python: pinned version per salesagent's uv.lock as of 2026-05-09
- AdCP spec: 3.0.8
- a2a-sdk: 1.x
Summary
The Python SDK's A2A server registers a Starlette route for the canonical
/.well-known/agent-card.json(viaa2a-sdk'screate_agent_card_routes) but does not register a route for the 0.3-compat alias/.well-known/agent.json. Any healthyadcp-client-pythonagent therefore returns 404 (or 503 at edge proxies that don't return application 404 from no-route conditions, e.g. Fly.io) on the alias.This breaks
@adcp/sdk(JS) CLI auto-detection of A2A transport — the JS SDK probes both well-known paths and uses the alias as a positive A2A signal. Reported by users runningnpx @adcp/sdk@6.15.0 storyboard run <agent>against a Python-SDK-built seller — A2A transport not detected, despite the agent being fully functional on the canonical path.Repro
Any agent built on
adcp-client-pythonwithserve(transport=\"a2a\", ...)orserve(transport=\"both\", ...):```bash
curl -i 'https:///.well-known/agent-card.json' # 200 OK (canonical, works)
curl -i 'https:///.well-known/agent.json' # 404 (or 503 at edge — route never registered)
```
Live example:
https://wonderstruck.sales-agent.scope3.com/.well-known/agent.json→ HTTP 503 from Fly's edge.Root cause
adcp/server/a2a_server.py:833:```python
routes = list(create_agent_card_routes(agent_card=agent_card)) + list(
create_jsonrpc_routes(**jsonrpc_kwargs)
)
```
create_agent_card_routes(froma2a-sdk) registers exactly one route atAGENT_CARD_WELL_KNOWN_PATH = '/.well-known/agent-card.json'. The 0.3 alias path is mentioned only defensively elsewhere —adcp/server/auth.py:603-604exempts both paths from auth ("Legacy 0.3 alias retained by enable_v0_3_compat=True"), but the auth list doesn't create routes, it only exempts them.enable_v0_3_compat=True(line 829) is passed tocreate_jsonrpc_routes(which dual-serves 0.3/1.0 RPC formats on/) but is not plumbed through to the agent-card route construction — the flag's promise of "0.3-compat retention" is incomplete for discovery.Why it matters
The 0.3 alias is documented in your code as "retained" by
enable_v0_3_compat=True. Auth was wired for it. Buyer SDKs still probe it. But the route was never registered. This makesenable_v0_3_compatpartially load-bearing in a way that surprises adopters: their 0.3 RPC traffic works, their 0.3 discovery silently breaks. SDK auto-detection logic that uses the alias as a transport hint (as@adcp/sdk's does) treats Python-SDK agents as if they don't speak A2A.Suggested fix
In
adcp/server/a2a_server.py:833, whenenable_v0_3_compat=Trueis in effect, append a redirect or duplicate route at/.well-known/agent.json:```python
from starlette.responses import RedirectResponse
from starlette.routing import Route
... after create_agent_card_routes(...) ...
if enable_v0_3_compat:
async def _redirect_v0_3_alias(request):
return RedirectResponse('/.well-known/agent-card.json', status_code=308)
```
308 (vs 301/302) preserves request method and is the modern correct status for permanent redirects to a different URL where the original is deprecated. Buyer SDKs that follow redirects (which both
@adcp/sdkand most HTTP clients do by default) get the canonical agent-card content transparently.Alternative: register a duplicate route handler that returns the same body. Slightly more bytes per request, no redirect round-trip. Either is a valid resolution.
Workaround (already shipped on our agent)
We shipped an ASGI redirect middleware on our agent as the immediate workaround: bokelley/salesagent#269 (closes bokelley/salesagent#267). After this lands upstream, we'd remove our middleware as cleanup.
Tests
A regression test in
tests/server/test_a2a_server.py(or wherever the agent-card route tests live) that asserts:/.well-known/agent.jsonreturns 308 (or 200 if you choose the duplicate-handler path).Environment
adcp-client-python: pinned version per salesagent'suv.lockas of 2026-05-09