Merge pull request #157 from micpst/async-resources

feat: add async resources for FastMCP
This commit is contained in:
David Soria Parra
2025-01-22 13:37:41 +00:00
committed by GitHub
3 changed files with 33 additions and 9 deletions

View File

@@ -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):

View File

@@ -1,6 +1,5 @@
"""FastMCP - A more ergonomic interface for MCP servers."""
import functools
import inspect
import json
import re
@@ -305,9 +304,19 @@ class FastMCP:
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 @@ class FastMCP:
)
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 @@ class FastMCP:
# Register as template
self._resource_manager.add_template(
wrapper,
fn=fn,
uri_template=uri,
name=name,
description=description,
@@ -351,10 +356,10 @@ class FastMCP:
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

View File

@@ -120,3 +120,19 @@ class TestFunctionResource:
)
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"