Let me start with a correction, because getting this wrong wastes real hours: Codex CLI does not read an mcp.json file with read/write/deny permission arrays. A schema like that circulated widely in early Laravel tutorials — an earlier version of this very guide included one — and it is fiction. The real configuration lives in ~/.codex/config.toml, and the real permission model is three separate mechanisms: MCP server definitions, an approval policy, and a sandbox mode. I wire agent CLIs into Laravel projects daily, including this site's own repo where Claude Code and Codex run side by side, and the discipline below is the one that actually holds.

The three controls, and what each one governs
Codex CLI's safety model separates concerns that beginner guides mash together:
mcp_serversdefines which external tools the agent can reach at all — your Laravel context server, a browser, a database inspector.approval_policycontrols when Codex must stop and ask you before acting — approvals are the human gate.sandbox_modecontrols what Codex can technically do regardless of what it asks — the enforcement layer. The three modes areread-only,workspace-write, anddanger-full-access.
Approvals and sandboxing work independently, and that is the insight that makes the rest of this coherent: the sandbox is what protects you when the model does something you did not anticipate, and approvals are what keep you in the loop for the things it plans. You want both, tuned separately.
A working config.toml for a Laravel project
Here is the shape I run, in ~/.codex/config.toml:
approval_policy = "on-request"
sandbox_mode = "workspace-write"
[mcp_servers.laravel-boost]
command = "php"
args = ["artisan", "boost:mcp"]
Three things to know about this file:
The key is mcp_servers, snake_case. Codex silently ignores the camelCase mcpServers you may have copied from a Claude Desktop config — no error, no warning, the server just never appears. This is the single most common "my MCP isn't loading" cause when people migrate configs between tools.
The server above is real, not hypothetical. Laravel Boost ships an MCP server that exposes routes, schema, config, Tinker, and the last error to any MCP client — the same server my repo declares for Claude Code in .mcp.json, pointed at php artisan boost:mcp. One server definition, every agent CLI speaks to it. My Laravel Boost setup guide covers what it exposes and why it beats letting the model guess your schema.
workspace-write is the correct default for real work. read-only is where I start an agent on an unfamiliar codebase — it can analyze but not touch. workspace-write lets it edit files and run commands inside the project while the sandbox blocks writes outside it and network access unless you enable it. danger-full-access is named honestly; I have yet to meet the Laravel task that justifies it.
If MCP itself is new to you, the protocol basics — what servers and clients are, how transports work — are in my MCP setup guide for Mac; everything there transfers directly to Codex.
The .env problem nobody states honestly
Here is the uncomfortable truth most "safe AI setup" posts dodge: the sandbox does not hide your secrets. workspace-write means the workspace, and your .env is in the workspace. An agent that can read your project can read DB_PASSWORD. No config line changes that, and pretending a deny-list schema protects you — the fiction I opened with — is worse than knowing the real situation.
So the protection is operational, not configurational:
- Keep production credentials out of local
.envfiles entirely. The local file gets local database credentials, test API keys, nothing that matters if leaked. This is the same rule that predates AI agents; agents just raise the cost of breaking it. - Tell the agent the boundaries in
AGENTS.md. Codex reads anAGENTS.mdfile at the project root for standing instructions — the same file my repo already carries for Laravel conventions. Mine includes explicit lines: never read or print.envvalues, never editvendor/ornode_modules/, never run destructive artisan commands. Instructions are not enforcement, but they shape default behavior, and in practice the model respects them the overwhelming majority of the time. - Let the sandbox and approvals catch the rest. A command like
php artisan migrate:freshor anything reaching outside the workspace triggers the gate. That is whaton-requestapproval is for.
The layering matters: instructions guide, approvals interrupt, the sandbox enforces. Any one alone is a hole.
What to allow, learned from a live allowlist
The best evidence for what an agent actually needs on a Laravel project is the permission allowlist that accumulates on a real one. The one on this site's repo — built up over months of daily agent sessions — is dominated by exactly what you would predict: php artisan *, vendor/bin/pint, composer *, npm run build, git status/diff/log/add/commit, and read-only inspection commands like grep, find, and cat. What is conspicuously absent: rm, anything touching storage/, raw mysql writes, and unscoped ssh. When I do allow a remote command, it is pinned to the exact host and command string, not a wildcard.
Translate that into your Codex workflow as a policy: approve the narrow, repeated, reversible commands quickly; make anything destructive, networked, or novel go through the approval gate every time. The pattern applies across agent CLIs — the tools differ, the permission discipline does not, which is exactly what I found running Codex and Claude Code side by side on the same repo.
Two coexistence rules if you run both: give each tool its own branch or worktree (their diffs interleave badly on one branch), and add each tool's state directory to .gitignore. They can share the same MCP server definitions; they should not share uncommitted working state.
Verify the wiring — both directions
A setup you have not tested for success and refusal is assumed, not configured. Two tests, thirty seconds:
Success path: ask Codex to summarize a model's relationships or list the registered routes. If the answer comes back from your actual code — through the Boost server, not from generic Laravel knowledge — the MCP connection is live. Wrong-sounding routes mean it is guessing, which means the server never loaded (check the snake_case key first).
Refusal path: ask it to write a file outside the project directory, or run a command that should require approval. Confirm the sandbox blocks the first and the approval prompt appears for the second. If either sails through, your sandbox_mode or approval_policy is not what you think it is.
Then widen scope the way you would onboard a contractor: after demonstrated competence. Two weeks of clean reads and well-formed test generation earns broader write access; day-one full trust just skips the only feedback loop that catches configuration mistakes while they are still cheap. Keep a dated note of what you granted and why — six months from now, the scope you chose will otherwise be a mystery.
For a comparison of how the two big agent CLIs behave once configured — where Codex's approval flow feels tighter and where Claude Code's tooling wins — my head-to-head test of Codex and Claude Code has the receipts.
Wiring a team's Laravel repos for Codex or Claude Code — sandbox mode, approval policy, MCP keys in the file that is not committed — is a half-day job I am glad to do alongside you rather than for you. Send me your current config and we will walk both the success and the refusal path together.