Skip to main content
This example demonstrates how to create an in-process MCP (Model Context Protocol) server with calculator tools using the Claude Agent SDK.
Unlike external MCP servers that require separate processes, this server runs directly within your Python application, providing better performance and simpler deployment.

Overview

We’ll create a calculator server with the following tools:
  • add - Add two numbers
  • subtract - Subtract one number from another
  • multiply - Multiply two numbers
  • divide - Divide one number by another
  • sqrt - Calculate square root
  • power - Raise a number to a power

Defining Tools

Use the @tool decorator to create MCP tools:
from typing import Any
from claude_agent_sdk import tool

@tool("add", "Add two numbers", {"a": float, "b": float})
async def add_numbers(args: dict[str, Any]) -> dict[str, Any]:
    """Add two numbers together."""
    result = args["a"] + args["b"]
    return {
        "content": [{"type": "text", "text": f"{args['a']} + {args['b']} = {result}"}]
    }


@tool("subtract", "Subtract one number from another", {"a": float, "b": float})
async def subtract_numbers(args: dict[str, Any]) -> dict[str, Any]:
    """Subtract b from a."""
    result = args["a"] - args["b"]
    return {
        "content": [{"type": "text", "text": f"{args['a']} - {args['b']} = {result}"}]
    }


@tool("multiply", "Multiply two numbers", {"a": float, "b": float})
async def multiply_numbers(args: dict[str, Any]) -> dict[str, Any]:
    """Multiply two numbers."""
    result = args["a"] * args["b"]
    return {
        "content": [{"type": "text", "text": f"{args['a']} × {args['b']} = {result}"}]
    }


@tool("divide", "Divide one number by another", {"a": float, "b": float})
async def divide_numbers(args: dict[str, Any]) -> dict[str, Any]:
    """Divide a by b."""
    if args["b"] == 0:
        return {
            "content": [
                {"type": "text", "text": "Error: Division by zero is not allowed"}
            ],
            "is_error": True,
        }

    result = args["a"] / args["b"]
    return {
        "content": [{"type": "text", "text": f"{args['a']} ÷ {args['b']} = {result}"}]
    }

Advanced Tools

Tools can include error handling and validation:
import math
from typing import Any
from claude_agent_sdk import tool

@tool("sqrt", "Calculate square root", {"n": float})
async def square_root(args: dict[str, Any]) -> dict[str, Any]:
    """Calculate the square root of a number."""
    n = args["n"]
    if n < 0:
        return {
            "content": [
                {
                    "type": "text",
                    "text": f"Error: Cannot calculate square root of negative number {n}",
                }
            ],
            "is_error": True,
        }

    result = math.sqrt(n)
    return {"content": [{"type": "text", "text": f"√{n} = {result}"}]}


@tool("power", "Raise a number to a power", {"base": float, "exponent": float})
async def power(args: dict[str, Any]) -> dict[str, Any]:
    """Raise base to the exponent power."""
    result = args["base"] ** args["exponent"]
    return {
        "content": [
            {"type": "text", "text": f"{args['base']}^{args['exponent']} = {result}"}
        ]
    }

Creating the MCP Server

Use create_sdk_mcp_server to create your calculator server:
import asyncio
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    create_sdk_mcp_server,
)

async def main():
    # Create the calculator server with all tools
    calculator = create_sdk_mcp_server(
        name="calculator",
        version="2.0.0",
        tools=[
            add_numbers,
            subtract_numbers,
            multiply_numbers,
            divide_numbers,
            square_root,
            power,
        ],
    )

    # Configure Claude to use the calculator server
    # Pre-approve all calculator MCP tools
    options = ClaudeAgentOptions(
        mcp_servers={"calc": calculator},
        allowed_tools=[
            "mcp__calc__add",
            "mcp__calc__subtract",
            "mcp__calc__multiply",
            "mcp__calc__divide",
            "mcp__calc__sqrt",
            "mcp__calc__power",
        ],
    )

    # Use the calculator
    async with ClaudeSDKClient(options=options) as client:
        await client.query("Calculate 15 + 27")
        
        async for message in client.receive_response():
            # Handle messages...
            pass

if __name__ == "__main__":
    asyncio.run(main())
MCP tool names follow the pattern mcp__{server_name}__{tool_name}. In this example, the add tool becomes mcp__calc__add.

Running Multiple Calculations

Here’s a complete example with multiple prompts:
#!/usr/bin/env python3
"""Example: Calculator MCP Server.

This example demonstrates how to create an in-process MCP server with
calculator tools using the Claude Code Python SDK.
"""

import asyncio
from typing import Any

from claude_agent_sdk import (
    ClaudeAgentOptions,
    ClaudeSDKClient,
    create_sdk_mcp_server,
    tool,
    AssistantMessage,
    ResultMessage,
    TextBlock,
    ToolUseBlock,
)

# Define calculator tools using the @tool decorator

@tool("add", "Add two numbers", {"a": float, "b": float})
async def add_numbers(args: dict[str, Any]) -> dict[str, Any]:
    """Add two numbers together."""
    result = args["a"] + args["b"]
    return {
        "content": [{"type": "text", "text": f"{args['a']} + {args['b']} = {result}"}]
    }


@tool("subtract", "Subtract one number from another", {"a": float, "b": float})
async def subtract_numbers(args: dict[str, Any]) -> dict[str, Any]:
    """Subtract b from a."""
    result = args["a"] - args["b"]
    return {
        "content": [{"type": "text", "text": f"{args['a']} - {args['b']} = {result}"}]
    }


@tool("multiply", "Multiply two numbers", {"a": float, "b": float})
async def multiply_numbers(args: dict[str, Any]) -> dict[str, Any]:
    """Multiply two numbers."""
    result = args["a"] * args["b"]
    return {
        "content": [{"type": "text", "text": f"{args['a']} × {args['b']} = {result}"}]
    }


@tool("divide", "Divide one number by another", {"a": float, "b": float})
async def divide_numbers(args: dict[str, Any]) -> dict[str, Any]:
    """Divide a by b."""
    if args["b"] == 0:
        return {
            "content": [
                {"type": "text", "text": "Error: Division by zero is not allowed"}
            ],
            "is_error": True,
        }

    result = args["a"] / args["b"]
    return {
        "content": [{"type": "text", "text": f"{args['a']} ÷ {args['b']} = {result}"}]
    }


@tool("sqrt", "Calculate square root", {"n": float})
async def square_root(args: dict[str, Any]) -> dict[str, Any]:
    """Calculate the square root of a number."""
    n = args["n"]
    if n < 0:
        return {
            "content": [
                {
                    "type": "text",
                    "text": f"Error: Cannot calculate square root of negative number {n}",
                }
            ],
            "is_error": True,
        }

    import math

    result = math.sqrt(n)
    return {"content": [{"type": "text", "text": f"√{n} = {result}"}]}


@tool("power", "Raise a number to a power", {"base": float, "exponent": float})
async def power(args: dict[str, Any]) -> dict[str, Any]:
    """Raise base to the exponent power."""
    result = args["base"] ** args["exponent"]
    return {
        "content": [
            {"type": "text", "text": f"{args['base']}^{args['exponent']} = {result}"}
        ]
    }


def display_message(msg):
    """Display message content in a clean format."""
    if isinstance(msg, AssistantMessage):
        for block in msg.content:
            if isinstance(block, TextBlock):
                print(f"Claude: {block.text}")
            elif isinstance(block, ToolUseBlock):
                print(f"Using tool: {block.name}")
                # Show tool inputs for calculator
                if block.input:
                    print(f"  Input: {block.input}")
    elif isinstance(msg, ResultMessage):
        print("Result ended")
        if msg.total_cost_usd:
            print(f"Cost: ${msg.total_cost_usd:.6f}")


async def main():
    """Run example calculations using the SDK MCP server with streaming client."""
    # Create the calculator server with all tools
    calculator = create_sdk_mcp_server(
        name="calculator",
        version="2.0.0",
        tools=[
            add_numbers,
            subtract_numbers,
            multiply_numbers,
            divide_numbers,
            square_root,
            power,
        ],
    )

    # Configure Claude to use the calculator server with allowed tools
    # Pre-approve all calculator MCP tools so they can be used without permission prompts
    options = ClaudeAgentOptions(
        mcp_servers={"calc": calculator},
        allowed_tools=[
            "mcp__calc__add",
            "mcp__calc__subtract",
            "mcp__calc__multiply",
            "mcp__calc__divide",
            "mcp__calc__sqrt",
            "mcp__calc__power",
        ],
    )

    # Example prompts to demonstrate calculator usage
    prompts = [
        "List your tools",
        "Calculate 15 + 27",
        "What is 100 divided by 7?",
        "Calculate the square root of 144",
        "What is 2 raised to the power of 8?",
        "Calculate (12 + 8) * 3 - 10",  # Complex calculation
    ]

    for prompt in prompts:
        print(f"\n{'=' * 50}")
        print(f"Prompt: {prompt}")
        print(f"{'=' * 50}")

        async with ClaudeSDKClient(options=options) as client:
            await client.query(prompt)

            async for message in client.receive_response():
                display_message(message)


if __name__ == "__main__":
    asyncio.run(main())

Tool Response Format

Tools must return a dictionary with:
  • content: List of content blocks (typically text)
  • is_error (optional): Boolean indicating if an error occurred
# Success response
return {
    "content": [{"type": "text", "text": "Result: 42"}]
}

# Error response
return {
    "content": [{"type": "text", "text": "Error: Division by zero"}],
    "is_error": True,
}

Key Takeaways

  • Use @tool decorator to define MCP tools
  • MCP tools are async functions that accept a dict and return a dict
  • Use create_sdk_mcp_server() to create the server
  • Add server to mcp_servers in ClaudeAgentOptions
  • Pre-approve tools using the mcp__{server}__{tool} naming pattern
  • Include error handling in your tools

Next Steps

Tool Permissions

Control tool access with callbacks

Hooks

Customize behavior with hooks

Build docs developers (and LLMs) love