diff --git a/docs/protocol.md b/docs/protocol.md index d40ed916..47ae2c32 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -2,14 +2,14 @@ # Support for the MCP base protocol 1. [Lifecycle](#lifecycle) - 1. [Discovery (`server/discover`)](#discovery-(server/discover)) - 1. [Per-request `_meta` keys](#per-request-meta-keys) - 1. [Per-response `_meta` keys](#per-response-meta-keys) - 1. [Subscriptions (`subscriptions/listen`)](#subscriptions-(subscriptions/listen)) + 1. [Discovery](#discovery) + 1. [Per-request metadata keys](#per-request-metadata-keys) + 1. [Per-response metadata keys](#per-response-metadata-keys) + 1. [Subscriptions](#subscriptions) 1. [Transports](#transports) 1. [Stdio Transport](#stdio-transport) 1. [Streamable Transport](#streamable-transport) - 1. [SSE Transport (legacy)](#sse-transport-(legacy)) + 1. [Legacy SSE Transport](#legacy-sse-transport) 1. [Custom transports](#custom-transports) 1. [Concurrency](#concurrency) 1. [Authorization](#authorization) @@ -111,7 +111,7 @@ func Example_lifecycle() { } ``` -### Discovery (`server/discover`) +### Discovery Introduced in `2026-07-28` by [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575), @@ -128,7 +128,7 @@ request. Servers implementing `2026-07-28` MUST implement it. fails or the server does not support the latest version, the client falls back to the legacy `initialize` handshake. -### Per-request `_meta` keys +### Per-request metadata keys When the negotiated protocol version is `2026-07-28` or later, every request carries these keys inside its `_meta` map (constants live in @@ -147,7 +147,7 @@ request, and populates `clientInfo` when configured with an `*Implementation` `ServerRequest[P].ProtocolVersion()`, `ServerRequest[P].ClientInfo()`, and `ServerRequest[P].ClientCapabilities()`. -### Per-response `_meta` keys +### Per-response metadata keys Under the same protocol version, servers SHOULD identify themselves on every response. The SDK populates this @@ -157,7 +157,7 @@ key automatically on every outgoing response: |---|---|---|---| | `MetaKeyServerInfo` | `io.modelcontextprotocol/serverInfo` | `*Implementation` | No | -### Subscriptions (`subscriptions/listen`) +### Subscriptions Introduced in `2026-07-28` by [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575), @@ -343,7 +343,7 @@ _See [examples/server/distributed](https://github.com/modelcontextprotocol/go-sd an example using stateless mode to implement a server distributed across multiple processes._ -### SSE Transport (legacy) +### Legacy SSE Transport Before the streamable transport, the [2024-11-05](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports) diff --git a/docs/server.md b/docs/server.md index 9eb4ec22..966da750 100644 --- a/docs/server.md +++ b/docs/server.md @@ -241,7 +241,7 @@ to be notified of changes to subscribed resources. On `2026-07-28` and later sessions the SDK delivers these notifications over a `subscriptions/listen` stream instead of the legacy `resources/subscribe` RPC; see [Subscriptions -(`subscriptions/listen`)](protocol.md#subscriptions-subscriptionslisten) +(`subscriptions/listen`)](protocol.md#subscriptions) for the wire-level details. **Server-side**: diff --git a/internal/docs/protocol.src.md b/internal/docs/protocol.src.md index 1eb3c2f3..446b5558 100644 --- a/internal/docs/protocol.src.md +++ b/internal/docs/protocol.src.md @@ -55,7 +55,7 @@ it is the client's responsibility to end the session. %include ../../mcp/mcp_example_test.go lifecycle - -### Discovery (`server/discover`) +### Discovery Introduced in `2026-07-28` by [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575), @@ -72,7 +72,7 @@ request. Servers implementing `2026-07-28` MUST implement it. fails or the server does not support the latest version, the client falls back to the legacy `initialize` handshake. -### Per-request `_meta` keys +### Per-request metadata keys When the negotiated protocol version is `2026-07-28` or later, every request carries these keys inside its `_meta` map (constants live in @@ -91,7 +91,7 @@ request, and populates `clientInfo` when configured with an `*Implementation` `ServerRequest[P].ProtocolVersion()`, `ServerRequest[P].ClientInfo()`, and `ServerRequest[P].ClientCapabilities()`. -### Per-response `_meta` keys +### Per-response metadata keys Under the same protocol version, servers SHOULD identify themselves on every response. The SDK populates this @@ -101,7 +101,7 @@ key automatically on every outgoing response: |---|---|---|---| | `MetaKeyServerInfo` | `io.modelcontextprotocol/serverInfo` | `*Implementation` | No | -### Subscriptions (`subscriptions/listen`) +### Subscriptions Introduced in `2026-07-28` by [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575), @@ -267,7 +267,7 @@ _See [examples/server/distributed](https://github.com/modelcontextprotocol/go-sd an example using stateless mode to implement a server distributed across multiple processes._ -### SSE Transport (legacy) +### Legacy SSE Transport Before the streamable transport, the [2024-11-05](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports) diff --git a/internal/docs/server.src.md b/internal/docs/server.src.md index cbc56893..8ad704a7 100644 --- a/internal/docs/server.src.md +++ b/internal/docs/server.src.md @@ -87,7 +87,7 @@ to be notified of changes to subscribed resources. On `2026-07-28` and later sessions the SDK delivers these notifications over a `subscriptions/listen` stream instead of the legacy `resources/subscribe` RPC; see [Subscriptions -(`subscriptions/listen`)](protocol.md#subscriptions-subscriptionslisten) +(`subscriptions/listen`)](protocol.md#subscriptions) for the wire-level details. **Server-side**: diff --git a/internal/docs/toc_test.go b/internal/docs/toc_test.go new file mode 100644 index 00000000..23b02e78 --- /dev/null +++ b/internal/docs/toc_test.go @@ -0,0 +1,70 @@ +// Copyright 2025 The Go MCP SDK Authors. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package docs + +import ( + "os" + "path/filepath" + "regexp" + "strings" + "testing" +) + +// Headings are slugged twice by two different tools: weave writes the %toc +// links, and the renderer (GitHub, and mkdocs on go.sdk.modelcontextprotocol.io) +// assigns the heading ids. The two agree on plain words, and disagree on +// punctuation: weave keeps "(", ")" and "/" and drops "_", the renderers drop +// the first three and keep the last. A heading carrying any of them therefore +// gets a table-of-contents entry that lands nowhere. +// +// This test reads the generated docs and checks every %toc entry against the +// ids the renderers will actually produce. + +var ( + headingRE = regexp.MustCompile(`(?m)^(#{1,6})\s+(.*?)\s*$`) + tocLineRE = regexp.MustCompile(`(?m)^\s*1\. \[(.*?)\]\((#.*?)\)\s*$`) + dropRE = regexp.MustCompile(`[^\w\s-]`) + spaceRE = regexp.MustCompile(`[-\s]+`) +) + +// renderedID reproduces the anchor GitHub and python-markdown's toc extension +// derive from heading text: take the text, drop everything that is not a word +// character, whitespace or a hyphen, lowercase it, and join on hyphens. +func renderedID(heading string) string { + s := strings.ReplaceAll(heading, "`", "") + s = dropRE.ReplaceAllString(s, "") + s = strings.ToLower(strings.TrimSpace(s)) + return spaceRE.ReplaceAllString(s, "-") +} + +func TestTOCAnchorsResolve(t *testing.T) { + files, err := filepath.Glob(filepath.Join("..", "..", "docs", "*.md")) + if err != nil { + t.Fatal(err) + } + if len(files) == 0 { + t.Fatal("no generated docs found") + } + for _, file := range files { + data, err := os.ReadFile(file) + if err != nil { + t.Fatal(err) + } + text := string(data) + + ids := make(map[string]bool) + for _, m := range headingRE.FindAllStringSubmatch(text, -1) { + ids[renderedID(m[2])] = true + } + for _, m := range tocLineRE.FindAllStringSubmatch(text, -1) { + label, target := m[1], strings.TrimPrefix(m[2], "#") + if !ids[target] { + t.Errorf("%s: table-of-contents entry %q points at #%s, which no heading produces "+ + "(want #%s); drop (, ), / and _ from the heading", + filepath.Base(file), label, target, renderedID(label)) + } + } + } +}