just-bash-mcp 2.6.0 → 2.8.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/package.json +24 -22
- package/src/config/index.ts +358 -0
- package/src/index.ts +36 -0
- package/src/tools/bash-instance.ts +160 -0
- package/src/tools/exec-tools.ts +133 -0
- package/src/tools/file-tools.ts +206 -0
- package/src/tools/index.ts +42 -0
- package/src/tools/info-tools.ts +149 -0
- package/src/tools/sandbox-tools.ts +161 -0
- package/src/utils/index.ts +84 -0
- package/tsconfig.json +18 -0
- package/build/index.d.ts +0 -3
- package/build/index.d.ts.map +0 -1
- package/build/index.js +0 -446
- package/build/index.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "just-bash-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "MCP server providing a sandboxed bash environment using just-bash",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
7
|
-
"types": "./build/index.d.ts",
|
|
6
|
+
"main": "./src/index.ts",
|
|
8
7
|
"exports": {
|
|
9
8
|
".": {
|
|
10
|
-
"
|
|
11
|
-
"import": "./build/index.js"
|
|
9
|
+
"import": "./src/index.ts"
|
|
12
10
|
}
|
|
13
11
|
},
|
|
14
12
|
"bin": {
|
|
15
|
-
"just-bash-mcp": "
|
|
13
|
+
"just-bash-mcp": "src/index.ts"
|
|
16
14
|
},
|
|
17
15
|
"files": [
|
|
18
|
-
"
|
|
16
|
+
"src",
|
|
17
|
+
"tsconfig.json",
|
|
19
18
|
"README.md",
|
|
20
19
|
"LICENSE"
|
|
21
20
|
],
|
|
22
21
|
"scripts": {
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"format": "
|
|
29
|
-
"check": "
|
|
30
|
-
"
|
|
22
|
+
"start": "node src/index.ts",
|
|
23
|
+
"dev": "node --watch src/index.ts",
|
|
24
|
+
"lint": "oxlint --type-aware",
|
|
25
|
+
"lint:fix": "oxlint --type-aware --fix",
|
|
26
|
+
"typecheck": "oxlint --type-aware --type-check",
|
|
27
|
+
"format": "oxfmt --write src/",
|
|
28
|
+
"format:check": "oxfmt --check src/",
|
|
29
|
+
"check": "oxlint --type-aware --type-check && oxfmt --check src/",
|
|
30
|
+
"ci": "oxlint --type-aware --type-check && oxfmt --check src/"
|
|
31
31
|
},
|
|
32
32
|
"keywords": [
|
|
33
33
|
"mcp",
|
|
@@ -56,16 +56,18 @@
|
|
|
56
56
|
},
|
|
57
57
|
"homepage": "https://github.com/dalist1/just-bash-mcp#readme",
|
|
58
58
|
"engines": {
|
|
59
|
-
"node": ">=
|
|
59
|
+
"node": ">=22.0.0"
|
|
60
60
|
},
|
|
61
|
+
"packageManager": "bun@1.3.8",
|
|
61
62
|
"dependencies": {
|
|
62
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
63
|
-
"just-bash": "^2.
|
|
64
|
-
"zod": "^4.3.
|
|
63
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
64
|
+
"just-bash": "^2.9.6",
|
|
65
|
+
"zod": "^4.3.6"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
67
|
-
"@
|
|
68
|
-
"
|
|
69
|
-
"
|
|
68
|
+
"@types/node": "^25.2.1",
|
|
69
|
+
"oxfmt": "^0.28.0",
|
|
70
|
+
"oxlint": "^1.43.0",
|
|
71
|
+
"oxlint-tsgolint": "^0.11.4"
|
|
70
72
|
}
|
|
71
73
|
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration module for just-bash-mcp
|
|
3
|
+
* Handles environment variable parsing and configuration building
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type BashLogger,
|
|
8
|
+
type BashOptions,
|
|
9
|
+
type CommandName,
|
|
10
|
+
InMemoryFs,
|
|
11
|
+
MountableFs,
|
|
12
|
+
type MountConfig,
|
|
13
|
+
type NetworkConfig,
|
|
14
|
+
OverlayFs,
|
|
15
|
+
ReadWriteFs,
|
|
16
|
+
} from "just-bash";
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
|
|
23
|
+
|
|
24
|
+
export interface TraceEvent {
|
|
25
|
+
category: string;
|
|
26
|
+
name: string;
|
|
27
|
+
durationMs: number;
|
|
28
|
+
details?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type TraceCallback = (event: TraceEvent) => void;
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Environment Variable Parsing
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
function parseEnvString(key: string, defaultValue: string): string {
|
|
38
|
+
return process.env[key] || defaultValue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseEnvBoolean(key: string, defaultValue: boolean): boolean {
|
|
42
|
+
return process.env[key] === "true" ? true : defaultValue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseEnvInt(key: string, defaultValue: number): number {
|
|
46
|
+
const value = process.env[key];
|
|
47
|
+
if (!value) return defaultValue;
|
|
48
|
+
const parsed = Number.parseInt(value, 10);
|
|
49
|
+
return Number.isNaN(parsed) ? defaultValue : parsed;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseEnvStringArray(key: string): string[] {
|
|
53
|
+
return process.env[key]?.split(",").filter(Boolean) || [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Configuration Constants
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
export interface Config {
|
|
61
|
+
readonly VERSION: string;
|
|
62
|
+
readonly SERVER_NAME: string;
|
|
63
|
+
readonly OVERLAY_ROOT: string | undefined;
|
|
64
|
+
readonly READ_WRITE_ROOT: string | undefined;
|
|
65
|
+
readonly MOUNTS_CONFIG: string | undefined;
|
|
66
|
+
readonly INITIAL_CWD: string;
|
|
67
|
+
readonly ALLOW_NETWORK: boolean;
|
|
68
|
+
readonly ALLOWED_URL_PREFIXES: string[];
|
|
69
|
+
readonly ALLOWED_METHODS: HttpMethod[];
|
|
70
|
+
readonly MAX_REDIRECTS: number;
|
|
71
|
+
readonly NETWORK_TIMEOUT_MS: number;
|
|
72
|
+
readonly MAX_RESPONSE_SIZE: number | undefined;
|
|
73
|
+
readonly MAX_CALL_DEPTH: number;
|
|
74
|
+
readonly MAX_COMMAND_COUNT: number;
|
|
75
|
+
readonly MAX_LOOP_ITERATIONS: number;
|
|
76
|
+
readonly MAX_AWK_ITERATIONS: number;
|
|
77
|
+
readonly MAX_SED_ITERATIONS: number;
|
|
78
|
+
readonly MAX_JQ_ITERATIONS: number;
|
|
79
|
+
readonly MAX_GLOB_OPERATIONS: number;
|
|
80
|
+
readonly MAX_STRING_LENGTH: number;
|
|
81
|
+
readonly MAX_ARRAY_ELEMENTS: number;
|
|
82
|
+
readonly MAX_HEREDOC_SIZE: number;
|
|
83
|
+
readonly MAX_SUBSTITUTION_DEPTH: number;
|
|
84
|
+
readonly MAX_SQLITE_TIMEOUT_MS: number;
|
|
85
|
+
readonly MAX_PYTHON_TIMEOUT_MS: number;
|
|
86
|
+
readonly MAX_OUTPUT_LENGTH: number;
|
|
87
|
+
readonly MAX_FILE_READ_SIZE: number | undefined;
|
|
88
|
+
readonly ENABLE_LOGGING: boolean;
|
|
89
|
+
readonly ENABLE_TRACING: boolean;
|
|
90
|
+
readonly ENABLE_PYTHON: boolean;
|
|
91
|
+
readonly ENABLE_DEFENSE_IN_DEPTH: boolean;
|
|
92
|
+
readonly OVERLAY_READ_ONLY: boolean;
|
|
93
|
+
readonly ALLOWED_COMMANDS: CommandName[] | undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getAllowedMethods(): HttpMethod[] {
|
|
97
|
+
const methods = parseEnvStringArray("JUST_BASH_ALLOWED_METHODS");
|
|
98
|
+
return methods.length > 0 ? (methods as HttpMethod[]) : ["GET", "HEAD"];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getAllowedCommands(): CommandName[] | undefined {
|
|
102
|
+
const commands = parseEnvStringArray("JUST_BASH_ALLOWED_COMMANDS");
|
|
103
|
+
return commands.length > 0 ? (commands as CommandName[]) : undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseEnvOptionalInt(key: string): number | undefined {
|
|
107
|
+
const value = process.env[key];
|
|
108
|
+
if (!value) return undefined;
|
|
109
|
+
const parsed = Number.parseInt(value, 10);
|
|
110
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const config: Config = {
|
|
114
|
+
// Server info
|
|
115
|
+
VERSION: "2.8.0",
|
|
116
|
+
SERVER_NAME: "just-bash-mcp",
|
|
117
|
+
|
|
118
|
+
// Filesystem configuration
|
|
119
|
+
OVERLAY_ROOT: process.env.JUST_BASH_OVERLAY_ROOT,
|
|
120
|
+
READ_WRITE_ROOT: process.env.JUST_BASH_READ_WRITE_ROOT,
|
|
121
|
+
MOUNTS_CONFIG: process.env.JUST_BASH_MOUNTS,
|
|
122
|
+
INITIAL_CWD: parseEnvString("JUST_BASH_CWD", "/home/user"),
|
|
123
|
+
|
|
124
|
+
// Network configuration
|
|
125
|
+
ALLOW_NETWORK: parseEnvBoolean("JUST_BASH_ALLOW_NETWORK", false),
|
|
126
|
+
ALLOWED_URL_PREFIXES: parseEnvStringArray("JUST_BASH_ALLOWED_URLS"),
|
|
127
|
+
ALLOWED_METHODS: getAllowedMethods(),
|
|
128
|
+
MAX_REDIRECTS: parseEnvInt("JUST_BASH_MAX_REDIRECTS", 20),
|
|
129
|
+
NETWORK_TIMEOUT_MS: parseEnvInt("JUST_BASH_NETWORK_TIMEOUT_MS", 30000),
|
|
130
|
+
MAX_RESPONSE_SIZE: parseEnvOptionalInt("JUST_BASH_MAX_RESPONSE_SIZE"),
|
|
131
|
+
|
|
132
|
+
// Execution limits
|
|
133
|
+
MAX_CALL_DEPTH: parseEnvInt("JUST_BASH_MAX_CALL_DEPTH", 100),
|
|
134
|
+
MAX_COMMAND_COUNT: parseEnvInt("JUST_BASH_MAX_COMMAND_COUNT", 10000),
|
|
135
|
+
MAX_LOOP_ITERATIONS: parseEnvInt("JUST_BASH_MAX_LOOP_ITERATIONS", 10000),
|
|
136
|
+
MAX_AWK_ITERATIONS: parseEnvInt("JUST_BASH_MAX_AWK_ITERATIONS", 10000),
|
|
137
|
+
MAX_SED_ITERATIONS: parseEnvInt("JUST_BASH_MAX_SED_ITERATIONS", 10000),
|
|
138
|
+
MAX_JQ_ITERATIONS: parseEnvInt("JUST_BASH_MAX_JQ_ITERATIONS", 10000),
|
|
139
|
+
MAX_GLOB_OPERATIONS: parseEnvInt("JUST_BASH_MAX_GLOB_OPERATIONS", 100000),
|
|
140
|
+
MAX_STRING_LENGTH: parseEnvInt("JUST_BASH_MAX_STRING_LENGTH", 10485760),
|
|
141
|
+
MAX_ARRAY_ELEMENTS: parseEnvInt("JUST_BASH_MAX_ARRAY_ELEMENTS", 100000),
|
|
142
|
+
MAX_HEREDOC_SIZE: parseEnvInt("JUST_BASH_MAX_HEREDOC_SIZE", 10485760),
|
|
143
|
+
MAX_SUBSTITUTION_DEPTH: parseEnvInt("JUST_BASH_MAX_SUBSTITUTION_DEPTH", 50),
|
|
144
|
+
MAX_SQLITE_TIMEOUT_MS: parseEnvInt("JUST_BASH_MAX_SQLITE_TIMEOUT_MS", 5000),
|
|
145
|
+
MAX_PYTHON_TIMEOUT_MS: parseEnvInt("JUST_BASH_MAX_PYTHON_TIMEOUT_MS", 30000),
|
|
146
|
+
|
|
147
|
+
// Output limits
|
|
148
|
+
MAX_OUTPUT_LENGTH: parseEnvInt("JUST_BASH_MAX_OUTPUT_LENGTH", 30000),
|
|
149
|
+
|
|
150
|
+
// Filesystem limits
|
|
151
|
+
MAX_FILE_READ_SIZE: parseEnvOptionalInt("JUST_BASH_MAX_FILE_READ_SIZE"),
|
|
152
|
+
|
|
153
|
+
// Debugging
|
|
154
|
+
ENABLE_LOGGING: parseEnvBoolean("JUST_BASH_ENABLE_LOGGING", false),
|
|
155
|
+
ENABLE_TRACING: parseEnvBoolean("JUST_BASH_ENABLE_TRACING", false),
|
|
156
|
+
|
|
157
|
+
// Feature flags
|
|
158
|
+
ENABLE_PYTHON: parseEnvBoolean("JUST_BASH_ENABLE_PYTHON", false),
|
|
159
|
+
ENABLE_DEFENSE_IN_DEPTH: parseEnvBoolean("JUST_BASH_DEFENSE_IN_DEPTH", false),
|
|
160
|
+
OVERLAY_READ_ONLY: parseEnvBoolean("JUST_BASH_OVERLAY_READ_ONLY", false),
|
|
161
|
+
|
|
162
|
+
// Command filtering
|
|
163
|
+
ALLOWED_COMMANDS: getAllowedCommands(),
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// Logger and Trace Callback
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
export const bashLogger: BashLogger | undefined = config.ENABLE_LOGGING
|
|
171
|
+
? {
|
|
172
|
+
info(message: string, data?: Record<string, unknown>): void {
|
|
173
|
+
if (data) {
|
|
174
|
+
console.error(`[just-bash] INFO: ${message}`, data);
|
|
175
|
+
} else {
|
|
176
|
+
console.error(`[just-bash] INFO: ${message}`);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
debug(message: string, data?: Record<string, unknown>): void {
|
|
180
|
+
if (data) {
|
|
181
|
+
console.error(`[just-bash] DEBUG: ${message}`, data);
|
|
182
|
+
} else {
|
|
183
|
+
console.error(`[just-bash] DEBUG: ${message}`);
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
: undefined;
|
|
188
|
+
|
|
189
|
+
export const traceCallback: TraceCallback | undefined = config.ENABLE_TRACING
|
|
190
|
+
? (event: TraceEvent) => {
|
|
191
|
+
if (event.details) {
|
|
192
|
+
console.error(
|
|
193
|
+
`[just-bash] TRACE: ${event.category}/${event.name} ${event.durationMs}ms`,
|
|
194
|
+
JSON.stringify(event.details),
|
|
195
|
+
);
|
|
196
|
+
} else {
|
|
197
|
+
console.error(`[just-bash] TRACE: ${event.category}/${event.name} ${event.durationMs}ms`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
: undefined;
|
|
201
|
+
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// Configuration Builders
|
|
204
|
+
// ============================================================================
|
|
205
|
+
|
|
206
|
+
export function buildNetworkConfig(): NetworkConfig | undefined {
|
|
207
|
+
if (!config.ALLOW_NETWORK) {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (config.ALLOWED_URL_PREFIXES.length > 0) {
|
|
212
|
+
return {
|
|
213
|
+
allowedUrlPrefixes: config.ALLOWED_URL_PREFIXES,
|
|
214
|
+
allowedMethods: config.ALLOWED_METHODS,
|
|
215
|
+
maxRedirects: config.MAX_REDIRECTS,
|
|
216
|
+
timeoutMs: config.NETWORK_TIMEOUT_MS,
|
|
217
|
+
...(config.MAX_RESPONSE_SIZE !== undefined && {
|
|
218
|
+
maxResponseSize: config.MAX_RESPONSE_SIZE,
|
|
219
|
+
}),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
dangerouslyAllowFullInternetAccess: true,
|
|
225
|
+
maxRedirects: config.MAX_REDIRECTS,
|
|
226
|
+
timeoutMs: config.NETWORK_TIMEOUT_MS,
|
|
227
|
+
...(config.MAX_RESPONSE_SIZE !== undefined && {
|
|
228
|
+
maxResponseSize: config.MAX_RESPONSE_SIZE,
|
|
229
|
+
}),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function buildExecutionLimits(): NonNullable<BashOptions["executionLimits"]> {
|
|
234
|
+
return {
|
|
235
|
+
maxCallDepth: config.MAX_CALL_DEPTH,
|
|
236
|
+
maxCommandCount: config.MAX_COMMAND_COUNT,
|
|
237
|
+
maxLoopIterations: config.MAX_LOOP_ITERATIONS,
|
|
238
|
+
maxAwkIterations: config.MAX_AWK_ITERATIONS,
|
|
239
|
+
maxSedIterations: config.MAX_SED_ITERATIONS,
|
|
240
|
+
maxJqIterations: config.MAX_JQ_ITERATIONS,
|
|
241
|
+
maxGlobOperations: config.MAX_GLOB_OPERATIONS,
|
|
242
|
+
maxStringLength: config.MAX_STRING_LENGTH,
|
|
243
|
+
maxArrayElements: config.MAX_ARRAY_ELEMENTS,
|
|
244
|
+
maxHeredocSize: config.MAX_HEREDOC_SIZE,
|
|
245
|
+
maxSubstitutionDepth: config.MAX_SUBSTITUTION_DEPTH,
|
|
246
|
+
maxSqliteTimeoutMs: config.MAX_SQLITE_TIMEOUT_MS,
|
|
247
|
+
maxPythonTimeoutMs: config.MAX_PYTHON_TIMEOUT_MS,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function parseMountsConfig(): MountConfig[] {
|
|
252
|
+
if (!config.MOUNTS_CONFIG) return [];
|
|
253
|
+
try {
|
|
254
|
+
const parsed: unknown = JSON.parse(config.MOUNTS_CONFIG);
|
|
255
|
+
if (!Array.isArray(parsed)) return [];
|
|
256
|
+
return parsed.map(
|
|
257
|
+
(mount: { mountPoint: string; root: string; type?: string; readOnly?: boolean }) => {
|
|
258
|
+
const fsType = mount.type || "overlay";
|
|
259
|
+
const maxFileReadSize = config.MAX_FILE_READ_SIZE;
|
|
260
|
+
const filesystem =
|
|
261
|
+
fsType === "readwrite"
|
|
262
|
+
? new ReadWriteFs({
|
|
263
|
+
root: mount.root,
|
|
264
|
+
...(maxFileReadSize !== undefined && { maxFileReadSize }),
|
|
265
|
+
})
|
|
266
|
+
: new OverlayFs({
|
|
267
|
+
root: mount.root,
|
|
268
|
+
readOnly: mount.readOnly,
|
|
269
|
+
...(maxFileReadSize !== undefined && { maxFileReadSize }),
|
|
270
|
+
});
|
|
271
|
+
return { mountPoint: mount.mountPoint, filesystem };
|
|
272
|
+
},
|
|
273
|
+
);
|
|
274
|
+
} catch {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// Environment Variables Documentation
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
export const ENVIRONMENT_VARIABLES = {
|
|
284
|
+
JUST_BASH_OVERLAY_ROOT: "Real directory to mount as overlay (read from disk, write to memory)",
|
|
285
|
+
JUST_BASH_OVERLAY_READ_ONLY:
|
|
286
|
+
"If true, all writes to overlay filesystem throw errors (default: false)",
|
|
287
|
+
JUST_BASH_READ_WRITE_ROOT: "Real directory with read-write access",
|
|
288
|
+
JUST_BASH_MOUNTS: "JSON array of mount configurations (supports type, readOnly fields)",
|
|
289
|
+
JUST_BASH_CWD: "Initial working directory (default: /home/user)",
|
|
290
|
+
JUST_BASH_ALLOW_NETWORK: "Enable network access (default: false)",
|
|
291
|
+
JUST_BASH_ALLOWED_URLS: "Comma-separated URL prefixes to allow",
|
|
292
|
+
JUST_BASH_ALLOWED_METHODS: "Comma-separated HTTP methods (default: GET,HEAD)",
|
|
293
|
+
JUST_BASH_ALLOWED_COMMANDS: "Comma-separated list of allowed commands",
|
|
294
|
+
JUST_BASH_MAX_REDIRECTS: "Max HTTP redirects (default: 20)",
|
|
295
|
+
JUST_BASH_NETWORK_TIMEOUT_MS: "Network timeout in ms (default: 30000)",
|
|
296
|
+
JUST_BASH_MAX_RESPONSE_SIZE: "Max network response body size in bytes (default: 10MB)",
|
|
297
|
+
JUST_BASH_MAX_CALL_DEPTH: "Max recursion depth (default: 100)",
|
|
298
|
+
JUST_BASH_MAX_COMMAND_COUNT: "Max commands per execution (default: 10000)",
|
|
299
|
+
JUST_BASH_MAX_LOOP_ITERATIONS: "Max bash loop iterations (default: 10000)",
|
|
300
|
+
JUST_BASH_MAX_AWK_ITERATIONS: "Max AWK while/for loop iterations (default: 10000)",
|
|
301
|
+
JUST_BASH_MAX_SED_ITERATIONS: "Max sed branch loop iterations (default: 10000)",
|
|
302
|
+
JUST_BASH_MAX_JQ_ITERATIONS: "Max jq loop iterations (default: 10000)",
|
|
303
|
+
JUST_BASH_MAX_GLOB_OPERATIONS: "Max glob filesystem operations (default: 100000)",
|
|
304
|
+
JUST_BASH_MAX_STRING_LENGTH: "Max string length in bytes (default: 10485760 = 10MB)",
|
|
305
|
+
JUST_BASH_MAX_ARRAY_ELEMENTS: "Max array elements (default: 100000)",
|
|
306
|
+
JUST_BASH_MAX_HEREDOC_SIZE: "Max heredoc size in bytes (default: 10485760 = 10MB)",
|
|
307
|
+
JUST_BASH_MAX_SUBSTITUTION_DEPTH: "Max command substitution nesting depth (default: 50)",
|
|
308
|
+
JUST_BASH_MAX_SQLITE_TIMEOUT_MS: "SQLite timeout in ms (default: 5000)",
|
|
309
|
+
JUST_BASH_MAX_PYTHON_TIMEOUT_MS: "Python timeout in ms (default: 30000)",
|
|
310
|
+
JUST_BASH_MAX_OUTPUT_LENGTH: "Max output length (default: 30000)",
|
|
311
|
+
JUST_BASH_MAX_FILE_READ_SIZE:
|
|
312
|
+
"Max file read size in bytes for OverlayFs/ReadWriteFs (default: 10MB)",
|
|
313
|
+
JUST_BASH_ENABLE_LOGGING: "Enable debug logging (default: false)",
|
|
314
|
+
JUST_BASH_ENABLE_TRACING: "Enable performance tracing (default: false)",
|
|
315
|
+
JUST_BASH_ENABLE_PYTHON: "Enable python3/python commands via Pyodide (default: false)",
|
|
316
|
+
JUST_BASH_DEFENSE_IN_DEPTH:
|
|
317
|
+
"Enable defense-in-depth mode that patches dangerous JS globals (default: false)",
|
|
318
|
+
} as const;
|
|
319
|
+
|
|
320
|
+
// ============================================================================
|
|
321
|
+
// Command Categories Documentation
|
|
322
|
+
// ============================================================================
|
|
323
|
+
|
|
324
|
+
export const COMMAND_CATEGORIES = {
|
|
325
|
+
fileOperations: "cat, cp, file, ln, ls, mkdir, mv, readlink, rm, rmdir, split, stat, touch, tree",
|
|
326
|
+
textProcessing:
|
|
327
|
+
"awk, base64, column, comm, cut, diff, expand, fold, grep (egrep, fgrep), head, join, md5sum, nl, od, paste, printf, rev, rg (ripgrep), sed, sha1sum, sha256sum, sort, strings, tac, tail, tr, unexpand, uniq, wc, xargs",
|
|
328
|
+
dataProcessing:
|
|
329
|
+
"jq (JSON), python3/python (Python via Pyodide), sqlite3 (SQLite), xan (CSV), yq (YAML/XML/TOML/CSV)",
|
|
330
|
+
compression: "gzip (gunzip, zcat), tar",
|
|
331
|
+
navigation:
|
|
332
|
+
"basename, cd, dirname, du, echo, env, export, find, hostname, printenv, pwd, tee, whoami",
|
|
333
|
+
shellUtilities:
|
|
334
|
+
"alias, bash, chmod, clear, date, expr, false, help, history, seq, sh, sleep, time, timeout, true, unalias, which",
|
|
335
|
+
network: "curl, html-to-markdown (when network enabled)",
|
|
336
|
+
} as const;
|
|
337
|
+
|
|
338
|
+
// ============================================================================
|
|
339
|
+
// Features Documentation
|
|
340
|
+
// ============================================================================
|
|
341
|
+
|
|
342
|
+
export const FEATURES = {
|
|
343
|
+
customCommands: "Define custom TypeScript commands using defineCommand()",
|
|
344
|
+
rawScript: "Preserve leading whitespace in scripts (useful for here-docs)",
|
|
345
|
+
logger: "Optional execution logging via BashLogger interface",
|
|
346
|
+
trace: "Performance profiling via TraceCallback",
|
|
347
|
+
commandFilter: "Restrict available commands via JUST_BASH_ALLOWED_COMMANDS env var",
|
|
348
|
+
sandboxApi: "Vercel Sandbox compatible API via bash_sandbox_* tools",
|
|
349
|
+
python: "Python support via Pyodide (opt-in via JUST_BASH_ENABLE_PYTHON=true)",
|
|
350
|
+
defenseInDepth:
|
|
351
|
+
"Defense-in-depth mode that monkey-patches dangerous JS globals during execution (opt-in via JUST_BASH_DEFENSE_IN_DEPTH=true)",
|
|
352
|
+
overlayReadOnly:
|
|
353
|
+
"Read-only overlay filesystem mode (opt-in via JUST_BASH_OVERLAY_READ_ONLY=true)",
|
|
354
|
+
networkResponseSize:
|
|
355
|
+
"Configurable max network response body size via JUST_BASH_MAX_RESPONSE_SIZE",
|
|
356
|
+
fileReadSizeLimit:
|
|
357
|
+
"Configurable max file read size for OverlayFs/ReadWriteFs via JUST_BASH_MAX_FILE_READ_SIZE",
|
|
358
|
+
} as const;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* just-bash-mcp - MCP Server for sandboxed bash execution
|
|
5
|
+
*
|
|
6
|
+
* A Model Context Protocol (MCP) server that provides AI agents with a
|
|
7
|
+
* secure, sandboxed bash environment powered by just-bash from Vercel Labs.
|
|
8
|
+
*
|
|
9
|
+
* @see https://github.com/vercel-labs/just-bash
|
|
10
|
+
* @see https://modelcontextprotocol.io
|
|
11
|
+
*/
|
|
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";
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Server Initialization
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
const server = new McpServer({
|
|
24
|
+
name: config.SERVER_NAME,
|
|
25
|
+
version: config.VERSION,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Register all tools with the server
|
|
29
|
+
registerAllTools(server);
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Start Server
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
const transport = new StdioServerTransport();
|
|
36
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash instance management
|
|
3
|
+
* Handles creation and lifecycle of Bash instances
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Bash,
|
|
8
|
+
type BashOptions,
|
|
9
|
+
type CustomCommand,
|
|
10
|
+
InMemoryFs,
|
|
11
|
+
MountableFs,
|
|
12
|
+
OverlayFs,
|
|
13
|
+
ReadWriteFs,
|
|
14
|
+
Sandbox,
|
|
15
|
+
type SandboxOptions,
|
|
16
|
+
} from "just-bash";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
bashLogger,
|
|
20
|
+
buildExecutionLimits,
|
|
21
|
+
buildNetworkConfig,
|
|
22
|
+
config,
|
|
23
|
+
parseMountsConfig,
|
|
24
|
+
traceCallback,
|
|
25
|
+
} from "../config/index.ts";
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Bash Instance Factory
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a new Bash instance with the given configuration
|
|
33
|
+
*/
|
|
34
|
+
export function createBashInstance(
|
|
35
|
+
files?: Record<string, string>,
|
|
36
|
+
customCommands?: CustomCommand[],
|
|
37
|
+
env?: Record<string, string>,
|
|
38
|
+
): Bash {
|
|
39
|
+
const networkConfig = buildNetworkConfig();
|
|
40
|
+
const executionLimits = buildExecutionLimits();
|
|
41
|
+
|
|
42
|
+
const baseOptions: BashOptions = {
|
|
43
|
+
network: networkConfig,
|
|
44
|
+
executionLimits,
|
|
45
|
+
files,
|
|
46
|
+
env,
|
|
47
|
+
logger: bashLogger,
|
|
48
|
+
trace: traceCallback,
|
|
49
|
+
customCommands,
|
|
50
|
+
commands: config.ALLOWED_COMMANDS,
|
|
51
|
+
python: config.ENABLE_PYTHON,
|
|
52
|
+
defenseInDepth: config.ENABLE_DEFENSE_IN_DEPTH,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Check for mountable filesystem configuration
|
|
56
|
+
const mounts = parseMountsConfig();
|
|
57
|
+
if (mounts.length > 0) {
|
|
58
|
+
const mountableFs = new MountableFs({
|
|
59
|
+
base: new InMemoryFs(),
|
|
60
|
+
mounts,
|
|
61
|
+
});
|
|
62
|
+
return new Bash({
|
|
63
|
+
...baseOptions,
|
|
64
|
+
fs: mountableFs,
|
|
65
|
+
cwd: config.INITIAL_CWD,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check for read-write filesystem configuration
|
|
70
|
+
if (config.READ_WRITE_ROOT) {
|
|
71
|
+
const rwfs = new ReadWriteFs({
|
|
72
|
+
root: config.READ_WRITE_ROOT,
|
|
73
|
+
...(config.MAX_FILE_READ_SIZE !== undefined && {
|
|
74
|
+
maxFileReadSize: config.MAX_FILE_READ_SIZE,
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
return new Bash({
|
|
78
|
+
...baseOptions,
|
|
79
|
+
fs: rwfs,
|
|
80
|
+
cwd: config.READ_WRITE_ROOT,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check for overlay filesystem configuration
|
|
85
|
+
if (config.OVERLAY_ROOT) {
|
|
86
|
+
const overlay = new OverlayFs({
|
|
87
|
+
root: config.OVERLAY_ROOT,
|
|
88
|
+
readOnly: config.OVERLAY_READ_ONLY,
|
|
89
|
+
...(config.MAX_FILE_READ_SIZE !== undefined && {
|
|
90
|
+
maxFileReadSize: config.MAX_FILE_READ_SIZE,
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
return new Bash({
|
|
94
|
+
...baseOptions,
|
|
95
|
+
fs: overlay,
|
|
96
|
+
cwd: overlay.getMountPoint(),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Default: in-memory filesystem
|
|
101
|
+
return new Bash({
|
|
102
|
+
...baseOptions,
|
|
103
|
+
cwd: config.INITIAL_CWD,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// Persistent Bash Instance (singleton pattern)
|
|
109
|
+
// ============================================================================
|
|
110
|
+
|
|
111
|
+
let persistentBash: Bash | null = null;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get or create the persistent Bash instance
|
|
115
|
+
*/
|
|
116
|
+
export function getPersistentBash(): Bash {
|
|
117
|
+
if (!persistentBash) {
|
|
118
|
+
persistentBash = createBashInstance();
|
|
119
|
+
}
|
|
120
|
+
return persistentBash;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Reset the persistent Bash instance
|
|
125
|
+
*/
|
|
126
|
+
export function resetPersistentBash(): void {
|
|
127
|
+
persistentBash = null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// Persistent Sandbox Instance (singleton pattern)
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
let persistentSandbox: Sandbox | null = null;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get or create the persistent Sandbox instance
|
|
138
|
+
*/
|
|
139
|
+
export async function getPersistentSandbox(): Promise<Sandbox> {
|
|
140
|
+
if (!persistentSandbox) {
|
|
141
|
+
const networkConfig = buildNetworkConfig();
|
|
142
|
+
const options: SandboxOptions = {
|
|
143
|
+
cwd: config.INITIAL_CWD,
|
|
144
|
+
network: networkConfig,
|
|
145
|
+
maxCallDepth: config.MAX_CALL_DEPTH,
|
|
146
|
+
maxCommandCount: config.MAX_COMMAND_COUNT,
|
|
147
|
+
maxLoopIterations: config.MAX_LOOP_ITERATIONS,
|
|
148
|
+
...(config.OVERLAY_ROOT && { overlayRoot: config.OVERLAY_ROOT }),
|
|
149
|
+
};
|
|
150
|
+
persistentSandbox = await Sandbox.create(options);
|
|
151
|
+
}
|
|
152
|
+
return persistentSandbox;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Reset the persistent Sandbox instance
|
|
157
|
+
*/
|
|
158
|
+
export function resetPersistentSandbox(): void {
|
|
159
|
+
persistentSandbox = null;
|
|
160
|
+
}
|