Skip to content

Commit 9c50f40

Browse files
committed
Create specific ToolNotFoundError for easier handling in multi-tenant environments
1 parent f27d2aa commit 9c50f40

File tree

4 files changed

+31
-5
lines changed

4 files changed

+31
-5
lines changed

src/mcp/server/mcpserver/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,9 @@ class ToolError(MCPServerError):
1717
"""Error in tool operations."""
1818

1919

20+
class ToolNotFoundError(ToolError):
21+
"""Tool not found."""
22+
23+
2024
class InvalidSignature(Exception):
2125
"""Invalid signature for use with MCPServer."""

src/mcp/server/mcpserver/tools/tool_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from collections.abc import Callable
44
from typing import TYPE_CHECKING, Any
55

6-
from mcp.server.mcpserver.exceptions import ToolError
6+
from mcp.server.mcpserver.exceptions import ToolNotFoundError
77
from mcp.server.mcpserver.tools.base import Tool
88
from mcp.server.mcpserver.utilities.logging import get_logger
99
from mcp.types import Icon, ToolAnnotations
@@ -74,7 +74,7 @@ def add_tool(
7474
def remove_tool(self, name: str) -> None:
7575
"""Remove a tool by name."""
7676
if name not in self._tools:
77-
raise ToolError(f"Unknown tool: {name}")
77+
raise ToolNotFoundError(f"Unknown tool: {name}")
7878
del self._tools[name]
7979

8080
async def call_tool(
@@ -87,6 +87,6 @@ async def call_tool(
8787
"""Call a tool by name with arguments."""
8888
tool = self.get_tool(name)
8989
if not tool:
90-
raise ToolError(f"Unknown tool: {name}")
90+
raise ToolNotFoundError(f"Unknown tool: {name}")
9191

9292
return await tool.run(arguments, context, convert_result=convert_result)

tests/server/mcpserver/test_server.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from mcp.server.context import ServerRequestContext
1414
from mcp.server.experimental.request_context import Experimental
1515
from mcp.server.mcpserver import Context, MCPServer
16-
from mcp.server.mcpserver.exceptions import ToolError
16+
from mcp.server.mcpserver.exceptions import ToolError, ToolNotFoundError
1717
from mcp.server.mcpserver.prompts.base import Message, UserMessage
1818
from mcp.server.mcpserver.resources import FileResource, FunctionResource
1919
from mcp.server.mcpserver.utilities.types import Audio, Image
@@ -636,6 +636,13 @@ async def test_remove_nonexistent_tool(self):
636636
with pytest.raises(ToolError, match="Unknown tool: nonexistent"):
637637
mcp.remove_tool("nonexistent")
638638

639+
async def test_remove_nonexistent_tool_raises_tool_not_found_error(self):
640+
"""Test that removing a non-existent tool raises ToolNotFoundError."""
641+
mcp = MCPServer()
642+
643+
with pytest.raises(ToolNotFoundError, match="Unknown tool: nonexistent"):
644+
mcp.remove_tool("nonexistent")
645+
639646
async def test_remove_tool_and_list(self):
640647
"""Test that a removed tool doesn't appear in list_tools."""
641648
mcp = MCPServer()

tests/server/mcpserver/test_tool_manager.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from mcp.server.context import LifespanContextT, RequestT
1010
from mcp.server.mcpserver import Context, MCPServer
11-
from mcp.server.mcpserver.exceptions import ToolError
11+
from mcp.server.mcpserver.exceptions import ToolError, ToolNotFoundError
1212
from mcp.server.mcpserver.tools import Tool, ToolManager
1313
from mcp.server.mcpserver.utilities.func_metadata import ArgModelBase, FuncMetadata
1414
from mcp.types import TextContent, ToolAnnotations
@@ -820,6 +820,13 @@ def test_remove_nonexistent_tool(self):
820820
with pytest.raises(ToolError, match="Unknown tool: nonexistent"):
821821
manager.remove_tool("nonexistent")
822822

823+
def test_remove_nonexistent_tool_raises_tool_not_found_error(self):
824+
"""Test removing a non-existent tool raises ToolError."""
825+
manager = ToolManager()
826+
827+
with pytest.raises(ToolNotFoundError, match="Unknown tool: nonexistent"):
828+
manager.remove_tool("nonexistent")
829+
823830
def test_remove_tool_from_multiple_tools(self):
824831
"""Test removing one tool when multiple tools exist."""
825832

@@ -877,6 +884,10 @@ def greet(name: str) -> str:
877884
with pytest.raises(ToolError, match="Unknown tool: greet"):
878885
await manager.call_tool("greet", {"name": "World"}, Context())
879886

887+
# Verify calling removed tool raises ToolNotFoundError
888+
with pytest.raises(ToolNotFoundError, match="Unknown tool: greet"):
889+
await manager.call_tool("greet", {"name": "World"}, Context())
890+
880891
def test_remove_tool_case_sensitive(self):
881892
"""Test that tool removal is case-sensitive."""
882893

@@ -894,6 +905,10 @@ def test_func() -> str: # pragma: no cover
894905
with pytest.raises(ToolError, match="Unknown tool: Test_Func"):
895906
manager.remove_tool("Test_Func")
896907

908+
# Try to remove with different case - should raise ToolNotFoundError
909+
with pytest.raises(ToolNotFoundError, match="Unknown tool: Test_Func"):
910+
manager.remove_tool("Test_Func")
911+
897912
# Verify original tool still exists
898913
assert manager.get_tool("test_func") is not None
899914

0 commit comments

Comments
 (0)