feat(mcp/parity): 三端经稳定 helper 入口连项目专用地址(P1 + P3) #24

Merged
lanrtop merged 15 commits from feat/agent-client-parity into master 2026-10-02 12:05:16 +08:00
Owner

目标

蓝图 agent-client-parity 的 P1 + P3:Codex、Claude、DSH 三端在子项目目录里都连到项目专用地址(/mcp/projects/<project_id>),并统一经稳定 helper 入口取认证;项目内配置不再写本机 exe 路径。

改动

  • 稳定入口:%LOCALAPPDATA%\lian-jing\bin\lianjing-mcp-header.cmd,炼境每次启动时重写,指向当前 exe(% 转义;非 ASCII 路径加 chcp 65001)
  • Codex:.codex/config.toml 的 helper 改为稳定入口;verify_onboarding 把仍带本机路径的旧 helper 报为 Legacy,供升级
  • Claude:写 local 条目(优先于 user 级全局)。写入一律经 claude CLI(cwd=项目目录),本机现状以 claude mcp get 的回答为准,规则如下:
    • 本项目托管条目 → 刷新或移除;写入失败回滚到原条目
    • 他项目托管条目、手写条目 → 不改动,只记日志
    • 现状无法确认 → 不写入,只记 warn
  • DSH:~/.dsh/cordis.patch.yml 托管块内嵌炼境登记的「项目根 → project_id」白名单。从启动目录向上精确匹配到项目根才用项目地址,未命中则回退全局;控制面项目以 null 作为停止哨兵;手写同名配置不覆盖
  • 服务端:scoped 路由只服务已登记项目,未知 project_id 返回 404(数据库不可用时同样拒绝)
  • 控制面:.lian-jing/context.json 新增 mcp_scope: "global"(炼境自身),不写 Claude local 条目,不进 DSH 白名单
  • 生命周期:
    • 项目根目录变更时迁移客户端配置
    • 移除时先撤 token,再清 DSH 和项目文件
    • 进程内接入操作串行(MCP_OP_LOCK)
    • 删组 / 移出组先删成员关系,再清配置
    • 6 个涉及 CLI 的 Tauri 命令改为 #[tauri::command(async)]

自验(agent_verify)

  • cargo test --locked:243 passed / 5 ignored;pnpm typecheck:通过
  • 三端无头验收(2026-10-01,enterprise-system,零凭据):F 项用 read_project_test_account(name=LJ_PROBE_NONEXISTENT) 判定认证,返回「开发需求未登记」即视为通过。Codex、Claude、DSH 的 B/C/D/E/F 全部通过;基线时 Claude、DSH 的 C/E/F 失败,Claude 的 B 也失败
  • Claude 同步实测(enterprise):
    • 稳态注入 174ms,不启动 CLI
    • 条目缺失 → 写入
    • helper 过期 → 刷新
    • 手写条目 → 保留
    • 以上每种情况 claude mcp get 均显示 Local 且 ✔ Connected
  • 经稳定入口取认证头的零凭据探针返回 200;不带认证返回 403;未知 project_id 返回 404
  • DSH url 表达式:在项目根、子目录、带尾分隔符的路径下解析为项目地址;在上级目录和无关目录回退全局
  • 六轮对抗审查(9 / 13 / 10 / 10 / 13 条),均已处理;详见蓝图 P1 的 review 字段

没验什么

  • DSH 桌面 GUI 模式:cwd 不随工作区切换,项目路由目前只对在项目目录启动的 headless/CLI 生效,GUI 回退全局(已知限制)
  • UI 路径未在本轮实测:删组、移出组、删除项目、修改项目根目录、修改 MCP 端口。本轮的顺序调整与 async 改造只经过编译、单元测试和代码审查
  • 共享 Claude 项目键(嵌套项目 / worktree):只经代码推理和单元测试,未实机构造
  • 非 ASCII exe 路径下的 shim(chcp 65001 分支):未实测
  • claude mcp get 输出格式:解析依赖 claude 2.x 当前格式;格式变化时会退化为只告警、不写入
  • 正式安装版:未验证,全部实测基于 worktree 的 dev 构建。安装版需合并后重新构建安装,再确认 shim 指向安装版 exe

人终验建议(约 3 分钟)

  1. 合并后从主仓库重启日常炼境(或重装安装版),在 enterprise-system 目录执行 claude mcp get lian-jing:Scope 应为 Local,URL 为项目地址,状态 ✔ Connected
  2. 在炼境 UI 里移出一个测试组成员、再加回,界面不卡顿,日志里没有 error
  3. 在 enterprise 目录分别开 Claude / Codex / DSH 会话,询问「炼境有哪些开发变量工具」,三端都应答出项目工具

🤖 Generated with Claude Code

## 目标 蓝图 `agent-client-parity` 的 P1 + P3:Codex、Claude、DSH 三端在子项目目录里都连到**项目专用地址**(`/mcp/projects/<project_id>`),并统一经**稳定 helper 入口**取认证;项目内配置不再写本机 exe 路径。 ## 改动 - **稳定入口**:`%LOCALAPPDATA%\lian-jing\bin\lianjing-mcp-header.cmd`,炼境每次启动时重写,指向当前 exe(`%` 转义;非 ASCII 路径加 `chcp 65001`) - **Codex**:`.codex/config.toml` 的 helper 改为稳定入口;`verify_onboarding` 把仍带本机路径的旧 helper 报为 Legacy,供升级 - **Claude**:写 local 条目(优先于 user 级全局)。写入一律经 `claude` CLI(cwd=项目目录),本机现状以 `claude mcp get` 的回答为准,规则如下: - 本项目托管条目 → 刷新或移除;写入失败回滚到原条目 - 他项目托管条目、手写条目 → 不改动,只记日志 - 现状无法确认 → 不写入,只记 warn - **DSH**:`~/.dsh/cordis.patch.yml` 托管块内嵌炼境登记的「项目根 → project_id」白名单。从启动目录向上精确匹配到项目根才用项目地址,未命中则回退全局;控制面项目以 null 作为停止哨兵;手写同名配置不覆盖 - **服务端**:scoped 路由只服务已登记项目,未知 project_id 返回 404(数据库不可用时同样拒绝) - **控制面**:`.lian-jing/context.json` 新增 `mcp_scope: "global"`(炼境自身),不写 Claude local 条目,不进 DSH 白名单 - **生命周期**: - 项目根目录变更时迁移客户端配置 - 移除时先撤 token,再清 DSH 和项目文件 - 进程内接入操作串行(`MCP_OP_LOCK`) - 删组 / 移出组先删成员关系,再清配置 - 6 个涉及 CLI 的 Tauri 命令改为 `#[tauri::command(async)]` ## 自验(agent_verify) - `cargo test --locked`:243 passed / 5 ignored;`pnpm typecheck`:通过 - **三端无头验收**(2026-10-01,enterprise-system,零凭据):F 项用 `read_project_test_account(name=LJ_PROBE_NONEXISTENT)` 判定认证,返回「开发需求未登记」即视为通过。Codex、Claude、DSH 的 B/C/D/E/F 全部通过;基线时 Claude、DSH 的 C/E/F 失败,Claude 的 B 也失败 - **Claude 同步实测**(enterprise): - 稳态注入 174ms,不启动 CLI - 条目缺失 → 写入 - helper 过期 → 刷新 - 手写条目 → 保留 - 以上每种情况 `claude mcp get` 均显示 Local 且 ✔ Connected - 经稳定入口取认证头的零凭据探针返回 200;不带认证返回 403;未知 project_id 返回 404 - DSH url 表达式:在项目根、子目录、带尾分隔符的路径下解析为项目地址;在上级目录和无关目录回退全局 - 六轮对抗审查(9 / 13 / 10 / 10 / 13 条),均已处理;详见蓝图 P1 的 review 字段 ## 没验什么 - **DSH 桌面 GUI 模式**:cwd 不随工作区切换,项目路由目前只对在项目目录启动的 headless/CLI 生效,GUI 回退全局(已知限制) - **UI 路径未在本轮实测**:删组、移出组、删除项目、修改项目根目录、修改 MCP 端口。本轮的顺序调整与 async 改造只经过编译、单元测试和代码审查 - **共享 Claude 项目键**(嵌套项目 / worktree):只经代码推理和单元测试,未实机构造 - **非 ASCII exe 路径**下的 shim(`chcp 65001` 分支):未实测 - **`claude mcp get` 输出格式**:解析依赖 claude 2.x 当前格式;格式变化时会退化为只告警、不写入 - **正式安装版**:未验证,全部实测基于 worktree 的 dev 构建。安装版需合并后重新构建安装,再确认 shim 指向安装版 exe ## 人终验建议(约 3 分钟) 1. 合并后从主仓库重启日常炼境(或重装安装版),在 enterprise-system 目录执行 `claude mcp get lian-jing`:Scope 应为 Local,URL 为项目地址,状态 ✔ Connected 2. 在炼境 UI 里移出一个测试组成员、再加回,界面不卡顿,日志里没有 error 3. 在 enterprise 目录分别开 Claude / Codex / DSH 会话,询问「炼境有哪些开发变量工具」,三端都应答出项目工具 🤖 Generated with [Claude Code](https://claude.com/claude-code)
lanrtop added 12 commits 2026-10-01 16:01:04 +08:00
- P3:炼境启动写 %LOCALAPPDATA%\lian-jing\bin\lianjing-mcp-header.cmd 指向当前 exe;
  Codex 项目配置改为引用该入口,不再写本机 exe 绝对路径;verify_onboarding 对旧写法报可升级
- P1:inject_mcp 以 local scope 为 Claude 注册项目专用地址 + headersHelper(不进仓库);
  DSH 机器级补丁写托管块,!!js 按启动目录选项目地址并经 cmd 调用同一入口,项目外回退全局
- 服务端:scoped route 拒绝未登记的 project_id(404,数据库不可用同样拒绝)

Feature-Confirmed: true
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 控制面项目(context.json mcp_scope=global,炼境自身已声明)不写 Claude local 条目、
  不入 DSH 白名单,避免遮蔽全局注册与跨项目工具(审查 high)
- DSH 托管块内嵌炼境登记的「项目根目录→project_id」白名单,不再以任意目录里的
  context.json 作为取 token 依据;LOCALAPPDATA/SystemRoot 须为绝对路径
- remove_project_mcp 同步清理 Claude local 条目并剔除 DSH 白名单;SSE 消息路由复核登记
- Claude local 只替换炼境托管条目,手写不覆盖,替换失败回滚;改走 silent_cmd 并移至后台线程
- 手写 DSH 条目改用 serde_yaml 检测(引号/注释/流式写法),无法解析时保守拒绝
- shim 中 exe 路径的 % 转义为 %%;注入日志 context 统一为 JSON,诊断查询防御非 JSON 行

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Claude local 同步统一投递到单个后台串行队列(注册与移除同队列、按提交顺序),
  消除并发 claude CLI 竞态与「注入后立即移除」留下孤儿条目;移除路径不再阻塞界面
- DSH 白名单只收有项目 token 的项目,退组/移除后剔除持久;控制面项目写 null 停止哨兵,
  不再越过它匹配到已登记的上级目录;Rust 与 JS 两端键规范化对齐(重复分隔符、盘符根、UNC)
- 客户端侧同步日志改为 mcp_client_sync 分类,不覆盖诊断中每个项目的注入结果
- 批量重注入只在末尾刷新一次 DSH;启动刷新移到后台线程
- 项目根目录/平台变更时迁移客户端配置(清旧根 Claude 条目,已接入项目在新根重新注入)
- SSE 消息路由仅在会话属于该项目时才移除;注入模块的进程调用统一 silent_cmd

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Claude local 条目不再经 claude CLI(cwd=项目目录)+ 后台队列管理,改为直接编辑
  ~/.claude.json 中本项目键下的 mcpServers.lian-jing:同步、毫秒级、原子替换,只动该字段;
  不依赖项目目录存在,消除删除本地文件夹时的竞态、目录移动后旧条目无法清理、队列丢任务/卡死
- 项目键用真实长路径(实测 8.3 短路径键不被 Claude 识别);手写条目不碰
- 注入、移除、根目录迁移在进程锁内串行;移除时最先删 token,后续清理收集错误统一返回
- 根目录迁移:旧根解析不出也迁移;重新注入失败归入 mcp_inject 并补刷 DSH;DSH 补丁写入加锁

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
最终审查指出直接编辑 ~/.claude.json 须复刻 Claude 私有机制(git 根 / worktree→主仓库 /
NFC 键推导、.claude.json.lock 锁协议、CLAUDE_CONFIG_DIR 与旧版 .config.json、BOM/符号链接/
fsync/键顺序),随 Claude 升级易失效,违背「标准优先、私有机制只进适配层」。

- 写入一律经 claude CLI(cwd=项目目录,同步、带 20 秒超时),由 Claude 自己负责键、锁与路径
- 只读扫描按 URL 中 /mcp/projects/<id> 认领炼境托管条目(不依赖键推导);已一致则不起 CLI
  (实测稳态注入 0.04 秒),手写条目不碰;项目目录已不存在时只记录残留、不写文件
- 全局注册只读确认已存在就跳过 CLI;配置路径按 Claude 规则解析,容忍 BOM
- 批量重注入在锁内复查组成员关系,已移出组的项目不再注入、不重建 token

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
收尾对抗审查指出:只读扫描 ~/.claude.json 的结论不能代表 Claude 在项目目录实际生效的条目
(共享项目键、worktree、目录迁移下会误删他项目条目或漏写),且同步 CLI 调用会阻塞界面线程。

- 快路径只认「项目根或其祖先目录键下」的一致托管条目;其余一律在项目目录执行
  `claude mcp get lian-jing` 取实际生效条目,按归属决策:本项目托管条目 → 刷新/移除
  (写入失败用原条目回滚);他项目托管条目或手写条目 → 不碰并记录;无 local 条目 → 写入
- 输出无法识别、CLI 出错或目录不存在时一律不写,只记 warn
- 全局迁移需 claude CLI 与 git-bash 均可用才置完成标记;批量重注入的成员复查失败计入错误
- 删组/移出组先删成员关系再清 MCP 配置,避免并发重注入把项目加回;调 CLI 前释放数据库连接
- delete_group / add_group_member / remove_group_member / delete_project / update_settings /
  apply_blueprint_buff 改为 #[tauri::command(async)],在线程池执行
- 新增 parse_claude_mcp_get / lianjing_project_of / key_covers_root 单元测试

实测(enterprise):稳态注入 174ms 不起 CLI;条目缺失 → 写入;过期 helper → 刷新;
手写条目 → 保留;三种情况 claude mcp get 均为 Local 且 Connected;经 shim 零凭据探针认证通过。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(blueprint/agent-client-parity): P1 收尾审查记录与已知限制更新为 claude mcp get 为准的方案
All checks were successful
Push & PR Check / check (push) Successful in 1m32s
Push & PR Check / check (pull_request) Successful in 1m5s
1888f71626
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lanrtop added 2 commits 2026-10-01 19:07:03 +08:00
人终验发现 Codex Desktop 会话里 lian-jing 完全未加载,Codex 日志为 headers helper exit 1。
根因:Codex Desktop 是 MSIX 打包应用,看不见它安装之后在 %LOCALAPPDATA% / %APPDATA% 下新建的
顶层目录(Invoke-CommandInDesktopPackage 在包上下文对照实测:lian-jing 与新建对照目录均不可见,
与 ACL 无关;已有目录内的新文件、%USERPROFILE% 下的新目录可见)。原验收用包外的 codex exec,未覆盖。

- shim 迁至 %USERPROFILE%\.lian-jing\bin\;Codex 配置、Claude local 条目、DSH 托管块统一引用新入口
- 炼境 exe 位于 AppData 下(安装版默认位置)时,按大小+修改时间指纹复制到入口目录,清理旧副本;
  复制失败退回直接调用当前 exe 并记 warn
- 旧 %LOCALAPPDATA% 入口迁移期同步写入;verify_onboarding 把旧写法报 Legacy 引导 inject_mcp 升级
  (测试断言随之由 Ready 改为 Legacy,理由同上)
- 新增 helper 副本与 AppData 判定测试

复验:Codex 包内(等价 Desktop)、Codex 包外、Claude、DSH 无头四路零凭据探针均返回「开发需求未登记」
(修复前 Codex 包内为「工具不可用」);cargo test 245 passed / 5 ignored。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(mcp/parity): 审查修复——shim 单行退出、副本刷盘后改名、DSH 仅 headless 按目录路由
All checks were successful
Push & PR Check / check (push) Successful in 1m28s
Push & PR Check / check (pull_request) Successful in 1m5s
cca3bf9db1
对抗审查(14 个 agent,3 条核实成立)与 DSH 桌面版只读调研的修复:

- shim 改为调用与退出同一行(`& call exit /b %%ERRORLEVEL%%`):cmd 在命令返回后按字节偏移重读
  批处理,入口恰在 helper 运行期间被改写时,分行写法会执行到错位片段(实测 exit 9009)
- helper exe 副本 sync_all 后再改名:避免断电留下残缺副本却因同名被一直复用
- 旧副本改为入口写好之后再清理;旧位置入口写失败时暂不清理,避免入口悬空
- DSH 托管块只在 headless profile 按启动目录路由:web/acp/tui 一个进程可服务多个工作区,
  按启动目录会让别的工作区会话连到该项目并带上认证(跨项目越权);profile 取 profileContext.name,
  DSH 0.1.x 无该服务时退回看启动参数
- AGENTS.md 入口路径补全反斜杠;蓝图立 P9(DSH 桌面 GUI 会话级插件,待人确认)

验证:cargo test 245 passed;shim 认证成功 exit 0 / 92 字节、错误 digest exit 1;DSH url 表达式按
Loader 方式求值 7 种情形均符合预期;四路零凭据探针(Codex 包内、Codex 包外、Claude、DSH 无头)复跑通过。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lanrtop added 1 commit 2026-10-02 10:32:14 +08:00
fix(mcp/parity): 更正 Codex Desktop 取认证失败的根因,撤销基于错误前提的 exe 副本逻辑
All checks were successful
Push & PR Check / check (push) Successful in 1m37s
Push & PR Check / check (pull_request) Successful in 1m2s
c4241f075d
2b0a55a 判断「Codex Desktop(MSIX)看不见其安装后在 AppData 下新建的顶层目录」是错的:当时的对照
目录本身也是在 Claude 桌面版的包环境里建的。真正原因:worktree 版炼境由 Claude 桌面版(MSIX)里的
agent 启动,继承了其包环境,写到 %LOCALAPPDATA%\lian-jing 的入口被重定向进
%LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Local\,只有 Claude 的进程树看得到。
对照:经 WMI 启动的非包进程在 AppData 下新建的顶层目录,Codex 包内可见;%USERPROFILE% 根不做重定向。

- 入口迁至 %USERPROFILE%\.lian-jing\bin 仍是正解(与炼境在什么环境里启动无关),保留
- 撤销「exe 在 AppData 下就复制副本」逻辑及其测试:exe 总在真实路径上,前提不成立(按「退出」原则)
- 更正代码注释、verify_onboarding 的 Legacy 提示、契约文档与蓝图 P3 返工记录

验证:cargo test 243 passed / 5 ignored(删去 2 个只测副本逻辑的测试)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lanrtop merged commit 53af8c36d8 into master 2026-10-02 12:05:16 +08:00
Sign in to join this conversation.
No reviewers
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: lanrtop/dev-manager-tauri#24
No description provided.