Skip to content

Conversation

@BrandonShar
Copy link
Contributor

@BrandonShar BrandonShar commented Oct 28, 2025

Description

As part of supporting OpenAI's Apps SDK, the underlying MCP python sdk added support for meta in tool call payloads in v1.19.0.

This PR modifies the ToolResult object to additionally support the meta field. It makes the same change to CachedToolResult to support the caching middleware and the local CallToolResult dataclass in the client.

It also adds some additional testing for ToolResult and CachedToolResult

Contributors Checklist

  • My change helps close Support for OpenAI Apps SDK #2014
  • I have followed the repository's development workflow
  • I have tested my changes manually and by adding relevant tests
  • I have performed all required documentation updates

Review Checklist

  • I have self-reviewed my changes
  • My Pull Request is ready for review

Summary by CodeRabbit

  • New Features

    • Tools can now return optional metadata alongside content and structured data; clients receive metadata when provided.
    • Added an example demonstrating metadata in tool results.
  • Documentation

    • Docs updated to describe metadata inclusion and related behavior notes.
  • Tests

    • Added/updated tests to verify metadata round-trips and various content/structured/meta scenarios.
  • Chores

    • Raised minimum MCP dependency requirement.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 28, 2025

Walkthrough

FastMCP adds optional metadata to tool results: ToolResult.meta is introduced and propagated through ToolResult.to_mcp_result, the client call_tool return value, and the server caching middleware; docs, an example echo tool, and tests were updated accordingly.

Changes

Cohort / File(s) Change Summary
Core tool & client
src/fastmcp/tools/tool.py, src/fastmcp/client/client.py
Add optional `meta: dict[str, Any]
Server middleware (caching)
src/fastmcp/server/middleware/caching.py
CachableToolResult gains meta; wrap and unwrap updated to preserve meta during cache round-trips.
Documentation & example
docs/servers/tools.mdx, examples/tool_result_echo.py
Docs updated to describe meta semantics; new example examples/tool_result_echo.py demonstrates returning content, structured_content, and meta.
Tests
tests/server/middleware/test_caching.py, tests/server/test_server_interactions.py, tests/tools/test_tool.py
Tests updated/added to assert meta preservation in caching and call results; snapshots adjusted; TestToolResultCasting tests added (duplicate block present in diff).
Project config
pyproject.toml
Bumped mcp dependency minimum from >=1.17.0 to >=1.19.0 (upper bound unchanged).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant FastMCP as FastMCP Server
    participant Tool
    participant Cache as Caching Middleware
    participant CallResult as CallToolResult

    Client->>FastMCP: call_tool(tool_name, args)
    FastMCP->>Tool: invoke tool(...)
    Tool->>Tool: produce ToolResult(content, structured_content, meta)
    Tool->>FastMCP: ToolResult.to_mcp_result()
    note right of FastMCP #f3f4f6: if meta present → returns CallToolResult
    FastMCP->>Cache: wrap(CallToolResult) — preserve meta
    Cache-->>FastMCP: unwrap() → CallToolResult (meta preserved)
    FastMCP->>Client: return CallToolResult(content, structured_content, data, meta)
Loading

Poem

🐰 I bounced a bit of meta in my paw,
Echoed content, structure, and more—haw!
Through tool and cache, I made it clear,
A tiny payload hopping near.
Echo Server smiles, metadata here.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Add meta support to ToolResult" is concise, specific, and directly summarizes the primary objective of the changeset. It clearly communicates the main feature being added without unnecessary noise or vague terminology. The title accurately reflects the core changes across multiple files (ToolResult, CachedToolResult, CallToolResult) and supporting documentation, making it clear to reviewers scanning the history what the PR accomplishes.
Linked Issues Check ✅ Passed The changes directly implement the objective stated in linked issue #2014 to add first-class FastMCP support for OpenAI's Apps SDK. The PR adds meta field support to ToolResult, CachedToolResult, and CallToolResult, aligning with MCP Python SDK v1.19.0 which introduced meta support for tool call payloads. The dependency update to mcp>=1.19.0, documentation updates describing metadata support, new examples demonstrating meta usage, and comprehensive tests all serve the goal of enabling OpenAI Apps SDK compatibility and integration with FastMCP.
Out of Scope Changes Check ✅ Passed All code changes are directly related to implementing meta field support for OpenAI Apps SDK compatibility. The changes include: documentation updates explaining the meta field, a new example demonstrating meta usage, dependency updates to support v1.19.0 features, core implementation in ToolResult and related classes, middleware updates for caching support, and comprehensive tests validating the new functionality. No refactoring, unrelated bug fixes, or out-of-scope modifications are present in the changeset.
Description Check ✅ Passed The pull request description follows the required template structure and includes all key sections: a clear Description section explaining the change's purpose (supporting OpenAI's Apps SDK via MCP Python SDK v1.19.0), a completed Contributors Checklist (4/4 items checked), and a completed Review Checklist (2/2 items checked). The description provides sufficient context about what was changed (ToolResult, CachedToolResult, CallToolResult, documentation, and testing) and justifies the changes through reference to the linked issue and upstream SDK updates.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d291499 and 33374c1.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • pyproject.toml (1 hunks)
🔇 Additional comments (1)
pyproject.toml (1)

10-10: Verification complete: mcp 1.19.0 version bump is justified and safe.

The codebase extensively uses meta field support introduced in v1.19.0:

  • Tool transformation tests confirm proper meta inheritance and override (tests/tools/test_tool_transform.py:1413-1471)
  • Component management implements get_meta() with fastmcp metadata (src/fastmcp/utilities/components.py)
  • Web search confirms no breaking changes between v1.17.0 and v1.19.0

The version bump is necessary and compatible within the <2.0.0 constraint.


Comment @coderabbitai help to get the list of available commands and usage tips.

@marvin-context-protocol marvin-context-protocol bot added the enhancement Improvement to existing functionality. For issues and smaller PR improvements. label Oct 28, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/servers/tools.mdx (1)

621-631: Example not runnable: missing TextContent import.

Add the import so users can copy/paste.

Apply:

 from fastmcp.tools.tool import ToolResult
+from mcp.types import TextContent
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ea896c and 7949879.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • docs/servers/tools.mdx (2 hunks)
  • examples/tool_result_echo.py (1 hunks)
  • pyproject.toml (1 hunks)
  • src/fastmcp/client/client.py (2 hunks)
  • src/fastmcp/server/middleware/caching.py (1 hunks)
  • src/fastmcp/tools/tool.py (3 hunks)
  • tests/server/middleware/test_caching.py (2 hunks)
  • tests/server/test_server_interactions.py (1 hunks)
  • tests/tools/test_tool.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Use Python ≥ 3.10 and provide full type annotations for library code

Files:

  • src/fastmcp/client/client.py
  • src/fastmcp/server/middleware/caching.py
  • src/fastmcp/tools/tool.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Never use bare except; always catch specific exception types

Files:

  • src/fastmcp/client/client.py
  • tests/server/middleware/test_caching.py
  • src/fastmcp/server/middleware/caching.py
  • tests/tools/test_tool.py
  • src/fastmcp/tools/tool.py
  • examples/tool_result_echo.py
  • tests/server/test_server_interactions.py
docs/**/*.mdx

📄 CodeRabbit inference engine (docs/.cursor/rules/mintlify.mdc)

docs/**/*.mdx: Use clear, direct language appropriate for technical audiences
Write instructions and procedures in second person ("you")
Use active voice over passive voice
Use present tense for current states and future tense for outcomes
Maintain consistent terminology across the documentation
Keep sentences concise while preserving necessary context
Use parallel structure in lists, headings, and procedures
Lead with the most important information (inverted pyramid)
Use progressive disclosure: basic concepts before advanced ones
Break complex procedures into numbered steps
Include prerequisites and context before instructions
Provide expected outcomes for each major step
End sections with next steps or related information
Use descriptive, keyword-rich headings for navigation and SEO
Focus on user goals and outcomes rather than system features
Anticipate common questions and address them proactively
Include troubleshooting for likely failure points
Offer multiple pathways when appropriate (beginner vs advanced) and provide an opinionated recommended path
Use for supplementary information that supports the main content
Use for expert advice, shortcuts, or best practices
Use for critical cautions, breaking changes, or destructive actions
Use for neutral background or contextual information
Use to confirm success or completion
Provide single code examples using fenced code blocks with language (and filename when relevant)
Use to present the same concept in multiple languages
For API docs, use to show requests
For API docs, use to show responses
Use and to document procedures and sequential instructions
Use and for platform-specific or alternative approaches
Use / for supplementary content that might interrupt flow
In API docs, use for parameters (path, body, query, header) with type and required/default as appropria...

Files:

  • docs/servers/tools.mdx
docs/**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

docs/**/*.{md,mdx}: Documentation code examples should be explained before the code and be fully runnable, including imports
Use clear headers with logical H2/H3 hierarchy to form navigation
Write user-focused content that motivates the why before the how
Prefer prose for important information over code comments in docs

Files:

  • docs/servers/tools.mdx
**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{md,mdx}: Be brief and to the point in written materials; avoid regurgitating obvious code details
Avoid defensive constructions like "This isn't X" or "Not just X, but Y"; state what it is directly

Files:

  • docs/servers/tools.mdx
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.py: Tests must be atomic, self-contained, and cover a single functionality
Use pytest parameterization for multiple examples of the same functionality
Use separate tests for distinct pieces of functionality
Always put imports at the top of test files; do not import inside test bodies
Do not add @pytest.mark.asyncio; asyncio_mode = "auto" is set globally
Prefer in-memory transport for tests; use HTTP transport only when explicitly testing networking
For slow/long-running tests, mark them as integration or optimize (default timeout is 5s)
In tests, use # type: ignore[attr-defined] for MCP results instead of type assertions

Files:

  • tests/server/middleware/test_caching.py
  • tests/tools/test_tool.py
  • tests/server/test_server_interactions.py
🧬 Code graph analysis (7)
src/fastmcp/client/client.py (1)
src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py (2)
  • CallToolRequestResult (25-48)
  • from_call_tool_result (37-48)
tests/server/middleware/test_caching.py (2)
src/fastmcp/server/middleware/caching.py (5)
  • CachableToolResult (63-81)
  • wrap (52-53)
  • wrap (69-74)
  • unwrap (56-60)
  • unwrap (76-81)
src/fastmcp/tools/tool.py (1)
  • ToolResult (66-112)
src/fastmcp/server/middleware/caching.py (1)
src/fastmcp/tools/tool.py (1)
  • ToolResult (66-112)
tests/tools/test_tool.py (3)
src/fastmcp/client/client.py (1)
  • call_tool (865-934)
src/fastmcp/tools/tool.py (1)
  • ToolResult (66-112)
src/fastmcp/tools/tool_manager.py (1)
  • call_tool (153-172)
src/fastmcp/tools/tool.py (3)
src/fastmcp/client/client.py (1)
  • CallToolResult (946-951)
src/fastmcp/contrib/mcp_mixin/mcp_mixin.py (1)
  • mcp_tool (25-52)
src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py (1)
  • CallToolRequestResult (25-48)
examples/tool_result_echo.py (1)
src/fastmcp/tools/tool.py (1)
  • ToolResult (66-112)
tests/server/test_server_interactions.py (2)
tests/contrib/test_mcp_mixin.py (2)
  • test_tool_with_title_and_meta (257-279)
  • MyToolWithMeta (263-269)
tests/server/test_server.py (1)
  • test_tool_decorator_with_meta (434-448)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run tests with lowest-direct dependencies
  • GitHub Check: Run tests: Python 3.10 on windows-latest
🔇 Additional comments (11)
src/fastmcp/server/middleware/caching.py (2)

66-75: Meta preserved in cache wrapper. LGTM.


78-81: Unwrap returns ToolResult with meta. LGTM.

tests/server/middleware/test_caching.py (2)

26-30: New import for CachableToolResult — OK.


511-523: Round‑trip test for meta/content/structured_content — OK.

tests/server/test_server_interactions.py (1)

1182-1183: Snapshot updated to include meta=None — OK.

Keeps equality stable after dataclass change.

src/fastmcp/client/client.py (1)

931-931: Code change verified—no issues found.

The script confirms that result.meta is available in the installed MCP SDK's CallToolResult model. The code correctly accesses the field without errors. The original review's approval stands.

pyproject.toml (1)

10-10: Constraint is correctly configured in pyproject.toml and uv.lock, but runtime mcp 1.16.0 violates the ≥1.19.0 floor.

What's correct:

  • pyproject.toml pins mcp>=1.19.0,<2.0.0 (line 10)
  • uv.lock resolves to mcp 1.19.0
  • CI matrix includes run_tests_lowest_direct job that enforces floor version (Python 3.10 via uv sync --resolution lowest-direct)
  • Both ubuntu-latest and windows-latest tested

Critical gap:
Runtime resolved to mcp 1.16.0, which violates the constraint and risks CallToolResult.meta attribute mismatches. Verify that:

  • Deployment environments use uv.lock or respect pyproject.toml constraints
  • No cached/stale environments are bypassing the floor version
  • All dependency resolution (pip, poetry, uv) respects the ≥1.19.0 constraint
examples/tool_result_echo.py (1)

1-21: LGTM! Clear demonstration of the new meta field.

This example effectively demonstrates the new meta support in ToolResult. The usage of all three fields (content, structured_content, and meta) clearly illustrates the intended API for users.

src/fastmcp/tools/tool.py (3)

19-19: LGTM! Necessary import for meta support.

The CallToolResult import from mcp.types is required for returning tool results with metadata, aligning with the MCP SDK v1.19.0 changes.


67-79: LGTM! Proper parameter addition and storage.

The meta parameter is correctly added with appropriate type annotations (dict[str, Any] | None) and default value (None), ensuring backwards compatibility. The storage on self.meta is straightforward and correctly typed.


99-112: LGTM! Correct implementation of meta support.

The to_mcp_result method correctly handles the new meta field:

  • When meta is present, returns a CallToolResult with all three fields, using the correct MCP SDK conventions (structuredContent in camelCase, _meta with underscore prefix)
  • Falls back to existing behavior when meta is None
  • Return type annotation properly reflects the new CallToolResult option

The logic correctly prioritizes meta handling and maintains backwards compatibility.

class CallToolResult:
content: list[mcp.types.ContentBlock]
structured_content: dict[str, Any] | None
meta: dict[str, Any] | None
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 28, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Dataclass extended with meta — update downstream extenders.

Classes extending/deriving from CallToolResult should propagate structured_content and meta. Example fix for bulk caller:

 class CallToolRequestResult(CallToolResult):
@@
     def from_call_tool_result(
         cls, result: CallToolResult, tool: str, arguments: dict[str, Any]
     ) -> "CallToolRequestResult":
         return cls(
             tool=tool,
             arguments=arguments,
-            isError=result.isError,
-            content=result.content,
+            isError=result.is_error,
+            content=result.content,
+            structured_content=result.structured_content,
+            meta=result.meta,
         )

Search for similar helpers to update:


🏁 Script executed:

#!/bin/bash
rg -nP 'from_call_tool_result|class\s+CallToolResult|result\.(structuredContent|meta)\b' -C2

Length of output: 5210


🏁 Script executed:

#!/bin/bash
# View the implementation of from_call_tool_result
cat src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py | head -60

Length of output: 1693


🏁 Script executed:

#!/bin/bash
# View the CallToolResult dataclass definition
sed -n '945,960p' src/fastmcp/client/client.py

Length of output: 200


Update CallToolRequestResult.from_call_tool_result() to propagate all fields.

The method currently attempts to access result.isError (which doesn't exist) and fails to propagate structured_content and meta. Fix:

 @classmethod
 def from_call_tool_result(
     cls, result: CallToolResult, tool: str, arguments: dict[str, Any]
 ) -> "CallToolRequestResult":
     """
     Create a CallToolRequestResult from a CallToolResult.
     """
     return cls(
         tool=tool,
         arguments=arguments,
-        isError=result.isError,
+        is_error=result.is_error,
         content=result.content,
+        structured_content=result.structured_content,
+        meta=result.meta,
     )

Location: src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py lines 37–48

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/fastmcp/client/client.py around line 949, update
CallToolRequestResult.from_call_tool_result to stop referencing the non-existent
result.isError and instead propagate the actual fields from the incoming result:
use result.error (or set is_error = bool(result.error)) for error state, and
copy result.structured_content and result.meta into the returned
CallToolRequestResult object so all fields are preserved.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This wasn't changed in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

@BrandonShar You're absolutely right! The bulk_tool_caller.py file wasn't modified in this PR. I was flagging a downstream compatibility issue that exists in that separate module—since CallToolResult now includes the meta field, the CallToolRequestResult.from_call_tool_result() method in that file should ideally be updated to propagate it.

Since this is outside the scope of your current changes, would you like me to open a follow-up issue to track updating the bulk tool caller to support the new meta field?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7949879 and d291499.

📒 Files selected for processing (1)
  • examples/tool_result_echo.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Never use bare except; always catch specific exception types

Files:

  • examples/tool_result_echo.py
🧬 Code graph analysis (1)
examples/tool_result_echo.py (2)
src/fastmcp/server/server.py (1)
  • FastMCP (148-2693)
src/fastmcp/tools/tool.py (1)
  • ToolResult (66-112)
🔇 Additional comments (1)
examples/tool_result_echo.py (1)

1-8: LGTM!

The imports are appropriate for demonstrating the new meta feature in ToolResult.

Copy link
Owner

@jlowin jlowin left a comment

Choose a reason for hiding this comment

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

Thank you @BrandonShar!

jlowin added a commit that referenced this pull request Nov 2, 2025
@jlowin jlowin merged commit c18782f into jlowin:main Nov 2, 2025
6 checks passed
jlowin added a commit that referenced this pull request Nov 2, 2025
@jlowin jlowin mentioned this pull request Nov 2, 2025
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement to existing functionality. For issues and smaller PR improvements.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for OpenAI Apps SDK

2 participants