@ahmedshaikh/api-diff-mcp
v0.1.0
Published
Detect breaking changes to a TypeScript/JavaScript module's public API as an MCP server — extract the export surface (signatures) and diff two versions, flagging removed/changed exports. Built on the TypeScript compiler API.
Maintainers
Readme
api-diff
Did my refactor break the public API? Extract a TypeScript/JavaScript module's export surface (functions, classes, interfaces, types, consts — with their signatures) and diff two versions to flag breaking changes. Built on the TypeScript compiler API, so it's accurate — not regex guessing.
api_diff({ old_path: "src/api.ts@HEAD", new_path: "src/api.ts" })
→ { breaking: true, breaking_count: 1,
summary: { added: 1, removed: 0, changed: 1 },
changes: [
{ name: "fetchUser", change: "changed", breaking: true,
old: "function fetchUser(id: string): User",
new: "function fetchUser(id: string, opts: Opts): User" },
{ name: "fetchUsers", change: "added", breaking: false } ] }Why it's new
The rest of an agent toolkit handles making edits safely (checkpoint, apply-patch, verify). None of them answer "is this change backward-compatible?" — agents rename a param or drop an export and silently break every consumer. This is the missing refactor-safety check, and a natural semver gate.
Tools
api_surface(code | path)— the public API of a module: every export + a normalized signature. (Great for "what does this module expose?")api_diff(old, new | old_path, new_path)—{ breaking, breaking_count, summary{added,removed,changed}, changes[] }. Removed and signature-changed exports are breaking; added exports are compatible.
Signatures are whitespace-normalized, so formatting/reorder noise doesn't show up — only real surface changes do. Private/protected class members are ignored.
Setup
npm install
npm testclaude mcp add api-diff -- node /abs/path/api-diff/dist/server.js
# CLI: agent-apidiff old.ts new.ts (exit 1 if breaking)
# agent-apidiff surface src/api.ts
# git: agent-apidiff <(git show HEAD:src/api.ts) src/api.tsHonest limits
- TS/JS surface only — exported names + their written signatures. It does not
resolve types across files or run a full type-checker, so an unannotated
export const x = makeThing()is compared by name, not inferred type. - "Breaking" is structural and conservative: any removed/changed export is
flagged. A widened-but-compatible type change still shows as
changed— treat it as a prompt to look, not a verdict. - Re-exports are tracked by name; it doesn't follow them into other modules.
