@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/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/index.ts
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 server = new McpServer({
16177
- name: "sandboxes",
16178
- version: getPackageVersion()
16179
- });
16180
- server.tool("create_sandbox", "Create a new sandbox", {
16181
- provider: exports_external2.string().optional().describe("Provider name (e2b, daytona, modal)"),
16182
- image: exports_external2.string().optional().describe("Container image"),
16183
- timeout: exports_external2.number().optional().describe("Timeout in seconds"),
16184
- name: exports_external2.string().optional().describe("Sandbox name"),
16185
- env_vars: exports_external2.record(exports_external2.string()).optional().describe("Environment variables"),
16186
- template_id: exports_external2.string().optional().describe("Template ID to base this sandbox on"),
16187
- on_timeout: exports_external2.enum(["pause", "terminate"]).optional().describe("What to do on timeout: pause (saves state) or terminate"),
16188
- auto_resume: exports_external2.boolean().optional().describe("Auto-resume paused sandbox on next connect"),
16189
- snapshot_id: exports_external2.string().optional().describe("Snapshot ID to restore from"),
16190
- network: exports_external2.enum(["full", "restricted", "none"]).optional().describe("Network access policy for the sandbox"),
16191
- budget_limit_usd: exports_external2.number().optional().describe("Auto-terminate sandbox if compute cost exceeds this USD amount"),
16192
- on_budget_exceeded: exports_external2.enum(["terminate", "pause", "notify"]).optional().describe("Action when budget limit is reached (default: terminate)")
16193
- }, async (params) => {
16194
- let sandboxId;
16195
- try {
16196
- const providerName = params.provider ?? getDefaultProvider();
16197
- const timeout = params.timeout ?? getDefaultTimeout();
16198
- let templateData = {};
16199
- if (params.template_id) {
16200
- const tmpl = getTemplate(params.template_id);
16201
- templateData = { image: tmpl.image ?? undefined, env_vars: tmpl.env_vars, setup_script: tmpl.setup_script };
16202
- }
16203
- const rawImage = params.image ?? templateData.image;
16204
- const resolvedImage = rawImage ? resolveImage(rawImage) : rawImage;
16205
- const builtinSetupScript = rawImage ? getBuiltinImageSetupScript(rawImage) : undefined;
16206
- const envVars = { ...templateData.env_vars, ...params.env_vars };
16207
- const onTimeout = params.on_timeout ?? "terminate";
16208
- const autoResume = params.auto_resume ?? false;
16209
- const sandbox = createSandbox({
16210
- provider: providerName,
16211
- image: resolvedImage,
16212
- timeout,
16213
- name: params.name,
16214
- env_vars: envVars,
16215
- on_timeout: onTimeout,
16216
- auto_resume: autoResume,
16217
- template_id: params.template_id,
16218
- config: { network: params.network ?? "full" },
16219
- budget_limit_usd: params.budget_limit_usd,
16220
- on_budget_exceeded: params.on_budget_exceeded
16221
- });
16222
- sandboxId = sandbox.id;
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
- emitLifecycleEvent(sandbox.id, `Sandbox restored from snapshot ${snapshot.id}`);
16232
- return ok(updated2);
16233
- }
16234
- const result = await provider.create({
16235
- image: resolvedImage,
16236
- timeout,
16237
- envVars,
16238
- onTimeout,
16239
- autoResume
16240
- });
16241
- const updated = updateSandbox(sandbox.id, {
16242
- provider_sandbox_id: result.id,
16243
- status: "running",
16244
- started_at: new Date().toISOString()
16245
- });
16246
- emitLifecycleEvent(sandbox.id, "sandbox created");
16247
- if (templateData.setup_script && result.id) {
16248
- try {
16249
- await provider.exec(result.id, templateData.setup_script);
16250
- } catch {}
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
- if (builtinSetupScript && result.id) {
16253
- try {
16254
- await provider.exec(result.id, builtinSetupScript);
16255
- } catch {}
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
- return ok(updated);
16258
- } catch (e) {
16259
- if (sandboxId) {
16260
- finalizeSandboxProvisionFailure(sandboxId, e);
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
- return err(e);
16263
- }
16264
- });
16265
- server.tool("get_sandbox", "Get sandbox details by ID", {
16266
- id: exports_external2.string().describe("Sandbox ID or partial ID")
16267
- }, async (params) => {
16268
- try {
16269
- const sandbox = getSandbox(params.id);
16270
- const cost = estimateCost(sandbox.provider, sandbox.started_at);
16271
- return ok({ ...sandbox, ...cost });
16272
- } catch (e) {
16273
- return err(e);
16274
- }
16275
- });
16276
- server.tool("list_sandboxes", "List sandboxes with filters", {
16277
- status: exports_external2.string().optional().describe("Filter by status"),
16278
- provider: exports_external2.string().optional().describe("Filter by provider")
16279
- }, async (params) => {
16280
- try {
16281
- const sandboxes = listSandboxes({
16282
- status: params.status,
16283
- provider: params.provider
16284
- });
16285
- return ok(sandboxes.map((s) => ({ ...s, ...estimateCost(s.provider, s.started_at) })));
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.delete(sandbox.provider_sandbox_id);
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
- deleteSandbox(sandbox.id);
16300
- emitLifecycleEvent(sandbox.id, "sandbox deleted");
16301
- return ok({ deleted: sandbox.id });
16302
- } catch (e) {
16303
- return err(e);
16304
- }
16305
- });
16306
- server.tool("stop_sandbox", "Stop a running sandbox", {
16307
- id: exports_external2.string().describe("Sandbox ID or partial ID")
16308
- }, async (params) => {
16309
- try {
16310
- const sandbox = getSandbox(params.id);
16311
- if (!sandbox.provider_sandbox_id)
16312
- throw new Error("Sandbox has no provider ID");
16313
- const provider = await getProvider(sandbox.provider);
16314
- await provider.stop(sandbox.provider_sandbox_id);
16315
- const updated = updateSandbox(sandbox.id, { status: "stopped" });
16316
- emitLifecycleEvent(sandbox.id, "sandbox stopped");
16317
- return ok(updated);
16318
- } catch (e) {
16319
- return err(e);
16320
- }
16321
- });
16322
- server.tool("keep_alive", "Extend sandbox lifetime", {
16323
- sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
16324
- duration_seconds: exports_external2.number().optional().describe("Duration in seconds (default 300)")
16325
- }, async (params) => {
16326
- try {
16327
- const sandbox = getSandbox(params.sandbox_id);
16328
- if (!sandbox.provider_sandbox_id)
16329
- throw new Error("Sandbox has no provider ID");
16330
- const provider = await getProvider(sandbox.provider);
16331
- const durationMs = (params.duration_seconds ?? 300) * 1000;
16332
- await provider.keepAlive(sandbox.provider_sandbox_id, durationMs);
16333
- return ok({ kept_alive: sandbox.id, duration_seconds: params.duration_seconds ?? 300 });
16334
- } catch (e) {
16335
- return err(e);
16336
- }
16337
- });
16338
- server.tool("exec_command", "Execute a command in a sandbox", {
16339
- sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
16340
- command: exports_external2.string().describe("Command to execute"),
16341
- background: exports_external2.boolean().optional().describe("Run in background"),
16342
- env_vars: exports_external2.record(exports_external2.string()).optional().describe("Per-call environment variables (merged with sandbox env_vars, not persisted)"),
16343
- stdin: exports_external2.string().optional().describe("String to pipe as stdin to the command"),
16344
- tty: exports_external2.boolean().optional().describe("Allocate a TTY for the session (best-effort)")
16345
- }, async (params) => {
16346
- let sessionId;
16347
- try {
16348
- const sandbox = getSandbox(params.sandbox_id);
16349
- if (!sandbox.provider_sandbox_id)
16350
- throw new Error("Sandbox has no provider ID");
16351
- const session = createSession({
16352
- sandbox_id: sandbox.id,
16353
- command: params.command
16354
- });
16355
- sessionId = session.id;
16356
- const collector = createStreamCollector(sandbox.id, session.id);
16357
- const provider = await getProvider(sandbox.provider);
16358
- const callEnv = { ...sandbox.env_vars, ...params.env_vars };
16359
- const env = Object.keys(callEnv).length > 0 ? callEnv : undefined;
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
- background: true,
16378
- message: "Command started in background. Use get_session to check completion status and exit_code. Use bg_wait_session to block until done."
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
- const result = await provider.exec(sandbox.provider_sandbox_id, effectiveCommand, {
16382
- onStdout: collector.onStdout,
16383
- onStderr: collector.onStderr,
16384
- env,
16385
- stdin: params.stdin,
16386
- tty: params.tty
16387
- });
16388
- const execResult = result;
16389
- finalizeSessionExit(session.id, execResult.exit_code);
16390
- return ok({
16391
- session_id: session.id,
16392
- exit_code: execResult.exit_code,
16393
- stdout: execResult.stdout,
16394
- stderr: execResult.stderr
16395
- });
16396
- } catch (e) {
16397
- if (sessionId) {
16398
- finalizeSessionFailure(sessionId, e);
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
- return err(e);
16401
- }
16402
- });
16403
- server.tool("read_file", "Read a file from a sandbox", {
16404
- sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
16405
- path: exports_external2.string().describe("File path"),
16406
- offset: exports_external2.number().optional().describe("Line or byte offset to start reading from"),
16407
- limit: exports_external2.number().optional().describe("Max lines or bytes to return"),
16408
- encoding: exports_external2.enum(["utf8", "base64", "hex"]).optional().describe("Output encoding (default: utf8)")
16409
- }, async (params) => {
16410
- try {
16411
- const sandbox = getSandbox(params.sandbox_id);
16412
- if (!sandbox.provider_sandbox_id)
16413
- throw new Error("Sandbox has no provider ID");
16414
- const provider = await getProvider(sandbox.provider);
16415
- const content = await provider.readFile(sandbox.provider_sandbox_id, params.path, {
16416
- encoding: params.encoding,
16417
- offset: params.offset,
16418
- limit: params.limit
16419
- });
16420
- return ok({ path: params.path, content, encoding: params.encoding ?? "utf8" });
16421
- } catch (e) {
16422
- return err(e);
16423
- }
16424
- });
16425
- server.tool("write_file", "Write a file to a sandbox", {
16426
- sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
16427
- path: exports_external2.string().describe("File path"),
16428
- content: exports_external2.string().describe("File content")
16429
- }, async (params) => {
16430
- try {
16431
- const sandbox = getSandbox(params.sandbox_id);
16432
- if (!sandbox.provider_sandbox_id)
16433
- throw new Error("Sandbox has no provider ID");
16434
- const provider = await getProvider(sandbox.provider);
16435
- await provider.writeFile(sandbox.provider_sandbox_id, params.path, params.content);
16436
- return ok({ path: params.path, written: true });
16437
- } catch (e) {
16438
- return err(e);
16439
- }
16440
- });
16441
- server.tool("list_files", "List files in a sandbox directory", {
16442
- sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
16443
- path: exports_external2.string().describe("Directory path"),
16444
- recursive: exports_external2.boolean().optional().describe("List files recursively"),
16445
- glob: exports_external2.string().optional().describe("Glob pattern to filter files")
16446
- }, async (params) => {
16447
- try {
16448
- const sandbox = getSandbox(params.sandbox_id);
16449
- if (!sandbox.provider_sandbox_id)
16450
- throw new Error("Sandbox has no provider ID");
16451
- const provider = await getProvider(sandbox.provider);
16452
- const files = await provider.listFiles(sandbox.provider_sandbox_id, params.path, {
16453
- recursive: params.recursive,
16454
- glob: params.glob
16455
- });
16456
- return ok(files);
16457
- } catch (e) {
16458
- return err(e);
16459
- }
16460
- });
16461
- server.tool("upload_dir", "Upload a local directory into a sandbox as a single archive (fast, no git clone)", {
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
- if (session.status === "completed" || session.status === "failed" || session.status === "killed") {
16504
- const events = listEvents({ session_id: session.id, limit: 1e4 });
16505
- const stdout = events.filter((e) => e.type === "stdout").map((e) => e.data).join("");
16506
- const stderr = events.filter((e) => e.type === "stderr").map((e) => e.data).join("");
16507
- return ok({
16508
- session_id: session.id,
16509
- status: session.status,
16510
- exit_code: session.exit_code ?? (session.status === "failed" || session.status === "killed" ? 1 : 0),
16511
- stdout,
16512
- stderr
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
- await new Promise((r) => setTimeout(r, pollMs));
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
- return err(`Session ${params.session_id} did not complete within ${params.timeout_seconds ?? 300}s`);
16518
- } catch (e) {
16519
- return err(e);
16520
- }
16521
- });
16522
- server.tool("get_logs", "Get sandbox/session event logs", {
16523
- sandbox_id: exports_external2.string().optional().describe("Filter by sandbox ID"),
16524
- session_id: exports_external2.string().optional().describe("Filter by session ID"),
16525
- limit: exports_external2.number().optional().describe("Max events to return")
16526
- }, async (params) => {
16527
- try {
16528
- return ok(listEvents({
16529
- sandbox_id: params.sandbox_id,
16530
- session_id: params.session_id,
16531
- limit: params.limit
16532
- }));
16533
- } catch (e) {
16534
- return err(e);
16535
- }
16536
- });
16537
- server.tool("register_agent", "Register an agent", {
16538
- name: exports_external2.string().describe("Agent name"),
16539
- description: exports_external2.string().optional().describe("Agent description")
16540
- }, async (params) => {
16541
- try {
16542
- return ok(registerAgent({ name: params.name, description: params.description }));
16543
- } catch (e) {
16544
- return err(e);
16545
- }
16546
- });
16547
- server.tool("list_agents", "List all registered agents", {}, async () => {
16548
- try {
16549
- return ok(listAgents());
16550
- } catch (e) {
16551
- return err(e);
16552
- }
16553
- });
16554
- 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) => {
16555
- try {
16556
- return ok(heartbeatAgent(params.agent_id));
16557
- } catch (e) {
16558
- return err(e);
16559
- }
16560
- });
16561
- server.tool("set_focus", "Set active project context for this agent session.", {
16562
- agent_id: exports_external2.string().describe("Agent ID or name"),
16563
- project_id: exports_external2.string().nullable().optional().describe("Project ID to focus on, or null to clear")
16564
- }, async (params) => {
16565
- try {
16566
- return ok(setAgentFocus(params.agent_id, params.project_id ?? null));
16567
- } catch (e) {
16568
- return err(e);
16569
- }
16570
- });
16571
- server.tool("register_project", "Register a project", {
16572
- name: exports_external2.string().describe("Project name"),
16573
- path: exports_external2.string().describe("Project path"),
16574
- description: exports_external2.string().optional().describe("Project description")
16575
- }, async (params) => {
16576
- try {
16577
- return ok(ensureProject(params.name, params.path, params.description));
16578
- } catch (e) {
16579
- return err(e);
16580
- }
16581
- });
16582
- server.tool("list_projects", "List all projects", {}, async () => {
16583
- try {
16584
- return ok(listProjects());
16585
- } catch (e) {
16586
- return err(e);
16587
- }
16588
- });
16589
- server.tool("describe_tools", "List all available tools", {}, async () => {
16590
- try {
16591
- return ok(TOOL_CATALOG);
16592
- } catch (e) {
16593
- return err(e);
16594
- }
16595
- });
16596
- server.tool("search_tools", "Search tools by keyword", {
16597
- query: exports_external2.string().describe("Search query")
16598
- }, async (params) => {
16599
- try {
16600
- const q = params.query.toLowerCase();
16601
- const matches = TOOL_CATALOG.filter((t) => t.name.includes(q) || t.description.toLowerCase().includes(q));
16602
- return ok(matches);
16603
- } catch (e) {
16604
- return err(e);
16605
- }
16606
- });
16607
- server.tool("run_agent", "Run an AI agent inside a sandbox", {
16608
- sandbox_id: exports_external2.string().describe("Sandbox ID"),
16609
- agent_type: exports_external2.enum(["claude", "codex", "gemini", "opencode", "pi", "takumi", "custom"]).describe("Agent type"),
16610
- prompt: exports_external2.string().describe("Prompt for the agent"),
16611
- agent_name: exports_external2.string().optional().describe("Agent name"),
16612
- command: exports_external2.string().optional().describe("Custom command (for 'custom' type)"),
16613
- env_vars: exports_external2.record(exports_external2.string()).optional().describe("Per-call environment variables (merged with sandbox env_vars, not persisted)"),
16614
- 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)"),
16615
- webhook_url: exports_external2.string().optional().describe("URL to POST result to when agent finishes"),
16616
- webhook_events: exports_external2.array(exports_external2.enum(["start", "complete", "error"])).optional().describe("Which events to notify on (default: all)")
16617
- }, async (params) => {
16618
- try {
16619
- let callEnvVars = params.env_vars;
16620
- if (params.secrets && params.secrets.length > 0) {
16621
- const { resolveSecretSpecs: resolveSecretSpecs2 } = await Promise.resolve().then(() => exports_secrets);
16622
- const secretEnv = await resolveSecretSpecs2(params.secrets);
16623
- callEnvVars = { ...secretEnv, ...params.env_vars };
16624
- }
16625
- const session = await runAgent(params.sandbox_id, {
16626
- agentType: params.agent_type,
16627
- prompt: params.prompt,
16628
- agentName: params.agent_name,
16629
- command: params.command,
16630
- callEnvVars,
16631
- webhookUrl: params.webhook_url,
16632
- webhookEvents: params.webhook_events
16633
- });
16634
- return ok({ session_id: session.id, status: session.status });
16635
- } catch (e) {
16636
- return err(e);
16637
- }
16638
- });
16639
- server.tool("stop_agent", "Stop a running agent in a sandbox", {
16640
- sandbox_id: exports_external2.string().describe("Sandbox ID")
16641
- }, async (params) => {
16642
- try {
16643
- await stopAgent(params.sandbox_id);
16644
- return ok({ stopped: true });
16645
- } catch (e) {
16646
- return err(e);
16647
- }
16648
- });
16649
- server.tool("get_agent_output", "Get output from an agent session", {
16650
- sandbox_id: exports_external2.string().describe("Sandbox ID"),
16651
- session_id: exports_external2.string().optional().describe("Session ID"),
16652
- limit: exports_external2.number().optional().describe("Max events"),
16653
- offset: exports_external2.number().optional().describe("Skip first N events (for incremental polling)")
16654
- }, async (params) => {
16655
- try {
16656
- const events = listEvents({
16657
- sandbox_id: params.sandbox_id,
16658
- session_id: params.session_id,
16659
- limit: params.limit || 100,
16660
- offset: params.offset
16661
- });
16662
- const stdout = events.filter((e) => e.type === "stdout").map((e) => e.data).join("");
16663
- const stderr = events.filter((e) => e.type === "stderr").map((e) => e.data).join("");
16664
- return ok({ stdout, stderr, event_count: events.length, next_offset: (params.offset ?? 0) + events.length });
16665
- } catch (e) {
16666
- return err(e);
16667
- }
16668
- });
16669
- server.tool("pause_sandbox", "Pause a running sandbox, saving its state for later resume", {
16670
- id: exports_external2.string().describe("Sandbox ID or partial ID")
16671
- }, async (params) => {
16672
- try {
16673
- const sandbox = getSandbox(params.id);
16674
- if (!sandbox.provider_sandbox_id)
16675
- throw new Error("Sandbox has no provider ID");
16676
- const provider = await getProvider(sandbox.provider);
16677
- await provider.pause(sandbox.provider_sandbox_id);
16678
- const updated = updateSandbox(sandbox.id, { status: "paused" });
16679
- emitLifecycleEvent(sandbox.id, "sandbox paused");
16680
- return ok(updated);
16681
- } catch (e) {
16682
- return err(e);
16683
- }
16684
- });
16685
- server.tool("resume_sandbox", "Resume a paused sandbox", {
16686
- id: exports_external2.string().describe("Sandbox ID or partial ID")
16687
- }, async (params) => {
16688
- try {
16689
- const sandbox = getSandbox(params.id);
16690
- if (!sandbox.provider_sandbox_id)
16691
- throw new Error("Sandbox has no provider ID");
16692
- const provider = await getProvider(sandbox.provider);
16693
- await provider.resume(sandbox.provider_sandbox_id);
16694
- const updated = updateSandbox(sandbox.id, { status: "running" });
16695
- emitLifecycleEvent(sandbox.id, "sandbox resumed");
16696
- return ok(updated);
16697
- } catch (e) {
16698
- return err(e);
16699
- }
16700
- });
16701
- server.tool("create_template", "Create a reusable sandbox template", {
16702
- name: exports_external2.string().describe("Template name"),
16703
- description: exports_external2.string().optional(),
16704
- image: exports_external2.string().optional().describe("Container image"),
16705
- env_vars: exports_external2.record(exports_external2.string()).optional().describe("Environment variables"),
16706
- setup_script: exports_external2.string().optional().describe("Shell script to run on sandbox creation"),
16707
- tags: exports_external2.array(exports_external2.string()).optional()
16708
- }, async (params) => {
16709
- try {
16710
- return ok(createTemplate(params));
16711
- } catch (e) {
16712
- return err(e);
16713
- }
16714
- });
16715
- server.tool("list_templates", "List all sandbox templates", {}, async () => {
16716
- try {
16717
- return ok(listTemplates());
16718
- } catch (e) {
16719
- return err(e);
16720
- }
16721
- });
16722
- server.tool("get_template", "Get a sandbox template by ID", {
16723
- id: exports_external2.string().describe("Template ID or partial ID")
16724
- }, async (params) => {
16725
- try {
16726
- return ok(getTemplate(params.id));
16727
- } catch (e) {
16728
- return err(e);
16729
- }
16730
- });
16731
- server.tool("delete_template", "Delete a sandbox template", {
16732
- id: exports_external2.string().describe("Template ID or partial ID")
16733
- }, async (params) => {
16734
- try {
16735
- deleteTemplate(params.id);
16736
- return ok({ deleted: params.id });
16737
- } catch (e) {
16738
- return err(e);
16739
- }
16740
- });
16741
- server.tool("get_sandbox_status", "Get running processes, disk usage and uptime in a sandbox", {
16742
- sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID")
16743
- }, async (params) => {
16744
- try {
16745
- const sandbox = getSandbox(params.sandbox_id);
16746
- if (!sandbox.provider_sandbox_id)
16747
- throw new Error("Sandbox has no provider ID");
16748
- const provider = await getProvider(sandbox.provider);
16749
- const [psResult, dfResult, uptimeResult] = await Promise.all([
16750
- 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"),
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
- const parts = line.trim().split(/\s+/);
16757
- return {
16758
- pid: parts[1] || "",
16759
- cpu: parts[2] || "0",
16760
- mem: parts[3] || "0",
16761
- command: parts.slice(10).join(" ") || parts.slice(4).join(" ")
16762
- };
16763
- });
16764
- return ok({
16765
- sandbox_id: sandbox.id,
16766
- status: sandbox.status,
16767
- processes,
16768
- disk: (dfResult.stdout || "").trim(),
16769
- uptime: (uptimeResult.stdout || "").trim()
16770
- });
16771
- } catch (e) {
16772
- return err(e);
16773
- }
16774
- });
16775
- server.tool("snapshot_sandbox", "Capture sandbox filesystem state as a snapshot", {
16776
- id: exports_external2.string().describe("Sandbox ID or partial ID"),
16777
- name: exports_external2.string().optional().describe("Snapshot name")
16778
- }, async (params) => {
16779
- try {
16780
- const sandbox = getSandbox(params.id);
16781
- if (!sandbox.provider_sandbox_id)
16782
- throw new Error("Sandbox has no provider ID");
16783
- const provider = await getProvider(sandbox.provider);
16784
- await provider.pause(sandbox.provider_sandbox_id);
16785
- updateSandbox(sandbox.id, { status: "paused" });
16786
- const snapshot = createSnapshot({
16787
- sandbox_id: sandbox.id,
16788
- provider_sandbox_id: sandbox.provider_sandbox_id,
16789
- provider: sandbox.provider,
16790
- name: params.name
16791
- });
16792
- emitLifecycleEvent(sandbox.id, `Snapshot created: ${snapshot.id}`);
16793
- return ok(snapshot);
16794
- } catch (e) {
16795
- return err(e);
16796
- }
16797
- });
16798
- server.tool("list_snapshots", "List filesystem snapshots", {
16799
- sandbox_id: exports_external2.string().optional().describe("Filter by sandbox ID")
16800
- }, async (params) => {
16801
- try {
16802
- return ok(listSnapshots(params.sandbox_id));
16803
- } catch (e) {
16804
- return err(e);
16805
- }
16806
- });
16807
- server.tool("delete_snapshot", "Delete a snapshot", {
16808
- id: exports_external2.string().describe("Snapshot ID or partial ID")
16809
- }, async (params) => {
16810
- try {
16811
- deleteSnapshot(params.id);
16812
- return ok({ deleted: params.id });
16813
- } catch (e) {
16814
- return err(e);
16815
- }
16816
- });
16817
- server.tool("expose_port", "Forward a sandbox port and get a public URL", {
16818
- sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
16819
- port: exports_external2.number().describe("Port number to expose"),
16820
- protocol: exports_external2.string().optional().describe("Protocol: http or ws (default: http)")
16821
- }, async (params) => {
16822
- try {
16823
- const sandbox = getSandbox(params.sandbox_id);
16824
- if (!sandbox.provider_sandbox_id)
16825
- throw new Error("Sandbox has no provider ID");
16826
- const provider = await getProvider(sandbox.provider);
16827
- const url = await provider.getPublicUrl(sandbox.provider_sandbox_id, params.port, params.protocol);
16828
- if (!exposedPorts.has(sandbox.id))
16829
- exposedPorts.set(sandbox.id, new Map);
16830
- exposedPorts.get(sandbox.id).set(params.port, url);
16831
- return ok({ sandbox_id: sandbox.id, port: params.port, url });
16832
- } catch (e) {
16833
- return err(e);
16834
- }
16835
- });
16836
- server.tool("list_exposed_ports", "List all forwarded ports for a sandbox", {
16837
- sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID")
16838
- }, async (params) => {
16839
- try {
16840
- const sandbox = getSandbox(params.sandbox_id);
16841
- const ports = exposedPorts.get(sandbox.id) ?? new Map;
16842
- const result = Array.from(ports.entries()).map(([port, url]) => ({ port, url }));
16843
- return ok(result);
16844
- } catch (e) {
16845
- return err(e);
16846
- }
16847
- });
16848
- server.tool("close_port", "Stop forwarding a sandbox port", {
16849
- sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
16850
- port: exports_external2.number().describe("Port number to close")
16851
- }, async (params) => {
16852
- try {
16853
- const sandbox = getSandbox(params.sandbox_id);
16854
- exposedPorts.get(sandbox.id)?.delete(params.port);
16855
- return ok({ sandbox_id: sandbox.id, port: params.port, closed: true });
16856
- } catch (e) {
16857
- return err(e);
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
- server.tool("get_network_log", "Get outbound network connections from a sandbox", {
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
- server.tool("watch_file", "Get new content from a file since a previous read (tail -f equivalent)", {
16875
- sandbox_id: exports_external2.string().describe("Sandbox ID or partial ID"),
16876
- path: exports_external2.string().describe("File path to watch"),
16877
- offset: exports_external2.number().optional().describe("Line offset to read from (use next_offset from previous call)"),
16878
- limit: exports_external2.number().optional().describe("Max lines to return (default: 100)")
16879
- }, async (params) => {
16880
- try {
16881
- const sandbox = getSandbox(params.sandbox_id);
16882
- if (!sandbox.provider_sandbox_id)
16883
- throw new Error("Sandbox has no provider ID");
16884
- const provider = await getProvider(sandbox.provider);
16885
- const content = await provider.readFile(sandbox.provider_sandbox_id, params.path, {
16886
- offset: params.offset,
16887
- limit: params.limit ?? 100
16888
- });
16889
- const lines = content.split(`
16890
- `);
16891
- return ok({
16892
- path: params.path,
16893
- content,
16894
- lines_read: lines.length,
16895
- next_offset: (params.offset ?? 0) + lines.length
16896
- });
16897
- } catch (e) {
16898
- return err(e);
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
- server.tool("list_images", "List available pre-warmed sandbox image aliases", {}, async () => {
16902
- try {
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
- 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) => {
16913
- try {
16914
- const db2 = getDatabase();
16915
- db2.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", [params.message, params.email || null, params.category || "general", "0.1.17"]);
16916
- return ok("Feedback saved. Thank you!");
16917
- } catch (e) {
16918
- return err(e);
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
- var transport = new StdioServerTransport;
16922
- registerCloudTools(server, "sandboxes");
16923
- await server.connect(transport);
17021
+ const server = buildServer();
17022
+ const transport = new StdioServerTransport;
17023
+ await server.connect(transport);
17024
+ }
17025
+ main().catch(console.error);