fix(mcp-client): await Cordis startup discovery

This commit is contained in:
Tianyi Cui
2026-08-08 21:13:20 +08:00
parent 67b9e9b1d6
commit 913ecf5f1d
18 changed files with 108 additions and 39 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-08-trusted-repository-package-code.md
2026-08-08-trusted-repository-package-code.md: 4968d29a6da4180a8b016eef9176acb9bc64d8b1
2026-08-08-trusted-repository-package-code.zh.md: 2e9f59bb1f571bb1bace9623a9e6976e0238a017
2026-08-08-trusted-repository-package-code.md: cf72853836901af9c1c9e9f0d3d23f43997a4e97
2026-08-08-trusted-repository-package-code.zh.md: cea47d85973a81f904b1596051ac58996459c635

View File

@@ -18,7 +18,7 @@ The package owns its npm dependencies and build toolchain. `scripts.prepack` is
The generated wrapper first mounts the DSH-owned static runtime for skills and MCP definitions, then dynamically imports and unwraps the explicit entry and mounts it as a child. Both children must reach Cordis `ACTIVE`; an unsatisfied `inject` or startup exception rejects the repository Loader transaction instead of committing an inert generation. Loader removal, failed replacement, and parent disposal unwind the entry, skill providers, MCP clients, and their effects together.
`dsh-mcp-client` resolves its initial connection and tool synchronization promise as part of Plugin application. A valid server's tools therefore exist before its parent repository wrapper activates and before a one-shot application starts its first model request. Initial connection failure keeps the existing contained failure contract: it is logged, the client activates with no tools, and disposal still closes the transport.
`dsh-mcp-client` resolves its initial connection and tool synchronization promise as part of Plugin application. Its entry is an `async function`, not an ordinary function returning a Promise: Cordis identifies prototype-bearing ordinary functions as constructors and does not treat a constructor's returned Promise as startup work. A valid server's tools therefore exist before its parent repository wrapper activates and before a one-shot application starts its first model request. Its `failOnStartupError` config preserves optional standalone servers by default while letting repository adapters require their declared servers. Repository-translated MCP clients enable that mode, so initial connection or discovery failure rejects the candidate generation and rollback still closes the transport.
## Trust boundary
@@ -41,11 +41,11 @@ Model-visible behavior remains governed by the owning DSH seam. A repository ent
- A TypeScript DSH Plugin can live in a GitHub repository, install ordinary npm dependencies, compile during `prepack`, and run without publishing the Plugin package to npm.
- Static-only repository packages remain valid and retain import-free wrappers; adding `dsh.entry` opts that package into runtime code import.
- A package build, dependency install, entry import, unmet service, or Plugin startup failure prevents the candidate generation from replacing the last good configuration.
- The initial MCP connection can lengthen application startup, while a contained connection failure still yields a running application with no tools from that server.
- The initial MCP connection can lengthen application startup, and a repository-declared server that is unavailable prevents that candidate generation from activating.
- Repository code receives host authority, so source review and immutable pinning are operational security requirements rather than optional hardening.
## Testing
Repository-format tests prepare and mount default-export code entries through the real Loader, observe an entry-owned service, remove the Loader row, and observe cleanup; they also retain skill/MCP preparation, containment, damaged-package, pending-service, and rollback coverage. MCP lifecycle tests require `apply` to settle only after initial tool publication while preserving contained connect failure and teardown.
Repository-format tests prepare and mount default-export code entries through the real Loader, observe an entry-owned service, remove the Loader row, and observe cleanup; they also retain skill/MCP preparation, containment, damaged-package, pending-service, and rollback coverage. MCP lifecycle tests require `apply` to settle only after initial tool publication, preserve opt-in contained connect failure, and prove strict startup rejection still closes the client.
The Node 24 consumer acceptance uses the actual built `dsh run` command with a fresh DSH home and an authenticated private GitHub source pinned to the pull request's exact head SHA. That repository package installs pinned runtime and development dependencies, type-checks and bundles TypeScript during `prepack`, prepares a skill plus a stdio MCP server and `dsh.entry`, exposes the skill and MCP schema in the first real model request, executes the MCP tool, and lets the compiled Cordis entry append a second marker to the result observed in the following request. Cache assertions require source files to be absent from the packed installation while both built modules, their installed dependency, copied assets, and generated wrapper are present.

View File

@@ -18,7 +18,7 @@
生成的包装层先挂载 DSH 自有的静态运行时来处理 skill 和 MCP 定义,再动态导入显式入口、解包其导出并将其挂载为子级。两个子级都必须进入 Cordis `ACTIVE`;无法满足的 `inject` 或启动异常会拒绝 repository Loader 事务,而不会提交未激活的 generation。Loader 移除、替换失败和父级 dispose资源释放会一并撤销入口、skill 提供方、MCP client 及其 effect。
`dsh-mcp-client` 会在插件应用期间完成其初始连接和工具同步 promise。因此,有效 server 的工具会在父级 repository 包装层激活前、一次性应用发起首个模型请求前就已存在。初始连接失败沿用既有的收束失败契约系统会记录日志client 激活但不注册工具dispose 仍会关闭 transport。
`dsh-mcp-client` 会在插件应用期间完成其初始连接和工具同步 promise。其入口必须是 `async function`,而不是返回 Promise 的普通函数Cordis 会把带 prototype 的普通函数识别为 constructor不会把 constructor 返回的 Promise 当作启动工作。因此,有效 server 的工具会在父级 repository 包装层激活前、一次性应用发起首个模型请求前就已存在。其 `failOnStartupError` 配置默认保留独立可选 server 的行为,同时允许 repository adapter 要求已声明 server 必须可用。Repository 转换出的 MCP client 会启用该模式,因此初始连接或发现失败会拒绝候选 generation回滚仍会关闭 transport。
## 信任边界
@@ -41,11 +41,11 @@
- TypeScript DSH 插件可以存放在 GitHub 仓库中,安装普通 NPM 依赖,在 `prepack` 期间完成编译,并在无需把插件包发布到 NPM 的情况下运行。
- 仅含静态贡献的 repository 包仍然有效,并保留无 import 包装层;添加 `dsh.entry` 会使该包选择启用运行时代码导入。
- 包构建、依赖安装、入口导入、所需服务未满足或插件启动失败,都会阻止候选 generation 替换最后一个可用配置。
- 初始 MCP 连接可能延长应用启动时间;连接失败被收束后,仍会得到一个正常运行、但不含该 server 工具的应用
- 初始 MCP 连接可能延长应用启动时间;repository 声明的 server 不可用时,该候选 generation 无法激活
- Repository 代码获得宿主权限,因此源码评审和锁定不可变 ref 是运行安全要求,而不是可选加固措施。
## 测试
repository 格式测试通过真实 Loader 准备并挂载使用 default export 的代码入口,观察入口自有服务,移除 Loader 配置项,再观察清理;测试还保留针对 skillMCP 准备、路径包含约束、包损坏、等待服务和回滚的覆盖。MCP 生命周期测试要求 `apply` 只在初始工具发布后完成,同时保留收束连接失败与清理覆盖
repository 格式测试通过真实 Loader 准备并挂载使用 default export 的代码入口,观察入口自有服务,移除 Loader 配置项,再观察清理;测试还保留针对 skillMCP 准备、路径包含约束、包损坏、等待服务和回滚的覆盖。MCP 生命周期测试要求 `apply` 只在初始工具发布后完成,保留选择收束连接失败的能力,并证明严格启动拒绝仍会关闭 client
Node 24 消费方验收使用实际构建的 `dsh run` 命令、全新 DSH 主目录,以及锁定到 PRPull Request的精确 head SHA 且经过认证的私有 GitHub 源。该 repository 包安装固定版本的运行时依赖与开发依赖,在 `prepack` 期间对 TypeScript 进行类型检查和打包,准备一个 skill、一个 stdio MCP server 及 `dsh.entry`,在首个真实模型请求中暴露 skill 与 MCP schema执行 MCP 工具,并让已编译 Cordis 入口向结果追加第二个标记,供后续请求观察。缓存断言要求打包安装中不存在源码文件,同时必须存在两个已构建模块、其已安装依赖、复制资源和生成包装层。