@usex/mikrotik-mcp 3.19.0 → 3.21.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 +194 -31
- package/dist/index.d.ts +16 -2
- package/dist/index.js +194 -31
- package/package.json +1 -1
- package/schemas/tool-catalog.json +207 -3
- package/schemas/tools/create_mangle_rule.json +91 -1
- package/schemas/tools/create_nat_rule.json +23 -0
- package/schemas/tools/update_mangle_rule.json +71 -0
- package/schemas/tools/update_nat_rule.json +20 -0
package/dist/index.js
CHANGED
|
@@ -609,7 +609,7 @@ class MikroTikMacTelnetClient {
|
|
|
609
609
|
return false;
|
|
610
610
|
}
|
|
611
611
|
}
|
|
612
|
-
async run(command) {
|
|
612
|
+
async run(command, _opts = {}) {
|
|
613
613
|
if (!this.console || !this.console.isReady) {
|
|
614
614
|
throw new Error("Not connected to MikroTik device (MAC-Telnet)");
|
|
615
615
|
}
|
|
@@ -686,7 +686,7 @@ class MikroTikSSHClient {
|
|
|
686
686
|
}).connect(cfg);
|
|
687
687
|
});
|
|
688
688
|
}
|
|
689
|
-
run(command) {
|
|
689
|
+
run(command, opts = {}) {
|
|
690
690
|
if (!this.client) {
|
|
691
691
|
return Promise.reject(new Error("Not connected to MikroTik device"));
|
|
692
692
|
}
|
|
@@ -699,11 +699,30 @@ class MikroTikSSHClient {
|
|
|
699
699
|
}
|
|
700
700
|
const stdout = [];
|
|
701
701
|
const stderrBuf = [];
|
|
702
|
-
|
|
702
|
+
let settled = false;
|
|
703
|
+
let timer;
|
|
704
|
+
const finish = () => {
|
|
705
|
+
if (settled)
|
|
706
|
+
return;
|
|
707
|
+
settled = true;
|
|
708
|
+
if (timer)
|
|
709
|
+
clearTimeout(timer);
|
|
703
710
|
const out = decodeOutput(Buffer.concat(stdout));
|
|
704
711
|
const error = decodeOutput(Buffer.concat(stderrBuf));
|
|
705
712
|
resolve2(error && !out ? error : out);
|
|
706
|
-
}
|
|
713
|
+
};
|
|
714
|
+
if (opts.maxMs && opts.maxMs > 0) {
|
|
715
|
+
timer = setTimeout(() => {
|
|
716
|
+
try {
|
|
717
|
+
stream.signal("INT");
|
|
718
|
+
} catch {}
|
|
719
|
+
try {
|
|
720
|
+
stream.close();
|
|
721
|
+
} catch {}
|
|
722
|
+
finish();
|
|
723
|
+
}, opts.maxMs);
|
|
724
|
+
}
|
|
725
|
+
stream.on("close", finish).on("data", (d) => stdout.push(d)).stderr.on("data", (d) => stderrBuf.push(d));
|
|
707
726
|
});
|
|
708
727
|
});
|
|
709
728
|
}
|
|
@@ -768,7 +787,6 @@ function connectErrorMessage(name, dc, lastError) {
|
|
|
768
787
|
|
|
769
788
|
// src/ssh/safe-mode.ts
|
|
770
789
|
var PROMPT_RE = /\[.+?@.+?\] (?:<SAFE> )?> ?$/m;
|
|
771
|
-
var NORMAL_PROMPT_RE = /\[.+?@.+?\] > ?$/m;
|
|
772
790
|
var ANSI_RE = /\x1B(?:\[[0-9;]*[mA-HJ-MSTfhilnprsu]|[()][0-9A-Za-z]|\[?\?\d+[hl])/g;
|
|
773
791
|
function stripAnsi(text) {
|
|
774
792
|
return text.replace(ANSI_RE, "");
|
|
@@ -779,13 +797,17 @@ function isSafeModeActivated(response) {
|
|
|
779
797
|
return true;
|
|
780
798
|
return /safe mode[^\n]*\b(?:success|taken|enabled|active)\b/i.test(response);
|
|
781
799
|
}
|
|
782
|
-
function
|
|
800
|
+
function lastNonEmptyLine(response) {
|
|
783
801
|
const lines = response.replace(/\r/g, "").split(`
|
|
784
802
|
`).map((l) => l.trimEnd()).filter(Boolean);
|
|
785
|
-
|
|
786
|
-
|
|
803
|
+
return lines.at(-1) ?? "";
|
|
804
|
+
}
|
|
805
|
+
function classifyPrompt(response) {
|
|
806
|
+
const last = lastNonEmptyLine(response);
|
|
807
|
+
if (!PROMPT_RE.test(last))
|
|
808
|
+
return "unknown";
|
|
809
|
+
return last.includes("<SAFE>") ? "safe" : "released";
|
|
787
810
|
}
|
|
788
|
-
|
|
789
811
|
class SafeModeManager {
|
|
790
812
|
deviceName;
|
|
791
813
|
ssh = null;
|
|
@@ -860,13 +882,30 @@ class SafeModeManager {
|
|
|
860
882
|
return this.lock(async () => {
|
|
861
883
|
if (!this.active || !this.channel)
|
|
862
884
|
return "Safe mode is not active. Nothing to commit.";
|
|
885
|
+
this.channel.write(`
|
|
886
|
+
`);
|
|
887
|
+
const probe = await this.readSettledPrompt();
|
|
888
|
+
const before = classifyPrompt(probe);
|
|
889
|
+
if (before === "released") {
|
|
890
|
+
this.cleanup();
|
|
891
|
+
return "Safe mode already exited \u2014 your changes are committed. Safe mode DISABLED.";
|
|
892
|
+
}
|
|
893
|
+
if (before === "unknown") {
|
|
894
|
+
return "Could not read a prompt to determine Safe Mode state. The session is left open so " + "nothing is reverted \u2014 call get_safe_mode_status, retry commit_safe_mode, or " + `rollback_safe_mode. Last output: ${probe.slice(-160)}`;
|
|
895
|
+
}
|
|
863
896
|
this.channel.write(CTRL_X);
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
897
|
+
this.channel.write(`
|
|
898
|
+
`);
|
|
899
|
+
const after = await this.readSettledPrompt();
|
|
900
|
+
switch (classifyPrompt(after)) {
|
|
901
|
+
case "released":
|
|
902
|
+
this.cleanup();
|
|
903
|
+
return "Changes committed successfully. Safe mode DISABLED.";
|
|
904
|
+
case "safe":
|
|
905
|
+
return "Commit not completed \u2014 the device is still in Safe Mode. Your changes remain held " + "in memory (not reverted); call commit_safe_mode again to retry, or rollback_safe_mode " + "to discard them.";
|
|
906
|
+
default:
|
|
907
|
+
return "Commit status unclear \u2014 no prompt seen after exiting Safe Mode. The session is left " + "open so nothing is reverted; verify with get_safe_mode_status or retry commit_safe_mode. " + `Last output: ${after.slice(-160)}`;
|
|
867
908
|
}
|
|
868
|
-
this.cleanup();
|
|
869
|
-
return "Changes committed successfully. Safe mode DISABLED.";
|
|
870
909
|
});
|
|
871
910
|
}
|
|
872
911
|
rollback() {
|
|
@@ -902,6 +941,33 @@ class SafeModeManager {
|
|
|
902
941
|
channel.on("data", onData);
|
|
903
942
|
});
|
|
904
943
|
}
|
|
944
|
+
readSettledPrompt(quietMs = 450, maxMs = 8000) {
|
|
945
|
+
const channel = this.channel;
|
|
946
|
+
if (!channel)
|
|
947
|
+
return Promise.resolve("");
|
|
948
|
+
return new Promise((resolve2) => {
|
|
949
|
+
let buf = "";
|
|
950
|
+
let quiet;
|
|
951
|
+
let hard;
|
|
952
|
+
function done() {
|
|
953
|
+
clearTimeout(hard);
|
|
954
|
+
if (quiet)
|
|
955
|
+
clearTimeout(quiet);
|
|
956
|
+
channel.removeListener("data", onData);
|
|
957
|
+
resolve2(stripAnsi(buf));
|
|
958
|
+
}
|
|
959
|
+
function onData(chunk) {
|
|
960
|
+
buf += decodeOutput(chunk);
|
|
961
|
+
if (PROMPT_RE.test(lastNonEmptyLine(stripAnsi(buf)))) {
|
|
962
|
+
if (quiet)
|
|
963
|
+
clearTimeout(quiet);
|
|
964
|
+
quiet = setTimeout(done, quietMs);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
hard = setTimeout(done, maxMs);
|
|
968
|
+
channel.on("data", onData);
|
|
969
|
+
});
|
|
970
|
+
}
|
|
905
971
|
extractOutput(raw, command) {
|
|
906
972
|
const text = raw.replace(/\r\n/g, `
|
|
907
973
|
`).replace(/\r/g, `
|
|
@@ -949,7 +1015,7 @@ function getSafeModeManager(deviceName) {
|
|
|
949
1015
|
}
|
|
950
1016
|
|
|
951
1017
|
// src/core/connector.ts
|
|
952
|
-
async function runOnce(command, deviceName) {
|
|
1018
|
+
async function runOnce(command, deviceName, opts) {
|
|
953
1019
|
const name = resolveDeviceName(deviceName);
|
|
954
1020
|
const dc = getDevice(deviceName);
|
|
955
1021
|
const client = createDeviceClient(dc);
|
|
@@ -957,12 +1023,12 @@ async function runOnce(command, deviceName) {
|
|
|
957
1023
|
if (!await client.connect()) {
|
|
958
1024
|
throw new Error(connectErrorMessage(name, dc, client.lastError));
|
|
959
1025
|
}
|
|
960
|
-
return await client.run(command);
|
|
1026
|
+
return await client.run(command, opts);
|
|
961
1027
|
} finally {
|
|
962
1028
|
client.disconnect();
|
|
963
1029
|
}
|
|
964
1030
|
}
|
|
965
|
-
async function executeMikrotikCommand(command, ctx) {
|
|
1031
|
+
async function executeMikrotikCommand(command, ctx, opts) {
|
|
966
1032
|
const deviceName = resolveDeviceName(ctx.device);
|
|
967
1033
|
const safe = getSafeModeManager(deviceName);
|
|
968
1034
|
if (safe.isActive) {
|
|
@@ -970,7 +1036,7 @@ async function executeMikrotikCommand(command, ctx) {
|
|
|
970
1036
|
return safe.execute(command);
|
|
971
1037
|
}
|
|
972
1038
|
ctx.info(`[${deviceName}] Executing MikroTik command: ${command}`);
|
|
973
|
-
return runOnce(command, ctx.device);
|
|
1039
|
+
return runOnce(command, ctx.device, opts);
|
|
974
1040
|
}
|
|
975
1041
|
// src/core/registry.ts
|
|
976
1042
|
import { z as z2 } from "zod";
|
|
@@ -991,7 +1057,7 @@ function createContext(sendLog, device) {
|
|
|
991
1057
|
}
|
|
992
1058
|
|
|
993
1059
|
// src/core/routeros.ts
|
|
994
|
-
var BARE_SAFE = /^[\w
|
|
1060
|
+
var BARE_SAFE = /^[\w.\-:/,*@!]+$/;
|
|
995
1061
|
function quoteValue(value) {
|
|
996
1062
|
if (typeof value === "number" || typeof value === "boolean")
|
|
997
1063
|
return String(value);
|
|
@@ -1062,6 +1128,16 @@ function isEmpty(result) {
|
|
|
1062
1128
|
const t = result.trim();
|
|
1063
1129
|
return t === "" || t === "no such item" || t === "no such item (4)";
|
|
1064
1130
|
}
|
|
1131
|
+
function flattenLiveOutput(text) {
|
|
1132
|
+
return text.split(`
|
|
1133
|
+
`).map((line) => {
|
|
1134
|
+
const segs = line.split("\r").filter((s) => s.trim() !== "");
|
|
1135
|
+
return segs.length ? segs[segs.length - 1] : "";
|
|
1136
|
+
}).join(`
|
|
1137
|
+
`).replace(/\n{3,}/g, `
|
|
1138
|
+
|
|
1139
|
+
`).trim();
|
|
1140
|
+
}
|
|
1065
1141
|
function extractCreatedId(output) {
|
|
1066
1142
|
const star = output.match(/\*[0-9A-Fa-f]+/);
|
|
1067
1143
|
if (star)
|
|
@@ -6329,6 +6405,12 @@ async function updateNatRule(a, ctx) {
|
|
|
6329
6405
|
put("protocol", a.protocol);
|
|
6330
6406
|
put("in-interface", a.in_interface);
|
|
6331
6407
|
put("out-interface", a.out_interface);
|
|
6408
|
+
put("in-interface-list", a.in_interface_list);
|
|
6409
|
+
put("out-interface-list", a.out_interface_list);
|
|
6410
|
+
put("src-address-list", a.src_address_list);
|
|
6411
|
+
put("dst-address-list", a.dst_address_list);
|
|
6412
|
+
put("connection-mark", a.connection_mark);
|
|
6413
|
+
put("connection-nat-state", a.connection_nat_state);
|
|
6332
6414
|
put("to-addresses", a.to_addresses);
|
|
6333
6415
|
put("to-ports", a.to_ports);
|
|
6334
6416
|
if (a.comment !== undefined)
|
|
@@ -6365,8 +6447,14 @@ var firewallNatTools = [
|
|
|
6365
6447
|
src_port: z24.string().optional(),
|
|
6366
6448
|
dst_port: z24.string().optional(),
|
|
6367
6449
|
protocol: z24.string().optional(),
|
|
6368
|
-
in_interface: z24.string().optional(),
|
|
6450
|
+
in_interface: z24.string().optional().describe('Negatable, e.g. "!ether1"'),
|
|
6369
6451
|
out_interface: z24.string().optional(),
|
|
6452
|
+
in_interface_list: z24.string().optional().describe('Interface list, e.g. "WAN" or "!LAN"'),
|
|
6453
|
+
out_interface_list: z24.string().optional(),
|
|
6454
|
+
src_address_list: z24.string().optional().describe('Match src in a named list; negate "!name"'),
|
|
6455
|
+
dst_address_list: z24.string().optional().describe('Match dst in a named list; negate "!name"'),
|
|
6456
|
+
connection_mark: z24.string().optional(),
|
|
6457
|
+
connection_nat_state: z24.string().optional().describe('"srcnat" / "dstnat" / "!dstnat"'),
|
|
6370
6458
|
to_addresses: z24.string().optional().describe('Single IP or range e.g. "10.0.0.1" or "10.0.0.1-10.0.0.10"'),
|
|
6371
6459
|
to_ports: z24.string().optional().describe('Single port or range e.g. "8080" or "8080-8090"'),
|
|
6372
6460
|
comment: z24.string().optional(),
|
|
@@ -6406,7 +6494,7 @@ var firewallNatTools = [
|
|
|
6406
6494
|
} else if (a.chain === "dstnat" && !dstnatActions.includes(a.action)) {
|
|
6407
6495
|
return `Error: Invalid action '${a.action}' for dstnat. Must be one of: ${dstnatActions.join(", ")}`;
|
|
6408
6496
|
}
|
|
6409
|
-
const cmd = new Cmd("/ip firewall nat add").set("chain", a.chain).set("action", a.action).opt("src-address", a.src_address).opt("dst-address", a.dst_address).opt("src-port", a.src_port).opt("dst-port", a.dst_port).opt("protocol", a.protocol).opt("in-interface", a.in_interface).opt("out-interface", a.out_interface).opt("to-addresses", a.to_addresses).opt("to-ports", a.to_ports).opt("comment", a.comment).flag("disabled", a.disabled).flag("log", a.log).opt("log-prefix", a.log ? a.log_prefix : undefined).opt("place-before", a.place_before).build();
|
|
6497
|
+
const cmd = new Cmd("/ip firewall nat add").set("chain", a.chain).set("action", a.action).opt("src-address", a.src_address).opt("dst-address", a.dst_address).opt("src-port", a.src_port).opt("dst-port", a.dst_port).opt("protocol", a.protocol).opt("in-interface", a.in_interface).opt("out-interface", a.out_interface).opt("in-interface-list", a.in_interface_list).opt("out-interface-list", a.out_interface_list).opt("src-address-list", a.src_address_list).opt("dst-address-list", a.dst_address_list).opt("connection-mark", a.connection_mark).opt("connection-nat-state", a.connection_nat_state).opt("to-addresses", a.to_addresses).opt("to-ports", a.to_ports).opt("comment", a.comment).flag("disabled", a.disabled).flag("log", a.log).opt("log-prefix", a.log ? a.log_prefix : undefined).opt("place-before", a.place_before).build();
|
|
6410
6498
|
const result = await executeMikrotikCommand(cmd, ctx);
|
|
6411
6499
|
const trimmed = result.trim();
|
|
6412
6500
|
if (looksLikeError(trimmed)) {
|
|
@@ -6504,6 +6592,12 @@ ${result}`;
|
|
|
6504
6592
|
protocol: z24.string().optional(),
|
|
6505
6593
|
in_interface: z24.string().optional(),
|
|
6506
6594
|
out_interface: z24.string().optional(),
|
|
6595
|
+
in_interface_list: z24.string().optional(),
|
|
6596
|
+
out_interface_list: z24.string().optional(),
|
|
6597
|
+
src_address_list: z24.string().optional().describe('Negate with "!name"'),
|
|
6598
|
+
dst_address_list: z24.string().optional().describe('Negate with "!name"'),
|
|
6599
|
+
connection_mark: z24.string().optional(),
|
|
6600
|
+
connection_nat_state: z24.string().optional(),
|
|
6507
6601
|
to_addresses: z24.string().optional(),
|
|
6508
6602
|
to_ports: z24.string().optional(),
|
|
6509
6603
|
comment: z24.string().optional(),
|
|
@@ -6596,10 +6690,33 @@ async function updateMangleRule(a, ctx) {
|
|
|
6596
6690
|
put("protocol", a.protocol);
|
|
6597
6691
|
put("in-interface", a.in_interface);
|
|
6598
6692
|
put("out-interface", a.out_interface);
|
|
6693
|
+
put("in-interface-list", a.in_interface_list);
|
|
6694
|
+
put("out-interface-list", a.out_interface_list);
|
|
6695
|
+
put("src-address-list", a.src_address_list);
|
|
6696
|
+
put("dst-address-list", a.dst_address_list);
|
|
6697
|
+
put("src-address-type", a.src_address_type);
|
|
6698
|
+
put("dst-address-type", a.dst_address_type);
|
|
6599
6699
|
put("connection-mark", a.connection_mark);
|
|
6600
6700
|
put("packet-mark", a.packet_mark);
|
|
6601
6701
|
put("routing-mark", a.routing_mark);
|
|
6602
6702
|
put("connection-state", a.connection_state);
|
|
6703
|
+
put("connection-nat-state", a.connection_nat_state);
|
|
6704
|
+
put("connection-type", a.connection_type);
|
|
6705
|
+
put("connection-bytes", a.connection_bytes);
|
|
6706
|
+
put("connection-limit", a.connection_limit);
|
|
6707
|
+
put("connection-rate", a.connection_rate);
|
|
6708
|
+
put("per-connection-classifier", a.per_connection_classifier);
|
|
6709
|
+
put("tcp-flags", a.tcp_flags);
|
|
6710
|
+
put("dscp", a.dscp);
|
|
6711
|
+
put("priority", a.priority);
|
|
6712
|
+
put("packet-size", a.packet_size);
|
|
6713
|
+
put("layer7-protocol", a.layer7_protocol);
|
|
6714
|
+
put("ipsec-policy", a.ipsec_policy);
|
|
6715
|
+
put("nth", a.nth);
|
|
6716
|
+
put("random", a.random);
|
|
6717
|
+
put("time", a.time);
|
|
6718
|
+
put("hotspot", a.hotspot);
|
|
6719
|
+
put("p2p", a.p2p);
|
|
6603
6720
|
put("new-connection-mark", a.new_connection_mark);
|
|
6604
6721
|
put("new-packet-mark", a.new_packet_mark);
|
|
6605
6722
|
put("new-routing-mark", a.new_routing_mark);
|
|
@@ -6636,7 +6753,7 @@ var firewallMangleTools = [
|
|
|
6636
6753
|
name: "create_mangle_rule",
|
|
6637
6754
|
title: "Create IPv4 Firewall Mangle Rule",
|
|
6638
6755
|
annotations: WRITE,
|
|
6639
|
-
description: "Creates an IPv4 mangle rule (`/ip firewall mangle add`) \u2014 the packet-marking and header-modification table, " + "used to mark connections/packets/routing (for policy routing, QoS and per-connection classification) or to change DSCP/TTL/MSS. " + "For accept/drop decisions use create_filter_rule; for address translation use create_nat_rule; for IPv6 mangle use create_ipv6_mangle_rule. " + "chain: prerouting/input/forward/output/postrouting. " + "action: mark-connection/mark-packet/mark-routing/change-dscp/change-ttl/change-mss/add-src-to-address-list/add-dst-to-address-list/fasttrack-connection/route/set-priority/accept/etc. " + "Set the matching new-*-mark field for mark-* actions and keep passthrough=true so later rules can also match the same packet; " + "for add-*-to-address-list set address_list (and optionally address_list_timeout). " + "place_before accepts a rule number or ID (*N) to control insertion position. " + "Returns the created rule's detail including its `.id`.",
|
|
6756
|
+
description: "Creates an IPv4 mangle rule (`/ip firewall mangle add`) \u2014 the packet-marking and header-modification table, " + "used to mark connections/packets/routing (for policy routing, QoS and per-connection classification) or to change DSCP/TTL/MSS. " + "For accept/drop decisions use create_filter_rule; for address translation use create_nat_rule; for IPv6 mangle use create_ipv6_mangle_rule. " + "chain: prerouting/input/forward/output/postrouting. " + "action: mark-connection/mark-packet/mark-routing/change-dscp/change-ttl/change-mss/add-src-to-address-list/add-dst-to-address-list/fasttrack-connection/route/set-priority/accept/etc. " + "Set the matching new-*-mark field for mark-* actions and keep passthrough=true so later rules can also match the same packet; " + "for add-*-to-address-list set address_list (and optionally address_list_timeout). " + "Full match surface: address-lists (src_address_list/dst_address_list \u2014 negate with a leading '!', e.g. dst_address_list='!IR' to route traffic NOT bound for a list), " + "interface-lists, per_connection_classifier (PCC load-balancing), connection_nat_state, tcp_flags, dscp, layer7_protocol, time, and more. " + "Typical policy-routing rule: chain=prerouting, src_address=<lan>, dst_address_list='!IR', action=mark-routing, new_routing_mark=<table>, then a routing rule/route uses that table. " + "place_before accepts a rule number or ID (*N) to control insertion position. " + "Returns the created rule's detail including its `.id`.",
|
|
6640
6757
|
inputSchema: {
|
|
6641
6758
|
chain: z25.enum(["prerouting", "input", "forward", "output", "postrouting"]),
|
|
6642
6759
|
action: z25.enum([
|
|
@@ -6666,12 +6783,35 @@ var firewallMangleTools = [
|
|
|
6666
6783
|
src_port: z25.string().optional(),
|
|
6667
6784
|
dst_port: z25.string().optional(),
|
|
6668
6785
|
protocol: z25.string().optional(),
|
|
6669
|
-
in_interface: z25.string().optional(),
|
|
6786
|
+
in_interface: z25.string().optional().describe('Negatable, e.g. "!ether1"'),
|
|
6670
6787
|
out_interface: z25.string().optional(),
|
|
6788
|
+
in_interface_list: z25.string().optional().describe('Interface list, negatable e.g. "!WAN"'),
|
|
6789
|
+
out_interface_list: z25.string().optional(),
|
|
6790
|
+
src_address_list: z25.string().optional().describe('Match src in a named list; negate "!name"'),
|
|
6791
|
+
dst_address_list: z25.string().optional().describe('Match dst in a named list; negate "!name" (e.g. "!IR" routes foreign traffic)'),
|
|
6792
|
+
src_address_type: z25.string().optional().describe('e.g. "local", "unicast", "!local"'),
|
|
6793
|
+
dst_address_type: z25.string().optional(),
|
|
6671
6794
|
connection_mark: z25.string().optional(),
|
|
6672
6795
|
packet_mark: z25.string().optional(),
|
|
6673
6796
|
routing_mark: z25.string().optional(),
|
|
6674
|
-
connection_state: z25.string().optional().describe('e.g. "new", "established,related", "invalid"'),
|
|
6797
|
+
connection_state: z25.string().optional().describe('e.g. "new", "established,related", "!invalid"'),
|
|
6798
|
+
connection_nat_state: z25.string().optional().describe('"srcnat" / "dstnat" / "!dstnat"'),
|
|
6799
|
+
connection_type: z25.string().optional().describe('Helper, e.g. "sip", "ftp"'),
|
|
6800
|
+
connection_bytes: z25.string().optional().describe('e.g. "1000000-0" (>1 MB connections)'),
|
|
6801
|
+
connection_limit: z25.string().optional().describe('e.g. "100,32"'),
|
|
6802
|
+
connection_rate: z25.string().optional().describe('e.g. "100k-1M"'),
|
|
6803
|
+
per_connection_classifier: z25.string().optional().describe('PCC for load balancing, e.g. "both-addresses:2/0"'),
|
|
6804
|
+
tcp_flags: z25.string().optional().describe('RouterOS flag expression e.g. "syn,!ack"'),
|
|
6805
|
+
dscp: z25.string().optional().describe("Match incoming DSCP (0-63)"),
|
|
6806
|
+
priority: z25.string().optional().describe("Match packet/queue priority (0-63)"),
|
|
6807
|
+
packet_size: z25.string().optional().describe('e.g. "1500" or "0-500"'),
|
|
6808
|
+
layer7_protocol: z25.string().optional().describe("Name of an /ip firewall layer7-protocol regex"),
|
|
6809
|
+
ipsec_policy: z25.string().optional().describe('e.g. "in,ipsec" or "out,none"'),
|
|
6810
|
+
nth: z25.string().optional().describe('e.g. "2,1" \u2014 every 2nd packet'),
|
|
6811
|
+
random: z25.string().optional().describe("Match a random N% of packets (1-99)"),
|
|
6812
|
+
time: z25.string().optional().describe('e.g. "8h-16h,mon,tue,wed,thu,fri"'),
|
|
6813
|
+
hotspot: z25.string().optional().describe('e.g. "auth", "!auth", "from-client"'),
|
|
6814
|
+
p2p: z25.string().optional(),
|
|
6675
6815
|
new_connection_mark: z25.string().optional(),
|
|
6676
6816
|
new_packet_mark: z25.string().optional(),
|
|
6677
6817
|
new_routing_mark: z25.string().optional(),
|
|
@@ -6690,7 +6830,7 @@ var firewallMangleTools = [
|
|
|
6690
6830
|
},
|
|
6691
6831
|
async handler(a, ctx) {
|
|
6692
6832
|
ctx.info(`Creating mangle rule: chain=${a.chain}, action=${a.action}`);
|
|
6693
|
-
const cmd = new Cmd("/ip firewall mangle add").set("chain", a.chain).set("action", a.action).opt("src-address", a.src_address).opt("dst-address", a.dst_address).opt("src-port", a.src_port).opt("dst-port", a.dst_port).opt("protocol", a.protocol).opt("in-interface", a.in_interface).opt("out-interface", a.out_interface).opt("connection-mark", a.connection_mark).opt("packet-mark", a.packet_mark).opt("routing-mark", a.routing_mark).opt("connection-state", a.connection_state).opt("new-connection-mark", a.new_connection_mark).opt("new-packet-mark", a.new_packet_mark).opt("new-routing-mark", a.new_routing_mark).opt("new-dscp", a.new_dscp).opt("new-ttl", a.new_ttl).opt("new-mss", a.new_mss).opt("routing-table", a.routing_table).opt("address-list", a.address_list).opt("address-list-timeout", a.address_list_timeout).bool("passthrough", a.passthrough).opt("comment", a.comment).flag("disabled", a.disabled).flag("log", a.log).opt("log-prefix", a.log ? a.log_prefix : undefined).opt("place-before", a.place_before).build();
|
|
6833
|
+
const cmd = new Cmd("/ip firewall mangle add").set("chain", a.chain).set("action", a.action).opt("src-address", a.src_address).opt("dst-address", a.dst_address).opt("src-port", a.src_port).opt("dst-port", a.dst_port).opt("protocol", a.protocol).opt("in-interface", a.in_interface).opt("out-interface", a.out_interface).opt("in-interface-list", a.in_interface_list).opt("out-interface-list", a.out_interface_list).opt("src-address-list", a.src_address_list).opt("dst-address-list", a.dst_address_list).opt("src-address-type", a.src_address_type).opt("dst-address-type", a.dst_address_type).opt("connection-mark", a.connection_mark).opt("packet-mark", a.packet_mark).opt("routing-mark", a.routing_mark).opt("connection-state", a.connection_state).opt("connection-nat-state", a.connection_nat_state).opt("connection-type", a.connection_type).opt("connection-bytes", a.connection_bytes).opt("connection-limit", a.connection_limit).opt("connection-rate", a.connection_rate).opt("per-connection-classifier", a.per_connection_classifier).opt("tcp-flags", a.tcp_flags).opt("dscp", a.dscp).opt("priority", a.priority).opt("packet-size", a.packet_size).opt("layer7-protocol", a.layer7_protocol).opt("ipsec-policy", a.ipsec_policy).opt("nth", a.nth).opt("random", a.random).opt("time", a.time).opt("hotspot", a.hotspot).opt("p2p", a.p2p).opt("new-connection-mark", a.new_connection_mark).opt("new-packet-mark", a.new_packet_mark).opt("new-routing-mark", a.new_routing_mark).opt("new-dscp", a.new_dscp).opt("new-ttl", a.new_ttl).opt("new-mss", a.new_mss).opt("routing-table", a.routing_table).opt("address-list", a.address_list).opt("address-list-timeout", a.address_list_timeout).bool("passthrough", a.passthrough).opt("comment", a.comment).flag("disabled", a.disabled).flag("log", a.log).opt("log-prefix", a.log ? a.log_prefix : undefined).opt("place-before", a.place_before).build();
|
|
6694
6834
|
const result = await executeMikrotikCommand(cmd, ctx);
|
|
6695
6835
|
const trimmed = result.trim();
|
|
6696
6836
|
if (looksLikeError(trimmed)) {
|
|
@@ -6784,10 +6924,33 @@ ${result}`;
|
|
|
6784
6924
|
protocol: z25.string().optional(),
|
|
6785
6925
|
in_interface: z25.string().optional(),
|
|
6786
6926
|
out_interface: z25.string().optional(),
|
|
6927
|
+
in_interface_list: z25.string().optional(),
|
|
6928
|
+
out_interface_list: z25.string().optional(),
|
|
6929
|
+
src_address_list: z25.string().optional().describe('Negate with "!name" (e.g. "!IR")'),
|
|
6930
|
+
dst_address_list: z25.string().optional().describe('Negate with "!name" (e.g. "!IR")'),
|
|
6931
|
+
src_address_type: z25.string().optional(),
|
|
6932
|
+
dst_address_type: z25.string().optional(),
|
|
6787
6933
|
connection_mark: z25.string().optional(),
|
|
6788
6934
|
packet_mark: z25.string().optional(),
|
|
6789
6935
|
routing_mark: z25.string().optional(),
|
|
6790
6936
|
connection_state: z25.string().optional(),
|
|
6937
|
+
connection_nat_state: z25.string().optional(),
|
|
6938
|
+
connection_type: z25.string().optional(),
|
|
6939
|
+
connection_bytes: z25.string().optional(),
|
|
6940
|
+
connection_limit: z25.string().optional(),
|
|
6941
|
+
connection_rate: z25.string().optional(),
|
|
6942
|
+
per_connection_classifier: z25.string().optional(),
|
|
6943
|
+
tcp_flags: z25.string().optional(),
|
|
6944
|
+
dscp: z25.string().optional(),
|
|
6945
|
+
priority: z25.string().optional(),
|
|
6946
|
+
packet_size: z25.string().optional(),
|
|
6947
|
+
layer7_protocol: z25.string().optional(),
|
|
6948
|
+
ipsec_policy: z25.string().optional(),
|
|
6949
|
+
nth: z25.string().optional(),
|
|
6950
|
+
random: z25.string().optional(),
|
|
6951
|
+
time: z25.string().optional(),
|
|
6952
|
+
hotspot: z25.string().optional(),
|
|
6953
|
+
p2p: z25.string().optional(),
|
|
6791
6954
|
new_connection_mark: z25.string().optional(),
|
|
6792
6955
|
new_packet_mark: z25.string().optional(),
|
|
6793
6956
|
new_routing_mark: z25.string().optional(),
|
|
@@ -11284,7 +11447,7 @@ var networkToolTools = [
|
|
|
11284
11447
|
async handler(a, ctx) {
|
|
11285
11448
|
ctx.info(`Pinging ${a.address} (count=${a.count})`);
|
|
11286
11449
|
const cmd = new Cmd(`/ping ${a.address}`).set("count", a.count).opt("interface", a.interface).opt("src-address", a.src_address).opt("size", a.size).build();
|
|
11287
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
11450
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: a.count * 1500 + 6000 }));
|
|
11288
11451
|
if (looksLikeError(result))
|
|
11289
11452
|
return `Failed to ping ${a.address}: ${result}`;
|
|
11290
11453
|
return isEmpty(result) ? `No response from ${a.address}.` : `PING ${a.address}:
|
|
@@ -11305,7 +11468,7 @@ ${result}`;
|
|
|
11305
11468
|
async handler(a, ctx) {
|
|
11306
11469
|
ctx.info(`Tracerouting ${a.address} (count=${a.count})`);
|
|
11307
11470
|
const cmd = new Cmd(`/tool traceroute ${a.address}`).set("count", a.count).flag("use-dns", a.use_dns).build();
|
|
11308
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
11471
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: 30000 + a.count * 3000 }));
|
|
11309
11472
|
if (looksLikeError(result))
|
|
11310
11473
|
return `Failed to traceroute ${a.address}: ${result}`;
|
|
11311
11474
|
return isEmpty(result) ? `No route information for ${a.address}.` : `TRACEROUTE ${a.address}:
|
|
@@ -11329,7 +11492,7 @@ ${result}`;
|
|
|
11329
11492
|
async handler(a, ctx) {
|
|
11330
11493
|
ctx.info(`Bandwidth test to ${a.address} (duration=${a.duration}s, direction=${a.direction})`);
|
|
11331
11494
|
const cmd = new Cmd(`/tool bandwidth-test ${a.address}`).set("duration", `${a.duration}s`).set("direction", a.direction).set("protocol", a.protocol).opt("user", a.user).opt("password", a.password).build();
|
|
11332
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
11495
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: a.duration * 1000 + 12000 }));
|
|
11333
11496
|
if (looksLikeError(result))
|
|
11334
11497
|
return `Failed to run bandwidth test to ${a.address}: ${result}`;
|
|
11335
11498
|
return isEmpty(result) ? `No bandwidth test results for ${a.address}.` : `BANDWIDTH TEST:
|
|
@@ -11981,7 +12144,7 @@ var floodPingTools = [
|
|
|
11981
12144
|
async handler(a, ctx) {
|
|
11982
12145
|
ctx.info(`Flood-pinging ${a.address} (count=${a.count})`);
|
|
11983
12146
|
const cmd = new Cmd(`/tool flood-ping ${a.address}`).set("count", a.count).opt("size", a.size).opt("interface", a.interface).opt("src-address", a.src_address).build();
|
|
11984
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
12147
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: 30000 }));
|
|
11985
12148
|
if (looksLikeError(result))
|
|
11986
12149
|
return `Failed to flood-ping ${a.address}: ${result}`;
|
|
11987
12150
|
return isEmpty(result) ? `No response from ${a.address}.` : `FLOOD PING ${a.address}:
|
|
@@ -12645,7 +12808,7 @@ var speedTestTools = [
|
|
|
12645
12808
|
async handler(a, ctx) {
|
|
12646
12809
|
ctx.info(`Speed test to ${a.address} (duration=${a.duration}s, direction=${a.direction})`);
|
|
12647
12810
|
const cmd = new Cmd(`/tool speed-test address=${a.address}`).set("duration", `${a.duration}s`).set("direction", a.direction).opt("tcp-connection-count", a.tcp_connection_count).opt("user", a.user).opt("password", a.password).build();
|
|
12648
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
12811
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: a.duration * 1000 + 15000 }));
|
|
12649
12812
|
if (looksLikeError(result))
|
|
12650
12813
|
return `Failed to run speed test to ${a.address}: ${result}`;
|
|
12651
12814
|
return isEmpty(result) ? `No speed-test results for ${a.address}.` : `SPEED TEST:
|
|
@@ -22188,7 +22351,7 @@ function selectToolModules(filter = {}, catalog = moduleCatalog) {
|
|
|
22188
22351
|
// package.json
|
|
22189
22352
|
var package_default = {
|
|
22190
22353
|
name: "@usex/mikrotik-mcp",
|
|
22191
|
-
version: "3.
|
|
22354
|
+
version: "3.21.0",
|
|
22192
22355
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
22193
22356
|
keywords: [
|
|
22194
22357
|
"ai",
|
package/package.json
CHANGED