> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/anthropics/claude-agent-sdk-python/llms.txt
> Use this file to discover all available pages before exploring further.

# Tool Permission Examples

> Control which tools Claude can use and modify their inputs with permission callbacks

Tool permission callbacks allow you to control which tools Claude can use and modify their inputs before execution. This is essential for building secure applications.

## Basic Permission Callback

Create a callback function that returns `PermissionResultAllow` or `PermissionResultDeny`:

```python theme={null}
from claude_agent_sdk import (
    ClaudeAgentOptions,
    PermissionResultAllow,
    PermissionResultDeny,
    ToolPermissionContext,
)

async def my_permission_callback(
    tool_name: str,
    input_data: dict,
    context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    """Control tool permissions based on tool type and input."""
    
    # Always allow read operations
    if tool_name in ["Read", "Glob", "Grep"]:
        print(f"✅ Automatically allowing {tool_name} (read-only operation)")
        return PermissionResultAllow()
    
    # Deny other operations by default
    return PermissionResultDeny(
        message=f"Tool {tool_name} is not approved for use"
    )
```

## Allowing Specific Operations

Automatically approve safe, read-only operations:

```python theme={null}
async def safe_permission_callback(
    tool_name: str,
    input_data: dict,
    context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    """Allow read operations, ask for write operations."""
    
    print(f"\n🔧 Tool Permission Request: {tool_name}")
    print(f"   Input: {json.dumps(input_data, indent=2)}")

    # Always allow read operations
    if tool_name in ["Read", "Glob", "Grep"]:
        print(f"   ✅ Automatically allowing {tool_name} (read-only operation)")
        return PermissionResultAllow()

    # Prompt for write operations
    user_input = input(f"   Allow {tool_name}? (y/N): ").strip().lower()
    
    if user_input in ("y", "yes"):
        return PermissionResultAllow()
    else:
        return PermissionResultDeny(
            message="User denied permission"
        )
```

## Denying Dangerous Operations

Block operations that could be harmful:

```python theme={null}
import json

async def security_callback(
    tool_name: str,
    input_data: dict,
    context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    """Block dangerous operations."""
    
    # Deny write operations to system directories
    if tool_name in ["Write", "Edit", "MultiEdit"]:
        file_path = input_data.get("file_path", "")
        if file_path.startswith("/etc/") or file_path.startswith("/usr/"):
            print(f"   ❌ Denying write to system directory: {file_path}")
            return PermissionResultDeny(
                message=f"Cannot write to system directory: {file_path}"
            )

    # Check dangerous bash commands
    if tool_name == "Bash":
        command = input_data.get("command", "")
        dangerous_commands = ["rm -rf", "sudo", "chmod 777", "dd if=", "mkfs"]

        for dangerous in dangerous_commands:
            if dangerous in command:
                print(f"   ❌ Denying dangerous command: {command}")
                return PermissionResultDeny(
                    message=f"Dangerous command pattern detected: {dangerous}"
                )

        print(f"   ✅ Allowing bash command: {command}")
        return PermissionResultAllow()

    return PermissionResultAllow()
```

## Modifying Tool Inputs

Redirect operations to safe locations by modifying inputs:

```python theme={null}
async def redirect_callback(
    tool_name: str,
    input_data: dict,
    context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    """Redirect writes to a safe directory."""
    
    if tool_name in ["Write", "Edit", "MultiEdit"]:
        file_path = input_data.get("file_path", "")
        
        # Redirect writes to a safe directory
        if not file_path.startswith("/tmp/") and not file_path.startswith("./"):
            safe_path = f"./safe_output/{file_path.split('/')[-1]}"
            print(f"   ⚠️  Redirecting write from {file_path} to {safe_path}")
            
            modified_input = input_data.copy()
            modified_input["file_path"] = safe_path
            
            return PermissionResultAllow(
                updated_input=modified_input
            )
    
    return PermissionResultAllow()
```

## Logging Tool Usage

Track which tools are being used:

```python theme={null}
import json

# Track tool usage for demonstration
tool_usage_log = []

async def logging_callback(
    tool_name: str,
    input_data: dict,
    context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    """Log all tool requests."""
    
    # Log the tool request
    tool_usage_log.append({
        "tool": tool_name,
        "input": input_data,
        "suggestions": context.suggestions
    })
    
    print(f"\n🔧 Tool Permission Request: {tool_name}")
    print(f"   Input: {json.dumps(input_data, indent=2)}")
    
    # Allow all operations but log them
    return PermissionResultAllow()
```

## Using Permission Callbacks

Configure the callback in `ClaudeAgentOptions`:

```python theme={null}
import asyncio
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    AssistantMessage,
    ResultMessage,
    TextBlock,
)

async def main():
    # Configure options with our callback
    options = ClaudeAgentOptions(
        can_use_tool=my_permission_callback,
        permission_mode="default",  # Ensure callbacks are invoked
        cwd="."  # Set working directory
    )

    # Create client and send a query
    async with ClaudeSDKClient(options) as client:
        await client.query(
            "Please do the following:\n"
            "1. List the files in the current directory\n"
            "2. Create a simple Python hello world script at hello.py\n"
            "3. Run the script to test it"
        )

        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"\n💬 Claude: {block.text}")

            elif isinstance(message, ResultMessage):
                print("\n✅ Task completed!")
                print(f"   Duration: {message.duration_ms}ms")
                if message.total_cost_usd:
                    print(f"   Cost: ${message.total_cost_usd:.4f}")

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

## Complete Example

<Accordion title="Full working example with comprehensive callbacks">
  ```python theme={null}
  #!/usr/bin/env python3
  """Example: Tool Permission Callbacks.

  This example demonstrates how to use tool permission callbacks to control
  which tools Claude can use and modify their inputs.
  """

  import asyncio
  import json

  from claude_agent_sdk import (
      AssistantMessage,
      ClaudeAgentOptions,
      ClaudeSDKClient,
      PermissionResultAllow,
      PermissionResultDeny,
      ResultMessage,
      TextBlock,
      ToolPermissionContext,
  )

  # Track tool usage for demonstration
  tool_usage_log = []


  async def my_permission_callback(
      tool_name: str,
      input_data: dict,
      context: ToolPermissionContext
  ) -> PermissionResultAllow | PermissionResultDeny:
      """Control tool permissions based on tool type and input."""

      # Log the tool request
      tool_usage_log.append({
          "tool": tool_name,
          "input": input_data,
          "suggestions": context.suggestions
      })

      print(f"\n🔧 Tool Permission Request: {tool_name}")
      print(f"   Input: {json.dumps(input_data, indent=2)}")

      # Always allow read operations
      if tool_name in ["Read", "Glob", "Grep"]:
          print(f"   ✅ Automatically allowing {tool_name} (read-only operation)")
          return PermissionResultAllow()

      # Deny write operations to system directories
      if tool_name in ["Write", "Edit", "MultiEdit"]:
          file_path = input_data.get("file_path", "")
          if file_path.startswith("/etc/") or file_path.startswith("/usr/"):
              print(f"   ❌ Denying write to system directory: {file_path}")
              return PermissionResultDeny(
                  message=f"Cannot write to system directory: {file_path}"
              )

          # Redirect writes to a safe directory
          if not file_path.startswith("/tmp/") and not file_path.startswith("./"):
              safe_path = f"./safe_output/{file_path.split('/')[-1]}"
              print(f"   ⚠️  Redirecting write from {file_path} to {safe_path}")
              modified_input = input_data.copy()
              modified_input["file_path"] = safe_path
              return PermissionResultAllow(
                  updated_input=modified_input
              )

      # Check dangerous bash commands
      if tool_name == "Bash":
          command = input_data.get("command", "")
          dangerous_commands = ["rm -rf", "sudo", "chmod 777", "dd if=", "mkfs"]

          for dangerous in dangerous_commands:
              if dangerous in command:
                  print(f"   ❌ Denying dangerous command: {command}")
                  return PermissionResultDeny(
                      message=f"Dangerous command pattern detected: {dangerous}"
                  )

          # Allow but log the command
          print(f"   ✅ Allowing bash command: {command}")
          return PermissionResultAllow()

      # For all other tools, ask the user
      print(f"   ❓ Unknown tool: {tool_name}")
      print(f"      Input: {json.dumps(input_data, indent=6)}")
      user_input = input("   Allow this tool? (y/N): ").strip().lower()

      if user_input in ("y", "yes"):
          return PermissionResultAllow()
      else:
          return PermissionResultDeny(
              message="User denied permission"
          )


  async def main():
      """Run example with tool permission callbacks."""

      print("=" * 60)
      print("Tool Permission Callback Example")
      print("=" * 60)
      print("\nThis example demonstrates how to:")
      print("1. Allow/deny tools based on type")
      print("2. Modify tool inputs for safety")
      print("3. Log tool usage")
      print("4. Prompt for unknown tools")
      print("=" * 60)

      # Configure options with our callback
      options = ClaudeAgentOptions(
          can_use_tool=my_permission_callback,
          # Use default permission mode to ensure callbacks are invoked
          permission_mode="default",
          cwd="."  # Set working directory
      )

      # Create client and send a query that will use multiple tools
      async with ClaudeSDKClient(options) as client:
          print("\n📝 Sending query to Claude...")
          await client.query(
              "Please do the following:\n"
              "1. List the files in the current directory\n"
              "2. Create a simple Python hello world script at hello.py\n"
              "3. Run the script to test it"
          )

          print("\n📨 Receiving response...")
          message_count = 0

          async for message in client.receive_response():
              message_count += 1

              if isinstance(message, AssistantMessage):
                  # Print Claude's text responses
                  for block in message.content:
                      if isinstance(block, TextBlock):
                          print(f"\n💬 Claude: {block.text}")

              elif isinstance(message, ResultMessage):
                  print("\n✅ Task completed!")
                  print(f"   Duration: {message.duration_ms}ms")
                  if message.total_cost_usd:
                      print(f"   Cost: ${message.total_cost_usd:.4f}")
                  print(f"   Messages processed: {message_count}")

      # Print tool usage summary
      print("\n" + "=" * 60)
      print("Tool Usage Summary")
      print("=" * 60)
      for i, usage in enumerate(tool_usage_log, 1):
          print(f"\n{i}. Tool: {usage['tool']}")
          print(f"   Input: {json.dumps(usage['input'], indent=6)}")
          if usage['suggestions']:
              print(f"   Suggestions: {usage['suggestions']}")


  if __name__ == "__main__":
      asyncio.run(main())
  ```
</Accordion>

## Permission Context

The `ToolPermissionContext` provides additional information:

```python theme={null}
class ToolPermissionContext:
    suggestions: list[str]  # Suggested actions or warnings
```

You can use this context to make more informed decisions:

```python theme={null}
async def context_aware_callback(
    tool_name: str,
    input_data: dict,
    context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    """Use context to inform decisions."""
    
    # Check if there are any warnings
    if context.suggestions:
        print(f"⚠️ Warnings: {context.suggestions}")
    
    return PermissionResultAllow()
```

## Key Takeaways

* Use `can_use_tool` parameter in `ClaudeAgentOptions` to set your callback
* Return `PermissionResultAllow()` to allow tool use
* Return `PermissionResultDeny(message="...")` to deny tool use
* Use `updated_input` to modify tool inputs before execution
* Set `permission_mode="default"` to ensure callbacks are invoked
* Log tool usage for auditing and debugging

## Next Steps

<CardGroup cols={2}>
  <Card title="Hooks" icon="hook" href="/examples/hooks-example">
    Advanced customization with hooks
  </Card>

  <Card title="MCP Calculator" icon="calculator" href="/examples/mcp-calculator">
    Build custom MCP tools
  </Card>
</CardGroup>
