1. 多 Docker 里 Codex 的 MCP 和 Skills 为什么总有一个不生效
如果你在多个 Docker 容器或多台机器上共用同一个 workspace 目录,大概率会遇到这个场景:Skills 明明放在共享目录里,Codex 能识别;但 MCP 面板里翻来覆去只显示一个codex_apps,之前配好的 context7、sequential-thinking、chrome-devtools 全都不见了。更让人头疼的是,有时候 Codex 直接报failed to load configuration: duplicate key,连启动都启动不了。
这个问题的核心在于:Codex 对 Skills 和 MCP 的发现机制完全不同。Skills 走的是用户级目录扫描,只要~/.agents/skills或项目.agents/skills下有SKILL.md,基本都能被识别;而 MCP 强依赖config.toml的加载路径和 trust 状态,项目级配置如果没被正确加载,MCP server 就不会出现在面板里。再加上多个容器各自维护~/.codex/config.toml,反复用cat >>追加配置,很容易出现同名 TOML table 重复定义,直接导致整个配置文件加载失败。
这篇内容面向正在用 Codex + Docker 做多环境开发的用户,目标是把 MCP 和 Skills 的跨容器共享方案讲清楚,给出一份可以直接复制的config.toml骨架,配合 Docker 挂载验证步骤,让多个容器稳定复用同一套 MCP 与 Skills 配置。同时把 API Key 统一管理的问题一起解决,避免每个容器里散落一堆明文 Key。
2. 用 TaoToken 统一 Key,先把 MCP 的鉴权入口收拢
在讲 config.toml 之前,先解决一个前置问题:MCP server 和 Codex 本身都需要 API Key,如果每个 Docker 里都手动配一遍,不仅容易漏,还会出现 Key 版本不一致的情况。我的做法是用 TaoToken 作为统一的 Key 管理入口,所有容器从同一个环境变量读取。
TaoToken 的定位是给开发者提供统一的模型调用入口,支持模型对话、Coding Plan、API Keys 管理等功能。你可以在官网注册后进入控制台创建 API Key,然后在每个 Docker 的共享 bashrc 里统一 export,这样所有容器读到的都是同一套 Key。
具体操作路径:
- 注册并登录官网:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
- 进入控制台创建 API Key:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
- API Key 管理页:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
- 需要验证模型是否可用时,用模型对话页测试:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
- 长期编码或 Agent 场景,可以看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
- 接入文档参考:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
API 端点统一用:https://taotoken.net/api
拿到 Key 之后,不要写进config.toml,而是通过环境变量注入。这样做的原因是:config.toml会被多个容器共享或挂载,明文 Key 写进去等于把凭据暴露在共享目录里;而环境变量可以在每个容器启动时独立注入,互不干扰。
3. 可复制的 config.toml 骨架与 Docker 挂载方案
3.1 用户级 config.toml 完整骨架
最稳妥的做法是把 MCP server 配置放在用户级~/.codex/config.toml,这样不依赖当前打开的是哪个目录。下面这份配置可以直接复制,把 Key 相关的部分留空,靠环境变量填充:
model = "gpt-5.5" model_reasoning_effort = "high" sandbox_mode = "workspace-write" personality = "pragmatic" [plugins."github@openai-curated"] enabled = true [projects."/workspace"] trust_level = "trusted" [projects."/root/workspace"] trust_level = "trusted" [tui.model_availability_nux] "gpt-5.5" = 1 # ========================= # MCP Servers # User-level config: always available in this Docker # ========================= [mcp_servers.chrome-devtools] command = "npx" args = ["-y", "chrome-devtools-mcp@latest"] startup_timeout_sec = 30 tool_timeout_sec = 120 enabled = true [mcp_servers.context7] command = "npx" args = ["-y", "@upstash/context7-mcp"] env_vars = ["CONTEXT7_API_KEY"] startup_timeout_sec = 30 tool_timeout_sec = 120 enabled = true [mcp_servers.sequential-thinking] command = "npx" args = ["-y", "@modelcontextprotocol/server-sequential-thinking"] startup_timeout_sec = 30 tool_timeout_sec = 120 enabled = true [mcp_servers.arxiv-mcp-server] command = "npx" args = ["-y", "@langgpt/arxiv-mcp-server@latest"] env_vars = ["SILICONFLOW_API_KEY"] startup_timeout_sec = 30 tool_timeout_sec = 120 enabled = true [mcp_servers.arxiv-mcp-server.env] WORK_DIR = "/workspace/arxiv-mcp-server" [mcp_servers.drawio] command = "npx" args = ["-y", "@next-ai-drawio/mcp-server@latest"] startup_timeout_sec = 30 tool_timeout_sec = 120 enabled = true这份配置的关键点:env_vars声明了需要从环境变量读取的 Key,真实值不落盘;projects段把/workspace和/root/workspace都标记为 trusted,避免因为软链接路径导致 trust 判断失败。
3.2 共享 bashrc 统一注入 Key
多个 Docker 共用同一个 workspace 时,推荐用一个共享 bashrc 文件来统一管理环境变量,每个容器的~/.bashrc只负责 source 它:
cat > /workspace/.shared_bashrc <<'EOF' # Shared bashrc for all Docker containers export TAOTOKEN_API_KEY="你的 TaoToken API Key" export CONTEXT7_API_KEY="你的 Context7 API Key" export SILICONFLOW_API_KEY="你的 SiliconFlow API Key" export WORKSPACE=/workspace export CODEX_WORKSPACE=/workspace alias ll='ls -alF' alias la='ls -A' alias l='ls -CF' export NPM_CONFIG_CACHE=/workspace/.cache/npm export PIP_CACHE_DIR=/workspace/.cache/pip mkdir -p /workspace/.cache/npm /workspace/.cache/pip 2>/dev/null || true EOF然后在每个容器的~/.bashrc里加一行 source,用 grep 判断避免重复追加:
grep -qxF '[ -f /workspace/.shared_bashrc ] && source /workspace/.shared_bashrc' ~/.bashrc || \ echo '[ -f /workspace/.shared_bashrc ] && source /workspace/.shared_bashrc' >> ~/.bashrc source ~/.bashrc3.3 Docker 挂载验证步骤
假设你的 Docker 启动命令里已经挂载了 workspace,验证挂载是否生效可以按下面几步走:
# 1. 确认 workspace 挂载点 mount | grep workspace # 2. 确认共享 bashrc 可读 ls -l /workspace/.shared_bashrc # 3. 确认环境变量已注入 python3 - <<'PY' import os for k in ["TAOTOKEN_API_KEY", "CONTEXT7_API_KEY", "SILICONFLOW_API_KEY"]: print(f"{k}: {'OK' if os.environ.get(k) else 'MISSING'}") PY # 4. 确认 config.toml 存在且语法正确 ls -l ~/.codex/config.toml如果第 3 步输出 MISSING,说明 bashrc 没被 source,检查~/.bashrc里那行 source 语句是否在非交互式 shell 下也能执行。VS Code 的 Codex 插件进程不一定继承.bashrc,这种情况需要 Reload Window 或重连 Remote-SSH。
4. 验证 MCP 与 Skills 是否真正生效
4.1 手动测试 MCP server 能否启动
stdio 类型的 MCP 不建议手动长期后台运行,Codex 会根据command和args自动拉起。手动运行只用于排错:
# Context7 timeout 10s npx -y @upstash/context7-mcp echo "context7 exit=$?" # Sequential Thinking timeout 10s npx -y @modelcontextprotocol/server-sequential-thinking echo "sequential-thinking exit=$?" # arxiv MCP mkdir -p /workspace/arxiv-mcp-server timeout 10s env WORK_DIR=/workspace/arxiv-mcp-server SILICONFLOW_API_KEY="$SILICONFLOW_API_KEY" \ npx -y @langgpt/arxiv-mcp-server@latest echo "arxiv exit=$?" # drawio MCP timeout 10s npx -y @next-ai-drawio/mcp-server@latest echo "drawio exit=$?"如果返回124,通常表示该 stdio MCP server 能正常启动,只是在等待 MCP client 输入,属于正常现象。如果返回其他非零值,说明依赖缺失或包名有误。
4.2 检查 TOML 语法
Python 3.11 以上自带tomllib,可以直接校验:
python3 - <<'PY' import os try: import tomllib except ModuleNotFoundError: print("Python < 3.11, skip TOML check.") raise SystemExit(0) path = os.path.expanduser("~/.codex/config.toml") with open(path, "rb") as f: tomllib.load(f) print("config.toml OK") PY输出config.toml OK说明没有 duplicate key 或语法错误。
4.3 检查 Skills 是否被扫描到
find ~/.agents/skills -maxdepth 3 -name SKILL.md -print如果 Skills 放在共享目录/workspace/.agents/skills,需要在每个容器里做软链接:
mkdir -p ~/.agents rm -rf ~/.agents/skills ln -s /workspace/.agents/skills ~/.agents/skills对于层级较深的子 skills,比如ai-infra-skills/.claude/skills/01-server/SKILL.md,普通扫描器不一定能发现,需要把子 skill 软链接到顶层:
for d in ~/.agents/skills/ai-infra-skills/.claude/skills/*; do if [ -f "$d/SKILL.md" ]; then name="ai-infra-$(basename "$d")" ln -sfn "$d" "$HOME/.agents/skills/$name" echo "linked: $HOME/.agents/skills/$name -> $d" fi done4.4 Reload Window 是必须步骤
修改 MCP 配置或环境变量后,旧 Codex 会话不会热加载。推荐顺序:
- 保存
~/.codex/config.toml source ~/.bashrc- VS Code 执行
Developer: Reload Window - 新建 Codex 会话
- 如果仍不生效,执行
Remote-SSH: Kill VS Code Server on Host后重连
实测下来,Reload Window 之后所有 MCP 工具都能恢复显示。
5. 本篇常见错误排查
5.1 duplicate key 报错
报错信息:
failed to load configuration: /root/.codex/config.toml:15:11: duplicate key原因是多次用cat >>追加配置,导致同名 TOML table 重复出现:
[projects."/workspace"] trust_level = "trusted" [projects."/workspace"] trust_level = "trusted"TOML 不允许同一个 key 或 table 重复定义。解决方式是备份后重建:
cp ~/.codex/config.toml ~/.codex/config.toml.bak.$(date +%Y%m%d_%H%M%S) 2>/dev/null || true然后手动编辑,删除重复的 table。添加新 MCP 前先 grep 检查:
grep -n '^\[mcp_servers\.context7\]' ~/.codex/config.toml grep -n '^\[mcp_servers\.sequential-thinking\]' ~/.codex/config.toml grep -n '^\[projects\."/workspace"\]' ~/.codex/config.toml5.2 MCP 面板只显示 codex_apps
这个现象说明 Codex 当前只加载了用户级~/.codex/config.toml,没有加载项目级/workspace/.codex/config.toml里的 MCP server。常见原因:
- VS Code 当前打开的根目录不是
/workspace,而是某个子目录 - Codex 会话是在 MCP 配置修改前创建的,旧会话没有热加载
- 项目级配置没有被 trust
- VS Code Codex 插件没有 Reload Window
config.toml有 duplicate key,导致整个配置加载失败
最稳的解决方式是把 MCP server 写入用户级~/.codex/config.toml,不依赖项目级配置。
5.3 /root/workspace 与 /workspace 路径不一致
检查结果:
pwd -P # /workspace/.codex readlink -f /workspace # /workspace readlink -f /root/workspace # /workspace说明/root/workspace是/workspace的软链接,实际指向同一目录。所以 MCP 不显示的根因不是路径不一致,而是项目级配置没被加载。
5.4 npx 依赖缺失
很多 MCP 用 npx 启动,每个 Docker 内部都需要检查:
node -v npm -v npx -v python3 --version如果缺失:
apt update apt install -y nodejs npm5.5 环境变量在插件进程里读不到
.bashrc新增环境变量后,VS Code Codex 插件进程不一定立刻继承。需要 Reload Window 或重连 Remote-SSH。如果还是不行,检查~/.bashrc里的 source 语句是否在非交互式 shell 下也会执行,必要时把 export 语句放到/etc/profile.d/下。
6. 后续规范与统一接入建议
把 MCP 和 Skills 的长期维护规范固定下来,能省掉大量重复排查时间。
MCP 规范:
- MCP server 优先写入用户级
~/.codex/config.toml - 共享项目级
/workspace/.codex/config.toml可以保留,但不要依赖它作为唯一来源 - 不要反复
cat >>追加同名 table - 添加前先 grep 检查是否存在
- API Key 用
env_vars,不直接写入config.toml - 修改后必须 Reload Window
Skills 规范:
- 每个 skill 必须有
SKILL.md - 可共享的 skills 放
/workspace/.agents/skills - 每个 Docker 用软链接把
~/.agents/skills指向共享目录 - 深层子 skills 用软链接暴露到顶层
- 修改或新增后 Reload Window 或新建 Codex 会话
多 Docker 共享结构:
/workspace/ ├── .codex/config.toml # 可选:项目级 MCP 配置 ├── .agents/skills/ # 共享 skills ├── AGENTS.md # 项目级 Codex 使用规则 ├── .shared_bashrc # 共享环境变量和 alias └── arxiv-mcp-server/ # arxiv MCP 工作目录每个 Docker 独立维护:~/.codex/config.toml中的最终 MCP 配置、Codex 登录状态、API Key 环境变量、node/npm/python 依赖。
Key 统一管理这块,用 TaoToken 的 API Keys 页面创建 Key 后,通过共享 bashrc 注入到所有容器,避免每个容器单独配置。需要验证模型连通性时,用模型对话页快速测试;长期编码或 Agent 场景,可以看 Coding Plan 的额度方案。接入细节参考接入文档,API 端点统一用https://taotoken.net/api。
最后提醒一个容易忽略的点:如果想让所有 Docker 共用 bashrc,不建议直接把~/.bashrc软链接为同一个文件,因为不同容器可能有自己的 conda、CUDA、PATH 配置。推荐每个容器保留自己的~/.bashrc,只 source 共享文件/workspace/.shared_bashrc,这样既能统一 Key,又不会破坏各容器的独立环境。