diff --git a/.planning/2026-10-08-codex-mcp-without-type/findings.md b/.planning/2026-10-08-codex-mcp-without-type/findings.md new file mode 100644 index 0000000..e57c2af --- /dev/null +++ b/.planning/2026-10-08-codex-mcp-without-type/findings.md @@ -0,0 +1,14 @@ +# 已知事实 +- 接续时 git 工作树干净;本次尚未修改业务源码。 +- Codebase Memory 项目 home-cc-web 当前索引 ready。 +- 历史定位:CLI 注入在 lib/agent-runtime.js;App 注入在 server.js;Gitea 构造在 lib/gitea-workflow-codex.js。 +- 用户更正为 `/home/hdzx/.codex/config.toml`:TOML 可解析,3 个 MCP server 都无顶层 `type`,无需改写。 +- 用户要求删除 Codex 出站配置的 type,同时保留旧输入兼容。 +- 当前本机 Codex 版本为 0.161.0,不能沿用此前 0.157.1 的版本说法。 +- 用户配置服务为 playwright/laya(url)和 fastctx(command),均不含 type。 +- 修改前配置 SHA256:fbbdd99bda63d1776cc513d699936bcbb4b8cdc2ef1d913fba5414b7449725f9;交付前核对是否未变。 +- 计划复审通过;按 Trellis 约定由实现子代理修改代码与回归,主代理管理用户配置核查和收尾。 +- 已记录 Codex MCP 配置出口约定,防止后续再次把内部 type 直接注入线程配置。 +- 共享出站函数只复制并删除服务器顶层 type,原对象及嵌套 env.type 保留;CLI 独立省略该参数。 +- App 在线程配置返回前统一处理,Gitea thread.config 使用副本而 mcpServer 内部元数据保留。 +- 回归已直接检查 CLI 的 ccweb/project 参数及 App 首次 start、resume、重载后的字段存在性,Gitea 检查内部元数据和其他连接字段保留。 diff --git a/.planning/2026-10-08-codex-mcp-without-type/progress.md b/.planning/2026-10-08-codex-mcp-without-type/progress.md new file mode 100644 index 0000000..f83d881 --- /dev/null +++ b/.planning/2026-10-08-codex-mcp-without-type/progress.md @@ -0,0 +1,13 @@ +# 进度 +- 2026-10-08:接续中断任务,核对工作树、读取跟踪技能、确认代码索引和指定配置可访问性。 +- 已建立三步计划与独立任务记录;不重启服务。 +- 用户更正配置路径,已确认 3 个 MCP server 无 `type`;完成检查阶段。 +- FastCtx 两次中止后已用本机工具核实,未输出敏感配置。 +- 已派发实现代理,覆盖 CLI、App 与 Gitea 三个出口;当前本机版本 0.161.0。 +- 跟踪文档补丁因上下文不匹配和 PRD 尚不存在失败各一次,已按当前文件状态修正;业务源码未受影响。 +- 代码修改完成,独立代码审查通过;补齐 CLI ccweb、App start/resume 的直接断言。 +- `timeout 60s npm run regression` 已完成且退出码 0,日志为 `/tmp/ccweb-codex-mcp-type-regression.log`,输出 Regression checks passed。 +- Gitea 单元检查及所有修改 JS 的语法检查通过,最后增补的回归和单元文件已再次语法检查;git diff --check 通过。 +- 当前仅剩配置指纹复核及本任务 CSV 清理;全程未重启服务。 +- 最终核对用户配置指纹未变、3 个服务均无 type;本次 CSV 全部完成并清理,任务收尾。 +- 修复位于源文件;未构建发布包、未重启运行进程、未提交或推送。 diff --git a/.planning/2026-10-08-codex-mcp-without-type/task_plan.md b/.planning/2026-10-08-codex-mcp-without-type/task_plan.md new file mode 100644 index 0000000..0413b05 --- /dev/null +++ b/.planning/2026-10-08-codex-mcp-without-type/task_plan.md @@ -0,0 +1,34 @@ +# Codex MCP 配置出口省略 type + +## 目标 +兼容已有 cc-web MCP 配置,在传给 Codex CLI 和 App Server 时省略服务器顶层的 `type`;检查并仅在用户指定 `/home/hdzx/.codex/config.toml` 确有 MCP `type` 时清理。 + +## 约束 +- 不重启、重载或停止 cc-web 服务。 +- 保留内部传输类型元数据、旧输入解析以及 command/url/env/认证/超时等连接字段。 +- 不清理 disable_response_storage 或其他告警字段,不替换为其他用户的配置。 +- 不覆盖其他任务的计划文件,不提交或推送。 + +## 步骤 +1. 核对当前实现、回归覆盖和指定 Codex 用户配置:completed +2. 在 Codex 出站配置中移除 type 并保留旧配置兼容:completed +3. 运行回归检查并清理本次任务跟踪文件:completed + +## 验收 +- CLI 参数和 App Server thread/start、resume、fork 的 MCP 配置无顶层 type。 +- 内置 ccweb、项目 MCP、Gitea MCP 的连接设置仍完整,旧含 type 配置可解析。 +- 回归验证明确检查 type 字段不存在,不能只从 command/url 推导类型后断言。 +- 指定用户配置当前可读,包含 3 个 MCP server,均无顶层 `type`;保持原文件不变。若后续需修改,在修改前创建同目录时间戳备份,仅改 MCP 段中的 `type`,并对比其他配置值保持不变。 +- 执行 `timeout 60s npm run regression`、`timeout 60s node scripts/gitea-workflow-codex-unit.js`,对修改的 JS 运行 `node --check`,并检查 `git diff --check`。 +- 验证矩阵:CLI ccweb/project 注入无 `.type`;App start/resume/fork 配置无显式 `type` 且 command/url/env/认证/超时完整;Gitea 线程配置无 `type`,内部类型元数据仍存在;含 `type` 的旧 TOML fixture 仍能解析。 +- 完成后只删除本任务创建的临时 CSV;该清理属于用户已要求的任务跟踪流程,不需要重复请求授权。 + +## 已知限制与错误 +- 用户更正路径为 `/home/hdzx/.codex/config.toml`;3 个 MCP server 均无显式 `type`,此项已经满足要求,未改写配置。 +- 先前官方文档访问返回 HTTP 403;不重复已失败的 strict-config 子命令实验。 +- FastCtx 连续两次调用中止,改用本机工具检查;不输出用户配置值。 + +## 交付状态 +- 源码修复、必要协议断言、全量回归、Gitea 单元、语法和差异检查均通过。 +- 用户配置 SHA256 与检查前相同,3 个 MCP 服务均无 type。 +- 未重启服务;当前常驻进程尚未加载本次源码改动。 diff --git a/.trellis/spec/backend/codex-mcp-config.md b/.trellis/spec/backend/codex-mcp-config.md new file mode 100644 index 0000000..9f68c4a --- /dev/null +++ b/.trellis/spec/backend/codex-mcp-config.md @@ -0,0 +1,8 @@ +# Codex MCP 配置输出 + +- cc-web 内部允许读取旧 MCP `type` 并保留传输类型元数据,用于工具目录和工作流判断。 +- 发送给 Codex 的 MCP 服务器配置不输出顶层 `type`。Codex 通过 `command` 或 `url` 识别传输方式。 +- 使用 `lib/codex-mcp-config.js` 生成出站副本,不直接删除内部对象上的字段;嵌套 `env.type` 等同名字段保留。 +- CLI 的内置和项目 MCP 参数均省略 `type`;App Server 在公共线程配置出口统一处理,覆盖 start/resume/fork 和 Gitea 注入。 +- Gitea 内部 `mcpServer` 元数据和对外 `config` 分开,线程配置使用出站副本。 +- 回归直接检查实际参数或协议对象没有顶层 `type`,同时检查连接、环境、认证及超时字段保持完整。仅从 `command`/`url` 推导传输方式无法证明字段已经省略。 diff --git a/.trellis/spec/backend/index.md b/.trellis/spec/backend/index.md index 1c0b4c4..fd06c56 100644 --- a/.trellis/spec/backend/index.md +++ b/.trellis/spec/backend/index.md @@ -19,6 +19,7 @@ This directory contains guidelines for backend development. Fill in each file wi | [Error Handling](./error-handling.md) | Error types, handling strategies | To fill | | [Quality Guidelines](./quality-guidelines.md) | Code standards, forbidden patterns | To fill | | [Logging Guidelines](./logging-guidelines.md) | Structured logging, log levels | To fill | +| [Codex MCP 配置输出](./codex-mcp-config.md) | 旧类型配置兼容及 Codex 配置出口 | 已记录 | --- diff --git a/.trellis/tasks/10-08-codex-mcp-without-type/check.jsonl b/.trellis/tasks/10-08-codex-mcp-without-type/check.jsonl new file mode 100644 index 0000000..825fe4b --- /dev/null +++ b/.trellis/tasks/10-08-codex-mcp-without-type/check.jsonl @@ -0,0 +1,2 @@ +{"_example": "Fill with {\"file\": \"\", \"reason\": \"\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."} +{"file": ".trellis/spec/backend/index.md", "reason": "质量检查规范入口"} diff --git a/.trellis/tasks/10-08-codex-mcp-without-type/implement.jsonl b/.trellis/tasks/10-08-codex-mcp-without-type/implement.jsonl new file mode 100644 index 0000000..458d03e --- /dev/null +++ b/.trellis/tasks/10-08-codex-mcp-without-type/implement.jsonl @@ -0,0 +1,2 @@ +{"_example": "Fill with {\"file\": \"\", \"reason\": \"\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."} +{"file": ".trellis/spec/backend/index.md", "reason": "后端实现规范入口"} diff --git a/.trellis/tasks/10-08-codex-mcp-without-type/prd.md b/.trellis/tasks/10-08-codex-mcp-without-type/prd.md new file mode 100644 index 0000000..605714c --- /dev/null +++ b/.trellis/tasks/10-08-codex-mcp-without-type/prd.md @@ -0,0 +1,25 @@ +# Codex MCP 配置出口省略 type + +## 用户已确认的需求 +- cc-web 发送给 Codex 的 MCP 配置不再包含服务器顶层 `type`。 +- 保留旧配置读取兼容及内部类型元数据,仅在出站配置省略该字段。 +- 用户更正为 `/home/hdzx/.codex/config.toml`;已确认 3 个 MCP 服务均没有 `type`,无需改写。 + +## 范围与限制 +- CLI:`lib/agent-runtime.js` 的内置 ccweb 与项目 MCP 参数。 +- App Server:`server.js` 的 start/resume/fork 共用线程配置出口,包括 Gitea 分支。 +- Gitea:保留 `buildGiteaMcpServerConfig()` 内部元数据,线程配置出口不含 `type`。 +- 原有 command/url/env/env_vars/认证/超时字段保持不变,不删除嵌套 env 内名为 type 的变量。 +- 不删除 disable_response_storage 或其他未知字段,不重启/重载/停止服务,不提交/推送。 + +## 验证 +- 在实际参数/协议载荷中断言服务器顶层没有 `type`,不能只推导传输类型。 +- 旧含 type 的 TOML fixture 正常读取,连接和环境上下文字段保持完整。 +- `timeout 60s npm run regression`。 +- `timeout 60s node scripts/gitea-workflow-codex-unit.js`。 +- 所有修改的 JS 文件运行 `node --check`,`git diff --check` 通过。 +- 按需使用已有 focused target 对相关路径检查,不重复已通过且无新变化的测试。 + +## 参考 +- `.planning/2026-10-08-codex-mcp-without-type/task_plan.md` +- `.planning/2026-10-08-codex-mcp-without-type/findings.md` diff --git a/.trellis/tasks/10-08-codex-mcp-without-type/task.json b/.trellis/tasks/10-08-codex-mcp-without-type/task.json new file mode 100644 index 0000000..6a2906d --- /dev/null +++ b/.trellis/tasks/10-08-codex-mcp-without-type/task.json @@ -0,0 +1,26 @@ +{ + "id": "codex-mcp-without-type", + "name": "codex-mcp-without-type", + "title": "Codex MCP type 配置出口兼容修复", + "description": "", + "status": "completed", + "dev_type": null, + "scope": null, + "package": null, + "priority": "P2", + "creator": "shiyue", + "assignee": "shiyue", + "createdAt": "2026-10-08", + "completedAt": "2026-10-08", + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [], + "notes": "", + "meta": {} +} diff --git a/dist-exe/cc-web-bun-linux-x64-baseline.tar.gz b/dist-exe/cc-web-bun-linux-x64-baseline.tar.gz index 5956a97..b5b4ddf 100644 Binary files a/dist-exe/cc-web-bun-linux-x64-baseline.tar.gz and b/dist-exe/cc-web-bun-linux-x64-baseline.tar.gz differ diff --git a/lib/agent-runtime.js b/lib/agent-runtime.js index 5b3e39d..4e2291d 100644 --- a/lib/agent-runtime.js +++ b/lib/agent-runtime.js @@ -105,7 +105,6 @@ function createAgentRuntime(deps) { ? ccwebMcpServerArgs : [ccwebMcpServerArg]; args.push( - '-c', 'mcp_servers.ccweb.type="stdio"', '-c', `mcp_servers.ccweb.command=${tomlString(nodePath || 'node')}`, '-c', `mcp_servers.ccweb.args=${tomlStringArray(serverArgs)}`, '-c', `mcp_servers.ccweb.env_vars=${tomlStringArray(envVars)}`, @@ -119,7 +118,7 @@ function createAgentRuntime(deps) { const config = mcpServer?.config && typeof mcpServer.config === 'object' ? mcpServer.config : null; if (!server || !config) return; const prefix = `mcp_servers.${tomlKeySegment(server)}`; - for (const key of ['type', 'command', 'args', 'env', 'env_vars', 'url', 'bearer_token_env_var', 'startup_timeout_sec', 'tool_timeout_sec']) { + for (const key of ['command', 'args', 'env', 'env_vars', 'url', 'bearer_token_env_var', 'startup_timeout_sec', 'tool_timeout_sec']) { if (!Object.prototype.hasOwnProperty.call(config, key)) continue; args.push('-c', `${prefix}.${key}=${tomlValue(config[key])}`); } diff --git a/lib/codex-mcp-config.js b/lib/codex-mcp-config.js new file mode 100644 index 0000000..eff8edd --- /dev/null +++ b/lib/codex-mcp-config.js @@ -0,0 +1,16 @@ +'use strict'; + +/** + * 生成发送给 Codex 的 MCP server 配置副本。 + * + * 内部配置仍保留 type 作为传输类型元数据,Codex 出站协议只省略 + * 服务器顶层 type;嵌套 env 等对象保持原样。 + */ +function toCodexMcpServerConfig(config) { + if (!config || typeof config !== 'object' || Array.isArray(config)) return config; + const result = { ...config }; + delete result.type; + return result; +} + +module.exports = { toCodexMcpServerConfig }; diff --git a/lib/gitea-workflow-codex.js b/lib/gitea-workflow-codex.js index 65a3683..52ec2a5 100644 --- a/lib/gitea-workflow-codex.js +++ b/lib/gitea-workflow-codex.js @@ -1,5 +1,7 @@ 'use strict'; +const { toCodexMcpServerConfig } = require('./codex-mcp-config'); + /** * Gitea Webhook 工作流与 Codex App 之间的薄适配层。 * @@ -118,7 +120,7 @@ function buildGiteaThreadConfig(options = {}) { return { cwd, config: { - 'mcp_servers.gitea': gitea, + 'mcp_servers.gitea': toCodexMcpServerConfig(gitea), }, // 便于调用方在需要时直接合并到 thread/start 参数;server.js 使用 config。 mcpServer: gitea, diff --git a/scripts/gitea-workflow-codex-unit.js b/scripts/gitea-workflow-codex-unit.js index 9879f89..493a8ef 100644 --- a/scripts/gitea-workflow-codex-unit.js +++ b/scripts/gitea-workflow-codex-unit.js @@ -2,6 +2,7 @@ /** Gitea Workflow Codex/App 协议适配器离线单测。 */ const assert = require('node:assert/strict'); +const { toCodexMcpServerConfig } = require('../lib/codex-mcp-config'); const { buildGiteaMcpServerConfig, buildGiteaThreadConfig, @@ -17,6 +18,21 @@ const { } = require('../lib/gitea-workflow-codex'); async function main() { + const legacyMcp = { + type: 'streamable_http', + url: 'https://mcp.example/tools', + bearer_token_env_var: 'MCP_TOKEN', + startup_timeout_sec: 20, + tool_timeout_sec: 120, + env: { type: '保留这个环境变量' }, + }; + const outgoingMcp = toCodexMcpServerConfig(legacyMcp); + assert.equal(Object.hasOwn(outgoingMcp, 'type'), false); + assert.equal(legacyMcp.type, 'streamable_http'); + assert.notEqual(outgoingMcp, legacyMcp); + const { type: legacyType, ...connectionFields } = legacyMcp; + assert.deepEqual(outgoingMcp, connectionFields); + const mcp = buildGiteaMcpServerConfig({ host: 'https://gitea.example/', accessToken: 'secret-token', @@ -31,7 +47,10 @@ async function main() { host: 'https://gitea.example', accessToken: 'secret-token', }); - assert.equal(thread.config['mcp_servers.gitea'].type, 'stdio'); + assert.equal(Object.hasOwn(thread.config['mcp_servers.gitea'], 'type'), false); + assert.equal(thread.mcpServer.type, 'stdio'); + assert.equal(thread.config['mcp_servers.gitea'].command, 'gitea-mcp'); + assert.deepEqual(thread.config['mcp_servers.gitea'].args.slice(0, 4), ['-t', 'stdio', '-H', 'https://gitea.example']); assert.equal(thread.config['mcp_servers.gitea'].env.GITEA_ACCESS_TOKEN, 'secret-token'); assert.equal(Object.hasOwn(thread.config, 'model'), false); diff --git a/scripts/mock-codex-app-server.js b/scripts/mock-codex-app-server.js index bb06a27..5454211 100755 --- a/scripts/mock-codex-app-server.js +++ b/scripts/mock-codex-app-server.js @@ -690,6 +690,8 @@ function completeMcpToolTurn(thread, turnId) { webSearchMode: thread.config?.web_search || null, hasCcwebMcpConfig: Boolean(ccwebConfig), hasProjectMcpConfig: Boolean(projectConfig), + ccwebHasTopLevelType: Boolean(ccwebConfig && Object.hasOwn(ccwebConfig, 'type')), + projectHasTopLevelType: Boolean(projectConfig && Object.hasOwn(projectConfig, 'type')), ccwebType: ccwebConfig?.type || (ccwebConfig?.url ? 'streamable_http' : (ccwebConfig?.command ? 'stdio' : null)), ccwebUrl: ccwebConfig?.url || null, ccwebBearerTokenEnvVar: ccwebConfig?.bearer_token_env_var || null, diff --git a/scripts/regression.js b/scripts/regression.js index af5e263..fab597a 100644 --- a/scripts/regression.js +++ b/scripts/regression.js @@ -8540,6 +8540,7 @@ async function main() { .split('\n') .find((line) => line.includes(`"event":"process_spawn"`) && line.includes(crossTargetSession.sessionId.slice(0, 8))); assert(mcpSpawnLine && mcpSpawnLine.includes('mcp_servers.ccweb.command') && mcpSpawnLine.includes('mcp_servers.ccweb.env_vars'), 'Codex spawn should inject ccweb MCP config'); + assert(!mcpSpawnLine.includes('mcp_servers.ccweb.type'), 'Codex CLI ccweb MCP 配置应省略服务器顶层 type'); assert(mcpSpawnLine.includes('server.js') && mcpSpawnLine.includes('--ccweb-mcp-server'), 'Codex spawn should launch ccweb MCP through server.js in Node mode'); assert(!mcpSpawnLine.includes(internalMcpToken), 'Codex spawn log should not expose internal MCP token'); const projectMcpSpawnLine = processLogAfterMcp @@ -8547,6 +8548,7 @@ async function main() { .split('\n') .find((line) => line.includes(`"event":"process_spawn"`) && line.includes(codexSession.sessionId.slice(0, 8))); assert(projectMcpSpawnLine && projectMcpSpawnLine.includes('mcp_servers.reg-project.command'), 'Codex spawn should inject project MCP config from session cwd'); + assert(!projectMcpSpawnLine.includes('mcp_servers.reg-project.type'), 'Codex CLI project MCP config should omit the server top-level type'); ws.send(JSON.stringify({ type: 'list_cwd_suggestions' })); const cwdSuggestions = await nextMessage(messages, ws, (msg) => msg.type === 'cwd_suggestions'); @@ -8826,6 +8828,8 @@ async function main() { const codexAppEnabledSearchDynamicTool = await nextMessage(messages, ws, (msg) => msg.type === 'tool_end' && msg.sessionId === codexAppSession.sessionId && msg.toolUseId === 'mcp-ccweb-list'); assert(/"threadStartWebSearchMode": "live"/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread/start should pass web_search=live when Web Search is enabled'); assert(/"webSearchMode": "live"/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread config should keep web_search=live while Web Search is enabled'); + assert(/"ccwebHasTopLevelType": false/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread/start 的 ccweb 配置应省略顶层 type'); + assert(/"projectHasTopLevelType": false/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread/start 的旧项目配置应省略顶层 type'); assert(!/"webSearchToolConfig"/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App should use top-level web_search instead of tools.web_search as the mode switch'); await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId); @@ -8857,6 +8861,8 @@ async function main() { ws.send(JSON.stringify({ type: 'message', text: 'codexapp dynamic after web search disabled prompt', sessionId: codexAppSession.sessionId, mode: 'yolo', agent: 'codexapp' })); const codexAppDisabledSearchDynamicTool = await nextMessage(messages, ws, (msg) => msg.type === 'tool_end' && msg.sessionId === codexAppSession.sessionId && msg.toolUseId === 'mcp-ccweb-list'); assert(/"threadConfigMethod": "thread\/resume"/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App existing thread should refresh config through thread/resume'); + assert(/"ccwebHasTopLevelType": false/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App thread/resume 的 ccweb 配置应省略顶层 type'); + assert(/"projectHasTopLevelType": false/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App thread/resume 的项目配置应省略顶层 type'); assert(/"webSearchMode": "disabled"/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App thread/resume should pass web_search=disabled after Web Search is disabled'); assert(!/"webSearchToolConfig"/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App should not use tools.web_search as the Web Search mode switch'); await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId); @@ -9141,6 +9147,8 @@ async function main() { assert(/currentConversationId/.test(codexAppDynamicTool.result || ''), 'Codex App MCP tool should return ccweb conversation data'); assert(/"hasCcwebMcpConfig": true/.test(codexAppDynamicTool.result || ''), 'Codex App thread/start should pass ccweb MCP config'); assert(/"hasProjectMcpConfig": true/.test(codexAppDynamicTool.result || ''), 'Codex App thread/start should pass project MCP config from session cwd'); + assert(/"ccwebHasTopLevelType": false/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP config should omit the server top-level type'); + assert(/"projectHasTopLevelType": false/.test(codexAppDynamicTool.result || ''), 'Codex App project MCP config should omit the server top-level type'); assert(/"ccwebType": "streamable_http"/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should default to shared streamable HTTP'); assert(/"ccwebUrl": "http:\/\/127\.0\.0\.1:\d+\/api\/internal\/mcp\/stream\?/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should point to the shared cc-web HTTP endpoint'); assert(/"ccwebBearerTokenEnvVar": "CC_WEB_CODEX_APP_MCP_TOKEN"/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should use bearer_token_env_var for the shared endpoint'); diff --git a/server.js b/server.js index bbb6265..d7e1a7a 100644 --- a/server.js +++ b/server.js @@ -6,6 +6,7 @@ const os = require('os'); const { spawn, spawnSync } = require('child_process'); const { WebSocketServer } = require('ws'); const { createAgentRuntime } = require('./lib/agent-runtime'); +const { toCodexMcpServerConfig } = require('./lib/codex-mcp-config'); const { createCodexAppServerClient } = require('./lib/codex-app-server-client'); const { createCodexAppWorkerClient } = require('./lib/codex-app-worker-client'); const { createCodexAppRuntime } = require('./lib/codex-app-runtime'); @@ -14144,6 +14145,11 @@ function codexAppThreadConfig(session, options = {}) { cwd: session?.cwd || options.cwd || getDefaultSessionCwd(), })); } + for (const [key, value] of Object.entries(config)) { + if (key.startsWith('mcp_servers.') && value && typeof value === 'object') { + config[key] = toCodexMcpServerConfig(value); + } + } return config; }