"""Tests for FastMCPServer adapter."""
from typing import Dict, Any
from mcp_chain.fastmcp import FastMCPServer
class MockDictMCPServer:
"""Mock DictMCPServer for testing."""
def __init__(self, metadata: Dict[str, Any] = None):
if metadata is None:
self._metadata = {
"tools": [{"name": "test_tool", "description": "A test tool"}],
"resources": [{"uri": "test://resource", "name": "test_resource"}],
}
else:
self._metadata = metadata
def get_metadata(self) -> Dict[str, Any]:
return self._metadata
def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
return {"result": f"Handled: {request.get('method', 'unknown')}"}
def test_fastmcp_server_initialization():
"""Test FastMCPServer can be initialized with a DictMCPServer."""
mock_server = MockDictMCPServer()
# This should create a FastMCPServer instance
fastmcp_server = FastMCPServer(mock_server)
assert fastmcp_server is not None
assert fastmcp_server._downstream == mock_server
def test_fastmcp_server_dynamic_tool_registration():
"""Test that FastMCPServer registers tools from downstream metadata."""
mock_server = MockDictMCPServer(
{
"tools": [
{"name": "query_db", "description": "Query the database"},
{"name": "update_record", "description": "Update a record"},
]
}
)
fastmcp_server = FastMCPServer(mock_server)
# Should have registered tools with FastMCP
# We'll verify this by checking that the tools are properly extracted
metadata = fastmcp_server._downstream.get_metadata()
assert len(metadata["tools"]) == 2
assert metadata["tools"][0]["name"] == "query_db"
assert metadata["tools"][1]["name"] == "update_record"
def test_fastmcp_server_dynamic_resource_registration():
"""Test that FastMCPServer registers resources from downstream metadata."""
mock_server = MockDictMCPServer(
{
"resources": [
{"uri": "db://users", "name": "users_table"},
{"uri": "db://orders", "name": "orders_table"},
]
}
)
fastmcp_server = FastMCPServer(mock_server)
# Should have registered resources with FastMCP
metadata = fastmcp_server._downstream.get_metadata()
assert len(metadata["resources"]) == 2
assert metadata["resources"][0]["uri"] == "db://users"
assert metadata["resources"][1]["uri"] == "db://orders"
def test_fastmcp_server_handles_empty_metadata():
"""Test FastMCPServer handles empty metadata gracefully."""
mock_server = MockDictMCPServer({})
# Should not fail with empty metadata
fastmcp_server = FastMCPServer(mock_server)
assert fastmcp_server is not None
metadata = fastmcp_server._downstream.get_metadata()
assert metadata == {}
def test_fastmcp_server_has_run_method():
"""Test that FastMCPServer has a run method for starting the server."""
mock_server = MockDictMCPServer()
fastmcp_server = FastMCPServer(mock_server)
# Should have a run method (we'll mock the actual execution)
assert hasattr(fastmcp_server, "run")
assert callable(fastmcp_server.run)
def test_fastmcp_server_tool_registration_with_missing_description():
"""Test tool registration when description is missing."""
mock_server = MockDictMCPServer(
{
"tools": [
{"name": "tool_no_desc"} # Missing description
]
}
)
# Should handle missing description gracefully
fastmcp_server = FastMCPServer(mock_server)
assert fastmcp_server is not None
metadata = fastmcp_server._downstream.get_metadata()
assert metadata["tools"][0]["name"] == "tool_no_desc"
def test_fastmcp_server_resource_registration_with_missing_name():
"""Test resource registration when name is missing."""
mock_server = MockDictMCPServer(
{
"resources": [
{"uri": "test://resource"} # Missing name
]
}
)
# Should handle missing name gracefully (using URI as name)
fastmcp_server = FastMCPServer(mock_server)
assert fastmcp_server is not None
metadata = fastmcp_server._downstream.get_metadata()
assert metadata["resources"][0]["uri"] == "test://resource"
def test_fastmcp_server_run_filters_name_parameter():
"""Test that run() method filters out 'name' parameter."""
mock_server = MockDictMCPServer()
FastMCPServer(mock_server, name="test-server")
# Test the filtering logic directly
kwargs = {"transport": "stdio", "name": "should-be-filtered", "port": 8080}
filtered = {k: v for k, v in kwargs.items() if k != "name"}
assert "name" not in filtered
assert "transport" in filtered
assert "port" in filtered
assert filtered["transport"] == "stdio"
assert filtered["port"] == 8080
def test_fastmcp_server_tool_handler_execution():
"""Test that tool handlers delegate to downstream server correctly."""
responses = []
class MockServerWithCapture(MockDictMCPServer):
def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
responses.append(request)
return {"result": "tool_executed", "tool": request["params"]["name"]}
mock_server = MockServerWithCapture(
{"tools": [{"name": "test_tool", "description": "Test tool"}]}
)
FastMCPServer(mock_server)
# Simulate tool execution by accessing the registered tool handler
# Note: In real usage, FastMCP would handle this, but we test the logic
test_request = {
"method": "tools/call",
"params": {"name": "test_tool", "arguments": {"param1": "value1"}},
}
response = mock_server.handle_request(test_request)
assert response["result"] == "tool_executed"
assert response["tool"] == "test_tool"
assert len(responses) == 1
assert responses[0]["method"] == "tools/call"
def test_fastmcp_server_resource_handler_execution():
"""Test that resource handlers delegate to downstream server correctly."""
responses = []
class MockServerWithCapture(MockDictMCPServer):
def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
responses.append(request)
return {"result": "resource_read", "uri": request["params"]["uri"]}
mock_server = MockServerWithCapture(
{"resources": [{"uri": "test://resource", "name": "test_resource"}]}
)
FastMCPServer(mock_server)
# Simulate resource access by accessing the registered resource handler
test_request = {"method": "resources/read", "params": {"uri": "test://resource"}}
response = mock_server.handle_request(test_request)
assert response["result"] == "resource_read"
assert response["uri"] == "test://resource"
assert len(responses) == 1
assert responses[0]["method"] == "resources/read"
def test_fastmcp_server_custom_name():
"""Test that FastMCPServer accepts custom name."""
mock_server = MockDictMCPServer()
fastmcp_server = FastMCPServer(mock_server, name="custom-server-name")
assert fastmcp_server is not None
# The name is passed to FastMCP constructor, we can't easily test it
# but we verify the server initializes correctly