Skip to content

feat: implement identity.brand_json_url resolver + verifier (resolve_agent / verify_request_signature) + CLI #344

@bokelley

Description

@bokelley

Summary

AdCP 3.x adds identity.brand_json_url to get_adcp_capabilities (PR adcontextprotocol/adcp#3690). Verifiers can now bootstrap from an agent URL to that agent's signing keys without out-of-band knowledge of the operator domain. This issue tracks the Python SDK port.

Spec references

API to implement

from adcp import resolve_agent, get_agent_jwks, verify_request_signature

result = resolve_agent("https://buyer.example.com/mcp")
# result: AgentResolution { agent_url, brand_json_url, agent_entry, jwks_uri,
#                          jwks, identity_posture, consistency, freshness, trace }

verified = verify_request_signature(
    request,                                  # httpx.Request
    agent_url="https://buyer.example.com/mcp",
    allowed_algs=("EdDSA",),
)
# Raises typed exceptions matching the spec's request_signature_* error codes.

CLI

python -m adcp resolve <agent-url> — same output format as the TypeScript CLI (npx @adcp/client resolve). Flags: --json, --fresh, --quiet. Output portable across SDKs so shell pipelines work either way.

Implementation notes

  • SSRF hardening: HTTPS only, address-family filter before DNS-pin, IPv4 RFC 1918 + IPv6 ULA + cloud-metadata IP blocks, body cap 256 KiB on brand.json / 16 KiB JWKS / 64 KiB capabilities, no redirects, connect 5s / total 10s. httpx provides hooks for connect-time IP filtering.
  • PSL: use publicsuffixlist or tldextract with a pinned snapshot. Don't fetch the PSL at runtime.
  • Cache: brand.json TTL bounded above by JWKS revocation polling interval. Negative-cache ≤ 60s. No SWR on JWKS.
  • Error class: AgentResolverError(Exception) with code and detail matching the spec's taxonomy. Subclasses keyed by error code for except clarity.
  • Pydantic models for AgentResolution, Trace, IdentityPosture, etc.
  • Reference design: see adcontextprotocol/adcp@bd89c62ff4 for the dropped Node.js reference implementation — the algorithm/structure ports cleanly.

Tests

  • Unit: pure-function consistency tests covering every storyboard variant
  • Integration: spin up a fixture HTTP server with brand.json + JWKS, drive the resolver and assert wire shape, error codes, JOSE round-trip via python-jose
  • E2E gated by env var against a known-good agent

Acceptance

  • resolve_agent, get_agent_jwks, verify_request_signature exported from adcp
  • CLI python -m adcp resolve <url> printing the chain + trace
  • All 8 request_signature_* codes surface as typed exceptions
  • SSRF hardening covers the full list from security.mdx §"Quickstart"

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions