mcp-compress-router 1.0.0 → 1.1.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 +451 -0
- package/build/cli/add-command.js +64 -13
- package/build/cli/disable-command.js +29 -0
- package/build/cli/enable-command.js +35 -0
- package/build/cli/index.js +3 -0
- package/build/cli/list-command.js +45 -3
- package/build/cli/register-commands.js +197 -0
- package/build/cli/router-runner.js +89 -0
- package/build/cli/tools-command.js +108 -0
- package/build/index.js +3 -207
- package/build/services/catalog.js +49 -14
- package/build/services/config.js +59 -2
- package/build/services/discovery.js +71 -2
- package/build/services/index.js +1 -1
- package/build/utils/index.js +3 -0
- package/build/utils/tool-filter.js +87 -0
- package/build/utils/validate-glob.js +22 -0
- package/package.json +7 -3
- package/build/catalog.js +0 -54
- package/build/cli/config-path-note.js +0 -21
- package/build/config.js +0 -80
- package/build/discovery.js +0 -39
- package/build/text-format.js +0 -24
- package/build/types.js +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# MCP Compressing Router
|
|
2
2
|
|
|
3
|
+
[](https://github.com/ameshkov/mcp-compress-router/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/mcp-compress-router)
|
|
5
|
+
[](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,28 @@
|
|
|
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
|
+
- [Custom Headers](#custom-headers)
|
|
31
|
+
- [Secrets and Variable Expansion](#secrets-and-variable-expansion)
|
|
32
|
+
- [Connecting Coding Agents](#connecting-coding-agents)
|
|
33
|
+
- [Opencode](#opencode)
|
|
34
|
+
- [Claude Code](#claude-code)
|
|
35
|
+
- [Codex](#codex)
|
|
36
|
+
- [GitHub Copilot](#github-copilot)
|
|
37
|
+
- [How It Works](#how-it-works)
|
|
38
|
+
|
|
13
39
|
## The Problem
|
|
14
40
|
|
|
15
41
|
When you have multiple MCPs every request to the LLM will include ALL their
|
|
@@ -49,3 +75,428 @@ an average coding session will be about **$0.032175** so we saved about
|
|
|
49
75
|
|
|
50
76
|
This is just a basic example with just 3 MCP servers, the more MCP servers you
|
|
51
77
|
have, the more you save.
|
|
78
|
+
|
|
79
|
+
## Prerequisites
|
|
80
|
+
|
|
81
|
+
- **Node.js 24 or later** — the router runs on Node.js and is launched
|
|
82
|
+
via `npx`, so no separate install step is needed.
|
|
83
|
+
- **A coding agent that supports stdio MCP servers** — this covers
|
|
84
|
+
virtually every modern coding agent (opencode, Claude Code, Codex,
|
|
85
|
+
GitHub Copilot, Cursor, etc.). The router exposes itself as a single
|
|
86
|
+
stdio MCP server, so any agent that can spawn a local MCP process
|
|
87
|
+
works.
|
|
88
|
+
|
|
89
|
+
## Quick Start
|
|
90
|
+
|
|
91
|
+
The router is published on npm as
|
|
92
|
+
[`mcp-compress-router`](https://www.npmjs.com/package/mcp-compress-router).
|
|
93
|
+
You do not need to install it — just run it with `npx`:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx mcp-compress-router add github -- npx -y @modelcontextprotocol/server-github
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This registers a downstream MCP server named `github` and writes it to
|
|
100
|
+
your [config file](#config-file-location). Repeat for every MCP server you
|
|
101
|
+
want to compress.
|
|
102
|
+
|
|
103
|
+
Then point your [coding agent](#connecting-coding-agents) at the router:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npx mcp-compress-router
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
When started without a subcommand, the router runs the MCP server over
|
|
110
|
+
stdio and exposes exactly two tools (`get_tool_schema`,
|
|
111
|
+
`invoke_tool`) to the agent.
|
|
112
|
+
|
|
113
|
+
## Configuration
|
|
114
|
+
|
|
115
|
+
The router reads its configuration from a single JSON(C) file that lists
|
|
116
|
+
every downstream MCP server to compress. You can edit this file by hand
|
|
117
|
+
or use the `add` / `remove` / `get` / `list` CLI commands.
|
|
118
|
+
|
|
119
|
+
### Config File Location
|
|
120
|
+
|
|
121
|
+
By default, the config file lives in a platform-specific directory
|
|
122
|
+
(`mcp.jsonc` is preferred over `mcp.json` when both exist):
|
|
123
|
+
|
|
124
|
+
- **Windows:** `%APPDATA%\mcp-compress-router\`
|
|
125
|
+
- **macOS:** `~/Library/Application Support/mcp-compress-router/`
|
|
126
|
+
- **Linux:** `~/.local/share/mcp-compress-router/`
|
|
127
|
+
|
|
128
|
+
You can override this with:
|
|
129
|
+
|
|
130
|
+
- The `-c, --config <path>` flag on any command, or
|
|
131
|
+
- The `MCP_COMPRESS_ROUTER_HOME` environment variable (points to a
|
|
132
|
+
directory containing the config file).
|
|
133
|
+
|
|
134
|
+
If the file does not exist when a management command runs, it is created
|
|
135
|
+
automatically with an empty `{ "mcpServers": {} }` body.
|
|
136
|
+
|
|
137
|
+
A `.env` file in the **same directory** is loaded automatically at
|
|
138
|
+
startup, so you can keep secrets out of the config (see
|
|
139
|
+
[Secrets and Variable Expansion](#secrets-and-variable-expansion)).
|
|
140
|
+
|
|
141
|
+
> **Note on `-c` and credential storage:** when you override the config
|
|
142
|
+
> path with `-c /some/dir/mcp.json`, both `credentials.json` (OAuth
|
|
143
|
+
> tokens) and `mcp.json` live in `/some/dir/` — i.e. next to the config
|
|
144
|
+
> file you specified. The `.env` file, however, is loaded from the
|
|
145
|
+
> [configuration directory](#config-file-location) resolved by
|
|
146
|
+
> `MCP_COMPRESS_ROUTER_HOME` or the platform default, *not* from beside
|
|
147
|
+
> the explicit `-c` path. To co-locate `.env` with a custom config, set
|
|
148
|
+
> `MCP_COMPRESS_ROUTER_HOME` to the same directory.
|
|
149
|
+
|
|
150
|
+
### Adding Downstream Servers
|
|
151
|
+
|
|
152
|
+
Use the `add` command to register a downstream MCP server.
|
|
153
|
+
|
|
154
|
+
A good description helps the LLM route requests to the correct server.
|
|
155
|
+
When several servers are compressed behind the router, the model sees
|
|
156
|
+
each server's name, its description, and a list of tool names in the
|
|
157
|
+
`get_tool_schema` catalog. A clear description (e.g. *"GitHub API tools
|
|
158
|
+
for issues, PRs, and repos"*) steers the model toward the right server
|
|
159
|
+
far better than a bare name.
|
|
160
|
+
|
|
161
|
+
**stdio server** (a local process):
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npx mcp-compress-router add github --description "GitHub API tools" \
|
|
165
|
+
-- npx -y @modelcontextprotocol/server-github
|
|
166
|
+
|
|
167
|
+
# With environment variables
|
|
168
|
+
npx mcp-compress-router add github -e GITHUB_PERSONAL_TOKEN=ghp_xxx \
|
|
169
|
+
--description "GitHub API tools" \
|
|
170
|
+
-- npx -y @modelcontextprotocol/server-github
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**HTTP server** (a remote endpoint; transport auto-detected from the
|
|
174
|
+
URL):
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
npx mcp-compress-router add my-http https://localhost:3100/mcp
|
|
178
|
+
|
|
179
|
+
# With a custom header
|
|
180
|
+
npx mcp-compress-router add my-http \
|
|
181
|
+
--header "Authorization: Bearer mytoken" \
|
|
182
|
+
https://localhost:3100/mcp
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
This produces a config file that looks like:
|
|
186
|
+
|
|
187
|
+
```jsonc
|
|
188
|
+
{
|
|
189
|
+
"mcpServers": {
|
|
190
|
+
"github": {
|
|
191
|
+
"type": "stdio",
|
|
192
|
+
"command": "npx",
|
|
193
|
+
"args": ["-y", "@modelcontextprotocol/server-github"],
|
|
194
|
+
"env": { "GITHUB_PERSONAL_TOKEN": "ghp_xxx" },
|
|
195
|
+
"description": "GitHub API tools"
|
|
196
|
+
},
|
|
197
|
+
"my-http": {
|
|
198
|
+
"type": "http",
|
|
199
|
+
"url": "https://localhost:3100/mcp",
|
|
200
|
+
"headers": { "Authorization": "Bearer mytoken" }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Both `.json` and `.jsonc` (JSON with comments and trailing commas) are
|
|
207
|
+
supported. CLI commands write plain `.json`; hand-edited files may use
|
|
208
|
+
`.jsonc`.
|
|
209
|
+
|
|
210
|
+
Other management commands:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npx mcp-compress-router list # list all servers + auth status
|
|
214
|
+
npx mcp-compress-router get my-http # show one server's config
|
|
215
|
+
npx mcp-compress-router remove my-http # remove a server
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Per-Server Enable/Disable
|
|
219
|
+
|
|
220
|
+
Every server entry accepts an optional `enabled` boolean. When set to
|
|
221
|
+
`false`, the router skips that server entirely at startup — no process
|
|
222
|
+
spawn, no network connection, no discovery — and it is absent from the
|
|
223
|
+
`get_tool_schema` catalog. All configuration is preserved so the server
|
|
224
|
+
can be turned back on instantly. Omitting `enabled` (the default) means
|
|
225
|
+
enabled, keeping `mcp.json` clean and fully backward compatible.
|
|
226
|
+
|
|
227
|
+
Toggle it from the CLI without touching the rest of the config:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
npx mcp-compress-router disable github # writes "enabled": false
|
|
231
|
+
npx mcp-compress-router enable github # removes the field
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
You can also set it at creation time:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
npx mcp-compress-router add archive --disabled -- npx -y server-archive
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Per-Server Tool Selection
|
|
241
|
+
|
|
242
|
+
Two optional fields control which of a server's advertised tools are
|
|
243
|
+
exposed to the LLM. Both are arrays of glob patterns
|
|
244
|
+
([picomatch](https://github.com/micromatch/picomatch) syntax: `*`, `?`,
|
|
245
|
+
`{a,b}`, `[abc]`) matched against bare tool names:
|
|
246
|
+
|
|
247
|
+
- **`allowedTools`** — when present, only matching tools are exposed.
|
|
248
|
+
An empty array (`[]`) exposes *no* tools (handy for staging a server
|
|
249
|
+
while you build the list).
|
|
250
|
+
- **`disabledTools`** — removes matching tools from whatever would
|
|
251
|
+
otherwise be exposed. The denylist wins: a tool matching both lists
|
|
252
|
+
is blocked.
|
|
253
|
+
|
|
254
|
+
Filtered tools are hidden from the catalog *and* hard-rejected by
|
|
255
|
+
`invoke_tool`, so even an LLM that guesses a filtered name cannot
|
|
256
|
+
reach the downstream server.
|
|
257
|
+
|
|
258
|
+
```jsonc
|
|
259
|
+
"dangerous": {
|
|
260
|
+
"type": "stdio",
|
|
261
|
+
"command": "npx",
|
|
262
|
+
"args": ["-y", "@some/mcp-server"],
|
|
263
|
+
"allowedTools": ["list_issues", "get_pull_request"],
|
|
264
|
+
"disabledTools": ["*_delete"]
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
A pattern that matches no real tool is not an error — the router logs a
|
|
269
|
+
warning (visible with `-v`) and continues. A malformed pattern is a
|
|
270
|
+
hard error at startup. Set filters at creation time with repeatable
|
|
271
|
+
flags:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
npx mcp-compress-router add github \
|
|
275
|
+
--allowed-tools list_issues \
|
|
276
|
+
--allowed-tools get_pull_request \
|
|
277
|
+
-- npx -y server-github
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Inspecting Tools
|
|
281
|
+
|
|
282
|
+
To see exactly which tools a server advertises — and which are
|
|
283
|
+
`[exposed]` or `[filtered]` under your current selection — connect to
|
|
284
|
+
it live without starting the full router:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
npx mcp-compress-router tools github
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
This works regardless of the server's `enabled` state (inspecting a
|
|
291
|
+
disabled server is the primary way to build its allowlist). For HTTP
|
|
292
|
+
servers, stored OAuth credentials and `oauth` overrides are reused. If
|
|
293
|
+
the server cannot be reached or is missing required auth, the command
|
|
294
|
+
exits non-zero with a clear error and prints no partial list.
|
|
295
|
+
|
|
296
|
+
### OAuth
|
|
297
|
+
|
|
298
|
+
HTTP servers that require OAuth are supported. When you `add` an HTTP
|
|
299
|
+
server, the router probes it for OAuth metadata and starts the login
|
|
300
|
+
flow automatically if OAuth is advertised. You can also trigger it
|
|
301
|
+
manually:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
npx mcp-compress-router login my-http
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
This opens your browser to complete the authorization-code flow. Tokens
|
|
308
|
+
are stored in a separate `credentials.json` in the same directory as
|
|
309
|
+
`mcp.json` (with `0600` permissions on Unix), so you can safely
|
|
310
|
+
share or version-control `mcp.json` without exposing tokens. Add
|
|
311
|
+
`credentials.json` to your `.gitignore`.
|
|
312
|
+
|
|
313
|
+
By default the router uses
|
|
314
|
+
[Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591).
|
|
315
|
+
If your server requires a pre-registered client, add an `oauth` block to
|
|
316
|
+
the server entry (in `mcp.json`):
|
|
317
|
+
|
|
318
|
+
```jsonc
|
|
319
|
+
"my-http": {
|
|
320
|
+
"type": "http",
|
|
321
|
+
"url": "https://example.com/mcp",
|
|
322
|
+
"oauth": {
|
|
323
|
+
"clientId": "${MY_CLIENT_ID}",
|
|
324
|
+
"clientSecret": "${MY_CLIENT_SECRET}",
|
|
325
|
+
"scope": "read write"
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Only `clientId` is required; `clientSecret` and `scope` are optional.
|
|
331
|
+
|
|
332
|
+
Other OAuth commands:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
npx mcp-compress-router logout my-http # remove stored credentials
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
For headless or CI environments, override the browser with the
|
|
339
|
+
`MCP_COMPRESS_ROUTER_BROWSER` environment variable. The authorization
|
|
340
|
+
URL is appended as a single final argument (no shell):
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
MCP_COMPRESS_ROUTER_BROWSER="node /path/to/headless-browser.js" \
|
|
344
|
+
npx mcp-compress-router login my-http
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
The default login timeout is 120 seconds; override it with
|
|
348
|
+
`MCP_COMPRESS_ROUTER_LOGIN_TIMEOUT_MS`.
|
|
349
|
+
|
|
350
|
+
### Custom Headers
|
|
351
|
+
|
|
352
|
+
For HTTP servers that authenticate with a static API key or bearer
|
|
353
|
+
token instead of OAuth, use the `headers` field. You can set it via the
|
|
354
|
+
CLI or directly in `mcp.json`:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
npx mcp-compress-router add my-http \
|
|
358
|
+
--header "Authorization: Bearer mytoken" \
|
|
359
|
+
--header "X-Custom: value" \
|
|
360
|
+
https://example.com/mcp
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
```jsonc
|
|
364
|
+
"my-http": {
|
|
365
|
+
"type": "http",
|
|
366
|
+
"url": "https://example.com/mcp",
|
|
367
|
+
"headers": {
|
|
368
|
+
"Authorization": "Bearer ${MY_SERVER_TOKEN}",
|
|
369
|
+
"X-Custom": "value"
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Header values support
|
|
375
|
+
[variable expansion](#secrets-and-variable-expansion), so you can keep
|
|
376
|
+
the actual token out of the config file.
|
|
377
|
+
|
|
378
|
+
### Secrets and Variable Expansion
|
|
379
|
+
|
|
380
|
+
Every string field in a server entry (`command`, `args`, `env`,
|
|
381
|
+
`headers`, `url`, `oauth.*`) is expanded against the process
|
|
382
|
+
environment at load time. Two syntaxes are supported:
|
|
383
|
+
|
|
384
|
+
| Syntax | Behavior |
|
|
385
|
+
| --- | --- |
|
|
386
|
+
| `${VAR}` | Replaced with the value of `VAR`. Throws if unset. |
|
|
387
|
+
| `${VAR:-default}` | Replaced with `VAR` when set and non-empty, otherwise `default`. |
|
|
388
|
+
|
|
389
|
+
Put your secrets in a `.env` file next to `mcp.json`:
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
# <config directory>/.env
|
|
393
|
+
GITHUB_PERSONAL_TOKEN=ghp_abc123
|
|
394
|
+
MY_SERVER_TOKEN=secret-token
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Shell environment variables always take precedence over `.env` values.
|
|
398
|
+
|
|
399
|
+
## Connecting Coding Agents
|
|
400
|
+
|
|
401
|
+
Once your downstream servers are configured, connect your agent to the
|
|
402
|
+
router the same way you would connect any other MCP server — by
|
|
403
|
+
pointing it at `npx mcp-compress-router`. The examples below assume the
|
|
404
|
+
default [config location](#config-file-location); pass `-c <path>` if
|
|
405
|
+
you use a custom one.
|
|
406
|
+
|
|
407
|
+
### Opencode
|
|
408
|
+
|
|
409
|
+
Add the router to your `opencode.json` under `mcp`:
|
|
410
|
+
|
|
411
|
+
```json
|
|
412
|
+
{
|
|
413
|
+
"$schema": "https://opencode.ai/config.json",
|
|
414
|
+
"mcp": {
|
|
415
|
+
"compress-router": {
|
|
416
|
+
"type": "local",
|
|
417
|
+
"command": ["npx", "-y", "mcp-compress-router"],
|
|
418
|
+
"enabled": true
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Claude Code
|
|
425
|
+
|
|
426
|
+
Add this to a project-level `.mcp.json` in your workspace root, or to
|
|
427
|
+
your user-level config (applies to every project):
|
|
428
|
+
|
|
429
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
430
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
431
|
+
- **Linux:** `~/.config/Claude/claude_desktop_config.json`
|
|
432
|
+
|
|
433
|
+
```json
|
|
434
|
+
{
|
|
435
|
+
"mcpServers": {
|
|
436
|
+
"compress-router": {
|
|
437
|
+
"command": "npx",
|
|
438
|
+
"args": ["-y", "mcp-compress-router"]
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Codex
|
|
445
|
+
|
|
446
|
+
Add a `[mcp_servers.compress-router]` table to your Codex config (note
|
|
447
|
+
the snake_case key). The config path is `~/.codex/config.toml` on
|
|
448
|
+
macOS/Linux, or `%USERPROFILE%\.codex\config.toml` on Windows; you can
|
|
449
|
+
also scope it to a single project via `.codex/config.toml` in trusted
|
|
450
|
+
projects.
|
|
451
|
+
|
|
452
|
+
```toml
|
|
453
|
+
[mcp_servers.compress-router]
|
|
454
|
+
command = "npx"
|
|
455
|
+
args = ["-y", "mcp-compress-router"]
|
|
456
|
+
enabled = true
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### GitHub Copilot
|
|
460
|
+
|
|
461
|
+
Add this to `.vscode/mcp.json` in your workspace (project-level, applies
|
|
462
|
+
only to that workspace), or to your **user-level** MCP settings which
|
|
463
|
+
apply across every workspace: open the Command Palette →
|
|
464
|
+
`Preferences: Open User Settings (JSON)` and add the same `servers`
|
|
465
|
+
block under the `mcp` key. Project-level and user-level entries are
|
|
466
|
+
merged, with project-level taking precedence.
|
|
467
|
+
|
|
468
|
+
```json
|
|
469
|
+
{
|
|
470
|
+
"servers": {
|
|
471
|
+
"compress-router": {
|
|
472
|
+
"command": "npx",
|
|
473
|
+
"args": ["-y", "mcp-compress-router"]
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## How It Works
|
|
480
|
+
|
|
481
|
+
Once connected, the agent sees exactly **two tools**:
|
|
482
|
+
|
|
483
|
+
- **`get_tool_schema(server, tools)`** — Retrieves the JSON parameter
|
|
484
|
+
schema for one or more tools on a downstream MCP server. The tool's
|
|
485
|
+
description includes a compact listing of all servers and their
|
|
486
|
+
available tool names.
|
|
487
|
+
- **`invoke_tool(server, tool, arguments)`** — Forwards a tool call to
|
|
488
|
+
the downstream MCP server and returns the result.
|
|
489
|
+
|
|
490
|
+
The typical workflow:
|
|
491
|
+
|
|
492
|
+
1. The agent reads the compact catalog from the `get_tool_schema`
|
|
493
|
+
description and identifies which tools it needs.
|
|
494
|
+
2. It calls `get_tool_schema` to learn the exact parameters.
|
|
495
|
+
3. It calls `invoke_tool` to execute a tool, validated against the
|
|
496
|
+
cached schema.
|
|
497
|
+
|
|
498
|
+
This replaces thousands of tokens of tool listings with a compact ~900
|
|
499
|
+
token catalog, regardless of how many downstream servers you have.
|
|
500
|
+
|
|
501
|
+
For the full configuration and environment variable reference, see
|
|
502
|
+
[configuration.md](docs/configuration.md).
|
package/build/cli/add-command.js
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
1
|
import { ensureConfigDir, readConfigFile, writeConfigFile, readCredentials, writeCredentials, } from './config-io.js';
|
|
2
|
+
import { validateGlobPattern } from '../utils/index.js';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Validates every glob pattern in an optional tool list, throwing with
|
|
5
|
+
* the field name and offending pattern on the first invalid entry.
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
7
|
+
* @param field - "allowedTools" or "disabledTools" (for the message).
|
|
8
|
+
* @param patterns - Patterns to validate; undefined/empty is a no-op.
|
|
9
|
+
* @throws If any pattern is rejected by picomatch.
|
|
10
|
+
*/
|
|
11
|
+
function validateToolListPatterns(field, patterns) {
|
|
12
|
+
if (!patterns || patterns.length === 0)
|
|
13
|
+
return;
|
|
14
|
+
for (const pattern of patterns) {
|
|
15
|
+
try {
|
|
16
|
+
validateGlobPattern(pattern);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
20
|
+
throw new Error(`Invalid "${field}" pattern "${pattern}": ${reason}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Builds the raw server entry from parsed CLI options, including
|
|
26
|
+
* transport auto-detection, env/headers, description, and the optional
|
|
27
|
+
* enable/filter fields.
|
|
8
28
|
*
|
|
9
|
-
* @param configPath - Absolute path to the mcp.json file.
|
|
10
29
|
* @param opts - Parsed CLI options.
|
|
11
|
-
* @returns
|
|
12
|
-
* @throws If the server name already exists.
|
|
30
|
+
* @returns The constructed raw server entry and its resolved transport type.
|
|
13
31
|
*/
|
|
14
|
-
|
|
15
|
-
await ensureConfigDir(configPath);
|
|
16
|
-
const servers = await readConfigFile(configPath);
|
|
17
|
-
if (opts.name in servers) {
|
|
18
|
-
throw new Error(`Server "${opts.name}" already exists. Use "remove ${opts.name}" first to replace it.`);
|
|
19
|
-
}
|
|
32
|
+
function buildServerEntry(opts) {
|
|
20
33
|
// Auto-detect HTTP from URL pattern
|
|
21
34
|
const isUrl = opts.commandOrUrl.startsWith('http://') || opts.commandOrUrl.startsWith('https://');
|
|
22
35
|
const type = isUrl ? 'http' : opts.transport;
|
|
@@ -36,6 +49,44 @@ export async function handleAdd(configPath, opts) {
|
|
|
36
49
|
entry.env = opts.env;
|
|
37
50
|
}
|
|
38
51
|
}
|
|
52
|
+
if (opts.description) {
|
|
53
|
+
entry.description = opts.description;
|
|
54
|
+
}
|
|
55
|
+
if (opts.disabled) {
|
|
56
|
+
entry.enabled = false;
|
|
57
|
+
}
|
|
58
|
+
if (opts.allowedTools && opts.allowedTools.length > 0) {
|
|
59
|
+
entry.allowedTools = opts.allowedTools;
|
|
60
|
+
}
|
|
61
|
+
if (opts.disabledTools && opts.disabledTools.length > 0) {
|
|
62
|
+
entry.disabledTools = opts.disabledTools;
|
|
63
|
+
}
|
|
64
|
+
return { entry, type };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Handles the `add <name> <commandOrUrl> [args...]` subcommand.
|
|
68
|
+
*
|
|
69
|
+
* - If commandOrUrl starts with http:// or https://, auto-detects as HTTP.
|
|
70
|
+
* - Otherwise treats it as a stdio command.
|
|
71
|
+
* - Writes the entry to the mcpServers object and saves the config file.
|
|
72
|
+
*
|
|
73
|
+
* @param configPath - Absolute path to the mcp.json file.
|
|
74
|
+
* @param opts - Parsed CLI options.
|
|
75
|
+
* @returns Human-readable confirmation message.
|
|
76
|
+
* @throws If the server name already exists.
|
|
77
|
+
*/
|
|
78
|
+
export async function handleAdd(configPath, opts) {
|
|
79
|
+
if (opts.enabled && opts.disabled) {
|
|
80
|
+
throw new Error('Cannot specify both --enabled and --disabled.');
|
|
81
|
+
}
|
|
82
|
+
validateToolListPatterns('allowedTools', opts.allowedTools);
|
|
83
|
+
validateToolListPatterns('disabledTools', opts.disabledTools);
|
|
84
|
+
await ensureConfigDir(configPath);
|
|
85
|
+
const servers = await readConfigFile(configPath);
|
|
86
|
+
if (opts.name in servers) {
|
|
87
|
+
throw new Error(`Server "${opts.name}" already exists. Use "remove ${opts.name}" first to replace it.`);
|
|
88
|
+
}
|
|
89
|
+
const { entry, type } = buildServerEntry(opts);
|
|
39
90
|
servers[opts.name] = entry;
|
|
40
91
|
await writeConfigFile(configPath, servers);
|
|
41
92
|
let result = `Added server "${opts.name}" (${type}).`;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ensureConfigDir, readConfigFile, writeConfigFile } from './config-io.js';
|
|
2
|
+
/**
|
|
3
|
+
* Handles the `disable <name>` subcommand: sets `enabled: false` on a
|
|
4
|
+
* server entry, preserving every other field. Idempotent — if the server
|
|
5
|
+
* is already disabled, reports it and makes no change. Pure config edit;
|
|
6
|
+
* no network access, probe, or login.
|
|
7
|
+
*
|
|
8
|
+
* @param configPath - Absolute path to the mcp.json file.
|
|
9
|
+
* @param name - Server name to disable.
|
|
10
|
+
* @returns Human-readable confirmation message.
|
|
11
|
+
* @throws If the server name is not found in mcp.json.
|
|
12
|
+
*/
|
|
13
|
+
export async function handleDisable(configPath, name) {
|
|
14
|
+
await ensureConfigDir(configPath);
|
|
15
|
+
const servers = await readConfigFile(configPath);
|
|
16
|
+
if (!(name in servers)) {
|
|
17
|
+
const available = Object.keys(servers);
|
|
18
|
+
const hint = available.length > 0
|
|
19
|
+
? ` Available servers: ${available.join(', ')}`
|
|
20
|
+
: ' No servers configured.';
|
|
21
|
+
throw new Error(`Server "${name}" not found.${hint}`);
|
|
22
|
+
}
|
|
23
|
+
if (servers[name].enabled === false) {
|
|
24
|
+
return `Server "${name}" is already disabled.`;
|
|
25
|
+
}
|
|
26
|
+
servers[name].enabled = false;
|
|
27
|
+
await writeConfigFile(configPath, servers);
|
|
28
|
+
return `Disabled server "${name}".`;
|
|
29
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ensureConfigDir, readConfigFile, writeConfigFile } from './config-io.js';
|
|
2
|
+
/**
|
|
3
|
+
* Handles the `enable <name>` subcommand: removes the `enabled` field
|
|
4
|
+
* from a server entry so it defaults to enabled, preserving every other
|
|
5
|
+
* field. Idempotent — if the server is already enabled (field absent or
|
|
6
|
+
* `true`), reports it and makes no change (a stray `true` is normalized
|
|
7
|
+
* away by deletion). Pure config edit; no network access, probe, or login.
|
|
8
|
+
*
|
|
9
|
+
* @param configPath - Absolute path to the mcp.json file.
|
|
10
|
+
* @param name - Server name to enable.
|
|
11
|
+
* @returns Human-readable confirmation message.
|
|
12
|
+
* @throws If the server name is not found in mcp.json.
|
|
13
|
+
*/
|
|
14
|
+
export async function handleEnable(configPath, name) {
|
|
15
|
+
await ensureConfigDir(configPath);
|
|
16
|
+
const servers = await readConfigFile(configPath);
|
|
17
|
+
if (!(name in servers)) {
|
|
18
|
+
const available = Object.keys(servers);
|
|
19
|
+
const hint = available.length > 0
|
|
20
|
+
? ` Available servers: ${available.join(', ')}`
|
|
21
|
+
: ' No servers configured.';
|
|
22
|
+
throw new Error(`Server "${name}" not found.${hint}`);
|
|
23
|
+
}
|
|
24
|
+
if (servers[name].enabled === undefined || servers[name].enabled === true) {
|
|
25
|
+
// Normalize a stray explicit `true` away so the file stays clean.
|
|
26
|
+
if ('enabled' in servers[name]) {
|
|
27
|
+
delete servers[name].enabled;
|
|
28
|
+
await writeConfigFile(configPath, servers);
|
|
29
|
+
}
|
|
30
|
+
return `Server "${name}" is already enabled.`;
|
|
31
|
+
}
|
|
32
|
+
delete servers[name].enabled;
|
|
33
|
+
await writeConfigFile(configPath, servers);
|
|
34
|
+
return `Enabled server "${name}".`;
|
|
35
|
+
}
|
package/build/cli/index.js
CHANGED
|
@@ -4,3 +4,6 @@ export { handleGet } from './get-command.js';
|
|
|
4
4
|
export { handleList } from './list-command.js';
|
|
5
5
|
export { handleLogin } from './login-command.js';
|
|
6
6
|
export { handleLogout } from './logout-command.js';
|
|
7
|
+
export { handleEnable } from './enable-command.js';
|
|
8
|
+
export { handleDisable } from './disable-command.js';
|
|
9
|
+
export { handleTools } from './tools-command.js';
|