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.
- 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,workdirsupport - Uses the local
claudeCLI
- Provider registry — configure any OpenAI-compatible provider
- Declare providers in
~/.cc_bridge/config.json - Route by
provider,modelorprovider/modelformat - Full
tools,tool_calls,tool_choicepassthrough - Streaming SSE passthrough with usage capture
- Backward compatible:
OLLAMA_URLauto-registers asollamaprovider
- Declare providers in
- SQLite usage tracking
- Powered by Gorm
- Logs model, provider, tokens, duration, and errors
- Built-in dashboard at
/dashboard - Usage JSON API —
/v1/usageand/v1/usage/recent - Optional
Authorization: Bearerauth key
- Go 1.25+
- Claude Code CLI installed and authenticated
- Optional: Ollama running locally
- Optional: Docker
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.mdgo mod tidygo build -o claude-bridge ./cmd/claude-bridgeOn Windows:
go build -o claude-bridge.exe ./cmd/claude-bridge./claude-bridgeOn Windows:
.\claude-bridge.exeWith 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-bridgeOn 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.exeThe 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.
docker build -t claude-bridge .docker run --rm -it \
--name claude-bridge \
-p 8080:8080 \
-v claude_bridge_data:/data \
claude-bridgeThen open:
http://127.0.0.1:8080/dashboardHealth check:
curl http://127.0.0.1:8080/healthInside 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-bridgeOn Docker Desktop for Windows or macOS, use:
OLLAMA_URL=http://host.docker.internal:11434Test 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-bridgedocker run --rm -it \
--name claude-bridge \
-e CLAUDE_BRIDGE_AUTH_KEY=your-local-secret \
-p 8080:8080 \
-v claude_bridge_data:/data \
claude-bridgeThen 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"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
claudebinary 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_BINandCLAUDE_WORKDIRvalues.
For most local development workflows, run claude-bridge directly on the host when using Claude Code.
| 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. |
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.dbFor Docker, use:
HOST=0.0.0.0
PORT=8080
OLLAMA_URL=http://host.docker.internal:11434
USAGE_DB_PATH=/data/usage.dbConfigure 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).
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. |
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.
Requests are resolved in this order:
- Registry match → routes to the configured external provider
- 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) |
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"
}'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
--allowedToolsMCP is only supported for Claude Code requests. Ollama requests ignore MCP fields.
| 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. |
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.jsonDo not commit real MCP credentials.
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>curl http://127.0.0.1:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "claude-code",
"messages": [
{
"role": "user",
"content": "Hello"
}
]
}'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"
}
]
}'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."
}
]
}'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"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 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."
}
]
}'curl http://127.0.0.1:8080/healthExample 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
}curl http://127.0.0.1:8080/v1/modelsReturns 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.
Open in browser:
http://127.0.0.1:8080/dashboardThe dashboard shows usage grouped by model and provider.
It auto-refreshes every 30 seconds.
curl http://127.0.0.1:8080/v1/usageExample response:
[
{
"model": "claude-code",
"provider": "claude",
"total_requests": 42,
"errors": 1,
"prompt_tokens": 18400,
"completion_tokens": 6200,
"total_tokens": 24600,
"avg_duration_ms": 3200
}
]curl http://127.0.0.1:8080/v1/usage/recentWith custom limit:
curl "http://127.0.0.1:8080/v1/usage/recent?limit=100"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>claude-bridge is intended to run locally.
By default, when running locally, it binds to:
127.0.0.1:8080When running in Docker, it binds to:
0.0.0.0:8080Avoid exposing it publicly unless you add stronger authentication, rate limiting, request limits, and sandboxing.
Be careful with:
CLAUDE_SKIP_PERMS=trueThis passes --dangerously-skip-permissions to Claude Code and should only be used in trusted local environments.
Run:
go run ./cmd/claude-bridgeRun tests:
go test ./...Build:
go build -o claude-bridge ./cmd/claude-bridgeBuild Docker image:
docker build -t claude-bridge .MIT