Skip to main content

Unit test tools directly

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