mirror of
https://github.com/aljazceru/mcp-python-sdk.git
synced 2025-12-19 14:54:24 +01:00
Merge pull request #157 from micpst/async-resources
feat: add async resources for FastMCP
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
"""Concrete resource implementations."""
|
"""Concrete resource implementations."""
|
||||||
|
|
||||||
|
import inspect
|
||||||
import json
|
import json
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -53,7 +54,9 @@ class FunctionResource(Resource):
|
|||||||
async def read(self) -> str | bytes:
|
async def read(self) -> str | bytes:
|
||||||
"""Read the resource by calling the wrapped function."""
|
"""Read the resource by calling the wrapped function."""
|
||||||
try:
|
try:
|
||||||
result = self.fn()
|
result = (
|
||||||
|
await self.fn() if inspect.iscoroutinefunction(self.fn) else self.fn()
|
||||||
|
)
|
||||||
if isinstance(result, Resource):
|
if isinstance(result, Resource):
|
||||||
return await result.read()
|
return await result.read()
|
||||||
if isinstance(result, bytes):
|
if isinstance(result, bytes):
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"""FastMCP - A more ergonomic interface for MCP servers."""
|
"""FastMCP - A more ergonomic interface for MCP servers."""
|
||||||
|
|
||||||
import functools
|
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
@@ -305,9 +304,19 @@ class FastMCP:
|
|||||||
def get_data() -> str:
|
def get_data() -> str:
|
||||||
return "Hello, world!"
|
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")
|
@server.resource("resource://{city}/weather")
|
||||||
def get_weather(city: str) -> str:
|
def get_weather(city: str) -> str:
|
||||||
return f"Weather for {city}"
|
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
|
# Check if user passed function directly instead of calling decorator
|
||||||
if callable(uri):
|
if callable(uri):
|
||||||
@@ -317,10 +326,6 @@ class FastMCP:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def decorator(fn: Callable) -> Callable:
|
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
|
# Check if this should be a template
|
||||||
has_uri_params = "{" in uri and "}" in uri
|
has_uri_params = "{" in uri and "}" in uri
|
||||||
has_func_params = bool(inspect.signature(fn).parameters)
|
has_func_params = bool(inspect.signature(fn).parameters)
|
||||||
@@ -338,7 +343,7 @@ class FastMCP:
|
|||||||
|
|
||||||
# Register as template
|
# Register as template
|
||||||
self._resource_manager.add_template(
|
self._resource_manager.add_template(
|
||||||
wrapper,
|
fn=fn,
|
||||||
uri_template=uri,
|
uri_template=uri,
|
||||||
name=name,
|
name=name,
|
||||||
description=description,
|
description=description,
|
||||||
@@ -351,10 +356,10 @@ class FastMCP:
|
|||||||
name=name,
|
name=name,
|
||||||
description=description,
|
description=description,
|
||||||
mime_type=mime_type or "text/plain",
|
mime_type=mime_type or "text/plain",
|
||||||
fn=wrapper,
|
fn=fn,
|
||||||
)
|
)
|
||||||
self.add_resource(resource)
|
self.add_resource(resource)
|
||||||
return wrapper
|
return fn
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|||||||
@@ -120,3 +120,19 @@ class TestFunctionResource:
|
|||||||
)
|
)
|
||||||
content = await resource.read()
|
content = await resource.read()
|
||||||
assert isinstance(content, str)
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user