@usex/mikrotik-mcp 3.30.0 → 3.31.1
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 +46 -11
- package/dist/index.js +46 -11
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -626,10 +626,10 @@ class MikroTikMacTelnetClient {
|
|
|
626
626
|
const explicitSourceMac = this.opts.sourceMac ? parseMac(this.opts.sourceMac) : undefined;
|
|
627
627
|
const host = this.opts.host ?? DEFAULT_MAC_TELNET_BROADCAST;
|
|
628
628
|
const askedDiscovery = host === DEFAULT_MAC_TELNET_BROADCAST && !explicitSourceMac;
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
629
|
+
const candidates = askedDiscovery ? listBroadcastInterfaces() : [];
|
|
630
|
+
const candidatesText = candidates.length ? candidates.map((i) => `${i.name}(${formatMac(i.mac)}\u2192${i.broadcast})`).join(", ") : "NONE \u2014 no usable non-internal IPv4 interface found (is this host on the device's LAN?)";
|
|
631
|
+
if (askedDiscovery)
|
|
632
|
+
logger.info(`[mac-telnet] discovery candidates: ${candidatesText}`);
|
|
633
633
|
logger.info(`[mac-telnet] resolving route to ${this.opts.mac}\u2026`);
|
|
634
634
|
const route = await resolveMacTelnetRoute({
|
|
635
635
|
destinationMac,
|
|
@@ -639,7 +639,7 @@ class MikroTikMacTelnetClient {
|
|
|
639
639
|
explicitSourceMac
|
|
640
640
|
});
|
|
641
641
|
const discoveryFailed = askedDiscovery && route.host === DEFAULT_MAC_TELNET_BROADCAST;
|
|
642
|
-
this.routeHint = discoveryFailed ? "no local interface got a reply from the device during discovery \u2014
|
|
642
|
+
this.routeHint = discoveryFailed ? "no local interface got a reply from the device during discovery \u2014 MAC-Telnet is Layer-2 " + "only, so the machine running this MCP server MUST be on the SAME Ethernet/Wi-Fi segment as " + "the device (it cannot cross any router or the internet). Likely: this host is not on that " + "segment, mac-server is disabled on the device's facing interface (`/tool mac-server`), or a " + "host firewall is dropping the UDP 20561 reply. " + `Interfaces tried: ${candidatesText}. ` + "To pin it, set the device's `macHost` to the LAN's directed broadcast (e.g. 192.168.88.255) " + "and `sourceMac` to the real MAC of the interface on that LAN." : undefined;
|
|
643
643
|
logger.info(`[mac-telnet] route: source ${formatMac(route.sourceMac)} \u2192 ${route.host} ` + `(${explicitSourceMac ? "explicit" : discoveryFailed ? "DISCOVERY FAILED \u2014 limited broadcast" : "discovered"})`);
|
|
644
644
|
const transport = createUdpMacTelnetTransport({
|
|
645
645
|
host: route.host,
|
|
@@ -692,6 +692,7 @@ class MikroTikMacTelnetClient {
|
|
|
692
692
|
// src/ssh/client.ts
|
|
693
693
|
import { readFileSync as readFileSync2 } from "fs";
|
|
694
694
|
import { Client } from "ssh2";
|
|
695
|
+
var RUN_IDLE_TIMEOUT_MS = 60000;
|
|
695
696
|
function decodeOutput(data) {
|
|
696
697
|
if (!data || data.length === 0)
|
|
697
698
|
return "";
|
|
@@ -762,16 +763,43 @@ class MikroTikSSHClient {
|
|
|
762
763
|
const stderrBuf = [];
|
|
763
764
|
let settled = false;
|
|
764
765
|
let timer;
|
|
766
|
+
let idleTimer;
|
|
767
|
+
const clearTimers = () => {
|
|
768
|
+
if (timer)
|
|
769
|
+
clearTimeout(timer);
|
|
770
|
+
if (idleTimer)
|
|
771
|
+
clearTimeout(idleTimer);
|
|
772
|
+
};
|
|
765
773
|
const finish = () => {
|
|
766
774
|
if (settled)
|
|
767
775
|
return;
|
|
768
776
|
settled = true;
|
|
769
|
-
|
|
770
|
-
clearTimeout(timer);
|
|
777
|
+
clearTimers();
|
|
771
778
|
const out = decodeOutput(Buffer.concat(stdout));
|
|
772
779
|
const error = decodeOutput(Buffer.concat(stderrBuf));
|
|
773
780
|
resolve2(error && !out ? error : out);
|
|
774
781
|
};
|
|
782
|
+
const fail = (e) => {
|
|
783
|
+
if (settled)
|
|
784
|
+
return;
|
|
785
|
+
settled = true;
|
|
786
|
+
clearTimers();
|
|
787
|
+
try {
|
|
788
|
+
stream.close();
|
|
789
|
+
} catch {}
|
|
790
|
+
reject(e);
|
|
791
|
+
};
|
|
792
|
+
const armIdle = () => {
|
|
793
|
+
if (idleTimer)
|
|
794
|
+
clearTimeout(idleTimer);
|
|
795
|
+
idleTimer = setTimeout(() => {
|
|
796
|
+
try {
|
|
797
|
+
stream.signal("INT");
|
|
798
|
+
} catch {}
|
|
799
|
+
fail(new Error(`MikroTik command produced no output for ${RUN_IDLE_TIMEOUT_MS / 1000}s and the SSH ` + "channel never closed \u2014 it appears wedged (often a malformed or unbalanced command). " + `Aborted to avoid hanging the connection. Command: ${command.slice(0, 120)}`));
|
|
800
|
+
}, RUN_IDLE_TIMEOUT_MS);
|
|
801
|
+
};
|
|
802
|
+
armIdle();
|
|
775
803
|
if (opts.maxMs && opts.maxMs > 0) {
|
|
776
804
|
timer = setTimeout(() => {
|
|
777
805
|
try {
|
|
@@ -783,7 +811,13 @@ class MikroTikSSHClient {
|
|
|
783
811
|
finish();
|
|
784
812
|
}, opts.maxMs);
|
|
785
813
|
}
|
|
786
|
-
stream.on("close", finish).on("data", (d) =>
|
|
814
|
+
stream.on("close", finish).on("data", (d) => {
|
|
815
|
+
stdout.push(d);
|
|
816
|
+
armIdle();
|
|
817
|
+
}).stderr.on("data", (d) => {
|
|
818
|
+
stderrBuf.push(d);
|
|
819
|
+
armIdle();
|
|
820
|
+
});
|
|
787
821
|
});
|
|
788
822
|
});
|
|
789
823
|
}
|
|
@@ -27075,7 +27109,7 @@ function registerPrompts(server) {
|
|
|
27075
27109
|
// package.json
|
|
27076
27110
|
var package_default = {
|
|
27077
27111
|
name: "@usex/mikrotik-mcp",
|
|
27078
|
-
version: "3.
|
|
27112
|
+
version: "3.31.1",
|
|
27079
27113
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
27080
27114
|
keywords: [
|
|
27081
27115
|
"ai",
|
|
@@ -27168,11 +27202,11 @@ var package_default = {
|
|
|
27168
27202
|
"class-variance-authority": "^0.7.1",
|
|
27169
27203
|
clsx: "^2.1.1",
|
|
27170
27204
|
gsap: "^3.15.0",
|
|
27171
|
-
prettier: "^3.
|
|
27205
|
+
prettier: "^3.9.1",
|
|
27172
27206
|
react: "^19.2.7",
|
|
27173
27207
|
"react-dom": "^19.2.7",
|
|
27174
27208
|
recharts: "^3.9.0",
|
|
27175
|
-
"release-it": "^20.2.
|
|
27209
|
+
"release-it": "^20.2.1",
|
|
27176
27210
|
"tailwind-merge": "^3.6.0",
|
|
27177
27211
|
tailwindcss: "^4.3.1",
|
|
27178
27212
|
vite: "npm:@voidzero-dev/vite-plus-core@latest",
|
|
@@ -27249,6 +27283,7 @@ to use the default. Use list_mikrotik_devices to see them. For cross-device work
|
|
|
27249
27283
|
(e.g. a tunnel between two routers) configure each side by passing the matching
|
|
27250
27284
|
"device", then verify reachability with ping/traceroute from each end.`;
|
|
27251
27285
|
function createServer(opts = {}) {
|
|
27286
|
+
process.title = `Mikrotik MCP Server v${VERSION}`;
|
|
27252
27287
|
const { names, default: defaultDevice } = listDevices();
|
|
27253
27288
|
const readOnly = getConfig().readOnly;
|
|
27254
27289
|
const instructions = names.length > 1 ? INSTRUCTIONS + MULTI_DEVICE_INSTRUCTIONS.replace("{{names}}", names.join(", ")).replace("{{default}}", defaultDevice) : INSTRUCTIONS;
|
package/dist/index.js
CHANGED
|
@@ -618,10 +618,10 @@ class MikroTikMacTelnetClient {
|
|
|
618
618
|
const explicitSourceMac = this.opts.sourceMac ? parseMac(this.opts.sourceMac) : undefined;
|
|
619
619
|
const host = this.opts.host ?? DEFAULT_MAC_TELNET_BROADCAST;
|
|
620
620
|
const askedDiscovery = host === DEFAULT_MAC_TELNET_BROADCAST && !explicitSourceMac;
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
621
|
+
const candidates = askedDiscovery ? listBroadcastInterfaces() : [];
|
|
622
|
+
const candidatesText = candidates.length ? candidates.map((i) => `${i.name}(${formatMac(i.mac)}\u2192${i.broadcast})`).join(", ") : "NONE \u2014 no usable non-internal IPv4 interface found (is this host on the device's LAN?)";
|
|
623
|
+
if (askedDiscovery)
|
|
624
|
+
logger.info(`[mac-telnet] discovery candidates: ${candidatesText}`);
|
|
625
625
|
logger.info(`[mac-telnet] resolving route to ${this.opts.mac}\u2026`);
|
|
626
626
|
const route = await resolveMacTelnetRoute({
|
|
627
627
|
destinationMac,
|
|
@@ -631,7 +631,7 @@ class MikroTikMacTelnetClient {
|
|
|
631
631
|
explicitSourceMac
|
|
632
632
|
});
|
|
633
633
|
const discoveryFailed = askedDiscovery && route.host === DEFAULT_MAC_TELNET_BROADCAST;
|
|
634
|
-
this.routeHint = discoveryFailed ? "no local interface got a reply from the device during discovery \u2014
|
|
634
|
+
this.routeHint = discoveryFailed ? "no local interface got a reply from the device during discovery \u2014 MAC-Telnet is Layer-2 " + "only, so the machine running this MCP server MUST be on the SAME Ethernet/Wi-Fi segment as " + "the device (it cannot cross any router or the internet). Likely: this host is not on that " + "segment, mac-server is disabled on the device's facing interface (`/tool mac-server`), or a " + "host firewall is dropping the UDP 20561 reply. " + `Interfaces tried: ${candidatesText}. ` + "To pin it, set the device's `macHost` to the LAN's directed broadcast (e.g. 192.168.88.255) " + "and `sourceMac` to the real MAC of the interface on that LAN." : undefined;
|
|
635
635
|
logger.info(`[mac-telnet] route: source ${formatMac(route.sourceMac)} \u2192 ${route.host} ` + `(${explicitSourceMac ? "explicit" : discoveryFailed ? "DISCOVERY FAILED \u2014 limited broadcast" : "discovered"})`);
|
|
636
636
|
const transport = createUdpMacTelnetTransport({
|
|
637
637
|
host: route.host,
|
|
@@ -684,6 +684,7 @@ class MikroTikMacTelnetClient {
|
|
|
684
684
|
// src/ssh/client.ts
|
|
685
685
|
import { readFileSync as readFileSync2 } from "fs";
|
|
686
686
|
import { Client } from "ssh2";
|
|
687
|
+
var RUN_IDLE_TIMEOUT_MS = 60000;
|
|
687
688
|
function decodeOutput(data) {
|
|
688
689
|
if (!data || data.length === 0)
|
|
689
690
|
return "";
|
|
@@ -754,16 +755,43 @@ class MikroTikSSHClient {
|
|
|
754
755
|
const stderrBuf = [];
|
|
755
756
|
let settled = false;
|
|
756
757
|
let timer;
|
|
758
|
+
let idleTimer;
|
|
759
|
+
const clearTimers = () => {
|
|
760
|
+
if (timer)
|
|
761
|
+
clearTimeout(timer);
|
|
762
|
+
if (idleTimer)
|
|
763
|
+
clearTimeout(idleTimer);
|
|
764
|
+
};
|
|
757
765
|
const finish = () => {
|
|
758
766
|
if (settled)
|
|
759
767
|
return;
|
|
760
768
|
settled = true;
|
|
761
|
-
|
|
762
|
-
clearTimeout(timer);
|
|
769
|
+
clearTimers();
|
|
763
770
|
const out = decodeOutput(Buffer.concat(stdout));
|
|
764
771
|
const error = decodeOutput(Buffer.concat(stderrBuf));
|
|
765
772
|
resolve2(error && !out ? error : out);
|
|
766
773
|
};
|
|
774
|
+
const fail = (e) => {
|
|
775
|
+
if (settled)
|
|
776
|
+
return;
|
|
777
|
+
settled = true;
|
|
778
|
+
clearTimers();
|
|
779
|
+
try {
|
|
780
|
+
stream.close();
|
|
781
|
+
} catch {}
|
|
782
|
+
reject(e);
|
|
783
|
+
};
|
|
784
|
+
const armIdle = () => {
|
|
785
|
+
if (idleTimer)
|
|
786
|
+
clearTimeout(idleTimer);
|
|
787
|
+
idleTimer = setTimeout(() => {
|
|
788
|
+
try {
|
|
789
|
+
stream.signal("INT");
|
|
790
|
+
} catch {}
|
|
791
|
+
fail(new Error(`MikroTik command produced no output for ${RUN_IDLE_TIMEOUT_MS / 1000}s and the SSH ` + "channel never closed \u2014 it appears wedged (often a malformed or unbalanced command). " + `Aborted to avoid hanging the connection. Command: ${command.slice(0, 120)}`));
|
|
792
|
+
}, RUN_IDLE_TIMEOUT_MS);
|
|
793
|
+
};
|
|
794
|
+
armIdle();
|
|
767
795
|
if (opts.maxMs && opts.maxMs > 0) {
|
|
768
796
|
timer = setTimeout(() => {
|
|
769
797
|
try {
|
|
@@ -775,7 +803,13 @@ class MikroTikSSHClient {
|
|
|
775
803
|
finish();
|
|
776
804
|
}, opts.maxMs);
|
|
777
805
|
}
|
|
778
|
-
stream.on("close", finish).on("data", (d) =>
|
|
806
|
+
stream.on("close", finish).on("data", (d) => {
|
|
807
|
+
stdout.push(d);
|
|
808
|
+
armIdle();
|
|
809
|
+
}).stderr.on("data", (d) => {
|
|
810
|
+
stderrBuf.push(d);
|
|
811
|
+
armIdle();
|
|
812
|
+
});
|
|
779
813
|
});
|
|
780
814
|
});
|
|
781
815
|
}
|
|
@@ -25053,7 +25087,7 @@ function selectToolModules(filter = {}, catalog = moduleCatalog) {
|
|
|
25053
25087
|
// package.json
|
|
25054
25088
|
var package_default = {
|
|
25055
25089
|
name: "@usex/mikrotik-mcp",
|
|
25056
|
-
version: "3.
|
|
25090
|
+
version: "3.31.1",
|
|
25057
25091
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
25058
25092
|
keywords: [
|
|
25059
25093
|
"ai",
|
|
@@ -25146,11 +25180,11 @@ var package_default = {
|
|
|
25146
25180
|
"class-variance-authority": "^0.7.1",
|
|
25147
25181
|
clsx: "^2.1.1",
|
|
25148
25182
|
gsap: "^3.15.0",
|
|
25149
|
-
prettier: "^3.
|
|
25183
|
+
prettier: "^3.9.1",
|
|
25150
25184
|
react: "^19.2.7",
|
|
25151
25185
|
"react-dom": "^19.2.7",
|
|
25152
25186
|
recharts: "^3.9.0",
|
|
25153
|
-
"release-it": "^20.2.
|
|
25187
|
+
"release-it": "^20.2.1",
|
|
25154
25188
|
"tailwind-merge": "^3.6.0",
|
|
25155
25189
|
tailwindcss: "^4.3.1",
|
|
25156
25190
|
vite: "npm:@voidzero-dev/vite-plus-core@latest",
|
|
@@ -25227,6 +25261,7 @@ to use the default. Use list_mikrotik_devices to see them. For cross-device work
|
|
|
25227
25261
|
(e.g. a tunnel between two routers) configure each side by passing the matching
|
|
25228
25262
|
"device", then verify reachability with ping/traceroute from each end.`;
|
|
25229
25263
|
function createServer(opts = {}) {
|
|
25264
|
+
process.title = `Mikrotik MCP Server v${VERSION}`;
|
|
25230
25265
|
const { names, default: defaultDevice } = listDevices();
|
|
25231
25266
|
const readOnly = getConfig().readOnly;
|
|
25232
25267
|
const instructions = names.length > 1 ? INSTRUCTIONS + MULTI_DEVICE_INSTRUCTIONS.replace("{{names}}", names.join(", ")).replace("{{default}}", defaultDevice) : INSTRUCTIONS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usex/mikrotik-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.31.1",
|
|
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",
|
|
@@ -93,11 +93,11 @@
|
|
|
93
93
|
"class-variance-authority": "^0.7.1",
|
|
94
94
|
"clsx": "^2.1.1",
|
|
95
95
|
"gsap": "^3.15.0",
|
|
96
|
-
"prettier": "^3.
|
|
96
|
+
"prettier": "^3.9.1",
|
|
97
97
|
"react": "^19.2.7",
|
|
98
98
|
"react-dom": "^19.2.7",
|
|
99
99
|
"recharts": "^3.9.0",
|
|
100
|
-
"release-it": "^20.2.
|
|
100
|
+
"release-it": "^20.2.1",
|
|
101
101
|
"tailwind-merge": "^3.6.0",
|
|
102
102
|
"tailwindcss": "^4.3.1",
|
|
103
103
|
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
|