ADR-0016: WoT Discovery endpoint at /.well-known/wot¶
- Status: Accepted (2026-04-19)
- Depends on: ADR-0015 (AgentCard ⊑
wot:Thing) — assumed Accepted - Related: ADR-0005 (rich capability manifest; MCP as projection), ADR-0008 (MCP primary, HTTP secondary)
- Supersedes: —
- Superseded by: —
Context¶
W3C Web of Things (WoT) Discovery defines a standard "Introduction" surface at the well-known URI /.well-known/wot. Any WoT-compliant client expects that path to produce a Thing Description (TD) it can follow — either the TD of the server itself, or a self-describing TD that links out to further Things.
ADR-0015 models Trails' AgentCard as a subclass of wot:Thing, so each registered agent already serializes to a valid TD. What the framework currently lacks is the directory surface that tells WoT ecosystems where to find those TDs.
Today Trails exposes:
GET /capabilities— bespoke JSON, the HTTP projection of the rich capability manifest (ADR-0005).- MCP
tools/list— MCP-native capability enumeration (ADR-0008). GET /health— liveness probe.
Neither /capabilities nor MCP is a W3C-standard discovery path. A generic WoT client landing on a Trails host has no conventional entry point and will not find the agents running there. The sibling project Fabrica already ships /.well-known/wot and (per the WoT-Discovery handoff convention) assumes Trails will too; aligning here keeps both projects mutually discoverable and avoids inventing a third convention.
WoT Discovery response shape — spec alignment¶
The task brief proposed a straw-man response of the form:
Reading the normative spec text, this does not match the canonical /.well-known/wot shape:
/.well-known/wotis an Introduction mechanism. The spec mandates that the response body MUST be a single Thing Description, not an array and not a bare IRI list.- Preferred Content-Type is
application/td+json(which impliesapplication/json/application/ld+json). @contexton a TD is the TD contexthttps://www.w3.org/2022/wot/td/v1.1, optionally extended with the discovery contexthttps://www.w3.org/2022/wot/discoverywhen registration metadata is embedded.- The IRI-array shape the straw-man describes is closer to — but still not identical to — the Thing Description Directory (TDD)
/thingslisting endpoint, which returns an array of full enriched TDs, not IRIs.
The pattern that is idiomatic for "host exposes N agents" is a self-describing TD at /.well-known/wot whose links[] array uses rel: "item" (or rel: "describedby") entries, one per registered AgentCard IRI. WoT clients follow the links to retrieve each AgentCard TD individually. This is the directory-hub idiom the straw-man was reaching for.
Decision¶
Trails exposes W3C WoT Discovery at GET /.well-known/wot, returning a self-describing Thing Description for the Trails host, whose links[] array enumerates the IRIs of all AgentCards registered on that host.
Concrete response:
- Content-Type:
application/td+json - Body: a TD of the form
{
"@context": [
"https://www.w3.org/2022/wot/td/v1.1",
"https://www.w3.org/2022/wot/discovery"
],
"@type": "Thing",
"id": "https://host.example/",
"title": "Trails host",
"security": "nosec_sc",
"securityDefinitions": {"nosec_sc": {"scheme": "nosec"}},
"links": [
{"rel": "item", "href": "https://host.example/agents/foo",
"type": "application/td+json"},
{"rel": "item", "href": "https://host.example/agents/bar",
"type": "application/td+json"}
]
}
The security field is a minimum-viable placeholder; production deployments will project the biscuit/DID stack (ADR-0010, ADR-0011) into the TD securityDefinitions once ADR-0015 nails the concrete shape. This ADR does not settle that mapping.
Where it lives¶
New route in python/src/trails/http_adapter.py, a single ~15-LOC function next to /health. It enumerates server.list_tools() (or the agent registry, once ADR-0015 lands an explicit AgentCard registry) and emits the JSON-LD body above. No new module, no new dependency.
Relationship to MCP and /capabilities¶
/.well-known/wot is a peer to MCP tools/list and to /capabilities, not a replacement for either:
- MCP
tools/listremains the canonical agent-to-agent / tool-invocation surface (ADR-0008). /capabilitiesremains the HTTP projection of the rich capability manifest (ADR-0005)./.well-known/wotis the ecosystem-discovery surface: "does a WoT client that has never heard of Trails find anything useful here?"
Rule of thumb: MCP for agent-to-agent invocation; WoT for ecosystem discovery. The three surfaces stay aligned because all three derive from the same underlying capability / AgentCard registry.
Consequences¶
Positive¶
- Standards-compliant discovery in ~15 lines. Any WoT Discovery client (Eclipse Thingweb, node-wot,
td-tools, etc.) finds Trails hosts without a Trails-specific shim. - Alignment with Fabrica and any future sibling project that assumes the well-known URI.
- Zero new dependency on MCP. The WoT surface works even if
[transport.mcp] enabled = false. - Cheap extension path. A future full-TDD surface at
/things(array of enriched AgentCard TDs) is a natural next step if and when Trails needs to act as a Thing Description Directory rather than just a host.
Negative¶
- Another surface to maintain. Three discovery-flavored endpoints (
/capabilities, MCPtools/list,/.well-known/wot) must stay mutually consistent. Mitigated by deriving all three from the single underlying registry. - TD shape is under-specified here. Concrete
securityDefinitions,@contextextensions, and affordance projection are left to ADR-0015 and follow-up work.
Security¶
- The endpoint enumerates AgentCard IRIs that are already reachable at
/capabilities. No new information is leaked and no new attack surface is introduced relative to today. - Unauthenticated probing of
/.well-known/wotreveals the count and IRIs of registered agents on the host. Operators who need to hide that must block the path at the reverse proxy — same knob as for/capabilities. - Content-Type is pinned to
application/td+json; the response is static JSON-LD with no user-supplied fields, so no injection vector.
Non-consequences¶
- No change to MCP or to
/capabilities. - No change to the rich capability manifest at
/.well-known/capabilities(ADR-0005). - No WoT-specific dependency (e.g.,
wot-thing-descriptionpackages) is added to the runtime; the adapter emits plain JSON-LD.
Revisit conditions¶
- If Trails needs to act as a Thing Description Directory (serve other hosts' TDs, not just its own), promote the endpoint to a full TDD (
/thingslisting,POST /thingsregistration,GET /things/{id}) per the WoT-Discovery Directory profile. - If WoT Discovery publishes a v1.2 context or changes the well-known path, track the change in a follow-up ADR.
- If ADR-0015 lands a security/identity mapping that materially changes the TD shape, fold it in here.
Alternatives considered¶
- Use
/capabilitiesas the discovery path. Rejected: non-standard name; WoT clients don't probe it. - Require MCP clients only. Rejected: excludes the entire WoT ecosystem (Eclipse Thingweb, node-wot, existing Fabrica clients) and contradicts ADR-0008's "HTTP always available" rule.
- Full TDD-style response at
/.well-known/wot(array of enriched TDs). Rejected: response bloat as the agent count grows, plus it conflates the Introduction role with the Directory listing role the spec keeps separate. Clients that want full TDs can follow thelinks[]to each AgentCard IRI individually. - Bare IRI-array response shape (the task's straw-man). Rejected after checking the spec:
/.well-known/wotmandates a single TD, not an IRI array. The self-describing TD withlinks[]is the idiomatic way to express "here are the Things I host."
Open questions¶
- Aggregation vs. self-registration. Does the framework auto-aggregate AgentCards from the existing registry (today:
TrailsMCPServer.list_tools()), or does ADR-0015 introduce an explicit AgentCard registry that agents register themselves into? The latter is cleaner and lets agents across processes / mounts contribute to the same/.well-known/wot. - Cache policy. What
Cache-Controlheaders should the endpoint set? Agent sets churn during development but are static in production. Proposal:Cache-Control: no-cachein M1, revisit once agent hot-reload semantics are nailed down. - Cross-origin. Should
/.well-known/wotemitAccess-Control-Allow-Origin: *? WoT clients in browsers need it; servers behind auth may want it off. Default on, configurable via[transport.http] cors_wot_discovery = true.