feat(server): /.well-known/adcp-agents.json discovery endpoint (#381)#406
Merged
feat(server): /.well-known/adcp-agents.json discovery endpoint (#381)#406
Conversation
bokelley
added a commit
that referenced
this pull request
May 3, 2026
…(PR #406 fix-pack) Discovery manifest must publish https:// URLs per the AdCP schema's ``^https://`` pattern (loopback exception only). resolve_base_url now raises at boot when a non-loopback bind has no base_url, or when an explicit http:// base_url is paired with a non-loopback host — silent mis-publication survives in CDNs and conformance reports for hours. Also: strip separators before the 64-char agent_id cap so truncation never lands on a stripped char, and quantize last_updated to whole-hour granularity so consecutive requests within an hour are byte-identical (HTTP caches collapse the duplicates). Tests restore the ``^https://`` pattern in the inlined schema and add coverage for the new boot-time errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per AdCP PR #3903, every AdCP host publishes an origin-scoped manifest enumerating the agents it serves at /.well-known/adcp-agents.json. serve() now exposes this on every HTTP transport (streamable-http, a2a, both); stdio has no HTTP surface and skips the route. The manifest builder is a pure function in adcp.server.discovery so adopters can also publish a static manifest from CI. New optional serve() kwargs base_url / specialisms / description populate the manifest's url + specialism + description fields; defaults are correct for local development but production should set base_url to the public TLS-terminated origin. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…(PR #406 fix-pack) Discovery manifest must publish https:// URLs per the AdCP schema's ``^https://`` pattern (loopback exception only). resolve_base_url now raises at boot when a non-loopback bind has no base_url, or when an explicit http:// base_url is paired with a non-loopback host — silent mis-publication survives in CDNs and conformance reports for hours. Also: strip separators before the 64-char agent_id cap so truncation never lands on a stripped char, and quantize last_updated to whole-hour granularity so consecutive requests within an hour are byte-identical (HTTP caches collapse the duplicates). Tests restore the ``^https://`` pattern in the inlined schema and add coverage for the new boot-time errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
defffb0 to
6773db7
Compare
This was referenced May 3, 2026
Closed
bokelley
added a commit
that referenced
this pull request
May 3, 2026
… worktrees (#433) * fix(testing): make a2a_compat_shim resilient to wrong a2a-sdk in /tmp worktrees Attribute assignments like `pb.Role.user = pb.Role.ROLE_USER` at module import time would raise AttributeError if a2a-sdk isn't at the pinned version (>=1.0.1,<1.0.2), propagating through conftest.py's top-level import and breaking pytest collection entirely. Agents running in fresh /tmp worktrees with uninitialized environments hit this on PRs #391, #406, #407. Two changes: - `a2a_compat_shim.py`: introduce `_proto_alias()` helper that guards each attribute alias independently with hasattr + a per-alias RuntimeWarning (includes install command) rather than letting AttributeError propagate. - `conftest.py`: wrap the shim import in try/except (ImportError|AttributeError) with a fallback to None; update the autouse fixture to no-op when the shim is unavailable, so collection always succeeds and only A2A tests fail. https://claude.ai/code/session_01AnL37fUet4e3yXt9YBxd7a * fix(testing): stacklevel=2 in _proto_alias + document _STATE_STRING_MAP asymmetry stacklevel=2 makes the per-alias warning point at the _proto_alias() call site in the module body (the useful diagnostic location) rather than at the warnings.warn() line inside the helper. Add a comment at _STATE_STRING_MAP explaining that any AttributeError from the dict literal is caught by conftest.py's import guard, so the different guard pattern is intentional and collection still succeeds. https://claude.ai/code/session_01AnL37fUet4e3yXt9YBxd7a --------- Co-authored-by: Claude <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
May 3, 2026
…readiness flake (#435) * perf(server): lazy-load Pydantic outputSchema generation to fix storyboard readiness flake _generate_pydantic_schemas(), _generate_pydantic_output_schemas(), and _apply_pydantic_schemas() previously ran at module import time, causing heavy Pydantic type imports to race with the storyboard readiness probe and producing "Agent unreachable" failures across PRs #391, #405, #406, #407. Generation is now deferred to the first get_tools_for_handler() call (which fires during create_mcp_tools() at server construction, not at import time). _PYDANTIC_SCHEMAS and _PYDANTIC_OUTPUT_SCHEMAS start as empty dicts and are populated via .update() so external references stay valid. The _schemas_applied sentinel makes subsequent calls no-ops (~0ms overhead on the hot path). Import-time delta: ~4.5s of schema generation is moved from `import adcp.server` to the first `create_mcp_tools()` call. Tests updated: conftest.py gains a session-scoped autouse fixture that triggers lazy init before any test reads ADCP_TOOL_DEFINITIONS schema fields; stale "at import time" references in docstrings and error messages are updated. Closes #412 https://claude.ai/code/session_01NnoQN3c6Wi5LY5DEUBp8W2 * fixup: update stale 'at import time' docstrings and error messages Addresses pre-PR review findings: test_spec_coverage.py assertion message still referenced 'at import time', and _ensure_pydantic_schemas_applied docstring understated the in-place mutation and misdirected to get_tools_for_handler instead of create_mcp_tools. https://claude.ai/code/session_01NnoQN3c6Wi5LY5DEUBp8W2 --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
5c3e3e626) — every AdCP host now publishes an origin-scoped multi-agent topology manifest at/.well-known/adcp-agents.json.serve()for every HTTP transport (streamable-http,a2a,both);stdiohas no HTTP surface and skips the route.adcp.server.discoveryas a pure function (also exposed viamake_discovery_route) so adopters can publish a static manifest from CI.What buyers see
$ curl http://localhost:3001/.well-known/adcp-agents.json { "$schema": "/schemas/adcp-agents.json", "version": "1.0", "agents": [ { "agent_id": "my-seller-mcp", "url": "https://sales.example.com/mcp", "transport": "mcp", "specialisms": ["sales-non-guaranteed"] }, { "agent_id": "my-seller-a2a", "url": "https://sales.example.com", "transport": "a2a", "specialisms": ["sales-non-guaranteed"] } ], "contact": {"name": "my-seller"}, "last_updated": "2026-05-02T00:00:00Z" }New
serve()kwargsbase_url— public origin used in manifest URLs. Defaults tohttp://<bind-host>:<port>; production deployments behind a TLS-terminating proxy SHOULD set this.specialisms— list of AdCP specialism tags (e.g.["sales-non-guaranteed"]). Falls back to a placeholder; adopters who know their specialism SHOULD pass it.description— human-readable description surfaced in operator UIs.Known gaps / TODOs
build_manifest()currently accepts specialisms verbatim. The handler's advertised tools could in principle infer a default specialism (e.g.get_products→sales-non-guaranteed), but that's a non-trivial mapping and would conflate "implements the tool" with "operator declares the specialism." Left as aTODO(#381)indiscovery.py; placeholder works for adopters who haven't classified yet.auth_hint— schema-optional; not surfaced because the SDK doesn't own auth wiring. Adopters who want to advertise it can build their own manifest viabuild_manifest()and serve it through a custom route.Test plan
ruff check src/mypy src/adcp/pytest tests/ -k "discovery"(31 passed, 9 skipped)pytest tests/test_unified_mcp_a2a.py tests/test_serve_dx_polish.py tests/test_serve_asgi_middleware.py tests/test_a2a_server.py tests/test_server_dx.py tests/test_server_framework.py tests/test_server_helpers.py(194 passed) — no regressionstests/test_discovery_endpoint.pycovers GET on each transport, JSON-schema validation, POST falls through, agent_id normalization, builder unit tests🤖 Generated with Claude Code