@usex/mikrotik-mcp 3.18.0 → 3.19.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
@@ -2174,7 +2174,7 @@ ${result}`;
2174
2174
  name: "create_export",
2175
2175
  title: "Create Full Configuration Export",
2176
2176
  annotations: READ,
2177
- description: "Exports the complete device configuration to a `.rsc` plain-text script file (`/export" + " file=<name>`), re-applicable via `import_configuration`. Unlike `create_backup`, the result" + " is human-readable plain text \u2014 not a binary snapshot \u2014 and passwords are hidden by default" + " (`hide_sensitive=true`). `file_format` changes only the file extension used when looking up" + " the saved file \u2014 no `format=` flag is ever sent to RouterOS, so the content is always" + " RouterOS script text regardless of the chosen extension; selecting `json` or `xml` will also" + " cause the post-export file lookup to fail because RouterOS saves the file as `.rsc`. Use the" + " `compact` boolean to omit default values, or `verbose` to include all parameters;" + " `export_type` only controls where the `file=` argument is positioned in the command and" + " does not independently drive compactness or verbosity. For a single subsection only use" + " `export_section`. Returns file details of the created export file.",
2177
+ description: "Exports the complete device configuration to a `.rsc` plain-text script file (`/export" + " file=<name>`), re-applicable via `import_configuration`. Unlike `create_backup`, the result" + " is human-readable plain text \u2014 not a binary snapshot \u2014 and passwords are hidden by default" + " (`hide_sensitive=true`). `file_format` changes only the file extension used when looking up" + " the saved file \u2014 no `format=` flag is ever sent to RouterOS, so the content is always" + " RouterOS script text regardless of the chosen extension; selecting `json` or `xml` will also" + " cause the post-export file lookup to fail because RouterOS saves the file as `.rsc`." + " ALWAYS default to a FULL configuration export: leave `compact` and `verbose` off (the plain" + " full export) unless the user explicitly asks for less or more. Set `compact` ONLY when the" + " user wants a smaller diff that omits default values, or `verbose` ONLY when they want every" + " parameter (including defaults). `export_type` only controls where the `file=` argument is" + " positioned in the command and does not independently drive compactness or verbosity. For a" + " single subsection only use `export_section`. Returns file details of the created export file.",
2178
2178
  inputSchema: {
2179
2179
  name: z4.string().optional(),
2180
2180
  file_format: z4.enum(["rsc", "json", "xml"]).default("rsc"),
@@ -2367,6 +2367,12 @@ ${result}`;
2367
2367
  // src/tools/local-backup.ts
2368
2368
  import { z as z5 } from "zod";
2369
2369
 
2370
+ // src/core/slug.ts
2371
+ function deviceSlug(name) {
2372
+ const s = (name ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
2373
+ return s || "device";
2374
+ }
2375
+
2370
2376
  // src/backups/vault.ts
2371
2377
  import {
2372
2378
  existsSync,
@@ -2487,15 +2493,17 @@ function labelSlug(label) {
2487
2493
  const s = (label ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
2488
2494
  return s ? `_${s}` : "";
2489
2495
  }
2496
+ function buildExportCommand(opts = {}) {
2497
+ return new Cmd("/export").raw(opts.verbose ? "verbose" : opts.compact ? "compact" : undefined).raw(opts.terse ? "terse" : undefined).raw(opts.showSensitive ? "show-sensitive" : undefined).build();
2498
+ }
2490
2499
  async function createLocalBackup(ctx, opts = {}) {
2491
2500
  const device = resolveDeviceName(ctx.device);
2492
- const cmd = new Cmd("/export").raw(opts.showSensitive ? "show-sensitive" : undefined).build();
2493
- const body = await executeMikrotikCommand(cmd, ctx);
2501
+ const body = await executeMikrotikCommand(buildExportCommand(opts), ctx);
2494
2502
  if (isEmpty(body) || looksLikeError(body)) {
2495
2503
  return { ok: false, device, error: body.trim() || "(empty export)" };
2496
2504
  }
2497
2505
  const stamp = await deviceDateStamp(ctx);
2498
- const name = `${device}_${stamp}${labelSlug(opts.label)}.rsc`;
2506
+ const name = `${deviceSlug(device)}_${stamp}${labelSlug(opts.label)}.rsc`;
2499
2507
  return { ok: true, device, name: writeBackup(name, body), bytes: Buffer.byteLength(body) };
2500
2508
  }
2501
2509
 
@@ -2566,13 +2574,22 @@ var localBackupTools = [
2566
2574
  name: "create_local_backup",
2567
2575
  title: "Create Local Config Backup (host vault)",
2568
2576
  annotations: WRITE,
2569
- description: "Captures the device's full configuration with `/export` and saves it as a timestamped " + "plain-text `.rsc` file in the MCP server's LOCAL backup vault (default " + "`~/.mikrotik-mcp/backups/`, override with the `MIKROTIK_BACKUP_DIR` env var) \u2014 NOT on the " + "device and NOT in S3. The filename is `<device>_<date_time>.rsc` stamped in the device's " + "local clock (24-hour; Jalali for the Tehran timezone, Gregorian otherwise). This is a " + "host-side, human-readable, diffable copy you can restore later with restore_local_backup. " + "Compare: create_backup makes a binary `/system backup` file ON the device; " + "capture_config_snapshot stores an export in the local snapshot database; upload_backup_to_s3 " + "pushes to S3. show_sensitive=true includes secrets (keys/passwords) in the export. " + "Returns the saved filename, byte size and vault path.",
2577
+ description: "Captures the device's full configuration with `/export` and saves it as a timestamped " + "plain-text `.rsc` file in the MCP server's LOCAL backup vault (default " + "`~/.mikrotik-mcp/backups/`, override with the `MIKROTIK_BACKUP_DIR` env var) \u2014 NOT on the " + "device and NOT in S3. The filename is `<device-slug>_<date_time>.rsc` \u2014 the device name is " + "slugified (spaces/underscores/etc \u2192 dash) \u2014 stamped in the device's local clock (24-hour; " + "Jalali for the Tehran timezone, Gregorian otherwise). This is a host-side, human-readable, " + "diffable copy you can restore later with restore_local_backup. Compare: create_backup makes " + "a binary `/system backup` file ON the device; capture_config_snapshot stores an export in " + "the local snapshot database; upload_backup_to_s3 pushes to S3. ALWAYS default to a FULL " + "backup of the complete configuration: call this with no option flags (the bare `/export`) " + "unless the user explicitly asks for less or more. Narrow it ONLY on request \u2014 set compact to " + "drop default values, or use export_section for a single subsection. Broaden it ONLY on " + "request \u2014 set verbose to include every parameter (even defaults), show_sensitive to include " + "secrets (keys/passwords), or use create_backup for a binary full-system snapshot. terse just " + "changes the text to one machine-readable line per item. Returns the saved filename, byte size " + "and vault path.",
2570
2578
  inputSchema: {
2571
2579
  label: z5.string().optional().describe('Optional label appended to the filename, e.g. "pre-upgrade".'),
2572
- show_sensitive: z5.boolean().default(false).describe("Include secrets (keys/passwords) in the export. Default false.")
2580
+ show_sensitive: z5.boolean().default(false).describe("Include secrets (keys/passwords) in the export. Default false."),
2581
+ verbose: z5.boolean().default(false).describe("Include every parameter, even defaults (RouterOS `verbose`)."),
2582
+ compact: z5.boolean().default(false).describe("Export only non-default values (RouterOS `compact`; ignored if verbose)."),
2583
+ terse: z5.boolean().default(false).describe("One self-contained, machine-readable line per item (RouterOS `terse`).")
2573
2584
  },
2574
2585
  async handler(a, ctx) {
2575
- const r = await createLocalBackup(ctx, { label: a.label, showSensitive: a.show_sensitive });
2586
+ const r = await createLocalBackup(ctx, {
2587
+ label: a.label,
2588
+ showSensitive: a.show_sensitive,
2589
+ verbose: a.verbose,
2590
+ compact: a.compact,
2591
+ terse: a.terse
2592
+ });
2576
2593
  if (!r.ok)
2577
2594
  return `Failed to capture export for a local backup: ${r.error}`;
2578
2595
  return `Saved local backup '${r.name}' (${r.bytes} bytes) for device '${r.device}' to ` + `${backupDir()}. Restore it with restore_local_backup name=${r.name}.`;
@@ -2708,7 +2725,7 @@ function trimSlashes(s) {
2708
2725
  return s.replace(/^\/+|\/+$/g, "");
2709
2726
  }
2710
2727
  function s3DevicePrefix(device) {
2711
- const segments = [getS3Config()?.prefix ?? "", device ?? ""].map(trimSlashes).filter(Boolean);
2728
+ const segments = [getS3Config()?.prefix ?? "", device ? deviceSlug(device) : ""].map(trimSlashes).filter(Boolean);
2712
2729
  return segments.length ? `${segments.join("/")}/` : "";
2713
2730
  }
2714
2731
  function s3Key(name, device) {
@@ -23449,7 +23466,10 @@ async function featureRoutes(req, url) {
23449
23466
  const ctx = createContext(undefined, b?.device);
23450
23467
  const r = await createLocalBackup(ctx, {
23451
23468
  label: b?.label,
23452
- showSensitive: b?.show_sensitive === true
23469
+ showSensitive: b?.show_sensitive === true,
23470
+ verbose: b?.verbose === true,
23471
+ compact: b?.compact === true,
23472
+ terse: b?.terse === true
23453
23473
  });
23454
23474
  return r.ok ? json(r) : json({ error: r.error ?? "export failed" }, 502);
23455
23475
  }
@@ -23768,7 +23788,7 @@ function registerPrompts(server) {
23768
23788
  // package.json
23769
23789
  var package_default = {
23770
23790
  name: "@usex/mikrotik-mcp",
23771
- version: "3.18.0",
23791
+ version: "3.19.0",
23772
23792
  description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
23773
23793
  keywords: [
23774
23794
  "ai",
package/dist/index.js CHANGED
@@ -2186,7 +2186,7 @@ ${result}`;
2186
2186
  name: "create_export",
2187
2187
  title: "Create Full Configuration Export",
2188
2188
  annotations: READ,
2189
- description: "Exports the complete device configuration to a `.rsc` plain-text script file (`/export" + " file=<name>`), re-applicable via `import_configuration`. Unlike `create_backup`, the result" + " is human-readable plain text \u2014 not a binary snapshot \u2014 and passwords are hidden by default" + " (`hide_sensitive=true`). `file_format` changes only the file extension used when looking up" + " the saved file \u2014 no `format=` flag is ever sent to RouterOS, so the content is always" + " RouterOS script text regardless of the chosen extension; selecting `json` or `xml` will also" + " cause the post-export file lookup to fail because RouterOS saves the file as `.rsc`. Use the" + " `compact` boolean to omit default values, or `verbose` to include all parameters;" + " `export_type` only controls where the `file=` argument is positioned in the command and" + " does not independently drive compactness or verbosity. For a single subsection only use" + " `export_section`. Returns file details of the created export file.",
2189
+ description: "Exports the complete device configuration to a `.rsc` plain-text script file (`/export" + " file=<name>`), re-applicable via `import_configuration`. Unlike `create_backup`, the result" + " is human-readable plain text \u2014 not a binary snapshot \u2014 and passwords are hidden by default" + " (`hide_sensitive=true`). `file_format` changes only the file extension used when looking up" + " the saved file \u2014 no `format=` flag is ever sent to RouterOS, so the content is always" + " RouterOS script text regardless of the chosen extension; selecting `json` or `xml` will also" + " cause the post-export file lookup to fail because RouterOS saves the file as `.rsc`." + " ALWAYS default to a FULL configuration export: leave `compact` and `verbose` off (the plain" + " full export) unless the user explicitly asks for less or more. Set `compact` ONLY when the" + " user wants a smaller diff that omits default values, or `verbose` ONLY when they want every" + " parameter (including defaults). `export_type` only controls where the `file=` argument is" + " positioned in the command and does not independently drive compactness or verbosity. For a" + " single subsection only use `export_section`. Returns file details of the created export file.",
2190
2190
  inputSchema: {
2191
2191
  name: z5.string().optional(),
2192
2192
  file_format: z5.enum(["rsc", "json", "xml"]).default("rsc"),
@@ -2379,6 +2379,12 @@ ${result}`;
2379
2379
  // src/tools/local-backup.ts
2380
2380
  import { z as z6 } from "zod";
2381
2381
 
2382
+ // src/core/slug.ts
2383
+ function deviceSlug(name) {
2384
+ const s = (name ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
2385
+ return s || "device";
2386
+ }
2387
+
2382
2388
  // src/backups/vault.ts
2383
2389
  import {
2384
2390
  existsSync,
@@ -2499,15 +2505,17 @@ function labelSlug(label) {
2499
2505
  const s = (label ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
2500
2506
  return s ? `_${s}` : "";
2501
2507
  }
2508
+ function buildExportCommand(opts = {}) {
2509
+ return new Cmd("/export").raw(opts.verbose ? "verbose" : opts.compact ? "compact" : undefined).raw(opts.terse ? "terse" : undefined).raw(opts.showSensitive ? "show-sensitive" : undefined).build();
2510
+ }
2502
2511
  async function createLocalBackup(ctx, opts = {}) {
2503
2512
  const device = resolveDeviceName(ctx.device);
2504
- const cmd = new Cmd("/export").raw(opts.showSensitive ? "show-sensitive" : undefined).build();
2505
- const body = await executeMikrotikCommand(cmd, ctx);
2513
+ const body = await executeMikrotikCommand(buildExportCommand(opts), ctx);
2506
2514
  if (isEmpty(body) || looksLikeError(body)) {
2507
2515
  return { ok: false, device, error: body.trim() || "(empty export)" };
2508
2516
  }
2509
2517
  const stamp = await deviceDateStamp(ctx);
2510
- const name = `${device}_${stamp}${labelSlug(opts.label)}.rsc`;
2518
+ const name = `${deviceSlug(device)}_${stamp}${labelSlug(opts.label)}.rsc`;
2511
2519
  return { ok: true, device, name: writeBackup(name, body), bytes: Buffer.byteLength(body) };
2512
2520
  }
2513
2521
 
@@ -2578,13 +2586,22 @@ var localBackupTools = [
2578
2586
  name: "create_local_backup",
2579
2587
  title: "Create Local Config Backup (host vault)",
2580
2588
  annotations: WRITE,
2581
- description: "Captures the device's full configuration with `/export` and saves it as a timestamped " + "plain-text `.rsc` file in the MCP server's LOCAL backup vault (default " + "`~/.mikrotik-mcp/backups/`, override with the `MIKROTIK_BACKUP_DIR` env var) \u2014 NOT on the " + "device and NOT in S3. The filename is `<device>_<date_time>.rsc` stamped in the device's " + "local clock (24-hour; Jalali for the Tehran timezone, Gregorian otherwise). This is a " + "host-side, human-readable, diffable copy you can restore later with restore_local_backup. " + "Compare: create_backup makes a binary `/system backup` file ON the device; " + "capture_config_snapshot stores an export in the local snapshot database; upload_backup_to_s3 " + "pushes to S3. show_sensitive=true includes secrets (keys/passwords) in the export. " + "Returns the saved filename, byte size and vault path.",
2589
+ description: "Captures the device's full configuration with `/export` and saves it as a timestamped " + "plain-text `.rsc` file in the MCP server's LOCAL backup vault (default " + "`~/.mikrotik-mcp/backups/`, override with the `MIKROTIK_BACKUP_DIR` env var) \u2014 NOT on the " + "device and NOT in S3. The filename is `<device-slug>_<date_time>.rsc` \u2014 the device name is " + "slugified (spaces/underscores/etc \u2192 dash) \u2014 stamped in the device's local clock (24-hour; " + "Jalali for the Tehran timezone, Gregorian otherwise). This is a host-side, human-readable, " + "diffable copy you can restore later with restore_local_backup. Compare: create_backup makes " + "a binary `/system backup` file ON the device; capture_config_snapshot stores an export in " + "the local snapshot database; upload_backup_to_s3 pushes to S3. ALWAYS default to a FULL " + "backup of the complete configuration: call this with no option flags (the bare `/export`) " + "unless the user explicitly asks for less or more. Narrow it ONLY on request \u2014 set compact to " + "drop default values, or use export_section for a single subsection. Broaden it ONLY on " + "request \u2014 set verbose to include every parameter (even defaults), show_sensitive to include " + "secrets (keys/passwords), or use create_backup for a binary full-system snapshot. terse just " + "changes the text to one machine-readable line per item. Returns the saved filename, byte size " + "and vault path.",
2582
2590
  inputSchema: {
2583
2591
  label: z6.string().optional().describe('Optional label appended to the filename, e.g. "pre-upgrade".'),
2584
- show_sensitive: z6.boolean().default(false).describe("Include secrets (keys/passwords) in the export. Default false.")
2592
+ show_sensitive: z6.boolean().default(false).describe("Include secrets (keys/passwords) in the export. Default false."),
2593
+ verbose: z6.boolean().default(false).describe("Include every parameter, even defaults (RouterOS `verbose`)."),
2594
+ compact: z6.boolean().default(false).describe("Export only non-default values (RouterOS `compact`; ignored if verbose)."),
2595
+ terse: z6.boolean().default(false).describe("One self-contained, machine-readable line per item (RouterOS `terse`).")
2585
2596
  },
2586
2597
  async handler(a, ctx) {
2587
- const r = await createLocalBackup(ctx, { label: a.label, showSensitive: a.show_sensitive });
2598
+ const r = await createLocalBackup(ctx, {
2599
+ label: a.label,
2600
+ showSensitive: a.show_sensitive,
2601
+ verbose: a.verbose,
2602
+ compact: a.compact,
2603
+ terse: a.terse
2604
+ });
2588
2605
  if (!r.ok)
2589
2606
  return `Failed to capture export for a local backup: ${r.error}`;
2590
2607
  return `Saved local backup '${r.name}' (${r.bytes} bytes) for device '${r.device}' to ` + `${backupDir()}. Restore it with restore_local_backup name=${r.name}.`;
@@ -2720,7 +2737,7 @@ function trimSlashes(s) {
2720
2737
  return s.replace(/^\/+|\/+$/g, "");
2721
2738
  }
2722
2739
  function s3DevicePrefix(device) {
2723
- const segments = [getS3Config()?.prefix ?? "", device ?? ""].map(trimSlashes).filter(Boolean);
2740
+ const segments = [getS3Config()?.prefix ?? "", device ? deviceSlug(device) : ""].map(trimSlashes).filter(Boolean);
2724
2741
  return segments.length ? `${segments.join("/")}/` : "";
2725
2742
  }
2726
2743
  function s3Key(name, device) {
@@ -22171,7 +22188,7 @@ function selectToolModules(filter = {}, catalog = moduleCatalog) {
22171
22188
  // package.json
22172
22189
  var package_default = {
22173
22190
  name: "@usex/mikrotik-mcp",
22174
- version: "3.18.0",
22191
+ version: "3.19.0",
22175
22192
  description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
22176
22193
  keywords: [
22177
22194
  "ai",