Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Introduce ResourceResult as canonical resource return type#2734

Merged
jlowin merged 4 commits intomainfrom
resourceresult-cleanup
Dec 26, 2025
Merged

Introduce ResourceResult as canonical resource return type#2734
jlowin merged 4 commits intomainfrom
resourceresult-cleanup

Conversation

@jlowin
Copy link
Member

@jlowin jlowin commented Dec 26, 2025

Introduces ResourceResult and ResourceContent as the canonical types for resource returns, following up on the work in #2611.

The MCP SDK requires resources to return a list of content items, which is awkward DX for most resources that return a single value. ResourceResult wraps this pattern so users can return a single coherent object:

from fastmcp.resources import ResourceContent, ResourceResult

# Simple text/bytes - unchanged
@mcp.resource("file://readme")
def get_readme() -> str:
    return Path("README.md").read_text()

# Structured data - explicit serialization
@mcp.resource("data://config")
def get_config() -> str:
    return json.dumps({"key": "value"})

# Multiple items or metadata - use ResourceResult
@mcp.resource("data://users")
def get_users() -> ResourceResult:
    return ResourceResult([
        ResourceContent(user, meta={"id": i}) 
        for i, user in enumerate(users)
    ])

ResourceContent auto-serializes dict/list/BaseModel to JSON, so structured data works when wrapped explicitly. ResourceResult accepts str | bytes | list[ResourceContent] with strict typing - no implicit conversion of raw dicts or lists.

This moves validation from runtime (when a client reads a resource) to development time (when your type checker runs).

Introduces ResourceResult as the canonical wrapper for resource reads, matching
the ToolResult/PromptResult pattern. ResourceResult normalizes various return
types (str, bytes, dict, list) and provides result-level meta alongside
content-level meta via ResourceContent.

Key changes:
- Add ResourceResult with __init__ normalization and to_mcp_result()
- Add convert_result() to Resource and ResourceTemplate classes
- Update middleware signatures to use ResourceResult
- Update FastMCPProvider to return full ResourceResult
- Comprehensive unit tests for ResourceContent and ResourceResult
- Integration tests for meta propagation to MCP clients
ResourceResult now only accepts str | bytes | list[ResourceContent].
Dict and list values are no longer auto-converted; use ResourceContent
for serialization or json.dumps() for explicit JSON strings.
@marvin-context-protocol marvin-context-protocol bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. server Related to FastMCP server implementation or server-side functionality. labels Dec 26, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 26, 2025

Warning

Rate limit exceeded

@jlowin has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 20 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 47fbaee and 48b7a44.

📒 Files selected for processing (1)
  • docs/servers/resources.mdx

Walkthrough

This PR introduces a new ResourceResult type throughout the fastmcp codebase to encapsulate multiple resource content items with optional metadata, replacing the previous single-content ResourceContent model. The change updates resource read operations across documentation, core resource classes, server endpoints, middleware, and provider implementations to return ResourceResult instead of ResourceContent or lists thereof. Method signatures for read(), convert_result(), and context/server resource handlers are updated accordingly, along with corresponding type annotations and import statements.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 82.46% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The PR description clearly explains the changes, provides usage examples, and references the issue number, but the contributors checklist items are not checked off.
Title check ✅ Passed The title accurately summarizes the main change: introducing ResourceResult as the canonical resource return type across the codebase.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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: 0

Caution

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

⚠️ Outside diff range comments (2)
docs/servers/resources.mdx (2)

406-410: Inconsistent example with new typing requirements.

This annotations example shows def get_config() -> dict: which contradicts the new strict typing introduced in this PR. Resource functions must now return str | bytes | ResourceResult, not dict.

🔎 Proposed fix
 @mcp.resource(
     "data://config",
     annotations={
         "readOnlyHint": True,
         "idempotentHint": True
     }
 )
-def get_config() -> dict:
+def get_config() -> str:
     """Get application configuration."""
-    return {"version": "1.0", "debug": False}
+    return json.dumps({"version": "1.0", "debug": False})

440-471: Resource template examples show inconsistent return types.

The template examples show -> dict return types, which is inconsistent with the new strict typing. While resource templates may have different handling, the documentation should be consistent with the enforced types to avoid confusion.

🔎 Proposed fix for get_weather
 @mcp.resource("weather://{city}/current")
-def get_weather(city: str) -> dict:
+def get_weather(city: str) -> str:
     """Provides weather information for a specific city."""
-    return {
+    return json.dumps({
         "city": city.capitalize(),
         "temperature": 22,
         "condition": "Sunny",
         "unit": "celsius"
-    }
+    })
🔎 Proposed fix for get_repo_info
 def get_repo_info(owner: str, repo: str) -> dict:
+def get_repo_info(owner: str, repo: str) -> str:
     """Retrieves information about a GitHub repository."""
-    return {
+    return json.dumps({
         "owner": owner,
         "name": repo,
         "full_name": f"{owner}/{repo}",
         "stars": 120,
         "forks": 48
-    }
+    })
🧹 Nitpick comments (4)
src/fastmcp/server/providers/fastmcp_provider.py (1)

22-23: Provider resource/template wrappers correctly propagate ResourceResult

The provider now consistently treats resource reads as ResourceResult | mcp.types.CreateTaskResult:

  • FastMCPProviderResource._read and FastMCPProviderResourceResourceTemplate._read simply delegate to the nested server’s read_resource, preserving ResourceResult | CreateTaskResult without re-wrapping.
  • FastMCPProviderResourceTemplate.read (Docket background path) correctly expects a concrete ResourceResult and defensively raises if a nested server ever returns CreateTaskResult there, which would violate the contract.

This aligns the provider layer with the new ResourceResult-centric API and task routing model.

Also applies to: 143-156, 272-275, 287-304, 308-325

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

1783-1804: Update @resource decorator docs to match new ResourceResult behavior

The FastMCP.resource decorator docstring still says “other types will be converted to JSON”. With ResourceResult now only accepting str | bytes | list[ResourceContent], unwrapped dict/list returns will raise TypeError via Resource.convert_result(ResourceResult(raw_value)).

Consider updating this docstring to clarify that structured data must be:

  • explicitly serialized (e.g., json.dumps()), or
  • wrapped via ResourceContent/ResourceResult.

This will help users align their resource return types with the stricter runtime contract.

src/fastmcp/resources/resource.py (1)

37-93: ResourceContent / ResourceResult pipeline cleanly enforces the new typing contract

  • ResourceContent.__init__ now takes Any and centralizes auto-serialization:
    • str/bytes pass through with sensible default MIME types.
    • All other types (dict/list/BaseModel/etc.) are JSON-serialized via pydantic_core.to_json(...).decode() and default to application/json.
  • ResourceResult is now strictly constructed from str | bytes | list[ResourceContent], with an explicit TypeError if a list contains non‑ResourceContent items or if the top-level type is unsupported. This is exactly where dict/bare-list inputs are rejected.
  • Resource.convert_result and _read ensure that:
    • subclasses can keep read() simple (str | bytes | ResourceResult),
    • all public and MCP-facing paths see a normalized ResourceResult.

This matches the PR’s goal of pushing implicit dict/list handling into ResourceContent and making ResourceResult strongly typed.

Also applies to: 122-212, 281-289, 293-307, 309-333

src/fastmcp/resources/template.py (1)

24-25: Templates now match ResourceResult-centered behavior of resources

  • ResourceTemplate.read and FunctionResourceTemplate.read both advertise str | bytes | ResourceResult, with normalization centralized in convert_result, mirroring the Resource class.
  • Both _read implementations (ResourceTemplate and FunctionResourceTemplate) now return ResourceResult | CreateTaskResult and consistently apply convert_result to whatever their read returns (or to Docket task results).
  • FunctionResourceTemplate.create_resource uses an async resource_read_fn that simply calls self.read(arguments=params) and returns its str | bytes | ResourceResult output, leaving normalization to downstream callers.

This keeps templates and concrete resources aligned on the stricter ResourceResult contract.

Also applies to: 158-162, 164-179, 180-211, 297-320, 325-329, 340-369

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f5a177 and 47fbaee.

⛔ Files ignored due to path filters (21)
  • tests/client/test_client.py is excluded by none and included by none
  • tests/client/test_sse.py is excluded by none and included by none
  • tests/client/test_streamable_http.py is excluded by none and included by none
  • tests/deprecated/test_import_server.py is excluded by none and included by none
  • tests/resources/test_file_resources.py is excluded by none and included by none
  • tests/resources/test_function_resources.py is excluded by none and included by none
  • tests/resources/test_resource_template.py is excluded by none and included by none
  • tests/resources/test_resources.py is excluded by none and included by none
  • tests/server/http/test_http_dependencies.py is excluded by none and included by none
  • tests/server/middleware/test_caching.py is excluded by none and included by none
  • tests/server/middleware/test_middleware.py is excluded by none and included by none
  • tests/server/middleware/test_tool_injection.py is excluded by none and included by none
  • tests/server/providers/test_fastmcp_provider.py is excluded by none and included by none
  • tests/server/providers/test_local_provider_resources.py is excluded by none and included by none
  • tests/server/providers/test_local_provider_tools.py is excluded by none and included by none
  • tests/server/proxy/test_proxy_server.py is excluded by none and included by none
  • tests/server/tasks/test_task_mount.py is excluded by none and included by none
  • tests/server/tasks/test_task_return_types.py is excluded by none and included by none
  • tests/server/test_file_server.py is excluded by none and included by none
  • tests/server/test_mount.py is excluded by none and included by none
  • v3-notes/resource-internal-types.md is excluded by none and included by none
📒 Files selected for processing (15)
  • docs/servers/resources.mdx
  • src/fastmcp/resources/__init__.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/resources/template.py
  • src/fastmcp/resources/types.py
  • src/fastmcp/server/context.py
  • src/fastmcp/server/low_level.py
  • src/fastmcp/server/middleware/caching.py
  • src/fastmcp/server/middleware/middleware.py
  • src/fastmcp/server/middleware/tool_injection.py
  • src/fastmcp/server/providers/fastmcp_provider.py
  • src/fastmcp/server/providers/openapi/components.py
  • src/fastmcp/server/providers/proxy.py
  • src/fastmcp/server/server.py
  • src/fastmcp/server/tasks/requests.py
🧰 Additional context used
📓 Path-based instructions (4)
src/fastmcp/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/server/middleware/middleware.py
  • src/fastmcp/server/providers/openapi/components.py
  • src/fastmcp/server/tasks/requests.py
  • src/fastmcp/server/low_level.py
  • src/fastmcp/server/middleware/tool_injection.py
  • src/fastmcp/resources/types.py
  • src/fastmcp/server/providers/proxy.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/server/middleware/caching.py
  • src/fastmcp/server/context.py
  • src/fastmcp/server/server.py
  • src/fastmcp/server/providers/fastmcp_provider.py
  • src/fastmcp/resources/template.py
  • src/fastmcp/resources/__init__.py
docs/**/*.mdx

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

docs/**/*.mdx: Use clear, direct language appropriate for technical audiences
Write in second person ('you') for instructions and procedures in MDX documentation
Use active voice over passive voice in MDX technical documentation
Employ present tense for current states and future tense for outcomes in MDX documentation
Maintain consistent terminology throughout all MDX documentation
Keep sentences concise while providing necessary context in MDX documentation
Use parallel structure in lists, headings, and procedures in MDX documentation
Lead with the most important information using inverted pyramid structure in MDX documentation
Use progressive disclosure in MDX documentation: present basic concepts before advanced ones
Break complex procedures into numbered steps in MDX documentation
Include prerequisites and context before instructions in MDX documentation
Provide expected outcomes for each major step in MDX documentation
End sections with next steps or related information in MDX documentation
Use descriptive, keyword-rich headings for navigation and SEO in MDX documentation
Focus on user goals and outcomes rather than system features in MDX documentation
Anticipate common questions and address them proactively in MDX documentation
Include troubleshooting for likely failure points in MDX documentation
Provide multiple pathways (beginner vs advanced) but offer an opinionated path to avoid overwhelming users in MDX documentation
Always include complete, runnable code examples that users can copy and execute in MDX documentation
Show proper error handling and edge case management in MDX code examples
Use realistic data instead of placeholder values in MDX code examples
Include expected outputs and results for verification in MDX code examples
Test all code examples thoroughly before publishing in MDX documentation
Specify language and include filename when relevant in MDX code examples
Add explanatory comments for complex logic in MDX code examples
Document all API...

Files:

  • docs/servers/resources.mdx
docs/**/*.{md,mdx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Documentation uses Mintlify framework. Files must be in docs.json to be included. Never modify docs/python-sdk/** (auto-generated)

Files:

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

📄 CodeRabbit inference engine (AGENTS.md)

docs/**/*.{md,mdx}: Code examples in documentation must explain before showing code and make blocks fully runnable (include imports)
Documentation structure: Headers form navigation guide with logical H2/H3 hierarchy. Content should be user-focused with sections motivating features (why) before mechanics (how). Use prose over code comments for important information
Never use 'This isn't...' or 'not just...' constructions in writing - state what something IS directly. Avoid defensive writing patterns

Files:

  • docs/servers/resources.mdx
🧠 Learnings (4)
📚 Learning: 2025-12-25T15:53:07.646Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.646Z
Learning: Applies to src/fastmcp/**/*.py : Python ≥ 3.10 with full type annotations required

Applied to files:

  • src/fastmcp/server/middleware/middleware.py
  • src/fastmcp/server/providers/openapi/components.py
  • src/fastmcp/server/low_level.py
  • src/fastmcp/resources/types.py
  • src/fastmcp/server/providers/proxy.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/server/middleware/caching.py
  • src/fastmcp/server/context.py
  • src/fastmcp/server/server.py
  • src/fastmcp/server/providers/fastmcp_provider.py
📚 Learning: 2025-12-25T15:53:07.646Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.646Z
Learning: Applies to src/fastmcp/**/*.py : Never use bare except - be specific with exception types

Applied to files:

  • src/fastmcp/resources/types.py
📚 Learning: 2025-11-26T21:51:44.174Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2025-11-26T21:51:44.174Z
Learning: Note that Resources and Resource Templates are distinct objects but both handled by ResourceManager, requiring coordinated updates when changes affect either object type

Applied to files:

  • src/fastmcp/resources/template.py
📚 Learning: 2025-12-25T15:53:07.646Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.646Z
Learning: Applies to src/fastmcp/**/*.py : Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types

Applied to files:

  • src/fastmcp/resources/__init__.py
🧬 Code graph analysis (14)
src/fastmcp/server/middleware/middleware.py (1)
src/fastmcp/resources/resource.py (2)
  • Resource (214-388)
  • ResourceResult (122-211)
src/fastmcp/server/providers/openapi/components.py (3)
src/fastmcp/resources/resource.py (5)
  • Resource (214-388)
  • ResourceContent (37-119)
  • ResourceResult (122-211)
  • read (281-291)
  • read (453-467)
src/fastmcp/resources/template.py (3)
  • ResourceTemplate (97-289)
  • read (158-162)
  • read (340-369)
src/fastmcp/resources/types.py (5)
  • read (26-30)
  • read (38-42)
  • read (83-94)
  • read (106-115)
  • read (160-172)
src/fastmcp/server/tasks/requests.py (2)
src/fastmcp/resources/resource.py (3)
  • convert_result (293-307)
  • to_mcp_result (198-211)
  • Resource (214-388)
src/fastmcp/resources/template.py (1)
  • convert_result (164-178)
src/fastmcp/server/low_level.py (1)
src/fastmcp/client/tasks.py (4)
  • result (202-207)
  • result (336-393)
  • result (427-458)
  • result (497-551)
src/fastmcp/server/middleware/tool_injection.py (1)
src/fastmcp/resources/resource.py (1)
  • ResourceResult (122-211)
src/fastmcp/resources/types.py (1)
src/fastmcp/resources/resource.py (5)
  • Resource (214-388)
  • ResourceContent (37-119)
  • ResourceResult (122-211)
  • read (281-291)
  • read (453-467)
src/fastmcp/server/providers/proxy.py (1)
src/fastmcp/resources/resource.py (4)
  • ResourceContent (37-119)
  • ResourceResult (122-211)
  • read (281-291)
  • read (453-467)
src/fastmcp/resources/resource.py (4)
src/fastmcp/resources/types.py (5)
  • read (26-30)
  • read (38-42)
  • read (83-94)
  • read (106-115)
  • read (160-172)
src/fastmcp/server/providers/fastmcp_provider.py (1)
  • read (308-324)
src/fastmcp/server/providers/openapi/components.py (1)
  • read (193-272)
src/fastmcp/server/providers/proxy.py (1)
  • read (208-242)
src/fastmcp/server/middleware/caching.py (2)
src/fastmcp/resources/resource.py (3)
  • Resource (214-388)
  • ResourceContent (37-119)
  • ResourceResult (122-211)
src/fastmcp/server/middleware/middleware.py (1)
  • CallNext (42-43)
src/fastmcp/server/context.py (2)
src/fastmcp/server/server.py (2)
  • resource (1783-1865)
  • read_resource (1195-1287)
src/fastmcp/resources/resource.py (1)
  • ResourceResult (122-211)
src/fastmcp/server/server.py (2)
src/fastmcp/resources/resource.py (3)
  • Resource (214-388)
  • ResourceResult (122-211)
  • to_mcp_result (198-211)
src/fastmcp/utilities/components.py (1)
  • make_key (80-91)
src/fastmcp/server/providers/fastmcp_provider.py (3)
src/fastmcp/server/server.py (2)
  • resource (1783-1865)
  • read_resource (1195-1287)
src/fastmcp/resources/resource.py (5)
  • Resource (214-388)
  • ResourceResult (122-211)
  • _read (309-332)
  • read (281-291)
  • read (453-467)
src/fastmcp/resources/types.py (5)
  • read (26-30)
  • read (38-42)
  • read (83-94)
  • read (106-115)
  • read (160-172)
src/fastmcp/resources/template.py (1)
src/fastmcp/resources/resource.py (6)
  • Resource (214-388)
  • ResourceResult (122-211)
  • read (281-291)
  • read (453-467)
  • convert_result (293-307)
  • _read (309-332)
src/fastmcp/resources/__init__.py (1)
src/fastmcp/resources/resource.py (3)
  • Resource (214-388)
  • ResourceContent (37-119)
  • ResourceResult (122-211)
🪛 Ruff (0.14.10)
src/fastmcp/resources/resource.py

189-192: Avoid specifying long messages outside the exception class

(TRY003)


194-196: Avoid specifying long messages outside the exception class

(TRY003)

src/fastmcp/server/server.py

1249-1249: Consider moving this statement to an else block

(TRY300)


1273-1273: Consider moving this statement to an else block

(TRY300)

src/fastmcp/server/providers/fastmcp_provider.py

322-322: Prefer TypeError exception for invalid type

(TRY004)


322-322: Avoid specifying long messages outside the exception class

(TRY003)

⏰ 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). (4)
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (21)
docs/servers/resources.mdx (2)

42-50: LGTM!

Good update demonstrating the explicit json.dumps() serialization pattern for returning structured data as JSON strings. This aligns with the stricter typing requirements and will help users understand the expected approach.


137-196: LGTM!

The ResourceResult documentation is clear and comprehensive:

  • Explains the three accepted types (str | bytes | list[ResourceContent])
  • Shows both simple and advanced usage patterns
  • Documents both ResourceResult and ResourceContent parameters with appropriate types

The note about serializing structured data with json.dumps() on lines 143-145 effectively communicates the rationale for this design choice.

src/fastmcp/server/middleware/tool_injection.py (2)

12-12: LGTM!

Import correctly updated to use ResourceResult from fastmcp.resources.resource.


98-103: LGTM!

The return type annotation is correctly updated to ResourceResult, and the implementation properly delegates to context.read_resource() which returns ResourceResult. The type flow is consistent.

src/fastmcp/server/context.py (2)

41-41: LGTM!

Import correctly updated to ResourceResult.


313-327: LGTM!

The read_resource method is correctly updated:

  • Return type annotation changed to ResourceResult
  • Docstring accurately reflects the new return type
  • Implementation properly handles the CreateTaskResult check before returning

This aligns with the broader PR goal of consistent ResourceResult typing throughout the codebase.

src/fastmcp/server/tasks/requests.py (1)

325-335: LGTM!

The resource result handling is now consistent with the ResourceResult pattern:

  1. Uses component.convert_result(raw_value) to normalize to ResourceResult
  2. Calls to_mcp_result() with the appropriate URI (uri_template for templates, str(component.uri) for resources)
  3. Attaches related-task metadata consistently with Tool and Prompt branches

The pattern mirrors the implementation in src/fastmcp/resources/resource.py lines 197-210 for the to_mcp_result conversion.

src/fastmcp/resources/__init__.py (1)

1-22: LGTM!

ResourceResult is appropriately exported as part of the public API:

  • It's a core type defining the module's purpose (canonical result type for resource reads)
  • The __all__ list maintains alphabetical ordering for consistency
  • This aligns with the coding guideline to be intentional about re-exports—core types should be exported while specialized features can remain in submodules.

Based on learnings, this export is appropriate since ResourceResult is a fundamental type for the resources module.

src/fastmcp/server/middleware/middleware.py (2)

21-21: LGTM!

Import correctly updated to include both Resource (used in on_list_resources) and ResourceResult (used in on_read_resource).


163-168: LGTM!

The on_read_resource middleware hook signature is correctly updated:

  • CallNext return type changed from Sequence[ResourceContent] to ResourceResult
  • Method return type changed to ResourceResult

This provides a cleaner API for middleware implementers—they now work with a single ResourceResult object rather than a sequence, which better represents the semantic of reading a resource.

src/fastmcp/server/providers/openapi/components.py (2)

14-19: LGTM!

Import block correctly updated to include ResourceContent and ResourceResult from fastmcp.resources. This follows the cleaner import pattern from the package's public API.


193-257: LGTM!

The read() method is correctly updated to return ResourceResult:

  • Return type annotation is properly set to ResourceResult
  • All three content branches wrap content appropriately:
    • JSON: json.dumps(result) for explicit serialization
    • Text/XML: response.text (str)
    • Binary: response.content (bytes)
  • Each branch correctly constructs ResourceResult(contents=[ResourceContent(...)])
  • MIME type handling is preserved

This aligns with the patterns established in src/fastmcp/resources/types.py.

src/fastmcp/resources/types.py (5)

15-15: LGTM!

Import correctly updated to include ResourceResult alongside Resource and ResourceContent.


26-42: LGTM!

TextResource.read() and BinaryResource.read() are correctly updated:

  • Return type annotations set to ResourceResult
  • Content properly wrapped in ResourceContent within a single-element contents list
  • MIME types correctly passed through from the resource's mime_type attribute

82-94: LGTM!

FileResource.read() is correctly updated:

  • Return type annotation set to ResourceResult
  • Both binary and text content paths properly construct ResourceResult
  • Exception handling uses except Exception (not bare except) and wraps errors in ResourceError, complying with coding guidelines

105-115: LGTM!

HttpResource.read() is correctly updated with consistent ResourceResult construction pattern.


159-172: LGTM!

DirectoryResource.read() is correctly updated:

  • Returns ResourceResult with JSON content representing the file listing
  • Exception handling follows the established pattern with ResourceError wrapping
src/fastmcp/server/low_level.py (1)

213-220: Updated read_resource decorator typing is consistent with new MCP result flow

The decorator’s type hints and handler now correctly treat the wrapped callable as returning mcp.types.ReadResourceResult | mcp.types.CreateTaskResult and simply wrap that in ServerResult. This matches _read_resource_mcp and keeps MCP conversion centralized at the server layer. No changes needed.

Also applies to: 231-239, 241-245

src/fastmcp/server/middleware/caching.py (1)

21-22: ResourceResult-aware caching is symmetric and respects stricter typing

  • CachableResourceResult.wrap/unwrap correctly round-trip ResourceResult by cloning each ResourceContent and reconstructing with contents: list[ResourceContent], which is compatible with the new ResourceResult constructor.
  • _read_resource_cache and on_read_resource now operate purely on ResourceResult, and both cached and fresh values are returned via unwrap(), preserving the “no-op clone” semantics of this middleware.

Looks consistent with the new ResourceResult contract.

Also applies to: 37-75, 227-233, 395-422

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

69-70: Core read_resource / _read_resource_mcp flow is consistent with ResourceResult contract

  • FastMCP.read_resource now consistently returns ResourceResult | mcp.types.CreateTaskResult from both middleware and non-middleware paths, relying on Resource._read / ResourceTemplate._read to normalize via Resource.convert_result / ResourceTemplate.convert_result.
  • _read_resource_mcp correctly:
    • Pulls SEP‑1686 task metadata from self._mcp_server.request_context.
    • Sets _task_metadata and _docket_fn_key(Resource.make_key(str(uri))) so downstream resource/task routing sees the right key.
    • Calls read_resource(str(uri)) and converts a ResourceResult to mcp.types.ReadResourceResult via to_mcp_result(uri), while passing through CreateTaskResult unchanged.

This cleanly separates internal ResourceResult usage from the external MCP ReadResourceResult type.

Also applies to: 1195-1214, 1219-1239, 1241-1250, 1263-1274, 1539-1554, 1557-1579

src/fastmcp/server/providers/proxy.py (1)

39-40: Proxy resources correctly normalize remote MCP contents into ResourceResult

  • ProxyResource.read now returns a ResourceResult, translating the first TextResourceContents/BlobResourceContents from the remote server into a single ResourceContent inside contents=[...].
  • ProxyTemplate.create_resource eagerly reads once from the backend, builds a cached_content: ResourceResult, and injects it into the returned ProxyResource, so template-instantiated proxies have a stable snapshot.
  • All ResourceResult constructions use contents=[ResourceContent(...)], which is compatible with the stricter ResourceResult constructor.

No functional issues spotted with the new ResourceResult-centric behavior.

Also applies to: 157-170, 208-240, 314-347

@jlowin jlowin added v3 Targeted for FastMCP 3 and removed breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. labels Dec 26, 2025
@jlowin jlowin changed the title Enforce strict typing for ResourceResult Introduce ResourceResult as canonical resource return type Dec 26, 2025
@jlowin jlowin added feature Major new functionality. Reserved for 2-4 significant PRs per release. Not for issues. and removed enhancement Improvement to existing functionality. For issues and smaller PR improvements. labels Dec 26, 2025
@jlowin jlowin merged commit 648684d into main Dec 26, 2025
10 checks passed
@jlowin jlowin deleted the resourceresult-cleanup branch December 26, 2025 02:21
jlowin added a commit that referenced this pull request Dec 26, 2025
Move task metadata extraction and MCP conversion from
LowLevelServer.get_prompt() decorator into FastMCP._get_prompt_mcp(),
matching the resource handler architecture from PR #2734.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Major new functionality. Reserved for 2-4 significant PRs per release. Not for issues. server Related to FastMCP server implementation or server-side functionality. v3 Targeted for FastMCP 3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments