@hasna/sandboxes 0.1.24 → 0.1.25
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/LICENSE +5 -15
- package/README.md +11 -0
- package/dist/mcp/http.d.ts +14 -0
- package/dist/mcp/index.js +826 -724
- package/dist/mcp/server.d.ts +3 -0
- package/dist/server/index.js +31276 -9235
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -842,9 +842,11 @@ var cliSecretResolver = async (key) => {
|
|
|
842
842
|
};
|
|
843
843
|
|
|
844
844
|
// src/mcp/index.ts
|
|
845
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
846
845
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
847
846
|
|
|
847
|
+
// src/mcp/server.ts
|
|
848
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
849
|
+
|
|
848
850
|
// node_modules/@hasna/cloud/dist/index.js
|
|
849
851
|
import { createRequire } from "module";
|
|
850
852
|
import { Database } from "bun:sqlite";
|
|
@@ -16111,7 +16113,7 @@ function getPackageVersion() {
|
|
|
16111
16113
|
return cachedVersion;
|
|
16112
16114
|
}
|
|
16113
16115
|
|
|
16114
|
-
// src/mcp/
|
|
16116
|
+
// src/mcp/server.ts
|
|
16115
16117
|
var E2B_COST_PER_SECOND = 0.000014;
|
|
16116
16118
|
var DAYTONA_COST_PER_SECOND = 0.00001;
|
|
16117
16119
|
function estimateCost(providerName, startedAt) {
|
|
@@ -16173,751 +16175,851 @@ var TOOL_CATALOG = [
|
|
|
16173
16175
|
{ name: "watch_file", description: "Get new content from a file since a previous read (tail -f equivalent)" },
|
|
16174
16176
|
{ name: "list_images", description: "List available pre-warmed sandbox image aliases" }
|
|
16175
16177
|
];
|
|
16176
|
-
var
|
|
16177
|
-
|
|
16178
|
-
|
|
16179
|
-
|
|
16180
|
-
|
|
16181
|
-
|
|
16182
|
-
|
|
16183
|
-
|
|
16184
|
-
|
|
16185
|
-
|
|
16186
|
-
|
|
16187
|
-
|
|
16188
|
-
|
|
16189
|
-
|
|
16190
|
-
|
|
16191
|
-
|
|
16192
|
-
|
|
16193
|
-
|
|
16194
|
-
|
|
16195
|
-
|
|
16196
|
-
|
|
16197
|
-
|
|
16198
|
-
|
|
16199
|
-
|
|
16200
|
-
|
|
16201
|
-
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
|
|
16205
|
-
|
|
16206
|
-
|
|
16207
|
-
|
|
16208
|
-
|
|
16209
|
-
|
|
16210
|
-
|
|
16211
|
-
|
|
16212
|
-
|
|
16213
|
-
|
|
16214
|
-
|
|
16215
|
-
|
|
16216
|
-
|
|
16217
|
-
|
|
16218
|
-
|
|
16219
|
-
|
|
16220
|
-
|
|
16221
|
-
|
|
16222
|
-
|
|
16223
|
-
const provider = await getProvider(providerName);
|
|
16224
|
-
if (params.snapshot_id) {
|
|
16225
|
-
const snapshot = getSnapshot(params.snapshot_id);
|
|
16226
|
-
await provider.resume(snapshot.provider_sandbox_id);
|
|
16227
|
-
const updated2 = updateSandbox(sandbox.id, {
|
|
16228
|
-
provider_sandbox_id: snapshot.provider_sandbox_id,
|
|
16229
|
-
status: "running"
|
|
16178
|
+
var MCP_NAME = "sandboxes";
|
|
16179
|
+
function buildServer() {
|
|
16180
|
+
const server = new McpServer({
|
|
16181
|
+
name: MCP_NAME,
|
|
16182
|
+
version: getPackageVersion()
|
|
16183
|
+
});
|
|
16184
|
+
server.tool("create_sandbox", "Create a new sandbox", {
|
|
16185
|
+
provider: exports_external2.string().optional().describe("Provider name (e2b, daytona, modal)"),
|
|
16186
|
+
image: exports_external2.string().optional().describe("Container image"),
|
|
16187
|
+
timeout: exports_external2.number().optional().describe("Timeout in seconds"),
|
|
16188
|
+
name: exports_external2.string().optional().describe("Sandbox name"),
|
|
16189
|
+
env_vars: exports_external2.record(exports_external2.string()).optional().describe("Environment variables"),
|
|
16190
|
+
template_id: exports_external2.string().optional().describe("Template ID to base this sandbox on"),
|
|
16191
|
+
on_timeout: exports_external2.enum(["pause", "terminate"]).optional().describe("What to do on timeout: pause (saves state) or terminate"),
|
|
16192
|
+
auto_resume: exports_external2.boolean().optional().describe("Auto-resume paused sandbox on next connect"),
|
|
16193
|
+
snapshot_id: exports_external2.string().optional().describe("Snapshot ID to restore from"),
|
|
16194
|
+
network: exports_external2.enum(["full", "restricted", "none"]).optional().describe("Network access policy for the sandbox"),
|
|
16195
|
+
budget_limit_usd: exports_external2.number().optional().describe("Auto-terminate sandbox if compute cost exceeds this USD amount"),
|
|
16196
|
+
on_budget_exceeded: exports_external2.enum(["terminate", "pause", "notify"]).optional().describe("Action when budget limit is reached (default: terminate)")
|
|
16197
|
+
}, async (params) => {
|
|
16198
|
+
let sandboxId;
|
|
16199
|
+
try {
|
|
16200
|
+
const providerName = params.provider ?? getDefaultProvider();
|
|
16201
|
+
const timeout = params.timeout ?? getDefaultTimeout();
|
|
16202
|
+
let templateData = {};
|
|
16203
|
+
if (params.template_id) {
|
|
16204
|
+
const tmpl = getTemplate(params.template_id);
|
|
16205
|
+
templateData = { image: tmpl.image ?? undefined, env_vars: tmpl.env_vars, setup_script: tmpl.setup_script };
|
|
16206
|
+
}
|
|
16207
|
+
const rawImage = params.image ?? templateData.image;
|
|
16208
|
+
const resolvedImage = rawImage ? resolveImage(rawImage) : rawImage;
|
|
16209
|
+
const builtinSetupScript = rawImage ? getBuiltinImageSetupScript(rawImage) : undefined;
|
|
16210
|
+
const envVars = { ...templateData.env_vars, ...params.env_vars };
|
|
16211
|
+
const onTimeout = params.on_timeout ?? "terminate";
|
|
16212
|
+
const autoResume = params.auto_resume ?? false;
|
|
16213
|
+
const sandbox = createSandbox({
|
|
16214
|
+
provider: providerName,
|
|
16215
|
+
image: resolvedImage,
|
|
16216
|
+
timeout,
|
|
16217
|
+
name: params.name,
|
|
16218
|
+
env_vars: envVars,
|
|
16219
|
+
on_timeout: onTimeout,
|
|
16220
|
+
auto_resume: autoResume,
|
|
16221
|
+
template_id: params.template_id,
|
|
16222
|
+
config: { network: params.network ?? "full" },
|
|
16223
|
+
budget_limit_usd: params.budget_limit_usd,
|
|
16224
|
+
on_budget_exceeded: params.on_budget_exceeded
|
|
16230
16225
|
});
|
|
16231
|
-
|
|
16232
|
-
|
|
16233
|
-
|
|
16234
|
-
|
|
16235
|
-
|
|
16236
|
-
|
|
16237
|
-
|
|
16238
|
-
|
|
16239
|
-
|
|
16240
|
-
|
|
16241
|
-
|
|
16242
|
-
|
|
16243
|
-
|
|
16244
|
-
|
|
16245
|
-
|
|
16246
|
-
|
|
16247
|
-
|
|
16248
|
-
|
|
16249
|
-
|
|
16250
|
-
|
|
16226
|
+
sandboxId = sandbox.id;
|
|
16227
|
+
const provider = await getProvider(providerName);
|
|
16228
|
+
if (params.snapshot_id) {
|
|
16229
|
+
const snapshot = getSnapshot(params.snapshot_id);
|
|
16230
|
+
await provider.resume(snapshot.provider_sandbox_id);
|
|
16231
|
+
const updated2 = updateSandbox(sandbox.id, {
|
|
16232
|
+
provider_sandbox_id: snapshot.provider_sandbox_id,
|
|
16233
|
+
status: "running"
|
|
16234
|
+
});
|
|
16235
|
+
emitLifecycleEvent(sandbox.id, `Sandbox restored from snapshot ${snapshot.id}`);
|
|
16236
|
+
return ok(updated2);
|
|
16237
|
+
}
|
|
16238
|
+
const result = await provider.create({
|
|
16239
|
+
image: resolvedImage,
|
|
16240
|
+
timeout,
|
|
16241
|
+
envVars,
|
|
16242
|
+
onTimeout,
|
|
16243
|
+
autoResume
|
|
16244
|
+
});
|
|
16245
|
+
const updated = updateSandbox(sandbox.id, {
|
|
16246
|
+
provider_sandbox_id: result.id,
|
|
16247
|
+
status: "running",
|
|
16248
|
+
started_at: new Date().toISOString()
|
|
16249
|
+
});
|
|
16250
|
+
emitLifecycleEvent(sandbox.id, "sandbox created");
|
|
16251
|
+
if (templateData.setup_script && result.id) {
|
|
16252
|
+
try {
|
|
16253
|
+
await provider.exec(result.id, templateData.setup_script);
|
|
16254
|
+
} catch {}
|
|
16255
|
+
}
|
|
16256
|
+
if (builtinSetupScript && result.id) {
|
|
16257
|
+
try {
|
|
16258
|
+
await provider.exec(result.id, builtinSetupScript);
|
|
16259
|
+
} catch {}
|
|
16260
|
+
}
|
|
16261
|
+
return ok(updated);
|
|
16262
|
+
} catch (e) {
|
|
16263
|
+
if (sandboxId) {
|
|
16264
|
+
finalizeSandboxProvisionFailure(sandboxId, e);
|
|
16265
|
+
}
|
|
16266
|
+
return err(e);
|
|
16251
16267
|
}
|
|
16252
|
-
|
|
16253
|
-
|
|
16254
|
-
|
|
16255
|
-
|
|
16268
|
+
});
|
|
16269
|
+
server.tool("get_sandbox", "Get sandbox details by ID", {
|
|
16270
|
+
id: exports_external2.string().describe("Sandbox ID or partial ID")
|
|
16271
|
+
}, async (params) => {
|
|
16272
|
+
try {
|
|
16273
|
+
const sandbox = getSandbox(params.id);
|
|
16274
|
+
const cost = estimateCost(sandbox.provider, sandbox.started_at);
|
|
16275
|
+
return ok({ ...sandbox, ...cost });
|
|
16276
|
+
} catch (e) {
|
|
16277
|
+
return err(e);
|
|
16256
16278
|
}
|
|
16257
|
-
|
|
16258
|
-
|
|
16259
|
-
|
|
16260
|
-
|
|
16279
|
+
});
|
|
16280
|
+
server.tool("list_sandboxes", "List sandboxes with filters", {
|
|
16281
|
+
status: exports_external2.string().optional().describe("Filter by status"),
|
|
16282
|
+
provider: exports_external2.string().optional().describe("Filter by provider")
|
|
16283
|
+
}, async (params) => {
|
|
16284
|
+
try {
|
|
16285
|
+
const sandboxes = listSandboxes({
|
|
16286
|
+
status: params.status,
|
|
16287
|
+
provider: params.provider
|
|
16288
|
+
});
|
|
16289
|
+
return ok(sandboxes.map((s) => ({ ...s, ...estimateCost(s.provider, s.started_at) })));
|
|
16290
|
+
} catch (e) {
|
|
16291
|
+
return err(e);
|
|
16261
16292
|
}
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
16268
|
-
|
|
16269
|
-
|
|
16270
|
-
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
|
|
16274
|
-
|
|
16275
|
-
})
|
|
16276
|
-
|
|
16277
|
-
|
|
16278
|
-
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16284
|
-
|
|
16285
|
-
|
|
16286
|
-
} catch (e) {
|
|
16287
|
-
return err(e);
|
|
16288
|
-
}
|
|
16289
|
-
});
|
|
16290
|
-
server.tool("delete_sandbox", "Delete a sandbox", {
|
|
16291
|
-
id: exports_external2.string().describe("Sandbox ID or partial ID")
|
|
16292
|
-
}, async (params) => {
|
|
16293
|
-
try {
|
|
16294
|
-
const sandbox = getSandbox(params.id);
|
|
16295
|
-
if (sandbox.provider_sandbox_id) {
|
|
16293
|
+
});
|
|
16294
|
+
server.tool("delete_sandbox", "Delete a sandbox", {
|
|
16295
|
+
id: exports_external2.string().describe("Sandbox ID or partial ID")
|
|
16296
|
+
}, async (params) => {
|
|
16297
|
+
try {
|
|
16298
|
+
const sandbox = getSandbox(params.id);
|
|
16299
|
+
if (sandbox.provider_sandbox_id) {
|
|
16300
|
+
const provider = await getProvider(sandbox.provider);
|
|
16301
|
+
await provider.delete(sandbox.provider_sandbox_id);
|
|
16302
|
+
}
|
|
16303
|
+
deleteSandbox(sandbox.id);
|
|
16304
|
+
emitLifecycleEvent(sandbox.id, "sandbox deleted");
|
|
16305
|
+
return ok({ deleted: sandbox.id });
|
|
16306
|
+
} catch (e) {
|
|
16307
|
+
return err(e);
|
|
16308
|
+
}
|
|
16309
|
+
});
|
|
16310
|
+
server.tool("stop_sandbox", "Stop a running sandbox", {
|
|
16311
|
+
id: exports_external2.string().describe("Sandbox ID or partial ID")
|
|
16312
|
+
}, async (params) => {
|
|
16313
|
+
try {
|
|
16314
|
+
const sandbox = getSandbox(params.id);
|
|
16315
|
+
if (!sandbox.provider_sandbox_id)
|
|
16316
|
+
throw new Error("Sandbox has no provider ID");
|
|
16296
16317
|
const provider = await getProvider(sandbox.provider);
|
|
16297
|
-
await provider.
|
|
16318
|
+
await provider.stop(sandbox.provider_sandbox_id);
|
|
16319
|
+
const updated = updateSandbox(sandbox.id, { status: "stopped" });
|
|
16320
|
+
emitLifecycleEvent(sandbox.id, "sandbox stopped");
|
|
16321
|
+
return ok(updated);
|
|
16322
|
+
} catch (e) {
|
|
16323
|
+
return err(e);
|
|
16298
16324
|
}
|
|
16299
|
-
|
|
16300
|
-
|
|
16301
|
-
|
|
16302
|
-
|
|
16303
|
-
|
|
16304
|
-
|
|
16305
|
-
|
|
16306
|
-
|
|
16307
|
-
|
|
16308
|
-
|
|
16309
|
-
|
|
16310
|
-
|
|
16311
|
-
|
|
16312
|
-
|
|
16313
|
-
|
|
16314
|
-
|
|
16315
|
-
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
16321
|
-
|
|
16322
|
-
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16336
|
-
|
|
16337
|
-
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
|
|
16345
|
-
|
|
16346
|
-
|
|
16347
|
-
|
|
16348
|
-
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
|
|
16354
|
-
|
|
16355
|
-
|
|
16356
|
-
|
|
16357
|
-
|
|
16358
|
-
|
|
16359
|
-
|
|
16360
|
-
const needsShell = /<<\s*['"]?[A-Z]+['"]?/.test(params.command);
|
|
16361
|
-
const effectiveCommand = needsShell ? `bash -c ${JSON.stringify(params.command)}` : params.command;
|
|
16362
|
-
if (params.background) {
|
|
16363
|
-
provider.exec(sandbox.provider_sandbox_id, effectiveCommand, {
|
|
16325
|
+
});
|
|
16326
|
+
server.tool("keep_alive", "Extend sandbox lifetime", {
|
|
16327
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
|
|
16328
|
+
duration_seconds: exports_external2.number().optional().describe("Duration in seconds (default 300)")
|
|
16329
|
+
}, async (params) => {
|
|
16330
|
+
try {
|
|
16331
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
16332
|
+
if (!sandbox.provider_sandbox_id)
|
|
16333
|
+
throw new Error("Sandbox has no provider ID");
|
|
16334
|
+
const provider = await getProvider(sandbox.provider);
|
|
16335
|
+
const durationMs = (params.duration_seconds ?? 300) * 1000;
|
|
16336
|
+
await provider.keepAlive(sandbox.provider_sandbox_id, durationMs);
|
|
16337
|
+
return ok({ kept_alive: sandbox.id, duration_seconds: params.duration_seconds ?? 300 });
|
|
16338
|
+
} catch (e) {
|
|
16339
|
+
return err(e);
|
|
16340
|
+
}
|
|
16341
|
+
});
|
|
16342
|
+
server.tool("exec_command", "Execute a command in a sandbox", {
|
|
16343
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
|
|
16344
|
+
command: exports_external2.string().describe("Command to execute"),
|
|
16345
|
+
background: exports_external2.boolean().optional().describe("Run in background"),
|
|
16346
|
+
env_vars: exports_external2.record(exports_external2.string()).optional().describe("Per-call environment variables (merged with sandbox env_vars, not persisted)"),
|
|
16347
|
+
stdin: exports_external2.string().optional().describe("String to pipe as stdin to the command"),
|
|
16348
|
+
tty: exports_external2.boolean().optional().describe("Allocate a TTY for the session (best-effort)")
|
|
16349
|
+
}, async (params) => {
|
|
16350
|
+
let sessionId;
|
|
16351
|
+
try {
|
|
16352
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
16353
|
+
if (!sandbox.provider_sandbox_id)
|
|
16354
|
+
throw new Error("Sandbox has no provider ID");
|
|
16355
|
+
const session = createSession({
|
|
16356
|
+
sandbox_id: sandbox.id,
|
|
16357
|
+
command: params.command
|
|
16358
|
+
});
|
|
16359
|
+
sessionId = session.id;
|
|
16360
|
+
const collector = createStreamCollector(sandbox.id, session.id);
|
|
16361
|
+
const provider = await getProvider(sandbox.provider);
|
|
16362
|
+
const callEnv = { ...sandbox.env_vars, ...params.env_vars };
|
|
16363
|
+
const env = Object.keys(callEnv).length > 0 ? callEnv : undefined;
|
|
16364
|
+
const needsShell = /<<\s*['"]?[A-Z]+['"]?/.test(params.command);
|
|
16365
|
+
const effectiveCommand = needsShell ? `bash -c ${JSON.stringify(params.command)}` : params.command;
|
|
16366
|
+
if (params.background) {
|
|
16367
|
+
provider.exec(sandbox.provider_sandbox_id, effectiveCommand, {
|
|
16368
|
+
onStdout: collector.onStdout,
|
|
16369
|
+
onStderr: collector.onStderr,
|
|
16370
|
+
env,
|
|
16371
|
+
stdin: params.stdin,
|
|
16372
|
+
tty: params.tty
|
|
16373
|
+
}).then((res) => {
|
|
16374
|
+
const r = res;
|
|
16375
|
+
finalizeSessionExit(session.id, r.exit_code ?? 0);
|
|
16376
|
+
}).catch(() => {
|
|
16377
|
+
finalizeSessionFailure(session.id);
|
|
16378
|
+
});
|
|
16379
|
+
return ok({
|
|
16380
|
+
session_id: session.id,
|
|
16381
|
+
background: true,
|
|
16382
|
+
message: "Command started in background. Use get_session to check completion status and exit_code. Use bg_wait_session to block until done."
|
|
16383
|
+
});
|
|
16384
|
+
}
|
|
16385
|
+
const result = await provider.exec(sandbox.provider_sandbox_id, effectiveCommand, {
|
|
16364
16386
|
onStdout: collector.onStdout,
|
|
16365
16387
|
onStderr: collector.onStderr,
|
|
16366
16388
|
env,
|
|
16367
16389
|
stdin: params.stdin,
|
|
16368
16390
|
tty: params.tty
|
|
16369
|
-
}).then((res) => {
|
|
16370
|
-
const r = res;
|
|
16371
|
-
finalizeSessionExit(session.id, r.exit_code ?? 0);
|
|
16372
|
-
}).catch(() => {
|
|
16373
|
-
finalizeSessionFailure(session.id);
|
|
16374
16391
|
});
|
|
16392
|
+
const execResult = result;
|
|
16393
|
+
finalizeSessionExit(session.id, execResult.exit_code);
|
|
16375
16394
|
return ok({
|
|
16376
16395
|
session_id: session.id,
|
|
16377
|
-
|
|
16378
|
-
|
|
16396
|
+
exit_code: execResult.exit_code,
|
|
16397
|
+
stdout: execResult.stdout,
|
|
16398
|
+
stderr: execResult.stderr
|
|
16379
16399
|
});
|
|
16400
|
+
} catch (e) {
|
|
16401
|
+
if (sessionId) {
|
|
16402
|
+
finalizeSessionFailure(sessionId, e);
|
|
16403
|
+
}
|
|
16404
|
+
return err(e);
|
|
16380
16405
|
}
|
|
16381
|
-
|
|
16382
|
-
|
|
16383
|
-
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
|
|
16406
|
+
});
|
|
16407
|
+
server.tool("read_file", "Read a file from a sandbox", {
|
|
16408
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
|
|
16409
|
+
path: exports_external2.string().describe("File path"),
|
|
16410
|
+
offset: exports_external2.number().optional().describe("Line or byte offset to start reading from"),
|
|
16411
|
+
limit: exports_external2.number().optional().describe("Max lines or bytes to return"),
|
|
16412
|
+
encoding: exports_external2.enum(["utf8", "base64", "hex"]).optional().describe("Output encoding (default: utf8)")
|
|
16413
|
+
}, async (params) => {
|
|
16414
|
+
try {
|
|
16415
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
16416
|
+
if (!sandbox.provider_sandbox_id)
|
|
16417
|
+
throw new Error("Sandbox has no provider ID");
|
|
16418
|
+
const provider = await getProvider(sandbox.provider);
|
|
16419
|
+
const content = await provider.readFile(sandbox.provider_sandbox_id, params.path, {
|
|
16420
|
+
encoding: params.encoding,
|
|
16421
|
+
offset: params.offset,
|
|
16422
|
+
limit: params.limit
|
|
16423
|
+
});
|
|
16424
|
+
return ok({ path: params.path, content, encoding: params.encoding ?? "utf8" });
|
|
16425
|
+
} catch (e) {
|
|
16426
|
+
return err(e);
|
|
16399
16427
|
}
|
|
16400
|
-
|
|
16401
|
-
|
|
16402
|
-
|
|
16403
|
-
|
|
16404
|
-
|
|
16405
|
-
|
|
16406
|
-
|
|
16407
|
-
|
|
16408
|
-
|
|
16409
|
-
|
|
16410
|
-
|
|
16411
|
-
|
|
16412
|
-
|
|
16413
|
-
|
|
16414
|
-
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
|
|
16418
|
-
|
|
16419
|
-
|
|
16420
|
-
|
|
16421
|
-
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
|
|
16425
|
-
|
|
16426
|
-
|
|
16427
|
-
|
|
16428
|
-
|
|
16429
|
-
|
|
16430
|
-
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
|
|
16435
|
-
|
|
16436
|
-
|
|
16437
|
-
|
|
16438
|
-
|
|
16439
|
-
|
|
16440
|
-
|
|
16441
|
-
|
|
16442
|
-
|
|
16443
|
-
|
|
16444
|
-
|
|
16445
|
-
|
|
16446
|
-
|
|
16447
|
-
|
|
16448
|
-
|
|
16449
|
-
|
|
16450
|
-
|
|
16451
|
-
|
|
16452
|
-
|
|
16453
|
-
|
|
16454
|
-
|
|
16455
|
-
|
|
16456
|
-
|
|
16457
|
-
}
|
|
16458
|
-
|
|
16459
|
-
|
|
16460
|
-
})
|
|
16461
|
-
|
|
16462
|
-
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
|
|
16463
|
-
local_dir: exports_external2.string().describe("Local directory path on the host to upload"),
|
|
16464
|
-
remote_dir: exports_external2.string().describe("Destination directory inside the sandbox"),
|
|
16465
|
-
exclude: exports_external2.array(exports_external2.string()).optional().describe("Patterns to exclude (defaults to node_modules, .git, dist, \u2026)")
|
|
16466
|
-
}, async (params) => {
|
|
16467
|
-
try {
|
|
16468
|
-
const sandbox = getSandbox(params.sandbox_id);
|
|
16469
|
-
if (!sandbox.provider_sandbox_id)
|
|
16470
|
-
throw new Error("Sandbox has no provider ID");
|
|
16471
|
-
const provider = await getProvider(sandbox.provider);
|
|
16472
|
-
const result = await provider.uploadDir(sandbox.provider_sandbox_id, params.local_dir, params.remote_dir, params.exclude ? { exclude: params.exclude } : undefined);
|
|
16473
|
-
return ok({
|
|
16474
|
-
local_dir: params.local_dir,
|
|
16475
|
-
remote_dir: params.remote_dir,
|
|
16476
|
-
bytes: result.bytes
|
|
16477
|
-
});
|
|
16478
|
-
} catch (e) {
|
|
16479
|
-
return err(e);
|
|
16480
|
-
}
|
|
16481
|
-
});
|
|
16482
|
-
server.tool("get_session", "Get session details and exit code (useful for polling background command results)", {
|
|
16483
|
-
session_id: exports_external2.string().describe("Session ID")
|
|
16484
|
-
}, async (params) => {
|
|
16485
|
-
try {
|
|
16486
|
-
const session = getSession(params.session_id);
|
|
16487
|
-
return ok(session);
|
|
16488
|
-
} catch (e) {
|
|
16489
|
-
return err(e);
|
|
16490
|
-
}
|
|
16491
|
-
});
|
|
16492
|
-
server.tool("bg_wait_session", "Wait (poll) for a background command session to complete. Returns exit_code, stdout, stderr when done. Use after exec_command with background:true.", {
|
|
16493
|
-
session_id: exports_external2.string().describe("Session ID from exec_command background:true response"),
|
|
16494
|
-
timeout_seconds: exports_external2.number().optional().describe("Max seconds to wait (default: 300)"),
|
|
16495
|
-
poll_interval_ms: exports_external2.number().optional().describe("Poll interval in ms (default: 1000)")
|
|
16496
|
-
}, async (params) => {
|
|
16497
|
-
try {
|
|
16498
|
-
const timeoutMs = (params.timeout_seconds ?? 300) * 1000;
|
|
16499
|
-
const pollMs = params.poll_interval_ms ?? 1000;
|
|
16500
|
-
const deadline = Date.now() + timeoutMs;
|
|
16501
|
-
while (Date.now() < deadline) {
|
|
16428
|
+
});
|
|
16429
|
+
server.tool("write_file", "Write a file to a sandbox", {
|
|
16430
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
|
|
16431
|
+
path: exports_external2.string().describe("File path"),
|
|
16432
|
+
content: exports_external2.string().describe("File content")
|
|
16433
|
+
}, async (params) => {
|
|
16434
|
+
try {
|
|
16435
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
16436
|
+
if (!sandbox.provider_sandbox_id)
|
|
16437
|
+
throw new Error("Sandbox has no provider ID");
|
|
16438
|
+
const provider = await getProvider(sandbox.provider);
|
|
16439
|
+
await provider.writeFile(sandbox.provider_sandbox_id, params.path, params.content);
|
|
16440
|
+
return ok({ path: params.path, written: true });
|
|
16441
|
+
} catch (e) {
|
|
16442
|
+
return err(e);
|
|
16443
|
+
}
|
|
16444
|
+
});
|
|
16445
|
+
server.tool("list_files", "List files in a sandbox directory", {
|
|
16446
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
|
|
16447
|
+
path: exports_external2.string().describe("Directory path"),
|
|
16448
|
+
recursive: exports_external2.boolean().optional().describe("List files recursively"),
|
|
16449
|
+
glob: exports_external2.string().optional().describe("Glob pattern to filter files")
|
|
16450
|
+
}, async (params) => {
|
|
16451
|
+
try {
|
|
16452
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
16453
|
+
if (!sandbox.provider_sandbox_id)
|
|
16454
|
+
throw new Error("Sandbox has no provider ID");
|
|
16455
|
+
const provider = await getProvider(sandbox.provider);
|
|
16456
|
+
const files = await provider.listFiles(sandbox.provider_sandbox_id, params.path, {
|
|
16457
|
+
recursive: params.recursive,
|
|
16458
|
+
glob: params.glob
|
|
16459
|
+
});
|
|
16460
|
+
return ok(files);
|
|
16461
|
+
} catch (e) {
|
|
16462
|
+
return err(e);
|
|
16463
|
+
}
|
|
16464
|
+
});
|
|
16465
|
+
server.tool("upload_dir", "Upload a local directory into a sandbox as a single archive (fast, no git clone)", {
|
|
16466
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
|
|
16467
|
+
local_dir: exports_external2.string().describe("Local directory path on the host to upload"),
|
|
16468
|
+
remote_dir: exports_external2.string().describe("Destination directory inside the sandbox"),
|
|
16469
|
+
exclude: exports_external2.array(exports_external2.string()).optional().describe("Patterns to exclude (defaults to node_modules, .git, dist, \u2026)")
|
|
16470
|
+
}, async (params) => {
|
|
16471
|
+
try {
|
|
16472
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
16473
|
+
if (!sandbox.provider_sandbox_id)
|
|
16474
|
+
throw new Error("Sandbox has no provider ID");
|
|
16475
|
+
const provider = await getProvider(sandbox.provider);
|
|
16476
|
+
const result = await provider.uploadDir(sandbox.provider_sandbox_id, params.local_dir, params.remote_dir, params.exclude ? { exclude: params.exclude } : undefined);
|
|
16477
|
+
return ok({
|
|
16478
|
+
local_dir: params.local_dir,
|
|
16479
|
+
remote_dir: params.remote_dir,
|
|
16480
|
+
bytes: result.bytes
|
|
16481
|
+
});
|
|
16482
|
+
} catch (e) {
|
|
16483
|
+
return err(e);
|
|
16484
|
+
}
|
|
16485
|
+
});
|
|
16486
|
+
server.tool("get_session", "Get session details and exit code (useful for polling background command results)", {
|
|
16487
|
+
session_id: exports_external2.string().describe("Session ID")
|
|
16488
|
+
}, async (params) => {
|
|
16489
|
+
try {
|
|
16502
16490
|
const session = getSession(params.session_id);
|
|
16503
|
-
|
|
16504
|
-
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
|
|
16491
|
+
return ok(session);
|
|
16492
|
+
} catch (e) {
|
|
16493
|
+
return err(e);
|
|
16494
|
+
}
|
|
16495
|
+
});
|
|
16496
|
+
server.tool("bg_wait_session", "Wait (poll) for a background command session to complete. Returns exit_code, stdout, stderr when done. Use after exec_command with background:true.", {
|
|
16497
|
+
session_id: exports_external2.string().describe("Session ID from exec_command background:true response"),
|
|
16498
|
+
timeout_seconds: exports_external2.number().optional().describe("Max seconds to wait (default: 300)"),
|
|
16499
|
+
poll_interval_ms: exports_external2.number().optional().describe("Poll interval in ms (default: 1000)")
|
|
16500
|
+
}, async (params) => {
|
|
16501
|
+
try {
|
|
16502
|
+
const timeoutMs = (params.timeout_seconds ?? 300) * 1000;
|
|
16503
|
+
const pollMs = params.poll_interval_ms ?? 1000;
|
|
16504
|
+
const deadline = Date.now() + timeoutMs;
|
|
16505
|
+
while (Date.now() < deadline) {
|
|
16506
|
+
const session = getSession(params.session_id);
|
|
16507
|
+
if (session.status === "completed" || session.status === "failed" || session.status === "killed") {
|
|
16508
|
+
const events = listEvents({ session_id: session.id, limit: 1e4 });
|
|
16509
|
+
const stdout = events.filter((e) => e.type === "stdout").map((e) => e.data).join("");
|
|
16510
|
+
const stderr = events.filter((e) => e.type === "stderr").map((e) => e.data).join("");
|
|
16511
|
+
return ok({
|
|
16512
|
+
session_id: session.id,
|
|
16513
|
+
status: session.status,
|
|
16514
|
+
exit_code: session.exit_code ?? (session.status === "failed" || session.status === "killed" ? 1 : 0),
|
|
16515
|
+
stdout,
|
|
16516
|
+
stderr
|
|
16517
|
+
});
|
|
16518
|
+
}
|
|
16519
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
16514
16520
|
}
|
|
16515
|
-
|
|
16521
|
+
return err(`Session ${params.session_id} did not complete within ${params.timeout_seconds ?? 300}s`);
|
|
16522
|
+
} catch (e) {
|
|
16523
|
+
return err(e);
|
|
16516
16524
|
}
|
|
16517
|
-
|
|
16518
|
-
|
|
16519
|
-
|
|
16520
|
-
|
|
16521
|
-
|
|
16522
|
-
|
|
16523
|
-
|
|
16524
|
-
|
|
16525
|
-
|
|
16526
|
-
|
|
16527
|
-
|
|
16528
|
-
|
|
16529
|
-
|
|
16530
|
-
|
|
16531
|
-
|
|
16532
|
-
|
|
16533
|
-
|
|
16534
|
-
|
|
16535
|
-
|
|
16536
|
-
})
|
|
16537
|
-
|
|
16538
|
-
|
|
16539
|
-
|
|
16540
|
-
|
|
16541
|
-
|
|
16542
|
-
|
|
16543
|
-
}
|
|
16544
|
-
|
|
16545
|
-
|
|
16546
|
-
})
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
|
|
16550
|
-
}
|
|
16551
|
-
|
|
16552
|
-
|
|
16553
|
-
})
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16560
|
-
})
|
|
16561
|
-
|
|
16562
|
-
|
|
16563
|
-
|
|
16564
|
-
|
|
16565
|
-
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
}
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
})
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
|
|
16585
|
-
}
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
})
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
}
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
}
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
const
|
|
16622
|
-
|
|
16623
|
-
|
|
16624
|
-
|
|
16625
|
-
|
|
16626
|
-
|
|
16627
|
-
|
|
16628
|
-
|
|
16629
|
-
|
|
16630
|
-
|
|
16631
|
-
|
|
16632
|
-
|
|
16633
|
-
}
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
}
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
|
|
16641
|
-
}
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
16648
|
-
|
|
16649
|
-
|
|
16650
|
-
|
|
16651
|
-
|
|
16652
|
-
|
|
16653
|
-
|
|
16654
|
-
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
|
|
16659
|
-
|
|
16660
|
-
|
|
16661
|
-
})
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
|
|
16667
|
-
}
|
|
16668
|
-
|
|
16669
|
-
|
|
16670
|
-
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
|
|
16674
|
-
|
|
16675
|
-
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16682
|
-
|
|
16683
|
-
}
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
16695
|
-
|
|
16696
|
-
|
|
16697
|
-
|
|
16698
|
-
|
|
16699
|
-
|
|
16700
|
-
|
|
16701
|
-
|
|
16702
|
-
|
|
16703
|
-
|
|
16704
|
-
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
|
|
16710
|
-
|
|
16711
|
-
}
|
|
16712
|
-
|
|
16713
|
-
|
|
16714
|
-
})
|
|
16715
|
-
|
|
16716
|
-
|
|
16717
|
-
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
}
|
|
16721
|
-
|
|
16722
|
-
|
|
16723
|
-
|
|
16724
|
-
|
|
16725
|
-
|
|
16726
|
-
|
|
16727
|
-
|
|
16728
|
-
|
|
16729
|
-
}
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
}
|
|
16734
|
-
|
|
16735
|
-
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
|
|
16739
|
-
}
|
|
16740
|
-
|
|
16741
|
-
|
|
16742
|
-
|
|
16743
|
-
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
16751
|
-
provider.exec(sandbox.provider_sandbox_id, "df -h / 2>/dev/null || df -h 2>/dev/null | head -5"),
|
|
16752
|
-
provider.exec(sandbox.provider_sandbox_id, "uptime 2>/dev/null || echo unknown")
|
|
16753
|
-
]);
|
|
16754
|
-
const processes = (psResult.stdout || "").trim().split(`
|
|
16525
|
+
});
|
|
16526
|
+
server.tool("get_logs", "Get sandbox/session event logs", {
|
|
16527
|
+
sandbox_id: exports_external2.string().optional().describe("Filter by sandbox ID"),
|
|
16528
|
+
session_id: exports_external2.string().optional().describe("Filter by session ID"),
|
|
16529
|
+
limit: exports_external2.number().optional().describe("Max events to return")
|
|
16530
|
+
}, async (params) => {
|
|
16531
|
+
try {
|
|
16532
|
+
return ok(listEvents({
|
|
16533
|
+
sandbox_id: params.sandbox_id,
|
|
16534
|
+
session_id: params.session_id,
|
|
16535
|
+
limit: params.limit
|
|
16536
|
+
}));
|
|
16537
|
+
} catch (e) {
|
|
16538
|
+
return err(e);
|
|
16539
|
+
}
|
|
16540
|
+
});
|
|
16541
|
+
server.tool("register_agent", "Register an agent", {
|
|
16542
|
+
name: exports_external2.string().describe("Agent name"),
|
|
16543
|
+
description: exports_external2.string().optional().describe("Agent description")
|
|
16544
|
+
}, async (params) => {
|
|
16545
|
+
try {
|
|
16546
|
+
return ok(registerAgent({ name: params.name, description: params.description }));
|
|
16547
|
+
} catch (e) {
|
|
16548
|
+
return err(e);
|
|
16549
|
+
}
|
|
16550
|
+
});
|
|
16551
|
+
server.tool("list_agents", "List all registered agents", {}, async () => {
|
|
16552
|
+
try {
|
|
16553
|
+
return ok(listAgents());
|
|
16554
|
+
} catch (e) {
|
|
16555
|
+
return err(e);
|
|
16556
|
+
}
|
|
16557
|
+
});
|
|
16558
|
+
server.tool("heartbeat", "Update last_seen_at to signal agent is active. Call periodically during long tasks.", { agent_id: exports_external2.string().describe("Agent ID or name") }, async (params) => {
|
|
16559
|
+
try {
|
|
16560
|
+
return ok(heartbeatAgent(params.agent_id));
|
|
16561
|
+
} catch (e) {
|
|
16562
|
+
return err(e);
|
|
16563
|
+
}
|
|
16564
|
+
});
|
|
16565
|
+
server.tool("set_focus", "Set active project context for this agent session.", {
|
|
16566
|
+
agent_id: exports_external2.string().describe("Agent ID or name"),
|
|
16567
|
+
project_id: exports_external2.string().nullable().optional().describe("Project ID to focus on, or null to clear")
|
|
16568
|
+
}, async (params) => {
|
|
16569
|
+
try {
|
|
16570
|
+
return ok(setAgentFocus(params.agent_id, params.project_id ?? null));
|
|
16571
|
+
} catch (e) {
|
|
16572
|
+
return err(e);
|
|
16573
|
+
}
|
|
16574
|
+
});
|
|
16575
|
+
server.tool("register_project", "Register a project", {
|
|
16576
|
+
name: exports_external2.string().describe("Project name"),
|
|
16577
|
+
path: exports_external2.string().describe("Project path"),
|
|
16578
|
+
description: exports_external2.string().optional().describe("Project description")
|
|
16579
|
+
}, async (params) => {
|
|
16580
|
+
try {
|
|
16581
|
+
return ok(ensureProject(params.name, params.path, params.description));
|
|
16582
|
+
} catch (e) {
|
|
16583
|
+
return err(e);
|
|
16584
|
+
}
|
|
16585
|
+
});
|
|
16586
|
+
server.tool("list_projects", "List all projects", {}, async () => {
|
|
16587
|
+
try {
|
|
16588
|
+
return ok(listProjects());
|
|
16589
|
+
} catch (e) {
|
|
16590
|
+
return err(e);
|
|
16591
|
+
}
|
|
16592
|
+
});
|
|
16593
|
+
server.tool("describe_tools", "List all available tools", {}, async () => {
|
|
16594
|
+
try {
|
|
16595
|
+
return ok(TOOL_CATALOG);
|
|
16596
|
+
} catch (e) {
|
|
16597
|
+
return err(e);
|
|
16598
|
+
}
|
|
16599
|
+
});
|
|
16600
|
+
server.tool("search_tools", "Search tools by keyword", {
|
|
16601
|
+
query: exports_external2.string().describe("Search query")
|
|
16602
|
+
}, async (params) => {
|
|
16603
|
+
try {
|
|
16604
|
+
const q = params.query.toLowerCase();
|
|
16605
|
+
const matches = TOOL_CATALOG.filter((t) => t.name.includes(q) || t.description.toLowerCase().includes(q));
|
|
16606
|
+
return ok(matches);
|
|
16607
|
+
} catch (e) {
|
|
16608
|
+
return err(e);
|
|
16609
|
+
}
|
|
16610
|
+
});
|
|
16611
|
+
server.tool("run_agent", "Run an AI agent inside a sandbox", {
|
|
16612
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID"),
|
|
16613
|
+
agent_type: exports_external2.enum(["claude", "codex", "gemini", "opencode", "pi", "takumi", "custom"]).describe("Agent type"),
|
|
16614
|
+
prompt: exports_external2.string().describe("Prompt for the agent"),
|
|
16615
|
+
agent_name: exports_external2.string().optional().describe("Agent name"),
|
|
16616
|
+
command: exports_external2.string().optional().describe("Custom command (for 'custom' type)"),
|
|
16617
|
+
env_vars: exports_external2.record(exports_external2.string()).optional().describe("Per-call environment variables (merged with sandbox env_vars, not persisted)"),
|
|
16618
|
+
secrets: exports_external2.array(exports_external2.string()).optional().describe("Vault secrets to inject as env vars: array of 'ENV_NAME=vault/key' (resolved from @hasna/secrets at call time, never persisted)"),
|
|
16619
|
+
webhook_url: exports_external2.string().optional().describe("URL to POST result to when agent finishes"),
|
|
16620
|
+
webhook_events: exports_external2.array(exports_external2.enum(["start", "complete", "error"])).optional().describe("Which events to notify on (default: all)")
|
|
16621
|
+
}, async (params) => {
|
|
16622
|
+
try {
|
|
16623
|
+
let callEnvVars = params.env_vars;
|
|
16624
|
+
if (params.secrets && params.secrets.length > 0) {
|
|
16625
|
+
const { resolveSecretSpecs: resolveSecretSpecs2 } = await Promise.resolve().then(() => exports_secrets);
|
|
16626
|
+
const secretEnv = await resolveSecretSpecs2(params.secrets);
|
|
16627
|
+
callEnvVars = { ...secretEnv, ...params.env_vars };
|
|
16628
|
+
}
|
|
16629
|
+
const session = await runAgent(params.sandbox_id, {
|
|
16630
|
+
agentType: params.agent_type,
|
|
16631
|
+
prompt: params.prompt,
|
|
16632
|
+
agentName: params.agent_name,
|
|
16633
|
+
command: params.command,
|
|
16634
|
+
callEnvVars,
|
|
16635
|
+
webhookUrl: params.webhook_url,
|
|
16636
|
+
webhookEvents: params.webhook_events
|
|
16637
|
+
});
|
|
16638
|
+
return ok({ session_id: session.id, status: session.status });
|
|
16639
|
+
} catch (e) {
|
|
16640
|
+
return err(e);
|
|
16641
|
+
}
|
|
16642
|
+
});
|
|
16643
|
+
server.tool("stop_agent", "Stop a running agent in a sandbox", {
|
|
16644
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID")
|
|
16645
|
+
}, async (params) => {
|
|
16646
|
+
try {
|
|
16647
|
+
await stopAgent(params.sandbox_id);
|
|
16648
|
+
return ok({ stopped: true });
|
|
16649
|
+
} catch (e) {
|
|
16650
|
+
return err(e);
|
|
16651
|
+
}
|
|
16652
|
+
});
|
|
16653
|
+
server.tool("get_agent_output", "Get output from an agent session", {
|
|
16654
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID"),
|
|
16655
|
+
session_id: exports_external2.string().optional().describe("Session ID"),
|
|
16656
|
+
limit: exports_external2.number().optional().describe("Max events"),
|
|
16657
|
+
offset: exports_external2.number().optional().describe("Skip first N events (for incremental polling)")
|
|
16658
|
+
}, async (params) => {
|
|
16659
|
+
try {
|
|
16660
|
+
const events = listEvents({
|
|
16661
|
+
sandbox_id: params.sandbox_id,
|
|
16662
|
+
session_id: params.session_id,
|
|
16663
|
+
limit: params.limit || 100,
|
|
16664
|
+
offset: params.offset
|
|
16665
|
+
});
|
|
16666
|
+
const stdout = events.filter((e) => e.type === "stdout").map((e) => e.data).join("");
|
|
16667
|
+
const stderr = events.filter((e) => e.type === "stderr").map((e) => e.data).join("");
|
|
16668
|
+
return ok({ stdout, stderr, event_count: events.length, next_offset: (params.offset ?? 0) + events.length });
|
|
16669
|
+
} catch (e) {
|
|
16670
|
+
return err(e);
|
|
16671
|
+
}
|
|
16672
|
+
});
|
|
16673
|
+
server.tool("pause_sandbox", "Pause a running sandbox, saving its state for later resume", {
|
|
16674
|
+
id: exports_external2.string().describe("Sandbox ID or partial ID")
|
|
16675
|
+
}, async (params) => {
|
|
16676
|
+
try {
|
|
16677
|
+
const sandbox = getSandbox(params.id);
|
|
16678
|
+
if (!sandbox.provider_sandbox_id)
|
|
16679
|
+
throw new Error("Sandbox has no provider ID");
|
|
16680
|
+
const provider = await getProvider(sandbox.provider);
|
|
16681
|
+
await provider.pause(sandbox.provider_sandbox_id);
|
|
16682
|
+
const updated = updateSandbox(sandbox.id, { status: "paused" });
|
|
16683
|
+
emitLifecycleEvent(sandbox.id, "sandbox paused");
|
|
16684
|
+
return ok(updated);
|
|
16685
|
+
} catch (e) {
|
|
16686
|
+
return err(e);
|
|
16687
|
+
}
|
|
16688
|
+
});
|
|
16689
|
+
server.tool("resume_sandbox", "Resume a paused sandbox", {
|
|
16690
|
+
id: exports_external2.string().describe("Sandbox ID or partial ID")
|
|
16691
|
+
}, async (params) => {
|
|
16692
|
+
try {
|
|
16693
|
+
const sandbox = getSandbox(params.id);
|
|
16694
|
+
if (!sandbox.provider_sandbox_id)
|
|
16695
|
+
throw new Error("Sandbox has no provider ID");
|
|
16696
|
+
const provider = await getProvider(sandbox.provider);
|
|
16697
|
+
await provider.resume(sandbox.provider_sandbox_id);
|
|
16698
|
+
const updated = updateSandbox(sandbox.id, { status: "running" });
|
|
16699
|
+
emitLifecycleEvent(sandbox.id, "sandbox resumed");
|
|
16700
|
+
return ok(updated);
|
|
16701
|
+
} catch (e) {
|
|
16702
|
+
return err(e);
|
|
16703
|
+
}
|
|
16704
|
+
});
|
|
16705
|
+
server.tool("create_template", "Create a reusable sandbox template", {
|
|
16706
|
+
name: exports_external2.string().describe("Template name"),
|
|
16707
|
+
description: exports_external2.string().optional(),
|
|
16708
|
+
image: exports_external2.string().optional().describe("Container image"),
|
|
16709
|
+
env_vars: exports_external2.record(exports_external2.string()).optional().describe("Environment variables"),
|
|
16710
|
+
setup_script: exports_external2.string().optional().describe("Shell script to run on sandbox creation"),
|
|
16711
|
+
tags: exports_external2.array(exports_external2.string()).optional()
|
|
16712
|
+
}, async (params) => {
|
|
16713
|
+
try {
|
|
16714
|
+
return ok(createTemplate(params));
|
|
16715
|
+
} catch (e) {
|
|
16716
|
+
return err(e);
|
|
16717
|
+
}
|
|
16718
|
+
});
|
|
16719
|
+
server.tool("list_templates", "List all sandbox templates", {}, async () => {
|
|
16720
|
+
try {
|
|
16721
|
+
return ok(listTemplates());
|
|
16722
|
+
} catch (e) {
|
|
16723
|
+
return err(e);
|
|
16724
|
+
}
|
|
16725
|
+
});
|
|
16726
|
+
server.tool("get_template", "Get a sandbox template by ID", {
|
|
16727
|
+
id: exports_external2.string().describe("Template ID or partial ID")
|
|
16728
|
+
}, async (params) => {
|
|
16729
|
+
try {
|
|
16730
|
+
return ok(getTemplate(params.id));
|
|
16731
|
+
} catch (e) {
|
|
16732
|
+
return err(e);
|
|
16733
|
+
}
|
|
16734
|
+
});
|
|
16735
|
+
server.tool("delete_template", "Delete a sandbox template", {
|
|
16736
|
+
id: exports_external2.string().describe("Template ID or partial ID")
|
|
16737
|
+
}, async (params) => {
|
|
16738
|
+
try {
|
|
16739
|
+
deleteTemplate(params.id);
|
|
16740
|
+
return ok({ deleted: params.id });
|
|
16741
|
+
} catch (e) {
|
|
16742
|
+
return err(e);
|
|
16743
|
+
}
|
|
16744
|
+
});
|
|
16745
|
+
server.tool("get_sandbox_status", "Get running processes, disk usage and uptime in a sandbox", {
|
|
16746
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID")
|
|
16747
|
+
}, async (params) => {
|
|
16748
|
+
try {
|
|
16749
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
16750
|
+
if (!sandbox.provider_sandbox_id)
|
|
16751
|
+
throw new Error("Sandbox has no provider ID");
|
|
16752
|
+
const provider = await getProvider(sandbox.provider);
|
|
16753
|
+
const [psResult, dfResult, uptimeResult] = await Promise.all([
|
|
16754
|
+
provider.exec(sandbox.provider_sandbox_id, "ps aux --no-headers 2>/dev/null | head -30 || ps aux 2>/dev/null | tail -n +2 | head -30"),
|
|
16755
|
+
provider.exec(sandbox.provider_sandbox_id, "df -h / 2>/dev/null || df -h 2>/dev/null | head -5"),
|
|
16756
|
+
provider.exec(sandbox.provider_sandbox_id, "uptime 2>/dev/null || echo unknown")
|
|
16757
|
+
]);
|
|
16758
|
+
const processes = (psResult.stdout || "").trim().split(`
|
|
16755
16759
|
`).filter(Boolean).map((line) => {
|
|
16756
|
-
|
|
16757
|
-
|
|
16758
|
-
|
|
16759
|
-
|
|
16760
|
-
|
|
16761
|
-
|
|
16762
|
-
|
|
16763
|
-
|
|
16764
|
-
|
|
16765
|
-
|
|
16766
|
-
|
|
16767
|
-
|
|
16768
|
-
|
|
16769
|
-
|
|
16770
|
-
|
|
16771
|
-
|
|
16772
|
-
|
|
16773
|
-
|
|
16774
|
-
});
|
|
16775
|
-
server.tool("snapshot_sandbox", "Capture sandbox filesystem state as a snapshot", {
|
|
16776
|
-
|
|
16777
|
-
|
|
16778
|
-
}, async (params) => {
|
|
16779
|
-
|
|
16780
|
-
|
|
16781
|
-
|
|
16782
|
-
|
|
16783
|
-
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
16797
|
-
});
|
|
16798
|
-
server.tool("list_snapshots", "List filesystem snapshots", {
|
|
16799
|
-
|
|
16800
|
-
}, async (params) => {
|
|
16801
|
-
|
|
16802
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
});
|
|
16807
|
-
server.tool("delete_snapshot", "Delete a snapshot", {
|
|
16808
|
-
|
|
16809
|
-
}, async (params) => {
|
|
16810
|
-
|
|
16811
|
-
|
|
16812
|
-
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
});
|
|
16817
|
-
server.tool("expose_port", "Forward a sandbox port and get a public URL", {
|
|
16818
|
-
|
|
16819
|
-
|
|
16820
|
-
|
|
16821
|
-
}, async (params) => {
|
|
16822
|
-
|
|
16823
|
-
|
|
16824
|
-
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
|
|
16828
|
-
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
|
|
16832
|
-
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
});
|
|
16836
|
-
server.tool("list_exposed_ports", "List all forwarded ports for a sandbox", {
|
|
16837
|
-
|
|
16838
|
-
}, async (params) => {
|
|
16839
|
-
|
|
16840
|
-
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
|
|
16844
|
-
|
|
16845
|
-
|
|
16846
|
-
|
|
16847
|
-
});
|
|
16848
|
-
server.tool("close_port", "Stop forwarding a sandbox port", {
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
}, async (params) => {
|
|
16852
|
-
|
|
16853
|
-
|
|
16854
|
-
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
|
|
16760
|
+
const parts = line.trim().split(/\s+/);
|
|
16761
|
+
return {
|
|
16762
|
+
pid: parts[1] || "",
|
|
16763
|
+
cpu: parts[2] || "0",
|
|
16764
|
+
mem: parts[3] || "0",
|
|
16765
|
+
command: parts.slice(10).join(" ") || parts.slice(4).join(" ")
|
|
16766
|
+
};
|
|
16767
|
+
});
|
|
16768
|
+
return ok({
|
|
16769
|
+
sandbox_id: sandbox.id,
|
|
16770
|
+
status: sandbox.status,
|
|
16771
|
+
processes,
|
|
16772
|
+
disk: (dfResult.stdout || "").trim(),
|
|
16773
|
+
uptime: (uptimeResult.stdout || "").trim()
|
|
16774
|
+
});
|
|
16775
|
+
} catch (e) {
|
|
16776
|
+
return err(e);
|
|
16777
|
+
}
|
|
16778
|
+
});
|
|
16779
|
+
server.tool("snapshot_sandbox", "Capture sandbox filesystem state as a snapshot", {
|
|
16780
|
+
id: exports_external2.string().describe("Sandbox ID or partial ID"),
|
|
16781
|
+
name: exports_external2.string().optional().describe("Snapshot name")
|
|
16782
|
+
}, async (params) => {
|
|
16783
|
+
try {
|
|
16784
|
+
const sandbox = getSandbox(params.id);
|
|
16785
|
+
if (!sandbox.provider_sandbox_id)
|
|
16786
|
+
throw new Error("Sandbox has no provider ID");
|
|
16787
|
+
const provider = await getProvider(sandbox.provider);
|
|
16788
|
+
await provider.pause(sandbox.provider_sandbox_id);
|
|
16789
|
+
updateSandbox(sandbox.id, { status: "paused" });
|
|
16790
|
+
const snapshot = createSnapshot({
|
|
16791
|
+
sandbox_id: sandbox.id,
|
|
16792
|
+
provider_sandbox_id: sandbox.provider_sandbox_id,
|
|
16793
|
+
provider: sandbox.provider,
|
|
16794
|
+
name: params.name
|
|
16795
|
+
});
|
|
16796
|
+
emitLifecycleEvent(sandbox.id, `Snapshot created: ${snapshot.id}`);
|
|
16797
|
+
return ok(snapshot);
|
|
16798
|
+
} catch (e) {
|
|
16799
|
+
return err(e);
|
|
16800
|
+
}
|
|
16801
|
+
});
|
|
16802
|
+
server.tool("list_snapshots", "List filesystem snapshots", {
|
|
16803
|
+
sandbox_id: exports_external2.string().optional().describe("Filter by sandbox ID")
|
|
16804
|
+
}, async (params) => {
|
|
16805
|
+
try {
|
|
16806
|
+
return ok(listSnapshots(params.sandbox_id));
|
|
16807
|
+
} catch (e) {
|
|
16808
|
+
return err(e);
|
|
16809
|
+
}
|
|
16810
|
+
});
|
|
16811
|
+
server.tool("delete_snapshot", "Delete a snapshot", {
|
|
16812
|
+
id: exports_external2.string().describe("Snapshot ID or partial ID")
|
|
16813
|
+
}, async (params) => {
|
|
16814
|
+
try {
|
|
16815
|
+
deleteSnapshot(params.id);
|
|
16816
|
+
return ok({ deleted: params.id });
|
|
16817
|
+
} catch (e) {
|
|
16818
|
+
return err(e);
|
|
16819
|
+
}
|
|
16820
|
+
});
|
|
16821
|
+
server.tool("expose_port", "Forward a sandbox port and get a public URL", {
|
|
16822
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
|
|
16823
|
+
port: exports_external2.number().describe("Port number to expose"),
|
|
16824
|
+
protocol: exports_external2.string().optional().describe("Protocol: http or ws (default: http)")
|
|
16825
|
+
}, async (params) => {
|
|
16826
|
+
try {
|
|
16827
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
16828
|
+
if (!sandbox.provider_sandbox_id)
|
|
16829
|
+
throw new Error("Sandbox has no provider ID");
|
|
16830
|
+
const provider = await getProvider(sandbox.provider);
|
|
16831
|
+
const url = await provider.getPublicUrl(sandbox.provider_sandbox_id, params.port, params.protocol);
|
|
16832
|
+
if (!exposedPorts.has(sandbox.id))
|
|
16833
|
+
exposedPorts.set(sandbox.id, new Map);
|
|
16834
|
+
exposedPorts.get(sandbox.id).set(params.port, url);
|
|
16835
|
+
return ok({ sandbox_id: sandbox.id, port: params.port, url });
|
|
16836
|
+
} catch (e) {
|
|
16837
|
+
return err(e);
|
|
16838
|
+
}
|
|
16839
|
+
});
|
|
16840
|
+
server.tool("list_exposed_ports", "List all forwarded ports for a sandbox", {
|
|
16841
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID")
|
|
16842
|
+
}, async (params) => {
|
|
16843
|
+
try {
|
|
16844
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
16845
|
+
const ports = exposedPorts.get(sandbox.id) ?? new Map;
|
|
16846
|
+
const result = Array.from(ports.entries()).map(([port, url]) => ({ port, url }));
|
|
16847
|
+
return ok(result);
|
|
16848
|
+
} catch (e) {
|
|
16849
|
+
return err(e);
|
|
16850
|
+
}
|
|
16851
|
+
});
|
|
16852
|
+
server.tool("close_port", "Stop forwarding a sandbox port", {
|
|
16853
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
|
|
16854
|
+
port: exports_external2.number().describe("Port number to close")
|
|
16855
|
+
}, async (params) => {
|
|
16856
|
+
try {
|
|
16857
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
16858
|
+
exposedPorts.get(sandbox.id)?.delete(params.port);
|
|
16859
|
+
return ok({ sandbox_id: sandbox.id, port: params.port, closed: true });
|
|
16860
|
+
} catch (e) {
|
|
16861
|
+
return err(e);
|
|
16862
|
+
}
|
|
16863
|
+
});
|
|
16864
|
+
server.tool("get_network_log", "Get outbound network connections from a sandbox", {
|
|
16865
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID")
|
|
16866
|
+
}, async (params) => {
|
|
16867
|
+
try {
|
|
16868
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
16869
|
+
if (!sandbox.provider_sandbox_id)
|
|
16870
|
+
throw new Error("Sandbox has no provider ID");
|
|
16871
|
+
const provider = await getProvider(sandbox.provider);
|
|
16872
|
+
const result = await provider.exec(sandbox.provider_sandbox_id, "ss -tnp 2>/dev/null || netstat -tnp 2>/dev/null || echo 'Network log not available'");
|
|
16873
|
+
return ok({ sandbox_id: sandbox.id, connections: (result.stdout || "").trim() });
|
|
16874
|
+
} catch (e) {
|
|
16875
|
+
return err(e);
|
|
16876
|
+
}
|
|
16877
|
+
});
|
|
16878
|
+
server.tool("watch_file", "Get new content from a file since a previous read (tail -f equivalent)", {
|
|
16879
|
+
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
|
|
16880
|
+
path: exports_external2.string().describe("File path to watch"),
|
|
16881
|
+
offset: exports_external2.number().optional().describe("Line offset to read from (use next_offset from previous call)"),
|
|
16882
|
+
limit: exports_external2.number().optional().describe("Max lines to return (default: 100)")
|
|
16883
|
+
}, async (params) => {
|
|
16884
|
+
try {
|
|
16885
|
+
const sandbox = getSandbox(params.sandbox_id);
|
|
16886
|
+
if (!sandbox.provider_sandbox_id)
|
|
16887
|
+
throw new Error("Sandbox has no provider ID");
|
|
16888
|
+
const provider = await getProvider(sandbox.provider);
|
|
16889
|
+
const content = await provider.readFile(sandbox.provider_sandbox_id, params.path, {
|
|
16890
|
+
offset: params.offset,
|
|
16891
|
+
limit: params.limit ?? 100
|
|
16892
|
+
});
|
|
16893
|
+
const lines = content.split(`
|
|
16894
|
+
`);
|
|
16895
|
+
return ok({
|
|
16896
|
+
path: params.path,
|
|
16897
|
+
content,
|
|
16898
|
+
lines_read: lines.length,
|
|
16899
|
+
next_offset: (params.offset ?? 0) + lines.length
|
|
16900
|
+
});
|
|
16901
|
+
} catch (e) {
|
|
16902
|
+
return err(e);
|
|
16903
|
+
}
|
|
16904
|
+
});
|
|
16905
|
+
server.tool("list_images", "List available pre-warmed sandbox image aliases", {}, async () => {
|
|
16906
|
+
try {
|
|
16907
|
+
return ok(Object.entries(BUILTIN_IMAGES).map(([name, info]) => ({
|
|
16908
|
+
name,
|
|
16909
|
+
description: info.description,
|
|
16910
|
+
has_setup_script: !!info.setup_script
|
|
16911
|
+
})));
|
|
16912
|
+
} catch (e) {
|
|
16913
|
+
return err(e);
|
|
16914
|
+
}
|
|
16915
|
+
});
|
|
16916
|
+
server.tool("send_feedback", "Send feedback about this service", { message: exports_external2.string(), email: exports_external2.string().optional(), category: exports_external2.enum(["bug", "feature", "general"]).optional() }, async (params) => {
|
|
16917
|
+
try {
|
|
16918
|
+
const db2 = getDatabase();
|
|
16919
|
+
db2.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", [params.message, params.email || null, params.category || "general", "0.1.17"]);
|
|
16920
|
+
return ok("Feedback saved. Thank you!");
|
|
16921
|
+
} catch (e) {
|
|
16922
|
+
return err(e);
|
|
16923
|
+
}
|
|
16924
|
+
});
|
|
16925
|
+
registerCloudTools(server, "sandboxes");
|
|
16926
|
+
return server;
|
|
16927
|
+
}
|
|
16928
|
+
|
|
16929
|
+
// src/mcp/http.ts
|
|
16930
|
+
import { createServer } from "http";
|
|
16931
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
16932
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
16933
|
+
var DEFAULT_MCP_HTTP_PORT = 8831;
|
|
16934
|
+
function isHttpMode(argv) {
|
|
16935
|
+
return argv.includes("--http") || process.env["MCP_HTTP"] === "1";
|
|
16936
|
+
}
|
|
16937
|
+
function resolveMcpHttpPort(argv) {
|
|
16938
|
+
const portIdx = argv.indexOf("--port");
|
|
16939
|
+
if (portIdx >= 0 && argv[portIdx + 1]) {
|
|
16940
|
+
return parseInt(argv[portIdx + 1], 10);
|
|
16858
16941
|
}
|
|
16859
|
-
|
|
16860
|
-
|
|
16861
|
-
sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID")
|
|
16862
|
-
}, async (params) => {
|
|
16863
|
-
try {
|
|
16864
|
-
const sandbox = getSandbox(params.sandbox_id);
|
|
16865
|
-
if (!sandbox.provider_sandbox_id)
|
|
16866
|
-
throw new Error("Sandbox has no provider ID");
|
|
16867
|
-
const provider = await getProvider(sandbox.provider);
|
|
16868
|
-
const result = await provider.exec(sandbox.provider_sandbox_id, "ss -tnp 2>/dev/null || netstat -tnp 2>/dev/null || echo 'Network log not available'");
|
|
16869
|
-
return ok({ sandbox_id: sandbox.id, connections: (result.stdout || "").trim() });
|
|
16870
|
-
} catch (e) {
|
|
16871
|
-
return err(e);
|
|
16942
|
+
if (process.env["MCP_HTTP_PORT"]) {
|
|
16943
|
+
return parseInt(process.env["MCP_HTTP_PORT"], 10);
|
|
16872
16944
|
}
|
|
16873
|
-
|
|
16874
|
-
|
|
16875
|
-
|
|
16876
|
-
|
|
16877
|
-
|
|
16878
|
-
|
|
16879
|
-
|
|
16880
|
-
|
|
16881
|
-
|
|
16882
|
-
|
|
16883
|
-
|
|
16884
|
-
|
|
16885
|
-
|
|
16886
|
-
|
|
16887
|
-
|
|
16888
|
-
|
|
16889
|
-
|
|
16890
|
-
|
|
16891
|
-
|
|
16892
|
-
|
|
16893
|
-
|
|
16894
|
-
|
|
16895
|
-
|
|
16896
|
-
|
|
16897
|
-
|
|
16898
|
-
|
|
16945
|
+
return DEFAULT_MCP_HTTP_PORT;
|
|
16946
|
+
}
|
|
16947
|
+
function healthPayload(name = MCP_NAME) {
|
|
16948
|
+
return { status: "ok", name };
|
|
16949
|
+
}
|
|
16950
|
+
function startMcpHttpServer(options = {}) {
|
|
16951
|
+
const hostname2 = options.hostname ?? "127.0.0.1";
|
|
16952
|
+
const requestedPort = options.port ?? DEFAULT_MCP_HTTP_PORT;
|
|
16953
|
+
const httpServer = createServer(async (req, res) => {
|
|
16954
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? `${hostname2}:${requestedPort}`}`);
|
|
16955
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
16956
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
16957
|
+
res.end(JSON.stringify(healthPayload()));
|
|
16958
|
+
return;
|
|
16959
|
+
}
|
|
16960
|
+
if (url.pathname === "/mcp") {
|
|
16961
|
+
const server = buildServer();
|
|
16962
|
+
const transport = new StreamableHTTPServerTransport({
|
|
16963
|
+
sessionIdGenerator: undefined
|
|
16964
|
+
});
|
|
16965
|
+
try {
|
|
16966
|
+
await server.connect(transport);
|
|
16967
|
+
await transport.handleRequest(req, res);
|
|
16968
|
+
} finally {
|
|
16969
|
+
res.on("close", () => {
|
|
16970
|
+
transport.close();
|
|
16971
|
+
server.close();
|
|
16972
|
+
});
|
|
16973
|
+
}
|
|
16974
|
+
return;
|
|
16975
|
+
}
|
|
16976
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
16977
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
16978
|
+
});
|
|
16979
|
+
httpServer.listen(requestedPort, hostname2, () => {
|
|
16980
|
+
const address = httpServer.address();
|
|
16981
|
+
const port = typeof address === "object" && address ? address.port : requestedPort;
|
|
16982
|
+
options.onListening?.(port);
|
|
16983
|
+
console.error(`[${MCP_NAME}-mcp] HTTP listening on http://${hostname2}:${port}/mcp`);
|
|
16984
|
+
});
|
|
16985
|
+
return httpServer;
|
|
16986
|
+
}
|
|
16987
|
+
|
|
16988
|
+
// src/mcp/index.ts
|
|
16989
|
+
function handleCliFlags(argv) {
|
|
16990
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
16991
|
+
console.log("Usage: sandboxes-mcp [options]");
|
|
16992
|
+
console.log("");
|
|
16993
|
+
console.log("MCP server for @hasna/sandboxes (stdio default, optional Streamable HTTP)");
|
|
16994
|
+
console.log("");
|
|
16995
|
+
console.log("Options:");
|
|
16996
|
+
console.log(" --http Start Streamable HTTP transport on 127.0.0.1");
|
|
16997
|
+
console.log(" --port <n> HTTP port (default 8831, or MCP_HTTP_PORT env)");
|
|
16998
|
+
console.log(" -h, --help display help");
|
|
16999
|
+
console.log(" -V, --version display version");
|
|
17000
|
+
console.log("");
|
|
17001
|
+
console.log("Environment:");
|
|
17002
|
+
console.log(" MCP_HTTP=1 Enable HTTP mode");
|
|
17003
|
+
console.log(" MCP_HTTP_PORT Override default HTTP port");
|
|
17004
|
+
return true;
|
|
16899
17005
|
}
|
|
16900
|
-
|
|
16901
|
-
|
|
16902
|
-
|
|
16903
|
-
return ok(Object.entries(BUILTIN_IMAGES).map(([name, info]) => ({
|
|
16904
|
-
name,
|
|
16905
|
-
description: info.description,
|
|
16906
|
-
has_setup_script: !!info.setup_script
|
|
16907
|
-
})));
|
|
16908
|
-
} catch (e) {
|
|
16909
|
-
return err(e);
|
|
17006
|
+
if (argv.includes("--version") || argv.includes("-V")) {
|
|
17007
|
+
console.log(getPackageVersion());
|
|
17008
|
+
return true;
|
|
16910
17009
|
}
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
|
|
16916
|
-
|
|
16917
|
-
|
|
16918
|
-
|
|
17010
|
+
return false;
|
|
17011
|
+
}
|
|
17012
|
+
var argv = process.argv.slice(2);
|
|
17013
|
+
if (handleCliFlags(argv)) {
|
|
17014
|
+
process.exit(0);
|
|
17015
|
+
}
|
|
17016
|
+
async function main() {
|
|
17017
|
+
if (isHttpMode(argv)) {
|
|
17018
|
+
startMcpHttpServer({ port: resolveMcpHttpPort(argv) });
|
|
17019
|
+
return;
|
|
16919
17020
|
}
|
|
16920
|
-
|
|
16921
|
-
|
|
16922
|
-
|
|
16923
|
-
|
|
17021
|
+
const server = buildServer();
|
|
17022
|
+
const transport = new StdioServerTransport;
|
|
17023
|
+
await server.connect(transport);
|
|
17024
|
+
}
|
|
17025
|
+
main().catch(console.error);
|