Skip to main content

Install

pip install dedalus-mcp
Or with uv:
uv add dedalus-mcp

Create a server

# server.py
from dedalus_mcp import MCPServer, tool

@tool(description="Add two numbers")
def add(a: int, b: int) -> int:
    return a + b

@tool(description="Multiply two numbers")
def multiply(a: int, b: int) -> int:
    return a * b

server = MCPServer("calculator")
server.collect(add, multiply)

if __name__ == "__main__":
    import asyncio
    asyncio.run(server.serve())
Run it:
python server.py
Server starts on http://127.0.0.1:8000/mcp.

Test with a client

# client.py
from dedalus_mcp.client import MCPClient
import asyncio

async def main():
    client = await MCPClient.connect("http://127.0.0.1:8000/mcp")
    
    # List available tools
    tools = await client.list_tools()
    print([t.name for t in tools.tools])  # ['add', 'multiply']
    
    # Call a tool
    result = await client.call_tool("add", {"a": 2, "b": 3})
    print(result.content[0].text)  # "5"
    
    await client.close()

asyncio.run(main())

How registration works

Decorators attach metadata. collect() registers them. This separation matters:
@tool(description="Shared utility")
def timestamp() -> int:
    return int(time.time())

# Same function, different servers
server_a = MCPServer("service-a")
server_b = MCPServer("service-b")

server_a.collect(timestamp)
server_b.collect(timestamp)
No globals, no state conflicts. Tests stay isolated. For modules:
from tools import math, text

server = MCPServer("multi")
server.collect_from(math, text)  # Registers all decorated functions

Context

Inside a tool, access logging and progress:
from dedalus_mcp import tool, get_context

@tool(description="Process items")
async def process(items: list[str]) -> dict:
    ctx = get_context()
    
    await ctx.info("Starting", data={"count": len(items)})
    
    async with ctx.progress(total=len(items)) as tracker:
        for item in items:
            await do_work(item)
            await tracker.advance(1)
    
    return {"processed": len(items)}

Transports

HTTP (default):
await server.serve()  # http://127.0.0.1:8000/mcp
STDIO for CLI integration:
await server.serve(transport="stdio")

Authentication

Bearer token:
from dedalus_mcp.client import MCPClient, BearerAuth

client = await MCPClient.connect(
    "http://127.0.0.1:8000/mcp",
    auth=BearerAuth(access_token="your-token")
)

Production checklist

  • Configure TransportSecuritySettings for allowed hosts
  • Add authorization via AuthorizationConfig
  • Test with real clients (Claude Desktop, Cursor)
  • Enable allow_dynamic_tools=True if adding tools at runtime

Next