Skip to main content

Claude Bridge

API gateway that intercepts Claude Code CLI and exposes it through Anthropic Messages API.

Overview

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.

Run it with an ANTHROPIC_API_KEY in the environment. The CLI can also run on a Pro/Max subscription token, but Anthropic's terms reserve that credential for ordinary interactive use of Claude Code rather than for fronting an API — see Authentication before choosing.

Features

  • 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 claude CLI
  • Locked down by default - Tools that execute, mutate, delegate, persist, or reach the network are withheld, and no MCP servers are loaded (see Security Implications)

Quick Start (No Installation Required)

Run Claude Bridge instantly with uvx (no installation needed):

uvx claude-bridge

This will download, install, and run the server in an isolated environment.

Note: uvx requires the package to be published to PyPI. For local development, use uv run claude-bridge instead.


Installation

Prerequisites

  • Python 3.12+
  • uv (recommended) - Fast Python package installer and runner: pip install uv
  • Claude CLI - Install from claude.com
    • The claude command must be available in your PATH
  • An Anthropic API key - export ANTHROPIC_API_KEY=sk-ant-...
    • See Authentication for why this, and not claude setup-token, is the supported way to run the bridge

Option 1: Run with uvx (Recommended - No Installation)

# Run directly without installing
uvx claude-bridge

Option 2: Install Locally

Using 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]"

Usage

Start the server

With uvx (no installation required):

uvx claude-bridge

After local installation:

claude-bridge

Or with uv run:

uv run claude-bridge

Or directly with Python:

python main.py

The server will start on http://localhost:8000 by default.

Configuration

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/directory

⚠️ File Upload Configuration (Required)

File upload is disabled by default for security. To enable:

1. Configure via .env file OR command-line arguments

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=bypassPermissions

Option B: Using CLI arguments (override .env, higher priority)

claude-bridge --allowed-tools Read --allowed-directories /tmp

Note: CLI arguments take precedence over environment variables.

2. Understand Security Implications

What you're allowing:

  • CLAUDE_ALLOWED_TOOLS_STR=Read - makes Read available, for file upload only
  • CLAUDE_ALLOWED_DIRECTORIES_STR=/tmp - the directories Claude may reach
  • CLAUDE_PERMISSION_MODE=bypassPermissions - No interactive permission prompts (required for API mode). Informational only: the bridge always passes this mode regardless of the setting.

What actually protects you — CLAUDE_DISALLOWED_TOOLS_STR, not the allow-list.

Under bypassPermissions the allow-list pre-approves the tools you name but does not withhold the rest: with --allowed-tools Read, Bash and Write are still reachable. Only the deny-list withholds a tool. The bridge therefore ships a secure default (DEFAULT_DISALLOWED_TOOLS in config.py) that withholds every tool which executes, mutates, delegates to a subagent, outlives the request, or reaches the network, leaving Read/Glob/Grep. It also passes --strict-mcp-config so no MCP server is loaded — otherwise the CLI inherits the operator's own MCP configuration and an HTTP caller reaches whatever that exposes.

What is protected (with the defaults):

  • ❌ Claude cannot execute code, write files, or browse the web
  • ❌ Claude cannot spawn subagents, schedule cron jobs, or send messages
  • ❌ Claude cannot reach your MCP servers

This matters because bypassPermissions applies no path restriction to Read/Glob/Grep, and --add-dir only adds directories rather than limiting them. Had those tools stayed available, a caller could read any file the server process can — .env, ~/.claude/.credentials.json, SSH and cloud credentials — and get the contents back in the response body. The bridge therefore passes --tools "", so they are not available at all. Enabling file upload swaps that for --tools Read, which is the minimum the feature needs; Grep stays out because it reads file contents too.

What is still not protected: --tools gates the CLI's built-in set, so a tool supplied by local configuration outside that set can survive it. The bridge also has no authentication of its own — it binds to 127.0.0.1 by default, and anything that can reach the port can spend your credentials. Put your own auth in front of it before binding it anywhere else.

The deny-list underneath is defense-in-depth, and it only names tools that exist today; --tools is what bounds a tool a future CLI adds. Treat every prompt that reaches this API as untrusted input — instructions hidden in an uploaded document are indistinguishable from the caller's own.

3. Adjust for Your Environment

macOS/Linux:

CLAUDE_ALLOWED_DIRECTORIES_STR=/tmp

Windows:

CLAUDE_ALLOWED_DIRECTORIES_STR=C:\Temp

Custom temp directory:

CLAUDE_ALLOWED_DIRECTORIES_STR=/var/myapp/temp

Multiple directories (comma-separated):

CLAUDE_ALLOWED_DIRECTORIES_STR=/tmp,/var/app-temp

4. Start Server

After configuring .env or using CLI arguments:

claude-bridge

You should see:

INFO: Claude CLI permissions configured: tools=[Read], directories=[/tmp], mode=bypassPermissions
INFO: File upload is ENABLED

Troubleshooting

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

Advanced: Using All Permission Arguments

# 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

Example: Upload File

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..."
        }
      }]
    }]
  }'

Authentication

The bridge does not authenticate anything itself — it inherits whatever credentials the claude CLI runs under. The CLI accepts two, and which one you use determines whether running this bridge is permitted at all.

Supported: API key
export ANTHROPIC_API_KEY=sk-ant-...
claude-bridge

Get a key from the Claude Console. Usage bills to that key and falls under Anthropic's Commercial Terms, which is the agreement that covers programmatic access. apiKeyHelper works too if you resolve keys dynamically. This is the mode to use for anything that is not a single person poking at the bridge on their own laptop — any deployment, any shared instance, anything backing an application, anything with more than one user.

Not supported for applications: subscription OAuth

The CLI can also authenticate against a Claude Pro/Max subscription via claude setup-token, and the bridge will happily run that way. Anthropic's Claude Code legal terms are explicit about what that credential is for:

OAuth authentication is intended exclusively for purchasers of Claude Free, Pro, Max, Team, and Enterprise subscription plans and is designed to support ordinary use of Claude Code and other native Anthropic applications.

Developers building products or services that interact with Claude's capabilities, including those using the Agent SDK, should use API key authentication through Claude Console or a supported cloud provider. Anthropic does not permit third-party developers to offer Claude.ai login or to route requests through Free, Pro, or Max plan credentials on behalf of their users.

Two consequences worth being clear about:

  • Fronting a subscription token with an HTTP API is not "ordinary use of Claude Code," even with one user on one machine. If you do it, understand that is the posture you are in.
  • Standing this up for anyone but yourself — a team instance, a hosted endpoint, a product backend — is the case the terms name outright. Don't.

Anthropic "reserves the right to take measures to enforce these restrictions and may do so without prior notice," and enforcement lands on the subscription account. Set ANTHROPIC_API_KEY and the question does not arise.

The bridge logs a warning at startup when it finds no API key in the environment.

Verify either way
claude --version
claude --print "Hello, Claude!"

API Endpoints

POST /anthropic/v1/messages

Create a message using Claude Code.

Supported Model Names:

The bridge automatically maps Anthropic API model names to Claude CLI aliases:

  • claude-sonnet-4sonnet (latest Sonnet)
  • claude-opus-4opus (latest Opus)
  • claude-haiku-4haiku (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?"}
    ]
  }'

GET /health

Health check endpoint.

curl http://localhost:8000/health

Using with Anthropic SDK

The 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",  # Not checked by the bridge; the CLI holds the real credential
    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 the api_key you pass here, and it is not an access control — anything that can reach the port can spend your credentials. Bind it to localhost, or put your own auth in front of it. The real credential is whichever one the claude CLI is running under; see Authentication.

Development

Setup Pre-commit Hooks

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-files

Hooks 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)

Run tests

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 browser

Test Structure:

  • tests/unit/ - Fast, isolated unit tests for individual components
  • tests/integration/ - Integration tests with mocked external dependencies
  • tests/fixtures/ - Sample data for tests
  • tests/conftest.py - Shared pytest fixtures

Manual code quality checks

# Linting and formatting
ruff check src/ --fix
ruff format src/

# Type checking
mypy src/

# Security scanning
bandit -c pyproject.toml -r src/

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

claude_bridge-0.1.0.tar.gz (101.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

claude_bridge-0.1.0-py3-none-any.whl (30.0 kB view details)

Uploaded Python 3

File details

Details for the file claude_bridge-0.1.0.tar.gz.

File metadata

  • Download URL: claude_bridge-0.1.0.tar.gz
  • Upload date:
  • Size: 101.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for claude_bridge-0.1.0.tar.gz
Algorithm Hash digest
SHA256 085ecdb3f61599f8abc637edb1a5275d6813d6bd959dde63fce54a5d0af51672
MD5 22622230dfc4f21d38f6a24562b09ccd
BLAKE2b-256 5257f351e39af7389e423fcdd05c56dc18ccb28cbd767d59eab6bf4409e8c834

See more details on using hashes here.

File details

Details for the file claude_bridge-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: claude_bridge-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 30.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for claude_bridge-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d1e72744eab8074155e00dd8efbf6585a732041c61516a75fde67e936b4361b5
MD5 149404360b36b45483422fea8c7af73a
BLAKE2b-256 bf859fc069561beeeb7c4bfdc017e0e345206c1b85a60dd7ac5c66ed7d07f4c0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page