Skip to content

feat: add run_sandbox_command and get_task_run_status MCP tools#224

Merged
sweetmantech merged 8 commits intotestfrom
sweetmantech/myc-4263-api-mcp-tool-for-sandbox-commands
Feb 16, 2026
Merged

feat: add run_sandbox_command and get_task_run_status MCP tools#224
sweetmantech merged 8 commits intotestfrom
sweetmantech/myc-4263-api-mcp-tool-for-sandbox-commands

Conversation

@sweetmantech
Copy link
Contributor

@sweetmantech sweetmantech commented Feb 16, 2026

Summary

  • Extract shared sandbox creation logic into processCreateSandbox.ts for DRY reuse between REST handler and MCP tools
  • Add run_sandbox_command MCP tool that creates a sandbox and runs a command in it
  • Add get_task_run_status MCP tool that retrieves Trigger.dev task run status by run ID
  • Refactor createSandboxPostHandler.ts to use the shared processCreateSandbox() function

Test plan

  • 7 unit tests for processCreateSandbox (creation with/without command, snapshot usage, error handling)
  • 4 unit tests for registerRunSandboxCommandTool (registration, auth, success, error)
  • 5 unit tests for registerGetTaskRunStatusTool (registration, auth, success, not found, error)
  • All 10 existing createSandboxPostHandler tests still pass after refactor
  • Full test suite: 136 files, 1141 tests passing

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added MCP tools to create sandboxes and run commands with progress tracking.
    • Added an MCP tool to query task run status and monitor command execution.
  • Improvements

    • Streamlined sandbox creation flow: creation is centralized, returns the created sandbox, and includes an optional run identifier when a command is triggered.

Extract shared sandbox creation logic into processCreateSandbox.ts for
DRY reuse between the POST /api/sandboxes handler and the new MCP tools.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@vercel
Copy link
Contributor

vercel bot commented Feb 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
recoup-api Ready Ready Preview Feb 16, 2026 8:04pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Warning

Rate limit exceeded

@sweetmantech has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 59 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Adds sandbox and task-status MCP tools and centralizes sandbox creation into a new processCreateSandbox module; the POST /api/sandboxes handler now delegates to that module and tool registrations call the new sandbox/task registration functions. All changes are internal wiring and new registrations.

Changes

Cohort / File(s) Summary
MCP Tool Registry
lib/mcp/tools/index.ts
Adds import and call to register sandbox tools to include sandbox-related MCP tools during server startup.
Sandbox Tools
lib/mcp/tools/sandbox/index.ts, lib/mcp/tools/sandbox/registerRunSandboxCommandTool.ts
New sandbox tool registration: run_sandbox_command with input validation (Zod), auth via extra.authInfo.accountId, delegates to processCreateSandbox, returns sandbox info and optional runId or errors.
Task Tools
lib/mcp/tools/tasks/index.ts, lib/mcp/tools/tasks/registerGetTaskRunStatusTool.ts
New task tool registration: get_task_run_status accepting runId, authenticating via accountId, retrieving run via retrieveTaskRun, returning success or error results.
Sandbox Creation Flow
lib/sandbox/processCreateSandbox.ts, lib/sandbox/createSandboxPostHandler.ts
Introduces processCreateSandbox API (new types plus function) that creates sandboxes from latest snapshot, maps account→sandbox, optionally triggers run-sandbox-command and returns optional runId. POST handler simplified to call this function and return its result.

Sequence Diagram

sequenceDiagram
    participant Client
    participant MCPServer
    participant Handler as registerRunSandboxCommandTool
    participant ProcessCreate as processCreateSandbox
    participant SnapshotDB as AccountSnapshotDB
    participant SandboxSvc as SandboxService
    participant TaskQueue as TaskQueue

    Client->>MCPServer: run_sandbox_command(command, args, cwd)
    MCPServer->>Handler: invoke handler (with extra.authInfo)
    Handler->>Handler: validate input (Zod) & extract accountId
    alt missing accountId
        Handler-->>MCPServer: return auth error
        MCPServer-->>Client: error
    else accountId present
        Handler->>ProcessCreate: processCreateSandbox(accountId, command, args, cwd)
        ProcessCreate->>SnapshotDB: fetch latest snapshot for account
        SnapshotDB-->>ProcessCreate: snapshot (or none)
        ProcessCreate->>SandboxSvc: create sandbox (from snapshot or fresh)
        SandboxSvc-->>ProcessCreate: SandboxCreatedResponse
        ProcessCreate->>SnapshotDB: insert account↔sandbox mapping
        alt command provided
            ProcessCreate->>TaskQueue: enqueue run-sandbox-command task
            TaskQueue-->>ProcessCreate: runId (or error logged)
        end
        ProcessCreate-->>Handler: SandboxCreatedResponse (+ optional runId)
        Handler-->>MCPServer: success result
        MCPServer-->>Client: sandbox info (+ runId)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🏖️ New sandboxes rise with careful hand,
Commands queued to run at your command,
A status tool checks each task in flight,
Creation now flows clean and light,
Small changes stitched to form a sturdier land.

🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning PR enforces resolveAccountId() in CLAUDE.md but registerGetTaskRunStatusTool.ts violates this standard by using direct extraction instead of the documented pattern. Update registerGetTaskRunStatusTool.ts to use resolveAccountId() with proper error handling, matching registerRunSandboxCommandTool.ts implementation for consistency.
✅ Passed checks (1 passed)
Check name Status Explanation
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into test

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sweetmantech/myc-4263-api-mcp-tool-for-sandbox-commands

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@lib/mcp/tools/sandbox/registerGetTaskRunStatusTool.ts`:
- Around line 38-43: The registerGetTaskRunStatusTool extracts accountId but
doesn't pass it to retrieveTaskRun, allowing cross-account access; update
registerGetTaskRunStatusTool to call retrieveTaskRun(args.runId, accountId) and
change retrieveTaskRun's signature to accept (runId, accountId) and enforce
account-scoped lookup (e.g., include accountId in the query/filter). Update any
other callers of retrieveTaskRun to the new two-arg signature and ensure
tests/usage reflect the account-scoped authorization.

In `@lib/mcp/tools/sandbox/registerRunSandboxCommandTool.ts`:
- Around line 31-32: The code currently extracts accountId directly from
extra.authInfo (extra.authInfo as McpAuthInfo and authInfo?.extra?.accountId) in
registerRunSandboxCommandTool; replace that manual extraction by invoking the
centralized n() resolver (imported from lib/mcp/resolveAccountId.ts) with the
authInfo and an accountIdOverride (undefined or derived from schema), await its
result, check for error and return getToolResultError(error) if present, and
then use the returned accountId; this ensures registerRunSandboxCommandTool uses
the same validation/override logic as other tools like
registerCreateNewArtistTool and registerUpdatePulseTool.

In `@lib/sandbox/processCreateSandbox.ts`:
- Around line 36-39: The call to insertAccountSandbox in processCreateSandbox
silently ignores its result so a created sandbox can become orphaned if the DB
insert fails; update processCreateSandbox to capture the return (e.g., const res
= await insertAccountSandbox(...)), check res.error (or equivalent) and if
present either throw an error or log and perform cleanup (delete the created
sandbox using result.sandboxId) to avoid orphaning; ensure the error path
includes the sandbox id and accountId in the message and that any thrown error
bubbles up from processCreateSandbox.
🧹 Nitpick comments (2)
lib/sandbox/processCreateSandbox.ts (1)

6-13: Consider exporting the input/result types for consumers.

ProcessCreateSandboxInput and ProcessCreateSandboxResult are only accessible within this module. If the MCP tool or handler tests need to reference these types, they'll need to be exported. This is minor now but good hygiene for a shared utility.

lib/sandbox/createSandboxPostHandler.ts (1)

40-45: All errors map to HTTP 400 — consider 500 for internal failures.

processCreateSandbox can fail due to sandbox service errors or database issues, which are server-side faults (5xx), not client errors (4xx). A blanket 400 may mislead API consumers and monitoring systems.

Comment on lines +38 to +43
try {
const result = await retrieveTaskRun(args.runId);

if (!result) {
return getToolResultError(`Task run with ID "${args.runId}" not found.`);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n lib/mcp/tools/sandbox/registerGetTaskRunStatusTool.ts

Repository: recoupable/api

Length of output: 2375


🏁 Script executed:

rg -n "retrieveTaskRun" --type=ts -A10 -B2

Repository: recoupable/api

Length of output: 28433


🏁 Script executed:

fd "\.ts$" lib/mcp/tools/sandbox -type f

Repository: recoupable/api

Length of output: 228


🏁 Script executed:

fd -e ts lib/mcp/tools | head -20

Repository: recoupable/api

Length of output: 415


🏁 Script executed:

cat -n lib/mcp/tools/sandbox/__tests__/registerGetTaskRunStatusTool.test.ts | head -60

Repository: recoupable/api

Length of output: 2298


🏁 Script executed:

rg -n "accountId" lib/mcp/tools --type=ts -A2 -B2 | head -100

Repository: recoupable/api

Length of output: 8285


🏁 Script executed:

cat -n lib/trigger/retrieveTaskRun.ts

Repository: recoupable/api

Length of output: 2975


🏁 Script executed:

rg -n "accountId" lib/trigger --type=ts -B2 -A5 | head -80

Repository: recoupable/api

Length of output: 4331


🏁 Script executed:

rg -n "retrieveTaskRun" lib/tasks --type=ts -B5 -A10

Repository: recoupable/api

Length of output: 10797


🏁 Script executed:

rg -n "Trigger" lib --type=ts | grep -i "config\|auth\|token\|credential" | head -20

Repository: recoupable/api

Length of output: 40


🏁 Script executed:

cat -n lib/trigger/retrieveTaskRun.ts

Repository: recoupable/api

Length of output: 2975


🏁 Script executed:

rg -n "@trigger.dev" lib --type=ts -B2 -A2 | head -50

Repository: recoupable/api

Length of output: 2880


Pass accountId to retrieveTaskRun() to enforce account-scoped authorization.

The tool verifies authentication but doesn't scope the task run query to the authenticated account. Unlike other MCP tools in the codebase (e.g., registerRunSandboxCommandTool, registerGetPulsesTool), the accountId is extracted but never used. Update retrieveTaskRun() to accept an accountId parameter and apply it during retrieval, ensuring each account can only access their own task runs.

🤖 Prompt for AI Agents
In `@lib/mcp/tools/sandbox/registerGetTaskRunStatusTool.ts` around lines 38 - 43,
The registerGetTaskRunStatusTool extracts accountId but doesn't pass it to
retrieveTaskRun, allowing cross-account access; update
registerGetTaskRunStatusTool to call retrieveTaskRun(args.runId, accountId) and
change retrieveTaskRun's signature to accept (runId, accountId) and enforce
account-scoped lookup (e.g., include accountId in the query/filter). Update any
other callers of retrieveTaskRun to the new two-arg signature and ensure
tests/usage reflect the account-scoped authorization.

Comment on lines +36 to +39
await insertAccountSandbox({
account_id: accountId,
sandbox_id: result.sandboxId,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

insertAccountSandbox error is silently ignored — orphaned sandbox risk.

The return value of insertAccountSandbox includes an error field (per insertAccountSandbox.ts), but it's never checked here. If the insert fails, you've created a sandbox that isn't associated with the account — it becomes orphaned and unmanageable. At minimum, throw or log on failure.

🛡️ Proposed fix
-  await insertAccountSandbox({
+  const { error: insertError } = await insertAccountSandbox({
     account_id: accountId,
     sandbox_id: result.sandboxId,
   });
+
+  if (insertError) {
+    console.error("Failed to associate sandbox with account:", insertError);
+    throw new Error("Failed to save sandbox association");
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await insertAccountSandbox({
account_id: accountId,
sandbox_id: result.sandboxId,
});
const { error: insertError } = await insertAccountSandbox({
account_id: accountId,
sandbox_id: result.sandboxId,
});
if (insertError) {
console.error("Failed to associate sandbox with account:", insertError);
throw new Error("Failed to save sandbox association");
}
🤖 Prompt for AI Agents
In `@lib/sandbox/processCreateSandbox.ts` around lines 36 - 39, The call to
insertAccountSandbox in processCreateSandbox silently ignores its result so a
created sandbox can become orphaned if the DB insert fails; update
processCreateSandbox to capture the return (e.g., const res = await
insertAccountSandbox(...)), check res.error (or equivalent) and if present
either throw an error or log and perform cleanup (delete the created sandbox
using result.sandboxId) to avoid orphaning; ensure the error path includes the
sandbox id and accountId in the message and that any thrown error bubbles up
from processCreateSandbox.

The tool is general-purpose (works for any Trigger.dev run), not
sandbox-specific, so it belongs with the other task tools.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
sweetmantech and others added 2 commits February 16, 2026 14:49
Replace manual authInfo extraction with the centralized resolveAccountId
resolver, consistent with registerCreateNewArtistTool and
registerUpdatePulseTool.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add explicit warning against manual authInfo extraction and include the
full error-handling pattern with both error and null-accountId checks.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.

In `@lib/mcp/tools/tasks/registerGetTaskRunStatusTool.ts`:
- Around line 29-30: The code manually extracts accountId by casting
extra.authInfo to McpAuthInfo and reading authInfo.extra.accountId; replace this
with a call to resolveAccountId(extra.authInfo) so the shared helper handles
resolution and keeps behavior consistent. Update registerGetTaskRunStatusTool to
import/use resolveAccountId and remove the manual cast/dig pattern (references:
extra.authInfo, McpAuthInfo, resolveAccountId, registerGetTaskRunStatusTool) so
account IDs are derived via the helper used elsewhere.

Replace manual authInfo extraction with the centralized resolveAccountId
resolver, consistent with all other MCP tools.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace duplicate ProcessCreateSandboxInput interface with the existing
SandboxBody type from validateSandboxBody.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
SandboxBody includes AuthContext fields (orgId, authToken) that MCP tool
callers don't have. Pick only the fields the function actually uses.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@sweetmantech sweetmantech merged commit 226223b into test Feb 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments