feat!: 3.0 — major dependency upgrades + MCP draft-spec forward-compat#56
Draft
SamMorrowDrums wants to merge 5 commits into
Draft
Conversation
- 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.recordnow requires(keyType, valueType); the one call site insrc/probe.ts(custom-message response schema) was updated toz.record(z.string(), z.unknown()).undici^6 → ^8 — also updates theoverridesblock (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 insrc/diff.tsis custom).@actions/core^1 → ^3,@actions/exec^1 → ^3,@actions/io^1 → ^3eslint^9 → ^10,@eslint/js^9 → ^10typescript^5 → ^6 — required adding"types": ["node"]totsconfig.json(TS 6 no longer auto-includes ambient@types/node).jest^29 → ^30,@types/jest^29 → ^30@types/node^22 → ^24 (CI still tests Node 20 + 22, action recipes are unchanged)@vercel/ncc^0.38 → ^0.44prettier,eslint-config-prettier,typescript-eslint: latestKept at current major:
ts-jeststays on^29.4.x— there is no v30 published yet, but29.4.11peer-depends onjest ^29 || ^30andtypescript >=4.3 <7, so it works with the new jest + ts.@modelcontextprotocol/sdkstays on^1.13.2— v2 is not published.package.jsonis bumped to3.0.0-rc.0.dist/was regenerated vianpm run build(never hand-edited).MCP draft-spec forward-compat
Targets the changes in the draft changelog without depending on SDK v2:
normalizeProbeResultaccepts{ stripCacheHints }and removes top-levelttlMs/cacheScopefromtools/list,prompts/list,resources/list, andresources/templates/listresults before snapshotting.initialize→server/discover(SEP-2575). Not renaming the snapshot file yet — SDK v2 isn't out. NewCANONICAL_SNAPSHOT_NAMEStable inprobe.tsis wired in now so when the rename happens we map both spellings to the sameinitializesnapshot 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.capabilitiesisRecord<string, unknown>with a comment confirming it stays open-ended.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-25and 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):
_metaprotocol plumbing — an exact-key denylist is stripped from every_metaobject at any depth:io.modelcontextprotocol/protocolVersion,clientInfo,clientCapabilities,subscriptionId,logLevel. Not by prefix — official extensions live under the same reserved namespace (MCP Apps'_meta.uiper SEP-1865, Tasks'io.modelcontextprotocol/related-task) and must round-trip. An emptied_metais dropped entirely._meta—traceparent,tracestate,baggage(transport-injected for OTel propagation) are stripped from_meta.initializeenvelope churn —protocolVersionandcapabilities.experimentalare excluded from theinitializediff 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.ttlMs/cacheScopethat live inside a tool/prompt/resource definition (those would be part of the public surface, not envelope hints)._metakey 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(...)beforeclient.connect(...)— the SDK calls it after a successfulinitialize. The captured value lands onresult.initialize.protocolVersionand is then propagated intoTestResult.branchProtocolVersion/baseProtocolVersioninrunner.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:
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:normalizeProbeResultstrip behaviour forttlMs/cacheScope(top-level only)._metascrubbing ofio.modelcontextprotocol/*and W3C trace-context keys, including the "drop empty_meta" path.probeResultToFilesstrips cache hints for all four list/templates endpoints and leavesinitializeintact except forprotocolVersion+capabilities.experimental.2025-11-25) and once with the draft envelope (CacheableResult + protocol_meta) — and asserts the normalizedtools/listandinitializesnapshots are byte-identical. A negative test confirms a realtoolsdelta still produces a diff.src/__tests__/reporter.test.ts(extended): unit tests forformatProtocolVersionBanner(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 checkpasses — typecheck + lint + prettier + 72 jest tests across 4 suites.Follow-up (not in this PR)
@modelcontextprotocol/sdkv2 once released, switch the SDK call frominitializetoserver/discover, and re-evaluate whetherclient.getProtocolVersion()lands publicly so we can drop the transport-hook trick.Notes
sammorrowdrums/mcp-spec-update-check. Once chore: in-range dep updates + dist rebuild (supersedes #51) #55 merges intomain, retarget this PR tomain(or merge chore: in-range dep updates + dist rebuild (supersedes #51) #55 first, then rebase).Follow-up: MCP Apps + extension preservation (commit
8181d45)Caught a regression in my own
_metascrubber before merge: the original implementation matchedio.modelcontextprotocol/*by prefix, which would silently delete the entire MCP Apps surface (_meta.uiper 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/ResourceTemplatesResultelement types with index signatures soannotations,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.tsround-trips a tool withannotations+outputSchema+_meta.ui, a UI resource with the full MCP Apps_meta.uishape (CSPconnectDomains/resourceDomains/frameDomains/baseUriDomains, permissions), prompt arguments, and a resource template — every advertised field has to survive normalization. 74 tests pass.