@mgsoftwarebv/mg-dashboard-mcp 3.10.0 → 3.10.2
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/dist/index.js +86 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2149,52 +2149,89 @@ async function r2List(bucket, prefix, options) {
|
|
|
2149
2149
|
} while (continuationToken && entries.length < options.maxResults);
|
|
2150
2150
|
return entries;
|
|
2151
2151
|
}
|
|
2152
|
+
function r2WrapError(bucket, key, e) {
|
|
2153
|
+
const err = e;
|
|
2154
|
+
const status = err?.$metadata?.httpStatusCode;
|
|
2155
|
+
const name = err?.name || "";
|
|
2156
|
+
if (name === "NoSuchKey" || name === "NotFound" || status === 404) {
|
|
2157
|
+
return new Error(`r2://${bucket}/${key} not found`);
|
|
2158
|
+
}
|
|
2159
|
+
if (name === "NoSuchBucket") return new Error(`R2 bucket "${bucket}" not found`);
|
|
2160
|
+
if (status === 403 || name === "AccessDenied") return new Error(`Access denied for r2://${bucket}/${key} (check R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY)`);
|
|
2161
|
+
return new Error(`R2 error (${name || "unknown"}${status ? ` ${status}` : ""}): ${err?.message || String(e)}`);
|
|
2162
|
+
}
|
|
2152
2163
|
async function r2GetObject(bucket, key, maxBytes) {
|
|
2153
2164
|
const client = getR2Client();
|
|
2154
|
-
|
|
2155
|
-
|
|
2165
|
+
let size = 0;
|
|
2166
|
+
try {
|
|
2167
|
+
const head = await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
|
|
2168
|
+
size = head.ContentLength || 0;
|
|
2169
|
+
} catch (e) {
|
|
2170
|
+
throw r2WrapError(bucket, key, e);
|
|
2171
|
+
}
|
|
2156
2172
|
if (size > maxBytes) {
|
|
2157
2173
|
throw new Error(`Object too large: ${size} bytes (max ${maxBytes} for inline read; use sftp-write with sourcePath to mirror locally instead, or pass { offset, length } for a ranged read)`);
|
|
2158
2174
|
}
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2175
|
+
try {
|
|
2176
|
+
const result = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
2177
|
+
const body = result.Body;
|
|
2178
|
+
if (!body?.transformToString) throw new Error("R2 returned no readable body");
|
|
2179
|
+
return await body.transformToString();
|
|
2180
|
+
} catch (e) {
|
|
2181
|
+
throw r2WrapError(bucket, key, e);
|
|
2182
|
+
}
|
|
2163
2183
|
}
|
|
2164
2184
|
async function r2GetObjectRange(bucket, key, range) {
|
|
2165
2185
|
const client = getR2Client();
|
|
2166
|
-
|
|
2167
|
-
|
|
2186
|
+
let size = 0;
|
|
2187
|
+
try {
|
|
2188
|
+
const head = await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
|
|
2189
|
+
size = head.ContentLength || 0;
|
|
2190
|
+
} catch (e) {
|
|
2191
|
+
throw r2WrapError(bucket, key, e);
|
|
2192
|
+
}
|
|
2168
2193
|
if (range.offset >= size && size > 0) throw new Error(`offset ${range.offset} is past end of object (size ${size})`);
|
|
2169
2194
|
const MAX = 1048576;
|
|
2170
2195
|
const remaining = Math.max(0, size - range.offset);
|
|
2171
2196
|
const effectiveLen = range.length !== void 0 ? Math.min(range.length, remaining, MAX) : Math.min(remaining, MAX);
|
|
2172
2197
|
const end = range.offset + effectiveLen - 1;
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2198
|
+
try {
|
|
2199
|
+
const result = await client.send(new GetObjectCommand({
|
|
2200
|
+
Bucket: bucket,
|
|
2201
|
+
Key: key,
|
|
2202
|
+
Range: `bytes=${range.offset}-${end}`
|
|
2203
|
+
}));
|
|
2204
|
+
const body = result.Body;
|
|
2205
|
+
if (!body?.transformToString) throw new Error("R2 returned no readable body");
|
|
2206
|
+
const text = await body.transformToString();
|
|
2207
|
+
const header = `# range: bytes ${range.offset}-${end} of ${size} (${effectiveLen} bytes)`;
|
|
2208
|
+
return `${header}
|
|
2183
2209
|
${text}`;
|
|
2210
|
+
} catch (e) {
|
|
2211
|
+
throw r2WrapError(bucket, key, e);
|
|
2212
|
+
}
|
|
2184
2213
|
}
|
|
2185
2214
|
async function r2PutObject(bucket, key, body, contentLength) {
|
|
2186
2215
|
const client = getR2Client();
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2216
|
+
try {
|
|
2217
|
+
await client.send(new PutObjectCommand({
|
|
2218
|
+
Bucket: bucket,
|
|
2219
|
+
Key: key,
|
|
2220
|
+
Body: body,
|
|
2221
|
+
ContentLength: contentLength,
|
|
2222
|
+
ContentType: "application/octet-stream"
|
|
2223
|
+
}));
|
|
2224
|
+
} catch (e) {
|
|
2225
|
+
throw r2WrapError(bucket, key, e);
|
|
2226
|
+
}
|
|
2194
2227
|
}
|
|
2195
2228
|
async function r2DeleteObject(bucket, key) {
|
|
2196
2229
|
const client = getR2Client();
|
|
2197
|
-
|
|
2230
|
+
try {
|
|
2231
|
+
await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
|
|
2232
|
+
} catch (e) {
|
|
2233
|
+
throw r2WrapError(bucket, key, e);
|
|
2234
|
+
}
|
|
2198
2235
|
}
|
|
2199
2236
|
async function r2DeletePrefix(bucket, prefix) {
|
|
2200
2237
|
const client = getR2Client();
|
|
@@ -3627,43 +3664,47 @@ ${result.stderr}`);
|
|
|
3627
3664
|
if (containers.length === 0) {
|
|
3628
3665
|
return { content: [{ type: "text", text: "Error: no valid containerName provided" }] };
|
|
3629
3666
|
}
|
|
3630
|
-
const
|
|
3667
|
+
const followSeconds = Math.max(0, Math.min(300, Number(a.followSeconds) || 0));
|
|
3668
|
+
const userTail = a.tail !== void 0 ? Number(a.tail) : a.lines !== void 0 ? Number(a.lines) : void 0;
|
|
3669
|
+
const tailArg = userTail !== void 0 && Number.isFinite(userTail) && userTail >= 0 ? userTail : followSeconds > 0 ? 0 : 100;
|
|
3631
3670
|
const sinceRaw = typeof a.since === "string" ? a.since.trim() : "";
|
|
3632
3671
|
const grepRaw = typeof a.grep === "string" ? a.grep : "";
|
|
3633
|
-
const followSeconds = Math.max(0, Math.min(300, Number(a.followSeconds) || 0));
|
|
3634
3672
|
if (sinceRaw && !/^\d+[smhd]$/i.test(sinceRaw) && !/^\d{4}-\d{2}-\d{2}/.test(sinceRaw)) {
|
|
3635
3673
|
return { content: [{ type: "text", text: 'Error: invalid `since` format (expected e.g. "10m", "2h", or ISO timestamp)' }] };
|
|
3636
3674
|
}
|
|
3637
3675
|
const sinceArg = sinceRaw ? ` --since ${posixQuote(sinceRaw)}` : "";
|
|
3638
3676
|
const grepSuffix = grepRaw ? ` | grep -i -E ${posixQuote(grepRaw)} --line-buffered` : "";
|
|
3639
|
-
const followTail = a.tail !== void 0 || a.lines !== void 0 ? tail : 0;
|
|
3640
|
-
const tailArg = followSeconds > 0 ? followTail : tail;
|
|
3641
3677
|
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
3642
|
-
|
|
3678
|
+
const wallSeconds = followSeconds > 0 ? followSeconds : 30;
|
|
3679
|
+
conn.timeout = (wallSeconds + 10) * 1e3;
|
|
3643
3680
|
const followFlag = followSeconds > 0 ? " -f" : "";
|
|
3644
3681
|
if (containers.length === 1) {
|
|
3645
3682
|
const c = containers[0];
|
|
3646
|
-
const
|
|
3647
|
-
const cmd2 =
|
|
3683
|
+
const inner = `docker logs${followFlag} --tail ${tailArg}${sinceArg} ${c} 2>&1${grepSuffix}`;
|
|
3684
|
+
const cmd2 = `timeout --signal=INT ${wallSeconds} sh -c ${posixQuote(inner)}`;
|
|
3648
3685
|
const result2 = await sshExec(conn, cmd2, proxy);
|
|
3649
|
-
const acceptable2 =
|
|
3650
|
-
if (!acceptable2) {
|
|
3651
|
-
return { content: [{ type: "text", text: `Error (exit ${result2.exitCode}): ${result2.stderr ||
|
|
3686
|
+
const acceptable2 = result2.exitCode === 0 || result2.exitCode === 124 || result2.exitCode === 130 || result2.exitCode === 143 || !!grepRaw && result2.exitCode === 1;
|
|
3687
|
+
if (!acceptable2 && !result2.stdout) {
|
|
3688
|
+
return { content: [{ type: "text", text: `Error (exit ${result2.exitCode}): ${result2.stderr || "(no output)"}` }] };
|
|
3652
3689
|
}
|
|
3653
|
-
|
|
3690
|
+
const note2 = result2.exitCode === 124 ? `
|
|
3691
|
+
(note: command exceeded ${wallSeconds}s wall budget; partial output)` : "";
|
|
3692
|
+
return { content: [{ type: "text", text: (result2.stdout || "(no log lines matched)") + note2 }] };
|
|
3654
3693
|
}
|
|
3655
3694
|
const subShells = containers.map((c) => {
|
|
3656
|
-
const
|
|
3657
|
-
return `(${
|
|
3695
|
+
const inner = `docker logs${followFlag} --tail ${tailArg}${sinceArg} ${c} 2>&1${grepSuffix}`;
|
|
3696
|
+
return `(${inner} | sed -u -e ${posixQuote(`s/^/[${c}] /`)})`;
|
|
3658
3697
|
});
|
|
3659
|
-
const
|
|
3660
|
-
const cmd =
|
|
3698
|
+
const innerCmd = subShells.join(" & ") + " & wait";
|
|
3699
|
+
const cmd = `timeout --signal=INT ${wallSeconds} sh -c ${posixQuote(innerCmd)}`;
|
|
3661
3700
|
const result = await sshExec(conn, cmd, proxy);
|
|
3662
|
-
const acceptable =
|
|
3663
|
-
if (!acceptable) {
|
|
3664
|
-
return { content: [{ type: "text", text: `Error (exit ${result.exitCode}): ${result.stderr ||
|
|
3701
|
+
const acceptable = result.exitCode === 0 || result.exitCode === 124 || result.exitCode === 130 || result.exitCode === 143 || !!grepRaw && result.exitCode === 1;
|
|
3702
|
+
if (!acceptable && !result.stdout) {
|
|
3703
|
+
return { content: [{ type: "text", text: `Error (exit ${result.exitCode}): ${result.stderr || "(no output)"}` }] };
|
|
3665
3704
|
}
|
|
3666
|
-
|
|
3705
|
+
const note = result.exitCode === 124 ? `
|
|
3706
|
+
(note: one or more containers exceeded ${wallSeconds}s wall budget; partial output)` : "";
|
|
3707
|
+
return { content: [{ type: "text", text: (result.stdout || "(no log lines matched)") + note }] };
|
|
3667
3708
|
}
|
|
3668
3709
|
case "docker-exec": {
|
|
3669
3710
|
const container = String(a.container).replace(/[^a-zA-Z0-9._-]/g, "");
|