We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/lin2000wl/Serena-cursor-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""Tests for the mcp.py module in serena."""
import pytest
from mcp.server.fastmcp.tools.base import Tool as MCPTool
from serena.agent import Tool, ToolRegistry
from serena.mcp import SerenaMCPFactory
make_tool = SerenaMCPFactory.make_mcp_tool
class BaseMockTool(Tool):
"""A mock Tool class for testing."""
def __init__(self) -> None:
pass
class BasicTool(BaseMockTool):
"""A mock Tool class for testing."""
def apply(self, name: str, age: int = 0) -> str:
"""This is a test function.
:param name: The person's name
:param age: The person's age
:return: A greeting message
"""
return f"Hello {name}, you are {age} years old!"
def apply_ex(
self,
log_call: bool = True,
catch_exceptions: bool = True,
**kwargs,
) -> str:
"""Mock implementation of apply_ex."""
return self.apply(**kwargs)
def test_make_tool_basic() -> None:
"""Test that make_tool correctly creates an MCP tool from a Tool object."""
mock_tool = BasicTool()
mcp_tool = make_tool(mock_tool)
# Test that the MCP tool has the correct properties
assert isinstance(mcp_tool, MCPTool)
assert mcp_tool.name == "basic"
assert "This is a test function. Returns A greeting message." in mcp_tool.description
# Test that the parameters were correctly processed
parameters = mcp_tool.parameters
assert "properties" in parameters
assert "name" in parameters["properties"]
assert "age" in parameters["properties"]
assert parameters["properties"]["name"]["description"] == "The person's name."
assert parameters["properties"]["age"]["description"] == "The person's age."
def test_make_tool_execution() -> None:
"""Test that the execution function created by make_tool works correctly."""
mock_tool = BasicTool()
mcp_tool = make_tool(mock_tool)
# Execute the MCP tool function
result = mcp_tool.fn(name="Alice", age=30)
assert result == "Hello Alice, you are 30 years old!"
def test_make_tool_no_params() -> None:
"""Test make_tool with a function that has no parameters."""
class NoParamsTool(BaseMockTool):
def apply(self) -> str:
"""This is a test function with no parameters.
:return: A simple result
"""
return "Simple result"
def apply_ex(self, *args, **kwargs) -> str:
return self.apply()
tool = NoParamsTool()
mcp_tool = make_tool(tool)
assert mcp_tool.name == "no_params"
assert "This is a test function with no parameters. Returns A simple result." in mcp_tool.description
assert mcp_tool.parameters["properties"] == {}
def test_make_tool_no_return_description() -> None:
"""Test make_tool with a function that has no return description."""
class NoReturnTool(BaseMockTool):
def apply(self, param: str) -> str:
"""This is a test function.
:param param: The parameter
"""
return f"Processed: {param}"
def apply_ex(self, *args, **kwargs) -> str:
return self.apply(**kwargs)
tool = NoReturnTool()
mcp_tool = make_tool(tool)
assert mcp_tool.name == "no_return"
assert mcp_tool.description == "This is a test function."
assert mcp_tool.parameters["properties"]["param"]["description"] == "The parameter."
def test_make_tool_parameter_not_in_docstring() -> None:
"""Test make_tool when a parameter in properties is not in the docstring."""
class MissingParamTool(BaseMockTool):
def apply(self, name: str, missing_param: str = "") -> str:
"""This is a test function.
:param name: The person's name
"""
return f"Hello {name}! Missing param: {missing_param}"
def apply_ex(self, *args, **kwargs) -> str:
return self.apply(**kwargs)
tool = MissingParamTool()
mcp_tool = make_tool(tool)
assert "name" in mcp_tool.parameters["properties"]
assert "missing_param" in mcp_tool.parameters["properties"]
assert mcp_tool.parameters["properties"]["name"]["description"] == "The person's name."
assert "description" not in mcp_tool.parameters["properties"]["missing_param"]
def test_make_tool_multiline_docstring() -> None:
"""Test make_tool with a complex multi-line docstring."""
class ComplexDocTool(BaseMockTool):
def apply(self, project_file_path: str, host: str, port: int) -> str:
"""Create an MCP server.
This function creates and configures a Model Context Protocol server
with the specified settings.
:param project_file_path: The path to the project file, or None
:param host: The host to bind to
:param port: The port to bind to
:return: A configured FastMCP server instance
"""
return f"Server config: {project_file_path}, {host}:{port}"
def apply_ex(self, *args, **kwargs) -> str:
return self.apply(**kwargs)
tool = ComplexDocTool()
mcp_tool = make_tool(tool)
assert "Create an MCP server" in mcp_tool.description
assert "Returns A configured FastMCP server instance" in mcp_tool.description
assert mcp_tool.parameters["properties"]["project_file_path"]["description"] == "The path to the project file, or None."
assert mcp_tool.parameters["properties"]["host"]["description"] == "The host to bind to."
assert mcp_tool.parameters["properties"]["port"]["description"] == "The port to bind to."
def test_make_tool_capitalization_and_periods() -> None:
"""Test that make_tool properly handles capitalization and periods in descriptions."""
class FormatTool(BaseMockTool):
def apply(self, param1: str, param2: str, param3: str) -> str:
"""Test function.
:param param1: lowercase description
:param param2: description with period.
:param param3: description with Capitalized word.
"""
return f"Formatted: {param1}, {param2}, {param3}"
def apply_ex(self, *args, **kwargs) -> str:
return self.apply(**kwargs)
tool = FormatTool()
mcp_tool = make_tool(tool)
assert mcp_tool.parameters["properties"]["param1"]["description"] == "Lowercase description."
assert mcp_tool.parameters["properties"]["param2"]["description"] == "Description with period."
assert mcp_tool.parameters["properties"]["param3"]["description"] == "Description with Capitalized word."
def test_make_tool_missing_apply() -> None:
"""Test make_tool with a tool that doesn't have an apply method."""
class BadTool(BaseMockTool):
pass
tool = BadTool()
with pytest.raises(AttributeError):
make_tool(tool)
@pytest.mark.parametrize(
"docstring, expected_description",
[
(
"""This is a test function.
:param param: The parameter
:return: A result
""",
"This is a test function. Returns A result.",
),
(
"""
:param param: The parameter
:return: A result
""",
"Returns A result.",
),
(
"""
:param param: The parameter
""",
"",
),
("Description without params.", "Description without params."),
],
)
def test_make_tool_descriptions(docstring, expected_description) -> None:
"""Test make_tool with various docstring formats."""
class TestTool(BaseMockTool):
def apply(self, param: str) -> str:
return f"Result: {param}"
def apply_ex(self, *args, **kwargs) -> str:
return self.apply(**kwargs)
# Dynamically set the docstring
TestTool.apply.__doc__ = docstring
tool = TestTool()
mcp_tool = make_tool(tool)
assert mcp_tool.name == "test"
assert mcp_tool.description == expected_description
def is_test_mock_class(tool_class: type) -> bool:
"""Check if a class is a test mock class."""
# Check if the class is defined in a test module
module_name = tool_class.__module__
return (
module_name.startswith(("test.", "tests."))
or "test_" in module_name
or tool_class.__name__
in [
"BaseMockTool",
"BasicTool",
"BadTool",
"NoParamsTool",
"NoReturnTool",
"MissingParamTool",
"ComplexDocTool",
"FormatTool",
"NoDescriptionTool",
]
)
@pytest.mark.parametrize("tool_class", ToolRegistry.get_all_tool_classes())
def test_make_tool_all_tools(tool_class) -> None:
"""Test that make_tool works for all tools in the codebase."""
# Create a mock agent for tool initialization
class MockAgent:
def __init__(self):
self.project_config = None
self.serena_config = None
# Create an instance of the tool
tool_instance = tool_class(MockAgent())
# Try to create an MCP tool from it
mcp_tool = make_tool(tool_instance)
# Basic validation
assert isinstance(mcp_tool, MCPTool)
assert mcp_tool.name == tool_class.get_name_from_cls()
# The description should be a string (either from docstring or default)
assert isinstance(mcp_tool.description, str)