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/README.md +47 -5
- package/package.json +3 -2
- package/src/config/index.ts +346 -326
- package/src/index.ts +8 -12
- package/src/tools/bash-instance.ts +66 -131
- package/src/tools/exec-tools.ts +92 -139
- package/src/tools/file-tools.ts +89 -182
- package/src/tools/index.ts +19 -27
- package/src/tools/info-tools.ts +103 -135
- package/src/tools/sandbox-tools.ts +114 -209
- package/src/types.ts +1 -1
- package/src/utils/index.ts +20 -55
package/src/index.ts
CHANGED
|
@@ -10,27 +10,23 @@
|
|
|
10
10
|
* @see https://modelcontextprotocol.io
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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():
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
143
|
+
if (persistentSandbox) {
|
|
144
|
+
await persistentSandbox.stop()
|
|
145
|
+
}
|
|
146
|
+
persistentSandbox = null
|
|
212
147
|
}
|
package/src/tools/exec-tools.ts
CHANGED
|
@@ -9,157 +9,110 @@
|
|
|
9
9
|
* - SecurityViolationError: Defense-in-depth violation detected
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
}
|