Propagate x-fastmcp-wrap-result in tool result _meta#3490
Conversation
🤖 Generated with Claude Code Co-authored-by: Claude <[email protected]>
🤖 Generated with Claude Code Co-authored-by: Claude <[email protected]>
🤖 Generated with Claude Code Co-authored-by: Claude <[email protected]>
…allToolResult import, extract fastmcp_meta 🤖 Generated with Claude Code Co-authored-by: Claude <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 71f3b7567f
ℹ️ 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".
| if wrap_from_meta: | ||
| # The result is self-describing — unwrap directly without | ||
| # a listTools round-trip or schema lookup. | ||
| data = result.structuredContent.get("result") |
There was a problem hiding this comment.
Preserve schema-based decoding for wrapped tool results
When _meta.fastmcp.wrap_result is true (now emitted for wrapped FastMCP tool outputs), this branch returns result.structuredContent["result"] directly and bypasses the existing schema/type-adapter validation path. That changes result.data from validated Python types to raw JSON shapes for wrapped schemas (for example, set[int] now comes back as list[int], and AnyUrl as str), which is a behavioral regression for client.call_tool() consumers that rely on typed decoding.
Useful? React with 👍 / 👎.
🤖 Generated with Claude Code
🤖 Generated with Claude Code
Test Failure Analysis
Summary: 2 tests in Root Cause: In if wrap_from_meta:
data = result.structuredContent.get("result") # raw JSON value, no coercion
else:
# ... schema lookup + pydantic validate_python(structured_content)This skips the
Suggested Solution: In # Skip listTools only if meta already tells us it's wrapped
if not wrap_from_meta and name not in tool_output_schemas:
await list_tools_fn()
if name in tool_output_schemas:
output_schema = tool_output_schemas.get(name)
if output_schema:
if output_schema.get("x-fastmcp-wrap-result") or wrap_from_meta:
output_schema = output_schema.get("properties", {}).get("result")
structured_content = result.structuredContent.get("result")
else:
structured_content = result.structuredContent
output_type = json_schema_to_type(output_schema)
type_adapter = get_cached_typeadapter(output_type)
data = type_adapter.validate_python(structured_content)
else:
data = result.structuredContent
elif wrap_from_meta:
# Schema unavailable but we know it's wrapped — unwrap without coercion
data = result.structuredContent.get("result")This preserves the optimization (no Detailed AnalysisFailing tests:
Failure 1 — datetime:
Failure 2 — set:
Both failures are in the task code path ( Related Files
🤖 Generated with Claude Code (edited — reflects latest run analysis) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 969aafbd3c
ℹ️ 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".
| fastmcp_meta = (result.meta or {}).get("fastmcp") or {} | ||
| wrap_from_meta = fastmcp_meta.get("wrap_result", False) |
There was a problem hiding this comment.
Guard fastmcp meta parsing against non-dict values
_meta is free-form, but this path assumes result.meta["fastmcp"] is always a dict and immediately calls .get() on it. If a server (or a tool returning ToolResult(meta=...)) sends "fastmcp" as a non-object value, this raises, gets swallowed by the broad except, and leaves result.data unset even when schema-based decoding would have succeeded. This makes client parsing fragile for mixed/third-party MCP servers and malformed-but-recoverable metadata.
Useful? React with 👍 / 👎.
🤖 Generated with Claude Code
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 22b7bbe791
ℹ️ 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".
| fastmcp_meta = ( | ||
| raw_fastmcp_meta if isinstance(raw_fastmcp_meta, dict) else {} | ||
| ) | ||
| wrap_from_meta = fastmcp_meta.get("wrap_result", False) |
There was a problem hiding this comment.
Require boolean wrap_result before unwrapping content
wrap_from_meta = fastmcp_meta.get("wrap_result", False) treats any truthy value (for example the string "false" or integer 1) as a signal to unwrap structuredContent["result"]. In that case the client will incorrectly unwrap valid object payloads and can fall into the exception path, leaving result.data unset even when schema-based decoding would otherwise succeed. This is a regression in mixed/third-party environments because _meta is free-form and not type-enforced for nested values.
Useful? React with 👍 / 👎.
When FastMCP wraps non-object tool results in a
{"result": ...}envelope forstructuredContent, consumers like the Prefab renderer need to know whether to unwrap it. Currently this is only discoverable via the tool'soutputSchema, which requires alistToolscall and isn't available in all contexts (e.g., tool calls initiated from within a UI). This change propagates thex-fastmcp-wrap-resultflag directly on each tool result's_meta, making it self-describing. The client-side parser now checks_metafirst before falling back to the schema lookup.