@aicommander/mcp 1.0.50 → 1.0.51
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/README.md +3 -3
- package/dist/bin/mcp.js +74 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Universal **stdio MCP server** for [AI Commander](https://aicommander.dev) — **remote command execution / remote shell** that lets your AI client run shell/bash commands on remote machines, servers and laptops. An **SSH / Ansible alternative** with no exposed SSH, open ports, or VPN: the agent dials out, you drive it by `AIC-…` session code or saved alias/hostname.
|
|
4
4
|
|
|
5
|
-
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
|
|
5
|
+
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.
|
|
6
6
|
|
|
7
7
|
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.
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@ Use this package to connect any MCP client that speaks **stdio** (Codex CLI, Cla
|
|
|
20
20
|
| `remote_exec` | Run a shell command on one of your machines — the tool for any "connect to / remote shell / run X on" request. Name the machine by its `AIC-…` session code or (with an API key) a saved alias/hostname like `wearfits-m3`. Output is **buffered**: stdout and stderr come back in one reply once the command finishes, and if the call is cut short (timeout, agent error, agent disconnect) you get whatever had been buffered, marked PARTIAL — an unknown outcome, not an empty failure. Optional `env` (string→string) sets variables for that one command; it is refused together with `elevated: true`. |
|
|
21
21
|
| `session_status` | Check whether a machine is online/active/reachable (e.g. "is wearfits-m3 up?"). Same machine naming as above. Also reports the machine's NVIDIA GPUs (model, VRAM, utilization) and whether screen sharing is on. |
|
|
22
22
|
| `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. |
|
|
23
|
-
| `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.
|
|
23
|
+
| `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). |
|
|
24
24
|
| `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. |
|
|
25
25
|
| `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. |
|
|
26
26
|
| `remote_job_logs` | A bounded slice of the job's output (stdout+stderr interleaved). Last 200 lines by default; page a long log with `offset_bytes`. |
|
|
@@ -33,7 +33,7 @@ These are the canonical way to reach your machines — your AI client should use
|
|
|
33
33
|
|
|
34
34
|
> **Numeric arguments are validated, not clamped.** `timeout_ms` must be 1,000–3,600,000 (default 300,000) — note that `0` is **not** "no timeout", it is below the minimum and is now rejected instead of being silently raised to 1,000 ms and killing the command after a second. `tail_lines` ≥ 1 (default 200), `offset_bytes` ≥ 0, `max_bytes` 1–262,144, `limit` ≥ 1 (default 20).
|
|
35
35
|
|
|
36
|
-
> **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
|
|
36
|
+
> **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).
|
|
37
37
|
|
|
38
38
|
## Configuration
|
|
39
39
|
|
package/dist/bin/mcp.js
CHANGED
|
@@ -56,10 +56,13 @@ Tools:
|
|
|
56
56
|
Use remote_exec for seconds/minutes. Use remote_job_start for builds, downloads,
|
|
57
57
|
batch processing, rendering, fine-tuning, ML training, or anything that could run
|
|
58
58
|
longer than a few minutes. Jobs continue after the call and conversation end.
|
|
59
|
-
Surviving an agent restart differs per platform: on macOS a job
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
Surviving an agent restart differs per platform: on macOS a job reparents to PID 1
|
|
60
|
+
and is out of reach of anything aimed at the app; on Windows it survives the agent
|
|
61
|
+
process dying by itself (a crash, or a taskkill /F /IM of that one process without
|
|
62
|
+
/T) but is LOST to a TREE kill ('End task', taskkill /T, an installer that stops
|
|
63
|
+
the app and everything it started) — treat an auto-update as a tree kill unless
|
|
64
|
+
you know that machine's installer does otherwise; on Linux restarting or upgrading the agent's systemd
|
|
65
|
+
service stops its jobs.
|
|
63
66
|
|
|
64
67
|
Docs: https://aicommander.dev/docs/#jobs
|
|
65
68
|
Guide: https://aicommander.dev/use-cases/ml-training-jobs/`);
|
|
@@ -99,10 +102,14 @@ const server = new McpServer({
|
|
|
99
102
|
"session_status report each machine's NVIDIA GPUs (model, VRAM, utilization) — that is how you " +
|
|
100
103
|
"choose which box to run compute on.\n\n" +
|
|
101
104
|
"SHELL DIALECT — CHECK `platform` BEFORE YOU WRITE A COMMAND: list_machines and session_status " +
|
|
102
|
-
"report each machine's platform ('darwin', 'linux', 'win32'). POSIX machines run
|
|
103
|
-
"`/bin/sh -c
|
|
104
|
-
"not fail loudly — `;` is not a separator there, so `echo a ; echo b` prints the rest
|
|
105
|
-
"text and exits 0. Write the dialect the machine actually speaks
|
|
105
|
+
"report each machine's platform ('darwin', 'linux', 'win32'). By default POSIX machines run " +
|
|
106
|
+
"commands through `/bin/sh -c` and Windows machines through cmd.exe. A POSIX one-liner sent to " +
|
|
107
|
+
"Windows does not fail loudly — `;` is not a separator there, so `echo a ; echo b` prints the rest " +
|
|
108
|
+
"as literal text and exits 0. Write the dialect the machine actually speaks, or pass remote_exec's " +
|
|
109
|
+
"`shell` argument to pick another interpreter explicitly: `powershell` on Windows, `bash` on " +
|
|
110
|
+
"macOS/Linux. `shell` is only accepted by remote_exec (not by remote_job_start, where it is " +
|
|
111
|
+
"rejected), and a value the target machine cannot run is always rejected rather than silently " +
|
|
112
|
+
"replaced by the default.\n\n" +
|
|
106
113
|
"MACHINE NOTES — DON'T RE-EXPLORE A MACHINE YOU ALREADY KNOW: with an API key (AICOMMANDER_TOKEN) " +
|
|
107
114
|
"set and the machine online, session_status returns the path of a note file kept ON that machine " +
|
|
108
115
|
"for this account (each account has its own; they are not shared between users). Without a key " +
|
|
@@ -167,20 +174,26 @@ server.registerTool("remote_exec", {
|
|
|
167
174
|
"instead: a job has neither cap, its output is written to a file on the machine (which stops recording at " +
|
|
168
175
|
"256 MiB — the job is NOT killed, it just stops being logged), and it keeps running after the call returns, " +
|
|
169
176
|
"after the network drops and after this conversation ends.\n\n" +
|
|
170
|
-
"JOB SURVIVAL ACROSS AN AGENT RESTART
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"
|
|
177
|
+
"JOB SURVIVAL ACROSS AN AGENT RESTART — a job outlives the agent PROCESS on every platform; what " +
|
|
178
|
+
"differs is what can still take it down, and it matters when you are choosing where to put a " +
|
|
179
|
+
"multi-hour run. macOS: the job reparents to PID 1, which puts it out of reach of ANYTHING aimed at " +
|
|
180
|
+
"the app — a crash, a hard kill, even an explicit kill of the whole process tree. Windows: the job " +
|
|
181
|
+
"survives the agent process dying by itself — a crash, or a `taskkill /F /IM` of that one process " +
|
|
182
|
+
"without `/T` — and keeps writing its log straight through; what it does NOT survive is a TREE kill " +
|
|
183
|
+
"(Task Manager's 'End task', `taskkill /T`, or an installer that stops the app and everything it " +
|
|
184
|
+
"started), because Windows never reparents. Treat an auto-update as a tree kill unless you know that " +
|
|
185
|
+
"machine's installer does otherwise — the silent updater runs the installer, which stops the running " +
|
|
186
|
+
"app before replacing its files — so make an unattended Windows run resumable and check " +
|
|
187
|
+
"remote_job_status afterwards. Linux: the agent runs as a systemd service and its jobs " +
|
|
188
|
+
"stay inside that service's control group, so stopping, restarting or upgrading the service stops " +
|
|
189
|
+
"running jobs too — the one platform where an agent upgrade really does end a job.",
|
|
177
190
|
inputSchema: {
|
|
178
191
|
code: z
|
|
179
192
|
.string()
|
|
180
193
|
.describe("How the user named the machine, passed exactly as given: an AIC- session code (e.g. AIC-XYZ-1234), or — with an API key — a saved alias/hostname like 'wearfits-m3' or 'aic-wearfits'. A string containing 'aic-' is almost certainly one of the user's machines."),
|
|
181
194
|
command: z
|
|
182
195
|
.string()
|
|
183
|
-
.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
|
|
196
|
+
.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."),
|
|
184
197
|
cwd: z
|
|
185
198
|
.string()
|
|
186
199
|
.optional()
|
|
@@ -193,12 +206,16 @@ server.registerTool("remote_exec", {
|
|
|
193
206
|
.number()
|
|
194
207
|
.optional()
|
|
195
208
|
.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."),
|
|
209
|
+
shell: z
|
|
210
|
+
.enum(["sh", "bash", "cmd", "powershell"])
|
|
211
|
+
.optional()
|
|
212
|
+
.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."),
|
|
196
213
|
elevated: z
|
|
197
214
|
.boolean()
|
|
198
215
|
.optional()
|
|
199
|
-
.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."),
|
|
216
|
+
.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."),
|
|
200
217
|
},
|
|
201
|
-
}, async ({ code, command, cwd, env, timeout_ms, elevated }) => {
|
|
218
|
+
}, async ({ code, command, cwd, env, timeout_ms, elevated, shell }) => {
|
|
202
219
|
const res = await fetch(MCP_URL, {
|
|
203
220
|
method: "POST",
|
|
204
221
|
headers: {
|
|
@@ -212,7 +229,7 @@ server.registerTool("remote_exec", {
|
|
|
212
229
|
method: "tools/call",
|
|
213
230
|
params: {
|
|
214
231
|
name: "remote_exec",
|
|
215
|
-
arguments: { code, command, cwd, env, timeout_ms, elevated },
|
|
232
|
+
arguments: { code, command, cwd, env, timeout_ms, elevated, shell },
|
|
216
233
|
},
|
|
217
234
|
}),
|
|
218
235
|
});
|
|
@@ -488,13 +505,23 @@ server.registerTool("remote_job_start", {
|
|
|
488
505
|
"stdout+stderr go to a file ON THE MACHINE — up to 256 MiB, after which the machine stops recording output " +
|
|
489
506
|
"but the job itself runs on unaffected — and it keeps running after this call returns, after the network " +
|
|
490
507
|
"drops and after this conversation ends.\n\n" +
|
|
491
|
-
"SURVIVING AN AGENT RESTART —
|
|
492
|
-
"
|
|
493
|
-
"
|
|
494
|
-
"
|
|
495
|
-
"
|
|
496
|
-
"
|
|
497
|
-
"
|
|
508
|
+
"SURVIVING AN AGENT RESTART — a job outlives the agent PROCESS on every platform; what differs is what can " +
|
|
509
|
+
"still take it down, so check the machine's `platform` before committing a multi-hour run to it. macOS: the " +
|
|
510
|
+
"job reparents to PID 1, which puts it out of reach of ANYTHING aimed at the app — a crash, a hard kill, even " +
|
|
511
|
+
"an explicit kill of the whole process tree; short of killing the job itself or the machine going down, " +
|
|
512
|
+
"nothing stops it. Windows: the job survives the agent process dying BY ITSELF — a crash, or a kill aimed at " +
|
|
513
|
+
"that one process (`taskkill /F /IM \"AI Commander.exe\"`, no `/T`) — measured running straight through such " +
|
|
514
|
+
"a kill with no gap in its output, and the agent picks it up again when it comes back. What it does NOT " +
|
|
515
|
+
"survive is a TREE kill: Task Manager's 'End task', `taskkill /T`, or an installer that stops the app and " +
|
|
516
|
+
"everything it started — Windows never reparents, so the job stays inside the app's tree and goes down with " +
|
|
517
|
+
"it. TREAT AN AUTO-UPDATE AS A TREE KILL unless you know that machine's installer does otherwise: the silent " +
|
|
518
|
+
"updater runs the installer, and the installer stops the running app before it replaces its files — older " +
|
|
519
|
+
"ones do that with a tree kill, which takes running jobs with it. Updates arrive on their own schedule, " +
|
|
520
|
+
"nobody has to be at the machine, so before leaving a multi-hour run unattended on Windows make it RESUMABLE " +
|
|
521
|
+
"(checkpoint to disk), and afterwards confirm with remote_job_status instead of assuming it ran through. " +
|
|
522
|
+
"Linux: the agent runs as a systemd service and its jobs " +
|
|
523
|
+
"stay inside that service's control group, so stopping or restarting the service — including an agent " +
|
|
524
|
+
"upgrade — stops running jobs too, so finish or checkpoint long runs before upgrading a Linux agent.\n\n" +
|
|
498
525
|
"Name the machine with `code` exactly as the user said it — an AIC- session code (e.g. AIC-XYZ-1234) or, when " +
|
|
499
526
|
"authenticated with an API key, a saved alias or hostname such as 'wearfits-m3'; if the user's text contains " +
|
|
500
527
|
"'aic-'/'AIC-' in any case, that is one of their machines. The call returns as soon as the job is spawned, with " +
|
|
@@ -524,7 +551,7 @@ server.registerTool("remote_job_start", {
|
|
|
524
551
|
code: z.string().describe(MACHINE_CODE_DESCRIPTION),
|
|
525
552
|
command: z
|
|
526
553
|
.string()
|
|
527
|
-
.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."),
|
|
554
|
+
.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."),
|
|
528
555
|
cwd: z
|
|
529
556
|
.string()
|
|
530
557
|
.optional()
|
|
@@ -541,8 +568,27 @@ server.registerTool("remote_job_start", {
|
|
|
541
568
|
.number()
|
|
542
569
|
.optional()
|
|
543
570
|
.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."),
|
|
571
|
+
// DECLARED SO THEY CAN BE REFUSED, not because a job supports them.
|
|
572
|
+
//
|
|
573
|
+
// This bridge validates with zod, whose default is to STRIP any key the
|
|
574
|
+
// schema does not name. An undeclared `shell` / `elevated` would therefore
|
|
575
|
+
// be DELETED here, the relay would never see it, and the job would start in
|
|
576
|
+
// the machine's default interpreter (or as the ordinary user) and come back
|
|
577
|
+
// with a normal jobId — a silent false success, and the precise failure mode
|
|
578
|
+
// the `shell` work exists to eliminate. The Worker's own JSON-Schema copy of
|
|
579
|
+
// this tool does not list them because JSON Schema passes unknown properties
|
|
580
|
+
// through untouched; here they must be listed for the documented refusal to
|
|
581
|
+
// actually reach the caller.
|
|
582
|
+
shell: z
|
|
583
|
+
.enum(["sh", "bash", "cmd", "powershell"])
|
|
584
|
+
.optional()
|
|
585
|
+
.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`."),
|
|
586
|
+
elevated: z
|
|
587
|
+
.boolean()
|
|
588
|
+
.optional()
|
|
589
|
+
.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."),
|
|
544
590
|
},
|
|
545
|
-
}, async ({ code, command, cwd, env, name, gpu_index }) => callTool("remote_job_start", { code, command, cwd, env, name, gpu_index }));
|
|
591
|
+
}, async ({ code, command, cwd, env, name, gpu_index, shell, elevated }) => callTool("remote_job_start", { code, command, cwd, env, name, gpu_index, shell, elevated }));
|
|
546
592
|
// Tool: remote_job_list
|
|
547
593
|
server.registerTool("remote_job_list", {
|
|
548
594
|
description: "List the detached jobs on a remote machine — what is running right now, and which recently finished jobs are " +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aicommander/mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.51",
|
|
4
4
|
"mcpName": "dev.aicommander/mcp",
|
|
5
5
|
"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.",
|
|
6
6
|
"keywords": [
|