openai / openai/codex

MCP /export Activity shows both content and structuredContent: clarify model-visible output and token accounting

Open
#45,637 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

CLI documentation mcp rate-limits
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

What is the type of issue?

Documentation is missing / confusing; MCP export observability.

What is the issue?

Codex CLI /export includes both the JSON text fallback and structuredContent in Markdown Activity blocks. This can look like two copies were added to the model context, even when the recorded output of the surrounding programmatic tool call contains only one copy.

Please clarify which export/trace records represent the raw MCP response and which represent the post-processing output included in the next model request. This matters for MCP server interoperability, context budgets, and reliable token-efficiency evaluations.

The export/output discrepancy is verified below. The outgoing model request and billable token impact have not been captured or measured.

Why an MCP server returns both fields

The MCP specification recommends that a tool returning structuredContent SHOULD also return its serialized JSON in a TextContent block for backward compatibility. This is a SHOULD, not a MUST.

Minimal illustrative response shape:

{
  "content": [
    { "type": "text", "text": "{\"body\":\"MCP_AUDIT_MARKER\"}" }
  ],
  "structuredContent": {
    "body": "MCP_AUDIT_MARKER"
  },
  "isError": false
}

Source: https://modelcontextprotocol.io/specification/2026-07-28/server/tools#structured-content

Removing the fallback can break clients that do not expose structured results to their models. Keeping both should not automatically be interpreted as proof that both enter the model context.

Environment and verified local observations
  • Codex CLI 0.154.0, macOS.
  • Affected rollout metadata: originator: codex-tui, source: cli, cli_version: 0.154.0.
  • Model: gpt-6-astra, reasoning effort max.
  • MCP calls nested in functions.exec; Markdown produced with /export.
  • Audit performed on September 15, 2026, using an existing session and its export.

Two read-only calls returned the same JSON business object in the same version. A local verification script matched the surrounding calls and their recorded custom_tool_call_output items, parsed the exported JSON, checked object equality, and counted a marker appearing once in the original business object.

Result handling inside functions.exec Copies in exported MCP Activity result Copies in recorded outer tool-output text Outer tool-output text length
Print the entire MCP result with text(result) 2 2 32,344 characters
Select response.structuredContent, then print that value 2 1 16,013 characters

Lengths exclude the execution header. They are character counts, not token counts.

For both calls, the export contains the text fallback followed by a structured result: representation. Both parse to the same object. In the second call, the recorded outer output contains only that selected object.

Private business data and raw conversation logs are omitted. The JSON above illustrates the response shape; it is not a claim that a new synthetic-server run was performed.

Reproduction outline
  1. Connect a read-only MCP tool that returns an object in structuredContent and the identical serialized object in one content text block.
  2. In the programmatic tool-calling path, invoke it and print the complete returned MCP result.
  3. Invoke it again with identical arguments, then print only the returned structuredContent value.
  4. Export the conversation with /export.
  5. Compare each nested MCP Activity result with the corresponding outer tool-output record.
  6. If supported, also inspect the actual outgoing model input and count occurrences there.

Steps 1-5 were checked against the existing session. Step 6 remains unverified.

Native MCP handling is a separate path

In the public rust-v0.154.0 source, as_function_call_output_payload selects a non-null structuredContent value for ordinary JSON/text responses instead of appending both representations. There is a separate encrypted-content branch.

Source: https://github.com/openai/codex/blob/rust-v0.154.0/codex-rs/protocol/src/models.rs#L2258

This selection must not be generalized to a programmatic call that explicitly serializes the entire result envelope. The recorded observations above show why the distinction matters.

Clarification requested
  1. Does /export intentionally include raw nested MCP results independently of the values forwarded by functions.exec? Are Activity blocks explicitly unsuitable as an exact model-context export?
  2. Which supported record or command establishes the actual tool payload included in each subsequent model request? Can this be inspected for an existing session? The documented codex debug prompt-input describes exact model-visible input, but the relationship to an existing session's processed MCP outputs is unclear.
  3. What is the supported policy for content plus structuredContent in native MCP calls versus programmatic calls? Please document a reliable single-representation forwarding pattern that preserves errors and non-redundant content.
  4. Can export labels or a dedicated diagnostic view distinguish raw MCP data, post-processing tool output, and actual model input? Where can per-request/per-output token use be checked?

If an actual request includes both identical representations, the redundant payload increases input-token use and context occupation. The effect on total cost depends on the full request, tokenization, caching, and subsequent turns. This report does not establish a twofold total bill.

Related reports and documentation

I searched existing Codex issues and public discussions for the combination of /export, Activity output, and redundant content/structuredContent. I did not find an existing report covering this exact observability question.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the /export output and the as_function_call_output_payload path in codex-rs/protocol/src/models.rs around line 2258. Compare those records with codex debug prompt-input and the programmatic tool-calling documentation. Done means the documentation clearly distinguishes raw MCP results, processed tool output, and model-visible input, including where token usage can be checked.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli, documentation, observability
Issue type
Documentation
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.