Skip to content

Conversation

@gt-oai
Copy link
Contributor

@gt-oai gt-oai commented Jan 12, 2026

Enterprises want to restrict the MCP servers their users can use.

Admins can now specify an allowlist of MCPs in requirements.toml. The MCP servers are matched on both Name and Transport (local path or HTTP URL) -- both must match to allow the MCP server. This prevents circumventing the allowlist by renaming MCP servers in user config. (It is still possible to replace the local path e.g. rewrite say /usr/local/github-mcp with a nefarious MCP. We could allow hash pinning in the future, but that would break updates. I also think this represents a broader, out-of-scope problem.)

We introduce a new field to Constrained: "normalizer". In general, it is a fn(T) -> T and applies when Constrained<T>.set() is called. In this particular case, it disables MCP servers which do not match the allowlist. An alternative solution would remove this and instead throw a ConstraintError. That would stop Codex launching if any MCP server was configured which didn't match. I think this is bad.

We currently reuse the enabled flag on MCP servers to disable them, but don't propagate any information about why they are disabled. I'd like to add that in a follow up PR, possibly by switching out enabled with an enum.

In action:

# MCP server config has two MCPs. We are going to allowlist one of them.
➜  codex git:(gt/restrict-mcps) ✗ cat ~/.codex/config.toml | grep mcp_servers -A1
[mcp_servers.hello_world]
command = "hello-world-mcp"
--
[mcp_servers.docs]
command = "docs-mcp"

# Restrict the MCPs to the hello_world MCP.
➜  codex git:(gt/restrict-mcps) ✗ defaults read com.openai.codex requirements_toml_base64 | base64 -d
[mcp_server_allowlist.hello_world]
command = "hello-world-mcp"

# List the MCPs, observe hello_world is enabled and docs is disabled.
➜  codex git:(gt/restrict-mcps) ✗ just codex mcp list
cargo run --bin codex -- "$@"
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/codex mcp list`
Name         Command          Args  Env  Cwd  Status    Auth
docs         docs-mcp         -     -    -    disabled  Unsupported
hello_world  hello-world-mcp  -     -    -    enabled   Unsupported

# Remove the restrictions.
➜  codex git:(gt/restrict-mcps) ✗ defaults delete com.openai.codex requirements_toml_base64

# Observe both MCPs are enabled.
➜  codex git:(gt/restrict-mcps) ✗ just codex mcp list
cargo run --bin codex -- "$@"
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/codex mcp list`
Name         Command          Args  Env  Cwd  Status   Auth
docs         docs-mcp         -     -    -    enabled  Unsupported
hello_world  hello-world-mcp  -     -    -    enabled  Unsupported

# A new requirements that updates the command to one that does not match.
➜  codex git:(gt/restrict-mcps) ✗ cat ~/requirements.toml
[mcp_server_allowlist.hello_world]
command = "hello-world-mcp-v2"

# Use those requirements.
➜  codex git:(gt/restrict-mcps) ✗ defaults write com.openai.codex requirements_toml_base64 "$(base64 -i /Users/gt/requirements.toml)"

# Observe both MCPs are disabled.
➜  codex git:(gt/restrict-mcps) ✗ just codex mcp list
cargo run --bin codex -- "$@"
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.75s
     Running `target/debug/codex mcp list`
Name         Command          Args  Env  Cwd  Status    Auth
docs         docs-mcp         -     -    -    disabled  Unsupported
hello_world  hello-world-mcp  -     -    -    disabled  Unsupported

@gt-oai gt-oai marked this pull request as ready for review January 12, 2026 18:21
Copy link
Collaborator

@bolinfest bolinfest left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, so if you declare one MCP server in requirements.toml, that means an end user can't use any MCP servers of their own, correct?

Do we need a way to say, "If you use MCP server xyz, then this is how you must configure it?" I realize that's arguably what should go in /etc/config/codex.toml, so maybe that doesn't really make sense either.

I'm just having trouble envisioning an enterprise enumerating all the accepted MCP servers, though I guess we do it with Chrome extensions?

@gt-oai gt-oai force-pushed the gt/restrict-mcps branch 2 times, most recently from dace824 to 92c8b1d Compare January 13, 2026 16:27
Copy link
Collaborator

@bolinfest bolinfest left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe this would prevent the user from running codex mcp add, though I suppose it's fine for them to add anything they want to their config.toml: it will just blow up at runtime if they declare an out-of-bounds MCP server in there?

fn constrain_mcp_servers(
mcp_servers: HashMap<String, McpServerConfig>,
mcp_requirements: Option<&BTreeMap<String, McpServerRequirement>>,
) -> ConstraintResult<Constrained<HashMap<String, McpServerConfig>>> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this return Constrained::allow_any() in some cases, though?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constrained::normalized is effectively allow_any when the requirements are empty. But I've added a shortcircuit for that case now.

@gt-oai gt-oai force-pushed the gt/restrict-mcps branch 3 times, most recently from 9ec3702 to 14fd9f4 Compare January 13, 2026 17:27
@gt-oai gt-oai merged commit 2651980 into main Jan 13, 2026
69 of 74 checks passed
@gt-oai gt-oai deleted the gt/restrict-mcps branch January 13, 2026 19:45
@github-actions github-actions bot locked and limited conversation to collaborators Jan 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants