From 299c62a4ba545a4e5c7086754364f4c1f7785e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Karaaslan?= Date: Thu, 15 Oct 2026 05:20:58 +0300 Subject: [PATCH 1/4] fix(docs): add focused AuthInfo type link targeting --- docs/.vitepress/authInfoLinks.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 docs/.vitepress/authInfoLinks.ts diff --git a/docs/.vitepress/authInfoLinks.ts b/docs/.vitepress/authInfoLinks.ts new file mode 100644 index 0000000000..2e7b526db4 --- /dev/null +++ b/docs/.vitepress/authInfoLinks.ts @@ -0,0 +1,17 @@ +/** Stable anchor for the public AuthInfo interface, distinct from authInfo members. */ +export const AUTH_INFO_INTERFACE_ANCHOR = 'authinfo-interface'; + +const typePage = /^api\/index\/@modelcontextprotocol\/(?:client|server)\/index\.md$/; +const typeTarget = /(?:^|\/)index\/@modelcontextprotocol\/(?:client|server)\/index\.md$/; + +export function isAuthInfoInterfacePage(page: string): boolean { + return typePage.test(page); +} + +/** Rewrite only links whose *text* names the AuthInfo type, never member links. */ +export function correctAuthInfoTypeHref(href: string, label: string | undefined, page: string): string { + if (label !== 'AuthInfo' || !href.endsWith('#authinfo')) return href; + const path = href.slice(0, -'#authinfo'.length); + if (path ? !typeTarget.test(path) : !isAuthInfoInterfacePage(page)) return href; + return `${path}#${AUTH_INFO_INTERFACE_ANCHOR}`; +} From 02e6dd2d417b000b7f0c29dd694ad8e8f361a833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Karaaslan?= Date: Thu, 15 Oct 2026 05:21:14 +0300 Subject: [PATCH 2/4] test(docs): cover AuthInfo links and unrelated anchors --- docs/.vitepress/authInfoLinks.test.ts | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 docs/.vitepress/authInfoLinks.test.ts diff --git a/docs/.vitepress/authInfoLinks.test.ts b/docs/.vitepress/authInfoLinks.test.ts new file mode 100644 index 0000000000..a185cb9ba6 --- /dev/null +++ b/docs/.vitepress/authInfoLinks.test.ts @@ -0,0 +1,32 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { AUTH_INFO_INTERFACE_ANCHOR, correctAuthInfoTypeHref, isAuthInfoInterfacePage } from './authInfoLinks.ts'; + +const server = 'api/index/@modelcontextprotocol/server/index.md'; +const client = 'api/index/@modelcontextprotocol/client/index.md'; +const middleware = 'api/@modelcontextprotocol/server/server/middleware/bearerAuth.md'; + +test('public client/server interface pages receive a stable target', () => { + assert.equal(AUTH_INFO_INTERFACE_ANCHOR, 'authinfo-interface'); + assert.equal(isAuthInfoInterfacePage(server), true); + assert.equal(isAuthInfoInterfacePage(client), true); + assert.equal(isAuthInfoInterfacePage(middleware), false); +}); + +test('correct AuthInfo type links but never member links', () => { + const href = '../../../../index/@modelcontextprotocol/server/index.md#authinfo'; + assert.equal(correctAuthInfoTypeHref(href, 'AuthInfo', middleware), href.replace('#authinfo', '#authinfo-interface')); + assert.equal(correctAuthInfoTypeHref(href, 'authInfo', middleware), href); + const sibling = '../../../../index/@modelcontextprotocol/server/index.md#authinfo-1'; + assert.equal(correctAuthInfoTypeHref(sibling, 'AuthInfo', middleware), sibling); +}); + +test('preserve unrelated local anchors and targets', () => { + assert.equal(correctAuthInfoTypeHref('#authinfo', 'AuthInfo', server), '#authinfo-interface'); + assert.equal(correctAuthInfoTypeHref('#authinfo', 'authInfo', server), '#authinfo'); + assert.equal(correctAuthInfoTypeHref('#authinfo', 'AuthInfo', middleware), '#authinfo'); + const unrelated = '../../../../auth/index.md#authinfo'; + assert.equal(correctAuthInfoTypeHref(unrelated, 'AuthInfo', middleware), unrelated); + const clientHref = '../../../index/@modelcontextprotocol/client/index.md#authinfo'; + assert.equal(correctAuthInfoTypeHref(clientHref, 'AuthInfo', middleware), clientHref.replace('#authinfo', '#authinfo-interface')); +}); From 57a48a94a8c62a7405aa4c3a73457947adebc5fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Karaaslan?= Date: Thu, 15 Oct 2026 05:21:40 +0300 Subject: [PATCH 3/4] fix(docs): anchor AuthInfo interface without changing member links --- docs/.vitepress/config.mts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index a027bfaafb..9ab11932b9 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -7,6 +7,7 @@ import { defineConfig, type DefaultTheme } from 'vitepress'; import { generateLlmsArtifacts } from './llms'; import { guideSidebar } from './nav'; import { githubSlug, rewriteSkillLink } from './skill'; +import { AUTH_INFO_INTERFACE_ANCHOR, correctAuthInfoTypeHref, isAuthInfoInterfacePage } from './authInfoLinks'; const docsDir = resolve(dirname(fileURLToPath(import.meta.url)), '..'); @@ -44,11 +45,29 @@ export default defineConfig({ const orig = md.renderer.rules.link_open ?? ((tokens, idx, options, _env, self) => self.renderToken(tokens, idx, options)); md.renderer.rules.link_open = (tokens, idx, options, env, self) => { const href = tokens[idx].attrGet('href'); + if (href) { + const corrected = correctAuthInfoTypeHref(href, tokens[idx + 1]?.content, env.relativePath); + if (corrected !== href) tokens[idx].attrSet('href', corrected); + } if (href?.startsWith('/specification/')) { tokens[idx].attrSet('href', `https://modelcontextprotocol.io${href}`); } return orig(tokens, idx, options, env, self); }; + // AuthInfo is a public interface, but earlier authInfo member headings win the + // auto-generated #authinfo slug. Keep all existing headings/links intact and + // give only the interface a stable second anchor. + md.core.ruler.push('authinfo_interface_anchor', state => { + if (!isAuthInfoInterfacePage(state.env.relativePath)) return; + const i = state.tokens.findIndex( + (token, index) => token.type === 'heading_open' && state.tokens[index + 1]?.content === 'AuthInfo' + ); + if (i < 0) return; + const anchor = new state.Token('html_block', '', 0); + anchor.content = `\n`; + state.tokens.splice(i, 0, anchor); + }); + // Resolve skill links on the assembled page, then GitHub-style anchors to VitePress's heading ids. md.core.ruler.push('page_links', state => { const ids = new Set(); From 6c6705e2b7eb7dd5f0a10db8001350b046d8cea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Karaaslan?= Date: Thu, 15 Oct 2026 05:25:41 +0300 Subject: [PATCH 4/4] test(docs): include AuthInfo link regression in docs CI --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0be02df4ee..cdf17cdccd 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "docs:dev": "vitepress dev docs", "docs:build": "pnpm docs:api && vitepress build docs", "docs:multi": "bash scripts/build-docs-site.sh", - "docs:check": "pnpm docs:build", + "docs:check": "tsx --test docs/.vitepress/authInfoLinks.test.ts && pnpm docs:build", "typecheck:all": "pnpm -r typecheck", "build:all": "pnpm -r build", "ci:publish": "pnpm run build:all && pnpm changeset publish",