@moor-sh/mcp 0.17.0 → 0.18.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 +58 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -810,6 +810,64 @@ server.registerTool(
|
|
|
810
810
|
},
|
|
811
811
|
);
|
|
812
812
|
|
|
813
|
+
// #80 PR #4: moor_update_apply — kick off a transient-respawner update of
|
|
814
|
+
// moor itself. The respawner runs async; this tool returns the audit_id
|
|
815
|
+
// immediately. Poll moor_update_status (or, eventually, moor_update_audit)
|
|
816
|
+
// for the outcome.
|
|
817
|
+
//
|
|
818
|
+
// IMPORTANT: PR #4 has no rollback. A failed compose-up / --wait timeout /
|
|
819
|
+
// health check writes a `failed` marker and exits; compose state is left
|
|
820
|
+
// as Compose left it. Recovery: manual `docker compose up`, OR wait for the
|
|
821
|
+
// 30-min stale-in_progress sweep to reclaim the audit row. Automatic
|
|
822
|
+
// rollback (retag + `--pull never`) lands in PR #5.
|
|
823
|
+
server.registerTool(
|
|
824
|
+
"moor_update_apply",
|
|
825
|
+
{
|
|
826
|
+
title: "Apply moor update (transient respawner)",
|
|
827
|
+
description:
|
|
828
|
+
"Update moor in-place via a transient respawner container. Runs preflight, enables drain, takes a fresh DB backup, then launches a one-shot Compose-aware respawner that pulls + re-creates the moor service. The respawner writes a marker file when done; this tool returns the audit_id immediately so the caller can poll. PR #4 happy path ONLY — no automatic rollback. A failed up/health writes `failed` and leaves the compose state; recovery may require manual `docker compose up`. Bypass is per-blocker: pass {bypass:['active_work']} to interrupt in-flight builds/execs/crons via the existing shutdown coordinator; {bypass:['unknown_digest']} when the registry comparison was inconclusive. Backup is mandatory and not bypassable.",
|
|
829
|
+
inputSchema: z.object({
|
|
830
|
+
target_digest: z
|
|
831
|
+
.string()
|
|
832
|
+
.regex(/^sha256:[0-9a-f]{64}$/, "target_digest must be sha256:<64 hex>")
|
|
833
|
+
.optional()
|
|
834
|
+
.describe(
|
|
835
|
+
"Pin the update to this exact image digest. Default: the registry's current `:latest` digest from moor_update_status.",
|
|
836
|
+
),
|
|
837
|
+
bypass: z
|
|
838
|
+
.array(z.enum(["active_work", "unknown_digest"]))
|
|
839
|
+
.optional()
|
|
840
|
+
.describe(
|
|
841
|
+
"Per-blocker bypass. `active_work` accepts that in-flight builds/execs/crons will be interrupted via the shutdown coordinator. `unknown_digest` accepts proceeding when the registry comparison is inconclusive (locally-built image, GHCR unreachable). Backup is mandatory and not in this list.",
|
|
842
|
+
),
|
|
843
|
+
}),
|
|
844
|
+
},
|
|
845
|
+
async (input) => {
|
|
846
|
+
const res = await apiPost("/api/server/update/apply", input ?? {});
|
|
847
|
+
if (res.status === 202) {
|
|
848
|
+
const { audit_id } = (await res.json()) as { audit_id: number };
|
|
849
|
+
return {
|
|
850
|
+
content: [
|
|
851
|
+
{
|
|
852
|
+
type: "text",
|
|
853
|
+
text: `Update started: audit_id=${audit_id}. Respawner is running async. Poll moor_update_status to watch the version change. Expected transitions: in_progress → success (clean apply) or → failed (compose/health failure; PR #4 has no rollback so the compose state is left where Compose left it). If the new moor never becomes healthy enough to ingest the marker, the audit row may stay in_progress until the 30-min stale sweep marks it crashed — at which point manual recovery (docker compose up) may be required.`,
|
|
854
|
+
},
|
|
855
|
+
],
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
// Error: surface the structured reason so callers can act on it.
|
|
859
|
+
const body = (await res.json().catch(() => ({}))) as {
|
|
860
|
+
error?: { code: string; reason?: string; unsafe_reasons?: string[] };
|
|
861
|
+
};
|
|
862
|
+
const code = body.error?.code ?? `HTTP ${res.status}`;
|
|
863
|
+
const reason = body.error?.reason ?? "no detail";
|
|
864
|
+
const extra = body.error?.unsafe_reasons
|
|
865
|
+
? `\nunsafe_reasons:\n - ${body.error.unsafe_reasons.join("\n - ")}`
|
|
866
|
+
: "";
|
|
867
|
+
throw new Error(`moor_update_apply refused [${code}]: ${reason}${extra}`);
|
|
868
|
+
},
|
|
869
|
+
);
|
|
870
|
+
|
|
813
871
|
server.registerTool(
|
|
814
872
|
"moor_cleanup_plan",
|
|
815
873
|
{
|