# Mcp Bridge

> MCP Bridge — Multiplex your MCP servers into one interface. Works with Claude Desktop, Cursor, Windsurf, and any MCP client.

- **Type:** MCP server
- **Install:** `agentstack add mcp-aiwerk-mcp-bridge`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [AIWerk](https://agentstack.voostack.com/s/aiwerk)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [AIWerk](https://github.com/AIWerk)
- **Source:** https://github.com/AIWerk/mcp-bridge

## Install

```sh
agentstack add mcp-aiwerk-mcp-bridge
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# @aiwerk/mcp-bridge

[](https://github.com/AIWerk/mcp-bridge/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/@aiwerk/mcp-bridge)
[](https://opensource.org/licenses/MIT)
[](#status)

**Your AI, Connected to Everything.** Multiplex multiple MCP servers into one interface. One config, one connection, all your tools.

🌐 **[aiwerkmcp.com](https://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.

## Status

`@aiwerk/mcp-bridge` is **actively developed** as of 2026-05-03.

The primary use case is the **local install path for the AIWerk catalog** at
[aiwerkmcp.com](https://aiwerkmcp.com). Some recipes (those marked
`localOnly: true`, e.g. `chrome-devtools`) need a browser, display, USB
device or user-specific local path that the hosted bridge cannot reach;
this package is the recommended way to run them.

It also serves:

- **OpenClaw plugin users** via [`@aiwerk/openclaw-mcp-bridge`](https://github.com/AIWerk/openclaw-mcp-bridge),
  which embeds this library and ships its own recipe-install tooling.
- **Self-hosted / offline deployments** where the hosted service is not an
  option.
- **Library consumers** that import `McpRouter`, transports and the OAuth2
  token manager directly.

Active development tracks the **Universal Recipe Spec v2** alongside the
hosted bridge — new fields like `localOnly`, `multiInstance`,
`auth.options[]` and `envBinding` are being ported, and the bundled
`servers/` directory is being kept in sync with the catalog. The install
helper does not fetch recipes over HTTP — recipes come from the bundled
`servers/` directory only.

## Why?

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:
- **Router mode**: all servers behind one `mcp` meta-tool (~99% token reduction)
- **Intent routing**: say what you need in plain language, the bridge finds the right tool
- **Schema compression**: tool descriptions compressed ~57%, full schema on demand
- **Security layer**: trust levels, tool deny/allow lists, result size limits
- **HTTP auth**: bearer token, custom headers, **OAuth2 Client Credentials**, and **OAuth2 Authorization Code + PKCE** (interactive browser login)
- **Result caching**: LRU cache with per-tool TTL overrides
- **Batch calls**: parallel multi-tool execution via `action=batch`
- **Multi-server resolution**: automatic tool disambiguation when multiple servers provide the same tool
- **Configurable retries**: exponential backoff for transient errors
- **Graceful shutdown**: clean process termination and connection cleanup
- **Direct mode**: all tools registered individually with automatic prefixing
- **3 transports**: stdio, SSE, streamable-http
- **Bundled recipe examples**: 14+ reference server recipes in `servers/` — copy one as a starting point for your own config, or bring your own MCP server entirely
- **Zero config secrets in files**: `${ENV_VAR}` resolution from `.env`

## Install

```bash
npm install -g @aiwerk/mcp-bridge
```

## Quick Start

```bash
# 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
```

## Use with Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "bridge": {
      "command": "mcp-bridge",
      "args": []
    }
  }
}
```

## Recipe Spec v2

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.

- Spec: [`docs/universal-recipe-spec.md`](docs/universal-recipe-spec.md)
- Runtime compatibility: v1 and v2 are both supported
- Existing v1-only servers continue to work unchanged

For third-party recipe authors:

1. Author `recipe.json` per the spec above.
2. Validate your recipe before publishing:

```bash
npx @aiwerk/mcp-bridge validate-recipe ./recipe.json
```

`config.json` (v1) remains supported, but `recipe.json` (v2) is the recommended format going forward.

## Catalog Integration (v2.8.0+)

mcp-bridge now fetches recipes from [catalog.aiwerk.ch](https://catalog.aiwerk.ch) instead of relying on bundled recipe files.

### How it works
1. **First run**: Automatically downloads the top 15 most popular recipes
2. **On-demand**: When you install a server, it checks the catalog first
3. **Offline**: Falls back to local cache if catalog is unreachable

### API
```typescript
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');
```

### Catalog & Auto-Merge Options

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 |

```json
{
  "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](https://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": true` to restore that behavior.

### Multiple instances of the same server

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:

```json
// 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.

## Use with Cursor / Windsurf

Add to your MCP config:

```json
{
  "mcpServers": {
    "bridge": {
      "command": "mcp-bridge",
      "args": ["--config", "/path/to/config.json"]
    }
  }
}
```

## Use with OpenClaw

Install as a plugin (handles everything automatically):

```bash
openclaw plugins install @aiwerk/openclaw-mcp-bridge
```

> ⚠️ **Important:** Always use the full scoped name `@aiwerk/openclaw-mcp-bridge`. The unscoped `openclaw-mcp-bridge` on npm is a **different, unrelated package**.

See [@aiwerk/openclaw-mcp-bridge](https://github.com/AIWerk/openclaw-mcp-bridge) for details.

## Configuration

Config: `~/.mcp-bridge/config.json` | Secrets: `~/.mcp-bridge/.env`

```json
{
  "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
  }
}
```

### Schema Compression

In router mode, tool descriptions from upstream servers can be verbose (100-300+ chars each). Schema compression truncates them to save tokens:

- **Enabled by default** — descriptions capped at 80 characters
- Cuts at sentence boundary when possible, otherwise word boundary
- Use `action=schema` to retrieve the full uncompressed schema for any tool on demand

```json
"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.

### Result Caching

Router mode can cache successful `action=call` tool results in memory using an LRU policy.

- Disabled by default (`resultCache.enabled: false`)
- No external dependencies (Map-based implementation)
- Defaults: `maxEntries: 100`, `defaultTtlMs: 300000` (5 minutes)
- Cache key: `server:tool:stableJson(params)`
- Per-tool TTL override via `resultCache.cacheTtl` (for example `"todoist:find-tasks": 60000`)
- `action=refresh` clears the result cache
- Error responses are never cached

```json
"resultCache": {
  "enabled": true,
  "maxEntries": 100,
  "defaultTtlMs": 300000,
  "cacheTtl": { "todoist:find-tasks": 60000 }
}
```

### Intent Routing

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 |

```json
"intentRouting": {
  "embedding": "auto",
  "minScore": 0.3
}
```

- `auto` (default): tries gemini, openai, ollama, then keyword - in order of availability
- `minScore`: minimum confidence to return a match (0-1, default: 0.3)
- Index is built lazily on first `action=intent` call

### Batch Calls

Run multiple tool calls in one round-trip with `action="batch"` (parallel execution):

```json
{"action":"batch","calls":[{"server":"todoist","tool":"find-tasks","params":{"query":"today"}},{"server":"github","tool":"list_repos","params":{}}]}
```

```json
{"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`.

### Security

Three layers of protection for tool results:

#### Trust Levels

Per-server control over how results are passed to the agent:

```json
"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) |

#### Tool Filter

Control which tools are visible and callable per server:

```json
"servers": {
  "github": {
    "toolFilter": {
      "deny": ["delete_repository"],
      "allow": ["list_repos", "create_issue", "search_code"]
    }
  }
}
```

- `deny`: block specific dangerous tools
- `allow`: whitelist mode - only these tools are visible
- If both: allowed tools minus denied ones
- Applied in both tool listing and execution (defense in depth)

#### Max Result Size

Prevent oversized responses from consuming your context:

```json
{
  "maxResultChars": 50000,
  "servers": {
    "verbose-server": {
      "maxResultChars": 10000
    }
  }
}
```

- Global default + per-server override
- Truncated results include `_truncated: true` and `_originalLength`

### Adaptive Promotion

Frequently 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.

```json
"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.

### Modes

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

```bash
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`:

```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.

### Multi-Server Tool Resolution

When `action="call"` is used without `server=`, mcp-bridge can resolve collisions automatically.

- Tool exists on exactly one server → direct dispatch.
- Tool exists on multiple servers + explicit `server=` → explicit target wins.
- Tool exists on multiple servers + no `server=` → score each candidate:
  - **base_priority**: reverse config order (`last=1.0`, then `0.9`, `0.8`, floor `0.1`)

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [AIWerk](https://github.com/AIWerk)
- **Source:** [AIWerk/mcp-bridge](https://github.com/AIWerk/mcp-bridge)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-aiwerk-mcp-bridge
- Seller: https://agentstack.voostack.com/s/aiwerk
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
