Skip to content

Commit ddeebbb

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 776aaf4 commit ddeebbb

3 files changed

Lines changed: 32 additions & 7 deletions

File tree

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

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

16-
from mcp.types import Annotations, Icon
16+
from mcp.types import (
17+
Annotations,
18+
BlobResourceContents,
19+
Icon,
20+
TextResourceContents,
21+
)
1722

1823

1924
class Resource(BaseModel, abc.ABC):
@@ -44,6 +49,6 @@ def set_default_name(cls, name: str | None, info: ValidationInfo) -> str:
4449
raise ValueError("Either name or uri must be provided")
4550

4651
@abc.abstractmethod
47-
async def read(self) -> str | bytes:
52+
async def read(self) -> str | bytes | TextResourceContents | BlobResourceContents:
4853
"""Read the resource content."""
4954
pass # pragma: no cover

‎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 Annotations, 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): # pragma: no cover
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: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,17 @@
6565
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
6666
from mcp.server.transport_security import TransportSecuritySettings
6767
from mcp.shared.context import LifespanContextT, RequestContext, RequestT
68-
from mcp.types import Annotations, AnyFunction, ContentBlock, GetPromptResult, Icon, ToolAnnotations
68+
from mcp.types import (
69+
Annotations,
70+
AnyFunction,
71+
BlobResourceContents,
72+
ContentBlock,
73+
GetPromptResult,
74+
Icon,
75+
ResourceContents,
76+
TextResourceContents,
77+
ToolAnnotations,
78+
)
6979
from mcp.types import Prompt as MCPPrompt
7080
from mcp.types import PromptArgument as MCPPromptArgument
7181
from mcp.types import Resource as MCPResource
@@ -377,7 +387,9 @@ async def list_resource_templates(self) -> list[MCPResourceTemplate]:
377387
for template in templates
378388
]
379389

380-
async def read_resource(self, uri: AnyUrl | str) -> Iterable[ReadResourceContents]:
390+
async def read_resource(
391+
self, uri: AnyUrl | str
392+
) -> Iterable[ReadResourceContents | TextResourceContents | BlobResourceContents]:
381393
"""Read a resource by URI."""
382394

383395
context = self.get_context()
@@ -387,7 +399,10 @@ async def read_resource(self, uri: AnyUrl | str) -> Iterable[ReadResourceContent
387399

388400
try:
389401
content = await resource.read()
390-
return [ReadResourceContents(content=content, mime_type=resource.mime_type)]
402+
if isinstance(content, ResourceContents):
403+
return [content]
404+
else:
405+
return [ReadResourceContents(content=content, mime_type=resource.mime_type)]
391406
except Exception as e: # pragma: no cover
392407
logger.exception(f"Error reading resource {uri}")
393408
raise ResourceError(str(e))
@@ -1173,7 +1188,9 @@ async def report_progress(self, progress: float, total: float | None = None, mes
11731188
message=message,
11741189
)
11751190

1176-
async def read_resource(self, uri: str | AnyUrl) -> Iterable[ReadResourceContents]:
1191+
async def read_resource(
1192+
self, uri: str | AnyUrl
1193+
) -> Iterable[ReadResourceContents | TextResourceContents | BlobResourceContents]:
11771194
"""Read a resource by URI.
11781195
11791196
Args:

0 commit comments

Comments
 (0)