Documentation Index
Fetch the complete documentation index at: https://docs.dedaluslabs.ai/llms.txt
Use this file to discover all available pages before exploring further.
Tools let agents call your Python functions. Decorate, register, serve.
Tools are the core building blocks that allow an MCP client to invoke your Python functions via the MCP protocol:
- A client discovers tools via
tools/list (each tool includes inputSchema and optional outputSchema).
- A client calls a tool via
tools/call with arguments matching the schema.
- The server executes your callable.
- The server returns a
CallToolResult containing content (and optionally structuredContent) per MCP Spec.
from dedalus_mcp import MCPServer, tool
@tool(description="Add two numbers")
def add(a: int, b: int) -> int:
return a + b
server = MCPServer("math")
server.collect(add)
The description tells the LLM what the tool does. Type hints become JSON Schema.
import anyio
from dedalus_mcp import tool
@tool(description="Fetch user data (simulated I/O)")
async def get_user(user_id: str) -> dict:
await anyio.sleep(0.1)
return {"user_id": user_id, "status": "ok"}
Prefer async for I/O.
Important: in Dedalus MCP, sync tools run inline (they are not automatically moved to a thread pool). If you need concurrency for blocking work, use async def and offload explicitly.
Type inference
Type hints become JSON Schema automatically:
from typing import Literal
from pydantic import BaseModel
from dedalus_mcp import tool
class SearchFilters(BaseModel):
category: str | None = None
min_price: float = 0.0
@tool(description="Search products")
def search(
query: str,
limit: int = 10,
sort: Literal["relevance", "price", "date"] = "relevance",
filters: SearchFilters | None = None,
) -> list[dict]:
return [{"query": query, "limit": limit, "sort": sort, "filters": filters.model_dump() if filters else None}]
Supported: primitives, list, dict, Literal, Enum, optionals/unions, Pydantic models, dataclasses, nested models.
Required parameters have no default. Optional parameters have one.
Decorator options
from dedalus_mcp import tool
@tool(
name="find_products", # Override tool name
description="Search catalog", # Tool description
tags={"search", "catalog"}, # For filtering/metadata
)
def search_products_impl(query: str) -> list[dict]:
return [{"id": "p_1", "name": "Widget", "query": query}]
Structured returns
Return JSON-serializable values:
from dedalus_mcp import tool
@tool(description="Analyze text")
def analyze(text: str) -> dict:
return {"word_count": len(text.split()), "char_count": len(text)}
For explicit control, return CallToolResult:
from dedalus_mcp import tool
from dedalus_mcp.types import CallToolResult, TextContent
@tool(description="Custom result")
def custom() -> CallToolResult:
return CallToolResult(
content=[TextContent(type="text", text="Custom message")],
isError=False,
)
Context access
Logging and progress via get_context():
import anyio
from dedalus_mcp import tool, get_context
@tool(description="Process files with progress reporting")
async def process_files(paths: list[str]) -> dict:
ctx = get_context()
await ctx.info("Starting", data={"count": len(paths)})
processed = 0
try:
async with ctx.progress(total=len(paths)) as tracker:
for path in paths:
# Simulate work; cancellation is delivered as task cancellation
await anyio.sleep(0.01)
processed += 1
await tracker.advance(1)
except anyio.get_cancelled_exc_class():
await ctx.warning("Cancelled", data={"processed": processed})
raise
return {"processed": processed}
Allow-lists
Restrict visible tools:
from dedalus_mcp import MCPServer, tool
@tool(description="Add")
def add(a: int, b: int) -> int:
return a + b
@tool(description="Multiply")
def multiply(a: int, b: int) -> int:
return a * b
server = MCPServer("gated")
server.collect(add, multiply)
server.allow_tools({"add"})
Calling a hidden tool returns an error CallToolResult indicating the tool is not available.
Error handling
Raise exceptions normally:
from dedalus_mcp import tool
@tool(description="Divide")
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
Testing
Test tools as normal functions:
def test_add():
assert add(2, 3) == 5
For tools using context, test the core logic separately (or use an integration-style harness).