Ability to disable built-in tools for MCP-only execution

Open 💬 3 comments Opened Oct 31, 2025 by provencher

Problem Statement

When running codex exec in headless/automated environments, there is currently no way to restrict the agent to only use MCP (Model Context Protocol) tools. This creates potential security and control concerns when you want to limit the agent's capabilities to a specific set of custom tools.

Proposed Solution

Add a configuration option (CLI flag or config setting) to disable Codex's built-in tools while keeping MCP tools available. This would enable:

  1. Minimum requirement: Ability to disable shell command execution
  2. Ideal solution: Ability to disable all built-in tools (shell, file operations, plan, apply_patch) for true MCP-only mode

Use Cases

  • Running codex exec in production/headless environments with restricted capabilities
  • Enforcing security policies that prevent arbitrary shell execution
  • Custom workflows that rely exclusively on carefully designed MCP tools
  • Better control over agent behavior in automated systems

Suggested Implementation

A --disable-builtin-tools flag (or similar) that:

  • Disables built-in tools (shell, file operations, plan, apply_patch)
  • Keeps MCP tools, web_search, and view_image available
  • Provides clear feedback when disabled tools are attempted

Related

This request is based on PR #5001 which was closed as community contributions are not currently being accepted. However, the functionality would be valuable for users who need greater control over agent capabilities in production environments.

Thank you for considering this feature request!

View original on GitHub ↗

3 Comments

realmehmetali · 1 month ago

Codex app-server native-tool allowlist contract

Problem

OpenClaw stateless research supplies exactly two dynamic tools: web_search and
web_fetch. Codex app-server 0.144.4 still registers the native update_plan
handler unconditionally in core/src/tools/spec_plan.rs, even when the thread
has no environments and every configurable native feature is disabled.

This means OpenClaw cannot enforce an exact two-tool model-visible surface with
the current app-server protocol. Prompt instructions and downstream rejection
are not sufficient because the unwanted tool is still advertised to the model.

Required protocol

Add a sticky app-server thread setting:

thread/start.nativeTools?: string[] | null
thread/fork.nativeTools?: string[] | null

Semantics:

  • omitted or null: preserve the existing default native-tool behavior;
  • []: expose and register no Codex-native tools;
  • a non-empty list: expose and register only the named Codex-native tools;
  • dynamicTools are independent and are never filtered by nativeTools;
  • hosted tools, MCP resource tools, extension/app/plugin tools, code-mode

wrappers, shell/patch/image tools, collaboration tools, tool search, and core
utilities such as update_plan are all native for this contract;

  • filtering applies before both the model-visible spec list and dispatch

registry are built, so a filtered tool cannot be invoked by name;

  • while an allowlist is active, future native tools fail closed unless their

canonical name is explicitly listed;

  • thread resume preserves the setting; fork inherits it unless explicitly

narrowed; turn settings such as cwd or environments cannot widen it;

  • managed configuration may narrow the allowlist but cannot widen a

caller-supplied restriction (intersection semantics).

App-server initialize must advertise a machine-readable capability such as:

{ "capabilities": { "nativeToolAllowlist": true } }

Clients must not infer support from a version string.

Canonical planner implementation

In codex-rs/core/src/tools/spec_plan.rs, distinguish Codex-native sources from
caller-provided dynamic_tools. Apply the native allowlist to every native
runtime and hosted spec before building ToolRegistry and model-visible specs,
then append the dynamic tools unchanged. Avoid a denylist and avoid special
casing update_plan; the allowlist must automatically cover future native
tools.

Required regression test

Add a planner/app-server integration test equivalent to:

#[test]
fn empty_native_tool_allowlist_leaves_only_dynamic_tools() {
    let plan = plan_tools(TestTurn {
        native_tools: Some(vec![]),
        dynamic_tools: vec![dynamic("web_search"), dynamic("web_fetch")],
        environment: Some(local_environment()),
        // Enable every normally available native surface to prove the
        // allowlist, rather than feature defaults, owns the boundary.
        all_native_features_enabled: true,
    });

    assert_eq!(plan.visible_names(), ["web_search", "web_fetch"]);
    assert_eq!(plan.registered_names(), ["web_search", "web_fetch"]);
    assert!(!plan.visible_names().contains(&"update_plan"));
    assert!(!plan.registered_names().contains(&"update_plan"));
}

Additional required cases:

  1. omitted allowlist preserves today's tool surface;
  2. a non-empty allowlist includes only named native tools plus all dynamic tools;
  3. cwd/environment changes cannot widen an empty sticky allowlist;
  4. resume and fork preserve or narrow the restriction;
  5. hosted search, MCP, extension, tool-search, and code-mode tools are filtered;
  6. duplicate native/dynamic names fail deterministically rather than silently

selecting the wrong handler.

OpenClaw integration after Codex ships the capability

  1. Require initialize.capabilities.nativeToolAllowlist === true for research.
  2. Send nativeTools: [] on thread/start.
  3. Continue sending environments: [] and the current feature/MCP restrictions

as defense in depth, not as the authoritative boundary.

  1. Assert the configured dynamic tool names are exactly web_search and

web_fetch.

  1. Fail research mode closed with a clear unsupported-runtime error on older

Codex versions.

  1. Bump the managed @openai/codex pin only after a stable npm release contains

the capability and the upstream regression tests.

Upstream issue path

Do not open a disconnected feature request. Extend the existing upstream issue
openai/codex#6049, “Ability to
disable built-in tools for MCP-only execution,” with the app-server protocol and
exact registered/visible-tool test above. Cross-reference
openai/codex#21952, which
documents the same class of app-server tool-capability mismatch for hosted image
generation and already links #6049.

The smallest correct upstream PR is the generic native allowlist. A dedicated
disable_plan flag would unblock this one caller but would regress whenever a
new unconditional native utility is added, so it is not an adequate long-term
contract.

brianynwu · 1 month ago

Adding a threat-model angle: fully tool-free mode for untrusted-content review (prompt-injection safety)

We hit this need from a different direction — using codex exec as an independent cross-vendor auditor (a plan/diff authored by one model is reviewed by Codex for independence). For that, the reviewer must run fully tool-free over content that may be attacker-influenced, because the audited material itself can carry prompt-injection.

The gap: --sandbox read-only gates tool effects but doesn't remove tools — the model still gets a shell and, per the still-open #12459 (which references #8396), read access in read-only mode isn't bounded to the workspace (a readable $HOME). So injected text in the reviewed content can reach shell execution or read credentials. We want the equivalent of Claude's claude -p --tools "": zero tools — no shell, no file ops, no MCP.

Two things worth linking to this "disable all built-in tools" request:

  • The mechanism appears to exist internally already: #31922 wires a tool_free empty tool-router (validated to send zero tools), but it's scoped to helper-thread hygiene and not surfaced as a CLI flag.
  • #5001 implemented --disable-builtin-tools for codex exec (closed for contribution-policy reasons, not on merit).

Ask: surface a fully tool-free mode on codex exec (empty tool-router; ideally assertable — the init handshake reporting zero tools) so Codex can be a safe headless reviewer of untrusted content. Happy to help test.

gprocunier · 1 month ago

Adding an independent app-server use case: controlled automated sessions sometimes need a non-empty, exact subset of Codex-native tools—not MCP-only and not fully tool-free.

I reproduced the current gap on codex-cli/app-server 0.145.0. The generated ThreadStartParams schema exposes additive dynamicTools, while neither thread/start nor turn/start provides a restrictive native-tool selector. InitializeResponse also exposes no machine-readable native-tool restriction capability or effective set. Current upstream main, checked at 84ccb2938bfeee481e9481244f073d27d3c672cf, retains that protocol shape.

Current main now includes #35054, which adds tools.update_plan.enabled and verifies that disabling it removes update_plan from both the model-visible and registered tool sets. That is useful progress, but a per-tool opt-out does not provide an exact non-empty subset or fail closed when future native tools are added.

The nativeTools contract proposed in the July 15 comment above would meet this use case if it filters both advertised specifications and registered handlers, keeps dynamicTools independent, advertises support plus the effective native set, and remains sticky across resume and fork without allowing a turn to widen it.

A concrete regression case is: request only exec_command, apply_patch, and write_stdin; prove every other native tool is absent from model-visible specifications and rejected by dispatch before side effects; then prove resume and fork preserve or narrow that exact set. Without a server-enforced allowlist, clients must either refuse the session or rely on weaker post-hoc detection, which cannot guarantee restriction before side effects.

Would maintainers confirm whether this generic allowlist fits the intended app-server architecture? If so, would you explicitly invite a focused external PR under the repository contribution policy? I can prepare the protocol/schema change, tool-planner and registry filtering, persistence behavior, and regression tests.