Browse documentation
Codex CLI: connect unlob over MCP
TOML rather than JSON, and a bridge if your build predates remote servers.
Configuration
Codex reads ~/.codex/config.toml. Servers go under mcp_servers.
Recent versions can connect to a Streamable HTTP server directly:
[mcp_servers.unlob]
url = "https://api.unlob.com/mcp"
[mcp_servers.unlob.http_headers]
"x-api-key" = "ulb_…"
Remote-server support and the exact spelling of these keys have changed as Codex has
evolved, so check your own codex --version and its documentation before assuming the
above. If it does not take, the bridge below works on every version.
The bridge
[mcp_servers.unlob]
command = "npx"
args = ["-y", "mcp-remote", "https://api.unlob.com/mcp", "--header", "x-api-key:${UNLOB_API_KEY}"]
[mcp_servers.unlob.env]
UNLOB_API_KEY = "ulb_…"
No space after the colon in the header argument — argument splitting eats it.
Check it
codex mcp list
The server should be listed with its tools. Inside a session, ask Codex what tools it has.
Why this one matters
Codex is a strict MCP client. It is the client that finds handshake bugs other clients
paper over — if it registers zero tools against a server, that server has a real
conformance problem, not a Codex problem. Against api.unlob.com it should register
the tools the profile advertises; if it does not, tell us rather than working around it.
Rate limits look the same as running out of credits
A 429 from unlob means two different things, and Codex has no way to tell them apart on
its own — it sees a failed tool call either way. The distinction lives in one header:
retry-after present → per-minute rate limit; retry once, after that many seconds
retry-after absent → out of monthly credits; retrying changes nothing
If you drive tool calls through a wrapper script rather than letting Codex call the server
directly, branch on the header there rather than on the status code alone — a bare 429
retried on a blind loop against a hard-capped key produces nothing but identical failures.
402 is separate again: the account itself is suspended, and no amount of retrying
reaches a working key. Rate limits and credits has the full table.
Why this one matters, restated for the config layer
The strictness that makes Codex useful for finding handshake bugs also means it is
unforgiving of a config that is nearly right. A url key where unlob expects the header
table nested one level differently, or a TOML table name that does not match what a given
Codex build parses, produces the same symptom as a real server bug: zero tools, no error.
That is worth remembering before assuming a fresh conformance issue — check the config
against codex mcp list first, since a malformed entry there often does not even appear as
a server Codex attempted to reach, rather than one it reached and failed to talk to.
Scripting outside a session
codex mcp list and the config above are for an interactive session. For a headless script
that only ever needs web_search or assemble_context and does not want to drive an MCP
handshake at all, calling the REST endpoint directly is usually less code — see the
SDK quickstarts for a client in five languages. Reach for MCP when Codex
itself is the caller; reach for REST when a shell script or CI step is.
A CI job that runs codex exec non-interactively is a middle case worth naming
specifically: it still goes through the MCP config, so the same mcp_servers entry applies,
but it is worth confirming the key is present in whatever environment that job runs under —
a config file committed with an ${UNLOB_API_KEY} reference is only as good as the
pipeline actually setting that variable.
Next
- MCP overview — the lifecycle Codex is strict about
- Troubleshooting