/** * Schema parity: every tool declared in PLUGIN.md frontmatter must have a * handler registered in index.ts, and every handler in index.ts must be * declared in PLUGIN.md. Blocks silent drift between the manifest the * platform reads and the tool surface the server exposes. */ import { describe, expect, it } from "vitest"; import { readFileSync } from "node:fs"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; const here = dirname(fileURLToPath(import.meta.url)); const PLUGIN_ROOT = resolve(here, "../../.."); function readPluginMdTools(): string[] { const md = readFileSync(resolve(PLUGIN_ROOT, "PLUGIN.md"), "utf-8"); const fmMatch = md.match(/^---\n([\s\S]*?)\n---/); if (!fmMatch) throw new Error("PLUGIN.md frontmatter not found"); const tools: string[] = []; for (const line of fmMatch[1].split("\n")) { const m = line.match(/^\s+-\s+name:\s+(\S+)/); if (m) tools.push(m[1]); } return tools; } function readIndexTools(): string[] { const src = readFileSync(resolve(PLUGIN_ROOT, "mcp/src/index.ts"), "utf-8"); const matches = src.matchAll(/server\.tool\(\s*"([^"]+)"/g); return Array.from(matches, (m) => m[1]); } describe("schema parity", () => { it("PLUGIN.md frontmatter and index.ts register the same tool set", () => { const declared = readPluginMdTools().sort(); const registered = readIndexTools().sort(); expect(registered).toEqual(declared); }); it("declares all 22 tools (3 key-mgmt + 19 endpoints)", () => { const declared = readPluginMdTools(); expect(declared).toHaveLength(22); expect(declared).toContain("property-data-key-register"); expect(declared).toContain("property-data-valuation-sale"); expect(declared).toContain("property-data-national-hpi"); for (const name of [ "property-data-prices-per-sqf", "property-data-sold-prices-per-sqf", "property-data-crime", "property-data-flood-risk", "property-data-council-tax", "property-data-planning-applications", "property-data-address-match-uprn", "property-data-growth-psf", "property-data-demand-rent", "property-data-property-types", ]) { expect(declared).toContain(name); } }); });