mikromcp 1.9.0 → 1.10.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.
Files changed (3) hide show
  1. package/README.md +5 -1
  2. package/dist/main.js +1012 -342
  3. package/package.json +3 -1
package/dist/main.js CHANGED
@@ -498,6 +498,9 @@ function parseRouterOSValue(key2, value) {
498
498
  function isTrue(v) {
499
499
  return v === true || v === "true" || v === "yes";
500
500
  }
501
+ function sameValue(recordValue, desired) {
502
+ return String(recordValue) === String(desired);
503
+ }
501
504
  function parseRecord(raw) {
502
505
  const parsed = {};
503
506
  for (const [key2, value] of Object.entries(raw)) {
@@ -602,12 +605,18 @@ var listInterfacesTool = {
602
605
  return true;
603
606
  });
604
607
  }
605
- const { items: paginated, total, hasMore } = paginate(interfaces, parsed.offset, parsed.limit);
608
+ const {
609
+ items: paginated,
610
+ total,
611
+ hasMore
612
+ } = paginate(interfaces, parsed.offset, parsed.limit);
606
613
  const results2 = paginated.map((iface) => {
607
614
  const rec = iface;
608
615
  const isRunning2 = isTrue(rec.running);
609
616
  const isDisabled = isTrue(rec.disabled);
610
- const enriched = { status: isDisabled ? "disabled" : isRunning2 ? "up" : "down" };
617
+ const enriched = {
618
+ status: isDisabled ? "disabled" : isRunning2 ? "up" : "down"
619
+ };
611
620
  for (const [key2, value] of Object.entries(rec)) {
612
621
  if (parsed.includeCounters || !COUNTER_PROPS.includes(key2)) {
613
622
  enriched[key2] = value;
@@ -809,9 +818,13 @@ var manageIpAddressTool = {
809
818
  const changes = {};
810
819
  const diff = [];
811
820
  const disabledStr = parsed.disabled ? "true" : "false";
812
- if (rec.disabled !== disabledStr) {
821
+ if (isTrue(rec.disabled) !== parsed.disabled) {
813
822
  changes.disabled = disabledStr;
814
- diff.push({ property: "disabled", before: rec.disabled ?? null, after: disabledStr });
823
+ diff.push({
824
+ property: "disabled",
825
+ before: rec.disabled === void 0 ? null : String(rec.disabled),
826
+ after: disabledStr
827
+ });
815
828
  }
816
829
  if (comment !== void 0 && (rec.comment ?? "") !== comment) {
817
830
  changes.comment = comment;
@@ -1038,7 +1051,11 @@ var manageDhcpLeaseTool = {
1038
1051
  if (isAlreadyStatic) {
1039
1052
  return {
1040
1053
  content: `Lease for ${upperMac} (${existing.address ?? "unknown"}) is already static. No changes made.`,
1041
- structuredContent: { action: "already_static", macAddress: upperMac, id: existing[".id"] }
1054
+ structuredContent: {
1055
+ action: "already_static",
1056
+ macAddress: upperMac,
1057
+ id: existing[".id"]
1058
+ }
1042
1059
  };
1043
1060
  }
1044
1061
  if (parsed.dryRun) {
@@ -1144,7 +1161,14 @@ var listRoutesTool = {
1144
1161
  paginated,
1145
1162
  total,
1146
1163
  parsed.offset,
1147
- (r) => compactFields(r, ["dst-address", "gateway", "distance", "routing-table", "active", "disabled"])
1164
+ (r) => compactFields(r, [
1165
+ "dst-address",
1166
+ "gateway",
1167
+ "distance",
1168
+ "routing-table",
1169
+ "active",
1170
+ "disabled"
1171
+ ])
1148
1172
  ),
1149
1173
  structuredContent: {
1150
1174
  routerId: context.routerId,
@@ -1221,9 +1245,9 @@ var manageRouteTool = {
1221
1245
  if (parsed.action === "add") {
1222
1246
  if (existing) {
1223
1247
  const rec = existing;
1224
- const existingDistance = rec.distance ?? "1";
1248
+ const existingDistance = String(rec.distance ?? "1");
1225
1249
  const existingDisabled = isTrue(rec.disabled);
1226
- const sameDistance = existingDistance === String(parsed.distance);
1250
+ const sameDistance = sameValue(rec.distance ?? 1, parsed.distance);
1227
1251
  const sameDisabled = existingDisabled === parsed.disabled;
1228
1252
  if (sameDistance && sameDisabled) {
1229
1253
  return {
@@ -1927,7 +1951,14 @@ function parseRouterClock(dateStr, timeStr) {
1927
1951
  const ss = parseInt(time[3], 10);
1928
1952
  const iso = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateStr);
1929
1953
  if (iso) {
1930
- return new Date(parseInt(iso[1], 10), parseInt(iso[2], 10) - 1, parseInt(iso[3], 10), hh, mm, ss);
1954
+ return new Date(
1955
+ parseInt(iso[1], 10),
1956
+ parseInt(iso[2], 10) - 1,
1957
+ parseInt(iso[3], 10),
1958
+ hh,
1959
+ mm,
1960
+ ss
1961
+ );
1931
1962
  }
1932
1963
  const named = /^([a-z]{3})\/(\d{2})\/(\d{4})$/i.exec(dateStr);
1933
1964
  if (named) {
@@ -1963,7 +1994,8 @@ var getLogTool = {
1963
1994
  const clock = Array.isArray(clockResult) ? clockResult[0] : clockResult;
1964
1995
  const routerNow = parseRouterClock(clock?.date, clock?.time);
1965
1996
  if (routerNow !== null) now = routerNow;
1966
- else log7.warn({ routerId: context.routerId }, "Unparseable router clock; using host clock");
1997
+ else
1998
+ log7.warn({ routerId: context.routerId }, "Unparseable router clock; using host clock");
1967
1999
  } catch {
1968
2000
  log7.warn({ routerId: context.routerId }, "Router clock unavailable; using host clock");
1969
2001
  }
@@ -2077,9 +2109,7 @@ function checkCommand(command, policy) {
2077
2109
  }
2078
2110
  if (policy.allow.length > 0) {
2079
2111
  const normAllow = policy.allow.map((p) => normalizeCommand(p));
2080
- const everySegmentAllowed = normSegs.every(
2081
- (seg) => normAllow.some((p) => globMatch(p, seg))
2082
- );
2112
+ const everySegmentAllowed = normSegs.every((seg) => normAllow.some((p) => globMatch(p, seg)));
2083
2113
  if (!everySegmentAllowed) {
2084
2114
  throw new MikroMCPError({
2085
2115
  category: "VALIDATION" /* VALIDATION */,
@@ -2350,7 +2380,14 @@ var listBridgesTool = {
2350
2380
  paginated,
2351
2381
  total,
2352
2382
  parsed.offset,
2353
- (b) => compactFields(b, ["name", "protocol-mode", "vlan-filtering", "mtu", "disabled", "comment"])
2383
+ (b) => compactFields(b, [
2384
+ "name",
2385
+ "protocol-mode",
2386
+ "vlan-filtering",
2387
+ "mtu",
2388
+ "disabled",
2389
+ "comment"
2390
+ ])
2354
2391
  ),
2355
2392
  structuredContent: {
2356
2393
  routerId: context.routerId,
@@ -2601,7 +2638,11 @@ var listWifiTool = {
2601
2638
  limit: void 0,
2602
2639
  offset: void 0
2603
2640
  });
2604
- const { items: paginated, total, hasMore } = paginate(interfaces, parsed.offset, parsed.limit);
2641
+ const {
2642
+ items: paginated,
2643
+ total,
2644
+ hasMore
2645
+ } = paginate(interfaces, parsed.offset, parsed.limit);
2605
2646
  return {
2606
2647
  content: listContent(
2607
2648
  "WiFi interfaces",
@@ -2725,16 +2766,22 @@ var manageWifiTool = {
2725
2766
  const rec = existing[0];
2726
2767
  const changes = {};
2727
2768
  const diff = [];
2728
- if (parsed.disabled !== void 0) {
2769
+ if (parsed.disabled !== void 0 && isTrue(rec.disabled) !== parsed.disabled) {
2729
2770
  const desired = parsed.disabled ? "true" : "false";
2730
- if (rec.disabled !== desired) {
2731
- changes.disabled = desired;
2732
- diff.push({ property: "disabled", before: rec.disabled ?? null, after: desired });
2733
- }
2771
+ changes.disabled = desired;
2772
+ diff.push({
2773
+ property: "disabled",
2774
+ before: rec.disabled === void 0 ? null : String(rec.disabled),
2775
+ after: desired
2776
+ });
2734
2777
  }
2735
- if (parsed.ssid !== void 0 && rec.ssid !== parsed.ssid) {
2778
+ if (parsed.ssid !== void 0 && !sameValue(rec.ssid, parsed.ssid)) {
2736
2779
  changes.ssid = parsed.ssid;
2737
- diff.push({ property: "ssid", before: rec.ssid ?? null, after: parsed.ssid });
2780
+ diff.push({
2781
+ property: "ssid",
2782
+ before: rec.ssid === void 0 ? null : String(rec.ssid),
2783
+ after: parsed.ssid
2784
+ });
2738
2785
  }
2739
2786
  if (diff.length === 0) {
2740
2787
  return {
@@ -2788,7 +2835,11 @@ var listWgTool = {
2788
2835
  limit: void 0,
2789
2836
  offset: void 0
2790
2837
  });
2791
- const { items: paginated, total, hasMore } = paginate(interfaces, parsed.offset, parsed.limit);
2838
+ const {
2839
+ items: paginated,
2840
+ total,
2841
+ hasMore
2842
+ } = paginate(interfaces, parsed.offset, parsed.limit);
2792
2843
  return {
2793
2844
  content: listContent(
2794
2845
  "WireGuard interfaces",
@@ -2848,7 +2899,13 @@ var listPeersTool = {
2848
2899
  peers,
2849
2900
  total,
2850
2901
  parsed.offset,
2851
- (p) => compactFields(p, ["interface", "public-key", "endpoint-address", "allowed-address", "disabled"])
2902
+ (p) => compactFields(p, [
2903
+ "interface",
2904
+ "public-key",
2905
+ "endpoint-address",
2906
+ "allowed-address",
2907
+ "disabled"
2908
+ ])
2852
2909
  ),
2853
2910
  structuredContent: {
2854
2911
  routerId: context.routerId,
@@ -3018,10 +3075,7 @@ var manageWgIfaceTool = {
3018
3075
  const body = { name: parsed.name, mtu: String(parsed.mtu) };
3019
3076
  if (parsed.listenPort !== void 0) body["listen-port"] = String(parsed.listenPort);
3020
3077
  if (parsed.comment) body.comment = parsed.comment;
3021
- const created = await context.routerClient.create(
3022
- "interface/wireguard",
3023
- body
3024
- );
3078
+ const created = await context.routerClient.create("interface/wireguard", body);
3025
3079
  log11.info({ name: parsed.name, id: created[".id"] }, "WireGuard interface created");
3026
3080
  return {
3027
3081
  content: `Created WireGuard interface "${parsed.name}". Public key: ${created["public-key"] ?? "unknown"}`,
@@ -3366,7 +3420,6 @@ var manageDnsSettingsTool = {
3366
3420
  try {
3367
3421
  const results2 = await context.routerClient.get("ip/dns");
3368
3422
  const current = Array.isArray(results2) && results2.length > 0 ? results2[0] : results2;
3369
- const id = current[".id"];
3370
3423
  const changes = {};
3371
3424
  const diff = [];
3372
3425
  if (parsed.servers !== void 0 && current.servers !== parsed.servers) {
@@ -3375,27 +3428,43 @@ var manageDnsSettingsTool = {
3375
3428
  }
3376
3429
  if (parsed.allowRemoteRequests !== void 0) {
3377
3430
  const next = String(parsed.allowRemoteRequests);
3378
- if (current["allow-remote-requests"] !== next) {
3431
+ if (isTrue(current["allow-remote-requests"]) !== parsed.allowRemoteRequests) {
3379
3432
  changes["allow-remote-requests"] = next;
3380
- diff.push({ property: "allow-remote-requests", before: current["allow-remote-requests"] ?? null, after: next });
3433
+ diff.push({
3434
+ property: "allow-remote-requests",
3435
+ before: current["allow-remote-requests"] === void 0 ? null : String(current["allow-remote-requests"]),
3436
+ after: next
3437
+ });
3381
3438
  }
3382
3439
  }
3383
3440
  if (parsed.maxUdpPacketSize !== void 0) {
3384
3441
  const next = String(parsed.maxUdpPacketSize);
3385
- if (current["max-udp-packet-size"] !== next) {
3442
+ if (!sameValue(current["max-udp-packet-size"], next)) {
3386
3443
  changes["max-udp-packet-size"] = next;
3387
- diff.push({ property: "max-udp-packet-size", before: current["max-udp-packet-size"] ?? null, after: next });
3444
+ diff.push({
3445
+ property: "max-udp-packet-size",
3446
+ before: current["max-udp-packet-size"] === void 0 ? null : String(current["max-udp-packet-size"]),
3447
+ after: next
3448
+ });
3388
3449
  }
3389
3450
  }
3390
3451
  if (parsed.cacheMaxTtl !== void 0 && current["cache-max-ttl"] !== parsed.cacheMaxTtl) {
3391
3452
  changes["cache-max-ttl"] = parsed.cacheMaxTtl;
3392
- diff.push({ property: "cache-max-ttl", before: current["cache-max-ttl"] ?? null, after: parsed.cacheMaxTtl });
3453
+ diff.push({
3454
+ property: "cache-max-ttl",
3455
+ before: current["cache-max-ttl"] ?? null,
3456
+ after: parsed.cacheMaxTtl
3457
+ });
3393
3458
  }
3394
3459
  if (parsed.cacheSize !== void 0) {
3395
3460
  const next = String(parsed.cacheSize);
3396
- if (current["cache-size"] !== next) {
3461
+ if (!sameValue(current["cache-size"], next)) {
3397
3462
  changes["cache-size"] = next;
3398
- diff.push({ property: "cache-size", before: current["cache-size"] ?? null, after: next });
3463
+ diff.push({
3464
+ property: "cache-size",
3465
+ before: current["cache-size"] === void 0 ? null : String(current["cache-size"]),
3466
+ after: next
3467
+ });
3399
3468
  }
3400
3469
  }
3401
3470
  if (Object.keys(changes).length === 0) {
@@ -3410,8 +3479,11 @@ var manageDnsSettingsTool = {
3410
3479
  structuredContent: { action: "dry_run", diff }
3411
3480
  };
3412
3481
  }
3413
- await context.routerClient.update("ip/dns", id, changes);
3414
- log12.info({ routerId: context.routerId, changes: Object.keys(changes) }, "DNS settings updated");
3482
+ await context.routerClient.execute("ip/dns/set", changes);
3483
+ log12.info(
3484
+ { routerId: context.routerId, changes: Object.keys(changes) },
3485
+ "DNS settings updated"
3486
+ );
3415
3487
  return {
3416
3488
  content: `Updated DNS settings on ${context.routerId}.`,
3417
3489
  structuredContent: { action: "updated", routerId: context.routerId, diff }
@@ -3421,7 +3493,12 @@ var manageDnsSettingsTool = {
3421
3493
  }
3422
3494
  }
3423
3495
  };
3424
- var dnsTools = [listDnsTool, manageDnsTool, getDnsSettingsTool, manageDnsSettingsTool];
3496
+ var dnsTools = [
3497
+ listDnsTool,
3498
+ manageDnsTool,
3499
+ getDnsSettingsTool,
3500
+ manageDnsSettingsTool
3501
+ ];
3425
3502
 
3426
3503
  // src/domain/tools/mangle-tools.ts
3427
3504
  import { z as z15 } from "zod";
@@ -3905,7 +3982,14 @@ var listRoutingRulesTool = {
3905
3982
  rules,
3906
3983
  rules.length,
3907
3984
  0,
3908
- (r) => compactFields(r, ["src-address", "dst-address", "action", "table", "interface", "disabled"])
3985
+ (r) => compactFields(r, [
3986
+ "src-address",
3987
+ "dst-address",
3988
+ "action",
3989
+ "table",
3990
+ "interface",
3991
+ "disabled"
3992
+ ])
3909
3993
  ),
3910
3994
  structuredContent: { routerId: context.routerId, rules, total: rules.length }
3911
3995
  };
@@ -5074,14 +5158,27 @@ var uploadFileTool = {
5074
5158
  await context.sftpClient.upload(`${parsed.name}.mikromcp-probe`, "");
5075
5159
  return {
5076
5160
  content: `Dry run: SFTP connectivity to ${context.routerId} verified. Would upload "${parsed.name}".`,
5077
- structuredContent: { action: "dry_run", transport: "sftp", name: parsed.name, routerId: context.routerId }
5161
+ structuredContent: {
5162
+ action: "dry_run",
5163
+ transport: "sftp",
5164
+ name: parsed.name,
5165
+ routerId: context.routerId
5166
+ }
5078
5167
  };
5079
5168
  } catch (sftpErr) {
5080
- log20.warn({ err: sftpErr, routerId: context.routerId }, "SFTP dry-run probe failed \u2014 falling back to FTP");
5169
+ log20.warn(
5170
+ { err: sftpErr, routerId: context.routerId },
5171
+ "SFTP dry-run probe failed \u2014 falling back to FTP"
5172
+ );
5081
5173
  await context.ftpClient.connect();
5082
5174
  return {
5083
5175
  content: `Dry run: FTP connectivity to ${context.routerId} verified${ftpPlaintextNote}. Would upload "${parsed.name}".`,
5084
- structuredContent: { action: "dry_run", transport: "ftp", name: parsed.name, routerId: context.routerId }
5176
+ structuredContent: {
5177
+ action: "dry_run",
5178
+ transport: "ftp",
5179
+ name: parsed.name,
5180
+ routerId: context.routerId
5181
+ }
5085
5182
  };
5086
5183
  }
5087
5184
  }
@@ -5090,15 +5187,28 @@ var uploadFileTool = {
5090
5187
  log20.info({ name: parsed.name }, "File uploaded via SFTP");
5091
5188
  return {
5092
5189
  content: `Uploaded "${parsed.name}" to ${context.routerId} via SFTP.`,
5093
- structuredContent: { action: "uploaded", transport: "sftp", name: parsed.name, routerId: context.routerId }
5190
+ structuredContent: {
5191
+ action: "uploaded",
5192
+ transport: "sftp",
5193
+ name: parsed.name,
5194
+ routerId: context.routerId
5195
+ }
5094
5196
  };
5095
5197
  } catch (sftpErr) {
5096
- log20.warn({ err: sftpErr, routerId: context.routerId }, "SFTP upload failed \u2014 falling back to FTP");
5198
+ log20.warn(
5199
+ { err: sftpErr, routerId: context.routerId },
5200
+ "SFTP upload failed \u2014 falling back to FTP"
5201
+ );
5097
5202
  await context.ftpClient.upload(parsed.name, parsed.content);
5098
5203
  log20.info({ name: parsed.name }, "File uploaded via FTP");
5099
5204
  return {
5100
5205
  content: `Uploaded "${parsed.name}" to ${context.routerId}${ftpPlaintextNote}.`,
5101
- structuredContent: { action: "uploaded", transport: "ftp", name: parsed.name, routerId: context.routerId }
5206
+ structuredContent: {
5207
+ action: "uploaded",
5208
+ transport: "ftp",
5209
+ name: parsed.name,
5210
+ routerId: context.routerId
5211
+ }
5102
5212
  };
5103
5213
  }
5104
5214
  } catch (err) {
@@ -6235,12 +6345,12 @@ var RouterOSRestClient = class {
6235
6345
  const query = buildListQuery(options);
6236
6346
  let raw;
6237
6347
  if (query.method === "POST") {
6238
- raw = await this.doRequest("POST", `${this.baseUrl}/${path}`, query.body);
6348
+ raw = await this.doRequest("POST", `${this.baseUrl}/${path}/print`, query.body);
6239
6349
  } else {
6240
6350
  const qs = query.queryParams ? "?" + new URLSearchParams(query.queryParams).toString() : "";
6241
6351
  raw = await this.doRequest("GET", `${this.baseUrl}/${path}${qs}`);
6242
6352
  }
6243
- const rawArray = Array.isArray(raw) ? raw : [raw];
6353
+ const rawArray = Array.isArray(raw) ? raw : raw === void 0 ? [] : [raw];
6244
6354
  const parsed = parseRecords(rawArray);
6245
6355
  if (options?.limit !== void 0 || options?.offset !== void 0) {
6246
6356
  const page = applyPagination(parsed, options?.limit, options?.offset);
@@ -6550,7 +6660,12 @@ var RUNTIME_FIELDS = /* @__PURE__ */ new Set([
6550
6660
  "tx-byte",
6551
6661
  "rx-packet",
6552
6662
  "tx-packet",
6553
- "uptime"
6663
+ "uptime",
6664
+ // Read-only fields on set-menu singletons. Harmless in a normal diff, but a
6665
+ // singleton is restored by writing the whole record back through /set, and
6666
+ // RouterOS rejects the request outright if it carries a read-only parameter.
6667
+ "cache-used",
6668
+ "dynamic-servers"
6554
6669
  ]);
6555
6670
  var ORDER_SENSITIVE_PATHS = /* @__PURE__ */ new Set([
6556
6671
  "ip/firewall/filter",
@@ -6586,16 +6701,16 @@ var SEMANTIC_KEYS = {
6586
6701
  "system/scheduler": ["name"],
6587
6702
  "system/script": ["name"],
6588
6703
  "system/package": ["name"],
6589
- "container": ["name"],
6590
- "certificate": ["name"],
6591
- "file": ["name"],
6704
+ container: ["name"],
6705
+ certificate: ["name"],
6706
+ file: ["name"],
6592
6707
  "interface/vrrp": ["name"],
6593
6708
  "ip/dhcp-server": ["name"],
6594
6709
  "ip/ipsec/peer": ["name"],
6595
6710
  "ip/pool": ["name"],
6596
6711
  "queue/simple": ["name"],
6597
6712
  "tool/netwatch": ["host"],
6598
- "user": ["name"]
6713
+ user: ["name"]
6599
6714
  };
6600
6715
  function normalizeValue(v) {
6601
6716
  if (v === true || v === "true" || v === "yes") return "true";
@@ -6641,6 +6756,24 @@ function computeRestorePlan(path, beforeRaw, currentRaw) {
6641
6756
  `Restored entries for ${path} are re-created at the end of the list \u2014 rule ORDER is not restored. Review ordering manually after rollback.`
6642
6757
  );
6643
6758
  }
6759
+ if (isSingleton(before, current)) {
6760
+ const beforeRecord = before[0];
6761
+ if (beforeRecord === void 0) {
6762
+ warnings.push(
6763
+ `Snapshot for ${path} holds no record for this singleton \u2014 its current settings are left untouched, as a set-menu singleton cannot be removed.`
6764
+ );
6765
+ return { path, toCreate: [], toRemove: [], toUpdate: [], warnings };
6766
+ }
6767
+ const currentRecord = current[0];
6768
+ if (currentRecord !== void 0 && recordsAreEqual(beforeRecord, currentRecord)) {
6769
+ return { path, toCreate: [], toRemove: [], toUpdate: [], warnings };
6770
+ }
6771
+ const { ".id": _id, ...data } = beforeRecord;
6772
+ const toSet = Object.fromEntries(
6773
+ Object.entries(data).map(([k, v]) => [k, String(v)])
6774
+ );
6775
+ return { path, toCreate: [], toRemove: [], toUpdate: [], toSet, warnings };
6776
+ }
6644
6777
  const keys = SEMANTIC_KEYS[path];
6645
6778
  const canUseKeys = keys !== void 0 && !hasDuplicateKeys(before, keys) && !hasDuplicateKeys(current, keys);
6646
6779
  if (!canUseKeys) {
@@ -6670,7 +6803,16 @@ function computeRestorePlan(path, beforeRaw, currentRaw) {
6670
6803
  const toRemove = current.filter((r) => !beforeByKey.has(semanticKey(r, keys))).map((r) => r[".id"]);
6671
6804
  return applyUserRestriction(path, { path, toCreate, toRemove, toUpdate, warnings });
6672
6805
  }
6806
+ function isSingleton(before, current) {
6807
+ if (before.length > 1 || current.length > 1) return false;
6808
+ if (before.length === 0 && current.length === 0) return false;
6809
+ return [...before, ...current].every((r) => r[".id"] === void 0);
6810
+ }
6673
6811
  async function applyRestorePlan(plan, client) {
6812
+ if (plan.toSet !== void 0) {
6813
+ await client.execute(`${plan.path}/set`, plan.toSet);
6814
+ return;
6815
+ }
6674
6816
  for (const id of plan.toRemove) {
6675
6817
  await client.remove(plan.path, id);
6676
6818
  }
@@ -6947,10 +7089,18 @@ function createChangeManagementTools(baseTools2) {
6947
7089
  const snapshotIds = [];
6948
7090
  for (const path of snapshotPathsFor(tool, stepParams)) {
6949
7091
  try {
6950
- const meta = await takeSnapshot(context.deviceClient, context.routerId, path, snapshotDir);
7092
+ const meta = await takeSnapshot(
7093
+ context.deviceClient,
7094
+ context.routerId,
7095
+ path,
7096
+ snapshotDir
7097
+ );
6951
7098
  snapshotIds.push(meta.id);
6952
7099
  } catch (err) {
6953
- log24.warn({ err, path, routerId: context.routerId, step: i }, "apply_plan step snapshot failed \u2014 proceeding without snapshot");
7100
+ log24.warn(
7101
+ { err, path, routerId: context.routerId, step: i },
7102
+ "apply_plan step snapshot failed \u2014 proceeding without snapshot"
7103
+ );
6954
7104
  }
6955
7105
  }
6956
7106
  const stepJournalId = recordAttempt({
@@ -6966,7 +7116,12 @@ function createChangeManagementTools(baseTools2) {
6966
7116
  try {
6967
7117
  const runStep = () => tool.handler(stepParams, context);
6968
7118
  const result = context.circuitBreaker ? await context.circuitBreaker.execute(runStep) : await runStep();
6969
- recordOutcome({ journalPath, journalId: stepJournalId, phase: "success", durationMs: Date.now() - stepStartMs });
7119
+ recordOutcome({
7120
+ journalPath,
7121
+ journalId: stepJournalId,
7122
+ phase: "success",
7123
+ durationMs: Date.now() - stepStartMs
7124
+ });
6970
7125
  results2.push({
6971
7126
  stepIndex: i,
6972
7127
  tool: step.tool,
@@ -6976,7 +7131,13 @@ function createChangeManagementTools(baseTools2) {
6976
7131
  });
6977
7132
  } catch (err) {
6978
7133
  const error = err instanceof MikroMCPError ? err : enrichError(err, { tool: step.tool });
6979
- recordOutcome({ journalPath, journalId: stepJournalId, phase: "failure", outcome: error.code, durationMs: Date.now() - stepStartMs });
7134
+ recordOutcome({
7135
+ journalPath,
7136
+ journalId: stepJournalId,
7137
+ phase: "failure",
7138
+ outcome: error.code,
7139
+ durationMs: Date.now() - stepStartMs
7140
+ });
6980
7141
  log24.error({ err: error, tool: step.tool, step: i }, "apply_plan step failed");
6981
7142
  return {
6982
7143
  content: `Apply failed at step ${i + 1}/${parsed.steps.length} (${step.tool}): ${error.message}. ${i} step(s) completed before failure.`,
@@ -7058,7 +7219,7 @@ function createChangeManagementTools(baseTools2) {
7058
7219
  restorePlans.push(plan);
7059
7220
  }
7060
7221
  const totalOps = restorePlans.reduce(
7061
- (sum, p) => sum + p.toCreate.length + p.toRemove.length + p.toUpdate.length,
7222
+ (sum, p) => sum + p.toCreate.length + p.toRemove.length + p.toUpdate.length + (p.toSet ? 1 : 0),
7062
7223
  0
7063
7224
  );
7064
7225
  const warnings = restorePlans.flatMap((p) => p.warnings);
@@ -7068,7 +7229,12 @@ ${warnings.map((w) => ` - ${w}`).join("\n")}` : "";
7068
7229
  if (parsed.dryRun) {
7069
7230
  return {
7070
7231
  content: `Rollback dry run for journal entry "${parsed.journalId}": ${totalOps} operation(s) would be applied across ${restorePlans.length} path(s).${warningSuffix}`,
7071
- structuredContent: { action: "dry_run", journalId: parsed.journalId, restorePlans, warnings }
7232
+ structuredContent: {
7233
+ action: "dry_run",
7234
+ journalId: parsed.journalId,
7235
+ restorePlans,
7236
+ warnings
7237
+ }
7072
7238
  };
7073
7239
  }
7074
7240
  for (const plan of restorePlans) {
@@ -7076,7 +7242,12 @@ ${warnings.map((w) => ` - ${w}`).join("\n")}` : "";
7076
7242
  }
7077
7243
  return {
7078
7244
  content: `Rolled back journal entry "${parsed.journalId}": ${totalOps} operation(s) applied across ${restorePlans.length} path(s).${warningSuffix}`,
7079
- structuredContent: { action: "rolled_back", journalId: parsed.journalId, restorePlans, warnings }
7245
+ structuredContent: {
7246
+ action: "rolled_back",
7247
+ journalId: parsed.journalId,
7248
+ restorePlans,
7249
+ warnings
7250
+ }
7080
7251
  };
7081
7252
  }
7082
7253
  };
@@ -7173,7 +7344,14 @@ var listIpsecPoliciesTool = {
7173
7344
  policies,
7174
7345
  allPolicies.length,
7175
7346
  0,
7176
- (p) => compactFields(p, ["src-address", "dst-address", "protocol", "action", "level", "disabled"])
7347
+ (p) => compactFields(p, [
7348
+ "src-address",
7349
+ "dst-address",
7350
+ "protocol",
7351
+ "action",
7352
+ "level",
7353
+ "disabled"
7354
+ ])
7177
7355
  ),
7178
7356
  structuredContent: {
7179
7357
  routerId: context.routerId,
@@ -7221,9 +7399,7 @@ var manageIpsecPeerTool = {
7221
7399
  limit: void 0,
7222
7400
  offset: void 0
7223
7401
  });
7224
- const existing = allPeers.find(
7225
- (p) => p.name === parsed.name
7226
- );
7402
+ const existing = allPeers.find((p) => p.name === parsed.name);
7227
7403
  if (parsed.action === "add") {
7228
7404
  if (existing) {
7229
7405
  if (existing.address === parsed.address) {
@@ -7382,9 +7558,8 @@ var manageIpsecPolicyTool = {
7382
7558
  limit: void 0,
7383
7559
  offset: void 0
7384
7560
  });
7385
- const tunnelStr = String(parsed.tunnel);
7386
7561
  const existing = all.find(
7387
- (p) => p["src-address"] === parsed.srcAddress && p["dst-address"] === parsed.dstAddress && p.tunnel === tunnelStr
7562
+ (p) => p["src-address"] === parsed.srcAddress && p["dst-address"] === parsed.dstAddress && isTrue(p.tunnel) === parsed.tunnel
7388
7563
  );
7389
7564
  if (parsed.action === "add") {
7390
7565
  if (existing) {
@@ -7420,7 +7595,7 @@ var manageIpsecPolicyTool = {
7420
7595
  const body = {
7421
7596
  "src-address": parsed.srcAddress,
7422
7597
  "dst-address": parsed.dstAddress,
7423
- tunnel: tunnelStr,
7598
+ tunnel: String(parsed.tunnel),
7424
7599
  action: parsed.ipsecAction,
7425
7600
  level: parsed.level
7426
7601
  };
@@ -7623,7 +7798,7 @@ var manageCertificateTool = {
7623
7798
  };
7624
7799
  }
7625
7800
  if (parsed.action === "trust") {
7626
- if (cert.trusted === "yes") {
7801
+ if (isTrue(cert.trusted)) {
7627
7802
  return {
7628
7803
  content: `Certificate "${parsed.name}" is already trusted. No changes made.`,
7629
7804
  structuredContent: { action: "already_trusted", name: parsed.name }
@@ -7646,7 +7821,7 @@ var manageCertificateTool = {
7646
7821
  structuredContent: { action: "trusted", name: parsed.name }
7647
7822
  };
7648
7823
  }
7649
- if (cert.trusted === "no") {
7824
+ if (cert.trusted !== void 0 && !isTrue(cert.trusted)) {
7650
7825
  return {
7651
7826
  content: `Certificate "${parsed.name}" is already untrusted. No changes made.`,
7652
7827
  structuredContent: { action: "already_untrusted", name: parsed.name }
@@ -7792,14 +7967,16 @@ var manageUserTool = {
7792
7967
  limit: void 0,
7793
7968
  offset: void 0
7794
7969
  });
7795
- const existing2 = allUsers2.find(
7796
- (u) => u.name === parsed.name
7797
- );
7970
+ const existing2 = allUsers2.find((u) => u.name === parsed.name);
7798
7971
  if (existing2) {
7799
7972
  if (existing2.group === parsed.group) {
7800
7973
  return {
7801
7974
  content: `User "${parsed.name}" already exists in group "${parsed.group}". No changes made.`,
7802
- structuredContent: { action: "already_exists", name: parsed.name, group: existing2.group }
7975
+ structuredContent: {
7976
+ action: "already_exists",
7977
+ name: parsed.name,
7978
+ group: existing2.group
7979
+ }
7803
7980
  };
7804
7981
  }
7805
7982
  throw new MikroMCPError({
@@ -7839,7 +8016,12 @@ var manageUserTool = {
7839
8016
  log27.info({ name: parsed.name, id: created[".id"] }, "User added");
7840
8017
  return {
7841
8018
  content: `Added user "${parsed.name}" in group "${parsed.group}".`,
7842
- structuredContent: { action: "created", routerId: context.routerId, name: parsed.name, group: parsed.group }
8019
+ structuredContent: {
8020
+ action: "created",
8021
+ routerId: context.routerId,
8022
+ name: parsed.name,
8023
+ group: parsed.group
8024
+ }
7843
8025
  };
7844
8026
  }
7845
8027
  if (parsed.action === "set-password") {
@@ -7859,9 +8041,7 @@ var manageUserTool = {
7859
8041
  limit: void 0,
7860
8042
  offset: void 0
7861
8043
  });
7862
- const existing2 = allUsers2.find(
7863
- (u) => u.name === parsed.name
7864
- );
8044
+ const existing2 = allUsers2.find((u) => u.name === parsed.name);
7865
8045
  if (!existing2) {
7866
8046
  throw new MikroMCPError({
7867
8047
  category: "NOT_FOUND" /* NOT_FOUND */,
@@ -7890,16 +8070,18 @@ var manageUserTool = {
7890
8070
  log27.info({ name: parsed.name }, "User password updated");
7891
8071
  return {
7892
8072
  content: `Password updated for user "${parsed.name}".`,
7893
- structuredContent: { action: "password_set", routerId: context.routerId, name: parsed.name }
8073
+ structuredContent: {
8074
+ action: "password_set",
8075
+ routerId: context.routerId,
8076
+ name: parsed.name
8077
+ }
7894
8078
  };
7895
8079
  }
7896
8080
  const allUsers = await context.routerClient.get("user", {
7897
8081
  limit: void 0,
7898
8082
  offset: void 0
7899
8083
  });
7900
- const existing = allUsers.find(
7901
- (u) => u.name === parsed.name
7902
- );
8084
+ const existing = allUsers.find((u) => u.name === parsed.name);
7903
8085
  if (parsed.action === "remove") {
7904
8086
  if (!existing) {
7905
8087
  return {
@@ -8053,7 +8235,11 @@ var manageUserGroupTool = {
8053
8235
  if (parsed.policy === void 0 || existing2.policy === parsed.policy) {
8054
8236
  return {
8055
8237
  content: `User group "${parsed.name}" already exists. No changes made.`,
8056
- structuredContent: { action: "already_exists", name: parsed.name, policy: existing2.policy }
8238
+ structuredContent: {
8239
+ action: "already_exists",
8240
+ name: parsed.name,
8241
+ policy: existing2.policy
8242
+ }
8057
8243
  };
8058
8244
  }
8059
8245
  throw new MikroMCPError({
@@ -8270,9 +8456,7 @@ var manageDhcpServerTool = {
8270
8456
  limit: void 0,
8271
8457
  offset: void 0
8272
8458
  });
8273
- const existing = allServers.find(
8274
- (s) => s.name === parsed.name
8275
- );
8459
+ const existing = allServers.find((s) => s.name === parsed.name);
8276
8460
  if (parsed.action === "add") {
8277
8461
  if (!parsed.interface) {
8278
8462
  throw new MikroMCPError({
@@ -8499,7 +8683,10 @@ var manageIpPoolTool = {
8499
8683
  snapshotPaths: ["ip/pool"],
8500
8684
  async handler(params, context) {
8501
8685
  const parsed = manageIpPoolInputSchema.parse(params);
8502
- log30.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing IP pool");
8686
+ log30.info(
8687
+ { routerId: context.routerId, action: parsed.action, name: parsed.name },
8688
+ "Managing IP pool"
8689
+ );
8503
8690
  try {
8504
8691
  const allPools = await context.routerClient.get("ip/pool", {
8505
8692
  limit: void 0,
@@ -8567,7 +8754,10 @@ var manageIpPoolTool = {
8567
8754
  if (parsed.dryRun) {
8568
8755
  return {
8569
8756
  content: `Dry run: Would remove IP pool "${parsed.name}".`,
8570
- structuredContent: { action: "dry_run", diff: [{ property: "name", before: parsed.name, after: null }] }
8757
+ structuredContent: {
8758
+ action: "dry_run",
8759
+ diff: [{ property: "name", before: parsed.name, after: null }]
8760
+ }
8571
8761
  };
8572
8762
  }
8573
8763
  await context.routerClient.remove("ip/pool", existing[".id"]);
@@ -8911,7 +9101,7 @@ var manageVrrpInstanceTool = {
8911
9101
  });
8912
9102
  }
8913
9103
  if (existing) {
8914
- if (existing.interface === parsed.interface && existing.vrid === String(parsed.vrid)) {
9104
+ if (existing.interface === parsed.interface && sameValue(existing.vrid, parsed.vrid)) {
8915
9105
  return {
8916
9106
  content: `VRRP instance "${parsed.name}" already exists with the same interface and VRID. No changes made.`,
8917
9107
  structuredContent: { action: "already_exists", instance: existing }
@@ -9360,7 +9550,14 @@ var listNeighborsTool = {
9360
9550
  neighbors,
9361
9551
  allNeighbors.length,
9362
9552
  0,
9363
- (n) => compactFields(n, ["interface", "address", "mac-address", "identity", "platform", "board"])
9553
+ (n) => compactFields(n, [
9554
+ "interface",
9555
+ "address",
9556
+ "mac-address",
9557
+ "identity",
9558
+ "platform",
9559
+ "board"
9560
+ ])
9364
9561
  ),
9365
9562
  structuredContent: {
9366
9563
  routerId: context.routerId,
@@ -9411,7 +9608,14 @@ var listArpEntriesTool = {
9411
9608
  entries,
9412
9609
  allEntries.length,
9413
9610
  0,
9414
- (e) => compactFields(e, ["address", "mac-address", "interface", "status", "complete", "dynamic"])
9611
+ (e) => compactFields(e, [
9612
+ "address",
9613
+ "mac-address",
9614
+ "interface",
9615
+ "status",
9616
+ "complete",
9617
+ "dynamic"
9618
+ ])
9415
9619
  ),
9416
9620
  structuredContent: {
9417
9621
  routerId: context.routerId,
@@ -9451,12 +9655,15 @@ var manageNtpClientTool = {
9451
9655
  try {
9452
9656
  const results2 = await context.routerClient.get("system/ntp/client");
9453
9657
  const current = Array.isArray(results2) && results2.length > 0 ? results2[0] : results2;
9454
- const id = current[".id"];
9455
9658
  const changes = {};
9456
9659
  const diff = [];
9457
- if (parsed.enabled !== void 0 && current.enabled !== String(parsed.enabled)) {
9660
+ if (parsed.enabled !== void 0 && isTrue(current.enabled) !== parsed.enabled) {
9458
9661
  changes.enabled = String(parsed.enabled);
9459
- diff.push({ property: "enabled", before: current.enabled, after: String(parsed.enabled) });
9662
+ diff.push({
9663
+ property: "enabled",
9664
+ before: current.enabled === void 0 ? void 0 : String(current.enabled),
9665
+ after: String(parsed.enabled)
9666
+ });
9460
9667
  }
9461
9668
  if (parsed.mode !== void 0 && current.mode !== parsed.mode) {
9462
9669
  changes.mode = parsed.mode;
@@ -9486,7 +9693,7 @@ var manageNtpClientTool = {
9486
9693
  structuredContent: { action: "dry_run", diff }
9487
9694
  };
9488
9695
  }
9489
- await context.routerClient.update("system/ntp/client", id, changes);
9696
+ await context.routerClient.execute("system/ntp/client/set", changes);
9490
9697
  log33.info({ routerId: context.routerId }, "NTP client updated");
9491
9698
  return {
9492
9699
  content: `NTP client on ${context.routerId} updated.`,
@@ -9517,7 +9724,9 @@ function sanitizeComment4(comment) {
9517
9724
  var manageVlanInputSchema = z34.object({
9518
9725
  routerId,
9519
9726
  action: z34.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
9520
- name: z34.string().regex(/^[a-zA-Z0-9_-]+$/).max(15).describe("VLAN interface name \u2014 idempotency key (alphanumeric, hyphens, underscores, max 15 chars)"),
9727
+ name: z34.string().regex(/^[a-zA-Z0-9_-]+$/).max(15).describe(
9728
+ "VLAN interface name \u2014 idempotency key (alphanumeric, hyphens, underscores, max 15 chars)"
9729
+ ),
9521
9730
  vlanId: z34.number().int().min(1).max(4094).optional().describe("VLAN ID (1-4094; required for add)"),
9522
9731
  parentInterface: z34.string().optional().describe("Parent interface name (e.g., ether1, bridge1; required for add)"),
9523
9732
  mtu: z34.number().int().min(68).max(9e3).default(1500).describe("MTU size (add only)"),
@@ -9540,13 +9749,18 @@ var manageVlanTool = {
9540
9749
  async handler(params, context) {
9541
9750
  const parsed = manageVlanInputSchema.parse(params);
9542
9751
  const comment = sanitizeComment4(parsed.comment);
9543
- log34.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing VLAN");
9752
+ log34.info(
9753
+ { routerId: context.routerId, action: parsed.action, name: parsed.name },
9754
+ "Managing VLAN"
9755
+ );
9544
9756
  try {
9545
9757
  const allVlans = await context.routerClient.get("interface/vlan", {
9546
9758
  limit: void 0,
9547
9759
  offset: void 0
9548
9760
  });
9549
- const existing = allVlans.find((v) => v.name === parsed.name);
9761
+ const existing = allVlans.find(
9762
+ (v) => sameValue(v.name, parsed.name)
9763
+ );
9550
9764
  if (parsed.action === "add") {
9551
9765
  if (parsed.vlanId === void 0) {
9552
9766
  throw new MikroMCPError({
@@ -9563,11 +9777,14 @@ var manageVlanTool = {
9563
9777
  code: "VLAN_PARENT_REQUIRED",
9564
9778
  message: "parentInterface is required when adding a VLAN.",
9565
9779
  details: { name: parsed.name },
9566
- recoverability: { retryable: false, suggestedAction: "Provide the parent interface name." }
9780
+ recoverability: {
9781
+ retryable: false,
9782
+ suggestedAction: "Provide the parent interface name."
9783
+ }
9567
9784
  });
9568
9785
  }
9569
9786
  if (existing) {
9570
- if (existing["vlan-id"] === String(parsed.vlanId) && existing.interface === parsed.parentInterface) {
9787
+ if (sameValue(existing["vlan-id"], parsed.vlanId) && existing.interface === parsed.parentInterface) {
9571
9788
  log34.info({ name: parsed.name }, "VLAN already exists with matching config");
9572
9789
  return {
9573
9790
  content: `VLAN "${parsed.name}" already exists with VLAN ID ${parsed.vlanId} on ${parsed.parentInterface}. No changes made.`,
@@ -9628,7 +9845,10 @@ var manageVlanTool = {
9628
9845
  if (parsed.dryRun) {
9629
9846
  return {
9630
9847
  content: `Dry run: Would remove VLAN "${parsed.name}".`,
9631
- structuredContent: { action: "dry_run", diff: [{ property: "name", before: parsed.name, after: null }] }
9848
+ structuredContent: {
9849
+ action: "dry_run",
9850
+ diff: [{ property: "name", before: parsed.name, after: null }]
9851
+ }
9632
9852
  };
9633
9853
  }
9634
9854
  await context.routerClient.remove("interface/vlan", existing[".id"]);
@@ -9656,12 +9876,20 @@ var manageVlanTool = {
9656
9876
  content: `Dry run: Would ${parsed.action} VLAN "${parsed.name}".`,
9657
9877
  structuredContent: {
9658
9878
  action: "dry_run",
9659
- diff: [{ property: "disabled", before: existing.disabled, after: parsed.action === "disable" ? "true" : "false" }]
9879
+ diff: [
9880
+ {
9881
+ property: "disabled",
9882
+ before: existing.disabled,
9883
+ after: parsed.action === "disable" ? "true" : "false"
9884
+ }
9885
+ ]
9660
9886
  }
9661
9887
  };
9662
9888
  }
9663
9889
  const disabledValue = parsed.action === "disable" ? "true" : "false";
9664
- await context.routerClient.update("interface/vlan", existing[".id"], { disabled: disabledValue });
9890
+ await context.routerClient.update("interface/vlan", existing[".id"], {
9891
+ disabled: disabledValue
9892
+ });
9665
9893
  const resultAction = parsed.action === "disable" ? "disabled" : "enabled";
9666
9894
  log34.info({ name: parsed.name, action: resultAction }, "VLAN updated");
9667
9895
  return {
@@ -9759,7 +9987,10 @@ var manageDhcpClientTool = {
9759
9987
  snapshotPaths: ["ip/dhcp-client"],
9760
9988
  async handler(params, context) {
9761
9989
  const parsed = manageDhcpClientInputSchema.parse(params);
9762
- log35.info({ routerId: context.routerId, action: parsed.action, interface: parsed.interface }, "Managing DHCP client");
9990
+ log35.info(
9991
+ { routerId: context.routerId, action: parsed.action, interface: parsed.interface },
9992
+ "Managing DHCP client"
9993
+ );
9763
9994
  try {
9764
9995
  const allClients = await context.routerClient.get("ip/dhcp-client", {
9765
9996
  limit: void 0,
@@ -9780,7 +10011,11 @@ var manageDhcpClientTool = {
9780
10011
  { property: "interface", before: null, after: parsed.interface },
9781
10012
  { property: "use-peer-dns", before: null, after: parsed.usePeerDns ? "yes" : "no" },
9782
10013
  { property: "use-peer-ntp", before: null, after: parsed.usePeerNtp ? "yes" : "no" },
9783
- { property: "add-default-route", before: null, after: parsed.addDefaultRoute ? "yes" : "no" }
10014
+ {
10015
+ property: "add-default-route",
10016
+ before: null,
10017
+ after: parsed.addDefaultRoute ? "yes" : "no"
10018
+ }
9784
10019
  ];
9785
10020
  return {
9786
10021
  content: `Dry run: Would add DHCP client on "${parsed.interface}".`,
@@ -9821,7 +10056,11 @@ var manageDhcpClientTool = {
9821
10056
  log35.info({ interface: parsed.interface }, "DHCP client removed");
9822
10057
  return {
9823
10058
  content: `Removed DHCP client on "${parsed.interface}".`,
9824
- structuredContent: { action: "removed", interface: parsed.interface, id: existing[".id"] }
10059
+ structuredContent: {
10060
+ action: "removed",
10061
+ interface: parsed.interface,
10062
+ id: existing[".id"]
10063
+ }
9825
10064
  };
9826
10065
  }
9827
10066
  if (!existing) {
@@ -9853,12 +10092,18 @@ var manageDhcpClientTool = {
9853
10092
  };
9854
10093
  }
9855
10094
  const disabledValue = parsed.action === "disable" ? "true" : "false";
9856
- await context.routerClient.update("ip/dhcp-client", existing[".id"], { disabled: disabledValue });
10095
+ await context.routerClient.update("ip/dhcp-client", existing[".id"], {
10096
+ disabled: disabledValue
10097
+ });
9857
10098
  const resultAction = parsed.action === "disable" ? "disabled" : "enabled";
9858
10099
  log35.info({ interface: parsed.interface, action: resultAction }, "DHCP client updated");
9859
10100
  return {
9860
10101
  content: `DHCP client on "${parsed.interface}" ${resultAction}.`,
9861
- structuredContent: { action: resultAction, interface: parsed.interface, id: existing[".id"] }
10102
+ structuredContent: {
10103
+ action: resultAction,
10104
+ interface: parsed.interface,
10105
+ id: existing[".id"]
10106
+ }
9862
10107
  };
9863
10108
  } catch (err) {
9864
10109
  throw toolError(err, context, "manage_dhcp_client");
@@ -9870,7 +10115,16 @@ var dhcpClientTools = [listDhcpClientsTool, manageDhcpClientTool];
9870
10115
  // src/domain/tools/ip-service-tools.ts
9871
10116
  import { z as z36 } from "zod";
9872
10117
  var log36 = createLogger("ip-service-tools");
9873
- var SERVICE_NAMES = ["api", "api-ssl", "ssh", "telnet", "www", "www-ssl", "winbox", "ftp"];
10118
+ var SERVICE_NAMES = [
10119
+ "api",
10120
+ "api-ssl",
10121
+ "ssh",
10122
+ "telnet",
10123
+ "www",
10124
+ "www-ssl",
10125
+ "winbox",
10126
+ "ftp"
10127
+ ];
9874
10128
  var listIpServicesInputSchema = z36.object({
9875
10129
  routerId,
9876
10130
  name: z36.enum(SERVICE_NAMES).optional().describe("Filter by service name (api, api-ssl, ssh, telnet, www, www-ssl, winbox, ftp)"),
@@ -9946,13 +10200,18 @@ var manageIpServiceTool = {
9946
10200
  },
9947
10201
  async handler(params, context) {
9948
10202
  const parsed = manageIpServiceInputSchema.parse(params);
9949
- log36.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing IP service");
10203
+ log36.info(
10204
+ { routerId: context.routerId, action: parsed.action, name: parsed.name },
10205
+ "Managing IP service"
10206
+ );
9950
10207
  try {
9951
10208
  const allServices = await context.routerClient.get("ip/service", {
9952
10209
  limit: void 0,
9953
10210
  offset: void 0
9954
10211
  });
9955
- const existing = allServices.find((s) => s.name === parsed.name);
10212
+ const existing = allServices.find(
10213
+ (s) => s.name === parsed.name
10214
+ );
9956
10215
  if (!existing) {
9957
10216
  throw new MikroMCPError({
9958
10217
  category: "NOT_FOUND" /* NOT_FOUND */,
@@ -10097,13 +10356,18 @@ var managePppoeClientTool = {
10097
10356
  snapshotPaths: [PPPOE_PATH],
10098
10357
  async handler(params, context) {
10099
10358
  const parsed = managePppoeClientInputSchema.parse(params);
10100
- log37.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing PPPoE client");
10359
+ log37.info(
10360
+ { routerId: context.routerId, action: parsed.action, name: parsed.name },
10361
+ "Managing PPPoE client"
10362
+ );
10101
10363
  try {
10102
10364
  const allClients = await context.routerClient.get(PPPOE_PATH, {
10103
10365
  limit: void 0,
10104
10366
  offset: void 0
10105
10367
  });
10106
- const existing = allClients.find((c) => c.name === parsed.name);
10368
+ const existing = allClients.find(
10369
+ (c) => sameValue(c.name, parsed.name)
10370
+ );
10107
10371
  if (parsed.action === "add") {
10108
10372
  if (!parsed.interface) {
10109
10373
  throw new MikroMCPError({
@@ -10163,10 +10427,18 @@ var managePppoeClientTool = {
10163
10427
  diff.push({ property: "service-name", before: null, after: parsed.serviceName });
10164
10428
  }
10165
10429
  if (parsed.addDefaultRoute !== void 0) {
10166
- diff.push({ property: "add-default-route", before: null, after: parsed.addDefaultRoute ? "yes" : "no" });
10430
+ diff.push({
10431
+ property: "add-default-route",
10432
+ before: null,
10433
+ after: parsed.addDefaultRoute ? "yes" : "no"
10434
+ });
10167
10435
  }
10168
10436
  if (parsed.dialOnDemand !== void 0) {
10169
- diff.push({ property: "dial-on-demand", before: null, after: parsed.dialOnDemand ? "yes" : "no" });
10437
+ diff.push({
10438
+ property: "dial-on-demand",
10439
+ before: null,
10440
+ after: parsed.dialOnDemand ? "yes" : "no"
10441
+ });
10170
10442
  }
10171
10443
  if (parsed.dryRun) {
10172
10444
  return {
@@ -10181,8 +10453,10 @@ var managePppoeClientTool = {
10181
10453
  };
10182
10454
  if (parsed.password !== void 0) body.password = parsed.password;
10183
10455
  if (parsed.serviceName !== void 0) body["service-name"] = parsed.serviceName;
10184
- if (parsed.addDefaultRoute !== void 0) body["add-default-route"] = parsed.addDefaultRoute ? "yes" : "no";
10185
- if (parsed.dialOnDemand !== void 0) body["dial-on-demand"] = parsed.dialOnDemand ? "yes" : "no";
10456
+ if (parsed.addDefaultRoute !== void 0)
10457
+ body["add-default-route"] = parsed.addDefaultRoute ? "yes" : "no";
10458
+ if (parsed.dialOnDemand !== void 0)
10459
+ body["dial-on-demand"] = parsed.dialOnDemand ? "yes" : "no";
10186
10460
  const created = await context.routerClient.create(PPPOE_PATH, body);
10187
10461
  log37.info({ name: parsed.name, id: created[".id"] }, "PPPoE client created");
10188
10462
  return {
@@ -10216,21 +10490,29 @@ var managePppoeClientTool = {
10216
10490
  }
10217
10491
  if (parsed.serviceName !== void 0 && existing["service-name"] !== parsed.serviceName) {
10218
10492
  updates["service-name"] = parsed.serviceName;
10219
- diff.push({ property: "service-name", before: existing["service-name"] ?? null, after: parsed.serviceName });
10493
+ diff.push({
10494
+ property: "service-name",
10495
+ before: existing["service-name"] ?? null,
10496
+ after: parsed.serviceName
10497
+ });
10220
10498
  }
10221
- if (parsed.addDefaultRoute !== void 0) {
10499
+ if (parsed.addDefaultRoute !== void 0 && isTrue(existing["add-default-route"]) !== parsed.addDefaultRoute) {
10222
10500
  const desired = parsed.addDefaultRoute ? "yes" : "no";
10223
- if (existing["add-default-route"] !== desired) {
10224
- updates["add-default-route"] = desired;
10225
- diff.push({ property: "add-default-route", before: existing["add-default-route"] ?? null, after: desired });
10226
- }
10501
+ updates["add-default-route"] = desired;
10502
+ diff.push({
10503
+ property: "add-default-route",
10504
+ before: existing["add-default-route"] === void 0 ? null : String(existing["add-default-route"]),
10505
+ after: desired
10506
+ });
10227
10507
  }
10228
- if (parsed.dialOnDemand !== void 0) {
10508
+ if (parsed.dialOnDemand !== void 0 && isTrue(existing["dial-on-demand"]) !== parsed.dialOnDemand) {
10229
10509
  const desired = parsed.dialOnDemand ? "yes" : "no";
10230
- if (existing["dial-on-demand"] !== desired) {
10231
- updates["dial-on-demand"] = desired;
10232
- diff.push({ property: "dial-on-demand", before: existing["dial-on-demand"] ?? null, after: desired });
10233
- }
10510
+ updates["dial-on-demand"] = desired;
10511
+ diff.push({
10512
+ property: "dial-on-demand",
10513
+ before: existing["dial-on-demand"] === void 0 ? null : String(existing["dial-on-demand"]),
10514
+ after: desired
10515
+ });
10234
10516
  }
10235
10517
  if (parsed.password !== void 0) {
10236
10518
  updates.password = parsed.password;
@@ -10363,13 +10645,18 @@ var manageOvpnClientTool = {
10363
10645
  snapshotPaths: [OVPN_CLIENT_PATH],
10364
10646
  async handler(params, context) {
10365
10647
  const parsed = manageOvpnClientInputSchema.parse(params);
10366
- log38.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing OpenVPN client");
10648
+ log38.info(
10649
+ { routerId: context.routerId, action: parsed.action, name: parsed.name },
10650
+ "Managing OpenVPN client"
10651
+ );
10367
10652
  try {
10368
10653
  const allClients = await context.routerClient.get(OVPN_CLIENT_PATH, {
10369
10654
  limit: void 0,
10370
10655
  offset: void 0
10371
10656
  });
10372
- const existing = allClients.find((c) => c.name === parsed.name);
10657
+ const existing = allClients.find(
10658
+ (c) => sameValue(c.name, parsed.name)
10659
+ );
10373
10660
  if (parsed.action === "add") {
10374
10661
  if (!parsed.connectTo) {
10375
10662
  throw new MikroMCPError({
@@ -10452,11 +10739,19 @@ var manageOvpnClientTool = {
10452
10739
  const diff = [];
10453
10740
  if (parsed.connectTo !== void 0 && existing["connect-to"] !== parsed.connectTo) {
10454
10741
  updates["connect-to"] = parsed.connectTo;
10455
- diff.push({ property: "connect-to", before: existing["connect-to"] ?? null, after: parsed.connectTo });
10742
+ diff.push({
10743
+ property: "connect-to",
10744
+ before: existing["connect-to"] ?? null,
10745
+ after: parsed.connectTo
10746
+ });
10456
10747
  }
10457
- if (parsed.port !== void 0 && existing.port !== String(parsed.port)) {
10748
+ if (parsed.port !== void 0 && !sameValue(existing.port, parsed.port)) {
10458
10749
  updates.port = String(parsed.port);
10459
- diff.push({ property: "port", before: existing.port ?? null, after: String(parsed.port) });
10750
+ diff.push({
10751
+ property: "port",
10752
+ before: existing.port === void 0 ? null : String(existing.port),
10753
+ after: String(parsed.port)
10754
+ });
10460
10755
  }
10461
10756
  if (parsed.mode !== void 0 && existing.mode !== parsed.mode) {
10462
10757
  updates.mode = parsed.mode;
@@ -10464,11 +10759,19 @@ var manageOvpnClientTool = {
10464
10759
  }
10465
10760
  if (parsed.protocol !== void 0 && existing.protocol !== parsed.protocol) {
10466
10761
  updates.protocol = parsed.protocol;
10467
- diff.push({ property: "protocol", before: existing.protocol ?? null, after: parsed.protocol });
10762
+ diff.push({
10763
+ property: "protocol",
10764
+ before: existing.protocol ?? null,
10765
+ after: parsed.protocol
10766
+ });
10468
10767
  }
10469
10768
  if (parsed.certificate !== void 0 && existing.certificate !== parsed.certificate) {
10470
10769
  updates.certificate = parsed.certificate;
10471
- diff.push({ property: "certificate", before: existing.certificate ?? null, after: parsed.certificate });
10770
+ diff.push({
10771
+ property: "certificate",
10772
+ before: existing.certificate ?? null,
10773
+ after: parsed.certificate
10774
+ });
10472
10775
  }
10473
10776
  if (parsed.user !== void 0 && existing.user !== parsed.user) {
10474
10777
  updates.user = parsed.user;
@@ -10540,8 +10843,33 @@ function fetchOvpnServer(allRecords, routerId2) {
10540
10843
  }
10541
10844
  });
10542
10845
  }
10846
+ if (allRecords.length > 1) {
10847
+ const names = allRecords.map((r) => r.name ?? "?").join(", ");
10848
+ throw new MikroMCPError({
10849
+ category: "CONFLICT" /* CONFLICT */,
10850
+ code: "OVPN_SERVER_AMBIGUOUS",
10851
+ message: `Multiple OpenVPN server instances exist (${names}); this tool cannot pick one safely.`,
10852
+ details: { routerId: routerId2, instances: names },
10853
+ recoverability: {
10854
+ retryable: false,
10855
+ suggestedAction: "Remove the extra instances so exactly one remains.",
10856
+ alternativeTools: []
10857
+ }
10858
+ });
10859
+ }
10543
10860
  return allRecords[0];
10544
10861
  }
10862
+ async function writeOvpnServer(context, server, changes) {
10863
+ const serverId = server[".id"];
10864
+ if (serverId !== void 0) {
10865
+ await context.routerClient.update(OVPN_SERVER_PATH, serverId, changes);
10866
+ } else {
10867
+ await context.routerClient.execute(`${OVPN_SERVER_PATH}/set`, changes);
10868
+ }
10869
+ }
10870
+ function ovpnServerEnabled(server) {
10871
+ return server.enabled !== void 0 ? isTrue(server.enabled) : !isTrue(server.disabled);
10872
+ }
10545
10873
  var getOvpnServerTool = {
10546
10874
  name: "get_ovpn_server",
10547
10875
  title: "Get OpenVPN Server",
@@ -10563,7 +10891,7 @@ var getOvpnServerTool = {
10563
10891
  });
10564
10892
  const server = fetchOvpnServer(records, context.routerId);
10565
10893
  return {
10566
- content: `OpenVPN server on ${context.routerId}: enabled=${server.enabled}, port=${server.port}, protocol=${server.protocol}`,
10894
+ content: `OpenVPN server on ${context.routerId}: enabled=${ovpnServerEnabled(server)}, port=${server.port}, protocol=${server.protocol}`,
10567
10895
  structuredContent: { routerId: context.routerId, server }
10568
10896
  };
10569
10897
  } catch (err) {
@@ -10578,7 +10906,15 @@ var manageOvpnServerInputSchema = z38.object({
10578
10906
  mode: z38.enum(["ip", "ethernet"]).optional().describe("Tunnel mode (set action only)"),
10579
10907
  protocol: z38.enum(["tcp-server", "udp"]).optional().describe("Transport protocol (set action only)"),
10580
10908
  certificate: z38.string().optional().describe("Server certificate name (set action only)"),
10581
- cipher: z38.enum(["blowfish128", "aes128-cbc", "aes192-cbc", "aes256-cbc", "aes128-gcm", "aes256-gcm", "none"]).optional().describe("Encryption cipher (set action only)"),
10909
+ cipher: z38.enum([
10910
+ "blowfish128",
10911
+ "aes128-cbc",
10912
+ "aes192-cbc",
10913
+ "aes256-cbc",
10914
+ "aes128-gcm",
10915
+ "aes256-gcm",
10916
+ "none"
10917
+ ]).optional().describe("Encryption cipher (set action only)"),
10582
10918
  auth: z38.enum(["md5", "sha1", "sha256", "sha512", "null"]).optional().describe("Authentication algorithm (set action only)"),
10583
10919
  dryRun
10584
10920
  }).strict();
@@ -10603,32 +10939,42 @@ var manageOvpnServerTool = {
10603
10939
  offset: void 0
10604
10940
  });
10605
10941
  const server = fetchOvpnServer(records, context.routerId);
10606
- const serverId = server[".id"];
10607
10942
  if (parsed.action === "enable" || parsed.action === "disable") {
10608
10943
  const desiredEnabled = parsed.action === "enable";
10609
- const currentEnabled = server.enabled === "yes";
10944
+ const legacyDialect = server.enabled !== void 0;
10945
+ const currentEnabled = ovpnServerEnabled(server);
10610
10946
  if (desiredEnabled === currentEnabled) {
10611
10947
  return {
10612
10948
  content: `OpenVPN server on ${context.routerId} is already ${parsed.action}d. No changes made.`,
10613
10949
  structuredContent: { action: "no_change", server }
10614
10950
  };
10615
10951
  }
10952
+ const change = legacyDialect ? {
10953
+ property: "enabled",
10954
+ before: String(server.enabled),
10955
+ after: desiredEnabled ? "yes" : "no"
10956
+ } : {
10957
+ property: "disabled",
10958
+ before: server.disabled === void 0 ? null : String(server.disabled),
10959
+ after: desiredEnabled ? "no" : "yes"
10960
+ };
10616
10961
  if (parsed.dryRun) {
10617
10962
  return {
10618
10963
  content: `Dry run: Would ${parsed.action} OpenVPN server on ${context.routerId}.`,
10619
10964
  structuredContent: {
10620
10965
  action: "dry_run",
10621
- diff: [{ property: "enabled", before: server.enabled, after: desiredEnabled ? "yes" : "no" }]
10966
+ diff: [change]
10622
10967
  }
10623
10968
  };
10624
10969
  }
10625
- await context.routerClient.update(OVPN_SERVER_PATH, serverId, {
10626
- enabled: desiredEnabled ? "yes" : "no"
10627
- });
10970
+ await writeOvpnServer(context, server, { [change.property]: change.after });
10628
10971
  log38.info({ routerId: context.routerId, action: parsed.action }, "OpenVPN server updated");
10629
10972
  return {
10630
10973
  content: `OpenVPN server on ${context.routerId} ${parsed.action}d.`,
10631
- structuredContent: { action: parsed.action === "enable" ? "enabled" : "disabled", server }
10974
+ structuredContent: {
10975
+ action: parsed.action === "enable" ? "enabled" : "disabled",
10976
+ server
10977
+ }
10632
10978
  };
10633
10979
  }
10634
10980
  const hasFields = parsed.port !== void 0 || parsed.mode !== void 0 || parsed.protocol !== void 0 || parsed.certificate !== void 0 || parsed.cipher !== void 0 || parsed.auth !== void 0;
@@ -10647,9 +10993,13 @@ var manageOvpnServerTool = {
10647
10993
  }
10648
10994
  const updates = {};
10649
10995
  const diff = [];
10650
- if (parsed.port !== void 0 && server.port !== String(parsed.port)) {
10996
+ if (parsed.port !== void 0 && !sameValue(server.port, parsed.port)) {
10651
10997
  updates.port = String(parsed.port);
10652
- diff.push({ property: "port", before: server.port ?? null, after: String(parsed.port) });
10998
+ diff.push({
10999
+ property: "port",
11000
+ before: server.port === void 0 ? null : String(server.port),
11001
+ after: String(parsed.port)
11002
+ });
10653
11003
  }
10654
11004
  if (parsed.mode !== void 0 && server.mode !== parsed.mode) {
10655
11005
  updates.mode = parsed.mode;
@@ -10657,11 +11007,19 @@ var manageOvpnServerTool = {
10657
11007
  }
10658
11008
  if (parsed.protocol !== void 0 && server.protocol !== parsed.protocol) {
10659
11009
  updates.protocol = parsed.protocol;
10660
- diff.push({ property: "protocol", before: server.protocol ?? null, after: parsed.protocol });
11010
+ diff.push({
11011
+ property: "protocol",
11012
+ before: server.protocol ?? null,
11013
+ after: parsed.protocol
11014
+ });
10661
11015
  }
10662
11016
  if (parsed.certificate !== void 0 && server.certificate !== parsed.certificate) {
10663
11017
  updates.certificate = parsed.certificate;
10664
- diff.push({ property: "certificate", before: server.certificate ?? null, after: parsed.certificate });
11018
+ diff.push({
11019
+ property: "certificate",
11020
+ before: server.certificate ?? null,
11021
+ after: parsed.certificate
11022
+ });
10665
11023
  }
10666
11024
  if (parsed.cipher !== void 0 && server.cipher !== parsed.cipher) {
10667
11025
  updates.cipher = parsed.cipher;
@@ -10683,7 +11041,7 @@ var manageOvpnServerTool = {
10683
11041
  structuredContent: { action: "dry_run", diff }
10684
11042
  };
10685
11043
  }
10686
- await context.routerClient.update(OVPN_SERVER_PATH, serverId, updates);
11044
+ await writeOvpnServer(context, server, updates);
10687
11045
  log38.info({ routerId: context.routerId }, "OpenVPN server configuration updated");
10688
11046
  return {
10689
11047
  content: `Updated OpenVPN server configuration on ${context.routerId}.`,
@@ -10714,12 +11072,20 @@ var listPppProfilesTool = {
10714
11072
  title: "List PPP Profiles",
10715
11073
  description: "List PPP profiles including the built-in default and default-encryption profiles.",
10716
11074
  inputSchema: listPppProfilesInputSchema,
10717
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
11075
+ annotations: {
11076
+ readOnlyHint: true,
11077
+ destructiveHint: false,
11078
+ idempotentHint: true,
11079
+ openWorldHint: false
11080
+ },
10718
11081
  async handler(params, context) {
10719
11082
  const parsed = listPppProfilesInputSchema.parse(params);
10720
11083
  try {
10721
11084
  log39.info({ routerId: context.routerId }, "Listing PPP profiles");
10722
- const all = await context.routerClient.get("ppp/profile", { limit: void 0, offset: void 0 });
11085
+ const all = await context.routerClient.get("ppp/profile", {
11086
+ limit: void 0,
11087
+ offset: void 0
11088
+ });
10723
11089
  const filtered = parsed.name ? all.filter((p) => p.name === parsed.name) : all;
10724
11090
  const profiles = filtered.slice(0, parsed.limit);
10725
11091
  return {
@@ -10731,7 +11097,12 @@ var listPppProfilesTool = {
10731
11097
  0,
10732
11098
  (p) => compactFields(p, ["name", "local-address", "remote-address", "dns-server", "rate-limit"])
10733
11099
  ),
10734
- structuredContent: { routerId: context.routerId, profiles, total: all.length, returned: profiles.length }
11100
+ structuredContent: {
11101
+ routerId: context.routerId,
11102
+ profiles,
11103
+ total: all.length,
11104
+ returned: profiles.length
11105
+ }
10735
11106
  };
10736
11107
  } catch (err) {
10737
11108
  throw toolError(err, context, "list_ppp_profiles");
@@ -10755,13 +11126,24 @@ var managePppProfileTool = {
10755
11126
  title: "Manage PPP Profile",
10756
11127
  description: "Add, update, or remove a PPP profile. Idempotent by name. update returns no_change when requested values match. Built-in profiles (default, default-encryption) cannot be removed \u2014 RouterOS blocks this and the error is surfaced.",
10757
11128
  inputSchema: managePppProfileInputSchema,
10758
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
11129
+ annotations: {
11130
+ readOnlyHint: false,
11131
+ destructiveHint: false,
11132
+ idempotentHint: true,
11133
+ openWorldHint: false
11134
+ },
10759
11135
  snapshotPaths: ["ppp/profile"],
10760
11136
  async handler(params, context) {
10761
11137
  const parsed = managePppProfileInputSchema.parse(params);
10762
- log39.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing PPP profile");
11138
+ log39.info(
11139
+ { routerId: context.routerId, action: parsed.action, name: parsed.name },
11140
+ "Managing PPP profile"
11141
+ );
10763
11142
  try {
10764
- const all = await context.routerClient.get("ppp/profile", { limit: void 0, offset: void 0 });
11143
+ const all = await context.routerClient.get("ppp/profile", {
11144
+ limit: void 0,
11145
+ offset: void 0
11146
+ });
10765
11147
  const existing = all.find((p) => p.name === parsed.name);
10766
11148
  if (parsed.action === "add") {
10767
11149
  if (existing) {
@@ -10774,12 +11156,18 @@ var managePppProfileTool = {
10774
11156
  const diff = [
10775
11157
  { property: "name", before: null, after: parsed.name }
10776
11158
  ];
10777
- if (parsed.localAddress) diff.push({ property: "local-address", before: null, after: parsed.localAddress });
10778
- if (parsed.remoteAddress) diff.push({ property: "remote-address", before: null, after: parsed.remoteAddress });
10779
- if (parsed.dnsServer) diff.push({ property: "dns-server", before: null, after: parsed.dnsServer });
10780
- if (parsed.rateLimit) diff.push({ property: "rate-limit", before: null, after: parsed.rateLimit });
10781
- if (parsed.sessionTimeout) diff.push({ property: "session-timeout", before: null, after: parsed.sessionTimeout });
10782
- if (parsed.comment) diff.push({ property: "comment", before: null, after: parsed.comment });
11159
+ if (parsed.localAddress)
11160
+ diff.push({ property: "local-address", before: null, after: parsed.localAddress });
11161
+ if (parsed.remoteAddress)
11162
+ diff.push({ property: "remote-address", before: null, after: parsed.remoteAddress });
11163
+ if (parsed.dnsServer)
11164
+ diff.push({ property: "dns-server", before: null, after: parsed.dnsServer });
11165
+ if (parsed.rateLimit)
11166
+ diff.push({ property: "rate-limit", before: null, after: parsed.rateLimit });
11167
+ if (parsed.sessionTimeout)
11168
+ diff.push({ property: "session-timeout", before: null, after: parsed.sessionTimeout });
11169
+ if (parsed.comment)
11170
+ diff.push({ property: "comment", before: null, after: parsed.comment });
10783
11171
  return {
10784
11172
  content: `Dry run: Would create PPP profile "${parsed.name}".`,
10785
11173
  structuredContent: { action: "dry_run", diff }
@@ -10806,34 +11194,62 @@ var managePppProfileTool = {
10806
11194
  code: "PPP_PROFILE_NOT_FOUND",
10807
11195
  message: `PPP profile "${parsed.name}" not found.`,
10808
11196
  details: { name: parsed.name },
10809
- recoverability: { retryable: false, suggestedAction: "Verify with list_ppp_profiles.", alternativeTools: ["list_ppp_profiles"] }
11197
+ recoverability: {
11198
+ retryable: false,
11199
+ suggestedAction: "Verify with list_ppp_profiles.",
11200
+ alternativeTools: ["list_ppp_profiles"]
11201
+ }
10810
11202
  });
10811
11203
  }
10812
11204
  const changes = {};
10813
11205
  const diff = [];
10814
11206
  if (parsed.localAddress !== void 0 && existing["local-address"] !== parsed.localAddress) {
10815
11207
  changes["local-address"] = parsed.localAddress;
10816
- diff.push({ property: "local-address", before: existing["local-address"] ?? null, after: parsed.localAddress });
11208
+ diff.push({
11209
+ property: "local-address",
11210
+ before: existing["local-address"] ?? null,
11211
+ after: parsed.localAddress
11212
+ });
10817
11213
  }
10818
11214
  if (parsed.remoteAddress !== void 0 && existing["remote-address"] !== parsed.remoteAddress) {
10819
11215
  changes["remote-address"] = parsed.remoteAddress;
10820
- diff.push({ property: "remote-address", before: existing["remote-address"] ?? null, after: parsed.remoteAddress });
11216
+ diff.push({
11217
+ property: "remote-address",
11218
+ before: existing["remote-address"] ?? null,
11219
+ after: parsed.remoteAddress
11220
+ });
10821
11221
  }
10822
11222
  if (parsed.dnsServer !== void 0 && existing["dns-server"] !== parsed.dnsServer) {
10823
11223
  changes["dns-server"] = parsed.dnsServer;
10824
- diff.push({ property: "dns-server", before: existing["dns-server"] ?? null, after: parsed.dnsServer });
11224
+ diff.push({
11225
+ property: "dns-server",
11226
+ before: existing["dns-server"] ?? null,
11227
+ after: parsed.dnsServer
11228
+ });
10825
11229
  }
10826
11230
  if (parsed.rateLimit !== void 0 && existing["rate-limit"] !== parsed.rateLimit) {
10827
11231
  changes["rate-limit"] = parsed.rateLimit;
10828
- diff.push({ property: "rate-limit", before: existing["rate-limit"] ?? null, after: parsed.rateLimit });
11232
+ diff.push({
11233
+ property: "rate-limit",
11234
+ before: existing["rate-limit"] ?? null,
11235
+ after: parsed.rateLimit
11236
+ });
10829
11237
  }
10830
11238
  if (parsed.sessionTimeout !== void 0 && existing["session-timeout"] !== parsed.sessionTimeout) {
10831
11239
  changes["session-timeout"] = parsed.sessionTimeout;
10832
- diff.push({ property: "session-timeout", before: existing["session-timeout"] ?? null, after: parsed.sessionTimeout });
11240
+ diff.push({
11241
+ property: "session-timeout",
11242
+ before: existing["session-timeout"] ?? null,
11243
+ after: parsed.sessionTimeout
11244
+ });
10833
11245
  }
10834
11246
  if (parsed.comment !== void 0 && existing.comment !== parsed.comment) {
10835
11247
  changes.comment = parsed.comment;
10836
- diff.push({ property: "comment", before: existing.comment ?? null, after: parsed.comment });
11248
+ diff.push({
11249
+ property: "comment",
11250
+ before: existing.comment ?? null,
11251
+ after: parsed.comment
11252
+ });
10837
11253
  }
10838
11254
  if (Object.keys(changes).length === 0) {
10839
11255
  return {
@@ -10863,7 +11279,10 @@ var managePppProfileTool = {
10863
11279
  if (parsed.dryRun) {
10864
11280
  return {
10865
11281
  content: `Dry run: Would remove PPP profile "${parsed.name}".`,
10866
- structuredContent: { action: "dry_run", diff: [{ property: "name", before: parsed.name, after: null }] }
11282
+ structuredContent: {
11283
+ action: "dry_run",
11284
+ diff: [{ property: "name", before: parsed.name, after: null }]
11285
+ }
10867
11286
  };
10868
11287
  }
10869
11288
  await context.routerClient.remove("ppp/profile", existing[".id"]);
@@ -11032,7 +11451,9 @@ var createBackupTool = {
11032
11451
  var exportConfigInputSchema = z41.object({
11033
11452
  routerId,
11034
11453
  compact: z41.boolean().default(false).describe("Export only non-default settings (compact format)"),
11035
- file: z41.string().optional().describe("Save the export to a file on the router (without extension); omit to return inline")
11454
+ file: z41.string().optional().describe(
11455
+ "Save the export to a file on the router (without extension); omit to return inline"
11456
+ )
11036
11457
  }).strict();
11037
11458
  var exportConfigTool = {
11038
11459
  name: "export_config",
@@ -11204,7 +11625,10 @@ var manageLogRuleTool = {
11204
11625
  };
11205
11626
  }
11206
11627
  const created = await context.routerClient.create(LOG_RULES_PATH, body);
11207
- log42.info({ topics: parsed.topics, logAction: parsed.logAction, id: created[".id"] }, "Log rule added");
11628
+ log42.info(
11629
+ { topics: parsed.topics, logAction: parsed.logAction, id: created[".id"] },
11630
+ "Log rule added"
11631
+ );
11208
11632
  return {
11209
11633
  content: `Added log rule topics="${parsed.topics}" action="${parsed.logAction}".`,
11210
11634
  structuredContent: {
@@ -11260,7 +11684,13 @@ var manageLogRuleTool = {
11260
11684
  structuredContent: {
11261
11685
  action: "dry_run",
11262
11686
  id,
11263
- diff: [{ property: "disabled", before: existing.disabled ?? "false", after: String(wantDisabled) }]
11687
+ diff: [
11688
+ {
11689
+ property: "disabled",
11690
+ before: existing.disabled ?? "false",
11691
+ after: String(wantDisabled)
11692
+ }
11693
+ ]
11264
11694
  }
11265
11695
  };
11266
11696
  }
@@ -11492,17 +11922,25 @@ var bandwidthTestTool = {
11492
11922
  // Reaches an external host and saturates the link; a silent retry would
11493
11923
  // double the load, so opt out of auto-retry.
11494
11924
  retryable: false,
11495
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
11925
+ annotations: {
11926
+ readOnlyHint: true,
11927
+ destructiveHint: false,
11928
+ idempotentHint: false,
11929
+ openWorldHint: true
11930
+ },
11496
11931
  async handler(params, context) {
11497
11932
  const parsed = bandwidthTestInputSchema.parse(params);
11498
11933
  log43.info({ routerId: context.routerId, address: parsed.address }, "Running bandwidth test");
11499
11934
  try {
11500
- const result = await context.routerClient.execute("tool/bandwidth-test", {
11501
- address: parsed.address,
11502
- protocol: parsed.protocol,
11503
- direction: parsed.direction,
11504
- duration: String(parsed.duration)
11505
- });
11935
+ const result = await context.routerClient.execute(
11936
+ "tool/bandwidth-test",
11937
+ {
11938
+ address: parsed.address,
11939
+ protocol: parsed.protocol,
11940
+ direction: parsed.direction,
11941
+ duration: String(parsed.duration)
11942
+ }
11943
+ );
11506
11944
  const txBps = Number(result["tx-current"] ?? 0);
11507
11945
  const rxBps = Number(result["rx-current"] ?? 0);
11508
11946
  const txMbps = Math.round(txBps / 1e6 * 100) / 100;
@@ -11538,12 +11976,23 @@ var fetchUrlTool = {
11538
11976
  inputSchema: fetchUrlInputSchema,
11539
11977
  // Not read-only: a POST has external side effects and outputFile writes a
11540
11978
  // file on the router. openWorld: reaches arbitrary external hosts.
11541
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
11979
+ annotations: {
11980
+ readOnlyHint: false,
11981
+ destructiveHint: false,
11982
+ idempotentHint: false,
11983
+ openWorldHint: true
11984
+ },
11542
11985
  async handler(params, context) {
11543
11986
  const parsed = fetchUrlInputSchema.parse(params);
11544
- log43.info({ routerId: context.routerId, url: parsed.url, method: parsed.method }, "Fetching URL");
11987
+ log43.info(
11988
+ { routerId: context.routerId, url: parsed.url, method: parsed.method },
11989
+ "Fetching URL"
11990
+ );
11545
11991
  try {
11546
- const body = { url: parsed.url, "http-method": parsed.method.toLowerCase() };
11992
+ const body = {
11993
+ url: parsed.url,
11994
+ "http-method": parsed.method.toLowerCase()
11995
+ };
11547
11996
  if (parsed.httpData) body["http-data"] = parsed.httpData;
11548
11997
  if (parsed.outputFile) {
11549
11998
  body.output = "file";
@@ -11551,13 +12000,21 @@ var fetchUrlTool = {
11551
12000
  } else {
11552
12001
  body.output = "user";
11553
12002
  }
11554
- const sections = await context.routerClient.execute("tool/fetch", body);
12003
+ const sections = await context.routerClient.execute(
12004
+ "tool/fetch",
12005
+ body
12006
+ );
11555
12007
  const finished = sections.find((s) => s.status === "finished") ?? sections[sections.length - 1];
11556
12008
  const statusCode = finished?.code ?? null;
11557
12009
  if (parsed.outputFile) {
11558
12010
  return {
11559
12011
  content: `Fetched ${parsed.url} \u2192 saved to ${parsed.outputFile} (status ${statusCode})`,
11560
- structuredContent: { routerId: context.routerId, url: parsed.url, statusCode, outputFile: parsed.outputFile }
12012
+ structuredContent: {
12013
+ routerId: context.routerId,
12014
+ url: parsed.url,
12015
+ statusCode,
12016
+ outputFile: parsed.outputFile
12017
+ }
11561
12018
  };
11562
12019
  }
11563
12020
  let responseBody = finished?.data ?? "";
@@ -11566,7 +12023,12 @@ var fetchUrlTool = {
11566
12023
  }
11567
12024
  return {
11568
12025
  content: `Fetched ${parsed.url}: status=${statusCode} body_length=${responseBody.length}`,
11569
- structuredContent: { routerId: context.routerId, url: parsed.url, statusCode, body: responseBody }
12026
+ structuredContent: {
12027
+ routerId: context.routerId,
12028
+ url: parsed.url,
12029
+ statusCode,
12030
+ body: responseBody
12031
+ }
11570
12032
  };
11571
12033
  } catch (err) {
11572
12034
  throw toolError(err, context, "fetch_url");
@@ -11585,7 +12047,12 @@ var listConnectionsTool = {
11585
12047
  title: "List Connections",
11586
12048
  description: "List active connection tracking entries from the router firewall table. Filters are applied client-side. Useful for diagnosing NAT and firewall behavior.",
11587
12049
  inputSchema: listConnectionsInputSchema,
11588
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
12050
+ annotations: {
12051
+ readOnlyHint: true,
12052
+ destructiveHint: false,
12053
+ idempotentHint: true,
12054
+ openWorldHint: false
12055
+ },
11589
12056
  async handler(params, context) {
11590
12057
  const parsed = listConnectionsInputSchema.parse(params);
11591
12058
  log43.info({ routerId: context.routerId }, "Listing connections");
@@ -11602,7 +12069,11 @@ var listConnectionsTool = {
11602
12069
  limit: void 0,
11603
12070
  offset: void 0
11604
12071
  });
11605
- const filtered = all.filter((c) => parsed.srcAddress ? (c["src-address"] ?? "").includes(parsed.srcAddress) : true).filter((c) => parsed.dstAddress ? (c["dst-address"] ?? "").includes(parsed.dstAddress) : true).filter((c) => parsed.protocol ? c.protocol === parsed.protocol : true);
12072
+ const filtered = all.filter(
12073
+ (c) => parsed.srcAddress ? (c["src-address"] ?? "").includes(parsed.srcAddress) : true
12074
+ ).filter(
12075
+ (c) => parsed.dstAddress ? (c["dst-address"] ?? "").includes(parsed.dstAddress) : true
12076
+ ).filter((c) => parsed.protocol ? c.protocol === parsed.protocol : true);
11606
12077
  const connections = filtered.slice(0, parsed.limit);
11607
12078
  return {
11608
12079
  content: listContent(
@@ -11619,14 +12090,23 @@ var listConnectionsTool = {
11619
12090
  "connection-mark"
11620
12091
  ])
11621
12092
  ),
11622
- structuredContent: { routerId: context.routerId, connections, total: all.length, returned: connections.length }
12093
+ structuredContent: {
12094
+ routerId: context.routerId,
12095
+ connections,
12096
+ total: all.length,
12097
+ returned: connections.length
12098
+ }
11623
12099
  };
11624
12100
  } catch (err) {
11625
12101
  throw toolError(err, context, "list_connections");
11626
12102
  }
11627
12103
  }
11628
12104
  };
11629
- var networkTestTools = [bandwidthTestTool, fetchUrlTool, listConnectionsTool];
12105
+ var networkTestTools = [
12106
+ bandwidthTestTool,
12107
+ fetchUrlTool,
12108
+ listConnectionsTool
12109
+ ];
11630
12110
 
11631
12111
  // src/domain/tools/container-config-tools.ts
11632
12112
  import { z as z44 } from "zod";
@@ -11637,7 +12117,12 @@ var getContainerConfigTool = {
11637
12117
  title: "Get Container Config",
11638
12118
  description: "Read global container configuration: registry URL, RAM high-water mark, and veth interface.",
11639
12119
  inputSchema: getContainerConfigInputSchema,
11640
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
12120
+ annotations: {
12121
+ readOnlyHint: true,
12122
+ destructiveHint: false,
12123
+ idempotentHint: true,
12124
+ openWorldHint: false
12125
+ },
11641
12126
  async handler(params, context) {
11642
12127
  getContainerConfigInputSchema.parse(params);
11643
12128
  log44.info({ routerId: context.routerId }, "Getting container config");
@@ -11670,7 +12155,12 @@ var manageContainerConfigTool = {
11670
12155
  title: "Manage Container Config",
11671
12156
  description: "Update global container settings. Idempotent: returns no_change if nothing differs.",
11672
12157
  inputSchema: manageContainerConfigInputSchema,
11673
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
12158
+ annotations: {
12159
+ readOnlyHint: false,
12160
+ destructiveHint: false,
12161
+ idempotentHint: true,
12162
+ openWorldHint: false
12163
+ },
11674
12164
  snapshotPaths: ["container/config"],
11675
12165
  async handler(params, context) {
11676
12166
  const parsed = manageContainerConfigInputSchema.parse(params);
@@ -11678,23 +12168,34 @@ var manageContainerConfigTool = {
11678
12168
  try {
11679
12169
  const results2 = await context.routerClient.get("container/config");
11680
12170
  const current = Array.isArray(results2) && results2.length > 0 ? results2[0] : results2;
11681
- const id = current[".id"];
11682
12171
  const changes = {};
11683
12172
  const diff = [];
11684
12173
  if (parsed.registryUrl !== void 0 && current["registry-url"] !== parsed.registryUrl) {
11685
12174
  changes["registry-url"] = parsed.registryUrl;
11686
- diff.push({ property: "registry-url", before: current["registry-url"] ?? null, after: parsed.registryUrl });
12175
+ diff.push({
12176
+ property: "registry-url",
12177
+ before: current["registry-url"] ?? null,
12178
+ after: parsed.registryUrl
12179
+ });
11687
12180
  }
11688
12181
  if (parsed.ramHighMb !== void 0) {
11689
12182
  const next = String(parsed.ramHighMb);
11690
- if (current["ram-high"] !== next) {
12183
+ if (!sameValue(current["ram-high"], next)) {
11691
12184
  changes["ram-high"] = next;
11692
- diff.push({ property: "ram-high", before: current["ram-high"] ?? null, after: next });
12185
+ diff.push({
12186
+ property: "ram-high",
12187
+ before: current["ram-high"] === void 0 ? null : String(current["ram-high"]),
12188
+ after: next
12189
+ });
11693
12190
  }
11694
12191
  }
11695
12192
  if (parsed.vethInterface !== void 0 && current["veth-interface"] !== parsed.vethInterface) {
11696
12193
  changes["veth-interface"] = parsed.vethInterface;
11697
- diff.push({ property: "veth-interface", before: current["veth-interface"] ?? null, after: parsed.vethInterface });
12194
+ diff.push({
12195
+ property: "veth-interface",
12196
+ before: current["veth-interface"] ?? null,
12197
+ after: parsed.vethInterface
12198
+ });
11698
12199
  }
11699
12200
  if (Object.keys(changes).length === 0) {
11700
12201
  return {
@@ -11708,7 +12209,7 @@ var manageContainerConfigTool = {
11708
12209
  structuredContent: { action: "dry_run", diff }
11709
12210
  };
11710
12211
  }
11711
- await context.routerClient.update("container/config", id, changes);
12212
+ await context.routerClient.execute("container/config/set", changes);
11712
12213
  log44.info({ routerId: context.routerId }, "Container config updated");
11713
12214
  return {
11714
12215
  content: `Updated container config on ${context.routerId}.`,
@@ -11729,7 +12230,12 @@ var listContainerEnvsTool = {
11729
12230
  title: "List Container Envs",
11730
12231
  description: "List container environment variable entries, optionally filtered by container name.",
11731
12232
  inputSchema: listContainerEnvsInputSchema,
11732
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
12233
+ annotations: {
12234
+ readOnlyHint: true,
12235
+ destructiveHint: false,
12236
+ idempotentHint: true,
12237
+ openWorldHint: false
12238
+ },
11733
12239
  async handler(params, context) {
11734
12240
  const parsed = listContainerEnvsInputSchema.parse(params);
11735
12241
  log44.info({ routerId: context.routerId }, "Listing container envs");
@@ -11749,7 +12255,12 @@ var listContainerEnvsTool = {
11749
12255
  0,
11750
12256
  (e) => compactFields(e, ["name", "key", "value"])
11751
12257
  ),
11752
- structuredContent: { routerId: context.routerId, envs, total: all.length, returned: envs.length }
12258
+ structuredContent: {
12259
+ routerId: context.routerId,
12260
+ envs,
12261
+ total: all.length,
12262
+ returned: envs.length
12263
+ }
11753
12264
  };
11754
12265
  } catch (err) {
11755
12266
  throw toolError(err, context, "list_container_envs");
@@ -11769,7 +12280,12 @@ var manageContainerEnvTool = {
11769
12280
  title: "Manage Container Env",
11770
12281
  description: "Add or remove a container environment variable. Idempotent by name+key. add returns already_exists if the entry exists with the same value; throws CONFLICT if the value differs.",
11771
12282
  inputSchema: manageContainerEnvInputSchema,
11772
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
12283
+ annotations: {
12284
+ readOnlyHint: false,
12285
+ destructiveHint: false,
12286
+ idempotentHint: true,
12287
+ openWorldHint: false
12288
+ },
11773
12289
  async handler(params, context) {
11774
12290
  const parsed = manageContainerEnvInputSchema.parse(params);
11775
12291
  log44.info(
@@ -11873,7 +12389,12 @@ var listContainerMountsTool = {
11873
12389
  title: "List Container Mounts",
11874
12390
  description: "List container volume mount definitions with source path, destination path, and mount name.",
11875
12391
  inputSchema: listContainerMountsInputSchema,
11876
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
12392
+ annotations: {
12393
+ readOnlyHint: true,
12394
+ destructiveHint: false,
12395
+ idempotentHint: true,
12396
+ openWorldHint: false
12397
+ },
11877
12398
  async handler(params, context) {
11878
12399
  const parsed = listContainerMountsInputSchema.parse(params);
11879
12400
  log44.info({ routerId: context.routerId }, "Listing container mounts");
@@ -11893,7 +12414,12 @@ var listContainerMountsTool = {
11893
12414
  0,
11894
12415
  (m) => compactFields(m, ["name", "src", "dst"])
11895
12416
  ),
11896
- structuredContent: { routerId: context.routerId, mounts, total: all.length, returned: mounts.length }
12417
+ structuredContent: {
12418
+ routerId: context.routerId,
12419
+ mounts,
12420
+ total: all.length,
12421
+ returned: mounts.length
12422
+ }
11897
12423
  };
11898
12424
  } catch (err) {
11899
12425
  throw toolError(err, context, "list_container_mounts");
@@ -11913,10 +12439,18 @@ var manageContainerMountTool = {
11913
12439
  title: "Manage Container Mount",
11914
12440
  description: "Add or remove a container volume mount. Idempotent by name: add returns already_exists if the mount exists with matching src/dst; throws CONFLICT if name exists with different paths.",
11915
12441
  inputSchema: manageContainerMountInputSchema,
11916
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
12442
+ annotations: {
12443
+ readOnlyHint: false,
12444
+ destructiveHint: false,
12445
+ idempotentHint: true,
12446
+ openWorldHint: false
12447
+ },
11917
12448
  async handler(params, context) {
11918
12449
  const parsed = manageContainerMountInputSchema.parse(params);
11919
- log44.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing container mount");
12450
+ log44.info(
12451
+ { routerId: context.routerId, action: parsed.action, name: parsed.name },
12452
+ "Managing container mount"
12453
+ );
11920
12454
  try {
11921
12455
  const all = await context.routerClient.get("container/mounts", {
11922
12456
  limit: void 0,
@@ -11951,7 +12485,10 @@ var manageContainerMountTool = {
11951
12485
  category: "VALIDATION" /* VALIDATION */,
11952
12486
  code: "MOUNT_PATHS_REQUIRED",
11953
12487
  message: "src and dst are required for add.",
11954
- recoverability: { retryable: false, suggestedAction: "Provide src and dst parameters." }
12488
+ recoverability: {
12489
+ retryable: false,
12490
+ suggestedAction: "Provide src and dst parameters."
12491
+ }
11955
12492
  });
11956
12493
  }
11957
12494
  if (parsed.dryRun) {
@@ -12021,7 +12558,12 @@ var listInterfaceListsTool = {
12021
12558
  title: "List Interface Lists",
12022
12559
  description: "List all interface lists defined on the router.",
12023
12560
  inputSchema: listInterfaceListsInputSchema,
12024
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
12561
+ annotations: {
12562
+ readOnlyHint: true,
12563
+ destructiveHint: false,
12564
+ idempotentHint: true,
12565
+ openWorldHint: false
12566
+ },
12025
12567
  async handler(params, context) {
12026
12568
  const parsed = listInterfaceListsInputSchema.parse(params);
12027
12569
  log45.info({ routerId: context.routerId }, "Listing interface lists");
@@ -12040,7 +12582,12 @@ var listInterfaceListsTool = {
12040
12582
  0,
12041
12583
  (l) => compactFields(l, ["name", "include", "exclude", "comment"])
12042
12584
  ),
12043
- structuredContent: { routerId: context.routerId, lists, total: all.length, returned: lists.length }
12585
+ structuredContent: {
12586
+ routerId: context.routerId,
12587
+ lists,
12588
+ total: all.length,
12589
+ returned: lists.length
12590
+ }
12044
12591
  };
12045
12592
  } catch (err) {
12046
12593
  throw toolError(err, context, "list_interface_lists");
@@ -12059,10 +12606,18 @@ var manageInterfaceListTool = {
12059
12606
  title: "Manage Interface List",
12060
12607
  description: "Add or remove an interface list. Idempotent by name. Removing a list that has members is blocked by RouterOS \u2014 the error is surfaced as-is.",
12061
12608
  inputSchema: manageInterfaceListInputSchema,
12062
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
12609
+ annotations: {
12610
+ readOnlyHint: false,
12611
+ destructiveHint: true,
12612
+ idempotentHint: true,
12613
+ openWorldHint: false
12614
+ },
12063
12615
  async handler(params, context) {
12064
12616
  const parsed = manageInterfaceListInputSchema.parse(params);
12065
- log45.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing interface list");
12617
+ log45.info(
12618
+ { routerId: context.routerId, action: parsed.action, name: parsed.name },
12619
+ "Managing interface list"
12620
+ );
12066
12621
  try {
12067
12622
  const all = await context.routerClient.get("interface/list", {
12068
12623
  limit: void 0,
@@ -12133,11 +12688,21 @@ var manageInterfaceListMemberTool = {
12133
12688
  title: "Manage Interface List Member",
12134
12689
  description: "Add or remove an interface from an interface list. Idempotent by list+interface composite key. add returns already_exists if the membership exists. remove returns not_found gracefully.",
12135
12690
  inputSchema: manageInterfaceListMemberInputSchema,
12136
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
12691
+ annotations: {
12692
+ readOnlyHint: false,
12693
+ destructiveHint: true,
12694
+ idempotentHint: true,
12695
+ openWorldHint: false
12696
+ },
12137
12697
  async handler(params, context) {
12138
12698
  const parsed = manageInterfaceListMemberInputSchema.parse(params);
12139
12699
  log45.info(
12140
- { routerId: context.routerId, action: parsed.action, list: parsed.list, interface: parsed.interface },
12700
+ {
12701
+ routerId: context.routerId,
12702
+ action: parsed.action,
12703
+ list: parsed.list,
12704
+ interface: parsed.interface
12705
+ },
12141
12706
  "Managing interface list member"
12142
12707
  );
12143
12708
  try {
@@ -12152,7 +12717,11 @@ var manageInterfaceListMemberTool = {
12152
12717
  if (existing) {
12153
12718
  return {
12154
12719
  content: `Interface "${parsed.interface}" is already a member of list "${parsed.list}". No changes made.`,
12155
- structuredContent: { action: "already_exists", list: parsed.list, interface: parsed.interface }
12720
+ structuredContent: {
12721
+ action: "already_exists",
12722
+ list: parsed.list,
12723
+ interface: parsed.interface
12724
+ }
12156
12725
  };
12157
12726
  }
12158
12727
  if (parsed.dryRun) {
@@ -12167,16 +12736,28 @@ var manageInterfaceListMemberTool = {
12167
12736
  const body = { list: parsed.list, interface: parsed.interface };
12168
12737
  if (parsed.comment) body.comment = parsed.comment;
12169
12738
  const created = await context.routerClient.create("interface/list/member", body);
12170
- log45.info({ list: parsed.list, interface: parsed.interface, id: created[".id"] }, "Interface list member added");
12739
+ log45.info(
12740
+ { list: parsed.list, interface: parsed.interface, id: created[".id"] },
12741
+ "Interface list member added"
12742
+ );
12171
12743
  return {
12172
12744
  content: `Added "${parsed.interface}" to list "${parsed.list}".`,
12173
- structuredContent: { action: "created", list: parsed.list, interface: parsed.interface, id: created[".id"] }
12745
+ structuredContent: {
12746
+ action: "created",
12747
+ list: parsed.list,
12748
+ interface: parsed.interface,
12749
+ id: created[".id"]
12750
+ }
12174
12751
  };
12175
12752
  }
12176
12753
  if (!existing) {
12177
12754
  return {
12178
12755
  content: `Interface "${parsed.interface}" is not a member of list "${parsed.list}". Nothing to remove.`,
12179
- structuredContent: { action: "not_found", list: parsed.list, interface: parsed.interface }
12756
+ structuredContent: {
12757
+ action: "not_found",
12758
+ list: parsed.list,
12759
+ interface: parsed.interface
12760
+ }
12180
12761
  };
12181
12762
  }
12182
12763
  if (parsed.dryRun) {
@@ -12647,7 +13228,11 @@ function checkAuthz(identity, toolName, routerId2) {
12647
13228
  category: "PERMISSION_DENIED" /* PERMISSION_DENIED */,
12648
13229
  code: "TOOL_NOT_ALLOWED",
12649
13230
  message: `Identity "${identity.id}" is not allowed to call tool "${toolName}".`,
12650
- details: { identityId: identity.id, toolName, allowedToolPatterns: identity.allowedToolPatterns },
13231
+ details: {
13232
+ identityId: identity.id,
13233
+ toolName,
13234
+ allowedToolPatterns: identity.allowedToolPatterns
13235
+ },
12651
13236
  recoverability: {
12652
13237
  retryable: false,
12653
13238
  suggestedAction: `Add a matching pattern to allowedToolPatterns for identity "${identity.id}" in identities.yaml.`
@@ -13465,17 +14050,20 @@ async function executeToolCall(tool, args, deps) {
13465
14050
  }
13466
14051
  const shouldAudit = tool.annotations.destructiveHint || !tool.annotations.readOnlyHint;
13467
14052
  if (shouldAudit) {
13468
- auditLog({
13469
- type: "audit",
13470
- ts: (/* @__PURE__ */ new Date()).toISOString(),
13471
- correlationId,
13472
- identityId: identity.id,
13473
- role: identity.role,
13474
- tool: tool.name,
13475
- routerId: routerId2,
13476
- phase: "attempt",
13477
- params: args
13478
- }, config.auditLogPath);
14053
+ auditLog(
14054
+ {
14055
+ type: "audit",
14056
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
14057
+ correlationId,
14058
+ identityId: identity.id,
14059
+ role: identity.role,
14060
+ tool: tool.name,
14061
+ routerId: routerId2,
14062
+ phase: "attempt",
14063
+ params: args
14064
+ },
14065
+ config.auditLogPath
14066
+ );
13479
14067
  }
13480
14068
  const toolContext = buildRouterToolContext({
13481
14069
  routerConfig,
@@ -13492,7 +14080,12 @@ async function executeToolCall(tool, args, deps) {
13492
14080
  if (!tool.annotations.readOnlyHint) {
13493
14081
  for (const path of snapshotPathsFor(tool, handlerArgs)) {
13494
14082
  try {
13495
- const meta = await takeSnapshot(toolContext.deviceClient, routerId2, path, config.snapshotDir);
14083
+ const meta = await takeSnapshot(
14084
+ toolContext.deviceClient,
14085
+ routerId2,
14086
+ path,
14087
+ config.snapshotDir
14088
+ );
13496
14089
  snapshotIds.push(meta.id);
13497
14090
  log48.debug({ snapshotId: meta.id, path }, "Snapshot taken");
13498
14091
  } catch (err) {
@@ -13524,18 +14117,21 @@ async function executeToolCall(tool, args, deps) {
13524
14117
  });
13525
14118
  }
13526
14119
  if (shouldAudit) {
13527
- auditLog({
13528
- type: "audit",
13529
- ts: (/* @__PURE__ */ new Date()).toISOString(),
13530
- correlationId,
13531
- identityId: identity.id,
13532
- role: identity.role,
13533
- tool: tool.name,
13534
- routerId: routerId2,
13535
- phase: "success",
13536
- params: args,
13537
- durationMs: Date.now() - startMs
13538
- }, config.auditLogPath);
14120
+ auditLog(
14121
+ {
14122
+ type: "audit",
14123
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
14124
+ correlationId,
14125
+ identityId: identity.id,
14126
+ role: identity.role,
14127
+ tool: tool.name,
14128
+ routerId: routerId2,
14129
+ phase: "success",
14130
+ params: args,
14131
+ durationMs: Date.now() - startMs
14132
+ },
14133
+ config.auditLogPath
14134
+ );
13539
14135
  }
13540
14136
  log48.info({ tool: tool.name, routerId: routerId2, correlationId }, "Tool executed successfully");
13541
14137
  recordToolCall(tool.name, "success");
@@ -13565,19 +14161,22 @@ async function executeToolCall(tool, args, deps) {
13565
14161
  if (shouldAudit && error.category !== "APPROVAL_REQUIRED" /* APPROVAL_REQUIRED */) {
13566
14162
  const routerId2 = args.routerId ?? "unknown";
13567
14163
  const identity = getCurrentIdentity() ?? getStdioIdentity(config.stdioIdentity, identityRegistry);
13568
- auditLog({
13569
- type: "audit",
13570
- ts: (/* @__PURE__ */ new Date()).toISOString(),
13571
- correlationId,
13572
- identityId: identity.id,
13573
- role: identity.role,
13574
- tool: tool.name,
13575
- routerId: routerId2,
13576
- phase: "failure",
13577
- params: args,
13578
- outcome: error.code,
13579
- durationMs: Date.now() - startMs
13580
- }, config.auditLogPath);
14164
+ auditLog(
14165
+ {
14166
+ type: "audit",
14167
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
14168
+ correlationId,
14169
+ identityId: identity.id,
14170
+ role: identity.role,
14171
+ tool: tool.name,
14172
+ routerId: routerId2,
14173
+ phase: "failure",
14174
+ params: args,
14175
+ outcome: error.code,
14176
+ durationMs: Date.now() - startMs
14177
+ },
14178
+ config.auditLogPath
14179
+ );
13581
14180
  }
13582
14181
  log48.error({ err: error, tool: tool.name, correlationId }, "Tool execution failed");
13583
14182
  recordToolCall(tool.name, "error");
@@ -13716,10 +14315,16 @@ var listRoutersTool = {
13716
14315
  const total = rows.length;
13717
14316
  const header = `Routers: ${total === 0 ? "none" : `1-${total} of ${total}`}.`;
13718
14317
  const lines = rows.map(
13719
- (r) => ` ${compactFields(
13720
- { ...r, tags: r.tags.join(",") },
13721
- ["id", "host", "port", "deviceType", "tlsEnabled", "tags", "rosVersion", "isDefault"]
13722
- )}`
14318
+ (r) => ` ${compactFields({ ...r, tags: r.tags.join(",") }, [
14319
+ "id",
14320
+ "host",
14321
+ "port",
14322
+ "deviceType",
14323
+ "tlsEnabled",
14324
+ "tags",
14325
+ "rosVersion",
14326
+ "isDefault"
14327
+ ])}`
13723
14328
  );
13724
14329
  return {
13725
14330
  content: total === 0 ? header : [header, ...lines].join("\n"),
@@ -13808,17 +14413,20 @@ function createFleetTools(baseTools2) {
13808
14413
  { toolName: parsed.toolName, concurrency: parsed.concurrency },
13809
14414
  "bulk_execute invoked"
13810
14415
  );
13811
- auditLog({
13812
- type: "audit",
13813
- ts: (/* @__PURE__ */ new Date()).toISOString(),
13814
- correlationId: context.correlationId,
13815
- identityId: context.identity.id,
13816
- role: context.identity.role,
13817
- tool: "bulk_execute",
13818
- routerId: "(fleet)",
13819
- phase: "attempt",
13820
- params: { toolName: parsed.toolName, routerIds: parsed.routerIds, tags: parsed.tags }
13821
- }, context.appConfig.auditLogPath);
14416
+ auditLog(
14417
+ {
14418
+ type: "audit",
14419
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
14420
+ correlationId: context.correlationId,
14421
+ identityId: context.identity.id,
14422
+ role: context.identity.role,
14423
+ tool: "bulk_execute",
14424
+ routerId: "(fleet)",
14425
+ phase: "attempt",
14426
+ params: { toolName: parsed.toolName, routerIds: parsed.routerIds, tags: parsed.tags }
14427
+ },
14428
+ context.appConfig.auditLogPath
14429
+ );
13822
14430
  if (parsed.toolName === "bulk_execute" || parsed.toolName === "check_router_health") {
13823
14431
  throw new MikroMCPError({
13824
14432
  category: "VALIDATION" /* VALIDATION */,
@@ -13891,17 +14499,20 @@ function createFleetTools(baseTools2) {
13891
14499
  );
13892
14500
  }
13893
14501
  if (routers.length === 0 && preErrors.length === 0) {
13894
- auditLog({
13895
- type: "audit",
13896
- ts: (/* @__PURE__ */ new Date()).toISOString(),
13897
- correlationId: context.correlationId,
13898
- identityId: context.identity.id,
13899
- role: context.identity.role,
13900
- tool: "bulk_execute",
13901
- routerId: "(fleet)",
13902
- phase: "success",
13903
- params: { toolName: parsed.toolName, succeeded: 0, failed: 0 }
13904
- }, context.appConfig.auditLogPath);
14502
+ auditLog(
14503
+ {
14504
+ type: "audit",
14505
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
14506
+ correlationId: context.correlationId,
14507
+ identityId: context.identity.id,
14508
+ role: context.identity.role,
14509
+ tool: "bulk_execute",
14510
+ routerId: "(fleet)",
14511
+ phase: "success",
14512
+ params: { toolName: parsed.toolName, succeeded: 0, failed: 0 }
14513
+ },
14514
+ context.appConfig.auditLogPath
14515
+ );
13905
14516
  return {
13906
14517
  content: `Executed ${parsed.toolName} on 0 routers: 0 succeeded, 0 failed`,
13907
14518
  structuredContent: {
@@ -13934,7 +14545,10 @@ function createFleetTools(baseTools2) {
13934
14545
  const toolParams = { ...parsed.params, routerId: router.id };
13935
14546
  const snapshotIds = [];
13936
14547
  if (isWrite) {
13937
- for (const path of snapshotPathsFor(targetTool, toolParams)) {
14548
+ for (const path of snapshotPathsFor(
14549
+ targetTool,
14550
+ toolParams
14551
+ )) {
13938
14552
  try {
13939
14553
  const meta = await takeSnapshot(
13940
14554
  routerContext.deviceClient,
@@ -13971,40 +14585,57 @@ function createFleetTools(baseTools2) {
13971
14585
  );
13972
14586
  const elapsed = Date.now() - start;
13973
14587
  if (journalId) {
13974
- recordOutcome({ journalPath, journalId, phase: "success", durationMs: elapsed });
14588
+ recordOutcome({
14589
+ journalPath,
14590
+ journalId,
14591
+ phase: "success",
14592
+ durationMs: elapsed
14593
+ });
13975
14594
  }
13976
- auditLog({
13977
- type: "audit",
13978
- ts: (/* @__PURE__ */ new Date()).toISOString(),
13979
- correlationId: context.correlationId,
13980
- identityId: context.identity.id,
13981
- role: context.identity.role,
13982
- tool: parsed.toolName,
13983
- routerId: router.id,
13984
- phase: "success",
13985
- params: parsed.params,
13986
- durationMs: elapsed
13987
- }, context.appConfig.auditLogPath);
14595
+ auditLog(
14596
+ {
14597
+ type: "audit",
14598
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
14599
+ correlationId: context.correlationId,
14600
+ identityId: context.identity.id,
14601
+ role: context.identity.role,
14602
+ tool: parsed.toolName,
14603
+ routerId: router.id,
14604
+ phase: "success",
14605
+ params: parsed.params,
14606
+ durationMs: elapsed
14607
+ },
14608
+ context.appConfig.auditLogPath
14609
+ );
13988
14610
  return { routerId: router.id, status: "ok", result, durationMs: elapsed };
13989
14611
  } catch (err) {
13990
14612
  const elapsed = Date.now() - start;
13991
14613
  const message = err instanceof Error ? err.message : String(err);
13992
14614
  if (journalId) {
13993
- recordOutcome({ journalPath, journalId, phase: "failure", outcome: message, durationMs: elapsed });
14615
+ recordOutcome({
14616
+ journalPath,
14617
+ journalId,
14618
+ phase: "failure",
14619
+ outcome: message,
14620
+ durationMs: elapsed
14621
+ });
13994
14622
  }
13995
- auditLog({
13996
- type: "audit",
13997
- ts: (/* @__PURE__ */ new Date()).toISOString(),
13998
- correlationId: context.correlationId,
13999
- identityId: context.identity.id,
14000
- role: context.identity.role,
14001
- tool: parsed.toolName,
14002
- routerId: router.id,
14003
- phase: "failure",
14004
- params: parsed.params,
14005
- outcome: message,
14006
- durationMs: elapsed
14007
- }, context.appConfig.auditLogPath);
14623
+ auditLog(
14624
+ {
14625
+ type: "audit",
14626
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
14627
+ correlationId: context.correlationId,
14628
+ identityId: context.identity.id,
14629
+ role: context.identity.role,
14630
+ tool: parsed.toolName,
14631
+ routerId: router.id,
14632
+ phase: "failure",
14633
+ params: parsed.params,
14634
+ outcome: message,
14635
+ durationMs: elapsed
14636
+ },
14637
+ context.appConfig.auditLogPath
14638
+ );
14008
14639
  return { routerId: router.id, status: "error", error: message, durationMs: elapsed };
14009
14640
  }
14010
14641
  }
@@ -14016,17 +14647,20 @@ function createFleetTools(baseTools2) {
14016
14647
  }
14017
14648
  const succeeded = results2.filter((r) => r.status === "ok").length;
14018
14649
  const failed = results2.filter((r) => r.status === "error").length;
14019
- auditLog({
14020
- type: "audit",
14021
- ts: (/* @__PURE__ */ new Date()).toISOString(),
14022
- correlationId: context.correlationId,
14023
- identityId: context.identity.id,
14024
- role: context.identity.role,
14025
- tool: "bulk_execute",
14026
- routerId: "(fleet)",
14027
- phase: "success",
14028
- params: { toolName: parsed.toolName, succeeded, failed }
14029
- }, context.appConfig.auditLogPath);
14650
+ auditLog(
14651
+ {
14652
+ type: "audit",
14653
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
14654
+ correlationId: context.correlationId,
14655
+ identityId: context.identity.id,
14656
+ role: context.identity.role,
14657
+ tool: "bulk_execute",
14658
+ routerId: "(fleet)",
14659
+ phase: "success",
14660
+ params: { toolName: parsed.toolName, succeeded, failed }
14661
+ },
14662
+ context.appConfig.auditLogPath
14663
+ );
14030
14664
  return {
14031
14665
  content: `Executed ${parsed.toolName} on ${results2.length} routers: ${succeeded} succeeded, ${failed} failed`,
14032
14666
  structuredContent: {
@@ -14152,9 +14786,7 @@ var RouterConfigSchema = z49.object({
14152
14786
  cmdDeny: z49.array(z49.string()).optional(),
14153
14787
  maintenanceWindows: z49.array(
14154
14788
  z49.object({
14155
- days: z49.array(
14156
- z49.enum(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"])
14157
- ),
14789
+ days: z49.array(z49.enum(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"])),
14158
14790
  startTime: z49.string().regex(/^\d{2}:\d{2}$/, "startTime must be HH:MM"),
14159
14791
  endTime: z49.string().regex(/^\d{2}:\d{2}$/, "endTime must be HH:MM"),
14160
14792
  timezone: z49.string().min(1)
@@ -14230,7 +14862,18 @@ ${issues}`);
14230
14862
  getRouter(id) {
14231
14863
  const router = this.routers.get(id);
14232
14864
  if (!router) {
14233
- throw new Error(`Router not found: ${id}`);
14865
+ const available = this.routerIds();
14866
+ throw new MikroMCPError({
14867
+ category: "NOT_FOUND" /* NOT_FOUND */,
14868
+ code: "ROUTER_NOT_FOUND",
14869
+ message: available.length > 0 ? `Router not found: ${id}. Available: ${available.join(", ")}.` : `Router not found: ${id}. No routers are configured.`,
14870
+ details: { routerId: id, availableRouters: available },
14871
+ recoverability: {
14872
+ retryable: false,
14873
+ suggestedAction: "Check the routerId, or use list_routers to see the configured routers.",
14874
+ alternativeTools: ["list_routers"]
14875
+ }
14876
+ });
14234
14877
  }
14235
14878
  return router;
14236
14879
  }
@@ -14317,7 +14960,7 @@ var ConnectionPool = class {
14317
14960
  };
14318
14961
 
14319
14962
  // src/version.ts
14320
- var VERSION = "1.9.0";
14963
+ var VERSION = "1.10.0";
14321
14964
 
14322
14965
  // src/mcp/server.ts
14323
14966
  var SERVER_INSTRUCTIONS = [
@@ -14503,7 +15146,18 @@ async function connectHttp(makeServer, config, identityRegistry) {
14503
15146
  }
14504
15147
  const boundIdentity = identity;
14505
15148
  await withIdentity(boundIdentity, async () => {
14506
- await handleMcpRequest(pathname, req, res, url, port, maxBodyBytes, makeServer, streamableSessions, sseTransports, boundIdentity.id);
15149
+ await handleMcpRequest(
15150
+ pathname,
15151
+ req,
15152
+ res,
15153
+ url,
15154
+ port,
15155
+ maxBodyBytes,
15156
+ makeServer,
15157
+ streamableSessions,
15158
+ sseTransports,
15159
+ boundIdentity.id
15160
+ );
14507
15161
  });
14508
15162
  return;
14509
15163
  }
@@ -14741,9 +15395,7 @@ async function runServe() {
14741
15395
  void pruneSnapshots(config.snapshotDir, config.retention.snapshotMaxAgeDays);
14742
15396
  const identityRegistry = new IdentityRegistry(config.identitiesPath);
14743
15397
  if (config.transport === "http") {
14744
- const hasLimitedRoles = identityRegistry.getIdentities().some(
14745
- (i) => i.role === "readonly" || i.role === "operator"
14746
- );
15398
+ const hasLimitedRoles = identityRegistry.getIdentities().some((i) => i.role === "readonly" || i.role === "operator");
14747
15399
  if (hasLimitedRoles && !config.confirmationSecret) {
14748
15400
  log55.error(
14749
15401
  "MIKROMCP_CONFIRMATION_SECRET is required in HTTP mode when identities with role readonly or operator are configured"
@@ -14786,7 +15438,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
14786
15438
  import { homedir as homedir2 } from "os";
14787
15439
  import * as net from "net";
14788
15440
  import chalk from "chalk";
14789
- var localVersion = "1.9.0";
15441
+ var localVersion = "1.10.0";
14790
15442
  var results = [];
14791
15443
  function ok(msg) {
14792
15444
  console.log(chalk.green("\u2705 ") + msg);
@@ -14883,7 +15535,9 @@ function checkDefaultRouter(defaultRouter, routers) {
14883
15535
  const ids = routers.map((r) => r.id);
14884
15536
  if (defaultRouter) {
14885
15537
  if (ids.includes(defaultRouter)) {
14886
- ok(`Default router: ${defaultRouter} (MIKROMCP_DEFAULT_ROUTER; used when routerId is omitted)`);
15538
+ ok(
15539
+ `Default router: ${defaultRouter} (MIKROMCP_DEFAULT_ROUTER; used when routerId is omitted)`
15540
+ );
14887
15541
  } else {
14888
15542
  fail(
14889
15543
  `MIKROMCP_DEFAULT_ROUTER="${defaultRouter}" does not match any configured router` + (ids.length > 0 ? ` (available: ${ids.join(", ")})` : "")
@@ -14930,7 +15584,9 @@ async function checkRouter(router) {
14930
15584
  indentWarn(`read policy: ip/address returned ${code} \u2014 check user permissions`);
14931
15585
  results.push({ passed: true, warning: true });
14932
15586
  } else {
14933
- indentFail(`read policy: ip/address failed \u2014 ${readErr instanceof Error ? readErr.message : String(readErr)}`);
15587
+ indentFail(
15588
+ `read policy: ip/address failed \u2014 ${readErr instanceof Error ? readErr.message : String(readErr)}`
15589
+ );
14934
15590
  results.push({ passed: false, warning: false });
14935
15591
  }
14936
15592
  }
@@ -15013,7 +15669,9 @@ function checkClaudeDesktop() {
15013
15669
  );
15014
15670
  }
15015
15671
  } catch (err) {
15016
- warn(`Could not parse Claude Desktop config: ${err instanceof Error ? err.message : String(err)}`);
15672
+ warn(
15673
+ `Could not parse Claude Desktop config: ${err instanceof Error ? err.message : String(err)}`
15674
+ );
15017
15675
  }
15018
15676
  }
15019
15677
  function checkSkill() {
@@ -15092,13 +15750,7 @@ async function runDoctor() {
15092
15750
  // src/cli/init.ts
15093
15751
  import { input, confirm, select } from "@inquirer/prompts";
15094
15752
  import { randomBytes as randomBytes2 } from "crypto";
15095
- import {
15096
- existsSync as existsSync4,
15097
- readFileSync as readFileSync6,
15098
- writeFileSync,
15099
- mkdirSync,
15100
- copyFileSync
15101
- } from "fs";
15753
+ import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync, mkdirSync, copyFileSync } from "fs";
15102
15754
  import { homedir as homedir3 } from "os";
15103
15755
  import { join as join4 } from "path";
15104
15756
  import { parse as yamlParse, Document, visit } from "yaml";
@@ -15199,7 +15851,18 @@ async function collectRouterInfo() {
15199
15851
  default: "7",
15200
15852
  validate: (v) => v.trim().length > 0 ? true : "Version is required"
15201
15853
  });
15202
- return { routerId: routerId2, host, port, tlsEnabled, rejectUnauthorized, envPrefix, routerUser, routerPass, tags, rosVersion };
15854
+ return {
15855
+ routerId: routerId2,
15856
+ host,
15857
+ port,
15858
+ tlsEnabled,
15859
+ rejectUnauthorized,
15860
+ envPrefix,
15861
+ routerUser,
15862
+ routerPass,
15863
+ tags,
15864
+ rosVersion
15865
+ };
15203
15866
  }
15204
15867
  async function collectDefaultRouterPreference(routerId2) {
15205
15868
  return confirm({
@@ -15266,7 +15929,10 @@ async function collectTransport() {
15266
15929
  const transport2 = await select({
15267
15930
  message: "Transport mode:",
15268
15931
  choices: [
15269
- { value: "stdio", name: "stdio \u2014 launched by Claude Desktop or an MCP client (recommended)" },
15932
+ {
15933
+ value: "stdio",
15934
+ name: "stdio \u2014 launched by Claude Desktop or an MCP client (recommended)"
15935
+ },
15270
15936
  { value: "http", name: "http \u2014 standalone HTTP server with bearer-token auth" }
15271
15937
  ],
15272
15938
  default: "stdio"
@@ -15484,21 +16150,25 @@ async function runInit() {
15484
16150
  }
15485
16151
  if (data.createIdentity && data.rawToken) {
15486
16152
  console.log(
15487
- chalk2.bold.yellow(` \u2705 Bearer token: ${data.rawToken} (save this \u2014 it won't be shown again)`)
16153
+ chalk2.bold.yellow(
16154
+ ` \u2705 Bearer token: ${data.rawToken} (save this \u2014 it won't be shown again)`
16155
+ )
15488
16156
  );
15489
16157
  }
15490
16158
  console.log(chalk2.bold("\nNext steps:"));
15491
16159
  console.log(chalk2.dim(" 1. Run: mikromcp doctor"));
15492
16160
  console.log(chalk2.dim(" 2. Restart Claude Desktop to load the MCP server"));
15493
16161
  console.log(
15494
- chalk2.dim(" 3. (Optional) Install the usage skill so your AI assistant drives MikroMCP safely")
16162
+ chalk2.dim(
16163
+ " 3. (Optional) Install the usage skill so your AI assistant drives MikroMCP safely"
16164
+ )
15495
16165
  );
15496
16166
  console.log(chalk2.dim(" See: docs/wiki/Using-the-Skill.md"));
15497
16167
  console.log();
15498
16168
  }
15499
16169
 
15500
16170
  // src/cli/index.ts
15501
- var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.9.0");
16171
+ var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.10.0");
15502
16172
  program.command("serve", { isDefault: true }).description("Start the MCP server (default)").action(runServe);
15503
16173
  program.command("doctor").description("Check config, connectivity, and permissions").action(runDoctor);
15504
16174
  program.command("init").description("Interactive setup wizard").action(runInit);