@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 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).flag("terse", opts.terse).flag("show-sensitive", opts.showSensitive).build();
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
- await Promise.all(Object.entries(cfg.devices).map(([name, dc]) => probeDevice(name, dc).catch(() => {})));
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.1.0",
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).flag("terse", opts.terse).flag("show-sensitive", opts.showSensitive).build();
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.1.0",
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",