Skip to content

Commit

Permalink
Merge pull request #11 from jlowin/list-templates
Browse files Browse the repository at this point in the history
Prepare to list templates
  • Loading branch information
jlowin authored Nov 30, 2024
2 parents 8a1237b + 96cf90b commit 00629e9
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 6 deletions.
5 changes: 5 additions & 0 deletions src/fastmcp/resources/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ def list_resources(self) -> list[Resource]:
logger.debug("Listing resources", extra={"count": len(self._resources)})
return list(self._resources.values())

def list_templates(self) -> list[ResourceTemplate]:
"""List all registered templates."""
logger.debug("Listing templates", extra={"count": len(self._templates)})
return list(self._templates.values())

def add_resource(self, resource: Resource) -> Resource:
"""Add a resource to the manager.
Expand Down
20 changes: 17 additions & 3 deletions src/fastmcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from mcp.server.sse import SseServerTransport
from mcp.types import (
Resource as MCPResource,
Tool,
Tool as MCPTool,
ResourceTemplate as MCPResourceTemplate,
TextContent,
ImageContent,
)
Expand Down Expand Up @@ -95,12 +96,14 @@ def _setup_handlers(self) -> None:
self._mcp_server.call_tool()(self.call_tool)
self._mcp_server.list_resources()(self.list_resources)
self._mcp_server.read_resource()(self.read_resource)
# TODO: This has not been added to MCP yet, see https://github.com/jlowin/fastmcp/issues/10
# self._mcp_server.list_resource_templates()(self.list_resource_templates)

async def list_tools(self) -> list[Tool]:
async def list_tools(self) -> list[MCPTool]:
"""List all available tools."""
tools = self._tool_manager.list_tools()
return [
Tool(
MCPTool(
name=info.name,
description=info.description,
inputSchema=info.parameters,
Expand Down Expand Up @@ -139,6 +142,17 @@ async def list_resources(self) -> list[MCPResource]:
for resource in resources
]

async def list_resource_templates(self) -> list[MCPResourceTemplate]:
templates = self._resource_manager.list_templates()
return [
MCPResourceTemplate(
uriTemplate=template.uri_template,
name=template.name,
description=template.description,
)
for template in templates
]

async def read_resource(self, uri: _BaseUrl) -> Union[str, bytes]:
"""Read a resource by URI."""
resource = await self._resource_manager.get_resource(uri)
Expand Down
26 changes: 23 additions & 3 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,11 @@ async def test_file_resource_binary(self, tmp_path: Path):
== base64.b64encode(b"Binary file data").decode()
)


class TestServerResourceTemplates:
async def test_resource_with_params(self):
"""Test that a resource with function parameters is automatically a template"""
"""Test that a resource with function parameters raises an error if the URI
parameters don't match"""
mcp = FastMCP()

with pytest.raises(ValueError, match="Mismatch between URI parameters"):
Expand All @@ -285,6 +288,14 @@ async def test_resource_with_uri_params(self):
def get_data() -> str:
return "Data"

async def test_resource_with_untyped_params(self):
"""Test that a resource with untyped parameters raises an error"""
mcp = FastMCP()

@mcp.resource("resource://{param}")
def get_data(param) -> str:
return "Data"

async def test_resource_matching_params(self):
"""Test that a resource with matching URI and function parameters works"""
mcp = FastMCP()
Expand Down Expand Up @@ -319,7 +330,16 @@ def get_data(org: str, repo: str) -> str:
result = await client.read_resource("resource://cursor/fastmcp/data")
assert result.contents[0].text == "Data for cursor/fastmcp"

async def test_resource_no_params(self):
async def test_resource_multiple_mismatched_params(self):
"""Test that mismatched parameters raise an error"""
mcp = FastMCP()

with pytest.raises(ValueError, match="Mismatch between URI parameters"):

@mcp.resource("resource://{org}/{repo}/data")
def get_data(org: str, repo_2: str) -> str:
return f"Data for {org}"

"""Test that a resource with no parameters works as a regular resource"""
mcp = FastMCP()

Expand All @@ -341,7 +361,7 @@ def get_data(name: str) -> str:

# Should be registered as a template
assert len(mcp._resource_manager._templates) == 1
assert len(mcp._resource_manager.list_resources()) == 0
assert len(await mcp.list_resources()) == 0

# When accessed, should create a concrete resource
resource = await mcp._resource_manager.get_resource("resource://test/data")
Expand Down

0 comments on commit 00629e9

Please sign in to comment.