Skip to content

01 - Tools

Learn how to create tools with different parameter types and how arcade_mcp_server discovers them automatically.

Running the Example

  • Run (HTTP default): uv run 01_tools.py
  • Run (stdio for Claude Desktop): uv run 01_tools.py stdio

Source Code

#!/usr/bin/env python3
"""
01_tools.py - Tool creation and parameter types

This example demonstrates:
1. How to create tools with @app.tool decorator in MCPApp
2. Different parameter types (simple, lists, TypedDict)
3. Direct Python execution for better control

To run:
    uv run 01_tools.py                  # HTTP transport (default)
    uv run 01_tools.py stdio           # stdio transport for Claude Desktop
"""

import sys
from typing import Annotated

from arcade_mcp_server import MCPApp
from typing_extensions import TypedDict

# Create the MCP application
app = MCPApp(
    name="tools_example",
    version="1.0.0",
    instructions="Example server demonstrating various tool parameter types",
)

# === SIMPLE TOOLS ===


@app.tool
def hello(name: Annotated[str, "Name to greet"]) -> Annotated[str, "Greeting message"]:
    """Say hello to someone."""
    return f"Hello, {name}!"


@app.tool
def add(
    a: Annotated[float, "First number"], b: Annotated[float, "Second number"]
) -> Annotated[float, "Sum of the numbers"]:
    """Add two numbers together."""
    return a + b


# === TOOLS WITH LIST PARAMETERS ===


@app.tool
def calculate_average(
    numbers: Annotated[list[float], "List of numbers to average"],
) -> Annotated[float, "Average of all numbers"]:
    """Calculate the average of a list of numbers."""
    if not numbers:
        return 0.0
    return sum(numbers) / len(numbers)


@app.tool
def factorial(n: Annotated[int, "Non-negative integer"]) -> Annotated[int, "Factorial of n"]:
    """Calculate the factorial of a number."""
    if n < 0:
        raise ValueError("Factorial not defined for negative numbers")
    if n == 0:
        return 1

    result = 1
    for i in range(1, n + 1):
        result *= i
    return result


# === TOOLS WITH COMPLEX TYPES (TypedDict) ===


class PersonInfo(TypedDict):
    name: str
    age: int
    email: str
    is_active: bool


@app.tool
def create_user_profile(
    person: Annotated[PersonInfo, "Person's information"],
) -> Annotated[str, "Formatted user profile"]:
    """Create a formatted user profile from person information."""
    status = "Active" if person["is_active"] else "Inactive"
    return f"""
User Profile:
- Name: {person["name"]}
- Age: {person["age"]}
- Email: {person["email"]}
- Status: {status}
""".strip()


class CalculationResult(TypedDict):
    sum: float
    average: float
    min: float
    max: float
    count: int


@app.tool
def analyze_numbers(
    values: Annotated[list[float], "List of numbers to analyze"],
) -> Annotated[CalculationResult, "Statistical analysis of the numbers"]:
    """Analyze a list of numbers and return statistics."""
    if not values:
        return {"sum": 0.0, "average": 0.0, "min": 0.0, "max": 0.0, "count": 0}

    return {
        "sum": sum(values),
        "average": sum(values) / len(values),
        "min": min(values),
        "max": max(values),
        "count": len(values),
    }


if __name__ == "__main__":
    # Check if stdio transport was requested
    transport = "stdio" if len(sys.argv) > 1 and sys.argv[1] == "stdio" else "http"

    print(f"Starting {app.name} v{app.version}")
    print(f"Transport: {transport}")

    # Run the server
    app.run(transport=transport, host="127.0.0.1", port=8000)

Creating Tools

1. Simple Tools

Basic tools with simple parameter types:

@app.tool
def hello(name: Annotated[str, "Name to greet"]) -> str:
    """Say hello to someone."""
    return f"Hello, {name}!"

@app.tool
def add(
    a: Annotated[float, "First number"],
    b: Annotated[float, "Second number"]
) -> Annotated[float, "Sum of the numbers"]:
    """Add two numbers together."""
    return a + b

2. List Parameters

Working with lists of values:

@app.tool
def calculate_average(
    numbers: Annotated[list[float], "List of numbers to average"]
) -> Annotated[float, "Average of all numbers"]:
    """Calculate the average of a list of numbers."""
    if not numbers:
        return 0.0
    return sum(numbers) / len(numbers)

3. Complex Types with TypedDict

Using TypedDict for structured input and output:

class PersonInfo(TypedDict):
    name: str
    age: int
    email: str
    is_active: bool

@tool
def create_user_profile(
    person: Annotated[PersonInfo, "Person's information"]
) -> Annotated[str, "Formatted user profile"]:
    """Create a formatted user profile from person information."""
    # Implementation here

Managing Tools in MCPApp

With the direct Python approach, you have full control over your tools:

1. Defining Tools Directily

Use @app.tool to define tools directly on your MCPApp instance:

@app.tool
def my_tool(param: str) -> str:
    """Tool description."""
    return f"Processed: {param}"

2. Importing Tools from Files

You can import tools from other files and add them explicitly:

from my_tools import calculate, process_data

# Add imported tools to the app
app.add_tool(calculate)
app.add_tool(process_data)

3. Project Organization

Example project structure:

my_project/
├── server.py          # Main MCPApp
├── tools/
│   ├── math.py       # Tools using @tool decorator
│   └── utils.py      # More tools
└── pyproject.toml    # Dependencies

This approach gives you explicit control over which tools are loaded and how they're organized.

Best Practices

Parameter Annotations

  • Always use Annotated: Provide descriptions for all parameters
  • Clear descriptions: Help the AI understand what each parameter does
  • Type hints: Use proper Python type hints for validation

Tool Design

  • Single purpose: Each tool should do one thing well
  • Error handling: Add validation and helpful error messages
  • Return types: Always annotate return types with descriptions

Organization

  • Group related tools: Use directories to organize by functionality
  • Naming conventions: Use clear, descriptive names
  • Documentation: Write clear docstrings for each tool

Key Concepts

  • Explicit Control: Use @app.tool decorators and app.add_tool() for precise tool management
  • Type Safety: Full type annotation support with runtime validation
  • TypedDict Support: Use TypedDict for complex structured data
  • Import Flexibility: Import tools from your own files and external packages
  • Direct Execution: Run servers directly with uv run for better development experience