@moor-sh/mcp 0.23.1 → 0.25.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 +73 -6
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -574,7 +574,7 @@ server.registerTool(
|
|
|
574
574
|
{
|
|
575
575
|
title: "Server Stats",
|
|
576
576
|
description:
|
|
577
|
-
"Get server resource usage: load, memory,
|
|
577
|
+
"Get server resource usage: load, memory, per-filesystem disk usage (every real mount, not just root — so a separate data volume can't hide), Docker disk by category (images/containers/volumes/build cache) with reclaimable bytes, and container counts. Note: cpu.percent is load-derived (load avg ÷ cores), not instantaneous CPU; use the `load` field for the same signal with explicit naming.",
|
|
578
578
|
},
|
|
579
579
|
async () => {
|
|
580
580
|
const res = await apiGet("/api/server/stats");
|
|
@@ -587,6 +587,7 @@ server.registerTool(
|
|
|
587
587
|
load?: { one_min: number; cores: number; normalized_percent: number };
|
|
588
588
|
memory: { total: string; used: string; percent: number };
|
|
589
589
|
disk: { total: string; used: string; percent: number };
|
|
590
|
+
disks?: { mount: string; total: string; used: string; percent: number }[];
|
|
590
591
|
containers: { running: number; total: number };
|
|
591
592
|
docker?: {
|
|
592
593
|
images: { bytes: number; reclaimable_bytes: number; count: number; unused_count: number };
|
|
@@ -611,11 +612,12 @@ server.registerTool(
|
|
|
611
612
|
`Load (1m): ${s.load.one_min.toFixed(2)} on ${s.load.cores} cores (${s.load.normalized_percent}%)`,
|
|
612
613
|
);
|
|
613
614
|
}
|
|
614
|
-
lines.push(
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
`
|
|
618
|
-
|
|
615
|
+
lines.push(`Memory: ${s.memory.used} / ${s.memory.total} (${s.memory.percent}%)`);
|
|
616
|
+
const disks = s.disks?.length ? s.disks : [{ mount: "/", ...s.disk }];
|
|
617
|
+
for (const d of disks) {
|
|
618
|
+
lines.push(`Disk ${d.mount}: ${d.used} / ${d.total} (${d.percent}%)`);
|
|
619
|
+
}
|
|
620
|
+
lines.push(`Containers: ${s.containers.running} running / ${s.containers.total} total`);
|
|
619
621
|
if (s.docker) {
|
|
620
622
|
const d = s.docker;
|
|
621
623
|
lines.push(
|
|
@@ -1123,6 +1125,71 @@ function formatBytes(bytes: number): string {
|
|
|
1123
1125
|
return `${val.toFixed(val < 10 ? 1 : 0)} ${units[i]}`;
|
|
1124
1126
|
}
|
|
1125
1127
|
|
|
1128
|
+
server.registerTool(
|
|
1129
|
+
"moor_project_history",
|
|
1130
|
+
{
|
|
1131
|
+
title: "Project History (stored)",
|
|
1132
|
+
description:
|
|
1133
|
+
"Stored resource history + lifecycle events for one project over a time window — answers 'what was going on with this project around this case?' (NOT live: use moor_project_stats for a current snapshot). Resource samples are taken ~every minute; CPU is averaged across each interval and network/block reported as rates, both computed from raw counters and reset-aware. Events come from the Docker event stream (start/die/oom/kill/restart) and moor's own state changes. Window defaults to the last `hours` (24); pass from_ms/to_ms (epoch ms) for an exact window. A gap warning means events may be incomplete in that window.",
|
|
1134
|
+
inputSchema: z.object({
|
|
1135
|
+
project: z.string().describe("Project name or ID"),
|
|
1136
|
+
hours: z
|
|
1137
|
+
.number()
|
|
1138
|
+
.optional()
|
|
1139
|
+
.describe("Lookback window in hours (default 24). Ignored if from_ms/to_ms are given."),
|
|
1140
|
+
from_ms: z.number().optional().describe("Window start, epoch milliseconds"),
|
|
1141
|
+
to_ms: z.number().optional().describe("Window end, epoch milliseconds"),
|
|
1142
|
+
}),
|
|
1143
|
+
},
|
|
1144
|
+
async ({ project, hours, from_ms, to_ms }) => {
|
|
1145
|
+
const p = await resolveProject(project);
|
|
1146
|
+
const to = to_ms ?? Date.now();
|
|
1147
|
+
const from = from_ms ?? to - (hours ?? 24) * 3_600_000;
|
|
1148
|
+
const res = await apiGet(`/api/projects/${p.id}/stats/history?from=${from}&to=${to}`);
|
|
1149
|
+
if (!res.ok) throw new Error(`Failed: ${res.status} ${await res.text()}`);
|
|
1150
|
+
const h = (await res.json()) as {
|
|
1151
|
+
from_ms: number;
|
|
1152
|
+
to_ms: number;
|
|
1153
|
+
events: Array<{ occurred_at_ms: number; source: string; action: string }>;
|
|
1154
|
+
summary: {
|
|
1155
|
+
sample_count: number;
|
|
1156
|
+
running_sample_count: number;
|
|
1157
|
+
cpu_percent_avg: number | null;
|
|
1158
|
+
cpu_percent_max: number | null;
|
|
1159
|
+
mem_bytes_max: number | null;
|
|
1160
|
+
net_rx_bytes_total: number;
|
|
1161
|
+
net_tx_bytes_total: number;
|
|
1162
|
+
event_counts: Record<string, number>;
|
|
1163
|
+
has_gap: boolean;
|
|
1164
|
+
};
|
|
1165
|
+
};
|
|
1166
|
+
const s = h.summary;
|
|
1167
|
+
const windowH = Math.round(((h.to_ms - h.from_ms) / 3_600_000) * 10) / 10;
|
|
1168
|
+
const lines = [
|
|
1169
|
+
`${p.name} history — window ~${windowH}h${s.has_gap ? " [⚠ event gap recorded: events may be incomplete]" : ""}`,
|
|
1170
|
+
`Samples: ${s.sample_count} total, ${s.running_sample_count} running`,
|
|
1171
|
+
`CPU: avg ${s.cpu_percent_avg ?? "n/a"}% / max ${s.cpu_percent_max ?? "n/a"}%`,
|
|
1172
|
+
`Memory: max ${s.mem_bytes_max !== null ? formatBytes(s.mem_bytes_max) : "n/a"}`,
|
|
1173
|
+
`Network: in ${formatBytes(s.net_rx_bytes_total)} / out ${formatBytes(s.net_tx_bytes_total)}`,
|
|
1174
|
+
];
|
|
1175
|
+
const counts = Object.entries(s.event_counts);
|
|
1176
|
+
if (counts.length > 0) {
|
|
1177
|
+
lines.push(`Events: ${counts.map(([a, n]) => `${a} ${n}`).join(", ")}`);
|
|
1178
|
+
}
|
|
1179
|
+
const recent = h.events.slice(-8);
|
|
1180
|
+
if (recent.length > 0) {
|
|
1181
|
+
lines.push("Recent events:");
|
|
1182
|
+
for (const e of recent) {
|
|
1183
|
+
lines.push(` ${new Date(e.occurred_at_ms).toISOString()} ${e.action} (${e.source})`);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (s.sample_count === 0 && h.events.length === 0) {
|
|
1187
|
+
lines.push("(no stored history in this window)");
|
|
1188
|
+
}
|
|
1189
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1190
|
+
},
|
|
1191
|
+
);
|
|
1192
|
+
|
|
1126
1193
|
server.registerTool(
|
|
1127
1194
|
"moor_project_get",
|
|
1128
1195
|
{
|