Skip to content

Commit 3a46483

Browse files
committed
v0.2.0: split into mcp-core library + mcp-gateway CLI
Monorepo restructure (pnpm workspace) and feature expansion: - Extract pure primitives into @swarmclawai/mcp-core: config schema, DownstreamManager (transport-injectable), McpRequestRouter, McpMultiClient, SessionToolPromoter, token estimator. Embedders can use these in-process without pulling in the CLI or stdio server binding. - @swarmclawai/mcp-gateway becomes the CLI + stdio/HTTP server wrapping on top of mcp-core. Re-exports core for back-compat. - Add mcp_tool_search meta-tool: always-exposed built-in that fuzzy-matches downstream tools by name/description, promotes matches for the rest of the session. Agents use it to discover lazy (alwaysExpose: false) tools on demand instead of eagerly binding every tool schema. - Add streamable-HTTP transport on both sides. Upstream: `start --http --port 3477` runs the gateway as a streamable-http server at POST /mcp. Downstream: config servers can now be remote HTTP endpoints (url + headers) alongside the existing stdio command spec. Auto-selects transport per spec via defaultClientTransportFactory. - Add `npx mcp-gateway add <agent>` installer for claude-code, cursor, cline, windsurf. --dry-run, --force, .bak backups. - Add weekly token leaderboard: bench/servers.json + scripts/benchmark.ts + .github/workflows/leaderboard.yml publishes per-server token costs to bench/leaderboard.md. - README overhaul leading with the token problem, launch post drafts for HN/r/ClaudeAI/r/LocalLLaMA/r/mcp, CHANGELOGs for both packages, RELEASE.md with publish process, sync-docs script so tarball docs can't drift, .github/workflows/release.yml for tag-triggered publish. 37 tests passing across both packages.
1 parent 4763f2c commit 3a46483

52 files changed

Lines changed: 3349 additions & 371 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/leaderboard.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: leaderboard
2+
3+
on:
4+
schedule:
5+
# Every Monday at 08:00 UTC
6+
- cron: "0 8 * * 1"
7+
workflow_dispatch:
8+
push:
9+
paths:
10+
- "bench/servers.json"
11+
- "scripts/benchmark.ts"
12+
- ".github/workflows/leaderboard.yml"
13+
14+
permissions:
15+
contents: write
16+
17+
jobs:
18+
run-benchmark:
19+
runs-on: ubuntu-latest
20+
timeout-minutes: 20
21+
steps:
22+
- uses: actions/checkout@v4
23+
- uses: pnpm/action-setup@v3
24+
with:
25+
version: 10
26+
- uses: actions/setup-node@v4
27+
with:
28+
node-version: "20"
29+
cache: pnpm
30+
- run: pnpm install --frozen-lockfile
31+
- run: pnpm -r run build
32+
- name: Run benchmark
33+
run: pnpm tsx scripts/benchmark.ts
34+
- name: Commit results
35+
if: github.event_name != 'pull_request'
36+
run: |
37+
git config user.name "github-actions[bot]"
38+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
39+
git add bench/results.json bench/leaderboard.md
40+
if git diff --cached --quiet; then
41+
echo "no changes to commit"
42+
exit 0
43+
fi
44+
git commit -m "chore(bench): refresh MCP token leaderboard"
45+
git push

.github/workflows/release.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
id-token: write
12+
13+
jobs:
14+
publish:
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 15
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: pnpm/action-setup@v3
20+
with:
21+
version: 10
22+
- uses: actions/setup-node@v4
23+
with:
24+
node-version: "20"
25+
cache: pnpm
26+
registry-url: "https://registry.npmjs.org"
27+
- run: pnpm install --frozen-lockfile
28+
- run: pnpm sync-docs
29+
- run: pnpm -r run build
30+
- run: pnpm -r run typecheck
31+
- run: pnpm -r run test
32+
- name: Publish mcp-core
33+
env:
34+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
35+
run: pnpm --filter @swarmclawai/mcp-core publish --access public --no-git-checks
36+
- name: Publish mcp-gateway
37+
env:
38+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
39+
run: pnpm --filter @swarmclawai/mcp-gateway publish --access public --no-git-checks

README.md

Lines changed: 97 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,61 @@
11
# mcp-gateway
22

3-
> Local MCP gateway that fans out to N downstream MCP servers, namespaces their tools, and lazy-loads their schemas — so your coding agent's context isn't eaten by MCP boilerplate.
3+
> **Your coding agent is spending 47,000 tokens on MCP boilerplate before you type a word.** `mcp-gateway` fans out to your downstream MCP servers, namespaces their tools, and only exposes what you ask for. Agents discover the rest on demand via `mcp_tool_search`.
44
55
[![npm version](https://img.shields.io/npm/v/@swarmclawai/mcp-gateway.svg)](https://www.npmjs.com/package/@swarmclawai/mcp-gateway)
66
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
77
[![CI](https://github.com/swarmclawai/mcp-gateway/actions/workflows/ci.yml/badge.svg)](https://github.com/swarmclawai/mcp-gateway/actions/workflows/ci.yml)
88

9-
## Why this exists
9+
<p align="center">
10+
<!-- TODO: replace with recorded before/after GIF of /context in Claude Code -->
11+
<img src="./docs/assets/before-after.gif" alt="Claude Code /context dropping from 47K to 3K tokens after installing mcp-gateway" width="720" />
12+
</p>
1013

11-
Install more than a handful of MCP servers and something ugly happens: every one of them dumps its full tool schema into your coding agent's context at startup. People routinely report **30,000 – 60,000 tokens** of MCP boilerplate consumed before they've typed a single message. The 1M-context upgrade makes this worse, not better — people just install more servers.
14+
## 30-second install
1215

13-
Existing tooling solves the wrong half:
14-
15-
- Registries (wong2/awesome-mcp-servers, mcp.so, smithery, glama) solve **discovery**.
16-
- Docker's own gateway is great at **multi-tenancy** and **auth**.
17-
- Nobody owns the **local runtime** problem: "I have 15 MCP servers installed. I want 3 of them exposed by default and the other 12 to only show up when I actually ask for them."
16+
```bash
17+
# Drop-in for Claude Code
18+
npx @swarmclawai/mcp-gateway@latest add claude-code
1819

19-
`mcp-gateway` is that tool. You point your upstream client (Claude Code, Cursor, Cline, Aider, Windsurf, etc.) at one MCP endpoint — the gateway. It fans out to all your downstream servers, prefixes their tool names to prevent collisions, and only exposes the tools you've explicitly chosen to pre-load.
20+
# Or Cursor, Cline, Windsurf
21+
npx @swarmclawai/mcp-gateway@latest add cursor
22+
npx @swarmclawai/mcp-gateway@latest add cline
23+
npx @swarmclawai/mcp-gateway@latest add windsurf
24+
```
2025

21-
## 30-second demo
26+
That edits the agent's MCP config (with a `.bak` backup) so it talks to one gateway endpoint instead of N individual servers. Then:
2227

2328
```bash
24-
# Generate a starter config
29+
# Generate a starter config and edit to taste
2530
npx @swarmclawai/mcp-gateway@latest init --write
2631

27-
# Edit mcp-gateway.config.json — set alwaysExpose per server
32+
# See how many tokens each downstream server is spending on tool schemas
33+
npx @swarmclawai/mcp-gateway@latest token-report
34+
```
2835

29-
# See how many tokens each server is spending
30-
npx @swarmclawai/mcp-gateway token-report
36+
## How it works
3137

32-
# Point Claude Code at the gateway instead of at each server individually
33-
claude mcp add gateway -- npx -y @swarmclawai/mcp-gateway@latest start
3438
```
39+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
40+
│ Claude Code, │ │ │ stdio │ filesystem │
41+
│ Cursor, Cline, │ stdio │ mcp-gateway ├────────┤ github │
42+
│ Windsurf, ... ├────────┤ (one endpoint) │ │ sentry │
43+
│ │ │ │ http │ your HTTP MCP │
44+
└─────────────────┘ └─────────────────┘ └─────────────────┘
45+
46+
└── only `alwaysExpose` tools are bound
47+
at startup. Rest surface via
48+
`mcp_tool_search` on demand.
49+
```
50+
51+
- **Namespaces downstream tools.** Two servers can both expose `read_file` — you see `fs__read_file` and `github__read_file`.
52+
- **Lazy-loads by default.** Tools from `alwaysExpose: false` servers stay hidden until an agent calls `mcp_tool_search({query: "..."})`, which promotes them for the rest of the session.
53+
- **Speaks stdio upstream.** Or streamable-HTTP — see [HTTP mode](#http-mode).
54+
- **Stdio or HTTP downstream.** Set `command` for local processes, `url` for remote MCP servers.
3555

3656
## Config
3757

38-
A single `mcp-gateway.config.json` at your project root (or `--config <path>`):
58+
`mcp-gateway.config.json` at your project root (or `--config <path>`):
3959

4060
```json
4161
{
@@ -59,77 +79,97 @@ A single `mcp-gateway.config.json` at your project root (or `--config <path>`):
5979
"command": "npx",
6080
"args": ["-y", "@sentry/mcp-server@latest"],
6181
"alwaysExpose": ["issue_details"]
82+
},
83+
{
84+
"name": "remote",
85+
"url": "https://mcp.example.com/mcp",
86+
"headers": { "Authorization": "Bearer ${SECRET_TOKEN}" },
87+
"alwaysExpose": false
6288
}
6389
]
6490
}
6591
```
6692

6793
- `alwaysExpose: true` — every tool from this server is in your agent's context on startup.
68-
- `alwaysExpose: false` — tools aren't exposed at first; the gateway connects to the server when the agent calls a tool whose name begins with `<prefix>__`.
94+
- `alwaysExpose: false` — tools aren't exposed at first; `mcp_tool_search` surfaces them on demand.
6995
- `alwaysExpose: ["tool_a", "tool_b"]` — only the listed tools are pre-exposed.
96+
- Each server is **stdio** (`command`) or **HTTP** (`url`), not both.
97+
98+
## The `mcp_tool_search` meta-tool
99+
100+
The gateway always exposes one built-in tool: `mcp_tool_search({query, limit?})`. Agents call it to discover lazy tools by name or description keywords. Matched tools are promoted for the rest of the session and start showing up in subsequent `list_tools` responses.
70101

71-
The gateway prefixes every downstream tool with its server name and the namespace separator (`__` by default) so two servers can both expose a tool called `read_file` without collision — your agent sees `fs__read_file` and `github__read_file`.
102+
This is the feature that makes `alwaysExpose: false` actually usable — an agent doesn't need to know a tool exists up-front, it searches when it needs one.
72103

73-
## Install
104+
## HTTP mode
74105

75106
```bash
76-
pnpm add -g @swarmclawai/mcp-gateway
77-
# or
78-
npm i -g @swarmclawai/mcp-gateway
79-
# or run on demand
80-
npx @swarmclawai/mcp-gateway@latest --help
107+
# Listen on http://127.0.0.1:3477/mcp instead of stdio
108+
npx @swarmclawai/mcp-gateway@latest start --http --port 3477
81109
```
82110

111+
Useful when:
112+
- Your agent runs on a server (e.g. SwarmClaw on a VPS) and wants to talk to a local gateway over the network.
113+
- You want to run one persistent gateway and point multiple agents at it.
114+
- Your client prefers streamable-HTTP to spawning stdio child processes.
115+
83116
## Commands
84117

85118
| Command | Purpose |
86119
|---|---|
87-
| `init` | Create a starter config |
88-
| `validate` | Validate the config file without connecting to any downstream |
89-
| `status` | Connect to every enabled downstream and report status + tool counts |
90-
| `token-report` | Estimate how many tokens each downstream's schemas cost |
91-
| `add-server <name> <command> [args...]` | Append a server to the config |
92-
| `start` | Start the gateway (stdio MCP server for an upstream client) |
120+
| `add <agent>` | Install the gateway into an agent's MCP config (claude-code, cursor, cline, windsurf) |
121+
| `init` | Create a starter `mcp-gateway.config.json` |
122+
| `validate` | Validate the config without connecting |
123+
| `status` | Connect to each downstream and report status + tool counts |
124+
| `token-report` | Estimate token cost per downstream |
125+
| `add-server` | Append a downstream server to the config |
126+
| `start` | Start the gateway (stdio by default, `--http --port` for streamable-HTTP) |
93127
| `help-agents` | Print the machine-readable command catalog |
94128

95129
Every command accepts `--json` and returns a one-line JSON envelope. Exit codes: `0` success, `1` user error, `2` internal error.
96130

97-
## How token-report works
131+
## Token leaderboard
98132

99-
The report walks every downstream server, connects to it over stdio, calls `tools/list`, and estimates the token cost of each tool's name + description + input schema. We don't call a real tokenizer — that'd introduce a heavy dep for a directional number. Chars / 3.5 is close enough to tell you which server is blowing up your window.
133+
Every week, `mcp-gateway` benchmarks a curated set of popular MCP servers and publishes the results: which are the leanest, which are the heaviest, how much you're spending.
100134

101-
## Wiring it into your agent
135+
See [`bench/leaderboard.md`](./bench/leaderboard.md) — or open a PR against [`bench/servers.json`](./bench/servers.json) to add your server.
102136

103-
### Claude Code
137+
## Used by
104138

105-
```bash
106-
claude mcp add gateway -- npx -y @swarmclawai/mcp-gateway@latest start
107-
```
139+
- **[SwarmClaw](https://github.com/swarmclawai/swarmclaw)** — self-hosted autonomous-agent runtime. Embeds `@swarmclawai/mcp-core` (the library half of this repo) so every SwarmClaw agent gets lazy tool exposure + `mcp_tool_search` without running a separate gateway process.
108140

109-
Remove your individual server entries — the gateway replaces them.
141+
Ship something on top of it? Open a PR and we'll add you.
110142

111-
### Cursor
143+
## Library use (`@swarmclawai/mcp-core`)
112144

113-
In `~/.cursor/mcp.json`:
145+
The pure primitives — config parsing, downstream multiplexing, token estimation, `McpRequestRouter`, `McpMultiClient`, `mcp_tool_search` — live in a separate package so embedders can use them in-process:
114146

115-
```json
116-
{
117-
"mcpServers": {
118-
"gateway": {
119-
"command": "npx",
120-
"args": ["-y", "@swarmclawai/mcp-gateway@latest", "start"]
121-
}
122-
}
123-
}
147+
```bash
148+
pnpm add @swarmclawai/mcp-core
124149
```
125150

126-
### Cline, Aider, Windsurf
151+
```ts
152+
import { McpMultiClient } from "@swarmclawai/mcp-core";
153+
154+
const mc = new McpMultiClient({
155+
config: {
156+
version: 1,
157+
servers: [
158+
{ name: "fs", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "."], alwaysExpose: true },
159+
{ name: "github", command: "docker", args: [...], alwaysExpose: false },
160+
],
161+
},
162+
});
163+
await mc.connectEager();
164+
const tools = await mc.listExposedTools();
165+
// → [fs__read_file, fs__write_file, ..., mcp_tool_search]
166+
```
127167

128-
Same pattern: one `mcp-gateway start` entry in place of N individual server entries. See [`awesome-mcp-for-coding-agents`](https://github.com/swarmclawai/awesome-mcp-for-coding-agents#how-to-install) for the exact config syntax per agent.
168+
See the [package README](./packages/mcp-core/README.md) for the full API surface.
129169

130170
## Built for coding agents
131171

132-
Every swarmclawai CLI follows the same agent conventions so Claude Code, Cursor, Cline, Aider, Codex et al can drive them without guessing:
172+
Every `@swarmclawai/*` CLI follows the same agent conventions so Claude Code, Cursor, Cline, Codex, Factory Droid, Cursor Agent et al can drive them without guessing:
133173

134174
- `--json` everywhere, one-line envelope on stdout
135175
- Stderr for logs, stdout for data
@@ -141,11 +181,10 @@ See [`AGENTS.md`](./AGENTS.md) for the full machine-readable reference.
141181

142182
## Roadmap
143183

144-
- Session-scoped explicit exposure: an agent can ask the gateway "expose github__* for the rest of this session" without restarting
145-
- Schema compression: strip optional descriptions on lazy-exposed tools to shrink `tools/list` replies further
146-
- Observability endpoint: count tool calls per server to justify what should actually be alwaysExpose
147-
- Remote (HTTP/SSE) transport for upstream clients, not just stdio
148-
- Per-tool deny/allow list beyond the name prefix
184+
- Tool **profiles / groups**: `"profile": "coding"` exposes a curated subset across servers
185+
- Schema compression for lazy-exposed tools
186+
- Per-agent session state so gateways serving multiple clients don't cross-promote tools
187+
- Per-tool deny/allow list beyond the namespace prefix
149188

150189
## Contributing
151190

RELEASE.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Release process
2+
3+
Two packages ship from this monorepo:
4+
5+
- `@swarmclawai/mcp-core` — the library primitives
6+
- `@swarmclawai/mcp-gateway` — the CLI + stdio/HTTP server that wraps them
7+
8+
`mcp-gateway` depends on `mcp-core` via `workspace:^`, so `pnpm publish` rewrites that to the published version automatically. **Always use `pnpm publish`**`npm publish` will leave the `workspace:^` literal in the published package.json and break installs.
9+
10+
## Manual release
11+
12+
From a clean working tree on `main`:
13+
14+
```bash
15+
pnpm install --frozen-lockfile
16+
pnpm -r run build
17+
pnpm -r run typecheck
18+
pnpm -r run test
19+
20+
# Bump versions — bump mcp-core first so the gateway's workspace:^ resolves
21+
# to the new version.
22+
pnpm --filter @swarmclawai/mcp-core version patch # or minor / major
23+
pnpm --filter @swarmclawai/mcp-gateway version patch
24+
25+
git add .
26+
git commit -m "release: mcp-core vX.Y.Z + mcp-gateway vX.Y.Z"
27+
git tag vX.Y.Z
28+
git push && git push --tags
29+
30+
# Publish mcp-core first (gateway depends on it being resolvable).
31+
pnpm --filter @swarmclawai/mcp-core publish --access public
32+
pnpm --filter @swarmclawai/mcp-gateway publish --access public
33+
```
34+
35+
## Automated release
36+
37+
Tagging a commit matching `v*` triggers `.github/workflows/release.yml`, which runs the full build + test + publish for both packages using an `NPM_TOKEN` secret.
38+
39+
## Pre-release checklist
40+
41+
- `pnpm -r run test` — all green.
42+
- `pnpm -r run typecheck` — clean.
43+
- `pnpm --filter @swarmclawai/mcp-gateway exec node dist/cli.js help-agents` — CLI snapshot works.
44+
- README is up to date with the new surface area.
45+
- Breaking changes have a `CHANGELOG.md` entry and a migration note.
46+
- For major bumps of `mcp-core`: downstream embedders (SwarmClaw at minimum) have a PR open against the new API.

0 commit comments

Comments
 (0)