claude-code-sdk-python/e2e-tests/test_tool_permissions.py
Ashwin Bhat 839300404f
Add custom tool callbacks and e2e tests (#157)
## 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>
2025-09-08 08:51:40 -07:00

43 lines
No EOL
1.4 KiB
Python

"""End-to-end tests for tool permission callbacks with real Claude API calls."""
import pytest
from claude_code_sdk import (
ClaudeCodeOptions,
ClaudeSDKClient,
PermissionResultAllow,
PermissionResultDeny,
ToolPermissionContext,
)
@pytest.mark.e2e
@pytest.mark.asyncio
async def test_permission_callback_gets_called():
"""Test that can_use_tool callback gets invoked."""
callback_invocations = []
async def permission_callback(
tool_name: str,
input_data: dict,
context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
"""Track callback invocation."""
print(f"Permission callback called for: {tool_name}, input: {input_data}")
callback_invocations.append(tool_name)
return PermissionResultAllow()
options = ClaudeCodeOptions(
can_use_tool=permission_callback,
)
async with ClaudeSDKClient(options=options) as client:
await client.query("Write 'hello world' to /tmp/test.txt")
async for message in client.receive_response():
print(f"Got message: {message}")
pass # Just consume messages
print(f'Callback invocations: {callback_invocations}')
# Verify callback was invoked
assert "Write" in callback_invocations, f"can_use_tool callback should have been invoked for Write tool, got: {callback_invocations}"