Skip to content

Repository files navigation

claude-bridge

OpenAI-compatible API bridge that routes requests to Claude Code CLI or any configured external provider (Ollama, DeepSeek, OpenRouter, etc.), with a provider registry, full tool/tool_calls passthrough, SQLite usage tracking, streaming SSE, and a built-in dashboard.

Features

  • OpenAI-compatible REST API:
    • /v1/chat/completions
    • /v1/models
  • Streaming support using Server-Sent Events (stream: true)
  • Claude Code backend — default executor, fallback for all unmatched models
    • Stateless requests
    • Stateful sessions using X-Session-ID
    • MCP tools, allowed_tools, workdir support
    • Uses the local claude CLI
  • Provider registry — configure any OpenAI-compatible provider
    • Declare providers in ~/.cc_bridge/config.json
    • Route by provider,model or provider/model format
    • Full tools, tool_calls, tool_choice passthrough
    • Streaming SSE passthrough with usage capture
    • Backward compatible: OLLAMA_URL auto-registers as ollama provider
  • SQLite usage tracking
    • Powered by Gorm
    • Logs model, provider, tokens, duration, and errors
  • Built-in dashboard at /dashboard
  • Usage JSON API/v1/usage and /v1/usage/recent
  • Optional Authorization: Bearer auth key

Requirements

Project Structure

claude-bridge/
├─ cmd/
│  └─ claude-bridge/
│     └─ main.go
│
├─ internal/
│  ├─ config/
│  │  ├─ config.go        # env-based config
│  │  └─ providers.go     # BridgeConfig loader (~/.cc_bridge/config.json)
│  ├─ domain/
│  ├─ http/
│  │  ├─ handlers/
│  │  ├─ middleware/
│  │  └─ router.go
│  ├─ providers/
│  │  ├─ claude/          # Claude Code CLI executor
│  │  ├─ external/        # Generic OpenAI-compatible HTTP provider
│  │  ├─ ollama/          # Legacy Ollama provider (kept for reference)
│  │  └─ registry/        # Provider registry + route resolution
│  ├─ services/
│  ├─ sessions/
│  └─ storage/
│     ├─ models/
│     └─ repository/
│
├─ web/
│  └─ dashboard/
│     └─ template.go
│
├─ Dockerfile
├─ .env.example
├─ go.mod
└─ README.md

Installation

go mod tidy

Build

go build -o claude-bridge ./cmd/claude-bridge

On Windows:

go build -o claude-bridge.exe ./cmd/claude-bridge

Run Locally

./claude-bridge

On Windows:

.\claude-bridge.exe

With environment variables:

HOST=127.0.0.1 \
PORT=8080 \
CLAUDE_WORKDIR=/path/to/workspace \
CLAUDE_SKIP_PERMS=false \
OLLAMA_URL=http://localhost:11434 \
USAGE_DB_PATH=./usage.db \
./claude-bridge

On Windows PowerShell:

$env:HOST="127.0.0.1"
$env:PORT="8080"
$env:CLAUDE_WORKDIR="C:\Users\your-user\Development\my-project"
$env:CLAUDE_SKIP_PERMS="false"
$env:OLLAMA_URL="http://localhost:11434"
$env:USAGE_DB_PATH="./usage.db"

.\claude-bridge.exe

Docker

The project includes a Dockerfile for running claude-bridge in a container.

This is mainly useful when using the Ollama backend. For Claude Code, running directly on the host is usually simpler because the container would need access to the claude CLI and its authenticated credentials.

Build Docker Image

docker build -t claude-bridge .

Run Docker Container

docker run --rm -it \
  --name claude-bridge \
  -p 8080:8080 \
  -v claude_bridge_data:/data \
  claude-bridge

Then open:

http://127.0.0.1:8080/dashboard

Health check:

curl http://127.0.0.1:8080/health

Run Docker with Environment Variables

Inside Docker, HOST should be 0.0.0.0 so the published port is reachable from the host.

docker run --rm -it \
  --name claude-bridge \
  -e HOST=0.0.0.0 \
  -e PORT=8080 \
  -e USAGE_DB_PATH=/data/usage.db \
  -e OLLAMA_URL=http://host.docker.internal:11434 \
  -p 8080:8080 \
  -v claude_bridge_data:/data \
  claude-bridge

Docker with Ollama on Host

On Docker Desktop for Windows or macOS, use:

OLLAMA_URL=http://host.docker.internal:11434

Test an Ollama model:

curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ollama/llama3.2",
    "messages": [
      {
        "role": "user",
        "content": "Hello"
      }
    ]
  }'

On Linux, if host.docker.internal is not available, run the container with:

docker run --rm -it \
  --name claude-bridge \
  --add-host=host.docker.internal:host-gateway \
  -p 8080:8080 \
  -v claude_bridge_data:/data \
  claude-bridge

Docker with Local Auth

docker run --rm -it \
  --name claude-bridge \
  -e CLAUDE_BRIDGE_AUTH_KEY=your-local-secret \
  -p 8080:8080 \
  -v claude_bridge_data:/data \
  claude-bridge

Then requests must include:

-H "Authorization: Bearer your-local-secret"

Example:

curl http://127.0.0.1:8080/v1/models \
  -H "Authorization: Bearer your-local-secret"

Docker Notes for Claude Code

The Docker image does not install or authenticate the Claude Code CLI by default.

For the Claude Code backend to work inside Docker, the container needs:

  • The claude binary installed inside the image.
  • Claude Code credentials available inside the container.
  • A mounted workspace if you want Claude Code to operate on local files.
  • Correct CLAUDE_BIN and CLAUDE_WORKDIR values.

For most local development workflows, run claude-bridge directly on the host when using Claude Code.

Environment Variables

Variable Default Description
HOST 127.0.0.1 locally, 0.0.0.0 in Docker Listening host.
PORT 8080 HTTP server port.
CLAUDE_BRIDGE_AUTH_KEY empty Optional local auth key. If set, requests must include Authorization: Bearer <key>.
CLAUDE_BIN PATH lookup Override Claude CLI binary path.
CLAUDE_WORKDIR empty Working directory for the Claude subprocess.
CLAUDE_SKIP_PERMS false Pass --dangerously-skip-permissions to Claude. Use with caution.
CLAUDE_DEFAULT_MODEL claude-code Default model when none is specified.
OLLAMA_URL http://localhost:11434 locally, http://host.docker.internal:11434 in Docker Legacy Ollama URL. Auto-registers an ollama provider when no config file is present.
CCB_CONFIG_PATH ~/.cc_bridge/config.json Path to the provider registry config file.
USAGE_DB_PATH ./usage.db locally, /data/usage.db in Docker SQLite database path. Created automatically.

.env.example

HOST=127.0.0.1
PORT=8080

CLAUDE_BRIDGE_AUTH_KEY=

CLAUDE_BIN=
CLAUDE_WORKDIR=
CLAUDE_DEFAULT_MODEL=claude-code
CLAUDE_SKIP_PERMS=false

# Legacy: auto-registers an "ollama" provider when CCB_CONFIG_PATH is not set
OLLAMA_URL=http://localhost:11434

# Provider registry config (preferred over OLLAMA_URL)
CCB_CONFIG_PATH=

USAGE_DB_PATH=./usage.db

For Docker, use:

HOST=0.0.0.0
PORT=8080
OLLAMA_URL=http://host.docker.internal:11434
USAGE_DB_PATH=/data/usage.db

Provider Registry

Configure any OpenAI-compatible provider in ~/.cc_bridge/config.json (or set CCB_CONFIG_PATH):

{
  "providers": [
    {
      "name": "ollama",
      "api_base_url": "http://localhost:11434/v1",
      "api_key": "ollama",
      "models": ["qwen3-coder:latest", "llama3.2:latest"]
    },
    {
      "name": "deepseek",
      "api_base_url": "https://api.deepseek.com/v1",
      "api_key": "sk-your-key",
      "models": ["deepseek-chat", "deepseek-reasoner"]
    },
    {
      "name": "openrouter",
      "api_base_url": "https://openrouter.ai/api/v1",
      "api_key": "sk-or-your-key",
      "models": []
    }
  ]
}

When no config file is found and OLLAMA_URL is set, an ollama provider is auto-registered pointing at that URL (backward compatible).

Provider Drivers

Each provider has a driver that decides how its requests are executed:

Driver Behaviour
openai (default) Generic OpenAI-compatible HTTP passthrough. The configured model is both brain and responder — cc_bridge forwards the request and returns the response verbatim (including any tool_calls, which the caller is responsible for executing).
claude Runs the Claude Code CLI (full agentic harness: bash, file ops, MCP, tool loop) with ANTHROPIC_BASE_URL pointed at the provider's api_base_url. Claude Code becomes the body; the configured model becomes the brain. Tools execute for real.

Swapping Claude Code's brain (driver: "claude")

Claude Code normally runs on Anthropic models. With a claude-driver provider you can point its harness at any backend that speaks the Anthropic Messages API (e.g. Ollama v0.20+ serving /v1/messages), so a local/cheap model drives Claude Code's bash and tools:

{
  "providers": [
    {
      "name": "claude-minimax",
      "driver": "claude",
      "api_base_url": "http://localhost:11434",
      "api_key": "ollama",
      "models": ["minimax-m2.7:cloud"]
    }
  ]
}
curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-minimax,minimax-m2.7:cloud",
    "messages": [{"role": "user", "content": "run: ping -c 4 google.com and report the latencies"}]
  }'

cc_bridge launches claude --model minimax-m2.7:cloud with ANTHROPIC_BASE_URL=http://localhost:11434 + ANTHROPIC_AUTH_TOKEN=ollama. Claude Code's harness (bash) executes the ping for real, piloted by minimax — not Anthropic. The Anthropic traffic flows Claude Code → the backend directly; cc_bridge only orchestrates the subprocess.

Requires a backend that serves the Anthropic Messages API. Ollama v0.20+ does this natively at /v1/messages.

Model Routing

Requests are resolved in this order:

  1. Registry match → routes to the configured external provider
  2. Claude Code fallback → all unmatched models go to the Claude Code CLI
Format Example Resolves to
provider,model ollama,qwen3-coder:latest Provider named ollama, model qwen3-coder:latest
provider/model ollama/llama3.2 Provider named ollama, model llama3.2
bare model name qwen3-coder:latest First provider that declares it in models[]
claude-driver provider claude-minimax,minimax-m2.7:cloud Claude Code CLI with that backend as the brain
anything else claude-code Claude Code CLI (real Anthropic)

Tool Use with External Providers

External providers receive the full tools, tool_calls, and tool_choice fields — nothing is stripped. Any provider that supports OpenAI-compatible tool calling (Ollama, DeepSeek, etc.) will work end-to-end:

curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ollama,qwen3-coder:latest",
    "messages": [{"role": "user", "content": "list files in /tmp"}],
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "run_command",
          "description": "Execute a shell command",
          "parameters": {
            "type": "object",
            "properties": {
              "command": {"type": "string"}
            },
            "required": ["command"]
          }
        }
      }
    ],
    "tool_choice": "auto"
  }'

MCP Support

claude-bridge can optionally enable MCP tools for the Claude Code backend.

Claude Code already supports MCP, so claude-bridge only forwards the correct flags to the claude CLI:

--mcp-config
--strict-mcp-config
--allowedTools

MCP is only supported for Claude Code requests. Ollama requests ignore MCP fields.

Environment Variables

Variable Default Description
MCP_CONFIG_PATH empty Path to a local MCP registry JSON file.
MCP_ALWAYS false If true, load the MCP registry on every Claude request.

MCP Registry

Create a local MCP registry file based on mcp.example.json.

Example:

{
  "mcpServers": {
    "projecthub": {
      "command": "npx",
      "args": ["-y", "projecthub-mcp"],
      "env": {
        "API_KEY": "REPLACE_ME"
      }
    }
  }
}

Then set:

MCP_CONFIG_PATH=./mcp.json

Do not commit real MCP credentials.

Per-request MCP

You can enable MCP tools per request using these fields:

Field Type Description
allowed_tools string[] Tool allowlist passed to Claude Code.
mcp_servers string[] MCP servers to load from MCP_CONFIG_PATH.
mcp_config object Inline MCP config. Overrides the registry for that request.
workdir string Working directory for the Claude subprocess.

Example:

curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-code",
    "messages": [
      {
        "role": "user",
        "content": "Use the project MCP tools for this task."
      }
    ],
    "mcp_servers": ["projecthub"],
    "allowed_tools": ["mcp__projecthub"],
    "workdir": "/path/to/project"
  }'

Tool names follow Claude Code MCP naming:

mcp__<server>__<tool>

You can also allow all tools from a server with:

mcp__<server>

API Endpoints

Chat Completions

Claude Code

curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-code",
    "messages": [
      {
        "role": "user",
        "content": "Hello"
      }
    ]
  }'

Ollama

curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ollama/llama3.2",
    "messages": [
      {
        "role": "user",
        "content": "Hello"
      }
    ]
  }'

Streaming

curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-code",
    "stream": true,
    "messages": [
      {
        "role": "user",
        "content": "Write a short paragraph about Go."
      }
    ]
  }'

With Ollama:

curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ollama/llama3.2",
    "stream": true,
    "messages": [
      {
        "role": "user",
        "content": "Write a short paragraph about Go."
      }
    ]
  }'

Local Auth

Auth is optional.

If CLAUDE_BRIDGE_AUTH_KEY is empty, no authorization header is required.

If CLAUDE_BRIDGE_AUTH_KEY is set, every protected request must include:

-H "Authorization: Bearer your-local-secret"

Example:

curl http://127.0.0.1:8080/v1/models \
  -H "Authorization: Bearer your-local-secret"

Stateful Sessions

Claude Code Sessions

Pass X-Session-ID to keep context across requests.

curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "X-Session-ID: demo-session-1" \
  -d '{
    "model": "claude-code",
    "messages": [
      {
        "role": "user",
        "content": "Remember that my project is written in Go."
      }
    ]
  }'

The bridge maps your client session ID to a Claude session ID and uses Claude Code session resume internally.

Ollama Sessions

Ollama does not provide native session resume in the same way, so claude-bridge keeps the conversation history in memory and injects it into each request.

curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "X-Session-ID: local-ollama-session-1" \
  -d '{
    "model": "ollama/llama3.2",
    "messages": [
      {
        "role": "user",
        "content": "Remember that I like concise answers."
      }
    ]
  }'

Health Check

curl http://127.0.0.1:8080/health

Example response:

{
  "status": "ok",
  "local": true,
  "host": "127.0.0.1",
  "port": "8080",
  "auth": false,
  "ollama_url": "http://localhost:11434",
  "usage_db": "./usage.db",
  "skip_perms": false
}

Models List

curl http://127.0.0.1:8080/v1/models

Returns all Claude Code models plus every model declared in the provider registry:

{
  "object": "list",
  "data": [
    {"id": "claude-code",          "owned_by": "anthropic"},
    {"id": "claude-sonnet-4-6",    "owned_by": "anthropic"},
    {"id": "ollama,qwen3-coder:latest", "owned_by": "ollama"},
    {"id": "deepseek,deepseek-chat",    "owned_by": "deepseek"}
  ]
}

Model IDs from the registry follow the provider,model format and can be used directly in /v1/chat/completions.

Usage Dashboard

Open in browser:

http://127.0.0.1:8080/dashboard

The dashboard shows usage grouped by model and provider.

It auto-refreshes every 30 seconds.

Usage JSON

Summary

curl http://127.0.0.1:8080/v1/usage

Example response:

[
  {
    "model": "claude-code",
    "provider": "claude",
    "total_requests": 42,
    "errors": 1,
    "prompt_tokens": 18400,
    "completion_tokens": 6200,
    "total_tokens": 24600,
    "avg_duration_ms": 3200
  }
]

Recent Records

curl http://127.0.0.1:8080/v1/usage/recent

With custom limit:

curl "http://127.0.0.1:8080/v1/usage/recent?limit=100"

Delegation Pattern

claude-bridge can be used by an orchestrator agent or any OpenAI-compatible client.

{
  "model": "claude-code",
  "messages": [
    {
      "role": "system",
      "content": "<briefing: user, project context, absolute paths>"
    },
    {
      "role": "user",
      "content": "<concrete task>"
    }
  ],
  "stream": false
}

For multi-turn tasks, add:

X-Session-ID: <your-session-id>

Safety Notes

claude-bridge is intended to run locally.

By default, when running locally, it binds to:

127.0.0.1:8080

When running in Docker, it binds to:

0.0.0.0:8080

Avoid exposing it publicly unless you add stronger authentication, rate limiting, request limits, and sandboxing.

Be careful with:

CLAUDE_SKIP_PERMS=true

This passes --dangerously-skip-permissions to Claude Code and should only be used in trusted local environments.

Development

Run:

go run ./cmd/claude-bridge

Run tests:

go test ./...

Build:

go build -o claude-bridge ./cmd/claude-bridge

Build Docker image:

docker build -t claude-bridge .

License

MIT

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages