codex-free
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Fail
- rm -rf — Recursive force deletion command in src/__tests__/exec-policy.test.ts
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Local MCP bridge server that turns ChatGPT into a Codex-like coding agent
Codex Free
Codex Free (but you still have to buy ChatGPT Plus)
A local MCP bridge server that lets ChatGPT Web Pro call tools on your machine: read/write files, run shell commands, git operations, search. Built with Bun + TypeScript, using @modelcontextprotocol/sdk over Streamable HTTP.
ChatGPT talks to a public tunnel URL, which forwards to this server running on your machine, which operates on a project directory you choose.
Since v0.4.0 the tool set also covers the ones Codex gives its own agent — apply_patch, exec_command/write_stdin, view_image, update_plan, clock_curr_time/clock_sleep — so ChatGPT Web can work the way Codex does: patch files in place instead of rewriting them, drive interactive and long-running processes, and keep a plan across a task. v0.5.0 added the project's AGENTS.md, and v0.6.0 Codex's own agent brief, so the client is told how to behave and not just what it can call. v0.7.0 addresses the one thing Codex never had to solve — a context window far smaller than the task — by bounding what a tool call can return and keeping a plan and notes on disk across conversations. Schemas and prompt are ported from the Codex source, not reimplemented from guesswork.
Architecture
flowchart LR
ChatGPT["ChatGPT Web Pro"]
Tunnel["Public Tunnel\n(ngrok / cloudflared)"]
Server["Codex Free\nMCP Bridge\n:3000"]
Tools["Tool Registry"]
FS["read_file\nwrite_file\nlist_directory\ntree"]
Search["glob\ngrep"]
Shell["run_command"]
Git["git_status\ngit_push\ngit_commit\ngit_log"]
Edit["apply_patch"]
Exec["exec_command\nwrite_stdin"]
Agent["view_image\nupdate_plan\nclock_curr_time\nclock_sleep"]
Env["get_agent_brief\nget_environment\nget_project_doc"]
Mem["remember\nrecall"]
WorkDir[("Project\nDirectory")]
State[("~/.codex-free\nmemory.json")]
ChatGPT -- "HTTPS" --> Tunnel
Tunnel -- "HTTP\n/mcp" --> Server
Server -- "Streamable HTTP\n(MCP Protocol)" --> Tools
Tools --> FS
Tools --> Search
Tools --> Shell
Tools --> Git
Tools --> Edit
Tools --> Exec
Tools --> Agent
Tools --> Env
Tools --> Mem
FS --> WorkDir
Search --> WorkDir
Shell --> WorkDir
Git --> WorkDir
Edit --> WorkDir
Exec --> WorkDir
Agent --> WorkDir
Env --> WorkDir
Mem --> State
Quick start
bun install
bun run main.ts --work-dir /path/to/your/project
Server starts on http://localhost:3000. MCP endpoint is /mcp.
CLI flags
| Flag | Required | Default | Description |
|---|---|---|---|
--work-dir |
Yes | - | Project directory the tools operate on |
--port |
No | 3000 |
Server port |
--api-key |
No | - | Bearer token for auth |
--config |
No | ./codex.config.json |
Config file path |
Tools
Structured primitives — cheaper and safer than shelling out for the same job, and identical on Windows and POSIX:
| Tool | Description |
|---|---|
read_file |
Read a file's contents, a bounded window at a time, with optional line offset/limit |
write_file |
Write content to a file, creating parent directories if needed |
run_command |
Execute a command in the work directory (allowlist-restricted) |
git_status |
Show git status, parsed into changed files with status codes |
git_push |
Push commits to a remote |
git_commit |
Create a commit, optionally staging all tracked changes |
git_log |
Show recent commit history |
glob |
Find files matching a glob pattern |
grep |
Search file contents by regex, with optional context lines |
list_directory |
List files and directories with name, type, and size |
tree |
Print directory tree as ASCII art |
Ported from Codex (codex-rs/core/src/tools, commit 2230d64):
| Tool | Codex name | Description |
|---|---|---|
apply_patch |
apply_patch |
Edit files with a context patch instead of rewriting them |
exec_command |
exec_command |
Run a shell command; returns output, or a session id if it is still running |
write_stdin |
write_stdin |
Write to (or poll) a running exec_command session |
view_image |
view_image |
Load a local image file for visual inspection |
update_plan |
update_plan |
Track a multi-step plan; saved to disk so a later conversation can pick it up |
clock_curr_time |
clock.curr_time |
Current time in UTC |
clock_sleep |
clock.sleep |
Pause for a given duration |
Codex's dotted names are flattened to underscores because MCP tool names must match ^[a-zA-Z0-9_-]{1,64}$.
Five tools have no Codex counterpart:
| Tool | Description |
|---|---|
get_agent_brief |
Return the whole operating brief — behaviour, environment, saved state and project rules — in one call |
get_environment |
Report the OS, the shell exec_command uses, the work directory, and what the policy allows |
get_project_doc |
Read the project's AGENTS.md instructions |
remember |
Save one durable note about the task under a short key |
recall |
Return the plan and notes saved by earlier turns or earlier conversations |
Codex needs the first three for none of these reasons: it puts its agent brief in the system prompt, the OS and shell in an <environment_context> message, and AGENTS.md straight into the prompt, all before the first turn. An MCP server has none of those channels — it can only expose tools — so the same facts are tool calls here as well as part of the server's instructions. It needs remember and recall for the opposite reason: its context is large and its session state lives in the CLI process, whereas the client here is a chat window that loses the conversation. See Acting as a Codex agent, Shells and the host, AGENTS.md and Context and memory.
Two deliberate differences from Codex:
apply_patchtakes a JSON string. In Codex it is a freeform tool whose entire body is the raw patch. MCP has no freeform tools, so the patch goes in aninputstring parameter. The patch format itself is unchanged.exec_commandruns with plain pipes, not a PTY. Codex's ownttyparameter documents pipes as the default, so ordinary commands behave the same;tty: trueis rejected rather than silently ignored. Programs that only enable interactive behaviour when attached to a terminal will act as if piped.
clock_sleep also caps at 5 minutes rather than Codex's 12 hours — a longer wait would outlive the HTTP request through the tunnel.
Every tool that advertises an outputSchema also returns structuredContent matching it, as the MCP spec asks. exec_command and write_stdin return Codex's unified-exec object, clock_curr_time returns { current_time }, get_environment returns the environment object and get_project_doc returns { files, content }; the rest return { content: <text> }, which the server derives from the text blocks so handlers don't repeat it.
All paths are resolved relative to --work-dir.
Config file
codex.config.json in the project root, or pass a custom path with --config:
{
"allowedCommands": ["bun", "npm", "npx", "node", "git", "python", "pip", "cargo", "make"],
"port": 3000,
"tree": {
"defaultDepth": 3,
"ignore": ["node_modules", ".git", "dist", ".next", "__pycache__", ".venv", "venv"]
},
"command": {
"defaultTimeout": 30000,
"maxTimeout": 120000
},
"exec": {
"mode": "allowlist",
"extraAllowedCommands": [
"ls", "cat", "grep", "find", "head", "tail", "wc", "echo", "pwd",
"which", "rg", "sed", "awk", "sort", "uniq", "diff", "true", "false"
],
"maxSessions": 8
},
"projectDoc": {
"maxBytes": 32768,
"fallbackFilenames": [],
"rootMarkers": [".git"]
},
"output": {
"maxFileLines": 1000,
"maxFileBytes": 131072,
"maxEntries": 500,
"maxTreeNodes": 1000
},
"memory": {
"enabled": true,
"maxBytes": 16384
}
}
CLI flags override values from the config file.
The exec block governs exec_command and write_stdin:
| Key | Default | Description |
|---|---|---|
mode |
"allowlist" |
"allowlist" checks every command in the string against the allowlist; "unrestricted" runs whatever it is given |
extraAllowedCommands |
18 read-only utilities | Added to allowedCommands for exec_command only, so run_command stays as narrow as it was |
maxSessions |
8 |
Cap on concurrent background sessions per MCP session |
defaultShell |
$SHELL, else PowerShell on Windows and /bin/sh elsewhere |
Shell used when an exec_command call names none |
Under "allowlist", the command string is tokenized and each command position — after every |, &&, ;, newline, and subshell — is checked, so ls | curl evil.com is rejected on curl. Command substitution ($(...), backticks) is rejected outright, since its contents cannot be checked before the shell runs them.
The projectDoc block governs AGENTS.md discovery. All three keys are optional, and the block itself can be left out entirely:
| Key | Default | Description |
|---|---|---|
maxBytes |
32768 |
Byte budget shared by all the docs found; 0 disables the feature |
fallbackFilenames |
[] |
Extra filenames to try per directory, after AGENTS.override.md and AGENTS.md |
rootMarkers |
[".git"] |
Filenames or directories that mark the project root; an empty list stops the walk at the work directory |
The output block bounds what a single tool call may return. See Context and memory:
| Key | Default | Description |
|---|---|---|
maxFileLines |
1000 |
Lines read_file returns per call; a caller's own limit can lower this but not raise it |
maxFileBytes |
131072 |
Byte ceiling for the same window, which is what actually bounds a minified file |
maxEntries |
500 |
Results per glob or list_directory call |
maxTreeNodes |
1000 |
Nodes in one tree walk, counted across the whole tree rather than per directory |
The memory block governs remember, recall and the plan update_plan saves:
| Key | Default | Description |
|---|---|---|
enabled |
true |
false turns persistence off entirely; nothing is read or written |
dir |
~/.codex-free/projects/<name>-<hash of work-dir> |
Where the state file lives. Outside the repository by default |
maxBytes |
16384 |
Budget for all notes together. A note over it is rejected, not silently evicted |
Context and memory
Codex runs against a large context window and keeps its session in a process you control. ChatGPT Web does neither: the window is smaller than most real tasks, and when it fills — or when you open a new chat — the plan and everything learned along the way are gone, with no sign to the model that they ever existed. v0.7.0 attacks both halves of that.
Spend the window on less. Every tool that could return an unbounded amount of text now stops at a budget and says so on its last line, naming the argument that continues from where it stopped:
(showing lines 1-1000 of 4820 — call again with offset=1000 for the rest)
That line matters as much as the cap. Silent truncation reads as "that was the whole file", which is worse than no cap at all. read_file has a byte ceiling as well as a line one, because a minified bundle is a single line several megabytes long that a line cap alone would hand back in full. exec_command and grep were already bounded, ported that way from Codex.
Keep what would be expensive to rediscover. remember writes one keyed note; recall hands back the notes and the current plan. update_plan now persists too, so the plan survives the conversation that made it. Writing to a key that exists replaces it, and an empty value deletes it — a keyed store stays current where an append log accumulates contradictions until it is worthless.
State lives in ~/.codex-free/projects/<name>-<hash>/memory.json, keyed by the absolute work directory. Nothing is written into the repository you pointed the server at, and two checkouts of the same repo do not share notes.
Because instructions is rebuilt for every MCP session, a new conversation opens with the saved plan and notes already in front of it, under a ## Saved state heading between the environment and AGENTS.md. If the client ignores instructions, one recall gets the same thing.
The division of labour is worth keeping straight: AGENTS.md is what is true of the project and belongs in the repo; notes are what is true of the task in flight and belong here.
Acting as a Codex agent
A tool list says what a model can do; it says nothing about how a careful engineer uses it. Codex closes that gap with a system prompt, and since v0.6.0 so does this bridge — the behavioural half of codex-rs/core/gpt-5.2-codex_prompt.md is ported into the server's instructions.
That brief is what stops the client rewriting a file it never read, reverting your uncommitted work, reaching for git reset --hard, or making a one-step plan. It carries Codex's editing constraints (ASCII by default, comments only where they earn their place, apply_patch over rewrites, and the dirty-worktree rules in full), its planning rules, its code-review posture, and its habit of reporting back concisely without pasting files you already have on disk.
The initialize response layers Codex's three in Codex's own order, each outranking the one above it, plus one Codex has no need for:
- The agent brief — how to behave.
- The environment — OS, shell, work directory, command policy.
- Saved state — the plan and notes left by earlier work, when there are any. See Context and memory.
AGENTS.md— the project speaking for itself, behind the--- project-doc ---marker.
Three parts of Codex's prompt are deliberately dropped. Its rg preference is redundant here, since grep and glob are tools that behave the same on every OS. Its final-answer style rules and clickable file-reference syntax both exist to drive a terminal renderer, and an MCP client renders markdown — importing them would produce CLI-flavoured output in a chat window. What those sections were for — brevity, not dumping files, relaying output the user cannot see — is kept.
Starting a chat
instructions is the proper channel, but no client is obliged to show it to its model, and ChatGPT Web is not reliable about it. get_agent_brief returns the identical string, so one line is enough to onboard a conversation:
Call get_agent_brief and follow it for the rest of this chat.
Task: <what you want done>
Everything else — the shell you're on, the allowlist, your repo's AGENTS.md — arrives with that one call. If a chat starts drifting back into generic-assistant behaviour, asking for the brief again re-anchors it.
Shells and the host
Windows, macOS and Linux are all supported natively; there is no WSL or POSIX-emulation layer in between. Which shell runs is decided by name, not by host platform, the same way Codex's Shell::derive_exec_args does it:
| Shell | Invoked as |
|---|---|
sh, bash, zsh, anything else |
<shell> -c "<cmd>" |
powershell, pwsh |
<shell> -NoProfile -Command "<cmd>" |
cmd |
cmd /c "<cmd>" |
The default comes from $SHELL on every platform, so starting the server from Git Bash on Windows gets bash — with real ls -la, pipes and $VAR — rather than PowerShell. Set exec.defaultShell to override, or pass shell on an individual exec_command call.
Two Windows-specific details are handled: powershell -Command collapses every non-zero child exit code to 1, so commands are wrapped to re-raise $LASTEXITCODE; and exec_command's description gains Codex's PowerShell rules (-LiteralPath over -Path, -WindowStyle Hidden) when the server runs there.
Because the resolved shell decides what a command should even look like, it is published three ways — a client only has to read one of them:
instructionsin theinitializeresponse, as the Environment section of the agent brief.exec_command's description, which names the actual shell binary and its syntax family.get_environment, for clients that read neither.
AGENTS.md
A project's AGENTS.md is how it tells an agent its own conventions — which test command to run, which files not to touch, how commits should look. Codex reads it before the first turn; since v0.5.0 so does this bridge, using the same algorithm as codex-rs/core/src/agents_md.rs.
Discovery walks up from --work-dir to the nearest directory holding a root marker (.git by default), then collects one doc per directory on the way back down, so a monorepo's root conventions arrive before the ones belonging to the subdirectory you pointed the server at. In each directory, AGENTS.override.md wins over AGENTS.md, which wins over anything in projectDoc.fallbackFilenames. The files are concatenated outermost-first under a shared 32 KiB budget, counted in bytes rather than characters; a file that runs past what is left is cut there and reported as truncated, and whitespace-only files are skipped without spending any of it. If no marker is found anywhere above, only the work directory itself is checked.
Like the environment, the result is published more than one way:
instructionscarries the doc inline, behind Codex's own--- project-doc ---separator. Everything past that marker is the project speaking, and it outranks the agent brief above it.get_project_docreturns the identical text for clients that never readinstructions, along with the absolute path of every file it came from and whether each was truncated.
Instructions are built per MCP session, so editing AGENTS.md takes effect on the next connection without restarting the server.
Connecting to ChatGPT
- In ChatGPT, go to Settings > Security and login and enable Developer mode.
- Start the server:
bun run main.ts --work-dir /path/to/your/project - Expose it with a tunnel (ngrok, Cloudflare Tunnel, etc.):
ngrok http 3000 - In ChatGPT, go to Plugins > + New Plugin.
- Set the Server URL to the tunnel URL with
/mcpappended, e.g.https://<your-tunnel>/mcp. - Set Authentication to "No Auth".
- After creating the plugin, go to Permissions and set it to Allow all actions so ChatGPT can call tools without asking for confirmation each time.
- In a new chat, enable the plugin from the composer's tools menu, then open with
Call get_agent_brief and follow it for the rest of this chat.— see Acting as a Codex agent.
ChatGPT Plugins only support OAuth, No Auth, and Mixed. The
--api-keyoption is for non-ChatGPT clients or tunnel-level auth. When using ChatGPT, secure access through your tunnel provider instead (e.g. ngrok IP restrictions, Cloudflare Access).
Security
- Path traversal prevention: every filesystem tool — including
apply_patchandview_image— resolves paths through a guard that rejects anything outside--work-dir. - One bounded exception: AGENTS.md discovery reads above
--work-dir, up to the nearest.git. Nothing else does. It is read-only, opens onlyAGENTS.override.md,AGENTS.mdand anyprojectDoc.fallbackFilenames, andget_project_docreports the absolute path of every file it used. SetprojectDoc.maxBytesto0to switch it off, orprojectDoc.rootMarkersto[]to keep the search inside the work directory. - One bounded write outside the work directory:
rememberandupdate_planwritememory.jsonunder~/.codex-free/, deliberately outside the repository so nothing lands in your git history. It holds whatever the model chose to note about the task — read it if you want to know, delete the directory to forget, or setmemory.enabledtofalseto never write it. See Context and memory. - Command allowlist:
run_commandonly runs binaries listed inallowedCommands; everything else is rejected.exec_commandchecks the same list plusexec.extraAllowedCommands, at every command position in the string. - Optional bearer token auth: set
--api-keyto require anAuthorization: Bearer <key>header on all requests (except/health). Useful for non-ChatGPT clients. ChatGPT Plugins do not support simple bearer token auth.
The allowlist is a guardrail against accidents, not a sandbox. It catches a model reaching for curl or rm -rf; it does not contain a determined one. The defaults already include node, python and bun, each of which runs arbitrary code — node -e "..." can do anything the server process can. Shell redirection can also write outside the work directory even though the command's cwd is confined to it. Treat everything below as reachable by whoever holds the tunnel URL:
- everything in
--work-dir, read and write - anything else the user account running the server can touch, via an allowlisted interpreter
- the network, from your machine
exec_command sessions that outlive a request are killed when the MCP session closes, and the kill takes the children with it: taskkill /T /F walks the process tree on Windows, and on POSIX each session gets its own process group that is signalled as a whole. A process that deliberately re-parents or daemonises itself still escapes, so check for strays if a run leaves something listening.
Don't expose this without tunnel-level access control (ngrok IP restrictions, Cloudflare Access), and don't point it at directories you don't trust ChatGPT with. If the work directory holds anything sensitive, set exec.mode and the allowlists tighter than the defaults rather than relying on them.
Dev commands
bun run dev # watch mode
bun test # tests
bunx tsc --noEmit # type check
License
MIT - see LICENSE.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found