@moor-sh/mcp 0.11.0 → 0.13.0
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/package.json +1 -1
- package/src/index.ts +79 -14
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -90,6 +90,17 @@ type Project = {
|
|
|
90
90
|
domain: string | null;
|
|
91
91
|
docker_image: string | null;
|
|
92
92
|
github_url: string | null;
|
|
93
|
+
// #71: live_* fields are written by the API's status reconciler.
|
|
94
|
+
// status above is moor's RECORDED state (only changes on explicit
|
|
95
|
+
// start/stop/build/cancel). live_status reflects Docker's view at
|
|
96
|
+
// last successful inspect. Differences mean moor missed an external
|
|
97
|
+
// change (or the reconciler hasn't run yet). live_error non-null
|
|
98
|
+
// means the most recent inspect failed; the live_status / exit_code
|
|
99
|
+
// shown is the last successful snapshot.
|
|
100
|
+
live_status?: "running" | "stopped" | "error" | "missing" | null;
|
|
101
|
+
live_exit_code?: number | null;
|
|
102
|
+
live_checked_at?: string | null;
|
|
103
|
+
live_error?: string | null;
|
|
93
104
|
};
|
|
94
105
|
|
|
95
106
|
async function resolveProject(name: string): Promise<Project> {
|
|
@@ -166,9 +177,9 @@ function validateGithubRepoUrl(url: string): void {
|
|
|
166
177
|
} catch {
|
|
167
178
|
throw new Error(`github_url is not a valid URL: ${url}`);
|
|
168
179
|
}
|
|
169
|
-
// The downstream build path (apps/api/docker.ts:
|
|
170
|
-
// branch ref to whatever URL we forward, so a non-http protocol, query string,
|
|
171
|
-
// fragment quietly mangles the resulting git remote. Reject those up front.
|
|
180
|
+
// The downstream build path (apps/api/docker.ts:buildImageStreaming) appends ".git"
|
|
181
|
+
// and a branch ref to whatever URL we forward, so a non-http protocol, query string,
|
|
182
|
+
// or fragment quietly mangles the resulting git remote. Reject those up front.
|
|
172
183
|
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
173
184
|
throw new Error(`github_url must use http or https (got protocol "${parsed.protocol}")`);
|
|
174
185
|
}
|
|
@@ -284,7 +295,8 @@ server.registerTool(
|
|
|
284
295
|
"moor_status",
|
|
285
296
|
{
|
|
286
297
|
title: "List Projects",
|
|
287
|
-
description:
|
|
298
|
+
description:
|
|
299
|
+
"List all projects managed by Moor. `status` is moor's recorded state (only changes on explicit start/stop/build/cancel). `live_status` is Docker's view at last successful inspect; differences (e.g. recorded='running' live='error') mean moor missed an external change like a host docker stop, crash, or OOM kill. `live_error` non-null means the most recent inspect failed and the live_* values are the last successful snapshot, not necessarily current.",
|
|
288
300
|
},
|
|
289
301
|
async () => {
|
|
290
302
|
const res = await apiGet("/api/projects");
|
|
@@ -293,6 +305,10 @@ server.registerTool(
|
|
|
293
305
|
const summary = projects.map((p) => ({
|
|
294
306
|
name: p.name,
|
|
295
307
|
status: p.status,
|
|
308
|
+
live_status: p.live_status ?? null,
|
|
309
|
+
live_exit_code: p.live_exit_code ?? null,
|
|
310
|
+
live_checked_at: p.live_checked_at ?? null,
|
|
311
|
+
live_error: p.live_error ?? null,
|
|
296
312
|
source: p.docker_image || p.github_url || null,
|
|
297
313
|
domain: p.domain,
|
|
298
314
|
}));
|
|
@@ -326,7 +342,7 @@ server.registerTool(
|
|
|
326
342
|
{
|
|
327
343
|
title: "Rebuild Project",
|
|
328
344
|
description:
|
|
329
|
-
"Rebuild a project from source (git pull + docker build) and restart the container. Returns the build output.",
|
|
345
|
+
"Rebuild a project from source (git pull + docker build) and restart the container. Returns the build output when it finishes. While a build is in flight, the most recent moor_runs entry has finished_at=null — call moor_run_get on its id to tail the live output. Use moor_rebuild for code, Dockerfile, or base-image changes. For env vars / resource limits / port / volume / restart-policy changes, or to recover a crashed container from the existing image, use moor_restart — it skips the build and is much faster.",
|
|
330
346
|
inputSchema: z.object({
|
|
331
347
|
project: z.string().describe("Project name or ID"),
|
|
332
348
|
no_cache: z.boolean().optional().default(false).describe("Build without Docker cache"),
|
|
@@ -346,7 +362,8 @@ server.registerTool(
|
|
|
346
362
|
"moor_restart",
|
|
347
363
|
{
|
|
348
364
|
title: "Restart Project",
|
|
349
|
-
description:
|
|
365
|
+
description:
|
|
366
|
+
"Stop and recreate a project's container from its existing image. Does NOT pull from git or rebuild — uses the existing image_tag. Right tool for: applying changed env vars / resource limits / ports / volumes / restart policy, recovering a crashed container, or simply bouncing the process. Wrong tool for: code or Dockerfile changes (use moor_rebuild — those need a new image).",
|
|
350
367
|
inputSchema: z.object({
|
|
351
368
|
project: z.string().describe("Project name or ID"),
|
|
352
369
|
}),
|
|
@@ -1264,7 +1281,7 @@ server.registerTool(
|
|
|
1264
1281
|
{
|
|
1265
1282
|
title: "List Project Run History",
|
|
1266
1283
|
description:
|
|
1267
|
-
"Paginated list of cron runs and build runs for a project. Returns one compact line per run (id, type, status, exit code, duration, output byte counts, timestamps) — stdout/stderr bodies are NOT included to avoid blowing token budgets on large build outputs. Use moor_run_get(run_id) to fetch the
|
|
1284
|
+
"Paginated list of cron runs and build runs for a project. Returns one compact line per run (id, type, status, exit code, duration, output byte counts, timestamps) — stdout/stderr bodies are NOT included to avoid blowing token budgets on large build outputs. Use moor_run_get(run_id) to fetch the stored output for a single run (cron rows store full output; build/manual rows store at most a 64 KiB tail with the original total bytes recorded separately).",
|
|
1268
1285
|
inputSchema: z.object({
|
|
1269
1286
|
project: z.string().describe("Project name or ID"),
|
|
1270
1287
|
page: z
|
|
@@ -1292,6 +1309,8 @@ server.registerTool(
|
|
|
1292
1309
|
duration_ms: number | null;
|
|
1293
1310
|
stdout_bytes: number;
|
|
1294
1311
|
stderr_bytes: number;
|
|
1312
|
+
stdout_total_bytes?: number;
|
|
1313
|
+
stderr_total_bytes?: number;
|
|
1295
1314
|
}>;
|
|
1296
1315
|
total: number;
|
|
1297
1316
|
};
|
|
@@ -1302,15 +1321,22 @@ server.registerTool(
|
|
|
1302
1321
|
}
|
|
1303
1322
|
const lines: string[] = [];
|
|
1304
1323
|
lines.push(
|
|
1305
|
-
`${p.name}: ${data.runs.length} run(s) on page ${page}, ${data.total} total. Use moor_run_get(run_id) for
|
|
1324
|
+
`${p.name}: ${data.runs.length} run(s) on page ${page}, ${data.total} total. Use moor_run_get(run_id) for stored output (build/manual rows are tail-truncated; total bytes shown below).`,
|
|
1306
1325
|
);
|
|
1307
1326
|
for (const r of data.runs) {
|
|
1308
1327
|
const type = deriveRunType(r);
|
|
1309
1328
|
const status = deriveRunStatus(r);
|
|
1310
1329
|
const exit = r.exit_code != null ? ` exit=${r.exit_code}` : "";
|
|
1311
1330
|
const cmd = r.cron_command ? ` cmd="${r.cron_command}"` : "";
|
|
1331
|
+
// #65: surface "what was emitted" (total) per byte field. For live or
|
|
1332
|
+
// already-truncated build runs total > stored; for crons and historical
|
|
1333
|
+
// build rows they're equal. Showing total is the operationally useful
|
|
1334
|
+
// number — "what did Docker actually produce" — and stays accurate as a
|
|
1335
|
+
// build streams in. Fall back to stdout_bytes if the API is old.
|
|
1336
|
+
const outTotal = r.stdout_total_bytes ?? r.stdout_bytes;
|
|
1337
|
+
const errTotal = r.stderr_total_bytes ?? r.stderr_bytes;
|
|
1312
1338
|
lines.push(
|
|
1313
|
-
`id=${r.id} ${type} ${status}${exit} dur=${formatMsShort(r.duration_ms)} stdout=${
|
|
1339
|
+
`id=${r.id} ${type} ${status}${exit} dur=${formatMsShort(r.duration_ms)} stdout=${outTotal}B stderr=${errTotal}B started=${r.started_at}${cmd}`,
|
|
1314
1340
|
);
|
|
1315
1341
|
}
|
|
1316
1342
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
@@ -1352,6 +1378,8 @@ server.registerTool(
|
|
|
1352
1378
|
duration_ms: number | null;
|
|
1353
1379
|
stdout: string | null;
|
|
1354
1380
|
stderr: string | null;
|
|
1381
|
+
stdout_total_bytes?: number | null;
|
|
1382
|
+
stderr_total_bytes?: number | null;
|
|
1355
1383
|
};
|
|
1356
1384
|
const lines: string[] = [];
|
|
1357
1385
|
const type = deriveRunType(r);
|
|
@@ -1361,18 +1389,55 @@ server.registerTool(
|
|
|
1361
1389
|
if (r.cron_command) lines.push(`cron_command: ${r.cron_command}`);
|
|
1362
1390
|
lines.push(`started_at: ${r.started_at}`);
|
|
1363
1391
|
if (r.finished_at) lines.push(`finished_at: ${r.finished_at}`);
|
|
1364
|
-
// runs.stdout/stderr
|
|
1365
|
-
//
|
|
1366
|
-
//
|
|
1392
|
+
// #65: runs.stdout/stderr for build runs is a server-side 64 KiB tail
|
|
1393
|
+
// (TAIL_CAP_BYTES). Use stdout_total_bytes / stderr_total_bytes when the
|
|
1394
|
+
// API provides them so appendStream can honestly report "last X of Y".
|
|
1395
|
+
// For cron rows the stored payload IS the full output, and total == stored.
|
|
1396
|
+
// Fall back to encoded length for older APIs that don't return the totals.
|
|
1367
1397
|
const stdoutStr = r.stdout ?? "";
|
|
1368
1398
|
const stderrStr = r.stderr ?? "";
|
|
1369
1399
|
const enc = new TextEncoder();
|
|
1370
|
-
|
|
1371
|
-
|
|
1400
|
+
const stdoutTotal = r.stdout_total_bytes ?? enc.encode(stdoutStr).length;
|
|
1401
|
+
const stderrTotal = r.stderr_total_bytes ?? enc.encode(stderrStr).length;
|
|
1402
|
+
appendStream(lines, "stdout", stdoutStr, stdoutTotal, cap);
|
|
1403
|
+
appendStream(lines, "stderr", stderrStr, stderrTotal, cap);
|
|
1372
1404
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1373
1405
|
},
|
|
1374
1406
|
);
|
|
1375
1407
|
|
|
1408
|
+
server.registerTool(
|
|
1409
|
+
"moor_run_stop",
|
|
1410
|
+
{
|
|
1411
|
+
title: "Stop or Cancel a Run",
|
|
1412
|
+
description:
|
|
1413
|
+
"Stops an active cron run or cancels an active build/pull run (from moor_rebuild / moor_deploy). Closing the connection to the Docker build/pull endpoint aborts the daemon-side job. Cancellation is only valid during the build/pull streaming phase — once the build finishes and container start has begun, the call returns not_cancellable. Returns one of: cancelled, cancelled_cron, not_cancellable, already_finished, not_active, not_found. These are all expected outcomes, not errors — the tool throws only on unexpected server failures.",
|
|
1414
|
+
inputSchema: z.object({
|
|
1415
|
+
run_id: z.number().int().positive().describe("Run ID from moor_runs"),
|
|
1416
|
+
}),
|
|
1417
|
+
},
|
|
1418
|
+
async ({ run_id }) => {
|
|
1419
|
+
const res = await apiPost(`/api/runs/${run_id}/stop`);
|
|
1420
|
+
// The /stop route returns 200 for cancelled/cancelled_cron and 4xx
|
|
1421
|
+
// for the rest of the known result categories (with a result field
|
|
1422
|
+
// either way). All of those are expected outcomes — render them as
|
|
1423
|
+
// content so the agent can react without try/catch. Only surface as
|
|
1424
|
+
// an error if the response doesn't fit the documented shape (server
|
|
1425
|
+
// error, parse failure, etc).
|
|
1426
|
+
let data: { ok?: boolean; result?: string; error?: string };
|
|
1427
|
+
try {
|
|
1428
|
+
data = (await res.json()) as { ok?: boolean; result?: string; error?: string };
|
|
1429
|
+
} catch {
|
|
1430
|
+
throw new Error(`run_id=${run_id} server error: ${res.status} ${res.statusText}`);
|
|
1431
|
+
}
|
|
1432
|
+
if (typeof data.result === "string") {
|
|
1433
|
+
return { content: [{ type: "text", text: `run_id=${run_id} ${data.result}` }] };
|
|
1434
|
+
}
|
|
1435
|
+
throw new Error(
|
|
1436
|
+
`run_id=${run_id} unexpected response: status=${res.status} body=${JSON.stringify(data)}`,
|
|
1437
|
+
);
|
|
1438
|
+
},
|
|
1439
|
+
);
|
|
1440
|
+
|
|
1376
1441
|
server.registerTool(
|
|
1377
1442
|
"moor_deploy",
|
|
1378
1443
|
{
|