Codex App Server MCP
# Codex App Server MCP
[](https://github.com/j-pollack/codex-app-server-mcp/actions/workflows/ci.yml)
An MCP server that makes Codex a usable subagent pool inside an MCP host: a supervisor agent can create, coordinate, steer, inspect, and interrupt multiple Codex agents through the stateful Codex app-server protocol, and run them alongside the host's own subagents.
It works directly with MCP hosts such as Claude Code. A bundled Pi extension bridges the same MCP tools into Pi, whose core currently does not load MCP servers directly.
## What it provides
- Concurrent independent Codex threads through one long-lived app-server process
- Non-blocking dispatch and fleet-wide collection, so supervising many workers costs one call
- New, resumed, and forked workers
- Protocol-level steering of active turns
- Follow-up turns on completed workers
- Bounded event history and reduced status instead of raw transcript flooding
- Correlated command, file, permission, user-input, dynamic-tool, and MCP elicitation requests
- Interruption, persistent goals, and archiving
- Discovery of Codex-created descendant threads when the app-server event stream identifies them
- An explicitly opt-in raw RPC escape hatch for new app-server methods
The implementation uses `codex app-server`, not `codex exec`.
## Requirements
- Node.js 24 or newer
- A current Codex CLI available as `codex`
- A working Codex login or configured model provider
Check the local setup:
```sh
node --version
codex --version
codex login status
```
## Install and build
```sh
git clone https://github.com/j-pollack/codex-app-server-mcp.git
cd codex-app-server-mcp
npm install
npm run build
```
The MCP entry point is:
```text
./dist/index.js
```
## Claude Code setup
Add it as a user-scoped stdio MCP server:
```sh
claude mcp add --scope user codex-agents -- \
node "$(pwd)/dist/index.js"
```
Run this command from the repository root. `$(pwd)` is expanded when the server is registered, so Claude Code stores an absolute path to your checkout.
For only the current project, use `--scope local` instead. Confirm it is present:
```sh
claude mcp list
```
Restart Claude Code after adding the server. Its tools will be namespaced by the MCP server name in Claude's internal tool catalog.
## Pi setup
Pi currently uses an extension for MCP integration. This package includes one and declares it in the package's `pi.extensions` manifest.
After building, install the local package:
```sh
pi install .
```
Or try the adapter without installing it:
```sh
pi -e ./pi-extension/index.mjs
```
Run either command from the repository root.
The adapter starts this MCP server, discovers its tools, and registers them in Pi with a `codex_` prefix, such as `codex_agent_start` and `codex_agent_wait`. Run `/codex-mcp` in Pi to inspect the connection.
### Subagents in Pi
Pi has no agent registry to add to, so a first-class subagent there is not a registered agent type
but a tool that streams while it runs and draws itself. The extension registers one:
```
codex { task, cwd, mode: "task" | "handoff" | "review", label?, model?, sandbox? }
codex { cwd, tasks: [ { task, label }, ... ] }
```
It dispatches the workers, tails them through the same `agent_wait` loop a relay agent would run,
and pushes every tick into the tool call's own display: a status icon per worker, its recent event
summaries, and its token spend, with the whole transcript and each final answer under the expanded
view (Ctrl+O). Several `tasks` run concurrently and stream side by side.
Aborting the call (Ctrl+C) interrupts the workers rather than leaving them running. Pi hands the
tool a real `AbortSignal`, so this is a direct `agent_interrupt` — none of the hook plumbing the
Claude Code plugin needs, and the server's watchdog stays as the backstop for the case where even
that fails.
`codex` blocks for the workers it starts, which is the Pi idiom and the opposite of the non-blocking
default elsewhere in this README. The raw `codex_agent_start` / `codex_agent_wait` tools are still
registered for the fleet style, where the point is to dispatch and keep working.
## Codex as a second subagent pool
The design goal is that a supervisor can treat Codex workers the way it already treats its
host's native subagents, and run both pools at the same time. Three properties make that work:
- **Dispatch is non-blocking.** `agent_start` returns as soon as the turn is accepted. A worker
needs no attention to keep running, so several `agent_start` calls can be issued in one message
alongside whatever host-native subagents the supervisor also wants.
- **Collection is fleet-wide.** `agent_result` with no arguments returns a lean report for every
worker in a single call, so supervising ten workers costs the same as supervising one.
- **Blocking, when it is needed at all, is also fleet-wide.** `agent_wait` takes `agentIds` with
`mode: "any"` or `mode: "all"`, so a supervisor never serialises one wait per worker.
### The loop
1. **Dispatch.** One message, several `agent_start` calls, plus any host-native subagents. Each
prompt must be self-contained: the worker cannot see the supervisor's conversation.
2. **Work.** Do supervisor work while the fleet runs. Nothing needs to be watched.
3. **Collect.** `agent_result` returns `{status, done, message, error, tokens}` per worker, and
surfaces any worker blocked on an approval.
4. **Only if idle, wait.** `agent_wait({agentIds, mode: "any"})` wakes on the first worker to
finish so its result can be used while the rest continue.
5. **Zoom in when needed.** `agent_status` for one worker's plan and diff, `agent_events` for its
event history, `agent_send` to steer or follow up, `agent_interrupt` to stop redundant work.
### Choosing what to send where
Codex workers are worth using where a different model is the point: an independent implementation
pass to compare against, a second-opinion diagnosis, adversarial review of the supervisor's own
work. Split the task so both pools run concurrently rather than in sequence.
An example request to the supervisor host:
```text
Use Codex workers to investigate this failure. Start separate agents for the
runtime path, the test coverage, and a skeptical review. Let them run in
parallel, steer them if their scopes overlap, and synthesize their verified
findings. Do not approve destructive or externally visible actions.
```
## Codex workers as first-class subagents
An MCP server cannot register an agent type, so a Codex worker dispatched with `agent_start` renders
as a tool call in whichever transcript called it. The bundled Claude Code plugin in
[`plugin/`](plugin/README.md) closes that gap the only way a host allows: a real host-native
subagent, `codex-worker`, that does nothing but drive one Codex worker and print its events. The
user gets a subagent row, an openable transcript, a `/tasks` entry, and an address they can message
— messages are forwarded to the worker with `agent_send`.
The cost is honest: the relay is a live model context that wakes on each tick to echo reduced
events, so the transcript is a relay at up to 55-second granularity, not Codex's own stream. Pin it
to a small model and forbid it from doing any thinking of its own.
The plugin also carries the hooks that stop a worker when the subagent driving it is interrupted.
See [Abandoned workers](#abandoned-workers).
The repository root is a plugin marketplace, so installing it from a checkout is two commands:
```sh
claude plugin marketplace add "$(pwd)"
claude plugin install codex-agents@codex-app-server-mcp
```
Restart Claude Code afterwards. The MCP server must be registered as `codex-agents`: the subagent's
tool list and the hook matchers both name `mcp__codex-agents__*`.
## Abandoned workers
A worker keeps running whether or not anyone watches it, which is the point — and also a hazard
when the supervisor is interrupted mid-turn. Pass `watchdogSeconds` to `agent_start` to tie a
worker's life to its supervisor's attention: any `agent_result`, `agent_wait`, `agent_status`,
`agent_events`, or `agent_send` resets the window, and a worker nothing has read for a full window
is interrupted. One fleet-wide `agent_result` keeps a whole fleet alive.
Omit it and nothing changes: a worker with no watchdog is never interrupted for going unread, which
is right for work meant to outlive the call that dispatched it.
Two faster signals feed the same mechanism. A host that interrupts a turn cancels the in-flight MCP
call, and `agent_wait` treats that cancellation as evidence its caller is gone. Host hooks, which
cannot call MCP tools, reach an interrupt-only unix socket at
`~/.codex-app-server-mcp/servers/<pid>.sock`. Neither is load-bearing on its own — hooks can be
absent and a killed host runs none — which is why the watchdog exists behind them.
Interrupting a turn leaves its thread log intact, so the work stays recoverable with `agent_resume`.
## MCP tools
| Tool | Purpose |
| --- | --- |
| `server_info` | App-server health, worker counts, and unscoped pending requests |
| `agent_start` | Start a background worker and return without waiting for completion |
| `agent_review` | Dispatch Codex's built-in reviewer against a diff, as a worker |
| `agent_handoff` | Dispatch a worker that inherits the host's conversation |
| `agent_resume` | Load and subscribe to a persisted Codex thread |
| `agent_fork` | Branch a worker or raw thread into a new worker |
| `agent_send` | Automatically steer an active worker or start its follow-up turn |
| `agent_steer` | Explicitly append guidance to an in-flight turn |
| `agent_interrupt` | Interrupt an active turn |
| `agent_result` | Collect lean per-worker results for a fleet, or for every worker, in one call |
| `agent_status` | Read one worker's detailed reduced state, plan, diff, and pending requests |
| `agent_list` | List all managed and discovered workers |
| `agent_events` | Page through bounded reduced events using a cursor |
| `agent_wait` | Block for up to 55 seconds awaiting one or all of a set of workers |
| `agent_request_resolve` | Answer a correlated app-server request |
| `agent_goal_set` | Set or update a persistent thread goal |
| `agent_archive` | Archive an idle worker thread |
| `raw_rpc` | Send arbitrary app-server RPC when explicitly enabled |
### Native review
`agent_review` starts a worker whose first turn is Codex's own reviewer rather than a prompt. Codex
supplies the review instructions, which is worth more than asking a model to "review this diff", and
the result is an ordinary worker: same reduced state, same `agent_result`, same watchdog.
```json
{ "scope": "base-branch", "baseBranch": "main", "cwd": "/abs/path/to/repo" }
```
`scope` is `uncommitted` (the working tree, the default) or `base-branch`. The thread is read-only
unless `threadOptions` says otherwise, and `delivery: "detached"` runs the review on its own thread,
registered as a child worker. Reach for `agent_start` with a prompt instead whenever you want a
specific lens — adversarial review, one dimension, a design critique — since the native reviewer
takes no focus text.
### Handing over a conversation
`agent_start` and `agent_review` both dispatch a worker that cannot see the supervisor's
conversation, which is why their prompts have to be self-contained. `agent_handoff` removes that
constraint: it imports a host agent's transcript into a Codex thread and starts the worker there, so
the worker reads the conversation first and the prompt is only what to do next.
```json
{ "prompt": "Finish the migration we just designed.", "cwd": "/abs/path/to/repo" }
```
With no `sessionId` or `transcriptPath`, the most recently written session for `cwd` is taken, which
on a live host is the running one. Detection is home-scoped and covers every project, so `cwd` is
what selects the conversation, not a search root.
The import is a real write: it creates a durable Codex thread, resumable from any Codex client, and
it stays after the worker finishes. Prefer `agent_start` when the task genuinely is self-contained —
a handoff costs an import and gives the worker the whole history, including the parts of it that
have since turned out to be wrong.
### Advanced app-server options
`agent_start` exposes common settings directly: `cwd`, `model`, `effort`, `serviceTier`, `personality`, `sandbox`, `permissions`, `approvalPolicy`, `approvalsReviewer`, instruction overrides, workspace roots, thread config, an output schema, and `watchdogSeconds`.
`agent_wait` can also carry each worker's new events with `includeEvents`, paged from the cursor the wait itself used, so a supervisor tailing a worker spends one call per tick instead of a wait followed by `agent_events`.
Use `threadOptions`, `turnOptions`, `resumeOptions`, or `forkOptions` for fields introduced by newer app-server versions. The server always owns identity fields such as `threadId`, expected active turn IDs, and user input; callers cannot replace those invariants through an options object.
The installed Codex CLI can generate its exact protocol definitions:
```sh
npm run protocol:generate
```
Generated files go to the ignored `.generated/app-server` directory.
## Approvals and server requests
App-server can stop a worker and request a client response. The MCP server records the request, marks the worker `waiting`, and returns a public `requestId` through `agent_status` or `agent_wait`.
For command and file approvals, `decision` is shorthand:
```json
{
"requestId": "request-1-abcd1234",
"decision": "accept"
}
```
For permission requests, user input, dynamic tools, and MCP elicitation, pass the response object required by the installed app-server schema:
```json
{
"requestId": "request-2-efgh5678",
"response": {
"permissions": {},
"scope": "turn"
}
}
```
The broker does not auto-approve. A supervisor can apply policy, but destructive actions, credentials, publication, deployments, and external communication should remain human decisions.
## Concurrency and filesystem isolation
Concurrent threads are independent conversations, not isolated filesystems. Multiple write-capable workers pointed at the same checkout can overwrite or invalidate one another's work.
Prefer one of these arrangements:
- one Git worktree per write-capable worker;
- explicit non-overlapping file ownership;
- several read-only investigators and one serialized writer.
Pass each worktree as `cwd` and, where appropriate, in `runtimeWorkspaceRoots`.
## Environment variables
| Variable | Default | Meaning |
| --- | --- | --- |
| `CODEX_MCP_CODEX_BIN` | `codex` | Codex executable path |
| `CODEX_MCP_CODEX_ARGS_JSON` | `["app-server"]` | JSON array of app-server process arguments |
| `CODEX_MCP_APP_SERVER_CWD` | MCP process cwd | App-server process working directory |
| `CODEX_MCP_REQUEST_TIMEOUT_MS` | `30000` | RPC request timeout for cancellable calls |
| `CODEX_MCP_THREAD_REQUEST_TIMEOUT_MS` | `300000` | RPC request timeout for `thread/start`, `thread/resume`, and `thread/fork`, which cannot be cancelled |
| `CODEX_MCP_EXPERIMENTAL_API` | `true` | Initialize with experimental app-server APIs enabled |
| `CODEX_MCP_MAX_EVENTS_PER_AGENT` | `250` | Per-worker reduced event retention |
| `CODEX_MCP_MAX_TEXT_CHARS` | `32000` | Maximum retained live/final text, diff, or event payload |
| `CODEX_MCP_MAX_AGENTS` | `256` | Maximum supervisor-managed workers in one MCP process |
| `CODEX_MCP_MAX_ACTIVE_AGENTS` | `32` | Maximum simultaneously starting, running, or waiting workers |
| `CODEX_MCP_ENABLE_RAW_RPC` | `false` | Expose the unrestricted `raw_rpc` MCP tool |
| `CODEX_MCP_WATCHDOG_SECONDS` | `0` | Default abandonment window for every worker; `0` disables it |
| `CODEX_MCP_CONTROL_SOCKET` | `true` | Listen on the interrupt-only control socket |
| `CODEX_MCP_CONTROL_DIR` | `~/.codex-app-server-mcp/servers` | Directory holding control sockets and their registry files |
| `CODEX_APP_SERVER_MCP_COMMAND` | current Node executable | Pi adapter override for starting this MCP server |
| `CODEX_APP_SERVER_MCP_ARGS_JSON` | built server entry | Pi adapter command arguments |
Example enabling the raw escape hatch in Claude Code:
```sh
claude mcp add --scope user -e CODEX_MCP_ENABLE_RAW_RPC=true codex-agents -- \
node "$(pwd)/dist/index.js"
```
`raw_rpc` bypasses worker-registry invariants and can invoke destructive app-server methods. Leave it disabled unless you specifically need a protocol method that does not yet have a typed orchestration tool.
## Persistence model
The MCP worker registry is in memory. Codex thread logs remain managed by app-server, so non-ephemeral threads survive an MCP process restart. Keep the returned `threadId` and use `agent_resume` to recover one.
Opaque `agentId` values are scoped to one MCP process and are not durable identifiers.
## Development and verification
```sh
npm run check
npm run build
npm run smoke:real
npm run probe:protocol
```
`npm run check` is exactly what CI runs on Node 24, in the same order: formatting, lint, type checking over `src`, `test`, and `scripts`, then the fake app-server integration suite, an in-memory MCP client suite, and the spawned Pi adapter test. It needs no Codex binary, account, or network. `npm install` also points `core.hooksPath` at `.githooks/`. On commit, `lint-staged` formats and auto-fixes the staged files, then lint and types are verified across the project; on push, the suite runs. See [CONTRIBUTING.md](CONTRIBUTING.md).
The other two scripts do talk to a locally installed Codex and spend real model usage, which is why CI excludes them. `npm run smoke:real` starts three simultaneous workers, waits on the fleet in one call, collects it in one more, and archives the threads at the end; it uses durable threads deliberately, because that is the shape whose abandoned turn can be cancelled by id. `npm run probe:protocol` re-checks the three live protocol facts the timeout-cancellation path relies on: that `turn/start` answers *before* it emits `turn/started`, that `thread/turns/list` still names the turn a timed-out call abandoned, and that `turn/interrupt` with an empty `turnId` stops a running turn without naming it.
The implementation was verified against Codex CLI `0.151.0` and `@modelcontextprotocol/sdk` `1.30.0`, with three concurrent real worker turns.
## Architecture
See [Architecture](docs/architecture.md). The app-server protocol itself is documented in the [official OpenAI Codex App Server documentation](https://developers.openai.com/codex/app-server).
## License
MIT
TDQS
Scored across 15 tools
The tools cluster into clear lifecycle, control, and observation groups. The closest pairs—agent_send/agent_steer and agent_status/agent_result—have explicit usage guidance that distinguishes them, so an agent can usually select correctly.
The agent_ prefix is consistent, but the suffix style is mixed: most tools use imperative verbs (start, send, wait), while status, events, and result are nouns, and request_resolve/goal_set reverse the expected verb-noun order. server_info also breaks the agent_ naming pattern, though reasonably for a server-level tool.
Fifteen tools is on the higher end but fits the complexity of orchestrating background agent workers. Each tool covers a distinct lifecycle, observation, or control function, and none feels redundant or unnecessary.
The tool set covers the full agent lifecycle: start, resume, fork, guide, interrupt, observe, wait, collect results, resolve requests, set goals, and archive. There are no obvious dead ends, and every state change has a corresponding way to inspect or move forward.