Files
Genarrative/docs/technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md
kdletters 1f904d28e9
Project CI / Frontend tests (push) Successful in 4m2s
Project CI / Repository checks (push) Successful in 2m16s
Project CI / Backend tests (push) Successful in 10m14s
Project CI / Native shell tests (push) Failing after 19m54s
AGC 客户端 MCP 能力暴露 (#274)
## 目标
保留现有客户端对话与 Codex app-server 链路,把客户端自身受控业务能力通过 MCP 暴露给 Codex。

## 范围
- 客户端会话、项目文件、资源、画布、生成、预览等稳定能力
- 审核 Skill 的索引与按需指导资源
- 复用现有账号、项目路径、权限、计费、幂等、锁和恢复边界

## 明确不做
- 不替换客户端对话入口或 Codex app-server
- 不让客户端替 Codex 判断高层意图、完成状态或规划
- 不暴露任意 Tauri command、shell、凭据、内部 URL、数据库和管理能力

当前 PR 先建立独立分支与审查边界,后续提交实现与定向验证。

Reviewed-on: #274
2026-09-08 22:01:29 +08:00

22 KiB
Raw Permalink Blame History

DirectProject 客户端 Skill 与 MCP 扩展导入方案

更新时间:2026-08-31

1. 文档定位

本方案是 DirectProject 客户端第三方 Skill、MCP 和 Plugin 导入能力的当前实现依据。它描述客户端侧的导入、拆分、命名、启用和 DirectProject 启动注入边界,不改变 AGC 内置 Skill Pack、agc_tools MCP、项目文件工具和现有 Runtime 权威。

本方案只覆盖客户端安装和下一次 Codex 启动时接入。第三方扩展不是安装到全局运行时 Codex,也不要求用户把市面上的扩展重新打包为 AGC 自定义格式。

2. 一句话交付结果

客户端提供一个全局“扩展”入口,直接接受文件、目录、zip 和标准 Plugin;导入后把其中发现的每个 Skill 和每个 MCP Server 拆成独立扩展项,用户可以分别重命名、启用、禁用和删除;DirectProject 启动 Codex 时只注入已启用的独立项。

3. 已确定的产品边界

3.0 客户端能力 MCP 暴露边界(2026-09-03

客户端仍由现有对话入口启动并驱动 Codex;MCP 只是把客户端已审核的项目、文件、资源、画布、生成和预览能力暴露给该 Codex 或其它 Host。客户端只负责账号、项目路径、权限、计费、幂等、锁和恢复等自身安全,不替 Codex 做高层意图/完成门禁。审核 Skill 的索引和正文可作为只读 MCP resource 提供,第三方扩展不得获得客户端会话凭据、内部路径或 bridge token;该能力与公网 /api/external/v1/mcp 保持独立。

3.1 客户端安装、运行时注入

  • 扩展内容保存在 AGC 客户端的扩展仓库。
  • 不把扩展永久安装到用户的全局 CODEX_HOME
  • DirectProject 创建或复用 app-server 时,读取客户端当前已启用的扩展并生成本次运行的临时 Skill root 与 MCP 配置。
  • 导入、启用、禁用和重命名不热更新正在运行的 Codex;变更从下一次 DirectProject 启动生效。
  • 扩展默认对客户端内所有 DirectProject 生效,不做项目级启用映射。

3.2 用户信任和最小处理

用户自行负责第三方扩展的信任判断。客户端不审核 Skill 文案、脚本、二进制、MCP tool description、网络域名或扩展行为。

客户端只做导入流程正常运行所需的最小结构处理:

  • 识别标准 Skill、MCP 和 Plugin 结构;
  • 读取必要的 TOML/JSON 配置;
  • 生成运行时名称和临时配置;
  • 解压时防止路径穿越到 staging 目录外。

导入阶段不执行脚本、不启动 MCP、不调用第三方网络服务。

即使不审核第三方行为,也不得向第三方扩展注入 AGC provider key、Codex 登录态、Cookie、bridge token、内部服务令牌或客户端内部路径。

3.3 明确不支持

  • 单个 .exe.py.js 或其它可执行文件/脚本由用户手动指定为 MCP 入口;
  • 根据 README 或文件后缀猜测如何启动普通程序;
  • 完整 Codex Plugin Runtime
  • Plugin hooks、apps、remote plugin 和依赖这些能力的运行时行为;
  • 自定义扩展 manifest、审核清单或自定义安装包格式;
  • Skill/MCP 行为安全扫描、脚本沙箱和网络白名单;
  • 项目级扩展配置;
  • 热更新、后台扩展服务、在线市场、版本历史、回滚和自动升级。

4. 导入与拆分模型

目录、zip 和 Plugin 是“导入来源”,不是管理对象。一个来源中识别出的每个 Skill 和每个 MCP Server 都独立成为客户端扩展项。

导入来源
  ↓
保存原始内容
  ↓
发现标准结构
  ↓
拆成独立 Skill/MCP 扩展项
  ↓
分别列表显示、命名、启用和删除

4.1 Skill 发现

以下内容分别导入为独立 Skill

  • 根目录的 SKILL.md
  • 标准 Skill root 中的每个 */SKILL.md
  • Plugin 中包含的标准 Skill 目录。

例如:

my-package/
└── skills/
    ├── art-skill/SKILL.md
    └── code-skill/SKILL.md

导入结果是两个独立项:

art-skill
code-skill

4.2 MCP 发现

以下配置中的每个 Server 分别导入为独立 MCP 项:

  • Codex 原生 config.toml[mcp_servers.<name>]
  • 兼容的 .mcp.json 顶层 mcpServers.<name>
  • 标准 Plugin 中能够解析出的 MCP 配置。

例如一个配置包含:

[mcp_servers.search]
command = "..."

[mcp_servers.filesystem]
command = "..."

导入结果是两个独立项:

search
filesystem

.mcp.json 只作为兼容输入读取,不作为新的 AGC 配置格式;只支持能直接转换为 MCP Server 连接描述的字段,不为各客户端的私有扩展建立独立系统。

4.3 Plugin 处理

存在 .codex-plugin/plugin.json 时,将目录或 zip 识别为标准 Plugin 导入来源。

本期只提取其中可识别的 Skill 和 MCP,并分别创建独立扩展项:

Plugin
├── Skill A     → 独立 Skill 项
├── Skill B     → 独立 Skill 项
└── MCP C       → 独立 MCP 项

Plugin 自身不生成父级列表项,也不提供 Plugin 级开关。hooks、apps、remote plugin 以及依赖完整 Plugin Runtime 的内容忽略;如果没有任何可支持的 Skill/MCP,则来源保留为未知内容。

4.4 未知输入

  • 单个无关文件或二进制:保存为一个 unknown 项,不执行。
  • 完全无法发现标准内容的目录或 zip:保存一个 unknown 项。
  • 含有已识别 Skill/MCP 的来源中其它普通文件:不把每个普通文件都变成独立项,但保留原始来源用于溯源。

未知项可以展示和删除,但没有有效的启动入口。

5. 命名规则

5.1 一个名称同时用于 UI 和 Codex

每个独立 Skill/MCP 项使用一个有效名称:

name          当前名称,前端和 Codex 运行时使用
original_name 原始名称,用于溯源,不随用户改名变化

不维护 display_nameruntime_name 两套可见名称。

用户把 Skill 从 art-skill 改为 pixel-art-skill 后:

  • 前端列表显示 pixel-art-skill
  • Codex 的 Skill 列表也使用 pixel-art-skill
  • 客户端只在运行时副本中修改 Skill 的有效名称;
  • 原始 SKILL.md 不修改。

MCP 重命名同理:客户端列表名称和运行时生成配置中的 Server key 使用同一个名称,原始 TOML/JSON 不修改。

5.2 自动重命名

每次重复导入都保留为新的独立项,不覆盖旧项、不去重。

名称冲突统一使用原生标识形式追加后缀:

art-skill
art-skill-2
art-skill-3

不使用 art-skill (2) 这一类仅适用于 UI 的第二套名称。

同一类型内名称需要唯一:

  • Skill 与 Skill 之间避免重复;
  • MCP 与 MCP 之间避免重复;
  • Skill 和 MCP 可以同名,因为属于不同的运行时命名空间。

Skill 名称冲突按 ASCII 大小写不敏感判断(例如 Foofoo 视为冲突),以匹配 Windows 和默认大小写不敏感 macOS 文件系统上的运行时 Skill 目录;MCP Server 名称继续按原生大小写敏感规则处理。

如果名称相同但内容不同,视为名称冲突;如果内容指纹相同,视为重复导入。两种情况都保留新项并使用同一套后缀分配逻辑。

提示合并为一次普通通知,例如:

检测到重复或同名扩展,已自动命名为“art-skill-2”。

内容指纹只用于识别重复导入,不用于安全审核。

5.3 手动重命名

前端允许用户直接编辑独立扩展项的 name

  • 名称不能为空;
  • 使用对应 Skill/MCP 的原生标识格式;
  • 保存时检查同类型名称冲突;
  • 冲突时自动分配 -2-3 后缀并提示用户。

Skill 的手动重命名必须反映到 Codex 运行时名称。客户端保留原始内容,在临时运行副本中修改标准名称字段,不修改用户导入的源文件。

目录或 Plugin 中的多个 Skill 已经在导入时拆成独立项,因此用户可以分别重命名每个 Skill,不存在“重命名整个多 Skill 包”的操作。

6. 客户端存储

扩展仓库使用客户端本地存储和内部元数据,不对用户要求任何自定义包结构。

ImportedSource
├── id
├── original_name
├── storage_path
└── fingerprint

ExtensionItem
├── id
├── source_id
├── type: skill | mcp | unknown
├── name
├── original_name
├── source_relative_path
├── enabled
├── fingerprint
└── last_error

source_id 只用于来源溯源和清理,不形成可操作的父级扩展项,也不产生父子级联启用状态。

对于目录或 zip

  • 原始来源保存一次;
  • 每个发现的 Skill/MCP 保存独立元数据;
  • 删除某个子项不影响其它子项;
  • 原始来源不因删除一个子项而立即删除。

重复导入同一来源时创建新的来源和新的子项,名称按规则追加后缀。

7. 前端设计

7.1 入口

复用现有 RuntimeConfigDialog.tsx 的设置弹窗,在设置导航中增加“扩展”分区。

不在项目聊天区新增入口,不按项目创建扩展页面。

7.2 页面

页面只包含:

  • “导入扩展”按钮;
  • 独立扩展项列表;
  • 每项的名称、类型、来源摘要和当前状态;
  • 启用/禁用开关;
  • 重命名操作;
  • 删除操作;
  • 必要时的一行启动错误。

一个目录、zip 或 Plugin 中的多个内容直接平铺显示,不显示父级包和父级开关:

art-skill
Skill · 来自 my-plugin.zip                    已启用

code-skill
Skill · 来自 my-plugin.zip                    已禁用

search
MCP · 来自 my-plugin.zip                      启动失败

完整列表包括:

  • 已启用;
  • 已禁用;
  • 未识别;
  • 当前不可用;
  • 启动失败。

扩展列表首次读取完成前显示加载态;只有读取成功且结果为空时才显示“还没有导入扩展”,读取失败时显示加载错误,不把未完成的异步读取当作空列表。

“完整列表”指所有用户导入的独立扩展项,不指 zip 内每个普通文件或每个 MCP tool。

7.3 导入后的默认状态

已识别的 Skill/MCP 导入后默认启用,下一次 DirectProject 启动时加载。

未识别内容保留在列表中,但没有有效启动能力。

导入和状态变化不需要和 Agent 模型配置共用“保存”按钮,扩展操作可以单项即时保存。

8. DirectProject 运行时接入

8.1 Skill

启动准备阶段:

读取 enabled=true 的 Skill 项
  ↓
创建本次运行的临时 Skill root
  ↓
为重命名 Skill 生成运行时副本
  ↓
调用 skills/extraRoots/set
  ↓
调用 skills/list 复核

Skill root 是当前启用 Skill 集合的完整投影;每次准备时先清理已有运行时目录,再重新复制当前有效项,避免禁用、删除、重命名或源内容变化后的旧文件继续被 Codex 发现。

每个独立 Skill 的启用状态单独生效。Skill 内容中的脚本、二进制和引用资源不做行为扫描。

8.2 MCP

启动准备阶段:

读取 enabled=true 的 MCP 项
  ↓
为每个项生成 mcp_servers 配置
  ↓
处理同类型 Server key 冲突
  ↓
与 AGC 内置 agc_tools 合并
  ↓
启动 Codex

每个 MCP Server 独立启用和禁用。相对路径按来源目录解析,原始 command、args、cwd、env 或 URL 按原生配置保留。

运行时只转换 Codex 原生能够直接使用的标准字段。STDIO 支持 command / args / env / env_vars / cwdHTTP 支持 url / bearer_token_env_var / http_headers / env_http_headers;两类都支持原生启动超时、工具超时和工具筛选字段。客户端启用状态是运行时权威,每个第三方项固定 enabled=truerequired=false,并沿用 DirectProject 无逐工具弹窗的自动批准方式。第三方 MCP 配置写入本次隔离 CODEX_HOME/config.tomlDirectProject 启动命令同时以 -c mcp_optional_startup_grace_ms=120000 传入同一值,命令行 override 是 Codex 最终生效来源。DirectProject 在首次 turn/start 前消费 app-server 已有的 mcpServer/startupStatus/updated 通知,等待每个已启用第三方 MCP 进入 readyfailedcancelled;最长等待 120000 毫秒,超时后继续对话。ready 的 MCP 进入首轮,失败或超时的 MCP 被跳过并记录状态。该配置只作用于本次 DirectProject 隔离运行,不修改用户全局 Codex 配置。DirectProject 系统提示会声明客户端扩展列表中已启用的第三方 MCP;用户明确指定 Server 或工具时,模型只在当前可用工具中查找,找不到则如实说明,不伪造结果。

客户端扩展索引的所有读改写操作在进程内使用同一把互斥锁串行化;原子写文件只负责避免半截文件,不能替代这层读改写协调。每个运行时 MCP 同时保留客户端扩展项 ID 和当前 app-server 连接 token,启动状态通知按“连接建立时的 Server 名称 → 扩展项 ID + 连接 token”映射回写;迟到的旧连接通知、扩展启停/删除后的失效 token 直接忽略。连接 token 只存在进程内 owner registry,不写入持久化索引。已启用 MCP 的连接池 fingerprint 包含扩展项 ID 和来源 ID,避免删除后重导入相同内容时复用旧连接。

用户导入来源中的相对 cwd 按该 MCP 配置文件所在目录解析;未声明 cwd 的 STDIO Server 默认以该目录启动,从而保留脚本参数的原生相对路径语义。客户端不替用户猜测普通文件如何启动。

agc_tools 为 AGC 内部保留名称,第三方扩展不得覆盖。第三方 MCP 的工具调用继续继承 DirectProject 当前既有运行策略;本功能不新增逐工具审核系统。

客户端不会把 AGC provider session token、工具桥地址或受控搜索标记通过 env_varsbearer_token_env_varenv_http_headers 转发给第三方 MCP。扩展自身静态声明的 envhttp_headers 仍按原生配置使用。OAuth 登录 UI、OAuth 凭据中心和自动登录不属于本阶段。

8.3 失败处理

第三方扩展的解析或启动失败时:

  • 记录对应独立项的 last_error
  • 列表显示“启动失败”或“当前不可用”;
  • 尽量跳过失败项,保留 AGC 内置 Skill 和 agc_tools
  • 不自动重试、不启动后台修复服务、不删除原始内容。

DirectProject 首轮使用 Codex 已有的 Eager MCP 启动和 AGC 侧有界 readiness gate:所有已启用第三方 MCP 并行尝试启动,AGC 在首次 turn/start 前等待现有 app-server 状态通知,最多等待 120000 毫秒;等待实现必须先注册 Notify future,再读取共享状态,以免 notify_waiters 在状态检查和等待注册之间丢失唤醒;不执行工具探测调用,也不新增外部 MCP 服务。第三方 MCP 仍保持 required=false,单项进入 failed/cancelled 或等待超时后,DirectProject 继续对话;后续回合可重新发现等待窗口结束后才 ready 的 MCP。

扩展集合的 fingerprint 纳入现有 DirectProject app-server pool key,扩展集合变化后不复用不匹配的旧运行实例。

9. 客户端接口

Skill 和 MCP 共用客户端扩展接口:

list_client_extensions()
import_client_extension(path)
set_client_extension_enabled(id, enabled)
rename_client_extension(id, name)
remove_client_extension(id)

不新增按类型分开的安装接口。导入接口返回拆分后的多个独立项:

{
  "imported": [
    {
      "id": "extension-...",
      "name": "art-skill",
      "originalName": "art-skill",
      "extensionType": "skill",
      "sourceName": "bundle.zip",
      "sourceRelativePath": "skills/art/SKILL.md",
      "enabled": true,
      "status": "enabled",
      "lastError": null
    },
    {
      "id": "extension-...",
      "name": "search",
      "originalName": "search",
      "extensionType": "mcp",
      "sourceName": "bundle.zip",
      "sourceRelativePath": "mcp.json",
      "enabled": true,
      "status": "enabled",
      "lastError": null
    }
  ],
  "sourceName": "bundle.zip",
  "renamed": false,
  "duplicate": false
}

以上是 Tauri command 对外返回的 camelCase DTO。未识别内容不会生成顶层 unknown 字段,而是作为 imported 中的一项返回,其 extensionType"unknown"enabledfalsestatus"unknown"

前端不直接处理 Skill root 路径、MCP command 或 zip staging 路径;这些属于客户端后端和 DirectProject 启动适配层。

10. 分阶段实施与验收

不采用一次性大改动。按垂直闭环分三阶段,每阶段完成后验收再进入下一阶段。

阶段 0:冻结契约和样例

确认:

  • 导入来源与独立扩展项的关系;
  • 名称统一使用原生标识;
  • 重复导入自动追加后缀;
  • 已识别内容默认启用;
  • Plugin 只作为导入容器;
  • 单个可执行文件/脚本不作为 MCP 入口;
  • 客户端全局生效。

准备最小样例并确认每个样例的预期拆分结果。

阶段 0 样例位于 apps/ai-game-creator-shell/src-tauri/tests/fixtures/direct_extensions/,其 README.mdexpected-imports.json 是本阶段输入分类、拆分和命名预期的机器可读/人工可读基线。zip 场景使用 mixed-source/ 在测试运行时生成临时归档,不提交生成的二进制 zip。

阶段 1:客户端导入和列表

当前实施状态:已完成。客户端侧已落地本地扩展索引、来源副本、文件/目录/zip 导入、标准 Skill/MCP 结构发现、独立项拆分、原生命名、重复导入命名、启用/禁用、重命名、删除和设置弹窗列表。DirectProject 运行时注入仍留在阶段 2/3,不属于本阶段。阶段 1 定向 Rust 测试 6 项全部通过,前端 typecheck 和配置契约检查通过。

完成:

  • 客户端存储;
  • 文件、目录、zip 导入;
  • Skill/MCP/Plugin 结构发现;
  • 多内容拆分为独立项;
  • 未知项保留;
  • 默认启用;
  • 原生名称自动追加后缀;
  • 手动重命名;
  • 独立启用、禁用、删除;
  • 设置弹窗中的完整列表。

验收:

  • 一个来源包含多个 Skill/MCP 时,列表出现多个独立项;
  • 相同来源重复导入时不覆盖旧项;
  • 同名和重复内容均自动使用 -2-3
  • 单个二进制不会被启动;
  • 原始来源内容未被改写。

阶段 2Skill 运行时闭环

当前实施状态:已完成。DirectProject 启动准备会在隔离 HOME 中分别创建内置 AGC Skill root 和客户端 Skill root,并通过同一次 skills/extraRoots/set 注册;客户端 Skill root 只包含当前 enabled=true 的 Skill 独立副本。用户重命名时仅在该临时副本的标准 frontmatter 中更新 name,原始来源副本和客户端索引保持不变。已启用 Skill 集合的名称、来源相对路径和内容指纹已纳入 DirectProject app-server pool key,启停或重命名从下一次启动生效,不热更新现有连接。

完成:

  • 读取客户端已启用 Skill
  • 临时 Skill root 生成;
  • Skill 重命名同步到运行时副本;
  • skills/extraRoots/setskills/list 接入;
  • 扩展 fingerprint 纳入 app-server pool key。

验收:

  • 多个 Skill 可以独立发现;
  • 重命名后的 Skill 在 skills/list 中使用新名称;
  • 单独禁用一个 Skill 不影响其它 Skill
  • 当前 Codex 不热更新;
  • AGC 内置 Skill 不受影响。

阶段 3MCP 和 Plugin 部分闭环

当前实施状态:已完成。客户端会在 DirectProject 启动前读取所有已启用 MCP 独立项,把可转换的 STDIO/HTTP 原生字段合并进本次隔离 CODEX_HOME/config.toml,再由现有启动参数单独注入内置 agc_tools。第三方项固定为非 required,结构错误或启动失败只更新对应扩展项的 last_errorCodex app-server 的 mcpServer/startupStatus/updated 通知用于清除或记录单项启动状态。已启用 MCP 的名称、来源相对路径和内容指纹已纳入 app-server pool key。Plugin 样例已覆盖同一来源中的 Skill/MCP 独立提取,不开启 Plugin Runtime。

完成:

  • config.toml 每个 Server 独立导入;
  • .mcp.json 每个 Server 独立导入;
  • Plugin 中 Skill/MCP 独立导入;
  • MCP Server 重名自动追加原生后缀;
  • MCP 运行时配置合并;
  • 单项错误记录和内置能力保留。

验收:

  • 一个 MCP 配置包含多个 Server 时,列表出现多个独立项;
  • 每个 MCP 可以单独启用、禁用和重命名;
  • 重名 Server 能生成稳定的唯一 key;
  • Plugin 不会开启 hooks、apps 或完整 Plugin Runtime
  • 单个可执行文件/脚本仍不会自动作为 MCP 入口。

11. 最小验证范围

只增加和本功能直接相关的定向验证:

  • 多 Skill/MCP 来源拆分;
  • 原生名称自动重命名和手动重命名;
  • 重复导入保留多个独立项;
  • Skill 运行时名称复核;
  • MCP Server key 冲突;
  • 未知文件不执行;
  • zip 解压路径边界。

不新增完整第三方扩展行为测试、安全扫描框架、在线测试服务或大规模 E2E 基础设施。

12. 非目标和后续议题

以下内容不属于本方案的交付判据:

  • 完整 Codex Plugin Runtime
  • Plugin hooks/apps/remote plugin
  • 单文件 MCP 入口配置;
  • 项目级扩展开关;
  • MCP 工具逐项权限管理;
  • OAuth/密钥管理中心;
  • 在线市场和自动更新;
  • 第三方扩展安全评级和行为沙箱。

后续如果要支持这些能力,必须另立技术方案,不在本方案中预留复杂抽象或隐藏开关。