API gateway that intercepts Claude Code CLI and exposes it through Anthropic Messages API.
Claude Bridge wraps the Claude Code CLI (not the SDK) and exposes it through an Anthropic-compatible REST API. It acts as an interceptor, translating HTTP API requests into CLI subprocess calls. Each request is handled as a single, stateless task to Claude Code.
- CLI Interceptor - Wraps Claude Code CLI, not the Python SDK
- Anthropic Messages API compatibility - Drop-in replacement for Anthropic API
- Anthropic SDK compatible - Works seamlessly with the official Anthropic Python SDK
- Streaming support - Server-Sent Events (SSE) for real-time responses
- Stateless architecture - Each request is independent
- No SDK dependency - Direct subprocess calls to
claudeCLI - Full Claude Code capabilities - All CLI features available
Run Claude Bridge instantly with uvx (no installation needed):
uvx claude-bridgeThis will download, install, and run the server in an isolated environment.
Note:
uvxrequires the package to be published to PyPI. For local development, useuv run claude-bridgeinstead.
- Python 3.12+
- uv (recommended) - Fast Python package installer and runner:
pip install uv - Claude CLI - Install from claude.com
- The
claudecommand must be available in your PATH - Authenticate using:
claude setup-token
- The
# Run directly without installing
uvx claude-bridgeUsing uv (recommended):
uv pip install -e .Or for development:
uv pip install -e ".[dev]"This will install the claude-bridge CLI command in your virtual environment.
Alternatively, using pip:
pip install -e ".[dev]"With uvx (no installation required):
uvx claude-bridgeAfter local installation:
claude-bridgeOr with uv run:
uv run claude-bridgeOr directly with Python:
python main.pyThe server will start on http://localhost:8000 by default.
Create a .env file in the project root (or use environment variables):
# Application settings
DEBUG=false
HOST=0.0.0.0
PORT=8000
# Claude CLI settings
CLAUDE_CLI_PATH=claude # Path to claude binary
CLAUDE_CWD=/path/to/working/directoryFile upload is disabled by default for security. To enable:
Option A: Using .env file (persistent)
# Required for file upload
CLAUDE_ALLOWED_TOOLS_STR=Read
CLAUDE_ALLOWED_DIRECTORIES_STR=/tmp
# Permission mode (required for non-interactive API)
CLAUDE_PERMISSION_MODE=bypassPermissionsOption B: Using CLI arguments (override .env, higher priority)
claude-bridge --allowed-tools Read --allowed-directories /tmpNote: CLI arguments take precedence over environment variables.
What you're allowing:
CLAUDE_ALLOWED_TOOLS_STR=Read- Claude can read files (but not write/execute)CLAUDE_ALLOWED_DIRECTORIES_STR=/tmp- Claude can only access/tmpdirectoryCLAUDE_PERMISSION_MODE=bypassPermissions- No interactive permission prompts (required for API mode)
What is protected:
- ❌ Claude cannot execute code (no Bash tool)
- ❌ Claude cannot modify files (no Write/Edit tools)
- ❌ Claude cannot access other directories (only those you specify)
macOS/Linux:
CLAUDE_ALLOWED_DIRECTORIES_STR=/tmpWindows:
CLAUDE_ALLOWED_DIRECTORIES_STR=C:\TempCustom temp directory:
CLAUDE_ALLOWED_DIRECTORIES_STR=/var/myapp/tempMultiple directories (comma-separated):
CLAUDE_ALLOWED_DIRECTORIES_STR=/tmp,/var/app-tempAfter configuring .env or using CLI arguments:
claude-bridgeYou should see:
INFO: Claude CLI permissions configured: tools=[Read], directories=[/tmp], mode=bypassPermissions
INFO: File upload is ENABLED
Error: "CLAUDE_ALLOWED_TOOLS_STR not configured"
→ Add CLAUDE_ALLOWED_TOOLS_STR=Read to your .env file, OR
→ Use CLI argument: --allowed-tools Read
Error: "contains non-existent directory"
→ Verify the directory exists: ls -la /tmp
# Enable multiple tools and directories via CLI
claude-bridge \
--allowed-tools "Read,Bash" \
--disallowed-tools "Write,Edit" \
--allowed-directories "/tmp,/var/app-data"Permission errors in responses → Ensure your temp directory matches where files are created
curl -X POST http://localhost:8000/anthropic/v1/messages \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5-20250929",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": [{
"type": "text",
"text": "What is in this image?"
}, {
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": "iVBORw0KGgoAAAA...base64_image_data..."
}
}]
}]
}'The bridge uses the Claude CLI's existing authentication. Make sure you've authenticated:
# Authenticate Claude CLI
claude setup-token
# Verify it works
claude --version
claude --print "Hello, Claude!"The bridge will automatically use the CLI's credentials. No need to configure API keys separately!
Create a message using Claude Code.
Supported Model Names:
The bridge automatically maps Anthropic API model names to Claude CLI aliases:
claude-sonnet-4→sonnet(latest Sonnet)claude-opus-4→opus(latest Opus)claude-haiku-4→haiku(latest Haiku)- Or use full model names like
claude-sonnet-4-5-20250929(recommended for stability)
Non-streaming request:
curl -X POST http://localhost:8000/anthropic/v1/messages \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5-20250929",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hello, Claude!"}
]
}'Streaming request:
curl -X POST http://localhost:8000/anthropic/v1/messages \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5-20250929",
"max_tokens": 1024,
"stream": true,
"messages": [
{"role": "user", "content": "Count to 10"}
]
}'With system prompt:
curl -X POST http://localhost:8000/anthropic/v1/messages \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5-20250929",
"max_tokens": 1024,
"system": "You are a helpful assistant.",
"messages": [
{"role": "user", "content": "What is 2+2?"}
]
}'Health check endpoint.
curl http://localhost:8000/healthThe bridge is fully compatible with the official Anthropic Python SDK. Simply configure the client with a custom base_url:
import anthropic
client = anthropic.AsyncAnthropic(
api_key="not-needed", # Bridge uses CLI authentication
base_url="http://localhost:8000/anthropic"
)
# Streaming example
async with client.messages.stream(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}]
) as stream:
async for text in stream.text_stream:
print(text, end="", flush=True)
# Non-streaming example
message = await client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{"role": "user", "content": "What is 2+2?"}]
)
print(message.content[0].text)Note: The bridge does not validate API keys. Authentication is handled by the Claude CLI itself via claude setup-token.
This project uses pre-commit hooks for code quality and consistency:
# Install hooks (one-time setup)
pre-commit install
# Run manually on all files (optional)
pre-commit run --all-filesHooks will automatically run on git commit and include:
- ✅ Code formatting (ruff)
- ✅ Linting (ruff)
- ✅ Type checking (mypy)
- ✅ Security scanning (bandit)
- ✅ File hygiene (trailing whitespace, EOF, etc.)
- ✅ Secret detection (detect-private-key)
The project includes comprehensive unit and integration tests:
# Run all tests with coverage
pytest
# Run specific test categories
pytest tests/unit/ # Unit tests only
pytest tests/integration/ # Integration tests only
# Run with verbose output
pytest -v
# Run specific test file
pytest tests/unit/test_adapter.py
# Run without coverage report
pytest --no-cov
# Generate HTML coverage report
pytest --cov-report=html
# Then open htmlcov/index.html in your browserTest Structure:
tests/unit/- Fast, isolated unit tests for individual componentstests/integration/- Integration tests with mocked external dependenciestests/fixtures/- Sample data for teststests/conftest.py- Shared pytest fixtures
# Linting and formatting
ruff check src/ --fix
ruff format src/
# Type checking
mypy src/
# Security scanning
bandit -c pyproject.toml -r src/MIT