/** * Offline payload audit: imports definitions, never starts the MCP server or calls * a handler. UTF-8 bytes are exact; they are NOT tokenizer counts or proof that a * client put the catalog in model context. Run with tsx from a source checkout. * * Optional --baseline-ref compares CLAUDE.md bytes with a local * git object. No fetch, authentication, tenant request, or file write is performed. */ import { createHash } from "node:crypto"; import { execFileSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { FEATURE_IDS, normalizeFeatures } from "../src/config/features.js"; import { SERVER_INSTRUCTIONS } from "../src/server-instructions.js"; import { getToolsForFeatures, tools } from "../src/tools/index.js"; const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), ".."); export function utf8Bytes(value: string): number { return Buffer.byteLength(value, "utf8"); } export function canonicalJson(value: unknown): string { return ( JSON.stringify(value, (_key, entry: unknown) => { if (entry !== null && typeof entry === "object" && !Array.isArray(entry)) { const object = entry as Record; return Object.fromEntries( Object.keys(object) .sort() .map((key) => [key, object[key]]), ); } return entry; }) ?? "null" ); } export function fingerprint(value: unknown): string { return createHash("sha256").update(canonicalJson(value), "utf8").digest("hex"); } export function getFeatureSurfaces() { // Every subset, including empty (the registry normalizes required dependencies). return Array.from({ length: 2 ** FEATURE_IDS.length }, (_entry, mask) => { const requested = FEATURE_IDS.filter((_feature, index) => (mask & (1 << index)) !== 0); return { requested, normalized: normalizeFeatures(requested), tools: getToolsForFeatures(requested) .map((tool) => tool.name) .sort(), }; }); } export function measureCatalog(catalog: readonly Tool[]) { const sorted = [...catalog].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); const descriptions = catalog.map((tool) => ({ name: tool.name, utf8Bytes: utf8Bytes(tool.description ?? ""), })); return { toolCount: catalog.length, catalogJsonUtf8Bytes: utf8Bytes(JSON.stringify(catalog)), toolsListResultJsonUtf8Bytes: utf8Bytes(JSON.stringify({ tools: catalog })), namesJsonUtf8Bytes: utf8Bytes(JSON.stringify(catalog.map((tool) => tool.name))), descriptionsUtf8Bytes: descriptions.reduce((sum, item) => sum + item.utf8Bytes, 0), inputSchemasJsonUtf8Bytes: catalog.reduce( (sum, tool) => sum + utf8Bytes(JSON.stringify(tool.inputSchema)), 0, ), outputSchemasJsonUtf8Bytes: catalog.reduce( (sum, tool) => sum + (tool.outputSchema ? utf8Bytes(JSON.stringify(tool.outputSchema)) : 0), 0, ), annotationsJsonUtf8Bytes: catalog.reduce( (sum, tool) => sum + (tool.annotations ? utf8Bytes(JSON.stringify(tool.annotations)) : 0), 0, ), largestDescriptionUtf8Bytes: Math.max(0, ...descriptions.map((item) => item.utf8Bytes)), descriptionsOver2048Bytes: descriptions.filter((item) => item.utf8Bytes > 2048), catalogSha256: fingerprint(sorted), // Exclude only the top-level discovery description. Parameter descriptions, // input/output schemas, annotations and every other contract field remain. contractsSha256: fingerprint( sorted.map((tool) => { const contract = { ...tool }; delete contract.description; return contract; }), ), identityAnnotationsSha256: fingerprint( sorted.map((tool) => ({ name: tool.name, annotations: tool.annotations })), ), }; } export function measureMcpContext(root = ROOT) { const surfaces = getFeatureSurfaces(); return { formatVersion: 1, measurement: "Exact UTF-8 bytes, not model tokens or observed loaded context.", scope: "Offline definitions and documentation only; no server startup or tool execution.", catalog: measureCatalog(tools), artifacts: { claudeStartupUtf8Bytes: utf8Bytes(readFileSync(join(root, "CLAUDE.md"), "utf8")), toolReferenceUtf8Bytes: utf8Bytes( readFileSync(join(root, "docs", "TOOL_REFERENCE.md"), "utf8"), ), initializeInstructionsUtf8Bytes: utf8Bytes(SERVER_INSTRUCTIONS), }, featureSurfaces: { subsetCount: surfaces.length, sha256: fingerprint(surfaces), profiles: FEATURE_IDS.map((feature) => ({ requestedFeature: feature, normalized: normalizeFeatures([feature]), toolCount: getToolsForFeatures([feature]).length, })), }, }; } export function compareStartupBaseline(root: string, commit: string, currentUtf8Bytes: number) { if (!/^[a-fA-F0-9]{40}$/.test(commit)) { throw new Error("Baseline ref must be a full 40-character local commit SHA."); } let source: string; try { source = execFileSync("git", ["show", `${commit}:CLAUDE.md`], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 2 * 1024 * 1024, }); } catch { throw new Error( "Cannot read CLAUDE.md from that local baseline commit; no fetch was attempted.", ); } const baselineUtf8Bytes = utf8Bytes(source); const removedUtf8Bytes = baselineUtf8Bytes - currentUtf8Bytes; return { commit, claudeStartupUtf8Bytes: baselineUtf8Bytes, currentClaudeStartupUtf8Bytes: currentUtf8Bytes, removedUtf8Bytes, reductionPercent: baselineUtf8Bytes === 0 ? null : Number(((removedUtf8Bytes / baselineUtf8Bytes) * 100).toFixed(2)), }; } export function parseAuditArguments(args: readonly string[]): { baselineRef?: string } { if (args.length === 0) return {}; if ( args.length === 2 && args[0] === "--baseline-ref" && /^[a-fA-F0-9]{40}$/.test(args[1] ?? "") ) { return { baselineRef: args[1] }; } throw new Error( "Usage: tsx scripts/measure-mcp-context.ts [--baseline-ref ]", ); } const entrypoint = process.argv[1]; if (entrypoint && import.meta.url === pathToFileURL(resolve(entrypoint)).href) { try { const { baselineRef } = parseAuditArguments(process.argv.slice(2)); const audit = measureMcpContext(); const baseline = baselineRef ? compareStartupBaseline(ROOT, baselineRef, audit.artifacts.claudeStartupUtf8Bytes) : undefined; console.log(JSON.stringify({ ...audit, ...(baseline ? { baseline } : {}) }, null, 2)); } catch (error) { console.error(error instanceof Error ? error.message : "Offline context audit failed."); process.exitCode = 1; } }