@atlassian/mcp-compressor 0.21.1 → 0.21.3

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.
@@ -2,22 +2,42 @@ import { defineCommand } from "just-bash";
2
2
  import { parseToolArgv } from "./rust_core.js";
3
3
  import { normalizeStructuredArgValues } from "./tool_specs.js";
4
4
  export function createJustBashCommandRegistrations(sources) {
5
- return sources.map((source) => ({
6
- providerName: source.providerName,
7
- commandName: source.commandName,
8
- backendToolName: source.backendToolName,
9
- helpToolName: source.helpToolName,
10
- command: defineCommand(source.commandName, async (args) => {
11
- try {
12
- const parsedInput = parseToolArgv(source.tool, args);
13
- const toolInput = normalizeStructuredArgValues(source.tool.inputSchema, parsedInput);
14
- return output(await source.invoke(toolInput));
15
- }
16
- catch (error) {
17
- return failure(error);
18
- }
19
- }),
20
- }));
5
+ const grouped = new Map();
6
+ for (const source of sources) {
7
+ grouped.set(source.providerName, [...(grouped.get(source.providerName) ?? []), source]);
8
+ }
9
+ return [...grouped.entries()].map(([providerName, providerSources]) => {
10
+ const bySubcommand = new Map(providerSources.flatMap((source) => [
11
+ [source.commandName, source],
12
+ [source.backendToolName.replaceAll("_", "-"), source],
13
+ [source.backendToolName, source],
14
+ ]));
15
+ const first = providerSources[0];
16
+ return {
17
+ providerName,
18
+ commandName: providerName,
19
+ backendToolName: providerName,
20
+ helpToolName: first?.helpToolName ?? `${providerName}_help`,
21
+ command: defineCommand(providerName, async (args) => {
22
+ try {
23
+ const [subcommand, ...toolArgs] = args;
24
+ if (subcommand === undefined || subcommand === "--help" || subcommand === "-h") {
25
+ return output(dispatcherHelp(providerName, providerSources));
26
+ }
27
+ const source = bySubcommand.get(subcommand);
28
+ if (source === undefined) {
29
+ throw new Error(`Unknown ${providerName} subcommand: ${subcommand}`);
30
+ }
31
+ const parsedInput = parseToolArgv(source.tool, toolArgs);
32
+ const toolInput = normalizeStructuredArgValues(source.tool.inputSchema, parsedInput);
33
+ return output(await source.invoke(toolInput));
34
+ }
35
+ catch (error) {
36
+ return failure(error);
37
+ }
38
+ }),
39
+ };
40
+ });
21
41
  }
22
42
  export function installJustBashRegistrations(bash, registrations) {
23
43
  const host = bash;
@@ -32,6 +52,22 @@ export function installJustBashRegistrations(bash, registrations) {
32
52
  ];
33
53
  }
34
54
  }
55
+ function dispatcherHelp(providerName, sources) {
56
+ const maxNameLength = Math.max(...sources.map((source) => source.backendToolName.replaceAll("_", "-").length), 0);
57
+ return [
58
+ `${providerName} - the ${providerName} toolset`,
59
+ "",
60
+ "USAGE:",
61
+ ` ${providerName} <subcommand> [options]`,
62
+ "",
63
+ "SUBCOMMANDS:",
64
+ ...sources.map((source) => {
65
+ const description = (source.tool.description ?? "").replace(/\s+/g, " ").trim();
66
+ const subcommand = source.backendToolName.replaceAll("_", "-");
67
+ return ` ${subcommand.padEnd(maxNameLength + 2)}${description}`.trimEnd();
68
+ }),
69
+ ].join("\n");
70
+ }
35
71
  function output(stdout) {
36
72
  return { stdout: `${stdout}\n`, stderr: "", exitCode: 0 };
37
73
  }
@@ -17,7 +17,26 @@ export function normalizeServerName(name) {
17
17
  return normalized || "tools";
18
18
  }
19
19
  export function stringifyToolResult(value) {
20
- return typeof value === "string" ? value : JSON.stringify(value);
20
+ if (typeof value === "string")
21
+ return value;
22
+ const mcpText = stringifyMcpTextContent(value);
23
+ if (mcpText !== undefined)
24
+ return mcpText;
25
+ return JSON.stringify(value);
26
+ }
27
+ function stringifyMcpTextContent(value) {
28
+ if (!value || typeof value !== "object" || Array.isArray(value))
29
+ return undefined;
30
+ const content = value.content;
31
+ if (!Array.isArray(content))
32
+ return undefined;
33
+ const textParts = content.flatMap((item) => {
34
+ if (!item || typeof item !== "object" || Array.isArray(item))
35
+ return [];
36
+ const candidate = item;
37
+ return candidate.type === "text" && typeof candidate.text === "string" ? [candidate.text] : [];
38
+ });
39
+ return textParts.length > 0 ? textParts.join("\n") : undefined;
21
40
  }
22
41
  export function normalizeStructuredArgValues(schema, input) {
23
42
  const properties = schema.properties;
@@ -7,7 +7,12 @@ export function transformToolsForJustBash(tools, options) {
7
7
  const serverName = normalizeServerName(options.serverName);
8
8
  const registrations = createJustBashCommandRegistrations(Object.entries(tools).map(([name, tool]) => justBashSource(serverName, name, tool)));
9
9
  installJustBashRegistrations(options.bash, registrations);
10
- const helpDescription = justBashHelpDescription(serverName, registrations);
10
+ const helpDescription = shellToolHelpDescription({
11
+ command: serverName,
12
+ cliName: serverName,
13
+ tools: executableToolsToSpecs(tools),
14
+ commandNameForTool: cliSubcommandName,
15
+ });
11
16
  return {
12
17
  registrations,
13
18
  tools: helpTools({
@@ -89,43 +94,38 @@ function generatedHelpDescription(options) {
89
94
  const command = options.commandName ?? `${options.outputDir}/${options.serverName}`;
90
95
  return cliHelpDescription(command, options.serverName, options.tools);
91
96
  }
92
- const language = options.kind === "python" ? "Python" : "TypeScript";
93
- const moduleName = options.kind === "python" ? `${options.serverName}.py` : `${options.serverName}.ts`;
94
- return [
95
- `Functionality associated with the ${options.serverName} toolset is provided via generated ${language} code. Do not call this tool - import and use the generated code instead.`,
96
- `${options.serverName} - the ${options.serverName} toolset`,
97
- "",
98
- `Generated files are available in ${options.outputDir}.`,
99
- `Primary module: ${options.outputDir}/${moduleName}`,
100
- "",
101
- "When relevant, outputs from generated clients will prefer using the TOON format for more efficient representation of data.",
102
- "",
103
- "Available generated files:",
104
- ...options.files.map((file) => ` - ${options.outputDir}/${file}`),
105
- ].join("\n");
97
+ return codeHelpDescription(options);
106
98
  }
107
99
  function cliHelpDescription(command, cliName, tools) {
100
+ return shellToolHelpDescription({
101
+ command,
102
+ cliName,
103
+ tools,
104
+ commandNameForTool: cliSubcommandName,
105
+ });
106
+ }
107
+ function shellToolHelpDescription(options) {
108
108
  return [
109
- `Functionality associated with the ${cliName} toolset is provided via the \`${command}\` CLI. Do not call this tool - use the CLI instead.`,
110
- `${cliName} - the ${cliName} toolset`,
109
+ `Functionality associated with the ${options.cliName} toolset is provided via the \`${options.command}\` CLI. Do not call this tool - use the CLI instead.`,
110
+ `${options.cliName} - the ${options.cliName} toolset`,
111
111
  "",
112
112
  "When relevant, outputs from this CLI will prefer using the TOON format for more efficient representation of data.",
113
113
  "",
114
114
  "USAGE:",
115
- ` ${command} <subcommand> [options]`,
115
+ ` ${options.command} <subcommand> [options]`,
116
116
  "",
117
117
  "SUBCOMMANDS:",
118
- ...formatSubcommands(tools),
118
+ ...formatSubcommands(options.tools, options.commandNameForTool),
119
119
  "",
120
- `Run '${command} --help' in the shell for usage.`,
121
- `Run '${command} <subcommand> --help' for per-command help.`,
122
- `Run '${command} <subcommand> [options]' to invoke a tool.`,
120
+ `Run '${options.command} --help' in the shell for usage.`,
121
+ `Run '${options.command} <subcommand> --help' for per-command help.`,
122
+ `Run '${options.command} <subcommand> [options]' to invoke a tool.`,
123
123
  ].join("\n");
124
124
  }
125
- function formatSubcommands(tools) {
126
- const maxNameLength = Math.max(...tools.map((tool) => cliSubcommandName(tool.name).length), 0);
125
+ function formatSubcommands(tools, commandNameForTool) {
126
+ const maxNameLength = Math.max(...tools.map((tool) => commandNameForTool(tool.name).length), 0);
127
127
  return tools.map((tool) => {
128
- const subcommand = cliSubcommandName(tool.name);
128
+ const subcommand = commandNameForTool(tool.name);
129
129
  const description = compactDescription(tool.description ?? undefined);
130
130
  return ` ${subcommand.padEnd(maxNameLength + 2)}${description}`.trimEnd();
131
131
  });
@@ -136,19 +136,46 @@ function cliSubcommandName(toolName) {
136
136
  function compactDescription(description) {
137
137
  return (description ?? "").replace(/\s+/g, " ").trim();
138
138
  }
139
- function justBashHelpDescription(serverName, registrations) {
139
+ function codeHelpDescription(options) {
140
+ const language = options.kind === "python" ? "Python" : "TypeScript";
141
+ const languageLower = options.kind === "python" ? "python" : "typescript";
142
+ const moduleName = options.kind === "python" ? `${options.serverName}.py` : `${options.serverName}.ts`;
143
+ const functions = formatCodeFunctions(options.kind, options.tools);
144
+ const details = options.kind === "python"
145
+ ? [
146
+ "For details on a specific function, run:",
147
+ "```python",
148
+ `from ${options.serverName} import <function>`,
149
+ "print(help(<function>))",
150
+ "```",
151
+ ]
152
+ : [
153
+ "For details on a specific function, inspect the generated TypeScript declarations or editor hover documentation.",
154
+ `Primary declarations: ${options.outputDir}/${options.serverName}.d.ts`,
155
+ ];
140
156
  return [
141
- `Functionality associated with the ${serverName} toolset is provided via bash commands. Do not call this tool - use the bash commands instead.`,
142
- `${serverName} - the ${serverName} toolset`,
157
+ `Functionality associated with the ${options.serverName} toolset is provided via a ${language} module. Do not call this tool - import and use the ${languageLower} functionality instead.`,
158
+ `${options.serverName} - the ${options.serverName} toolset`,
143
159
  "",
144
- "When relevant, outputs from these commands will prefer using the TOON format for more efficient representation of data.",
160
+ `${language} source code is available in ${options.outputDir}/${moduleName}`,
145
161
  "",
146
- "COMMANDS:",
147
- ...registrations.map((registration) => ` ${registration.commandName}`),
162
+ "Available functions:",
163
+ ...functions,
148
164
  "",
149
- "Run these commands with the bash tool.",
165
+ ...details,
150
166
  ].join("\n");
151
167
  }
168
+ function formatCodeFunctions(kind, tools) {
169
+ const names = tools.map((tool) => (kind === "python" ? tool.name : snakeToCamel(tool.name)));
170
+ const maxNameLength = Math.max(...names.map((name) => name.length), 0);
171
+ return tools.map((tool, index) => {
172
+ const name = names[index] ?? tool.name;
173
+ return ` ${name.padEnd(maxNameLength + 2)}${compactDescription(tool.description ?? undefined)}`.trimEnd();
174
+ });
175
+ }
176
+ function snakeToCamel(name) {
177
+ return name.replace(/[_-]([a-zA-Z0-9])/g, (_match, char) => char.toUpperCase());
178
+ }
152
179
  function defaultCliOutputDir() {
153
180
  const envDir = process.env.MCP_COMPRESSOR_CLI_OUTPUT_DIR;
154
181
  if (envDir !== undefined && envDir.length > 0) {
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.21.1",
3
+ "version": "0.21.3",
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",