Skip to content

Refactor resource behavior and add meta support#2598

Merged
jlowin merged 4 commits intomainfrom
resource-content-canonical
Dec 13, 2025
Merged

Refactor resource behavior and add meta support#2598
jlowin merged 4 commits intomainfrom
resource-content-canonical

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Dec 12, 2025

IMPORTANT: this was merged and reverted. See #2609 for new PR


Resources can now include metadata via ResourceContent.meta, giving resource authors a way to pass structured information alongside content.

@mcp.resource("data://report")
def get_report() -> ResourceContent:
    return ResourceContent(
        content="Report data...",
        mime_type="text/plain",
        meta={"generated_at": "2024-01-15", "version": 2}
    )

Internally, this represents a significant refactor of resource result handling, with ResourceContent now the canonical type for all resource reads. Custom resources returning str or bytes from read() continue to work with a deprecation warning when enabled.

Add Resource._read() private method that always returns ResourceContent,
maintaining backwards compatibility for custom resources returning str/bytes
from read(). Includes deprecation warning when str/bytes is returned.
@jlowin jlowin changed the title feat: make ResourceContent the canonical internal type for resources feat: add meta support to ResourceContent Dec 12, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 12, 2025

Walkthrough

A new ResourceContent model was added to encapsulate resource content, mime_type, and runtime meta. Resource.read implementations were standardized to return ResourceContent (with a new internal Resource._read helper and deprecation of plain str/bytes). The resources package now exports ResourceContent and ResourceManager. Server, proxy, middleware, caching, tooling, OpenAPI, task converters, and documentation were updated to produce, consume, cache, and serialize ResourceContent instead of raw text/bytes or ReadResourceContents.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description provides a clear overview of the feature (metadata support via ResourceContent.meta) with a practical example. However, the Contributors Checklist items are not marked, which are required per the repository's description template. Complete the Contributors Checklist by checking off all applicable items (especially the issue reference, workflow adherence, testing, and documentation updates) to meet repository requirements.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately captures the main feature being added: metadata support to ResourceContent, which aligns with the comprehensive resource handling refactor shown in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 81.40% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch resource-content-canonical

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6276c54 and 4f95f95.

📒 Files selected for processing (1)
  • src/fastmcp/server/proxy.py (8 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bare except - be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code organization and style

Files:

  • src/fastmcp/server/proxy.py
🧠 Learnings (1)
📓 Common learnings
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
🧬 Code graph analysis (1)
src/fastmcp/server/proxy.py (3)
src/fastmcp/resources/resource.py (3)
  • ResourceContent (35-130)
  • read (216-228)
  • read (354-373)
src/fastmcp/resources/resource_manager.py (1)
  • read_resource (289-347)
src/fastmcp/server/middleware/tool_injection.py (1)
  • read_resource (98-103)
🪛 Ruff (0.14.8)
src/fastmcp/server/proxy.py

200-202: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


457-459: 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). (3)
  • GitHub Check: Run tests with lowest-direct dependencies
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
🔇 Additional comments (3)
src/fastmcp/server/proxy.py (3)

3-3: LGTM: Necessary imports added.

The base64 import is used for decoding blob content from remote servers, and ResourceContent import provides the new canonical type for resource reads.

Also applies to: 35-35


189-218: Excellent implementation of ResourceContent support with proper error handling.

This correctly addresses previous review concerns:

  • Empty result lists are now handled (lines 199-202)
  • BlobResourceContents.blob is properly base64-decoded from string to bytes (line 211)
  • Meta is preserved for both text and blob content

The implementation correctly wraps both TextResourceContents and BlobResourceContents in the canonical ResourceContent type.


349-349: Excellent refactoring that resolves previous meta and type information concerns.

The change from _value: str | bytes | None to _cached_content: ResourceContent | None correctly addresses the previous review's concerns about:

  • Runtime meta being lost on the fast-path (line 386-387 now returns full ResourceContent with meta)
  • Blob type information being indistinguishable from text when cached

The implementation consistently handles empty results, base64 decoding for blobs, and meta preservation.

Also applies to: 355-355, 360-360, 384-406


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
Copy Markdown
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: 4

🧹 Nitpick comments (5)
src/fastmcp/server/context.py (1)

38-38: Type update for Context.read_resource() is aligned with the new canonical content type.

Optional: consider rewording the Line 287 comment to clarify why the context path returns a list (protocol shape) vs internal single-item canonicalization, if that distinction matters to readers.

Also applies to: 278-289

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

190-272: Potential MIME type mismatch for text/XML responses.

When the response content-type is text/* or application/xml, the code uses self.mime_type (which defaults to "application/json" per line 168) rather than the actual response content-type. This could result in an XML response being labeled as JSON.

Consider using the actual content-type from the response header for non-JSON text responses:

             if "application/json" in content_type:
                 result = response.json()
                 return ResourceContent(
                     content=json.dumps(result), mime_type="application/json"
                 )
             elif any(ct in content_type for ct in ["text/", "application/xml"]):
-                return ResourceContent(content=response.text, mime_type=self.mime_type)
+                # Use actual content-type or fallback to configured mime_type
+                actual_mime = content_type.split(";")[0].strip() or self.mime_type
+                return ResourceContent(content=response.text, mime_type=actual_mime)
             else:
                 return ResourceContent(
                     content=response.content, mime_type=self.mime_type
                 )
src/fastmcp/server/server.py (1)

1706-1711: Mutating content.mime_type in-place could cause side effects.

The code directly mutates content.mime_type on the ResourceContent object returned from read_resource(). If that same object is cached or reused elsewhere, this mutation could cause unexpected behavior.

Consider creating a new ResourceContent instance instead of mutating:

                 content = await self._resource_manager.read_resource(uri_str)
                 # read_resource() always returns ResourceContent now
                 # Use mime_type from ResourceContent if set, otherwise from resource
                 if content.mime_type is None:
-                    content.mime_type = resource.mime_type
+                    content = ResourceContent(
+                        content=content.content,
+                        mime_type=resource.mime_type,
+                        meta=content.meta,
+                    )
                 return [content]

Alternatively, use Pydantic's model_copy:

content = content.model_copy(update={"mime_type": resource.mime_type})
src/fastmcp/server/tasks/converters.py (1)

151-156: Unused server parameter.

The server parameter is not used in this function (also flagged by Ruff ARG001). If kept for API consistency with convert_tool_result and convert_prompt_result, consider prefixing with underscore to indicate intentional non-use.

 async def convert_resource_result(
-    server: FastMCP,
+    server: FastMCP,  # noqa: ARG001 - kept for API consistency
     raw_value: str | bytes | ResourceContent,
     uri: str,
     client_task_id: str,
 ) -> dict[str, Any]:

Or alternatively:

 async def convert_resource_result(
-    server: FastMCP,
+    _server: FastMCP,
     raw_value: str | bytes | ResourceContent,
     uri: str,
     client_task_id: str,
 ) -> dict[str, Any]:
src/fastmcp/resources/resource.py (1)

229-248: Review stacklevel for deprecation warning.

The stacklevel=2 points to the caller of _read() (likely ResourceManager), not to the user's read() implementation that actually returns the deprecated type. Consider whether this provides useful information to the user trying to locate the deprecated code.

A higher stacklevel (e.g., 3 or 4) might better point to user code, but this depends on the call chain depth. Alternatively, the warning message already includes the class name and URI which helps identify the source.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d6fd46 and b3ce61a.

⛔ Files ignored due to path filters (6)
  • 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_manager.py is excluded by none and included by none
  • tests/resources/test_resource_template.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/test_server.py is excluded by none and included by none
📒 Files selected for processing (14)
  • docs/servers/resources.mdx (1 hunks)
  • src/fastmcp/__init__.py (2 hunks)
  • src/fastmcp/resources/__init__.py (2 hunks)
  • src/fastmcp/resources/resource.py (4 hunks)
  • src/fastmcp/resources/resource_manager.py (3 hunks)
  • src/fastmcp/resources/types.py (5 hunks)
  • src/fastmcp/server/context.py (2 hunks)
  • src/fastmcp/server/middleware/caching.py (5 hunks)
  • src/fastmcp/server/middleware/middleware.py (2 hunks)
  • src/fastmcp/server/middleware/tool_injection.py (2 hunks)
  • src/fastmcp/server/openapi/components.py (3 hunks)
  • src/fastmcp/server/proxy.py (4 hunks)
  • src/fastmcp/server/server.py (8 hunks)
  • src/fastmcp/server/tasks/converters.py (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bare except - be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code organization and style

Files:

  • src/fastmcp/server/context.py
  • src/fastmcp/server/middleware/tool_injection.py
  • src/fastmcp/server/server.py
  • src/fastmcp/server/tasks/converters.py
  • src/fastmcp/resources/resource_manager.py
  • src/fastmcp/__init__.py
  • src/fastmcp/server/middleware/middleware.py
  • src/fastmcp/server/middleware/caching.py
  • src/fastmcp/server/proxy.py
  • src/fastmcp/resources/types.py
  • src/fastmcp/server/openapi/components.py
  • src/fastmcp/resources/resource.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
🧠 Learnings (1)
📚 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/__init__.py
🧬 Code graph analysis (11)
src/fastmcp/server/context.py (3)
src/fastmcp/resources/resource.py (1)
  • ResourceContent (35-129)
src/fastmcp/server/middleware/tool_injection.py (1)
  • read_resource (98-103)
src/fastmcp/server/proxy.py (1)
  • read_resource (188-213)
src/fastmcp/server/middleware/tool_injection.py (1)
src/fastmcp/resources/resource.py (1)
  • ResourceContent (35-129)
src/fastmcp/server/tasks/converters.py (1)
src/fastmcp/resources/resource.py (2)
  • ResourceContent (35-129)
  • from_value (64-103)
src/fastmcp/resources/resource_manager.py (1)
src/fastmcp/resources/resource.py (3)
  • Resource (132-282)
  • ResourceContent (35-129)
  • _read (229-248)
src/fastmcp/__init__.py (1)
src/fastmcp/resources/resource.py (1)
  • ResourceContent (35-129)
src/fastmcp/server/middleware/middleware.py (1)
src/fastmcp/resources/resource.py (2)
  • Resource (132-282)
  • ResourceContent (35-129)
src/fastmcp/server/proxy.py (3)
src/fastmcp/resources/resource.py (3)
  • ResourceContent (35-129)
  • read (215-227)
  • read (353-372)
src/fastmcp/resources/resource_manager.py (1)
  • read_resource (289-347)
src/fastmcp/resources/types.py (5)
  • read (26-28)
  • read (36-38)
  • read (79-88)
  • read (100-105)
  • read (150-160)
src/fastmcp/resources/types.py (2)
src/fastmcp/resources/resource.py (4)
  • Resource (132-282)
  • ResourceContent (35-129)
  • read (215-227)
  • read (353-372)
src/fastmcp/server/openapi/components.py (1)
  • read (190-290)
src/fastmcp/server/openapi/components.py (1)
src/fastmcp/resources/resource.py (4)
  • Resource (132-282)
  • ResourceContent (35-129)
  • read (215-227)
  • read (353-372)
src/fastmcp/resources/resource.py (2)
src/fastmcp/server/dependencies.py (1)
  • without_injected_parameters (85-139)
src/fastmcp/server/openapi/components.py (1)
  • read (190-290)
src/fastmcp/resources/__init__.py (2)
src/fastmcp/resources/resource.py (3)
  • FunctionResource (285-372)
  • Resource (132-282)
  • ResourceContent (35-129)
src/fastmcp/resources/resource_manager.py (1)
  • ResourceManager (25-347)
🪛 LanguageTool
docs/servers/resources.mdx

[style] ~167-~167: To form a complete sentence, be sure to include a subject.
Context: ...ntent** - The actual resource content. Can be str(text content) orbytes` (bin...

(MISSING_IT_THERE)

🪛 Ruff (0.14.8)
src/fastmcp/server/tasks/converters.py

152-152: Unused function argument: server

(ARG001)

⏰ 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). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest
🔇 Additional comments (24)
src/fastmcp/__init__.py (1)

22-23: Public API export for ResourceContent looks correct.

Also applies to: 32-38

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

21-22: on_read_resource now cleanly carries ResourceContent through the middleware pipeline.

Also applies to: 163-168

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

12-13: Import and typing changes are correct and consistent. The ResourceContent return type annotation is properly typed and follows Python 3.10+ requirements with full type annotations. Pydantic v2.11.x bytes fields (without explicit ser_json_bytes configuration) serialize as UTF-8 strings by default, which is consistent with both the JSON schema generation and runtime serialization via pydantic_core.to_jsonable_python(). The code is readable, follows existing patterns, and requires no changes.

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

12-12: LGTM!

The import correctly adds ResourceContent alongside existing Resource and ResourceTemplate imports from the fastmcp.resources package.

src/fastmcp/resources/resource_manager.py (2)

14-14: LGTM!

Import correctly brings in ResourceContent alongside Resource from the resource module.


289-328: LGTM! Correct implementation of ResourceContent as canonical type.

The changes properly implement the new pattern:

  • Return type updated to ResourceContent
  • Docstring accurately describes the new behavior
  • Internal calls use resource._read() which wraps legacy str/bytes returns with deprecation warnings
  • Both concrete resources and template-created resources follow the same path

This ensures uniform handling throughout the read pipeline.

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

65-65: LGTM!

Import correctly adds ResourceContent to the existing imports from fastmcp.resources.resource.


738-757: LGTM!

The conversion from ResourceContent to MCP contents using to_mcp_resource_contents(uri) is correctly applied in both the task metadata path and the normal response path.


1627-1638: LGTM!

Return type correctly updated to list[ResourceContent] and the explicit cast ensures type safety.


1646-1673: LGTM!

Return types for _read_resource_middleware and _read_resource are consistently updated to list[ResourceContent].

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

1-23: LGTM! Public API correctly updated.

The changes properly expose ResourceContent and ResourceManager as part of the public API:

  • Imports are consolidated and clean
  • __all__ list maintains alphabetical ordering
  • This enables users to import ResourceContent directly from fastmcp.resources
src/fastmcp/resources/types.py (6)

15-15: LGTM!

Import correctly adds ResourceContent alongside Resource from the resource module.


26-28: LGTM!

TextResource.read() correctly returns ResourceContent wrapping the text content with the configured mime_type.


36-38: LGTM!

BinaryResource.read() correctly returns ResourceContent wrapping the binary data with the configured mime_type.


79-86: LGTM!

FileResource.read() correctly handles both binary and text content paths, wrapping the result in ResourceContent with the appropriate mime_type.


100-105: LGTM!

HttpResource.read() correctly returns ResourceContent wrapping the HTTP response text with the configured mime_type.


150-158: LGTM!

DirectoryResource.read() correctly returns ResourceContent wrapping the JSON directory listing with the configured mime_type (defaults to application/json).

src/fastmcp/server/tasks/converters.py (2)

14-15: LGTM on import changes.

The ResourceContent import aligns with the PR's goal of making it the canonical internal type for resource reads.


175-203: LGTM on the ResourceContent normalization and content building logic.

The implementation correctly:

  • Normalizes input to ResourceContent via from_value
  • Extracts and propagates content, MIME type, and metadata
  • Applies correct default MIME types matching ResourceContent behavior
  • Preserves both content metadata (_meta) and related-task metadata separately
src/fastmcp/resources/resource.py (3)

35-103: Well-designed ResourceContent model with comprehensive factory method.

The from_value class method correctly handles the conversion hierarchy:

  • ResourceContent → pass-through
  • str → text content with text/plain default
  • bytes → binary content with application/octet-stream default
  • Other types → JSON serialization with application/json default

This provides a clean upgrade path from legacy str/bytes returns.


105-129: LGTM on MCP conversion method.

The to_mcp_resource_contents method correctly produces the appropriate MCP type based on content type, with proper base64 encoding for binary content and consistent MIME type defaults.


215-227: Good backward-compatible return type with clear deprecation guidance.

The str | bytes | ResourceContent union return type allows existing subclasses to continue working while the docstring clearly communicates the deprecation path.

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

37-65: LGTM on CachableReadResourceContents updates.

The wrapper class correctly:

  • Adds the meta field to match ResourceContent
  • Preserves all fields through wrap()/unwrap() round-trips
  • Uses correct type annotations (Sequence[ResourceContent], list[ResourceContent])

388-416: LGTM on on_read_resource type updates.

The method signature and internal handling are correctly updated to use ResourceContent throughout the caching flow. The wrap()/unwrap() calls properly convert between ResourceContent and CachableReadResourceContents for cache storage.

Comment thread docs/servers/resources.mdx
Comment thread src/fastmcp/resources/resource.py
Comment thread src/fastmcp/server/proxy.py
Comment thread src/fastmcp/server/proxy.py
Comment thread src/fastmcp/__init__.py Outdated
- Remove ResourceContent from root exports (import from fastmcp.resources)
- Fix FunctionResource.read() return type to str | bytes | ResourceContent
- Decode base64 blobs in proxy when receiving from remote servers
- Preserve meta in ProxyResource cached content
Copy link
Copy Markdown
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)
src/fastmcp/server/proxy.py (2)

198-214: Guard against empty result list before accessing result[0].

If the remote server returns an empty content list, accessing result[0] will raise IndexError. Add a guard to handle this edge case.

             async with client:
                 result = await client.read_resource(uri)
+                if not result:
+                    raise ResourceError(f"Remote server returned empty content for {uri}")
                 if isinstance(result[0], TextResourceContents):
                     return ResourceContent(

This same pattern should be applied to the other locations that access result[0]}:

  • ProxyResource.read() at line 387
  • ProxyTemplate.create_resource() at line 448

447-476: Add empty result guard in create_resource.

Same issue as other locations - result[0] accessed without checking if result is non-empty.

         async with self._client:
             result = await self._client.read_resource(parameterized_uri)
+        if not result:
+            raise ResourceError(f"Remote server returned empty content for {parameterized_uri}")

         if isinstance(result[0], TextResourceContents):
♻️ Duplicate comments (3)
docs/servers/resources.mdx (2)

174-186: Add error handling to the binary content example.

Per the MDX documentation guidelines, code examples should include realistic error handling. The file read operation should handle FileNotFoundError.

 # Binary content with metadata
 @mcp.resource("images://logo")
 def get_logo() -> ResourceContent:
     """Returns a logo image with caching metadata."""
-    with open("logo.png", "rb") as f:
-        image_data = f.read()
+    try:
+        with open("logo.png", "rb") as f:
+            image_data = f.read()
+    except FileNotFoundError:
+        raise ResourceError("Logo file not found")
     return ResourceContent(
         content=image_data,
         mime_type="image/png",
         meta={"cache-control": "max-age=3600"}
     )

You'll also need to add the import at the top of the example:

from fastmcp.exceptions import ResourceError

Based on learnings, MDX documentation should always include realistic error handling in code examples.


168-168: Fix sentence fragment for grammar consistency.

The description is a sentence fragment. Adding "It" makes it a complete sentence.

-**`content`** - The actual resource content. Can be `str` (text content) or `bytes` (binary content). This is the data that will be returned to the client.
+**`content`** - The actual resource content. It can be `str` (text content) or `bytes` (binary content). This is the data that will be returned to the client.
src/fastmcp/resources/resource.py (1)

368-373: Use _read() for recursive Resource reads to ensure consistent return type.

When result is a Resource, calling result.read() may return str | bytes | ResourceContent, but this value is returned directly without normalization. Using result._read() would ensure the recursive read always returns ResourceContent, maintaining internal consistency.

         # If user returned another Resource, read it recursively
         if isinstance(result, Resource):
-            return await result.read()
+            return await result._read()
🧹 Nitpick comments (1)
src/fastmcp/server/tasks/converters.py (1)

152-157: Remove or prefix unused server parameter.

The server parameter is declared but never used in the function body. Either remove it if not needed for the API contract, or prefix with underscore to indicate it's intentionally unused.

 async def convert_resource_result(
-    server: FastMCP,
+    _server: FastMCP,
     raw_value: str | bytes | ResourceContent,
     uri: str,
     client_task_id: str,
 ) -> dict[str, Any]:

Alternatively, if this parameter is kept for API consistency with other converters but truly unused, add a comment explaining why.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3ce61a and 6276c54.

⛔ Files ignored due to path filters (1)
  • tests/server/test_server.py is excluded by none and included by none
📒 Files selected for processing (4)
  • docs/servers/resources.mdx (1 hunks)
  • src/fastmcp/resources/resource.py (4 hunks)
  • src/fastmcp/server/proxy.py (8 hunks)
  • src/fastmcp/server/tasks/converters.py (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bare except - be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code organization and style

Files:

  • src/fastmcp/resources/resource.py
  • src/fastmcp/server/tasks/converters.py
  • src/fastmcp/server/proxy.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
🧠 Learnings (5)
📚 Learning: 2025-11-26T21:52:08.947Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-11-26T21:52:08.947Z
Learning: Applies to docs/**/*.mdx : Always include realistic error handling in code examples in MDX documentation

Applied to files:

  • docs/servers/resources.mdx
📚 Learning: 2025-11-26T21:52:08.947Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-11-26T21:52:08.947Z
Learning: Applies to docs/**/*.mdx : Show both success and error response examples with realistic data in MDX API documentation

Applied to files:

  • docs/servers/resources.mdx
📚 Learning: 2025-11-26T21:52:08.947Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-11-26T21:52:08.947Z
Learning: Applies to docs/**/*.mdx : Show proper error handling and edge case management in MDX code examples

Applied to files:

  • docs/servers/resources.mdx
📚 Learning: 2025-11-26T21:52:08.947Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-11-26T21:52:08.947Z
Learning: Applies to docs/**/*.mdx : Explain all HTTP status codes and error handling in MDX API documentation

Applied to files:

  • docs/servers/resources.mdx
📚 Learning: 2025-11-26T21:52:08.947Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-11-26T21:52:08.947Z
Learning: Applies to docs/**/*.mdx : Add appropriate warnings for destructive or security-sensitive actions in MDX documentation

Applied to files:

  • docs/servers/resources.mdx
🧬 Code graph analysis (3)
src/fastmcp/resources/resource.py (3)
src/fastmcp/server/dependencies.py (1)
  • without_injected_parameters (85-139)
src/fastmcp/utilities/types.py (1)
  • get_fn_name (34-35)
src/fastmcp/resources/types.py (5)
  • read (26-28)
  • read (36-38)
  • read (79-88)
  • read (100-105)
  • read (150-160)
src/fastmcp/server/tasks/converters.py (1)
src/fastmcp/resources/resource.py (2)
  • ResourceContent (35-130)
  • from_value (65-104)
src/fastmcp/server/proxy.py (4)
src/fastmcp/resources/resource.py (3)
  • ResourceContent (35-130)
  • read (216-228)
  • read (354-373)
src/fastmcp/resources/resource_manager.py (1)
  • read_resource (289-347)
src/fastmcp/server/middleware/tool_injection.py (1)
  • read_resource (98-103)
src/fastmcp/resources/types.py (5)
  • read (26-28)
  • read (36-38)
  • read (79-88)
  • read (100-105)
  • read (150-160)
🪛 LanguageTool
docs/servers/resources.mdx

[style] ~168-~168: To form a complete sentence, be sure to include a subject.
Context: ...ntent** - The actual resource content. Can be str(text content) orbytes` (bin...

(MISSING_IT_THERE)

🪛 Ruff (0.14.8)
src/fastmcp/server/tasks/converters.py

153-153: Unused function argument: server

(ARG001)

⏰ 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). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest
🔇 Additional comments (6)
src/fastmcp/server/tasks/converters.py (1)

176-183: LGTM on ResourceContent normalization.

The conversion logic correctly uses ResourceContent.from_value() to normalize input and properly extracts content, mime_type, and meta fields for downstream processing.

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

35-104: Well-designed ResourceContent class with comprehensive type handling.

The from_value factory method correctly handles all expected input types:

  • Pass-through for existing ResourceContent
  • String content with text/plain default
  • Binary content with application/octet-stream default
  • JSON serialization for other types using pydantic_core.to_json

The fallback to str in pydantic_core.to_json ensures graceful handling of non-serializable types.


106-130: LGTM on MCP content conversion.

The to_mcp_resource_contents method correctly:

  • Handles string/URI type coercion
  • Uses appropriate MCP types (TextResourceContents vs BlobResourceContents)
  • Applies proper base64 encoding for binary content
  • Propagates metadata via _meta

230-249: Clean deprecation path with backward compatibility.

The _read() internal API correctly wraps legacy str/bytes returns in ResourceContent while emitting a deprecation warning (when enabled). The stacklevel=2 ensures the warning points to the caller.

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

345-356: Good refactor: _cached_content preserves full ResourceContent including metadata.

The change from _value: str | bytes | None to _cached_content: ResourceContent | None properly preserves MIME type and metadata through the caching layer, addressing the previous concern about lost runtime meta.


380-400: LGTM on ProxyResource.read() with proper base64 decoding.

The implementation correctly:

  • Returns cached content when available
  • Decodes base64 blob content to bytes
  • Preserves MIME type and metadata in ResourceContent

However, add an empty result guard as noted above:

         async with self._client:
             result = await self._client.read_resource(self.uri)
+        if not result:
+            raise ResourceError(f"Remote server returned empty content for {self.uri}")
         if isinstance(result[0], TextResourceContents):

@jlowin jlowin added the feature Major new functionality. Reserved for 2-4 significant PRs per release. Not for issues. label Dec 13, 2025
@jlowin jlowin changed the title feat: add meta support to ResourceContent Refactor resource behavior and add meta support Dec 13, 2025
@jlowin jlowin merged commit 12f2422 into main Dec 13, 2025
14 checks passed
@jlowin jlowin deleted the resource-content-canonical branch December 13, 2025 16:45
@jlowin jlowin added the ignore in release notes Minor change for release notes. Use sparingly for meta PRs like workflow tests. label Dec 13, 2025
jlowin added a commit that referenced this pull request Dec 13, 2025
jlowin added a commit that referenced this pull request Dec 13, 2025
jlowin added a commit that referenced this pull request Dec 13, 2025
jlowin added a commit that referenced this pull request Dec 15, 2025
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. ignore in release notes Minor change for release notes. Use sparingly for meta PRs like workflow tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant