@atlassian/mcp-compressor 0.33.2 → 0.35.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.
@@ -29,15 +29,21 @@ export function createJustBashCommandRegistrations(sources) {
29
29
  }
30
30
  const source = bySubcommand.get(subcommand);
31
31
  if (source === undefined) {
32
- throw new Error(`Unknown ${providerName} subcommand: ${subcommand}`);
32
+ return usageFailure(`parse error: unknown subcommand: ${subcommand}`, providerName);
33
33
  }
34
34
  // Per-subcommand help must match the generated CLI's rich `--help`
35
35
  // output, so render it from the shared Rust renderer.
36
36
  if (toolArgs.includes("--help") || toolArgs.includes("-h")) {
37
37
  return output(renderCliSubcommandHelp(providerName, source.tool));
38
38
  }
39
- const parsedInput = parseToolArgv(source.tool, toolArgs);
40
- const toolInput = normalizeStructuredArgValues(source.tool.inputSchema, parsedInput);
39
+ let toolInput;
40
+ try {
41
+ const parsedInput = parseToolArgv(source.tool, toolArgs);
42
+ toolInput = normalizeStructuredArgValues(source.tool.inputSchema, parsedInput);
43
+ }
44
+ catch (error) {
45
+ return usageFailure(errorMessage(error), `${providerName} ${subcommand}`);
46
+ }
41
47
  return output(await source.invoke(toolInput));
42
48
  }
43
49
  catch (error) {
@@ -63,7 +69,21 @@ export function installJustBashRegistrations(bash, registrations) {
63
69
  function output(stdout) {
64
70
  return { stdout: `${stdout}\n`, stderr: "", exitCode: 0 };
65
71
  }
72
+ function errorMessage(error) {
73
+ return error instanceof Error ? error.message : String(error);
74
+ }
75
+ /** A failed tool call: the tool's own message, exit 1. */
66
76
  function failure(error) {
67
- const message = error instanceof Error ? error.message : String(error);
68
- return { stdout: "", stderr: `${message}\n`, exitCode: 1 };
77
+ return { stdout: "", stderr: `${errorMessage(error)}\n`, exitCode: 1 };
78
+ }
79
+ /**
80
+ * A malformed invocation, reported exactly like the generated CLI script:
81
+ * the parser's message, a pointer to `--help`, and exit 2.
82
+ */
83
+ function usageFailure(message, helpCommand) {
84
+ return {
85
+ stdout: "",
86
+ stderr: `${message}\nRun '${helpCommand} --help' for usage.\n`,
87
+ exitCode: 2,
88
+ };
69
89
  }
@@ -1,4 +1,4 @@
1
- import { compressToolListing, formatToolSchemaResponse } from "./rust_core.js";
1
+ import { compressToolListing, formatToolSchemaResponse, normalizeHostToolResult, } from "./rust_core.js";
2
2
  import { stringifyToolResult } from "./tool_specs.js";
3
3
  function wrapperName(prefix, name) {
4
4
  return prefix ? `${prefix}_${name}` : name;
@@ -36,12 +36,10 @@ function validateRequiredToolInput(spec, toolInput) {
36
36
  JSON.stringify(spec.inputSchema, null, 2));
37
37
  }
38
38
  function normalizeResult(value, toonify) {
39
- const json = stringifyToolResult(value);
40
39
  if (!toonify)
41
- return json;
42
- // Keep local compression dependency-light for now. Runtime MCP proxy paths use
43
- // Rust TOON support; local in-process tools return JSON-compatible strings.
44
- return json;
40
+ return stringifyToolResult(value);
41
+ // Same conversion the proxy and host transforms apply.
42
+ return normalizeHostToolResult(value, true);
45
43
  }
46
44
  export function compressTools(tools, options = {}) {
47
45
  const compressionLevel = options.compressionLevel ?? "medium";
Binary file
Binary file
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.33.2",
3
+ "version": "0.35.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",
@@ -1,75 +0,0 @@
1
- /**
2
- * AST-level pipe/redirection detection for just-bash custom commands.
3
- *
4
- * Registers a ``TransformPlugin`` that walks the parsed AST and injects
5
- * ``MCP_TOONIFY=true|false`` env-var prefixes onto each invocation of one
6
- * of our wrapper commands, based on whether its stdout is piped
7
- * (``cmd | jq``) or redirected (``cmd > out.json``). The wrapper command
8
- * in ``bash_commands.ts`` reads ``ctx.env.MCP_TOONIFY`` to choose between
9
- * TOON and raw JSON output. User-supplied ``MCP_TOONIFY=...`` prefixes
10
- * are preserved.
11
- */
12
- import type { Bash } from "just-bash";
13
- /** Env var injected into wrapper invocations: "true" -> TOON, "false" -> JSON. */
14
- export declare const MCP_TOONIFY_ENV_VAR = "MCP_TOONIFY";
15
- interface WordPart {
16
- type: string;
17
- value?: string;
18
- }
19
- interface WordNode {
20
- type: "Word";
21
- parts: WordPart[];
22
- }
23
- interface AssignmentNode {
24
- type: "Assignment";
25
- name: string;
26
- value: WordNode | null;
27
- append: boolean;
28
- array: WordNode[] | null;
29
- }
30
- interface RedirectionNode {
31
- type: "Redirection";
32
- fd: number | null;
33
- fdVariable?: string;
34
- operator: string;
35
- target: unknown;
36
- }
37
- interface SimpleCommandNode {
38
- type: "SimpleCommand";
39
- assignments: AssignmentNode[];
40
- name: WordNode | null;
41
- args: WordNode[];
42
- redirections: RedirectionNode[];
43
- line?: number;
44
- }
45
- interface PipelineNode {
46
- type: "Pipeline";
47
- commands: AnyCommandNode[];
48
- }
49
- interface StatementNode {
50
- type: "Statement";
51
- pipelines: PipelineNode[];
52
- }
53
- interface ScriptNode {
54
- type: "Script";
55
- statements: StatementNode[];
56
- }
57
- type AnyCommandNode = SimpleCommandNode | Record<string, unknown>;
58
- interface TransformInput {
59
- ast: ScriptNode;
60
- metadata: Record<string, unknown>;
61
- }
62
- interface TransformOutput {
63
- ast: ScriptNode;
64
- metadata?: Record<string, unknown>;
65
- }
66
- interface TransformPlugin {
67
- transform(input: TransformInput): TransformOutput;
68
- }
69
- /** Build a ``TransformPlugin`` that injects ``MCP_TOONIFY`` prefixes (in-place AST mutation). */
70
- export declare function createPipingHintPlugin(customCommandNames: readonly string[]): TransformPlugin;
71
- /** Read ``MCP_TOONIFY`` from *env*, returning *defaultValue* if absent/invalid. */
72
- export declare function resolveToonifyFromEnv(env: Map<string, string> | Record<string, string> | undefined, defaultValue: boolean): boolean;
73
- /** Register :func:`createPipingHintPlugin` on *bash*. */
74
- export declare function installPipingHintPlugin(bash: Bash, customCommandNames: readonly string[]): void;
75
- export {};
@@ -1,167 +0,0 @@
1
- /**
2
- * AST-level pipe/redirection detection for just-bash custom commands.
3
- *
4
- * Registers a ``TransformPlugin`` that walks the parsed AST and injects
5
- * ``MCP_TOONIFY=true|false`` env-var prefixes onto each invocation of one
6
- * of our wrapper commands, based on whether its stdout is piped
7
- * (``cmd | jq``) or redirected (``cmd > out.json``). The wrapper command
8
- * in ``bash_commands.ts`` reads ``ctx.env.MCP_TOONIFY`` to choose between
9
- * TOON and raw JSON output. User-supplied ``MCP_TOONIFY=...`` prefixes
10
- * are preserved.
11
- */
12
- /** Env var injected into wrapper invocations: "true" -> TOON, "false" -> JSON. */
13
- export const MCP_TOONIFY_ENV_VAR = "MCP_TOONIFY";
14
- /** Redirection operators that move stdout off the caller's pipe. */
15
- const OUTPUT_REDIR_OPERATORS = new Set([">", ">>", ">|", "&>", "&>>", "<>"]);
16
- // --- helpers ----------------------------------------------------------------
17
- function isLiteralPart(part) {
18
- return part.type === "Literal" && typeof part.value === "string";
19
- }
20
- /** Return *cmd*'s literal name, or ``null`` for dynamic invocations. */
21
- function simpleCommandName(cmd) {
22
- if (cmd.name === null) {
23
- return null;
24
- }
25
- const pieces = [];
26
- for (const part of cmd.name.parts) {
27
- if (!isLiteralPart(part)) {
28
- return null;
29
- }
30
- pieces.push(part.value);
31
- }
32
- const joined = pieces.join("");
33
- return joined.length > 0 ? joined : null;
34
- }
35
- /** Return ``true`` if *cmd* redirects fd 1 (stdout) somewhere else. */
36
- function hasStdoutRedirection(cmd) {
37
- for (const redir of cmd.redirections ?? []) {
38
- if (!OUTPUT_REDIR_OPERATORS.has(redir.operator)) {
39
- continue;
40
- }
41
- // fd defaults to 1 for >/>>/>|; &>/&>> always cover stdout.
42
- if (redir.fd === null ||
43
- redir.fd === 1 ||
44
- redir.operator === "&>" ||
45
- redir.operator === "&>>") {
46
- return true;
47
- }
48
- }
49
- return false;
50
- }
51
- function makeAssignment(name, value) {
52
- return {
53
- type: "Assignment",
54
- name,
55
- value: { type: "Word", parts: [{ type: "Literal", value }] },
56
- append: false,
57
- array: null,
58
- };
59
- }
60
- function isSimpleCommand(node) {
61
- return (typeof node === "object" &&
62
- node !== null &&
63
- node.type === "SimpleCommand");
64
- }
65
- function injectToonify(cmd, toonify) {
66
- // Preserve user-set MCP_TOONIFY assignments.
67
- for (const assignment of cmd.assignments ?? []) {
68
- if (assignment.name === MCP_TOONIFY_ENV_VAR) {
69
- return;
70
- }
71
- }
72
- cmd.assignments = [
73
- makeAssignment(MCP_TOONIFY_ENV_VAR, toonify ? "true" : "false"),
74
- ...(cmd.assignments ?? []),
75
- ];
76
- }
77
- function transformPipeline(pipeline, names) {
78
- const n = pipeline.commands.length;
79
- pipeline.commands.forEach((cmd, index) => {
80
- if (isSimpleCommand(cmd)) {
81
- const name = simpleCommandName(cmd);
82
- if (name !== null && names.has(name)) {
83
- const isLast = index === n - 1;
84
- const isPiped = !isLast || hasStdoutRedirection(cmd);
85
- injectToonify(cmd, !isPiped);
86
- }
87
- }
88
- else {
89
- transformCompound(cmd, names);
90
- }
91
- });
92
- }
93
- function transformStatements(statements, names) {
94
- for (const stmt of statements) {
95
- for (const pipeline of stmt.pipelines) {
96
- transformPipeline(pipeline, names);
97
- }
98
- }
99
- }
100
- /** Recurse into compound nodes (``if``/``for``/``while``/subshell/etc.) by shape. */
101
- function transformCompound(node, names) {
102
- for (const value of Object.values(node)) {
103
- if (Array.isArray(value)) {
104
- for (const item of value) {
105
- if (item && typeof item === "object") {
106
- const t = item.type;
107
- if (t === "Statement") {
108
- transformStatements(value, names);
109
- break;
110
- }
111
- if ("body" in item ||
112
- "condition" in item ||
113
- "patterns" in item ||
114
- "clauses" in item ||
115
- "elseBody" in item) {
116
- transformCompound(item, names);
117
- }
118
- }
119
- }
120
- }
121
- else if (value && typeof value === "object") {
122
- transformCompound(value, names);
123
- }
124
- }
125
- }
126
- // --- public plugin factory --------------------------------------------------
127
- /** Build a ``TransformPlugin`` that injects ``MCP_TOONIFY`` prefixes (in-place AST mutation). */
128
- export function createPipingHintPlugin(customCommandNames) {
129
- const names = new Set(customCommandNames);
130
- return {
131
- transform({ ast, metadata }) {
132
- if (names.size === 0) {
133
- return { ast, metadata };
134
- }
135
- transformStatements(ast.statements, names);
136
- return { ast, metadata };
137
- },
138
- };
139
- }
140
- /** Read ``MCP_TOONIFY`` from *env*, returning *defaultValue* if absent/invalid. */
141
- export function resolveToonifyFromEnv(env, defaultValue) {
142
- if (!env) {
143
- return defaultValue;
144
- }
145
- let raw;
146
- if (env instanceof Map) {
147
- raw = env.get(MCP_TOONIFY_ENV_VAR);
148
- }
149
- else {
150
- raw = env[MCP_TOONIFY_ENV_VAR];
151
- }
152
- if (raw === undefined) {
153
- return defaultValue;
154
- }
155
- const normalized = raw.trim().toLowerCase();
156
- if (normalized === "true") {
157
- return true;
158
- }
159
- if (normalized === "false") {
160
- return false;
161
- }
162
- return defaultValue;
163
- }
164
- /** Register :func:`createPipingHintPlugin` on *bash*. */
165
- export function installPipingHintPlugin(bash, customCommandNames) {
166
- bash.registerTransformPlugin(createPipingHintPlugin(customCommandNames));
167
- }