Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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