@atlassian/mcp-compressor 0.35.0 → 0.36.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/dist/cli.js CHANGED
@@ -1,9 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command, Option } from "commander";
3
3
  import { spawn } from "node:child_process";
4
- import { accessSync, closeSync, constants, existsSync, openSync, readSync } from "node:fs";
5
- import { delimiter, dirname, join } from "node:path";
6
- import { fileURLToPath } from "node:url";
4
+ import { loadNativeCore } from "./native.js";
7
5
  import { VERSION } from "./version.js";
8
6
  function parseBackendArg(backendArgs) {
9
7
  if (backendArgs.length === 0) {
@@ -120,90 +118,6 @@ function parseClearOAuthArgs(args) {
120
118
  const options = clearProgram.opts();
121
119
  return { ...(target ? { target } : {}), all: options.all ?? false };
122
120
  }
123
- /**
124
- * Resolve a bare binary name to an absolute path using the PATH environment
125
- * variable. Returns null when the name cannot be found in any PATH directory.
126
- */
127
- function resolveFromPath(name) {
128
- const pathEnv = process.env.PATH ?? "";
129
- const dirs = pathEnv.split(delimiter);
130
- const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";") : [""];
131
- for (const dir of dirs) {
132
- for (const ext of exts) {
133
- const candidate = join(dir, name + ext);
134
- try {
135
- accessSync(candidate, constants.X_OK);
136
- return candidate;
137
- }
138
- catch {
139
- // not executable or not found — try next
140
- }
141
- }
142
- }
143
- return null;
144
- }
145
- /**
146
- * Return true when the file at `filePath` begins with a native-executable
147
- * magic number (ELF, Mach-O, or PE). A JS shim that starts with a `#!`
148
- * shebang (or any other non-native header) returns false.
149
- *
150
- * This guards against the fork-bomb that occurs when the npm-installed
151
- * `mcp-compressor` bin shim is the only entry on PATH: spawning it would
152
- * re-invoke this very script rather than the Rust binary.
153
- */
154
- function isNativeExecutable(filePath) {
155
- let fd;
156
- try {
157
- fd = openSync(filePath, "r");
158
- }
159
- catch {
160
- return false;
161
- }
162
- try {
163
- const buf = Buffer.alloc(4);
164
- const n = readSync(fd, buf, 0, 4, 0);
165
- if (n < 4)
166
- return false;
167
- // ELF (Linux/BSD): \x7fELF
168
- if (buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46)
169
- return true;
170
- // PE (Windows): MZ
171
- if (buf[0] === 0x4d && buf[1] === 0x5a)
172
- return true;
173
- // Mach-O (macOS) — big-endian and little-endian variants, including fat/universal
174
- const be = buf.readUInt32BE(0);
175
- if (be === 0xfeedface || // Mach-O 32-bit BE
176
- be === 0xfeedfacf || // Mach-O 64-bit BE
177
- be === 0xcefaedfe || // Mach-O 32-bit LE
178
- be === 0xcffaedfe || // Mach-O 64-bit LE
179
- be === 0xcafebabe || // Mach-O fat binary
180
- be === 0xbebafeca // Mach-O fat binary LE
181
- )
182
- return true;
183
- return false;
184
- }
185
- finally {
186
- closeSync(fd);
187
- }
188
- }
189
- function candidateCoreBinaries() {
190
- const candidates = [];
191
- if (process.env.MCP_COMPRESSOR_BINARY) {
192
- candidates.push(process.env.MCP_COMPRESSOR_BINARY);
193
- }
194
- // Resolve the bare name from PATH and only include it when the resolved
195
- // file is a native binary, not a JS shim. Without this check the npm bin
196
- // shim (which maps `mcp-compressor` back to this very script) would be
197
- // spawned indefinitely, creating a fork bomb.
198
- const resolved = resolveFromPath("mcp-compressor");
199
- if (resolved !== null && isNativeExecutable(resolved)) {
200
- candidates.push(resolved);
201
- }
202
- const here = dirname(fileURLToPath(import.meta.url));
203
- candidates.push(join(here, "..", "..", "target", "debug", process.platform === "win32" ? "mcp-compressor.exe" : "mcp-compressor"));
204
- candidates.push(join(process.cwd(), "..", "target", "debug", process.platform === "win32" ? "mcp-compressor.exe" : "mcp-compressor"));
205
- return candidates;
206
- }
207
121
  function translateArgsForRust(args) {
208
122
  const clearOAuth = parseClearOAuthArgs(args);
209
123
  if (clearOAuth) {
@@ -211,34 +125,44 @@ function translateArgsForRust(args) {
211
125
  }
212
126
  return args;
213
127
  }
128
+ /**
129
+ * Run the Rust CLI. The npm package ships the native addon, so by default the
130
+ * CLI runs in-process and needs no separately installed binary (like the
131
+ * Python package). `MCP_COMPRESSOR_BINARY` runs a specific binary instead.
132
+ */
214
133
  async function runRustCoreCli(args) {
215
- for (const binary of candidateCoreBinaries()) {
216
- if (!existsSync(binary)) {
217
- continue;
218
- }
219
- const child = spawn(binary, translateArgsForRust(args), { stdio: "inherit" });
220
- return await new Promise((resolve, reject) => {
221
- child.on("error", (error) => {
222
- if (error.code === "ENOENT") {
223
- resolve(127);
224
- return;
225
- }
226
- reject(error);
227
- });
228
- child.on("exit", (code, signal) => {
229
- if (signal) {
230
- resolve(1);
231
- return;
232
- }
233
- resolve(code ?? 0);
234
- });
235
- });
134
+ const rustArgs = translateArgsForRust(args);
135
+ const binary = process.env.MCP_COMPRESSOR_BINARY;
136
+ if (!binary) {
137
+ return loadNativeCore().runCliJson(JSON.stringify(["mcp-compressor", ...rustArgs]), VERSION);
236
138
  }
237
- console.error("mcp-compressor binary was not found. Build it with `cargo build -p mcp-compressor-core` or set MCP_COMPRESSOR_BINARY.");
238
- return 127;
139
+ return await new Promise((resolve, reject) => {
140
+ const child = spawn(binary, rustArgs, { stdio: "inherit" });
141
+ child.on("error", (error) => {
142
+ if (error.code === "ENOENT") {
143
+ console.error(`MCP_COMPRESSOR_BINARY not found: ${binary}`);
144
+ resolve(127);
145
+ return;
146
+ }
147
+ reject(error);
148
+ });
149
+ child.on("exit", (code, signal) => {
150
+ if (signal) {
151
+ resolve(1);
152
+ return;
153
+ }
154
+ resolve(code ?? 0);
155
+ });
156
+ });
239
157
  }
240
158
  async function main() {
241
- const exitCode = await runRustCoreCli(process.argv.slice(2));
159
+ const args = process.argv.slice(2);
160
+ if (args.length === 1 && (args[0] === "--version" || args[0] === "-V")) {
161
+ // The Rust core is versioned by the npm release, not its Cargo manifest.
162
+ console.log(`mcp-compressor ${VERSION}`);
163
+ return;
164
+ }
165
+ const exitCode = await runRustCoreCli(args);
242
166
  process.exitCode = exitCode;
243
167
  }
244
168
  main().catch((error) => {
package/dist/native.d.ts CHANGED
@@ -19,13 +19,15 @@ export interface NativeCore {
19
19
  rememberOauthBackendJson(backendUri: string, backendName: string, storeDir: string): void;
20
20
  listOauthCredentialsJson(): string;
21
21
  clearOauthCredentialsJson(target?: string | null): string;
22
+ /** Run the whole CLI in-process; blocks until it exits. */
23
+ runCliJson(argvJson: string, version?: string | null): number;
22
24
  startCompressedSessionJson(configJson: string, backendsJson: string): Promise<NativeCompressedSession>;
23
25
  startCompressedSessionWithProviderBackendsJson(configJson: string, backendsJson: string, providersJson: string, refreshProviders: () => Promise<string>): Promise<NativeCompressedSession>;
24
26
  startCompressedSessionFromMcpConfigJson(configJson: string, mcpConfigJson: string): Promise<NativeCompressedSession>;
25
27
  }
26
28
  export interface NativeCompressedSession {
27
29
  infoJson(): string;
28
- close(): void;
30
+ close(): Promise<void>;
29
31
  updateAuthProviderHeadersJson(providerIndex: number, headersJson: string): void;
30
32
  }
31
33
  export declare function loadNativeCore(): NativeCore;
@@ -65,12 +65,13 @@ export declare function normalizeServers(servers: NativeServersInput): Promise<N
65
65
  * Internal; not part of the public package surface.
66
66
  */
67
67
  export declare function adoptNativeSession<T>(session: {
68
- close(): void;
68
+ close(): void | Promise<void>;
69
69
  }, build: () => T): T;
70
70
  export declare class CompressorProxy {
71
71
  private readonly session;
72
72
  private readonly defaultServer;
73
73
  private closed;
74
+ private closing;
74
75
  constructor(session: CompressedSession, defaultServer: string | null);
75
76
  info(): CompressedSessionInfo;
76
77
  get bridgeUrl(): string;
@@ -84,7 +85,7 @@ export declare class CompressorProxy {
84
85
  invoke(tool: string, toolInput?: Record<string, unknown>, options?: {
85
86
  server?: string;
86
87
  }): Promise<string>;
87
- close(): void;
88
+ close(): Promise<void>;
88
89
  toExecutableTools(): Record<string, ExecutableTool>;
89
90
  writeClient(kind: GeneratedClientKind, outputDir: string, options?: {
90
91
  name?: string;
@@ -169,7 +169,7 @@ export function adoptNativeSession(session, build) {
169
169
  }
170
170
  catch (error) {
171
171
  try {
172
- session.close();
172
+ void Promise.resolve(session.close()).catch(() => { });
173
173
  }
174
174
  catch {
175
175
  // Closing a session that never became reachable is best effort.
@@ -181,6 +181,7 @@ export class CompressorProxy {
181
181
  session;
182
182
  defaultServer;
183
183
  closed = false;
184
+ closing = null;
184
185
  constructor(session, defaultServer) {
185
186
  this.session = session;
186
187
  this.defaultServer = defaultServer;
@@ -254,18 +255,16 @@ export class CompressorProxy {
254
255
  return (await this.invokeWrapper(wrapper, { tool_name: tool, tool_input: toolInput })).text;
255
256
  }
256
257
  close() {
257
- if (this.closed) {
258
- return;
258
+ if (this.closing) {
259
+ return this.closing;
259
260
  }
260
261
  this.closed = true;
261
- try {
262
- this.session.close();
263
- }
264
- finally {
265
- const onClose = proxyCloseCallbacks.get(this);
266
- proxyCloseCallbacks.delete(this);
267
- onClose?.();
268
- }
262
+ const onClose = proxyCloseCallbacks.get(this);
263
+ proxyCloseCallbacks.delete(this);
264
+ onClose?.();
265
+ const closing = this.session.close();
266
+ this.closing = closing.catch(() => { });
267
+ return closing;
269
268
  }
270
269
  toExecutableTools() {
271
270
  const result = {};
@@ -394,7 +393,7 @@ export class CompressorClient {
394
393
  }
395
394
  const proxy = this.proxy;
396
395
  try {
397
- proxy?.close();
396
+ await proxy?.close();
398
397
  }
399
398
  finally {
400
399
  if (this.proxy === proxy) {
@@ -83,9 +83,10 @@ export interface CompressedSessionInfo {
83
83
  }
84
84
  export declare class CompressedSession {
85
85
  private readonly nativeSession;
86
+ private closing;
86
87
  constructor(nativeSession: NativeCompressedSession);
87
88
  info(): CompressedSessionInfo;
88
- close(): void;
89
+ close(): Promise<void>;
89
90
  updateAuthProviderHeaders(providerIndex: number, headers: Record<string, string>): void;
90
91
  }
91
92
  export declare function startCompressedSession(config: CompressedSessionConfig, backends: BackendConfig[]): Promise<CompressedSession>;
package/dist/rust_core.js CHANGED
@@ -54,6 +54,7 @@ export function generateClientArtifactFiles(kind, config) {
54
54
  }
55
55
  export class CompressedSession {
56
56
  nativeSession;
57
+ closing = null;
57
58
  constructor(nativeSession) {
58
59
  this.nativeSession = nativeSession;
59
60
  }
@@ -61,7 +62,12 @@ export class CompressedSession {
61
62
  return JSON.parse(this.nativeSession.infoJson());
62
63
  }
63
64
  close() {
64
- this.nativeSession.close();
65
+ if (this.closing) {
66
+ return this.closing;
67
+ }
68
+ const closing = this.nativeSession.close();
69
+ this.closing = closing.catch(() => { });
70
+ return closing;
65
71
  }
66
72
  updateAuthProviderHeaders(providerIndex, headers) {
67
73
  this.nativeSession.updateAuthProviderHeadersJson(providerIndex, stringify(headers));
package/native/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  /* eslint-disable */
3
3
  export declare class NativeCompressedSession {
4
4
  infoJson(): string
5
- close(): void
5
+ close(): Promise<void>
6
6
  updateAuthProviderHeadersJson(providerIndex: number, headersJson: string): void
7
7
  }
8
8
 
@@ -36,6 +36,14 @@ export declare function renderCliSubcommandHelpJson(cliName: string, toolJson: s
36
36
 
37
37
  export declare function renderCliTopLevelHelpJson(command: string, cliName: string, toolsJson: string): string
38
38
 
39
+ /**
40
+ * Run the full `mcp-compressor` CLI in-process and return its exit code.
41
+ *
42
+ * Blocks the calling thread until the CLI exits, so the npm `mcp-compressor`
43
+ * bin works without a separately installed Rust binary. Not for library use.
44
+ */
45
+ export declare function runCliJson(argvJson: string, version?: string | undefined | null): number
46
+
39
47
  export declare function startCompressedSessionFromMcpConfigJson(configJson: string, mcpConfigJson: string): Promise<NativeCompressedSession>
40
48
 
41
49
  export declare function startCompressedSessionJson(configJson: string, backendsJson: string): Promise<NativeCompressedSession>
Binary file
Binary file
package/native/index.js CHANGED
@@ -592,6 +592,7 @@ module.exports.parseToolArgvJson = nativeBinding.parseToolArgvJson
592
592
  module.exports.rememberOauthBackendJson = nativeBinding.rememberOauthBackendJson
593
593
  module.exports.renderCliSubcommandHelpJson = nativeBinding.renderCliSubcommandHelpJson
594
594
  module.exports.renderCliTopLevelHelpJson = nativeBinding.renderCliTopLevelHelpJson
595
+ module.exports.runCliJson = nativeBinding.runCliJson
595
596
  module.exports.startCompressedSessionFromMcpConfigJson = nativeBinding.startCompressedSessionFromMcpConfigJson
596
597
  module.exports.startCompressedSessionJson = nativeBinding.startCompressedSessionJson
597
598
  module.exports.startCompressedSessionWithProviderBackendsJson = nativeBinding.startCompressedSessionWithProviderBackendsJson
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlassian/mcp-compressor",
3
- "version": "0.35.0",
3
+ "version": "0.36.0",
4
4
  "description": "TypeScript MCP server wrapper for reducing tokens consumed by MCP tools.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/atlassian-labs/mcp-compressor",