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.
package/src/index.ts CHANGED
@@ -10,27 +10,23 @@
10
10
  * @see https://modelcontextprotocol.io
11
11
  */
12
12
 
13
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
-
16
- import { config } from "./config/index.ts";
17
- import { registerAllTools } from "./tools/index.ts";
13
+ import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
14
+ import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js'
15
+ import {config} from './config/index.ts'
16
+ import {registerAllTools} from './tools/index.ts'
18
17
 
19
18
  // ============================================================================
20
19
  // Server Initialization
21
20
  // ============================================================================
22
21
 
23
- const server = new McpServer({
24
- name: config.SERVER_NAME,
25
- version: config.VERSION,
26
- });
22
+ const server = new McpServer({name: config.SERVER_NAME, version: config.VERSION})
27
23
 
28
24
  // Register all tools with the server
29
- registerAllTools(server);
25
+ registerAllTools(server)
30
26
 
31
27
  // ============================================================================
32
28
  // Start Server
33
29
  // ============================================================================
34
30
 
35
- const transport = new StdioServerTransport();
36
- await server.connect(transport);
31
+ const transport = new StdioServerTransport()
32
+ await server.connect(transport)
@@ -10,30 +10,8 @@
10
10
  * - Network error classes for rich error reporting (handled in exec-tools/sandbox-tools)
11
11
  */
12
12
 
13
- import {
14
- Bash,
15
- type BashOptions,
16
- type CustomCommand,
17
- DefenseInDepthBox,
18
- InMemoryFs,
19
- MountableFs,
20
- OverlayFs,
21
- ReadWriteFs,
22
- Sandbox,
23
- type SandboxOptions,
24
- defineCommand,
25
- } from "just-bash";
26
-
27
- import {
28
- bashLogger,
29
- buildDefenseInDepthConfig,
30
- buildExecutionLimits,
31
- buildNetworkConfig,
32
- config,
33
- parseMountsConfig,
34
- traceCallback,
35
- violationLogger,
36
- } from "../config/index.ts";
13
+ import {Bash, type BashOptions, type CustomCommand, DefenseInDepthBox as UpstreamDefenseInDepthBox, InMemoryFs, MountableFs, OverlayFs, ReadWriteFs, Sandbox, type SandboxOptions, defineCommand} from 'just-bash'
14
+ import {bashLogger, buildDefenseInDepthConfig, buildExecutionLimits, buildJavaScriptConfig, buildNetworkConfig, config, type DefenseInDepthConfig, type DefenseInDepthStats, parseMountsConfig, traceCallback, violationLogger} from '../config/index.ts'
37
15
 
38
16
  // ============================================================================
39
17
  // Bash Instance Factory
@@ -43,32 +21,39 @@ import {
43
21
  // Defense-in-Depth Box (singleton)
44
22
  // ============================================================================
45
23
 
46
- let defenseInDepthBox: DefenseInDepthBox | null = null;
24
+ interface DefenseInDepthBoxInstance {
25
+ isActive(): boolean
26
+ getStats(): DefenseInDepthStats
27
+ }
28
+
29
+ const DefenseInDepthBox = UpstreamDefenseInDepthBox as unknown as {getInstance(config?: DefenseInDepthConfig | boolean): DefenseInDepthBoxInstance}
30
+
31
+ let defenseInDepthBox: DefenseInDepthBoxInstance | null = null
47
32
 
48
33
  /**
49
34
  * Get or create the shared DefenseInDepthBox instance.
50
35
  * Returns null if defense-in-depth is disabled.
51
36
  */
52
- export function getDefenseInDepthBox(): DefenseInDepthBox | null {
53
- const didConfig = buildDefenseInDepthConfig();
54
- if (!didConfig) return null;
55
-
56
- if (!defenseInDepthBox) {
57
- defenseInDepthBox = new DefenseInDepthBox(didConfig);
58
- }
59
- return defenseInDepthBox;
37
+ export function getDefenseInDepthBox(): DefenseInDepthBoxInstance | null {
38
+ const didConfig = buildDefenseInDepthConfig()
39
+ if (!didConfig) return null
40
+
41
+ if (!defenseInDepthBox) {
42
+ defenseInDepthBox = DefenseInDepthBox.getInstance(didConfig)
43
+ }
44
+ return defenseInDepthBox
60
45
  }
61
46
 
62
47
  /**
63
48
  * Get the shared SecurityViolationLogger for querying violation stats.
64
49
  */
65
- export { violationLogger } from "../config/index.ts";
50
+ export {violationLogger} from '../config/index.ts'
66
51
 
67
52
  /**
68
53
  * Re-export defineCommand so downstream consumers can create custom commands
69
54
  * using the upstream API without importing just-bash directly.
70
55
  */
71
- export { defineCommand };
56
+ export {defineCommand}
72
57
 
73
58
  // ============================================================================
74
59
  // Bash Instance Factory
@@ -77,127 +62,77 @@ export { defineCommand };
77
62
  /**
78
63
  * Create a new Bash instance with the given configuration
79
64
  */
80
- export function createBashInstance(
81
- files?: Record<string, string>,
82
- customCommands?: CustomCommand[],
83
- env?: Record<string, string>,
84
- ): Bash {
85
- const networkConfig = buildNetworkConfig();
86
- const executionLimits = buildExecutionLimits();
87
- const defenseInDepthConfig = buildDefenseInDepthConfig();
88
-
89
- const baseOptions: BashOptions = {
90
- network: networkConfig,
91
- executionLimits,
92
- files,
93
- env,
94
- logger: bashLogger,
95
- trace: traceCallback,
96
- customCommands,
97
- commands: config.ALLOWED_COMMANDS,
98
- python: config.ENABLE_PYTHON,
99
- defenseInDepth: defenseInDepthConfig,
100
- };
101
-
102
- // Check for mountable filesystem configuration
103
- const mounts = parseMountsConfig();
104
- if (mounts.length > 0) {
105
- const mountableFs = new MountableFs({
106
- base: new InMemoryFs(),
107
- mounts,
108
- });
109
- return new Bash({
110
- ...baseOptions,
111
- fs: mountableFs,
112
- cwd: config.INITIAL_CWD,
113
- });
114
- }
115
-
116
- // Check for read-write filesystem configuration
117
- if (config.READ_WRITE_ROOT) {
118
- const rwfs = new ReadWriteFs({
119
- root: config.READ_WRITE_ROOT,
120
- ...(config.MAX_FILE_READ_SIZE !== undefined && {
121
- maxFileReadSize: config.MAX_FILE_READ_SIZE,
122
- }),
123
- });
124
- return new Bash({
125
- ...baseOptions,
126
- fs: rwfs,
127
- cwd: config.READ_WRITE_ROOT,
128
- });
129
- }
130
-
131
- // Check for overlay filesystem configuration
132
- if (config.OVERLAY_ROOT) {
133
- const overlay = new OverlayFs({
134
- root: config.OVERLAY_ROOT,
135
- readOnly: config.OVERLAY_READ_ONLY,
136
- ...(config.MAX_FILE_READ_SIZE !== undefined && {
137
- maxFileReadSize: config.MAX_FILE_READ_SIZE,
138
- }),
139
- });
140
- return new Bash({
141
- ...baseOptions,
142
- fs: overlay,
143
- cwd: overlay.getMountPoint(),
144
- });
145
- }
146
-
147
- // Default: in-memory filesystem
148
- return new Bash({
149
- ...baseOptions,
150
- cwd: config.INITIAL_CWD,
151
- });
65
+ export function createBashInstance(files?: Record<string, string>, customCommands?: CustomCommand[], env?: Record<string, string>): Bash {
66
+ const networkConfig = buildNetworkConfig()
67
+ const executionLimits = buildExecutionLimits()
68
+ const defenseInDepthConfig = buildDefenseInDepthConfig()
69
+ const javascriptConfig = buildJavaScriptConfig()
70
+
71
+ const baseOptions: BashOptions = {network: networkConfig, executionLimits, files, env, logger: bashLogger, trace: traceCallback, customCommands, commands: config.ALLOWED_COMMANDS, python: config.ENABLE_PYTHON, javascript: javascriptConfig, defenseInDepth: defenseInDepthConfig}
72
+
73
+ // Check for mountable filesystem configuration
74
+ const mounts = parseMountsConfig()
75
+ if (mounts.length > 0) {
76
+ const mountableFs = new MountableFs({base: new InMemoryFs(), mounts})
77
+ return new Bash({...baseOptions, fs: mountableFs, cwd: config.INITIAL_CWD})
78
+ }
79
+
80
+ // Check for read-write filesystem configuration
81
+ if (config.READ_WRITE_ROOT) {
82
+ const rwfs = new ReadWriteFs({root: config.READ_WRITE_ROOT, ...(config.MAX_FILE_READ_SIZE !== undefined && {maxFileReadSize: config.MAX_FILE_READ_SIZE})})
83
+ return new Bash({...baseOptions, fs: rwfs, cwd: config.READ_WRITE_ROOT})
84
+ }
85
+
86
+ // Check for overlay filesystem configuration
87
+ if (config.OVERLAY_ROOT) {
88
+ const overlay = new OverlayFs({root: config.OVERLAY_ROOT, readOnly: config.OVERLAY_READ_ONLY, ...(config.MAX_FILE_READ_SIZE !== undefined && {maxFileReadSize: config.MAX_FILE_READ_SIZE})})
89
+ return new Bash({...baseOptions, fs: overlay, cwd: overlay.getMountPoint()})
90
+ }
91
+
92
+ // Default: in-memory filesystem
93
+ return new Bash({...baseOptions, cwd: config.INITIAL_CWD})
152
94
  }
153
95
 
154
96
  // ============================================================================
155
97
  // Persistent Bash Instance (singleton pattern)
156
98
  // ============================================================================
157
99
 
158
- let persistentBash: Bash | null = null;
100
+ let persistentBash: Bash | null = null
159
101
 
160
102
  /**
161
103
  * Get or create the persistent Bash instance
162
104
  */
163
105
  export function getPersistentBash(): Bash {
164
- if (!persistentBash) {
165
- persistentBash = createBashInstance();
166
- }
167
- return persistentBash;
106
+ if (!persistentBash) {
107
+ persistentBash = createBashInstance()
108
+ }
109
+ return persistentBash
168
110
  }
169
111
 
170
112
  /**
171
113
  * Reset the persistent Bash instance
172
114
  */
173
115
  export function resetPersistentBash(): void {
174
- persistentBash = null;
116
+ persistentBash = null
175
117
  }
176
118
 
177
119
  // ============================================================================
178
120
  // Persistent Sandbox Instance (singleton pattern)
179
121
  // ============================================================================
180
122
 
181
- let persistentSandbox: Sandbox | null = null;
123
+ let persistentSandbox: Sandbox | null = null
182
124
 
183
125
  /**
184
126
  * Get or create the persistent Sandbox instance.
185
127
  * Passes all available configuration including network and filesystem options.
186
128
  */
187
129
  export async function getPersistentSandbox(): Promise<Sandbox> {
188
- if (!persistentSandbox) {
189
- const networkConfig = buildNetworkConfig();
190
- const options: SandboxOptions = {
191
- cwd: config.INITIAL_CWD,
192
- network: networkConfig,
193
- maxCallDepth: config.MAX_CALL_DEPTH,
194
- maxCommandCount: config.MAX_COMMAND_COUNT,
195
- maxLoopIterations: config.MAX_LOOP_ITERATIONS,
196
- ...(config.OVERLAY_ROOT && { overlayRoot: config.OVERLAY_ROOT }),
197
- };
198
- persistentSandbox = await Sandbox.create(options);
199
- }
200
- return persistentSandbox;
130
+ if (!persistentSandbox) {
131
+ const networkConfig = buildNetworkConfig()
132
+ const options: SandboxOptions = {cwd: config.INITIAL_CWD, network: networkConfig, maxCallDepth: config.MAX_CALL_DEPTH, maxCommandCount: config.MAX_COMMAND_COUNT, maxLoopIterations: config.MAX_LOOP_ITERATIONS, ...(config.OVERLAY_ROOT && {overlayRoot: config.OVERLAY_ROOT})}
133
+ persistentSandbox = await Sandbox.create(options)
134
+ }
135
+ return persistentSandbox
201
136
  }
202
137
 
203
138
  /**
@@ -205,8 +140,8 @@ export async function getPersistentSandbox(): Promise<Sandbox> {
205
140
  * Calls Sandbox.stop() to clean up resources before releasing.
206
141
  */
207
142
  export async function resetPersistentSandbox(): Promise<void> {
208
- if (persistentSandbox) {
209
- await persistentSandbox.stop();
210
- }
211
- persistentSandbox = null;
143
+ if (persistentSandbox) {
144
+ await persistentSandbox.stop()
145
+ }
146
+ persistentSandbox = null
212
147
  }
@@ -9,157 +9,110 @@
9
9
  * - SecurityViolationError: Defense-in-depth violation detected
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
- } from "just-bash";
19
- import { z } from "zod/v4";
20
- import { createErrorResponse, formatExecResult } from "../utils/index.ts";
21
- import { createBashInstance, getPersistentBash, resetPersistentBash } from "./bash-instance.ts";
12
+ import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
13
+ import {NetworkAccessDeniedError, RedirectNotAllowedError, SecurityViolationError, TooManyRedirectsError} from 'just-bash'
14
+ import {z} from 'zod/v4'
15
+ import {createErrorResponse, formatExecResult} from '../utils/index.ts'
16
+ import {createBashInstance, getPersistentBash, resetPersistentBash} from './bash-instance.ts'
22
17
 
23
18
  /**
24
19
  * Classify errors from just-bash into user-friendly messages.
25
20
  * Uses upstream error classes for precise classification.
26
21
  */
27
22
  function classifyError(error: unknown, prefix: string) {
28
- if (error instanceof NetworkAccessDeniedError) {
29
- return createErrorResponse(error, `${prefix} [Network Access Denied]`);
30
- }
31
- if (error instanceof TooManyRedirectsError) {
32
- return createErrorResponse(error, `${prefix} [Too Many Redirects]`);
33
- }
34
- if (error instanceof RedirectNotAllowedError) {
35
- return createErrorResponse(error, `${prefix} [Redirect Not Allowed]`);
36
- }
37
- if (error instanceof SecurityViolationError) {
38
- return createErrorResponse(error, `${prefix} [Security Violation]`);
39
- }
40
- return createErrorResponse(error, prefix);
23
+ if (error instanceof NetworkAccessDeniedError) {
24
+ return createErrorResponse(error, `${prefix} [Network Access Denied]`)
25
+ }
26
+ if (error instanceof TooManyRedirectsError) {
27
+ return createErrorResponse(error, `${prefix} [Too Many Redirects]`)
28
+ }
29
+ if (error instanceof RedirectNotAllowedError) {
30
+ return createErrorResponse(error, `${prefix} [Redirect Not Allowed]`)
31
+ }
32
+ if (error instanceof SecurityViolationError) {
33
+ return createErrorResponse(error, `${prefix} [Security Violation]`)
34
+ }
35
+ return createErrorResponse(error, prefix)
41
36
  }
42
37
 
43
38
  /**
44
39
  * Register bash execution tools with the MCP server
45
40
  */
46
41
  export function registerExecTools(server: McpServer): void {
47
- // ========================================================================
48
- // bash_exec - Isolated execution
49
- // ========================================================================
50
- server.registerTool(
51
- "bash_exec",
52
- {
53
- description:
54
- "Execute a bash command in a sandboxed environment. Each execution is isolated - environment variables, functions, and cwd don't persist across calls (filesystem does).",
55
- inputSchema: {
56
- command: z.string().describe("The bash command to execute"),
57
- cwd: z.string().optional().describe("Working directory for the command"),
58
- env: z
59
- .record(z.string(), z.string())
60
- .optional()
61
- .describe("Environment variables to set for execution"),
62
- initialEnv: z
63
- .record(z.string(), z.string())
64
- .optional()
65
- .describe("Initial environment variables to set when creating the bash instance"),
66
- files: z
67
- .record(z.string(), z.string())
68
- .optional()
69
- .describe("Files to create before execution (path -> content)"),
70
- rawScript: z
71
- .boolean()
72
- .optional()
73
- .describe(
74
- "If true, skip normalizing the script (preserves leading whitespace). Useful for here-docs.",
75
- ),
76
- },
77
- },
78
- async ({
79
- command,
80
- cwd,
81
- env,
82
- initialEnv,
83
- files,
84
- rawScript,
85
- }: {
86
- command: string;
87
- cwd?: string;
88
- env?: Record<string, string>;
89
- initialEnv?: Record<string, string>;
90
- files?: Record<string, string>;
91
- rawScript?: boolean;
92
- }) => {
93
- try {
94
- const bash = createBashInstance(files, undefined, initialEnv);
95
- const result = await bash.exec(command, { cwd, env, rawScript });
96
- return formatExecResult(result);
97
- } catch (error) {
98
- return classifyError(error, "Execution error");
99
- }
100
- },
101
- );
42
+ // ========================================================================
43
+ // bash - Upstream bash-tool compatible execution
44
+ // ========================================================================
45
+ server.registerTool(
46
+ 'bash',
47
+ {description: 'Execute bash commands in the sandbox environment. Upstream-compatible MCP exposure of the just-bash/bash-tool execute interface. The persistent filesystem is shared across calls.', inputSchema: {command: z.string().describe('The bash command to execute')}},
48
+ async ({command}: {command: string}) => {
49
+ try {
50
+ const bash = getPersistentBash()
51
+ const result = await bash.exec(command)
52
+ return formatExecResult(result)
53
+ } catch (error) {
54
+ return classifyError(error, 'Execution error')
55
+ }
56
+ }
57
+ )
102
58
 
103
- // ========================================================================
104
- // bash_exec_persistent - Persistent execution
105
- // ========================================================================
106
- server.registerTool(
107
- "bash_exec_persistent",
108
- {
109
- description:
110
- "Execute a bash command in a persistent sandboxed environment. The filesystem persists across calls, but env vars, functions, and cwd are reset each call.",
111
- inputSchema: {
112
- command: z.string().describe("The bash command to execute"),
113
- cwd: z.string().optional().describe("Working directory for the command"),
114
- env: z.record(z.string(), z.string()).optional().describe("Environment variables to set"),
115
- rawScript: z
116
- .boolean()
117
- .optional()
118
- .describe(
119
- "If true, skip normalizing the script (preserves leading whitespace). Useful for here-docs.",
120
- ),
121
- },
122
- },
123
- async ({
124
- command,
125
- cwd,
126
- env,
127
- rawScript,
128
- }: {
129
- command: string;
130
- cwd?: string;
131
- env?: Record<string, string>;
132
- rawScript?: boolean;
133
- }) => {
134
- try {
135
- const bash = getPersistentBash();
136
- const result = await bash.exec(command, { cwd, env, rawScript });
137
- return formatExecResult(result);
138
- } catch (error) {
139
- return classifyError(error, "Execution error");
140
- }
141
- },
142
- );
59
+ // ========================================================================
60
+ // bash_exec - Isolated execution
61
+ // ========================================================================
62
+ server.registerTool(
63
+ 'bash_exec',
64
+ {
65
+ description: "Execute a bash command in a sandboxed environment. Each execution is isolated - environment variables, functions, and cwd don't persist across calls (filesystem does).",
66
+ inputSchema: {
67
+ command: z.string().describe('The bash command to execute'),
68
+ cwd: z.string().optional().describe('Working directory for the command'),
69
+ env: z.record(z.string(), z.string()).optional().describe('Environment variables to set for execution'),
70
+ initialEnv: z.record(z.string(), z.string()).optional().describe('Initial environment variables to set when creating the bash instance'),
71
+ files: z.record(z.string(), z.string()).optional().describe('Files to create before execution (path -> content)'),
72
+ rawScript: z.boolean().optional().describe('If true, skip normalizing the script (preserves leading whitespace). Useful for here-docs.')
73
+ }
74
+ },
75
+ async ({command, cwd, env, initialEnv, files, rawScript}: {command: string; cwd?: string; env?: Record<string, string>; initialEnv?: Record<string, string>; files?: Record<string, string>; rawScript?: boolean}) => {
76
+ try {
77
+ const bash = createBashInstance(files, undefined, initialEnv)
78
+ const result = await bash.exec(command, {cwd, env, rawScript})
79
+ return formatExecResult(result)
80
+ } catch (error) {
81
+ return classifyError(error, 'Execution error')
82
+ }
83
+ }
84
+ )
143
85
 
144
- // ========================================================================
145
- // bash_reset - Reset persistent environment
146
- // ========================================================================
147
- server.registerTool(
148
- "bash_reset",
149
- {
150
- description: "Reset the persistent bash environment, clearing all files and state.",
151
- inputSchema: {},
152
- },
153
- async () => {
154
- resetPersistentBash();
155
- return {
156
- content: [
157
- {
158
- type: "text" as const,
159
- text: "Persistent bash environment has been reset.",
160
- },
161
- ],
162
- };
163
- },
164
- );
86
+ // ========================================================================
87
+ // bash_exec_persistent - Persistent execution
88
+ // ========================================================================
89
+ server.registerTool(
90
+ 'bash_exec_persistent',
91
+ {
92
+ description: 'Execute a bash command in a persistent sandboxed environment. The filesystem persists across calls, but env vars, functions, and cwd are reset each call.',
93
+ inputSchema: {
94
+ command: z.string().describe('The bash command to execute'),
95
+ cwd: z.string().optional().describe('Working directory for the command'),
96
+ env: z.record(z.string(), z.string()).optional().describe('Environment variables to set'),
97
+ rawScript: z.boolean().optional().describe('If true, skip normalizing the script (preserves leading whitespace). Useful for here-docs.')
98
+ }
99
+ },
100
+ async ({command, cwd, env, rawScript}: {command: string; cwd?: string; env?: Record<string, string>; rawScript?: boolean}) => {
101
+ try {
102
+ const bash = getPersistentBash()
103
+ const result = await bash.exec(command, {cwd, env, rawScript})
104
+ return formatExecResult(result)
105
+ } catch (error) {
106
+ return classifyError(error, 'Execution error')
107
+ }
108
+ }
109
+ )
110
+
111
+ // ========================================================================
112
+ // bash_reset - Reset persistent environment
113
+ // ========================================================================
114
+ server.registerTool('bash_reset', {description: 'Reset the persistent bash environment, clearing all files and state.', inputSchema: {}}, async () => {
115
+ resetPersistentBash()
116
+ return {content: [{type: 'text' as const, text: 'Persistent bash environment has been reset.'}]}
117
+ })
165
118
  }