mcp-compress-router 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # MCP Compressing Router
2
2
 
3
+ [![CI](https://github.com/ameshkov/mcp-compress-router/actions/workflows/ci.yml/badge.svg)](https://github.com/ameshkov/mcp-compress-router/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/mcp-compress-router)](https://www.npmjs.com/package/mcp-compress-router)
5
+ [![GitHub release](https://img.shields.io/github/v/release/ameshkov/mcp-compress-router)](https://github.com/ameshkov/mcp-compress-router/releases)
6
+
3
7
  <p align="center">
4
8
  Compress all connected MCP into a single router MCP and save up to 99% on
5
9
  tokens.
@@ -10,6 +14,30 @@
10
14
  alt="MCP Compress Router" width="600"/>
11
15
  </p>
12
16
 
17
+ ## Table of Contents
18
+
19
+ - [The Problem](#the-problem)
20
+ - [The Solution](#the-solution)
21
+ - [Prerequisites](#prerequisites)
22
+ - [Quick Start](#quick-start)
23
+ - [Configuration](#configuration)
24
+ - [Config File Location](#config-file-location)
25
+ - [Adding Downstream Servers](#adding-downstream-servers)
26
+ - [Per-Server Enable/Disable](#per-server-enabledisable)
27
+ - [Per-Server Tool Selection](#per-server-tool-selection)
28
+ - [Inspecting Tools](#inspecting-tools)
29
+ - [OAuth](#oauth)
30
+ - [Redirect URL](#redirect-url)
31
+ - [GitHub MCP (special case)](#github-mcp-special-case)
32
+ - [Custom Headers](#custom-headers)
33
+ - [Secrets and Variable Expansion](#secrets-and-variable-expansion)
34
+ - [Connecting Coding Agents](#connecting-coding-agents)
35
+ - [Opencode](#opencode)
36
+ - [Claude Code](#claude-code)
37
+ - [Codex](#codex)
38
+ - [GitHub Copilot](#github-copilot)
39
+ - [How It Works](#how-it-works)
40
+
13
41
  ## The Problem
14
42
 
15
43
  When you have multiple MCPs every request to the LLM will include ALL their
@@ -49,3 +77,526 @@ an average coding session will be about **$0.032175** so we saved about
49
77
 
50
78
  This is just a basic example with just 3 MCP servers, the more MCP servers you
51
79
  have, the more you save.
80
+
81
+ ## Prerequisites
82
+
83
+ - **Node.js 24 or later** — the router runs on Node.js and is launched
84
+ via `npx`, so no separate install step is needed.
85
+ - **A coding agent that supports stdio MCP servers** — this covers
86
+ virtually every modern coding agent (opencode, Claude Code, Codex,
87
+ GitHub Copilot, Cursor, etc.). The router exposes itself as a single
88
+ stdio MCP server, so any agent that can spawn a local MCP process
89
+ works.
90
+
91
+ ## Quick Start
92
+
93
+ The router is published on npm as
94
+ [`mcp-compress-router`](https://www.npmjs.com/package/mcp-compress-router).
95
+ You do not need to install it — just run it with `npx`:
96
+
97
+ ```bash
98
+ npx mcp-compress-router add github -- npx -y @modelcontextprotocol/server-github
99
+ ```
100
+
101
+ This registers a downstream MCP server named `github` and writes it to
102
+ your [config file](#config-file-location). Repeat for every MCP server you
103
+ want to compress.
104
+
105
+ Then point your [coding agent](#connecting-coding-agents) at the router:
106
+
107
+ ```bash
108
+ npx mcp-compress-router
109
+ ```
110
+
111
+ When started without a subcommand, the router runs the MCP server over
112
+ stdio and exposes exactly two tools (`get_tool_schema`,
113
+ `invoke_tool`) to the agent.
114
+
115
+ ## Configuration
116
+
117
+ The router reads its configuration from a single JSON(C) file that lists
118
+ every downstream MCP server to compress. You can edit this file by hand
119
+ or use the `add` / `remove` / `get` / `list` CLI commands.
120
+
121
+ ### Config File Location
122
+
123
+ By default, the config file lives in a platform-specific directory
124
+ (`mcp.jsonc` is preferred over `mcp.json` when both exist):
125
+
126
+ - **Windows:** `%APPDATA%\mcp-compress-router\`
127
+ - **macOS:** `~/Library/Application Support/mcp-compress-router/`
128
+ - **Linux:** `~/.local/share/mcp-compress-router/`
129
+
130
+ You can override this with:
131
+
132
+ - The `-c, --config <path>` flag on any command, or
133
+ - The `MCP_COMPRESS_ROUTER_HOME` environment variable (points to a
134
+ directory containing the config file).
135
+
136
+ If the file does not exist when a management command runs, it is created
137
+ automatically with an empty `{ "mcpServers": {} }` body.
138
+
139
+ A `.env` file in the **same directory** is loaded automatically at
140
+ startup, so you can keep secrets out of the config (see
141
+ [Secrets and Variable Expansion](#secrets-and-variable-expansion)).
142
+
143
+ > **Note on `-c` and credential storage:** when you override the config
144
+ > path with `-c /some/dir/mcp.json`, both `credentials.json` (OAuth
145
+ > tokens) and `mcp.json` live in `/some/dir/` — i.e. next to the config
146
+ > file you specified. The `.env` file, however, is loaded from the
147
+ > [configuration directory](#config-file-location) resolved by
148
+ > `MCP_COMPRESS_ROUTER_HOME` or the platform default, *not* from beside
149
+ > the explicit `-c` path. To co-locate `.env` with a custom config, set
150
+ > `MCP_COMPRESS_ROUTER_HOME` to the same directory.
151
+
152
+ ### Adding Downstream Servers
153
+
154
+ Use the `add` command to register a downstream MCP server.
155
+
156
+ A good description helps the LLM route requests to the correct server.
157
+ When several servers are compressed behind the router, the model sees
158
+ each server's name, its description, and a list of tool names in the
159
+ `get_tool_schema` catalog. A clear description (e.g. *"GitHub API tools
160
+ for issues, PRs, and repos"*) steers the model toward the right server
161
+ far better than a bare name.
162
+
163
+ **stdio server** (a local process):
164
+
165
+ ```bash
166
+ npx mcp-compress-router add github --description "GitHub API tools" \
167
+ -- npx -y @modelcontextprotocol/server-github
168
+
169
+ # With environment variables
170
+ npx mcp-compress-router add github -e GITHUB_PERSONAL_TOKEN=ghp_xxx \
171
+ --description "GitHub API tools" \
172
+ -- npx -y @modelcontextprotocol/server-github
173
+ ```
174
+
175
+ **HTTP server** (a remote endpoint; transport auto-detected from the
176
+ URL):
177
+
178
+ ```bash
179
+ npx mcp-compress-router add my-http https://localhost:3100/mcp
180
+
181
+ # With a custom header
182
+ npx mcp-compress-router add my-http \
183
+ --header "Authorization: Bearer mytoken" \
184
+ https://localhost:3100/mcp
185
+ ```
186
+
187
+ This produces a config file that looks like:
188
+
189
+ ```jsonc
190
+ {
191
+ "mcpServers": {
192
+ "github": {
193
+ "type": "stdio",
194
+ "command": "npx",
195
+ "args": ["-y", "@modelcontextprotocol/server-github"],
196
+ "env": { "GITHUB_PERSONAL_TOKEN": "ghp_xxx" },
197
+ "description": "GitHub API tools"
198
+ },
199
+ "my-http": {
200
+ "type": "http",
201
+ "url": "https://localhost:3100/mcp",
202
+ "headers": { "Authorization": "Bearer mytoken" }
203
+ }
204
+ }
205
+ }
206
+ ```
207
+
208
+ Both `.json` and `.jsonc` (JSON with comments and trailing commas) are
209
+ supported. CLI commands write plain `.json`; hand-edited files may use
210
+ `.jsonc`.
211
+
212
+ Other management commands:
213
+
214
+ ```bash
215
+ npx mcp-compress-router list # list all servers + auth status
216
+ npx mcp-compress-router get my-http # show one server's config
217
+ npx mcp-compress-router remove my-http # remove a server
218
+ ```
219
+
220
+ ### Per-Server Enable/Disable
221
+
222
+ Every server entry accepts an optional `enabled` boolean. When set to
223
+ `false`, the router skips that server entirely at startup — no process
224
+ spawn, no network connection, no discovery — and it is absent from the
225
+ `get_tool_schema` catalog. All configuration is preserved so the server
226
+ can be turned back on instantly. Omitting `enabled` (the default) means
227
+ enabled, keeping `mcp.json` clean and fully backward compatible.
228
+
229
+ Toggle it from the CLI without touching the rest of the config:
230
+
231
+ ```bash
232
+ npx mcp-compress-router disable github # writes "enabled": false
233
+ npx mcp-compress-router enable github # removes the field
234
+ ```
235
+
236
+ You can also set it at creation time:
237
+
238
+ ```bash
239
+ npx mcp-compress-router add archive --disabled -- npx -y server-archive
240
+ ```
241
+
242
+ ### Per-Server Tool Selection
243
+
244
+ Two optional fields control which of a server's advertised tools are
245
+ exposed to the LLM. Both are arrays of glob patterns
246
+ ([picomatch](https://github.com/micromatch/picomatch) syntax: `*`, `?`,
247
+ `{a,b}`, `[abc]`) matched against bare tool names:
248
+
249
+ - **`allowedTools`** — when present, only matching tools are exposed.
250
+ An empty array (`[]`) exposes *no* tools (handy for staging a server
251
+ while you build the list).
252
+ - **`disabledTools`** — removes matching tools from whatever would
253
+ otherwise be exposed. The denylist wins: a tool matching both lists
254
+ is blocked.
255
+
256
+ Filtered tools are hidden from the catalog *and* hard-rejected by
257
+ `invoke_tool`, so even an LLM that guesses a filtered name cannot
258
+ reach the downstream server.
259
+
260
+ ```jsonc
261
+ "dangerous": {
262
+ "type": "stdio",
263
+ "command": "npx",
264
+ "args": ["-y", "@some/mcp-server"],
265
+ "allowedTools": ["list_issues", "get_pull_request"],
266
+ "disabledTools": ["*_delete"]
267
+ }
268
+ ```
269
+
270
+ A pattern that matches no real tool is not an error — the router logs a
271
+ warning (visible with `-v`) and continues. A malformed pattern is a
272
+ hard error at startup. Set filters at creation time with repeatable
273
+ flags:
274
+
275
+ ```bash
276
+ npx mcp-compress-router add github \
277
+ --allowed-tools list_issues \
278
+ --allowed-tools get_pull_request \
279
+ -- npx -y server-github
280
+ ```
281
+
282
+ ### Inspecting Tools
283
+
284
+ To see exactly which tools a server advertises — and which are
285
+ `[exposed]` or `[filtered]` under your current selection — connect to
286
+ it live without starting the full router:
287
+
288
+ ```bash
289
+ npx mcp-compress-router tools github
290
+ ```
291
+
292
+ This works regardless of the server's `enabled` state (inspecting a
293
+ disabled server is the primary way to build its allowlist). For HTTP
294
+ servers, stored OAuth credentials and `oauth` overrides are reused. If
295
+ the server cannot be reached or is missing required auth, the command
296
+ exits non-zero with a clear error and prints no partial list.
297
+
298
+ ### OAuth
299
+
300
+ HTTP servers that require OAuth are supported. When you `add` an HTTP
301
+ server, the router probes it for OAuth metadata and starts the login
302
+ flow automatically if OAuth is advertised. You can also trigger it
303
+ manually:
304
+
305
+ ```bash
306
+ npx mcp-compress-router login my-http
307
+ ```
308
+
309
+ This opens your browser to complete the authorization-code flow. Tokens
310
+ are stored in a separate `credentials.json` in the same directory as
311
+ `mcp.json` (with `0600` permissions on Unix), so you can safely
312
+ share or version-control `mcp.json` without exposing tokens. Add
313
+ `credentials.json` to your `.gitignore`.
314
+
315
+ By default the router uses
316
+ [Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591).
317
+ If your server requires a pre-registered client, add an `oauth` block to
318
+ the server entry (in `mcp.json`):
319
+
320
+ ```jsonc
321
+ "my-http": {
322
+ "type": "http",
323
+ "url": "https://example.com/mcp",
324
+ "oauth": {
325
+ "clientId": "${MY_CLIENT_ID}",
326
+ "clientSecret": "${MY_CLIENT_SECRET}",
327
+ "scope": "read write"
328
+ }
329
+ }
330
+ ```
331
+
332
+ Only `clientId` is required; `clientSecret` and `scope` are optional.
333
+
334
+ #### Redirect URL
335
+
336
+ During `login` the router starts a temporary local HTTP server and uses
337
+ a loopback redirect URI (per [RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252)):
338
+
339
+ ```text
340
+ http://localhost:<port>/mcp-compress-router/oauth-callback
341
+ ```
342
+
343
+ `<port>` is chosen by the OS at login time, so there is no fixed port to
344
+ register. When a provider requires a pre-registered redirect URI,
345
+ register the loopback form **without a port**:
346
+
347
+ ```text
348
+ http://localhost/mcp-compress-router/oauth-callback
349
+ ```
350
+
351
+ Most providers (GitHub included) match the scheme, host, and path and
352
+ ignore the port on `localhost`. If your provider demands a redirect URI
353
+ with an **exact port**, pin it with `--port`:
354
+
355
+ ```bash
356
+ npx mcp-compress-router login my-http --port 8765
357
+ ```
358
+
359
+ This binds the callback server to `8765`, so the redirect URI becomes
360
+ `http://localhost:8765/mcp-compress-router/oauth-callback` — register
361
+ that exact URL with the provider. To reuse the same port on every
362
+ `login`, persist it in the server's `oauth` block instead of passing the
363
+ flag each time:
364
+
365
+ ```jsonc
366
+ "my-http": {
367
+ "type": "http",
368
+ "url": "https://example.com/mcp",
369
+ "oauth": { "clientId": "${ID}", "callbackPort": 8765 }
370
+ }
371
+ ```
372
+
373
+ `--port` overrides `oauth.callbackPort` for a single run. Pass `--port 0`
374
+ to force an OS-assigned port even when `oauth.callbackPort` is set.
375
+
376
+ #### GitHub MCP (special case)
377
+
378
+ The official GitHub MCP server at
379
+ `https://api.githubcopilot.com/mcp` advertises OAuth but does **not**
380
+ support Dynamic Client Registration, so you must pre-register a GitHub
381
+ OAuth App and pass its credentials via the `oauth` block. GitHub also
382
+ requires that the OAuth App be installed to the repositories and
383
+ organizations you want the MCP to access.
384
+
385
+ 1. **Create a GitHub OAuth App.**
386
+ Open <https://github.com/settings/developers> → *New OAuth App* (or
387
+ *Register an application*). Give it any name and homepage URL.
388
+ 2. **Configure the callback URL.**
389
+ Set the *Authorization callback URL* to:
390
+ `http://localhost/mcp-compress-router/oauth-callback`
391
+ 3. **Add the GitHub MCP server by URL.**
392
+
393
+ ```bash
394
+ npx mcp-compress-router add github https://api.githubcopilot.com/mcp
395
+ ```
396
+
397
+ 4. **Set `oauth` credentials in `mcp.json`.**
398
+ Copy the Client ID and generate a Client Secret, then put them in the
399
+ server entry (use variable expansion to keep secrets out of the
400
+ file):
401
+
402
+ ```jsonc
403
+ "github": {
404
+ "type": "http",
405
+ "url": "https://api.githubcopilot.com/mcp",
406
+ "oauth": {
407
+ "clientId": "${GITHUB_OAUTH_CLIENT_ID}",
408
+ "clientSecret": "${GITHUB_OAUTH_CLIENT_SECRET}",
409
+ "scope": "repo read:org"
410
+ }
411
+ }
412
+ ```
413
+
414
+ Request only the scopes the tools you need require; `repo read:org`
415
+ covers the common repo and organization operations. Put the actual
416
+ values in your `.env` file (see
417
+ [Secrets and Variable Expansion](#secrets-and-variable-expansion)).
418
+ 5. **Run the login command.**
419
+
420
+ ```bash
421
+ npx mcp-compress-router login github
422
+ ```
423
+
424
+ Your browser opens to authorize. After you approve, tokens are stored
425
+ in `credentials.json` and the router can call GitHub MCP tools.
426
+
427
+ > **Note:** if you used a *GitHub App* (not a classic OAuth App), the
428
+ > App must be installed to the accounts/repos you want to access before
429
+ > login will succeed, and its client secret is generated under *General*
430
+ > → *Generate a new client secret*.
431
+
432
+ Other OAuth commands:
433
+
434
+ ```bash
435
+ npx mcp-compress-router logout my-http # remove stored credentials
436
+ ```
437
+
438
+ For headless or CI environments, override the browser with the
439
+ `MCP_COMPRESS_ROUTER_BROWSER` environment variable. The authorization
440
+ URL is appended as a single final argument (no shell):
441
+
442
+ ```bash
443
+ MCP_COMPRESS_ROUTER_BROWSER="node /path/to/headless-browser.js" \
444
+ npx mcp-compress-router login my-http
445
+ ```
446
+
447
+ The default login timeout is 120 seconds; override it with
448
+ `MCP_COMPRESS_ROUTER_LOGIN_TIMEOUT_MS`.
449
+
450
+ ### Custom Headers
451
+
452
+ For HTTP servers that authenticate with a static API key or bearer
453
+ token instead of OAuth, use the `headers` field. You can set it via the
454
+ CLI or directly in `mcp.json`:
455
+
456
+ ```bash
457
+ npx mcp-compress-router add my-http \
458
+ --header "Authorization: Bearer mytoken" \
459
+ --header "X-Custom: value" \
460
+ https://example.com/mcp
461
+ ```
462
+
463
+ ```jsonc
464
+ "my-http": {
465
+ "type": "http",
466
+ "url": "https://example.com/mcp",
467
+ "headers": {
468
+ "Authorization": "Bearer ${MY_SERVER_TOKEN}",
469
+ "X-Custom": "value"
470
+ }
471
+ }
472
+ ```
473
+
474
+ Header values support
475
+ [variable expansion](#secrets-and-variable-expansion), so you can keep
476
+ the actual token out of the config file.
477
+
478
+ ### Secrets and Variable Expansion
479
+
480
+ Every string field in a server entry (`command`, `args`, `env`,
481
+ `headers`, `url`, `oauth.*`) is expanded against the process
482
+ environment at load time. Two syntaxes are supported:
483
+
484
+ | Syntax | Behavior |
485
+ | --- | --- |
486
+ | `${VAR}` | Replaced with the value of `VAR`. Throws if unset. |
487
+ | `${VAR:-default}` | Replaced with `VAR` when set and non-empty, otherwise `default`. |
488
+
489
+ Put your secrets in a `.env` file next to `mcp.json`:
490
+
491
+ ```bash
492
+ # <config directory>/.env
493
+ GITHUB_PERSONAL_TOKEN=ghp_abc123
494
+ MY_SERVER_TOKEN=secret-token
495
+ ```
496
+
497
+ Shell environment variables always take precedence over `.env` values.
498
+
499
+ ## Connecting Coding Agents
500
+
501
+ Once your downstream servers are configured, connect your agent to the
502
+ router the same way you would connect any other MCP server — by
503
+ pointing it at `npx mcp-compress-router`. The examples below assume the
504
+ default [config location](#config-file-location); pass `-c <path>` if
505
+ you use a custom one.
506
+
507
+ ### Opencode
508
+
509
+ Add the router to your `opencode.json` under `mcp`:
510
+
511
+ ```json
512
+ {
513
+ "$schema": "https://opencode.ai/config.json",
514
+ "mcp": {
515
+ "compress-router": {
516
+ "type": "local",
517
+ "command": ["npx", "-y", "mcp-compress-router"],
518
+ "enabled": true
519
+ }
520
+ }
521
+ }
522
+ ```
523
+
524
+ ### Claude Code
525
+
526
+ Add this to a project-level `.mcp.json` in your workspace root, or to
527
+ your user-level config (applies to every project):
528
+
529
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
530
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
531
+ - **Linux:** `~/.config/Claude/claude_desktop_config.json`
532
+
533
+ ```json
534
+ {
535
+ "mcpServers": {
536
+ "compress-router": {
537
+ "command": "npx",
538
+ "args": ["-y", "mcp-compress-router"]
539
+ }
540
+ }
541
+ }
542
+ ```
543
+
544
+ ### Codex
545
+
546
+ Add a `[mcp_servers.compress-router]` table to your Codex config (note
547
+ the snake_case key). The config path is `~/.codex/config.toml` on
548
+ macOS/Linux, or `%USERPROFILE%\.codex\config.toml` on Windows; you can
549
+ also scope it to a single project via `.codex/config.toml` in trusted
550
+ projects.
551
+
552
+ ```toml
553
+ [mcp_servers.compress-router]
554
+ command = "npx"
555
+ args = ["-y", "mcp-compress-router"]
556
+ enabled = true
557
+ ```
558
+
559
+ ### GitHub Copilot
560
+
561
+ Add this to `.vscode/mcp.json` in your workspace (project-level, applies
562
+ only to that workspace), or to your **user-level** MCP settings which
563
+ apply across every workspace: open the Command Palette →
564
+ `Preferences: Open User Settings (JSON)` and add the same `servers`
565
+ block under the `mcp` key. Project-level and user-level entries are
566
+ merged, with project-level taking precedence.
567
+
568
+ ```json
569
+ {
570
+ "servers": {
571
+ "compress-router": {
572
+ "command": "npx",
573
+ "args": ["-y", "mcp-compress-router"]
574
+ }
575
+ }
576
+ }
577
+ ```
578
+
579
+ ## How It Works
580
+
581
+ Once connected, the agent sees exactly **two tools**:
582
+
583
+ - **`get_tool_schema(server, tools)`** — Retrieves the JSON parameter
584
+ schema for one or more tools on a downstream MCP server. The tool's
585
+ description includes a compact listing of all servers and their
586
+ available tool names.
587
+ - **`invoke_tool(server, tool, arguments)`** — Forwards a tool call to
588
+ the downstream MCP server and returns the result.
589
+
590
+ The typical workflow:
591
+
592
+ 1. The agent reads the compact catalog from the `get_tool_schema`
593
+ description and identifies which tools it needs.
594
+ 2. It calls `get_tool_schema` to learn the exact parameters.
595
+ 3. It calls `invoke_tool` to execute a tool, validated against the
596
+ cached schema.
597
+
598
+ This replaces thousands of tokens of tool listings with a compact ~900
599
+ token catalog, regardless of how many downstream servers you have.
600
+
601
+ For the full configuration and environment variable reference, see
602
+ [configuration.md](docs/configuration.md).