Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/mcp/server/mcpserver/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ class ToolError(MCPServerError):
"""Error in tool operations."""


class ToolNotFoundError(ToolError):
"""Tool not found."""


class InvalidSignature(Exception):
"""Invalid signature for use with MCPServer."""
6 changes: 3 additions & 3 deletions src/mcp/server/mcpserver/tools/tool_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from collections.abc import Callable
from typing import TYPE_CHECKING, Any

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

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

return await tool.run(arguments, context, convert_result=convert_result)
9 changes: 8 additions & 1 deletion tests/server/mcpserver/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from mcp.server.context import ServerRequestContext
from mcp.server.experimental.request_context import Experimental
from mcp.server.mcpserver import Context, MCPServer
from mcp.server.mcpserver.exceptions import ToolError
from mcp.server.mcpserver.exceptions import ToolError, ToolNotFoundError
from mcp.server.mcpserver.prompts.base import Message, UserMessage
from mcp.server.mcpserver.resources import FileResource, FunctionResource
from mcp.server.mcpserver.utilities.types import Audio, Image
Expand Down Expand Up @@ -636,6 +636,13 @@ async def test_remove_nonexistent_tool(self):
with pytest.raises(ToolError, match="Unknown tool: nonexistent"):
mcp.remove_tool("nonexistent")

async def test_remove_nonexistent_tool_raises_tool_not_found_error(self):
"""Test that removing a non-existent tool raises ToolNotFoundError."""
mcp = MCPServer()

with pytest.raises(ToolNotFoundError, match="Unknown tool: nonexistent"):
mcp.remove_tool("nonexistent")

async def test_remove_tool_and_list(self):
"""Test that a removed tool doesn't appear in list_tools."""
mcp = MCPServer()
Expand Down
17 changes: 16 additions & 1 deletion tests/server/mcpserver/test_tool_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

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

def test_remove_nonexistent_tool_raises_tool_not_found_error(self):
"""Test removing a non-existent tool raises ToolError."""
manager = ToolManager()

with pytest.raises(ToolNotFoundError, match="Unknown tool: nonexistent"):
manager.remove_tool("nonexistent")

def test_remove_tool_from_multiple_tools(self):
"""Test removing one tool when multiple tools exist."""

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

# Verify calling removed tool raises ToolNotFoundError
with pytest.raises(ToolNotFoundError, match="Unknown tool: greet"):
await manager.call_tool("greet", {"name": "World"}, Context())

def test_remove_tool_case_sensitive(self):
"""Test that tool removal is case-sensitive."""

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

# Try to remove with different case - should raise ToolNotFoundError
with pytest.raises(ToolNotFoundError, match="Unknown tool: Test_Func"):
manager.remove_tool("Test_Func")

# Verify original tool still exists
assert manager.get_tool("test_func") is not None

Expand Down
Loading