Add typed hook input structures (PreToolUseHookInput,
PostToolUseHookInput, etc.) to provide better IDE autocomplete and type
safety for hook callbacks. Also convert HookContext from dataclass to
TypedDict to match runtime behavior.
Changes:
- Add BaseHookInput, PreToolUseHookInput, PostToolUseHookInput,
UserPromptSubmitHookInput, StopHookInput, SubagentStopHookInput, and
PreCompactHookInput TypedDict classes
- Update HookCallback signature to use HookInput union type
- Convert HookContext from dataclass to TypedDict (fixes type mismatch)
- Export all new hook input types from __init__.py
- Update all examples and tests to use typed hook inputs
Benefits:
- Zero breaking changes (TypedDict is dict-compatible at runtime)
- Full type safety and IDE autocomplete for hook callbacks
- Matches TypeScript SDK structure exactly
- Self-documenting hook input fields
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Fixes critical bug where hook outputs using `async_` and `continue_`
(Python-safe names avoiding keyword conflicts) were not being converted
to `async` and `continue` as expected by the CLI. This caused hook
control fields like `{"decision": "block"}` or `{"continue_": False}` to
be silently ignored.
Changes:
- Add _convert_hook_output_for_cli() to handle field name conversion
- Apply conversion in hook callback handling
- Update type documentation to clarify field name usage
- Add comprehensive test coverage for field name conversion
- Update existing tests to verify conversion occurs correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Closes the gap between Python and TypeScript SDK hook output types by
adding:
- `reason` field for explaining decisions
- `continue_` field for controlling execution flow
- `suppressOutput` field for hiding stdout
- `stopReason` field for stop explanations
- `decision` now supports both "approve" and "block" (not just "block")
- `AsyncHookJSONOutput` type for deferred hook execution
- Proper typing for `hookSpecificOutput` with discriminated unions
Also adds comprehensive examples and tests:
- New examples in hooks.py demonstrating all new fields
- Unit tests in test_tool_callbacks.py for new output types
- E2E tests in e2e-tests/test_hooks.py with real API calls
- CI integration in .github/workflows/test.yml
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
## 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>
Hide hooks, tool permission callbacks, and SDK MCP server APIs from
public interface while keeping implementation code intact. These
features are not yet stable and should not be documented or exposed to
users.
Changes:
- Remove hook-related exports (HookCallback, HookContext, HookMatcher)
from __all__
- Remove tool permission exports (CanUseTool, ToolPermissionContext)
from __all__
- Remove SDK MCP exports (tool, create_sdk_mcp_server, SdkMcpTool) from
__all__
- Delete examples using unstable APIs (tool_permission_callback.py,
mcp_calculator.py)
- Remove SDK MCP server documentation from README
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@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>