Skip to content

Commit 94e7668

Browse files
feat: Allow FastMCP resources to return ResourceContents objects directly
Updated FastMCP server and resource base classes to support returning TextResourceContents and BlobResourceContents objects directly from resource handlers, matching the low-level server functionality. - Updated Resource.read() abstract method to accept ResourceContents types - Modified FunctionResource to handle ResourceContents in wrapped functions - Updated FastMCP server read_resource to pass through ResourceContents - Updated Context.read_resource to match new return types This provides FastMCP users with more control over resource properties and maintains consistency with the low-level server API. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent eb83a33 commit 94e7668

File tree

3 files changed

+26
-6
lines changed

3 files changed

+26
-6
lines changed

src/mcp/server/fastmcp/resources/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
field_validator,
1414
)
1515

16+
import mcp.types as types
1617
from mcp.types import Icon
1718

1819

@@ -43,6 +44,6 @@ def set_default_name(cls, name: str | None, info: ValidationInfo) -> str:
4344
raise ValueError("Either name or uri must be provided")
4445

4546
@abc.abstractmethod
46-
async def read(self) -> str | bytes:
47+
async def read(self) -> str | bytes | types.TextResourceContents | types.BlobResourceContents:
4748
"""Read the resource content."""
4849
pass

src/mcp/server/fastmcp/resources/types.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import pydantic_core
1414
from pydantic import AnyUrl, Field, ValidationInfo, validate_call
1515

16+
import mcp.types as types
1617
from mcp.server.fastmcp.resources.base import Resource
1718
from mcp.types import Icon
1819

@@ -52,7 +53,7 @@ class FunctionResource(Resource):
5253

5354
fn: Callable[[], Any] = Field(exclude=True)
5455

55-
async def read(self) -> str | bytes:
56+
async def read(self) -> str | bytes | types.TextResourceContents | types.BlobResourceContents:
5657
"""Read the resource by calling the wrapped function."""
5758
try:
5859
# Call the function first to see if it returns a coroutine
@@ -63,6 +64,8 @@ async def read(self) -> str | bytes:
6364

6465
if isinstance(result, Resource):
6566
return await result.read()
67+
elif isinstance(result, (types.TextResourceContents, types.BlobResourceContents)):
68+
return result
6669
elif isinstance(result, bytes):
6770
return result
6871
elif isinstance(result, str):

src/mcp/server/fastmcp/server.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,16 @@
4343
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
4444
from mcp.server.transport_security import TransportSecuritySettings
4545
from mcp.shared.context import LifespanContextT, RequestContext, RequestT
46-
from mcp.types import AnyFunction, ContentBlock, GetPromptResult, Icon, ToolAnnotations
46+
from mcp.types import (
47+
AnyFunction,
48+
BlobResourceContents,
49+
ContentBlock,
50+
GetPromptResult,
51+
Icon,
52+
ResourceContents,
53+
TextResourceContents,
54+
ToolAnnotations,
55+
)
4756
from mcp.types import Prompt as MCPPrompt
4857
from mcp.types import PromptArgument as MCPPromptArgument
4958
from mcp.types import Resource as MCPResource
@@ -340,7 +349,9 @@ async def list_resource_templates(self) -> list[MCPResourceTemplate]:
340349
for template in templates
341350
]
342351

343-
async def read_resource(self, uri: AnyUrl | str) -> Iterable[ReadResourceContents]:
352+
async def read_resource(
353+
self, uri: AnyUrl | str
354+
) -> Iterable[ReadResourceContents | TextResourceContents | BlobResourceContents]:
344355
"""Read a resource by URI."""
345356

346357
context = self.get_context()
@@ -350,7 +361,10 @@ async def read_resource(self, uri: AnyUrl | str) -> Iterable[ReadResourceContent
350361

351362
try:
352363
content = await resource.read()
353-
return [ReadResourceContents(content=content, mime_type=resource.mime_type)]
364+
if isinstance(content, ResourceContents):
365+
return [content]
366+
else:
367+
return [ReadResourceContents(content=content, mime_type=resource.mime_type)]
354368
except Exception as e:
355369
logger.exception(f"Error reading resource {uri}")
356370
raise ResourceError(str(e))
@@ -1124,7 +1138,9 @@ async def report_progress(self, progress: float, total: float | None = None, mes
11241138
message=message,
11251139
)
11261140

1127-
async def read_resource(self, uri: str | AnyUrl) -> Iterable[ReadResourceContents]:
1141+
async def read_resource(
1142+
self, uri: str | AnyUrl
1143+
) -> Iterable[ReadResourceContents | TextResourceContents | BlobResourceContents]:
11281144
"""Read a resource by URI.
11291145
11301146
Args:

0 commit comments

Comments
 (0)