Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Details about this repository that neither the code nor the tools will tell you.
- **Tests** — Tests live under each package's `test/` directory; a test file next to its source never runs.
- **Adapters** — The packages under `packages/middleware/` are thin adapters, versioned separately from client and server, so a user can run a new server with an older adapter.
- **Package roots** — A package's root entry stays runtime-neutral so browser and Workers bundlers can use it; Node-only code lives at a subpath export such as `./stdio`.
- **Migration guide** — `docs/migration/upgrade-to-v2.md` covers the upgrade from v1 only; mechanical renames go in `packages/codemod/src/migrations/v1-to-v2/mappings/`.
- **Migration guide** — `docs/migration/upgrade-to-v2.md` covers the upgrade from v1 only. Its text lives in the agent skill `skills/mcp-typescript-sdk-upgrade-to-v2/` (one `references/` file per subsystem), which the page includes, so edit it there; mechanical renames go in `packages/codemod/src/migrations/v1-to-v2/mappings/`.

### Running Examples

Expand Down
24 changes: 24 additions & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { defineConfig, type DefaultTheme } from 'vitepress';

import { generateLlmsArtifacts } from './llms';
import { guideSidebar } from './nav';
import { githubSlug, rewriteSkillLink } from './skill';

const docsDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');

Expand Down Expand Up @@ -48,6 +49,29 @@ export default defineConfig({
}
return orig(tokens, idx, options, env, self);
};
// 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<string>();
const byGithubSlug = new Map<string, string>();
const seen = new Map<string, number>();
state.tokens.forEach((token, i) => {
const id = token.type === 'heading_open' ? token.attrGet('id') : null;
if (!id) return;
const slug = githubSlug(state.tokens[i + 1]!.content);
const n = seen.get(slug) ?? 0;
seen.set(slug, n + 1);
ids.add(id);
byGithubSlug.set(n ? `${slug}-${n}` : slug, id);
Comment thread
KKonstantinov marked this conversation as resolved.
});
for (const link of state.tokens.flatMap(token => token.children ?? [])) {
const href = link.type === 'link_open' ? link.attrGet('href') : null;
if (!href) continue;
const rewritten = rewriteSkillLink(href, state.env.relativePath, siteUrl);
const anchor = /^#(.+)$/.exec(rewritten)?.[1];
const id = anchor && !ids.has(anchor) ? byGithubSlug.get(decodeURIComponent(anchor)) : undefined;
link.attrSet('href', id ? `#${id}` : rewritten);
Comment thread
claude[bot] marked this conversation as resolved.
}
});
}
},
buildEnd(siteConfig) {
Expand Down
28 changes: 26 additions & 2 deletions docs/.vitepress/llms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { dirname, join, posix } from 'node:path';
import type { DefaultTheme } from 'vitepress';

import { guideSidebar } from './nav';
import { rewriteSkillLink } from './skill';

/**
* LLM-facing renditions of the docs, generated into the site output at build
Expand Down Expand Up @@ -68,6 +69,24 @@ function stripFrontmatter(markdown: string): string {
return end === -1 ? markdown : markdown.slice(end + 5).replace(/^\n+/, '');
}

/** Expand `<!--@include: path[#region]-->` directives (paths relative to `file`), throwing on a missing file or region. */
function expandIncludes(markdown: string, file: string): string {
return markdown.replace(/<!--\s*@include:\s*(.*?)\s*-->/g, (_match, spec: string) => {
const [path, region] = spec.split('#');
const target = join(dirname(file), path!);
const raw = readFileSync(target, 'utf8');
let content = stripFrontmatter(raw);
if (region) {
const lines = raw.split('\n');
const start = lines.indexOf(`<!-- #region ${region} -->`);
const end = lines.indexOf(`<!-- #endregion ${region} -->`);
if (start === -1 || end < start) throw new Error(`${file}: include region #${region} not found in ${path}`);
Comment thread
KKonstantinov marked this conversation as resolved.
content = lines.slice(start + 1, end).join('\n');
}
return expandIncludes(content.replace(/^\s*\n/, '').replace(/\n\s*$/, ''), target);
});
}

/** Drop the `source="…"` wiring attribute from fence info lines. */
function stripFenceAttributes(markdown: string): string {
return markdown.replace(/^```(\w+)\s+source="[^"]*"\s*$/gm, '```$1');
Expand Down Expand Up @@ -103,10 +122,15 @@ function absolutizeLinks(markdown: string, pageDir: string, site: string, source
}

function renderPage(docsDir: string, page: Page, site: string): { markdown: string; description?: string } {
const raw = readFileSync(join(docsDir, page.sourcePath), 'utf8');
const file = join(docsDir, page.sourcePath);
const raw = readFileSync(file, 'utf8');
const pageDir = posix.dirname(page.sourcePath);
const body = expandIncludes(stripFrontmatter(raw), file).replace(
/\]\(([^)\s]+)\)/g,
(_match, target: string) => `](${rewriteSkillLink(target, page.sourcePath, site)})`
);
return {
markdown: absolutizeLinks(stripFenceAttributes(stripFrontmatter(raw)), pageDir === '.' ? '' : pageDir, site, page.sourcePath),
markdown: absolutizeLinks(stripFenceAttributes(body), pageDir === '.' ? '' : pageDir, site, page.sourcePath),
description: frontmatterDescription(raw)
};
}
Expand Down
36 changes: 36 additions & 0 deletions docs/.vitepress/skill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { readdirSync } from 'node:fs';
import { dirname, posix, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

/** The docs page assembled with `<!--@include: -->` from the skill in `skills/mcp-typescript-sdk-upgrade-to-v2/`. */
export const SKILL_PAGE = 'migration/upgrade-to-v2.md';

const skillDir = resolve(dirname(fileURLToPath(import.meta.url)), '../../skills/mcp-typescript-sdk-upgrade-to-v2');
const references = new Set(readdirSync(resolve(skillDir, 'references')));

/**
* GitHub's heading slug (github-slugger) for a heading's markdown text — the
* anchor style the skill is written in, since GitHub and skills.sh render it.
*/
export function githubSlug(heading: string): string {
return heading
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
.replaceAll('`', '')
.toLowerCase()
.replace(/[^\p{L}\p{M}\p{N}\p{Pc} -]/gu, '')
.replaceAll(' ', '-');
}

/** Rewrite one link target on the skill-assembled page; every other page and link passes through. */
export function rewriteSkillLink(href: string, page: string, site: string): string {
if (page !== SKILL_PAGE) return href;
const hashIndex = href.indexOf('#');
const path = hashIndex === -1 ? href : href.slice(0, hashIndex);
const hash = hashIndex === -1 ? '' : href.slice(hashIndex);
if (path.startsWith(`${site}/`)) {
const relative = posix.relative(posix.dirname(page), path.slice(site.length + 1));
return `${relative.startsWith('../') ? relative : `./${relative}`}${hash}`;
}
const file = /^(?:\.\.\/)?SKILL\.md$/.test(path) ? 'SKILL.md' : path.replace(/^references\//, '');
return hash && (file === 'SKILL.md' || references.has(file)) ? hash : href;
}
2 changes: 1 addition & 1 deletion docs/behavior-surface-pins.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ surface-visible and must be deliberate:
accidental break.
2. Update the pin in the same PR.
3. Add a changeset if the surface is consumer-facing.
4. Update `docs/migration/upgrade-to-v2.md` (or `docs/migration/support-2026-07-28.md` if 2026-only) where consumer-facing.
4. Update the v1→v2 upgrade guide in `skills/mcp-typescript-sdk-upgrade-to-v2/references/` (or `docs/migration/support-2026-07-28.md` if 2026-only) where consumer-facing.

Never weaken a pin (loosen an exact match, delete an assertion) just to make
CI pass — that reopens the silent-drift hole the pin exists to close.
Expand Down
10 changes: 7 additions & 3 deletions docs/migration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,13 @@ wire-only members (`resultType`, envelope keys) directly.

## Using an LLM agent to migrate

[upgrade-to-v2.md](./upgrade-to-v2.md) is the agent skill — it carries skill
frontmatter and is structured for mechanical application. Point the agent at
the codemod first; the guide is the codemod's companion for what's left.
[upgrade-to-v2.md](./upgrade-to-v2.md) is also published as an agent skill, with
one reference file per subsystem so the agent reads only what your code needs. It
runs the codemod first and uses the guide for what's left:

```bash
npx skills add modelcontextprotocol/typescript-sdk --skill mcp-typescript-sdk-upgrade-to-v2
```

## See also

Expand Down
2 changes: 1 addition & 1 deletion docs/migration/support-2026-07-28.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ client.getProtocolEra(); // 'modern' | 'legacy'

`ProtocolOptions.supportedProtocolVersions` — the same option that pins what the legacy
`initialize` handshake offers (see
[upgrade-to-v2.md › Client connection & dispatch](./upgrade-to-v2.md#client-connection--dispatch))
[upgrade-to-v2.md › Client connection & dispatch](./upgrade-to-v2.md#client-connection-dispatch))
— shapes `'auto'`: the modern candidates are the option's modern entries (when it lists
any; otherwise the SDK's default modern set), and legacy fallback is available only if
the list has a pre-2026 entry. A `{ pin }` is honored as given — it must name a modern
Expand Down
Loading
Loading