What this is. The runnable discovery subset of webGCP interop for implementers that publish discovery surfaces but do not yet serve /webgcp/v0/query. The full L0 suite (/conformance/v0.1/ — WGCP-L0-001/002/003, 27 assertions, runner.py) requires a query endpoint; this checklist is what an implementation can prove before that endpoint exists. Passing it is not an L0 claim — it is the verified precondition that a restricted-domain implementer established in practice (2026-05-21), made repeatable.
Scope note (carrier precedence). ⚠ CORRECTED 2026-07-28. An earlier revision of this note called the A2A agent card's extensions.webgcp block "the canonical carrier" and described /.well-known/webgcp as a back-compat mirror. That was a drafting error and is withdrawn. It inverted the normative order. The precedence is:
/.well-known/webgcp descriptor at L0+, and §5.1.1 MUSTs the _webgcp.<host> SVCB record. Frozen text is byte- and URL-stable; no later document demotes a frozen MUST.What an A2A-native implementer actually does is unchanged, and this checklist already tested it correctly: the agent card carries an extensions.webgcp block whose descriptor member points at /.well-known/webgcp, and the descriptor holds the payload — spec_version, server_id, conformance_level, endpoints (D-2.A03, and the worked run below). The agent card is a discovery breadcrumb; the descriptor is the carrier. The checklist covers both surfaces; D-3 asserts their agreement.
GET /.well-known/webgcp)¶| # | Assertion | How |
|---|---|---|
| D-1.A01 | HTTP 200 with Content-Type: application/json |
curl -si http://<host>/.well-known/webgcp |
| D-1.A02 | spec_version present — "0.1" (plain v0.1 server) or "0.2-profile" with profile_of: "webmcp" (profile carrier) |
jq '.spec_version, .profile_of' |
| D-1.A03 | server_id is a typed URN (urn:webgcp:<id>) |
jq '.server_id' |
| D-1.A04 | conformance_level declared honestly — "L0-restricted-domain" MUST carry a restricted_domain_reason |
jq '.conformance_level, .restricted_domain_reason' |
| D-1.A05 | endpoints object present; absent capabilities are explicit null, never omitted or faked (e.g., endpoints.query: null until a query endpoint ships) |
jq '.endpoints' |
| D-1.A06 | Cacheable: Cache-Control max-age between 3600 and 86400 (v0.1 §5.1) |
response headers |
| D-1.N01 | No claimed-but-dead URLs: every non-null URL in endpoints answers non-404 |
curl each |
| D-1.N02 | No conformance_level above what the suite can verify (an L0 claim without a query endpoint is an over-claim — declare L0-restricted-domain or lower) |
review |
GET /a2a/.well-known/agent.json)¶| # | Assertion | How |
|---|---|---|
| D-2.A01 | HTTP 200, valid JSON, name present |
jq '.name' |
| D-2.A02 | extensions.webgcp block present (the profile carrier) |
jq '.extensions.webgcp' |
| D-2.A03 | extensions.webgcp.descriptor points at the D-1 surface (/.well-known/webgcp) |
jq '.extensions.webgcp.descriptor' |
| # | Assertion | How |
|---|---|---|
| D-3.A01 | Descriptor endpoints.agent_card and the agent card's extensions.webgcp.descriptor reference each other (the mirror closes) |
compare D-1/D-2 |
| D-3.A02 | Capability lists agree: descriptor supported_features ⊆ agent-card declared capabilities (or vice versa). Tie-break: the descriptor wins — it is the surface v0.1 §5.1 MUSTs, and the agent-card block is MAY at every level (corrected 2026-07-28; the prior "canonical carrier wins" left this undefined) |
compare |
| # | Assertion | How |
|---|---|---|
| D-4.A01 | If the host is DNS-controllable: _webgcp.<host> SVCB answers (then run full L0-003 via runner.py). If restricted-domain (*.run.app, *.web.app): the descriptor's restricted_domain_reason says so — that is the pass |
DoH: https://cloudflare-dns.com/dns-query?name=_webgcp.<host>&type=64 (Cloudflare primary; Google DoH unreliable for SVCB per the 2026-05-16 observation) |
A real pass, recorded 2026-05-21, with the implementer's name generalized. After the two-line application wiring described in the implementer's onboarding guide:
uv run uvicorn main:app --reload --port 8000
curl -s http://localhost:8000/.well-known/webgcp | jq '.spec_version, .profile_of, .conformance_level, .endpoints.query'
# → "0.2-profile" / "webmcp" / "L0-restricted-domain" / null (D-1.A02/A04/A05 pass)
curl -s http://localhost:8000/a2a/.well-known/agent.json | jq '.name, .extensions.webgcp.descriptor'
# → "example-agent" / "/.well-known/webgcp" (D-2.A01/A03 pass)
Note the direction in the second call: the agent card's extensions.webgcp.descriptor points at
/.well-known/webgcp. The descriptor is the carrier; the card is the breadcrumb.
D-4 = N/A-by-declaration (a managed serverless host with no controllable DNS zone;
restricted_domain_reason present).
Full L0 needs /webgcp/v0/query + a server descriptor advertising manifests_available and bundle_contracts_available so runner.py can exercise WGCP-L0-001/002. For a restricted-domain implementer this is a named ask in its own request-for-development doc (the implementing team owns it). Until it ships, this checklist is the interop gate of record for the discovery subset.
the published conformance suite at /conformance/v0.1/runner.py (the full suite) · the published v0.1 spec §5.1 + §5.1.1 · the second-implementer's onboarding README (the P1 surfaces + P2–P4 roadmap) · the DNSAID-composition deferral rationale (why webgcp._agents.<host> NXDOMAIN is conformant today).