Skip to content

structuredContent disabled-by-default causes -32600 on newer Claude Code MCP clients #161

@clemens-ui

Description

@clemens-ui

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

  1. 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-..." }
        }
      }
    }
  2. Ensure codex login status returns "Logged in" in a terminal (so the CLI layer works).
  3. 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")
    
  4. Observe the MCP client returns:
    Error: MCP error -32600: Tool codex has an output schema but did not return structured content
    
  5. 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:

  1. Default is off. isStructuredContentEnabled() returns false unless explicitly enabled.
  2. 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.
  3. 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."

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions