MCP /export Activity shows both content and structuredContent: clarify model-visible output and token accounting
Nobody has claimed this yet.
- 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 effortmax. - 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
- Connect a read-only MCP tool that returns an object in
structuredContentand the identical serialized object in onecontenttext block. - In the programmatic tool-calling path, invoke it and print the complete returned MCP result.
- Invoke it again with identical arguments, then print only the returned
structuredContentvalue. - Export the conversation with
/export. - Compare each nested MCP Activity result with the corresponding outer tool-output record.
- 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
- Does
/exportintentionally include raw nested MCP results independently of the values forwarded byfunctions.exec? Are Activity blocks explicitly unsuitable as an exact model-context export? - 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-inputdescribes exact model-visible input, but the relationship to an existing session's processed MCP outputs is unclear. - What is the supported policy for
contentplusstructuredContentin native MCP calls versus programmatic calls? Please document a reliable single-representation forwarding pattern that preserves errors and non-redundant content. - 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
- #38287 distinguishes a complete raw MCP event from an assistant-visible function output containing only
structuredContent. It concerns lost content, rather than the ambiguity of/exportwith redundant JSON. - #44126 documents a different mismatch between MCP event/export representations and the tool/model path, involving oversized images.
- #31136 concerns JSON serialization overhead, rather than two copies of the same payload.
- The
cursor-delegate-mcpmaintainer reports removing redundant structured results because of duplication in Codex: https://github.com/andreilungeanu/cursor-delegate-mcp/blob/main/CHANGELOG.md#1160---2026-08-07 . That entry does not identify the exact Codex version or invocation path, so it does not establish the behavior of native MCP calls in 0.154.0. - https://developers.openai.com/api/docs/guides/tools-programmatic-tool-calling explains that programmatic tool results can be processed before choosing what enters model context.
- https://learn.chatgpt.com/docs/developer-commands?surface=cli documents
codex debug prompt-input.
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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