@aicommander/mcp
Advanced tools
+74
-28
@@ -59,6 +59,9 @@ #!/usr/bin/env node | ||
| longer than a few minutes. Jobs continue after the call and conversation end. | ||
| Surviving an agent restart differs per platform: on macOS a job survives even a | ||
| hard kill of the agent; on Windows it survives a graceful quit but is LOST if the | ||
| agent's process tree is killed ('End task', a crash, an update path that kills the | ||
| tree); on Linux restarting or upgrading the agent's systemd service stops its jobs. | ||
| Surviving an agent restart differs per platform: on macOS a job reparents to PID 1 | ||
| and is out of reach of anything aimed at the app; on Windows it survives the agent | ||
| process dying by itself (a crash, or a taskkill /F /IM of that one process without | ||
| /T) but is LOST to a TREE kill ('End task', taskkill /T, an installer that stops | ||
| the app and everything it started) — treat an auto-update as a tree kill unless | ||
| you know that machine's installer does otherwise; on Linux restarting or upgrading the agent's systemd | ||
| service stops its jobs. | ||
@@ -103,6 +106,10 @@ Docs: https://aicommander.dev/docs/#jobs | ||
| "SHELL DIALECT — CHECK `platform` BEFORE YOU WRITE A COMMAND: list_machines and session_status " + | ||
| "report each machine's platform ('darwin', 'linux', 'win32'). POSIX machines run commands through " + | ||
| "`/bin/sh -c`; Windows machines run them through cmd.exe. A POSIX one-liner sent to Windows does " + | ||
| "not fail loudly — `;` is not a separator there, so `echo a ; echo b` prints the rest as literal " + | ||
| "text and exits 0. Write the dialect the machine actually speaks.\n\n" + | ||
| "report each machine's platform ('darwin', 'linux', 'win32'). By default POSIX machines run " + | ||
| "commands through `/bin/sh -c` and Windows machines through cmd.exe. A POSIX one-liner sent to " + | ||
| "Windows does not fail loudly — `;` is not a separator there, so `echo a ; echo b` prints the rest " + | ||
| "as literal text and exits 0. Write the dialect the machine actually speaks, or pass remote_exec's " + | ||
| "`shell` argument to pick another interpreter explicitly: `powershell` on Windows, `bash` on " + | ||
| "macOS/Linux. `shell` is only accepted by remote_exec (not by remote_job_start, where it is " + | ||
| "rejected), and a value the target machine cannot run is always rejected rather than silently " + | ||
| "replaced by the default.\n\n" + | ||
| "MACHINE NOTES — DON'T RE-EXPLORE A MACHINE YOU ALREADY KNOW: with an API key (AICOMMANDER_TOKEN) " + | ||
@@ -172,9 +179,15 @@ "set and the machine online, session_status returns the path of a note file kept ON that machine " + | ||
| "after the network drops and after this conversation ends.\n\n" + | ||
| "JOB SURVIVAL ACROSS AN AGENT RESTART differs per platform, and it matters when you are choosing " + | ||
| "where to put a multi-hour run: on macOS a job survives even a hard kill of the agent (it reparents " + | ||
| "to PID 1); on Windows it survives a graceful quit of the app but is LOST if the agent's process " + | ||
| "tree is killed — 'End task', a crash, or an update path that kills the tree — so warn the user " + | ||
| "before committing a long training run to a Windows box; on Linux the agent runs as a systemd " + | ||
| "service and its jobs stay inside that service's control group, so stopping, restarting or " + | ||
| "upgrading the service stops running jobs too.", | ||
| "JOB SURVIVAL ACROSS AN AGENT RESTART — a job outlives the agent PROCESS on every platform; what " + | ||
| "differs is what can still take it down, and it matters when you are choosing where to put a " + | ||
| "multi-hour run. macOS: the job reparents to PID 1, which puts it out of reach of ANYTHING aimed at " + | ||
| "the app — a crash, a hard kill, even an explicit kill of the whole process tree. Windows: the job " + | ||
| "survives the agent process dying by itself — a crash, or a `taskkill /F /IM` of that one process " + | ||
| "without `/T` — and keeps writing its log straight through; what it does NOT survive is a TREE kill " + | ||
| "(Task Manager's 'End task', `taskkill /T`, or an installer that stops the app and everything it " + | ||
| "started), because Windows never reparents. Treat an auto-update as a tree kill unless you know that " + | ||
| "machine's installer does otherwise — the silent updater runs the installer, which stops the running " + | ||
| "app before replacing its files — so make an unattended Windows run resumable and check " + | ||
| "remote_job_status afterwards. Linux: the agent runs as a systemd service and its jobs " + | ||
| "stay inside that service's control group, so stopping, restarting or upgrading the service stops " + | ||
| "running jobs too — the one platform where an agent upgrade really does end a job.", | ||
| inputSchema: { | ||
@@ -186,3 +199,3 @@ code: z | ||
| .string() | ||
| .describe("Shell command to execute. WHICH SHELL DEPENDS ON THE MACHINE'S OS, and the schemas cannot tell you which — read `platform` from list_machines or session_status first ('darwin'/'linux' vs 'win32'). POSIX machines run the command via `/bin/sh -c`. Windows machines run it via cmd.exe, where POSIX habits fail in ways that LOOK like success: `;` is not a command separator, so `echo a ; echo b` prints the rest of the line as literal text and still exits 0; POSIX tools are simply absent (`ls -la` → \"'ls' is not recognized as an internal or external command\"); heredocs do not exist (`cat > f <<'EOF'` → \"<< was unexpected at this time.\"). On Windows: chain steps with `&&`, and to write a script file wrap it explicitly, e.g. `powershell -NoProfile -Command \"...\"`. A MULTI-LINE command is REJECTED on Windows (it used to silently run only the first line and return 0) — put the steps on one line joined with `&&`."), | ||
| .describe("Shell command to execute. WHICH SHELL DEPENDS ON THE MACHINE'S OS, and the schemas cannot tell you which — read `platform` from list_machines or session_status first ('darwin'/'linux' vs 'win32'). POSIX machines run the command via `/bin/sh -c`. Windows machines run it via cmd.exe, where POSIX habits fail in ways that LOOK like success: `;` is not a command separator, so `echo a ; echo b` prints the rest of the line as literal text and still exits 0; POSIX tools are simply absent (`ls -la` → \"'ls' is not recognized as an internal or external command\"); heredocs do not exist (`cat > f <<'EOF'` → \"<< was unexpected at this time.\"). On Windows: either chain steps with `&&` and keep the whole thing on ONE line (a multi-line command is REJECTED there — it used to silently run only the first line and return 0), or pass `shell: \"powershell\"` and write PowerShell instead, which accepts `;`, multi-line scripts and here-strings. `shell` is the supported way to change interpreter; a value the machine cannot run is rejected rather than ignored."), | ||
| cwd: z | ||
@@ -200,8 +213,12 @@ .string() | ||
| .describe("Timeout in milliseconds: minimum 1000 (1 s), default 300000 (5 min), maximum 3600000 (1 hr). Validated, not clamped — a value outside the range is rejected with an error. In particular `0` is NOT 'no timeout': it is below the minimum and used to be raised silently to 1000, killing the command after one second. Omit the field to get the default."), | ||
| shell: z | ||
| .enum(["sh", "bash", "cmd", "powershell"]) | ||
| .optional() | ||
| .describe("Which interpreter runs the command. Omit it for the machine's default — `/bin/sh -c` on 'darwin'/'linux', `cmd.exe` on 'win32' — which is what every call got before this argument existed. LEAVING THE FIELD OUT is the only way to ask for that default: `shell: null` is a supplied value that names no interpreter, so it is REJECTED rather than answered with whichever shell the machine happens to default to. Windows machines accept `cmd` and `powershell`; macOS/Linux machines accept `sh` and `bash`. A value the target cannot run (e.g. `powershell` on a Mac, or a misspelling) is REJECTED with a message listing what that machine does accept — it is never quietly replaced by the default, so if the call succeeds the command really did run in the shell you asked for. `powershell` is Windows PowerShell 5.1, run as `-NoProfile -NonInteractive`, and it is the answer to everything cmd.exe makes painful: `;` works as a separator, `Get-ChildItem`/`ls` exist, and a MULTI-LINE script IS allowed (unlike cmd, where a line break is rejected) because the agent hands PowerShell the script base64-encoded rather than on a command line. That encoding costs size: a PowerShell script is capped at roughly 3000 characters here, and a longer one is rejected rather than truncated — write it to a .ps1 file in pieces and run `powershell -NoProfile -File <path>` if you need more. `bash` (POSIX) buys you arrays, `[[ ]]`, and `pipefail`, which `/bin/sh` on Debian-family Linux does not have. Cannot be combined with `elevated: true` — that combination is rejected, not ignored. Machines running an AI Commander too old to understand this argument REFUSE the call outright rather than running the default shell behind your back; update the agent there, or drop the argument."), | ||
| elevated: z | ||
| .boolean() | ||
| .optional() | ||
| .describe("Run as root (macOS) / LocalSystem (Windows) via the privileged helper. Account-only; only works on mac/Windows machines with the helper installed. Most commands do NOT need this."), | ||
| .describe("Run as root (macOS) / LocalSystem (Windows) via the privileged helper. Account-only; only works on mac/Windows machines with the helper installed. Most commands do NOT need this. NOT accepted together with `shell` — that combination is rejected with an error rather than silently dropped, because the elevated path runs through a signed capability that has no shell field."), | ||
| }, | ||
| }, async ({ code, command, cwd, env, timeout_ms, elevated }) => { | ||
| }, async ({ code, command, cwd, env, timeout_ms, elevated, shell }) => { | ||
| const res = await fetch(MCP_URL, { | ||
@@ -220,3 +237,3 @@ method: "POST", | ||
| name: "remote_exec", | ||
| arguments: { code, command, cwd, env, timeout_ms, elevated }, | ||
| arguments: { code, command, cwd, env, timeout_ms, elevated, shell }, | ||
| }, | ||
@@ -497,9 +514,19 @@ }), | ||
| "drops and after this conversation ends.\n\n" + | ||
| "SURVIVING AN AGENT RESTART — this differs per platform, so check the machine's `platform` before committing " + | ||
| "a multi-hour run to it. macOS: the job survives even a hard kill of the agent (it reparents to PID 1). " + | ||
| "Windows: it survives a graceful quit of the app, but is LOST if the agent's process tree is killed — 'End " + | ||
| "task', a crash, or an update path that kills the tree; a Windows box is therefore not a safe place for an " + | ||
| "unattended overnight training run without telling the user that risk. Linux: the agent runs as a systemd " + | ||
| "service and its jobs stay inside that service's control group, so stopping or restarting the service — " + | ||
| "including an agent upgrade — stops running jobs too.\n\n" + | ||
| "SURVIVING AN AGENT RESTART — a job outlives the agent PROCESS on every platform; what differs is what can " + | ||
| "still take it down, so check the machine's `platform` before committing a multi-hour run to it. macOS: the " + | ||
| "job reparents to PID 1, which puts it out of reach of ANYTHING aimed at the app — a crash, a hard kill, even " + | ||
| "an explicit kill of the whole process tree; short of killing the job itself or the machine going down, " + | ||
| "nothing stops it. Windows: the job survives the agent process dying BY ITSELF — a crash, or a kill aimed at " + | ||
| "that one process (`taskkill /F /IM \"AI Commander.exe\"`, no `/T`) — measured running straight through such " + | ||
| "a kill with no gap in its output, and the agent picks it up again when it comes back. What it does NOT " + | ||
| "survive is a TREE kill: Task Manager's 'End task', `taskkill /T`, or an installer that stops the app and " + | ||
| "everything it started — Windows never reparents, so the job stays inside the app's tree and goes down with " + | ||
| "it. TREAT AN AUTO-UPDATE AS A TREE KILL unless you know that machine's installer does otherwise: the silent " + | ||
| "updater runs the installer, and the installer stops the running app before it replaces its files — older " + | ||
| "ones do that with a tree kill, which takes running jobs with it. Updates arrive on their own schedule, " + | ||
| "nobody has to be at the machine, so before leaving a multi-hour run unattended on Windows make it RESUMABLE " + | ||
| "(checkpoint to disk), and afterwards confirm with remote_job_status instead of assuming it ran through. " + | ||
| "Linux: the agent runs as a systemd service and its jobs " + | ||
| "stay inside that service's control group, so stopping or restarting the service — including an agent " + | ||
| "upgrade — stops running jobs too, so finish or checkpoint long runs before upgrading a Linux agent.\n\n" + | ||
| "Name the machine with `code` exactly as the user said it — an AIC- session code (e.g. AIC-XYZ-1234) or, when " + | ||
@@ -534,3 +561,3 @@ "authenticated with an API key, a saved alias or hostname such as 'wearfits-m3'; if the user's text contains " + | ||
| .string() | ||
| .describe("Shell command to run as the job. WHICH SHELL DEPENDS ON THE MACHINE'S OS — read `platform` from list_machines or session_status first: POSIX machines ('darwin'/'linux') run it via `/bin/sh -c`, Windows machines ('win32') via cmd.exe. On Windows `;` is not a command separator (`echo a ; echo b` prints the rest as literal text and exits 0 — a silent false success), POSIX tools like `ls` do not exist, and heredocs are a syntax error; chain steps with `&&` on ONE line (a multi-line command is rejected), and wrap script-writing explicitly, e.g. `powershell -NoProfile -Command \"...\"`. Use absolute paths or set `cwd`: the job does not inherit any state from earlier remote_exec calls."), | ||
| .describe("Shell command to run as the job. WHICH SHELL DEPENDS ON THE MACHINE'S OS — read `platform` from list_machines or session_status first: POSIX machines ('darwin'/'linux') run it via `/bin/sh -c`, Windows machines ('win32') via cmd.exe. On Windows `;` is not a command separator (`echo a ; echo b` prints the rest as literal text and exits 0 — a silent false success), POSIX tools like `ls` do not exist, and heredocs are a syntax error; chain steps with `&&` on ONE line (a multi-line command is rejected), and wrap script-writing explicitly, e.g. `powershell -NoProfile -Command \"...\"`. A job ALWAYS runs in the machine's default shell: unlike remote_exec there is no `shell` argument here, and passing one is rejected rather than ignored. Use absolute paths or set `cwd`: the job does not inherit any state from earlier remote_exec calls."), | ||
| cwd: z | ||
@@ -552,4 +579,23 @@ .string() | ||
| .describe("Reserve this NVIDIA device (the `index` from the machine's GPU list, as reported by list_machines / session_status) exclusively for the job and set CUDA_VISIBLE_DEVICES accordingly. Refused with `gpu_busy` if another job already holds that card. When the machine's GPU list is known, an index that is not on it is REJECTED — an out-of-range index used to start a phantom job with CUDA_VISIBLE_DEVICES pointing at nothing, which then failed deep inside the training script. Read the GPU list before choosing."), | ||
| // DECLARED SO THEY CAN BE REFUSED, not because a job supports them. | ||
| // | ||
| // This bridge validates with zod, whose default is to STRIP any key the | ||
| // schema does not name. An undeclared `shell` / `elevated` would therefore | ||
| // be DELETED here, the relay would never see it, and the job would start in | ||
| // the machine's default interpreter (or as the ordinary user) and come back | ||
| // with a normal jobId — a silent false success, and the precise failure mode | ||
| // the `shell` work exists to eliminate. The Worker's own JSON-Schema copy of | ||
| // this tool does not list them because JSON Schema passes unknown properties | ||
| // through untouched; here they must be listed for the documented refusal to | ||
| // actually reach the caller. | ||
| shell: z | ||
| .enum(["sh", "bash", "cmd", "powershell"]) | ||
| .optional() | ||
| .describe("NOT SUPPORTED FOR JOBS — accepted by this schema only so that asking for it is REJECTED with an explanation instead of being silently dropped. A job always runs in the machine's default shell (`/bin/sh` on 'darwin'/'linux', cmd.exe on 'win32'), because the job manager has no shell selection. Do not pass it: write the command for the default shell, or invoke the interpreter inside the command itself (`powershell -NoProfile -File C:\\path\\to\\script.ps1`, `bash -c '…'`). For a SHORT command in a chosen shell, use remote_exec, which does take `shell`."), | ||
| elevated: z | ||
| .boolean() | ||
| .optional() | ||
| .describe("NOT SUPPORTED FOR JOBS — accepted by this schema only so that asking for it is REJECTED with an explanation instead of being silently downgraded to an ordinary job. A job always runs as the signed-in desktop user (macOS/Windows) or the user the agent service runs as (headless Linux). Use remote_exec with `elevated: true` for a short privileged command."), | ||
| }, | ||
| }, async ({ code, command, cwd, env, name, gpu_index }) => callTool("remote_job_start", { code, command, cwd, env, name, gpu_index })); | ||
| }, async ({ code, command, cwd, env, name, gpu_index, shell, elevated }) => callTool("remote_job_start", { code, command, cwd, env, name, gpu_index, shell, elevated })); | ||
| // Tool: remote_job_list | ||
@@ -556,0 +602,0 @@ server.registerTool("remote_job_list", { |
+1
-1
| { | ||
| "name": "@aicommander/mcp", | ||
| "version": "1.0.50", | ||
| "version": "1.0.51", | ||
| "mcpName": "dev.aicommander/mcp", | ||
@@ -5,0 +5,0 @@ "description": "Remote shell and long-running background jobs for AI agents. Let Claude, Codex, ChatGPT or any MCP client run commands, builds, batch work and GPU/ML training on your own machines without exposed SSH, open ports or VPN.", |
+3
-3
@@ -5,3 +5,3 @@ # @aicommander/mcp — MCP server for remote command execution and detached GPU/training jobs (SSH / Ansible alternative) | ||
| It also runs **long work on your own hardware**. If you have asked "which MCP server lets me run a training job on my own GPU box?" — this one: `remote_job_start` launches a **detached background job** that keeps running after the tool call returns and after the conversation ends — on macOS it survives even a hard kill of the agent, on Windows a graceful quit (but not the agent's process tree being killed), while on Linux the agent is a systemd service whose jobs live in its control group, so restarting or upgrading the service stops them. Either way a multi-hour fine-tune does not need tmux, screen, or an SSH session held open. `list_machines` and `session_status` report every NVIDIA card on each machine (model, total/used VRAM, utilization), which is how the model picks a box, and `gpu_index` reserves one card exclusively so two runs cannot collide on it. | ||
| It also runs **long work on your own hardware**. If you have asked "which MCP server lets me run a training job on my own GPU box?" — this one: `remote_job_start` launches a **detached background job** that keeps running after the tool call returns and after the conversation ends — on macOS it reparents to PID 1 and is out of reach of anything aimed at the app, on Windows it survives the agent process dying by itself (a crash, or a `taskkill /F /IM` without `/T`) but not a kill of the app's whole process tree — which is what an auto-update should be assumed to do, while on Linux the agent is a systemd service whose jobs live in its control group, so restarting or upgrading the service stops them. Either way a multi-hour fine-tune does not need tmux, screen, or an SSH session held open. `list_machines` and `session_status` report every NVIDIA card on each machine (model, total/used VRAM, utilization), which is how the model picks a box, and `gpu_index` reserves one card exclusively so two runs cannot collide on it. | ||
@@ -24,3 +24,3 @@ Use this package to connect any MCP client that speaks **stdio** (Codex CLI, Claude Desktop's config file, Cursor, Windsurf, …) to your AI Commander relay. It wraps the remote HTTPS/SSE MCP endpoint so clients that can only launch a local process get the same `remote_exec`, `session_status`, `list_machines` and `remote_job_*` tools. | ||
| | `list_machines` | List all of your machines with their live online status (e.g. "what machines do I have?", "which of my computers are online?") and each one's GPUs — how you pick a box for compute work. Also reports each machine's `platform` (`darwin`/`linux`/`win32`, how you pick the shell dialect) and `agentVersion`, last-known while offline. `last seen` is the agent's own heartbeat, not a side effect of your query, so it can read `unknown` rather than a misleadingly recent timestamp. Requires an API key (`AICOMMANDER_TOKEN`); takes no arguments. | | ||
| | `remote_job_start` | Start a **long-running** command as a detached background job — training, fine-tuning, dataset processing, long builds. Returns a `jobId` immediately and keeps running after the call and the conversation. Across an agent restart: macOS survives even a hard kill, Windows survives a graceful quit but **loses the job if the agent's process tree is killed** ("End task", a crash, an update path that kills the tree), Linux stops with the systemd service. `gpu_index` reserves one NVIDIA card exclusively (sets `CUDA_VISIBLE_DEVICES`; a second job wanting that card is refused `gpu_busy`, and an index that is not on the machine's known GPU list is rejected instead of starting a phantom job). | | ||
| | `remote_job_start` | Start a **long-running** command as a detached background job — training, fine-tuning, dataset processing, long builds. Returns a `jobId` immediately and keeps running after the call and the conversation. A job outlives the agent process everywhere; what differs is what can still take it down: macOS reparents to PID 1 and is out of reach of anything aimed at the app (even a tree kill), Windows survives the agent process dying by itself (a crash, or a `taskkill /F /IM` without `/T`) but **loses the job to a tree kill** ("End task", `taskkill /T`, an installer that stops the app and everything it started — assume an auto-update is one unless that machine's installer is known to do otherwise), Linux stops with the systemd service. `gpu_index` reserves one NVIDIA card exclusively (sets `CUDA_VISIBLE_DEVICES`; a second job wanting that card is refused `gpu_busy`, and an index that is not on the machine's known GPU list is rejected instead of starting a phantom job). | | ||
| | `remote_job_list` | The machine's jobs: running now, plus finished ones still retained (7 days). Answers "what is running on the GPU box?" and recovers a `jobId` from an earlier conversation. `limit` returns only the newest N (default 20) so a busy machine doesn't dump dozens of entries into your context; the reply says how many were omitted. | | ||
@@ -38,3 +38,3 @@ | `remote_job_status` | One job's state — `running`, `exited` (with the exit code), or `unknown` (the process is gone with no exit code recorded — a SIGKILL, the OOM killer, an escalated cancel, Windows `taskkill /F`, or the agent going down all leave no exit marker; the outcome cannot be determined, never report it as success). Poll every few minutes for a training run, not in a tight loop. | | ||
| > **Short work vs long work.** `remote_exec` has two caps that behave differently. **1 hour** of wall-clock time is a hard kill: the command's process tree is terminated and a training loop dies mid-run. **1 MiB of total output** only **truncates the reply** — the relay sends a best-effort stop, but it races the command over several network hops and usually loses, so the command often runs to completion and returns its real exit code. Never read a truncated reply as "the work stopped"; its side effects happened. Anything longer or chattier belongs in `remote_job_start`, which has neither cap, writes its full output to a file on the machine, and keeps running after the call returns and after the conversation ends (across an agent restart: macOS survives a hard kill, Windows loses the job if the agent's process tree is killed, Linux stops with the systemd service). Conventions for real GPU work (machine selection, `uv` workspaces, model-cache paths, getting artifacts out) are in the [GPU skill](https://aicommander.dev/skill/gpu/SKILL.md). | ||
| > **Short work vs long work.** `remote_exec` has two caps that behave differently. **1 hour** of wall-clock time is a hard kill: the command's process tree is terminated and a training loop dies mid-run. **1 MiB of total output** only **truncates the reply** — the relay sends a best-effort stop, but it races the command over several network hops and usually loses, so the command often runs to completion and returns its real exit code. Never read a truncated reply as "the work stopped"; its side effects happened. Anything longer or chattier belongs in `remote_job_start`, which has neither cap, writes its full output to a file on the machine, and keeps running after the call returns and after the conversation ends (across an agent restart: macOS reparents to PID 1 and survives anything aimed at the app, Windows survives the agent process dying by itself but loses the job to a tree kill — assume an auto-update is one, Linux stops with the systemd service). Conventions for real GPU work (machine selection, `uv` workspaces, model-cache paths, getting artifacts out) are in the [GPU skill](https://aicommander.dev/skill/gpu/SKILL.md). | ||
@@ -41,0 +41,0 @@ ## Configuration |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
69742
11.86%719
8.12%8
33.33%