@atlassian/mcp-compressor 0.0.2

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 ADDED
@@ -0,0 +1,393 @@
1
+ # @atlassian/mcp-compressor (TypeScript)
2
+
3
+ A TypeScript MCP proxy that wraps one or more MCP servers and reduces the token footprint exposed to LLMs.
4
+
5
+ ## How it works
6
+
7
+ mcp-compressor connects to upstream MCP servers (via stdio, HTTP, or SSE) and replaces their full tool catalogs with a compressed interface. Instead of exposing every tool individually, it provides:
8
+
9
+ - **`get_tool_schema(tool_name)`** — returns the full schema for a specific tool on demand
10
+ - **`invoke_tool(tool_name, tool_input)`** — calls an upstream tool
11
+ - **`list_tools()`** — lists available tool names (only at `max` compression)
12
+
13
+ This dramatically reduces the token cost of tool descriptions in LLM context windows while preserving full tool functionality.
14
+
15
+ ### Compression levels
16
+
17
+ | Level | What the LLM sees |
18
+ |---|---|
19
+ | `low` | Tool name, parameters, and full description |
20
+ | `medium` (default) | Tool name, parameters, and first sentence of description |
21
+ | `high` | Tool name and parameters only |
22
+ | `max` | Tool names only (requires `list_tools` call to discover) |
23
+
24
+ ### Three modes
25
+
26
+ | Mode | Tools exposed | How the LLM invokes tools |
27
+ |---|---|---|
28
+ | **Compressed** (default) | `get_tool_schema` + `invoke_tool` | Via MCP tool calls |
29
+ | **CLI** | Per-server `_help` tools | Via bash CLI commands (bridge + generated scripts) |
30
+ | **Bash** | Per-server `_help` tools + `bash` tool | Via a sandboxed [just-bash](https://www.npmjs.com/package/just-bash) shell |
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ npm install @atlassian/mcp-compressor
36
+
37
+ # Optional: for bash mode
38
+ npm install just-bash
39
+ ```
40
+
41
+ ## STDIO MCP proxy
42
+
43
+ The simplest way to use mcp-compressor is as a CLI that wraps another MCP server and exposes a compressed proxy over stdio.
44
+
45
+ ### Compressed mode (default)
46
+
47
+ ```bash
48
+ # Wrap a remote MCP server
49
+ bun cli -- https://mcp.atlassian.com/v1/mcp
50
+
51
+ # Wrap a local stdio server
52
+ bun cli --server-name filesystem -- npx -y @modelcontextprotocol/server-filesystem .
53
+
54
+ # With options
55
+ bun cli -c high --server-name atlassian --toonify -- https://mcp.atlassian.com/v1/mcp
56
+
57
+ # Multi-server via MCP config JSON
58
+ bun cli -- '{"mcpServers":{"jira":{"url":"https://jira-mcp.example.com"},"confluence":{"command":"node","args":["confluence-server.js"]}}}'
59
+ ```
60
+
61
+ ### CLI mode
62
+
63
+ CLI mode generates shell scripts for each backend server so the LLM (or user) can invoke tools directly from bash. The MCP proxy exposes per-server help tools that describe the available commands.
64
+
65
+ ```bash
66
+ # Start CLI mode
67
+ bun cli --cli-mode --server-name atlassian -- https://mcp.atlassian.com/v1/mcp
68
+
69
+ # In another terminal, use the generated CLI:
70
+ atlassian --help
71
+ atlassian search-confluence --query oauth
72
+ atlassian get-jira-issue --issue-url https://jira.example.com/browse/PROJ-123
73
+ ```
74
+
75
+ TOON output formatting is automatically enabled in CLI mode.
76
+
77
+ ### Bash mode
78
+
79
+ Bash mode registers all backend tools as custom commands in a sandboxed just-bash shell, then exposes a single `bash` MCP tool plus per-server help tools. The LLM can run MCP tools alongside standard Unix utilities, including pipes and composition.
80
+
81
+ ```bash
82
+ bun cli --just-bash --server-name atlassian -- https://mcp.atlassian.com/v1/mcp
83
+ ```
84
+
85
+ The LLM then sees:
86
+ - `bash` — execute commands in the sandboxed shell
87
+ - `atlassian_help` — lists available atlassian subcommands
88
+
89
+ Example commands the LLM can run via the bash tool:
90
+ ```bash
91
+ atlassian search-issues --jql "project=PROJ" | jq '.issues[].key'
92
+ atlassian get-page --page-id 12345 | grep "summary"
93
+ echo "hello" | grep hello
94
+ ```
95
+
96
+ ### OAuth
97
+
98
+ For remote OAuth backends, the CLI handles the authorization flow automatically — it opens the browser, completes the code exchange, and persists tokens for future sessions.
99
+
100
+ ```bash
101
+ # Clear cached OAuth state
102
+ bun cli clear-oauth https://mcp.atlassian.com/v1/mcp
103
+ ```
104
+
105
+ ## In-process usage (CompressorClient)
106
+
107
+ For TypeScript applications and agent frameworks, `CompressorClient` provides a single unified interface for all modes — no subprocess needed.
108
+
109
+ ### Compressed mode
110
+
111
+ ```typescript
112
+ import { CompressorClient } from '@atlassian/mcp-compressor';
113
+
114
+ const client = new CompressorClient({
115
+ servers: {
116
+ jira: { url: 'https://jira-mcp.example.com' },
117
+ confluence: { command: 'node', args: ['confluence-server.js'] },
118
+ },
119
+ compressionLevel: 'medium',
120
+ });
121
+
122
+ await client.connect();
123
+ const tools = await client.getTools();
124
+ // → { jira_get_tool_schema, jira_invoke_tool, confluence_get_tool_schema, confluence_invoke_tool }
125
+
126
+ // Use with any AI SDK-compatible framework
127
+ const schema = await tools.jira_get_tool_schema.execute({ tool_name: 'search_issues' });
128
+ const result = await tools.jira_invoke_tool.execute({
129
+ tool_name: 'search_issues',
130
+ tool_input: { query: 'oauth' },
131
+ });
132
+
133
+ await client.close();
134
+ ```
135
+
136
+ ### CLI mode
137
+
138
+ ```typescript
139
+ const client = new CompressorClient({
140
+ servers: { atlassian: { url: 'https://mcp.atlassian.com/v1/mcp' } },
141
+ mode: 'cli',
142
+ });
143
+
144
+ await client.connect();
145
+ const tools = await client.getTools();
146
+ // → { atlassian_help }
147
+ // Side effect: HTTP bridge started, shell script generated
148
+
149
+ // client.scripts has info about generated scripts
150
+ for (const script of client.scripts) {
151
+ console.log(`Run '${script.cliName} --help' for usage`);
152
+ }
153
+
154
+ await client.close();
155
+ ```
156
+
157
+ ### Bash mode
158
+
159
+ ```typescript
160
+ const client = new CompressorClient({
161
+ servers: {
162
+ jira: { url: 'https://jira-mcp.example.com' },
163
+ confluence: { command: 'node', args: ['confluence-server.js'] },
164
+ },
165
+ mode: 'bash',
166
+ });
167
+
168
+ await client.connect();
169
+ const tools = await client.getTools();
170
+ // → { bash, jira_help, confluence_help }
171
+
172
+ // Execute commands via the bash tool
173
+ const result = await tools.bash.execute({ command: 'jira search-issues --query oauth' });
174
+
175
+ // Access the Bash instance directly
176
+ const execResult = await client.bash!.exec('jira search-issues --query test | jq .issues');
177
+
178
+ await client.close();
179
+ ```
180
+
181
+ ### Bash mode with a pre-existing Bash instance
182
+
183
+ If your application already has a `Bash` instance (e.g. with its own custom commands), you can inject it:
184
+
185
+ ```typescript
186
+ import { Bash } from 'just-bash';
187
+
188
+ const existingBash = new Bash({ customCommands: [myCustomCommand] });
189
+
190
+ const client = new CompressorClient({
191
+ servers: { atlassian: { url: 'https://mcp.atlassian.com/v1/mcp' } },
192
+ mode: 'bash',
193
+ bash: { bash: existingBash },
194
+ });
195
+
196
+ await client.connect();
197
+ const tools = await client.getTools();
198
+ // → { bash, atlassian_help }
199
+ // MCP commands are registered into existingBash via registerCommand()
200
+ ```
201
+
202
+ ### Server configuration formats
203
+
204
+ `CompressorClient` accepts several formats for the `servers` option:
205
+
206
+ ```typescript
207
+ // Named servers map (recommended for multi-server)
208
+ new CompressorClient({
209
+ servers: {
210
+ jira: { url: 'https://jira-mcp.example.com' },
211
+ filesystem: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '.'] },
212
+ },
213
+ });
214
+
215
+ // Single BackendConfig
216
+ new CompressorClient({
217
+ servers: { type: 'http', url: 'https://mcp.example.com' },
218
+ });
219
+
220
+ // URL string
221
+ new CompressorClient({
222
+ servers: 'https://mcp.example.com',
223
+ });
224
+
225
+ // MCP config JSON string
226
+ new CompressorClient({
227
+ servers: '{"mcpServers":{"jira":{"url":"https://jira-mcp.example.com"}}}',
228
+ });
229
+ ```
230
+
231
+ ### Escape hatches
232
+
233
+ ```typescript
234
+ // Access individual runtimes
235
+ const jiraRuntime = client.getRuntime('jira');
236
+ await jiraRuntime.invokeTool('search_issues', { query: 'bug' });
237
+
238
+ // List all server names
239
+ console.log(client.serverNames); // ['jira', 'confluence']
240
+
241
+ // Access all runtimes
242
+ for (const runtime of client.runtimes) {
243
+ console.log(runtime.serverName, await runtime.listToolNames());
244
+ }
245
+ ```
246
+
247
+ ## Rust-backed native client
248
+
249
+ The Rust-core migration exposes a high-level native client for applications that want to start compressed MCP proxy sessions in-process, without spawning the `mcp-compressor` CLI as a stdio subprocess.
250
+
251
+ ```ts
252
+ import { CompressorClient } from "@atlassian/mcp-compressor";
253
+
254
+ const client = new CompressorClient({
255
+ servers: {
256
+ atlassian: {
257
+ url: "https://mcp.atlassian.com/v1/mcp",
258
+ headers: {
259
+ Authorization: `Basic ${token}`,
260
+ },
261
+ },
262
+ },
263
+ compressionLevel: "medium",
264
+ includeTools: ["getConfluencePage", "updateConfluencePage"],
265
+ toonify: true,
266
+ });
267
+
268
+ const proxy = await client.connect();
269
+ try {
270
+ console.log(proxy.tools.map((tool) => tool.name));
271
+ const output = await proxy.invoke("getAccessibleAtlassianResources", {}, { server: "atlassian" });
272
+ console.log(output);
273
+ } finally {
274
+ await client.close();
275
+ }
276
+ ```
277
+
278
+ The package root now exposes the Rust-backed `CompressorClient` as the primary SDK surface on the migration trunk.
279
+
280
+ Just Bash mode exposes typed provider metadata so language hosts can register backend MCP tools as Just Bash commands while Rust owns compression/proxy routing:
281
+
282
+ ```ts
283
+ const proxy = await new CompressorClient({ servers, mode: "bash" }).connect();
284
+ try {
285
+ for (const provider of proxy.justBashProviders) {
286
+ console.log(provider.providerName, provider.helpToolName);
287
+ for (const command of provider.tools) {
288
+ console.log(command.commandName, command.backendToolName, command.invokeToolName);
289
+ }
290
+ }
291
+ } finally {
292
+ proxy.close();
293
+ }
294
+ ```
295
+
296
+ Connected proxies can also write shell, Python, or TypeScript clients that call the live Rust proxy:
297
+
298
+ ```ts
299
+ const proxy = await client.connect();
300
+ try {
301
+ proxy.writeClient("cli", "./bin", { name: "atlassian" });
302
+ proxy.writeClient("python", "./generated-py", { name: "atlassian" });
303
+ proxy.writeClient("typescript", "./generated-ts", { name: "atlassian" });
304
+ } finally {
305
+ proxy.close();
306
+ }
307
+ ```
308
+
309
+ ## Development
310
+
311
+ ### Setup
312
+
313
+ ```bash
314
+ cd typescript
315
+ bun install
316
+ ```
317
+
318
+ ### Commands
319
+
320
+ ```bash
321
+ bun run test # run tests with vitest
322
+ bun run check # lint + format + typecheck + test
323
+ bun run lint # lint with oxlint
324
+ bun run format # format with oxfmt
325
+ bun run format:check # check formatting
326
+ bun run build # compile to dist/
327
+ bun cli # run the CLI directly from source
328
+ ```
329
+
330
+ ### Packaging smoke test
331
+
332
+ Build and test the npm package from a clean temporary project:
333
+
334
+ ```bash
335
+ bun install
336
+ bun run build
337
+ bun run build:native
338
+ bun pm pack --filename /tmp/mcp-compressor-ts-package.tgz
339
+ mkdir -p /tmp/mcp-compressor-ts-package-test
340
+ cd /tmp/mcp-compressor-ts-package-test
341
+ bun init -y
342
+ bun add --registry https://registry.npmjs.org /tmp/mcp-compressor-ts-package.tgz
343
+ bun --eval 'import { CompressorClient, compressToolListing } from "@atlassian/mcp-compressor"; console.log(typeof CompressorClient, compressToolListing("high", [{ name: "echo", inputSchema: { type: "object", properties: {} } }]))'
344
+ ```
345
+
346
+ CI runs the same package smoke test and uploads the packed tarball as an artifact.
347
+
348
+ ### Toolchain
349
+
350
+ | Tool | Purpose |
351
+ |---|---|
352
+ | [Bun](https://bun.sh/) | Package manager and runtime |
353
+ | [TypeScript](https://www.typescriptlang.org/) | Type checking |
354
+ | [Vitest](https://vitest.dev/) | Test runner |
355
+ | [oxlint](https://oxc.rs/docs/guide/usage/linter) | Linter |
356
+ | [oxfmt](https://oxc.rs/docs/guide/usage/formatter) | Formatter |
357
+ | [Commander](https://github.com/tj/commander.js) | CLI argument parsing |
358
+
359
+ ## Publishing
360
+
361
+ To publish from source, ensure you have active credentials for Artifactory, then run:
362
+
363
+ ```bash
364
+ bun run build
365
+ npm version <PUBLISH_VERSION> --no-git-tag-version
366
+ npm publish
367
+ ```
368
+
369
+ Published as `@atlassian/mcp-compressor` via Atlassian Artifactory → npmJS.
370
+
371
+ ```bash
372
+ npm install @atlassian/mcp-compressor
373
+ ```
374
+
375
+ ### Sub-path exports
376
+
377
+ `@atlassian/mcp-compressor` ships a small set of sub-path exports for consumers that only need the lightweight pieces. Importing from these sub-paths avoids loading `fastmcp`, the MCP SDK, and the rest of the runtime, which can add hundreds of ms of cold-import cost.
378
+
379
+ | Sub-path | Contents | When to use |
380
+ |---|---|---|
381
+ | `@atlassian/mcp-compressor` | Full runtime (`CompressorRuntime`, `CompressorServer`, `BackendClient`, `FastMCP`, ...) | Building / hosting a compressor proxy. |
382
+ | `@atlassian/mcp-compressor/bash` | The just-bash transform helpers | Bash-tool transformation. |
383
+ | `@atlassian/mcp-compressor/config` | `interpolateString`, `interpolateRecord`, `parseServerConfigJson` | Pure config parsing / `${ENV}` interpolation in tooling. |
384
+ | `@atlassian/mcp-compressor/errors` | `InvalidConfigurationError` and friends | Error type checks without the runtime. |
385
+ | `@atlassian/mcp-compressor/types` | `BackendConfig`, `CompressionLevel`, `CommonProxyOptions`, ... | Type-only consumers. |
386
+
387
+ ```ts
388
+ // heavy: pulls fastmcp + MCP SDK
389
+ import { CompressorRuntime } from "@atlassian/mcp-compressor";
390
+
391
+ // light: ~zero runtime deps
392
+ import { interpolateString } from "@atlassian/mcp-compressor/config";
393
+ ```
@@ -0,0 +1,17 @@
1
+ export interface ExecutableTool {
2
+ name: string;
3
+ description?: string;
4
+ inputSchema: Record<string, unknown>;
5
+ execute(input?: Record<string, unknown>): Promise<string>;
6
+ }
7
+ export interface AISDKToolFactory<TTool = unknown> {
8
+ (options: {
9
+ description?: string;
10
+ inputSchema: Record<string, unknown>;
11
+ execute: (input: Record<string, unknown>) => Promise<string>;
12
+ }): TTool;
13
+ }
14
+ export declare function toAISDKTools<TTool = unknown>(tools: Record<string, ExecutableTool>, options?: {
15
+ tool?: AISDKToolFactory<TTool>;
16
+ }): Record<string, TTool | Omit<ExecutableTool, "name">>;
17
+ export declare function toMastraTools(tools: Record<string, ExecutableTool>): Record<string, Omit<ExecutableTool, "name">>;
@@ -0,0 +1,23 @@
1
+ export function toAISDKTools(tools, options = {}) {
2
+ const result = {};
3
+ for (const [name, executable] of Object.entries(tools)) {
4
+ const definition = {
5
+ description: executable.description,
6
+ inputSchema: executable.inputSchema,
7
+ execute: (input) => executable.execute(input),
8
+ };
9
+ result[name] = options.tool ? options.tool(definition) : definition;
10
+ }
11
+ return result;
12
+ }
13
+ export function toMastraTools(tools) {
14
+ const result = {};
15
+ for (const [name, executable] of Object.entries(tools)) {
16
+ result[name] = {
17
+ description: executable.description,
18
+ inputSchema: executable.inputSchema,
19
+ execute: (input = {}) => executable.execute(input),
20
+ };
21
+ }
22
+ return result;
23
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import type { BackendConfig } from "./types.js";
3
+ export interface ParsedCliArgs {
4
+ backend: BackendConfig | string;
5
+ justBash: boolean;
6
+ cliMode: boolean;
7
+ cliPort: string | undefined;
8
+ compressionLevel: "low" | "medium" | "high" | "max" | undefined;
9
+ cwd: string | undefined;
10
+ env: Record<string, string>;
11
+ excludeTools: string[];
12
+ headers: Record<string, string>;
13
+ includeTools: string[];
14
+ logLevel: string;
15
+ serverName: string | undefined;
16
+ timeout: number;
17
+ toonify: boolean;
18
+ }
19
+ export declare function parseCliArgs(argv: string[]): ParsedCliArgs;
package/dist/cli.js ADDED
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+ import { Command, Option } from "commander";
3
+ import { spawn } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { VERSION } from "./version.js";
8
+ function parseBackendArg(backendArgs) {
9
+ if (backendArgs.length === 0) {
10
+ throw new Error("Expected a backend URL, MCP config JSON string, or stdio command.");
11
+ }
12
+ if (backendArgs.length === 1) {
13
+ return backendArgs[0];
14
+ }
15
+ return {
16
+ type: "stdio",
17
+ command: backendArgs[0],
18
+ args: backendArgs.slice(1),
19
+ };
20
+ }
21
+ function collect(value, previous) {
22
+ previous.push(value);
23
+ return previous;
24
+ }
25
+ function collectKeyValue(value, previous) {
26
+ const eqIndex = value.indexOf("=");
27
+ if (eqIndex === -1) {
28
+ throw new Error(`Invalid key=value format: '${value}'. Expected KEY=VALUE.`);
29
+ }
30
+ const key = value.slice(0, eqIndex);
31
+ let val = value.slice(eqIndex + 1);
32
+ // Support ${VAR_NAME} environment variable expansion
33
+ val = val.replace(/\$\{(\w+)\}/g, (_, name) => process.env[name] ?? "");
34
+ previous[key] = val;
35
+ return previous;
36
+ }
37
+ function buildProgram(options = {}) {
38
+ const program = new Command()
39
+ .name("mcp-compressor")
40
+ .description("Run the MCP Compressor proxy server.\n\n" +
41
+ "Connects to an MCP server (via stdio, HTTP, or SSE) and wraps it\n" +
42
+ "with a compressed tool interface.")
43
+ .allowUnknownOption(false)
44
+ .allowExcessArguments(true)
45
+ .version(VERSION, "-V, --version")
46
+ .argument("[command_or_url...]", "The backend to wrap: either a remote MCP URL, a stdio command plus\n" +
47
+ "arguments, or an MCP config JSON string with one or more servers.\n" +
48
+ "Example stdio usage: bun run dist/cli.js -- uvx mcp-server-fetch")
49
+ .option("--cwd <dir>", "The working directory to use when running stdio MCP servers.")
50
+ .option("-e, --env <VAR=VALUE>", "Environment variables to set when running stdio MCP servers, in the\n" +
51
+ "form VAR_NAME=VALUE. Can be used multiple times. Supports environment\n" +
52
+ "variable expansion with ${VAR_NAME} syntax.", collectKeyValue, {})
53
+ .option("-H, --header <NAME=VALUE>", "Headers to use for remote (HTTP/SSE) MCP server connections, in the\n" +
54
+ "form Header-Name=Header-Value. Can be used multiple times. Supports\n" +
55
+ "environment variable expansion with ${VAR_NAME} syntax.", collectKeyValue, {})
56
+ .option("-t, --timeout <seconds>", "The timeout in seconds for connecting to the MCP server and making requests.", "10")
57
+ .addOption(new Option("-c, --compression-level <level>", "The level of compression to apply to the tool descriptions of the wrapped MCP server.")
58
+ .choices(["low", "medium", "high", "max"])
59
+ .default("medium"))
60
+ .option("-n, --server-name <name>", "Optional custom name to prefix the wrapper tool names (get_tool_schema,\n" +
61
+ "invoke_tool, list_tools). The name will be sanitized to conform to MCP\n" +
62
+ "tool name specifications (only A-Z, a-z, 0-9, _, -, .).")
63
+ .option("-l, --log-level <level>", "The logging level.", "error")
64
+ .option("--toonify", "Convert JSON tool responses to TOON format automatically.")
65
+ .option("--cli-mode", "Start in CLI mode: expose a single help MCP tool, start a local HTTP\n" +
66
+ "bridge, and generate a shell script for interacting with the wrapped\n" +
67
+ "server via CLI. --toonify is automatically enabled in this mode.")
68
+ .option("--cli-port <port>", "Port for the local CLI bridge HTTP server (default: random free port).")
69
+ .option("--just-bash", "Start in just-bash mode: expose a single 'bash' MCP tool powered by\n" +
70
+ "just-bash, with all backend server tools available as custom commands.\n" +
71
+ "Requires the 'just-bash' package to be installed. --toonify is\n" +
72
+ "automatically enabled in this mode.")
73
+ .option("--include-tool <tool>", "Wrapped server tool name to expose. Can be used multiple times.\n" +
74
+ "If omitted, all tools are included.", collect, [])
75
+ .option("--exclude-tool <tool>", "Wrapped server tool name to hide. Can be used multiple times.", collect, []);
76
+ if (options.exitOverride) {
77
+ program.exitOverride();
78
+ }
79
+ return program;
80
+ }
81
+ function parseCliArgsWithOptions(argv, parseOptions = {}) {
82
+ const program = buildProgram(parseOptions);
83
+ program.parse(argv, { from: "user" });
84
+ const parsedOptions = program.opts();
85
+ const backend = parseBackendArg(program.args);
86
+ const justBash = parsedOptions.justBash ?? false;
87
+ const cliMode = parsedOptions.cliMode ?? false;
88
+ const toonify = (parsedOptions.toonify ?? false) || cliMode || justBash;
89
+ return {
90
+ backend,
91
+ justBash,
92
+ cliMode,
93
+ cliPort: parsedOptions.cliPort,
94
+ compressionLevel: parsedOptions.compressionLevel,
95
+ cwd: parsedOptions.cwd,
96
+ env: parsedOptions.env ?? {},
97
+ excludeTools: parsedOptions.excludeTool ?? [],
98
+ headers: parsedOptions.header ?? {},
99
+ includeTools: parsedOptions.includeTool ?? [],
100
+ logLevel: parsedOptions.logLevel,
101
+ serverName: parsedOptions.serverName,
102
+ timeout: Number.parseFloat(parsedOptions.timeout),
103
+ toonify,
104
+ };
105
+ }
106
+ export function parseCliArgs(argv) {
107
+ return parseCliArgsWithOptions(argv, { exitOverride: true });
108
+ }
109
+ function parseClearOAuthArgs(args) {
110
+ if (args[0] !== "clear-oauth") {
111
+ return null;
112
+ }
113
+ const clearProgram = new Command()
114
+ .name("mcp-compressor clear-oauth")
115
+ .exitOverride()
116
+ .allowExcessArguments(false)
117
+ .argument("[backend]", "backend URL or MCP config JSON string")
118
+ .option("--all", "also remove the encryption key");
119
+ clearProgram.parse(args.slice(1), { from: "user" });
120
+ const target = clearProgram.args[0];
121
+ const options = clearProgram.opts();
122
+ return { ...(target ? { target } : {}), all: options.all ?? false };
123
+ }
124
+ function candidateCoreBinaries() {
125
+ const candidates = [];
126
+ if (process.env.MCP_COMPRESSOR_BINARY) {
127
+ candidates.push(process.env.MCP_COMPRESSOR_BINARY);
128
+ }
129
+ candidates.push("mcp-compressor");
130
+ const here = dirname(fileURLToPath(import.meta.url));
131
+ candidates.push(join(here, "..", "..", "target", "debug", process.platform === "win32" ? "mcp-compressor.exe" : "mcp-compressor"));
132
+ candidates.push(join(process.cwd(), "..", "target", "debug", process.platform === "win32" ? "mcp-compressor.exe" : "mcp-compressor"));
133
+ return candidates;
134
+ }
135
+ function translateArgsForRust(args) {
136
+ const clearOAuth = parseClearOAuthArgs(args);
137
+ if (clearOAuth) {
138
+ return clearOAuth.target ? ["clear-oauth", clearOAuth.target] : ["clear-oauth"];
139
+ }
140
+ return args;
141
+ }
142
+ async function runRustCoreCli(args) {
143
+ for (const binary of candidateCoreBinaries()) {
144
+ if (binary !== "mcp-compressor" && !existsSync(binary)) {
145
+ continue;
146
+ }
147
+ const child = spawn(binary, translateArgsForRust(args), { stdio: "inherit" });
148
+ return await new Promise((resolve, reject) => {
149
+ child.on("error", (error) => {
150
+ if (error.code === "ENOENT") {
151
+ resolve(127);
152
+ return;
153
+ }
154
+ reject(error);
155
+ });
156
+ child.on("exit", (code, signal) => {
157
+ if (signal) {
158
+ resolve(1);
159
+ return;
160
+ }
161
+ resolve(code ?? 0);
162
+ });
163
+ });
164
+ }
165
+ console.error("mcp-compressor binary was not found. Build it with `cargo build -p mcp-compressor-core` or set MCP_COMPRESSOR_BINARY.");
166
+ return 127;
167
+ }
168
+ async function main() {
169
+ const exitCode = await runRustCoreCli(process.argv.slice(2));
170
+ process.exitCode = exitCode;
171
+ }
172
+ main().catch((error) => {
173
+ console.error(error instanceof Error ? (error.stack ?? error.message) : String(error));
174
+ process.exitCode = 1;
175
+ });
@@ -0,0 +1,36 @@
1
+ import type { BackendConfig, JsonConfigServerEntry, MCPConfigShape } from "./types.js";
2
+ /**
3
+ * Interpolate environment variables in a single string using ${VAR_NAME} or $VAR_NAME syntax.
4
+ * If a referenced variable is not set, the placeholder is left as-is (matching Python behaviour).
5
+ */
6
+ export declare function interpolateString(value: string): string;
7
+ export declare function interpolateRecord(record: Record<string, string> | undefined): Record<string, string> | undefined;
8
+ /**
9
+ * Interpolate environment variables in all string values of an MCP config object or JSON string.
10
+ *
11
+ * Accepts either a parsed `MCPConfigShape` object or a raw JSON string and returns an interpolated
12
+ * copy with `${VAR_NAME}` and `$VAR_NAME` placeholders replaced by their environment variable
13
+ * values. Unset variables are left as-is.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const config = interpolateMCPConfig({
18
+ * mcpServers: {
19
+ * myServer: { url: "https://example.com", headers: { Authorization: "Bearer $MY_TOKEN" } },
20
+ * },
21
+ * });
22
+ * ```
23
+ */
24
+ export declare function interpolateMCPConfig(config: MCPConfigShape | string): MCPConfigShape;
25
+ /**
26
+ * Parse an MCP config JSON string containing one or more servers.
27
+ *
28
+ * Returns an array of `{ backend, serverName }` entries — one per server in `mcpServers` — or
29
+ * `null` if the input is not a JSON object string. Throws {@link InvalidConfigurationError} for
30
+ * malformed JSON or an empty `mcpServers` map.
31
+ */
32
+ export declare function parseServerConfigJson(input: string): Array<{
33
+ backend: BackendConfig;
34
+ serverName: string;
35
+ }> | null;
36
+ export declare function normalizeConfigServer(entry: JsonConfigServerEntry): BackendConfig;