mcp-llm-bridge
Supports GitHub Copilot's API through token-based credentials, allowing code generation and model access.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-llm-bridgegenerate a short explanation of machine learning"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MCP LLM Bridge
Encrypted LLM gateway and MCP server for routing API keys, CLI subscriptions, and model selection through one OpenAI-compatible endpoint.
Read this in: English · Español
Demo Links
Live gateway: https://gateway.javierzader.com
GHAGGA integration target: https://github.com/JNZader/ghagga
OpenCode: https://github.com/anomalyco/opencode
Visuals coming soon.
Related MCP server: Proxima
Quick Portfolio Snapshot
One service for LLM routing, encrypted credential storage, MCP tooling, and OpenAI-compatible HTTP access.
11 provider adapters today: 5 direct API providers plus 6 CLI-backed providers.
Supports API keys and auth-file workflows, including
auth.jsonand.credentials.json.Includes task-aware bridge routing, model routing, project-scoped credentials with global fallback, semantic code search, context compression, and CRDT shared state.
Ships as a local dev tool, self-hosted HTTP gateway, MCP stdio server, and Docker deployment.
Why It Matters
Centralizes secrets instead of scattering provider tokens across every project and tool.
Lets you reuse CLI subscriptions such as OpenCode, Claude, Gemini, Codex, Qwen, and Copilot behind one interface.
Gives OpenAI-compatible tools a single stable endpoint while preserving provider/model resolution metadata.
Supports multi-project setups where project-specific credentials override
_globaldefaults cleanly.Exposes MCP tools beyond plain generation: vault operations, code search, shared state, usage inspection, and provider-group management.
Quick Start
pnpm install
pnpm run serveOpen http://localhost:3456.
Store a credential and generate text:
curl -X POST http://localhost:3456/v1/credentials \
-H 'Content-Type: application/json' \
-d '{"provider":"anthropic","apiKey":"sk-ant-..."}'
curl -X POST http://localhost:3456/v1/generate \
-H 'Content-Type: application/json' \
-d '{"prompt":"Explain quicksort in one paragraph"}'If you set LLM_GATEWAY_AUTH_TOKEN, add Authorization: Bearer <token> to every protected route.
Jump to Technical Docs
Full API reference: Technical README
Auth and credential model: Authentication, Credential Management
MCP integration: MCP Server
Docker and self-hosting: Docker Deployment
Technical README
Table of Contents
Quick Start
# Install dependencies
pnpm install
# Start the HTTP server + dashboard
pnpm run serve
# MCP stdio mode only
pnpm run startBasic HTTP flow:
# Store a global Anthropic key
curl -X POST http://localhost:3456/v1/credentials \
-H 'Content-Type: application/json' \
-d '{"provider":"anthropic","apiKey":"sk-ant-..."}'
# Generate text with automatic provider selection
curl -X POST http://localhost:3456/v1/generate \
-H 'Content-Type: application/json' \
-d '{"prompt":"Explain quicksort in one paragraph"}'If auth is enabled:
curl -X POST http://localhost:3456/v1/generate \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{"prompt":"Explain quicksort in one paragraph"}'Dashboard
The repo currently has two dashboard surfaces:
Local inline shell at
http://localhost:3456/— legacy local ops surface served directly by the bridge. This remains the source of truth for local credential/auth-file management and quick test generation.React admin app under
dashboard/(built intodocs/) — admin/observability surface for overview, providers, usage, groups, circuit breakers, settings, and related views.
They intentionally coexist for now and do not have full feature parity.
Hosted demo: https://gateway.javierzader.com
Local inline shell:
http://localhost:3456
First-Time Setup
Start the gateway with
pnpm run serve.Open the dashboard.
Enter the base URL for your gateway.
Enter the bearer token if
LLM_GATEWAY_AUTH_TOKENis configured.Test the connection and save.
Local Inline Shell Capabilities
Add, list, filter, and delete encrypted API keys.
Upload auth files for CLI-backed providers.
Inspect provider availability and available models.
Send test prompts and inspect returned provider/model metadata.
Work with project-scoped credentials without exposing raw secrets.
React Admin App Capabilities
Overview / provider status / usage / groups / circuit breakers / settings
Admin-facing operational visibility over bridge subsystems
Hosted separately from the inline shell via the
dashboard/app
Recommended auth-file mappings in the UI and API:
opencode->auth.jsonclaude->.credentials.jsoncodex->auth.jsongemini->settings.jsonandoauth_creds.jsonqwen->settings.jsonandoauth_creds.jsoncopilot-> use token credentials instead of auth files
API Reference
All protected endpoints require:
Authorization: Bearer <your-token>When LLM_GATEWAY_AUTH_TOKEN is not set, auth is disabled for local development. GET /health always stays public.
Core HTTP Endpoints
Endpoint | Method | Description |
| GET | Public health check for uptime monitors and platforms like Coolify |
| GET | Prometheus metrics export |
| POST | Native generation endpoint |
| POST | OpenAI-compatible chat completions |
| GET | OpenAI-compatible model list |
| GET | Provider availability and metadata |
| GET | Current latency measurements when latency routing is enabled |
| GET | Cost estimate for a model and token counts |
| GET | Model pricing table |
| GET | Raw usage records |
| GET | Aggregated usage summary |
| POST / GET | Store and list encrypted API keys |
| DELETE | Delete a stored credential |
| POST / GET | Store and list encrypted auth files |
| DELETE | Delete a stored auth file |
| GET / POST | List or create provider groups |
| PUT / DELETE | Update or delete a provider group |
POST /v1/generate
Native generation endpoint with provider/model selection and project-scoped credential resolution.
# Auto-select provider
curl -X POST http://localhost:3456/v1/generate \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{"prompt":"Explain quicksort in one paragraph"}'
# Explicit provider + model + project
curl -X POST http://localhost:3456/v1/generate \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'X-Project: my-app' \
-d '{
"prompt":"Write a haiku about Rust",
"provider":"groq",
"model":"llama-3.3-70b-versatile",
"maxTokens":256,
"system":"You are a poet.",
"project":"my-app"
}'Request body:
Field | Type | Required | Description |
| string | Yes | User prompt |
| string | No | System prompt |
| string | No | Preferred provider ID |
| string | No | Specific model ID |
| number | No | Max output tokens |
| string | No | Credential scope |
| boolean | No | Strict routing behavior when supported |
Response:
{
"text": "Quicksort is a divide-and-conquer...",
"provider": "anthropic",
"model": "claude-sonnet-4-20250514",
"tokensUsed": 150,
"requestedProvider": null,
"requestedModel": null,
"resolvedProvider": "anthropic",
"resolvedModel": "claude-sonnet-4-20250514",
"fallbackUsed": false
}POST /v1/chat/completions
OpenAI-compatible chat endpoint. This is the drop-in path for tools that already speak OpenAI format.
Non-streaming and streaming requests are supported.
System messages are collapsed into the system prompt.
Conversation context is reconstructed from earlier messages.
Response stays OpenAI-compatible and adds
x_gatewaymetadata.
curl -X POST http://localhost:3456/v1/chat/completions \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"model":"claude-sonnet-4-20250514",
"messages":[
{"role":"system","content":"You are a helpful assistant."},
{"role":"user","content":"What is the capital of France?"}
],
"max_tokens":1024
}'Response:
{
"id": "chatcmpl-<uuid>",
"object": "chat.completion",
"created": 1710000000,
"model": "claude-sonnet-4-20250514",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "The capital of France is Paris." },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 150 },
"x_gateway": {
"requestedProvider": null,
"requestedModel": "claude-sonnet-4-20250514",
"resolvedProvider": "anthropic",
"resolvedModel": "claude-sonnet-4-20250514",
"fallbackUsed": false,
"tokensUsed": 150
}
}GET /v1/models
Lists available models in OpenAI-compatible format.
curl http://localhost:3456/v1/models \
-H 'Authorization: Bearer YOUR_TOKEN'{
"object": "list",
"data": [
{
"id": "claude-sonnet-4-20250514",
"object": "model",
"created": 0,
"owned_by": "llm-gateway",
"name": "Claude Sonnet 4",
"provider": "anthropic",
"max_tokens": 8192
}
]
}GET /v1/providers
Lists registered providers and their availability.
curl http://localhost:3456/v1/providers \
-H 'Authorization: Bearer YOUR_TOKEN'{
"providers": [
{ "id": "anthropic", "name": "Anthropic", "type": "api", "available": true },
{ "id": "openai", "name": "OpenAI", "type": "api", "available": false },
{ "id": "opencode-cli", "name": "OpenCode CLI", "type": "cli", "available": true }
]
}Credentials API
Store API keys encrypted at rest. Upsert key is (provider, keyName, project).
# Global credential
curl -X POST http://localhost:3456/v1/credentials \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"provider":"anthropic",
"keyName":"default",
"apiKey":"sk-ant-api03-..."
}'
# Project-scoped credential
curl -X POST http://localhost:3456/v1/credentials \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"provider":"openai",
"keyName":"default",
"apiKey":"sk-proj-...",
"project":"my-app"
}'{ "id": 1, "provider": "anthropic", "keyName": "default", "project": "_global" }List credentials:
curl http://localhost:3456/v1/credentials \
-H 'Authorization: Bearer YOUR_TOKEN'
curl 'http://localhost:3456/v1/credentials?project=my-app' \
-H 'Authorization: Bearer YOUR_TOKEN'{
"credentials": [
{
"id": 1,
"provider": "anthropic",
"keyName": "default",
"project": "_global",
"maskedValue": "sk-ant-...***",
"createdAt": "2025-01-15 10:30:00",
"updatedAt": "2025-01-15 10:30:00"
}
]
}Delete a credential:
curl -X DELETE http://localhost:3456/v1/credentials/1 \
-H 'Authorization: Bearer YOUR_TOKEN'Auth Files API
Store auth files for CLI-backed providers encrypted at rest. Upsert key is (provider, fileName, project).
This is the path that preserves the older auth.json and .credentials.json workflows.
# OpenCode auth.json
curl -X POST http://localhost:3456/v1/files \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"provider":"opencode",
"fileName":"auth.json",
"content":"{\"token\":\"oc-...\"}",
"project":"_global"
}'
# Claude CLI .credentials.json
curl -X POST http://localhost:3456/v1/files \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"provider":"claude",
"fileName":".credentials.json",
"content":"{\"claudeAiOauth\":{...}}",
"project":"my-app"
}'{ "id": 1, "provider": "opencode", "fileName": "auth.json", "project": "_global" }List auth files:
curl http://localhost:3456/v1/files \
-H 'Authorization: Bearer YOUR_TOKEN'
curl 'http://localhost:3456/v1/files?project=my-app' \
-H 'Authorization: Bearer YOUR_TOKEN'{
"files": [
{
"id": 1,
"provider": "opencode",
"fileName": "auth.json",
"project": "_global",
"createdAt": "2025-01-15"
}
]
}Delete an auth file:
curl -X DELETE http://localhost:3456/v1/files/1 \
-H 'Authorization: Bearer YOUR_TOKEN'Usage, Cost, Metrics, and Health
Usage records:
curl 'http://localhost:3456/v1/usage?project=my-app&limit=50' \
-H 'Authorization: Bearer YOUR_TOKEN'Usage summary:
curl 'http://localhost:3456/v1/usage/summary?groupBy=provider&project=my-app' \
-H 'Authorization: Bearer YOUR_TOKEN'Cost estimate:
curl 'http://localhost:3456/v1/cost/estimate?model=claude-sonnet-4-20250514&inputTokens=1000&outputTokens=500' \
-H 'Authorization: Bearer YOUR_TOKEN'Prometheus metrics:
curl http://localhost:3456/metrics \
-H 'Authorization: Bearer YOUR_TOKEN'Health check:
curl http://localhost:3456/healthGET /health returns the runtime VERSION constant (src/core/constants.ts) plus uptime, auth mode, and provider counts:
{
"status": "ok",
"version": "0.3.1",
"timestamp": "2025-01-15T10:30:00.000Z",
"uptime": 3600,
"auth": { "enabled": true, "mode": "bearer" },
"providers": { "total": 11, "available": 3 }
}Note: the VERSION constant and the version field in package.json are not kept in lockstep — /health reports the former.
Provider Groups
Provider groups let you define logical pools for balancing and failover.
curl -X POST http://localhost:3456/v1/groups \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"name":"fast-models",
"modelPattern":"gpt-*,claude-*",
"members":[
{"provider":"groq","weight":2,"priority":1},
{"provider":"anthropic","weight":1,"priority":2}
],
"strategy":"weighted",
"stickyTTL":300
}'Providers
API Providers
Provider | ID | Auth | Example Models |
Anthropic |
| API key |
|
OpenAI |
| API key |
|
| API key |
| |
Groq |
| API key |
|
OpenRouter |
| API key |
|
CLI Providers
Provider | ID | Auth Material | Notes |
OpenCode CLI |
|
| Large model catalog via subscription routing |
Claude CLI |
|
| Uses Claude Max credentials |
Gemini CLI |
| CLI auth files | Local CLI-backed execution |
Codex CLI |
|
| OpenAI CLI-backed execution |
Qwen CLI |
| CLI auth files | Qwen local/subscription access |
Copilot CLI |
| token credentials | GitHub Copilot-backed routing |
OpenCode Model Coverage
OpenCode is the biggest catalog here and is one reason this bridge is useful.
Free tier models under
opencode/*OpenCode Go subscription models under
opencode-go/*Anthropic models under
anthropic/*GitHub Copilot-routed models under
github-copilot/*OpenAI-routed models under
openai/*
Representative examples from the current adapter list:
opencode/gpt-5-nanoanthropic/claude-sonnet-4.5github-copilot/gpt-5.4openai/gpt-5.4
Provider Priority and Fallback
Default behavior without an explicit provider/model:
API providers are tried first.
CLI providers follow as fallback.
If a model is explicitly requested, the owning provider is preferred.
If bridge routing is enabled, the bridge can override the initial provider choice and then walk the configured fallback chain.
Authentication
Bearer Token
Set LLM_GATEWAY_AUTH_TOKEN to protect HTTP routes.
# Generate a secure token
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
export LLM_GATEWAY_AUTH_TOKEN="your-64-char-hex-token"The token must be at least 32 characters.
Auth Rules
Path | Bearer Auth Required |
| No |
| No |
| No |
| No |
All other HTTP routes, including dashboard and | Yes |
Important behavior:
The bearer-auth middleware skips the entire
/v1/admin/*prefix, not just/v1/admin/auth-config. Admin routes gate themselves with their own dashboard/GitHub-OAuth JWT checks (verifyDashboardJwt) rather than the static bearer token. Keep this in mind when exposing the gateway publicly.The dashboard (non-admin routes) is protected when bearer auth is enabled.
MCP stdio does not use HTTP bearer auth because it runs as a local process.
Token comparison is constant-time via
timingSafeEqual.
Project Scoping
Project scope can be supplied in either place:
JSON body field:
"project": "my-app"Header:
X-Project: my-app
Body field wins over header.
Credential Management
Global vs Project Credentials
Credential resolution follows the same pattern for API keys and auth files:
Try the project-specific entry.
Fall back to
_global.
That lets you keep a shared default while still isolating overrides per app or customer.
API Keys
API keys are encrypted with AES-256-GCM and stored in SQLite.
# Global key
curl -X POST http://localhost:3456/v1/credentials \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{"provider":"anthropic","apiKey":"sk-ant-..."}'
# Project key
curl -X POST http://localhost:3456/v1/credentials \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{"provider":"anthropic","apiKey":"sk-ant-project-...","project":"my-app"}'Auth Files
CLI adapters use file-based auth where necessary. These files are also encrypted and stored in the vault.
# OpenCode auth.json
curl -X POST http://localhost:3456/v1/files \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"provider":"opencode",
"fileName":"auth.json",
"content":"{\"token\":\"oc-...\"}"
}'
# Claude CLI .credentials.json
curl -X POST http://localhost:3456/v1/files \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"provider":"claude",
"fileName":".credentials.json",
"content":"{\"claudeAiOauth\":{...}}"
}'Claude and OpenCode Credential Sync Pattern
The vault layer also contains a Claude OAuth integration that:
Reads
~/.claude/.credentials.jsonRefreshes the token when needed
Syncs the token into OpenCode-style
auth.json
That matters because this bridge can unify Claude CLI and OpenCode auth flows instead of treating them as separate credential silos.
Cross-Model Bridge
The bridge is an optional routing layer driven by ~/.llm-gateway/bridge.yaml.
Flow:
Classify the prompt into a task type.
Resolve a preferred provider from
routes.Try that provider first.
Walk
fallback_ordersequentially if it fails.
Supported Task Types
Task Type | Heuristic | Typical Route |
| Very large prompt/context |
|
| Review/audit/refactor keywords |
|
| Short prompt |
|
| No heuristic matched | configured default |
Example bridge.yaml
routes:
large-context: gemini-cli
code-review: claude-cli
fast-completion: groq
default: claude-cli
fallback_order:
- claude-cli
- gemini-cli
- opencode-cli
- anthropic
- groqIf the file is missing, the bridge is disabled and the normal router behavior is used.
Bridge Response Metadata
Field | Description |
| Generated text |
| Provider that answered |
| Model used |
| Classified task type |
| Whether a non-primary provider handled it |
| End-to-end latency |
Context Compression
The CompressorService adds background context compression with caching.
Strategies
Strategy | How It Works | Good For |
| Keeps high-scoring sentences | general text |
| Preserves headings and list structure | markdown/docs |
| Cuts to a size budget at sentence boundaries | hard token limits |
Usage
import { CompressorService } from './context-compression/index.js';
const compressor = new CompressorService({
maxCacheSize: 200,
workerIntervalMs: 5000,
defaultStrategy: 'extractive',
defaultRatio: 0.5,
});
compressor.submit(longContext);
const compressed = compressor.getCompressed(longContext);
const immediate = compressor.compressNow(longContext, 'structural');
compressor.destroy();Operational Characteristics
LRU cache for repeated content
Background worker for non-blocking pre-computation
Synchronous compression when you need the result immediately
Useful for prompt pipelines where raw context would otherwise blow up token budgets
Semantic Code Search
The code-search subsystem exposes three search modes through MCP:
keyword (default): exact/prefix/fuzzy matching with inverted index
vector: semantic similarity via dense embeddings
hybrid: RRF fusion of keyword + BM25 + vector for best results
It combines:
regex-based chunking
trigram fuzzy search
BM25 keyword scoring (via MiniSearch)
dense vector similarity (via transformer embeddings)
Reciprocal Rank Fusion (RRF) for hybrid ranking
optional multi-hop import following
Supported Languages
DEFAULT_EXTENSIONS (indexed by default) covers:
.ts, .tsx, .js, .jsx, .mjs, .cjs, .py, .go, .rs, .java, .rb, .lua
Dedicated chunk patterns exist for TypeScript/JavaScript, Python, Go, and Rust. Other indexed extensions (.java, .rb, .lua) fall back to the TypeScript/C-family chunk patterns.
MCP Search Tools
index_codebase:
{
"rootDir": "/path/to/project",
"extensions": [".ts", ".js"],
"ignorePatterns": ["node_modules", "dist"]
}code_search:
{
"query": "authentication middleware",
"scope": "/path/to/project",
"limit": 10,
"followImports": true,
"mode": "hybrid"
}Returned results include file path, symbol name, kind, content, line numbers, score, and related chunks when import following is enabled.
Search Modes
Mode | Description | Best For |
| Exact token matching, prefix search, trigram fuzzy fallback | Known symbol names, fast, no model needed |
| Cosine similarity over 384-dim embeddings | Conceptual queries, synonyms, semantic relatedness |
| RRF fusion of keyword + BM25 + vector | General use — combines precision + recall |
Keyword mode is the default and requires no setup. It scores exact name matches highest, then prefix matches, then keyword-in-content, then trigram fuzzy similarity.
Vector mode uses a local embedding model (Xenova/all-MiniLM-L6-v2, a small 384-dimensional model). On first run the model downloads automatically from HuggingFace and caches locally. Vector search finds semantically related code even when keywords don't overlap.
Hybrid mode runs all three strategies in parallel and fuses the rankings with Reciprocal Rank Fusion (RRF). Results include rrfScore (the fused score) and methodCount (how many strategies found the result). Items found by multiple methods rank higher, giving the best overall coverage.
Embedding Model
Model:
Xenova/all-MiniLM-L6-v2(small, 384-dim)Backend:
@xenova/transformers(ONNX runtime, runs locally)First run: model auto-downloads and caches to
~/.cache/huggingface/Fallback: if the local model fails to load, the embedder can fall back to OpenAI API (
text-embedding-3-small) whenOPENAI_API_KEYis set
Environment Variables
Variable | Default | Description |
|
|
|
| — | Fallback API embedder key (optional) |
| — | Alternative API embedder key (optional) |
| — | Set to |
CRDT Multi-Agent State
The shared_state MCP tool gives agents a conflict-free shared state layer.
Supported CRDTs
Type | Merge Semantics | Good For |
| max-per-node counter merge | token/request tracking |
| last-writer-wins by timestamp | status/assignment |
| observed-remove set | shared findings or artifacts |
Example Operations
{ "op": "write", "key": "tokens", "type": "g-counter", "nodeId": "agent-1", "amount": 150 }
{ "op": "write", "key": "status", "type": "lww-register", "nodeId": "agent-1", "value": "analyzing" }
{ "op": "write", "key": "findings", "type": "or-set", "nodeId": "agent-1", "action": "add", "element": "Issue in auth.ts:42" }
{ "op": "read", "key": "findings" }
{ "op": "snapshot" }
{ "op": "merge", "snapshot": { "entries": {} } }This is useful when multiple coding or review agents need to coordinate without central locking.
Integrations
OpenCode
Configure OpenCode to treat the gateway as an OpenAI-compatible provider.
{
"provider": {
"llm-gateway": {
"name": "LLM Gateway",
"api": "openai",
"apiKey": "env:LLM_GATEWAY_TOKEN",
"baseURL": "https://llm-gateway.yourdomain.com/v1",
"models": {
"gateway-anthropic": {
"name": "Anthropic via Gateway",
"id": "claude-sonnet-4-20250514",
"contextWindow": 200000,
"maxOutput": 8192
},
"gateway-groq": {
"name": "Groq via Gateway",
"id": "llama-3.3-70b-versatile",
"contextWindow": 128000,
"maxOutput": 4096
}
}
}
}
}export LLM_GATEWAY_TOKEN="your-gateway-auth-token"
opencodeGHAGGA
GHAGGA can use the bridge as a provider.
Select
LLM Gatewayin the GHAGGA dashboard.Enter the gateway base URL.
Enter the gateway bearer token.
Pick a model.
Typical review modes routed through the gateway:
simple
workflow
consensus
Any OpenAI-Compatible Tool
General settings:
Setting | Value |
Base URL |
|
API Key | your |
Works with LangChain, LlamaIndex, Cursor, Continue, and any HTTP client that can call /v1/chat/completions.
LangChain Python example:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
base_url="https://llm-gateway.yourdomain.com/v1",
api_key="your-gateway-token",
model="claude-sonnet-4-20250514",
)
response = llm.invoke("Explain quicksort")
print(response.content)LangChain TypeScript example:
import { ChatOpenAI } from '@langchain/openai';
const llm = new ChatOpenAI({
configuration: {
baseURL: 'https://llm-gateway.yourdomain.com/v1',
},
apiKey: 'your-gateway-token',
model: 'claude-sonnet-4-20250514',
});
const response = await llm.invoke('Explain quicksort');Docker Deployment
Docker Compose
services:
llm-gateway:
build: .
ports:
- "3456:3456"
volumes:
- llm-data:/root/.llm-gateway
environment:
- LLM_GATEWAY_PORT=3456
- LLM_GATEWAY_AUTH_TOKEN=your-secure-token-here
- LLM_GATEWAY_MASTER_KEY=your-64-char-hex-key
volumes:
llm-data:docker compose up -dDocker Build and Run
docker build -t llm-gateway .
docker run -d \
-p 3456:3456 \
-v llm-data:/root/.llm-gateway \
-e LLM_GATEWAY_AUTH_TOKEN="your-token" \
-e LLM_GATEWAY_MASTER_KEY="your-64-char-hex-key" \
llm-gatewayWhat the Image Includes
The Dockerfile currently installs:
pnpm9OpenCode CLI
Claude Code CLI
Gemini CLI
Codex CLI
Qwen CLI
GitHub Copilot CLI
Coolify
Create a new service pointing at this repository.
Use the Dockerfile build pack.
Set environment variables such as
LLM_GATEWAY_PORT,LLM_GATEWAY_AUTH_TOKEN, and optionallyLLM_GATEWAY_MASTER_KEY.Mount a persistent volume at
/root/.llm-gateway.Use
/healthfor health checks.
MCP Server
The project runs as an MCP stdio server by default.
Primary MCP Tools
Tool | Description |
| Generate text with provider routing and fallback |
| List available models |
| API key management |
| Auth-file management |
| Semantic code search |
| CRDT shared state |
| Provider group management |
| Cost and usage inspection |
| Provider failure-control tuning |
| Trigger HuggingFace-enriched model discovery |
| Approval-flow management (see Approval Flows) |
PageIndex Conversation Tools
Seven additional static MCP tools (defined in src/pageindex/tools.ts) handle long-conversation pagination and reasoning-based navigation over stored conversation history:
Tool | Description |
| Paginate a stored conversation |
| Fetch a specific page |
| Retrieve context around a point in the conversation |
| Navigate between pages/sections |
| Summary/metadata for a conversation |
| Find the most relevant pages for a query |
| Check whether the conversation should be compacted |
These are categorized as read tools, so they are available under both local-dev and restricted security profiles.
Claude Code Config
Add to ~/.config/claude/mcp.json:
{
"mcpServers": {
"llm-bridge": {
"command": "mcp-llm-bridge"
}
}
}For a local source checkout:
{
"mcpServers": {
"llm-bridge": {
"command": "npx",
"args": ["tsx", "/path/to/mcp-llm-bridge/src/index.ts"]
}
}
}Claude Desktop Config
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"llm-bridge": {
"command": "mcp-llm-bridge"
}
}
}MCP stdio runs locally and does not use the HTTP bearer-token middleware.
Dynamic MCP Servers
The bridge supports loading external .mcp-server.js plugin files at runtime. This lets you extend the toolset without modifying the core codebase.
What It Is
Any .mcp-server.js file placed in the plugin directory is loaded at startup and its tools are registered alongside the static tools (30 at time of writing: 23 core tools plus 7 PageIndex conversation tools). Plugins export a McpServerDefinition object (or use the builder) with tools, resources, and prompts.
Enable
Set MCP_DYNAMIC_SERVERS=true:
export MCP_DYNAMIC_SERVERS=true
export MCP_SERVERS_DIR=./mcp-serversCreate a Plugin
Create a .mcp-server.js file in the plugin directory:
import { McpServerBuilder } from 'mcp-llm-bridge/mcp-builder';
export default new McpServerBuilder()
.tool('greet', 'Say hello to someone', { name: { type: 'string' } }, async ({ name }) => {
return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
})
.build();The builder validates naming conventions, schema completeness, and description quality. Tools are registered on the MCP server and appear in ListTools.
Directory
The default plugin directory is ./mcp-servers. Override with:
export MCP_SERVERS_DIR=./my-custom-pluginsSecurity
Dynamic tools are registered with the read category by default. This means they are:
Allowed under
local-devandrestrictedprofilesBlocked under the
openprofile (which only allowsgeneratetools)
The enforcer applies the same category-based filtering to dynamic tools as it does to static tools.
Coexistence with Static Tools
Static tools (vault, search, generate, etc.) and dynamic tools appear together in the ListTools response. There is no namespacing — tool names must be unique across both sets. The approval flow and rate limiting apply uniformly to all tools.
Configuration
Core Environment Variables
Variable | Default | Description |
|
| HTTP server port |
|
| SQLite vault path |
| auto-generated | 64-char hex key, otherwise saved to |
| unset | Bearer token for HTTP routes |
| unset | Force auth on or off explicitly |
|
| Security profile for MCP tool exposure |
Optional Runtime Features
Variable | Effect |
| enables free-model fallback routing |
| loads the free-model catalog at startup |
| enables latency-based routing |
| caps comparison-service spending |
Master Key Priority
LLM_GATEWAY_MASTER_KEYexisting
~/.llm-gateway/master.keyauto-generated new key written with mode
0600
If you lose the master key, stored credentials are unrecoverable. Back it up in production.
Bridge Config Path
~/.llm-gateway/bridge.yaml
If that file does not exist, bridge routing is disabled.
Security Profiles
Security profiles enforce trust-level-based access control on both MCP tools and HTTP endpoints. Three profiles are built-in:
Profile | Allowed Categories | Rate Limit | Sandbox |
| all (destructive, read, generate, admin) | none | false |
| read + generate only | 100 req / 15 min | false |
| generate only | 10 req / 15 min | false |
Configuration
Set via environment variable:
LLM_GATEWAY_SECURITY_PROFILE=restrictedDefault is local-dev (backward compatible — no restrictions).
Each profile also carries a sandbox flag (default false). Today this flag is best understood as prepared infrastructure, not a guarantee of sandboxed runtime execution: it is exposed on the profile schema and through the admin API, and the repo includes a Docker/process sandbox runner under src/sandbox/, but the active runtime does not yet expose sandboxed execution tools or route normal tool execution through that runner. Also note that the helper falls back to plain process execution with a timeout when Docker is unavailable, so this should not be treated as complete containment.
HTTP Enforcement
Under restricted or open, the gateway blocks destructive HTTP endpoints (e.g., POST /v1/credentials) and returns:
{ "error": "Access denied: endpoint blocked by security profile", "code": "SECURITY_PROFILE_DENIED" }Read endpoints (GET /v1/providers, GET /v1/models) remain open under restricted.
MCP Enforcement
Under non-local-dev profiles, ListTools returns only tools in the allowed categories. CallTool is authorized before execution. Rate limiting is applied per profile.
Approval Flows
Destructive MCP tools can be paused for explicit human approval when the security profile is not local-dev.
How It Works
Client calls a destructive tool (e.g.,
vault_store).If approval is required, the gateway returns an
approvalRequiredpayload with arequestId.Admin reviews pending requests via
GET /v1/approvalsorapproval_listMCP tool.Admin approves or denies via
POST /v1/approvals/:id/approveorapproval_approveMCP tool.Original tool executes only after approval.
Auto-Approve List
Read-only tools (file_read, search, list, vault_list) bypass approval automatically.
HTTP Endpoints
Endpoint | Method | Description |
| GET | List pending approval requests |
| POST | Approve a request |
| POST | Deny a request |
MCP Tools
Tool | Description |
| List pending requests |
| Approve by request ID |
| Deny by request ID |
Three-Part Prompt
The three-part prompt pattern separates prompts into system (role/constraints), context (background data), and instruction (the actual task). Research shows measurable quality improvement, especially with smaller models.
HTTP API
Both /v1/generate and /v1/chat/completions accept the three fields:
curl -X POST http://localhost:3456/v1/generate \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"system": "You are a code reviewer.",
"context": "We use Zod 4 and Hono.",
"instruction": "Review this schema for edge cases."
}'Legacy flat prompt is still accepted and auto-detected when system/context/instruction are absent.
MCP Schema
The llm_generate tool exposes system, context, and instruction as optional fields alongside the legacy prompt:
{
"system": "You are a helpful assistant.",
"context": "The project uses TypeScript.",
"instruction": "Explain strict mode benefits."
}Enable/Disable
OPTIMIZE_MESSAGES_ENABLED=true # default: trueRTK Output Compression
RTK-style compression strips redundant content from tool call results before passing them to LLMs. This saves token budget on large structured outputs.
Strategies
Filter — remove noise fields (
created_at,id,etag, etc.)Group — merge repeated similar entries into count + sample
Truncate — enforce max length on string values
Deduplicate — remove exact-duplicate array entries
Configuration
ENABLE_OUTPUT_COMPRESSION=true # default: trueAnalytics Endpoint
curl http://localhost:3456/v1/compression/stats \
-H 'Authorization: Bearer YOUR_TOKEN'Response:
{ "totalCalls": 42, "compressedCalls": 42, "avgRatio": 0.65, "totalSavingsChars": 15200 }Local LLM Offloading
Offloadable tasks (summarization, formatting, classification) can be routed to local runtimes (Ollama, LM Studio) instead of cloud providers, cutting API token cost on those deterministic tasks. (The src/local-llm/ module documents an 86–95% design target for token savings on boilerplate tasks; this is a design goal, not a measured benchmark.)
Environment Variables
Variable | Default | Description |
|
| Enable local LLM routing |
|
| Ollama API endpoint |
|
| LM Studio API endpoint |
Detection
At startup, the gateway probes both backends. Models are listed at:
curl http://localhost:3456/v1/local/models \
-H 'Authorization: Bearer YOUR_TOKEN'Fallback
If the local LLM fails or the task is not offloadable, the gateway falls back to cloud providers automatically and emits a metric.
MCP Tool
Tool | Description |
| Generate via local LLM with offload detection |
Model Routing
Model routing adds task-aware provider selection that classifies each prompt and routes it based on configured rules, preferred endpoint order, cost tiers, and observed quality feedback.
What It Does
Classifies incoming prompts into runtime task types such as
code-review,large-context,fast-completion,summarization, andtranslationMatches the task against routing rules defined in
model-routing.jsonTries preferred endpoints in rule order while enforcing the configured cost cap
Falls back to more expensive endpoints if quality drops below threshold
Learns from feedback — records success/failure per endpoint+task for adaptive routing
Enable
MODEL_ROUTING_ENABLED=trueWhen enabled, the precedence stack becomes:
Session stickiness
Group-based routing
ModelRouter (task-aware selection)
Local-LLM offloading (only if ModelRouter is disabled or returns no match)
Standard resolution (model match → provider preference → API before CLI)
Latency reordering
Configuration
Create model-routing.json in the project root. The gateway loads it at startup.
{
"enabled": true,
"endpoints": [...],
"rules": [...],
"defaultEndpoint": "opencode-cli-default",
"qualityThreshold": 0.7,
"qualityWindowSize": 50
}Field | Type | Description |
| boolean | Whether model routing is active |
| array | Available model endpoints with cost tier and capabilities |
| array | Task-to-endpoint routing rules (first match wins) |
| string | Fallback endpoint ID when no rule matches |
| number | Minimum acceptable quality rate (0–1) |
| number | Number of recent requests to track per endpoint+task |
Endpoint fields:
Field | Type | Description |
| string | Unique endpoint identifier |
| string | Provider ID (e.g., |
| string | Model ID for API calls |
| string |
|
| array | Capability tags (e.g., |
| number | Maximum context window in tokens |
Rule fields:
Field | Type | Description |
| string | Unique rule identifier |
| string | One of |
| array | Ordered list of endpoint IDs to try |
| string | Most expensive tier allowed for this task |
| boolean | Whether to fall back to default endpoint if all preferred fail |
Example Task-to-Endpoint Mappings
Task Type | Preferred Endpoints | Cost Cap |
| Claude Sonnet → GPT-4.1 |
|
| Claude Sonnet → GPT-4.1 |
|
| GPT-4.1-mini → OpenCode CLI |
|
| GPT-4.1 → Claude Sonnet |
|
| OpenCode CLI → GPT-4.1-mini |
|
Coexistence with Local-LLM Offloading
Local-LLM offloading and model routing work together with clear precedence:
ModelRouter runs first. If it selects an endpoint, that provider is promoted to the top of the candidate list.
Local-LLM offloading runs only when ModelRouter is disabled or returns no match. This prevents conflicts: explicit routing rules always beat heuristic offloading.
If you want local models in your routing mix, register them as endpoints with "costTier": "free" and include them in rule preferredEndpoints.
Example File
See model-routing.example.json in the repository root for a full template with multiple endpoints and routing rules.
HF Auto-Discovery
At startup (when enabled), the gateway scans local backends and enriches detected models with HuggingFace metadata (tags, pipeline type, recommended tasks).
Configuration
AUTO_DISCOVER_MODELS=true # default: false
HF_TOKEN=hf_xxxxxxxxxx # optional, for private reposAdmin Endpoint
Trigger discovery on demand:
curl -X POST http://localhost:3456/v1/admin/discover \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{ "hfToken": "optional-override" }'Response:
{
"ok": true,
"backendsScanned": ["ollama", "lm-studio"],
"models": [...],
"enrichedCount": 3,
"unenrichedCount": 1
}Cache
Enriched metadata is persisted to SQLite (hf_model_cache table) so subsequent startups are fast even without HF API access.
Architecture
Clients (GHAGGA, OpenCode, curl, LangChain, any OpenAI-compatible tool)
|
| POST /v1/chat/completions | POST /v1/generate | MCP stdio
v
+-------------------------------------------------------------------+
| MCP LLM Bridge (Hono + MCP) |
| |
| HTTP Server MCP Server |
| - /v1/chat/completions - llm_generate |
| - /v1/generate - vault_* |
| - /v1/models - code_search |
| - /v1/providers - index_codebase |
| - /v1/credentials CRUD - shared_state |
| - /v1/files CRUD - usage_* |
| - /v1/groups CRUD - circuit_breaker_* |
| - /metrics /health - group tools |
| - /v1/compression/stats - approval_* |
| - /v1/local/models - local_llm_generate |
| - /v1/admin/discover - discover_models |
+-------------------------------------------------------------------+
| Bridge routing | Context compression | Code search |
| Provider groups | Cost tracking | CRDT state |
| Security profiles | Approval flows | Local LLM |
| HF discovery | Three-part prompt | Output compression |
+-------------------------+---------------------+--------------------+
| Router (model -> provider) | Vault (AES-256-GCM + SQLite) |
+-------------------------+---------------------+--------------------+
| |
v v
API providers CLI providers
Anthropic, OpenAI, Google, Groq, OpenRouter OpenCode, Claude,
Gemini, Codex, Qwen, CopilotDesign Notes
Hono keeps the HTTP layer small and fast.
better-sqlite3keeps the vault single-file and operationally simple.SQLite WAL mode improves concurrent read behavior.
API providers are preferred before CLI providers unless bridge logic says otherwise.
Vault writes use upsert semantics for repeatable automation.
CLI adapters materialize auth files into temp homes and clean them up in
finallyblocks.Bridge routing is intentionally optional and file-driven.
Code search stays in-memory for speed and freshness.
CRDTs reduce coordination pain for parallel agent workflows.
Experimental Modules
src/acp/— Agent Client Protocol implementation (server.ts,translator.ts,types.ts).
Present in the repo but not wired into the active runtime. There is no import path fromsrc/index.ts, no active HTTP/stdio ACP transport, and no live MCP tool-execution bridge yet. Treat it as a tested protocol prototype that still needs a dedicated ACP integration sprint.src/sandbox/— Docker/process sandbox runner.
Thesandboxflag now exists in security profiles, but the runtime still does not expose sandboxed execution tools likeexecute_codeorshell_command. In other words: the infrastructure is prepared, but the feature is not complete.
Session Systems
The gateway now uses SessionManager for both session-affinity concerns:
Router sticky sessions (
SessionManager.pinRouterStickySession) — Pins a specificclientId + modelto a provider/key with TTL-based expiry.Group/API sessions (
src/session/session-manager.ts) — Manages session affinity for multi-turn conversations and dashboard metrics.
They are separate by design inside the same manager instance: router stickiness handles provider selection, while group/API sessions handle conversation continuity.
Do not conflate the two entry kinds.
GET /v1/admin/sessions reports them separately for that reason:
routerStickySessionscomes fromSessionManagerrouter-sticky entries and reflects the pins the Router actually uses at request time.groupSessionscomes fromSessionManagerand reflects group-level session affinity metrics.The endpoint includes a
noteexplaining the split so the dashboard does not imply a single shared session pool.
Security
AES-256-GCM encryption for stored keys and auth files
constant-time bearer-token comparison
master key file stored with mode
0600config directory created with mode
0700credentials are never returned raw from listing endpoints
temp auth files are cleaned up after CLI invocations
minimum 32-character auth token requirement
public
/healthendpoint for safe monitoring
Development
pnpm run dev
pnpm run serve
pnpm run start
pnpm test
pnpm run typecheck
pnpm run buildScripts
Script | Command | Description |
|
| MCP stdio mode |
|
| local development |
|
| HTTP server and dashboard |
|
| test suite |
|
| production build |
|
| TypeScript checking |
Requirements
Node.js 22+
pnpm 9+
License
MIT, as declared in package.json ("license": "MIT"). Note: a standalone LICENSE file is not yet committed to the repository.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/JNZader/mcp-llm-bridge'
If you have feedback or need assistance with the MCP directory API, please join our Discord server