Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 1bdeed3

Browse filesBrowse files
authored
fix: Update @mcp.resource to use function documentation as default descrip… (#489)
1 parent c2f8730 commit 1bdeed3
Copy full SHA for 1bdeed3

File tree

Expand file treeCollapse file tree

4 files changed

+73
-9
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+73
-9
lines changed

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

Copy file name to clipboardExpand all lines: src/mcp/server/fastmcp/resources/types.py
+26-1Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import httpx
1212
import pydantic
1313
import pydantic_core
14-
from pydantic import Field, ValidationInfo
14+
from pydantic import AnyUrl, Field, ValidationInfo, validate_call
1515

1616
from mcp.server.fastmcp.resources.base import Resource
1717

@@ -68,6 +68,31 @@ async def read(self) -> str | bytes:
6868
except Exception as e:
6969
raise ValueError(f"Error reading resource {self.uri}: {e}")
7070

71+
@classmethod
72+
def from_function(
73+
cls,
74+
fn: Callable[..., Any],
75+
uri: str,
76+
name: str | None = None,
77+
description: str | None = None,
78+
mime_type: str | None = None,
79+
) -> "FunctionResource":
80+
"""Create a FunctionResource from a function."""
81+
func_name = name or fn.__name__
82+
if func_name == "<lambda>":
83+
raise ValueError("You must provide a name for lambda functions")
84+
85+
# ensure the arguments are properly cast
86+
fn = validate_call(fn)
87+
88+
return cls(
89+
uri=AnyUrl(uri),
90+
name=func_name,
91+
description=description or fn.__doc__ or "",
92+
mime_type=mime_type or "text/plain",
93+
fn=fn,
94+
)
95+
7196

7297
class FileResource(Resource):
7398
"""A resource that reads from a file.

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

Copy file name to clipboardExpand all lines: src/mcp/server/fastmcp/server.py
+10-8Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,11 @@ def __init__(
148148
self._mcp_server = MCPServer(
149149
name=name or "FastMCP",
150150
instructions=instructions,
151-
lifespan=lifespan_wrapper(self, self.settings.lifespan)
152-
if self.settings.lifespan
153-
else default_lifespan,
151+
lifespan=(
152+
lifespan_wrapper(self, self.settings.lifespan)
153+
if self.settings.lifespan
154+
else default_lifespan
155+
),
154156
)
155157
self._tool_manager = ToolManager(
156158
warn_on_duplicate_tools=self.settings.warn_on_duplicate_tools
@@ -465,16 +467,16 @@ def decorator(fn: AnyFunction) -> AnyFunction:
465467
uri_template=uri,
466468
name=name,
467469
description=description,
468-
mime_type=mime_type or "text/plain",
470+
mime_type=mime_type,
469471
)
470472
else:
471473
# Register as regular resource
472-
resource = FunctionResource(
473-
uri=AnyUrl(uri),
474+
resource = FunctionResource.from_function(
475+
fn=fn,
476+
uri=uri,
474477
name=name,
475478
description=description,
476-
mime_type=mime_type or "text/plain",
477-
fn=fn,
479+
mime_type=mime_type,
478480
)
479481
self.add_resource(resource)
480482
return fn

‎tests/server/fastmcp/resources/test_function_resources.py

Copy file name to clipboardExpand all lines: tests/server/fastmcp/resources/test_function_resources.py
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,22 @@ async def get_data() -> str:
136136
content = await resource.read()
137137
assert content == "Hello, world!"
138138
assert resource.mime_type == "text/plain"
139+
140+
@pytest.mark.anyio
141+
async def test_from_function(self):
142+
"""Test creating a FunctionResource from a function."""
143+
144+
async def get_data() -> str:
145+
"""get_data returns a string"""
146+
return "Hello, world!"
147+
148+
resource = FunctionResource.from_function(
149+
fn=get_data,
150+
uri="function://test",
151+
name="test",
152+
)
153+
154+
assert resource.description == "get_data returns a string"
155+
assert resource.mime_type == "text/plain"
156+
assert resource.name == "test"
157+
assert resource.uri == AnyUrl("function://test")

‎tests/server/fastmcp/test_server.py

Copy file name to clipboardExpand all lines: tests/server/fastmcp/test_server.py
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,24 @@ async def test_file_resource_binary(self, tmp_path: Path):
441441
== base64.b64encode(b"Binary file data").decode()
442442
)
443443

444+
@pytest.mark.anyio
445+
async def test_function_resource(self):
446+
mcp = FastMCP()
447+
448+
@mcp.resource("function://test", name="test_get_data")
449+
def get_data() -> str:
450+
"""get_data returns a string"""
451+
return "Hello, world!"
452+
453+
async with client_session(mcp._mcp_server) as client:
454+
resources = await client.list_resources()
455+
assert len(resources.resources) == 1
456+
resource = resources.resources[0]
457+
assert resource.description == "get_data returns a string"
458+
assert resource.uri == AnyUrl("function://test")
459+
assert resource.name == "test_get_data"
460+
assert resource.mimeType == "text/plain"
461+
444462

445463
class TestServerResourceTemplates:
446464
@pytest.mark.anyio

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.