@usex/mikrotik-mcp 3.1.0 → 3.3.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/dist/cli.js +117 -13
- package/dist/index.js +2 -2
- package/dist/ui/observability.html +10 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3328,7 +3328,7 @@ function snapshots() {
|
|
|
3328
3328
|
}
|
|
3329
3329
|
function exportCommand(opts) {
|
|
3330
3330
|
const base = opts.section ? `/${opts.section} export` : "/export";
|
|
3331
|
-
return new Cmd(base).
|
|
3331
|
+
return new Cmd(base).raw(opts.terse ? "terse" : undefined).raw(opts.showSensitive ? "show-sensitive" : undefined).build();
|
|
3332
3332
|
}
|
|
3333
3333
|
async function liveExport(ctx, opts) {
|
|
3334
3334
|
const body = await executeMikrotikCommand(exportCommand(opts), ctx);
|
|
@@ -19573,16 +19573,6 @@ function usedPct(total, free) {
|
|
|
19573
19573
|
return Math.max(0, Math.min(100, (total - free) / total * 100));
|
|
19574
19574
|
}
|
|
19575
19575
|
async function probeDevice(name, dc) {
|
|
19576
|
-
if (dc.mac) {
|
|
19577
|
-
const status2 = {
|
|
19578
|
-
reachable: null,
|
|
19579
|
-
checkedAt: Date.now(),
|
|
19580
|
-
latencyMs: null,
|
|
19581
|
-
error: "MAC-Telnet device \u2014 reachability is verified on tool use, not background-probed."
|
|
19582
|
-
};
|
|
19583
|
-
statuses.set(name, status2);
|
|
19584
|
-
return status2;
|
|
19585
|
-
}
|
|
19586
19576
|
const client = createDeviceClient({
|
|
19587
19577
|
...dc,
|
|
19588
19578
|
timeoutMs: Math.min(dc.timeoutMs ?? 1e4, 8000)
|
|
@@ -19653,13 +19643,23 @@ async function probeDevice(name, dc) {
|
|
|
19653
19643
|
statuses.set(name, status);
|
|
19654
19644
|
return status;
|
|
19655
19645
|
}
|
|
19646
|
+
var MAC_PROBE_INTERVAL_MS = 5 * 60000;
|
|
19647
|
+
var lastMacProbe = new Map;
|
|
19656
19648
|
async function probeAll() {
|
|
19657
19649
|
if (inFlight)
|
|
19658
19650
|
return;
|
|
19659
19651
|
inFlight = true;
|
|
19660
19652
|
try {
|
|
19661
19653
|
const cfg = getConfig();
|
|
19662
|
-
|
|
19654
|
+
const now = Date.now();
|
|
19655
|
+
await Promise.all(Object.entries(cfg.devices).map(([name, dc]) => {
|
|
19656
|
+
if (dc.mac) {
|
|
19657
|
+
if (now - (lastMacProbe.get(name) ?? 0) < MAC_PROBE_INTERVAL_MS)
|
|
19658
|
+
return Promise.resolve();
|
|
19659
|
+
lastMacProbe.set(name, now);
|
|
19660
|
+
}
|
|
19661
|
+
return probeDevice(name, dc).catch(() => {});
|
|
19662
|
+
}));
|
|
19663
19663
|
} finally {
|
|
19664
19664
|
inFlight = false;
|
|
19665
19665
|
}
|
|
@@ -20205,6 +20205,107 @@ async function captureRoutes(req, url) {
|
|
|
20205
20205
|
}
|
|
20206
20206
|
return null;
|
|
20207
20207
|
}
|
|
20208
|
+
var snapStorePromise = null;
|
|
20209
|
+
function snapStore() {
|
|
20210
|
+
if (!snapStorePromise)
|
|
20211
|
+
snapStorePromise = openSnapshotStore(DEFAULT_SNAPSHOT_DB);
|
|
20212
|
+
return snapStorePromise;
|
|
20213
|
+
}
|
|
20214
|
+
async function featureRoutes(req, url) {
|
|
20215
|
+
const p = url.pathname;
|
|
20216
|
+
if (p === "/api/snapshots" && req.method === "GET") {
|
|
20217
|
+
const store2 = await snapStore();
|
|
20218
|
+
const cfg = getConfig();
|
|
20219
|
+
const all = Object.keys(cfg.devices).flatMap((d) => store2.list(d, 200, false));
|
|
20220
|
+
all.sort((a, b) => b.ts - a.ts);
|
|
20221
|
+
return json({ snapshots: all });
|
|
20222
|
+
}
|
|
20223
|
+
const snapMatch = p.match(/^\/api\/snapshot\/(.+)$/);
|
|
20224
|
+
if (snapMatch && req.method === "GET") {
|
|
20225
|
+
const store2 = await snapStore();
|
|
20226
|
+
const s = store2.get(decodeURIComponent(snapMatch[1]));
|
|
20227
|
+
return s ? json(s) : json({ error: "not found" }, 404);
|
|
20228
|
+
}
|
|
20229
|
+
if (p === "/api/snapshots/diff" && req.method === "POST") {
|
|
20230
|
+
const store2 = await snapStore();
|
|
20231
|
+
const b = await readJson(req);
|
|
20232
|
+
const from = b?.from ? store2.get(b.from) : null;
|
|
20233
|
+
const to = b?.to ? store2.get(b.to) : null;
|
|
20234
|
+
if (!from || !to) {
|
|
20235
|
+
return json({ error: "both 'from' and 'to' snapshot ids are required" }, 400);
|
|
20236
|
+
}
|
|
20237
|
+
const diff = diffLines(normalizeExport(from.body), normalizeExport(to.body), {
|
|
20238
|
+
fromLabel: from.label ?? from.id,
|
|
20239
|
+
toLabel: to.label ?? to.id
|
|
20240
|
+
});
|
|
20241
|
+
return json({
|
|
20242
|
+
from: { id: from.id, label: from.label, ts: from.ts, device: from.device },
|
|
20243
|
+
to: { id: to.id, label: to.label, ts: to.ts, device: to.device },
|
|
20244
|
+
summary: diff.summary,
|
|
20245
|
+
unified: diff.unified
|
|
20246
|
+
});
|
|
20247
|
+
}
|
|
20248
|
+
if (p === "/api/plan" && req.method === "POST") {
|
|
20249
|
+
const b = await readJson(req);
|
|
20250
|
+
const commands = [
|
|
20251
|
+
...(b?.commands ?? []).map((c) => c.trim()).filter(Boolean),
|
|
20252
|
+
...b?.script ? splitCommands(b.script) : []
|
|
20253
|
+
];
|
|
20254
|
+
if (commands.length === 0)
|
|
20255
|
+
return json({ error: "no commands provided" }, 400);
|
|
20256
|
+
const plan = buildChangePlan(commands);
|
|
20257
|
+
return json({ plan, text: renderPlan(plan) });
|
|
20258
|
+
}
|
|
20259
|
+
if (p === "/api/s3" && req.method === "GET") {
|
|
20260
|
+
return json({ configured: isS3Configured(), target: isS3Configured() ? s3Target() : null });
|
|
20261
|
+
}
|
|
20262
|
+
if (p === "/api/s3/list" && req.method === "GET") {
|
|
20263
|
+
if (!isS3Configured())
|
|
20264
|
+
return json({ configured: false, objects: [] });
|
|
20265
|
+
const prefix = url.searchParams.get("prefix") ?? "";
|
|
20266
|
+
try {
|
|
20267
|
+
const res = await getS3Client().list({ prefix: prefix || undefined, maxKeys: 1000 });
|
|
20268
|
+
const objects = (res.contents ?? []).map((o) => ({
|
|
20269
|
+
key: o.key,
|
|
20270
|
+
size: o.size ?? 0,
|
|
20271
|
+
lastModified: o.lastModified ?? null
|
|
20272
|
+
}));
|
|
20273
|
+
return json({ configured: true, target: s3Target(), objects, truncated: !!res.isTruncated });
|
|
20274
|
+
} catch (e) {
|
|
20275
|
+
return json({ error: e instanceof Error ? e.message : String(e) }, 502);
|
|
20276
|
+
}
|
|
20277
|
+
}
|
|
20278
|
+
if (p === "/api/s3/presign" && req.method === "GET") {
|
|
20279
|
+
if (!isS3Configured())
|
|
20280
|
+
return json({ error: "S3 not configured" }, 400);
|
|
20281
|
+
const key = url.searchParams.get("key");
|
|
20282
|
+
if (!key)
|
|
20283
|
+
return json({ error: "key required" }, 400);
|
|
20284
|
+
try {
|
|
20285
|
+
const link = getS3Client().presign(key, { expiresIn: presignExpiresIn(), method: "GET" });
|
|
20286
|
+
return json({ url: link });
|
|
20287
|
+
} catch (e) {
|
|
20288
|
+
return json({ error: e instanceof Error ? e.message : String(e) }, 502);
|
|
20289
|
+
}
|
|
20290
|
+
}
|
|
20291
|
+
if (p === "/api/s3/delete" && req.method === "POST") {
|
|
20292
|
+
if (!isS3Configured())
|
|
20293
|
+
return json({ error: "S3 not configured" }, 400);
|
|
20294
|
+
const b = await readJson(req);
|
|
20295
|
+
if (!b?.key)
|
|
20296
|
+
return json({ error: "key required" }, 400);
|
|
20297
|
+
try {
|
|
20298
|
+
const client = getS3Client();
|
|
20299
|
+
if (!await client.exists(b.key))
|
|
20300
|
+
return json({ error: "not found" }, 404);
|
|
20301
|
+
await client.delete(b.key);
|
|
20302
|
+
return json({ ok: true, key: b.key });
|
|
20303
|
+
} catch (e) {
|
|
20304
|
+
return json({ error: e instanceof Error ? e.message : String(e) }, 502);
|
|
20305
|
+
}
|
|
20306
|
+
}
|
|
20307
|
+
return null;
|
|
20308
|
+
}
|
|
20208
20309
|
async function runDashboard(cfg, transportLabel) {
|
|
20209
20310
|
const store2 = await openSqliteStore(cfg.dbPath);
|
|
20210
20311
|
configureRecorder({
|
|
@@ -20268,6 +20369,9 @@ async function runDashboard(cfg, transportLabel) {
|
|
|
20268
20369
|
const captureResp = await captureRoutes(req, url);
|
|
20269
20370
|
if (captureResp)
|
|
20270
20371
|
return captureResp;
|
|
20372
|
+
const featureResp = await featureRoutes(req, url);
|
|
20373
|
+
if (featureResp)
|
|
20374
|
+
return featureResp;
|
|
20271
20375
|
const db = getEventStore();
|
|
20272
20376
|
if (!db)
|
|
20273
20377
|
return json({ error: "recorder not active" }, 503);
|
|
@@ -20509,7 +20613,7 @@ function registerPrompts(server) {
|
|
|
20509
20613
|
// package.json
|
|
20510
20614
|
var package_default = {
|
|
20511
20615
|
name: "@usex/mikrotik-mcp",
|
|
20512
|
-
version: "3.
|
|
20616
|
+
version: "3.3.0",
|
|
20513
20617
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
20514
20618
|
keywords: [
|
|
20515
20619
|
"ai",
|
package/dist/index.js
CHANGED
|
@@ -3401,7 +3401,7 @@ function snapshots() {
|
|
|
3401
3401
|
}
|
|
3402
3402
|
function exportCommand(opts) {
|
|
3403
3403
|
const base = opts.section ? `/${opts.section} export` : "/export";
|
|
3404
|
-
return new Cmd(base).
|
|
3404
|
+
return new Cmd(base).raw(opts.terse ? "terse" : undefined).raw(opts.showSensitive ? "show-sensitive" : undefined).build();
|
|
3405
3405
|
}
|
|
3406
3406
|
async function liveExport(ctx, opts) {
|
|
3407
3407
|
const body = await executeMikrotikCommand(exportCommand(opts), ctx);
|
|
@@ -19337,7 +19337,7 @@ var allToolModules = moduleCatalog.map((m) => m.tools);
|
|
|
19337
19337
|
// package.json
|
|
19338
19338
|
var package_default = {
|
|
19339
19339
|
name: "@usex/mikrotik-mcp",
|
|
19340
|
-
version: "3.
|
|
19340
|
+
version: "3.3.0",
|
|
19341
19341
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
19342
19342
|
keywords: [
|
|
19343
19343
|
"ai",
|