Skip to main content
Glama
0Pinky0

Codex MCP Sidecar

by 0Pinky0
README.md
# Codex MCP Sidecar

Codex MCP Sidecar is a local MCP server for letting Claude Code start, resume,
and wait for Codex `app-server` sessions while a human can inspect those live
sessions from a terminal.

It is designed for a specific collaboration pattern:

1. Claude starts Codex through MCP and gets back a small run handle.
2. The Codex turn keeps running in the background by default.
3. A human opens `codex-runs`, selects the active run, and attaches to the same
   live Codex session.
4. Claude calls `codex_wait` until the final result is ready.

This project is a local sidecar around the Codex CLI. It is not an official
OpenAI product.

## Why

When Claude Code calls another agent through a normal MCP tool, the human often
sees only one opaque tool-call placeholder. That makes supervision difficult,
especially for long running Codex work.

This sidecar keeps the Claude-facing MCP response compact, but stores the full
audit trail under the project working directory and exposes a local picker for
human inspection.

## Features

- Uses Codex `app-server` instead of `codex exec` for Claude-facing calls.
- Defaults `codex_run` and `codex_resume` to async mode so live turns remain
  inspectable.
- Provides `codex_wait` with a 240 second soft wait slice by default, helping
  Claude keep prompt cache warm during long Codex work.
- Stores audit artifacts in `<project-root>/.codex/codex-mcp/runs/`.
- Maintains a small user-level run pointer index in
  `$CODEX_HOME/mcp-wrapper/runs.index.jsonl`.
- Lets humans inspect active and completed runs through the `codex-runs` CLI.
- Keeps MCP tool payloads small and leaves detailed audit data on disk.
- Avoids exposing direct inspect commands, backend details, file paths, command
  logs, parse errors, or deep links in normal MCP responses.

## Requirements

- Node.js 20, 22, or 24 (the CI-supported versions).
- npm.
- Codex CLI available as `codex`.
- A Codex CLI build that supports `codex app-server` and
  `codex resume --remote`.
- Claude Code or another MCP client that can run stdio MCP servers.

## Installation

Clone the repository and install dependencies:

```bash
git clone git@github.com:0Pinky0/codex-mcp-sidecar.git
cd codex-mcp-sidecar
npm ci
```

Optional local bin setup:

```bash
npm link
```

That exposes:

- `codex-mcp-sidecar`: stdio MCP server entrypoint.
- `codex-mcp-wrapper`: compatibility alias for the same server.
- `codex-runs`: terminal picker for inspecting Codex runs.

You can also run the server directly with `node <repo-path>/server.mjs`.

## Claude Code Configuration

Register the server with Claude Code. The MCP server name can be `codex` if you
want Claude to see this sidecar as the main Codex integration:

```bash
claude mcp add-json codex '{
  "type": "stdio",
  "command": "node",
  "args": ["<repo-path>/server.mjs"],
  "env": {
    "CODEX_MCP_WRAPPER_APP_SERVER_MODE": "private"
  }
}' --scope user
```

Replace `<repo-path>` with the absolute path to this repository.

Dependencies are installed with `npm ci` from the checked-in lockfile. CI runs
`npm audit --omit=dev --audit-level=moderate`; transitive findings fail the
build and must be reviewed before a release. Use the npm registry (or an
approved internal proxy) rather than an unreviewed package mirror.

After registration, call `codex_wrapper_check` from Claude. A healthy local
configuration returns `ok: true` and the detected Codex CLI version.

## Exposed MCP Tools

### `codex_run`

Starts a new persisted Codex app-server turn.

Important defaults:

- `async: true`
- `sandbox: "read-only"` and `approval_policy: "never"`
- app-server mode: `private` (stdio; shared WebSocket is opt-in)
- audit directory: `<cwd>/.codex/codex-mcp/runs/`

Example:

```json
{
  "prompt": "Audit the current diff. Return findings first, then residual risk.",
  "run_name": "diff-audit",
  "cwd": "<project-root>",
  "approval_policy": "never",
  "timeout_ms": 1800000
}
```

Async calls return a compact running handle, typically including `runId`,
`status`, `threadId`, and timeout state.

### `codex_resume`

Resumes a previous Codex session by wrapper `run_id`.

The caller does not need to know the internal Codex thread id. The sidecar
resolves it from the active run metadata, the project run index, the user-level
pointer index, or compatible old audit artifacts.

Example:

```json
{
  "run_id": "<previous wrapper run id>",
  "prompt": "Continue from the previous audit and focus on test coverage.",
  "cwd": "<project-root>",
  "timeout_ms": 1800000
}
```

### `codex_wait`

Waits for an async `codex_run` or `codex_resume` turn.

By default, `codex_wait` soft-returns after 240 seconds if Codex is still
working. The Codex turn is not stopped. Claude should call `codex_wait` again
with the same `run_id`.

Example:

```json
{
  "run_id": "<run id from codex_run or codex_resume>",
  "timeout_ms": 1800000,
  "soft_timeout_ms": 240000
}
```

A soft timeout response looks like this:

```json
{
  "runId": "<same run id>",
  "status": "still_running",
  "threadId": "<codex thread id>",
  "timedOut": false,
  "waitElapsedMs": 240000,
  "nextWaitMs": 240000,
  "progress": {
    "events": 12,
    "totalTokens": 120,
    "outputTokens": 20,
    "reasoningTokens": 5
  }
}
```

## Codex App Server migration

App Server is the primary integration surface. The legacy `codex mcp-server`
command is deprecated by the Codex CLI; new Claude Code installations should
use the official Codex plugin when the sidecar's audit and human-inspection
workflow is not needed. This sidecar intentionally keeps its own `codex_*`
tools and asynchronous run handles, so it is not a drop-in replacement for the
legacy `codex`/`codex-reply` MCP tools. Any compatibility adapter is optional.

The wire contract is tracked against the upstream [Codex App Server
README](https://github.com/openai/codex/blob/main/codex-rs/app-server/README.md)
and the upstream [Codex MCP interface](https://github.com/openai/codex/blob/main/codex-rs/docs/codex_mcp_interface.md).

To opt in to the versioned adapter, set `CODEX_MCP_SIDECAR_COMPAT=1` before
starting the MCP server. It exposes the official `codex` and `codex-reply`
names and returns both `structuredContent` (`threadId` plus string `content`)
and legacy text content blocks. Official field spelling is preserved, including
`approval-policy`, `base-instructions`, `developer-instructions`, and
`compact-prompt`; `codex_home` is a sidecar-only extension for resolving a
thread across project homes. The adapter intentionally accepts only
`approval-policy: "never"`; interactive `on-request` calls are rejected rather
than silently downgraded.

Use absolute paths for `CODEX_HOME`, `cwd`, and `add_dirs` when overriding the
defaults. Tool arguments also accept `~` shorthand and the sidecar resolves it
before validation; MCP clients do not generally expand a literal `~` in an
environment value, so omitting `CODEX_HOME` or providing an absolute path is
the most portable setup.

Image inputs follow the current App Server contract: use an inline `data:` URL
or a local filesystem path. Remote `http://` and `https://` image URLs are
rejected before a turn starts.

Set `soft_timeout_ms` to `0` only when the caller deliberately wants one long
blocking wait.

### `codex_active_runs`

Lists currently active app-server turns from the audit directory. The response
is intentionally compact and does not include inspect commands or artifact
paths.

### `codex_runs`

Lists recent wrapper run handles without calling Codex.

Returned items are limited to:

```json
{
  "runId": "<wrapper run id>",
  "status": "completed",
  "title": "Readable title"
}
```

### `codex_wrapper_check`

Checks the local Codex binary, Codex home, run directory, pointer index, default
timeouts, and detected Codex CLI version.

## Human Inspection

The MCP tools do not return direct inspect commands. Humans inspect locally with
the terminal picker:

```bash
cd <project-root>
codex-runs
```

The picker shows two columns:

- `Running`: active background Codex turns.
- `Completed`: recent completed turns.

Use arrow keys to move, PageUp/PageDown to change pages, Enter to attach, and
`q`, Esc, or Ctrl-C to quit.

When a shared run is selected, the picker attaches using the underlying Codex
command:

```bash
codex resume --remote ws://127.0.0.1:45123 <threadId>
```

For a private stdio run, it uses `codex resume <threadId>` against the selected
workspace and persisted Codex home. A live private process is not exposed on a
network socket; the raw JSONL and active metadata remain available while it
runs.

After the attached Codex CLI exits, the picker returns to the main list instead
of closing.

Other useful modes:

```bash
codex-runs --all      # Read the user-level pointer index across projects.
codex-runs --json     # Print machine-readable session data.
codex-runs --no-watch # Disable automatic refresh.
```

## Audit Artifacts

For each run, the sidecar writes audit files under:

```text
<project-root>/.codex/codex-mcp/runs/
```

Typical files:

- `<runId>.jsonl`: raw Codex event stream.
- `<runId>.stderr.log`: Codex stderr.
- `<runId>.md`: human-readable audit summary.
- `<runId>.result.json`: atomic terminal result used for restart recovery.
- `index.jsonl`: append-only project run index.
- `active/<runId>.json`: active run metadata while Codex is still working.

The sidecar also writes a small pointer index under:

```text
$CODEX_HOME/mcp-wrapper/runs.index.jsonl
```

That pointer index is used to resolve `codex_resume(run_id)` across projects.

## Security Notes

The default policy is `sandbox=read-only` with `approval_policy=never` and no
interactive callbacks. Full access (`sandbox=danger-full-access` or an explicit
permission profile) must be selected by the caller and is recorded in the run
metadata. Interactive approval policies are rejected by this non-interactive
sidecar; use a callback-capable official integration when approvals are needed.

Recommended precautions:

- Run the sidecar only on a machine and workspace you trust.
- Do not expose the shared app-server WebSocket to untrusted networks.
- Treat `.codex/codex-mcp/runs/` as audit data that may contain prompts,
  command output, file paths, and model responses.
- Review audit artifacts before sharing them.

Pass `sandbox` explicitly when a broader or narrower Codex sandbox is needed.
`permissions` and `sandbox` are mutually exclusive; named profiles are checked
with App Server's `permissionProfile/list` method before a turn starts.
When `permissions` or `add_dirs` is supplied, the sidecar negotiates Codex's
experimental capability specifically for those implemented fields so absolute
runtime roots and profile identifiers can be sent; ordinary runs keep the
stable capability surface.

## Environment Variables

The environment variable names still use the historical
`CODEX_MCP_WRAPPER_*` prefix for compatibility.

- `CODEX_BIN`: Codex binary. Defaults to `codex`.
- `CODEX_MCP_WRAPPER_CODEX_HOME`: Codex home used by the sidecar. Defaults to
  `CODEX_HOME` or `~/.codex`.
- `CODEX_MCP_WRAPPER_RUN_DIR`: audit directory override. Defaults to
  `<cwd>/.codex/codex-mcp/runs`.
- `CODEX_MCP_WRAPPER_POINTER_DIR`: user-level pointer index directory. Defaults
  to `$CODEX_HOME/mcp-wrapper`.
- `CODEX_MCP_WRAPPER_TIMEOUT_MS`: default hard timeout. Defaults to 30 minutes.
- `CODEX_MCP_WRAPPER_WAIT_SOFT_TIMEOUT_MS`: default soft wait slice. Defaults
  to `240000`. Set to `0` to disable soft returns.
- `CODEX_MCP_WRAPPER_APP_SERVER_MODE`: `shared` or `private`. Defaults to
  `private` (stdio). Shared mode also accepts `unix://` (the Codex home control
  socket) or an absolute `unix://` endpoint; both use the Unix WebSocket
  Upgrade transport rather than raw JSONL.
- `CODEX_MCP_WRAPPER_APP_SERVER_URL`: shared app-server endpoint. Loopback
  `ws://`, authenticated non-loopback `wss://`, and Codex Unix control sockets
  (`unix://` or an absolute `unix://` path) are supported.
- `CODEX_MCP_WRAPPER_APP_SERVER_AUTH_TOKEN_ENV`: name of an environment
  variable holding an out-of-band bearer token for non-loopback `wss://`.
- `CODEX_MCP_WRAPPER_APP_SERVER_LISTEN`: compatibility alias for
  `CODEX_MCP_WRAPPER_APP_SERVER_URL`.
- `CODEX_MCP_WRAPPER_DEFAULT_PERMISSIONS`: default app-server permissions
  profile. Defaults to none (read-only sandbox is used).
- `CODEX_MCP_WRAPPER_SUPPORTED_CODEX_RANGE`: optional controlled-deployment
  override for the tested CLI semver interval. The default is
  `>=0.151.0 <0.152.0`; unknown or out-of-range versions fail closed.
- `CODEX_MCP_SIDECAR_COMPAT`: set to `1` to opt in to versioned `codex` and
  `codex-reply` compatibility tools; disabled by default.

## Development

Install dependencies:

```bash
npm ci
```

Run tests and syntax checks:

```bash
npm run ci
```

CI installs `@openai/codex@0.151.0` from npmjs.org on each Node 20/22/24
job so the initialize/thread-start/thread-resume and stable/experimental schema
contracts do not silently skip when Codex is absent.

Run the local configuration smoke check:

```bash
npm run check-config
```

`npm run check-config` depends on a local Codex CLI installation and is not part
of the GitHub Actions workflow.

## Repository Hygiene

The repository intentionally ignores local state:

- `node_modules/`
- `.codex/`
- `runs/`
- `.serena/`
- editor folders and environment files

Keep audit artifacts out of commits unless a sanitized fixture is deliberately
added for a test.

## License

UNLICENSED.