本文总结本次遇到的 Codex MCP / Skills 识别问题、根因、恢复方式,以及以后在多 Docker / 多机器环境下添加 MCP 工具与 Skills 的推荐做法。
1. 场景背景
当前环境特点:
- 多个 Docker / 多台机器共用主机上的同一个
workspace文件夹; - 每个 Docker 内部的
~/.codex、~/.agents、~/.bashrc、依赖环境可能不同; - 已经在某一台机器或 Docker 中配置好了 Codex MCP 工具和 Skills;
- 希望其他 Docker / 机器上的 Codex 也能复用同一套 MCP 与 Skills。
期望目标:
text
所有 Docker 复用:
- MCP 工具配置
- Skills 仓库
- AGENTS.md 使用规则
每个 Docker 自己维护:
- Codex 登录状态
- API Key 环境变量
- node/npm/npx/python 等运行依赖
2. 本次出现的问题
2.1 Codex MCP 面板只显示 codex_apps
现象:
text
MCP 面板只显示:
codex_apps 已通过身份验证(API 密钥) 已启用
没有显示之前配置过的:
text
context7
sequential-thinking
arxiv-mcp-server
chrome-devtools
drawio
2.2 config.toml 曾出现 duplicate key 错误
报错:
text
failed to load configuration: /root/.codex/config.toml:15:11: duplicate key
原因是多次使用 cat >> ~/.codex/config.toml 追加配置,导致重复出现了同名 TOML table,例如:
toml
[projects."/workspace"]
trust_level = "trusted"
[projects."/workspace"]
trust_level = "trusted"
或者重复出现:
toml
[mcp_servers.context7]
...
[mcp_servers.context7]
...
TOML 不允许同一个 key / table 重复定义,因此 Codex 无法加载配置。
3. 根因分析
3.1 Skills 能识别,但 MCP 不一定能识别
这是因为 Skills 与 MCP 的发现机制不同。
| 项目 | Skills | MCP |
|---|---|---|
| 常见位置 | ~/.agents/skills 或项目 .agents/skills |
~/.codex/config.toml 或项目 .codex/config.toml |
| 是否依赖当前项目路径 | 用户级 skills 不太依赖 | 项目级 MCP 强依赖 |
| 是否需要 trust 项目 | 用户级 skills 通常不需要 | 项目级 .codex/config.toml 通常需要 trust |
| 本次现象 | Skills 能识别 | MCP 只显示 codex_apps |
如果 Skills 放在:
bash
~/.agents/skills
那么它们是用户级资源,即使当前打开的是 /workspace、/root/workspace 或其他目录,也可能被识别。
但 MCP 如果只放在:
bash
/workspace/.codex/config.toml
就属于项目级配置。Codex 当前会话必须正确加载这个项目配置,否则不会显示这些 MCP server。
3.2 /root/workspace 与 /workspace 实际是同一个目录
检查结果:
bash
PWD=/root/workspace/.codex
pwd -P
/workspace/.codex
readlink -f /workspace
/workspace
readlink -f /root/workspace
/workspace
/root/workspace -> /workspace/
说明:
text
/root/workspace 是 /workspace 的软链接
因此这台机器上 MCP 不显示的根因不是路径不一致,而是:
text
Codex 当前只加载了用户级 ~/.codex/config.toml,
没有加载项目级 /workspace/.codex/config.toml 里的 MCP servers。
3.3 为什么项目级 .codex/config.toml 可能没有被加载
常见原因包括:
- VS Code / Codex 当前打开的根目录不是
/workspace,而是某个子目录; - Codex 当前会话是在 MCP 配置修改前创建的,旧会话没有热加载;
- 项目级配置没有被 trust;
- VS Code Codex 插件没有 Reload Window;
- MCP 配置放在项目级,但当前会话只读到了用户级配置;
config.toml有 duplicate key,导致整个配置加载失败。
4. 最稳妥的解决方式
4.1 MCP 配置优先放到用户级 ~/.codex/config.toml
为了避免项目级配置未加载,最稳妥的方式是:
text
把 MCP servers 放到用户级 ~/.codex/config.toml
这样不依赖当前打开的是哪个目录。
推荐的干净配置如下:
toml
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
4.2 重建用户级配置的命令
如果 ~/.codex/config.toml 已经混乱或有 duplicate key,可以直接备份后重建:
bash
cp ~/.codex/config.toml ~/.codex/config.toml.bak.$(date +%Y%m%d_%H%M%S) 2>/dev/null || true
cat > ~/.codex/config.toml <<'EOF'
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.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
EOF
5. 环境变量配置
MCP 配置中使用:
toml
env_vars = ["CONTEXT7_API_KEY"]
env_vars = ["SILICONFLOW_API_KEY"]
说明真实 API Key 不写入 config.toml,而是从环境变量读取。
简单方式:写入 ~/.bashrc:
bash
cat >> ~/.bashrc <<'EOF'
# MCP API Keys for Codex
export CONTEXT7_API_KEY="你的 Context7 API Key"
export SILICONFLOW_API_KEY="你的 SiliconFlow API Key"
EOF
source ~/.bashrc
检查是否生效:
bash
python3 - <<'PY'
import os
for k in ["CONTEXT7_API_KEY", "SILICONFLOW_API_KEY"]:
print(f"{k}: {'OK' if os.environ.get(k) else 'MISSING'}")
PY
注意:如果使用 VS Code Codex 插件,.bashrc 新增环境变量后,插件进程不一定立刻继承,需要 Reload Window 或重连 Remote-SSH。
6. 依赖检查
很多 MCP 使用 npx 启动,因此每个 Docker 内部都需要检查:
bash
node -v
npm -v
npx -v
python3 --version
如果缺失:
bash
apt update
apt install -y nodejs npm
7. 手动测试 MCP 是否能启动
stdio 类型 MCP 不建议手动长期后台启动。Codex 会根据 command 和 args 自动启动它们。
手动运行主要用于排错。
7.1 Context7
bash
timeout 10s npx -y @upstash/context7-mcp
echo "context7 exit=$?"
7.2 Sequential Thinking
bash
timeout 10s npx -y @modelcontextprotocol/server-sequential-thinking
echo "sequential-thinking exit=$?"
7.3 arxiv MCP
bash
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=$?"
7.4 drawio MCP
bash
timeout 10s npx -y @next-ai-drawio/mcp-server@latest
echo "drawio exit=$?"
如果返回:
text
124
通常表示该 stdio MCP server 能启动,只是在等待 MCP client 输入,属于正常现象。
8. Reload Window 很关键
修改 MCP 配置或环境变量后,旧 Codex 会话不一定热加载。
推荐顺序:
text
1. 保存 ~/.codex/config.toml
2. source ~/.bashrc
3. VS Code 执行 Developer: Reload Window
4. 新建 Codex 会话
5. 如果仍不生效,Remote-SSH: Kill VS Code Server on Host 后重连
之前已经验证过:
text
Reload Window 后,所有 MCP 工具恢复可用。
9. 多 Docker 共享的推荐结构
如果所有 Docker 共用 /workspace,推荐结构:
text
/workspace/
├── .codex/config.toml # 可选:项目级 MCP 配置
├── .agents/skills/ # 共享 skills
├── AGENTS.md # 项目级 Codex 使用规则
├── .shared_bashrc # 可选:共享环境变量和 alias
└── arxiv-mcp-server/ # arxiv MCP 工作目录
但是从稳定性角度看,MCP 最稳是放:
text
~/.codex/config.toml
Skills 可以共享:
text
/workspace/.agents/skills
然后在每个 Docker 中链接到用户级:
bash
mkdir -p ~/.agents
rm -rf ~/.agents/skills
ln -s /workspace/.agents/skills ~/.agents/skills
10. 如果想让所有 Docker 共用 bashrc
不建议直接把所有 Docker 的 ~/.bashrc 都软链接为同一个文件,因为不同容器可能有自己的 conda、CUDA、PATH 配置。
推荐方式是:每个 Docker 自己保留 ~/.bashrc,但都 source 一个共享文件:
text
/workspace/.shared_bashrc
创建共享文件:
bash
cat > /workspace/.shared_bashrc <<'EOF'
# Shared bashrc for all Docker containers
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
每个 Docker 的 ~/.bashrc 加入:
bash
grep -qxF '[ -f /workspace/.shared_bashrc ] && source /workspace/.shared_bashrc' ~/.bashrc || \
echo '[ -f /workspace/.shared_bashrc ] && source /workspace/.shared_bashrc' >> ~/.bashrc
source ~/.bashrc
11. 以后添加新的 MCP 工具应该怎么做
11.1 推荐流程
- 先不要直接追加到配置文件末尾;
- 先备份:
bash
cp ~/.codex/config.toml ~/.codex/config.toml.bak.$(date +%Y%m%d_%H%M%S)
- 打开配置文件手动编辑:
bash
nano ~/.codex/config.toml
- 添加新的 MCP table,例如:
toml
[mcp_servers.new-tool]
command = "npx"
args = ["-y", "some-mcp-package@latest"]
startup_timeout_sec = 30
tool_timeout_sec = 120
enabled = true
- 如果需要 API Key,优先使用环境变量:
toml
env_vars = ["NEW_TOOL_API_KEY"]
然后在 .bashrc 或 .shared_bashrc 中配置:
bash
export NEW_TOOL_API_KEY="你的 key"
- 检查 TOML 语法:
bash
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
- Reload Window 并新建 Codex 会话。
11.2 避免 duplicate key
添加前先检查是否已经存在:
bash
grep -n '^\[mcp_servers\.context7\]' ~/.codex/config.toml
grep -n '^\[mcp_servers\.sequential-thinking\]' ~/.codex/config.toml
grep -n '^\[projects\."/workspace"\]' ~/.codex/config.toml
不要重复添加同名 table。
错误示例:
toml
[mcp_servers.context7]
...
[mcp_servers.context7]
...
正确做法是编辑已有 table,而不是追加新的同名 table。
12. 以后添加新的 Skills 应该怎么做
12.1 用户级 Skills
放到:
text
~/.agents/skills/<skill-name>/SKILL.md
例如:
bash
mkdir -p ~/.agents/skills/my-new-skill
nano ~/.agents/skills/my-new-skill/SKILL.md
标准结构:
markdown
---
name: my-new-skill
description: Use this skill when ...
---
# My New Skill
## When to use
...
## Workflow
1. ...
2. ...
3. ...
## Output Format
...
检查是否能被扫描到:
bash
find ~/.agents/skills -maxdepth 3 -name SKILL.md -print
12.2 共享 Skills
如果希望多个 Docker 共用 Skills,推荐放到:
text
/workspace/.agents/skills
然后每个 Docker 创建软链接:
bash
mkdir -p ~/.agents
rm -rf ~/.agents/skills
ln -s /workspace/.agents/skills ~/.agents/skills
检查:
bash
find ~/.agents/skills -maxdepth 3 -name SKILL.md -print
12.3 如果一个仓库内部有很多子 skills
例如:
text
ai-infra-skills/.claude/skills/01-server/SKILL.md
ai-infra-skills/.claude/skills/02-env-source-log/SKILL.md
...
由于层级较深,普通扫描器不一定发现。推荐把子 skill 软链接到顶层:
bash
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
done
同时建议给总仓库补一个根目录入口:
text
~/.agents/skills/ai-infra-skills/SKILL.md
用来说明:
text
先读 INDEX.md
再读 EXPERT_KNOWLEDGE_MAP.md
再进入 knowledge / .claude/skills / .claude/rules
13. 推荐的长期规范
MCP 规范
text
1. MCP server 优先写入用户级 ~/.codex/config.toml。
2. 共享项目级 /workspace/.codex/config.toml 可以保留,但不要依赖它作为唯一来源。
3. 不要反复 cat >> 追加同名 table。
4. 添加前先 grep 检查是否存在。
5. API Key 用 env_vars,不直接写入 config.toml。
6. 修改后必须 Reload Window。
Skills 规范
text
1. 每个 skill 必须有 SKILL.md。
2. 可共享的 skills 放 /workspace/.agents/skills。
3. 每个 Docker 用软链接把 ~/.agents/skills 指向共享目录。
4. 深层子 skills 用软链接暴露到顶层。
5. 修改或新增后 Reload Window / 新建 Codex 会话。
多 Docker 规范
text
共享:
- /workspace/.agents/skills
- /workspace/AGENTS.md
- /workspace/.shared_bashrc
- 可选 /workspace/.codex/config.toml
每个 Docker 独立:
- ~/.codex/config.toml 中的最终 MCP 配置
- Codex 登录状态
- API Key 环境变量
- node/npm/python 依赖
14. 最终结论
本次问题的根本原因不是 /root/workspace 和 /workspace 路径不同,因为它们实际指向同一目录;真正原因是:
text
Codex 当前会话没有加载项目级 /workspace/.codex/config.toml 中的 MCP 配置,
因此只显示默认的 codex_apps。
最稳的解决方式是:
text
把 MCP servers 写入用户级 ~/.codex/config.toml,
API Key 通过环境变量提供,
Reload Window 后新建 Codex 会话。
以后添加 MCP 或 Skills 时,按以下原则:
text
MCP:编辑 ~/.codex/config.toml,避免重复 table,修改后 Reload Window。
Skills:确保每个 skill 有 SKILL.md,最好共享到 /workspace/.agents/skills,再软链接到 ~/.agents/skills。