Skip to content

06 - Tool Organization

This example demonstrates the power of direct Python server execution by showing how to organize tools across multiple files and packages.

Running the Example

  • Run HTTP: uv run 06_tool_organization.py
  • Run stdio: uv run 06_tool_organization.py stdio

Project Structure

The example demonstrates this recommended project structure:

my_server/
├── .env
├── server.py          # Main MCPApp
├── tools/
│   ├── __init__.py
│   ├── math_tools.py  # @tool decorated functions
│   └── text_tools.py  # @tool decorated functions
├── pyproject.toml
└── README.md

Source Code

#!/usr/bin/env python3
"""
06_tool_organization.py - Demonstrating modular tool organization

This example showcases the power of the direct Python approach by demonstrating:
- Tools defined in separate files and imported
- Tools imported from other Arcade packages
- Mixed approaches: @app.tool decorators + imported tools
- Explicit control over which tools are added to the server

Project Structure (recommended):
    my_server/
    ├── .env
    ├── server.py          # Main MCPApp
    ├── tools/
    │   ├── __init__.py
    │   ├── math_tools.py  # @tool decorated functions
    │   └── text_tools.py  # @tool decorated functions
    ├── pyproject.toml
    └── README.md

To run (HTTP transport by default):
    uv run 06_tool_organization.py

To run with stdio transport (for Claude Desktop):
    uv run 06_tool_organization.py stdio
"""

import sys
from typing import Annotated

from arcade_mcp_server import MCPApp

# Import tools from our 'mock' other_files module
# In a real project, these could come from actual separate files
from tools_math import add, multiply
from tools_text import capitalize_string, word_count

# In a real project, you could also import from Arcade PyPI packages:
# from arcade_gmail.tools import list_emails

# Create the MCP application
app = MCPApp(
    name="organized_server",
    version="1.0.0",
    instructions="Example server demonstrating modular tool organization",
)

# Method 1: Add imported tools explicitly
app.add_tool(add)
app.add_tool(multiply)
app.add_tool(capitalize_string)
app.add_tool(word_count)


# Method 2: Define tools directly on the app
@app.tool
def server_info() -> Annotated[dict, "Information about this server"]:
    """Return information about this MCP server."""
    return {
        "name": "Organized Server",
        "version": "1.0.0",
        "description": "Demonstrates modular tool organization",
        "total_tools": 6,  # 4 imported + 2 defined here
    }


@app.tool
def combine_results(
    text: Annotated[str, "Text to process"],
    add_num: Annotated[int, "Number to add"],
    multiply_num: Annotated[int, "Number to multiply"],
) -> Annotated[dict, "Combined results from multiple tools"]:
    """Demonstrate using multiple tools together."""
    return {
        "original_text": text,
        "capitalized": capitalize_string(text),
        "word_count": word_count(text),
        "math_result": multiply(add(5, add_num), multiply_num),
    }


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}")
    print("Setting up database...")
    # simulate a database setup
    print("Database setup complete")

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

Key Concepts

1. Modular Tool Organization

Define tools in separate files using the @tool decorator:

# tools/math_tools.py
from arcade_mcp_server import tool
from typing import Annotated

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

2. Importing Tools from Files

Import tools from your local files and add them explicitly:

# server.py
from tools_math import add, multiply
from tools_text import capitalize_string, word_count

app.add_tool(add)
app.add_tool(multiply)
app.add_tool(capitalize_string)
app.add_tool(word_count)

3. Importing Tools from Packages

You can also import tools from Arcade packages:

# Import tools from other Arcade packages
from arcade_gmail.tools import list_emails
from arcade_google.tools import search_web

app.add_tool(list_emails)
app.add_tool(search_web)

4. Mixed Approaches

Combine imported tools with direct tool definitions:

# Import tools from files
from tools_math import add
app.add_tool(add)

# Define tools directly
@app.tool
def server_info() -> dict:
    """Return information about this server."""
    return {"name": "My Server", "version": "1.0.0"}

Benefits of This Approach

Explicit Control

  • Choose exactly which tools to include
  • No auto-discovery surprises
  • Clear dependency management

Standard Python Patterns

  • Use normal Python imports
  • Follow Python packaging conventions
  • Leverage existing Python tools (uv, poetry, etc.)

Flexible Organization

  • Tools can be in separate files
  • Tools can be in separate packages
  • Easy to test individual tools

Development Workflow

  • Use uv run server.py for fast iteration
  • Standard Python debugging tools work
  • Easy to add CLI arguments for configuration

Running Your Own Organized Server

1. Create Your Project Structure

my_server/
├── .env
├── server.py
├── tools/
│   ├── __init__.py
│   ├── email_tools.py
│   ├── file_tools.py
│   └── api_tools.py
└── pyproject.toml

2. Create Tool Files

# tools/email_tools.py
from arcade_mcp_server import tool

@tool
def send_email(to: str, subject: str, body: str) -> dict:
    """Send an email."""
    # Implementation here
    return {"status": "sent", "to": to}

3. Build Your Server

# server.py
import sys
from arcade_mcp_server import MCPApp
from tools.email_tools import send_email
from tools.file_tools import read_file, write_file

app = MCPApp(name="my_server", version="1.0.0")

# Add imported tools
app.add_tool(send_email)
app.add_tool(read_file)
app.add_tool(write_file)

# Add direct tools
@app.tool
def server_status() -> str:
    return "Server is running"

if __name__ == "__main__":
    transport = sys.argv[1] if len(sys.argv) > 1 else "http"
    app.run(transport=transport)

4. Run Your Server

# Run with uv
uv run server.py

# Run with stdio for Claude Desktop
uv run server.py stdio

Comparison with CLI Approach

Feature Direct Python CLI Auto-discovery
Tool Selection Explicit with app.add_tool() Automatic discovery
File Organization Your choice Directory-based
Import Control Full control Limited
Deployment Standard Python Custom CLI needed
Testing Standard Python tools Mix Python + CLI
Debugging Python debuggers work Limited

The direct Python approach gives you full control and follows standard Python patterns, making it ideal for production servers and complex tool organization.