just-bash-mcp 2.9.4 → 3.0.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.
@@ -9,230 +9,135 @@
9
9
  * - OutputMessage type for streaming output
10
10
  */
11
11
 
12
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
- import {
14
- NetworkAccessDeniedError,
15
- RedirectNotAllowedError,
16
- SecurityViolationError,
17
- TooManyRedirectsError,
18
- type OutputMessage,
19
- } from "just-bash";
20
- import { z } from "zod/v4";
21
- import { config } from "../config/index.ts";
22
- import {
23
- createErrorResponse,
24
- createJsonResponse,
25
- createSuccessResponse,
26
- truncateOutput,
27
- } from "../utils/index.ts";
28
- import { getPersistentSandbox, resetPersistentSandbox } from "./bash-instance.ts";
12
+ import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
13
+ import {NetworkAccessDeniedError, RedirectNotAllowedError, SecurityViolationError, TooManyRedirectsError, type OutputMessage} from 'just-bash'
14
+ import {z} from 'zod/v4'
15
+ import {config} from '../config/index.ts'
16
+ import {createErrorResponse, createJsonResponse, createSuccessResponse, truncateOutput} from '../utils/index.ts'
17
+ import {getPersistentSandbox, resetPersistentSandbox} from './bash-instance.ts'
29
18
 
30
19
  function classifyError(error: unknown, prefix: string) {
31
- if (error instanceof NetworkAccessDeniedError) {
32
- return createErrorResponse(error, `${prefix} [Network Access Denied]`);
33
- }
34
- if (error instanceof TooManyRedirectsError) {
35
- return createErrorResponse(error, `${prefix} [Too Many Redirects]`);
36
- }
37
- if (error instanceof RedirectNotAllowedError) {
38
- return createErrorResponse(error, `${prefix} [Redirect Not Allowed]`);
39
- }
40
- if (error instanceof SecurityViolationError) {
41
- return createErrorResponse(error, `${prefix} [Security Violation]`);
42
- }
43
- return createErrorResponse(error, prefix);
20
+ if (error instanceof NetworkAccessDeniedError) {
21
+ return createErrorResponse(error, `${prefix} [Network Access Denied]`)
22
+ }
23
+ if (error instanceof TooManyRedirectsError) {
24
+ return createErrorResponse(error, `${prefix} [Too Many Redirects]`)
25
+ }
26
+ if (error instanceof RedirectNotAllowedError) {
27
+ return createErrorResponse(error, `${prefix} [Redirect Not Allowed]`)
28
+ }
29
+ if (error instanceof SecurityViolationError) {
30
+ return createErrorResponse(error, `${prefix} [Security Violation]`)
31
+ }
32
+ return createErrorResponse(error, prefix)
44
33
  }
45
34
 
46
35
  export function registerSandboxTools(server: McpServer): void {
47
- server.registerTool(
48
- "bash_sandbox_run",
49
- {
50
- description:
51
- "Run a command in a persistent isolated environment with optional structured output and logs.",
52
- inputSchema: {
53
- command: z.string().describe("The command to execute"),
54
- cwd: z.string().optional().describe("Working directory for the command"),
55
- env: z.record(z.string(), z.string()).optional().describe("Environment variables to set"),
56
- includeOutput: z
57
- .boolean()
58
- .optional()
59
- .describe("Include structured output messages from SandboxCommand.output()"),
60
- includeLogs: z
61
- .boolean()
62
- .optional()
63
- .describe("Include execution logs from SandboxCommand.logs()"),
64
- },
65
- },
66
- async ({
67
- command,
68
- cwd,
69
- env,
70
- includeOutput = false,
71
- includeLogs = false,
72
- }: {
73
- command: string;
74
- cwd?: string;
75
- env?: Record<string, string>;
76
- includeOutput?: boolean;
77
- includeLogs?: boolean;
78
- }) => {
79
- try {
80
- const sandbox = await getPersistentSandbox();
81
- const cmd = await sandbox.runCommand(command, { cwd, env });
82
- const result = await cmd.wait();
83
- const stdout = await cmd.stdout();
84
- const stderr = await cmd.stderr();
36
+ server.registerTool(
37
+ 'bash_sandbox_run',
38
+ {
39
+ description: 'Run a command in a persistent isolated environment with optional structured output and logs.',
40
+ inputSchema: {
41
+ command: z.string().describe('The command to execute'),
42
+ cwd: z.string().optional().describe('Working directory for the command'),
43
+ env: z.record(z.string(), z.string()).optional().describe('Environment variables to set'),
44
+ includeOutput: z.boolean().optional().describe('Include structured output messages from SandboxCommand.output()'),
45
+ includeLogs: z.boolean().optional().describe('Include execution logs from SandboxCommand.logs()')
46
+ }
47
+ },
48
+ async ({command, cwd, env, includeOutput = false, includeLogs = false}: {command: string; cwd?: string; env?: Record<string, string>; includeOutput?: boolean; includeLogs?: boolean}) => {
49
+ try {
50
+ const sandbox = await getPersistentSandbox()
51
+ const cmd = await sandbox.runCommand(command, {cwd, env})
52
+ const result = await cmd.wait()
53
+ const stdout = await cmd.stdout()
54
+ const stderr = await cmd.stderr()
85
55
 
86
- const response: {
87
- stdout: string;
88
- stderr: string;
89
- exitCode: number;
90
- output?: string;
91
- logs?: OutputMessage[];
92
- } = {
93
- stdout: truncateOutput(stdout, config.MAX_OUTPUT_LENGTH, "stdout"),
94
- stderr: truncateOutput(stderr, config.MAX_OUTPUT_LENGTH, "stderr"),
95
- exitCode: result.exitCode,
96
- };
56
+ const response: {stdout: string; stderr: string; exitCode: number; output?: string; logs?: OutputMessage[]} = {stdout: truncateOutput(stdout, config.MAX_OUTPUT_LENGTH, 'stdout'), stderr: truncateOutput(stderr, config.MAX_OUTPUT_LENGTH, 'stderr'), exitCode: result.exitCode}
97
57
 
98
- if (includeOutput) {
99
- response.output = truncateOutput(await cmd.output(), config.MAX_OUTPUT_LENGTH, "stdout");
100
- }
101
- if (includeLogs) {
102
- const logs: OutputMessage[] = [];
103
- for await (const message of cmd.logs()) {
104
- logs.push(message);
105
- }
106
- response.logs = logs;
107
- }
58
+ if (includeOutput) {
59
+ response.output = truncateOutput(await cmd.output(), config.MAX_OUTPUT_LENGTH, 'stdout')
60
+ }
61
+ if (includeLogs) {
62
+ const logs: OutputMessage[] = []
63
+ for await (const message of cmd.logs()) {
64
+ logs.push(message)
65
+ }
66
+ response.logs = logs
67
+ }
108
68
 
109
- return createJsonResponse(response, result.exitCode !== 0);
110
- } catch (error) {
111
- return classifyError(error, "Sandbox error");
112
- }
113
- },
114
- );
69
+ return createJsonResponse(response, result.exitCode !== 0)
70
+ } catch (error) {
71
+ return classifyError(error, 'Sandbox error')
72
+ }
73
+ }
74
+ )
115
75
 
116
- server.registerTool(
117
- "bash_sandbox_domain",
118
- {
119
- description: "Get the current sandbox domain or identifier.",
120
- inputSchema: {},
121
- },
122
- async () => {
123
- try {
124
- const sandbox = await getPersistentSandbox();
125
- return createJsonResponse({ domain: sandbox.domain });
126
- } catch (error) {
127
- return classifyError(error, "Domain error");
128
- }
129
- },
130
- );
76
+ server.registerTool('bash_sandbox_domain', {description: 'Get the current sandbox domain or identifier.', inputSchema: {}}, async () => {
77
+ try {
78
+ const sandbox = await getPersistentSandbox()
79
+ return createJsonResponse({domain: sandbox.domain})
80
+ } catch (error) {
81
+ return classifyError(error, 'Domain error')
82
+ }
83
+ })
131
84
 
132
- server.registerTool(
133
- "bash_sandbox_write_files",
134
- {
135
- description: "Write multiple files to the persistent isolated environment at once.",
136
- inputSchema: {
137
- files: z.record(z.string(), z.string()).describe("Files to write (path -> content)"),
138
- },
139
- },
140
- async ({ files }: { files: Record<string, string> }) => {
141
- try {
142
- const sandbox = await getPersistentSandbox();
143
- await sandbox.writeFiles(files);
85
+ server.registerTool('bash_sandbox_write_files', {description: 'Write multiple files to the persistent isolated environment at once.', inputSchema: {files: z.record(z.string(), z.string()).describe('Files to write (path -> content)')}}, async ({files}: {files: Record<string, string>}) => {
86
+ try {
87
+ const sandbox = await getPersistentSandbox()
88
+ await sandbox.writeFiles(files)
144
89
 
145
- return createSuccessResponse(
146
- `Successfully wrote ${Object.keys(files).length} file(s): ${Object.keys(files).join(", ")}`,
147
- );
148
- } catch (error) {
149
- return classifyError(error, "Write error");
150
- }
151
- },
152
- );
90
+ return createSuccessResponse(`Successfully wrote ${Object.keys(files).length} file(s): ${Object.keys(files).join(', ')}`)
91
+ } catch (error) {
92
+ return classifyError(error, 'Write error')
93
+ }
94
+ })
153
95
 
154
- server.registerTool(
155
- "bash_sandbox_read_file",
156
- {
157
- description: "Read a file from the persistent isolated environment.",
158
- inputSchema: {
159
- path: z.string().describe("The file path to read"),
160
- encoding: z.enum(["utf-8", "base64"]).optional().describe("File encoding (default: utf-8)"),
161
- },
162
- },
163
- async ({ path, encoding = "utf-8" }: { path: string; encoding?: "utf-8" | "base64" }) => {
164
- try {
165
- const sandbox = await getPersistentSandbox();
166
- const content = await sandbox.readFile(path, encoding);
96
+ server.registerTool(
97
+ 'bash_sandbox_read_file',
98
+ {description: 'Read a file from the persistent isolated environment.', inputSchema: {path: z.string().describe('The file path to read'), encoding: z.enum(['utf-8', 'base64']).optional().describe('File encoding (default: utf-8)')}},
99
+ async ({path, encoding = 'utf-8'}: {path: string; encoding?: 'utf-8' | 'base64'}) => {
100
+ try {
101
+ const sandbox = await getPersistentSandbox()
102
+ const content = await sandbox.readFile(path, encoding)
167
103
 
168
- return {
169
- content: [
170
- {
171
- type: "text" as const,
172
- text: truncateOutput(content, config.MAX_OUTPUT_LENGTH, "stdout"),
173
- },
174
- ],
175
- };
176
- } catch (error) {
177
- return classifyError(error, "Read error");
178
- }
179
- },
180
- );
104
+ return {content: [{type: 'text' as const, text: truncateOutput(content, config.MAX_OUTPUT_LENGTH, 'stdout')}]}
105
+ } catch (error) {
106
+ return classifyError(error, 'Read error')
107
+ }
108
+ }
109
+ )
181
110
 
182
- server.registerTool(
183
- "bash_sandbox_mkdir",
184
- {
185
- description: "Create a directory in the persistent isolated environment.",
186
- inputSchema: {
187
- path: z.string().describe("The directory path to create"),
188
- recursive: z
189
- .boolean()
190
- .optional()
191
- .describe("Create parent directories if needed (default: true)"),
192
- },
193
- },
194
- async ({ path, recursive = true }: { path: string; recursive?: boolean }) => {
195
- try {
196
- const sandbox = await getPersistentSandbox();
197
- await sandbox.mkDir(path, { recursive });
111
+ server.registerTool(
112
+ 'bash_sandbox_mkdir',
113
+ {description: 'Create a directory in the persistent isolated environment.', inputSchema: {path: z.string().describe('The directory path to create'), recursive: z.boolean().optional().describe('Create parent directories if needed (default: true)')}},
114
+ async ({path, recursive = true}: {path: string; recursive?: boolean}) => {
115
+ try {
116
+ const sandbox = await getPersistentSandbox()
117
+ await sandbox.mkDir(path, {recursive})
198
118
 
199
- return createSuccessResponse(`Successfully created directory: ${path}`);
200
- } catch (error) {
201
- return classifyError(error, "Mkdir error");
202
- }
203
- },
204
- );
119
+ return createSuccessResponse(`Successfully created directory: ${path}`)
120
+ } catch (error) {
121
+ return classifyError(error, 'Mkdir error')
122
+ }
123
+ }
124
+ )
205
125
 
206
- server.registerTool(
207
- "bash_sandbox_stop",
208
- {
209
- description:
210
- "Stop and clean up the persistent isolated environment, releasing all resources. Use bash_sandbox_reset to just clear state.",
211
- inputSchema: {},
212
- },
213
- async () => {
214
- try {
215
- await resetPersistentSandbox();
216
- return createSuccessResponse("Sandbox environment has been stopped and cleaned up.");
217
- } catch (error) {
218
- return classifyError(error, "Stop error");
219
- }
220
- },
221
- );
126
+ server.registerTool('bash_sandbox_stop', {description: 'Stop and clean up the persistent isolated environment, releasing all resources. Use bash_sandbox_reset to just clear state.', inputSchema: {}}, async () => {
127
+ try {
128
+ await resetPersistentSandbox()
129
+ return createSuccessResponse('Sandbox environment has been stopped and cleaned up.')
130
+ } catch (error) {
131
+ return classifyError(error, 'Stop error')
132
+ }
133
+ })
222
134
 
223
- server.registerTool(
224
- "bash_sandbox_reset",
225
- {
226
- description: "Reset the persistent isolated environment, clearing all files and state.",
227
- inputSchema: {},
228
- },
229
- async () => {
230
- try {
231
- await resetPersistentSandbox();
232
- return createSuccessResponse("Sandbox environment has been reset.");
233
- } catch (error) {
234
- return classifyError(error, "Reset error");
235
- }
236
- },
237
- );
135
+ server.registerTool('bash_sandbox_reset', {description: 'Reset the persistent isolated environment, clearing all files and state.', inputSchema: {}}, async () => {
136
+ try {
137
+ await resetPersistentSandbox()
138
+ return createSuccessResponse('Sandbox environment has been reset.')
139
+ } catch (error) {
140
+ return classifyError(error, 'Reset error')
141
+ }
142
+ })
238
143
  }
package/src/types.ts CHANGED
@@ -4,4 +4,4 @@
4
4
  * Keeping this as a wildcard export guarantees this wrapper stays 1:1 with
5
5
  * whatever the installed just-bash version exposes.
6
6
  */
7
- export * from "just-bash";
7
+ export * from 'just-bash'
@@ -2,83 +2,48 @@
2
2
  * Utility functions for just-bash-mcp
3
3
  */
4
4
 
5
- import { config } from "../config/index.ts";
5
+ import {config} from '../config/index.ts'
6
6
 
7
7
  /**
8
8
  * Truncate output to maximum length with notification message
9
9
  */
10
- export function truncateOutput(
11
- output: string,
12
- maxLength: number,
13
- streamName: "stdout" | "stderr",
14
- ): string {
15
- if (output.length <= maxLength) {
16
- return output;
17
- }
18
- const truncatedLength = output.length - maxLength;
19
- return `${output.slice(0, maxLength)}\n\n[${streamName} truncated: ${truncatedLength} characters removed]`;
10
+ export function truncateOutput(output: string, maxLength: number, streamName: 'stdout' | 'stderr'): string {
11
+ if (output.length <= maxLength) {
12
+ return output
13
+ }
14
+ const truncatedLength = output.length - maxLength
15
+ return `${output.slice(0, maxLength)}\n\n[${streamName} truncated: ${truncatedLength} characters removed]`
20
16
  }
21
17
 
22
18
  /**
23
19
  * Create a successful MCP tool response
24
20
  */
25
- export function createSuccessResponse(text: string): {
26
- content: Array<{ type: "text"; text: string }>;
27
- } {
28
- return {
29
- content: [{ type: "text" as const, text }],
30
- };
21
+ export function createSuccessResponse(text: string): {content: Array<{type: 'text'; text: string}>} {
22
+ return {content: [{type: 'text' as const, text}]}
31
23
  }
32
24
 
33
25
  /**
34
26
  * Create an error MCP tool response
35
27
  */
36
- export function createErrorResponse(
37
- error: unknown,
38
- prefix = "Error",
39
- ): { content: Array<{ type: "text"; text: string }>; isError: true } {
40
- const message = error instanceof Error ? error.message : String(error);
41
- return {
42
- content: [{ type: "text" as const, text: `${prefix}: ${message}` }],
43
- isError: true,
44
- };
28
+ export function createErrorResponse(error: unknown, prefix = 'Error'): {content: Array<{type: 'text'; text: string}>; isError: true} {
29
+ const message = error instanceof Error ? error.message : String(error)
30
+ return {content: [{type: 'text' as const, text: `${prefix}: ${message}`}], isError: true}
45
31
  }
46
32
 
47
33
  /**
48
34
  * Create a JSON MCP tool response
49
35
  */
50
- export function createJsonResponse(
51
- data: unknown,
52
- isError = false,
53
- ): { content: Array<{ type: "text"; text: string }>; isError?: boolean } {
54
- const response: {
55
- content: Array<{ type: "text"; text: string }>;
56
- isError?: boolean;
57
- } = {
58
- content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
59
- };
60
- if (isError) {
61
- response.isError = true;
62
- }
63
- return response;
36
+ export function createJsonResponse(data: unknown, isError = false): {content: Array<{type: 'text'; text: string}>; isError?: boolean} {
37
+ const response: {content: Array<{type: 'text'; text: string}>; isError?: boolean} = {content: [{type: 'text' as const, text: JSON.stringify(data, null, 2)}]}
38
+ if (isError) {
39
+ response.isError = true
40
+ }
41
+ return response
64
42
  }
65
43
 
66
44
  /**
67
45
  * Format bash execution result for MCP response
68
46
  */
69
- export function formatExecResult(result: {
70
- stdout: string;
71
- stderr: string;
72
- exitCode: number;
73
- env?: Record<string, string>;
74
- }): { content: Array<{ type: "text"; text: string }>; isError?: boolean } {
75
- return createJsonResponse(
76
- {
77
- stdout: truncateOutput(result.stdout, config.MAX_OUTPUT_LENGTH, "stdout"),
78
- stderr: truncateOutput(result.stderr, config.MAX_OUTPUT_LENGTH, "stderr"),
79
- exitCode: result.exitCode,
80
- ...(result.env && { env: result.env }),
81
- },
82
- result.exitCode !== 0,
83
- );
47
+ export function formatExecResult(result: {stdout: string; stderr: string; exitCode: number; env?: Record<string, string>}): {content: Array<{type: 'text'; text: string}>; isError?: boolean} {
48
+ return createJsonResponse({stdout: truncateOutput(result.stdout, config.MAX_OUTPUT_LENGTH, 'stdout'), stderr: truncateOutput(result.stderr, config.MAX_OUTPUT_LENGTH, 'stderr'), exitCode: result.exitCode, ...(result.env && {env: result.env})}, result.exitCode !== 0)
84
49
  }