Skip to content

chore(ci): diff each changed server's MCP interface, for every server - #5459

Open
cliffhall wants to merge 2 commits into
v2/mainfrom
v2/chore/5428-per-server-interface-diff
Open

cliffhall wants to merge 2 commits into
v2/mainfrom
v2/chore/5428-per-server-interface-diff

Conversation

@cliffhall

@cliffhall cliffhall commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Closes #5428

Description

The MCP interface diff now runs only for the servers a change touches, and it covers all seven servers, not just everything.

  • Selection (scripts/lib/interface-diff-servers.mjs, used by both CI and the gate). Changed paths are read NUL-delimited, so Git's C-quoting cannot hide a change. A server is diffed when one of these changes:
    • a file under src/<server>/ (for a Python server this includes its uv.lock)
    • for a TypeScript server, the root tsconfig.json, which every TS server extends
    • for a TypeScript server, a package-lock.json edit that changes a package that server resolves. The workspace's dependency tree is walked in the base and head lockfiles the way Node resolves it, so a lockfile rewrite that reaches no server selects none.
    • every server, when the diff tooling changes (the script, its server table, win-shell-args, the workflow) or the pinned mcp-server-diff version changes. A transitive bump under mcp-server-diff does not count, because it moves no probe.
  • scripts/interface-diff.mjs
    • new flags: --server <name> (repeatable), --all, and --list, which prints the selection as JSON and diffs nothing
    • all selected TypeScript bases share one npm ci and are then built one by one; each Python base gets its own uv sync --frozen --no-dev
    • a Python head is probed with uv run --frozen --no-sync --project src/<server>, using the same "read an environment, don't build one" rule as the smoke stage
    • each server gets its own boot arguments: an allowed dir for filesystem, a throwaway repo for git, a pinned zone for time, and a throwaway graph file for memory
    • one report per server, written to <report-dir>/<server>/
    • a change that touches no server passes with "nothing to diff"
  • Workflow: everything-mcp-diff.yml keeps its file name (renaming it needs to be staged across a milestone merge; see CI: rename everything-mcp-diff.yml to mcp-interface-diff.yml, staged across a milestone merge #5463) but is now "MCP Interface Diff"
    • a changes job resolves the base and runs npm run interface-diff -- --list (no install needed)
    • diff and comment fan out as a matrix, one leg per server. Each server gets its own sticky comment (marker <!-- mcp-interface-diff:<server> -->) and its own mcp-diff-report-<server> artifact.
    • kept: the read-only/write job split, the release and workflow_dispatch triggers, and the fork-PR fallback to the summary and artifact
    • comment checks each matrix server name against the known list before using it, because that list came from a job that ran PR code
    • the status context is now Interface diff (dispatched)
    • both version-PR workflows now dispatch it. The CalVer stamps touch Python servers that are now diffed, so Prepare Python Release dispatches it too.
  • Gate: the interface-diff stage uses the same selection against the merge-base with origin/v2/main.
  • Docs: docs/quality-gate.md, AGENTS.md (the tree entry and the gate description), RELEASING.md, the pre-push-gate and release skills, and version-pr-ci.test.mjs.

Server Details

  • Server: none (repository-wide: CI, gate tooling and docs)
  • Changes to: no server's code. Only how each server's interface is diffed.

Motivation and Context

The issue gives two examples. #5426 (a filesystem minimatch bump) and #5427 (a release audit touching only the root package.json and package-lock.json) both ran the everything diff, because the workflow's paths filter matched the root lockfile. The other six servers had no interface diff at all.

How Has This Been Tested?

This change has no client-facing surface, so the evidence is targeted probes.

Selection replayed on real merges (each merge commit against its first parent, with the real lockfiles):

PR Files changed Before (paths filter) Now
#5426 filesystem minimatch .changeset/…, package-lock.json, src/filesystem/package.json everything filesystem
#5427 release audit package.json, package-lock.json (a qs override bump) everything everything. qs is in its resolved tree via express; the other servers don't load it.
#5436 everything icons src/everything/… everything everything
#5435 sequentialthinking src/sequentialthinking/… none sequentialthinking

Every server probes cleanly: npm run interface-diff -- --all against origin/v2/main takes about 13 seconds and exits 0. The head counts (tools / prompts / resources / templates):

Server Tools Prompts Resources Templates Verdict
everything 22 4 8 5 ✅ no changes
filesystem 16 0 0 1 ✅ no changes
memory 9 0 1 0 ✅ no changes
sequentialthinking 1 0 0 0 ✅ no changes
fetch 1 1 0 0 ✅ no changes
git 14 2 0 0 ✅ no changes
time 3 0 0 0 ✅ no changes

A Python change is detected: I edited the description of the get_current_time tool in the working tree and ran npm run interface-diff -- --server time. Exit 0, and it reported:

- tools[get_current_time].description: "Get current time in a specific timezone"
+ tools[get_current_time].description: "Get the current time in a specific timezone (planted)"

This PR's own CI run changes the tooling, so the changes job should select all seven servers and post seven sticky comments. That is the live check of the matrix and comment jobs.

Tests:

  • scripts/lib/interface-diff-servers.test.mjs (new) covers lockfile resolution and closure, selection by own files, tsconfig and tooling, and lockfile edits that must not select a server. It also pins that the server table matches the src/ directories.
  • scripts/interface-diff.test.mjs covers the new flags, chooseServers and startCommand.
  • npm run local:gate exits 0.

Breaking Changes

None for users. For maintainers:

  • the dispatched status is now Interface diff (dispatched) (the workflow file name is unchanged)
  • the everything sticky comment uses a new marker, so a PR that is open at merge gets a new comment beside the old one
  • --report-dir now writes <dir>/<server>/report.md

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Protocol Documentation
  • My changes follow MCP security best practices (untrusted PR code still runs only in read-only jobs, and the comment job validates the server names it receives)
  • I have updated the server's README accordingly (not applicable: no server's tools, configuration or docs change; the repository docs that name the diff are updated)
  • I have added a changeset (npm run changeset) if this changes what a TypeScript server publishes (not applicable: nothing published changes)
  • I have tested this with an LLM client (not applicable: no client-observable change; see the targeted probes above)
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling (a server whose base does not build, or whose head is missing, is reported for that server without stopping the others)
  • I have documented all environment variables and configuration options (the new flags are in the script header, docs/quality-gate.md and the pre-push-gate skill)

Additional context

  • A server's sticky comment stays as it is if a later push stops touching that server.
  • The selection reads both sides of renames (--no-renames) and includes uncommitted and untracked files locally, because the gate probes the working tree's build.

🤖 Generated with Claude Code

The interface diff ran on any PR that touched the root lockfile, which is
almost every TypeScript PR, and covered only `everything`.

- scripts/lib/interface-diff-servers.mjs: the server table (how each of
  the seven boots for a probe) and the selection: a server's own files,
  the root tsconfig (TypeScript servers), a package-lock.json edit that
  changes a package the server resolves (its tree walked in both
  lockfiles), and every server when the diff tooling or the pinned
  mcp-server-diff changes.
- scripts/interface-diff.mjs: --server, --all and --list; builds each
  selected base (one npm ci for the TypeScript ones, uv sync for the
  Python ones) and probes Python servers through uv run --no-sync; one
  report per server under <report-dir>/<server>/.
- everything-mcp-diff.yml -> mcp-interface-diff.yml: a `changes` job
  lists the servers, `diff` and `comment` fan out per server with a
  per-server sticky comment and artifact. The security split, the release
  and dispatch triggers and the fork fallback are kept. Both version-PR
  workflows dispatch it, since the CalVer stamps now touch a diffed server.
- The gate's stage diffs only the servers changed since the merge-base,
  through the same selection.
- Docs: quality-gate.md, AGENTS.md, RELEASING.md, the pre-push-gate and
  release skills.

Closes #5428

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall cliffhall added the v2 label Nov 5, 2026
@changeset-bot

changeset-bot Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: b3f7149

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

everything: MCP interface diff

Base: 281b1671ed30 (Merge pull request #5437 from modelcontextprotocol/changeset-release/v2/main)
Head: the build in this checkout, at 6dde692ade95 (Merge b3f7149 into 594c2be)

Diffed because it was named (--server).

✅ No interface changes detected.

Tools Prompts Resources Resource templates
Base 22 4 8 5
Head 22 4 8 5

Updated by this run for b3f714938d0bfb0bd5046ac4916e8393d20c4731.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

filesystem: MCP interface diff

Base: 281b1671ed30 (Merge pull request #5437 from modelcontextprotocol/changeset-release/v2/main)
Head: the build in this checkout, at 6dde692ade95 (Merge b3f7149 into 594c2be)

Diffed because it was named (--server).

✅ No interface changes detected.

Tools Prompts Resources Resource templates
Base 16 0 0 1
Head 16 0 0 1

Updated by this run for b3f714938d0bfb0bd5046ac4916e8393d20c4731.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

fetch: MCP interface diff

Base: 281b1671ed30 (Merge pull request #5437 from modelcontextprotocol/changeset-release/v2/main)
Head: the build in this checkout, at 6dde692ade95 (Merge b3f7149 into 594c2be)

Diffed because it was named (--server).

✅ No interface changes detected.

Tools Prompts Resources Resource templates
Base 1 1 0 0
Head 1 1 0 0

Updated by this run for b3f714938d0bfb0bd5046ac4916e8393d20c4731.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

git: MCP interface diff

Base: 281b1671ed30 (Merge pull request #5437 from modelcontextprotocol/changeset-release/v2/main)
Head: the build in this checkout, at 6dde692ade95 (Merge b3f7149 into 594c2be)

Diffed because it was named (--server).

⚠️ Interface changes detected in 1 part(s) of the interface. Review them below to confirm they are intended.

Tools Prompts Resources Resource templates
Base 14 2 0 0
Head 14 2 0 0

tools

--- base/tools.json
+++ target/tools.json

+ tools[git_log].inputSchema.properties.max_count.description: "Maximum number of commits to show. Must be at least 1."
+ tools[git_log].inputSchema.properties.max_count.minimum: 1

Updated by this run for b3f714938d0bfb0bd5046ac4916e8393d20c4731.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

time: MCP interface diff

Base: 281b1671ed30 (Merge pull request #5437 from modelcontextprotocol/changeset-release/v2/main)
Head: the build in this checkout, at 6dde692ade95 (Merge b3f7149 into 594c2be)

Diffed because it was named (--server).

✅ No interface changes detected.

Tools Prompts Resources Resource templates
Base 3 0 0 0
Head 3 0 0 0

Updated by this run for b3f714938d0bfb0bd5046ac4916e8393d20c4731.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

memory: MCP interface diff

Base: 281b1671ed30 (Merge pull request #5437 from modelcontextprotocol/changeset-release/v2/main)
Head: the build in this checkout, at 6dde692ade95 (Merge b3f7149 into 594c2be)

Diffed because it was named (--server).

✅ No interface changes detected.

Tools Prompts Resources Resource templates
Base 9 0 1 0
Head 9 0 1 0

Updated by this run for b3f714938d0bfb0bd5046ac4916e8393d20c4731.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

sequentialthinking: MCP interface diff

Base: 281b1671ed30 (Merge pull request #5437 from modelcontextprotocol/changeset-release/v2/main)
Head: the build in this checkout, at 6dde692ade95 (Merge b3f7149 into 594c2be)

Diffed because it was named (--server).

✅ No interface changes detected.

Tools Prompts Resources Resource templates
Base 1 0 0 0
Head 1 0 0 0

Updated by this run for b3f714938d0bfb0bd5046ac4916e8393d20c4731.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The renamed workflow is not yet dispatchable on the default branch, blocking required interface-diff checks on version PRs, and C-quoted Git paths can cause changed servers to be skipped and falsely pass the diff.

2 open issues
What changed in this PR

Extends MCP interface diffs from everything to all seven reference servers, while selecting only servers affected by a change, as requested in #5428.

Changes:

  • Shares server selection between CI and the local gate, including resolved dependency-tree comparisons.
  • Adds server-specific probes, reports, artifacts and sticky PR comments.
  • Updates version-workflow dispatches, tests and maintainer documentation.
File Description
scripts/​version-pr-ci.test.mjs Updates dispatch targets and expected status names.
scripts/​lib/​interface-diff-servers.test.mjs Tests dependency resolution and server selection.
scripts/​lib/​interface-diff-servers.mjs Defines server launch settings and selection rules.
scripts/​interface-diff.test.mjs Tests new flags, launch commands and reports.
scripts/​interface-diff.mjs Builds and probes selected servers with individual reports.
RELEASING.md Documents interface-diff dispatches for version PRs.
docs/​quality-gate.md Explains selective diffs in CI and the local gate.
AGENTS.md Updates workflow references and gate descriptions.
.github/​workflows/​version-packages.yml Dispatches the renamed interface-diff workflow.
.github/​workflows/​prepare-python-release.yml Adds interface-diff dispatches for Python version PRs.
.github/​workflows/​mcp-interface-diff.yml Adds selection and per-server diff and comment matrices.
.claude/​skills/​release/​SKILL.md Updates release status and dispatch instructions.
.claude/​skills/​pre-push-gate/​SKILL.md Documents per-server reruns and troubleshooting.

Review effort: Balanced

Comment thread .github/workflows/version-packages.yml Outdated
Comment on lines 94 to 95
for workflow in typescript.yml python.yml dco.yml mcp-interface-diff.yml; do
gh workflow run "$workflow" --ref "$BRANCH"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b3f7149: the file keeps its name, everything-mcp-diff.yml, so both version workflows still dispatch a file that exists on main. Its display name ("MCP Interface Diff") and status context (Interface diff (dispatched)) still change, because those come from the file content at the dispatched ref. The workflow header now explains why the name stays. The rename itself is filed as #5463, staged across a milestone merge.

Comment thread scripts/interface-diff.mjs Outdated
Comment on lines +348 to +359
const lines = (text) => text.split("\n").filter(Boolean);
let baseLock = null;
try {
baseLock = JSON.parse(git("show", `${sha}:package-lock.json`));
} catch {
// No lockfile at the base: every locked package reads as changed.
}
const headFile = path.join(repoRoot, "package-lock.json");
return {
changedFiles: [
...lines(git("diff", "--name-only", "--no-renames", sha)),
...lines(git("ls-files", "--others", "--exclude-standard")),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b3f7149: both git diff --name-only and git ls-files --others now use -z and split on NUL. They are also called through run rather than git(), which trims its output and could clip a leading space. Checked with an untracked src/time/tests/test_é.py: git prints it C-quoted ("src/time/tests/test_\303\251.py"), and --list now selects time.

…elimited

- GitHub dispatches only a workflow whose file is on the default branch,
  and both version workflows dispatch this one by file name from v2/main,
  so the rename to mcp-interface-diff.yml would break their dispatch until
  the next milestone merge. The file stays everything-mcp-diff.yml; its
  display name and status context still change.
- git diff --name-only and ls-files C-quote a path holding a non-ASCII
  character, a tab or a newline, which then matched no server directory
  and passed with nothing to diff. Both now use -z.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 1: both findings were defects in what this PR added, and both are fixed in b3f7149 (gate green).

The PR body is updated to match.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI: run the MCP interface diff only for servers a PR changes, and add it for every server

2 participants