Code Mode functions.exec drops mcp/www_authenticate from nested MCP tool results

Open 💬 0 comments Opened Aug 2, 2026 by ivnzdodo

What version of the Codex App are you using?

Codex Desktop 26.727.51351 (bundle 6119, com.openai.codex)

Bundled/local CLI:

codex-cli 0.146.0-alpha.9.2

What subscription do you have?

Not relevant to the reproduction.

What platform is your computer?

macOS 26.5.1 (build 25F80), arm64

Which model were you using?

GPT-5.6 Sol

What issue are you seeing?

In Code Mode, when an MCP tool is invoked through the nested tools.* API inside functions.exec, Codex does not surface the standard OAuth reauthorization UI from the tool result's _meta["mcp/www_authenticate"].

The nested MCP call returns a valid CallToolResult similar to:

{
  "isError": true,
  "content": [
    {
      "type": "text",
      "text": "OAuth token must include \"example:write\"."
    }
  ],
  "_meta": {
    "mcp/www_authenticate": [
      "Bearer resource_metadata=\"https://example.com/.well-known/oauth-protected-resource\", scope=\"example:read example:write\", error=\"insufficient_scope\", error_description=\"OAuth token must include \\\"example:write\\\".\""
    ]
  }
}

The tool also declares its OAuth securitySchemes in tools/list.

The complete result, including the challenge, is visible to JavaScript inside functions.exec. However, the outer tool result only exposes whatever text/image items the JavaScript explicitly emits. No native OAuth card appears and no browser authorization flow starts.

A direct fallback works:

codex mcp login <server> --scopes example:read,example:write

That command opens the browser and completes authorization successfully, confirming that the MCP server's OAuth flow and requested scopes are valid.

What steps can reproduce the bug?

  1. Configure a Streamable HTTP MCP server using OAuth.
  2. Authorize it with an initial limited scope such as example:read.
  3. Expose a tool requiring a newly added scope such as example:write.
  4. Have the tool return isError: true plus _meta["mcp/www_authenticate"] with:
  • resource_metadata
  • the union of existing and missing scopes
  • error="insufficient_scope"
  1. Invoke that MCP tool from Code Mode through functions.exec:
const result = await tools.mcp__example__write_operation({});
text(JSON.stringify({
  isError: result.isError,
  meta: result._meta,
  content: result.content
}));
  1. Observe that the challenge is present in result._meta, but Codex does not show an OAuth card or start reauthorization.
  2. Run codex mcp login <server> --scopes example:read,example:write manually and observe that browser authorization succeeds.

What is the expected behavior?

Nested MCP calls made through functions.exec should preserve host-handled MCP metadata.

When a nested CallToolResult contains _meta["mcp/www_authenticate"], Codex should either:

  1. intercept and propagate the challenge to the same native OAuth/reauthorization flow used for direct MCP calls before returning control to JavaScript; or
  2. attach the nested challenge to the outer functions.exec result so the Desktop host can show the authorization UI.

After successful authorization, the current conversation should reload the updated MCP credential and retry or allow the call to be retried without restarting Codex.

What is the actual behavior?

  • The nested JavaScript receives the full challenge.
  • The outer Codex UI receives no auth elicitation.
  • No browser opens.
  • The user only sees the textual missing-scope error if the agent emits it.
  • Manual codex mcp login succeeds, but the already-open conversation can continue using its cached old token until Codex is restarted.

Additional information

This is related to, but distinct from, #20518:

  • #20518 covers HTTP 403 insufficient_scope and the WWW-Authenticate response header in the MCP transport.
  • This report covers an in-protocol MCP tool result where JSON-RPC succeeds and the runtime challenge is returned in CallToolResult._meta["mcp/www_authenticate"], but Code Mode's functions.exec nesting boundary does not propagate it to the Desktop auth UI.

It may also overlap with the credential-refresh/UI concerns in #17265 and #23453, but the minimal failure here occurs specifically at the nested tool-result metadata boundary.

View original on GitHub ↗