## Summary
This PR adds support for custom tool callbacks and comprehensive e2e
testing for MCP calculator functionality.
## Key Features Added
- **Custom tool permission callbacks** - Allow dynamic tool permission
control via `can_use_tool` callback
- **E2E test suite** - Real Claude API tests validating MCP tool
execution end-to-end
- **Fixed MCP calculator example** - Now properly uses `allowed_tools`
for permission management
## Changes
### Custom Callbacks
- Added `ToolPermissionContext` and `PermissionResult` types for tool
permission handling
- Implemented `can_use_tool` callback support in SDK client
- Added comprehensive tests in `tests/test_tool_callbacks.py`
### E2E Testing Infrastructure
- Created `e2e-tests/` directory with pytest-based test suite
- `test_mcp_calculator.py` - Tests all calculator operations with real
API calls
- `conftest.py` - Pytest config with mandatory API key validation
- GitHub Actions workflow for automated e2e testing on main branch
- Comprehensive documentation in `e2e-tests/README.md`
### Bug Fixes
- Fixed MCP calculator example to use `allowed_tools` instead of
incorrect `permission_mode`
- Resolved tool permission issues preventing MCP tools from executing
## Testing
E2E tests require `ANTHROPIC_API_KEY` environment variable and will fail
without it.
Run locally:
```bash
export ANTHROPIC_API_KEY=your-key
python -m pytest e2e-tests/ -v -m e2e
```
Run unit tests including callback tests:
```bash
python -m pytest tests/test_tool_callbacks.py -v
```
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Kashyap Murali <kashyap@anthropic.com>
## Summary
Adds comprehensive support for tool permission callbacks and hook
callbacks to the Python SDK, enabling fine-grained control over tool
execution and custom event handling.
## Key Changes
- **Tool Permission Callbacks**: Control which tools Claude can use and
modify their inputs
- type with async support
- with suggestions from CLI
- for structured responses
- **Hook Callbacks**: React to events in the Claude workflow
- type for event handlers
- for conditional hook execution
- Support for tool_use_start, tool_use_end events
- **Integration**: Full plumbing through ClaudeCodeOptions → Client →
Query
- **Examples**: Comprehensive example showing permission control
patterns
- **Tests**: Coverage for all callback scenarios
## Implementation Details
- Callbacks are registered during initialization phase
- Control protocol handles can_use_tool and hook_callback requests
- Backwards compatible with dict returns for tool permissions
- Proper error handling and type safety throughout
Builds on top of #139's control protocol implementation.
---------
Co-authored-by: Dickson Tsai <dickson@anthropic.com>
## Summary
Adds in-process SDK MCP server support to the Python SDK, building on
the control protocol from #139.
**Note: Targets `dickson/control` branch (PR #139), not `main`.**
## Key Changes
- Added `@tool` decorator and `create_sdk_mcp_server()` API for defining
in-process MCP servers
- SDK MCP servers run directly in the Python process (no subprocess
overhead)
- Moved SDK MCP handling from Transport to Query class for proper
architectural layering
- Added `McpSdkServerConfig` type and integrated with control protocol
## Example
```python
from claude_code_sdk import tool, create_sdk_mcp_server
@tool("greet", "Greet a user", {"name": str})
async def greet_user(args):
return {"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]}
server = create_sdk_mcp_server(name="my-tools", tools=[greet_user])
options = ClaudeCodeOptions(mcp_servers={"tools": server})
```
## Testing
- Added integration tests in `test_sdk_mcp_integration.py`
- Added example calculator server in `examples/mcp_calculator.py`
---------
Co-authored-by: Dickson Tsai <dickson@anthropic.com>
Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
This PR implements control protocol support in the Python SDK, aligning
it with the TypeScript implementation pattern. The refactor introduces a
Query + Transport separation to enable bidirectional communication
between the SDK and CLI.
## Motivation
The previous Python SDK implementation used a high-level abstraction in
the Transport ABC (`send_request`/`receive_messages`) that couldn't
handle bidirectional communication. This prevented support for:
- Control messages from CLI to SDK that need responses
- Hooks implementation
- Dynamic permission mode changes
- SDK MCP servers
## Changes
### Core Architecture Refactor
1. **New Query Class** (`src/claude_code_sdk/_internal/query.py`)
- Manages control protocol on top of Transport
- Handles control request/response routing
- Manages initialization handshake with timeout
- Supports hook callbacks and tool permission callbacks
- Implements message streaming
2. **Refactored Transport ABC**
(`src/claude_code_sdk/_internal/transport/__init__.py`)
- Changed from high-level (`send_request`/`receive_messages`) to
low-level (`write`/`read_messages`) interface
- Now handles raw I/O instead of protocol logic
- Aligns with TypeScript ProcessTransport pattern
3. **Updated SubprocessCLITransport**
(`src/claude_code_sdk/_internal/transport/subprocess_cli.py`)
- Simplified to focus on raw message streaming
- Removed protocol logic (moved to Query)
- Improved cleanup and error handling
4. **Enhanced ClaudeSDKClient** (`src/claude_code_sdk/client.py`)
- Now uses Query for control protocol
- Supports initialization messages
- Better error handling for control protocol failures
### Control Protocol Features
- **Initialization handshake**: SDK sends initialize request, CLI
responds with supported commands
- **Control message types**:
- `initialize`: Establish bidirectional connection
- `interrupt`: Cancel ongoing operations
- `set_permission_mode`: Change permission mode dynamically
- **Timeout handling**: 60-second timeout for initialization to handle
CLI versions without control support
### Examples
Updated `examples/streaming_mode.py` to demonstrate control protocol
initialization and error handling.
## Testing
- Tested with current CLI (no control protocol support yet) - gracefully
falls back
- Verified backward compatibility with existing `query()` function
- Tested initialization timeout handling
- Verified proper cleanup on errors
## Design Alignment
This implementation closely follows the TypeScript reference:
- `src/core/Query.ts` → `src/claude_code_sdk/_internal/query.py`
- `src/transport/ProcessTransport.ts` →
`src/claude_code_sdk/_internal/transport/subprocess_cli.py`
- `src/entrypoints/sdk.ts` → `src/claude_code_sdk/client.py`
## Next Steps
Once the CLI implements the control protocol handler, this will enable:
- Hooks support
- Dynamic permission mode changes
- SDK MCP servers
- Improved error recovery
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Kashyap Murali <kashyap@anthropic.com>
## Summary
- Replace asyncio.create_task() with anyio task group for trio
compatibility
- Update client.py docstring example to use anyio.sleep
- Add trio example demonstrating multi-turn conversation
## Details
The SDK already uses anyio for most async operations, but one line was
using asyncio.create_task() which broke trio compatibility. This PR
fixes that by using anyio's task group API with proper lifecycle
management.
### Changes:
1. **subprocess_cli.py**: Replace asyncio.create_task() with anyio task
group, ensuring proper cleanup on disconnect
2. **client.py**: Update docstring example to use anyio.sleep instead of
asyncio.sleep
3. **streaming_mode_trio.py**: Add new example showing how to use the
SDK with trio
## Test plan
- [x] All existing tests pass
- [x] Manually tested with trio runtime (created test script that
successfully runs multi-turn conversation)
- [x] Linting and type checking pass
🤖 Generated with [Claude Code](https://claude.ai/code)