@usex/mikrotik-mcp 3.39.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 +2 -2
- package/dist/index.js +2 -2
- package/dist/shared/{cli-ksdjs84m.js → cli-ef0ns1dq.js} +68 -39
- package/dist/shared/{cli-17657c2v.js → cli-fwswsq2a.js} +1 -1
- package/dist/shared/{library-dhx14y24.js → library-2s6sp2r5.js} +1 -1
- package/dist/shared/{library-pvb7kh40.js → library-vhp4xd29.js} +68 -39
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
toggleAaaEntity,
|
|
85
85
|
updateAaaEntity,
|
|
86
86
|
writeBackup
|
|
87
|
-
} from "./shared/cli-
|
|
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.
|
|
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-
|
|
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.
|
|
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-
|
|
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) {
|
|
@@ -8038,11 +8038,11 @@ ${details}`;
|
|
|
8038
8038
|
const c = count.trim();
|
|
8039
8039
|
if (isDigits(c) && Number.parseInt(c, 10) > 0) {
|
|
8040
8040
|
const details = await executeMikrotikCommand(`/ip firewall filter print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
8041
|
-
return `Firewall filter rule created successfully:
|
|
8041
|
+
return readBackUnavailable(details) ? "Firewall filter rule created successfully." : `Firewall filter rule created successfully:
|
|
8042
8042
|
|
|
8043
8043
|
${details}`;
|
|
8044
8044
|
}
|
|
8045
|
-
return "Firewall filter rule
|
|
8045
|
+
return "Firewall filter rule created successfully.";
|
|
8046
8046
|
}
|
|
8047
8047
|
}),
|
|
8048
8048
|
defineTool({
|
|
@@ -8452,11 +8452,11 @@ ${details}`;
|
|
|
8452
8452
|
const c = count.trim();
|
|
8453
8453
|
if (isDigits2(c) && Number.parseInt(c, 10) > 0) {
|
|
8454
8454
|
const details = await executeMikrotikCommand(`/ip firewall nat print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
8455
|
-
return `NAT rule created successfully:
|
|
8455
|
+
return readBackUnavailable(details) ? "NAT rule created successfully." : `NAT rule created successfully:
|
|
8456
8456
|
|
|
8457
8457
|
${details}`;
|
|
8458
8458
|
}
|
|
8459
|
-
return "NAT rule
|
|
8459
|
+
return "NAT rule created successfully.";
|
|
8460
8460
|
}
|
|
8461
8461
|
}),
|
|
8462
8462
|
defineTool({
|
|
@@ -8857,11 +8857,11 @@ ${details}`;
|
|
|
8857
8857
|
const c = count.trim();
|
|
8858
8858
|
if (isDigits3(c) && Number.parseInt(c, 10) > 0) {
|
|
8859
8859
|
const details = await executeMikrotikCommand(`/ip firewall mangle print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
8860
|
-
return `Mangle rule created successfully:
|
|
8860
|
+
return readBackUnavailable(details) ? "Mangle rule created successfully." : `Mangle rule created successfully:
|
|
8861
8861
|
|
|
8862
8862
|
${details}`;
|
|
8863
8863
|
}
|
|
8864
|
-
return "Mangle rule
|
|
8864
|
+
return "Mangle rule created successfully.";
|
|
8865
8865
|
}
|
|
8866
8866
|
}),
|
|
8867
8867
|
defineTool({
|
|
@@ -10765,11 +10765,11 @@ ${details}`;
|
|
|
10765
10765
|
const c = count.trim();
|
|
10766
10766
|
if (isDigits4(c) && Number.parseInt(c, 10) > 0) {
|
|
10767
10767
|
const details = await executeMikrotikCommand(`/ipv6 firewall filter print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
10768
|
-
return `IPv6 firewall filter rule created successfully:
|
|
10768
|
+
return readBackUnavailable(details) ? "IPv6 firewall filter rule created successfully." : `IPv6 firewall filter rule created successfully:
|
|
10769
10769
|
|
|
10770
10770
|
${details}`;
|
|
10771
10771
|
}
|
|
10772
|
-
return "IPv6 firewall filter rule
|
|
10772
|
+
return "IPv6 firewall filter rule created successfully.";
|
|
10773
10773
|
}
|
|
10774
10774
|
}),
|
|
10775
10775
|
defineTool({
|
|
@@ -11125,11 +11125,11 @@ ${details}`;
|
|
|
11125
11125
|
const c = count.trim();
|
|
11126
11126
|
if (isDigits5(c) && Number.parseInt(c, 10) > 0) {
|
|
11127
11127
|
const details = await executeMikrotikCommand(`/ipv6 firewall nat print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
11128
|
-
return `IPv6 firewall NAT rule created successfully:
|
|
11128
|
+
return readBackUnavailable(details) ? "IPv6 firewall NAT rule created successfully." : `IPv6 firewall NAT rule created successfully:
|
|
11129
11129
|
|
|
11130
11130
|
${details}`;
|
|
11131
11131
|
}
|
|
11132
|
-
return "IPv6 firewall NAT rule
|
|
11132
|
+
return "IPv6 firewall NAT rule created successfully.";
|
|
11133
11133
|
}
|
|
11134
11134
|
}),
|
|
11135
11135
|
defineTool({
|
|
@@ -11493,11 +11493,11 @@ ${details}`;
|
|
|
11493
11493
|
const c = count.trim();
|
|
11494
11494
|
if (isDigits6(c) && Number.parseInt(c, 10) > 0) {
|
|
11495
11495
|
const details = await executeMikrotikCommand(`/ipv6 firewall mangle print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
11496
|
-
return `IPv6 firewall mangle rule created successfully:
|
|
11496
|
+
return readBackUnavailable(details) ? "IPv6 firewall mangle rule created successfully." : `IPv6 firewall mangle rule created successfully:
|
|
11497
11497
|
|
|
11498
11498
|
${details}`;
|
|
11499
11499
|
}
|
|
11500
|
-
return "IPv6 firewall mangle rule
|
|
11500
|
+
return "IPv6 firewall mangle rule created successfully.";
|
|
11501
11501
|
}
|
|
11502
11502
|
}),
|
|
11503
11503
|
defineTool({
|
|
@@ -11794,11 +11794,11 @@ ${details}`;
|
|
|
11794
11794
|
const c = count.trim();
|
|
11795
11795
|
if (isDigits7(c) && Number.parseInt(c, 10) > 0) {
|
|
11796
11796
|
const details = await executeMikrotikCommand(`/ipv6 firewall raw print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
11797
|
-
return `IPv6 firewall raw rule created successfully:
|
|
11797
|
+
return readBackUnavailable(details) ? "IPv6 firewall raw rule created successfully." : `IPv6 firewall raw rule created successfully:
|
|
11798
11798
|
|
|
11799
11799
|
${details}`;
|
|
11800
11800
|
}
|
|
11801
|
-
return "IPv6 firewall raw rule
|
|
11801
|
+
return "IPv6 firewall raw rule created successfully.";
|
|
11802
11802
|
}
|
|
11803
11803
|
}),
|
|
11804
11804
|
defineTool({
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
20668
|
-
|
|
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
|
|
20676
|
-
|
|
20677
|
-
|
|
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
|
|
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 = [
|
|
@@ -21587,11 +21616,11 @@ ${details}`;
|
|
|
21587
21616
|
const c = count.trim();
|
|
21588
21617
|
if (isDigits8(c) && Number.parseInt(c, 10) > 0) {
|
|
21589
21618
|
const details = await executeMikrotikCommand(`/interface ethernet switch rule print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
21590
|
-
return `Switch rule added successfully:
|
|
21619
|
+
return readBackUnavailable(details) ? "Switch rule added successfully." : `Switch rule added successfully:
|
|
21591
21620
|
|
|
21592
21621
|
${details}`;
|
|
21593
21622
|
}
|
|
21594
|
-
return "Switch rule
|
|
21623
|
+
return "Switch rule added successfully.";
|
|
21595
21624
|
}
|
|
21596
21625
|
}),
|
|
21597
21626
|
defineTool({
|
|
@@ -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-
|
|
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) {
|
|
@@ -8014,11 +8014,11 @@ ${details}`;
|
|
|
8014
8014
|
const c = count.trim();
|
|
8015
8015
|
if (isDigits(c) && Number.parseInt(c, 10) > 0) {
|
|
8016
8016
|
const details = await executeMikrotikCommand(`/ip firewall filter print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
8017
|
-
return `Firewall filter rule created successfully:
|
|
8017
|
+
return readBackUnavailable(details) ? "Firewall filter rule created successfully." : `Firewall filter rule created successfully:
|
|
8018
8018
|
|
|
8019
8019
|
${details}`;
|
|
8020
8020
|
}
|
|
8021
|
-
return "Firewall filter rule
|
|
8021
|
+
return "Firewall filter rule created successfully.";
|
|
8022
8022
|
}
|
|
8023
8023
|
}),
|
|
8024
8024
|
defineTool({
|
|
@@ -8428,11 +8428,11 @@ ${details}`;
|
|
|
8428
8428
|
const c = count.trim();
|
|
8429
8429
|
if (isDigits2(c) && Number.parseInt(c, 10) > 0) {
|
|
8430
8430
|
const details = await executeMikrotikCommand(`/ip firewall nat print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
8431
|
-
return `NAT rule created successfully:
|
|
8431
|
+
return readBackUnavailable(details) ? "NAT rule created successfully." : `NAT rule created successfully:
|
|
8432
8432
|
|
|
8433
8433
|
${details}`;
|
|
8434
8434
|
}
|
|
8435
|
-
return "NAT rule
|
|
8435
|
+
return "NAT rule created successfully.";
|
|
8436
8436
|
}
|
|
8437
8437
|
}),
|
|
8438
8438
|
defineTool({
|
|
@@ -8833,11 +8833,11 @@ ${details}`;
|
|
|
8833
8833
|
const c = count.trim();
|
|
8834
8834
|
if (isDigits3(c) && Number.parseInt(c, 10) > 0) {
|
|
8835
8835
|
const details = await executeMikrotikCommand(`/ip firewall mangle print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
8836
|
-
return `Mangle rule created successfully:
|
|
8836
|
+
return readBackUnavailable(details) ? "Mangle rule created successfully." : `Mangle rule created successfully:
|
|
8837
8837
|
|
|
8838
8838
|
${details}`;
|
|
8839
8839
|
}
|
|
8840
|
-
return "Mangle rule
|
|
8840
|
+
return "Mangle rule created successfully.";
|
|
8841
8841
|
}
|
|
8842
8842
|
}),
|
|
8843
8843
|
defineTool({
|
|
@@ -10741,11 +10741,11 @@ ${details}`;
|
|
|
10741
10741
|
const c = count.trim();
|
|
10742
10742
|
if (isDigits4(c) && Number.parseInt(c, 10) > 0) {
|
|
10743
10743
|
const details = await executeMikrotikCommand(`/ipv6 firewall filter print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
10744
|
-
return `IPv6 firewall filter rule created successfully:
|
|
10744
|
+
return readBackUnavailable(details) ? "IPv6 firewall filter rule created successfully." : `IPv6 firewall filter rule created successfully:
|
|
10745
10745
|
|
|
10746
10746
|
${details}`;
|
|
10747
10747
|
}
|
|
10748
|
-
return "IPv6 firewall filter rule
|
|
10748
|
+
return "IPv6 firewall filter rule created successfully.";
|
|
10749
10749
|
}
|
|
10750
10750
|
}),
|
|
10751
10751
|
defineTool({
|
|
@@ -11101,11 +11101,11 @@ ${details}`;
|
|
|
11101
11101
|
const c = count.trim();
|
|
11102
11102
|
if (isDigits5(c) && Number.parseInt(c, 10) > 0) {
|
|
11103
11103
|
const details = await executeMikrotikCommand(`/ipv6 firewall nat print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
11104
|
-
return `IPv6 firewall NAT rule created successfully:
|
|
11104
|
+
return readBackUnavailable(details) ? "IPv6 firewall NAT rule created successfully." : `IPv6 firewall NAT rule created successfully:
|
|
11105
11105
|
|
|
11106
11106
|
${details}`;
|
|
11107
11107
|
}
|
|
11108
|
-
return "IPv6 firewall NAT rule
|
|
11108
|
+
return "IPv6 firewall NAT rule created successfully.";
|
|
11109
11109
|
}
|
|
11110
11110
|
}),
|
|
11111
11111
|
defineTool({
|
|
@@ -11469,11 +11469,11 @@ ${details}`;
|
|
|
11469
11469
|
const c = count.trim();
|
|
11470
11470
|
if (isDigits6(c) && Number.parseInt(c, 10) > 0) {
|
|
11471
11471
|
const details = await executeMikrotikCommand(`/ipv6 firewall mangle print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
11472
|
-
return `IPv6 firewall mangle rule created successfully:
|
|
11472
|
+
return readBackUnavailable(details) ? "IPv6 firewall mangle rule created successfully." : `IPv6 firewall mangle rule created successfully:
|
|
11473
11473
|
|
|
11474
11474
|
${details}`;
|
|
11475
11475
|
}
|
|
11476
|
-
return "IPv6 firewall mangle rule
|
|
11476
|
+
return "IPv6 firewall mangle rule created successfully.";
|
|
11477
11477
|
}
|
|
11478
11478
|
}),
|
|
11479
11479
|
defineTool({
|
|
@@ -11770,11 +11770,11 @@ ${details}`;
|
|
|
11770
11770
|
const c = count.trim();
|
|
11771
11771
|
if (isDigits7(c) && Number.parseInt(c, 10) > 0) {
|
|
11772
11772
|
const details = await executeMikrotikCommand(`/ipv6 firewall raw print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
11773
|
-
return `IPv6 firewall raw rule created successfully:
|
|
11773
|
+
return readBackUnavailable(details) ? "IPv6 firewall raw rule created successfully." : `IPv6 firewall raw rule created successfully:
|
|
11774
11774
|
|
|
11775
11775
|
${details}`;
|
|
11776
11776
|
}
|
|
11777
|
-
return "IPv6 firewall raw rule
|
|
11777
|
+
return "IPv6 firewall raw rule created successfully.";
|
|
11778
11778
|
}
|
|
11779
11779
|
}),
|
|
11780
11780
|
defineTool({
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
20644
|
-
|
|
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
|
|
20652
|
-
|
|
20653
|
-
|
|
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
|
|
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 = [
|
|
@@ -21563,11 +21592,11 @@ ${details}`;
|
|
|
21563
21592
|
const c = count.trim();
|
|
21564
21593
|
if (isDigits8(c) && Number.parseInt(c, 10) > 0) {
|
|
21565
21594
|
const details = await executeMikrotikCommand(`/interface ethernet switch rule print detail from=${Number.parseInt(c, 10) - 1}`, ctx);
|
|
21566
|
-
return `Switch rule added successfully:
|
|
21595
|
+
return readBackUnavailable(details) ? "Switch rule added successfully." : `Switch rule added successfully:
|
|
21567
21596
|
|
|
21568
21597
|
${details}`;
|
|
21569
21598
|
}
|
|
21570
|
-
return "Switch rule
|
|
21599
|
+
return "Switch rule added successfully.";
|
|
21571
21600
|
}
|
|
21572
21601
|
}),
|
|
21573
21602
|
defineTool({
|
package/package.json
CHANGED