Skip to content

feat(mt#4854): Migrate the MCP surface to the SDK v2 packages at current-protocol parity - #3547

Merged
edobry merged 5 commits into
mainfrom
task/mt-4854
Sep 1, 2026
Merged

edobry merged 5 commits into
mainfrom
task/mt-4854

Conversation

@minsky-ai

@minsky-ai minsky-ai Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

@modelcontextprotocol/sdk (v1) never implements the MCP 2026-07-28 revision at any published version — npm carries exactly one dist-tag, latest → 1.30.0 (published 2026-07-27, one day before the spec), whose LATEST_PROTOCOL_VERSION is 2025-11-25, with no inputResponses/resultType anywhere in it. Support ships in a separate v2 package family (@modelcontextprotocol/core|client|server|node, all 2.0.0).

This migrates every SDK import site to v2 at current-protocol parity: no wire change, no serving-entry change. Adopting 2026-07-28 stays with the parent mt#4608, gated on a decision ask#11232 deliberately deferred. This is the vendor's own staging — upgrade-to-v2.md and support-2026-07-28.md are two separate guides.

Key changes — what the codemod did vs. what was hand-edited (SC4)

Codemod (bunx @modelcontextprotocol/codemod v1-to-v2), reviewed not merged blind. Its dry run matched the planned file set exactly — 34 changes across 10 files + package.json — and every rewrite was checked against the SDK's own importMap.ts: Server/spec types → /server, StdioServerTransport → /server/stdio, StreamableHTTPServerTransport → NodeStreamableHTTPServerTransport from /node (Express hands us Node req/res, matching the guide's decision rule), JSONRPCMessageSchema → /core, Client + InMemoryTransport both from /client (the guide requires one package per linked pair), McpError/ErrorCode → ProtocolError/ProtocolErrorCode.

Hand-edited — three things the codemod flagged but could not resolve (8 warnings, 6 @mcp-codemod-error markers, all discharged):

  1. diagnostic-capture.ts — v1's flat extra became v2's structured ctx, and the two fields this capture reads moved differently: sessionId stayed top-level, _meta moved to ctx.mcpReq._meta (per the SDK's contextPropertyMap.ts). Reading ctx._meta would not throw — it would silently capture undefined forever, and nothing asserts on this research output.
  2. server.ts tools/list — v2 types Tool.inputSchema as { type: "object"; … } where v1 took a bare object. The {} fallback became { type: "object" }. Not a wire change: the only production addTool caller (command-mapper.ts:508) always supplies an inputSchema, so the fallback is unreachable there. Corrected rather than cast away — a bare {} was never spec-valid.
  3. Four test suites — v2 routes tools/call through _invokeInputRequiredCapableHandler, which reads ctx.mcpReq.requestState() before delegating, so the bare {} these tests passed as extra now throws inside the SDK. Fourteen reaches into the SDK's private _requestHandlers map collapse into one src/mcp/test-support/tools-call-handler.ts helper — which also reduces mt#4844's migration from fourteen sites to one.

Review response

R1 BLOCKING — "@modelcontextprotocol/sdk still present in bun.lock". The finding is correct and my criterion was wrong. @modelcontextprotocol/inspector@0.16.2 (a devDependency, out of scope) and its three sub-packages all declare "@modelcontextprotocol/sdk": "^1.17.0", so a v1 lockfile entry is not removable by this task at all. package.json itself declares none.

It pointed at something real that the wording hid: bun hoists the transitive v1 to node_modules/@modelcontextprotocol/sdk, so a stray import "@modelcontextprotocol/sdk" would still resolve and typecheck — the migration had no regression guard. Fixed with an ESLint no-restricted-imports ban on the package and its subpaths, which is now what enforces SC1. AT4 amended to its achievable form.

R1 NON-BLOCKING (notify) — correct, and latent rather than hypothetical: server.ts:1471 passes ctx.mcpReq.notify to buildProgressReporter whenever a request carries a progressToken. Added an async no-op default. Class scan run: grep -rn 'ctx\.mcpReq\.' over src/packages/scripts shows notify is the only other member our source reaches, so the helper now supplies exactly the reachable set.

R1 NON-BLOCKING (private _requestHandlers) — acknowledged, no change. That is mt#4844's scope; this PR shrank the surface from fourteen reaches to one to make that a single edit.

R2 NON-BLOCKING — "caller override order is inverted". Correct, and it was a comment contradicting its own code: the literal mcpReq sat after the outer spread, so a caller-supplied mcpReq was silently discarded while the docblock claimed caller-first. Fixed by spreading the caller's mcpReq over the defaults, and covered by a new test file so the contract is enforced rather than asserted.

Spec deviations recorded

  • SC1 corrected (12 → 10 files). The planning enumeration used grep -l on the package string, which counts mentions as imports. src/commands/mcp/start-command.ts, services/reviewer/src/mcp-client.ts and scripts/mt2677-live-verify-progress-notifications.ts only name the SDK in comments — the reviewer client's matched line literally reads "Plain fetch only — no @modelcontextprotocol/sdk dependency". The codemod independently confirmed 10.
  • SC3 + AT4 withdrawn/replaced. SC3 required the two families to coexist because the reviewer client would stay on v1 pending mt#2856. It was never a consumer, so v1 is removed outright. AT4 then had to be corrected a second time per R1 above.

Known overlap, acknowledged rather than discovered at merge

PR #3412 (mt#4639, open since 2026-08-27) touches 3 of this PR's files: src/mcp/server.ts (its import block), scripts/deploy-minsky-mcp.ts, scripts/verify-mcp-hidden-params.ts. Proceeding deliberately: #3412 already owes a rebase regardless of this PR (#3532 merged and modified two of its files), the edits are semantically independent (catch bodies vs. import specifiers), and blocking a 10-file migration on a 313-file PR inverts the cost.

Testing

Execution evidence:

SC1 — no v1 import remains anywhere:

$ grep -rn '@modelcontextprotocol/sdk' --include='*.ts' src packages services scripts | grep import
NONE

Every remaining specifier is v2: /server (Server, spec types, ProtocolError/ProtocolErrorCode, LATEST_PROTOCOL_VERSION), /server/stdio (StdioServerTransport), /client (Client, InMemoryTransport), /node (NodeStreamableHTTPServerTransport), /core (JSONRPCMessageSchema).

SC2 — no wire change, measured live rather than assumed:

BEFORE (v1, main's node_modules/@modelcontextprotocol/sdk/dist/esm/types.js):
  LATEST_PROTOCOL_VERSION = '2025-11-25'
AFTER  (v2, session's node_modules/@modelcontextprotocol/core/dist):
  LATEST_PROTOCOL_VERSION = "2025-11-25"

$ bun scripts/verify-mcp-hidden-params.ts
[verify-mcp-hidden-params] handshake ok at protocolVersion 2025-11-25 (requested 2025-11-25)

SC5 — the serving entry is untouched; the 2026-07-28 opt-in is NOT introduced:

$ grep -rn 'createMcpHandler\|serveStdio\|versionNegotiation' --include='*.ts' src packages scripts
NONE

Tests — each src/mcp file in its own process. Passing several to one bun test invocation reproduces the Bun defect CLAUDE.md documents (no summary, exit 0), which is a non-answer, not a pass, and is not counted here:

src/mcp/server.test.ts                            46 pass  0 fail
src/mcp/server-tool-name-resolution.test.ts        4 pass  0 fail
src/mcp/client-capabilities.test.ts               23 pass  0 fail
src/mcp/drift-gate.test.ts                        28 pass  0 fail
src/mcp/server-in-flight-tool-calls.test.ts        1 pass  0 fail
src/mcp/server-response-size-guard.test.ts         3 pass  0 fail
src/mcp/test-support/tools-call-handler.test.ts    4 pass  0 fail   (new this PR)

$ bun test --preload ./tests/setup.ts packages/domain/src/errors/mcp-structured-errors.test.ts \
    src/commands/mcp/start-command.test.ts scripts/deploy-minsky-mcp.test.ts
 96 pass
 0 fail
 236 expect() calls
Ran 96 tests across 3 files. [16.65s]

validate_typecheck clean across 8 projects; validate_lint clean across 4296 files.

Bundle-boot smoke — asserting the health body's service identity, not just the status code (per mt#3148):

$ bun run build            → dist/minsky.js, 26,048,532 bytes
$ bun run dist/minsky.js mcp start --http --host=127.0.0.1 --port=48799
$ curl http://127.0.0.1:48799/health
HTTP 200
{"status":"ok","service":"minsky-mcp","server":"Minsky MCP Server","transport":"http",
 "persistence":{"mode":"connected"},"ready":true,"db":"ok"}

Negative control — three, each run and observed rather than asserted:

(1) the v2 ctx fix, round 1 — src/mcp/server.test.ts before the fix:
 161 pass / 8 fail / 1 error
 TypeError: undefined is not an object (evaluating 'ctx.mcpReq.requestState')
   at _invokeInputRequiredCapableHandler (@modelcontextprotocol/server/dist/mcp-DXXb3Vv3.mjs:878)

(2) the v2 ctx fix, round 2 — caught by the pre-push gated suite, NOT by my targeted run:
 src/mcp/drift-gate.test.ts                    26 pass / 2 fail
 src/mcp/server-in-flight-tool-calls.test.ts    0 pass / 1 fail
 src/mcp/server-response-size-guard.test.ts     0 pass / 3 fail
 — same TypeError in all six

(3) the R1 lint ban — a scratch file importing v1, deleted after the run:
 1:1  error  '@modelcontextprotocol/sdk/server/index.js' import is restricted ...  no-restricted-imports
 2:1  error  '@modelcontextprotocol/sdk' import is restricted ...                  no-restricted-imports
 ✖ 2 problems (2 errors, 0 warnings)
 — fires on both the bare specifier and the subpath form

(4) the R2 override fix — caller spread removed from the helper, new suite re-run:
 (fail) getToolsCallHandler (mt#4854) > a caller-supplied mcpReq member WINS over the default, and the others survive
 3 pass / 1 fail
 — exactly the discriminating test fails; the other three are insensitive to the ordering by construction

Round 2 is worth naming for the reviewer: those three files break from the dependency change while the related-test selector keys on the files the diff changed, so it could not reach them by construction. A dependency upgrade's blast radius is its dependents, not its diff. Filed as mt#4862.

Deploy verification: src/mcp/server.ts ships in minsky-mcp, so this is deploy surface. After merge I will run deployment_wait-for-latest with notBefore = the merge timestamp and expectCommitSha = the merge SHA, read buildIdentity, and assert the /health body's service field, per §10.

edobry added 3 commits August 31, 2026 23:16
…t current-protocol parity

@modelcontextprotocol/sdk (v1) never implements the MCP 2026-07-28 revision at any
published version — npm carries one dist-tag, latest -> 1.30.0, whose
LATEST_PROTOCOL_VERSION is 2025-11-25. Support ships in a separate v2 package family.
This migrates every SDK import site to it at current-protocol parity: no wire change,
no serving-entry change. Adopting the 2026-07-28 revision stays with the parent,
mt#4608, gated on a decision ask#11232 deliberately deferred.

Applied the vendor codemod (bunx @modelcontextprotocol/codemod v1-to-v2), whose
dry-run scope matched the planned file set exactly: 34 changes across 10 files plus
package.json. Imports resolve per the SDK's own importMap — Server and the spec types
to /server, StdioServerTransport to /server/stdio, StreamableHTTPServerTransport to
NodeStreamableHTTPServerTransport from /node (Express hands us Node req/res, so the
Node variant is correct), JSONRPCMessageSchema to /core, Client + InMemoryTransport
both from /client (the guide requires one package per linked pair), and
McpError/ErrorCode to ProtocolError/ProtocolErrorCode.

Three manual changes the codemod could not make:

- diagnostic-capture.ts: v1's flat `extra` became v2's structured `ctx`, and the two
  fields this capture reads moved DIFFERENTLY — sessionId stayed top-level, _meta
  moved to ctx.mcpReq._meta. Reading ctx._meta would not throw, it would silently
  capture undefined forever, and nothing asserts on this output.
- server.ts tools/list: v2 types Tool.inputSchema as { type: "object"; ... } where v1
  took a bare object. The `{}` fallback becomes `{ type: "object" }` — NOT a wire
  change, because the only production addTool caller (command-mapper.ts:508) always
  supplies inputSchema, so the fallback is unreachable there. Corrected rather than
  cast away: a bare `{}` was never spec-valid.
- server.test.ts: v2 routes tools/call through _invokeInputRequiredCapableHandler,
  which reads ctx.mcpReq.requestState() before delegating, so the bare `{}` these
  tests passed as `extra` now throws inside the SDK. Eight sites reaching the SDK's
  private _requestHandlers map collapse into one getToolsCallHandler helper, which
  also shrinks mt#4844's migration surface from eight copies to one.

Corrects the planning enumeration: `grep -l` for the package string conflated mentions
with imports. src/commands/mcp/start-command.ts and
services/reviewer/src/mcp-client.ts only NAME the SDK in comments — the reviewer
client is explicitly "plain fetch only, no @modelcontextprotocol/sdk dependency". The
real import surface is 10 files, and the coexistence SC3 planned for is moot: v1 is
removed outright.

Typecheck clean across 8 projects, lint clean across 4292 files, 169 tests pass across
the 6 related suites.
… pre-push gate caught

The first commit fixed the eight `_requestHandlers` reaches in server.test.ts and
passed a targeted run of the six suites related to the files I changed. The pre-push
gated suite then failed three MORE src/mcp files with the identical
`ctx.mcpReq.requestState()` TypeError: drift-gate.test.ts (2),
server-response-size-guard.test.ts (3), server-in-flight-tool-calls.test.ts (1).

These are exactly the other three files mt#4844 enumerates as reaching the SDK's
private handler map. They break under the SDK upgrade without being edited by it, so
a related-test selection keyed on CHANGED files could not reach them — the gate is
what caught it, working as intended.

Rather than copy the wrapper a fourth time, the helper moves to
src/mcp/test-support/tools-call-handler.ts and all four suites import it. That turns
fourteen copies of the private-map reach into one, so mt#4844's migration becomes a
single edit; the module's docblock carries the v2 context rationale and drift-gate's
two sites lose a now-redundant hand-written cast.

Evidence, per file and in its own process (multiple src/mcp files in one bun test
invocation hit the Bun defect CLAUDE.md documents — no summary, exit 0, which is not
a pass and is recorded here as the non-answer it is):

  src/mcp/server.test.ts                        46 pass  0 fail
  src/mcp/server-tool-name-resolution.test.ts    4 pass  0 fail
  src/mcp/client-capabilities.test.ts           23 pass  0 fail
  src/mcp/drift-gate.test.ts                    28 pass  0 fail   (was 26 pass / 2 fail)
  src/mcp/server-in-flight-tool-calls.test.ts    1 pass  0 fail   (was  0 pass / 1 fail)
  src/mcp/server-response-size-guard.test.ts     3 pass  0 fail   (was  0 pass / 3 fail)

Plus 96 pass / 0 fail across mcp-structured-errors.test.ts, start-command.test.ts and
deploy-minsky-mcp.test.ts. Typecheck clean across 8 projects; lint clean across 4293
files.
@minsky-ai minsky-ai Bot added the authorship/co-authored Co-authored by human and AI agent label Sep 1, 2026
@minsky-reviewer

minsky-reviewer Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Minsky Reviewer Status

Verdict: APPROVED — no blocking findings
Review: View review
Model: openai/gpt-5 | Tokens: 334K prompt, 7K completion | Duration: 109s
Mode: normal

Commands

  • /review — request a fresh review

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


Key block: The replacement criterion for SC3/AT4 requires no @modelcontextprotocol/sdk in bun.lock; however, bun.lock still contains multiple v1 SDK entries (including an explicit @modelcontextprotocol/sdk@1.17.0 and nested inspector-related sections). This contradicts the spec/AT and must be resolved or the spec updated. Non-blocking notes: (1) test helper’s injected ctx lacks mcpReq.notify, which could break future tests using progressToken — suggest adding a no-op notify; (2) centralizing private _requestHandlers access is better, but migrating tests to a supported seam (connectTransport + InMemoryTransport) remains advisable (mt#4844). Aside from the lockfile inconsistency, the code migration appears coherent and parity-preserving. I did not re-grep the entire tree; verification of SC2/AT1-AT3 depended on live runs outside the diff.

Findings

  • [BLOCKING] bun.lock:1 — Replacement criterion (SC3/AT4) not met: @modelcontextprotocol/sdk still present in bun.lock
    The spec’s replacement for SC3/AT4 explicitly requires that @modelcontextprotocol/sdk appear in neither package.json nor bun.lock. While package.json removes the dependency, bun.lock still contains multiple @modelcontextprotocol/sdk entries via nested dependencies (e.g., an entry for "@modelcontextprotocol/sdk@1.17.0" and numerous @modelcontextprotocol/sdk/* subtrees associated with the inspector packages). See the diff hunks showing additions for "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.17.0", …] and related sections (@modelcontextprotocol/inspector-*/…/@modelcontextprotocol/sdk). This violates the criterion’s stated check that the lockfile not contain any @modelcontextprotocol/sdk entries. Either (a) purge the v1 package from the lock by updating/removing the transitive inspector dependencies that pull it in, or (b) update the task spec/criteria to accept transitive lockfile presence and adjust AT4 accordingly. As written, this is a blocking mismatch against the Success Criteria.
  • [NON-BLOCKING] src/mcp/test-support/tools-call-handler.ts:48 — Helper-supplied ctx lacks mcpReq.notify, risking future failures when tests set a progressToken
    buildProgressReporter in src/mcp/server.ts now expects a sendNotification function compatible with v2’s ctx.mcpReq.notify when a caller provides _meta.progressToken. The test helper’s wrapper context sets only mcpReq: { requestState: () => undefined } (see lines 53-62) and omits notify. Current tests likely don’t exercise progress (no _meta.progressToken), but adding one in a future test would cause a runtime error when the server attempts ctx.mcpReq.notify(...). Suggestion: include a no-op notify: async () => {} on the helper’s mcpReq to fully satisfy the v2 context shape and make tests robust to progress-token use.
  • [NON-BLOCKING] src/mcp/server.test.ts:546 — Tests still reach SDK private _requestHandlers (now via helper) — consider migrating to a supported seam
    The new getToolsCallHandler helper centralizes accesses to the SDK Server’s private _requestHandlers map, which is an improvement over ~14 scattered sites. However, this still couples tests to an internal detail of the SDK. Per testing-standards.mdc §Testable Design, prefer exercising behavior through supported seams. The code already exposes MinskyMCPServer.connectTransport(...) and v2 provides InMemoryTransport in @modelcontextprotocol/client — together these offer a public, supported path to drive requests. Suggest migrating these tests to construct a linked pair and call through the client, or add a designed observable that exposes the handler invocation without peering into private state (tracked in mt#4844). Marking as non-blocking advisory for test design.

Spec verification

Criterion Status Evidence
SC1 — every in-scope SDK import site is on v2. The 10 sites that actually IMPORT the SDK … Verified by: grep … returns only v2 specifiers, and no @modelcontextprotocol/sdk import remains anywhere. Met package.json removes "@modelcontextprotocol/sdk" and adds v2 packages (package.json: dependencies diff). Code imports updated across touched files: e.g., src/mcp/server.ts top imports now from "@modelcontextprotocol/server", "@modelcontextprotocol/node"; src/mcp/server-tool-name-resolution.test.ts imports { Client, InMemoryTransport } from "@modelcontextprotocol/client"; scripts/deploy-minsky-mcp.ts and scripts/verify-mcp-hidden-params.ts import LATEST_PROTOCOL_VERSION from "@modelcontextprotocol/server". No v1 imports are present in the shown diff hunks.
SC2 — no wire change, measured not assumed. For each client in AT1’s control set, the negotiated protocol version and header are byte-identical before and after. Unverifiable This criterion depends on live-run measurements (AT1) that are outside the diff. The PR body includes sample output, but as reviewers we cannot reproduce or verify from code alone.
SC3 — WITHDRAWN, premise false. Replacement criterion: @modelcontextprotocol/sdk is removed from package.json entirely and no file imports it; validate_* and build are clean afterward. Not Met While package.json removes the v1 SDK and code imports are migrated, bun.lock still contains multiple entries for "@modelcontextprotocol/sdk" (e.g., entries for "@modelcontextprotocol/sdk@1.17.0" and many nested @modelcontextprotocol/inspector-* → sdk references). AT4 replacement also requires bun.lock not to contain v1. This violates the replacement criterion as written; follow-up needed to remove or reconcile transitive v1 lock entries.
SC4 — the codemod's output is reviewed, not merged blind. State in the PR body which files the codemod rewrote and which were hand-edited. Met PR Description details the codemod vs hand-edited changes, including specific unresolved codemod warnings and how they were addressed (diagnostic-capture ctx mapping, inputSchema typing, and test helper extraction).
SC5 — no serving-entry change. src/mcp/server.ts keeps constructing a hand-built Server; createMcpHandler / serveStdio are NOT introduced here. Met src/mcp/server.ts constructs Server directly and uses StdioServerTransport or NodeStreamableHTTPServerTransport; there is no import or usage of createMcpHandler/serveStdio. See src/mcp/server.ts:1-20 and constructor/setups.
validate_typecheck and validate_lint clean; bun scripts/run-tests-gated.ts passes; the bundle-boot smoke passes. Unverifiable These are CI/runtime results not visible in the diff. The PR body claims they passed, but we cannot verify from repository contents alone.
AT1 — negative control, then comparison. BEFORE any edit, record the negotiated protocol version… After the migration, all three are identical. Unverifiable Live-run evidence outside the diff; cannot be verified here.
AT2 — the bundle boots (build and start, health 200 with identity). Unverifiable Requires building and running the bundle; outside the diff.
AT3 — no v1 import remains, anywhere (grep). Residual non-import mentions are fine. Unverifiable Repo-wide grep cannot be performed within this review context beyond the shown hunks. The visible code changes remove v1 imports in the touched files, but a full-tree assertion is out of scope here.
AT4 — REPLACED (coexistence withdrawn). Replacement asserts removal is complete and consistent — @modelcontextprotocol/sdk appears in neither package.json nor bun.lock, and the four v2 packages resolve from node_modules at 2.0.0. Not Met bun.lock in the diff still contains multiple @modelcontextprotocol/sdk entries (e.g., an explicit mapping for "@modelcontextprotocol/sdk@1.17.0" and numerous nested sections). This contradicts the replacement acceptance test as written.

Adoption sweep

Symbol Kind Consumers found Classification Notes
getToolsCallHandler function src/mcp/server.test.ts — imports and uses to obtain tools/call handler, src/mcp/server-response-size-guard.test.ts — imports and uses, src/mcp/drift-gate.test.ts — imports and uses, src/mcp/server-in-flight-tool-calls.test.ts — imports and uses Adopted New test-support export scoped to tests under src/mcp; consumers present in four suites as shown in the diff.

Documentation impact

  • no-update-needed — This PR migrates internal MCP SDK imports to the v2 package family at protocol parity without changing the serving entry or documented behavior. No new user-facing commands, flags, or behavior changes are introduced; server remains hand-constructed (no createMcpHandler/serveStdio). Documentation updates are not included in the PR and do not appear necessary for parity-only internal dependency changes.

…ect the criterion that was wrong

BLOCKING: "@modelcontextprotocol/sdk still present in bun.lock" — AT4 said it should
appear in neither package.json nor bun.lock. The finding is correct and the criterion
was wrong: @modelcontextprotocol/inspector@0.16.2 (a devDependency, out of scope) and
its three sub-packages all declare "@modelcontextprotocol/sdk": "^1.17.0", so a v1
lockfile entry is not removable by this task at all. package.json itself declares no
v1 dependency.

But the finding pointed at something real that the wording hid: bun hoists the
transitive v1 to node_modules/@modelcontextprotocol/sdk, so a stray
`import "@modelcontextprotocol/sdk"` would still RESOLVE and typecheck. The migration
had no regression guard — typecheck, tests and the existing lint set would all pass on
a file that reverted to v1.

So this does not just reword the criterion. eslint.config.js gains a
no-restricted-imports pattern banning "@modelcontextprotocol/sdk" and its subpaths,
which is now what actually enforces SC1. Negative control, run and observed:

  $ printf 'import { Server } from "@modelcontextprotocol/sdk/server/index.js";
            import { McpError } from "@modelcontextprotocol/sdk";' > control.ts
  $ bunx eslint control.ts
    1:1  error  '@modelcontextprotocol/sdk/server/index.js' import is restricted ...  no-restricted-imports
    2:1  error  '@modelcontextprotocol/sdk' import is restricted ...                  no-restricted-imports
  ✖ 2 problems (2 errors, 0 warnings)

Both the bare specifier and the subpath form fire. Control file deleted after the run.

NON-BLOCKING (1) — helper ctx lacks mcpReq.notify: correct, and more than
forward-looking. server.ts:1471 passes ctx.mcpReq.notify to buildProgressReporter
whenever a request carries a progressToken (mt#2677). No test sets one today, so the
gap is latent, but the first that did would fail on an undefined callee rather than on
its own assertion. Added an async no-op default — the spec makes progress delivery
optional — with the spread staying caller-first so a test asserting on progress
overrides it without touching the helper.

NON-BLOCKING (2) — tests still reach private _requestHandlers via the helper:
acknowledged, no change. That is mt#4844's scope, and this PR reduced the surface from
fourteen reaches to one to make that migration a single edit. The helper's docblock
records it.

AT4 amended in the spec to its achievable form: no direct dependency, v2 resolving at
2.0.0, every remaining bun.lock v1 entry transitive via the inspector, and the lint
rule failing any source file that imports v1.

Verification: typecheck clean across 8 projects; lint clean across 4295 files; the six
src/mcp suites pass individually (46/28/1/3/4/23, 0 fail).

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


Re-verification of R1 blocking found it addressed: the v1 SDK remains in bun.lock only as a transitive devDependency via @modelcontextprotocol/inspector, and the PR added an ESLint no-restricted-imports rule to prevent any source from importing v1. The migration to v2 packages is reflected in package.json and src imports, and no serving-entry changes were introduced. I found one minor issue in the new test helper: the context merge order overwrites a caller-provided ctx.mcpReq despite the comment promising caller-first; suggested a small merge fix. Otherwise, changes look coherent and low-risk, with codemod vs hand-edits clearly documented. Approving — please consider the non-blocking helper fix.

Findings

  • [NON-BLOCKING] src/mcp/test-support/tools-call-handler.ts:59 — Caller override order is inverted: helper overwrites a provided ctx.mcpReq despite comment claiming caller-first
    The returned wrapper builds the context as { ...(ctx as Record<string, unknown>), mcpReq: { requestState: () => undefined, notify: async () => {} } }. This places the default mcpReq AFTER the spread, so any mcpReq the caller supplies is overwritten. The block comment above states the opposite — that the spread is caller-first so tests can override defaults — but the code does not match it.

Suggested fix: move the defaults before the spread, or merge at the field level so caller wins, e.g. handler(request, { mcpReq: { requestState: () => undefined, notify: async () => {}, ...(ctx as any).mcpReq }, ...(ctx as Record<string, unknown>) }). This keeps today's behavior and allows future tests to inject a custom mcpReq without editing the helper.

Spec verification

Criterion Status Evidence
SC1 — every in-scope SDK import site is on v2; no @modelcontextprotocol/sdk imports remain and package.json dependency entry is updated. Met package.json: dependencies include @modelcontextprotocol/{server,client,node,core} at ^2.0.0 and contain no @modelcontextprotocol/sdk entry. ESLint rule added to forbid importing v1: eslint.config.js:746-757 adds no-restricted-imports pattern banning "@modelcontextprotocol/sdk" and subpaths.
SC2 — no wire change, measured not assumed (protocol negotiation identical before/after). Unverifiable Live-run negotiation evidence is not present in the repo diff. The criterion depends on execution output (AT1); this review cannot observe external run results.
SC3 — WITHDRAWN; replacement: v1 removed outright (no package.json entry; build/lint/tests clean). Met package.json has no @modelcontextprotocol/sdk dependency and v2 packages are present. Lint enforcement added in eslint.config.js to block any future v1 imports.
SC4 — the codemod's output is reviewed, not merged blind; PR body states which files were codemodded vs hand-edited. Met PR description includes sections "Key changes — what the codemod did vs. what was hand-edited" and enumerates codemod rewrites and manual fixes (diagnostic-capture.ts ctx mapping; tools/list inputSchema; test harness helper).
SC5 — no serving-entry change (no createMcpHandler/serveStdio/versionNegotiation introduced). Met src/mcp/server.ts continues to construct a hand-built Server via new Server(...) and StdioServerTransport/NodeStreamableHTTPServerTransport. No references to createMcpHandler/serveStdio/versionNegotiation are present.
validate_typecheck/lint clean; gated tests pass; bundle-boot smoke passes. Unverifiable These are CI/run-time outcomes and not encoded in the repo files. The diff shows no failing test artifacts; execution results are not observable from the code alone.

Documentation impact

  • no-update-needed — This PR migrates internal SDK imports from v1 to the v2 package family at protocol parity with no serving-entry changes. No new CLI/API surface or behavior is introduced; docs are unaffected. I checked for any docs specifically referencing the server construction entry (createMcpHandler/serveStdio) and none are added in this diff.

…nd cover it

NON-BLOCKING, and correct: the docblock claimed the caller's ctx was spread
caller-first, while the literal `mcpReq` key sat AFTER the outer spread and therefore
overwrote whatever the caller passed. A comment asserting behaviour the code does not
have is worse than no comment, so this fixes the code rather than the sentence.

The outer spread cannot express the intent — it sets sibling keys, so any literal
`mcpReq` after it discards the caller's. The defaults now live INSIDE `mcpReq` with the
caller's own members spread over them, which gives the documented semantics: supply one
member and it wins, supply none and both defaults stand, supply `notify` and
`requestState` still survives.

Added `src/mcp/test-support/tools-call-handler.test.ts` (4 tests) so the contract is
enforced rather than asserted — the helper is now load-bearing for four suites and
mt#4844 will build on it, and its own behaviour had no coverage at all. That absence is
what let the inverted ordering ship in the first place.

Negative control, run and observed: with the caller spread removed from the helper,

  (fail) getToolsCallHandler (mt#4854) > a caller-supplied mcpReq member WINS over the
         default, and the others survive
  3 pass / 1 fail

Exactly the discriminating test fails; the other three are insensitive to the ordering
by construction, which is stated in the file so a later reader does not mistake their
passing for coverage of this property.

Verification: typecheck clean across 8 projects; lint clean across 4296 files; the new
suite 4 pass / 0 fail; the four suites that consume the helper unchanged at 46/28/1/3,
0 fail.

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


Re-verified the latest commit and scoped diff. The prior BLOCKING concern about lingering v1 SDK imports is addressed elsewhere in the PR (package.json shows only v2 packages); nothing in this fix commit regresses that. The new getToolsCallHandler helper correctly supplies the v2-required ctx.mcpReq members with sensible defaults and now spreads caller overrides last, matching the stated contract; accompanying tests exercise both defaulting and override order. I find no critical new defects introduced by these additions. Some process assertions (wire-parity measurements, codemod provenance) are inherently unverifiable from source, but do not block merge. Verdict: APPROVE.

Spec verification

Criterion Status Evidence
SC1 — every in-scope SDK import site is on v2. Met server tests and helpers import only v2 packages: e.g., src/mcp/server.test.ts:7-8 import from "@modelcontextprotocol/node" and "@modelcontextprotocol/server" (v2 family). No v1 package appears in package.json dependencies; only v2 packages are declared (package.json: dependencies).
SC2 — no wire change, measured not assumed. Unverifiable This criterion depends on live negotiation measurements (AT1) outside the diff. The PR body claims these were run, but that evidence is not a repo artifact. Unable to verify negotiated protocol-version parity from files alone.
SC3 — WITHDRAWN, premise false. Replaced by: v1 is removed outright. Met package.json shows no @modelcontextprotocol/sdk dependency. Only v2 packages (@modelcontextprotocol/server|client|node|core) are present (package.json: dependencies). Import sites in changed tests also reference v2 packages (src/mcp/server.test.ts:7-8).
SC4 — the codemod's output is reviewed, not merged blind. Unverifiable This is a process assertion documented in the PR body rather than a code artifact. The repo changes are consistent with both codemod and hand edits, but the review-vs-codemod provenance itself cannot be verified from source files.
SC5 — no serving-entry change. src/mcp/server.ts keeps constructing a hand-built Server; createMcpHandler / serveStdio are NOT introduced here. Met src/mcp/server.test.ts continues to exercise a hand-constructed SDK Server via stdio/HTTP transports, and no imports of createMcpHandler/serveStdio or version-negotiation helpers are present in visible imports (src/mcp/server.test.ts:6-13). The package.json dependencies include only core v2 packages and do not add @modelcontextprotocol/hono or web-entry adapters that would imply a serving-entry change.
validate_typecheck/lint/tests/bundle smoke clean Unverifiable These are CI-run commands whose outputs are not in-repo artifacts. While the PR body lists passing runs, we cannot verify from source alone in this review context.

Adoption sweep

Symbol Kind Consumers found Classification Notes
getToolsCallHandler function src/mcp/server.test.ts:13 — imported and used to drive tools/call via SDK server, src/mcp/test-support/tools-call-handler.test.ts:12 — imported for unit tests of the helper Adopted

Documentation impact

  • no-update-needed — This PR introduces internal test-support helper code and migrates SDK imports to the v2 package family at protocol parity, with no serving-entry change and no user-facing CLI or API surface changes. package.json shows dependency swaps only (no new commands or flags). No docs files are touched in the diff, and no behavior changes are introduced per the task spec (SC5).

@edobry
edobry merged commit 741a71d into main Sep 1, 2026
20 checks passed
@edobry
edobry deleted the task/mt-4854 branch September 1, 2026 05:18

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


The prior BLOCKING concern about lingering v1 MCP SDK usage is addressed: package.json removes the v1 SDK, v2 packages are added, and an ESLint no-restricted-imports rule now enforces against regressions. All in-scope import sites have been migrated to v2, server handlers updated for v2’s context/typing, and a new test helper consolidates fragile private-map access while providing the required v2 ctx members; accompanying tests validate override order and defaults. I found no new critical issues introduced by the fixes. One advisory, non-blocking check remains to verify LATEST_PROTOCOL_VERSION is re-exported by @modelcontextprotocol/server. Overall, the change meets the spec’s criteria within what is verifiable from the diff, and is ready to merge.

Findings

  • [NON-BLOCKING] scripts/deploy-minsky-mcp.ts:67 — NEEDS VERIFICATION: LATEST_PROTOCOL_VERSION source package in v2
    The import was changed from @modelcontextprotocol/sdk/types.js to @modelcontextprotocol/server (scripts/deploy-minsky-mcp.ts:67 and scripts/verify-mcp-hidden-params.ts:23). In v2, some constants moved to @modelcontextprotocol/core and may or may not be re-exported by server. If LATEST_PROTOCOL_VERSION is not re-exported by @modelcontextprotocol/server, this will break typecheck/runtime. If CI passed, this is fine — otherwise, consider importing from @modelcontextprotocol/core directly. (This is advisory; I could not verify the package’s actual exports from the diff.)

Spec verification

Criterion Status Evidence
SC1 — every in-scope SDK import site is on v2. Met package.json: removes "@modelcontextprotocol/sdk" and adds v2 packages (@modelcontextprotocol/server|client|node|core). Updated imports across touched files reference only v2 packages, e.g., src/mcp/server.ts:1-7 (now imports from "@modelcontextprotocol/server", "@modelcontextprotocol/server/stdio", and "@modelcontextprotocol/node"); src/mcp/server.test.ts:9-14; src/mcp/server-tool-name-resolution.test.ts:25-26 (now "@modelcontextprotocol/client"); scripts/* now import LATEST_PROTOCOL_VERSION from "@modelcontextprotocol/server".
SC2 — no wire change, measured not assumed. Unverifiable This criterion requires live negotiation/version checks (AT1). The diff alone cannot demonstrate wire identity. No in-repo artifact records the before/after capture. Marking Unverifiable based on diff-only review.
SC3 — WITHDRAWN … Replacement: v1 is removed outright. Met package.json:168-176 — removes "@modelcontextprotocol/sdk". eslint.config.js:746-758 adds a no-restricted-imports ban for "@modelcontextprotocol/sdk" and subpaths to prevent regressions. bun.lock shows v2 packages added and any remaining v1 entries are transitive via inspector (not direct deps).
SC4 — the codemod's output is reviewed, not merged blind. Unverifiable The requirement is a PR-body statement of codemod vs hand edits. This cannot be verified from source files alone; the code changes do reflect targeted hand edits (e.g., diagnostic-capture ctx mapping, handler schema-to-string changes), but the presence of a PR-body declaration is not a repo artifact.
SC5 — no serving-entry change (no createMcpHandler/serveStdio). Met src/mcp/server.ts continues to construct a hand-built Server and Stdio/Node transports; no imports or references to createMcpHandler/serveStdio/versionNegotiation appear in the diff. The entry remains the custom class MinskyMCPServer.
validate_typecheck/lint/tests pass; bundle-boot smoke passes. Unverifiable Build/test execution status is not observable from the diff. While eslint.config.js was updated and tests were added/updated, CI/run outputs are not part of repo content.

Adoption sweep

Symbol Kind Consumers found Classification Notes
src/mcp/test-support/tools-call-handler.getToolsCallHandler function src/mcp/server.test.ts:547 — retrieves tools/call handler via helper, src/mcp/server-response-size-guard.test.ts:65 — uses helper to fetch handler (3 sites), src/mcp/drift-gate.test.ts:386, 422 — uses helper to fetch handler, src/mcp/server-in-flight-tool-calls.test.ts:48 — uses helper to fetch handler, src/mcp/test-support/tools-call-handler.test.ts — direct tests of the helper Adopted

Documentation impact

  • no-update-needed — This PR migrates internal MCP SDK imports from v1 to the v2 package family at protocol-parity, with no serving-entry change (no createMcpHandler/serveStdio) and no intended wire-level behavior change. The only user-facing surface touched is test-only helpers and internal server wiring; CLI commands, routes, and documented behavior remain the same. I did not find docs referencing the specific v1 import paths; therefore no docs addition or invalidation is required.

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

Labels

authorship/co-authored Co-authored by human and AI agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant