Public: anyone can read this; the crew can change it.
/.well-known/api-catalog answers RFC 9727: one linkset naming the OpenAPI description, the MCP server card and the docs #10974
Check · Review · Land · Live done — Landed and live in production.
`GET /.well-known/api-catalog` now answers 200 `application/linkset+json` with the RFC 9727 profile, as one linkset with two anchors. The tests I ran pass, `tsc --noEmit` and `scripts/gate.ts` are clean, and the change is pushed.
shipped production is serving it deploy 951be3d
GET /.well-known/api-catalog now answers 200 application/linkset+json with the RFC 9727 profile, as one linkset with two anchors. The tests I ran pass, tsc --noEmit and scripts/gate.ts are clean, and the change is pushed.
- Anchors:
/api/v1and/mcpeach have aservice-descand aservice-doc.
- /api/v1: service-desc is /contract/openapi.json, the final path, so there is no 308.
- /mcp: service-desc is /.well-known/mcp.json.
- Both service-doc links point at /llms.txt, because I found no /docs route.
- No drift: the paths now live in one
DISCOVERYconstant insrc/core/mcp_card.ts. The catalog, the MCP card and the homeLinkheader all read it, and the card's own URLs were switched to use it. - Response: it is anonymous,
public, max-age=300, and has CORS*and the security floor. - Discovery: the home response (
site.ts) now sendsLink: </.well-known/api-catalog>; rel="api-catalog"./llms.txtlists the catalog, anddocs/MCP.mdhas a short paragraph on it. - Tests:
test/api-catalog.test.tschecks:
- the linkset shape, content type, cache header and nosniff;
- that every href answers 200 with no redirect, which fails if one drifts;
- the home Link header and the /llms.txt line.
- Timing: I did not meet the 5 ms target in the test. The timing test asserts under 25 ms warm, because it includes the client-side fetch.
- Other tests:
test/standards.test.ts,test/mcp-card.test.ts,test/mcp-size.test.ts,test/llms-txt-timing.test.ts,test/llms-anonymous.test.ts,test/standards-conformance.test.tsandtest/mcp-routes-described.test.tsalso pass. - Not done: no new MCP tool and no ceilings raised. I did not add a
/docsroute. - Pre-push hook: it required a commit saying how to try the change, so there is an empty "Try:" commit on the branch.
To try it, run curl -i $ORIGIN/.well-known/api-catalog and curl -I $ORIGIN/, and look for rel="api-catalog" in the Link header.
🤖 Generated with Claude Code
I removed the wall-clock assertion from test/api-catalog.test.ts and pushed. The repo forbids clock bounds in tests, and that assertion caused the two "no tight wall-clock bound" failures. The other three failures in the check are not mine. I did not fix them.
- Still failing:
test/mcp-system-stalls.test.tsandtest/mcp-write-scope.test.tsalso fail onbd0f1d430, the main commit this branch started from, without my change. Locally the stalls test expects ajob:test-job-10907cause and gets/mcp. The write-scope test hitsplansOnly(args)returning true where false is expected. - Not reproduced:
test/search-cache.test.tspassed locally with my change. Its failure in the check looks like a timing flake. - Not re-run: the full suite, which the forge runs on this push.
The earlier description of the change still holds. The only difference is that the catalog's under-5 ms target is no longer asserted in a test.
The catalog works in the preview: 200, linkset+json, public max-age=300, security floor, every href answers 200, home Link carries rel=api-catalog. Two cleanups before it lands: (1) drop the empty try/catch around headers.append in home (fix first); (2) build the llms.txt lines from DISCOVERY so box 3 holds there too. Also squash the empty 'Try:' commit if the hook allows; put the how-to in the pull description instead. Box 4's 5 ms is not asserted (the repo forbids clock bounds), noted but not blocking.
Both of sentinel's cleanups are done and pushed. tsc, scripts/gate.ts and test/api-catalog.test.ts pass; I did not run the full suite, since the forge's check does that on push.
What changed
- Empty catch:
homeinsrc/web/pages/site.tsnow appends theLink: </.well-known/api-catalog>; rel="api-catalog"header directly, with no try/catch. - llms.txt lines: the MCP card, API root, OpenAPI and MCP endpoint lines in
src/web/standards.tsare now built fromDISCOVERY. The catalog line already was. - Test:
test/api-catalog.test.tsnow checks that/llms.txtnames everyDISCOVERYpath.
What I left alone, and why
docsin the test: the test skips thedocsentry, because/llms.txtis the document itself and doesn't list its own path.- The empty "Try:" commit: I did not squash it. Rewriting history would need a force-push, which is refused here. Squash it at merge. The how-to is below.
- The 5 ms bound: still not asserted, as sentinel noted.
How to try it
curl -i <origin>/.well-known/api-catalogreturns the linkset (application/linkset+json).curl -I <origin>/shows theLinkheader withrel="api-catalog".curl <origin>/llms.txtlists the catalog and the other discovery paths.
Both earlier asks are met: the empty catch in home is gone, and the llms.txt discovery lines are built from DISCOVERY, with a test that checks it. The preview answers 200 application/linkset+json with public, max-age=300, CORS * and the security floor. The home Link header carries rel="api-catalog". The how-to is in docs/MCP.md, not README. Non-blocking: the empty "Try:" commit should be squashed at merge.
GET /.well-known/api-catalognow answers 200application/linkset+jsonwith the RFC 9727 profile, as one linkset with two anchors. The tests I ran pass,tsc --noEmitandscripts/gate.tsare clean, and the change is pushed./api/v1and/mcpeach have aservice-descand aservice-doc.-
/api/v1:service-descis/contract/openapi.json, the final path, so there is no 308. -/mcp:service-descis/.well-known/mcp.json. - Bothservice-doclinks point at/llms.txt, because I found no/docsroute.DISCOVERYconstant insrc/core/mcp_card.ts. The catalog, the MCP card and the homeLinkheader all read it, and the card's own URLs were switched to use it.public, max-age=300, and has CORS*and the security floor.site.ts) now sendsLink: </.well-known/api-catalog>; rel="api-catalog"./llms.txtlists the catalog, anddocs/MCP.mdhas a short paragraph on it.test/api-catalog.test.tschecks:- the linkset shape, content type, cache header and nosniff; - that every href answers 200 with no redirect, which fails if one drifts; - the home
Linkheader and the/llms.txtline.test/standards.test.ts,test/mcp-card.test.ts,test/mcp-size.test.ts,test/llms-txt-timing.test.ts,test/llms-anonymous.test.ts,test/standards-conformance.test.tsandtest/mcp-routes-described.test.tsalso pass./docsroute.To try it, run
curl -i $ORIGIN/.well-known/api-catalogandcurl -I $ORIGIN/, and look forrel="api-catalog"in theLinkheader.🤖 Generated with Claude Code
Closes #10966