Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
d6d2b36
1
Parent(s): a1ec1e3
restore mcp tool generation to server
Browse files
src/fastmcp/server/server.py
CHANGED
|
@@ -188,10 +188,6 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 188 |
"""Get all registered tools, indexed by registered key."""
|
| 189 |
return self._tool_manager.get_tools()
|
| 190 |
|
| 191 |
-
def list_tools(self) -> list[Tool]:
|
| 192 |
-
"""List all registered tools."""
|
| 193 |
-
return self._tool_manager.list_tools()
|
| 194 |
-
|
| 195 |
async def _mcp_list_tools(self) -> list[MCPTool]:
|
| 196 |
"""
|
| 197 |
List all available tools, in the format expected by the low-level MCP
|
|
@@ -199,7 +195,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 199 |
|
| 200 |
See `list_tools` for a more ergonomic way to list tools.
|
| 201 |
"""
|
| 202 |
-
return self.
|
| 203 |
|
| 204 |
def get_context(self) -> "Context[ServerSession, LifespanResultT]":
|
| 205 |
"""
|
|
|
|
| 188 |
"""Get all registered tools, indexed by registered key."""
|
| 189 |
return self._tool_manager.get_tools()
|
| 190 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
async def _mcp_list_tools(self) -> list[MCPTool]:
|
| 192 |
"""
|
| 193 |
List all available tools, in the format expected by the low-level MCP
|
|
|
|
| 195 |
|
| 196 |
See `list_tools` for a more ergonomic way to list tools.
|
| 197 |
"""
|
| 198 |
+
return [tool.to_mcp_tool(name=key) for key, tool in self.get_tools().items()]
|
| 199 |
|
| 200 |
def get_context(self) -> "Context[ServerSession, LifespanResultT]":
|
| 201 |
"""
|
src/fastmcp/tools/tool_manager.py
CHANGED
|
@@ -7,7 +7,7 @@ from mcp.shared.context import LifespanContextT
|
|
| 7 |
|
| 8 |
from fastmcp.exceptions import ToolError
|
| 9 |
from fastmcp.settings import DuplicateBehavior
|
| 10 |
-
from fastmcp.tools.tool import
|
| 11 |
from fastmcp.utilities.logging import get_logger
|
| 12 |
|
| 13 |
if TYPE_CHECKING:
|
|
@@ -48,10 +48,6 @@ class ToolManager:
|
|
| 48 |
"""List all registered tools."""
|
| 49 |
return list(self.get_tools().values())
|
| 50 |
|
| 51 |
-
def list_mcp_tools(self) -> list[MCPTool]:
|
| 52 |
-
"""List all registered tools in the format expected by the low-level MCP server."""
|
| 53 |
-
return [tool.to_mcp_tool(name=key) for key, tool in self._tools.items()]
|
| 54 |
-
|
| 55 |
def add_tool_from_fn(
|
| 56 |
self,
|
| 57 |
fn: Callable[..., Any],
|
|
|
|
| 7 |
|
| 8 |
from fastmcp.exceptions import ToolError
|
| 9 |
from fastmcp.settings import DuplicateBehavior
|
| 10 |
+
from fastmcp.tools.tool import Tool
|
| 11 |
from fastmcp.utilities.logging import get_logger
|
| 12 |
|
| 13 |
if TYPE_CHECKING:
|
|
|
|
| 48 |
"""List all registered tools."""
|
| 49 |
return list(self.get_tools().values())
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
def add_tool_from_fn(
|
| 52 |
self,
|
| 53 |
fn: Callable[..., Any],
|
tests/server/test_proxy.py
CHANGED
|
@@ -80,9 +80,11 @@ async def test_create_proxy(fastmcp_server):
|
|
| 80 |
|
| 81 |
|
| 82 |
class TestTools:
|
| 83 |
-
async def
|
| 84 |
-
tools = proxy_server.
|
| 85 |
-
assert
|
|
|
|
|
|
|
| 86 |
|
| 87 |
async def test_list_tools_same_as_original(self, fastmcp_server, proxy_server):
|
| 88 |
assert (
|
|
|
|
| 80 |
|
| 81 |
|
| 82 |
class TestTools:
|
| 83 |
+
async def test_get_tools(self, proxy_server):
|
| 84 |
+
tools = proxy_server.get_tools()
|
| 85 |
+
assert "greet" in tools
|
| 86 |
+
assert "add" in tools
|
| 87 |
+
assert "error_tool" in tools
|
| 88 |
|
| 89 |
async def test_list_tools_same_as_original(self, fastmcp_server, proxy_server):
|
| 90 |
assert (
|
tests/server/test_server.py
CHANGED
|
@@ -57,6 +57,34 @@ class TestCreateServer:
|
|
| 57 |
assert "¡Hola, 世界! 👋" == content.text
|
| 58 |
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
class TestToolDecorator:
|
| 61 |
async def test_no_tools_before_decorator(self):
|
| 62 |
mcp = FastMCP()
|
|
@@ -216,9 +244,8 @@ class TestToolDecorator:
|
|
| 216 |
mcp.add_tool(multiply, name="custom_multiply")
|
| 217 |
|
| 218 |
# Check that the tool is registered with the custom name
|
| 219 |
-
tools = mcp.
|
| 220 |
-
|
| 221 |
-
assert "custom_multiply" in tool_names
|
| 222 |
|
| 223 |
# Call the tool by its custom name
|
| 224 |
result = await mcp.call_tool("custom_multiply", {"a": 5, "b": 3})
|
|
@@ -226,7 +253,7 @@ class TestToolDecorator:
|
|
| 226 |
assert result[0].text == "15"
|
| 227 |
|
| 228 |
# Original name should not be registered
|
| 229 |
-
assert "multiply" not in
|
| 230 |
|
| 231 |
|
| 232 |
class TestResourceDecorator:
|
|
|
|
| 57 |
assert "¡Hola, 世界! 👋" == content.text
|
| 58 |
|
| 59 |
|
| 60 |
+
class TestTools:
|
| 61 |
+
async def test_mcp_tool_name(self):
|
| 62 |
+
"""Test MCPTool name for add_tool (key != tool.name)."""
|
| 63 |
+
|
| 64 |
+
mcp = FastMCP()
|
| 65 |
+
|
| 66 |
+
@mcp.tool()
|
| 67 |
+
def fn(x: int) -> int:
|
| 68 |
+
return x + 1
|
| 69 |
+
|
| 70 |
+
mcp_tools = await mcp._mcp_list_tools()
|
| 71 |
+
assert len(mcp_tools) == 1
|
| 72 |
+
assert mcp_tools[0].name == "fn"
|
| 73 |
+
|
| 74 |
+
async def test_mcp_tool_custom_name(self):
|
| 75 |
+
"""Test MCPTool name for add_tool (key != tool.name)."""
|
| 76 |
+
|
| 77 |
+
mcp = FastMCP()
|
| 78 |
+
|
| 79 |
+
@mcp.tool(name="custom_name")
|
| 80 |
+
def fn(x: int) -> int:
|
| 81 |
+
return x + 1
|
| 82 |
+
|
| 83 |
+
mcp_tools = await mcp._mcp_list_tools()
|
| 84 |
+
assert len(mcp_tools) == 1
|
| 85 |
+
assert mcp_tools[0].name == "custom_name"
|
| 86 |
+
|
| 87 |
+
|
| 88 |
class TestToolDecorator:
|
| 89 |
async def test_no_tools_before_decorator(self):
|
| 90 |
mcp = FastMCP()
|
|
|
|
| 244 |
mcp.add_tool(multiply, name="custom_multiply")
|
| 245 |
|
| 246 |
# Check that the tool is registered with the custom name
|
| 247 |
+
tools = mcp.get_tools()
|
| 248 |
+
assert "custom_multiply" in tools
|
|
|
|
| 249 |
|
| 250 |
# Call the tool by its custom name
|
| 251 |
result = await mcp.call_tool("custom_multiply", {"a": 5, "b": 3})
|
|
|
|
| 253 |
assert result[0].text == "15"
|
| 254 |
|
| 255 |
# Original name should not be registered
|
| 256 |
+
assert "multiply" not in tools
|
| 257 |
|
| 258 |
|
| 259 |
class TestResourceDecorator:
|
tests/tools/test_tool_manager.py
CHANGED
|
@@ -612,35 +612,6 @@ class TestCustomToolNames:
|
|
| 612 |
with pytest.raises(ToolError):
|
| 613 |
await manager.call_tool("multiply", {"a": 5, "b": 3})
|
| 614 |
|
| 615 |
-
def test_tool_to_mcp_tool(self):
|
| 616 |
-
"""Test that to_mcp_tool uses the key, not the internal name."""
|
| 617 |
-
|
| 618 |
-
def some_function(x: int) -> int:
|
| 619 |
-
return x
|
| 620 |
-
|
| 621 |
-
manager = ToolManager()
|
| 622 |
-
tool = Tool.from_function(some_function, name="api_function")
|
| 623 |
-
manager.add_tool(tool)
|
| 624 |
-
|
| 625 |
-
mcp_tools = manager.list_mcp_tools()
|
| 626 |
-
assert len(mcp_tools) == 1
|
| 627 |
-
assert mcp_tools[0].name == "api_function"
|
| 628 |
-
|
| 629 |
-
def test_tool_to_mcp_tool_with_custom_key(self):
|
| 630 |
-
"""Test that to_mcp_tool uses the key, not the internal name."""
|
| 631 |
-
|
| 632 |
-
def some_function(x: int) -> int:
|
| 633 |
-
return x
|
| 634 |
-
|
| 635 |
-
manager = ToolManager()
|
| 636 |
-
tool = Tool.from_function(some_function, name="api_function")
|
| 637 |
-
manager.add_tool(tool, key="custom-key")
|
| 638 |
-
|
| 639 |
-
# When listing tools for MCP, the key should be used
|
| 640 |
-
mcp_tools = manager.list_mcp_tools()
|
| 641 |
-
assert len(mcp_tools) == 1
|
| 642 |
-
assert mcp_tools[0].name == "custom-key"
|
| 643 |
-
|
| 644 |
def test_import_tools_with_custom_names(self):
|
| 645 |
"""Test importing tools with custom names."""
|
| 646 |
|
|
@@ -688,28 +659,3 @@ class TestCustomToolNames:
|
|
| 688 |
|
| 689 |
# But the function is different
|
| 690 |
assert stored_tool.fn.__name__ == "replacement_fn"
|
| 691 |
-
|
| 692 |
-
def test_mcp_tool_name_for_add_tool(self):
|
| 693 |
-
"""Test MCPTool name for add_tool (key != tool.name)."""
|
| 694 |
-
|
| 695 |
-
def fn(x: int) -> int:
|
| 696 |
-
return x + 1
|
| 697 |
-
|
| 698 |
-
tool = Tool.from_function(fn, name="my_tool")
|
| 699 |
-
manager = ToolManager()
|
| 700 |
-
manager.add_tool(tool, key="proxy_tool")
|
| 701 |
-
mcp_tools = manager.list_mcp_tools()
|
| 702 |
-
assert len(mcp_tools) == 1
|
| 703 |
-
assert mcp_tools[0].name == "proxy_tool"
|
| 704 |
-
|
| 705 |
-
def test_mcp_tool_name_for_add_tool_from_fn(self):
|
| 706 |
-
"""Test MCPTool name for add_tool_from_fn (key == tool.name)."""
|
| 707 |
-
|
| 708 |
-
def fn(x: int) -> int:
|
| 709 |
-
return x + 1
|
| 710 |
-
|
| 711 |
-
manager = ToolManager()
|
| 712 |
-
manager.add_tool_from_fn(fn, name="custom_fn")
|
| 713 |
-
mcp_tools = manager.list_mcp_tools()
|
| 714 |
-
assert len(mcp_tools) == 1
|
| 715 |
-
assert mcp_tools[0].name == "custom_fn"
|
|
|
|
| 612 |
with pytest.raises(ToolError):
|
| 613 |
await manager.call_tool("multiply", {"a": 5, "b": 3})
|
| 614 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 615 |
def test_import_tools_with_custom_names(self):
|
| 616 |
"""Test importing tools with custom names."""
|
| 617 |
|
|
|
|
| 659 |
|
| 660 |
# But the function is different
|
| 661 |
assert stored_tool.fn.__name__ == "replacement_fn"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|