Docs: plugin .mcp.json example uses mcp_servers but Codex expects mcpServers

Open 💬 4 comments Opened May 11, 2026 by heliusjing

Affected documentation URL

https://developers.openai.com/codex/plugins/build#bundled-mcp-servers-and-lifecycle-config

Problem

The plugin documentation says that mcpServers in .codex-plugin/plugin.json can point to an .mcp.json file, and then shows this wrapped .mcp.json format:

{
  "mcp_servers": {
    "docs": {
      "command": "docs-mcp",
      "args": ["--stdio"]
    }
  }
}

In practice, this wrapped format is not recognized by Codex for plugin MCP config in my testing. Changing the wrapper key to mcpServers makes the MCP server load correctly:

{
  "mcpServers": {
    "docs": {
      "command": "docs-mcp",
      "args": ["--stdio"]
    }
  }
}

A direct top-level server map also works:

{
  "docs": {
    "command": "docs-mcp",
    "args": ["--stdio"]
  }
}

Evidence from current code

The current plugin MCP parser appears to deserialize the wrapped plugin MCP file with camelCase JSON field names:

  • codex-rs/core-plugins/src/loader.rs
  • PluginMcpServersFile has #[serde(rename_all = "camelCase")]
  • the Rust field is named mcp_servers, which maps to JSON mcpServers, not JSON mcp_servers
  • codex-rs/core-plugins/src/loader_tests.rs
  • plugin_mcp_file_supports_mcp_servers_object_format parses JSON containing "mcpServers"
  • the flat map test parses a direct top-level server map

This also matches the rust-v0.123.0 release note for PR #18780, which says plugin MCP loading supports .mcp.json files containing an mcpServers object or top-level server maps:

https://github.com/openai/codex/pull/18780

Local version where reproduced

codex-cli 0.130.0

Suggested docs change

In the "Bundled MCP servers and lifecycle config" section, change the wrapped server map example from mcp_servers to mcpServers, or clarify that plugin .mcp.json accepts mcpServers and direct top-level server maps, while mcp_servers is the TOML/config naming used in ~/.codex/config.toml.

The corrected plugin .mcp.json wrapper example would be:

{
  "mcpServers": {
    "docs": {
      "command": "docs-mcp",
      "args": ["--stdio"]
    }
  }
}

Notes

I tried to locate the source for the developers.openai.com Codex plugin docs in this public repository but could not find the page source via GitHub code search or the repo tree, so I am filing this as a docs issue instead of opening a documentation PR.

View original on GitHub ↗

4 Comments

slegarraga · 3 months ago

I took a small pass at this because the loader already has a narrow parsing point for plugin .mcp.json files.

Patch branch/commit:

What it does:

  • Adds #[serde(alias = "mcp_servers")] to the wrapped MCP server config field.
  • Keeps existing mcpServers support and the flat top-level server map format unchanged.
  • Adds a regression test showing the docs-style snake_case wrapper now resolves to the actual server map instead of treating mcp_servers as a server name.

Validation:

  • RED: cargo test -p codex-core-plugins plugin_mcp_file_supports_snake_case_mcp_servers_object_format -- --nocapture failed before the alias with "mcp_servers" parsed as the server key.
  • GREEN: same targeted test passed after the alias.
  • cargo test -p codex-core-plugins — 196 passed.
  • just fmt — completed successfully.

I attempted to open this as an upstream PR from the fork, but GitHub returned a create-pull-request permission/404 for openai/codex, so I’m leaving the verified patch here for maintainers rather than creating noise elsewhere.

whitford-olmst · 3 months ago

@slegarraga Are you allowing mcp_servers as an alias? Why? Isn't this just a documentation bug saying mcp_servers instead of mcpServers? (I'm concerned because .mcp.json is meant to be a _standard_ between agents.)

slegarraga · 3 months ago

Thanks — that’s a fair concern. Yes, the patch allowed mcp_servers only as a parser alias on Codex’s wrapped config field, not as a new canonical spelling or emitted format; the intent was to avoid breaking users who copied the current docs while keeping mcpServers as the canonical .mcp.json shape.

That said, I agree the standardization point matters. If the preferred direction is strict canonical input, I’m happy to drop the alias approach and make this a docs-only fix to use mcpServers instead.

yui-stingray · 3 months ago

I re-checked the current published docs page through the OpenAI docs source, and the Bundled MCP servers and lifecycle hooks section still describes the wrapped .mcp.json form as mcp_servers and shows that key in the JSON example.

That still appears to conflict with the current loader/tests: the wrapped config struct uses camelCase serde naming, so the Rust field mcp_servers maps to JSON mcpServers; the tests cover the mcpServers wrapper and the direct top-level server map.

Given the concern above about keeping .mcp.json interoperable, I agree this is best treated as a docs-only correction: use mcpServers in the wrapped JSON example, and keep plugins.<plugin>.mcp_servers.<server> only for the TOML/plugin-scoped policy example where snake_case is expected.