semver-checks
This server exposes two tools for analyzing a TypeScript library's public API and its SemVer impact.
semver_compare — compare an old and new version of a TypeScript library to detect breaking API changes and return a recommended
major/minor/patchbump plus a list of changes with severity and confidence (provenvsheuristic).Compare any combination of filesystem path, git ref (tag/branch/SHA), or npm spec (e.g.
lodash@4.17.21,pkg@latest), so a working tree can be checked against a published release.Force input interpretation with
oldAs/newAs(path/git/ref/npm) and analyze multiple entry points viaentry.Report both gate outcomes:
strict(fails only on proven breaks) andstrictReview(also fails on review-only majors), clarifying that a passingstrictis not a clean bill of health.Optionally grade a release's declared bump via
declared(major/minor/patch/none/autofrom.changeset/*.mdor package.json versions), yielding anok/review/mismatchverdict.Limit output with
maxChanges(proven breaks first, then additions, then review-only) with anomittedsummary, and useinstallDepsto install dependencies first.semver_snapshot — extract a structured JSON snapshot of a project's public API keyed by export subpath, from a path, git ref, or npm spec.
Snapshots list symbol names and kinds by default, or full type shapes with
detail, bounded by amaxBytesbudget that reports omitted symbols.
Compares TypeScript declarations between git refs such as tags, branches, and commits.
Provides a reusable composite GitHub Action that runs semver checks on pull requests, annotates changed lines, and can fail the build on breaking changes.
Fetches published packages from the npm registry and compares their TypeScript declarations against another version.
semver-checks
pino 10.4.0, a minor release, added export { pino as default, pino } inside its pino namespace. That turns off implicit export for 9 members of the namespace, so const t: pino.TimeFn = () => '' compiles against 10.3.1 and fails against 10.4.0 with TS2694, "Namespace 'pino' has no exported member 'TimeFn'".
npx semver-checks compare pino@10.3.1 pino@10.4.0Run with no arguments in a publishable package directory, npx semver-checks compares <name>@latest against .. This default is CLI-only: the GitHub Action and the MCP semver_compare tool need old explicitly.
semver-checks — Recommended bump: MAJOR
major: 20 (confident: 11, review: 9) minor: 88 patch: 0
Breaking Changes — confident (MAJOR)
✗ Export 'TimeFn' was removed
was: type-alias
✗ Export 'MixinFn' was removed
was: type-alias
✗ Export 'MixinMergeStrategyFn' was removed
was: type-alias
...The output is trimmed to the bump counts and three of the 9 removed exports (... marks the cut, and stderr warnings are omitted); the full run also lists the other six, two required properties added to MultiStreamRes, the review-only findings and the additions.
semver-checks compares the TypeScript declarations on both sides and recommends the bump the type changes require, so the answer comes from the API rather than from the commit message. Neither side needs a checkout: each can be an npm spec, a git ref, or the working tree.
npx semver-checks compare your-package@latest .The same check as a pull request gate, which annotates the changed lines and fails on a proven break:
# .github/workflows/semver-check.yml
on: pull_request
jobs:
semver-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- run: npm ci
- uses: kyungseopk1m/semver-checks@v0.15.2
with:
old: "your-package@latest"
strict: "true"Every input, and the release-pull-request variant that grades the declared bump and posts the verdict as a comment, are under GitHub Action.
Why semver-checks?
A bump is a claim about what a release did to its public API, and it is usually written by hand, from memory, at the end of the work. A refactor that drops a required export reads as a patch to the person who wrote it and as a broken build to everyone downstream.
Release tooling can only act on the evidence it is given. semantic-release derives the release type from commit conventions, and changesets records an author-written release note with the bump declared in its frontmatter. Both inherit whatever the author believed at the time.
semver-checks reads the public API directly using ts-morph and decides the bump from what the type signatures did.
The example above is not constructed. Run the tool across real releases and it finds breaking type changes that shipped as minors and patches. It is most dependable on structural changes, which is to say removed or renamed exports, narrowed signatures, and added required parameters and properties. Equivalence-preserving type rewrites are a known weak spot it can over-report, and docs/accuracy.md says exactly where to trust it and where not to.
// v1.0.0
export interface Config {
host: string;
port: number;
}
// Developer writes: "fix: add missing timeout config"
// Published as patch — but this is a MAJOR change:
export interface Config {
host: string;
port: number;
timeout: number;
}
// ^^^^^^^^^^^^^^^^ required-property-added// v1.0.0
export function findUser(id: string): User;
// Developer writes: "fix: findUser can come up empty"
// Published as minor — but no existing consumer is written to handle null:
export function findUser(id: string): User | null;
// ^^^^^^^^^^^ return-type-changed (MAJOR)The reverse of that change is not a break: narrowing User | null down to User hands every consumer a value they were already prepared for, and semver-checks reports it as return-type-narrowed, a MINOR.
semver-checks is complementary to your existing release workflow. Use it as a verification step before publishing — it tells you whether your intended bump is safe, or whether you're about to ship a breaking change by accident.
Related MCP server: vdiff
Quick Start
npm install --save-dev semver-checksCompare a git tag to the latest commit:
npx semver-checks compare v1.0.0 HEADCompare the published npm release against your working tree — answers "is my current change a breaking release?" without needing git tags:
npx semver-checks compare your-package@latestA <package>@<version> argument is fetched from the npm registry (via npm pack) and used as the old version. Concrete versions, ranges, and common dist-tags are auto-detected (your-package@1.2.3, your-package@^1, your-package@next). For an uncommon dist-tag, make the intent explicit with the npm: prefix or --old-as npm (npm:your-package@my-custom-tag) so it isn't mistaken for a git ref.
Compare two local directories:
npx semver-checks compare ./old ./newExisting relative paths without a ./ prefix are also treated as local directories:
npx semver-checks compare packages/core packages/core-nextIf a git ref collides with an existing path name, force ref interpretation explicitly:
npx semver-checks compare main HEAD --old-as refOutput as JSON, Markdown (for PR comments), or GitHub Actions annotations:
npx semver-checks compare v1.0.0 HEAD --format json
npx semver-checks compare v1.0.0 HEAD --format markdown
npx semver-checks compare v1.0.0 HEAD --format githubFail in CI if breaking changes are detected (exit 1):
npx semver-checks compare v1.0.0 HEAD --strictInspect the API surface of the current or a past version:
npx semver-checks snapshot
npx semver-checks snapshot --ref v1.0.0
npx semver-checks snapshot --npm p-limit@6.1.0Every flag is in docs/cli.md.
Multiple entry points
When package.json declares an "exports" map with several subpaths, every
subpath with a declared .d.ts entry is extracted and compared independently.
Adding a subpath is a MINOR change and removing one is MAJOR; a change inside a
subpath is reported with a # separator (e.g. ./utils#helper). No flags are
needed — the map is auto-detected.
For projects without an "exports" map, pass multiple entries explicitly by
repeating --entry or comma-separating them:
npx semver-checks compare v1.0.0 HEAD --entry src/index.ts --entry src/utils.ts
npx semver-checks compare v1.0.0 HEAD --entry src/index.ts,src/utils.tsExample output
semver-checks — Recommended bump: MAJOR
major: 2 (confident: 1, review: 1) minor: 1 patch: 0
Breaking Changes — confident (MAJOR)
✗ Required property 'timeout' was added to interface 'Config'
now: number
Needs review — couldn't prove safe (MAJOR)
? Call signatures of interface 'Middleware' changed
before: (req: string) => void
after: (request: string, next?: (() => void) | undefined) => void
New Features (MINOR)
+ Export 'createConfig' was addedThe second finding is a renamed parameter and an added optional one, which oblige nobody — but call signatures are compared as printed text, so the tool cannot prove that and says so instead of guessing. --strict exits 1 on the confident break only; the review-only item passes the gate unless you opt into --strict-review.
CI Integration
GitHub Action
semver-checks ships a reusable composite action. The most ergonomic setup compares the published latest release against the PR's working tree, so it needs no git tags and posts inline annotations on the diff:
name: SemVer Check
on:
pull_request:
branches: [main]
jobs:
semver-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- run: npm ci
- uses: kyungseopk1m/semver-checks@v0.15.2
with:
old: "your-package@latest" # the published version to compare against
format: "github" # inline ::error:: / ::warning:: annotations
strict: "true" # fail the PR on a confident (proven) breaking changeInput | Description | Default |
| Old version — an npm spec ( | (required) |
| New version — git ref or path |
|
| Entry file (auto-detected from | (auto) |
| Force | (auto-detect) |
| Force | (auto-detect) |
| Install dependencies on both sides before analysis, whatever their source kind. Needed for a local path; a git ref already installs on its own |
|
|
|
|
| Fail the step (exit 1) on a confident (proven) breaking change |
|
| Fail the step (exit 1) on any breaking change, including review-only (heuristic) ones |
|
| The bump this release declares: | (off) |
| Post the report to the PR as a comment, updated on later pushes (forces |
|
| Token used to post the comment. A composite action cannot read the |
|
| semver-checks version to run via | (matches the action ref) |
A full example lives in examples/github-actions.yml.
On a release pull request
Does the bump this release declares cover what its API surface did? @clerk/break-check and semvet also score the declared bump from the two package.json versions; reading it from .changeset files is what is specific here. declared: auto reads the bump from .changeset/*.md and falls back to the two package.json versions, and comment: true puts the verdict on the pull request instead of leaving it in the log.
permissions:
contents: read
pull-requests: write # required for `comment`
jobs:
semver-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- run: npm ci
- uses: kyungseopk1m/semver-checks@v0.15.2
with:
old: "your-package@latest"
declared: "auto"
comment: "true"The step fails when the declaration understates a proven break, and passes with a note when it only understates an addition. token defaults to github.token; pass it explicitly if your setup needs a different one. A pull request from a fork gets a read-only token, so the comment is skipped with a warning there rather than failing the step.
The comment is posted with gh pr comment --edit-last --create-if-none, which updates the token owner's most recent comment on the pull request. It does not look for a marker of its own, so if the same token posts other comments from other workflows, run this one under its own token or leave comment off.
Without the action
Run the CLI directly — for example, compare the published release to the working tree:
- name: Check for breaking changes
run: npx semver-checks compare your-package@latest --format github --strictOr compare against a git tag:
- name: Check for breaking changes
run: npx semver-checks compare v$(node -p "require('./package.json').version") HEAD --strictAccuracy
Every breaking change carries a confidence. proven means the change is its own evidence, or a type relation the analyzer resolved and found genuinely unrelated; --strict fails on those and only those. heuristic means a conservative MAJOR it could not prove — surfaced for review, off the default gate, unless you opt into --strict-review.
The scorecard uses tsc as its oracle rather than the author's published bump, because the tool exists on the premise that authors get that bump wrong. 111 adjacent minor/patch release pairs across 24 packages, each with a consumer program compiled against both sides:
Flag | Fails on | Result on 111 pairs (43 real breaks) | The contract it can hold |
|
| fires on 37 pairs, 36 of them real: precision 97.3%, recall 83.7% | A failure is a strong signal. A pass is not a clean bill of health |
| every MAJOR | fires on all 43 real breaks and 10 pairs holding none: recall 100%, precision 81.1% | Green means the analysis reported nothing breaking anywhere |
A name-and-arity baseline with no type resolution scores 87.5% / 32.6% on the same corpus. Leave --strict on for every push, and run --strict-review where a person reads the output before it ships.
docs/accuracy.md has the rest: how the corpus was built, the 7 pairs --strict stays quiet on and why each one, the single remaining false positive, what the variance probe refuses to answer, and the ten known limitations.
Programmatic API
import { compare, extract } from "semver-checks";
const report = await compare({
oldSource: { type: "git", ref: "v1.0.0" },
newSource: { type: "path", path: "." },
});
console.log(report.recommended); // 'major' | 'minor' | 'patch'
console.log(report.changes); // ApiChange[]
console.log(report.summary); // { major: 2, minor: 1, patch: 0, ... }interface CompareOptions {
oldSource: SourceRef;
newSource: SourceRef;
entry?: string | string[]; // Optional: specify one or more entry points
installDeps?: boolean; // Optional: install deps on both sides before analyzing
declared?: DeclaredBump | "auto"; // Optional: grade the bump this release declares
}
type DeclaredBump = "major" | "minor" | "patch" | "none";
type SourceRef =
| { type: "path"; path: string }
| { type: "git"; ref: string; cwd?: string }
| { type: "npm"; spec: string }; // e.g. { type: 'npm', spec: 'p-limit@6.1.0' }
interface SemverReport {
recommended: "major" | "minor" | "patch";
changes: ApiChange[];
summary: {
major: number;
minor: number;
patch: number;
majorProven: number;
majorReview: number;
};
// Present only when a declared bump was supplied or detected.
declaration?: BumpDeclaration;
}
interface BumpDeclaration {
declared: DeclaredBump;
source: string; // where it was read from: a changeset path, or the two versions
required: "major" | "minor" | "patch"; // what the proven breaks require
suggested: "major" | "minor" | "patch"; // what the whole change set argues for
verdict: "ok" | "review" | "mismatch";
}
interface ApiChange {
kind: ChangeKind;
severity: "major" | "minor" | "patch";
symbolPath: string;
message: string;
oldValue?: string;
newValue?: string;
confidence?: "proven" | "heuristic";
}You can also extract a snapshot independently:
import { extract } from "semver-checks";
const snapshot = await extract({ projectPath: "." });
// Snapshots are keyed by export subpath ('.' is the root entry; additional
// subpaths come from the package.json "exports" map).
console.log(Object.keys(snapshot.entrypoints["."])); // root entry's symbol namesReference
Document | What's in it |
Every | |
All | |
MCP server setup and both tool schemas | |
The corpus, the confidence grades, and the known limitations |
MCP server
semver-checks ships as a Model Context Protocol server, so an agent can call it as a tool:
claude mcp add semver-checks -- npx -y semver-checks --mcpTwo tools: semver_compare and semver_snapshot. Setup for other clients and the full argument schemas are in docs/mcp.md.
Comparison with Other Tools
semver-checks | semantic-release | changesets | npm-check-updates | |
Input | TypeScript API | Commit messages | Author-written changeset | package.json |
Detection | Typed API rules | Keyword matching | Developer-declared | Version range only |
Recommendation | Automatic | Based on message format | Manual per change | Dependency updates only |
semver-checks is a verification layer, not a release tool. Use it alongside semantic-release or changesets to check whether the declared bump matches the API changes.
How it differs
ts-semver-checks, @clerk/break-check and semvet each compare a local build against a baseline. ts-semver-checks and semvet use compiler assignability; ts-semver-checks takes the bump you expect through --expect and has no Action or MCP server, while semvet scores the declared bump from package.json. @clerk/break-check diffs API Extractor snapshots, adds an LLM review pass, scores the declared bump from package.json, and handles multi-package monorepos.
semver-checks can compare two published npm releases directly (compare pkg@x pkg@y), reads the declared bump from .changeset files, ships an MCP server, and publishes its accuracy numbers in docs/accuracy.md.
How It Works
Extract: Parse old and new TypeScript source files using ts-morph, building a typed API snapshot (functions, interfaces, enums, classes, type aliases, variables, namespaces)
Diff: Compare the two snapshots symbol by symbol — detect additions, removals, and signature changes
Classify: Assign each diff a
major,minor, orpatchseverityReport: Return a structured
SemverReportwith the recommended bump and per-change details
For git ref comparisons, the ref is extracted to a temporary directory via git archive, dependencies are installed there, and the directory is cleaned up afterwards. For npm specs, the published tarball is downloaded with npm pack and extracted to a temporary directory, with no dependency install, because the tarball already bundles its build output. Local path comparisons install nothing by default. --install-deps (or installDeps: true) overrides that and runs the install on both sides whatever their source kind — it is there for the local-path case, which is the only one that needs it.
FAQ
Will semver-checks catch every semver violation?
No. It catches API surface changes that are mechanically detectable from TypeScript's static type system: removed exports, signature changes, type changes, optionality changes, and similar structural changes. It does not detect behavioral changes, documentation changes, or changes hidden behind conditional compilation. When a package ships distinct ESM and CJS declaration files for the same entry point (for example, divergent import.types and require.types), only one surface is analyzed, so a break confined to the other surface can be missed. See docs/accuracy.md.
Does it have false positives?
Yes. It errs toward over-reporting MAJOR rather than missing a break, but the default CI gate only fails on proven breaks. Parameter and return type changes go through a structural assignability check, so widened parameters, narrowed returns, and equivalent rewrites such as readonly T[] vs ReadonlyArray<T> avoid false majors. Type aliases and variables still have conservative cases because they are compared as normalized serialized text first, and the probe upgrades that to a resolved relation only where it can. The concrete patterns are listed under Known limitations.
Does it support default exports?
Yes. A default export is extracted under the symbol name default and compared like the corresponding function, class, or value declaration.
Can I compare against a published npm version?
Yes. Pass a <package>@<version> spec and semver-checks downloads that release from the registry with npm pack, extracts the tarball, and analyzes its bundled .d.ts declarations:
npx semver-checks compare your-package@latest # published latest vs working tree
npx semver-checks compare your-package@1.0.0 your-package@2.0.0 # two published releasesBecause a published tarball ships compiled .d.ts files while your working tree ships .ts source, type representation can differ slightly between the two sides (TypeScript materializes some inferred types in declarations). Removals, additions, and signature changes are detected reliably; a handful of equivalent-but-reworded types may show up as a noisy diff. Comparing two published releases (.d.ts vs .d.ts) avoids that asymmetry.
Can I use it without a tsconfig.json?
Not for local-path or git-ref inputs: those are opened as a TypeScript project and need a tsconfig.json at the project root, or at the root inferred from the exports field in package.json. Published npm inputs are different — a permissive config is synthesized when the tarball does not ship one.
What happens if the analyzed project has TypeScript errors?
semver-checks will print a warning to stderr listing up to 5 errors and continue. Results may be incomplete if type errors affect the API surface. Set SEMVER_CHECKS_VERBOSE=1 for full diagnostics.
How is the entry point determined?
semver-checks looks for the entry file in this order:
The
--entryflag if providedThe declaration under
exports['.']inpackage.json— every condition is walked (types,require/import/node/browser/module/default, nested, and fallback arrays), and.d.ts/.d.mts/.d.ctsare all accepted. A bare-string"exports": "./index.js"or a flat conditions object"exports": { "types": "./index.d.ts", "default": "./index.js" }(no.subpath key) is treated as the.entry, so itstypescondition is read; for a published package, a.jstarget is also checked for a sibling declaration of the same name. A subpath-only map with no.key is left without a root entry (no fabricated root)The top-level
typesortypingsfield inpackage.jsonsrc/index.ts, thenindex.ts, then a conventional rootindex.d.ts/.d.mts/.d.ctsas fallbacks
If none of these resolve, pass --entry explicitly.
When a project ships an "exports" map with several subpaths, each subpath is resolved and compared independently (see Multiple entry points).
Does it work with monorepos?
Yes. Point --entry at the package's entry file, or run the CLI from that package's directory. A monorepo tag shaped like pkg@1.2.3 reads as an npm spec to auto-detection, so pass --old-as ref for those.
Requirements
Node.js
^20.0.0 || >=22.0.0(Node 21 is excluded by a transitive dependency and is itself end of life)For local path / git-ref inputs: a
tsconfig.jsonand TypeScript source files (.ts/.tsx) in the analyzed projectFor npm specs: nothing extra — the tarball's bundled
.d.tsdeclarations are analyzed, and atsconfig.jsonis synthesized if absent
Dual module support
semver-checks ships both CommonJS and ES module builds:
// ESM
import { compare } from "semver-checks";
// CJS
const { compare } = require("semver-checks");Contributing
Contributions are welcome. Please read CONTRIBUTING.md before submitting a pull request.
License
MIT. See LICENSE.
Author
Kyungseop Kim — @kyungseopk1m
Available Tools
2 toolssemver_compareA
Compare two versions of a TypeScript library and detect breaking API changes. Either side can be a filesystem path, a git ref, or an npm spec, so a working tree can be compared against the published release without checking anything out. npm specs and git refs need network access and are unpacked into temp directories that are removed afterwards; nothing in a local path is written unless "installDeps" is set. Returns the recommended SemVer bump (major/minor/patch) and a list of changes. Each change carries a confidence: "proven" (a structurally confident break) or "heuristic" (a conservative major the tool could not prove safe), and summary.majorProven / majorReview split the major count accordingly. The "gate" field gives what each CLI gate would exit with: "strict" fails only on a proven break, "strictReview" on any major. A "strict" pass does not mean nothing broke, since a break the tool could not prove stays review-only, so report both. Pass "declared" to grade the release against the bump it writes down instead of recommending one; a major it already declares then passes both gates.
| Name | Required | Description | Default |
|---|---|---|---|
| new | No | New version: a filesystem path, a git ref, or an npm spec. Defaults to current directory. | . |
| old | Yes | Old version: a filesystem path, a git ref (tag, branch, commit SHA), or an npm spec. A "name@version" that is not an existing path is read as npm (e.g. "lodash@4.17.21", "lodash@latest"); prefix with "npm:" to say so outright. | |
| entry | No | Entry file relative to project root (e.g. "src/index.ts"). Pass an array or a comma-separated string for a package with several entry points. Auto-detected from package.json if omitted. | |
| newAs | No | Force "new" to be treated as a filesystem path, a git ref, or an npm spec. "ref" is an accepted spelling of "git". | |
| oldAs | No | Force "old" to be treated as a filesystem path, a git ref, or an npm spec. "ref" is an accepted spelling of "git". Only needed when auto-detection would read the input as the wrong kind, which is what a monorepo tag shaped like "pkg@1.2.3" needs. | |
| declared | No | The bump this release declares, or "auto" to read it from .changeset/*.md and then the two package.json versions. Adds a "declaration" verdict to the report: "mismatch" when a proven break outranks the declaration, "review" when the changes argue for more than was declared, "ok" otherwise. Errors when "auto" finds nothing to read, rather than reporting a pass. | |
| maxChanges | No | How many changes to include (default 50). They are ordered proven breaks first, then additions, then review-only ones, so the cap drops the least actionable end. "summary" always counts every change, and an "omitted" field reports what was left out and the number to pass to get all of it. | |
| installDeps | No | Install dependencies before analysis. In a local path with no node_modules this runs "npm install --ignore-scripts" in that directory, and adds @types/node there if it is missing. Only a local path needs it: a git ref is always installed in its temp copy. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With zero annotations, the description carries the whole burden and does so: it discloses network access for npm specs/git refs, temp-dir unpacking that is cleaned up, that local paths are not written unless installDeps is set, and that installDeps runs "npm install --ignore-scripts" and may add @types/node. It also warns that a "strict" pass does not mean nothing broke, a genuinely important caveat.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded and each sentence carries real information, but it is a single dense block that mixes invocation guidance with return-value semantics (confidence, summary.majorProven/majorReview, gate). Slightly heavier than needed for a tool description, though little is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 8-parameter tool with no annotations and no output schema, the description supplies the missing return contract (recommended bump, change list, confidence tiers, gate exits, omission reporting) and the safety/network implications. An agent could call this correctly and interpret the result without further context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema baseline is 3; the description still adds cross-parameter meaning, e.g. declared="auto" reads .changeset/*.md then package.json versions and errors when nothing is readable, and maxChanges orders proven breaks first so the cap drops the least actionable end. These are behaviors the enum/integer constraints alone do not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource: "Compare two versions of a TypeScript library and detect breaking API changes." An agent immediately knows the domain and operation. It does not, however, differentiate itself from the sibling semver_snapshot, which the agent must infer from name alone.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete conditions for the input forms (path vs git ref vs npm spec, with "npm:" prefix escape) and for mode choices such as oldAs when a monorepo tag looks like "pkg@1.2.3", and declared when grading against a declared bump. No explicit routing versus semver_snapshot, so it stops short of full when-not guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
semver_snapshotA
Extract the public API surface of a TypeScript project as JSON, without comparing it to anything. Use it to see what a package exports; use semver_compare to decide a bump. The source can be a filesystem path, a git ref, or an npm spec, the same three forms semver_compare takes, with the same network and temp-directory behaviour. Returns {"entrypoints": {: {: }}}, with each symbol's full type shape in place of its kind when "detail" is set. Symbols stop at the "maxBytes" budget, and an "omitted" field then gives the number left out and the total. A source whose surface comes back empty is reported as an analysis_failed error, not as an empty result; pass "entry" to point at the declaration file.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | Project source: a filesystem path, a git ref, or an npm spec such as "lodash@4.17.21". Defaults to current directory. | . |
| entry | No | Entry file relative to project root (e.g. "src/index.ts"). Pass an array or a comma-separated string for a package with several entry points. | |
| detail | No | Include each symbol's full type shape instead of just its kind. A shape runs a median of about 370 bytes and a wide interface tens of kilobytes, against about 35 for a name and a kind, so a large package will hit the byte budget after a few dozen symbols. | |
| pathAs | No | Force "path" to be treated as a filesystem path, a git ref, or an npm spec. "ref" is an accepted spelling of "git". Only needed when auto-detection would read the input as the wrong kind. | |
| maxBytes | No | Byte budget for the symbols in the response (default 40000). Symbols are included while it lasts, and an "omitted" field then reports how many were left out along with the total. No symbol is exempt: under "detail" a single wide interface can cost tens of kilobytes, so a budget smaller than that returns no symbols and says so rather than overshooting. Only the entrypoint keys and the "omitted" field sit outside the budget, a few hundred bytes. | |
| installDeps | No | Install dependencies before analysis. In a local path with no node_modules this runs "npm install --ignore-scripts" in that directory, and adds @types/node there if it is missing. Only a local path needs it: a git ref is always installed in its temp copy. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the behavioral burden well: it discloses non-comparison behavior, source forms, shared network/temp behavior with semver_compare, the omitted-symbol budget behavior, and that an empty surface becomes an analysis_failed error. It does not explicitly restate side-effect implications such as installDeps running npm install, though that is covered in the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense and front-loaded with the core action and sibling routing. It is longer than ideal but nearly every sentence adds operational detail, though some clauses could be more cleanly structured.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex six-parameter tool with no annotations and no output schema, the description is complete enough to call correctly. It explains the return shape, budget truncation, error behavior, source handling, and the role of entry.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description still adds value by tying detail, maxBytes, and entry to observable behavior: full type shapes, budget truncation with omitted counts, and pointing at a declaration file when the surface is empty.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: extract the public API surface of a TypeScript project as JSON. It sharply distinguishes this from the sibling by saying it does not compare and by naming semver_compare as the tool for deciding a bump.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit usage: see what a package exports with this tool, and use semver_compare to decide a bump. It also explains source forms and the empty-surface error, which tells the agent when to adjust inputs rather than treating the result as valid.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v0.15.2- Changed
semver_compare1 field changed- changed
Input schema / properties / installDeps / descriptionPrevious value: -"Install dependencies before analysis (needed for local paths without node_modules)"New value: +"Install dependencies before analysis. In a local path with no node_modules this runs \"npm install --ignore-scripts\" in that directory, and adds @types/node there if it is missing. Only a local path needs it: a git ref is always installed in its temp copy."
- Changed
semver_snapshot1 field changed- changed
Input schema / properties / installDeps / descriptionPrevious value: -"Install dependencies before analysis"New value: +"Install dependencies before analysis. In a local path with no node_modules this runs \"npm install --ignore-scripts\" in that directory, and adds @types/node there if it is missing. Only a local path needs it: a git ref is always installed in its temp copy."
2 tool updates
v0.1.0- First observed
semver_compare - First observed
semver_snapshot
TDQS
Scored across 2 tools
semver_compare and semver_snapshot have clearly distinct purposes: one diffs two versions to recommend or grade a SemVer bump, while the other only extracts an API surface. The descriptions explicitly direct agents to use snapshot for inspection and compare for bump decisions.
Both tools follow a consistent semver_<action> snake_case pattern. The names are predictable and directly reflect their operations.
Two tools is slightly thin against the typical 3-15 range, but this server has a narrow, well-defined purpose where both tools earn their place. No unrelated tools are present.
The core workflow is well covered: snapshot exposes the API surface, compare detects changes, recommends a bump, supports declared-version grading, and reports gate outcomes. Minor gaps may exist around CI integration or batch/multi-package workflows, but no obvious dead end for the stated purpose.
Maintenance
Related MCP Connectors
Detect breaking changes, generate changelogs, diff, and validate OpenAPI specs.
Check exact npm/PyPI upgrades for evidence-backed breaking changes; query APIs and components.
Breaking changes and changelogs between two npm or PyPI package versions, from real release notes.
71Diff two npm/PyPI versions or lockfiles: new install scripts, network/shell/eval calls, new deps.
Related MCP Servers
- AlicenseAqualityAmaintenanceTranslates a lockfile diff into a human-readable upgrade plan. For every dependency bump (npm or PyPI) returns semver class, breaking changes from GitHub release notes, CVEs fixed in the range, migration guide links, and a clear per-package recommendation. Bulk tool ranks up to 50 package changes in parallel by risk (security > caution > review > likely-safe > safe).258 npm1MIT
- AlicenseAqualityCmaintenanceBreaking-change diffs for npm packages, served over REST and MCP, so coding agents stop writing code against outdated API knowledge.2Functional Source , Version 1.1, MIT Future
- AlicenseNot gradedqualityCmaintenanceAnalyzes npm dependency upgrades to reveal which files and functions in your codebase break, with confidence tiers and transitive call chain tracing.4 npmMIT
- FlicenseNot gradedqualityBmaintenanceAnalyzes Git pull request diffs to detect breaking REST and GraphQL API schema changes, providing deterministic AST-level code review and breaking change alerts for CI/CD workflows.8-