@usex/mikrotik-mcp 3.55.0 → 3.57.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 CHANGED
@@ -96,7 +96,7 @@ import {
96
96
  toggleAaaEntity,
97
97
  updateAaaEntity,
98
98
  writeBackup
99
- } from "./shared/cli-44v6pb38.js";
99
+ } from "./shared/cli-thhk9f7n.js";
100
100
 
101
101
  // src/cli.ts
102
102
  import { existsSync as existsSync2 } from "fs";
@@ -1155,9 +1155,9 @@ var pkg = JSON.parse(readFileSync2(join2(PROJECT_ROOT, "package.json"), "utf-8")
1155
1155
  var VERSION = pkg.version ?? "0.0.0";
1156
1156
  var WEBSITE_URL = pkg.homepage ?? "";
1157
1157
  var LOGO_URL = pkg.logoIcon ?? "";
1158
- var SERVER_TITLE = pkg.name ?? "mcp-mikrotik";
1158
+ var SERVER_TITLE = "MikroTik MCP";
1159
1159
  var SERVER_DESCRIPTION = pkg.description ?? "";
1160
- var SERVER_NAME = "mcp-mikrotik";
1160
+ var SERVER_NAME = "mikrotik-mcp";
1161
1161
  var PKG_META = pkg;
1162
1162
 
1163
1163
  // src/observability/drift-routes.ts
@@ -1559,6 +1559,7 @@ function devicesPayload(store) {
1559
1559
  authMode: dc.mac ? "mac-telnet" : dc.keyFilename || dc.privateKey ? "key" : dc.password ? "password" : "none",
1560
1560
  isDefault: name === cfg.defaultDevice,
1561
1561
  description: dc.description,
1562
+ disabled: !!dc.disabled,
1562
1563
  jumpVia: dc.jumpVia,
1563
1564
  jumpHost: dc.jumpHost ? { host: dc.jumpHost.host, port: dc.jumpHost.port } : undefined,
1564
1565
  status: getDeviceStatus(name),
@@ -2373,6 +2374,42 @@ async function runDashboard(cfg, transportLabel) {
2373
2374
  if (url.pathname === "/api/devices") {
2374
2375
  return json3(devicesPayload(db));
2375
2376
  }
2377
+ if (url.pathname === "/api/devices/toggle" && req.method === "POST") {
2378
+ const b = await readJson(req);
2379
+ if (typeof b?.device !== "string" || typeof b?.disabled !== "boolean") {
2380
+ return json3({ error: "device (string) and disabled (boolean) are required" }, 400);
2381
+ }
2382
+ const cfg2 = getConfig();
2383
+ if (!(b.device in cfg2.devices))
2384
+ return json3({ error: `unknown device: ${b.device}` }, 404);
2385
+ const dc = cfg2.devices[b.device];
2386
+ const next = {
2387
+ ...cfg2,
2388
+ devices: {
2389
+ ...cfg2.devices,
2390
+ [b.device]: { ...dc, disabled: b.disabled || undefined }
2391
+ }
2392
+ };
2393
+ setConfig(next);
2394
+ let persisted = true;
2395
+ let warning;
2396
+ try {
2397
+ atomicWrite(getConfigSource().path, serializeConfig(next));
2398
+ } catch (e) {
2399
+ persisted = false;
2400
+ warning = `applied live but not saved to disk: ${e instanceof Error ? e.message : String(e)}`;
2401
+ }
2402
+ if (persisted) {
2403
+ recordVersion(getConfig(), "auto", Date.now(), `device ${b.disabled ? "disabled" : "enabled"}: ${b.device}`);
2404
+ }
2405
+ return json3({
2406
+ ok: true,
2407
+ persisted,
2408
+ requiresReconnect: true,
2409
+ warning,
2410
+ ...devicesPayload(db)
2411
+ });
2412
+ }
2376
2413
  if (url.pathname === "/api/topology") {
2377
2414
  return json3(topologyPayload());
2378
2415
  }
@@ -2588,13 +2625,25 @@ function substitute(body, vars) {
2588
2625
  return typeof v === "object" ? JSON.stringify(v) : String(v);
2589
2626
  });
2590
2627
  }
2591
- function registerPrompts(server) {
2628
+ function deviceArgDescription(directory) {
2629
+ if (directory && directory.length > 0) {
2630
+ const rows = directory.map((d) => `\u2022 ${d.key}${d.label && d.label !== d.key ? ` ("${d.label}")` : ""} \u2192 ${d.target}${d.isDefault ? " [default]" : ""}`).join(`
2631
+ `);
2632
+ return "Which configured MikroTik device to run this workflow on. " + `Pass the EXACT config key or its label. Configured devices:
2633
+ ` + `${rows}
2634
+ ` + "Omit to use the default device.";
2635
+ }
2636
+ return "Which configured MikroTik device to run this workflow on. Omit to use the default device.";
2637
+ }
2638
+ function registerPrompts(server, opts = {}) {
2592
2639
  let files;
2593
2640
  try {
2594
2641
  files = readdirSync2(PROMPTS_DIR).filter((f) => f.endsWith(".md"));
2595
2642
  } catch {
2596
2643
  return 0;
2597
2644
  }
2645
+ const multiDevice = opts.deviceNames && opts.deviceNames.length > 1;
2646
+ const selectorNames = multiDevice ? [...new Set([...opts.deviceNames, ...opts.deviceAliases ?? []])] : [];
2598
2647
  let count = 0;
2599
2648
  for (const file of files) {
2600
2649
  let parsed;
@@ -2613,6 +2662,10 @@ function registerPrompts(server) {
2613
2662
  const s = z2.string().describe(arg.description ?? "");
2614
2663
  argsSchema[arg.name] = arg.required ? s : s.optional();
2615
2664
  }
2665
+ const hasOwnDevice = parsed.arguments.some((a) => a.name === "device" || a.name === "device_a");
2666
+ if (multiDevice && !hasOwnDevice) {
2667
+ argsSchema.device = z2.enum(selectorNames).optional().describe(deviceArgDescription(opts.deviceDirectory));
2668
+ }
2616
2669
  server.registerPrompt(parsed.name, { title: parsed.title, description: parsed.description, argsSchema }, (args) => ({
2617
2670
  messages: [
2618
2671
  {
@@ -2691,8 +2744,8 @@ function createServer(opts = {}) {
2691
2744
  icons: [
2692
2745
  {
2693
2746
  src: LOGO_URL,
2694
- mimeType: "image/png",
2695
- sizes: ["192x192"],
2747
+ mimeType: "image/svg+xml",
2748
+ sizes: ["any"],
2696
2749
  theme: "light"
2697
2750
  }
2698
2751
  ]
@@ -2714,7 +2767,11 @@ function createServer(opts = {}) {
2714
2767
  appViews: getConfig().mcp.appViews,
2715
2768
  readOnly
2716
2769
  });
2717
- const promptCount = registerPrompts(server);
2770
+ const promptCount = registerPrompts(server, {
2771
+ deviceNames: names,
2772
+ deviceAliases: deviceLabels(),
2773
+ deviceDirectory: deviceDirectory()
2774
+ });
2718
2775
  const uiViewCount = registerUiResources(server);
2719
2776
  installToolPagination(server, getConfig().mcp.toolPageSize);
2720
2777
  return { server, toolCount, promptCount, uiViewCount, readOnly };
package/dist/index.d.ts CHANGED
@@ -47,7 +47,7 @@ interface UiLink {
47
47
  }
48
48
  declare function setConfig(cfg: MikrotikConfig): void;
49
49
  declare function getConfig(): MikrotikConfig;
50
- /** Names of every configured device, plus which one is the default. */
50
+ /** Names of every ENABLED configured device, plus which one is the default. */
51
51
  declare function listDevices(): {
52
52
  names: string[];
53
53
  default: string;
@@ -55,6 +55,7 @@ declare function listDevices(): {
55
55
  /**
56
56
  * Resolve a (possibly undefined) device name to a concrete, existing config key.
57
57
  * Accepts a config key OR a device's free-text label; falls back to the default.
58
+ * Only resolves to enabled devices.
58
59
  *
59
60
  * Matching is EXACT (key first, then label) — never fuzzy/substring — so a name
60
61
  * like "Ali Home" can never collapse onto a different device such as "home".
@@ -71,7 +72,7 @@ interface DeviceDirectoryEntry {
71
72
  /**
72
73
  * Return the connection config for a device by key or label (or the default when
73
74
  * `name` is undefined). Throws if an explicit name matches neither a key nor a
74
- * label.
75
+ * label, or if the device is disabled.
75
76
  */
76
77
  declare function getDevice(name?: string): DeviceConfig;
77
78
  /** Options threaded into every tool registration. */
@@ -323,5 +324,5 @@ declare class SafeModeManager {
323
324
  declare function getSafeModeManager(deviceName: string): SafeModeManager;
324
325
  declare const allToolModules: ToolModule[];
325
326
  declare const VERSION: string;
326
- declare const SERVER_NAME = "mcp-mikrotik";
327
+ declare const SERVER_NAME = "mikrotik-mcp";
327
328
  export { setConfig, resolveDeviceName, registerTools, loadConfig, listDevices, getSafeModeManager, getDevice, getConfig, executeMikrotikCommand, defineTool, createServer, allToolModules, VERSION, ToolModule, ToolContext, SafeModeManager, SERVER_NAME, RegisterableTool, MikrotikConfigSchema, MikrotikConfig, MikroTikSSHClient, DeviceConfigSchema, DeviceConfig };
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  resolveDeviceName,
23
23
  selectToolModules,
24
24
  setConfig
25
- } from "./shared/library-83q9t27c.js";
25
+ } from "./shared/library-ymtf9zyk.js";
26
26
  // src/server.ts
27
27
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
28
28
  import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
@@ -89,13 +89,25 @@ function substitute(body, vars) {
89
89
  return typeof v === "object" ? JSON.stringify(v) : String(v);
90
90
  });
91
91
  }
92
- function registerPrompts(server) {
92
+ function deviceArgDescription(directory) {
93
+ if (directory && directory.length > 0) {
94
+ const rows = directory.map((d) => `\u2022 ${d.key}${d.label && d.label !== d.key ? ` ("${d.label}")` : ""} \u2192 ${d.target}${d.isDefault ? " [default]" : ""}`).join(`
95
+ `);
96
+ return "Which configured MikroTik device to run this workflow on. " + `Pass the EXACT config key or its label. Configured devices:
97
+ ` + `${rows}
98
+ ` + "Omit to use the default device.";
99
+ }
100
+ return "Which configured MikroTik device to run this workflow on. Omit to use the default device.";
101
+ }
102
+ function registerPrompts(server, opts = {}) {
93
103
  let files;
94
104
  try {
95
105
  files = readdirSync(PROMPTS_DIR).filter((f) => f.endsWith(".md"));
96
106
  } catch {
97
107
  return 0;
98
108
  }
109
+ const multiDevice = opts.deviceNames && opts.deviceNames.length > 1;
110
+ const selectorNames = multiDevice ? [...new Set([...opts.deviceNames, ...opts.deviceAliases ?? []])] : [];
99
111
  let count = 0;
100
112
  for (const file of files) {
101
113
  let parsed;
@@ -114,6 +126,10 @@ function registerPrompts(server) {
114
126
  const s = z.string().describe(arg.description ?? "");
115
127
  argsSchema[arg.name] = arg.required ? s : s.optional();
116
128
  }
129
+ const hasOwnDevice = parsed.arguments.some((a) => a.name === "device" || a.name === "device_a");
130
+ if (multiDevice && !hasOwnDevice) {
131
+ argsSchema.device = z.enum(selectorNames).optional().describe(deviceArgDescription(opts.deviceDirectory));
132
+ }
117
133
  server.registerPrompt(parsed.name, { title: parsed.title, description: parsed.description, argsSchema }, (args) => ({
118
134
  messages: [
119
135
  {
@@ -137,9 +153,9 @@ var pkg = JSON.parse(readFileSync2(join2(PROJECT_ROOT, "package.json"), "utf-8")
137
153
  var VERSION = pkg.version ?? "0.0.0";
138
154
  var WEBSITE_URL = pkg.homepage ?? "";
139
155
  var LOGO_URL = pkg.logoIcon ?? "";
140
- var SERVER_TITLE = pkg.name ?? "mcp-mikrotik";
156
+ var SERVER_TITLE = "MikroTik MCP";
141
157
  var SERVER_DESCRIPTION = pkg.description ?? "";
142
- var SERVER_NAME = "mcp-mikrotik";
158
+ var SERVER_NAME = "mikrotik-mcp";
143
159
 
144
160
  // src/server.ts
145
161
  var INSTRUCTIONS = `MikroTik RouterOS management over SSH.
@@ -203,8 +219,8 @@ function createServer(opts = {}) {
203
219
  icons: [
204
220
  {
205
221
  src: LOGO_URL,
206
- mimeType: "image/png",
207
- sizes: ["192x192"],
222
+ mimeType: "image/svg+xml",
223
+ sizes: ["any"],
208
224
  theme: "light"
209
225
  }
210
226
  ]
@@ -226,7 +242,11 @@ function createServer(opts = {}) {
226
242
  appViews: getConfig().mcp.appViews,
227
243
  readOnly
228
244
  });
229
- const promptCount = registerPrompts(server);
245
+ const promptCount = registerPrompts(server, {
246
+ deviceNames: names,
247
+ deviceAliases: deviceLabels(),
248
+ deviceDirectory: deviceDirectory()
249
+ });
230
250
  const uiViewCount = registerUiResources(server);
231
251
  installToolPagination(server, getConfig().mcp.toolPageSize);
232
252
  return { server, toolCount, promptCount, uiViewCount, readOnly };
@@ -4,7 +4,7 @@ import {
4
4
  allToolModules,
5
5
  moduleCatalog,
6
6
  selectToolModules
7
- } from "./cli-44v6pb38.js";
7
+ } from "./cli-thhk9f7n.js";
8
8
  export {
9
9
  selectToolModules,
10
10
  moduleCatalog,
@@ -337,7 +337,8 @@ var DeviceConfigSchema = z.object({
337
337
  sourceMac: z.string().optional(),
338
338
  macHost: z.string().optional(),
339
339
  macPort: z.coerce.number().int().positive().optional(),
340
- description: z.string().optional()
340
+ description: z.string().optional(),
341
+ disabled: z.boolean().optional()
341
342
  });
342
343
  var S3ConfigSchema = z.object({
343
344
  accessKeyId: z.string().optional(),
@@ -584,13 +585,17 @@ function setConfig(cfg) {
584
585
  function getConfig() {
585
586
  return active;
586
587
  }
588
+ function isEnabled(dc) {
589
+ return !dc.disabled;
590
+ }
587
591
  function listDevices() {
588
- return { names: Object.keys(active.devices), default: active.defaultDevice };
592
+ const names = Object.entries(active.devices).filter(([, dc]) => isEnabled(dc)).map(([k]) => k);
593
+ return { names, default: active.defaultDevice };
589
594
  }
590
595
  function deviceKeyForLabel(name) {
591
596
  const target = name.trim().toLowerCase();
592
597
  for (const [key, dc] of Object.entries(active.devices)) {
593
- if (dc.description && dc.description.trim().toLowerCase() === target)
598
+ if (isEnabled(dc) && dc.description && dc.description.trim().toLowerCase() === target)
594
599
  return key;
595
600
  }
596
601
  return;
@@ -599,6 +604,8 @@ function deviceLabels() {
599
604
  const seen = new Set;
600
605
  const out = [];
601
606
  for (const [key, dc] of Object.entries(active.devices)) {
607
+ if (!isEnabled(dc))
608
+ continue;
602
609
  const label = dc.description?.trim();
603
610
  if (label && label !== key && !(label in active.devices) && !seen.has(label)) {
604
611
  seen.add(label);
@@ -609,13 +616,17 @@ function deviceLabels() {
609
616
  }
610
617
  function resolveDeviceName(name) {
611
618
  if (name) {
612
- if (name in active.devices)
619
+ if (name in active.devices && isEnabled(active.devices[name]))
613
620
  return name;
614
621
  const byLabel = deviceKeyForLabel(name);
615
622
  if (byLabel)
616
623
  return byLabel;
617
624
  }
618
- return active.defaultDevice in active.devices ? active.defaultDevice : Object.keys(active.devices)[0] ?? active.defaultDevice;
625
+ if (active.defaultDevice in active.devices && isEnabled(active.devices[active.defaultDevice])) {
626
+ return active.defaultDevice;
627
+ }
628
+ const firstEnabled = Object.entries(active.devices).find(([, dc]) => isEnabled(dc));
629
+ return firstEnabled ? firstEnabled[0] : active.defaultDevice;
619
630
  }
620
631
  function deviceTarget(dc) {
621
632
  if (!dc)
@@ -623,7 +634,7 @@ function deviceTarget(dc) {
623
634
  return dc.mac ? `MAC ${dc.mac}` : `${dc.host}:${dc.port ?? 22}`;
624
635
  }
625
636
  function deviceDirectory() {
626
- return Object.entries(active.devices).map(([key, dc]) => ({
637
+ return Object.entries(active.devices).filter(([, dc]) => isEnabled(dc)).map(([key, dc]) => ({
627
638
  key,
628
639
  label: dc.description?.trim() || undefined,
629
640
  target: deviceTarget(dc),
@@ -643,6 +654,9 @@ function getDevice(name) {
643
654
  const dc = active.devices[key];
644
655
  if (!dc)
645
656
  throw new Error(`No device configuration available for '${key}'.`);
657
+ if (dc.disabled) {
658
+ throw new Error(`Device '${key}' is disabled. Enable it from the dashboard or config file.`);
659
+ }
646
660
  return dc;
647
661
  }
648
662
 
@@ -8313,7 +8327,7 @@ var cache = null;
8313
8327
  async function gateway() {
8314
8328
  if (cache)
8315
8329
  return cache;
8316
- const { moduleCatalog } = await import("./cli-yxah49n1.js");
8330
+ const { moduleCatalog } = await import("./cli-0ctwspf1.js");
8317
8331
  const forIndex = [];
8318
8332
  const byName = new Map;
8319
8333
  for (const mod of moduleCatalog) {
@@ -4,7 +4,7 @@ import {
4
4
  allToolModules,
5
5
  moduleCatalog,
6
6
  selectToolModules
7
- } from "./library-83q9t27c.js";
7
+ } from "./library-ymtf9zyk.js";
8
8
  export {
9
9
  selectToolModules,
10
10
  moduleCatalog,
@@ -337,7 +337,8 @@ var DeviceConfigSchema = z.object({
337
337
  sourceMac: z.string().optional(),
338
338
  macHost: z.string().optional(),
339
339
  macPort: z.coerce.number().int().positive().optional(),
340
- description: z.string().optional()
340
+ description: z.string().optional(),
341
+ disabled: z.boolean().optional()
341
342
  });
342
343
  var S3ConfigSchema = z.object({
343
344
  accessKeyId: z.string().optional(),
@@ -581,13 +582,17 @@ function setConfig(cfg) {
581
582
  function getConfig() {
582
583
  return active;
583
584
  }
585
+ function isEnabled(dc) {
586
+ return !dc.disabled;
587
+ }
584
588
  function listDevices() {
585
- return { names: Object.keys(active.devices), default: active.defaultDevice };
589
+ const names = Object.entries(active.devices).filter(([, dc]) => isEnabled(dc)).map(([k]) => k);
590
+ return { names, default: active.defaultDevice };
586
591
  }
587
592
  function deviceKeyForLabel(name) {
588
593
  const target = name.trim().toLowerCase();
589
594
  for (const [key, dc] of Object.entries(active.devices)) {
590
- if (dc.description && dc.description.trim().toLowerCase() === target)
595
+ if (isEnabled(dc) && dc.description && dc.description.trim().toLowerCase() === target)
591
596
  return key;
592
597
  }
593
598
  return;
@@ -596,6 +601,8 @@ function deviceLabels() {
596
601
  const seen = new Set;
597
602
  const out = [];
598
603
  for (const [key, dc] of Object.entries(active.devices)) {
604
+ if (!isEnabled(dc))
605
+ continue;
599
606
  const label = dc.description?.trim();
600
607
  if (label && label !== key && !(label in active.devices) && !seen.has(label)) {
601
608
  seen.add(label);
@@ -606,13 +613,17 @@ function deviceLabels() {
606
613
  }
607
614
  function resolveDeviceName(name) {
608
615
  if (name) {
609
- if (name in active.devices)
616
+ if (name in active.devices && isEnabled(active.devices[name]))
610
617
  return name;
611
618
  const byLabel = deviceKeyForLabel(name);
612
619
  if (byLabel)
613
620
  return byLabel;
614
621
  }
615
- return active.defaultDevice in active.devices ? active.defaultDevice : Object.keys(active.devices)[0] ?? active.defaultDevice;
622
+ if (active.defaultDevice in active.devices && isEnabled(active.devices[active.defaultDevice])) {
623
+ return active.defaultDevice;
624
+ }
625
+ const firstEnabled = Object.entries(active.devices).find(([, dc]) => isEnabled(dc));
626
+ return firstEnabled ? firstEnabled[0] : active.defaultDevice;
616
627
  }
617
628
  function deviceTarget(dc) {
618
629
  if (!dc)
@@ -620,7 +631,7 @@ function deviceTarget(dc) {
620
631
  return dc.mac ? `MAC ${dc.mac}` : `${dc.host}:${dc.port ?? 22}`;
621
632
  }
622
633
  function deviceDirectory() {
623
- return Object.entries(active.devices).map(([key, dc]) => ({
634
+ return Object.entries(active.devices).filter(([, dc]) => isEnabled(dc)).map(([key, dc]) => ({
624
635
  key,
625
636
  label: dc.description?.trim() || undefined,
626
637
  target: deviceTarget(dc),
@@ -640,6 +651,9 @@ function getDevice(name) {
640
651
  const dc = active.devices[key];
641
652
  if (!dc)
642
653
  throw new Error(`No device configuration available for '${key}'.`);
654
+ if (dc.disabled) {
655
+ throw new Error(`Device '${key}' is disabled. Enable it from the dashboard or config file.`);
656
+ }
643
657
  return dc;
644
658
  }
645
659
 
@@ -8243,7 +8257,7 @@ var cache = null;
8243
8257
  async function gateway() {
8244
8258
  if (cache)
8245
8259
  return cache;
8246
- const { moduleCatalog } = await import("./library-cs13jqp9.js");
8260
+ const { moduleCatalog } = await import("./library-jve65pzw.js");
8247
8261
  const forIndex = [];
8248
8262
  const byName = new Map;
8249
8263
  for (const mod of moduleCatalog) {