mcp-compress-router 1.3.0 → 1.4.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 +186 -71
- package/build/cli/add-command.js +8 -1
- package/build/cli/get-command.js +6 -0
- package/build/cli/list-command.js +16 -3
- package/build/cli/register-commands.js +2 -0
- package/build/cli/router-runner.js +6 -2
- package/build/services/catalog.js +5 -1
- package/build/services/config.js +35 -2
- package/build/tools/get-tool-schema.js +3 -1
- package/build/utils/argument-names.js +21 -0
- package/build/utils/compression-level.js +24 -0
- package/build/utils/description-truncator.js +29 -0
- package/build/utils/index.js +1 -0
- package/build/utils/text-format.js +57 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,10 +25,12 @@
|
|
|
25
25
|
- [Adding Downstream Servers](#adding-downstream-servers)
|
|
26
26
|
- [Per-Server Enable/Disable](#per-server-enabledisable)
|
|
27
27
|
- [Per-Server Tool Selection](#per-server-tool-selection)
|
|
28
|
+
- [Compression Levels](#compression-levels)
|
|
28
29
|
- [Inspecting Tools](#inspecting-tools)
|
|
29
30
|
- [OAuth](#oauth)
|
|
30
31
|
- [Redirect URL](#redirect-url)
|
|
31
32
|
- [GitHub MCP (special case)](#github-mcp-special-case)
|
|
33
|
+
- [Figma MCP (special case)](#figma-mcp-special-case)
|
|
32
34
|
- [Custom Headers](#custom-headers)
|
|
33
35
|
- [Secrets and Variable Expansion](#secrets-and-variable-expansion)
|
|
34
36
|
- [Connecting Coding Agents](#connecting-coding-agents)
|
|
@@ -37,6 +39,7 @@
|
|
|
37
39
|
- [Codex](#codex)
|
|
38
40
|
- [GitHub Copilot](#github-copilot)
|
|
39
41
|
- [How It Works](#how-it-works)
|
|
42
|
+
- [Aknowledgements](#acknowledgements)
|
|
40
43
|
|
|
41
44
|
## The Problem
|
|
42
45
|
|
|
@@ -95,17 +98,17 @@ The router is published on npm as
|
|
|
95
98
|
You do not need to install it — just run it with `npx`:
|
|
96
99
|
|
|
97
100
|
```bash
|
|
98
|
-
npx mcp-compress-router add
|
|
101
|
+
npx mcp-compress-router@latest add playwright -- npx -y @playwright/mcp
|
|
99
102
|
```
|
|
100
103
|
|
|
101
|
-
This registers a downstream MCP server named `
|
|
102
|
-
your [config file](#config-file-location). Repeat for every MCP server
|
|
103
|
-
want to compress.
|
|
104
|
+
This registers a downstream MCP server named `playwright` and writes it
|
|
105
|
+
to your [config file](#config-file-location). Repeat for every MCP server
|
|
106
|
+
you want to compress.
|
|
104
107
|
|
|
105
108
|
Then point your [coding agent](#connecting-coding-agents) at the router:
|
|
106
109
|
|
|
107
110
|
```bash
|
|
108
|
-
npx mcp-compress-router
|
|
111
|
+
npx mcp-compress-router@latest
|
|
109
112
|
```
|
|
110
113
|
|
|
111
114
|
When started without a subcommand, the router runs the MCP server over
|
|
@@ -163,11 +166,11 @@ far better than a bare name.
|
|
|
163
166
|
**stdio server** (a local process):
|
|
164
167
|
|
|
165
168
|
```bash
|
|
166
|
-
npx mcp-compress-router add github --description "GitHub API tools" \
|
|
169
|
+
npx mcp-compress-router@latest add github --description "GitHub API tools" \
|
|
167
170
|
-- npx -y @modelcontextprotocol/server-github
|
|
168
171
|
|
|
169
172
|
# With environment variables
|
|
170
|
-
npx mcp-compress-router add github -e GITHUB_PERSONAL_TOKEN=ghp_xxx \
|
|
173
|
+
npx mcp-compress-router@latest add github -e GITHUB_PERSONAL_TOKEN=ghp_xxx \
|
|
171
174
|
--description "GitHub API tools" \
|
|
172
175
|
-- npx -y @modelcontextprotocol/server-github
|
|
173
176
|
```
|
|
@@ -176,10 +179,10 @@ npx mcp-compress-router add github -e GITHUB_PERSONAL_TOKEN=ghp_xxx \
|
|
|
176
179
|
URL):
|
|
177
180
|
|
|
178
181
|
```bash
|
|
179
|
-
npx mcp-compress-router add my-http https://localhost:3100/mcp
|
|
182
|
+
npx mcp-compress-router@latest add my-http https://localhost:3100/mcp
|
|
180
183
|
|
|
181
184
|
# With a custom header
|
|
182
|
-
npx mcp-compress-router add my-http \
|
|
185
|
+
npx mcp-compress-router@latest add my-http \
|
|
183
186
|
--header "Authorization: Bearer mytoken" \
|
|
184
187
|
https://localhost:3100/mcp
|
|
185
188
|
```
|
|
@@ -212,9 +215,9 @@ supported. CLI commands write plain `.json`; hand-edited files may use
|
|
|
212
215
|
Other management commands:
|
|
213
216
|
|
|
214
217
|
```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
|
+
npx mcp-compress-router@latest list # list all servers + auth status
|
|
219
|
+
npx mcp-compress-router@latest get my-http # show one server's config
|
|
220
|
+
npx mcp-compress-router@latest remove my-http # remove a server
|
|
218
221
|
```
|
|
219
222
|
|
|
220
223
|
### Per-Server Enable/Disable
|
|
@@ -229,14 +232,14 @@ enabled, keeping `mcp.json` clean and fully backward compatible.
|
|
|
229
232
|
Toggle it from the CLI without touching the rest of the config:
|
|
230
233
|
|
|
231
234
|
```bash
|
|
232
|
-
npx mcp-compress-router disable github # writes "enabled": false
|
|
233
|
-
npx mcp-compress-router enable github # removes the field
|
|
235
|
+
npx mcp-compress-router@latest disable github # writes "enabled": false
|
|
236
|
+
npx mcp-compress-router@latest enable github # removes the field
|
|
234
237
|
```
|
|
235
238
|
|
|
236
239
|
You can also set it at creation time:
|
|
237
240
|
|
|
238
241
|
```bash
|
|
239
|
-
npx mcp-compress-router add archive --disabled -- npx -y server-archive
|
|
242
|
+
npx mcp-compress-router@latest add archive --disabled -- npx -y server-archive
|
|
240
243
|
```
|
|
241
244
|
|
|
242
245
|
### Per-Server Tool Selection
|
|
@@ -273,12 +276,67 @@ hard error at startup. Set filters at creation time with repeatable
|
|
|
273
276
|
flags:
|
|
274
277
|
|
|
275
278
|
```bash
|
|
276
|
-
npx mcp-compress-router add github \
|
|
279
|
+
npx mcp-compress-router@latest add github \
|
|
277
280
|
--allowed-tools list_issues \
|
|
278
281
|
--allowed-tools get_pull_request \
|
|
279
282
|
-- npx -y server-github
|
|
280
283
|
```
|
|
281
284
|
|
|
285
|
+
### Compression Levels
|
|
286
|
+
|
|
287
|
+
Each server's tools are listed in the `get_tool_schema` description at a
|
|
288
|
+
configurable `compressionLevel`. The level trades catalog compactness
|
|
289
|
+
for routing detail: lower levels give the LLM more information up front
|
|
290
|
+
(fewer `get_tool_schema` round-trips), while higher levels minimize the
|
|
291
|
+
per-request token overhead. The full JSON parameter schema is always
|
|
292
|
+
available via `get_tool_schema` regardless of the level — only the
|
|
293
|
+
catalog *listing* changes.
|
|
294
|
+
|
|
295
|
+
Four levels are supported, from most to least compact:
|
|
296
|
+
|
|
297
|
+
| Level | Tool listing format | Description shown? |
|
|
298
|
+
| --- | --- | --- |
|
|
299
|
+
| `max` | `toolA, toolB, toolC` (comma-separated, single line) | No |
|
|
300
|
+
| `high` (default) | `toolName(arg1, arg2)` (one per line) | No |
|
|
301
|
+
| `medium` | `toolName(arg1, arg2): first sentence...` (one per line) | Snippet |
|
|
302
|
+
| `low` | `<tool>toolName(arg1, arg2): full description</tool>` (one per line) | Full |
|
|
303
|
+
|
|
304
|
+
Argument names are extracted from each tool's `inputSchema.properties`
|
|
305
|
+
keys in definition order. When a tool has no description, the `medium`
|
|
306
|
+
and `low` listings omit the description portion and show just the
|
|
307
|
+
signature.
|
|
308
|
+
|
|
309
|
+
Omitting `compressionLevel` (the default) is equivalent to `high`. Set
|
|
310
|
+
it per server in `mcp.json`:
|
|
311
|
+
|
|
312
|
+
```jsonc
|
|
313
|
+
"github": {
|
|
314
|
+
"type": "stdio",
|
|
315
|
+
"command": "npx",
|
|
316
|
+
"args": ["-y", "@modelcontextprotocol/server-github"],
|
|
317
|
+
"compressionLevel": "medium"
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Or set it at creation time with the `--compression-level` flag:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
npx mcp-compress-router@latest add github \
|
|
325
|
+
--compression-level medium \
|
|
326
|
+
-- npx -y @modelcontextprotocol/server-github
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
A good rule of thumb:
|
|
330
|
+
|
|
331
|
+
- Use `max` for servers whose tool names are self-describing and you
|
|
332
|
+
want the smallest possible catalog.
|
|
333
|
+
- Use `high` (the default) for most servers — argument names are
|
|
334
|
+
usually enough for the LLM to pick the right tool.
|
|
335
|
+
- Use `medium` when tool names alone are ambiguous and a one-line
|
|
336
|
+
hint helps disambiguate.
|
|
337
|
+
- Use `low` sparingly — only when full descriptions must be visible
|
|
338
|
+
without a `get_tool_schema` call, since it costs the most tokens.
|
|
339
|
+
|
|
282
340
|
### Inspecting Tools
|
|
283
341
|
|
|
284
342
|
To see exactly which tools a server advertises — and which are
|
|
@@ -286,7 +344,7 @@ To see exactly which tools a server advertises — and which are
|
|
|
286
344
|
it live without starting the full router:
|
|
287
345
|
|
|
288
346
|
```bash
|
|
289
|
-
npx mcp-compress-router tools github
|
|
347
|
+
npx mcp-compress-router@latest tools github
|
|
290
348
|
```
|
|
291
349
|
|
|
292
350
|
This works regardless of the server's `enabled` state (inspecting a
|
|
@@ -303,7 +361,7 @@ flow automatically if OAuth is advertised. You can also trigger it
|
|
|
303
361
|
manually:
|
|
304
362
|
|
|
305
363
|
```bash
|
|
306
|
-
npx mcp-compress-router login my-http
|
|
364
|
+
npx mcp-compress-router@latest login my-http
|
|
307
365
|
```
|
|
308
366
|
|
|
309
367
|
This opens your browser to complete the authorization-code flow. Tokens
|
|
@@ -353,7 +411,7 @@ ignore the port on `localhost`. If your provider demands a redirect URI
|
|
|
353
411
|
with an **exact port**, pin it with `--port`:
|
|
354
412
|
|
|
355
413
|
```bash
|
|
356
|
-
npx mcp-compress-router login my-http --port 8765
|
|
414
|
+
npx mcp-compress-router@latest login my-http --port 8765
|
|
357
415
|
```
|
|
358
416
|
|
|
359
417
|
This binds the callback server to `8765`, so the redirect URI becomes
|
|
@@ -373,7 +431,7 @@ flag each time:
|
|
|
373
431
|
`--port` overrides `oauth.callbackPort` for a single run. Pass `--port 0`
|
|
374
432
|
to force an OS-assigned port even when `oauth.callbackPort` is set.
|
|
375
433
|
|
|
376
|
-
#### GitHub MCP (special case)
|
|
434
|
+
#### GitHub MCP with OAuth (special case)
|
|
377
435
|
|
|
378
436
|
The official GitHub MCP server at
|
|
379
437
|
`https://api.githubcopilot.com/mcp` advertises OAuth but does **not**
|
|
@@ -391,7 +449,7 @@ organizations you want the MCP to access.
|
|
|
391
449
|
3. **Add the GitHub MCP server by URL.**
|
|
392
450
|
|
|
393
451
|
```bash
|
|
394
|
-
npx mcp-compress-router add github https://api.githubcopilot.com/mcp
|
|
452
|
+
npx mcp-compress-router@latest add github https://api.githubcopilot.com/mcp
|
|
395
453
|
```
|
|
396
454
|
|
|
397
455
|
4. **Set `oauth` credentials in `mcp.json`.**
|
|
@@ -418,7 +476,7 @@ organizations you want the MCP to access.
|
|
|
418
476
|
5. **Run the login command.**
|
|
419
477
|
|
|
420
478
|
```bash
|
|
421
|
-
npx mcp-compress-router login github
|
|
479
|
+
npx mcp-compress-router@latest login github
|
|
422
480
|
```
|
|
423
481
|
|
|
424
482
|
Your browser opens to authorize. After you approve, tokens are stored
|
|
@@ -429,10 +487,93 @@ organizations you want the MCP to access.
|
|
|
429
487
|
> login will succeed, and its client secret is generated under *General*
|
|
430
488
|
> → *Generate a new client secret*.
|
|
431
489
|
|
|
490
|
+
#### Figma MCP with OAuth (special case)
|
|
491
|
+
|
|
492
|
+
The official Figma MCP server at `https://mcp.figma.com/mcp` does **not**
|
|
493
|
+
support Dynamic Client Registration through the standard MCP flow.
|
|
494
|
+
Instead you register an OAuth client via Figma's REST API using a
|
|
495
|
+
Personal Access Token, then pass the resulting credentials through the
|
|
496
|
+
`oauth` block. Figma also requires the redirect URI to use a **fixed
|
|
497
|
+
port** — the port you register is reused on every `login`, so you must
|
|
498
|
+
pin it with `oauth.callbackPort`.
|
|
499
|
+
|
|
500
|
+
1. **Create a Figma Personal Access Token.**
|
|
501
|
+
Follow
|
|
502
|
+
<https://developers.figma.com/docs/rest-api/personal-access-tokens/>
|
|
503
|
+
to generate a PAT and export it as `FIGMA_PERSONAL_ACCESS_TOKEN`. It
|
|
504
|
+
is only used to register the MCP client in the next step.
|
|
505
|
+
|
|
506
|
+
2. **Register the MCP client via Figma's API.**
|
|
507
|
+
The redirect URI must use `127.0.0.1` on a fixed port — **the port
|
|
508
|
+
matters**, it is reused on every `login`. This example uses `19876`:
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
curl -X POST https://api.figma.com/v1/oauth/mcp/register \
|
|
512
|
+
-H "Content-Type: application/json" \
|
|
513
|
+
-H "X-Figma-Token: $FIGMA_PERSONAL_ACCESS_TOKEN" \
|
|
514
|
+
-d '{
|
|
515
|
+
"client_name": "Claude Code (figma)",
|
|
516
|
+
"redirect_uris": ["http://127.0.0.1:19876/mcp-compress-router/oauth-callback"],
|
|
517
|
+
"grant_types": ["authorization_code", "refresh_token"],
|
|
518
|
+
"response_types": ["code"],
|
|
519
|
+
"token_endpoint_auth_method": "none"
|
|
520
|
+
}'
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
Save the `client_id` and `client_secret` from the response (also note
|
|
524
|
+
the `scope` is `mcp:connect`):
|
|
525
|
+
|
|
526
|
+
```json
|
|
527
|
+
{
|
|
528
|
+
"client_id": "CLIENTID",
|
|
529
|
+
"client_secret": "CLIENTSECRET",
|
|
530
|
+
"client_name": "Claude Code (figma)",
|
|
531
|
+
"redirect_uris": ["http://127.0.0.1:19876/mcp-compress-router/oauth-callback"],
|
|
532
|
+
"token_endpoint_auth_method": "none",
|
|
533
|
+
"scope": "mcp:connect"
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
3. **Add the Figma MCP server by URL.**
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
npx mcp-compress-router@latest add --transport http figma https://mcp.figma.com/mcp
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
4. **Set `oauth` credentials in `mcp.json`.**
|
|
544
|
+
Put the client ID and secret from step 2 in the server entry, using
|
|
545
|
+
the `mcp:connect` scope and the **same fixed port** you registered as
|
|
546
|
+
`callbackPort`:
|
|
547
|
+
|
|
548
|
+
```jsonc
|
|
549
|
+
"figma": {
|
|
550
|
+
"type": "http",
|
|
551
|
+
"url": "https://mcp.figma.com/mcp",
|
|
552
|
+
"oauth": {
|
|
553
|
+
"clientId": "${FIGMA_CLIENT_ID}",
|
|
554
|
+
"clientSecret": "${FIGMA_CLIENT_SECRET}",
|
|
555
|
+
"scope": "mcp:connect",
|
|
556
|
+
"callbackPort": 19876
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Put the actual values in your `.env` file (see
|
|
562
|
+
[Secrets and Variable Expansion](#secrets-and-variable-expansion)).
|
|
563
|
+
|
|
564
|
+
5. **Run the login command.**
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
npx mcp-compress-router@latest login figma
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
Your browser opens to authorize. After you approve, tokens are stored
|
|
571
|
+
in `credentials.json` and the router can call Figma MCP tools.
|
|
572
|
+
|
|
432
573
|
Other OAuth commands:
|
|
433
574
|
|
|
434
575
|
```bash
|
|
435
|
-
npx mcp-compress-router logout my-http # remove stored credentials
|
|
576
|
+
npx mcp-compress-router@latest logout my-http # remove stored credentials
|
|
436
577
|
```
|
|
437
578
|
|
|
438
579
|
For headless or CI environments, override the browser with the
|
|
@@ -441,7 +582,7 @@ URL is appended as a single final argument (no shell):
|
|
|
441
582
|
|
|
442
583
|
```bash
|
|
443
584
|
MCP_COMPRESS_ROUTER_BROWSER="node /path/to/headless-browser.js" \
|
|
444
|
-
npx mcp-compress-router login my-http
|
|
585
|
+
npx mcp-compress-router@latest login my-http
|
|
445
586
|
```
|
|
446
587
|
|
|
447
588
|
The default login timeout is 120 seconds; override it with
|
|
@@ -454,7 +595,7 @@ token instead of OAuth, use the `headers` field. You can set it via the
|
|
|
454
595
|
CLI or directly in `mcp.json`:
|
|
455
596
|
|
|
456
597
|
```bash
|
|
457
|
-
npx mcp-compress-router add my-http \
|
|
598
|
+
npx mcp-compress-router@latest add my-http \
|
|
458
599
|
--header "Authorization: Bearer mytoken" \
|
|
459
600
|
--header "X-Custom: value" \
|
|
460
601
|
https://example.com/mcp
|
|
@@ -500,77 +641,43 @@ Shell environment variables always take precedence over `.env` values.
|
|
|
500
641
|
|
|
501
642
|
Once your downstream servers are configured, connect your agent to the
|
|
502
643
|
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
|
|
644
|
+
pointing it at `npx mcp-compress-router@latest`. The examples below assume the
|
|
504
645
|
default [config location](#config-file-location); pass `-c <path>` if
|
|
505
646
|
you use a custom one.
|
|
506
647
|
|
|
507
648
|
### Opencode
|
|
508
649
|
|
|
509
|
-
|
|
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
|
-
}
|
|
650
|
+
```sh
|
|
651
|
+
opencode mcp add mcp-compress-router -- npx -y mcp-compress-router@latest
|
|
522
652
|
```
|
|
523
653
|
|
|
524
654
|
### Claude Code
|
|
525
655
|
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
}
|
|
656
|
+
```sh
|
|
657
|
+
claude mcp add mcp-compress-router -- npx -y mcp-compress-router@latest
|
|
542
658
|
```
|
|
543
659
|
|
|
544
660
|
### Codex
|
|
545
661
|
|
|
546
|
-
|
|
547
|
-
|
|
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
|
|
662
|
+
```sh
|
|
663
|
+
codex mcp add mcp-compress-router -- npx -y mcp-compress-router@latest
|
|
557
664
|
```
|
|
558
665
|
|
|
559
|
-
### GitHub Copilot
|
|
666
|
+
### GitHub Copilot (VS Code)
|
|
560
667
|
|
|
561
668
|
Add this to `.vscode/mcp.json` in your workspace (project-level, applies
|
|
562
669
|
only to that workspace), or to your **user-level** MCP settings which
|
|
563
|
-
apply across every workspace: open the Command Palette →
|
|
564
|
-
`
|
|
670
|
+
apply across every workspace: open the Command Palette (`Cmd+Shift+P`) →
|
|
671
|
+
`MCP: Open User Configuration` and add the same `servers`
|
|
565
672
|
block under the `mcp` key. Project-level and user-level entries are
|
|
566
673
|
merged, with project-level taking precedence.
|
|
567
674
|
|
|
568
675
|
```json
|
|
569
676
|
{
|
|
570
677
|
"servers": {
|
|
571
|
-
"compress-router": {
|
|
678
|
+
"mcp-compress-router": {
|
|
572
679
|
"command": "npx",
|
|
573
|
-
"args": ["-y", "mcp-compress-router"]
|
|
680
|
+
"args": ["-y", "mcp-compress-router@latest"]
|
|
574
681
|
}
|
|
575
682
|
}
|
|
576
683
|
}
|
|
@@ -600,3 +707,11 @@ token catalog, regardless of how many downstream servers you have.
|
|
|
600
707
|
|
|
601
708
|
For the full configuration and environment variable reference, see
|
|
602
709
|
[configuration.md](docs/configuration.md).
|
|
710
|
+
|
|
711
|
+
## Acknowledgements
|
|
712
|
+
|
|
713
|
+
- [mcp2cli](https://github.com/knowsuchagency/mcp2cli) — a very similar
|
|
714
|
+
idea of how MCP can be compressed, but to a CLI.
|
|
715
|
+
- [mcp-compressor](https://github.com/atlassian-labs/mcp-compressor) —
|
|
716
|
+
also a very similar idea; the "two tools" approach was borrowed from
|
|
717
|
+
this project, though it only compresses a single MCP server.
|
package/build/cli/add-command.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ensureConfigDir, readConfigFile, writeConfigFile, readCredentials, writeCredentials, } from './config-io.js';
|
|
2
|
-
import { validateGlobPattern } from '../utils/index.js';
|
|
2
|
+
import { isCompressionLevel, validateGlobPattern, VALID_COMPRESSION_LEVELS, } from '../utils/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Validates every glob pattern in an optional tool list, throwing with
|
|
5
5
|
* the field name and offending pattern on the first invalid entry.
|
|
@@ -61,6 +61,9 @@ function buildServerEntry(opts) {
|
|
|
61
61
|
if (opts.disabledTools && opts.disabledTools.length > 0) {
|
|
62
62
|
entry.disabledTools = opts.disabledTools;
|
|
63
63
|
}
|
|
64
|
+
if (opts.compressionLevel) {
|
|
65
|
+
entry.compressionLevel = opts.compressionLevel;
|
|
66
|
+
}
|
|
64
67
|
// A fixed callback port only applies to HTTP servers (OAuth). Persist
|
|
65
68
|
// it on the `oauth` block so `login` reuses the same redirect URI.
|
|
66
69
|
if (opts.port !== undefined) {
|
|
@@ -92,6 +95,10 @@ export async function handleAdd(configPath, opts) {
|
|
|
92
95
|
}
|
|
93
96
|
validateToolListPatterns('allowedTools', opts.allowedTools);
|
|
94
97
|
validateToolListPatterns('disabledTools', opts.disabledTools);
|
|
98
|
+
if (opts.compressionLevel !== undefined && !isCompressionLevel(opts.compressionLevel)) {
|
|
99
|
+
throw new Error(`Invalid "--compression-level" value "${opts.compressionLevel}": ` +
|
|
100
|
+
`must be one of ${VALID_COMPRESSION_LEVELS.join(', ')}.`);
|
|
101
|
+
}
|
|
95
102
|
await ensureConfigDir(configPath);
|
|
96
103
|
const servers = await readConfigFile(configPath);
|
|
97
104
|
if (opts.name in servers) {
|
package/build/cli/get-command.js
CHANGED
|
@@ -24,6 +24,12 @@ export async function handleGet(configPath, name) {
|
|
|
24
24
|
if (entry.description) {
|
|
25
25
|
lines.push(`Description: ${entry.description}`);
|
|
26
26
|
}
|
|
27
|
+
if (entry.compressionLevel) {
|
|
28
|
+
lines.push(`compressionLevel: ${entry.compressionLevel}`);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
lines.push('compressionLevel: high (default)');
|
|
32
|
+
}
|
|
27
33
|
if (entry.command) {
|
|
28
34
|
lines.push(`Command: ${entry.command}`);
|
|
29
35
|
}
|
|
@@ -55,6 +55,16 @@ function summarizeTools(server) {
|
|
|
55
55
|
}
|
|
56
56
|
return 'all';
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Renders the `Compression` cell: the explicit `compressionLevel`, or
|
|
60
|
+
* `high` when the field is absent (the default per PRD §"Solution").
|
|
61
|
+
*
|
|
62
|
+
* @param server - Typed downstream server config.
|
|
63
|
+
* @returns The resolved compression level string.
|
|
64
|
+
*/
|
|
65
|
+
function summarizeCompression(server) {
|
|
66
|
+
return server.compressionLevel ?? 'high';
|
|
67
|
+
}
|
|
58
68
|
/**
|
|
59
69
|
* Renders the list header and server rows as a fixed-width table. The
|
|
60
70
|
* final (Auth) column is left unpadded so lines never carry trailing
|
|
@@ -74,13 +84,14 @@ function formatList(configPath, rows) {
|
|
|
74
84
|
const commandWidth = Math.max('CommandOrUrl'.length, ...rows.map((r) => r.commandOrUrl.length));
|
|
75
85
|
const enabledWidth = Math.max('Enabled'.length, ...rows.map((r) => r.enabled.length));
|
|
76
86
|
const toolsWidth = Math.max('Tools'.length, ...rows.map((r) => r.tools.length));
|
|
87
|
+
const compressionWidth = Math.max('Compression'.length, ...rows.map((r) => r.compression.length));
|
|
77
88
|
const pad = (val, width) => val.padEnd(width);
|
|
78
|
-
const columns = (name, type, command, enabled, tools, auth) => `${pad(name, nameWidth)} ${pad(type, typeWidth)} ${pad(command, commandWidth)} ${pad(enabled, enabledWidth)} ${pad(tools, toolsWidth)} ${auth}`;
|
|
89
|
+
const columns = (name, type, command, enabled, tools, compression, auth) => `${pad(name, nameWidth)} ${pad(type, typeWidth)} ${pad(command, commandWidth)} ${pad(enabled, enabledWidth)} ${pad(tools, toolsWidth)} ${pad(compression, compressionWidth)} ${auth}`;
|
|
79
90
|
return [
|
|
80
91
|
header,
|
|
81
92
|
'',
|
|
82
|
-
columns('Name', 'Type', 'CommandOrUrl', 'Enabled', 'Tools', 'Auth'),
|
|
83
|
-
...rows.map((r) => columns(r.name, r.type, r.commandOrUrl, r.enabled, r.tools, r.auth)),
|
|
93
|
+
columns('Name', 'Type', 'CommandOrUrl', 'Enabled', 'Tools', 'Compression', 'Auth'),
|
|
94
|
+
...rows.map((r) => columns(r.name, r.type, r.commandOrUrl, r.enabled, r.tools, r.compression, r.auth)),
|
|
84
95
|
].join('\n');
|
|
85
96
|
}
|
|
86
97
|
/**
|
|
@@ -107,6 +118,7 @@ export async function handleList(configPath) {
|
|
|
107
118
|
enabled: entry.enabled,
|
|
108
119
|
allowedTools: entry.allowedTools,
|
|
109
120
|
disabledTools: entry.disabledTools,
|
|
121
|
+
compressionLevel: entry.compressionLevel,
|
|
110
122
|
};
|
|
111
123
|
return {
|
|
112
124
|
name,
|
|
@@ -114,6 +126,7 @@ export async function handleList(configPath) {
|
|
|
114
126
|
commandOrUrl: buildCommandOrUrl(typed),
|
|
115
127
|
enabled: summarizeEnabled(typed),
|
|
116
128
|
tools: summarizeTools(typed),
|
|
129
|
+
compression: summarizeCompression(typed),
|
|
117
130
|
auth: computeAuthStatus(typed, credentials[name]),
|
|
118
131
|
};
|
|
119
132
|
});
|
|
@@ -84,6 +84,7 @@ function buildAddOptions(name, commandOrUrl, rest, options) {
|
|
|
84
84
|
allowedTools: options.allowedTools && options.allowedTools.length > 0 ? options.allowedTools : undefined,
|
|
85
85
|
disabledTools: options.disabledTools && options.disabledTools.length > 0 ? options.disabledTools : undefined,
|
|
86
86
|
port: options.port,
|
|
87
|
+
compressionLevel: options.compressionLevel || undefined,
|
|
87
88
|
};
|
|
88
89
|
}
|
|
89
90
|
function registerAddCommand(program) {
|
|
@@ -99,6 +100,7 @@ function registerAddCommand(program) {
|
|
|
99
100
|
.option('--disabled', 'mark the server as disabled (writes "enabled": false)')
|
|
100
101
|
.option('--allowed-tools <pattern>', 'glob pattern allowlisting tool names (repeatable)', collectStringArray, [])
|
|
101
102
|
.option('--disabled-tools <pattern>', 'glob pattern denylisting tool names (repeatable)', collectStringArray, [])
|
|
103
|
+
.option('--compression-level <level>', 'tool listing compression level (max, high, medium, low; default high)')
|
|
102
104
|
.option('-p, --port <number>', 'fixed local OAuth callback port (HTTP only; written to oauth.callbackPort)', parsePort)
|
|
103
105
|
.action(guardedAction(async (name, commandOrUrl, rest, options) => {
|
|
104
106
|
const configPath = await resolveConfigPath(options.config);
|
|
@@ -38,7 +38,9 @@ async function startRouterServer(catalog, clients, logger) {
|
|
|
38
38
|
const invokeFn = (server, tool, args) => invokeDownstreamTool(clients, server, tool, args, logger);
|
|
39
39
|
router.registerTool('invoke_tool', {
|
|
40
40
|
title: 'Invoke Tool',
|
|
41
|
-
description: 'Invoke a specific tool on a connected MCP server.
|
|
41
|
+
description: 'Invoke a specific tool on a connected MCP server. ' +
|
|
42
|
+
'You MUST first use get_tool_schema to retrieve the required parameters ' +
|
|
43
|
+
'for this tool before calling invoke_tool.',
|
|
42
44
|
inputSchema: InvokeToolInputSchema,
|
|
43
45
|
}, createInvokeToolHandler(catalog, invokeFn, logger));
|
|
44
46
|
const transport = new StdioServerTransport();
|
|
@@ -78,12 +80,14 @@ export async function runRouter(configPath, verbose) {
|
|
|
78
80
|
})),
|
|
79
81
|
});
|
|
80
82
|
const selectionByServer = new Map();
|
|
83
|
+
const compressionLevelByServer = new Map();
|
|
81
84
|
for (const server of servers) {
|
|
82
85
|
selectionByServer.set(server.name, {
|
|
83
86
|
allowedTools: server.allowedTools,
|
|
84
87
|
disabledTools: server.disabledTools,
|
|
85
88
|
});
|
|
89
|
+
compressionLevelByServer.set(server.name, server.compressionLevel);
|
|
86
90
|
}
|
|
87
|
-
const catalog = buildCatalog(discovered, selectionByServer, logger);
|
|
91
|
+
const catalog = buildCatalog(discovered, selectionByServer, logger, compressionLevelByServer);
|
|
88
92
|
await startRouterServer(catalog, clients, logger);
|
|
89
93
|
}
|
|
@@ -14,9 +14,12 @@ import { filterTools } from '../utils/index.js';
|
|
|
14
14
|
* server name is absent from the map, all its tools are exposed.
|
|
15
15
|
* @param logger - Optional logger for unmatched-pattern warnings. When
|
|
16
16
|
* omitted, no warnings are emitted (backward compatible).
|
|
17
|
+
* @param compressionLevelByServer - Optional per-server compression level
|
|
18
|
+
* map. When a server name is absent or the entry is `undefined`, the
|
|
19
|
+
* level resolves to `high` (the default).
|
|
17
20
|
* @returns An immutable ToolCatalog containing only exposed tools.
|
|
18
21
|
*/
|
|
19
|
-
export function buildCatalog(discovered, selectionByServer = new Map(), logger) {
|
|
22
|
+
export function buildCatalog(discovered, selectionByServer = new Map(), logger, compressionLevelByServer = new Map()) {
|
|
20
23
|
const toolMap = new Map();
|
|
21
24
|
const filteredToolNames = new Set();
|
|
22
25
|
const servers = discovered.map((ds) => {
|
|
@@ -37,6 +40,7 @@ export function buildCatalog(discovered, selectionByServer = new Map(), logger)
|
|
|
37
40
|
name: ds.name,
|
|
38
41
|
description: ds.description,
|
|
39
42
|
tools: exposed,
|
|
43
|
+
compressionLevel: compressionLevelByServer.get(ds.name) ?? 'high',
|
|
40
44
|
};
|
|
41
45
|
});
|
|
42
46
|
return { servers, toolMap, filteredToolNames };
|
package/build/services/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
import * as os from 'node:os';
|
|
3
3
|
import * as fs from 'node:fs/promises';
|
|
4
|
-
import { expandEnvField, parseJsonc, validateGlobPattern } from '../utils/index.js';
|
|
4
|
+
import { expandEnvField, isCompressionLevel, parseJsonc, validateGlobPattern, VALID_COMPRESSION_LEVELS, } from '../utils/index.js';
|
|
5
5
|
/** Recognized MCP transport types. */
|
|
6
6
|
const VALID_TYPES = new Set(['stdio', 'http', 'streamable-http']);
|
|
7
7
|
/** Per-application directory name used inside the user data folder. */
|
|
@@ -243,6 +243,28 @@ function validateEnabled(name, server) {
|
|
|
243
243
|
}
|
|
244
244
|
return server.enabled;
|
|
245
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Validates the optional `compressionLevel` field on a server entry.
|
|
248
|
+
*
|
|
249
|
+
* Accepts `undefined` (absent — resolves to `high` downstream) and the
|
|
250
|
+
* four valid level strings. Any other value (invalid string, number,
|
|
251
|
+
* boolean, null) is a hard error at config load time, matching the
|
|
252
|
+
* existing fail-fast pattern for `type` and `enabled`.
|
|
253
|
+
*
|
|
254
|
+
* @param name - Server name (for error messages).
|
|
255
|
+
* @param value - The raw `compressionLevel` value from the server entry.
|
|
256
|
+
* @returns The validated level, or undefined when the field is absent.
|
|
257
|
+
* @throws If the value is present but not one of max, high, medium, low.
|
|
258
|
+
*/
|
|
259
|
+
function validateCompressionLevel(name, value) {
|
|
260
|
+
if (value === undefined)
|
|
261
|
+
return undefined;
|
|
262
|
+
if (!isCompressionLevel(value)) {
|
|
263
|
+
throw new Error(`Server "${name}" has invalid "compressionLevel" value "${String(value)}". ` +
|
|
264
|
+
`Must be one of: ${VALID_COMPRESSION_LEVELS.join(', ')}`);
|
|
265
|
+
}
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
246
268
|
/**
|
|
247
269
|
* Validates an optional tool-name glob list (`allowedTools` or
|
|
248
270
|
* `disabledTools`).
|
|
@@ -310,7 +332,18 @@ function parseServerEntry(name, entry, names) {
|
|
|
310
332
|
const enabled = validateEnabled(name, server);
|
|
311
333
|
const allowedTools = validateToolList(name, 'allowedTools', server.allowedTools);
|
|
312
334
|
const disabledTools = validateToolList(name, 'disabledTools', server.disabledTools);
|
|
313
|
-
|
|
335
|
+
const compressionLevel = validateCompressionLevel(name, server.compressionLevel);
|
|
336
|
+
return {
|
|
337
|
+
name,
|
|
338
|
+
type,
|
|
339
|
+
...fields,
|
|
340
|
+
description,
|
|
341
|
+
oauth,
|
|
342
|
+
enabled,
|
|
343
|
+
allowedTools,
|
|
344
|
+
disabledTools,
|
|
345
|
+
compressionLevel,
|
|
346
|
+
};
|
|
314
347
|
}
|
|
315
348
|
/**
|
|
316
349
|
* Loads and validates the MCP configuration file.
|
|
@@ -55,5 +55,7 @@ export function createGetToolSchemaHandler(catalog, logger) {
|
|
|
55
55
|
*/
|
|
56
56
|
export function buildGetToolSchemaDescription(catalog) {
|
|
57
57
|
const compact = renderCompactCatalog(catalog.servers);
|
|
58
|
-
return 'Get the JSON schema for one or more tools from a connected MCP server
|
|
58
|
+
return ('Get the JSON schema for one or more tools from a connected MCP server. ' +
|
|
59
|
+
'You MUST call this for a tool before you can invoke it with invoke_tool.\n\n' +
|
|
60
|
+
compact);
|
|
59
61
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts ordered argument names from a tool's `inputSchema`.
|
|
3
|
+
*
|
|
4
|
+
* Reads `inputSchema.properties` keys in definition order. Returns an
|
|
5
|
+
* empty array when the schema is undefined, has no `properties` key,
|
|
6
|
+
* or has a non-object `properties` value (including null). No failure
|
|
7
|
+
* modes — malformed input always yields an empty array.
|
|
8
|
+
*
|
|
9
|
+
* @param inputSchema - The JSON Schema object from a tool descriptor.
|
|
10
|
+
* @returns Ordered argument names (possibly empty).
|
|
11
|
+
*/
|
|
12
|
+
export function extractArgumentNames(inputSchema) {
|
|
13
|
+
if (inputSchema === undefined) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const properties = inputSchema.properties;
|
|
17
|
+
if (properties === undefined || properties === null || typeof properties !== 'object') {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
return Object.keys(properties);
|
|
21
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid `compressionLevel` values, in the order used for error messages
|
|
3
|
+
* and documentation.
|
|
4
|
+
*/
|
|
5
|
+
export const VALID_COMPRESSION_LEVELS = [
|
|
6
|
+
'max',
|
|
7
|
+
'high',
|
|
8
|
+
'medium',
|
|
9
|
+
'low',
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Type guard confirming a value is one of the four valid
|
|
13
|
+
* {@link CompressionLevel} strings (`max`, `high`, `medium`, `low`).
|
|
14
|
+
*
|
|
15
|
+
* Used by both the Config Loader (startup validation) and the `add` CLI
|
|
16
|
+
* command (flag validation) so both check against a single source of
|
|
17
|
+
* truth.
|
|
18
|
+
*
|
|
19
|
+
* @param value - Any value read from config or a CLI flag.
|
|
20
|
+
* @returns True when `value` is a string and one of the four valid levels.
|
|
21
|
+
*/
|
|
22
|
+
export function isCompressionLevel(value) {
|
|
23
|
+
return (typeof value === 'string' && VALID_COMPRESSION_LEVELS.includes(value));
|
|
24
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncates a tool description to the first sentence (text up to the
|
|
3
|
+
* first `.`), capped at 10 words. When the first sentence exceeds 10
|
|
4
|
+
* words, only the first 10 are kept and `...` (three ASCII dots) is
|
|
5
|
+
* appended. When the description is absent or empty, returns an empty
|
|
6
|
+
* string. No failure modes — malformed input always yields an empty
|
|
7
|
+
* string.
|
|
8
|
+
*
|
|
9
|
+
* Used by the catalog text renderer at the `medium` compression level.
|
|
10
|
+
* This is distinct from the character-based `truncateDescription` in
|
|
11
|
+
* `src/cli/tools-command.ts`, which serves the CLI table layout.
|
|
12
|
+
*
|
|
13
|
+
* @param description - The raw tool description, or undefined.
|
|
14
|
+
* @returns The truncated first-sentence snippet (possibly empty).
|
|
15
|
+
*/
|
|
16
|
+
export function truncateToFirstSentence(description) {
|
|
17
|
+
if (description === undefined || description.length === 0) {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
const firstSentence = description.split('.')[0].trim();
|
|
21
|
+
const words = firstSentence.length === 0 ? [] : firstSentence.split(/\s+/);
|
|
22
|
+
if (words.length === 0) {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
if (words.length <= 10) {
|
|
26
|
+
return words.join(' ');
|
|
27
|
+
}
|
|
28
|
+
return `${words.slice(0, 10).join(' ')}...`;
|
|
29
|
+
}
|
package/build/utils/index.js
CHANGED
|
@@ -6,5 +6,6 @@ export { validateGlobPattern } from './validate-glob.js';
|
|
|
6
6
|
export { expandEnvField } from './expand-env.js';
|
|
7
7
|
export { Logger } from './logger.js';
|
|
8
8
|
export { parseJsonc } from './parse-jsonc.js';
|
|
9
|
+
export { VALID_COMPRESSION_LEVELS, isCompressionLevel } from './compression-level.js';
|
|
9
10
|
/** @public */
|
|
10
11
|
export { openBrowser } from './open-browser.js';
|
|
@@ -1,33 +1,72 @@
|
|
|
1
|
+
import { extractArgumentNames } from './argument-names.js';
|
|
2
|
+
import { truncateToFirstSentence } from './description-truncator.js';
|
|
1
3
|
/**
|
|
2
4
|
* Renders the compact catalog as Markdown text suitable for inclusion
|
|
3
5
|
* in the `get_tool_schema` tool description.
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* {description (optional)}
|
|
7
|
+
* Each server's tools are rendered according to that server's
|
|
8
|
+
* `compressionLevel`:
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* - `max` — tool names only, comma-separated on a single line.
|
|
11
|
+
* - `high` (default) — `toolName(arg1, arg2)`, one tool per line.
|
|
12
|
+
* - `medium` — `toolName(arg1, arg2): first sentence...`, one tool per
|
|
13
|
+
* line (the snippet is omitted when the description is absent).
|
|
14
|
+
* - `low` — `<tool>toolName(arg1, arg2): full description</tool>`,
|
|
15
|
+
* one tool per line (description omitted when absent).
|
|
11
16
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
17
|
+
* Argument names are extracted from each tool's `inputSchema.properties`
|
|
18
|
+
* keys in definition order. When a server has no tools, only the header
|
|
19
|
+
* (and optional description) is rendered.
|
|
14
20
|
*
|
|
15
21
|
* @param servers - The catalog server entries.
|
|
16
22
|
* @returns Compact catalog text.
|
|
17
23
|
*/
|
|
18
24
|
export function renderCompactCatalog(servers) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
return servers.map((server) => renderServerBlock(server)).join('\n\n');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Renders a single server section: header, optional description, and
|
|
29
|
+
* the tool listing formatted for that server's compression level.
|
|
30
|
+
*
|
|
31
|
+
* @param server - The catalog server to render.
|
|
32
|
+
* @returns The server block text.
|
|
33
|
+
*/
|
|
34
|
+
function renderServerBlock(server) {
|
|
35
|
+
const lines = [`## ${server.name}`];
|
|
36
|
+
if (server.description) {
|
|
37
|
+
lines.push(server.description);
|
|
38
|
+
}
|
|
39
|
+
if (server.tools.length > 0) {
|
|
40
|
+
lines.push('', 'Available tools:');
|
|
41
|
+
if (server.compressionLevel === 'max') {
|
|
42
|
+
lines.push(server.tools.map((tool) => tool.name).join(', '));
|
|
24
43
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
44
|
+
else {
|
|
45
|
+
for (const tool of server.tools) {
|
|
46
|
+
lines.push(renderToolLine(tool, server.compressionLevel));
|
|
47
|
+
}
|
|
29
48
|
}
|
|
30
|
-
blocks.push(lines.join('\n'));
|
|
31
49
|
}
|
|
32
|
-
return
|
|
50
|
+
return lines.join('\n');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Renders a single tool line at the `high`, `medium`, or `low` level.
|
|
54
|
+
*
|
|
55
|
+
* @param tool - The tool descriptor.
|
|
56
|
+
* @param level - The compression level (never `max`).
|
|
57
|
+
* @returns The formatted tool line.
|
|
58
|
+
*/
|
|
59
|
+
function renderToolLine(tool, level) {
|
|
60
|
+
const args = extractArgumentNames(tool.inputSchema);
|
|
61
|
+
const signature = `${tool.name}(${args.join(', ')})`;
|
|
62
|
+
if (level === 'low') {
|
|
63
|
+
return tool.description
|
|
64
|
+
? `<tool>${signature}: ${tool.description}</tool>`
|
|
65
|
+
: `<tool>${signature}</tool>`;
|
|
66
|
+
}
|
|
67
|
+
if (level === 'medium') {
|
|
68
|
+
const snippet = truncateToFirstSentence(tool.description);
|
|
69
|
+
return snippet ? `${signature}: ${snippet}` : signature;
|
|
70
|
+
}
|
|
71
|
+
return signature;
|
|
33
72
|
}
|