|
| 1 | +--- |
| 2 | +title: Multi-agent topology discovery |
| 3 | +sidebarTitle: Multi-agent discovery |
| 4 | +description: "/.well-known/adcp-agents.json — origin-scoped manifest enumerating every AdCP agent served from a host. Lets buyers and conformance runners learn the full multi-agent topology in a single fetch." |
| 5 | +"og:title": "AdCP — Multi-agent topology discovery" |
| 6 | +--- |
| 7 | + |
| 8 | +# Multi-agent topology discovery |
| 9 | + |
| 10 | +A single AdCP origin often serves several agents — a sales agent at `/sales`, signals at `/signals`, governance at `/governance`, creative at `/creative`. `/.well-known/adcp-agents.json` is the standard machine-readable way for that operator to advertise which agents are live, what each one's URL is, and which [specialisms](/docs/protocol/required-tasks) each one claims. |
| 11 | + |
| 12 | +A single-agent operator MAY publish a one-entry manifest — the value to tooling is consistent: discovery is one fetch instead of "ask the operator for a doc." |
| 13 | + |
| 14 | +## Endpoint |
| 15 | + |
| 16 | +``` |
| 17 | +GET /.well-known/adcp-agents.json |
| 18 | +``` |
| 19 | + |
| 20 | +- MUST be readable without authentication (parity with `/.well-known/agent-card.json` and `/.well-known/adagents.json`). |
| 21 | +- For privacy, operators MAY return only public/test agents from this endpoint and gate production agents behind authenticated discovery on a different path. |
| 22 | +- Origin-scoped per RFC 5785 — a host serves one manifest covering every agent on that origin, regardless of path prefix. |
| 23 | +- On multi-tenant deployments where each tenant lives at its own subdomain, the manifest at `tenant-X.host` MUST contain only that tenant's agents and MUST NOT include `agent_id`, `description`, or `contact` values that disclose other tenants. Treat all manifest fields as public. |
| 24 | + |
| 25 | +## Shape |
| 26 | + |
| 27 | +```json |
| 28 | +{ |
| 29 | + "$schema": "/schemas/adcp-agents.json", |
| 30 | + "version": "1.0", |
| 31 | + "agents": [ |
| 32 | + { |
| 33 | + "agent_id": "sales", |
| 34 | + "url": "https://example.com/sales/mcp", |
| 35 | + "transport": "mcp", |
| 36 | + "specialisms": ["sales-non-guaranteed", "sales-guaranteed"], |
| 37 | + "auth_hint": "shared_bearer" |
| 38 | + }, |
| 39 | + { |
| 40 | + "agent_id": "signals", |
| 41 | + "url": "https://example.com/signals/mcp", |
| 42 | + "transport": "mcp", |
| 43 | + "specialisms": ["signal-marketplace", "signal-owned"], |
| 44 | + "auth_hint": "shared_bearer" |
| 45 | + }, |
| 46 | + { |
| 47 | + "agent_id": "governance", |
| 48 | + "url": "https://example.com/governance/mcp", |
| 49 | + "transport": "mcp", |
| 50 | + "specialisms": [ |
| 51 | + "governance-spend-authority", |
| 52 | + "governance-delivery-monitor", |
| 53 | + "property-lists", |
| 54 | + "collection-lists", |
| 55 | + "content-standards" |
| 56 | + ] |
| 57 | + } |
| 58 | + ], |
| 59 | + "contact": { "name": "Example Publisher Ad Ops", "email": "adops@example.com" }, |
| 60 | + "last_updated": "2026-05-02T00:00:00Z" |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +### Fields |
| 65 | + |
| 66 | +| Field | Required | Description | |
| 67 | +|---|---|---| |
| 68 | +| `version` | Required | Manifest version (`"1.0"` for this revision). Consumers SHOULD ignore unknown top-level fields rather than fail on a future minor revision. A future `2.x` will be served at a different well-known path or signalled via media-type parameter. | |
| 69 | +| `agents[]` | Required | Every AdCP agent served from this origin. One entry per endpoint. Max 256. | |
| 70 | +| `agents[].agent_id` | Required | Stable, operator-defined identifier for this agent within the manifest (e.g. `sales`, `signals`). Lowercase alphanumeric with hyphens/underscores, 1–64 characters, no leading/trailing separator. Unique within `agents[]`. | |
| 71 | +| `agents[].url` | Required | Agent endpoint URL. `https://` only in production. For MCP, the streamable-HTTP URL clients POST to (typically ending in `/mcp`). For A2A, the agent's base URL (the agent-card lives at `<url>/.well-known/agent-card.json`). | |
| 72 | +| `agents[].transport` | Required | Wire protocol — common values `"mcp"`, `"a2a"`. Open string; new transports may be added. Consumers SHOULD treat unknown values as unsupported rather than fail. Both MCP and A2A share AdCP semantics ([Calling an agent](/docs/protocol/calling-an-agent)). | |
| 73 | +| `agents[].specialisms` | Required | AdCP specialisms this agent currently implements. See [Required tasks by protocol](/docs/protocol/required-tasks) for the canonical list. Operators MUST list only specialisms whose required tasks the agent supports at publication time. Max 64. | |
| 74 | +| `agents[].auth_hint` | Optional | Informational hint for tooling. Common values below. Consumers MUST NOT use this to decide whether to attach credentials. | |
| 75 | +| `agents[].description` | Optional | Human-readable description, surfaced in operator UIs and conformance reports. Max 500 chars. Treat as public. | |
| 76 | +| `contact` | Optional | Operator contact information. When present, `contact.name` is required; `contact.email` and `contact.url` are optional. | |
| 77 | +| `last_updated` | Optional | ISO 8601 timestamp of the manifest's last change. | |
| 78 | + |
| 79 | +### `auth_hint` common values |
| 80 | + |
| 81 | +The hint is informational only — the actual auth contract is negotiated out-of-band. Buyers MUST NOT treat `auth_hint` as authoritative, and MUST NOT use it to decide whether to attach credentials (that decision belongs to the consumer's configured trust policy for the target origin). |
| 82 | + |
| 83 | +| Value | Meaning | |
| 84 | +|---|---| |
| 85 | +| `shared_bearer` | One bearer token works across every agent on this origin. | |
| 86 | +| `per_agent_bearer` | Each agent needs its own bearer token. | |
| 87 | +| `oauth` | OAuth 2.1 flow — see [`/.well-known/oauth-authorization-server`](https://datatracker.ietf.org/doc/html/rfc8414). | |
| 88 | + |
| 89 | +Operators MAY publish other values; tooling that doesn't recognise a value SHOULD fall back to "ask the operator". |
| 90 | + |
| 91 | +## Consumer requirements |
| 92 | + |
| 93 | +Manifests come from arbitrary origins. A misimplemented or compromised origin can publish URLs that point anywhere. Consumers walking this manifest MUST: |
| 94 | + |
| 95 | +- **HTTPS-only in production.** Reject any `agents[].url` that is not `https://` outside an explicit opt-in to a localhost/sandbox origin. |
| 96 | +- **Block private and metadata addresses.** Reject URLs that resolve (after DNS) to RFC 1918 ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), or cloud-metadata addresses (`169.254.169.254`, `metadata.google.internal`, `fd00:ec2::254`). This includes redirect targets if HTTP redirects are followed. |
| 97 | +- **Enforce origin binding.** A URL listed in this manifest is trusted only for the origin that served the manifest. Before sending credentials or making a transactional call to an `agents[].url` whose origin differs from the manifest origin, consumers MUST independently verify authorization via the target publisher's [`/.well-known/adagents.json`](/docs/governance/property/adagents) — the manifest is a discovery hint, not a trust anchor for cross-origin agents. |
| 98 | +- **Canonicalize before comparing.** When comparing `agents[].url` against an `adagents.json` entry, brand registry lookup, or any other authorization signal, apply the [AdCP URL canonicalization rules](/docs/reference/url-canonicalization). Two URLs that differ only in case, default port, or unreserved-character percent-encoding are the same agent. |
| 99 | +- **Cap the response.** Manifests SHOULD parse under a 1 MB body cap; reject larger responses outright. |
| 100 | + |
| 101 | +## Reconciliation with neighbouring well-known files |
| 102 | + |
| 103 | +| Question | Authoritative source | |
| 104 | +|---|---| |
| 105 | +| What agents does this operator run? | `/.well-known/adcp-agents.json` (this file) | |
| 106 | +| What does a specific agent advertise to A2A clients? | `<agent-url>/.well-known/agent-card.json` | |
| 107 | +| Can a given agent sell my (a publisher's) inventory? | The publisher's [`/.well-known/adagents.json`](/docs/governance/property/adagents) — **always**. Listing in `adcp-agents.json` does not imply authorization. | |
| 108 | +| What is the correct URL or transport for an agent? | When `adcp-agents.json` and the agent's `agent-card.json` disagree on URL or transport, the agent-card wins. The manifest is a hint; the agent's self-description is canonical. | |
| 109 | + |
| 110 | +| Endpoint | Scope | Purpose | |
| 111 | +|---|---|---| |
| 112 | +| `/.well-known/adcp-agents.json` | **Multi-agent topology** for one origin | "What agents does this operator run, and where?" | |
| 113 | +| `/.well-known/agent-card.json` | **One agent's** A2A descriptor | "What does this single agent advertise to A2A clients?" Per-agent. For multi-agent operators this card describes one agent at a time and is fetched at each agent's base URL. | |
| 114 | +| `/.well-known/adagents.json` | **Publisher-side authorization** for inventory and signals | "Which sales/signals agents am I authorising to represent my properties or signals?" Orthogonal to topology discovery. | |
| 115 | +| `/.well-known/brand.json` | **Brand identity** for one domain | Orthogonal — declares brand identity, not agent topology. | |
| 116 | +| `/.well-known/oauth-authorization-server` | **OAuth metadata** | Orthogonal — RFC 8414. Used when `auth_hint` is `oauth`. | |
| 117 | + |
| 118 | +## Discovery chain |
| 119 | + |
| 120 | +`adcp-agents.json` answers *what agents exist*, not *how to call them*. Once a buyer has picked an agent URL, they walk the standard chain documented in [Calling an AdCP agent](/docs/protocol/calling-an-agent#discovery-chain): |
| 121 | + |
| 122 | +1. Pick the agent for the specialism you need from `/.well-known/adcp-agents.json`. |
| 123 | +2. **Agent card** (A2A) or **`tools/list`** (MCP) — returns tool *names*. AdCP MCP servers do not publish per-tool parameter schemas in `tools/list`; don't infer shape from there. |
| 124 | +3. **`get_adcp_capabilities`** — supported protocols, AdCP versions, and feature flags. |
| 125 | +4. **`get_schema(tool_name)`** *(when exposed; pending standardization in [#3057](https://github.com/adcontextprotocol/adcp/issues/3057))* — JSON Schema for a specific tool's request/response. |
| 126 | +5. **Bundled schemas** — every published AdCP version ships JSON Schemas signed via Sigstore; let your SDK's loader find them rather than hardcoding paths. |
| 127 | + |
| 128 | +Capability-aware filtering at the manifest level (e.g. "only list agents that support `create_media_buy`") is intentionally out of scope. The `specialisms[]` claim is enough to route, and `get_adcp_capabilities` is the canonical truth for per-tool support. |
| 129 | + |
| 130 | +## Caching and error semantics |
| 131 | + |
| 132 | +- **Cache-Control.** Public/test manifests MAY use `public, max-age=300`. Production manifests that include any non-public agents SHOULD use `private, max-age=60` and `Vary: Authorization` if the response varies on auth. |
| 133 | +- **Missing manifest (404).** Consumers SHOULD fall back to fetching `/.well-known/agent-card.json` at the same origin and treating the operator as a single-agent deployment. The absence of the manifest is not an error. |
| 134 | +- **Malformed JSON or schema-invalid manifest.** Consumers MUST NOT block startup on this fetch; treat parse / validation failure as "no topology hint available" and degrade to single-agent fallback or operator-supplied configuration. |
| 135 | +- **Cache poisoning via `X-Forwarded-Host`.** Operators serving the manifest behind a reverse proxy MUST either set the manifest's host explicitly (e.g. via a `BASE_URL` config) or trust forwarded-host headers only from known proxies. A misconfigured edge that echoes attacker-controlled `X-Forwarded-Host` into emitted `agents[].url` becomes a cache-poisoning vector. |
| 136 | + |
| 137 | +## For tooling authors |
| 138 | + |
| 139 | +Common consumers: |
| 140 | + |
| 141 | +- **Buyers** — replace "ask the publisher for a doc" with one fetch. Cache the manifest with a short TTL; respect `Cache-Control` in production. |
| 142 | +- **Conformance runners** — `adcp storyboard run --discover https://publisher.example` can replace `--agents-map` for the common case where a publisher hosts a multi-agent topology. |
| 143 | +- **AAO Verified** — certification can verify a publisher's declared topology in one fetch; per-specialism capability verification still happens via `get_adcp_capabilities` against each listed agent. |
| 144 | + |
| 145 | +When parsing, ignore unknown top-level fields and unknown fields inside `agents[].*` — the schema declares `additionalProperties: true` so operators MAY add custom metadata without breaking consumers. |
| 146 | + |
| 147 | +## Out of scope (v1) |
| 148 | + |
| 149 | +- **Per-agent signed manifests.** TLS chain trust on the origin is sufficient for v1; per-entry signatures may follow. |
| 150 | +- **Capability-aware filtering at the manifest level.** Operators advertise full claims; runners fetch each agent's `get_adcp_capabilities` for the per-specialism detail. |
| 151 | +- **Versioning beyond a top-level `version` field.** Future revisions are additive within the `1.x` line; a `2.x` revision will be served at a different well-known path or signalled via media-type parameter. |
| 152 | + |
| 153 | +## Related |
| 154 | + |
| 155 | +- [Calling an AdCP agent](/docs/protocol/calling-an-agent) — the discovery chain after you've picked an agent URL. |
| 156 | +- [Required tasks by protocol](/docs/protocol/required-tasks) — canonical specialism names. |
| 157 | +- [`get_adcp_capabilities`](/docs/protocol/get_adcp_capabilities) — first call against any newly discovered agent. |
| 158 | +- [`adagents.json`](/docs/governance/property/adagents) — publisher-side authorization, the trust anchor for cross-origin agent calls. |
| 159 | +- [URL canonicalization](/docs/reference/url-canonicalization) — required when comparing manifest URLs against any authorization signal. |
0 commit comments