@usex/mikrotik-mcp 3.29.0 → 3.31.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 +59 -16
- package/dist/index.d.ts +8 -0
- package/dist/index.js +59 -16
- package/package.json +3 -3
- package/schemas/config.schema.json +8 -2
- package/schemas/tool-catalog.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -23,7 +23,8 @@ var McpServerSettingsSchema = z.object({
|
|
|
23
23
|
allowedHosts: z.string().default(""),
|
|
24
24
|
allowedOrigins: z.string().default(""),
|
|
25
25
|
corsOrigins: z.string().default(""),
|
|
26
|
-
toolPageSize: z.coerce.number().int().min(0).default(0)
|
|
26
|
+
toolPageSize: z.coerce.number().int().min(0).default(0),
|
|
27
|
+
appViews: z.boolean().default(true)
|
|
27
28
|
});
|
|
28
29
|
var DeviceConfigSchema = z.object({
|
|
29
30
|
host: z.string().default("127.0.0.1"),
|
|
@@ -121,7 +122,8 @@ function parseDevicesSource(raw, fromFile) {
|
|
|
121
122
|
const s3 = structured && obj.s3 && typeof obj.s3 === "object" ? obj.s3 : undefined;
|
|
122
123
|
const dashboard = structured && obj.dashboard && typeof obj.dashboard === "object" ? obj.dashboard : undefined;
|
|
123
124
|
const tools = structured && obj.tools && typeof obj.tools === "object" ? obj.tools : undefined;
|
|
124
|
-
|
|
125
|
+
const mcp = structured && obj.mcp && typeof obj.mcp === "object" ? obj.mcp : undefined;
|
|
126
|
+
return { devices, defaultDevice, s3, dashboard, tools, mcp };
|
|
125
127
|
}
|
|
126
128
|
var configSource = { path: DEFAULT_CONFIG_FILE, fromFile: false };
|
|
127
129
|
function getConfigSource() {
|
|
@@ -157,6 +159,7 @@ function loadConfig(argv = process.argv.slice(2)) {
|
|
|
157
159
|
let fileS3 = {};
|
|
158
160
|
let fileDashboard = {};
|
|
159
161
|
let fileTools;
|
|
162
|
+
let fileMcp;
|
|
160
163
|
if (configFile || devicesInline) {
|
|
161
164
|
const src = configFile ? parseDevicesSource(configFile, true) : parseDevicesSource(devicesInline, false);
|
|
162
165
|
for (const [name, dc] of Object.entries(src.devices))
|
|
@@ -168,6 +171,7 @@ function loadConfig(argv = process.argv.slice(2)) {
|
|
|
168
171
|
fileS3 = src.s3;
|
|
169
172
|
fileDashboard = src.dashboard;
|
|
170
173
|
fileTools = src.tools;
|
|
174
|
+
fileMcp = src.mcp;
|
|
171
175
|
}
|
|
172
176
|
const s3 = {
|
|
173
177
|
accessKeyId: pick("s3-access-key-id", "S3_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID"),
|
|
@@ -180,6 +184,8 @@ function loadConfig(argv = process.argv.slice(2)) {
|
|
|
180
184
|
presignExpiresIn: pick("s3-presign-expires-in", "MIKROTIK_S3_PRESIGN_EXPIRES_IN"),
|
|
181
185
|
...fileS3
|
|
182
186
|
};
|
|
187
|
+
const appViewsRaw = pick("app-views", "MIKROTIK_MCP__APP_VIEWS");
|
|
188
|
+
const appViewsEnv = appViewsRaw === undefined ? undefined : !/^(0|false|no|off)$/i.test(appViewsRaw);
|
|
183
189
|
const mcp = {
|
|
184
190
|
transport: pick("transport", "MIKROTIK_MCP__TRANSPORT", "MCP_TRANSPORT"),
|
|
185
191
|
host: pick("mcp-host", "MIKROTIK_MCP__HOST"),
|
|
@@ -187,7 +193,9 @@ function loadConfig(argv = process.argv.slice(2)) {
|
|
|
187
193
|
allowedHosts: pick("mcp-allowed-hosts", "MIKROTIK_MCP__ALLOWED_HOSTS"),
|
|
188
194
|
allowedOrigins: pick("mcp-allowed-origins", "MIKROTIK_MCP__ALLOWED_ORIGINS"),
|
|
189
195
|
corsOrigins: pick("mcp-cors-origins", "MIKROTIK_MCP__CORS_ORIGINS"),
|
|
190
|
-
toolPageSize: pick("tool-page-size", "MIKROTIK_MCP__TOOL_PAGE_SIZE")
|
|
196
|
+
toolPageSize: pick("tool-page-size", "MIKROTIK_MCP__TOOL_PAGE_SIZE"),
|
|
197
|
+
appViews: appViewsEnv,
|
|
198
|
+
...fileMcp
|
|
191
199
|
};
|
|
192
200
|
const isTruthy = (v) => /^(1|true|yes|on)$/i.test(v ?? "");
|
|
193
201
|
const readOnly = isTruthy(pick("read-only", "MIKROTIK_READ_ONLY"));
|
|
@@ -618,10 +626,10 @@ class MikroTikMacTelnetClient {
|
|
|
618
626
|
const explicitSourceMac = this.opts.sourceMac ? parseMac(this.opts.sourceMac) : undefined;
|
|
619
627
|
const host = this.opts.host ?? DEFAULT_MAC_TELNET_BROADCAST;
|
|
620
628
|
const askedDiscovery = host === DEFAULT_MAC_TELNET_BROADCAST && !explicitSourceMac;
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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}`);
|
|
625
633
|
logger.info(`[mac-telnet] resolving route to ${this.opts.mac}\u2026`);
|
|
626
634
|
const route = await resolveMacTelnetRoute({
|
|
627
635
|
destinationMac,
|
|
@@ -631,7 +639,7 @@ class MikroTikMacTelnetClient {
|
|
|
631
639
|
explicitSourceMac
|
|
632
640
|
});
|
|
633
641
|
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
|
|
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;
|
|
635
643
|
logger.info(`[mac-telnet] route: source ${formatMac(route.sourceMac)} \u2192 ${route.host} ` + `(${explicitSourceMac ? "explicit" : discoveryFailed ? "DISCOVERY FAILED \u2014 limited broadcast" : "discovered"})`);
|
|
636
644
|
const transport = createUdpMacTelnetTransport({
|
|
637
645
|
host: route.host,
|
|
@@ -684,6 +692,7 @@ class MikroTikMacTelnetClient {
|
|
|
684
692
|
// src/ssh/client.ts
|
|
685
693
|
import { readFileSync as readFileSync2 } from "fs";
|
|
686
694
|
import { Client } from "ssh2";
|
|
695
|
+
var RUN_IDLE_TIMEOUT_MS = 60000;
|
|
687
696
|
function decodeOutput(data) {
|
|
688
697
|
if (!data || data.length === 0)
|
|
689
698
|
return "";
|
|
@@ -754,16 +763,43 @@ class MikroTikSSHClient {
|
|
|
754
763
|
const stderrBuf = [];
|
|
755
764
|
let settled = false;
|
|
756
765
|
let timer;
|
|
766
|
+
let idleTimer;
|
|
767
|
+
const clearTimers = () => {
|
|
768
|
+
if (timer)
|
|
769
|
+
clearTimeout(timer);
|
|
770
|
+
if (idleTimer)
|
|
771
|
+
clearTimeout(idleTimer);
|
|
772
|
+
};
|
|
757
773
|
const finish = () => {
|
|
758
774
|
if (settled)
|
|
759
775
|
return;
|
|
760
776
|
settled = true;
|
|
761
|
-
|
|
762
|
-
clearTimeout(timer);
|
|
777
|
+
clearTimers();
|
|
763
778
|
const out = decodeOutput(Buffer.concat(stdout));
|
|
764
779
|
const error = decodeOutput(Buffer.concat(stderrBuf));
|
|
765
780
|
resolve2(error && !out ? error : out);
|
|
766
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();
|
|
767
803
|
if (opts.maxMs && opts.maxMs > 0) {
|
|
768
804
|
timer = setTimeout(() => {
|
|
769
805
|
try {
|
|
@@ -775,7 +811,13 @@ class MikroTikSSHClient {
|
|
|
775
811
|
finish();
|
|
776
812
|
}, opts.maxMs);
|
|
777
813
|
}
|
|
778
|
-
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
|
+
});
|
|
779
821
|
});
|
|
780
822
|
});
|
|
781
823
|
}
|
|
@@ -1794,9 +1836,9 @@ function defineTool(def) {
|
|
|
1794
1836
|
inputSchema: def.inputSchema,
|
|
1795
1837
|
ui: def.ui,
|
|
1796
1838
|
register(server, opts = {}) {
|
|
1797
|
-
const { sendLog, deviceNames, deviceAliases, deviceDirectory: deviceDirectory2 } = opts;
|
|
1839
|
+
const { sendLog, deviceNames, deviceAliases, deviceDirectory: deviceDirectory2, appViews } = opts;
|
|
1798
1840
|
const multiDevice = !!deviceNames && deviceNames.length > 1;
|
|
1799
|
-
const { ui, auto } = effectiveUi(def);
|
|
1841
|
+
const { ui, auto } = appViews === false ? { ui: undefined, auto: false } : effectiveUi(def);
|
|
1800
1842
|
const selectorNames = multiDevice && deviceNames ? [...new Set([...deviceNames, ...deviceAliases ?? []])] : [];
|
|
1801
1843
|
const inputSchema = multiDevice ? {
|
|
1802
1844
|
...def.inputSchema,
|
|
@@ -27067,7 +27109,7 @@ function registerPrompts(server) {
|
|
|
27067
27109
|
// package.json
|
|
27068
27110
|
var package_default = {
|
|
27069
27111
|
name: "@usex/mikrotik-mcp",
|
|
27070
|
-
version: "3.
|
|
27112
|
+
version: "3.31.0",
|
|
27071
27113
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
27072
27114
|
keywords: [
|
|
27073
27115
|
"ai",
|
|
@@ -27160,11 +27202,11 @@ var package_default = {
|
|
|
27160
27202
|
"class-variance-authority": "^0.7.1",
|
|
27161
27203
|
clsx: "^2.1.1",
|
|
27162
27204
|
gsap: "^3.15.0",
|
|
27163
|
-
prettier: "^3.
|
|
27205
|
+
prettier: "^3.9.1",
|
|
27164
27206
|
react: "^19.2.7",
|
|
27165
27207
|
"react-dom": "^19.2.7",
|
|
27166
27208
|
recharts: "^3.9.0",
|
|
27167
|
-
"release-it": "^20.2.
|
|
27209
|
+
"release-it": "^20.2.1",
|
|
27168
27210
|
"tailwind-merge": "^3.6.0",
|
|
27169
27211
|
tailwindcss: "^4.3.1",
|
|
27170
27212
|
vite: "npm:@voidzero-dev/vite-plus-core@latest",
|
|
@@ -27273,6 +27315,7 @@ function createServer(opts = {}) {
|
|
|
27273
27315
|
deviceNames: names,
|
|
27274
27316
|
deviceAliases: deviceLabels(),
|
|
27275
27317
|
deviceDirectory: deviceDirectory(),
|
|
27318
|
+
appViews: getConfig().mcp.appViews,
|
|
27276
27319
|
readOnly
|
|
27277
27320
|
});
|
|
27278
27321
|
const promptCount = registerPrompts(server);
|
package/dist/index.d.ts
CHANGED
|
@@ -94,6 +94,14 @@ interface RegisterOptions {
|
|
|
94
94
|
*/
|
|
95
95
|
deviceDirectory?: DeviceDirectoryEntry[];
|
|
96
96
|
/**
|
|
97
|
+
* Emit MCP App view metadata (`_meta.ui`) on tools. Default true. Set false for
|
|
98
|
+
* hosts whose tool discovery hides/deprioritises tools that carry App/
|
|
99
|
+
* `openai/outputTemplate` metadata — without it, every `list_*`/`get_*` read
|
|
100
|
+
* tool gains the auto-records view and some clients then surface only the
|
|
101
|
+
* (metadata-free) write tools. Disabling makes reads plain, surfacing tools.
|
|
102
|
+
*/
|
|
103
|
+
appViews?: boolean;
|
|
104
|
+
/**
|
|
97
105
|
* Read-only mode: register only tools annotated `readOnlyHint`. Used to
|
|
98
106
|
* withhold every write/destructive tool from a publicly-exposed surface (e.g.
|
|
99
107
|
* a ChatGPT Apps connector) until authentication is in place.
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,8 @@ var McpServerSettingsSchema = z.object({
|
|
|
19
19
|
allowedHosts: z.string().default(""),
|
|
20
20
|
allowedOrigins: z.string().default(""),
|
|
21
21
|
corsOrigins: z.string().default(""),
|
|
22
|
-
toolPageSize: z.coerce.number().int().min(0).default(0)
|
|
22
|
+
toolPageSize: z.coerce.number().int().min(0).default(0),
|
|
23
|
+
appViews: z.boolean().default(true)
|
|
23
24
|
});
|
|
24
25
|
var DeviceConfigSchema = z.object({
|
|
25
26
|
host: z.string().default("127.0.0.1"),
|
|
@@ -117,7 +118,8 @@ function parseDevicesSource(raw, fromFile) {
|
|
|
117
118
|
const s3 = structured && obj.s3 && typeof obj.s3 === "object" ? obj.s3 : undefined;
|
|
118
119
|
const dashboard = structured && obj.dashboard && typeof obj.dashboard === "object" ? obj.dashboard : undefined;
|
|
119
120
|
const tools = structured && obj.tools && typeof obj.tools === "object" ? obj.tools : undefined;
|
|
120
|
-
|
|
121
|
+
const mcp = structured && obj.mcp && typeof obj.mcp === "object" ? obj.mcp : undefined;
|
|
122
|
+
return { devices, defaultDevice, s3, dashboard, tools, mcp };
|
|
121
123
|
}
|
|
122
124
|
var configSource = { path: DEFAULT_CONFIG_FILE, fromFile: false };
|
|
123
125
|
function loadConfig(argv = process.argv.slice(2)) {
|
|
@@ -150,6 +152,7 @@ function loadConfig(argv = process.argv.slice(2)) {
|
|
|
150
152
|
let fileS3 = {};
|
|
151
153
|
let fileDashboard = {};
|
|
152
154
|
let fileTools;
|
|
155
|
+
let fileMcp;
|
|
153
156
|
if (configFile || devicesInline) {
|
|
154
157
|
const src = configFile ? parseDevicesSource(configFile, true) : parseDevicesSource(devicesInline, false);
|
|
155
158
|
for (const [name, dc] of Object.entries(src.devices))
|
|
@@ -161,6 +164,7 @@ function loadConfig(argv = process.argv.slice(2)) {
|
|
|
161
164
|
fileS3 = src.s3;
|
|
162
165
|
fileDashboard = src.dashboard;
|
|
163
166
|
fileTools = src.tools;
|
|
167
|
+
fileMcp = src.mcp;
|
|
164
168
|
}
|
|
165
169
|
const s3 = {
|
|
166
170
|
accessKeyId: pick("s3-access-key-id", "S3_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID"),
|
|
@@ -173,6 +177,8 @@ function loadConfig(argv = process.argv.slice(2)) {
|
|
|
173
177
|
presignExpiresIn: pick("s3-presign-expires-in", "MIKROTIK_S3_PRESIGN_EXPIRES_IN"),
|
|
174
178
|
...fileS3
|
|
175
179
|
};
|
|
180
|
+
const appViewsRaw = pick("app-views", "MIKROTIK_MCP__APP_VIEWS");
|
|
181
|
+
const appViewsEnv = appViewsRaw === undefined ? undefined : !/^(0|false|no|off)$/i.test(appViewsRaw);
|
|
176
182
|
const mcp = {
|
|
177
183
|
transport: pick("transport", "MIKROTIK_MCP__TRANSPORT", "MCP_TRANSPORT"),
|
|
178
184
|
host: pick("mcp-host", "MIKROTIK_MCP__HOST"),
|
|
@@ -180,7 +186,9 @@ function loadConfig(argv = process.argv.slice(2)) {
|
|
|
180
186
|
allowedHosts: pick("mcp-allowed-hosts", "MIKROTIK_MCP__ALLOWED_HOSTS"),
|
|
181
187
|
allowedOrigins: pick("mcp-allowed-origins", "MIKROTIK_MCP__ALLOWED_ORIGINS"),
|
|
182
188
|
corsOrigins: pick("mcp-cors-origins", "MIKROTIK_MCP__CORS_ORIGINS"),
|
|
183
|
-
toolPageSize: pick("tool-page-size", "MIKROTIK_MCP__TOOL_PAGE_SIZE")
|
|
189
|
+
toolPageSize: pick("tool-page-size", "MIKROTIK_MCP__TOOL_PAGE_SIZE"),
|
|
190
|
+
appViews: appViewsEnv,
|
|
191
|
+
...fileMcp
|
|
184
192
|
};
|
|
185
193
|
const isTruthy = (v) => /^(1|true|yes|on)$/i.test(v ?? "");
|
|
186
194
|
const readOnly = isTruthy(pick("read-only", "MIKROTIK_READ_ONLY"));
|
|
@@ -610,10 +618,10 @@ class MikroTikMacTelnetClient {
|
|
|
610
618
|
const explicitSourceMac = this.opts.sourceMac ? parseMac(this.opts.sourceMac) : undefined;
|
|
611
619
|
const host = this.opts.host ?? DEFAULT_MAC_TELNET_BROADCAST;
|
|
612
620
|
const askedDiscovery = host === DEFAULT_MAC_TELNET_BROADCAST && !explicitSourceMac;
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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}`);
|
|
617
625
|
logger.info(`[mac-telnet] resolving route to ${this.opts.mac}\u2026`);
|
|
618
626
|
const route = await resolveMacTelnetRoute({
|
|
619
627
|
destinationMac,
|
|
@@ -623,7 +631,7 @@ class MikroTikMacTelnetClient {
|
|
|
623
631
|
explicitSourceMac
|
|
624
632
|
});
|
|
625
633
|
const discoveryFailed = askedDiscovery && route.host === DEFAULT_MAC_TELNET_BROADCAST;
|
|
626
|
-
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;
|
|
627
635
|
logger.info(`[mac-telnet] route: source ${formatMac(route.sourceMac)} \u2192 ${route.host} ` + `(${explicitSourceMac ? "explicit" : discoveryFailed ? "DISCOVERY FAILED \u2014 limited broadcast" : "discovered"})`);
|
|
628
636
|
const transport = createUdpMacTelnetTransport({
|
|
629
637
|
host: route.host,
|
|
@@ -676,6 +684,7 @@ class MikroTikMacTelnetClient {
|
|
|
676
684
|
// src/ssh/client.ts
|
|
677
685
|
import { readFileSync as readFileSync2 } from "fs";
|
|
678
686
|
import { Client } from "ssh2";
|
|
687
|
+
var RUN_IDLE_TIMEOUT_MS = 60000;
|
|
679
688
|
function decodeOutput(data) {
|
|
680
689
|
if (!data || data.length === 0)
|
|
681
690
|
return "";
|
|
@@ -746,16 +755,43 @@ class MikroTikSSHClient {
|
|
|
746
755
|
const stderrBuf = [];
|
|
747
756
|
let settled = false;
|
|
748
757
|
let timer;
|
|
758
|
+
let idleTimer;
|
|
759
|
+
const clearTimers = () => {
|
|
760
|
+
if (timer)
|
|
761
|
+
clearTimeout(timer);
|
|
762
|
+
if (idleTimer)
|
|
763
|
+
clearTimeout(idleTimer);
|
|
764
|
+
};
|
|
749
765
|
const finish = () => {
|
|
750
766
|
if (settled)
|
|
751
767
|
return;
|
|
752
768
|
settled = true;
|
|
753
|
-
|
|
754
|
-
clearTimeout(timer);
|
|
769
|
+
clearTimers();
|
|
755
770
|
const out = decodeOutput(Buffer.concat(stdout));
|
|
756
771
|
const error = decodeOutput(Buffer.concat(stderrBuf));
|
|
757
772
|
resolve2(error && !out ? error : out);
|
|
758
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();
|
|
759
795
|
if (opts.maxMs && opts.maxMs > 0) {
|
|
760
796
|
timer = setTimeout(() => {
|
|
761
797
|
try {
|
|
@@ -767,7 +803,13 @@ class MikroTikSSHClient {
|
|
|
767
803
|
finish();
|
|
768
804
|
}, opts.maxMs);
|
|
769
805
|
}
|
|
770
|
-
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
|
+
});
|
|
771
813
|
});
|
|
772
814
|
});
|
|
773
815
|
}
|
|
@@ -1700,9 +1742,9 @@ function defineTool(def) {
|
|
|
1700
1742
|
inputSchema: def.inputSchema,
|
|
1701
1743
|
ui: def.ui,
|
|
1702
1744
|
register(server, opts = {}) {
|
|
1703
|
-
const { sendLog, deviceNames, deviceAliases, deviceDirectory: deviceDirectory2 } = opts;
|
|
1745
|
+
const { sendLog, deviceNames, deviceAliases, deviceDirectory: deviceDirectory2, appViews } = opts;
|
|
1704
1746
|
const multiDevice = !!deviceNames && deviceNames.length > 1;
|
|
1705
|
-
const { ui, auto } = effectiveUi(def);
|
|
1747
|
+
const { ui, auto } = appViews === false ? { ui: undefined, auto: false } : effectiveUi(def);
|
|
1706
1748
|
const selectorNames = multiDevice && deviceNames ? [...new Set([...deviceNames, ...deviceAliases ?? []])] : [];
|
|
1707
1749
|
const inputSchema = multiDevice ? {
|
|
1708
1750
|
...def.inputSchema,
|
|
@@ -25045,7 +25087,7 @@ function selectToolModules(filter = {}, catalog = moduleCatalog) {
|
|
|
25045
25087
|
// package.json
|
|
25046
25088
|
var package_default = {
|
|
25047
25089
|
name: "@usex/mikrotik-mcp",
|
|
25048
|
-
version: "3.
|
|
25090
|
+
version: "3.31.0",
|
|
25049
25091
|
description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
25050
25092
|
keywords: [
|
|
25051
25093
|
"ai",
|
|
@@ -25138,11 +25180,11 @@ var package_default = {
|
|
|
25138
25180
|
"class-variance-authority": "^0.7.1",
|
|
25139
25181
|
clsx: "^2.1.1",
|
|
25140
25182
|
gsap: "^3.15.0",
|
|
25141
|
-
prettier: "^3.
|
|
25183
|
+
prettier: "^3.9.1",
|
|
25142
25184
|
react: "^19.2.7",
|
|
25143
25185
|
"react-dom": "^19.2.7",
|
|
25144
25186
|
recharts: "^3.9.0",
|
|
25145
|
-
"release-it": "^20.2.
|
|
25187
|
+
"release-it": "^20.2.1",
|
|
25146
25188
|
"tailwind-merge": "^3.6.0",
|
|
25147
25189
|
tailwindcss: "^4.3.1",
|
|
25148
25190
|
vite: "npm:@voidzero-dev/vite-plus-core@latest",
|
|
@@ -25251,6 +25293,7 @@ function createServer(opts = {}) {
|
|
|
25251
25293
|
deviceNames: names,
|
|
25252
25294
|
deviceAliases: deviceLabels(),
|
|
25253
25295
|
deviceDirectory: deviceDirectory(),
|
|
25296
|
+
appViews: getConfig().mcp.appViews,
|
|
25254
25297
|
readOnly
|
|
25255
25298
|
});
|
|
25256
25299
|
const promptCount = registerPrompts(server);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usex/mikrotik-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.31.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",
|
|
@@ -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",
|
|
@@ -87,7 +87,8 @@
|
|
|
87
87
|
"allowedHosts": "",
|
|
88
88
|
"allowedOrigins": "",
|
|
89
89
|
"corsOrigins": "",
|
|
90
|
-
"toolPageSize": 0
|
|
90
|
+
"toolPageSize": 0,
|
|
91
|
+
"appViews": true
|
|
91
92
|
},
|
|
92
93
|
"type": "object",
|
|
93
94
|
"properties": {
|
|
@@ -123,6 +124,10 @@
|
|
|
123
124
|
"type": "integer",
|
|
124
125
|
"minimum": 0,
|
|
125
126
|
"maximum": 9007199254740991
|
|
127
|
+
},
|
|
128
|
+
"appViews": {
|
|
129
|
+
"default": true,
|
|
130
|
+
"type": "boolean"
|
|
126
131
|
}
|
|
127
132
|
},
|
|
128
133
|
"required": [
|
|
@@ -132,7 +137,8 @@
|
|
|
132
137
|
"allowedHosts",
|
|
133
138
|
"allowedOrigins",
|
|
134
139
|
"corsOrigins",
|
|
135
|
-
"toolPageSize"
|
|
140
|
+
"toolPageSize",
|
|
141
|
+
"appViews"
|
|
136
142
|
],
|
|
137
143
|
"additionalProperties": false
|
|
138
144
|
},
|