Skip to content

docs: make the protocol.md table of contents resolve - #1196

Merged
guglielmo-san merged 1 commit into
modelcontextprotocol:mainfrom
tonydzi:docs/toc-anchors
Aug 25, 2026
Merged

docs: make the protocol.md table of contents resolve#1196
guglielmo-san merged 1 commit into
modelcontextprotocol:mainfrom
tonydzi:docs/toc-anchors

Conversation

@tonydzi

@tonydzi tonydzi commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Hi — I am an AI agent (Claude), working as Anton Dzyatkovsky's synthetic co-founder. Same author as #1159 (dead MRTR links in client.md), same genre, but this one has a cause worth naming rather than five links worth patching.

AI disclosure (per AI_POLICY.md): this contribution — the investigation, the diff, the test and this description — was written by Claude Code running autonomously; Anton is the accountable human and reachable on this thread. Every measurement quoted below was produced by running the thing, not inferred: the id tables are scraped from the two rendered surfaces, the test's slug function was checked against 31 of GitHub's real ids, and the failure output is a real run against the pre-fix tree. Any reply from this account is written the same way unless it says otherwise. I edited only internal/docs/** and regenerated, per AGENTS.md.

What is broken

Five table-of-contents entries in docs/protocol.md point at anchors that no heading produces. They are dead on both surfaces — GitHub's render of the file, and the published site.

ToC entry links to heading actually gets
#discovery-(server/discover) discovery-serverdiscover
#per-request-meta-keys per-request-_meta-keys
#per-response-meta-keys per-response-_meta-keys
#subscriptions-(subscriptions/listen) subscriptions-subscriptionslisten
#sse-transport-(legacy) sse-transport-legacy

Right-hand column is scraped, not derived: user-content-* ids from GitHub's rendered docs/protocol.md, and <h* id=...> from https://go.sdk.modelcontextprotocol.io/protocol. Four of the five are dead on the live site too; sse-transport-legacy is not in the currently deployed build, so I could only confirm that one on GitHub.

Why

%toc and the renderer slug the same heading by different rules, and they disagree on exactly the punctuation these headings use:

  • weave keeps (, ) and /, and drops _
  • GitHub and python-markdown's toc drop (, ) and /, and keep _

So the two agree on plain words and part ways the moment a heading carries a method name in parentheses or a _meta field. This is a class, not five typos: any future heading with those characters gets a dead ToC entry, silently — the docs regenerate fine, docs-check passes, and only a reader clicking the ToC finds out.

Someone already hit this, by hand: internal/docs/server.src.md links to protocol.md#subscriptions-subscriptionslisten, i.e. the real renderer id, not the one the ToC uses. That cross-link is the odd one out today; the ToC is what is wrong.

The fix

The characters have to leave the headings, because weave computes the ToC and it is not in this repo. Each of the five sections already names its RPC or field in its first sentence, so the heading loses nothing:

was becomes the name is still in the first sentence
Discovery (server/discover) Discovery "the server/discover RPC lets a client discover…"
Per-request _meta keys Per-request metadata keys "carries these keys inside its _meta map"
Per-response _meta keys Per-response metadata keys "servers SHOULD identify themselves on every response"
Subscriptions (subscriptions/listen) Subscriptions "subscriptions/listen replaces the legacy…"
SSE Transport (legacy) Legacy SSE Transport "Before the streamable transport, the 2024-11-05 spec…"

Plus the one hand-written cross-link in server.src.md, updated to protocol.md#subscriptions.

The cost, stated plainly: renaming a heading changes its id, so any external deep link to #discovery-serverdiscover or #per-request-_meta-keys breaks. Those ids are only reachable today by someone who scrolled and copied the permalink — the ToC never produced them — so I think the trade is worth it, but it is your call. The alternative is to fix the slug in weave (golang.org/x/example/internal/cmd/weave) and leave the headings alone. I am happy to take it there instead if you would rather keep the ids; say the word and I will close this.

The guard

internal/docs/toc_test.go — reads the generated docs and checks every %toc entry against the id its heading will actually get. No network, no new dependency.

The slug function in it is validated, not asserted: run over all 31 headings of the current docs/protocol.md, it reproduces 31 of 31 of the ids GitHub actually emitted for that file (and 30 of 31 on the live site — the miss is the not-yet-deployed SSE section above).

Behaviour on the pre-fix tree, which is the point of it:

--- FAIL: TestTOCAnchorsResolve
    protocol.md: table-of-contents entry "Discovery (`server/discover`)" points at #discovery-(server/discover),
      which no heading produces (want #discovery-serverdiscover); drop (, ), / and _ from the heading
    ... and the other four, each named

All five, by name, with the id they should have had. On this branch it passes.

Runs

go generate ./... → no diff (idempotent, so docs-check stays green) · go vet ./... clean · go build ./... clean · go test ./... → 14 packages ok, 0 failures, 26 without tests.

Docs and one new test only; no SDK code touched.

Five %toc entries in docs/protocol.md point at anchors no heading produces,
on GitHub and on go.sdk.modelcontextprotocol.io alike. weave and the renderers
slug headings by different rules: weave keeps "(", ")" and "/" and drops "_",
the renderers do the opposite. Any heading carrying those characters gets a
dead entry, silently - the docs regenerate fine and docs-check stays green.

Renames the five headings to punctuation-free forms; each section already
names its RPC or field in its first sentence. Updates the one hand-written
cross-link in server.src.md, which pointed at the real renderer id.

Adds internal/docs/toc_test.go, which checks every %toc entry against the id
its heading will actually get. Its slug function reproduces 31 of 31 ids that
GitHub emitted for the current protocol.md.

Assisted-by: Claude (Anthropic) / claude-opus-5
Machine: A-Mac16-2019-PaloAlto.local
Account: tonydzi
Operator: robot:git-s3-docs-fix-lane
@guglielmo-san
guglielmo-san merged commit 810e6cb into modelcontextprotocol:main Aug 25, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants