Summary
isStructuredContentEnabled() in dist/tools/handlers.js defaults to false, omitting structuredContent from all tool responses unless the user explicitly sets STRUCTURED_CONTENT_ENABLED=true. Newer Claude Code MCP clients reject tool responses with -32600 "Tool codex has an output schema but did not return structured content" because every codex-mcp-server tool (codex, review, websearch) declares an outputSchema in dist/tools/definitions.js, and the spec requires structuredContent to be populated when outputSchema is present.
The defensive default was correct for an older Claude Code version that had a bug with structuredContent — but that bug has since been fixed and the behaviour has inverted. The defensive default is now the cause of failures, not a workaround for them.
Environment
codex-mcp-server version: 1.4.10
- Claude Code version: 2.1.92 (client that enforces the structuredContent requirement)
- Node: v24.14.0
- OS: Windows 10 (reproduced), Linux/macOS likely affected identically
- OpenAI Codex CLI: v0.118.0 (logged in via
sk-proj-* API key, codex login status returns "Logged in using an API key")
Steps to reproduce
- Install
codex-mcp-server@1.4.10 as a Claude Code MCP:
{
"mcpServers": {
"openai": {
"command": "npx",
"args": ["-y", "codex-mcp-server"],
"env": { "OPENAI_API_KEY": "sk-proj-..." }
}
}
}
- Ensure
codex login status returns "Logged in" in a terminal (so the CLI layer works).
- From Claude Code, invoke the
codex tool with any prompt:
mcp__openai__codex(prompt="say hi", model="gpt-5.2-codex", reasoningEffort="low", sandbox="read-only")
- Observe the MCP client returns:
Error: MCP error -32600: Tool codex has an output schema but did not return structured content
- The same call succeeds if the environment variable
STRUCTURED_CONTENT_ENABLED=true is set when spawning the server.
Expected behaviour
A default npx -y codex-mcp-server install should work with current Claude Code out of the box. No extra environment variables should be needed.
Actual behaviour
Every tool call returns -32600 until the user figures out that they need to set STRUCTURED_CONTENT_ENABLED=true. This is a frustrating discovery experience because:
- The error message suggests a schema violation, not a missing feature flag
- The env var isn't mentioned in the README
- The flag name suggests it's an experimental feature, not the correct default
Root cause
In dist/tools/handlers.js:
const isStructuredContentEnabled = () => {
const raw = process.env.STRUCTURED_CONTENT_ENABLED;
if (!raw) return false; // <-- defensive default, now the cause of failures
return ["1", "true", "yes", "on"].includes(raw.toLowerCase());
};
And in the CodexToolHandler.execute() return block:
return {
content: [
{
type: "text",
text: response,
_meta: metadata,
},
],
structuredContent:
isStructuredContentEnabled() && Object.keys(metadata).length > 0
? metadata
: undefined,
};
Three problems stacked:
- Default is off.
isStructuredContentEnabled() returns false unless explicitly enabled.
- Extra guard in codex handler. Even when enabled,
Object.keys(metadata).length > 0 rejects empty metadata. This is an additional guard not present in the review or websearch handlers.
- Response text is not in
structuredContent. When a client displays structuredContent instead of content[0].text, the user sees only the metadata object ({model, threadId, ...}) without the actual CLI response. This means even after flipping the default, clients that prefer structuredContent see no codex output.
The comments in handlers.js reference the old bug:
// - content[0]._meta: For Claude Code compatibility (avoids structuredContent bug)
// - structuredContent: For other MCP clients that properly support it
This was accurate for older Claude Code but is now inverted. Newer Claude Code requires structuredContent (the outputSchema declaration in definitions.js makes it mandatory per the MCP spec).
Proposed fix
Three changes to src/tools/handlers.ts:
Change 1 — Flip the default to opt-out instead of opt-in:
const isStructuredContentEnabled = () => {
const raw = process.env.STRUCTURED_CONTENT_ENABLED;
if (raw === undefined || raw === null || raw === "") return true; // default ON
return ["1", "true", "yes", "on"].includes(String(raw).toLowerCase());
};
Users who need the old behaviour can still opt out with STRUCTURED_CONTENT_ENABLED=false.
Change 2 — Remove the metadata.length > 0 guard in the codex handler (keeps parity with review/websearch handlers which already use the simpler pattern):
// In CodexToolHandler.execute() — return block
return {
content: [{ type: "text", text: response, _meta: metadata }],
structuredContent: isStructuredContentEnabled()
? structuredResponse
: undefined,
};
Change 3 — Include the CLI response text in structuredContent for all three handlers:
For codex:
const structuredResponse = { ...metadata, response };
For review:
const structuredResponse = { ...metadata, response };
For websearch:
const structuredResponse = { ...metadata, response };
Why this works: the outputSchema in definitions.js is permissive — it has no additionalProperties: false and no required fields, so adding a response string field is schema-compatible. Clients that prefer content[0].text get it there; clients that prefer structuredContent get the response inside the structured object. Both client behaviours work without extra config.
Tested locally
All three changes have been applied to the installed dist/tools/handlers.js and verified working end-to-end against Claude Code 2.1.92. mcp__openai__codex({prompt: "Reply with TEST-OK", model: "gpt-5.2-codex", reasoningEffort: "low", sandbox: "read-only"}) now returns:
{
"model": "gpt-5.2-codex",
"response": "TEST-OK\n"
}
No more -32600 errors. websearch similarly returns the full search response inside structuredContent.
Additional compatibility note
A separate issue worth flagging: when reasoningEffort='minimal' is passed to the codex tool, the underlying codex CLI auto-injects a web_search tool into the request, which OpenAI's Responses API then rejects with:
400 invalid_request_error: The following tools cannot be used with reasoning.effort 'minimal': web_search
This might be worth documenting in the codex-mcp-server README (noting that reasoningEffort floor is 'low') or guarding against in the tool schema (reject 'minimal' in the Zod validator before passing to the CLI). I can file this as a separate issue if it's in scope for this repo rather than codex CLI.
Broader observation
The comment pattern // Dual approach: For Claude Code compatibility (avoids structuredContent bug) suggests the codebase was written to work around a specific Claude Code regression. That regression has been fixed upstream, and the workaround is now the new regression. When upstream bugs get fixed, the defensive workarounds often need to be retired. This might be worth a one-time README note: "If you see -32600 errors on newer Claude Code, set STRUCTURED_CONTENT_ENABLED=true or update to the next release which defaults it on."
Summary
isStructuredContentEnabled()indist/tools/handlers.jsdefaults tofalse, omittingstructuredContentfrom all tool responses unless the user explicitly setsSTRUCTURED_CONTENT_ENABLED=true. Newer Claude Code MCP clients reject tool responses with-32600 "Tool codex has an output schema but did not return structured content"because every codex-mcp-server tool (codex,review,websearch) declares anoutputSchemaindist/tools/definitions.js, and the spec requiresstructuredContentto be populated whenoutputSchemais present.The defensive default was correct for an older Claude Code version that had a bug with
structuredContent— but that bug has since been fixed and the behaviour has inverted. The defensive default is now the cause of failures, not a workaround for them.Environment
codex-mcp-serverversion: 1.4.10sk-proj-*API key,codex login statusreturns "Logged in using an API key")Steps to reproduce
codex-mcp-server@1.4.10as a Claude Code MCP:{ "mcpServers": { "openai": { "command": "npx", "args": ["-y", "codex-mcp-server"], "env": { "OPENAI_API_KEY": "sk-proj-..." } } } }codex login statusreturns "Logged in" in a terminal (so the CLI layer works).codextool with any prompt:STRUCTURED_CONTENT_ENABLED=trueis set when spawning the server.Expected behaviour
A default
npx -y codex-mcp-serverinstall should work with current Claude Code out of the box. No extra environment variables should be needed.Actual behaviour
Every tool call returns
-32600until the user figures out that they need to setSTRUCTURED_CONTENT_ENABLED=true. This is a frustrating discovery experience because:Root cause
In
dist/tools/handlers.js:And in the
CodexToolHandler.execute()return block:Three problems stacked:
isStructuredContentEnabled()returnsfalseunless explicitly enabled.Object.keys(metadata).length > 0rejects empty metadata. This is an additional guard not present in therevieworwebsearchhandlers.structuredContent. When a client displaysstructuredContentinstead ofcontent[0].text, the user sees only the metadata object ({model, threadId, ...}) without the actual CLI response. This means even after flipping the default, clients that preferstructuredContentsee no codex output.The comments in handlers.js reference the old bug:
This was accurate for older Claude Code but is now inverted. Newer Claude Code requires
structuredContent(theoutputSchemadeclaration indefinitions.jsmakes it mandatory per the MCP spec).Proposed fix
Three changes to
src/tools/handlers.ts:Change 1 — Flip the default to opt-out instead of opt-in:
Users who need the old behaviour can still opt out with
STRUCTURED_CONTENT_ENABLED=false.Change 2 — Remove the
metadata.length > 0guard in the codex handler (keeps parity with review/websearch handlers which already use the simpler pattern):Change 3 — Include the CLI response text in
structuredContentfor all three handlers:For codex:
For review:
For websearch:
Why this works: the
outputSchemaindefinitions.jsis permissive — it has noadditionalProperties: falseand norequiredfields, so adding aresponsestring field is schema-compatible. Clients that prefercontent[0].textget it there; clients that preferstructuredContentget the response inside the structured object. Both client behaviours work without extra config.Tested locally
All three changes have been applied to the installed
dist/tools/handlers.jsand verified working end-to-end against Claude Code 2.1.92.mcp__openai__codex({prompt: "Reply with TEST-OK", model: "gpt-5.2-codex", reasoningEffort: "low", sandbox: "read-only"})now returns:{ "model": "gpt-5.2-codex", "response": "TEST-OK\n" }No more
-32600errors.websearchsimilarly returns the full search response insidestructuredContent.Additional compatibility note
A separate issue worth flagging: when
reasoningEffort='minimal'is passed to the codex tool, the underlying codex CLI auto-injects aweb_searchtool into the request, which OpenAI's Responses API then rejects with:This might be worth documenting in the codex-mcp-server README (noting that
reasoningEffortfloor is'low') or guarding against in the tool schema (reject'minimal'in the Zod validator before passing to the CLI). I can file this as a separate issue if it's in scope for this repo rather than codex CLI.Broader observation
The comment pattern
// Dual approach: For Claude Code compatibility (avoids structuredContent bug)suggests the codebase was written to work around a specific Claude Code regression. That regression has been fixed upstream, and the workaround is now the new regression. When upstream bugs get fixed, the defensive workarounds often need to be retired. This might be worth a one-time README note: "If you see-32600errors on newer Claude Code, setSTRUCTURED_CONTENT_ENABLED=trueor update to the next release which defaults it on."