How Tool Permissions Work
Three layers control what an agent can do:
Layer 1: tools (Availability)
Remove tools from Claude's context entirely.
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.
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?
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
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
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:
mcp<server><tool>Example:
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:
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
from claudeagentsdk import tool, createsdkmcpserver
import requests
@tool(
"get
weather",
"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
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)
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)
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
Read(filepath="/path/to/file.txt")
Returns: File contents as text
Bash
Bash(command="npm test")
Returns: stdout/stderr output
Grep
Grep(pattern="async def", filepath="/path/file.py")
Returns: Matching lines with line numbers
WebSearch
WebSearch(query="Claude Code agent SDK")
Returns: Search results with URLs
AskUserQuestion
AskUserQuestion(
question="Which database should we use?",
options=["PostgreSQL", "MongoDB", "DynamoDB"]
)
Returns: User's selection
---
Key Gotchas
Gotcha #1: Tool naming matters
mcpservertoolGotcha #2: Exceptions stop the loop
Gotcha #3: Permissions are checked in order
allowedtools is checked firstallowedtools, auto-approvedisallowedtools is checkedpermissionmode appliesGotcha #4: Tool outputs grow context
Gotcha #5: MCP server startup
---
Best Practices
allowedtools=["Read", "Grep"]Bash(ls ) instead of Bash---
Next Steps
You now understand tools and how they work. Next module: Advanced SDK Patterns — hooks, streaming, cost tracking, and more!