/** * Spec Schema Vendoring Script * * Fetches the core MCP specification's canonical JSON Schema * (schema/draft/schema.json in modelcontextprotocol/modelcontextprotocol) * and vendors it into this repository with commit provenance. * * Modeled on the TypeScript SDK's scripts/fetch-spec-types.ts: the vendored * artifact records the exact upstream commit it was pulled from, and updating * it is an explicit, reviewed action (`npm run fetch:spec-schema`), never an * implicit side effect of schema generation. This keeps `npm run * generate:schemas` deterministic and offline while giving spec corrections a * defined path into this repository. * * Usage: * npm run fetch:spec-schema # latest upstream commit for the draft schema * npm run fetch:spec-schema -- # a specific upstream commit * * Once this extension targets a released spec revision, pin its SHA in * RELEASED_REVISION_PINS (see the TypeScript SDK's lifecycle policy). */ import { writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const PROJECT_ROOT = join(__dirname, ".."); const SPEC_REPO = "modelcontextprotocol/modelcontextprotocol"; const UPSTREAM_SCHEMA_PATH = "schema/draft/schema.json"; const UPSTREAM_TYPES_PATH = "schema/draft/schema.ts"; const OUTPUT_FILE = join( PROJECT_ROOT, "schema", "draft", "spec.schema.json" ); const TYPES_OUTPUT_FILE = join(PROJECT_ROOT, "schema", "draft", "spec.types.ts"); /** * Generation pin per released revision. The draft schema floats to the * latest upstream commit; a released revision must be pinned here so it can * only change through a deliberate, reviewed repin. */ const RELEASED_REVISION_PINS: Record = {}; interface GitHubCommit { sha: string; } async function fetchLatestSHA(): Promise { const url = `https://api.github.com/repos/${SPEC_REPO}/commits?path=${UPSTREAM_SCHEMA_PATH}&per_page=1`; const response = await fetch(url); if (!response.ok) { throw new Error( `Failed to fetch commit info: ${response.status} ${response.statusText}` ); } const commits = (await response.json()) as GitHubCommit[]; if (!commits || commits.length === 0) { throw new Error("No commits found"); } return commits[0].sha; } async function fetchSpecFile(sha: string, path: string): Promise { const url = `https://raw.githubusercontent.com/${SPEC_REPO}/${sha}/${path}`; const response = await fetch(url); if (!response.ok) { throw new Error( `Failed to fetch ${path}: ${response.status} ${response.statusText}` ); } return await response.text(); } async function main() { const providedSHA = process.argv[2]; const pinnedSHA = RELEASED_REVISION_PINS["draft"]; let sha: string; if (providedSHA) { console.log(`Using provided SHA: ${providedSHA}`); sha = providedSHA; } else if (pinnedSHA) { console.log(`Using pinned SHA: ${pinnedSHA}`); sha = pinnedSHA; } else { console.log("Fetching latest upstream commit SHA..."); sha = await fetchLatestSHA(); } // Fetch BOTH artifacts before writing either, so a failed second fetch can // never leave the two vendored files at different upstream revisions. console.log(`Fetching ${UPSTREAM_SCHEMA_PATH} from commit: ${sha}`); const raw = await fetchSpecFile(sha, UPSTREAM_SCHEMA_PATH); const parsed = JSON.parse(raw) as Record; console.log(`Fetching ${UPSTREAM_TYPES_PATH} from commit: ${sha}`); const typesContent = await fetchSpecFile(sha, UPSTREAM_TYPES_PATH); // Record provenance in the vendored artifact itself. $comment is valid in // JSON Schema 2020-12 and ignored by validators. const vendored = { $comment: `Vendored from https://github.com/${SPEC_REPO}/blob/${sha}/${UPSTREAM_SCHEMA_PATH}. ` + "DO NOT EDIT MANUALLY. Update with: npm run fetch:spec-schema", ...parsed, }; const typesHeader = `/**\n` + ` * This file is automatically vendored from the Model Context Protocol specification.\n` + ` *\n` + ` * Source: https://github.com/${SPEC_REPO}/blob/${sha}/${UPSTREAM_TYPES_PATH}\n` + ` * Last updated from commit: ${sha}\n` + ` *\n` + ` * DO NOT EDIT THIS FILE MANUALLY. Update with: npm run fetch:spec-schema\n` + ` */\n`; writeFileSync(OUTPUT_FILE, JSON.stringify(vendored, null, 2) + "\n", "utf-8"); console.log(`✅ Written: ${OUTPUT_FILE}`); writeFileSync(TYPES_OUTPUT_FILE, typesHeader + typesContent, "utf-8"); console.log(`✅ Written: ${TYPES_OUTPUT_FILE}`); console.log("Now run: npm run generate:schemas"); } main().catch((error) => { console.error("❌ Spec schema fetch failed:", error); process.exit(1); });