cc-as-subagent
# cc-as-subagent
An MCP server that exposes the local Claude Code CLI as a subagent, for Codex to
delegate review and implementation work to.
> ## Read this first
>
> **This server is a hole in your caller's policy, by construction.** Codex's
> sandbox and approval settings do not govern MCP servers, and they do not
> govern anything an MCP server goes on to spawn. Approving this server means
> approving a path that runs `claude` with a policy you chose here, not one
> Codex chose.
>
> **On native Windows there is no sandbox underneath it.** Claude Code's
> `sandbox.enabled` covers macOS, Linux and WSL2 only. What confines a run here
> is a fixed permission tier plus the CLI's fail-closed behaviour when nothing
> can answer a prompt — not an OS boundary. A confused model is blunted; a
> hostile workspace is not defeated. See [SECURITY.md](SECURITY.md).
>
> **Set `default_tools_approval_mode = "prompt"` in your Codex config** (see
> [examples/codex-config.toml](examples/codex-config.toml)). It is the only
> human checkpoint anywhere on this path, and it is off by default.
## What it does
Submits a task to `claude` in a workspace and returns a job handle immediately.
The run continues in the background; you poll for the result. Submitting never
blocks, because a real review takes minutes and Codex's default tool timeout is
60 seconds.
Two permission tiers, chosen by the server — the caller names a tier, never a
CLI flag:
| Tier | Tools | Permission mode | Can |
|---|---|---|---|
| `consult` | `Read`, `Grep`, `Glob` | `dontAsk` | read and search; nothing that needs approval |
| `execute` | `+ Edit`, `Write`, `Bash` | `acceptEdits` | edit files in the workspace, plus a small set of filesystem commands |
`execute` is narrower than it sounds. In a non-interactive run with nobody to
answer a prompt, **anything that would ask is denied** — so `npm test`,
`git commit`, `cargo build` and `docker` all fail. Only file edits and
`mkdir`/`touch`/`mv`/`cp`/`sed` inside the workspace proceed without asking.
## Requirements
- Node 18+ (developed on 24)
- Claude Code CLI on `PATH`, logged in
- **Claude Code 2.1.227 or later**; verified against 2.1.283.
`--restricted` (2.1.248+) is **opt-in and off by default**, via
`CC_AS_SUBAGENT_RESTRICTED=1`. It confines the built-in file tools to the
working directory and loads only managed settings plus `--settings` — but that
last part is also why it is off: it excludes user settings, so it breaks
authentication wherever credentials come from there, and a sanitized
environment has no fallback. Measured on 2.1.283: full environment works,
sanitized environment fails with `Not logged in`. Turn it on only if your
credentials come from the environment *and* you pass them through — see
[SECURITY.md](SECURITY.md).
## Install
```bash
npm install
npm run build
node scripts/selfcheck.mjs # verifies the security assumptions against your CLI
npm run probe # optional: MCP handshake smoke test
```
## Configure Codex
Add the server to `~/.codex/config.toml` — a working example is in
[examples/codex-config.toml](examples/codex-config.toml).
```toml
[mcp_servers.cc-as-subagent]
command = "node"
args = ["<abs path>/cc-as-subagent/dist/index.js"]
default_tools_approval_mode = "prompt"
[mcp_servers.cc-as-subagent.env]
CC_AS_SUBAGENT_WORKSPACE_ROOTS = "C:\\code\\project-a;C:\\code\\project-b"
```
`default_tools_approval_mode = "prompt"` makes Codex ask a human before each
call. Without it nothing on this path asks anyone.
## Tools
| Tool | Arguments | Returns |
|---|---|---|
| `claude_run` | `tier`, `prompt`, `workspace`, `model?`, `effort?`, `maxTurns?`, `maxBudgetUsd?`, `waitSeconds?` | `jobId`, `sessionId`, `status` |
| `claude_status` | `jobId`, `cursor?`, `waitSeconds?` | `status`, `events`, `result`, `permissionDenials`, `cursor` |
| `claude_reply` | `jobId`, `prompt`, `model?`, `effort?`, `waitSeconds?` | a new `jobId` on the same session |
| `claude_cancel` | `jobId` | `status` |
`claude_reply` takes a **job id, never a session id**. The session id stays
server-side, because `--resume` also accepts a path to a transcript file and a
caller-controlled value there would be a file-read primitive.
Poll with the `cursor` from the previous response to get only new events. If
the log was compacted while you were away, the response carries `cursorResetTo`
instead of silently skipping.
## Configuration
| Variable | Default | Purpose |
|---|---|---|
| `CC_AS_SUBAGENT_WORKSPACE_ROOTS` | the server's own cwd | Path-delimiter-separated allowlist. A workspace outside every root is refused. |
| `CC_AS_SUBAGENT_STATE_DIR` | `%LOCALAPPDATA%\cc-as-subagent` | Where job state lives. Deliberately outside any workspace. |
| `CC_AS_SUBAGENT_PASSTHROUGH` | *(none)* | Comma-separated variable names to pass to `claude`. Everything else is dropped. |
| `CC_AS_SUBAGENT_USE_API_KEY` | off | Set to `1` to pass `ANTHROPIC_API_KEY` through. Off by default so an inherited key cannot silently switch you from a subscription to API billing. |
| `CC_AS_SUBAGENT_RESTRICTED` | off | Set to `1` to add `--restricted` (2.1.248+). Stronger confinement, but it loads no user settings — pair it with `CC_AS_SUBAGENT_PASSTHROUGH` for whatever supplies your credentials. |
| `CLAUDE_BIN` | resolved from `PATH` | Pin an exact CLI build. |
## Verified behaviour
`npm test` — 53 unit tests. `node scripts/e2e.mjs` — 13 checks against a real
CLI, including that a resumed turn still carries the isolation stack, that
`consult` can read a workspace while being granted no write tool, and that the
`execute` session is granted **exactly** its six tools — the CLI adds `GetTask`
to a Bash-capable session uninvited, so it is denied by name.
`node scripts/selfcheck.mjs` re-runs the security assumptions against whatever
CLI is installed and writes a verdict to the state directory. Re-run it after a
CLI upgrade.
## Known limitations
Stated plainly rather than buried — see [SECURITY.md](SECURITY.md) for the full
list and the reasoning.
- **No OS sandbox on Windows.** The confinement is a permission tier plus
fail-closed denial.
- **Command deny rules are not a security boundary.** The CLI's own
documentation says so. They blunt a confused model; they do not stop a
deliberately crafted one.
- **Reads inside the workspace are unrestricted.** Credentials sitting in a
project directory are readable unless named in the deny list.
- **A workspace the user once trusted interactively carries its own
`permissions.allow`** into these runs.
- **`consult` sessions are transcript-persisted** unless you pass
`--no-session-persistence`, which would also make them unresumable.
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 5 tools
The tools are mostly distinct: ping is a liveness check, claude_run starts a job, claude_status polls it, claude_cancel stops it, and claude_reply continues a finished job. The only mild ambiguity is between claude_run and claude_reply, since both start work in a session, but the descriptions clarify that reply is specifically for follow-up turns on an existing session.
All tools use the claude_ prefix with a clear verb: cancel, ping, run, status, reply. This is a consistent and predictable pattern. The minor deviation is that ping is a noun/verb that doesn't fit the job lifecycle as directly as the others, but it is still clearly named.
Five tools is well-scoped for a subagent orchestration server: start, poll, cancel, reply, and health check. Each tool earns its place and there is no bloat.
The core lifecycle of running and managing a Claude Code session is covered: start, poll, cancel, and follow-up. A minor gap is the lack of a way to list active jobs or sessions, which could be useful for recovery, but agents can work around it by tracking jobIds.