diff --git a/src/fastmcp/server.py b/src/fastmcp/server.py index 8004a6d55..8fb4758f3 100644 --- a/src/fastmcp/server.py +++ b/src/fastmcp/server.py @@ -67,50 +67,54 @@ class FastMCP: def _setup_handlers(self) -> None: """Set up core MCP protocol handlers.""" + self._mcp_server.list_tools()(self.list_tools) + self._mcp_server.call_tool()(self.call_tool) + self._mcp_server.list_resources()(self.list_resources) + self._mcp_server.read_resource()(self.read_resource) - @self._mcp_server.list_tools() - async def handle_list_tools() -> list[Tool]: - tools = self._tool_manager.list_tools() - return [ - Tool( - name=info.name, - description=info.description, - inputSchema=info.parameters, - ) - for info in tools - ] + async def list_tools(self) -> list[Tool]: + """List all available tools.""" + tools = self._tool_manager.list_tools() + return [ + Tool( + name=info.name, + description=info.description, + inputSchema=info.parameters, + ) + for info in tools + ] - @self._mcp_server.call_tool() - async def handle_call_tool( - name: str, arguments: dict - ) -> Sequence[Union[TextContent, ImageContent, EmbeddedResource]]: - result = await self._tool_manager.call_tool(name, arguments) - return [self._convert_to_content(result)] + async def call_tool( + self, name: str, arguments: dict + ) -> Sequence[Union[TextContent, ImageContent, EmbeddedResource]]: + """Call a tool by name with arguments.""" + result = await self._tool_manager.call_tool(name, arguments) + return [self._convert_to_content(result)] - @self._mcp_server.list_resources() - async def handle_list_resources() -> list[MCPResource]: - resources = self._resource_manager.list_resources() - return [ - MCPResource( - uri=resource.uri, - name=resource.name, - description=resource.description, - mimeType=resource.mime_type, - ) - for resource in resources - ] + async def list_resources(self) -> list[MCPResource]: + """List all available resources.""" + resources = self._resource_manager.list_resources() + return [ + MCPResource( + uri=resource.uri, + name=resource.name, + description=resource.description, + mimeType=resource.mime_type, + ) + for resource in resources + ] - @self._mcp_server.read_resource() - async def handle_read_resource(uri: str) -> Union[str, bytes]: - resource = self._resource_manager.get_resource(uri) - if not resource: - raise ResourceError(f"Unknown resource: {uri}") + async def read_resource(self, uri: str) -> Union[str, bytes]: + """Read a resource by URI.""" + resource = self._resource_manager.get_resource(uri) + if not resource: + raise ResourceError(f"Unknown resource: {uri}") - try: - return await resource.read() - except Exception as e: - logger.error(f"Error reading resource {uri}: {e}") - raise ResourceError(str(e)) + try: + return await resource.read() + except Exception as e: + logger.error(f"Error reading resource {uri}: {e}") + raise ResourceError(str(e)) def _convert_to_content( self, value: Any @@ -143,6 +147,7 @@ class FastMCP: self, name: Optional[str] = None, description: Optional[str] = None ) -> Callable: """Decorator to register a tool.""" + breakpoint() def decorator(func: Callable) -> Callable: self.add_tool(func, name=name, description=description) @@ -308,7 +313,6 @@ class FastMCP: app: "FastMCP", ) -> None: """Run the server using SSE transport.""" - from mcp.server.sse import SseServerTransport from starlette.applications import Starlette from starlette.routing import Route import uvicorn diff --git a/tests/test_server.py b/tests/test_server.py index cd049d5d8..90c291068 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -1,13 +1,25 @@ from mcp.shared.memory import ( create_connected_server_and_client_session as client_session, ) -from fastmcp.server import FastMCP +from fastmcp import FastMCP class TestServer: async def test_create_server(self): - server = FastMCP() - assert server.name == "FastMCP" + mcp = FastMCP() + assert mcp.name == "FastMCP" + + async def test_add_tool_decorator(self): + mcp = FastMCP() + + @mcp.tool + def add(x: int, y: int) -> int: + return x + y + + async with client_session(mcp._mcp_server) as client: + tools = await client.list_tools() + assert len(tools.tools) == 1 + assert tools.tools[0].name == "add" def tool_fn(x: int, y: int) -> int: @@ -16,22 +28,22 @@ def tool_fn(x: int, y: int) -> int: class TestServerTools: async def test_add_tool(self): - server = FastMCP() - server.add_tool(tool_fn) - server.add_tool(tool_fn) - assert len(server._tool_manager.list_tools()) == 1 + mcp = FastMCP() + mcp.add_tool(tool_fn) + mcp.add_tool(tool_fn) + assert len(mcp._tool_manager.list_tools()) == 1 async def test_list_tools(self): - server = FastMCP() - server.add_tool(tool_fn) - async with client_session(server._mcp_server) as client: + mcp = FastMCP() + mcp.add_tool(tool_fn) + async with client_session(mcp._mcp_server) as client: tools = await client.list_tools() assert len(tools.tools) == 1 async def test_call_tool(self): - server = FastMCP() - server.add_tool(tool_fn) - async with client_session(server._mcp_server) as client: + mcp = FastMCP() + mcp.add_tool(tool_fn) + async with client_session(mcp._mcp_server) as client: result = await client.call_tool("my_tool", {"arg1": "value"}) assert "error" not in result assert len(result.content) > 0