规范文件
Codex MCP 配置:config.toml 里 stdio 与 HTTP 服务的字段自查
按官方字段表区分 stdio 与 HTTP 服务,用自写 lint 检查字段混用、工具生效范围与密钥写法。
Codex 把 MCP 服务器写在 config.toml 的 [mcp_servers.<名称>] 表里,CLI、IDE 扩展和桌面应用共用这份配置。字段分两组:stdio 服务用 command 启动本地进程,HTTP 服务用 url 访问。把两组混在一张表里,或在文件里写下静态令牌,是最容易出的错。本文依据官方文档整理字段分组,并附一个自写的 Python lint,用来在提交配置前做初步检查。它不是官方校验器,没有启动 Codex,也没有连接任何 MCP 服务。
什么时候用
- 要手写或审阅 [mcp_servers.*] 配置,并想在连接前先排除字段混用和明文密钥。[1]
什么时候不用
- 需要验证服务器能否连接、OAuth 是否可登录,或工具返回内容是否可信,这些必须在真实环境里检查。[1]
两组字段:stdio 与 HTTP
官方把可配置字段分成三块。stdio 服务器以本地进程运行,HTTP 服务器通过地址访问;两者的必填项不同,其余选项是共用的。[1]
| 分组 | 字段 | 说明 |
|---|---|---|
| stdio | command(必填)、args、env、env_vars、cwd | command 启动服务器;env_vars 是允许并转发的环境变量名 |
| HTTP | url(必填)、auth、bearer_token_env_var、http_headers、env_http_headers、http_headers_helper | bearer 令牌通过环境变量名引用;http_headers 是静态值;helper 只用于本地环境发起的 HTTP 连接,不用于 stdio |
| 共用 | enabled、required、enabled_tools、disabled_tools、default_tools_approval_mode、tools.<工具>.approval_mode、startup_timeout_sec、tool_timeout_sec | disabled_tools 在 enabled_tools 之后生效;超时默认 10 秒启动、60 秒工具调用 |
default_tools_approval_mode 官方列出的取值是 auto、prompt、writes 和 approve,其中 writes 会对未标记为只读的工具提示确认。凭据方面:bearer_token_env_var 只写环境变量名;通过 codex mcp add 配置预注册 OAuth 客户端的密钥时,Codex 会把它以 mcp_servers.<名称>.oauth.client_secret 保存到 config.toml,官方要求保持该文件私有并且不进版本控制。[1]
自写 lint:提交配置前的初步检查
这个脚本读取一份 TOML,逐个服务器检查:command 与 url 是否恰好出现一个、是否混入另一组字段、enabled_tools 减去 disabled_tools 之后实际可用的工具、approval 模式是否在官方取值内、http_headers 里是否有疑似静态密钥的头、文件里是否保存了 OAuth client_secret。它依赖 Python 3.11 及以上的 tomllib,建议用 python3 -I 运行,传入你要检查的文件路径。检查规则是我按官方字段表自己写的,不是官方校验,也没有覆盖全部字段。[1]
import sys
import tomllib
STDIO_ONLY = {"args", "env", "env_vars", "cwd"}
HTTP_ONLY = {"auth", "bearer_token_env_var", "http_headers", "env_http_headers", "http_headers_helper"}
MODES = {"auto", "prompt", "writes", "approve"}
with open(sys.argv[1], "rb") as handle:
config = tomllib.load(handle)
for name, server in config.get("mcp_servers", {}).items():
keys, notes = set(server), []
if ("command" in keys) == ("url" in keys):
notes.append("need exactly one of command / url")
if "url" in keys and keys & STDIO_ONLY:
notes.append("stdio-only keys on an HTTP server")
if "command" in keys and keys & HTTP_ONLY:
notes.append("HTTP-only keys on a stdio server")
enabled, disabled = server.get("enabled_tools"), set(server.get("disabled_tools", []))
if enabled is not None:
notes.append("effective tools: " + str([t for t in enabled if t not in disabled]))
if server.get("default_tools_approval_mode", "auto") not in MODES:
notes.append("unknown default_tools_approval_mode")
for header in server.get("http_headers", {}):
if any(word in header.lower() for word in ("authorization", "token", "key")):
notes.append(f"static secret-like header in file: {header}")
if "client_secret" in server.get("oauth", {}):
notes.append("oauth.client_secret is stored in this file")
print(name, "->", notes or ["no findings"])
下面的样例配置里,docs_search 是带 bearer 环境变量的 HTTP 服务,local_files 是 stdio 服务,bad_mix 故意把 command 与 url 同时写进一张表。输出里的 effective tools 来自 enabled_tools 减去 disabled_tools。样例是原创内容,没有用于连接任何真实服务。[1]
[mcp_servers.docs_search]
url = "https://mcp.example.com/docs"
bearer_token_env_var = "DOCS_MCP_TOKEN"
enabled_tools = ["search", "fetch", "export"]
disabled_tools = ["export"]
default_tools_approval_mode = "writes"
tool_timeout_sec = 30
[mcp_servers.local_files]
command = "node"
args = ["./tools/files-server.mjs"]
env_vars = ["FILES_ROOT"]
startup_timeout_sec = 20
required = true
[mcp_servers.bad_mix]
url = "https://mcp.example.com/mix"
command = "node"
default_tools_approval_mode = "always"
http_headers = { "Authorization" = "Bearer demo-literal" }
docs_search -> ["effective tools: ['search', 'fetch']"]
local_files -> ['no findings']
bad_mix -> ['need exactly one of command / url', 'HTTP-only keys on a stdio server', 'unknown default_tools_approval_mode', 'static secret-like header in file: Authorization']
没有覆盖的部分与停止条件
- 官方没有写同时配置 command 与 url 时 Codex 会怎样处理;lint 把它标为冲突,只是因为两者在官方字段表里分别是各自传输方式的必填项,实际行为待核。
- lint 检查的是静态头里的名字(含 authorization、token、key),不检查 env 里的明文值,也不判断值本身是否真的是密钥,会漏报也会误报。
- 官方还支持 OAuth 回调、CIMD 与 DCR 注册、插件自带的 MCP 服务器、远程执行环境等配置;这些没有在 lint 里建模,需要时直接阅读官方对应段落。
- 服务器是否启动成功、工具列表是否符合预期,需要在真实环境用 codex mcp list 或 TUI 的 /mcp 观察;本文没有运行它们。
在真实采用前,先让 lint 对你的配置没有「need exactly one of command / url」和静态密钥提示,再确认你信任服务器来源,并核对工具名单与审批模式;任何一项不清楚就先把 enabled 设为 false,而不是带着默认审批直接连接。本文只核验了官方字段说明,没有启动 Codex,没有连接 MCP 服务,也不认证服务器的安全性或费用。[1]
来源
以下均为官方页面,核验日期见各条;链接指向核验时的版本。