-
Notifications
You must be signed in to change notification settings - Fork 0
Bridging MCP Servers
Codex Free can act as an MCP aggregator: it connects to other MCP servers (local stdio or remote Streamable HTTP), discovers their tools at startup, and re-exposes them through its own /mcp endpoint — so the ChatGPT-side agent can call them too.
Each upstream tool is offered as <server>__<tool> (e.g. remote_exec__docker_ps), and calls are forwarded verbatim — text, images, structured content, and error flags all pass through.
⚠️ Bridged servers carry delegated authority. A stdio upstream launches a real process running as your OS user; an HTTP upstream receives model-directed calls plus its configured token. Only bridge servers you trust. See Security Model.
Codex Free reads $CODEX_HOME/config.toml (or ~/.codex/config.toml) — read-only, never rewritten — and imports user-configured MCP servers without requiring a codex executable.
For each [mcp_servers.<name>] entry it preserves the fields it can:
-
command,args,env,cwdfor local stdio launch; - local
env_vars, resolved from Codex Free's process environment; -
urlfor Streamable HTTP; -
bearer_token_env_var,http_headers,env_http_headersfor HTTP auth and headers; -
startup_timeout_sec(or legacystartup_timeout_ms) andtool_timeout_sec; -
enabled = falseas a disabled upstream; -
enabled_toolsas an allow-list anddisabled_toolsas a deny-list applied afterward.
By default it also runs codex mcp list --json and, for servers in Codex's effective catalogue but absent from config.toml, codex mcp get <name> --json — so plugin-provided enablement and tool allow/deny lists are preserved. Each invocation is bounded to 30 s and 4 MiB of stdout, parsed in memory without logging literal env values.
When the CLI is missing/fails/times out, startup continues with the direct config.toml result and prints a warning that plugin-provided servers may be missing. Pass --codex-cli to make CLI discovery mandatory (the same condition then becomes a startup error). Configure via the codexMcp block:
{ "codexMcp": { "enabled": false }, "mcpServers": {} } // disable all auto-import, keep explicit
{ "codexMcp": { "enabled": true, "useCli": false } } // keep direct import, never run the CLIThe mcpServers map is always supported. A local (stdio) entry:
{
"mcpServers": {
"idasql": {
"command": "idasql-mcp",
"args": ["--stdio"],
"env": { "IDA_PATH": "C:/Program Files/IDA" }
}
}
}A remote (Streamable HTTP) entry — keep secrets in env vars, not the JSON:
{
"mcpServers": {
"remote-docs": {
"url": "https://mcp.example.com/mcp",
"bearerTokenEnvVar": "REMOTE_MCP_TOKEN",
"httpHeaders": { "X-Client": "codex-free" },
"envHttpHeaders": { "X-Tenant": "REMOTE_MCP_TENANT" },
"startupTimeoutSec": 20,
"toolTimeoutSec": 60
}
}
}bearerTokenEnvVar must exist and be non-empty when configured. Env-backed headers override a same-named static header. Don't configure both bearerTokenEnvVar and an Authorization header entry.
An explicit entry with the same name as an imported Codex server is a field-by-field overlay — reuse Codex's launch settings while adding bridge-only options without copying command/args/env:
{ "mcpServers": { "remote-exec": { "mode": "gateway", "tools": ["exec", "machine_list"] } } }Set an empty array/object to replace an imported collection with an empty one. Explicit command/url replace the imported transport.
-
disabled: true— keep config but skip it (shown-> disabled). -
tools: [...]— allow-list of upstream tool names to bridge. -
disabledTools: [...]— remove tools after the allow-list. -
cwd— child process working directory. -
startupTimeoutSec— bounds init + firsttools/list(default 20 s). -
toolTimeoutSec— bounds each forwarded call; sends MCP cancellation on timeout. - Bridged names are sanitized to
[A-Za-z0-9_](e.g.remote_exec__exec). - A bridged name colliding with a native tool is skipped with a warning.
-
typeis inferred (command→ stdio,url→ HTTP); aliases"http","streamable-http","streamable_http"accepted. Legacy SSE and WebSocket are rejected.
OAuth login/credential persistence is not implemented — an OAuth-protected upstream must be given a usable bearer token via bearerTokenEnvVar or an env-backed Authorization header.
Codex MCP config discovery: /home/user/.codex/config.toml
idasql -> imported from Codex config
Codex CLI MCP discovery: codex
idalib -> imported from Codex CLI (not present in config.toml)
bridged MCP server 'idasql': 12 tool(s)
Tools loaded (38): 26 native + 12 bridged from upstream MCP servers
Upstream MCP servers:
remote-exec -> 84 tool(s)
idasql -> FAILED: could not launch 'D:/wrong/path.exe': The system cannot find the path specified. (os error 3)
An upstream that fails to launch/connect/authenticate is skipped, never blocking startup or the native tools. If a server doesn't show up, check the banner first — the most common cause is a wrong command path.
Some clients (ChatGPT among them) won't reliably surface a large bridged tool set. mode: "gateway" collapses a whole server into a single dispatcher tool plus a generated skill:
{ "mcpServers": { "remote-exec": { "mode": "gateway" } } }It registers one tool named remote_exec taking { "function": "<name>", "arguments": { ... } }, and auto-generates a skill (skills_read name="remote-exec") documenting every function and its schema. So an 84-tool server shows up as 1 tool + 1 skill instead of 84. disabled, type, tools, and disabledTools still apply. When the server was imported from Codex, the overlay alone is enough; include command and launch fields when it exists only in codex.config.json.
-
Configuration — the
codexMcpandmcpServersblocks. - Security Model — the authority a bridged server delegates.
- AGENTS and Skills — gateway mode's generated skill.
Repository · Releases · Report an issue · MIT License
Getting started
Reference
How it works
Multi-project
Extending
Operations