loading…
Search for a command to run...
loading…
Standalone MCP server that multiplexes multiple MCP servers into one interface
Standalone MCP server that multiplexes multiple MCP servers into one interface
CI npm version License: MIT Status: maintenance
Your AI, Connected to Everything. Multiplex multiple MCP servers into one interface. One config, one connection, all your tools.
🌐 aiwerkmcp.com — Learn more about the AIWerk MCP Platform
Works with Claude Code, Codex (OpenAI), Claude Desktop, Cursor, Windsurf, Cline, OpenClaw, or any MCP client.
@aiwerk/mcp-bridge is in maintenance mode as of 2026-04-10.
Primary development has moved to the hosted AIWerk MCP Platform at aiwerkmcp.com — a multi-tenant hosted MCP bridge with catalog, authentication, per-user isolation and OAuth2 built in. For most use cases, the hosted platform is a simpler and more featureful choice than running the standalone router yourself.
This standalone package remains supported for:
McpRouter, transports and the OAuth2
token manager directly.The published package is frozen on version 2.8.x. Security fixes and
compatibility updates (new Node.js, new MCP protocol revisions) may still
ship, but no new features are planned. The bundled servers/ directory
is retained as a set of reference recipe examples you can copy into
your own ~/.mcp-bridge/config.json — it is not a curated catalog and
does not auto-update. As of this release the only remaining network
coupling with AIWerk infrastructure has been removed: the install helper
no longer fetches recipes over HTTP and only reads from the bundled
servers/ directory.
Most AI agents connect to MCP servers one-by-one. With 10+ servers, that's 10+ connections, 200+ tools in context, and thousands of wasted tokens.
MCP Bridge solves this:
mcp meta-tool (~99% token reduction)action=batchservers/ — copy one as a starting point for your own config, or bring your own MCP server entirely${ENV_VAR} resolution from .envnpm install -g @aiwerk/mcp-bridge
# 1. Initialize config and register with Claude Code
mcp-bridge init --register claude-code
# 2. Install a server from the catalog
mcp-bridge install todoist
# 3. Add your API key
echo "TODOIST_API_TOKEN=your-token" >> ~/.mcp-bridge/.env
# 4. Restart Claude Code — bridge is ready
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"bridge": {
"command": "mcp-bridge",
"args": []
}
}
}
Bundled servers now ship with recipe.json using Universal Recipe Spec v2.1 (15 servers with rich metadata: category, subcategory, origin, countries, audience, sideEffects).
During install, MCP Bridge prefers recipe.json when present and falls back to legacy config.json (v1) for backwards compatibility.
For third-party recipe authors:
recipe.json per the spec above.npx @aiwerk/mcp-bridge validate-recipe ./recipe.json
config.json (v1) remains supported, but recipe.json (v2) is the recommended format going forward.
mcp-bridge now fetches recipes from catalog.aiwerk.ch instead of relying on bundled recipe files.
import { CatalogClient, bootstrapCatalog, mergeRecipesIntoConfig } from '@aiwerk/mcp-bridge';
// Bootstrap: download top recipes
await bootstrapCatalog();
// Or use the client directly
const client = new CatalogClient();
const recipe = await client.resolve('todoist');
const results = await client.search('email');
Two config options control catalog behavior:
| Option | Type | Default | Description |
|---|---|---|---|
catalog |
boolean |
true |
Whether bootstrapCatalog() fetches recipes from the remote catalog |
autoMerge |
boolean |
false |
Whether mergeRecipesIntoConfig() auto-merges cached recipes into your config |
{
"catalog": true,
"autoMerge": true,
"servers": { ... }
}
autoMerge defaults to false (opt-in) — cached recipes are not automatically added to your server list unless you explicitly enable it. This prevents servers without required credentials from being silently activated.catalog defaults to true — recipe discovery from catalog.aiwerk.ch is enabled by default. Set to false to skip all remote fetching.Breaking change (v2.9.0): Previously, all cached recipes whose env vars were present were auto-merged. Now you must set
"autoMerge": trueto restore that behavior.
Auto-discovery uses the recipe name as the config key (e.g., gohighlevel). If you need multiple instances of the same server with different credentials (e.g., two GoHighLevel subaccounts), configure them manually:
// config.json or openclaw.json
{
"ghl-client-a": {
"transport": "streamable-http",
"url": "https://services.leadconnectorhq.com/mcp/",
"headers": {
"Authorization": "Bearer ${GHL_TOKEN_A}",
"locationId": "${GHL_LOCATION_A}"
}
},
"ghl-client-b": {
"transport": "streamable-http",
"url": "https://services.leadconnectorhq.com/mcp/",
"headers": {
"Authorization": "Bearer ${GHL_TOKEN_B}",
"locationId": "${GHL_LOCATION_B}"
}
}
}
Use unique env var names (e.g., GHL_TOKEN_A instead of GHL_PIT_TOKEN) to prevent auto-discovery from adding a duplicate third entry. Manual config always takes priority over auto-discovered recipes.
Note: The bundled
servers/directory is deprecated and will be removed in v3.0.0.
Add to your MCP config:
{
"mcpServers": {
"bridge": {
"command": "mcp-bridge",
"args": ["--config", "/path/to/config.json"]
}
}
}
Install as a plugin (handles everything automatically):
openclaw plugins install @aiwerk/openclaw-mcp-bridge
⚠️ Important: Always use the full scoped name
@aiwerk/openclaw-mcp-bridge. The unscopedopenclaw-mcp-bridgeon npm is a different, unrelated package.
See @aiwerk/openclaw-mcp-bridge for details.
Config: ~/.mcp-bridge/config.json | Secrets: ~/.mcp-bridge/.env
{
"mode": "router",
"servers": {
"todoist": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "@doist/todoist-ai"],
"env": { "TODOIST_API_KEY": "${TODOIST_API_TOKEN}" },
"description": "Task management"
},
"github": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" },
"description": "GitHub repos, issues, PRs"
},
"notion": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-notion"],
"env": { "NOTION_API_KEY": "${NOTION_TOKEN}" },
"description": "Notion pages and databases"
}
},
"toolPrefix": true,
"connectionTimeoutMs": 5000,
"requestTimeoutMs": 60000,
"maxBatchSize": 10,
"schemaCompression": {
"enabled": true,
"maxDescriptionLength": 80
}
}
In router mode, tool descriptions from upstream servers can be verbose (100-300+ chars each). Schema compression truncates them to save tokens:
action=schema to retrieve the full uncompressed schema for any tool on demand"schemaCompression": {
"enabled": true,
"maxDescriptionLength": 80
}
Token savings example: 30 Todoist tools: ~2800 tokens uncompressed -> ~1200 compressed (~57% reduction).
To get full details for a specific tool:
mcp(server="todoist", action="schema", tool="find-tasks")
Set "enabled": false to disable compression and return full descriptions.
Router mode can cache successful action=call tool results in memory using an LRU policy.
resultCache.enabled: false)maxEntries: 100, defaultTtlMs: 300000 (5 minutes)server:tool:stableJson(params)resultCache.cacheTtl (for example "todoist:find-tasks": 60000)action=refresh clears the result cache"resultCache": {
"enabled": true,
"maxEntries": 100,
"defaultTtlMs": 300000,
"cacheTtl": { "todoist:find-tasks": 60000 }
}
Instead of specifying the exact server and tool, describe what you need:
mcp(action="intent", intent="find my tasks for today")
The bridge uses vector embeddings to match your intent to the right server and tool automatically. Returns the best match with a confidence score and alternatives.
Embedding providers (configured via intentRouting.embedding):
| Provider | Config | Requires |
|---|---|---|
gemini (default for auto) |
GEMINI_API_KEY in .env |
Free tier available |
openai |
OPENAI_API_KEY in .env |
Paid API |
ollama |
Local Ollama running | No API key |
keyword |
Nothing | Offline fallback, less accurate |
"intentRouting": {
"embedding": "auto",
"minScore": 0.3
}
auto (default): tries gemini, openai, ollama, then keyword - in order of availabilityminScore: minimum confidence to return a match (0-1, default: 0.3)action=intent callRun multiple tool calls in one round-trip with action="batch" (parallel execution):
{"action":"batch","calls":[{"server":"todoist","tool":"find-tasks","params":{"query":"today"}},{"server":"github","tool":"list_repos","params":{}}]}
{"action":"batch","results":[{"server":"todoist","tool":"find-tasks","result":{"tasks":[]}}, {"server":"github","tool":"list_repos","error":{"error":"mcp_error","message":"..."}}]}
Use maxBatchSize in config to cap requests (default: 10). Failed calls return per-slot error while successful calls still return result.
Three layers of protection for tool results:
Per-server control over how results are passed to the agent:
"servers": {
"my-trusted-server": {
"trust": "trusted"
},
"unknown-server": {
"trust": "untrusted"
},
"sketchy-server": {
"trust": "sanitize"
}
}
| Level | Behavior |
|---|---|
trusted (default) |
Results pass through as-is |
untrusted |
Results tagged with _trust: "untrusted" metadata |
sanitize |
HTML tags stripped, known prompt injection patterns removed (best-effort — see note below) |
Control which tools are visible and callable per server:
"servers": {
"github": {
"toolFilter": {
"deny": ["delete_repository"],
"allow": ["list_repos", "create_issue", "search_code"]
}
}
}
deny: block specific dangerous toolsallow: whitelist mode - only these tools are visiblePrevent oversized responses from consuming your context:
{
"maxResultChars": 50000,
"servers": {
"verbose-server": {
"maxResultChars": 10000
}
}
}
_truncated: true and _originalLengthFrequently used tools can be automatically "promoted" to standalone tools alongside the mcp meta-tool. The promotion system tracks usage and reports which tools qualify — the host environment (e.g., OpenClaw plugin) decides how to register them.
"adaptivePromotion": {
"enabled": true,
"maxPromoted": 10,
"minCalls": 3,
"windowMs": 86400000,
"decayMs": 172800000
}
| Option | Default | Description |
|---|---|---|
enabled |
false |
Opt-in: must be explicitly enabled |
maxPromoted |
10 |
Maximum number of tools to promote |
minCalls |
3 |
Minimum calls within window to qualify |
windowMs |
86400000 (24h) |
Time window for counting calls |
decayMs |
172800000 (48h) |
Demote tools with no calls in this period |
Use action="promotions" to check current promotion state:
mcp(action="promotions")
Returns promoted tools (sorted by frequency) and full usage stats. All tracking is in-memory — promotion rebuilds naturally from usage after restart.
| Mode | Tools exposed | Best for |
|---|---|---|
router (default) |
Single mcp meta-tool |
3+ servers, token-conscious agents |
direct |
All tools individually | Clients with deferred/lazy tool loading (Claude Code), few servers |
Switch modes via CLI or config:
mcp-bridge init --mode direct # all tools exposed individually
mcp-bridge init --mode router # single mcp meta-tool (default)
Or set in ~/.mcp-bridge/config.json:
{ "mode": "direct" }
Router mode — the agent calls mcp(server="todoist", action="list") to discover, then mcp(server="todoist", tool="find-tasks", params={...}) to execute. Best when you have many servers and want minimal token usage.
Direct mode — all tools from all servers are registered individually as todoist_find_tasks, github_list_repos, etc. The bridge still provides unified config, catalog install, OAuth2, security, retries, and reconnection. Ideal for clients that support deferred/lazy tool loading, where tools are registered but not loaded into context until needed.
When action="call" is used without server=, mcp-bridge can resolve collisions automatically.
server= → explicit target wins.server= → score each candidate:last=1.0, then 0.9, 0.8, floor 0.1)+0.3 if server used in last 5 successful calls+0.2 based on parameter-name overlap with input schema>= 0.15 → auto-dispatch to the winner.< 0.15 → return normal { ambiguous: true, candidates: [...] } response.Direct mode — tools are registered as todoist_find_tasks, github_list_repos, etc.
| Transport | Config key | Use case |
|---|---|---|
stdio |
command, args |
Local CLI servers (most common) |
sse |
url, headers |
Remote SSE servers |
streamable-http |
url, headers |
Modern HTTP-based servers |
SSE and streamable-HTTP transports support three auth methods:
Bearer token:
{ "auth": { "type": "bearer", "token": "${MY_API_TOKEN}" } }
Custom headers:
{ "auth": { "type": "header", "headers": { "X-API-Key": "${MY_KEY}" } } }
OAuth2 Client Credentials (automatic token management):
{
"auth": {
"type": "oauth2",
"clientId": "${CLIENT_ID}",
"clientSecret": "${CLIENT_SECRET}",
"tokenUrl": "https://provider.com/oauth/token",
"scopes": ["read", "write"]
}
}
OAuth2 features: automatic token acquisition, caching with expiry-aware refresh, single-attempt 401 retry, env var substitution in credentials.
OAuth2 Authorization Code + PKCE (interactive browser login):
For MCP servers behind enterprise SSO or user-level OAuth2 that require browser-based login (desktop/laptop):
{
"auth": {
"type": "oauth2",
"grantType": "authorization_code",
"authorizationUrl": "https://auth.example.com/authorize",
"tokenUrl": "https://auth.example.com/oauth/token",
"clientId": "optional-public-client-id",
"scopes": ["read", "write"]
}
}
Then authenticate via CLI:
mcp-bridge auth login my-server # Opens browser, completes OAuth2 flow
mcp-bridge auth status # Check token status for all servers
mcp-bridge auth logout my-server # Remove stored token
Features:
clientSecret needed for public clients~/.mcp-bridge/tokens/ (chmod 600), survive bridge restartsrefresh_token grantOAuth2 Device Code (headless environments — VPS, Docker, SSH, CI):
For environments without a browser. You authenticate on a separate device using a short code:
{
"auth": {
"type": "oauth2",
"grantType": "device_code",
"deviceAuthorizationUrl": "https://github.com/login/device/code",
"tokenUrl": "https://github.com/login/oauth/access_token",
"clientId": "your-app-id",
"scopes": ["repo", "read:org"]
}
}
mcp-bridge auth login my-server
# ──────────────────────────────────────────
# Device authentication for "my-server"
#
# 1. Open: https://github.com/login/device
# 2. Enter code: ABCD-1234
# ──────────────────────────────────────────
# Waiting for authorization...
Features:
interval and slow_down responses~/.mcp-bridge/tokens/ with auto-refreshSecrets go in ~/.mcp-bridge/.env (chmod 600 on init):
TODOIST_API_TOKEN=your-token-here
GITHUB_TOKEN=ghp_xxxxx
NOTION_TOKEN=ntn_xxxxx
Use ${VAR_NAME} in config — resolved from .env + system env.
You can also set env vars via CLI or at runtime:
mcp-bridge set-env TODOIST_API_TOKEN your-token-here
Or via the agent: mcp(action="set-env", params={key: "TODOIST_API_TOKEN", value: "..."})
mcp-bridge # Start in stdio mode (default)
mcp-bridge --sse --port 3000 # Start as SSE server
mcp-bridge --http --port 3000 # Start as HTTP server
mcp-bridge --verbose # Info-level logs to stderr
mcp-bridge --debug # Full debug metadata in tool responses
mcp-bridge --config ./my.json # Custom config file
mcp-bridge init # Create ~/.mcp-bridge/ with template config
mcp-bridge init --register claude-code # Init + register with Claude Code
mcp-bridge init --register codex # Init + register with Codex
mcp-bridge init --register cursor # Init + register with Cursor
mcp-bridge init --register windsurf # Init + register with Windsurf
mcp-bridge install <server> # Install from online catalog
mcp-bridge set-env <KEY> <value> # Set an API key in ~/.mcp-bridge/.env
mcp-bridge catalog # Browse 100+ available servers
mcp-bridge servers # List configured servers and current mode
mcp-bridge search <query> # Search catalog by keyword
mcp-bridge update [--check] # Check for / install updates
mcp-bridge --version # Print version
mcp-bridge auth login <server> # OAuth2 browser login (Authorization Code + PKCE)
mcp-bridge auth logout <server> # Remove stored token
mcp-bridge auth status # Show auth status for all servers
When connected to an MCP client (Claude Code, Codex, Cursor, etc.), the bridge exposes a single mcp meta-tool. Agents can discover and install servers at runtime:
mcp(action="search", params={query: "task management"}) # Search catalog
mcp(action="install", params={name: "todoist"}) # Install server (persisted to config)
mcp(action="set-env", params={key: "TODOIST_API_TOKEN", value: "your-key"}) # Set API key
mcp(action="catalog") # Browse all servers
mcp(action="list", server="todoist") # Discover tools on a server
mcp(action="call", server="todoist", tool="find-tasks", params={query: "today"})
The tool description automatically includes all connected servers with their descriptions, so agents know which server to use for what. New servers installed via the bridge are persisted to ~/.mcp-bridge/config.json and survive restarts.
Browse and install from the AIWerk MCP Catalog with 100+ verified, signed recipes:
mcp-bridge catalog # Browse all 100+ servers
mcp-bridge search payments # Search by keyword
mcp-bridge install todoist # Install from catalog
Popular servers include: todoist, github, notion, stripe, linear, google-maps, slack, supabase, mongodb, playwright, docker, and many more.
All catalog recipes are Ed25519 signed and security-audited. The bridge verifies signatures before installation.
Note: The bundled
servers/directory is deprecated. All servers now come from the online catalog.
Use as a dependency in your own MCP server or OpenClaw plugin:
import { McpRouter, StandaloneServer, loadConfig } from "@aiwerk/mcp-bridge";
// Quick start
const config = loadConfig({ configPath: "./config.json" });
const server = new StandaloneServer(config, console);
await server.startStdio();
// Use the router directly
import { McpRouter } from "@aiwerk/mcp-bridge";
const router = new McpRouter(servers, config, logger);
const result = await router.dispatch("todoist", "call", "find-tasks", { query: "today" });
┌─────────────────┐ ┌──────────────────────────────────────────────┐
│ Claude Desktop │ │ MCP Bridge │
│ Cursor │◄───►│ │
│ Windsurf │stdio│ ┌──────────┐ ┌────────┐ ┌────────────┐ │
│ OpenClaw │ SSE │ │ Router │ │Security│ │ Backend │ │
│ Any MCP client │ HTTP│ │ Intent │─►│ Trust │─►│ servers: │ │
└─────────────────┘ │ │ Schema │ │ Filter │ │ • todoist │ │
│ │ Compress │ │ Limit │ │ • github │ │
│ └──────────┘ └────────┘ │ • notion │ │
│ │ • stripe │ │
│ └────────────┘ │
└──────────────────────────────────────────────┘
The built-in security layer (trust levels, tool filters, result sanitization) provides best-effort baseline protection for common threats:
⚠️
trust: "sanitize"is NOT a security boundary. It catches common/known injection patterns but is trivially bypassable via Unicode homoglyphs, zero-width characters, base64 encoding, or multi-step injection chains. Treat it as defense-in-depth, not a sole protection layer.
What it does NOT cover:
For production deployments with high security requirements, consider adding an external content filtering layer (e.g., guardrails, PII redaction service) between the bridge and your application.
| Status | Feature | Version |
|---|---|---|
| ✅ | Smart Router v2 (intent, cache, batch, resolution) | 1.9.0 |
| ✅ | HTTP auth (bearer, headers) | 2.0.0 |
| ✅ | Configurable retries + graceful shutdown | 2.0.0 |
| ✅ | OAuth2 Client Credentials | 2.1.0 |
| ✅ | OAuth2 Authorization Code + PKCE | 2.5.0 |
| ✅ | OAuth2 Device Code flow (headless) | 2.6.0 |
| ✅ | Agent-driven discovery (search/install at runtime) | 2.8.6 |
| 🔜 | Hosted bridge (bridge.aiwerk.ch) | planned |
| ✅ | Remote catalog integration | 2.8.0 |
| ✅ | CLI online catalog | 2.8.23 |
| ✅ | Debug mode (_debug metadata) | 2.8.4 |
| 🔜 | OpenTelemetry / Prometheus metrics | planned |
| 🔜 | PII redaction | planned |
| 🔜 | Skill system (recipe.json skills for agents) | planned |
See docs/hosted-bridge-spec.md for the hosted bridge architecture.
MIT — AIWerk
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"bridge": {
"command": "npx",
"args": [
"-y",
"@aiwerk/mcp-bridge"
]
}
}
}pro-tip
Поставил Bridge? Скажи Claude: «запомни почему я установил Bridge и что хочу попробовать» — попадёт в твой Vault.
как это работает →