diff --git a/examples/fastmcp/parameter_descriptions.py b/examples/fastmcp/parameter_descriptions.py new file mode 100644 index 00000000..dc56e918 --- /dev/null +++ b/examples/fastmcp/parameter_descriptions.py @@ -0,0 +1,21 @@ +""" +FastMCP Example showing parameter descriptions +""" + +from pydantic import Field + +from mcp.server.fastmcp import FastMCP + +# Create server +mcp = FastMCP("Parameter Descriptions Server") + + +@mcp.tool() +def greet_user( + name: str = Field(description="The name of the person to greet"), + title: str = Field(description="Optional title like Mr/Ms/Dr", default=""), + times: int = Field(description="Number of times to repeat the greeting", default=1), +) -> str: + """Greet a user with optional title and repetition""" + greeting = f"Hello {title + ' ' if title else ''}{name}!" + return "\n".join([greeting] * times) diff --git a/src/mcp/cli/cli.py b/src/mcp/cli/cli.py index f30529c2..1512e11d 100644 --- a/src/mcp/cli/cli.py +++ b/src/mcp/cli/cli.py @@ -6,10 +6,10 @@ import subprocess import sys from pathlib import Path +from typing import Annotated try: import typer - from typing_extensions import Annotated except ImportError: print("Error: typer is required. Install with 'pip install mcp[cli]'") sys.exit(1) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 7d3d2ba3..55d5a3c3 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -133,8 +133,7 @@ def _setup_handlers(self) -> None: self._mcp_server.read_resource()(self.read_resource) self._mcp_server.list_prompts()(self.list_prompts) self._mcp_server.get_prompt()(self.get_prompt) - # 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) + self._mcp_server.list_resource_templates()(self.list_resource_templates) async def list_tools(self) -> list[MCPTool]: """List all available tools.""" diff --git a/tests/issues/test_129_resource_templates.py b/tests/issues/test_129_resource_templates.py new file mode 100644 index 00000000..e6eff3d4 --- /dev/null +++ b/tests/issues/test_129_resource_templates.py @@ -0,0 +1,44 @@ +import pytest + +from mcp import types +from mcp.server.fastmcp import FastMCP + + +@pytest.mark.anyio +async def test_resource_templates(): + # Create an MCP server + mcp = FastMCP("Demo") + + # Add a dynamic greeting resource + @mcp.resource("greeting://{name}") + def get_greeting(name: str) -> str: + """Get a personalized greeting""" + return f"Hello, {name}!" + + @mcp.resource("users://{user_id}/profile") + def get_user_profile(user_id: str) -> str: + """Dynamic user data""" + return f"Profile data for user {user_id}" + + # Get the list of resource templates using the underlying server + # Note: list_resource_templates() returns a decorator that wraps the handler + # The handler returns a ServerResult with a ListResourceTemplatesResult inside + result = await mcp._mcp_server.request_handlers[types.ListResourceTemplatesRequest]( + types.ListResourceTemplatesRequest( + method="resources/templates/list", params=None, cursor=None + ) + ) + assert isinstance(result.root, types.ListResourceTemplatesResult) + templates = result.root.resourceTemplates + + # Verify we get both templates back + assert len(templates) == 2 + + # Verify template details + greeting_template = next(t for t in templates if t.name == "get_greeting") + assert greeting_template.uriTemplate == "greeting://{name}" + assert greeting_template.description == "Get a personalized greeting" + + profile_template = next(t for t in templates if t.name == "get_user_profile") + assert profile_template.uriTemplate == "users://{user_id}/profile" + assert profile_template.description == "Dynamic user data" diff --git a/tests/server/fastmcp/test_parameter_descriptions.py b/tests/server/fastmcp/test_parameter_descriptions.py new file mode 100644 index 00000000..0a45e4fe --- /dev/null +++ b/tests/server/fastmcp/test_parameter_descriptions.py @@ -0,0 +1,30 @@ +"""Test that parameter descriptions are properly exposed through list_tools""" + +import pytest +from pydantic import Field + +from mcp.server.fastmcp import FastMCP + + +@pytest.mark.asyncio +async def test_parameter_descriptions(): + mcp = FastMCP("Test Server") + + @mcp.tool() + def greet( + name: str = Field(description="The name to greet"), + title: str = Field(description="Optional title", default=""), + ) -> str: + """A greeting tool""" + return f"Hello {title} {name}" + + tools = await mcp.list_tools() + assert len(tools) == 1 + tool = tools[0] + + # Check that parameter descriptions are present in the schema + properties = tool.inputSchema["properties"] + assert "name" in properties + assert properties["name"]["description"] == "The name to greet" + assert "title" in properties + assert properties["title"]["description"] == "Optional title" diff --git a/uv.lock b/uv.lock index 1eec17f2..ad2ec817 100644 --- a/uv.lock +++ b/uv.lock @@ -191,7 +191,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.2.0.dev0" +version = "1.2.0" source = { editable = "." } dependencies = [ { name = "anyio" },