Skip to content

feat!: 3.0 — major dependency upgrades + MCP draft-spec forward-compat - #56

Merged
SamMorrowDrums merged 12 commits into
mainfrom
sammorrowdrums/3-0-major-upgrades
Jun 29, 2026
Merged

SamMorrowDrums merged 12 commits into
mainfrom
sammorrowdrums/3-0-major-upgrades

Conversation

@SamMorrowDrums

@SamMorrowDrums SamMorrowDrums commented Jun 10, 2026 •

Copy link
Copy Markdown
Owner

Stacked on top of #55 (sammorrowdrums/mcp-spec-update-check). Targets that branch so the in-range bumps land first.

Summary

Refresh of the whole dependency tree to land 3.0, plus a forward-compatibility pass on the MCP draft spec (publishes end of month). The probe is hardened so a server upgrading its SDK across MCP spec revisions — 2025-06-18 → 2025-11-25 → draft — produces a clean diff when the public surface is unchanged. SDK v2 is not yet released so we don't take a dependency on it here — that will be a follow-up.

Major dependency bumps

  • zod ^3 → ^4 — breaking: z.record now requires (keyType, valueType); the one call site in src/probe.ts (custom-message response schema) was updated to z.record(z.string(), z.unknown()).
  • undici ^6 → ^8 — also updates the overrides block (resolves remaining v6 WebSocket advisories on top of chore: in-range dep updates + dist rebuild (supersedes #51) #55).
  • diff ^8 → ^9 — no code change required; the package isn't imported directly any more (the diff logic in src/diff.ts is custom).
  • @actions/core ^1 → ^3, @actions/exec ^1 → ^3, @actions/io ^1 → ^3
  • eslint ^9 → ^10, @eslint/js ^9 → ^10
  • typescript ^5 → ^6 — required adding "types": ["node"] to tsconfig.json (TS 6 no longer auto-includes ambient @types/node).
  • jest ^29 → ^30, @types/jest ^29 → ^30
  • @types/node ^22 → ^24 (CI now tests Node 22 + 24; Node 20 is dropped — see breaking changes below)
  • @vercel/ncc ^0.38 → ^0.44
  • prettier, eslint-config-prettier, typescript-eslint: latest

Kept at current major:

  • ts-jest stays on ^29.4.x — there is no v30 published yet, but 29.4.11 peer-depends on jest ^29 || ^30 and typescript >=4.3 <7, so it works with the new jest + ts.
  • @modelcontextprotocol/sdk stays on ^1.13.2 — v2 is not published.

package.json is bumped to 3.0.0-rc.0. dist/ was regenerated via npm run build (never hand-edited).

MCP draft-spec forward-compat

Targets the changes in the draft changelog without depending on SDK v2:

  • CacheableResult stripping (SEP-2461). normalizeProbeResult accepts { stripCacheHints } and removes top-level ttlMs / cacheScope from tools/list, prompts/list, resources/list, and resources/templates/list results before snapshotting.
  • initialize → server/discover (SEP-2575). Not renaming the snapshot file yet — SDK v2 isn't out. New CANONICAL_SNAPSHOT_NAMES table in probe.ts is wired in now so when the rename happens we map both spellings to the same initialize snapshot file. That way a server moving across the rename shows up as a content diff on one file instead of "removed + added".
  • capabilities.extensions (SEP-2589). No code change needed; InitializeInfo.capabilities is Record<string, unknown> with a comment confirming it stays open-ended.
  • Deterministic ordering. Already handled; doc comment now notes the draft mandates this.

Cross-spec-version diff cleanliness

This is the larger of the two probe-level changes. During the draft-spec rollout it's completely normal for the base ref to be on 2025-06-18 / 2025-11-25 and the branch to be on the draft. The diff should highlight intentional API surface changes, not protocol churn.

What now gets normalized away before snapshotting (in addition to CacheableResult above):

  • _meta protocol plumbing — an exact-key denylist is stripped from every _meta object at any depth: io.modelcontextprotocol/protocolVersion, clientInfo, clientCapabilities, subscriptionId, logLevel. Not by prefix — official extensions live under the same reserved namespace (MCP Apps' _meta.ui per SEP-1865, Tasks' io.modelcontextprotocol/related-task) and must round-trip. An emptied _meta is dropped entirely.
  • W3C trace context inside _meta — traceparent, tracestate, baggage (transport-injected for OTel propagation) are stripped from _meta.
  • initialize envelope churn — protocolVersion and capabilities.experimental are excluded from the initialize diff body. Drift on those would otherwise dominate every cross-spec diff.

What is not normalized (intentionally):

  • serverInfo.version — the SDK version is a legitimate signal worth tracking.
  • Nested ttlMs / cacheScope that live inside a tool/prompt/resource definition (those would be part of the public surface, not envelope hints).
  • Any _meta key not on the exact denylist above — including the entire MCP Apps surface (_meta.ui) and vendor extensions (x.acme/*, etc.).

Protocol-version capture

The SDK does not expose the negotiated protocol version through a public getter. We attach (or wrap) transport.setProtocolVersion(...) before client.connect(...) — the SDK calls it after a successful initialize. The captured value lands on result.initialize.protocolVersion and is then propagated into TestResult.branchProtocolVersion / baseProtocolVersion in runner.ts. Works for both stdio and HTTP transports.

Reporter banner

When base vs branch negotiated different protocol versions, the report annotates the affected configuration with:

ℹ️ MCP protocol version changed: 2025-11-25 → draft. Protocol-level plumbing is normalized away; any diff below reflects real public-surface changes.

The PR summary surfaces the same drift at the very top, so reviewers immediately know the diff was taken across spec revisions even when the diff body is empty.

Test coverage

src/__tests__/probe.test.ts (new in this PR) covers:

  • normalizeProbeResult strip behaviour for ttlMs / cacheScope (top-level only).
  • _meta scrubbing of io.modelcontextprotocol/* and W3C trace-context keys, including the "drop empty _meta" path.
  • probeResultToFiles strips cache hints for all four list/templates endpoints and leaves initialize intact except for protocolVersion + capabilities.experimental.
  • Cross-version cleanliness suite that feeds the same logical server twice — once with a clean envelope (2025-11-25) and once with the draft envelope (CacheableResult + protocol _meta) — and asserts the normalized tools/list and initialize snapshots are byte-identical. A negative test confirms a real tools delta still produces a diff.

src/__tests__/reporter.test.ts (extended): unit tests for formatProtocolVersionBanner (null when versions match / either side unknown, formatted when they differ) and integration tests asserting both the markdown report and the PR summary surface the banner.

npm run check passes — typecheck + lint + prettier + 96 jest tests across 7 suites (including the folded-in #60 metadata-diff coverage and the #58 runner / reporter / config-missing tests).

Breaking changes — runtime

  • Drops Node.js 20 support. Node 20 reached LTS end-of-life on 2026-04-30 and undici v8 (the version this PR bumps to) requires Node 22+ — it calls webidl.util.markAsUncloneable, only present on Node 21+. The CI matrix is now [22, 24] (current LTS + current Current). package.json declares "engines": { "node": ">=22" }. The action's setup_node input defaults to 22 (was 20); workflows passing an explicit node_version of 20 should bump to 22 or newer.

Folded-in PRs (closing on merge)

  • Closes test: cover tool metadata diffs #60 — test: cover tool metadata diffs (@kigland). First direct unit tests for the diff engine (compareProbeResults): asserts tool-level description changes AND nested inputSchema.properties.<arg>.description changes both surface under the tools endpoint diff. Zero production-code change, applied clean.

  • Fixes One-sided startup failure should show a fail on that side and a full diff against empty on the other #57 (closes feat: diff one-sided startup failures against an empty baseline #58) — feat: diff one-sided startup failures against an empty baseline. When exactly one side fails to start (e.g. a PR introduces a new server configuration behind a CLI flag that doesn't exist on the compare ref), the failed side is now treated as an empty ProbeResult and the working side's full surface renders as added/removed instead of collapsing to an opaque Connection closed error. New config-missing diff category, non-fatal under fail_on_error (only genuine both-sides probe failures still hard-error). New compareConfigResults() exported from runner.ts for unit testability. Reporter classifies into passing / changed / config-missing / error and renders a dedicated 🚫 callout in both the markdown report and PR summary. Adds configMissing?: { side, error } to TestResult. The 2.4.0 version bump from the original PR was dropped (3.0 already moved to 3.0.0-rc.0).

Conflict resolution notes (for the #58 fold)

The only non-trivial conflict was in src/reporter.ts: both PR #56 (this branch) and PR #58 added new pre-diff renderer blocks to generateMarkdownReport. Resolved by keeping BOTH — the protocol-version banner renders first (cross-spec drift heads-up), then the config-missing callout (if applicable), then the existing diff section. Same change applied to generatePRSummary. No semantic conflict; both features render independently into the same lines array.

Follow-up (not in this PR)

  • Add a check for the server-card endpoint once the SEP lands (issue Reminder to add server card #65).
  • Adopt @modelcontextprotocol/sdk v2 once released, switch the SDK call from initialize to server/discover, and re-evaluate whether client.getProtocolVersion() lands publicly so we can drop the transport-hook trick.

Notes

Follow-up: MCP Apps + extension preservation (commit 8181d45)

Caught a regression in my own _meta scrubber before merge: the original implementation matched io.modelcontextprotocol/* by prefix, which would silently delete the entire MCP Apps surface (_meta.ui per SEP-1865) and Tasks' io.modelcontextprotocol/related-task. Flipped to an exact-key denylist so transport plumbing is stripped but extension surfaces round-trip.

Also widened ToolsResult / PromptsResult / ResourcesResult / ResourceTemplatesResult element types with index signatures so annotations, outputSchema, _meta, and MCP Apps fields are first-class at the type level (they were already captured at runtime — the types just understated it).

New kitchen-sink test in src/__tests__/probe.test.ts round-trips a tool with annotations + outputSchema + _meta.ui, a UI resource with the full MCP Apps _meta.ui shape (CSP connectDomains / resourceDomains / frameDomains / baseUriDomains, permissions), prompt arguments, and a resource template — every advertised field has to survive normalization. 74 tests pass.

@SamMorrowDrums
SamMorrowDrums changed the base branch from sammorrowdrums/mcp-spec-update-check to main June 29, 2026 08:21
@SamMorrowDrums
SamMorrowDrums marked this pull request as ready for review June 29, 2026 08:21
Copilot AI review requested due to automatic review settings June 29, 2026 08:21
@SamMorrowDrums
SamMorrowDrums force-pushed the sammorrowdrums/3-0-major-upgrades branch from 258c4c9 to e774c8f Compare June 29, 2026 08:24

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.

Pull request overview

This PR prepares mcp-server-diff for a 3.0 release by upgrading major dependencies and updating the probe/reporting pipeline to remain forward-compatible with upcoming MCP draft-spec changes, with an emphasis on producing clean diffs across spec revisions.

Changes:

  • Major dependency upgrades (TypeScript 6, Jest 30, Zod 4, Undici 8, Actions toolkit v3, ESLint 10) and regenerated dist/.
  • Added stateless server/discover probing with normalization to strip protocol-shaped noise (_meta plumbing, CacheableResult hints, tool-annotation defaults) and canonicalize snapshot naming.
  • Added/extended tests and documentation covering cross-spec diff cleanliness and protocol-version drift banners.
Show a summary per file
File Description
tsconfig.json Adds types: ["node"] for TS6 ambient node typings behavior.
src/types.ts Widens probe result types for forward-compatible fields; adds protocol version fields to results.
src/runner.ts Plumbs negotiated protocol versions into TestResult for reporting.
src/reporter.ts Adds protocol-version drift banners in markdown report and PR summary.
src/probe.ts Implements server/discover stateless probing, protocol-version capture hook, and normalization/canonical snapshot behavior.
src/tests/reporter.test.ts Adds unit/integration coverage for protocol-version banner formatting and placement.
src/tests/probe.test.ts Adds extensive normalization + cross-spec cleanliness + regression test coverage.
src/tests/fixtures/github-mcp-server-wire.json Adds real-wire fixture data for initialize vs discover cross-version normalization tests.
README.md Documents 3.0 migration notes and cross-spec-version diffing behavior.
package.json Bumps package version to 3.0.0-rc.0 and upgrades major dependency versions.
dist/types.d.ts Regenerated types reflecting updated source interfaces.
dist/reporter.d.ts Regenerated declarations including protocol banner API.
dist/probe.d.ts Regenerated declarations including discover probing + canonical snapshot naming exports.
dist/licenses.txt Updated bundled license attributions from rebuild.
dist/cli/types.d.ts Regenerated CLI types reflecting updated source interfaces.
dist/cli/reporter.d.ts Regenerated CLI declarations including protocol banner API.
dist/cli/probe.d.ts Regenerated CLI declarations including discover probing + canonical snapshot naming exports.
dist/cli/licenses.txt Updated bundled CLI license attributions from rebuild.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 9/19 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment thread src/probe.ts
Copilot AI review requested due to automatic review settings June 29, 2026 08:25
SamMorrowDrums added a commit that referenced this pull request Jun 29, 2026
undici 8 (already on the 3.0 branch) requires Node 22+: the v8 runtime
uses webidl.util.markAsUncloneable which is only present on Node 21+.
This surfaced as a CI failure on PR #56 (test (20) red, test (22) ok).

Node 20 reached LTS end-of-life on 2026-04-30, so dropping it from a
major release is the right call. Matrix replaces 20 with 24 so CI
exercises current LTS (22) plus current Current (24).

- .github/workflows/ci.yml: matrix [20, 22] → [22, 24]; the dist-up-
  to-date guard now runs on the 22 leg (was 20)
- action.yml: setup_node default 20 → 22
- package.json: add engines.node ">=22"
- README.md: replace "tested on Node 20 + 22" with a dedicated
  "Dropped support: Node.js 20" note in the Migration section
- CONTRIBUTING.md: Node 20+ → Node 22+

86/86 tests still pass locally on Node v22.23.1.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

Review details

  • Files reviewed: 12/22 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment thread src/probe.ts
Comment on lines +565 to +569
// The SDK doesn't expose the negotiated protocol version via a public
// getter. It does, however, call `transport.setProtocolVersion(...)` after
// initialize if the transport implements it. Wrap (or attach) that hook so
// we can capture the version for the snapshot. Works for stdio + HTTP.
let negotiatedProtocolVersion: string | undefined;
Comment thread src/probe.ts
Comment thread package.json
{
"name": "mcp-server-diff",
"version": "2.2.0",
"version": "3.0.0-rc.0",
Copilot AI review requested due to automatic review settings June 29, 2026 08:31
SamMorrowDrums and others added 11 commits June 29, 2026 10:31
- zod ^3 → ^4 (z.record signature now requires key+value schemas)
- undici ^6 → ^8 (and overrides block)
- diff ^8 → ^9
- @actions/{core,exec,io} ^1 → ^3
- eslint ^9 → ^10, @eslint/js ^9 → ^10
- typescript ^5 → ^6 (tsconfig now needs explicit "types": ["node"])
- jest ^29 → ^30, @types/jest ^29 → ^30
- @types/node ^22 → ^24 (CI still tests Node 20+22)
- @vercel/ncc ^0.38 → ^0.44
- prettier, eslint-config-prettier, typescript-eslint to latest
- ts-jest kept at ^29.4.x (peer-compatible with jest 30 + ts 6, no v30 yet)

Bump package version to 3.0.0-rc.0 and rebuild dist/.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prepare for the MCP draft spec (publishes end of month) without taking
a dependency on SDK v2:

- normalizeProbeResult accepts { stripCacheHints } and removes top-level
  `ttlMs` / `cacheScope` (CacheableResult, SEP-2461) from tools/list,
  prompts/list, resources/list, and resources/templates/list. These
  freshness hints vary run-to-run and would otherwise create diff noise.
- New probe.test.ts covers the strip behaviour and confirms nested
  ttlMs/cacheScope are preserved (only the result envelope is touched).
- TODO comment near initialize snapshot referencing SEP-2575 (the draft
  renames `initialize` → `server/discover`).
- Comment on InitializeInfo.capabilities confirming it stays open-ended
  for the draft's `capabilities.extensions` (SEP-2589).
- Doc comment notes the draft now mandates the deterministic ordering
  we already do.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summarise the major dep bumps, the new CacheableResult stripping
(SEP-2461), and note that adopting the renamed server/discover method
(SEP-2575) is deferred until SDK v2 ships.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The base ref and the current branch may negotiate different MCP protocol
versions during the draft-spec rollout. We want a server upgrading its
SDK without changing its public surface to produce an empty diff.

probe.ts
- Capture the negotiated MCP protocol version via a transport-level
  setProtocolVersion hook (works for stdio + HTTP). The SDK doesn't
  expose this through a public getter, so we wrap/attach it ourselves.
  Store it on result.initialize.protocolVersion.
- normalizeProbeResult now recursively scrubs `_meta` of protocol-only
  plumbing: keys prefixed with `io.modelcontextprotocol/`
  (protocolVersion, clientInfo, clientCapabilities, subscriptionId,
  logLevel) and W3C trace context (traceparent, tracestate, baggage).
  An emptied `_meta` is dropped entirely so it never appears in diffs.
- New normalizeInitializeForDiff drops `protocolVersion` and
  `capabilities.experimental` from the initialize snapshot body. Drift
  on those would otherwise dominate every cross-spec diff; the reporter
  surfaces protocol-version changes separately.
- Add CANONICAL_SNAPSHOT_NAMES so future endpoint renames (e.g. SEP-2575
  initialize → server/discover) map to one stable filename instead of
  showing up as removed+added.

types.ts
- InitializeInfo gains optional protocolVersion.
- TestResult gains branchProtocolVersion + baseProtocolVersion.

runner.ts
- Propagate per-probe protocolVersion into TestResult.

reporter.ts
- New formatProtocolVersionBanner helper.
- Per-config markdown report inserts the banner inline above the diff.
- PR summary surfaces version drift at the very top so reviewers
  immediately know the diff was taken across spec revisions.

tests
- probe.test.ts: cross-version cleanliness suite asserts that
  tools/list and initialize snapshots are byte-identical when only
  protocol envelope differs, and that real public-surface changes are
  still flagged. Plus unit coverage for the new _meta scrubbing.
- reporter.test.ts: banner unit tests + integration through
  generateMarkdownReport / generatePRSummary.

README
- New "Cross-spec-version diffing" subsection in Migration to 3.0
  documenting exactly what's normalized away (and what isn't).

Rebuild dist/. All 72 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous _meta scrubber stripped every key under the
io.modelcontextprotocol/* prefix. That namespace is reserved by the spec
but is also where official extensions live, so the prefix-based filter
would silently delete:

- MCP Apps (SEP-1865) UI metadata at _meta.ui — CSP, permissions, etc.
- Tasks' io.modelcontextprotocol/related-task linkage
- Any future official extension under the reserved namespace

Switch to an exact-key denylist limited to true transport plumbing
(protocolVersion, clientInfo, clientCapabilities, subscriptionId,
logLevel) plus W3C trace context. Everything else round-trips, which is
the whole point of this tool.

Also widen ToolsResult / PromptsResult / ResourcesResult /
ResourceTemplatesResult element types with index signatures so the type
layer no longer undersells the runtime shape (annotations, outputSchema,
_meta, MCP Apps fields are all first-class now).

Adds a kitchen-sink regression test that round-trips a tool with
annotations + outputSchema + _meta.ui, a UI resource with the full MCP
Apps _meta.ui surface (csp, permissions), prompt arguments, and a
resource template — every field must survive normalization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The MCP spec defines defaults for ToolAnnotations hints (readOnlyHint=false,
destructiveHint=true, idempotentHint=false, openWorldHint=true). A server
that omits a hint is semantically identical to one that emits the default
value, but an SDK upgrade can flip the wire encoding between the two — for
example go-sdk v1.7.0-pre.1 dropped `omitempty` on ReadOnlyHint and
IdempotentHint, so every tool gains `readOnlyHint: false` /
`idempotentHint: false` on the wire. Without normalization, a pure SDK
bump produces a noisy ~113-tool diff on github-mcp-server.

normalizeProbeResult now drops annotation fields that equal their spec
default, and drops the annotations object entirely if nothing is left.
This is the tools-list analogue of the existing _meta and CacheableResult
scrubbing — same principle: protocol-shape churn out, public surface in.

Verified by a four-case suite including an explicit omit-vs-default-emit
regression test mirroring the go-sdk v1.6 ↔ v1.7 transition.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adopt the SEP-2575 / SEP-2243 stateless probe path so each server is
probed at its OWN newest spec version, not silently negotiated down onto
the legacy initialize handshake. This is what makes the cross-version
diff honest:

- New-spec server (go-sdk >= v1.7.0-pre.1): probed via server/discover
  at 2026-07-28. No initialize, no notifications/initialized, no
  Mcp-Session-Id. Reserved _meta on every request (protocolVersion,
  clientInfo, clientCapabilities). HTTP responses parsed as SSE
  (text/event-stream, single frame); stdio uses line-delimited JSON-RPC.
- Legacy server (go-sdk v1.6.1 / pre-2026 SDKs): falls back to the
  existing client.connect() initialize handshake.

Fallback detection follows the empirical wire shape: a v1.6.1 server
returns HTTP 400 text/plain ('Unsupported protocol version' or
'JSON RPC not handled server/discover unsupported'), NOT a JSON-RPC
-32601. The orchestrator treats any HTTP non-200, any non-JSON body,
any JSON-RPC error, or a missing supportedVersions array as 'fall
back'; only HTTP 200 + a DiscoverResult with supportedVersions[]
counts as success.

normalizeInitializeForDiff now also strips top-level ttlMs / cacheScope
because the discover handshake carries CacheableResult hints too. The
canonical 'initialize' snapshot filename means base-via-initialize and
branch-via-discover collapse to one content diff. Public-interface
signals like 'instructions' and the capabilities shape are deliberately
NOT normalized — the go-sdk v1.7.0-pre.1 discover-omits-instructions
regression is exactly the kind of change this tool exists to surface.

Tests use fixtures lifted from real wire transcripts against
github-mcp-server v1.6.1 (initialize @ 2025-11-25) and v1.7.0-pre.1
(discover @ 2026-07-28). The integration test's stdio fixture only
speaks the legacy spec, so it exercises the discover-fails-fallback
path live.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…1.7.0-pre.1

Adds src/__tests__/fixtures/github-mcp-server-wire.json with the raw
JSON-RPC bodies captured live against:
- v1.7.0-pre.1 stateless server/discover @ 2026-07-28 (tools/list with
  the SEP-2243 reserved _meta path, get_me as the representative tool,
  the -32020 header-mismatch error envelope, the -32602 missing-_meta
  error envelope)
- v1.6.1 legacy initialize @ 2025-11-25 (paired baseline: instructions
  present, listChanged + logging capabilities, get_me annotations
  without the omitempty-dropped idempotentHint default)

The new test suite drives the case-study assertion off these bodies
verbatim:
- get_me tools-list snapshot: annotation defaults stripped, cache hints
  stripped, real `icons` field preserved as public surface
- initialize slot: protocolVersion + cache hints stripped, but the
  capability-shape delta (logging dropped, listChanged dropped,
  resources added) stays as a visible signal
- discover-omits-instructions vs initialize-emits-instructions
  regression preserved as a diff signal

base64 image payloads on icons are pinned as `<TRUNCATED>` to keep
the fixture small; refresh instructions are documented in the JSON
header so future contributors can regenerate from new wire captures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…egression

`instructions` is OPTIONAL in both InitializeResult and DiscoverResult
per the draft spec (required keys on DiscoverResult are capabilities,
resultType, serverInfo, supportedVersions). So a server emitting
instructions on initialize but not discover is spec-conformant — it's
a behavioral inconsistency / public-interface difference, not a
conformance failure. The diff still surfaces it (clients adopting
discover observe the gap), just labelled neutrally.

No code change — only test names, comments, and README narrative.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eslint 10.4→10.6, prettier 3.8→3.9, undici 8.4→8.5, typescript-eslint
8.61→8.62, @types/node 24.13.1→24.13.2. SDK already on 1.29.0 (latest).
Production audit: 0 vulnerabilities. Dev-only vulns are jest transitives
that can't be fixed without downgrading ts-jest.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
undici 8 (already on the 3.0 branch) requires Node 22+: the v8 runtime
uses webidl.util.markAsUncloneable which is only present on Node 21+.
This surfaced as a CI failure on PR #56 (test (20) red, test (22) ok).

Node 20 reached LTS end-of-life on 2026-04-30, so dropping it from a
major release is the right call. Matrix replaces 20 with 24 so CI
exercises current LTS (22) plus current Current (24).

- .github/workflows/ci.yml: matrix [20, 22] → [22, 24]; the dist-up-
  to-date guard now runs on the 22 leg (was 20)
- action.yml: setup_node default 20 → 22
- package.json: add engines.node ">=22"
- README.md: replace "tested on Node 20 + 22" with a dedicated
  "Dropped support: Node.js 20" note in the Migration section
- CONTRIBUTING.md: Node 20+ → Node 22+

86/86 tests still pass locally on Node v22.23.1.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SamMorrowDrums
SamMorrowDrums force-pushed the sammorrowdrums/3-0-major-upgrades branch from 1421255 to ce959c6 Compare June 29, 2026 08:33

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.

Review details

Comments suppressed due to low confidence (1)

src/probe.ts:518

  • probeViaInitialize still identifies as version "2.0.0" while the stateless path sends PROBE_CLIENT_INFO.version "3.0". This makes the client identity inconsistent across probe paths and could affect server behavior and/or show up in protocol metadata (even if normalized away). Use the shared PROBE_CLIENT_INFO version to keep both paths aligned.
  const client = new Client(
    {
      name: "mcp-server-diff-probe",
      version: "2.0.0",
    },
  • Files reviewed: 12/22 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment thread src/probe.ts
Comment thread .github/workflows/ci.yml
strategy:
matrix:
node-version: [20, 22]
node-version: [22, 24]
Copilot AI review requested due to automatic review settings June 29, 2026 08:35

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.

Review details

  • Files reviewed: 12/22 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment thread src/probe.ts
- src/probe.ts: probed-child stderr now logs at debug instead of info.
  Servers can write tokens/headers to stderr during startup; surfacing
  that at info on every CI run is noisy and risks leaking secrets into
  workflow logs. The existing comment already said "without spamming";
  this matches the intent.

- src/version.ts: new module reading the version from package.json
  (build-time import-attribute; ncc inlines the JSON). Single source
  of truth for the package version.

- src/cli.ts: --version reads from PACKAGE_VERSION instead of the
  stale hardcoded "v2.1.1".

- src/probe.ts: PROBE_CLIENT_INFO.version reads from PACKAGE_VERSION;
  the legacy initialize Client() constructor now consumes the same
  constant instead of the stale hardcoded "mcp-server-diff-probe/2.0.0".
  Wire captures show the same clientInfo regardless of whether the
  probe took the stateless server/discover path or the initialize
  fallback.

- src/__tests__/version.test.ts: smoke test asserting PACKAGE_VERSION
  matches package.json verbatim and is non-empty semver-shaped, so
  the version wiring can't drift silently. 88/88 tests now pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SamMorrowDrums
SamMorrowDrums merged commit 74c0058 into main Jun 29, 2026
6 checks passed
SamMorrowDrums added a commit that referenced this pull request Jun 29, 2026
undici 8 (already on the 3.0 branch) requires Node 22+: the v8 runtime
uses webidl.util.markAsUncloneable which is only present on Node 21+.
This surfaced as a CI failure on PR #56 (test (20) red, test (22) ok).

Node 20 reached LTS end-of-life on 2026-04-30, so dropping it from a
major release is the right call. Matrix replaces 20 with 24 so CI
exercises current LTS (22) plus current Current (24).

- .github/workflows/ci.yml: matrix [20, 22] → [22, 24]; the dist-up-
  to-date guard now runs on the 22 leg (was 20)
- action.yml: setup_node default 20 → 22
- package.json: add engines.node ">=22"
- README.md: replace "tested on Node 20 + 22" with a dedicated
  "Dropped support: Node.js 20" note in the Migration section
- CONTRIBUTING.md: Node 20+ → Node 22+

86/86 tests still pass locally on Node v22.23.1.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

One-sided startup failure should show a fail on that side and a full diff against empty on the other

3 participants