Skip to content

fix(mcp): pass structuredContent and _meta through to model-visible tool output - #2596

Merged
sailist merged 1 commit into
MoonshotAI:mainfrom
xpzouying:fix/mcp-structured-result-passthrough
Aug 4, 2026
Merged

fix(mcp): pass structuredContent and _meta through to model-visible tool output#2596
sailist merged 1 commit into
MoonshotAI:mainfrom
xpzouying:fix/mcp-structured-result-passthrough

Conversation

@xpzouying

@xpzouying xpzouying commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Related Issue

No linked issue — the problem is explained below.

Problem

The MCP tools/call result shape includes structuredContent (spec-defined, validated against a tool's outputSchema) and _meta (namespaced server metadata). kimi-code's client narrows results to {content, isError} in toMcpToolResult, so anything a server returns in these fields is silently dropped before the agent loop ever sees it.

Servers that put their machine-readable contract in structuredContent — or carry side-channel payloads such as browser-handoff URLs in _meta — appear to the model as if they returned only their prose blocks. Other MCP hosts (Claude Code, Codex) pass this data through to the model, so servers written against those hosts regress under kimi-code.

What changed

  • MCPToolResult gains optional structuredContent?: unknown and _meta?: Record<string, unknown> (v1 packages/agent-core/src/mcp/types.ts, v2 packages/agent-core-v2/src/mcpCore/types.ts).
  • toMcpToolResult (v1 mcp/client-shared.ts, v2 mcpCore/client-shared.ts) preserves both fields when present instead of discarding them.
  • mcpResultToExecutableOutput (v1 mcp/output.ts, v2 agent/mcp/output.ts) serializes them into a trailing <mcp-structured-result>{…}</mcp-structured-result> text part. Ordering and hardening:
    • appended after the media wrap, so a media-only result keeps its <mcp_tool_result name="…"> attribution;
    • appended before the text budget, so the existing 100K character budget still bounds oversized payloads;
    • literal </mcp-structured-result> sequences inside the serialized payload are stripped, so server-controlled data cannot fake an early end of the block;
    • non-serialisable payloads are dropped rather than failing the call.

This mirrors the existing in-tree convention of tagging machine-attributable tool output with a distinctive text wrapper (<mcp_tool_result> for media-only results); the tag carries no host-side semantics — nothing parses it back — it only lets the model attribute the JSON as a structured payload rather than prose.

Verification

  • Mirrored unit tests in v1 packages/agent-core/test/mcp/output.test.ts and v2 packages/agent-core-v2/test/agent/mcp/output.test.ts: passthrough serialization, media-only attribution preserved, closing-tag stripping.
  • tsc --noEmit passes for agent-core and agent-core-v2.
  • MCP test suites pass: 189 tests (v1), 168 tests (v2).
  • Changeset included (@moonshot-ai/kimi-code: patch).

@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5387387

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…ool output

MCP tool results were narrowed to {content, isError}, dropping the
spec-defined structuredContent field and _meta metadata. Servers that
return structured contracts in these fields (validated against
outputSchema, or namespaced metadata such as browser-handoff payloads)
were invisible to the agent. Surface them as a serialized
<mcp-structured-result> block appended to the tool output, still subject
to the existing text budget.
@xpzouying
xpzouying force-pushed the fix/mcp-structured-result-passthrough branch from 8f76ffb to 5387387 Compare August 4, 2026 09:10
@xpzouying
xpzouying marked this pull request as ready for review August 4, 2026 09:43

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5387387a42

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +160 to +162
if (result._meta !== undefined) {
structuredExtras['_meta'] = result._meta;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep MCP result _meta out of model output

When an MCP Apps-compatible server returns _meta for component/client-only data, copying it into structuredExtras puts that side channel into the model-visible tool result and transcript; the tool-result docs define only content/structuredContent as model-visible and _meta as component-only (https://developers.openai.com/plugins/reference#tool-results). This can leak full record maps, trace IDs, or OAuth challenges that were intentionally omitted from content, so keep raw _meta off the model path or gate only narrowly reviewed keys.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point — partially adopted in #2600: _meta keys under protocol-reserved prefixes (a modelcontextprotocol/mcp label followed by another label, per the spec's key-name rules) are now filtered before serialization; those do carry host/protocol plumbing. Kept forwarding unprefixed and vendor-prefixed keys: "_meta is component-only" is an OpenAI Apps convention rather than MCP spec semantics — the spec leaves non-reserved namespaces to the server, and the host can't know which of them the model is meant to see without hard-coding vendor knowledge.

}

const wrapped = wrapMediaOnly(converted, qualifiedToolName);
// Structured payloads (structuredContent per MCP spec, plus server metadata

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move the v2 implementation comment to the header

Under packages/agent-core-v2, implementation comments are supposed to be confined to the top-of-file /** */ role block; this new explanatory block sits inside mcpResultToExecutableOutput, so it violates the scoped convention. Please move any durable module-level rationale to the file header or drop the inline narration.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #2600 — commentary moved to the module header per the agent-core-v2 convention.

@sailist
sailist merged commit c32e661 into MoonshotAI:main Aug 4, 2026
14 of 15 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 4, 2026
@xpzouying
xpzouying deleted the fix/mcp-structured-result-passthrough branch August 4, 2026 10:05
sailist pushed a commit that referenced this pull request Aug 4, 2026
…#2600)

* fix(mcp): drop protocol-reserved _meta keys from model-visible output

Follow-up to #2596. The MCP spec reserves _meta key prefixes whose labels
include "modelcontextprotocol" or "mcp" for protocol use; those entries
carry host/protocol plumbing rather than model-facing data, so filter them
out before serializing the <mcp-structured-result> block. Unprefixed and
vendor-prefixed keys still pass through — their semantics belong to the
server. Also moves the v2 implementation commentary into the module header
per the agent-core-v2 comment convention.

* fix(mcp): reserve _meta prefixes only when a label follows mcp/modelcontextprotocol

Per the spec's key-name rules a prefix is reserved when a
modelcontextprotocol or mcp label is followed by at least one more label;
a trailing reserved word (com.example.mcp/) is a legitimate vendor
namespace and now passes through.

---------

Co-authored-by: zouying <zouying@moonshot.cn>
mbuckaway pushed a commit to mbuckaway/kimi-code that referenced this pull request Aug 4, 2026
…ool output (MoonshotAI#2596)

MCP tool results were narrowed to {content, isError}, dropping the
spec-defined structuredContent field and _meta metadata. Servers that
return structured contracts in these fields (validated against
outputSchema, or namespaced metadata such as browser-handoff payloads)
were invisible to the agent. Surface them as a serialized
<mcp-structured-result> block appended to the tool output, still subject
to the existing text budget.

Co-authored-by: zouying <zouying@moonshot.cn>
mbuckaway pushed a commit to mbuckaway/kimi-code that referenced this pull request Aug 4, 2026
…MoonshotAI#2600)

* fix(mcp): drop protocol-reserved _meta keys from model-visible output

Follow-up to MoonshotAI#2596. The MCP spec reserves _meta key prefixes whose labels
include "modelcontextprotocol" or "mcp" for protocol use; those entries
carry host/protocol plumbing rather than model-facing data, so filter them
out before serializing the <mcp-structured-result> block. Unprefixed and
vendor-prefixed keys still pass through — their semantics belong to the
server. Also moves the v2 implementation commentary into the module header
per the agent-core-v2 comment convention.

* fix(mcp): reserve _meta prefixes only when a label follows mcp/modelcontextprotocol

Per the spec's key-name rules a prefix is reserved when a
modelcontextprotocol or mcp label is followed by at least one more label;
a trailing reserved word (com.example.mcp/) is a legitimate vendor
namespace and now passes through.

---------

Co-authored-by: zouying <zouying@moonshot.cn>
daofazhiran pushed a commit to daofazhiran/kimi-code that referenced this pull request Aug 4, 2026
…ool output (MoonshotAI#2596)

MCP tool results were narrowed to {content, isError}, dropping the
spec-defined structuredContent field and _meta metadata. Servers that
return structured contracts in these fields (validated against
outputSchema, or namespaced metadata such as browser-handoff payloads)
were invisible to the agent. Surface them as a serialized
<mcp-structured-result> block appended to the tool output, still subject
to the existing text budget.

Co-authored-by: zouying <zouying@moonshot.cn>
daofazhiran pushed a commit to daofazhiran/kimi-code that referenced this pull request Aug 4, 2026
…MoonshotAI#2600)

* fix(mcp): drop protocol-reserved _meta keys from model-visible output

Follow-up to MoonshotAI#2596. The MCP spec reserves _meta key prefixes whose labels
include "modelcontextprotocol" or "mcp" for protocol use; those entries
carry host/protocol plumbing rather than model-facing data, so filter them
out before serializing the <mcp-structured-result> block. Unprefixed and
vendor-prefixed keys still pass through — their semantics belong to the
server. Also moves the v2 implementation commentary into the module header
per the agent-core-v2 comment convention.

* fix(mcp): reserve _meta prefixes only when a label follows mcp/modelcontextprotocol

Per the spec's key-name rules a prefix is reserved when a
modelcontextprotocol or mcp label is followed by at least one more label;
a trailing reserved word (com.example.mcp/) is a legitimate vendor
namespace and now passes through.

---------

Co-authored-by: zouying <zouying@moonshot.cn>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants