@yukkit/e2b-mcp-server 0.2.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 +49 -0
- package/build/index.js +557 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# E2B MCP Server (JavaScript)
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol server for running code in a secure sandbox by [E2B](https://e2b.dev).
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
Install dependencies:
|
|
8
|
+
```
|
|
9
|
+
pnpm install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Build the server:
|
|
13
|
+
```
|
|
14
|
+
pnpm build
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For development with auto-rebuild:
|
|
18
|
+
```
|
|
19
|
+
pnpm watch
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
To use with Claude Desktop, add the server config:
|
|
25
|
+
|
|
26
|
+
On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
27
|
+
On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"e2b-server": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["-y", "@e2b/mcp-server"],
|
|
35
|
+
"env": { "E2B_API_KEY": "${e2bApiKey}" }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Debugging
|
|
42
|
+
|
|
43
|
+
Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
pnpm inspector
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The Inspector will provide a URL to access debugging tools in your browser.
|
package/build/index.js
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* E2B MCP Server - Production-grade Model Context Protocol server for E2B sandboxes.
|
|
4
|
+
*
|
|
5
|
+
* This module provides a robust MCP server implementation that allows AI models to
|
|
6
|
+
* interact with E2B code execution sandboxes securely and efficiently.
|
|
7
|
+
*/
|
|
8
|
+
import { Sandbox } from "@e2b/code-interpreter";
|
|
9
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
14
|
+
import dotenv from "dotenv";
|
|
15
|
+
import { readFileSync } from "fs";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
import { dirname, join } from "path";
|
|
18
|
+
dotenv.config();
|
|
19
|
+
// Read version from package.json
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
23
|
+
const VERSION = packageJson.version;
|
|
24
|
+
// Constants
|
|
25
|
+
const DEFAULT_SANDBOX_TIMEOUT_MS = 300000; // 5 minutes
|
|
26
|
+
const MAX_SANDBOX_TIMEOUT_MS = 3600000; // 1 hour
|
|
27
|
+
const MAX_ACTIVE_SANDBOXES = parseInt(process.env.MAX_ACTIVE_SANDBOXES || "10", 10);
|
|
28
|
+
const LOG_LEVEL = process.env.LOG_LEVEL || "INFO";
|
|
29
|
+
// Schemas
|
|
30
|
+
const createSandboxSchema = z.object({
|
|
31
|
+
timeoutMs: z
|
|
32
|
+
.number()
|
|
33
|
+
.min(1000)
|
|
34
|
+
.max(MAX_SANDBOX_TIMEOUT_MS)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Timeout in milliseconds"),
|
|
37
|
+
});
|
|
38
|
+
const runCommandSchema = z.object({
|
|
39
|
+
command: z.string().min(1).describe("Shell command to execute"),
|
|
40
|
+
sandboxId: z.string().describe("Sandbox ID"),
|
|
41
|
+
background: z.boolean().default(false).describe("Run command in background"),
|
|
42
|
+
});
|
|
43
|
+
const readFileSchema = z.object({
|
|
44
|
+
filePath: z.string().min(1).describe("Path to the file"),
|
|
45
|
+
sandboxId: z.string().describe("Sandbox ID"),
|
|
46
|
+
});
|
|
47
|
+
const writeFileSchema = z.object({
|
|
48
|
+
filePath: z.string().min(1).describe("Path to the file"),
|
|
49
|
+
sandboxId: z.string().describe("Sandbox ID"),
|
|
50
|
+
fileContents: z.string().describe("Content to write to the file"),
|
|
51
|
+
});
|
|
52
|
+
const listFilesSchema = z.object({
|
|
53
|
+
sandboxId: z.string().describe("Sandbox ID"),
|
|
54
|
+
folderPath: z.string().min(1).describe("Path to the folder"),
|
|
55
|
+
});
|
|
56
|
+
const runCodeSchema = z.object({
|
|
57
|
+
code: z.string().min(1).describe("Python code to execute"),
|
|
58
|
+
sandboxId: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Optional sandbox ID. If not provided, a temporary sandbox will be created."),
|
|
62
|
+
});
|
|
63
|
+
const getSandboxUrlSchema = z.object({
|
|
64
|
+
port: z.number().min(1).max(65535).describe("Port number"),
|
|
65
|
+
sandboxId: z.string().describe("Sandbox ID"),
|
|
66
|
+
});
|
|
67
|
+
const killSandboxSchema = z.object({
|
|
68
|
+
sandboxId: z.string().describe("Sandbox ID"),
|
|
69
|
+
});
|
|
70
|
+
// Custom Errors
|
|
71
|
+
class SandboxError extends Error {
|
|
72
|
+
constructor(message) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = "SandboxError";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
class SandboxNotFoundError extends SandboxError {
|
|
78
|
+
constructor(message) {
|
|
79
|
+
super(message);
|
|
80
|
+
this.name = "SandboxNotFoundError";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
class SandboxLimitExceededError extends SandboxError {
|
|
84
|
+
constructor(message) {
|
|
85
|
+
super(message);
|
|
86
|
+
this.name = "SandboxLimitExceededError";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Logger utility
|
|
90
|
+
class Logger {
|
|
91
|
+
level;
|
|
92
|
+
constructor(level = "INFO") {
|
|
93
|
+
this.level = level;
|
|
94
|
+
}
|
|
95
|
+
log(level, message, ...args) {
|
|
96
|
+
const timestamp = new Date().toISOString();
|
|
97
|
+
console.error(`${timestamp} - e2b-mcp-server - ${level} - ${message}`, ...args);
|
|
98
|
+
}
|
|
99
|
+
info(message, ...args) {
|
|
100
|
+
this.log("INFO", message, ...args);
|
|
101
|
+
}
|
|
102
|
+
debug(message, ...args) {
|
|
103
|
+
if (this.level === "DEBUG") {
|
|
104
|
+
this.log("DEBUG", message, ...args);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
warning(message, ...args) {
|
|
108
|
+
this.log("WARNING", message, ...args);
|
|
109
|
+
}
|
|
110
|
+
error(message, ...args) {
|
|
111
|
+
this.log("ERROR", message, ...args);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const logger = new Logger(LOG_LEVEL);
|
|
115
|
+
// Sandbox Manager
|
|
116
|
+
class SandboxManager {
|
|
117
|
+
sandboxes = new Map();
|
|
118
|
+
maxSandboxes;
|
|
119
|
+
constructor(maxSandboxes = MAX_ACTIVE_SANDBOXES) {
|
|
120
|
+
this.maxSandboxes = maxSandboxes;
|
|
121
|
+
logger.info(`SandboxManager initialized with max_sandboxes=${maxSandboxes}`);
|
|
122
|
+
}
|
|
123
|
+
async createSandbox(timeoutMs) {
|
|
124
|
+
if (this.sandboxes.size >= this.maxSandboxes) {
|
|
125
|
+
throw new SandboxLimitExceededError(`Maximum number of sandboxes (${this.maxSandboxes}) reached`);
|
|
126
|
+
}
|
|
127
|
+
const timeout = Math.min(timeoutMs || DEFAULT_SANDBOX_TIMEOUT_MS, MAX_SANDBOX_TIMEOUT_MS);
|
|
128
|
+
try {
|
|
129
|
+
const sandbox = await Sandbox.create({ timeoutMs: timeout });
|
|
130
|
+
const sandboxId = sandbox.sandboxId;
|
|
131
|
+
this.sandboxes.set(sandboxId, sandbox);
|
|
132
|
+
logger.info(`Sandbox created: ${sandboxId} (timeout=${timeout}ms, active=${this.sandboxes.size})`);
|
|
133
|
+
return { sandboxId, sandbox };
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
logger.error(`Failed to create sandbox: ${error}`);
|
|
137
|
+
throw new SandboxError(`Failed to create sandbox: ${error}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
getSandbox(sandboxId) {
|
|
141
|
+
const sandbox = this.sandboxes.get(sandboxId);
|
|
142
|
+
if (!sandbox) {
|
|
143
|
+
throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
|
|
144
|
+
}
|
|
145
|
+
return sandbox;
|
|
146
|
+
}
|
|
147
|
+
async killSandbox(sandboxId) {
|
|
148
|
+
if (!this.sandboxes.has(sandboxId)) {
|
|
149
|
+
throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const sandbox = this.sandboxes.get(sandboxId);
|
|
153
|
+
await sandbox.kill();
|
|
154
|
+
this.sandboxes.delete(sandboxId);
|
|
155
|
+
logger.info(`Sandbox killed: ${sandboxId} (active=${this.sandboxes.size})`);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
logger.error(`Error killing sandbox ${sandboxId}: ${error}`);
|
|
159
|
+
// Still remove it from tracking even if kill fails
|
|
160
|
+
this.sandboxes.delete(sandboxId);
|
|
161
|
+
throw new SandboxError(`Error killing sandbox: ${error}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async cleanupAll() {
|
|
165
|
+
const sandboxIds = Array.from(this.sandboxes.keys());
|
|
166
|
+
logger.info(`Cleaning up ${sandboxIds.length} sandboxes`);
|
|
167
|
+
for (const sandboxId of sandboxIds) {
|
|
168
|
+
try {
|
|
169
|
+
const sandbox = this.sandboxes.get(sandboxId);
|
|
170
|
+
if (sandbox) {
|
|
171
|
+
await sandbox.kill();
|
|
172
|
+
logger.info(`Cleaned up sandbox: ${sandboxId}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
logger.error(`Error cleaning up sandbox ${sandboxId}: ${error}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
this.sandboxes.clear();
|
|
180
|
+
logger.info("All sandboxes cleaned up");
|
|
181
|
+
}
|
|
182
|
+
getStats() {
|
|
183
|
+
return {
|
|
184
|
+
active_sandboxes: this.sandboxes.size,
|
|
185
|
+
max_sandboxes: this.maxSandboxes,
|
|
186
|
+
sandbox_ids: Array.from(this.sandboxes.keys()),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// E2B MCP Server
|
|
191
|
+
class E2BServer {
|
|
192
|
+
server;
|
|
193
|
+
sandboxManager;
|
|
194
|
+
constructor() {
|
|
195
|
+
this.server = new Server({
|
|
196
|
+
name: "e2b-code-mcp-server",
|
|
197
|
+
version: VERSION,
|
|
198
|
+
}, {
|
|
199
|
+
capabilities: {
|
|
200
|
+
tools: {},
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
this.sandboxManager = new SandboxManager();
|
|
204
|
+
this.setupHandlers();
|
|
205
|
+
this.setupErrorHandling();
|
|
206
|
+
}
|
|
207
|
+
setupErrorHandling() {
|
|
208
|
+
this.server.onerror = (error) => {
|
|
209
|
+
logger.error("[MCP Error]", error);
|
|
210
|
+
};
|
|
211
|
+
process.on("SIGINT", async () => {
|
|
212
|
+
logger.info("Received shutdown signal");
|
|
213
|
+
await this.cleanup();
|
|
214
|
+
process.exit(0);
|
|
215
|
+
});
|
|
216
|
+
process.on("SIGTERM", async () => {
|
|
217
|
+
logger.info("Received shutdown signal");
|
|
218
|
+
await this.cleanup();
|
|
219
|
+
process.exit(0);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
async cleanup() {
|
|
223
|
+
logger.info("Shutting down server...");
|
|
224
|
+
await this.sandboxManager.cleanupAll();
|
|
225
|
+
await this.server.close();
|
|
226
|
+
logger.info("Server shutdown complete");
|
|
227
|
+
}
|
|
228
|
+
setupHandlers() {
|
|
229
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
230
|
+
tools: [
|
|
231
|
+
{
|
|
232
|
+
name: "create_sandbox",
|
|
233
|
+
description: "Create a new E2B sandbox",
|
|
234
|
+
inputSchema: zodToJsonSchema(createSandboxSchema),
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "run_command",
|
|
238
|
+
description: "Run a command in the sandbox",
|
|
239
|
+
inputSchema: zodToJsonSchema(runCommandSchema),
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: "read_file",
|
|
243
|
+
description: "Read a file from the sandbox",
|
|
244
|
+
inputSchema: zodToJsonSchema(readFileSchema),
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: "write_file",
|
|
248
|
+
description: "Write content to a file in the sandbox",
|
|
249
|
+
inputSchema: zodToJsonSchema(writeFileSchema),
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: "list_files",
|
|
253
|
+
description: "List files in a directory",
|
|
254
|
+
inputSchema: zodToJsonSchema(listFilesSchema),
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "run_code",
|
|
258
|
+
description: "Run python code in a secure sandbox by E2B. Using the Jupyter Notebook syntax. Optionally specify sandboxId to use an existing sandbox.",
|
|
259
|
+
inputSchema: zodToJsonSchema(runCodeSchema),
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "get_sandbox_url",
|
|
263
|
+
description: "Get the URL for a sandbox on a specific port",
|
|
264
|
+
inputSchema: zodToJsonSchema(getSandboxUrlSchema),
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: "kill_sandbox",
|
|
268
|
+
description: "Kill a sandbox",
|
|
269
|
+
inputSchema: zodToJsonSchema(killSandboxSchema),
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
}));
|
|
273
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
274
|
+
logger.debug(`Tool called: ${request.params.name}`, request.params.arguments);
|
|
275
|
+
try {
|
|
276
|
+
switch (request.params.name) {
|
|
277
|
+
case "create_sandbox":
|
|
278
|
+
return await this.handleCreateSandbox(request.params.arguments);
|
|
279
|
+
case "run_command":
|
|
280
|
+
return await this.handleRunCommand(request.params.arguments);
|
|
281
|
+
case "read_file":
|
|
282
|
+
return await this.handleReadFile(request.params.arguments);
|
|
283
|
+
case "write_file":
|
|
284
|
+
return await this.handleWriteFile(request.params.arguments);
|
|
285
|
+
case "list_files":
|
|
286
|
+
return await this.handleListFiles(request.params.arguments);
|
|
287
|
+
case "run_code":
|
|
288
|
+
return await this.handleRunCode(request.params.arguments);
|
|
289
|
+
case "get_sandbox_url":
|
|
290
|
+
return await this.handleGetSandboxUrl(request.params.arguments);
|
|
291
|
+
case "kill_sandbox":
|
|
292
|
+
return await this.handleKillSandbox(request.params.arguments);
|
|
293
|
+
default:
|
|
294
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
if (error instanceof McpError) {
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
if (error instanceof z.ZodError) {
|
|
302
|
+
logger.warning(`Validation error for tool ${request.params.name}: ${error}`);
|
|
303
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments: ${error.message}`);
|
|
304
|
+
}
|
|
305
|
+
if (error instanceof SandboxError) {
|
|
306
|
+
logger.error(`Sandbox error in tool ${request.params.name}: ${error}`);
|
|
307
|
+
throw new McpError(ErrorCode.InternalError, error.message);
|
|
308
|
+
}
|
|
309
|
+
logger.error(`Unexpected error executing tool ${request.params.name}: ${error}`);
|
|
310
|
+
throw new McpError(ErrorCode.InternalError, `Unexpected error: ${error}`);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
async handleCreateSandbox(args) {
|
|
315
|
+
const parsed = createSandboxSchema.parse(args);
|
|
316
|
+
const { sandboxId } = await this.sandboxManager.createSandbox(parsed.timeoutMs);
|
|
317
|
+
const timeout = parsed.timeoutMs || DEFAULT_SANDBOX_TIMEOUT_MS;
|
|
318
|
+
const result = {
|
|
319
|
+
sandboxId,
|
|
320
|
+
message: `Sandbox created successfully with timeout ${timeout}ms`,
|
|
321
|
+
stats: this.sandboxManager.getStats(),
|
|
322
|
+
};
|
|
323
|
+
return {
|
|
324
|
+
content: [
|
|
325
|
+
{
|
|
326
|
+
type: "text",
|
|
327
|
+
text: JSON.stringify(result, null, 2),
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
async handleRunCommand(args) {
|
|
333
|
+
const parsed = runCommandSchema.parse(args);
|
|
334
|
+
const sandbox = this.sandboxManager.getSandbox(parsed.sandboxId);
|
|
335
|
+
try {
|
|
336
|
+
let response;
|
|
337
|
+
if (parsed.background) {
|
|
338
|
+
const result = await sandbox.commands.run(parsed.command, { background: true });
|
|
339
|
+
response = {
|
|
340
|
+
message: "Command started in background",
|
|
341
|
+
pid: result.pid,
|
|
342
|
+
sandboxId: parsed.sandboxId,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
const result = await sandbox.commands.run(parsed.command);
|
|
347
|
+
response = {
|
|
348
|
+
stdout: result.stdout,
|
|
349
|
+
stderr: result.stderr,
|
|
350
|
+
exit_code: result.exitCode,
|
|
351
|
+
sandboxId: parsed.sandboxId,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
logger.info(`Command executed in sandbox ${parsed.sandboxId}: background=${parsed.background}`);
|
|
355
|
+
return {
|
|
356
|
+
content: [
|
|
357
|
+
{
|
|
358
|
+
type: "text",
|
|
359
|
+
text: JSON.stringify(response, null, 2),
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
logger.error(`Error running command in sandbox ${parsed.sandboxId}: ${error}`);
|
|
366
|
+
throw new SandboxError(`Failed to run command: ${error}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async handleReadFile(args) {
|
|
370
|
+
const parsed = readFileSchema.parse(args);
|
|
371
|
+
const sandbox = this.sandboxManager.getSandbox(parsed.sandboxId);
|
|
372
|
+
try {
|
|
373
|
+
const content = await sandbox.files.read(parsed.filePath);
|
|
374
|
+
const result = {
|
|
375
|
+
filePath: parsed.filePath,
|
|
376
|
+
content,
|
|
377
|
+
sandboxId: parsed.sandboxId,
|
|
378
|
+
};
|
|
379
|
+
logger.info(`Read file ${parsed.filePath} from sandbox ${parsed.sandboxId}`);
|
|
380
|
+
return {
|
|
381
|
+
content: [
|
|
382
|
+
{
|
|
383
|
+
type: "text",
|
|
384
|
+
text: JSON.stringify(result, null, 2),
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
logger.error(`Error reading file ${parsed.filePath}: ${error}`);
|
|
391
|
+
throw new SandboxError(`Failed to read file: ${error}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
async handleWriteFile(args) {
|
|
395
|
+
const parsed = writeFileSchema.parse(args);
|
|
396
|
+
const sandbox = this.sandboxManager.getSandbox(parsed.sandboxId);
|
|
397
|
+
try {
|
|
398
|
+
await sandbox.files.write(parsed.filePath, parsed.fileContents);
|
|
399
|
+
const result = {
|
|
400
|
+
filePath: parsed.filePath,
|
|
401
|
+
message: "File written successfully",
|
|
402
|
+
sandboxId: parsed.sandboxId,
|
|
403
|
+
size: parsed.fileContents.length,
|
|
404
|
+
};
|
|
405
|
+
logger.info(`Wrote file ${parsed.filePath} to sandbox ${parsed.sandboxId} (${parsed.fileContents.length} bytes)`);
|
|
406
|
+
return {
|
|
407
|
+
content: [
|
|
408
|
+
{
|
|
409
|
+
type: "text",
|
|
410
|
+
text: JSON.stringify(result, null, 2),
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
logger.error(`Error writing file ${parsed.filePath}: ${error}`);
|
|
417
|
+
throw new SandboxError(`Failed to write file: ${error}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async handleListFiles(args) {
|
|
421
|
+
const parsed = listFilesSchema.parse(args);
|
|
422
|
+
const sandbox = this.sandboxManager.getSandbox(parsed.sandboxId);
|
|
423
|
+
try {
|
|
424
|
+
const files = await sandbox.files.list(parsed.folderPath);
|
|
425
|
+
const result = {
|
|
426
|
+
folderPath: parsed.folderPath,
|
|
427
|
+
files: files.map((f) => ({ name: f.name, type: f.type })),
|
|
428
|
+
sandboxId: parsed.sandboxId,
|
|
429
|
+
count: files.length,
|
|
430
|
+
};
|
|
431
|
+
logger.info(`Listed ${files.length} files in ${parsed.folderPath} from sandbox ${parsed.sandboxId}`);
|
|
432
|
+
return {
|
|
433
|
+
content: [
|
|
434
|
+
{
|
|
435
|
+
type: "text",
|
|
436
|
+
text: JSON.stringify(result, null, 2),
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
logger.error(`Error listing files in ${parsed.folderPath}: ${error}`);
|
|
443
|
+
throw new SandboxError(`Failed to list files: ${error}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
async handleRunCode(args) {
|
|
447
|
+
const parsed = runCodeSchema.parse(args);
|
|
448
|
+
let shouldCleanup = false;
|
|
449
|
+
let sandbox = null;
|
|
450
|
+
try {
|
|
451
|
+
if (parsed.sandboxId) {
|
|
452
|
+
sandbox = this.sandboxManager.getSandbox(parsed.sandboxId);
|
|
453
|
+
logger.info(`Running code in existing sandbox ${parsed.sandboxId}`);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
sandbox = await Sandbox.create();
|
|
457
|
+
shouldCleanup = true;
|
|
458
|
+
logger.info("Running code in temporary sandbox");
|
|
459
|
+
}
|
|
460
|
+
const execution = await sandbox.runCode(parsed.code);
|
|
461
|
+
const result = {
|
|
462
|
+
stdout: execution.logs.stdout.join("\n"),
|
|
463
|
+
stderr: execution.logs.stderr.join("\n"),
|
|
464
|
+
};
|
|
465
|
+
if (parsed.sandboxId) {
|
|
466
|
+
result.sandboxId = parsed.sandboxId;
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
result.message = "Executed in temporary sandbox";
|
|
470
|
+
}
|
|
471
|
+
logger.info(`Code execution completed: stdout=${result.stdout.length} chars, stderr=${result.stderr.length} chars`);
|
|
472
|
+
return {
|
|
473
|
+
content: [
|
|
474
|
+
{
|
|
475
|
+
type: "text",
|
|
476
|
+
text: JSON.stringify(result, null, 2),
|
|
477
|
+
},
|
|
478
|
+
],
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
logger.error(`Error running code: ${error}`);
|
|
483
|
+
throw new SandboxError(`Failed to run code: ${error}`);
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
if (shouldCleanup && sandbox) {
|
|
487
|
+
try {
|
|
488
|
+
await sandbox.kill();
|
|
489
|
+
logger.info("Temporary sandbox cleaned up");
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
logger.warning(`Error cleaning up temporary sandbox: ${error}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
async handleGetSandboxUrl(args) {
|
|
498
|
+
const parsed = getSandboxUrlSchema.parse(args);
|
|
499
|
+
const sandbox = this.sandboxManager.getSandbox(parsed.sandboxId);
|
|
500
|
+
try {
|
|
501
|
+
const url = sandbox.getHost(parsed.port);
|
|
502
|
+
const result = {
|
|
503
|
+
sandboxId: parsed.sandboxId,
|
|
504
|
+
port: parsed.port,
|
|
505
|
+
url,
|
|
506
|
+
};
|
|
507
|
+
logger.info(`Got URL for sandbox ${parsed.sandboxId} on port ${parsed.port}: ${url}`);
|
|
508
|
+
return {
|
|
509
|
+
content: [
|
|
510
|
+
{
|
|
511
|
+
type: "text",
|
|
512
|
+
text: JSON.stringify(result, null, 2),
|
|
513
|
+
},
|
|
514
|
+
],
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
logger.error(`Error getting sandbox URL: ${error}`);
|
|
519
|
+
throw new SandboxError(`Failed to get sandbox URL: ${error}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
async handleKillSandbox(args) {
|
|
523
|
+
const parsed = killSandboxSchema.parse(args);
|
|
524
|
+
await this.sandboxManager.killSandbox(parsed.sandboxId);
|
|
525
|
+
const result = {
|
|
526
|
+
sandboxId: parsed.sandboxId,
|
|
527
|
+
message: "Sandbox killed successfully",
|
|
528
|
+
stats: this.sandboxManager.getStats(),
|
|
529
|
+
};
|
|
530
|
+
return {
|
|
531
|
+
content: [
|
|
532
|
+
{
|
|
533
|
+
type: "text",
|
|
534
|
+
text: JSON.stringify(result, null, 2),
|
|
535
|
+
},
|
|
536
|
+
],
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
async run() {
|
|
540
|
+
const transport = new StdioServerTransport();
|
|
541
|
+
await this.server.connect(transport);
|
|
542
|
+
logger.info("=".repeat(60));
|
|
543
|
+
logger.info("E2B MCP Server - Production Grade");
|
|
544
|
+
logger.info("=".repeat(60));
|
|
545
|
+
logger.info(`Max active sandboxes: ${MAX_ACTIVE_SANDBOXES}`);
|
|
546
|
+
logger.info(`Default sandbox timeout: ${DEFAULT_SANDBOX_TIMEOUT_MS}ms`);
|
|
547
|
+
logger.info(`Max sandbox timeout: ${MAX_SANDBOX_TIMEOUT_MS}ms`);
|
|
548
|
+
logger.info("=".repeat(60));
|
|
549
|
+
logger.info("Server ready to accept connections");
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// Main entry point
|
|
553
|
+
const server = new E2BServer();
|
|
554
|
+
server.run().catch((error) => {
|
|
555
|
+
logger.error("Server error:", error);
|
|
556
|
+
process.exit(1);
|
|
557
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yukkit/e2b-mcp-server",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A Model Context Protocol server",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/e2b-dev/mcp-server",
|
|
8
|
+
"directory": "packages/js"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"bin": {
|
|
12
|
+
"e2b-mcp-server": "./build/index.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"build"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
|
19
|
+
"prepare": "npm run build",
|
|
20
|
+
"watch": "tsc --watch",
|
|
21
|
+
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@e2b/code-interpreter": "^1.0.4",
|
|
25
|
+
"@modelcontextprotocol/sdk": "1.25.2",
|
|
26
|
+
"dotenv": "^16.4.5",
|
|
27
|
+
"zod": "^3.25.1",
|
|
28
|
+
"zod-to-json-schema": "^3.24.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^20.11.24",
|
|
32
|
+
"typescript": "^5.3.3"
|
|
33
|
+
}
|
|
34
|
+
}
|