diff --git a/src/mcp/server/fastmcp/resources/types.py b/src/mcp/server/fastmcp/resources/types.py index 79acf274f..d9fe2de6c 100644 --- a/src/mcp/server/fastmcp/resources/types.py +++ b/src/mcp/server/fastmcp/resources/types.py @@ -1,5 +1,6 @@ """Concrete resource implementations.""" +import inspect import json from collections.abc import Callable from pathlib import Path @@ -53,7 +54,9 @@ class FunctionResource(Resource): async def read(self) -> str | bytes: """Read the resource by calling the wrapped function.""" try: - result = self.fn() + result = ( + await self.fn() if inspect.iscoroutinefunction(self.fn) else self.fn() + ) if isinstance(result, Resource): return await result.read() if isinstance(result, bytes): diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 571c7c215..4a8e3aa1a 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -1,6 +1,5 @@ """FastMCP - A more ergonomic interface for MCP servers.""" -import functools import inspect import json import re @@ -305,9 +304,19 @@ def resource( def get_data() -> str: return "Hello, world!" + @server.resource("resource://my-resource") + async get_data() -> str: + data = await fetch_data() + return f"Hello, world! {data}" + @server.resource("resource://{city}/weather") def get_weather(city: str) -> str: return f"Weather for {city}" + + @server.resource("resource://{city}/weather") + async def get_weather(city: str) -> str: + data = await fetch_weather(city) + return f"Weather for {city}: {data}" """ # Check if user passed function directly instead of calling decorator if callable(uri): @@ -317,10 +326,6 @@ def get_weather(city: str) -> str: ) def decorator(fn: Callable) -> Callable: - @functools.wraps(fn) - def wrapper(*args: Any, **kwargs: Any) -> Any: - return fn(*args, **kwargs) - # Check if this should be a template has_uri_params = "{" in uri and "}" in uri has_func_params = bool(inspect.signature(fn).parameters) @@ -338,7 +343,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: # Register as template self._resource_manager.add_template( - wrapper, + fn=fn, uri_template=uri, name=name, description=description, @@ -351,10 +356,10 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: name=name, description=description, mime_type=mime_type or "text/plain", - fn=wrapper, + fn=fn, ) self.add_resource(resource) - return wrapper + return fn return decorator diff --git a/tests/server/fastmcp/resources/test_function_resources.py b/tests/server/fastmcp/resources/test_function_resources.py index b92af5c3a..5bfc72bf6 100644 --- a/tests/server/fastmcp/resources/test_function_resources.py +++ b/tests/server/fastmcp/resources/test_function_resources.py @@ -120,3 +120,19 @@ def get_data() -> CustomData: ) content = await resource.read() assert isinstance(content, str) + + @pytest.mark.anyio + async def test_async_read_text(self): + """Test reading text from async FunctionResource.""" + + async def get_data() -> str: + return "Hello, world!" + + resource = FunctionResource( + uri=AnyUrl("function://test"), + name="test", + fn=get_data, + ) + content = await resource.read() + assert content == "Hello, world!" + assert resource.mime_type == "text/plain"