Tools are just functions. Test them without a server:
def test_add():
assert add(2, 3) == 5
async def test_async_tool():
result = await fetch_user("123")
assert result["id"] == "123"
Tools using context
Tools that call get_context() require an active request. Test them via integration tests with a real server, or structure your tool to make the context-dependent part mockable:
@tool(description="Process with logging")
async def process(data: str) -> dict:
ctx = get_context()
await ctx.info("Processing")
return do_work(data) # Test do_work separately
def test_do_work():
assert do_work("input") == {"result": "output"}
Integration test with MCPClient
Test the full server:
import pytest
from dedalus_mcp import MCPServer, tool
from dedalus_mcp.client import MCPClient
@tool(description="Add")
def add(a: int, b: int) -> int:
return a + b
@pytest.fixture
async def server():
server = MCPServer("test")
server.collect(add)
# Start server in background
task = asyncio.create_task(server.serve())
await asyncio.sleep(0.1) # Let it start
yield server
task.cancel()
@pytest.fixture
async def client(server):
client = await MCPClient.connect("http://127.0.0.1:8000/mcp")
yield client
await client.close()
async def test_call_tool(client):
result = await client.call_tool("add", {"a": 2, "b": 3})
assert result.content[0].text == "5"
Test registration
Verify tools register correctly:
def test_registration():
server = MCPServer("test")
server.collect(add, multiply)
names = list(server.tool_names)
assert "add" in names
assert "multiply" in names
Isolation
Since decorators don’t bind to servers at import time, each test gets clean state:
def test_a():
server = MCPServer("test-a")
server.collect(tool_a)
# No teardown needed
def test_b():
server = MCPServer("test-b")
server.collect(tool_b)
# Completely independent
Last modified on February 28, 2026