Claude Agent SDK Quickstart: A Bug-Fixing Agent in Python or TS
The Claude Agent SDK gives your own programs the same agent loop that runs Claude Code: you call query() with a prompt and options, and Claude reads files, runs tools, observes the results, and keeps going until the task is done, while you consume a stream of messages. The SDK handles orchestration, tool execution, context management, and retries. It is not the plain Messages API, where you would have to implement every tool yourself; here the built-in tools (Read, Edit, Bash, Glob, Grep, WebSearch, and others) already work.
This guide follows Anthropic's SDK quickstart and custom-tools pages to build a small agent that finds and fixes crash bugs in a Python file, in both Python and TypeScript. It then covers the options that matter (tools, permission modes, system prompt), how to add a custom tool through an in-process MCP server, authentication rules, and where to go next. For a broader tour of the SDK, see Claude Agent SDK Guide.
Install and authenticate
You need Node.js 18+ or Python 3.10+ and an Anthropic account. Install the SDK for your language:
1# TypeScript (new project)2npm init -y3npm pkg set type=module4npm install @anthropic-ai/claude-agent-sdk5npm install --save-dev tsx6 7# Python8python3 -m venv .venv && source .venv/bin/activate9pip install claude-agent-sdk # or: uv add claude-agent-sdkBoth SDKs bundle a native Claude Code binary, so most installs need nothing else. Two exceptions: a Python source-distribution install (for example on ARM64 Windows) has no bundled binary, and a TypeScript install that skips optional dependencies (npm ci --omit=optional) does not get one either; install Claude Code natively in those cases.
Authenticate with an API key from the Claude Console:
1export ANTHROPIC_API_KEY=your-api-keyThe SDK reads the key from the process environment and does not load .env files; if you keep the key in one, load it yourself (for example with dotenv). To use a cloud provider instead, set CLAUDE_CODE_USE_BEDROCK=1, CLAUDE_CODE_USE_VERTEX=1, or CLAUDE_CODE_USE_FOUNDRY=1 with that provider's credentials.
Quick reference
- Anthropic does not allow third-party developers to offer claude.ai login or subscription rate limits in their products, including agents built on the SDK, unless previously approved. Use API keys or a cloud provider.
- Setting
"type": "module"enables top-levelawait; in a CommonJS project, name the scriptagent.mtsinstead. Not logged inorInvalid API keyalmost always means the variable is not set in the shell that runs the agent.
Remember this
Install one package, export ANTHROPIC_API_KEY in the same shell, and do not rely on .env loading.
A first agent: find and fix crash bugs
Create utils.py with two bugs: calculate_average([]) divides by zero, and get_user_name(None) raises a TypeError.
1def calculate_average(numbers):2 total = 03 for num in numbers:4 total += num5 return total / len(numbers)6 7def get_user_name(user):8 return user["name"].upper()Then the agent, in TypeScript (agent.ts):
1import { query } from "@anthropic-ai/claude-agent-sdk";2 3for await (const message of query({4 prompt: "Review utils.py for bugs that would cause crashes. Fix any issues you find.",5 options: {6 allowedTools: ["Read", "Edit", "Glob"], // pre-approve these tools7 permissionMode: "acceptEdits" // auto-approve file edits8 }9})) {10 if (message.type === "assistant" && message.message?.content) {11 for (const block of message.message.content) {12 if ("text" in block) console.log(block.text);13 else if ("name" in block) console.log(`Tool: ${block.name}`);14 }15 } else if (message.type === "result") {16 console.log(`Done: ${message.subtype}`);17 }18}Or in Python (agent.py):
1import asyncio2from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ResultMessage3 4async def main():5 async for message in query(6 prompt="Review utils.py for bugs that would cause crashes. Fix any issues you find.",7 options=ClaudeAgentOptions(8 allowed_tools=["Read", "Edit", "Glob"],9 permission_mode="acceptEdits",10 ),11 ):12 if isinstance(message, AssistantMessage):13 for block in message.content:14 if hasattr(block, "text"):15 print(block.text)16 elif hasattr(block, "name"):17 print(f"Tool: {block.name}")18 elif isinstance(message, ResultMessage):19 print(f"Done: {message.subtype}")20 21asyncio.run(main())Run it with npx tsx agent.ts or python agent.py. It prints Claude's reasoning and each tool call, ends with Done: success, and leaves utils.py with guards for the empty list and the missing user.
Quick reference
query()returns an async iterator; each item is a message: assistant text, a tool call, a tool result, a system message, or the final result.- The filtering in the loop is only for readability; unfiltered you also see system initialisation and internal state, which helps when debugging.
- The agent can access the working directory and its subdirectories by default, so run it from the folder it should work in.
Remember this
query() plus allowedTools and a permission mode is a complete agent; the loop just reads the message stream.
Options that shape the agent
Most behaviour comes from the options object. The ones to understand first:
- Tools. Choose by job:
Read,Glob,Grepfor read-only analysis; addEditto modify code; addBashfor full automation such as "write unit tests for utils.py, run them, and fix any failures".WebSearchadds web research. - Two layers of tool control.
tools: ["Read", "Grep"]changes availability: unlisted built-ins disappear from Claude's context (tools: []leaves only your MCP tools).allowedToolschanges permission: listed tools run without a prompt, others still go through the permission flow. A bare name indisallowedTools, such as"Bash", removes the tool; a scoped rule such as"Bash(rm *)"leaves it visible and denies matching calls. - Permission mode.
acceptEditsauto-approves file edits; other modes match Claude Code's. The SDK evaluates the mode together with your allow and deny rules in a fixed order. - System prompt.
systemPrompt: "You are a senior Python developer. Always follow PEP 8."sets role and conventions.
Prefer removing tools an agent should never use over denying them: a removed tool is never attempted, while a denied one can waste a turn when Claude tries it.
Quick reference
- Start with the narrowest tool set that can do the job, and add
Bashonly when the agent must run commands. - An agent with
BashandacceptEditsrunning unattended has the same reach as a shell session; run it in a container or VM for anything untrusted. - For background jobs or CI, you can collect all messages instead of streaming; the docs compare streaming and single-turn modes.
Remember this
Use tools to decide what Claude can see and allowedTools to decide what runs without asking.
Add a custom tool with an in-process MCP server
To let Claude call your own function, define a tool and wrap it in an in-process MCP server. A tool has a name, a description Claude reads to decide when to use it, an input schema (Zod in TypeScript, a type dict or JSON Schema in Python), and an async handler that returns a content array:
1import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";2import { z } from "zod";3 4const getTemperature = tool(5 "get_temperature",6 "Get the current temperature at a location",7 {8 latitude: z.number().describe("Latitude coordinate"),9 longitude: z.number().describe("Longitude coordinate")10 },11 async (args) => {12 const res = await fetch(13 `https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}¤t=temperature_2m&temperature_unit=fahrenheit`14 );15 const data: any = await res.json();16 return { content: [{ type: "text", text: `Temperature: ${data.current.temperature_2m}°F` }] };17 }18);19 20const weatherServer = createSdkMcpServer({ name: "weather", version: "1.0.0", tools: [getTemperature] });21 22for await (const message of query({23 prompt: "What's the temperature in San Francisco?",24 options: {25 mcpServers: { weather: weatherServer },26 allowedTools: ["mcp__weather__get_temperature"]27 }28})) {29 if (message.type === "result" && message.subtype === "success") console.log(message.result);30}The server runs inside your process. Each tool's full name is mcp__{server_name}__{tool_name}, where the server name is the key in mcpServers; list it in allowedTools so it runs without a prompt, or use mcp__weather__* for every tool on the server. Python uses the @tool decorator and create_sdk_mcp_server the same way.
Quick reference
- A handler that throws does not stop the agent: the error reaches Claude as a tool error. Return
isError: truewith your own message when the raw exception would not tell Claude what to do next. - Mark side-effect-free tools with
readOnlyHint: trueso Claude can call them in parallel; annotations are metadata, not enforcement. - Tool search is on by default, so Claude loads each SDK tool's full schema only when needed; many tools then cost little context.
Remember this
tool() plus createSdkMcpServer() exposes your function as mcp__server__tool; pre-approve it and return clear errors.
Where to go next, and what to watch
The quickstart's agent is a single query() call. Real agents usually add some of these, each covered in the SDK docs:
- Sessions: multi-turn agents that keep context across calls, and resuming earlier sessions.
- Hooks: run your own code before or after tool calls, for validation, logging, or blocking, in the same spirit as Claude Code hooks.
- Permissions: a
canUseToolcallback to approve or deny calls from your application, beyond static rules. - External MCP servers: databases, browsers, and SaaS APIs, instead of writing tools yourself.
- Hosting: running agents in Docker, cloud, or CI.
Two cautions carry over from Claude Code. First, cost follows context: an agent that reads many files or runs long loops uses many tokens, so give it narrow prompts and tools. Second, an autonomous agent is only as safe as its tools and environment; an agent with Bash and auto-approved edits should run where its mistakes cannot reach production data or credentials.
Quick reference
- Anthropic's example repository includes complete agents such as an email assistant and a research agent.
- If the CLI fails to start inside the SDK, or a result arrives without structured output, the SDK troubleshooting page covers the causes.
- Pin the SDK version in production; the agent loop and options evolve with Claude Code releases.
Remember this
Add sessions, hooks, permission callbacks, and MCP servers as needed, and sandbox any agent that can run commands.
Hands-on: extend the bug-fixer
1. Build the quickstart agent in your language and run it until Done: success.
2. Change the prompt to "Write unit tests for utils.py, run them, and fix any failures", add Bash to allowedTools, and run it again.
3. Replace allowedTools with tools: ["Read", "Glob", "Grep"] and no edit permission, and ask for a review only. Confirm no file changes.
4. Add the get_temperature tool (or a tool that reads from your own API), register it, and ask a question that needs it.
5. Make the tool's handler fail on purpose and return isError: true with a helpful message; watch how Claude reacts.
Pass criterion: the agent fixed and tested the file in step 2, could not edit in step 3, and handled the tool failure in step 5 by explaining or retrying rather than crashing.
Quick reference
- Run step 2 in a scratch directory; with
Bashallowed, the agent can run any command your user can. - Print every message type once (without filtering) to learn the stream's shape.
- Track the token use from the result message to see what each change costs.
Remember this
Fix, test, restrict, extend, and fail on purpose; that covers most of what a production agent needs.
Key takeaway
The Claude Agent SDK packages Claude Code's agent loop for your own programs. Install @anthropic-ai/claude-agent-sdk or claude-agent-sdk, export ANTHROPIC_API_KEY (the SDK does not read .env), and call query() with a prompt, allowedTools, and a permission mode; then read the message stream until the result arrives. Use tools to control what Claude can see and allowedTools to control what runs unasked, and add your own functions as in-process MCP tools named mcp__server__tool.
Products built on the SDK must use API keys or cloud providers, not claude.ai logins, unless Anthropic approves otherwise. Treat an agent with Bash like a shell session and give it a sandbox. For the permission model the SDK shares with Claude Code, see Claude Code Permissions; for hooks, see Claude Code Hooks.
Practice (45 min): complete the five steps above.
References
- Claude Agent SDK docs: Quickstart — checked Oct 17, 2026
- Claude Agent SDK docs: Give Claude custom tools — checked Oct 17, 2026
- Claude Agent SDK docs: Overview — checked Oct 17, 2026
Shoaib Hossain
Lead Author & Systems ArchitectSoftware engineer and distributed systems architect specializing in backend scalability, cloud-native infrastructure, databases, and AI engineering workflows. Author and maintainer of Core Concept Learning.
Related Articles
Explore this topic