Skip to content

A2A: /.well-known/agent.json (0.3 alias) returns 404 — route not registered, breaking SDK auto-detection #612

@bokelley

Description

@bokelley

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:

  1. GET /.well-known/agent.json returns 308 (or 200 if you choose the duplicate-handler path).
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions