refactor(mcp): migrate the legacy client path to SDK v2 - #1661
Conversation
38a690a to
6d30cf6
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
P1. The interface rewrite covers only one implementer. packages/runtime-host/src/server/client-capability-coordinator.ts:600 still implements McpToolProvider with the old surface (tools() plus the three-argument callTool), and the new callTool(binding, { ... }) drops the context: McpToolInvocationContext that main's packages/runtime/src/mcp-tools.ts now requires. Rebase is not a mechanical conflict resolution: runtime-host calls will fail typecheck or get rejected at runtime with "Client Capability invocation context is missing". The desktop snapshot watcher also moved from main.ts to boot.ts on main.
P2. createClient pins versionNegotiation: { mode: 'legacy' }, so getProtocolEra() never returns 'modern' and enforceMcpHeaders is always false. The whole SEP-2243 path ships unreachable: sep-2243.ts (~270 lines), the header checks, the snapshot partition, and the console.warn allow entry in scripts/check-console.mjs. No manager-level test can reach it. Either delete the path until 'auto' negotiation lands, or switch the mode and add a test that exercises exclusion and pre-call rejection.
P3. refreshToolLoop throws MCP server "x" connection changed during tool refresh: undefined when failure is undefined, because safeMcpOperationError defaults includeCause to true.
P3. mcpBoundToolSnapshotKey deep-clones and stringifies every descriptor on each status emit. The binding string already captures the same change set; JSON.stringify(bindings) would do.
6d30cf6 to
ad50f35
Compare
|
Thanks — I rebased onto the current main and addressed all four findings on the new head
I also kept the existing Runtime Host dependency boundary instead of adding an allowlist exception: the adapter derives its opaque binding type from On the current head, build, format, lint, typecheck, notices, MCP 62/62, Runtime MCP 7/7, Runtime Host seam 24/24, and Desktop surface 13/13 all pass. The full Runtime suite passed 3130 tests with 3 skipped. A full Runtime Host run had one unrelated five-second auxiliary-model timeout (663/664); that test passed immediately in isolation. |
ad50f35 to
8de0614
Compare
|
Thanks for the substantial revision here. The central direction looks right: MCP discovery should publish one validated, immutable tool snapshot, and execution should use an opaque binding from that same snapshot. That is a stronger authority boundary than resolving The previous review concerns around Runtime Host context propagation, unreachable SEP-2243 code, refresh diagnostics, and Desktop binding identity appear to be addressed. The remaining work is primarily integrating this design with constraints that have since landed on Recommended final architectureI suggest keeping the bound-snapshot design and completing it, rather than reintroducing a compatibility facade during the rebase: The responsibilities would remain:
In that end state, the old parallel routes can disappear: optional FindingsP1 — Preserve the managed-network authority from current The PR head calls the provider binding directly from Please keep the network expansion check in the Runtime adapter immediately before P1 — Persist the model's raw provider arguments, not host approval metadata The branch still defines The opaque binding and server identity should remain execution-only metadata. ToolRuntime should persist the original provider arguments. The MCP-specific T1/T2 tests can be collapsed to one SQLite replay test proving both properties:
P1 — Preserve an authoritative callable-snapshot revision for Runtime Host The new manager removes The clean solution is not to restore a separate counter beside the snapshot. Make P3 — Exercise the real Current tests call
Test cleanupThe persisted-argument/T1/T2 cases in I suggest replacing them with one focused SQLite replay test that proves raw provider arguments are durable while opaque bindings remain execution-only. The manager's pagination, refresh-race, stale-binding, validation, and diagnostic tests remain valuable and should stay. Overall, I do not think this needs another architectural rewrite. Please rebase onto current 中文对照感谢对这个 PR 做了大幅调整。核心方向是正确的:MCP 工具发现应当发布一份经过验证、不可变的工具快照,实际执行则使用同一份快照生成的 opaque binding。相比在调用时重新通过 上次 review 提到的 Runtime Host context 透传、不可达的 SEP-2243 代码、refresh 错误信息以及 Desktop binding identity,看起来都已经处理。现在主要需要把这套设计与后来进入 建议的最终架构建议继续完成 bound snapshot 方案,不要为了方便 rebase 又引入兼容 facade: 职责保持如下:
最终可以删除旧的平行路径:可选 FindingsP1 — 保留当前 PR head 的 请在 Runtime adapter 中、 P1 — 持久化模型的原始 provider 参数,而不是宿主审批元数据 当前分支仍通过 opaque binding 和 server identity 应仅作为执行期元数据存在。ToolRuntime 应持久化原始 provider 参数。MCP 专属的 T1/T2 测试可以收缩为一个 SQLite replay 测试,同时证明:
P1 — 为 Runtime Host 保留权威的 callable snapshot revision 新 manager 删除了 最干净的方案不是在 snapshot 之外恢复一个独立计数器,而是让 P3 — 覆盖真实的 当前测试都是直接调用
测试清理
建议替换为一个 focused SQLite replay 测试,证明模型原始参数能够持久化,而 opaque binding 只存在于执行期间。Manager 的分页、refresh race、stale binding、校验和诊断测试仍然有明确价值,应当保留。 总体上,这个 PR 不需要再次推翻架构。请 rebase 到当前 |
8de0614 to
7190761
Compare
|
Thanks, this was very helpful. I rebased onto the current The manager now publishes one immutable I also kept the managed-network expansion check immediately before provider dispatch. ToolRuntime now persists and replays the model's raw arguments only; the opaque binding remains execution-only. I replaced the repeated MCP persistence cases with one real SQLite replay test covering both properties. The SSE fixture now emits a real The optional binding route, string-based call route, wrapped MCP arguments, and host-side snapshot reconstruction are gone. All checks are green on the current head 中文对照谢谢,这些建议非常有帮助。我已经 rebase 到当前 现在 manager 会在完整发现和校验后发布一份不可变的 我也保留了 provider dispatch 之前的 managed-network expansion 检查。ToolRuntime 现在只持久化并 replay 模型的原始参数;opaque binding 只存在于执行期间。之前重复的 MCP 持久化测试已经收缩为一个真实的 SQLite replay 测试,同时覆盖这两个性质。 SSE fixture 现在会发送真实的 可选 binding 路径、基于字符串的调用路径、包装后的 MCP 参数以及 Host 侧 snapshot 重建都已经删除。当前 head |
3486b27 to
4b01fa1
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
Thanks — after looking at these findings together, I do not think the best fix is to patch schema validation and the initial-notification race independently. They have the same root cause: the manager owns the published snapshot, but it does not yet own the complete transition that makes a snapshot authoritative.
I suggest completing one generation-owned discovery transaction with these invariants:
- Every entry in a published snapshot is fully validated and immediately callable.
- A connection generation has exactly one discovery/refresh owner.
tools/list_changedis owned from the moment the connection is established, including during initial discovery.- A candidate observed to be stale before publication is discarded.
- Binding validity is decided only by the manager's current index and snapshot state.
A minimal flow would be:
install generation-fenced notification handler
→ record the current change epoch
→ fetch all pages
→ validate and prepare every definition
→ if the epoch changed, discard and repeat
→ otherwise atomically publish
The notification handler only needs to increment the generation's change epoch and join the existing single-flight refresh. There should be no separate initial-refresh path or parallel notification queue.
This also places output-schema compilation at the correct boundary. callPreparation should be required on every published entry, not optional and initialized by the first call. A malformed refresh must fail before publication and preserve the previous snapshot, revision, and callable binding.
For bindings, please keep definition comparison as an internal concern, but simplify the externally exposed binding to a manager-minted opaque token. The digest and parser are not authorities: validity is already determined by bindingIndex, the current generation, and snapshot identity. Removing the encoded identity would let the format-only binding tests disappear while retaining the important reuse, rotation, reconnect, and stale-before-wire behavior tests.
For the Runtime SDK v1 dependency, please let ownership decide the result: remove the direct dependency from packages/runtime, regenerate the lockfile and notices, and rerun package/audit. If a real production packaging path requires it, that path should identify the dependency owner and have a focused package test. Keep the Hono override only if the audit still requires it.
Please rebase onto current main first so this transaction is implemented on top of the new stable-provider/reconnect ownership. Preserve main's identity and lease tests together with this PR's context assertions.
The target is not more defensive layers. It is one transaction owner, one published authority, and one binding authority.
Astro-Han
left a comment
There was a problem hiding this comment.
Thanks — this approval supersedes my previous blocking recommendation. I recalibrated the review after separating architectural importance from user-facing severity.
The central design is sound: the manager owns one immutable callable snapshot, Runtime preserves the network authority and raw durable arguments, and execution uses a binding from the same validated snapshot. I am approving this direction and the current migration.
The following are non-blocking follow-ups rather than P0/P1 regressions:
- Complete the generation-owned discovery transaction so output-schema preparation happens before publication and
tools/list_changedis owned during initial discovery. The simplest model is one change epoch, one single-flight refresh owner, and publication only when the candidate is fully prepared and still current. - Consider simplifying the external binding to a manager-minted opaque token. Definition comparison may remain internal; the digest and parser do not need to act as a second identity representation.
- Let package ownership determine whether
packages/runtimeneeds the legacy MCP SDK directly. Remove it and rerun package/audit; retain it only if a concrete production packaging path demonstrates that requirement.
These recommendations should not lead to separate compatibility paths, notification queues, or additional authority layers. The desired end state remains one transaction owner, one published snapshot authority, and one binding authority.
Please still rebase onto current main and preserve both the stable-provider reconnect tests and this PR's invocation-context assertions when resolving the Runtime Host overlap.
4b01fa1 to
8fc084b
Compare
|
Thanks — I rebased the approved head onto current main and preserved main's stable provider/reconnect ownership. The Runtime Host adapter remains keyed by I kept the three non-blocking follow-ups separate from this migration: generation-owned initial discovery, opaque token simplification, and Runtime SDK dependency ownership. I did not add a compatibility facade or another authority path. The new head is 中文对照谢谢。我已经将已批准的实现 rebase 到当前 三个 non-blocking 后续方向没有混入本次迁移:generation-owned initial discovery、opaque token 简化,以及 Runtime SDK 依赖所有权。本次也没有增加兼容 facade 或另一条 authority 路径。 新的 head 是 |
8fc084b to
7280424
Compare
7251979 to
962878b
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
Thanks — I re-reviewed the current head 962878b6 after the final rebase and CLI snapshot adaptation.
The approved architecture still holds: the manager publishes one immutable callable snapshot; Runtime, Runtime Host, Desktop, and CLI consume bindings and revision from that same authority; managed-network approval remains immediately before provider dispatch; durable Tool state contains the model’s raw arguments; and the stable provider/reconnect ownership from main is preserved.
I found no P0–P2 issues. The current head is cleanly mergeable and all 13 GitHub checks are green.
One non-blocking cleanup remains:
- P3 — Remove the superseded revision field.
packages/mcp/src/index.tsstill declarestoolSnapshotRevisionValue, but the old accessor and mutation path are gone and the field is never used. Removing it would complete the deletion of the replaced parallel revision state.
This does not block approval. Approved.
Disclosure: Codex assisted with read-only source tracing and evidence organization across this multi-round review. I verified the findings against the current head and own this approval decision.
中文对照
最终 rebase 和 CLI snapshot 适配完成后,我重新审查了当前 head 962878b6。
此前批准的架构仍然成立:manager 发布唯一一份不可变的 callable snapshot;Runtime、Runtime Host、Desktop 和 CLI 都从同一个权威来源消费 binding 与 revision;managed-network 授权仍紧邻 provider dispatch;持久化的 Tool 状态只包含模型的原始参数;同时保留了 main 中稳定的 provider/reconnect 所有权。
我没有发现 P0–P2 问题。当前 head 可以干净合并,GitHub 上的 13 项检查均已通过。
仍有一项非阻塞清理:
- P3 — 删除已被替代的 revision 字段。
packages/mcp/src/index.ts仍声明了toolSnapshotRevisionValue,但旧 accessor 和 mutation 路径均已删除,该字段也从未被使用。删除它可以彻底移除已被替代的平行 revision 状态。
这不影响批准。Approved。
披露:Codex 在这次多轮审查中协助进行了只读源码追踪和证据整理。我核对了当前 head 上的 findings,并对本次批准决定负责。
Two manifest corrections found while auditing every declared dependency in the repo against its actual references. `packages/runtime` declared `@modelcontextprotocol/sdk` and imports it nowhere. It went dead in apache#1661, when the legacy client path migrated to SDK v2; the live consumer is `packages/mcp`, which declares it correctly as a devDependency for one test fixture. This does **not** shrink the packaged app, and the removal should not be read as a size win: `@openai/agents-core` declares the same package as an `optionalDependency`, so npm installs it and it stays in the desktop production closure either way. Measured before and after — 262 packages, 219 MB, unchanged. What the removal buys is an accurate manifest: the declaration claimed a direct dependency that no longer exists. `packages/ui` had `react` in `dependencies` and `react-dom` in `devDependencies` while shipped source imports both — `flushSync` in `use-message-selection-quote.ts`, reachable from the package entry through `chat-view.tsx` and emitted into `dist`. Nothing breaks today because `@maka/ui` is itself vite-bundled into the renderer, but the manifest disagreed with itself. Also verified this does not pull anything new into the desktop production closure, since `@maka/ui` is a devDependency there. Audited and deliberately left alone: - The nine renderer roots this PR moves to `devDependencies` — that is the documented policy, enforced by `maka.rendererBundledDependencies`. - `linkedom`, which looks test-shaped but is a real production import in `packages/runtime/src/local-web-fetch.ts`. - `@larksuiteoapi/node-sdk` (28 MB) and `@jackwener/opencli` (17 MB), the two largest entries in the closure. Both are genuinely imported; removing either would drop a feature, which this PR does not do. - `openai` (19 MB) and `@mixmark-io/domino` (9 MB), which no workspace declares — both arrive transitively. - `@types/node` in the production closure, which `@slack/socket-mode` declares as a real dependency. Not ours to fix from here. - Root `@ai-sdk/provider-utils` and `@astryxdesign/core`, which look unreferenced but are a patch target and a resolved peer respectively. No workspace package is unused: `@maka/eval` has no TypeScript importer but ships in the CLI release package with its Python harness. Claude-Session: https://claude.ai/code/session_014ajaRxC4jydavY9nYUFj5J
Summary
Refs #1650
This is PR 1 of 3 for the dual-era rollout. It upgrades the current legacy-only path to SDK v2 while keeping the existing configuration, transport selection, and user-visible protocol behavior unchanged.
What changed
versionNegotiation: { mode: "legacy" }explicitly. The fixture matrix keeps a real v1.29 stdio peer and uses v2 server packages for remote legacy transports.McpToolProviderexposes onetoolSnapshot()seam and one boundcallTool(binding, rawArgs, context)seam. The old optional binding route, string-based call route, and host-side snapshot reconstruction are gone.{ revision, tools }snapshot. It increments the revision only when the callable binding set changes, atomically replaces successful refreshes, and keeps the previous callable snapshot after a failed refresh.Regression coverage
notifications/tools/list_changed: a valid refresh publishes one new immutable snapshot, while an invalid refresh preserves the prior callable snapshot.Verification
npm test— all workspace tests passed before the final conflict-free rebasenpm run format:checknpm run lintnpm run typechecknpm run check:third-party-noticesnpm run test:scripts— 126 passed, 2 skippeda956756a:npm run build:testnpm --workspace @maka/mcp test— 63 passednpm --workspace @maka/desktop run test:dist— 1430 passedReview focus
Modern negotiation, protocol configuration/status UI, modern Tool-list subscriptions,
input_required, and narrowed Streamable HTTP-to-SSE fallback remain follow-up work.