-
Notifications
You must be signed in to change notification settings - Fork 3
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 server is re-exposed through one of three exposure modes, and calls are always forwarded verbatim — text, images, structured content, and error flags all pass through:
-
direct— every upstream tool is offered individually as<server>__<tool>(e.g.remote_exec__docker_ps). The default for explicitmcpServersentries. -
gateway— the whole server collapses to one dispatcher tool plus a generated skill. See Gateway mode below. -
catalog— the server's tools are indexed privately and reached through four fixed discovery tools instead of being advertised individually. The default for servers imported from Codex (config.tomland CLI, including plugins). See Catalog mode below.
Set mode per server to override its default. So an explicit entry stays direct unless you ask otherwise, while a large imported tool set stays out of the advertised surface until searched.
⚠️ 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). -
mode: "direct" | "gateway" | "catalog"— exposure mode (see above). Defaults todirectfor explicit entries,catalogfor Codex-imported ones. -
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)
Tools loaded (114): 26 native + 88 upstream-facing MCP tools
Upstream MCP servers:
idasql -> catalog (12 private tool(s))
idalib -> catalog (5 private tool(s))
remote-exec -> direct (84 tool(s))
brokensrv -> FAILED: could not launch 'D:/wrong/path.exe': The system cannot find the path specified. (os error 3)
Imported servers (idasql, idalib) default to catalog, so their tools are private and together add just the four mcp_* discovery tools; the explicit remote-exec is direct, contributing its 84 tools individually. That's why upstream-facing (88) is 4 + 84, not 12 + 5 + 84.
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.
mode: "catalog" keeps a server's tools out of the advertised surface entirely and makes them reachable through progressive disclosure. This is the default for imported Codex servers (config.toml and CLI, including plugins), so a large transitive tool set doesn't flood the connector; set mode explicitly to opt an explicit server in, or to force an imported one to direct/gateway.
{ "mcpServers": { "idasql": { "mode": "catalog" } } }However many catalog-mode servers you bridge, they collectively add just four fixed tools:
-
mcp_list_sources— list the indexed servers (id, name, provenance, transport category, tool count, and the server's own self-description). -
mcp_search_tools— weighted (BM25) search across the private tool index; returns opaque source/tool ids and summaries. -
mcp_get_tool— fetch one tool's full definition (description + input schema) by id. -
mcp_call_tool— invoke a tool by itssource/toolid witharguments; the call is forwarded verbatim to the upstream server.
The full tool definitions live only in Codex Free's process — the model sees them one lookup at a time. disabled, type, tools, and disabledTools still apply first, so a filtered-out tool never enters the index and can't be searched or called. mcp_call_tool resolves the model-supplied ids to a stored upstream tool name (never one reconstructed from model input) and fails closed on any unknown id. Only the server name, provenance, transport category (stdio/streamable-http), the upstream's self-reported implementation info, and per-tool definitions are ever exposed — never url, command, env, or auth headers. See Security Model.
-
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