@moor-sh/mcp 0.12.0 → 0.14.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +56 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moor-sh/mcp",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "MCP server for moor - lets AI agents (Claude Code, Cursor, etc.) manage your moor projects via the moor HTTP API.",
5
5
  "license": "MIT",
6
6
  "repository": {
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> {
@@ -284,7 +295,8 @@ server.registerTool(
284
295
  "moor_status",
285
296
  {
286
297
  title: "List Projects",
287
- description: "List all projects managed by Moor with their status, source, and domain.",
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
  }));
@@ -304,7 +320,8 @@ server.registerTool(
304
320
  "moor_logs",
305
321
  {
306
322
  title: "Get Container Logs",
307
- description: "Get recent logs from a project's container.",
323
+ description:
324
+ "Get recent logs from a project's container. Annotates output with state: ok (container running), exited (container is stopped but Docker still has logs), no_container (project never started), or missing (container_id is set but Docker doesn't have it). Throws only on docker_error (Docker daemon 5xx / unreachable) so an operator can distinguish infrastructure failure from app silence — pre-#74 the tool returned empty logs for all of these.",
308
325
  inputSchema: z.object({
309
326
  project: z.string().describe("Project name or ID"),
310
327
  lines: z.number().optional().default(100).describe("Number of log lines to retrieve"),
@@ -313,11 +330,43 @@ server.registerTool(
313
330
  async ({ project, lines }) => {
314
331
  const p = await resolveProject(project);
315
332
  const res = await apiGet(`/api/projects/${p.id}/logs?tail=${lines}`);
316
- if (!res.ok) throw new Error(`Failed: ${res.status}`);
317
- const data = (await res.json()) as { logs: string };
318
- return {
319
- content: [{ type: "text", text: data.logs || "(no logs)" }],
320
- };
333
+ // 502 = API surfaced a Docker daemon failure. Throw so the agent
334
+ // gets a tool error, not silent empty logs.
335
+ if (res.status === 502) {
336
+ const data = (await res.json().catch(() => ({}))) as { error?: string };
337
+ throw new Error(`Docker error: ${data.error ?? "unknown"}`);
338
+ }
339
+ if (!res.ok) throw new Error(`Failed: ${res.status} ${await res.text()}`);
340
+ const data = (await res.json()) as { logs: string; state?: string };
341
+ switch (data.state) {
342
+ case "no_container":
343
+ return {
344
+ content: [{ type: "text", text: "(project hasn't been started yet — no container)" }],
345
+ };
346
+ case "missing":
347
+ return {
348
+ content: [
349
+ {
350
+ type: "text",
351
+ text: "(container_id was recorded but Docker doesn't have it; moor may need to recreate the project)",
352
+ },
353
+ ],
354
+ };
355
+ case "exited":
356
+ return {
357
+ content: [
358
+ {
359
+ type: "text",
360
+ text: `${data.logs || "(no logs captured)"}\n\n(container is exited; logs above are from before)`,
361
+ },
362
+ ],
363
+ };
364
+ default:
365
+ // "ok" or undefined (older API) — render raw.
366
+ return {
367
+ content: [{ type: "text", text: data.logs || "(no logs)" }],
368
+ };
369
+ }
321
370
  },
322
371
  );
323
372