Skip to content

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:

{"@context": "https://www.w3.org/2022/wot/discovery",
 "things": ["https://host/agents/foo", ...]}

Reading the normative spec text, this does not match the canonical /.well-known/wot shape:

  • /.well-known/wot is 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 implies application/json / application/ld+json).
  • @context on a TD is the TD context https://www.w3.org/2022/wot/td/v1.1, optionally extended with the discovery context https://www.w3.org/2022/wot/discovery when 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) /things listing 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/list remains the canonical agent-to-agent / tool-invocation surface (ADR-0008).
  • /capabilities remains the HTTP projection of the rich capability manifest (ADR-0005).
  • /.well-known/wot is 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, MCP tools/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, @context extensions, 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/wot reveals 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-description packages) 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 (/things listing, POST /things registration, 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

  1. Use /capabilities as the discovery path. Rejected: non-standard name; WoT clients don't probe it.
  2. 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.
  3. 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 the links[] to each AgentCard IRI individually.
  4. Bare IRI-array response shape (the task's straw-man). Rejected after checking the spec: /.well-known/wot mandates a single TD, not an IRI array. The self-describing TD with links[] 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-Control headers should the endpoint set? Agent sets churn during development but are static in production. Proposal: Cache-Control: no-cache in M1, revisit once agent hot-reload semantics are nailed down.
  • Cross-origin. Should /.well-known/wot emit Access-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.