@usex/mikrotik-mcp 3.40.0 → 3.41.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
@@ -84,7 +84,7 @@ import {
84
84
  toggleAaaEntity,
85
85
  updateAaaEntity,
86
86
  writeBackup
87
- } from "./shared/cli-jsf7zy89.js";
87
+ } from "./shared/cli-ef0ns1dq.js";
88
88
 
89
89
  // src/cli.ts
90
90
  import { existsSync as existsSync2 } from "fs";
@@ -2242,7 +2242,7 @@ function registerPrompts(server) {
2242
2242
  // package.json
2243
2243
  var package_default = {
2244
2244
  name: "@usex/mikrotik-mcp",
2245
- version: "3.40.0",
2245
+ version: "3.41.0",
2246
2246
  description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
2247
2247
  keywords: [
2248
2248
  "ai",
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  resolveDeviceName,
22
22
  selectToolModules,
23
23
  setConfig
24
- } from "./shared/library-0x8fc0rw.js";
24
+ } from "./shared/library-vhp4xd29.js";
25
25
  // src/server.ts
26
26
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
27
27
  import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
@@ -131,7 +131,7 @@ function registerPrompts(server) {
131
131
  // package.json
132
132
  var package_default = {
133
133
  name: "@usex/mikrotik-mcp",
134
- version: "3.40.0",
134
+ version: "3.41.0",
135
135
  description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
136
136
  keywords: [
137
137
  "ai",
@@ -6141,7 +6141,7 @@ var cache = null;
6141
6141
  async function gateway() {
6142
6142
  if (cache)
6143
6143
  return cache;
6144
- const { moduleCatalog } = await import("./cli-28w9b076.js");
6144
+ const { moduleCatalog } = await import("./cli-fwswsq2a.js");
6145
6145
  const forIndex = [];
6146
6146
  const byName = new Map;
6147
6147
  for (const mod of moduleCatalog) {
@@ -20650,7 +20650,7 @@ var drDrillTools = [
20650
20650
  name: "run_failover_drill",
20651
20651
  title: "Run Failover DR Drill",
20652
20652
  annotations: DANGEROUS,
20653
- description: "Rehearses a disaster safely: INSIDE SAFE MODE it disables a target (a WAN/tunnel interface, or " + "a route), pings `verify_host` to check the backup path actually carries traffic, then ROLLS " + "BACK \u2014 Safe Mode auto-reverts, so the change is never committed. Proves your failover works " + "without a real outage. Requires `confirm=true` to run (it briefly disrupts traffic on the " + "target); with confirm=false it just describes the drill. SSH-only (Safe Mode is unavailable on " + "MAC-Telnet). Returns whether connectivity held during the simulated failure.",
20653
+ description: "Rehearses a disaster: disables a target (a WAN/tunnel interface, or a route), pings " + "`verify_host` to check the backup path actually carries traffic, then RE-ENABLES the target. " + "Prefers Safe Mode (auto-revert on disconnect) but falls back to direct commands when Safe " + "Mode is unavailable. Proves your failover works without a real outage. Requires `confirm=true` " + "to run (it briefly disrupts traffic on the target); with confirm=false it just describes the " + "drill. Returns whether connectivity held during the simulated failure.",
20654
20654
  inputSchema: {
20655
20655
  target_type: z98.enum(["interface", "route"]).default("interface"),
20656
20656
  target: z98.string().describe("Interface name (e.g. 'ether1-wan') or, for route, a find expression / .id"),
@@ -20659,37 +20659,66 @@ var drDrillTools = [
20659
20659
  confirm: z98.boolean().default(false).describe("Must be true to actually run the drill")
20660
20660
  },
20661
20661
  async handler(a, ctx) {
20662
- const disableCmd = a.target_type === "interface" ? `/interface disable "${a.target}"` : `/ip route disable [find ${a.target.startsWith("*") ? `.id=${a.target}` : a.target}]`;
20662
+ const findExpr = a.target.startsWith("*") ? `.id=${a.target}` : a.target;
20663
+ const disableCmd = a.target_type === "interface" ? `/interface disable "${a.target}"` : `/ip route disable [find ${findExpr}]`;
20664
+ const enableCmd = a.target_type === "interface" ? `/interface enable "${a.target}"` : `/ip route enable [find ${findExpr}]`;
20665
+ const pingCmd = `/ping address=${a.verify_host} count=${a.ping_count}`;
20663
20666
  if (!a.confirm) {
20664
- return `DRY RUN \u2014 would, inside Safe Mode: (1) ${disableCmd}; (2) /ping address=${a.verify_host} count=${a.ping_count}; (3) roll back (auto-revert). Set confirm=true to run the drill.`;
20667
+ return `DRY RUN \u2014 would: (1) ${disableCmd}; (2) ${pingCmd}; (3) re-enable. ` + "Set confirm=true to run the drill.";
20665
20668
  }
20666
20669
  const device = resolveDeviceName(ctx.device);
20667
- if (getDevice(device).mac) {
20668
- return "DR Drill needs Safe Mode, which is SSH-only \u2014 this device is reached over MAC-Telnet.";
20670
+ const isMac = !!getDevice(device).mac;
20671
+ let usedSafeMode = false;
20672
+ if (!isMac) {
20673
+ const mgr = getSafeModeManager(device);
20674
+ try {
20675
+ const en = await mgr.enable();
20676
+ if (!en.startsWith("Error")) {
20677
+ usedSafeMode = true;
20678
+ try {
20679
+ const disableOut2 = await mgr.execute(disableCmd);
20680
+ if (/no such item|failure:|syntax error/i.test(disableOut2)) {
20681
+ return `Could not disable target '${a.target}': ${disableOut2.trim()} (Safe Mode rolled back).`;
20682
+ }
20683
+ const pingOut = await mgr.execute(pingCmd);
20684
+ return formatResult(a, pingOut, "The change has been rolled back (Safe Mode); nothing was committed.");
20685
+ } finally {
20686
+ await mgr.rollback();
20687
+ }
20688
+ }
20689
+ } catch {
20690
+ try {
20691
+ await mgr.rollback();
20692
+ } catch {}
20693
+ }
20694
+ }
20695
+ if (!usedSafeMode) {
20696
+ ctx.info("Safe Mode unavailable \u2014 using direct disable/enable fallback");
20697
+ }
20698
+ const disableOut = await executeMikrotikCommand(disableCmd, ctx);
20699
+ if (looksLikeError(disableOut) || /no such item/i.test(disableOut)) {
20700
+ return `Could not disable target '${a.target}': ${disableOut.trim()}`;
20669
20701
  }
20670
- const mgr = getSafeModeManager(device);
20671
- const en = await mgr.enable();
20672
- if (en.startsWith("Error"))
20673
- return `Could not enter Safe Mode: ${en}`;
20674
20702
  try {
20675
- const disableOut = await mgr.execute(disableCmd);
20676
- if (/no such item|failure:|syntax error/i.test(disableOut)) {
20677
- return `Could not disable target '${a.target}': ${disableOut.trim()} (Safe Mode rolled back).`;
20678
- }
20679
- const pingOut = await mgr.execute(`/ping address=${a.verify_host} count=${a.ping_count}`);
20680
- const summary = parsePingSummary(pingOut);
20681
- const held = summary != null && summary.received > 0;
20682
- const detail = summary ? `${summary.received}/${summary.sent} replies (${summary.lossPct}% loss)` : "could not parse ping result";
20683
- const verdict = held ? "\u2705 Backup path HELD \u2014 failover works." : "\u26D4 NO connectivity during the outage \u2014 failover did NOT carry traffic.";
20684
- return `DR DRILL \u2014 disabled ${a.target_type} '${a.target}', pinged ${a.verify_host}: ${detail}.
20685
- ${verdict}
20686
- The change has been rolled back (Safe Mode); nothing was committed.`;
20703
+ const pingOut = await executeMikrotikCommand(pingCmd, ctx, {
20704
+ maxMs: (a.ping_count + 2) * 1500
20705
+ });
20706
+ return formatResult(a, pingOut, "The target has been re-enabled (direct command); the drill is complete.");
20687
20707
  } finally {
20688
- await mgr.rollback();
20708
+ await executeMikrotikCommand(enableCmd, ctx);
20689
20709
  }
20690
20710
  }
20691
20711
  })
20692
20712
  ];
20713
+ function formatResult(a, pingOut, footer) {
20714
+ const summary = parsePingSummary(pingOut);
20715
+ const held = summary != null && summary.received > 0;
20716
+ const detail = summary ? `${summary.received}/${summary.sent} replies (${summary.lossPct}% loss)` : "could not parse ping result";
20717
+ const verdict = held ? "PASS \u2014 Backup path HELD, failover works." : "FAIL \u2014 NO connectivity during the outage, failover did NOT carry traffic.";
20718
+ return `DR DRILL \u2014 disabled ${a.target_type} '${a.target}', pinged ${a.verify_host}: ${detail}.
20719
+ ${verdict}
20720
+ ${footer}`;
20721
+ }
20693
20722
 
20694
20723
  // src/tools/safe-mode.ts
20695
20724
  var safeModeTools = [
@@ -4,7 +4,7 @@ import {
4
4
  allToolModules,
5
5
  moduleCatalog,
6
6
  selectToolModules
7
- } from "./cli-jsf7zy89.js";
7
+ } from "./cli-ef0ns1dq.js";
8
8
  export {
9
9
  selectToolModules,
10
10
  moduleCatalog,
@@ -4,7 +4,7 @@ import {
4
4
  allToolModules,
5
5
  moduleCatalog,
6
6
  selectToolModules
7
- } from "./library-0x8fc0rw.js";
7
+ } from "./library-vhp4xd29.js";
8
8
  export {
9
9
  selectToolModules,
10
10
  moduleCatalog,
@@ -6117,7 +6117,7 @@ var cache = null;
6117
6117
  async function gateway() {
6118
6118
  if (cache)
6119
6119
  return cache;
6120
- const { moduleCatalog } = await import("./library-ke4kdmyj.js");
6120
+ const { moduleCatalog } = await import("./library-2s6sp2r5.js");
6121
6121
  const forIndex = [];
6122
6122
  const byName = new Map;
6123
6123
  for (const mod of moduleCatalog) {
@@ -20626,7 +20626,7 @@ var drDrillTools = [
20626
20626
  name: "run_failover_drill",
20627
20627
  title: "Run Failover DR Drill",
20628
20628
  annotations: DANGEROUS,
20629
- description: "Rehearses a disaster safely: INSIDE SAFE MODE it disables a target (a WAN/tunnel interface, or " + "a route), pings `verify_host` to check the backup path actually carries traffic, then ROLLS " + "BACK \u2014 Safe Mode auto-reverts, so the change is never committed. Proves your failover works " + "without a real outage. Requires `confirm=true` to run (it briefly disrupts traffic on the " + "target); with confirm=false it just describes the drill. SSH-only (Safe Mode is unavailable on " + "MAC-Telnet). Returns whether connectivity held during the simulated failure.",
20629
+ description: "Rehearses a disaster: disables a target (a WAN/tunnel interface, or a route), pings " + "`verify_host` to check the backup path actually carries traffic, then RE-ENABLES the target. " + "Prefers Safe Mode (auto-revert on disconnect) but falls back to direct commands when Safe " + "Mode is unavailable. Proves your failover works without a real outage. Requires `confirm=true` " + "to run (it briefly disrupts traffic on the target); with confirm=false it just describes the " + "drill. Returns whether connectivity held during the simulated failure.",
20630
20630
  inputSchema: {
20631
20631
  target_type: z98.enum(["interface", "route"]).default("interface"),
20632
20632
  target: z98.string().describe("Interface name (e.g. 'ether1-wan') or, for route, a find expression / .id"),
@@ -20635,37 +20635,66 @@ var drDrillTools = [
20635
20635
  confirm: z98.boolean().default(false).describe("Must be true to actually run the drill")
20636
20636
  },
20637
20637
  async handler(a, ctx) {
20638
- const disableCmd = a.target_type === "interface" ? `/interface disable "${a.target}"` : `/ip route disable [find ${a.target.startsWith("*") ? `.id=${a.target}` : a.target}]`;
20638
+ const findExpr = a.target.startsWith("*") ? `.id=${a.target}` : a.target;
20639
+ const disableCmd = a.target_type === "interface" ? `/interface disable "${a.target}"` : `/ip route disable [find ${findExpr}]`;
20640
+ const enableCmd = a.target_type === "interface" ? `/interface enable "${a.target}"` : `/ip route enable [find ${findExpr}]`;
20641
+ const pingCmd = `/ping address=${a.verify_host} count=${a.ping_count}`;
20639
20642
  if (!a.confirm) {
20640
- return `DRY RUN \u2014 would, inside Safe Mode: (1) ${disableCmd}; (2) /ping address=${a.verify_host} count=${a.ping_count}; (3) roll back (auto-revert). Set confirm=true to run the drill.`;
20643
+ return `DRY RUN \u2014 would: (1) ${disableCmd}; (2) ${pingCmd}; (3) re-enable. ` + "Set confirm=true to run the drill.";
20641
20644
  }
20642
20645
  const device = resolveDeviceName(ctx.device);
20643
- if (getDevice(device).mac) {
20644
- return "DR Drill needs Safe Mode, which is SSH-only \u2014 this device is reached over MAC-Telnet.";
20646
+ const isMac = !!getDevice(device).mac;
20647
+ let usedSafeMode = false;
20648
+ if (!isMac) {
20649
+ const mgr = getSafeModeManager(device);
20650
+ try {
20651
+ const en = await mgr.enable();
20652
+ if (!en.startsWith("Error")) {
20653
+ usedSafeMode = true;
20654
+ try {
20655
+ const disableOut2 = await mgr.execute(disableCmd);
20656
+ if (/no such item|failure:|syntax error/i.test(disableOut2)) {
20657
+ return `Could not disable target '${a.target}': ${disableOut2.trim()} (Safe Mode rolled back).`;
20658
+ }
20659
+ const pingOut = await mgr.execute(pingCmd);
20660
+ return formatResult(a, pingOut, "The change has been rolled back (Safe Mode); nothing was committed.");
20661
+ } finally {
20662
+ await mgr.rollback();
20663
+ }
20664
+ }
20665
+ } catch {
20666
+ try {
20667
+ await mgr.rollback();
20668
+ } catch {}
20669
+ }
20670
+ }
20671
+ if (!usedSafeMode) {
20672
+ ctx.info("Safe Mode unavailable \u2014 using direct disable/enable fallback");
20673
+ }
20674
+ const disableOut = await executeMikrotikCommand(disableCmd, ctx);
20675
+ if (looksLikeError(disableOut) || /no such item/i.test(disableOut)) {
20676
+ return `Could not disable target '${a.target}': ${disableOut.trim()}`;
20645
20677
  }
20646
- const mgr = getSafeModeManager(device);
20647
- const en = await mgr.enable();
20648
- if (en.startsWith("Error"))
20649
- return `Could not enter Safe Mode: ${en}`;
20650
20678
  try {
20651
- const disableOut = await mgr.execute(disableCmd);
20652
- if (/no such item|failure:|syntax error/i.test(disableOut)) {
20653
- return `Could not disable target '${a.target}': ${disableOut.trim()} (Safe Mode rolled back).`;
20654
- }
20655
- const pingOut = await mgr.execute(`/ping address=${a.verify_host} count=${a.ping_count}`);
20656
- const summary = parsePingSummary(pingOut);
20657
- const held = summary != null && summary.received > 0;
20658
- const detail = summary ? `${summary.received}/${summary.sent} replies (${summary.lossPct}% loss)` : "could not parse ping result";
20659
- const verdict = held ? "\u2705 Backup path HELD \u2014 failover works." : "\u26D4 NO connectivity during the outage \u2014 failover did NOT carry traffic.";
20660
- return `DR DRILL \u2014 disabled ${a.target_type} '${a.target}', pinged ${a.verify_host}: ${detail}.
20661
- ${verdict}
20662
- The change has been rolled back (Safe Mode); nothing was committed.`;
20679
+ const pingOut = await executeMikrotikCommand(pingCmd, ctx, {
20680
+ maxMs: (a.ping_count + 2) * 1500
20681
+ });
20682
+ return formatResult(a, pingOut, "The target has been re-enabled (direct command); the drill is complete.");
20663
20683
  } finally {
20664
- await mgr.rollback();
20684
+ await executeMikrotikCommand(enableCmd, ctx);
20665
20685
  }
20666
20686
  }
20667
20687
  })
20668
20688
  ];
20689
+ function formatResult(a, pingOut, footer) {
20690
+ const summary = parsePingSummary(pingOut);
20691
+ const held = summary != null && summary.received > 0;
20692
+ const detail = summary ? `${summary.received}/${summary.sent} replies (${summary.lossPct}% loss)` : "could not parse ping result";
20693
+ const verdict = held ? "PASS \u2014 Backup path HELD, failover works." : "FAIL \u2014 NO connectivity during the outage, failover did NOT carry traffic.";
20694
+ return `DR DRILL \u2014 disabled ${a.target_type} '${a.target}', pinged ${a.verify_host}: ${detail}.
20695
+ ${verdict}
20696
+ ${footer}`;
20697
+ }
20669
20698
 
20670
20699
  // src/tools/safe-mode.ts
20671
20700
  var safeModeTools = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usex/mikrotik-mcp",
3
- "version": "3.40.0",
3
+ "version": "3.41.0",
4
4
  "description": "MCP server for MikroTik RouterOS — 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
5
5
  "keywords": [
6
6
  "ai",