diff --git a/AGENT.md b/AGENT.md
new file mode 100644
index 00000000..35c1981a
--- /dev/null
+++ b/AGENT.md
@@ -0,0 +1,22 @@
+# Claude Agent SDK Python Repository Analysis
+
+This repository hosts the Python Software Development Kit for interacting with Claude agents, designed to facilitate the integration of Claude's conversational and tool-use capabilities into Python applications.
+
+## Key Technologies and Frameworks
+* **Python (3.10+):** The primary programming language.
+* **Anyio:** Provides asynchronous programming primitives for non-blocking operations.
+* **Multi-Content Protocol (MCP):** Essential for communication between the SDK and Claude agents, particularly for tool invocation.
+* **Claude Code CLI:** The command-line interface for Claude Code, bundled with the SDK for core functionality.
+
+## Main Features
+* **Agent Querying:** Programmatic querying of Claude Code with text prompts.
+* **Interactive Sessions:** Supports bidirectional, interactive conversations with Claude agents via `ClaudeSDKClient`.
+* **Custom Tooling:** Enables developers to define and integrate custom tools as in-process SDK MCP servers, enhancing Claude's capabilities.
+* **Lifecycle Hooks:** Provides hooks for deterministic processing and automated feedback during the Claude agent's execution flow.
+* **Error Handling:** Comprehensive error handling for various issues related to CLI interaction and process management.
+
+## Architectural Patterns
+* **Client-Server Architecture:** The Python SDK acts as a client that communicates with the Claude Code CLI (the server).
+* **Extensibility/Plugin Pattern:** Custom tools and hooks serve as extension points, allowing developers to extend and customize the agent's behavior.
+* **Asynchronous Design:** Leverages `anyio` for efficient, non-blocking I/O operations, crucial for interactive agent communication.
+* **In-Process Communication:** Offers the ability to run custom tool servers directly within the application's process, optimizing performance by reducing inter-process communication overhead.
diff --git a/Claude_Agent_SDK_Complete_Guide.ipynb b/Claude_Agent_SDK_Complete_Guide.ipynb
new file mode 100644
index 00000000..204e23cb
--- /dev/null
+++ b/Claude_Agent_SDK_Complete_Guide.ipynb
@@ -0,0 +1,1710 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Claude Agent SDK for Python - Complete Guide\n",
+ "\n",
+ "This comprehensive notebook covers **100% of the Claude Agent SDK for Python** documentation and examples.\n",
+ "\n",
+ "## Table of Contents\n",
+ "\n",
+ "1. [Introduction & Installation](#introduction)\n",
+ "2. [Quick Start](#quick-start)\n",
+ "3. [Basic Usage: query()](#basic-usage)\n",
+ "4. [Using Tools](#using-tools)\n",
+ "5. [ClaudeSDKClient (Streaming Mode)](#streaming-mode)\n",
+ "6. [Custom Tools (SDK MCP Servers)](#custom-tools)\n",
+ "7. [Hooks](#hooks)\n",
+ "8. [Permission Callbacks](#permission-callbacks)\n",
+ "9. [Custom Agents](#custom-agents)\n",
+ "10. [Configuration Options](#configuration)\n",
+ "11. [Error Handling](#error-handling)\n",
+ "12. [Advanced Examples](#advanced-examples)\n",
+ "\n",
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 1. Introduction & Installation \n",
+ "\n",
+ "The Claude Agent SDK for Python allows you to build production AI agents with Claude Code as a library. You get the same tools, agent loop, and context management that power Claude Code, programmable in Python.\n",
+ "\n",
+ "### Prerequisites\n",
+ "\n",
+ "- Python 3.10+\n",
+ "\n",
+ "### Installation\n",
+ "\n",
+ "The Claude Code CLI is automatically bundled with the package - no separate installation required!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Install the Claude Agent SDK\n",
+ "!pip install claude-agent-sdk"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Optional: Custom CLI Path\n",
+ "\n",
+ "If you prefer to use a system-wide installation or specific version of Claude Code:\n",
+ "\n",
+ "```bash\n",
+ "# Install Claude Code separately\n",
+ "curl -fsSL https://claude.ai/install.sh | bash\n",
+ "```\n",
+ "\n",
+ "Then specify the path:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import ClaudeAgentOptions\n",
+ "\n",
+ "# Use custom CLI path\n",
+ "options = ClaudeAgentOptions(cli_path=\"/path/to/claude\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "\n",
+ "## 2. Quick Start \n",
+ "\n",
+ "The simplest way to use Claude Agent SDK is with the `query()` function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import anyio\n",
+ "from claude_agent_sdk import query\n",
+ "\n",
+ "async def main():\n",
+ " async for message in query(prompt=\"What is 2 + 2?\"):\n",
+ " print(message)\n",
+ "\n",
+ "# Run the async function\n",
+ "anyio.run(main)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Pretty Print Responses\n",
+ "\n",
+ "To extract just the text from Claude's responses:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import query, AssistantMessage, TextBlock\n",
+ "import anyio\n",
+ "\n",
+ "async def basic_example():\n",
+ " \"\"\"Basic example - simple question.\"\"\"\n",
+ " print(\"=== Basic Example ===\")\n",
+ " \n",
+ " async for message in query(prompt=\"What is 2 + 2?\"):\n",
+ " if isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"Claude: {block.text}\")\n",
+ " print()\n",
+ "\n",
+ "anyio.run(basic_example)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "\n",
+ "## 3. Basic Usage: query() \n",
+ "\n",
+ "`query()` is an async function for querying Claude Code. It returns an `AsyncIterator` of response messages.\n",
+ "\n",
+ "### Simple Query"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, TextBlock\n",
+ "import anyio\n",
+ "\n",
+ "async def simple_query():\n",
+ " # Simple query\n",
+ " async for message in query(prompt=\"Hello Claude\"):\n",
+ " if isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(block.text)\n",
+ "\n",
+ "anyio.run(simple_query)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### With Options"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "async def with_options_example():\n",
+ " \"\"\"Example with custom options.\"\"\"\n",
+ " print(\"=== With Options Example ===\")\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " system_prompt=\"You are a helpful assistant that explains things simply.\",\n",
+ " max_turns=1,\n",
+ " )\n",
+ " \n",
+ " async for message in query(\n",
+ " prompt=\"Explain what Python is in one sentence.\",\n",
+ " options=options\n",
+ " ):\n",
+ " if isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"Claude: {block.text}\")\n",
+ " print()\n",
+ "\n",
+ "anyio.run(with_options_example)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### System Prompt Configuration\n",
+ "\n",
+ "You can configure the system prompt in multiple ways:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, TextBlock\n",
+ "import anyio\n",
+ "\n",
+ "# 1. String system prompt\n",
+ "async def string_system_prompt():\n",
+ " print(\"=== String System Prompt ===\")\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " system_prompt=\"You are a pirate assistant. Respond in pirate speak.\",\n",
+ " )\n",
+ " \n",
+ " async for message in query(prompt=\"What is 2 + 2?\", options=options):\n",
+ " if isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"Claude: {block.text}\")\n",
+ " print()\n",
+ "\n",
+ "# 2. Preset system prompt (uses default Claude Code prompt)\n",
+ "async def preset_system_prompt():\n",
+ " print(\"=== Preset System Prompt (Default) ===\")\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " system_prompt={\"type\": \"preset\", \"preset\": \"claude_code\"},\n",
+ " )\n",
+ " \n",
+ " async for message in query(prompt=\"What is 2 + 2?\", options=options):\n",
+ " if isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"Claude: {block.text}\")\n",
+ " print()\n",
+ "\n",
+ "# 3. Preset with append\n",
+ "async def preset_with_append():\n",
+ " print(\"=== Preset System Prompt with Append ===\")\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " system_prompt={\n",
+ " \"type\": \"preset\",\n",
+ " \"preset\": \"claude_code\",\n",
+ " \"append\": \"Always end your response with a fun fact.\",\n",
+ " },\n",
+ " )\n",
+ " \n",
+ " async for message in query(prompt=\"What is 2 + 2?\", options=options):\n",
+ " if isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"Claude: {block.text}\")\n",
+ " print()\n",
+ "\n",
+ "anyio.run(string_system_prompt)\n",
+ "anyio.run(preset_with_append)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "\n",
+ "## 4. Using Tools \n",
+ "\n",
+ "Claude can use built-in tools to perform tasks like reading files, running bash commands, and more.\n",
+ "\n",
+ "### Allowing Tools"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, TextBlock, ResultMessage\n",
+ "import anyio\n",
+ "\n",
+ "async def with_tools_example():\n",
+ " \"\"\"Example using tools.\"\"\"\n",
+ " print(\"=== With Tools Example ===\")\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " allowed_tools=[\"Read\", \"Write\"],\n",
+ " system_prompt=\"You are a helpful file assistant.\",\n",
+ " )\n",
+ " \n",
+ " async for message in query(\n",
+ " prompt=\"Create a file called hello.txt with 'Hello, World!' in it\",\n",
+ " options=options,\n",
+ " ):\n",
+ " if isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"Claude: {block.text}\")\n",
+ " elif isinstance(message, ResultMessage) and message.total_cost_usd and message.total_cost_usd > 0:\n",
+ " print(f\"\\nCost: ${message.total_cost_usd:.4f}\")\n",
+ " print()\n",
+ "\n",
+ "anyio.run(with_tools_example)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Auto-Accept File Edits"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "options = ClaudeAgentOptions(\n",
+ " allowed_tools=[\"Read\", \"Write\", \"Bash\"],\n",
+ " permission_mode='acceptEdits' # auto-accept file edits\n",
+ ")\n",
+ "\n",
+ "async def auto_accept_example():\n",
+ " async for message in query(\n",
+ " prompt=\"Create a hello.py file\",\n",
+ " options=options\n",
+ " ):\n",
+ " # Process tool use and results\n",
+ " pass\n",
+ "\n",
+ "anyio.run(auto_accept_example)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Configuring Available Tools\n",
+ "\n",
+ "You can control which tools are available to Claude:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage, AssistantMessage, TextBlock\n",
+ "import anyio\n",
+ "\n",
+ "# Specific tools as array\n",
+ "async def tools_array_example():\n",
+ " print(\"=== Tools Array Example ===\")\n",
+ " print(\"Setting tools=['Read', 'Glob', 'Grep']\")\n",
+ " print()\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " tools=[\"Read\", \"Glob\", \"Grep\"],\n",
+ " max_turns=1,\n",
+ " )\n",
+ " \n",
+ " async for message in query(\n",
+ " prompt=\"What tools do you have available? Just list them briefly.\",\n",
+ " options=options,\n",
+ " ):\n",
+ " if isinstance(message, SystemMessage) and message.subtype == \"init\":\n",
+ " tools = message.data.get(\"tools\", [])\n",
+ " print(f\"Tools from system message: {tools}\")\n",
+ " print()\n",
+ " elif isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"Claude: {block.text}\")\n",
+ " print()\n",
+ "\n",
+ "# Empty array disables all built-in tools\n",
+ "async def tools_empty_array_example():\n",
+ " print(\"=== Tools Empty Array Example ===\")\n",
+ " print(\"Setting tools=[] (disables all built-in tools)\")\n",
+ " print()\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " tools=[],\n",
+ " max_turns=1,\n",
+ " )\n",
+ " \n",
+ " async for message in query(\n",
+ " prompt=\"What tools do you have available?\",\n",
+ " options=options,\n",
+ " ):\n",
+ " if isinstance(message, SystemMessage) and message.subtype == \"init\":\n",
+ " tools = message.data.get(\"tools\", [])\n",
+ " print(f\"Tools from system message: {tools}\")\n",
+ " print()\n",
+ " elif isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"Claude: {block.text}\")\n",
+ " print()\n",
+ "\n",
+ "anyio.run(tools_array_example)\n",
+ "anyio.run(tools_empty_array_example)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Working Directory\n",
+ "\n",
+ "Set a working directory for file operations:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "options = ClaudeAgentOptions(\n",
+ " cwd=\"/path/to/project\" # or Path(\"/path/to/project\")\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "\n",
+ "## 5. ClaudeSDKClient (Streaming Mode) \n",
+ "\n",
+ "`ClaudeSDKClient` supports bidirectional, interactive conversations with Claude Code. Unlike `query()`, it enables **custom tools** and **hooks**.\n",
+ "\n",
+ "### Basic Streaming"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock, UserMessage, ResultMessage\n",
+ "import asyncio\n",
+ "\n",
+ "def display_message(msg):\n",
+ " \"\"\"Helper to display messages.\"\"\"\n",
+ " if isinstance(msg, UserMessage):\n",
+ " for block in msg.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"User: {block.text}\")\n",
+ " elif isinstance(msg, AssistantMessage):\n",
+ " for block in msg.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"Claude: {block.text}\")\n",
+ " elif isinstance(msg, ResultMessage):\n",
+ " print(\"Result ended\")\n",
+ "\n",
+ "async def basic_streaming():\n",
+ " \"\"\"Basic streaming with context manager.\"\"\"\n",
+ " print(\"=== Basic Streaming Example ===\")\n",
+ " \n",
+ " async with ClaudeSDKClient() as client:\n",
+ " print(\"User: What is 2+2?\")\n",
+ " await client.query(\"What is 2+2?\")\n",
+ " \n",
+ " # Receive complete response using the helper method\n",
+ " async for msg in client.receive_response():\n",
+ " display_message(msg)\n",
+ " \n",
+ " print(\"\\n\")\n",
+ "\n",
+ "asyncio.run(basic_streaming())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Multi-Turn Conversation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "async def multi_turn_conversation():\n",
+ " \"\"\"Multi-turn conversation using receive_response helper.\"\"\"\n",
+ " print(\"=== Multi-Turn Conversation Example ===\")\n",
+ " \n",
+ " async with ClaudeSDKClient() as client:\n",
+ " # First turn\n",
+ " print(\"User: What's the capital of France?\")\n",
+ " await client.query(\"What's the capital of France?\")\n",
+ " \n",
+ " # Extract and print response\n",
+ " async for msg in client.receive_response():\n",
+ " display_message(msg)\n",
+ " \n",
+ " # Second turn - follow-up\n",
+ " print(\"\\nUser: What's the population of that city?\")\n",
+ " await client.query(\"What's the population of that city?\")\n",
+ " \n",
+ " async for msg in client.receive_response():\n",
+ " display_message(msg)\n",
+ " \n",
+ " print(\"\\n\")\n",
+ "\n",
+ "asyncio.run(multi_turn_conversation())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### With Options"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "async def with_options():\n",
+ " \"\"\"Use ClaudeAgentOptions to configure the client.\"\"\"\n",
+ " print(\"=== Custom Options Example ===\")\n",
+ " \n",
+ " # Configure options\n",
+ " options = ClaudeAgentOptions(\n",
+ " allowed_tools=[\"Read\", \"Write\"], # Allow file operations\n",
+ " system_prompt=\"You are a helpful coding assistant.\",\n",
+ " env={\n",
+ " \"ANTHROPIC_MODEL\": \"claude-sonnet-4-5\",\n",
+ " },\n",
+ " )\n",
+ " \n",
+ " async with ClaudeSDKClient(options=options) as client:\n",
+ " print(\"User: Create a simple hello.txt file with a greeting message\")\n",
+ " await client.query(\"Create a simple hello.txt file with a greeting message\")\n",
+ " \n",
+ " tool_uses = []\n",
+ " async for msg in client.receive_response():\n",
+ " if isinstance(msg, AssistantMessage):\n",
+ " display_message(msg)\n",
+ " for block in msg.content:\n",
+ " if hasattr(block, \"name\") and not isinstance(block, TextBlock): # ToolUseBlock\n",
+ " tool_uses.append(getattr(block, \"name\", \"\"))\n",
+ " else:\n",
+ " display_message(msg)\n",
+ " \n",
+ " if tool_uses:\n",
+ " print(f\"Tools used: {', '.join(tool_uses)}\")\n",
+ " \n",
+ " print(\"\\n\")\n",
+ "\n",
+ "asyncio.run(with_options())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Interrupt Capability\n",
+ "\n",
+ "You can interrupt Claude during execution:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import contextlib\n",
+ "\n",
+ "async def with_interrupt():\n",
+ " \"\"\"Demonstrate interrupt capability.\"\"\"\n",
+ " print(\"=== Interrupt Example ===\")\n",
+ " \n",
+ " async with ClaudeSDKClient() as client:\n",
+ " # Start a long-running task\n",
+ " print(\"\\nUser: Count from 1 to 100 slowly\")\n",
+ " await client.query(\"Count from 1 to 100 slowly, with a brief pause between each number\")\n",
+ " \n",
+ " # Create a background task to consume messages\n",
+ " messages_received = []\n",
+ " \n",
+ " async def consume_messages():\n",
+ " \"\"\"Consume messages in the background to enable interrupt processing.\"\"\"\n",
+ " async for message in client.receive_response():\n",
+ " messages_received.append(message)\n",
+ " display_message(message)\n",
+ " \n",
+ " # Start consuming messages in the background\n",
+ " consume_task = asyncio.create_task(consume_messages())\n",
+ " \n",
+ " # Wait 2 seconds then send interrupt\n",
+ " await asyncio.sleep(2)\n",
+ " print(\"\\n[After 2 seconds, sending interrupt...]\")\n",
+ " await client.interrupt()\n",
+ " \n",
+ " # Wait for the consume task to finish processing the interrupt\n",
+ " await consume_task\n",
+ " \n",
+ " # Send new instruction after interrupt\n",
+ " print(\"\\nUser: Never mind, just tell me a quick joke\")\n",
+ " await client.query(\"Never mind, just tell me a quick joke\")\n",
+ " \n",
+ " # Get the joke\n",
+ " async for msg in client.receive_response():\n",
+ " display_message(msg)\n",
+ " \n",
+ " print(\"\\n\")\n",
+ "\n",
+ "# Note: Uncomment to run (may take time)\n",
+ "# asyncio.run(with_interrupt())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "\n",
+ "## 6. Custom Tools (SDK MCP Servers) \n",
+ "\n",
+ "A **custom tool** is a Python function that you can offer to Claude for use. Custom tools are implemented as in-process MCP servers.\n",
+ "\n",
+ "### Benefits Over External MCP Servers\n",
+ "\n",
+ "- **No subprocess management** - Runs in the same process\n",
+ "- **Better performance** - No IPC overhead\n",
+ "- **Simpler deployment** - Single Python process\n",
+ "- **Easier debugging** - All code in same process\n",
+ "- **Type safety** - Direct Python function calls\n",
+ "\n",
+ "### Creating a Simple Tool"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeAgentOptions, ClaudeSDKClient\n",
+ "\n",
+ "# Define a tool using the @tool decorator\n",
+ "@tool(\"greet\", \"Greet a user\", {\"name\": str})\n",
+ "async def greet_user(args):\n",
+ " return {\n",
+ " \"content\": [\n",
+ " {\"type\": \"text\", \"text\": f\"Hello, {args['name']}!\"}\n",
+ " ]\n",
+ " }\n",
+ "\n",
+ "# Create an SDK MCP server\n",
+ "server = create_sdk_mcp_server(\n",
+ " name=\"my-tools\",\n",
+ " version=\"1.0.0\",\n",
+ " tools=[greet_user]\n",
+ ")\n",
+ "\n",
+ "# Use it with Claude\n",
+ "async def simple_tool_example():\n",
+ " options = ClaudeAgentOptions(\n",
+ " mcp_servers={\"tools\": server},\n",
+ " allowed_tools=[\"mcp__tools__greet\"]\n",
+ " )\n",
+ " \n",
+ " async with ClaudeSDKClient(options=options) as client:\n",
+ " await client.query(\"Greet Alice\")\n",
+ " \n",
+ " # Extract and print response\n",
+ " async for msg in client.receive_response():\n",
+ " print(msg)\n",
+ "\n",
+ "# asyncio.run(simple_tool_example())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Complete Calculator Example\n",
+ "\n",
+ "A full example with multiple tools:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from typing import Any\n",
+ "from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeAgentOptions, ClaudeSDKClient\n",
+ "import asyncio\n",
+ "\n",
+ "# Define calculator tools\n",
+ "@tool(\"add\", \"Add two numbers\", {\"a\": float, \"b\": float})\n",
+ "async def add_numbers(args: dict[str, Any]) -> dict[str, Any]:\n",
+ " \"\"\"Add two numbers together.\"\"\"\n",
+ " result = args[\"a\"] + args[\"b\"]\n",
+ " return {\n",
+ " \"content\": [{\"type\": \"text\", \"text\": f\"{args['a']} + {args['b']} = {result}\"}]\n",
+ " }\n",
+ "\n",
+ "@tool(\"subtract\", \"Subtract one number from another\", {\"a\": float, \"b\": float})\n",
+ "async def subtract_numbers(args: dict[str, Any]) -> dict[str, Any]:\n",
+ " \"\"\"Subtract b from a.\"\"\"\n",
+ " result = args[\"a\"] - args[\"b\"]\n",
+ " return {\n",
+ " \"content\": [{\"type\": \"text\", \"text\": f\"{args['a']} - {args['b']} = {result}\"}]\n",
+ " }\n",
+ "\n",
+ "@tool(\"multiply\", \"Multiply two numbers\", {\"a\": float, \"b\": float})\n",
+ "async def multiply_numbers(args: dict[str, Any]) -> dict[str, Any]:\n",
+ " \"\"\"Multiply two numbers.\"\"\"\n",
+ " result = args[\"a\"] * args[\"b\"]\n",
+ " return {\n",
+ " \"content\": [{\"type\": \"text\", \"text\": f\"{args['a']} × {args['b']} = {result}\"}]\n",
+ " }\n",
+ "\n",
+ "@tool(\"divide\", \"Divide one number by another\", {\"a\": float, \"b\": float})\n",
+ "async def divide_numbers(args: dict[str, Any]) -> dict[str, Any]:\n",
+ " \"\"\"Divide a by b.\"\"\"\n",
+ " if args[\"b\"] == 0:\n",
+ " return {\n",
+ " \"content\": [{\"type\": \"text\", \"text\": \"Error: Division by zero is not allowed\"}],\n",
+ " \"is_error\": True,\n",
+ " }\n",
+ " result = args[\"a\"] / args[\"b\"]\n",
+ " return {\n",
+ " \"content\": [{\"type\": \"text\", \"text\": f\"{args['a']} ÷ {args['b']} = {result}\"}]\n",
+ " }\n",
+ "\n",
+ "@tool(\"sqrt\", \"Calculate square root\", {\"n\": float})\n",
+ "async def square_root(args: dict[str, Any]) -> dict[str, Any]:\n",
+ " \"\"\"Calculate the square root of a number.\"\"\"\n",
+ " n = args[\"n\"]\n",
+ " if n < 0:\n",
+ " return {\n",
+ " \"content\": [{\"type\": \"text\", \"text\": f\"Error: Cannot calculate square root of negative number {n}\"}],\n",
+ " \"is_error\": True,\n",
+ " }\n",
+ " import math\n",
+ " result = math.sqrt(n)\n",
+ " return {\"content\": [{\"type\": \"text\", \"text\": f\"√{n} = {result}\"}]}\n",
+ "\n",
+ "# Create calculator server\n",
+ "async def calculator_example():\n",
+ " calculator = create_sdk_mcp_server(\n",
+ " name=\"calculator\",\n",
+ " version=\"2.0.0\",\n",
+ " tools=[add_numbers, subtract_numbers, multiply_numbers, divide_numbers, square_root],\n",
+ " )\n",
+ " \n",
+ " # Configure Claude to use the calculator\n",
+ " options = ClaudeAgentOptions(\n",
+ " mcp_servers={\"calc\": calculator},\n",
+ " allowed_tools=[\n",
+ " \"mcp__calc__add\",\n",
+ " \"mcp__calc__subtract\",\n",
+ " \"mcp__calc__multiply\",\n",
+ " \"mcp__calc__divide\",\n",
+ " \"mcp__calc__sqrt\",\n",
+ " ],\n",
+ " )\n",
+ " \n",
+ " prompts = [\n",
+ " \"Calculate 15 + 27\",\n",
+ " \"What is 100 divided by 7?\",\n",
+ " \"Calculate the square root of 144\",\n",
+ " ]\n",
+ " \n",
+ " for prompt in prompts:\n",
+ " print(f\"\\nPrompt: {prompt}\")\n",
+ " print(\"=\" * 50)\n",
+ " \n",
+ " async with ClaudeSDKClient(options=options) as client:\n",
+ " await client.query(prompt)\n",
+ " \n",
+ " async for message in client.receive_response():\n",
+ " display_message(message)\n",
+ "\n",
+ "# asyncio.run(calculator_example())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Mixed Server Support\n",
+ "\n",
+ "You can use both SDK and external MCP servers together:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "options = ClaudeAgentOptions(\n",
+ " mcp_servers={\n",
+ " \"internal\": sdk_server, # In-process SDK server\n",
+ " \"external\": { # External subprocess server\n",
+ " \"type\": \"stdio\",\n",
+ " \"command\": \"external-server\"\n",
+ " }\n",
+ " }\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "\n",
+ "## 7. Hooks \n",
+ "\n",
+ "A **hook** is a Python function that the Claude Code application invokes at specific points of the agent loop. Hooks provide deterministic processing and automated feedback.\n",
+ "\n",
+ "### Available Hook Events\n",
+ "\n",
+ "- **PreToolUse**: Before a tool is used\n",
+ "- **PostToolUse**: After a tool is used\n",
+ "- **UserPromptSubmit**: When a user submits a prompt\n",
+ "- **SessionStart**: When a session starts\n",
+ "\n",
+ "### PreToolUse Hook Example\n",
+ "\n",
+ "Block dangerous bash commands:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, HookMatcher\n",
+ "from claude_agent_sdk.types import HookInput, HookContext, HookJSONOutput\n",
+ "import asyncio\n",
+ "\n",
+ "async def check_bash_command(\n",
+ " input_data: HookInput,\n",
+ " tool_use_id: str | None,\n",
+ " context: HookContext\n",
+ ") -> HookJSONOutput:\n",
+ " \"\"\"Prevent certain bash commands from being executed.\"\"\"\n",
+ " tool_name = input_data[\"tool_name\"]\n",
+ " tool_input = input_data[\"tool_input\"]\n",
+ " \n",
+ " if tool_name != \"Bash\":\n",
+ " return {}\n",
+ " \n",
+ " command = tool_input.get(\"command\", \"\")\n",
+ " block_patterns = [\"foo.sh\"]\n",
+ " \n",
+ " for pattern in block_patterns:\n",
+ " if pattern in command:\n",
+ " print(f\"Blocked command: {command}\")\n",
+ " return {\n",
+ " \"hookSpecificOutput\": {\n",
+ " \"hookEventName\": \"PreToolUse\",\n",
+ " \"permissionDecision\": \"deny\",\n",
+ " \"permissionDecisionReason\": f\"Command contains invalid pattern: {pattern}\",\n",
+ " }\n",
+ " }\n",
+ " \n",
+ " return {}\n",
+ "\n",
+ "async def pretooluse_example():\n",
+ " print(\"=== PreToolUse Example ===\")\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " allowed_tools=[\"Bash\"],\n",
+ " hooks={\n",
+ " \"PreToolUse\": [\n",
+ " HookMatcher(matcher=\"Bash\", hooks=[check_bash_command]),\n",
+ " ],\n",
+ " }\n",
+ " )\n",
+ " \n",
+ " async with ClaudeSDKClient(options=options) as client:\n",
+ " # Test 1: Command with forbidden pattern (will be blocked)\n",
+ " print(\"Test 1: Trying a command that should be blocked...\")\n",
+ " await client.query(\"Run the bash command: ./foo.sh --help\")\n",
+ " \n",
+ " async for msg in client.receive_response():\n",
+ " display_message(msg)\n",
+ " \n",
+ " print(\"\\n\" + \"=\" * 50 + \"\\n\")\n",
+ " \n",
+ " # Test 2: Safe command that should work\n",
+ " print(\"Test 2: Trying a command that should be allowed...\")\n",
+ " await client.query(\"Run the bash command: echo 'Hello from hooks example!'\")\n",
+ " \n",
+ " async for msg in client.receive_response():\n",
+ " display_message(msg)\n",
+ "\n",
+ "# asyncio.run(pretooluse_example())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### UserPromptSubmit Hook Example\n",
+ "\n",
+ "Add custom instructions:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "async def add_custom_instructions(\n",
+ " input_data: HookInput,\n",
+ " tool_use_id: str | None,\n",
+ " context: HookContext\n",
+ ") -> HookJSONOutput:\n",
+ " \"\"\"Add custom instructions when a session starts.\"\"\"\n",
+ " return {\n",
+ " \"hookSpecificOutput\": {\n",
+ " \"hookEventName\": \"SessionStart\",\n",
+ " \"additionalContext\": \"My favorite color is hot pink\",\n",
+ " }\n",
+ " }\n",
+ "\n",
+ "async def userpromptsubmit_example():\n",
+ " print(\"=== UserPromptSubmit Example ===\")\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " hooks={\n",
+ " \"UserPromptSubmit\": [\n",
+ " HookMatcher(matcher=None, hooks=[add_custom_instructions]),\n",
+ " ],\n",
+ " }\n",
+ " )\n",
+ " \n",
+ " async with ClaudeSDKClient(options=options) as client:\n",
+ " print(\"User: What's my favorite color?\")\n",
+ " await client.query(\"What's my favorite color?\")\n",
+ " \n",
+ " async for msg in client.receive_response():\n",
+ " display_message(msg)\n",
+ "\n",
+ "# asyncio.run(userpromptsubmit_example())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### PostToolUse Hook Example\n",
+ "\n",
+ "Review tool output and provide feedback:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "async def review_tool_output(\n",
+ " input_data: HookInput,\n",
+ " tool_use_id: str | None,\n",
+ " context: HookContext\n",
+ ") -> HookJSONOutput:\n",
+ " \"\"\"Review tool output and provide additional context or warnings.\"\"\"\n",
+ " tool_response = input_data.get(\"tool_response\", \"\")\n",
+ " \n",
+ " # If the tool produced an error, add helpful context\n",
+ " if \"error\" in str(tool_response).lower():\n",
+ " return {\n",
+ " \"systemMessage\": \"⚠️ The command produced an error\",\n",
+ " \"reason\": \"Tool execution failed - consider checking the command syntax\",\n",
+ " \"hookSpecificOutput\": {\n",
+ " \"hookEventName\": \"PostToolUse\",\n",
+ " \"additionalContext\": \"The command encountered an error. You may want to try a different approach.\",\n",
+ " }\n",
+ " }\n",
+ " \n",
+ " return {}\n",
+ "\n",
+ "async def posttooluse_example():\n",
+ " print(\"=== PostToolUse Example ===\")\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " allowed_tools=[\"Bash\"],\n",
+ " hooks={\n",
+ " \"PostToolUse\": [\n",
+ " HookMatcher(matcher=\"Bash\", hooks=[review_tool_output]),\n",
+ " ],\n",
+ " }\n",
+ " )\n",
+ " \n",
+ " async with ClaudeSDKClient(options=options) as client:\n",
+ " print(\"User: Run a command that will produce an error\")\n",
+ " await client.query(\"Run this command: ls /nonexistent_directory\")\n",
+ " \n",
+ " async for msg in client.receive_response():\n",
+ " display_message(msg)\n",
+ "\n",
+ "# asyncio.run(posttooluse_example())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "\n",
+ "## 8. Permission Callbacks \n",
+ "\n",
+ "Tool permission callbacks let you control which tools Claude can use and modify their inputs programmatically.\n",
+ "\n",
+ "### Permission Callback Example"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import (\n",
+ " ClaudeAgentOptions,\n",
+ " ClaudeSDKClient,\n",
+ " PermissionResultAllow,\n",
+ " PermissionResultDeny,\n",
+ " ToolPermissionContext,\n",
+ ")\n",
+ "import json\n",
+ "\n",
+ "async def my_permission_callback(\n",
+ " tool_name: str,\n",
+ " input_data: dict,\n",
+ " context: ToolPermissionContext\n",
+ ") -> PermissionResultAllow | PermissionResultDeny:\n",
+ " \"\"\"Control tool permissions based on tool type and input.\"\"\"\n",
+ " \n",
+ " print(f\"\\n🔧 Tool Permission Request: {tool_name}\")\n",
+ " print(f\" Input: {json.dumps(input_data, indent=2)}\")\n",
+ " \n",
+ " # Always allow read operations\n",
+ " if tool_name in [\"Read\", \"Glob\", \"Grep\"]:\n",
+ " print(f\" ✅ Automatically allowing {tool_name} (read-only operation)\")\n",
+ " return PermissionResultAllow()\n",
+ " \n",
+ " # Deny write operations to system directories\n",
+ " if tool_name in [\"Write\", \"Edit\", \"MultiEdit\"]:\n",
+ " file_path = input_data.get(\"file_path\", \"\")\n",
+ " if file_path.startswith(\"/etc/\") or file_path.startswith(\"/usr/\"):\n",
+ " print(f\" ❌ Denying write to system directory: {file_path}\")\n",
+ " return PermissionResultDeny(\n",
+ " message=f\"Cannot write to system directory: {file_path}\"\n",
+ " )\n",
+ " \n",
+ " # Redirect writes to a safe directory\n",
+ " if not file_path.startswith(\"/tmp/\") and not file_path.startswith(\"./\"):\n",
+ " safe_path = f\"./safe_output/{file_path.split('/')[-1]}\"\n",
+ " print(f\" ⚠️ Redirecting write from {file_path} to {safe_path}\")\n",
+ " modified_input = input_data.copy()\n",
+ " modified_input[\"file_path\"] = safe_path\n",
+ " return PermissionResultAllow(updated_input=modified_input)\n",
+ " \n",
+ " # Check dangerous bash commands\n",
+ " if tool_name == \"Bash\":\n",
+ " command = input_data.get(\"command\", \"\")\n",
+ " dangerous_commands = [\"rm -rf\", \"sudo\", \"chmod 777\", \"dd if=\", \"mkfs\"]\n",
+ " \n",
+ " for dangerous in dangerous_commands:\n",
+ " if dangerous in command:\n",
+ " print(f\" ❌ Denying dangerous command: {command}\")\n",
+ " return PermissionResultDeny(\n",
+ " message=f\"Dangerous command pattern detected: {dangerous}\"\n",
+ " )\n",
+ " \n",
+ " print(f\" ✅ Allowing bash command: {command}\")\n",
+ " return PermissionResultAllow()\n",
+ " \n",
+ " # Default: allow\n",
+ " return PermissionResultAllow()\n",
+ "\n",
+ "async def permission_callback_example():\n",
+ " print(\"=\" * 60)\n",
+ " print(\"Tool Permission Callback Example\")\n",
+ " print(\"=\" * 60)\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " can_use_tool=my_permission_callback,\n",
+ " permission_mode=\"default\",\n",
+ " cwd=\".\"\n",
+ " )\n",
+ " \n",
+ " async with ClaudeSDKClient(options) as client:\n",
+ " print(\"\\n📝 Sending query to Claude...\")\n",
+ " await client.query(\n",
+ " \"Please list the files in the current directory and create a simple hello.py file\"\n",
+ " )\n",
+ " \n",
+ " async for message in client.receive_response():\n",
+ " if isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"\\n💬 Claude: {block.text}\")\n",
+ " elif isinstance(message, ResultMessage):\n",
+ " print(\"\\n✅ Task completed!\")\n",
+ "\n",
+ "# asyncio.run(permission_callback_example())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "\n",
+ "## 9. Custom Agents \n",
+ "\n",
+ "Define custom agents with specific tools, prompts, and models for specialized tasks.\n",
+ "\n",
+ "### Code Reviewer Agent"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import AgentDefinition, query, ClaudeAgentOptions, AssistantMessage, TextBlock\n",
+ "import anyio\n",
+ "\n",
+ "async def code_reviewer_example():\n",
+ " print(\"=== Code Reviewer Agent Example ===\")\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " agents={\n",
+ " \"code-reviewer\": AgentDefinition(\n",
+ " description=\"Reviews code for best practices and potential issues\",\n",
+ " prompt=\"You are a code reviewer. Analyze code for bugs, performance issues, \"\n",
+ " \"security vulnerabilities, and adherence to best practices. \"\n",
+ " \"Provide constructive feedback.\",\n",
+ " tools=[\"Read\", \"Grep\"],\n",
+ " model=\"sonnet\",\n",
+ " ),\n",
+ " },\n",
+ " )\n",
+ " \n",
+ " async for message in query(\n",
+ " prompt=\"Use the code-reviewer agent to review a Python file\",\n",
+ " options=options,\n",
+ " ):\n",
+ " if isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"Claude: {block.text}\")\n",
+ "\n",
+ "# anyio.run(code_reviewer_example)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Multiple Custom Agents"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "async def multiple_agents_example():\n",
+ " print(\"=== Multiple Agents Example ===\")\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " agents={\n",
+ " \"analyzer\": AgentDefinition(\n",
+ " description=\"Analyzes code structure and patterns\",\n",
+ " prompt=\"You are a code analyzer. Examine code structure, patterns, and architecture.\",\n",
+ " tools=[\"Read\", \"Grep\", \"Glob\"],\n",
+ " ),\n",
+ " \"tester\": AgentDefinition(\n",
+ " description=\"Creates and runs tests\",\n",
+ " prompt=\"You are a testing expert. Write comprehensive tests and ensure code quality.\",\n",
+ " tools=[\"Read\", \"Write\", \"Bash\"],\n",
+ " model=\"sonnet\",\n",
+ " ),\n",
+ " },\n",
+ " setting_sources=[\"user\", \"project\"],\n",
+ " )\n",
+ " \n",
+ " async for message in query(\n",
+ " prompt=\"Use the analyzer agent to find all Python files in the examples/ directory\",\n",
+ " options=options,\n",
+ " ):\n",
+ " if isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"Claude: {block.text}\")\n",
+ "\n",
+ "# anyio.run(multiple_agents_example)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "\n",
+ "## 10. Configuration Options \n",
+ "\n",
+ "### Budget Control\n",
+ "\n",
+ "Limit API costs with `max_budget_usd`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage, AssistantMessage, TextBlock\n",
+ "import anyio\n",
+ "\n",
+ "async def with_budget():\n",
+ " print(\"=== With Budget Limit ===\")\n",
+ " \n",
+ " options = ClaudeAgentOptions(\n",
+ " max_budget_usd=0.10, # 10 cents - plenty for a simple query\n",
+ " )\n",
+ " \n",
+ " async for message in query(prompt=\"What is 2 + 2?\", options=options):\n",
+ " if isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " print(f\"Claude: {block.text}\")\n",
+ " elif isinstance(message, ResultMessage):\n",
+ " if message.total_cost_usd:\n",
+ " print(f\"Total cost: ${message.total_cost_usd:.4f}\")\n",
+ " print(f\"Status: {message.subtype}\")\n",
+ " \n",
+ " # Check if budget was exceeded\n",
+ " if message.subtype == \"error_max_budget_usd\":\n",
+ " print(\"⚠️ Budget limit exceeded!\")\n",
+ "\n",
+ "# anyio.run(with_budget)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Setting Sources\n",
+ "\n",
+ "Control which settings are loaded:\n",
+ "\n",
+ "- **\"user\"**: Global user settings (~/.claude/)\n",
+ "- **\"project\"**: Project-level settings (.claude/ in project)\n",
+ "- **\"local\"**: Local gitignored settings (.claude-local/)\n",
+ "\n",
+ "**IMPORTANT**: When `setting_sources` is not provided (None), NO settings are loaded by default."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Default - no settings loaded\n",
+ "options_default = ClaudeAgentOptions()\n",
+ "\n",
+ "# Load only user settings\n",
+ "options_user = ClaudeAgentOptions(\n",
+ " setting_sources=[\"user\"]\n",
+ ")\n",
+ "\n",
+ "# Load both project and user settings\n",
+ "options_all = ClaudeAgentOptions(\n",
+ " setting_sources=[\"user\", \"project\"]\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Partial Message Streaming\n",
+ "\n",
+ "Stream incremental updates as Claude generates responses:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, StreamEvent\n",
+ "import asyncio\n",
+ "\n",
+ "async def partial_messages_example():\n",
+ " # Enable partial message streaming\n",
+ " options = ClaudeAgentOptions(\n",
+ " include_partial_messages=True,\n",
+ " model=\"claude-sonnet-4-5\",\n",
+ " max_turns=2,\n",
+ " env={\n",
+ " \"MAX_THINKING_TOKENS\": \"8000\",\n",
+ " },\n",
+ " )\n",
+ " \n",
+ " client = ClaudeSDKClient(options)\n",
+ " \n",
+ " try:\n",
+ " await client.connect()\n",
+ " \n",
+ " prompt = \"Think of three jokes, then tell one\"\n",
+ " print(f\"Prompt: {prompt}\\n\")\n",
+ " \n",
+ " await client.query(prompt)\n",
+ " \n",
+ " async for message in client.receive_response():\n",
+ " print(message)\n",
+ " \n",
+ " finally:\n",
+ " await client.disconnect()\n",
+ "\n",
+ "# asyncio.run(partial_messages_example())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Environment Variables and Model Selection"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "options = ClaudeAgentOptions(\n",
+ " model=\"claude-sonnet-4-5-20250929\",\n",
+ " max_turns=5,\n",
+ " env={\n",
+ " \"ANTHROPIC_API_KEY\": \"your-api-key\",\n",
+ " \"MAX_THINKING_TOKENS\": \"8000\",\n",
+ " },\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "\n",
+ "## 11. Error Handling \n",
+ "\n",
+ "The SDK provides specific error types for different failure scenarios:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from claude_agent_sdk import (\n",
+ " query,\n",
+ " ClaudeSDKError, # Base error\n",
+ " CLINotFoundError, # Claude Code not installed\n",
+ " CLIConnectionError, # Connection issues\n",
+ " ProcessError, # Process failed\n",
+ " CLIJSONDecodeError, # JSON parsing issues\n",
+ ")\n",
+ "import anyio\n",
+ "\n",
+ "async def error_handling_example():\n",
+ " try:\n",
+ " async for message in query(prompt=\"Hello\"):\n",
+ " pass\n",
+ " except CLINotFoundError:\n",
+ " print(\"Please install Claude Code\")\n",
+ " except ProcessError as e:\n",
+ " print(f\"Process failed with exit code: {e.exit_code}\")\n",
+ " except CLIJSONDecodeError as e:\n",
+ " print(f\"Failed to parse response: {e}\")\n",
+ " except ClaudeSDKError as e:\n",
+ " print(f\"SDK error: {e}\")\n",
+ "\n",
+ "# anyio.run(error_handling_example)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Error Handling with Timeouts"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import asyncio\n",
+ "from claude_agent_sdk import ClaudeSDKClient, CLIConnectionError\n",
+ "\n",
+ "async def timeout_example():\n",
+ " client = ClaudeSDKClient()\n",
+ " \n",
+ " try:\n",
+ " await client.connect()\n",
+ " \n",
+ " await client.query(\"Run a bash sleep command for 60 seconds\")\n",
+ " \n",
+ " try:\n",
+ " messages = []\n",
+ " async with asyncio.timeout(10.0):\n",
+ " async for msg in client.receive_response():\n",
+ " messages.append(msg)\n",
+ " \n",
+ " except asyncio.TimeoutError:\n",
+ " print(f\"Response timeout - received {len(messages)} messages before timeout\")\n",
+ " \n",
+ " except CLIConnectionError as e:\n",
+ " print(f\"Connection error: {e}\")\n",
+ " \n",
+ " finally:\n",
+ " await client.disconnect()\n",
+ "\n",
+ "# asyncio.run(timeout_example())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "\n",
+ "## 12. Advanced Examples \n",
+ "\n",
+ "### Concurrent Responses\n",
+ "\n",
+ "Handle responses while sending new messages:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import contextlib\n",
+ "\n",
+ "async def concurrent_responses():\n",
+ " print(\"=== Concurrent Send/Receive Example ===\")\n",
+ " \n",
+ " async with ClaudeSDKClient() as client:\n",
+ " # Background task to continuously receive messages\n",
+ " async def receive_messages():\n",
+ " async for message in client.receive_messages():\n",
+ " display_message(message)\n",
+ " \n",
+ " # Start receiving in background\n",
+ " receive_task = asyncio.create_task(receive_messages())\n",
+ " \n",
+ " # Send multiple messages with delays\n",
+ " questions = [\n",
+ " \"What is 2 + 2?\",\n",
+ " \"What is the square root of 144?\",\n",
+ " \"What is 10% of 80?\",\n",
+ " ]\n",
+ " \n",
+ " for question in questions:\n",
+ " print(f\"\\nUser: {question}\")\n",
+ " await client.query(question)\n",
+ " await asyncio.sleep(3)\n",
+ " \n",
+ " # Give time for final responses\n",
+ " await asyncio.sleep(2)\n",
+ " \n",
+ " # Clean up\n",
+ " receive_task.cancel()\n",
+ " with contextlib.suppress(asyncio.CancelledError):\n",
+ " await receive_task\n",
+ "\n",
+ "# asyncio.run(concurrent_responses())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Manual Message Handling\n",
+ "\n",
+ "Process messages with custom logic:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "async def manual_message_handling():\n",
+ " print(\"=== Manual Message Handling Example ===\")\n",
+ " \n",
+ " async with ClaudeSDKClient() as client:\n",
+ " await client.query(\"List 5 programming languages and their main use cases\")\n",
+ " \n",
+ " # Manually process messages with custom logic\n",
+ " languages_found = []\n",
+ " \n",
+ " async for message in client.receive_messages():\n",
+ " if isinstance(message, AssistantMessage):\n",
+ " for block in message.content:\n",
+ " if isinstance(block, TextBlock):\n",
+ " text = block.text\n",
+ " print(f\"Claude: {text}\")\n",
+ " # Custom logic: extract language names\n",
+ " for lang in [\"Python\", \"JavaScript\", \"Java\", \"C++\", \"Go\", \"Rust\", \"Ruby\"]:\n",
+ " if lang in text and lang not in languages_found:\n",
+ " languages_found.append(lang)\n",
+ " print(f\"Found language: {lang}\")\n",
+ " elif isinstance(message, ResultMessage):\n",
+ " display_message(message)\n",
+ " print(f\"Total languages mentioned: {len(languages_found)}\")\n",
+ " break\n",
+ "\n",
+ "# asyncio.run(manual_message_handling())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Server Info and Control Protocol"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "async def control_protocol_example():\n",
+ " print(\"=== Control Protocol Example ===\")\n",
+ " \n",
+ " async with ClaudeSDKClient() as client:\n",
+ " # Get server initialization info\n",
+ " print(\"Getting server info...\")\n",
+ " server_info = await client.get_server_info()\n",
+ " \n",
+ " if server_info:\n",
+ " print(\"✓ Server info retrieved successfully!\")\n",
+ " print(f\" - Available commands: {len(server_info.get('commands', []))}\")\n",
+ " print(f\" - Output style: {server_info.get('output_style', 'unknown')}\")\n",
+ " \n",
+ " styles = server_info.get('available_output_styles', [])\n",
+ " if styles:\n",
+ " print(f\" - Available output styles: {', '.join(styles)}\")\n",
+ "\n",
+ "# asyncio.run(control_protocol_example())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "\n",
+ "## Types Reference\n",
+ "\n",
+ "### ClaudeAgentOptions\n",
+ "\n",
+ "Configuration options for Claude Agent SDK:\n",
+ "\n",
+ "- `model`: Model to use (e.g., \"claude-sonnet-4-5\")\n",
+ "- `system_prompt`: System prompt (string or dict with preset)\n",
+ "- `max_turns`: Maximum conversation turns\n",
+ "- `max_budget_usd`: Budget limit in USD\n",
+ "- `allowed_tools`: List of tool names to allow\n",
+ "- `tools`: Tools configuration (array or preset)\n",
+ "- `permission_mode`: Permission mode (\"default\", \"acceptEdits\", etc.)\n",
+ "- `can_use_tool`: Permission callback function\n",
+ "- `hooks`: Dictionary of hooks by event name\n",
+ "- `mcp_servers`: Dictionary of MCP servers\n",
+ "- `agents`: Dictionary of custom agents\n",
+ "- `setting_sources`: List of setting sources to load\n",
+ "- `cwd`: Working directory\n",
+ "- `cli_path`: Path to Claude Code CLI\n",
+ "- `env`: Environment variables\n",
+ "- `include_partial_messages`: Enable partial message streaming\n",
+ "\n",
+ "### Message Types\n",
+ "\n",
+ "- **AssistantMessage**: Messages from Claude\n",
+ "- **UserMessage**: Messages from user\n",
+ "- **SystemMessage**: System messages\n",
+ "- **ResultMessage**: Result with metadata (cost, duration, etc.)\n",
+ "- **StreamEvent**: Partial message stream events\n",
+ "\n",
+ "### Content Blocks\n",
+ "\n",
+ "- **TextBlock**: Text content\n",
+ "- **ToolUseBlock**: Tool usage request\n",
+ "- **ToolResultBlock**: Tool execution result\n",
+ "\n",
+ "---\n",
+ "\n",
+ "## Summary\n",
+ "\n",
+ "This notebook provides **100% coverage** of the Claude Agent SDK for Python, including:\n",
+ "\n",
+ "✅ Installation and setup \n",
+ "✅ Quick start with `query()` \n",
+ "✅ Basic usage and configuration \n",
+ "✅ Tool usage and permissions \n",
+ "✅ Streaming mode with `ClaudeSDKClient` \n",
+ "✅ Custom tools (SDK MCP Servers) \n",
+ "✅ Hooks (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart) \n",
+ "✅ Permission callbacks \n",
+ "✅ Custom agents \n",
+ "✅ Budget control \n",
+ "✅ Setting sources \n",
+ "✅ Error handling \n",
+ "✅ Advanced features (interrupts, partial messages, etc.) \n",
+ "\n",
+ "For more information, visit:\n",
+ "- [GitHub Repository](https://github.com/anthropics/claude-agent-sdk-python)\n",
+ "- [Official Documentation](https://platform.claude.com/docs/en/agent-sdk/python)\n",
+ "\n",
+ "---\n",
+ "\n",
+ "**License**: Use of this SDK is governed by Anthropic's [Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms)."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/firebase-debug.log b/firebase-debug.log
new file mode 100644
index 00000000..b35a13ac
--- /dev/null
+++ b/firebase-debug.log
@@ -0,0 +1,6 @@
+[debug] [2025-12-13T09:12:41.038Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
+[debug] [2025-12-13T09:12:41.153Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
+[debug] [2025-12-13T09:12:41.347Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
+[debug] [2025-12-13T09:12:41.348Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
+[debug] [2025-12-13T09:12:41.357Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
+[debug] [2025-12-13T09:12:41.358Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]