Command execution tools
These tools let an AI agent run shell commands and reviewed scripts on one or more servers and review what has already run. They back requests like “run df -h on web-01 and tell me the result,” “deploy this config to every server in the staging fleet,” or “run the deploy script I just uploaded.”
Command execution always requires ACL permission on the caller—see Command ACLs for how those rules are managed. When the caller authenticates through MCP OAuth or a browser session, the server also requires an active Work Session that includes the command scope; static API tokens and service tokens bypass this requirement. Pass work_session_id to link a command to a specific session, or set the ALPACON_WORK_SESSION environment variable to supply a default when the argument is omitted.
All tools accept workspace (required) and region (optional, defaults to the configured region). These common parameters are omitted from the tables below.
Summary
| Tool | Description | Access |
|---|---|---|
list_commands | List recent command execution history | Read-only |
execute_command | Run a command on a server and wait for the result | Additive |
execute_command_multi_server | Run the same command on multiple servers | Additive |
execute_file | Run a reviewed script that’s already on a server | Additive |
state_command_purpose | Answer a command’s pending purpose request | Additive |
Command history
list_commands
Lists recent commands executed on servers, including status, output, and timestamps. Filter by server_id to scope the history to a single server. Use this to review what has already run before executing something new, or to check the outcome of a command submitted earlier.
Access: Read-only
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
server_id | string | No | Filter results to commands run on this server |
limit | integer | No | Maximum number of results to return (default: 20) |
Example
“Show me the last 10 commands run on the db-01 server”
The agent calls list_commands with server_id set to db-01’s UUID and limit=10, returning each command’s status, output, and timestamp.
Running commands
execute_command
Runs a shell command on a server and waits for the result, returning stdout, stderr, and exit code in a single call. This is the recommended way to run a command—list_commands only reviews history, it does not execute anything. The tool polls for completion up to timeout seconds (default 300, i.e. 5 minutes), resetting the deadline while the command is actively running, with a hard cap at three times the timeout to prevent indefinite waiting.
Supports dependency chains (run_after), scheduled execution (scheduled_at), and stdin input (data). If the command reaches a terminal non-result state—denied, rejected, or stuck—the tool stops polling and returns an error immediately instead of waiting out the timeout.
If the command itself needs human approval before it can run at all (a Work Session gate, separate from the sudo handling below), the tool returns a structured pending-approval result with status awaiting_approval. Do not re-run the command while it is pending: resubmitting requires its own separate approval and risks double-executing the command once the original request is approved. Wait for the approval, then check the outcome with list_commands.
If the command needs sudo and hits a non-interactive denial, the tool always attaches a free-text hint describing the denial category. For the three categories a human can resolve out-of-band—a missing Work Session sudo policy, a required MFA step-up, or a pending sudo approval—it additionally attaches a structured pending-approval block alongside that hint, so an agent can branch on a stable category instead of parsing prose. A human can add the command to the session’s sudo policy via the Alpacon web console or the CLI’s work-session update --sudo, complete the step-up, or approve the request, then retry. SUDO_RISK_DENIED is excluded from this—it is a hard denial from runtime risk assessment, not retryable or approvable, and only the free-text hint is returned for it.
A command that isn’t routine may come back with a result of purpose_required instead of running—the server is asking why this particular command is needed on this host. Passing purpose up front (a sentence or two, stating something specific to this host rather than restating context the caller already gave) can let the command clear immediately instead of pausing; if you omit it and the server asks anyway, answer with state_command_purpose within about a minute.
Access: Additive
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
server_id | string | Yes | Target server ID |
command | string | Yes | Shell command to execute |
shell | string | No | Shell to run the command in (default: system) |
username | string | No | System user to run the command as |
groupname | string | No | System group to run the command as (default: alpacon) |
env | object | No | Environment variables to set for the command |
run_after | array | No | Command IDs this command should run after |
scheduled_at | string | No | Schedule the command for a future time instead of running immediately |
data | string | No | Data to pass to the command’s stdin |
timeout | integer | No | Seconds to wait for completion before timing out (default: 300) |
work_session_id | string | No | Work Session to link this command to, for audit. Falls back to ALPACON_WORK_SESSION if omitted |
purpose | string | No | Why this command is needed on this host, in a sentence or two. Answers a purpose_required result up front instead of pausing for it |
If command contains an inline credential—a secret-like KEY=VALUE pair, a password flag, or a connection string with an embedded password—the server rejects the call with command_inline_credential before the command is stored. Pass secrets through env instead; it’s write-only and never appears in command history or audit logs.
For a script, a heredoc, or anything you’d otherwise run as bash /path/to/file, use execute_file instead—an approved script can be re-run unchanged without pausing for another approval, which a plain shell line never can.
Example
“Run
df -hon web-01 and show me the disk usage”
The agent calls execute_command with server_id set to web-01’s UUID and command="df -h", waits for the result, and reports stdout back to the user.
“Restart nginx on api-02 inside my current work session”
The agent passes work_session_id (or relies on ALPACON_WORK_SESSION) so the restart is recorded against that session for audit.
execute_command_multi_server
Runs the same shell command on multiple servers, either in parallel (default) or sequentially with parallel=false. Returns per-server success/failure status. This tool only submits the commands—it does not wait for results; use list_commands to check on each one afterward.
Use this for fleet-wide operations: deploying a config change, checking a version, or running the same diagnostic across many servers in one call instead of repeated execute_command calls.
Access: Additive
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
server_ids | array | Yes | Target server IDs |
command | string | Yes | Shell command to execute |
shell | string | No | Shell to run the command in (default: system) |
username | string | No | System user to run the command as |
groupname | string | No | System group to run the command as (default: alpacon) |
env | object | No | Environment variables to set for the command |
parallel | boolean | No | Submit to all servers concurrently (default: true); set false to submit sequentially |
work_session_id | string | No | Work Session to link these commands to, for audit. Falls back to ALPACON_WORK_SESSION if omitted |
Example
“Run
systemctl status nginxon all three web servers”
The agent calls execute_command_multi_server with server_ids set to the three UUIDs and command="systemctl status nginx", then reports which servers succeeded and which failed.
Verified script execution
execute_file
Run a script that’s already on the server, as a reviewed execution: you submit its exact contents along with the path, and the run only proceeds if the bytes on the host’s disk still hash to what you submitted. Prefer this over execute_command for anything longer than a one-off line—a deploy script, a heredoc, anything you’d otherwise run as bash /path/to/file.
The advantage over a plain shell line shows up on repeat runs: once a reviewer approves a script, an unchanged re-run of it—same bytes, same server, same account, same arguments—can proceed without pausing for another approval. A shell line passed to execute_command never earns that, because nothing about a path alone proves what will actually run. Approval covers exactly those bytes, on that server, as that account, with those arguments; changing even one byte sends the script back for review. The interpreter, its libraries, and anything the script fetches or runs at runtime are the script’s own responsibility—only this entry point is checked.
The file must already exist at path on the target host—upload it first, e.g. with webftp_upload_content—and content must be byte-for-byte identical to what’s on disk, including any trailing newline; a mismatch fails the run rather than guessing at what was meant. path and interpreter must be absolute paths (e.g. /opt/deploy.sh, /bin/bash); a bare command name like bash is rejected. Put pipes, redirection, &&, and environment variables inside the script itself—free-form shell composition around a reviewed file isn’t available on this path; use execute_command for a one-off composed command instead.
Otherwise it behaves like execute_command: sudo inside the script is subject to the same approval and denial handling, a result of awaiting_approval means a human needs to act before it runs, and a result asking for a purpose is answered with state_command_purpose.
Access: Additive
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
server_id | string | Yes | Target server ID |
path | string | Yes | Absolute path to the script on the server. Must already exist there |
content | string | Yes | The script’s exact contents, byte-for-byte as they are on disk (up to 64 KB). This is what gets reviewed—it’s never uploaded to the server |
interpreter | string | No | Absolute path to the interpreter (default: /bin/bash) |
args | array | No | Arguments to pass to the script |
username | string | No | System user to run the script as |
groupname | string | No | System group to run the script as (default: alpacon) |
run_after | array | No | Command IDs this run should run after |
scheduled_at | string | No | Schedule the run for a future time instead of running immediately |
timeout | integer | No | Seconds to wait for completion before timing out (default: 300) |
work_session_id | string | No | Work Session to link this run to, for audit. Falls back to ALPACON_WORK_SESSION if omitted |
purpose | string | No | Why this run is needed on this host. Answers a purpose request up front instead of pausing for it |
Example
“Run the deploy script I just uploaded to /opt/deploy.sh on web-01”
The agent calls execute_file with server_id set to web-01’s UUID, path="/opt/deploy.sh", and content set to the script’s exact text. Once a reviewer approves that script for that server, later unchanged runs proceed without another approval round-trip.
state_command_purpose
Answer a pending purpose request on a command, then wait for the outcome. Use this when execute_command or execute_file returns a result asking for a purpose—the command_id to use here is in that response. The command is re-evaluated once with your purpose attached, and this call returns whatever that produces: the command’s output if it clears, a pending-approval result if a human is still needed, or an error if it’s denied outright.
Answer promptly—the request expires in about a minute—and only once: a late or second answer is refused, and only the caller that submitted the original command may answer for it. State something concrete and specific to this host; repeating context the caller already gave doesn’t change the outcome.
Access: Additive
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
command_id | string | Yes | Command ID from the result that asked for a purpose |
purpose | string | Yes | Why this command is needed on this host |
timeout | integer | No | Seconds to wait for the re-evaluated result (default: 300) |
Example
“The restart command is asking why—tell it we’re clearing a memory leak reported in last night’s incident”
The agent calls state_command_purpose with the pending command_id and purpose="Restarting nginx to clear a memory leak reported in incident #482", then reports the outcome once the command runs.
- Work Sessions: create and manage the Work Session required for OAuth and browser-based command execution
- Access control & approvals: manage the command ACL rules that gate
execute_command - File transfer tools: upload and download files to the same servers