@usex/mikrotik-mcp 3.20.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 +43 -14
- package/dist/index.d.ts +8 -2
- package/dist/index.js +43 -14
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -617,7 +617,7 @@ class MikroTikMacTelnetClient {
|
|
|
617
617
|
return false;
|
|
618
618
|
}
|
|
619
619
|
}
|
|
620
|
-
async run(command) {
|
|
620
|
+
async run(command, _opts = {}) {
|
|
621
621
|
if (!this.console || !this.console.isReady) {
|
|
622
622
|
throw new Error("Not connected to MikroTik device (MAC-Telnet)");
|
|
623
623
|
}
|
|
@@ -694,7 +694,7 @@ class MikroTikSSHClient {
|
|
|
694
694
|
}).connect(cfg);
|
|
695
695
|
});
|
|
696
696
|
}
|
|
697
|
-
run(command) {
|
|
697
|
+
run(command, opts = {}) {
|
|
698
698
|
if (!this.client) {
|
|
699
699
|
return Promise.reject(new Error("Not connected to MikroTik device"));
|
|
700
700
|
}
|
|
@@ -707,11 +707,30 @@ class MikroTikSSHClient {
|
|
|
707
707
|
}
|
|
708
708
|
const stdout = [];
|
|
709
709
|
const stderrBuf = [];
|
|
710
|
-
|
|
710
|
+
let settled = false;
|
|
711
|
+
let timer;
|
|
712
|
+
const finish = () => {
|
|
713
|
+
if (settled)
|
|
714
|
+
return;
|
|
715
|
+
settled = true;
|
|
716
|
+
if (timer)
|
|
717
|
+
clearTimeout(timer);
|
|
711
718
|
const out = decodeOutput(Buffer.concat(stdout));
|
|
712
719
|
const error = decodeOutput(Buffer.concat(stderrBuf));
|
|
713
720
|
resolve2(error && !out ? error : out);
|
|
714
|
-
}
|
|
721
|
+
};
|
|
722
|
+
if (opts.maxMs && opts.maxMs > 0) {
|
|
723
|
+
timer = setTimeout(() => {
|
|
724
|
+
try {
|
|
725
|
+
stream.signal("INT");
|
|
726
|
+
} catch {}
|
|
727
|
+
try {
|
|
728
|
+
stream.close();
|
|
729
|
+
} catch {}
|
|
730
|
+
finish();
|
|
731
|
+
}, opts.maxMs);
|
|
732
|
+
}
|
|
733
|
+
stream.on("close", finish).on("data", (d) => stdout.push(d)).stderr.on("data", (d) => stderrBuf.push(d));
|
|
715
734
|
});
|
|
716
735
|
});
|
|
717
736
|
}
|
|
@@ -1010,7 +1029,7 @@ function getSafeModeManager(deviceName) {
|
|
|
1010
1029
|
}
|
|
1011
1030
|
|
|
1012
1031
|
// src/core/connector.ts
|
|
1013
|
-
async function runOnce(command, deviceName) {
|
|
1032
|
+
async function runOnce(command, deviceName, opts) {
|
|
1014
1033
|
const name = resolveDeviceName(deviceName);
|
|
1015
1034
|
const dc = getDevice(deviceName);
|
|
1016
1035
|
const client = createDeviceClient(dc);
|
|
@@ -1018,12 +1037,12 @@ async function runOnce(command, deviceName) {
|
|
|
1018
1037
|
if (!await client.connect()) {
|
|
1019
1038
|
throw new Error(connectErrorMessage(name, dc, client.lastError));
|
|
1020
1039
|
}
|
|
1021
|
-
return await client.run(command);
|
|
1040
|
+
return await client.run(command, opts);
|
|
1022
1041
|
} finally {
|
|
1023
1042
|
client.disconnect();
|
|
1024
1043
|
}
|
|
1025
1044
|
}
|
|
1026
|
-
async function executeMikrotikCommand(command, ctx) {
|
|
1045
|
+
async function executeMikrotikCommand(command, ctx, opts) {
|
|
1027
1046
|
const deviceName = resolveDeviceName(ctx.device);
|
|
1028
1047
|
const safe = getSafeModeManager(deviceName);
|
|
1029
1048
|
if (safe.isActive) {
|
|
@@ -1031,7 +1050,7 @@ async function executeMikrotikCommand(command, ctx) {
|
|
|
1031
1050
|
return safe.execute(command);
|
|
1032
1051
|
}
|
|
1033
1052
|
ctx.info(`[${deviceName}] Executing MikroTik command: ${command}`);
|
|
1034
|
-
return runOnce(command, ctx.device);
|
|
1053
|
+
return runOnce(command, ctx.device, opts);
|
|
1035
1054
|
}
|
|
1036
1055
|
|
|
1037
1056
|
// src/core/registry.ts
|
|
@@ -1124,6 +1143,16 @@ function isEmpty(result) {
|
|
|
1124
1143
|
const t = result.trim();
|
|
1125
1144
|
return t === "" || t === "no such item" || t === "no such item (4)";
|
|
1126
1145
|
}
|
|
1146
|
+
function flattenLiveOutput(text) {
|
|
1147
|
+
return text.split(`
|
|
1148
|
+
`).map((line) => {
|
|
1149
|
+
const segs = line.split("\r").filter((s) => s.trim() !== "");
|
|
1150
|
+
return segs.length ? segs[segs.length - 1] : "";
|
|
1151
|
+
}).join(`
|
|
1152
|
+
`).replace(/\n{3,}/g, `
|
|
1153
|
+
|
|
1154
|
+
`).trim();
|
|
1155
|
+
}
|
|
1127
1156
|
function extractCreatedId(output) {
|
|
1128
1157
|
const star = output.match(/\*[0-9A-Fa-f]+/);
|
|
1129
1158
|
if (star)
|
|
@@ -11406,7 +11435,7 @@ var networkToolTools = [
|
|
|
11406
11435
|
async handler(a, ctx) {
|
|
11407
11436
|
ctx.info(`Pinging ${a.address} (count=${a.count})`);
|
|
11408
11437
|
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();
|
|
11409
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
11438
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: a.count * 1500 + 6000 }));
|
|
11410
11439
|
if (looksLikeError(result))
|
|
11411
11440
|
return `Failed to ping ${a.address}: ${result}`;
|
|
11412
11441
|
return isEmpty(result) ? `No response from ${a.address}.` : `PING ${a.address}:
|
|
@@ -11427,7 +11456,7 @@ ${result}`;
|
|
|
11427
11456
|
async handler(a, ctx) {
|
|
11428
11457
|
ctx.info(`Tracerouting ${a.address} (count=${a.count})`);
|
|
11429
11458
|
const cmd = new Cmd(`/tool traceroute ${a.address}`).set("count", a.count).flag("use-dns", a.use_dns).build();
|
|
11430
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
11459
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: 30000 + a.count * 3000 }));
|
|
11431
11460
|
if (looksLikeError(result))
|
|
11432
11461
|
return `Failed to traceroute ${a.address}: ${result}`;
|
|
11433
11462
|
return isEmpty(result) ? `No route information for ${a.address}.` : `TRACEROUTE ${a.address}:
|
|
@@ -11451,7 +11480,7 @@ ${result}`;
|
|
|
11451
11480
|
async handler(a, ctx) {
|
|
11452
11481
|
ctx.info(`Bandwidth test to ${a.address} (duration=${a.duration}s, direction=${a.direction})`);
|
|
11453
11482
|
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();
|
|
11454
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
11483
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: a.duration * 1000 + 12000 }));
|
|
11455
11484
|
if (looksLikeError(result))
|
|
11456
11485
|
return `Failed to run bandwidth test to ${a.address}: ${result}`;
|
|
11457
11486
|
return isEmpty(result) ? `No bandwidth test results for ${a.address}.` : `BANDWIDTH TEST:
|
|
@@ -12103,7 +12132,7 @@ var floodPingTools = [
|
|
|
12103
12132
|
async handler(a, ctx) {
|
|
12104
12133
|
ctx.info(`Flood-pinging ${a.address} (count=${a.count})`);
|
|
12105
12134
|
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();
|
|
12106
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
12135
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: 30000 }));
|
|
12107
12136
|
if (looksLikeError(result))
|
|
12108
12137
|
return `Failed to flood-ping ${a.address}: ${result}`;
|
|
12109
12138
|
return isEmpty(result) ? `No response from ${a.address}.` : `FLOOD PING ${a.address}:
|
|
@@ -12767,7 +12796,7 @@ var speedTestTools = [
|
|
|
12767
12796
|
async handler(a, ctx) {
|
|
12768
12797
|
ctx.info(`Speed test to ${a.address} (duration=${a.duration}s, direction=${a.direction})`);
|
|
12769
12798
|
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();
|
|
12770
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
12799
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: a.duration * 1000 + 15000 }));
|
|
12771
12800
|
if (looksLikeError(result))
|
|
12772
12801
|
return `Failed to run speed test to ${a.address}: ${result}`;
|
|
12773
12802
|
return isEmpty(result) ? `No speed-test results for ${a.address}.` : `SPEED TEST:
|
|
@@ -23922,7 +23951,7 @@ function registerPrompts(server) {
|
|
|
23922
23951
|
// package.json
|
|
23923
23952
|
var package_default = {
|
|
23924
23953
|
name: "@usex/mikrotik-mcp",
|
|
23925
|
-
version: "3.
|
|
23954
|
+
version: "3.21.0",
|
|
23926
23955
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
23927
23956
|
keywords: [
|
|
23928
23957
|
"ai",
|
package/dist/index.d.ts
CHANGED
|
@@ -26,8 +26,12 @@ type SendLog = (level: "info" | "error", message: string) => void;
|
|
|
26
26
|
*
|
|
27
27
|
* @param command Fully-formed RouterOS CLI command (e.g. `/ip address print`).
|
|
28
28
|
* @param ctx Per-call context carrying the target device.
|
|
29
|
+
* @param opts `maxMs` caps the one-shot read for interactive/streaming
|
|
30
|
+
* commands (ping, bandwidth-test) so they can't hang the tool.
|
|
29
31
|
*/
|
|
30
|
-
declare function executeMikrotikCommand(command: string, ctx: ToolContext
|
|
32
|
+
declare function executeMikrotikCommand(command: string, ctx: ToolContext, opts?: {
|
|
33
|
+
maxMs?: number;
|
|
34
|
+
}): Promise<string>;
|
|
31
35
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
32
36
|
import { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
33
37
|
import { ZodRawShape } from "zod";
|
|
@@ -147,7 +151,9 @@ declare class MikroTikSSHClient {
|
|
|
147
151
|
/** Establish the SSH connection. Resolves `true` on success, `false` on failure. */
|
|
148
152
|
connect(): Promise<boolean>;
|
|
149
153
|
/** Run a single command on a fresh SSH channel and return its decoded output. */
|
|
150
|
-
run(command: string
|
|
154
|
+
run(command: string, opts?: {
|
|
155
|
+
maxMs?: number;
|
|
156
|
+
}): Promise<string>;
|
|
151
157
|
/** Open a persistent interactive shell channel (used by Safe Mode). */
|
|
152
158
|
shell(opts?: {
|
|
153
159
|
term?: string;
|
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
|
}
|
|
@@ -996,7 +1015,7 @@ function getSafeModeManager(deviceName) {
|
|
|
996
1015
|
}
|
|
997
1016
|
|
|
998
1017
|
// src/core/connector.ts
|
|
999
|
-
async function runOnce(command, deviceName) {
|
|
1018
|
+
async function runOnce(command, deviceName, opts) {
|
|
1000
1019
|
const name = resolveDeviceName(deviceName);
|
|
1001
1020
|
const dc = getDevice(deviceName);
|
|
1002
1021
|
const client = createDeviceClient(dc);
|
|
@@ -1004,12 +1023,12 @@ async function runOnce(command, deviceName) {
|
|
|
1004
1023
|
if (!await client.connect()) {
|
|
1005
1024
|
throw new Error(connectErrorMessage(name, dc, client.lastError));
|
|
1006
1025
|
}
|
|
1007
|
-
return await client.run(command);
|
|
1026
|
+
return await client.run(command, opts);
|
|
1008
1027
|
} finally {
|
|
1009
1028
|
client.disconnect();
|
|
1010
1029
|
}
|
|
1011
1030
|
}
|
|
1012
|
-
async function executeMikrotikCommand(command, ctx) {
|
|
1031
|
+
async function executeMikrotikCommand(command, ctx, opts) {
|
|
1013
1032
|
const deviceName = resolveDeviceName(ctx.device);
|
|
1014
1033
|
const safe = getSafeModeManager(deviceName);
|
|
1015
1034
|
if (safe.isActive) {
|
|
@@ -1017,7 +1036,7 @@ async function executeMikrotikCommand(command, ctx) {
|
|
|
1017
1036
|
return safe.execute(command);
|
|
1018
1037
|
}
|
|
1019
1038
|
ctx.info(`[${deviceName}] Executing MikroTik command: ${command}`);
|
|
1020
|
-
return runOnce(command, ctx.device);
|
|
1039
|
+
return runOnce(command, ctx.device, opts);
|
|
1021
1040
|
}
|
|
1022
1041
|
// src/core/registry.ts
|
|
1023
1042
|
import { z as z2 } from "zod";
|
|
@@ -1109,6 +1128,16 @@ function isEmpty(result) {
|
|
|
1109
1128
|
const t = result.trim();
|
|
1110
1129
|
return t === "" || t === "no such item" || t === "no such item (4)";
|
|
1111
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
|
+
}
|
|
1112
1141
|
function extractCreatedId(output) {
|
|
1113
1142
|
const star = output.match(/\*[0-9A-Fa-f]+/);
|
|
1114
1143
|
if (star)
|
|
@@ -11418,7 +11447,7 @@ var networkToolTools = [
|
|
|
11418
11447
|
async handler(a, ctx) {
|
|
11419
11448
|
ctx.info(`Pinging ${a.address} (count=${a.count})`);
|
|
11420
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();
|
|
11421
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
11450
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: a.count * 1500 + 6000 }));
|
|
11422
11451
|
if (looksLikeError(result))
|
|
11423
11452
|
return `Failed to ping ${a.address}: ${result}`;
|
|
11424
11453
|
return isEmpty(result) ? `No response from ${a.address}.` : `PING ${a.address}:
|
|
@@ -11439,7 +11468,7 @@ ${result}`;
|
|
|
11439
11468
|
async handler(a, ctx) {
|
|
11440
11469
|
ctx.info(`Tracerouting ${a.address} (count=${a.count})`);
|
|
11441
11470
|
const cmd = new Cmd(`/tool traceroute ${a.address}`).set("count", a.count).flag("use-dns", a.use_dns).build();
|
|
11442
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
11471
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: 30000 + a.count * 3000 }));
|
|
11443
11472
|
if (looksLikeError(result))
|
|
11444
11473
|
return `Failed to traceroute ${a.address}: ${result}`;
|
|
11445
11474
|
return isEmpty(result) ? `No route information for ${a.address}.` : `TRACEROUTE ${a.address}:
|
|
@@ -11463,7 +11492,7 @@ ${result}`;
|
|
|
11463
11492
|
async handler(a, ctx) {
|
|
11464
11493
|
ctx.info(`Bandwidth test to ${a.address} (duration=${a.duration}s, direction=${a.direction})`);
|
|
11465
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();
|
|
11466
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
11495
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: a.duration * 1000 + 12000 }));
|
|
11467
11496
|
if (looksLikeError(result))
|
|
11468
11497
|
return `Failed to run bandwidth test to ${a.address}: ${result}`;
|
|
11469
11498
|
return isEmpty(result) ? `No bandwidth test results for ${a.address}.` : `BANDWIDTH TEST:
|
|
@@ -12115,7 +12144,7 @@ var floodPingTools = [
|
|
|
12115
12144
|
async handler(a, ctx) {
|
|
12116
12145
|
ctx.info(`Flood-pinging ${a.address} (count=${a.count})`);
|
|
12117
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();
|
|
12118
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
12147
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: 30000 }));
|
|
12119
12148
|
if (looksLikeError(result))
|
|
12120
12149
|
return `Failed to flood-ping ${a.address}: ${result}`;
|
|
12121
12150
|
return isEmpty(result) ? `No response from ${a.address}.` : `FLOOD PING ${a.address}:
|
|
@@ -12779,7 +12808,7 @@ var speedTestTools = [
|
|
|
12779
12808
|
async handler(a, ctx) {
|
|
12780
12809
|
ctx.info(`Speed test to ${a.address} (duration=${a.duration}s, direction=${a.direction})`);
|
|
12781
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();
|
|
12782
|
-
const result = await executeMikrotikCommand(cmd, ctx);
|
|
12811
|
+
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: a.duration * 1000 + 15000 }));
|
|
12783
12812
|
if (looksLikeError(result))
|
|
12784
12813
|
return `Failed to run speed test to ${a.address}: ${result}`;
|
|
12785
12814
|
return isEmpty(result) ? `No speed-test results for ${a.address}.` : `SPEED TEST:
|
|
@@ -22322,7 +22351,7 @@ function selectToolModules(filter = {}, catalog = moduleCatalog) {
|
|
|
22322
22351
|
// package.json
|
|
22323
22352
|
var package_default = {
|
|
22324
22353
|
name: "@usex/mikrotik-mcp",
|
|
22325
|
-
version: "3.
|
|
22354
|
+
version: "3.21.0",
|
|
22326
22355
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
22327
22356
|
keywords: [
|
|
22328
22357
|
"ai",
|
package/package.json
CHANGED