#475

feat: headless sandboxes and an MCP server to drive PentAGI without an LLM key

OpenIshanKulkarni02wants to merge intofrom

What

Adds a flow mode with no agent loop and no LLM provider behind it: a Docker sandbox (same hardened docker.WorkerSpec agents get) that a caller drives directly, so PentAGI's Docker sandboxing is usable with zero LLM provider configured. Alongside it, mcp/ is a small stdio MCP server wrapping both this new surface and the existing flow API, so a tool like Claude Code can drive a PentAGI instance over MCP — the motivating use case.

New REST endpoints (reusing the existing terminal tool's exec/file plumbing, not new exec logic):

POST    /sandboxes/
DELETE  /sandboxes/{sandboxID}
POST    /sandboxes/{sandboxID}/exec
GET/PUT /sandboxes/{sandboxID}/file

Design choices (happy to revisit)

  • One migration adds flows.headless; model_provider_name/model_provider_type carry inert 'headless'/'custom' placeholders rather than a new PROVIDER_TYPE enum value, so GraphQL/gqlgen/frontend stay untouched. A headless flow is a real row in flows (shows up in the normal flows list/UI), but LoadFlowWorker skips it on restart — it never had a FlowWorker to rebuild.
  • /sandboxes/* reuses the existing flows.* privileges rather than adding a new privilege namespace.
  • mcp/ is a standalone Node project (own package.json: @modelcontextprotocol/sdk, zod, both MIT), not wired into frontend/'s dependency tree or the existing license-scan scripts — let me know if you'd rather it live elsewhere or be folded into an existing scope.

I know examples/proposals/mcp_client_integration.md stages MCP work carefully given PR #268's review history (no implicit lifecycle state, explicit operator visibility/control) — that RFC is about the other direction (PentAGI as an MCP client calling external tools). This is PentAGI as something an MCP client drives, a narrower surface, but I'm glad to split this into smaller slices if that's preferred.

Testing

No existing test suite covers this (new feature), but it's verified live, not just compiled:

  • Full lifecycle against a real local Postgres + Docker: create → exec → file read/write → survives a server restart → stop → container actually removed.
  • Driven through real MCP tools/call requests end-to-end, not just the REST layer.
  • Found and fixed two real bugs this way before they shipped: a route that silently no-op'd DELETE (redirect instead of deleting), and an empty placeholder value that 500'd GET /flows/ for every flow once a headless one existed — both now pinned by regression tests (shown failing before the fix, per this repo's testing conventions).
  • go build ./..., go test ./..., go vet ./..., and golangci-lint run (v2.12.2) all clean.
  • swag/gqlgen: no schema change, so only swagger was regenerated (pinned swag v1.8.7 to match the committed output, not @latest, which silently produced an empty paths list on this toolchain — worth knowing about independent of this PR).

Not done here

  • No frontend UI for sandboxes (they're reachable via REST/MCP only; they do show up in the existing flows list/API since they're real flow rows).
  • No installer wizard changes (nothing to configure — no new provider, no new env var).
  • Open to feedback on whether /sandboxes is the right name/shape, or whether this belongs behind a feature flag given it's a new unauthenticated-by-default (well, same Bearer-token auth as everything else) way to get arbitrary command execution in a container — same privilege model as flows, but worth a second look given the dual-use nature of this tool.

🤖 Generated with Claude Code