Skip to content

Commit b6c0e56

Browse files
basgrclaude
andcommitted
Adopt three items from specification.website cross-review
After comparing cf-webmcp's surfaces against jdevalk/specification.website (Joost de Valk's worked-example agent-readiness reference), land the three items that fit our scope: 1. docs/scope.md: add A2A agent cards under "Alternative protocols" (presupposes a running A2A endpoint; cf-webmcp publishes browser-side WebMCP, not A2A). Add a new "Adjacent agent-readiness surfaces" sub-section explicitly framing markdown source endpoints, robots.txt Content-Signal, A2A, MCP server cards, web-bot-auth, schemamap and DNS-AID as out of scope, and pointing readers at specification.website as the worked-example reference. 2. README.md: cite specification.website in Acknowledgements as an independent reference that converges on the same Agent Skills Discovery, RFC 9727 api-catalog, rel="describedby" llms.txt, and rel="agent-skills" conventions cf-webmcp ships. 3. src/link-header.ts: add RFC 8288 title="..." human-readable metadata to every advertised rel (webmcp, api-catalog, agent-skills, describedby). The describedby title reuses specification.website's wording verbatim ("Site index for LLMs") so peer publishers serving the same target carry identical Link parameters. No protocol impact: parsers that don't care ignore the parameter. Stayed out of: Content-Signal robots.txt augmentation (not WebMCP- specific; publisher AI-policy territory) and Extra rel values for non-agent surfaces (sitemap, security, alternate/rss; same scope reason). 227 + 24 tests green, typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b1542b9 commit b6c0e56

4 files changed

Lines changed: 40 additions & 6 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ Full reference docs live in [`docs/`](docs/):
122122
- Suganthan Mohanadasan, ["WebMCP: I Made My Website AI Agent Ready"](https://suganthan.com/blog/webmcp-implementation-guide/) - the implementation guide that informed the publisher-side discovery patterns.
123123
- [jasonjmcghee/WebMCP](https://github.com/jasonjmcghee/WebMCP) - the fallback widget that bridges desktop MCP clients to WebMCP sites.
124124
- [webmachinelearning/webmcp](https://github.com/webmachinelearning/webmcp) - the W3C draft.
125+
- [`specification.website`](https://specification.website/) (Joost de Valk) - independent worked-example reference for agent-ready websites; converges on the same `/.well-known/agent-skills/index.json` (Cloudflare Discovery RFC v0.2.0), RFC 9727 `api-catalog`, `rel="describedby"` for llms.txt, and `rel="agent-skills"` conventions that cf-webmcp ships.
125126

126127
## License
127128

docs/scope.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ The following are real, useful things, but they are not WebMCP and would dilute
3232
### Alternative protocols
3333

3434
- **MCP server cards.** MCP is a JSON-RPC protocol distinct from WebMCP. cf-webmcp does not publish MCP server descriptors (`/.well-known/mcp/server-card.json`) and does not run a streamable HTTP MCP endpoint. Desktop MCP clients bridge into a WebMCP site via the fallback widget, which is a runtime concern, not a discovery one. Publishers running cf-webmcp *and* a separately-deployed MCP JSON-RPC endpoint at `POST /mcp` should be aware that cf-webmcp claims `GET /mcp` for its landing page by default; the two paths share a URL but differ by method. cf-webmcp's current router does not split on method, so a publisher needing both should move the landing page (`[webmcp_landing].path = "/pair"`) to free `/mcp` for the MCP JSON-RPC endpoint.
35+
- **A2A agent cards.** [Agent-to-Agent](https://a2a-protocol.org/) is a JSON-RPC protocol for agent-to-agent communication; its `/.well-known/agent-card.json` discovery file presupposes a running A2A endpoint. cf-webmcp publishes browser-side WebMCP, not an A2A endpoint, so emitting the card would be misleading. Publishers running A2A alongside can publish the card from origin.
3536
- *(Removed in v0.3.0)* ~~Agent Skills (Anthropic).~~ Originally listed as out-of-scope on the grounds that the manifest covered the same ground. Reversed in v0.3.0 after observing real-world adoption (Cloudflare Browser Run docs, isitagentready.com audits) and that the *operational hints* niche - "when to use which tool", "common pitfalls" - was not actually covered by either the structured manifest or the prose `agents.md`. cf-webmcp now publishes a `SKILL.md` at `/.well-known/agent-skills/site/SKILL.md`. See [`docs/agent-skills.md`](agent-skills.md).
3637
- **OpenAPI / AsyncAPI generation.** The WebMCP manifest already describes our tools in a WebMCP-native shape. cf-webmcp does not project that into OpenAPI.
3738

@@ -58,6 +59,10 @@ The following are real, useful things, but they are not WebMCP and would dilute
5859

5960
- **Image transformation, A/B testing, analytics, edge auth, geo-routing.** Worth doing, not by cf-webmcp.
6061

62+
### Adjacent agent-readiness surfaces
63+
64+
The broader "agent-ready website" landscape includes surfaces beyond WebMCP discovery: markdown source endpoints with `Accept: text/markdown` negotiation, AI-crawler preferences via robots.txt `Content-Signal`, A2A agent cards, MCP server cards, web-bot-auth, schemamap, DNS-AID. [`specification.website`](https://specification.website/) (Joost de Valk) documents and ships these as a worked example. cf-webmcp deliberately covers only the subset that is WebMCP discovery; publishers wanting the rest can layer them on top of the proxied origin, or take them straight from that reference.
65+
6166
## How feature requests get triaged
6267

6368
If a proposed feature is on the **in scope** list above, file an issue and let's discuss.

src/link-header.test.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ describe("buildLinkHeader", () => {
6464
const value = buildLinkHeader(makeConfig());
6565
expect(value).toContain('<https://example.com/.well-known/api-catalog>; rel="api-catalog"');
6666
expect(value).toContain('<https://example.com/.well-known/webmcp.json>; rel="webmcp"');
67-
// Comma-separated per RFC 8288
68-
expect(value).toMatch(/rel="webmcp", <.*>; rel="api-catalog"/);
67+
// Comma-separated per RFC 8288; allow any params (e.g. title) on the
68+
// webmcp entry before the comma that delimits the next entry.
69+
expect(value).toMatch(/rel="webmcp"[^,]*,\s*<[^>]+>;\s*rel="api-catalog"/);
6970
});
7071

7172
it("omits api-catalog when features.api_catalog is false", () => {
@@ -127,6 +128,19 @@ describe("buildLinkHeader", () => {
127128
);
128129
expect(value).not.toContain('rel="describedby"');
129130
});
131+
132+
it("emits RFC 8288 title parameter on every advertised rel", () => {
133+
const value = buildLinkHeader(makeConfig());
134+
// webmcp: our private rel, our wording.
135+
expect(value).toContain('rel="webmcp"; title="WebMCP tool catalogue"');
136+
// api-catalog: RFC 9727 / 9264.
137+
expect(value).toContain('rel="api-catalog"; title="API catalogue (RFC 9727 Linkset)"');
138+
// agent-skills: Anthropic SKILL.md.
139+
expect(value).toContain('rel="agent-skills"; title="Agent Skill (SKILL.md)"');
140+
// describedby: title matches specification.website (Joost de Valk) for the
141+
// same target, so peer publishers see identical wording on the same rel.
142+
expect(value).toContain('rel="describedby"; type="text/markdown"; title="Site index for LLMs"');
143+
});
130144
});
131145

132146
describe("mergeLinkHeader", () => {

src/link-header.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,41 @@
88
* When the api_catalog feature is enabled and not in passthrough mode,
99
* additionally advertises rel="api-catalog" (IANA-registered, RFC 9727)
1010
* so generic crawlers that scan for standard rels can find the catalog.
11+
*
12+
* Each entry carries an optional `title="..."` parameter (RFC 8288 sec 3.4.1),
13+
* human-readable metadata for the link target. No protocol impact: parsers
14+
* that don't care simply ignore it. The describedby title matches the
15+
* wording used by specification.website (Joost de Valk) for the same target,
16+
* for free interop with peer publishers.
1117
*/
1218

1319
import type { Config } from "./config-types";
1420

1521
export function buildLinkHeader(config: Config): string {
1622
const base = config.site.public_url ?? `https://${config.site.domain}`;
17-
const entries: string[] = [`<${base}${config.manifest.path}>; rel="webmcp"`];
23+
const entries: string[] = [
24+
`<${base}${config.manifest.path}>; rel="webmcp"; title="WebMCP tool catalogue"`,
25+
];
1826
if (config.features.api_catalog && config.api_catalog.mode !== "passthrough") {
19-
entries.push(`<${base}${config.api_catalog.path}>; rel="api-catalog"`);
27+
entries.push(
28+
`<${base}${config.api_catalog.path}>; rel="api-catalog"; title="API catalogue (RFC 9727 Linkset)"`,
29+
);
2030
}
2131
if (config.features.agent_skills && config.agent_skills.mode !== "passthrough") {
2232
// Not (yet) IANA-registered; matches the convention used in Anthropic's
2333
// Agent Skills format. Same pattern as our existing private rel="webmcp".
24-
entries.push(`<${base}${config.agent_skills.path}>; rel="agent-skills"`);
34+
entries.push(
35+
`<${base}${config.agent_skills.path}>; rel="agent-skills"; title="Agent Skill (SKILL.md)"`,
36+
);
2537
}
2638
if (config.features.llms_txt && config.llms_txt.mode !== "passthrough") {
2739
// IANA-registered general "describedby" relation (RFC 8288). Points at
2840
// /llms.txt - a publisher description in markdown. Generic agent-aware
2941
// scanners that anchor on registered rel-types only (not our private
3042
// rel="webmcp") find a description of the site through this entry.
31-
entries.push(`<${base}${config.llms_txt.path}>; rel="describedby"; type="text/markdown"`);
43+
entries.push(
44+
`<${base}${config.llms_txt.path}>; rel="describedby"; type="text/markdown"; title="Site index for LLMs"`,
45+
);
3246
}
3347
return entries.join(", ");
3448
}

0 commit comments

Comments
 (0)