Back to modules
Module 4

Tools & Tool Use

Master all built-in tools, how tools work, tool permissions, custom tools, and error handling.

📖 40 min
Tool CatalogTool PermissionsCustom ToolsError Handling

How Tool Permissions Work

Three layers control what an agent can do:

Layer 1: tools (Availability)

Remove tools from Claude's context entirely.

python
options = ClaudeAgentOptions(

tools=["Read", "Bash", "Grep"] # Only these visible to Claude

)

If a tool isn't here, Claude never sees it. Claude can't use what it doesn't know exists.

Layer 2: allowedtools (Auto-approval)

Pre-approve tools so the user isn't asked for permission.

python
options = ClaudeAgentOptions(

allowedtools=["Read", "Bash(ls )", "Grep"]

)

Pattern matching with wildcards:

  • "Bash" — All Bash commands are auto-approved
  • "Bash(ls )" — Only ls commands are auto-approved
  • "Bash(rm )" — Only rm commands (dangerous!)
  • This prevents permission prompts for trusted commands.

    Layer 3: permissionmode (Approval behavior)

    For tools not in allowedtools, how should they be handled?

    python
    options = ClaudeAgentOptions(

    allowedtools=["Read"],

    permissionmode="default" # Prompt user for anything else

    )

    Modes:

  • "default" — Ask user for permission
  • "dontAsk" — Deny permission, Claude can't use tool
  • "acceptEdits" — Auto-approve file edits (Read/Write/Edit only)
  • "plan" — Show plan before executing edits
  • "bypassPermissions" — Auto-approve everything (careful!)
  • Example: Restrictive Setup

    python
    options = ClaudeAgentOptions(

    allowedtools=["Read", "Grep", "WebSearch"],

    disallowedtools=["Bash", "Write", "Edit"], # Explicitly block these

    permissionmode="dontAsk" # Don't ask for others

    )

    This agent can: read files, search files, search web. Can't: run commands, write files, edit files.

    ---

    Tool Schemas

    Each tool has a schema — Claude needs to understand what parameters it needs.

    How It Works

    plaintext
    Claude: "I need to read the file"

    Sees: Read(filepath: string)

    Calls: Read(filepath="/path/to/file.txt")

    System: Runs Read, captures result

    Returns: File contents to Claude

    Claude knows what parameters each tool needs from the schema.

    Tool Naming for MCP Servers

    When using external MCP servers, tools are named with a specific format:

    plaintext
    mcp<server><tool>

    Example:

    python
    options = ClaudeAgentOptions(
    

    allowedtools=["mcpgithublistissues", "mcpgithubcreatepr"]

    )

    Means: "GitHub MCP server" + "listissues tool" and "createpr tool".

    ---

    How Tools Results Flow Back

    The agent loop feeds results back to Claude automatically:

    plaintext
    1. Claude decides to use Read("package.json")

    ↓

  • SDK executes the Read tool
  • ↓

  • System captures file contents
  • ↓

  • Results wrapped as UserMessage with tool results
  • ↓

  • Sent back to Claude automatically
  • ↓

  • Claude sees results, decides next step
  • ↓

  • Loop repeats until Claude produces text with no tools
  • You don't need to manage this loop — SDK handles it.

    ---

    Custom Tools (SDK Only)

    The Agent SDK lets you define custom tools that agents can use.

    Python Example

    python
    from claudeagentsdk import tool, createsdkmcpserver

    import requests

    @tool(

    "getweather",

    "Get current temperature at a location",

    {

    "latitude": float,

    "longitude": float,

    "unit": str # "celsius" or "fahrenheit"

    }

    )

    async def getweather(args: dict) -> dict:

    lat = args["latitude"]

    lon = args["longitude"]

    unit = args.get("unit", "celsius")

    # Call API

    response = requests.get(

    f"https://api.weather.example.com/temp?lat={lat}&lon={lon}&unit={unit}"

    )

    if response.statuscode != 200:

    return {

    "content": [{

    "type": "text",

    "text": f"Error: {response.statuscode}"

    }],

    "iserror": True

    }

    temp = response.json()["temperature"]

    return {

    "content": [{

    "type": "text",

    "text": f"Temperature at ({lat}, {lon}): {temp}°{unit[0].upper()}"

    }]

    }

    Create MCP server

    weatherserver = createsdkmcpserver(

    name="weather",

    version="1.0.0",

    tools=[getweather]

    )

    Use in agent

    options = ClaudeAgentOptions(

    mcpservers={

    "weather": weatherserver

    },

    allowedtools=["mcpweathergetweather"]

    )

    TypeScript Example

    typescript
    import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";

    import { z } from "zod";

    const getWeather = tool(

    "getweather",

    "Get current temperature at a location",

    {

    latitude: z.number(),

    longitude: z.number(),

    unit: z.enum(["celsius", "fahrenheit"]).default("celsius")

    },

    async (args) => {

    const response = await fetch(

    https://api.weather.example.com/temp?lat=${args.latitude}&lon=${args.longitude}&unit=${args.unit}

    );

    if (!response.ok) {

    return {

    content: [{

    type: "text" as const,

    text: Error: ${response.status}

    }],

    iserror: true

    };

    }

    const { temperature } = await response.json();

    return {

    content: [{

    type: "text" as const,

    text: Temperature: ${temperature}°${args.unit[0].toUpperCase()}

    }]

    };

    }

    );

    const weatherServer = createSdkMcpServer({

    name: "weather",

    version: "1.0.0",

    tools: [getWeather]

    });

    ---

    Error Handling

    Two Approaches

    #### Approach 1: Throw Exception (Stops Agent)

    python
    async def mytool(args):

    result = someoperation()

    if not result:

    raise ValueError("Operation failed") # ❌ Stops agent loop!

    When you throw, the entire agent stops. Avoid this for recoverable errors.

    #### Approach 2: Return Error Result (Agent Continues)

    python
    async def mytool(args):

    try:

    result = someoperation()

    if not result:

    return {

    "content": [{

    "type": "text",

    "text": "Operation failed, trying fallback"

    }],

    "iserror": True # Marks as error

    }

    return {

    "content": [{

    "type": "text",

    "text": f"Success: {result}"

    }]

    }

    except Exception as e:

    return {

    "content": [{

    "type": "text",

    "text": f"Exception: {str(e)}"

    }],

    "iserror": True

    }

    Return error results so Claude can retry or try a different approach.

    ---

    Built-In Tool Highlights

    Read

    python
    Read(filepath="/path/to/file.txt")

    Returns: File contents as text

    Bash

    python
    Bash(command="npm test")

    Returns: stdout/stderr output

    Grep

    python
    Grep(pattern="async def", filepath="/path/file.py")

    Returns: Matching lines with line numbers

    WebSearch

    python
    WebSearch(query="Claude Code agent SDK")

    Returns: Search results with URLs

    AskUserQuestion

    python
    AskUserQuestion(

    question="Which database should we use?",

    options=["PostgreSQL", "MongoDB", "DynamoDB"]

    )

    Returns: User's selection

    ---

    Key Gotchas

    Gotcha #1: Tool naming matters

  • Custom tools must follow MCP format: mcpservertool
  • CLI agent looks for these exact names
  • Typo = tool won't be found
  • Gotcha #2: Exceptions stop the loop

  • If your custom tool throws, agent stops completely
  • Always return error results instead
  • Let Claude decide to retry
  • Gotcha #3: Permissions are checked in order

  • allowedtools is checked first
  • If in allowedtools, auto-approve
  • Then disallowedtools is checked
  • Then permissionmode applies
  • Gotcha #4: Tool outputs grow context

  • Large outputs (big files, long command results) = many tokens
  • Grep results scale with matches
  • Bash output can be huge
  • Affects cost and remaining context
  • Gotcha #5: MCP server startup

  • External MCP servers need time to start
  • First tool call may be slow
  • Subsequent calls are faster
  • ---

    Best Practices

  • Pre-approve safe tools: allowedtools=["Read", "Grep"]
  • Return errors, don't throw: Let Claude retry
  • Limit tool output: Grep specific patterns, not entire files
  • Use specific tool patterns: Bash(ls ) instead of Bash
  • Test custom tools: Verify they work before deploying agents
  • ---

    Next Steps

    You now understand tools and how they work. Next module: Advanced SDK Patterns — hooks, streaming, cost tracking, and more!