Files
felixxia-oai db541f4553 [codex] Add managed MCP server matchers (#29648)
## Summary

This PR extends the existing managed `mcp_servers` identity requirement
so that one name-qualified rule can use either:

- the released exact command or URL identity;
- an exact stdio executable with an exact-length, ordered argument
matcher list; or
- a direct MCP URL matcher.

Matcher-based rules stay under the released `identity` key and use the
same `McpServerRequirement` abstraction and `mcp_servers.<server_name>`
namespace.

## Behavior

Policy activation and name qualification are unchanged:

- If `mcp_servers` is absent, ordinary configured MCP servers remain
unrestricted.
- If `mcp_servers` is present, a server needs a matching same-name
requirement.
- `mcp_servers = {}` continues to deny every configured MCP server.
- Existing exact identity requirements keep their released semantics.

Plugin-bundled MCP servers use the same requirement shapes under
`plugins.<plugin_name>.mcp_servers.<server_name>`. Top-level non-empty
rules continue to govern only ordinary configured servers; plugin rules
remain explicitly plugin-scoped. The existing globally empty
`mcp_servers = {}` plugin kill switch is preserved.

Requirements layers continue to use the existing regular TOML merge
behavior. Atomic replacement of named MCP requirements is intentionally
out of scope here and is tracked independently in #30118.

## Requirement contract

The released exact identity contract remains valid:

```toml
[mcp_servers.docs.identity]
command = "codex-mcp"

[mcp_servers.remote.identity]
url = "https://example.com/mcp"
```

Command identities continue to check only `command`; they do not inspect
arguments, `cwd`, `env`, or `env_vars`.

A command matcher uses an exact executable plus an exact-length, ordered
argument list. Each argument position supports `exact`, `prefix`, or
full-value `regex` matching:

```toml
[mcp_servers.internal_mcp_proxy.identity]
command = { executable = "company-cli", args = [
  { match = "exact", value = "mcp" },
  { match = "exact", value = "proxy" },
  { match = "exact", value = "--server" },
  { match = "regex", expression = '^https://[A-Za-z0-9-]+\.mcp\.internal\.example\.com(?::443)?(?:/.*)?$' },
] }
```

Direct streamable HTTP MCP definitions can use the same value matcher
types through `identity.url`:

```toml
[mcp_servers.internal_http.identity]
url = {
  match = "regex",
  expression = '^https://[A-Za-z0-9-]+\.mcp\.internal\.example\.com(?:/.*)?$',
}
```

Plugin-bundled MCP matchers use the same contract inside the
plugin-qualified allowlist:

```toml
[plugins."sample@test".mcp_servers.internal_mcp_proxy.identity]
command = { executable = "company-cli", args = [
  { match = "exact", value = "mcp" },
  { match = "exact", value = "proxy" },
] }
```

Regexes are validated while managed requirements are loaded, and regex
matching must cover the complete value. Command matchers constrain only
the executable and arguments.

## Why

Enterprise administrators need to allow MCP servers by executable and
positional-argument shape, including fixed arguments plus constrained
values such as internal MCP URLs passed to a proxy.

## Validation

- `just fmt`
- `git diff --check`
- `just test -p codex-config` (198 passed)
- `just test -p codex-core mcp_servers_by_matchers --lib` (2 passed)
2026-06-25 22:15:50 +01:00

74 lines
2.2 KiB
TOML

[package]
name = "codex-config"
version.workspace = true
edition.workspace = true
license.workspace = true
[[example]]
name = "generate-proto"
path = "examples/generate-proto.rs"
[lints]
workspace = true
[dependencies]
anyhow = { workspace = true }
base64 = { workspace = true }
codex-execpolicy = { workspace = true }
codex-features = { workspace = true }
codex-file-system = { workspace = true }
codex-git-utils = { workspace = true }
codex-model-provider-info = { workspace = true }
codex-network-proxy = { workspace = true }
codex-protocol = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-path = { workspace = true }
codex-utils-path-uri = { workspace = true }
dunce = { workspace = true }
futures = { workspace = true, features = ["alloc", "std"] }
gethostname = { workspace = true }
indexmap = { workspace = true, features = ["serde"] }
multimap = { workspace = true }
prost = "0.14.3"
regex-lite = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_ignored = { workspace = true }
serde_json = { workspace = true }
serde_path_to_error = { workspace = true }
sha2 = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["fs"] }
toml = { workspace = true, features = ["preserve_order"] }
toml_edit = { workspace = true }
tonic = { workspace = true }
tonic-prost = { workspace = true }
tracing = { workspace = true }
wildmatch = { workspace = true }
[target.'cfg(unix)'.dependencies]
dns-lookup = { workspace = true }
libc = { workspace = true }
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9"
[target.'cfg(target_os = "windows")'.dependencies]
winapi-util = { workspace = true }
windows-sys = { version = "0.52", features = [
"Win32_Foundation",
"Win32_System_Com",
"Win32_UI_Shell",
] }
[dev-dependencies]
pretty_assertions = { workspace = true }
tempfile = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tokio-stream = { workspace = true, features = ["net"] }
tonic = { workspace = true, features = ["router", "transport"] }
tonic-prost-build = { version = "=0.14.3", default-features = false, features = ["transport"] }
[lib]
doctest = false