mikromcp 1.5.0 → 1.6.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 +9 -2
  2. package/dist/main.js +1219 -1263
  3. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -24,6 +24,7 @@ function loadAppConfig() {
24
24
  bindHost: env.MIKROMCP_BIND_HOST ?? "127.0.0.1",
25
25
  logLevel: env.MIKROMCP_LOG_LEVEL ?? "info",
26
26
  configPath: env.MIKROMCP_CONFIG_PATH ?? join(mikromcpDir(), "routers.yaml"),
27
+ defaultRouter: env.MIKROMCP_DEFAULT_ROUTER || void 0,
27
28
  dataDir,
28
29
  snapshotDir: `${dataDir}/snapshots`,
29
30
  journalPath: `${dataDir}/write-journal.ndjson`,
@@ -60,10 +61,17 @@ function loadAppConfig() {
60
61
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
61
62
 
62
63
  // src/mcp/tool-registry.ts
63
- import { z as z46 } from "zod";
64
+ import { z as z47 } from "zod";
64
65
 
65
66
  // src/domain/tools/system-tools.ts
67
+ import { z as z2 } from "zod";
68
+
69
+ // src/domain/tools/schema-fields.ts
66
70
  import { z } from "zod";
71
+ var routerId = z.string().optional().describe("Router ID; omit to use the default router.");
72
+ var dryRun = z.boolean().default(false).describe("Preview changes without applying.");
73
+ var offset = z.number().int().min(0).default(0).describe("Pagination offset.");
74
+ var limit = z.number().int().min(1).max(500).default(100).describe("Max results to return.");
67
75
 
68
76
  // src/domain/errors/error-enricher.ts
69
77
  import { ZodError } from "zod";
@@ -274,6 +282,11 @@ function enrichError(error, context) {
274
282
  });
275
283
  }
276
284
 
285
+ // src/domain/tools/tool-definition.ts
286
+ function toolError(err, context, tool) {
287
+ return enrichError(err, { routerId: context.routerId, tool });
288
+ }
289
+
277
290
  // src/observability/logger.ts
278
291
  import pino from "pino";
279
292
  var isDevelopment = process.env.NODE_ENV === "development";
@@ -320,9 +333,9 @@ var SECTION_PATHS = {
320
333
  health: "system/health",
321
334
  clock: "system/clock"
322
335
  };
323
- var inputSchema = z.object({
324
- routerId: z.string().describe("Target router identifier from the router registry"),
325
- sections: z.array(z.enum(SECTION_VALUES)).default(["all"]).describe("Which system information sections to include")
336
+ var inputSchema = z2.object({
337
+ routerId,
338
+ sections: z2.array(z2.enum(SECTION_VALUES)).default(["all"]).describe("Which system information sections to include")
326
339
  }).strict();
327
340
  async function fetchSection(context, section) {
328
341
  const path = SECTION_PATHS[section];
@@ -419,23 +432,39 @@ var getSystemStatusTool = {
419
432
  }
420
433
  };
421
434
  } catch (err) {
422
- throw enrichError(err, { routerId: context.routerId, tool: "get_system_status" });
435
+ throw toolError(err, context, "get_system_status");
423
436
  }
424
437
  }
425
438
  };
426
439
  var systemTools = [getSystemStatusTool];
427
440
 
428
441
  // src/domain/tools/interface-tools.ts
429
- import { z as z2 } from "zod";
442
+ import { z as z3 } from "zod";
443
+
444
+ // src/domain/tools/pagination.ts
445
+ function paginate(items, offset2, limit2) {
446
+ const total = items.length;
447
+ return {
448
+ items: items.slice(offset2, offset2 + limit2),
449
+ total,
450
+ hasMore: offset2 + limit2 < total
451
+ };
452
+ }
453
+ function listSummary(label, routerId2, shown, total, offset2) {
454
+ const range = total === 0 ? "none" : `${offset2 + 1}-${offset2 + shown} of ${total}`;
455
+ return `${label} on ${routerId2}: ${range}. Full records in structuredContent.`;
456
+ }
457
+
458
+ // src/domain/tools/interface-tools.ts
430
459
  var log2 = createLogger("interface-tools");
431
- var listInputSchema = z2.object({
432
- routerId: z2.string().describe("Target router identifier from the router registry"),
433
- type: z2.enum(["ether", "vlan", "bridge", "bonding", "wireguard", "gre", "all"]).default("all").describe("Filter by interface type"),
434
- status: z2.enum(["up", "down", "all"]).default("all").describe("Filter by running status"),
435
- macAddress: z2.string().optional().describe("Filter by MAC address (case-insensitive exact match on mac-address field)"),
436
- includeCounters: z2.boolean().default(false).describe("Include traffic counters (tx-byte, rx-byte, etc.)"),
437
- limit: z2.number().int().min(1).max(500).default(100).describe("Maximum number of interfaces to return"),
438
- offset: z2.number().int().min(0).default(0).describe("Offset for pagination")
460
+ var listInputSchema = z3.object({
461
+ routerId,
462
+ type: z3.enum(["ether", "vlan", "bridge", "bonding", "wireguard", "gre", "all"]).default("all").describe("Filter by interface type"),
463
+ status: z3.enum(["up", "down", "all"]).default("all").describe("Filter by running status"),
464
+ macAddress: z3.string().optional().describe("Filter by MAC address (case-insensitive exact match on mac-address field)"),
465
+ includeCounters: z3.boolean().default(false).describe("Include traffic counters (tx-byte, rx-byte, etc.)"),
466
+ limit,
467
+ offset
439
468
  }).strict();
440
469
  var COUNTER_PROPS = [
441
470
  "tx-byte",
@@ -493,8 +522,7 @@ var listInterfacesTool = {
493
522
  return true;
494
523
  });
495
524
  }
496
- const total = interfaces.length;
497
- const paginated = interfaces.slice(parsed.offset, parsed.offset + parsed.limit);
525
+ const { items: paginated, total, hasMore } = paginate(interfaces, parsed.offset, parsed.limit);
498
526
  const results2 = paginated.map((iface) => {
499
527
  const rec = iface;
500
528
  const isRunning2 = rec.running === true || rec.running === "true";
@@ -507,19 +535,8 @@ var listInterfacesTool = {
507
535
  }
508
536
  return enriched;
509
537
  });
510
- const hasMore = parsed.offset + parsed.limit < total;
511
- const lines = [
512
- `Interfaces on ${context.routerId}: ${total} total, showing ${results2.length} (offset ${parsed.offset})`
513
- ];
514
- for (const iface of results2) {
515
- const rec = iface;
516
- const name = rec.name ?? rec[".id"] ?? "unknown";
517
- const type = rec.type ?? parsed.type;
518
- const status = String(rec.status).toUpperCase();
519
- lines.push(` ${name} [${type}] ${status}`);
520
- }
521
538
  return {
522
- content: lines.join("\n"),
539
+ content: listSummary("Interfaces", context.routerId, results2.length, total, parsed.offset),
523
540
  structuredContent: {
524
541
  routerId: context.routerId,
525
542
  interfaces: results2,
@@ -530,17 +547,17 @@ var listInterfacesTool = {
530
547
  }
531
548
  };
532
549
  } catch (err) {
533
- throw enrichError(err, { routerId: context.routerId, tool: "list_interfaces" });
550
+ throw toolError(err, context, "list_interfaces");
534
551
  }
535
552
  }
536
553
  };
537
554
  var interfaceTools = [listInterfacesTool];
538
555
 
539
556
  // src/domain/tools/ip-tools.ts
540
- import { z as z4 } from "zod";
557
+ import { z as z5 } from "zod";
541
558
 
542
559
  // src/domain/tools/cidr.ts
543
- import { z as z3 } from "zod";
560
+ import { z as z4 } from "zod";
544
561
  function validateCidr(value) {
545
562
  const withPrefix = value.includes("/") ? value : `${value}/32`;
546
563
  const slashIdx = withPrefix.lastIndexOf("/");
@@ -562,28 +579,28 @@ function validateCidr(value) {
562
579
  }
563
580
  return withPrefix;
564
581
  }
565
- var cidrSchema = z3.string().transform((v, ctx) => {
582
+ var cidrSchema = z4.string().transform((v, ctx) => {
566
583
  try {
567
584
  return validateCidr(v);
568
585
  } catch (e) {
569
- ctx.addIssue({ code: z3.ZodIssueCode.custom, message: e.message });
570
- return z3.NEVER;
586
+ ctx.addIssue({ code: z4.ZodIssueCode.custom, message: e.message });
587
+ return z4.NEVER;
571
588
  }
572
589
  });
573
590
 
574
591
  // src/domain/tools/ip-tools.ts
575
592
  var log3 = createLogger("ip-tools");
576
- var inputSchema2 = z4.object({
577
- routerId: z4.string().describe("Target router identifier from the router registry"),
578
- action: z4.enum(["add", "update", "remove"]).describe("Action to perform: add, update, or remove an IP address"),
593
+ var inputSchema2 = z5.object({
594
+ routerId,
595
+ action: z5.enum(["add", "update", "remove"]).describe("Action to perform: add, update, or remove an IP address"),
579
596
  address: cidrSchema.describe(
580
597
  "IP address with optional prefix length in CIDR notation (e.g., 192.168.1.1/24). Defaults to /32 if prefix is omitted."
581
598
  ),
582
- interface: z4.string().describe("Interface to assign the IP address to"),
583
- network: z4.string().optional().describe("Network address (auto-calculated from address if omitted)"),
584
- comment: z4.string().max(255).optional().describe("Optional comment for the IP address entry"),
585
- disabled: z4.boolean().default(false).describe("Whether the IP address should be disabled"),
586
- dryRun: z4.boolean().default(false).describe("If true, validate and return planned changes without applying")
599
+ interface: z5.string().describe("Interface to assign the IP address to"),
600
+ network: z5.string().optional().describe("Network address (auto-calculated from address if omitted)"),
601
+ comment: z5.string().max(255).optional().describe("Optional comment for the IP address entry"),
602
+ disabled: z5.boolean().default(false).describe("Whether the IP address should be disabled"),
603
+ dryRun: z5.boolean().default(false).describe("If true, validate and return planned changes without applying")
587
604
  }).strict();
588
605
  function sanitizeComment(comment) {
589
606
  if (comment === void 0) return void 0;
@@ -783,24 +800,23 @@ var manageIpAddressTool = {
783
800
  }
784
801
  });
785
802
  } catch (err) {
786
- if (err instanceof MikroMCPError) throw err;
787
- throw enrichError(err, { routerId: context.routerId, tool: "manage_ip_address" });
803
+ throw toolError(err, context, "manage_ip_address");
788
804
  }
789
805
  }
790
806
  };
791
807
  var ipTools = [manageIpAddressTool];
792
808
 
793
809
  // src/domain/tools/dhcp-tools.ts
794
- import { z as z5 } from "zod";
810
+ import { z as z6 } from "zod";
795
811
  var log4 = createLogger("dhcp-tools");
796
- var listDhcpLeasesInputSchema = z5.object({
797
- routerId: z5.string().describe("Target router identifier from the router registry"),
798
- server: z5.string().optional().describe("Filter by DHCP server name"),
799
- status: z5.enum(["bound", "waiting", "offered", "blocked", "all"]).default("all").describe("Filter by lease status"),
800
- macAddress: z5.string().optional().describe("Filter by MAC address (exact match, case-insensitive)"),
801
- leaseType: z5.enum(["dynamic", "static", "all"]).default("all").describe("Filter by lease type (dynamic or static)"),
802
- limit: z5.number().int().min(1).max(500).default(100).describe("Maximum number of leases to return"),
803
- offset: z5.number().int().min(0).default(0).describe("Offset for pagination")
812
+ var listDhcpLeasesInputSchema = z6.object({
813
+ routerId,
814
+ server: z6.string().optional().describe("Filter by DHCP server name"),
815
+ status: z6.enum(["bound", "waiting", "offered", "blocked", "all"]).default("all").describe("Filter by lease status"),
816
+ macAddress: z6.string().optional().describe("Filter by MAC address (exact match, case-insensitive)"),
817
+ leaseType: z6.enum(["dynamic", "static", "all"]).default("all").describe("Filter by lease type (dynamic or static)"),
818
+ limit,
819
+ offset
804
820
  }).strict();
805
821
  var listDhcpLeasesTool = {
806
822
  name: "list_dhcp_leases",
@@ -857,23 +873,9 @@ var listDhcpLeasesTool = {
857
873
  return !isDynamic;
858
874
  });
859
875
  }
860
- const total = leases.length;
861
- const paginated = leases.slice(parsed.offset, parsed.offset + parsed.limit);
862
- const hasMore = parsed.offset + parsed.limit < total;
863
- const lines = [
864
- `DHCP leases on ${context.routerId}: ${total} total, showing ${paginated.length} (offset ${parsed.offset})`
865
- ];
866
- for (const lease of paginated) {
867
- const rec = lease;
868
- const address = rec.address ?? rec["active-address"] ?? "unknown";
869
- const macAddress = rec["mac-address"] ?? rec["active-mac-address"] ?? "unknown";
870
- const hostname = rec["host-name"];
871
- const hostnameStr = hostname && hostname !== "" ? ` (${hostname})` : "";
872
- const status = rec.status ?? "unknown";
873
- lines.push(` ${address} ${macAddress}${hostnameStr} [${status}]`);
874
- }
876
+ const { items: paginated, total, hasMore } = paginate(leases, parsed.offset, parsed.limit);
875
877
  return {
876
- content: lines.join("\n"),
878
+ content: listSummary("DHCP leases", context.routerId, paginated.length, total, parsed.offset),
877
879
  structuredContent: {
878
880
  routerId: context.routerId,
879
881
  leases: paginated,
@@ -884,17 +886,17 @@ var listDhcpLeasesTool = {
884
886
  }
885
887
  };
886
888
  } catch (err) {
887
- throw enrichError(err, { routerId: context.routerId, tool: "list_dhcp_leases" });
889
+ throw toolError(err, context, "list_dhcp_leases");
888
890
  }
889
891
  }
890
892
  };
891
- var manageDhcpLeaseInputSchema = z5.object({
892
- routerId: z5.string().describe("Target router identifier from the router registry"),
893
- action: z5.enum(["make-static", "remove"]).describe(
893
+ var manageDhcpLeaseInputSchema = z6.object({
894
+ routerId,
895
+ action: z6.enum(["make-static", "remove"]).describe(
894
896
  "Action to perform: make-static converts a dynamic lease to static; remove deletes the lease"
895
897
  ),
896
- macAddress: z5.string().describe("MAC address of the lease \u2014 idempotency key (case-insensitive)"),
897
- dryRun: z5.boolean().default(false).describe("Preview changes without applying")
898
+ macAddress: z6.string().describe("MAC address of the lease \u2014 idempotency key (case-insensitive)"),
899
+ dryRun
898
900
  }).strict();
899
901
  var manageDhcpLeaseTool = {
900
902
  name: "manage_dhcp_lease",
@@ -986,22 +988,21 @@ var manageDhcpLeaseTool = {
986
988
  structuredContent: { action: "removed", macAddress: upperMac, id: existing[".id"] }
987
989
  };
988
990
  } catch (err) {
989
- if (err instanceof MikroMCPError) throw err;
990
- throw enrichError(err, { routerId: context.routerId, tool: "manage_dhcp_lease" });
991
+ throw toolError(err, context, "manage_dhcp_lease");
991
992
  }
992
993
  }
993
994
  };
994
995
  var dhcpTools = [listDhcpLeasesTool, manageDhcpLeaseTool];
995
996
 
996
997
  // src/domain/tools/route-tools.ts
997
- import { z as z6 } from "zod";
998
+ import { z as z7 } from "zod";
998
999
  var log5 = createLogger("route-tools");
999
- var listRoutesInputSchema = z6.object({
1000
- routerId: z6.string().describe("Target router identifier from the router registry"),
1001
- activeOnly: z6.boolean().default(false).describe("Return only active routes"),
1002
- staticOnly: z6.boolean().default(false).describe("Return only non-dynamic routes"),
1003
- limit: z6.number().int().min(1).max(500).default(100).describe("Maximum number of routes to return"),
1004
- offset: z6.number().int().min(0).default(0).describe("Offset for pagination")
1000
+ var listRoutesInputSchema = z7.object({
1001
+ routerId,
1002
+ activeOnly: z7.boolean().default(false).describe("Return only active routes"),
1003
+ staticOnly: z7.boolean().default(false).describe("Return only non-dynamic routes"),
1004
+ limit,
1005
+ offset
1005
1006
  }).strict();
1006
1007
  var listRoutesTool = {
1007
1008
  name: "list_routes",
@@ -1041,33 +1042,9 @@ var listRoutesTool = {
1041
1042
  return rec.dynamic !== true && rec.dynamic !== "true";
1042
1043
  });
1043
1044
  }
1044
- const total = routes.length;
1045
- const paginated = routes.slice(parsed.offset, parsed.offset + parsed.limit);
1046
- const hasMore = parsed.offset + parsed.limit < total;
1047
- const lines = [
1048
- `Routes on ${context.routerId}: ${total} total, showing ${paginated.length} (offset ${parsed.offset})`
1049
- ];
1050
- for (const route of paginated) {
1051
- const rec = route;
1052
- const dstAddress = rec["dst-address"] ?? "unknown";
1053
- const gateway = rec.gateway ?? rec["immediate-gw"] ?? "unknown";
1054
- const distance = rec.distance;
1055
- const active = rec.active === true || rec.active === "true";
1056
- const dynamic = rec.dynamic === true || rec.dynamic === "true";
1057
- let line = ` ${dstAddress} via ${gateway}`;
1058
- if (distance) {
1059
- line += ` [${distance}]`;
1060
- }
1061
- if (active) {
1062
- line += " ACTIVE";
1063
- }
1064
- if (dynamic) {
1065
- line += " DYNAMIC";
1066
- }
1067
- lines.push(line);
1068
- }
1045
+ const { items: paginated, total, hasMore } = paginate(routes, parsed.offset, parsed.limit);
1069
1046
  return {
1070
- content: lines.join("\n"),
1047
+ content: listSummary("Routes", context.routerId, paginated.length, total, parsed.offset),
1071
1048
  structuredContent: {
1072
1049
  routerId: context.routerId,
1073
1050
  routes: paginated,
@@ -1078,22 +1055,22 @@ var listRoutesTool = {
1078
1055
  }
1079
1056
  };
1080
1057
  } catch (err) {
1081
- throw enrichError(err, { routerId: context.routerId, tool: "list_routes" });
1058
+ throw toolError(err, context, "list_routes");
1082
1059
  }
1083
1060
  }
1084
1061
  };
1085
- var manageRouteInputSchema = z6.object({
1086
- routerId: z6.string().describe("Target router identifier from the router registry"),
1087
- action: z6.enum(["add", "remove"]).describe("Action to perform: add or remove a route"),
1062
+ var manageRouteInputSchema = z7.object({
1063
+ routerId,
1064
+ action: z7.enum(["add", "remove"]).describe("Action to perform: add or remove a route"),
1088
1065
  dstAddress: cidrSchema.describe(
1089
1066
  "Destination address in CIDR notation or plain IP (auto-converted to /32), e.g. 10.0.0.0/8 or 10.77.0.4"
1090
1067
  ),
1091
- gateway: z6.string().describe("Gateway IP address"),
1092
- distance: z6.number().int().min(1).max(255).default(1).describe("Route distance/metric (1-255)"),
1093
- comment: z6.string().max(255).optional().describe("Optional comment for the route"),
1094
- routingTable: z6.string().optional().describe("Routing table name (default: main). Use for policy routing with separate tables."),
1095
- disabled: z6.boolean().default(false).describe("Whether the route should be disabled"),
1096
- dryRun: z6.boolean().default(false).describe("If true, validate and return planned changes without applying")
1068
+ gateway: z7.string().describe("Gateway IP address"),
1069
+ distance: z7.number().int().min(1).max(255).default(1).describe("Route distance/metric (1-255)"),
1070
+ comment: z7.string().max(255).optional().describe("Optional comment for the route"),
1071
+ routingTable: z7.string().optional().describe("Routing table name (default: main). Use for policy routing with separate tables."),
1072
+ disabled: z7.boolean().default(false).describe("Whether the route should be disabled"),
1073
+ dryRun: z7.boolean().default(false).describe("If true, validate and return planned changes without applying")
1097
1074
  }).strict();
1098
1075
  function sanitizeComment2(comment) {
1099
1076
  if (comment === void 0) return void 0;
@@ -1251,15 +1228,14 @@ var manageRouteTool = {
1251
1228
  }
1252
1229
  });
1253
1230
  } catch (err) {
1254
- if (err instanceof MikroMCPError) throw err;
1255
- throw enrichError(err, { routerId: context.routerId, tool: "manage_route" });
1231
+ throw toolError(err, context, "manage_route");
1256
1232
  }
1257
1233
  }
1258
1234
  };
1259
1235
  var routeTools = [listRoutesTool, manageRouteTool];
1260
1236
 
1261
1237
  // src/domain/tools/firewall-tools.ts
1262
- import { z as z7 } from "zod";
1238
+ import { z as z8 } from "zod";
1263
1239
  var log6 = createLogger("firewall-tools");
1264
1240
  function tableToPath(table) {
1265
1241
  return table === "filter" ? "ip/firewall/filter" : "ip/firewall/nat";
@@ -1274,13 +1250,13 @@ async function findRuleByComment(context, path, comment) {
1274
1250
  });
1275
1251
  return results2.length > 0 ? results2[0] : void 0;
1276
1252
  }
1277
- var listFirewallRulesInputSchema = z7.object({
1278
- routerId: z7.string().describe("Target router identifier from the router registry"),
1279
- table: z7.enum(["filter", "nat"]).default("filter").describe("Firewall table to query: filter or nat"),
1280
- chain: z7.string().optional().describe("Filter rules by chain name (e.g. forward, input, srcnat)"),
1281
- disabled: z7.enum(["true", "false", "all"]).default("all").describe("Filter by disabled state: true, false, or all"),
1282
- limit: z7.number().int().min(1).max(500).default(100).describe("Maximum number of rules to return"),
1283
- offset: z7.number().int().min(0).default(0).describe("Offset for pagination")
1253
+ var listFirewallRulesInputSchema = z8.object({
1254
+ routerId,
1255
+ table: z8.enum(["filter", "nat"]).default("filter").describe("Firewall table to query: filter or nat"),
1256
+ chain: z8.string().optional().describe("Filter rules by chain name (e.g. forward, input, srcnat)"),
1257
+ disabled: z8.enum(["true", "false", "all"]).default("all").describe("Filter by disabled state: true, false, or all"),
1258
+ limit,
1259
+ offset
1284
1260
  }).strict();
1285
1261
  var listFirewallRulesTool = {
1286
1262
  name: "list_firewall_rules",
@@ -1323,38 +1299,15 @@ var listFirewallRulesTool = {
1323
1299
  return isDisabled === (parsed.disabled === "true");
1324
1300
  });
1325
1301
  }
1326
- const total = rules.length;
1327
- const paginated = rules.slice(parsed.offset, parsed.offset + parsed.limit);
1328
- const hasMore = parsed.offset + parsed.limit < total;
1329
- const lines = [
1330
- `Firewall ${parsed.table} rules on ${context.routerId}: ${total} total, showing ${paginated.length} (offset ${parsed.offset})`
1331
- ];
1332
- for (const rule of paginated) {
1333
- const rec = rule;
1334
- const chain = rec.chain ?? "?";
1335
- const action = rec.action ?? "?";
1336
- const isDisabled = rec.disabled === true || rec.disabled === "true";
1337
- let line = ` [${chain}]`;
1338
- if (rec.protocol !== void 0) {
1339
- line += ` ${rec.protocol}`;
1340
- }
1341
- if (rec["src-address"] !== void 0) {
1342
- line += ` src:${rec["src-address"]}`;
1343
- }
1344
- if (rec["dst-address"] !== void 0) {
1345
- line += ` dst:${rec["dst-address"]}`;
1346
- }
1347
- line += ` => ${action}`;
1348
- if (isDisabled) {
1349
- line += " [disabled]";
1350
- }
1351
- if (rec.comment !== void 0) {
1352
- line += ` // ${rec.comment}`;
1353
- }
1354
- lines.push(line);
1355
- }
1302
+ const { items: paginated, total, hasMore } = paginate(rules, parsed.offset, parsed.limit);
1356
1303
  return {
1357
- content: lines.join("\n"),
1304
+ content: listSummary(
1305
+ `Firewall ${parsed.table} rules`,
1306
+ context.routerId,
1307
+ paginated.length,
1308
+ total,
1309
+ parsed.offset
1310
+ ),
1358
1311
  structuredContent: {
1359
1312
  routerId: context.routerId,
1360
1313
  table: parsed.table,
@@ -1366,28 +1319,27 @@ var listFirewallRulesTool = {
1366
1319
  }
1367
1320
  };
1368
1321
  } catch (err) {
1369
- if (err instanceof MikroMCPError) throw err;
1370
- throw enrichError(err, { routerId: context.routerId, tool: "list_firewall_rules" });
1371
- }
1372
- }
1373
- };
1374
- var manageFirewallRuleInputSchema = z7.object({
1375
- routerId: z7.string().describe("Target router identifier from the router registry"),
1376
- table: z7.enum(["filter", "nat"]).default("filter").describe("Firewall table to manage: filter or nat"),
1377
- action: z7.enum(["add", "remove", "disable", "enable"]).describe("Action to perform: add, remove, disable, or enable a firewall rule"),
1378
- chain: z7.string().describe("Firewall chain (e.g. forward, input, output, srcnat, dstnat)"),
1379
- ruleAction: z7.string().describe("RouterOS rule action (e.g. accept, drop, reject, masquerade)"),
1380
- srcAddress: z7.string().optional().describe("Source address or network"),
1381
- dstAddress: z7.string().optional().describe("Destination address or network"),
1382
- protocol: z7.enum(["tcp", "udp", "icmp", "gre", "ospf", "all"]).optional().describe("Protocol to match"),
1383
- srcPort: z7.string().optional().describe("Source port or range"),
1384
- dstPort: z7.string().optional().describe("Destination port or range"),
1385
- inInterface: z7.string().optional().describe("Incoming interface"),
1386
- outInterface: z7.string().optional().describe("Outgoing interface"),
1387
- comment: z7.string().max(255).optional().describe("Comment to identify the rule (used as idempotency key)"),
1388
- disabled: z7.boolean().default(false).describe("Whether the rule should be disabled"),
1389
- placeBefore: z7.string().optional().describe("Place the new rule before this rule ID"),
1390
- dryRun: z7.boolean().default(false).describe("If true, validate and return planned changes without applying")
1322
+ throw toolError(err, context, "list_firewall_rules");
1323
+ }
1324
+ }
1325
+ };
1326
+ var manageFirewallRuleInputSchema = z8.object({
1327
+ routerId,
1328
+ table: z8.enum(["filter", "nat"]).default("filter").describe("Firewall table to manage: filter or nat"),
1329
+ action: z8.enum(["add", "remove", "disable", "enable"]).describe("Action to perform: add, remove, disable, or enable a firewall rule"),
1330
+ chain: z8.string().describe("Firewall chain (e.g. forward, input, output, srcnat, dstnat)"),
1331
+ ruleAction: z8.string().describe("RouterOS rule action (e.g. accept, drop, reject, masquerade)"),
1332
+ srcAddress: z8.string().optional().describe("Source address or network"),
1333
+ dstAddress: z8.string().optional().describe("Destination address or network"),
1334
+ protocol: z8.enum(["tcp", "udp", "icmp", "gre", "ospf", "all"]).optional().describe("Protocol to match"),
1335
+ srcPort: z8.string().optional().describe("Source port or range"),
1336
+ dstPort: z8.string().optional().describe("Destination port or range"),
1337
+ inInterface: z8.string().optional().describe("Incoming interface"),
1338
+ outInterface: z8.string().optional().describe("Outgoing interface"),
1339
+ comment: z8.string().max(255).optional().describe("Comment to identify the rule (used as idempotency key)"),
1340
+ disabled: z8.boolean().default(false).describe("Whether the rule should be disabled"),
1341
+ placeBefore: z8.string().optional().describe("Place the new rule before this rule ID"),
1342
+ dryRun: z8.boolean().default(false).describe("If true, validate and return planned changes without applying")
1391
1343
  }).strict();
1392
1344
  var manageFirewallRuleTool = {
1393
1345
  name: "manage_firewall_rule",
@@ -1419,13 +1371,17 @@ var manageFirewallRuleTool = {
1419
1371
  if (comment !== void 0) {
1420
1372
  const existing = await findRuleByComment(context, path, comment);
1421
1373
  if (existing) {
1374
+ const wantProtocol = parsed.protocol !== void 0 && parsed.protocol !== "all" ? parsed.protocol : "";
1422
1375
  const sameChain = existing.chain === parsed.chain;
1423
1376
  const sameRuleAction = existing.action === parsed.ruleAction;
1377
+ const sameSrcAddress = (existing["src-address"] ?? "") === (parsed.srcAddress ?? "");
1378
+ const sameDstAddress = (existing["dst-address"] ?? "") === (parsed.dstAddress ?? "");
1379
+ const sameProtocol = (existing.protocol ?? "") === wantProtocol;
1424
1380
  const sameSrcPort = (existing["src-port"] ?? "") === (parsed.srcPort ?? "");
1425
1381
  const sameDstPort = (existing["dst-port"] ?? "") === (parsed.dstPort ?? "");
1426
1382
  const sameInInterface = (existing["in-interface"] ?? "") === (parsed.inInterface ?? "");
1427
1383
  const sameOutInterface = (existing["out-interface"] ?? "") === (parsed.outInterface ?? "");
1428
- if (sameChain && sameRuleAction && sameSrcPort && sameDstPort && sameInInterface && sameOutInterface) {
1384
+ if (sameChain && sameRuleAction && sameSrcAddress && sameDstAddress && sameProtocol && sameSrcPort && sameDstPort && sameInInterface && sameOutInterface) {
1429
1385
  return {
1430
1386
  content: `Firewall ${parsed.table} rule with comment "${comment}" already exists. No changes made.`,
1431
1387
  structuredContent: { action: "already_exists", rule: existing }
@@ -1439,6 +1395,9 @@ var manageFirewallRuleTool = {
1439
1395
  existing: {
1440
1396
  chain: existing.chain,
1441
1397
  action: existing.action,
1398
+ "src-address": existing["src-address"],
1399
+ "dst-address": existing["dst-address"],
1400
+ protocol: existing.protocol,
1442
1401
  "src-port": existing["src-port"],
1443
1402
  "dst-port": existing["dst-port"],
1444
1403
  "in-interface": existing["in-interface"],
@@ -1447,6 +1406,9 @@ var manageFirewallRuleTool = {
1447
1406
  requested: {
1448
1407
  chain: parsed.chain,
1449
1408
  action: parsed.ruleAction,
1409
+ "src-address": parsed.srcAddress,
1410
+ "dst-address": parsed.dstAddress,
1411
+ protocol: wantProtocol || void 0,
1450
1412
  "src-port": parsed.srcPort,
1451
1413
  "dst-port": parsed.dstPort,
1452
1414
  "in-interface": parsed.inInterface,
@@ -1598,22 +1560,21 @@ var manageFirewallRuleTool = {
1598
1560
  }
1599
1561
  });
1600
1562
  } catch (err) {
1601
- if (err instanceof MikroMCPError) throw err;
1602
- throw enrichError(err, { routerId: context.routerId, tool: "manage_firewall_rule" });
1563
+ throw toolError(err, context, "manage_firewall_rule");
1603
1564
  }
1604
1565
  }
1605
1566
  };
1606
1567
  var firewallTools = [listFirewallRulesTool, manageFirewallRuleTool];
1607
1568
 
1608
1569
  // src/domain/tools/diagnostic-tools.ts
1609
- import { z as z8 } from "zod";
1570
+ import { z as z9 } from "zod";
1610
1571
  var log7 = createLogger("diagnostic-tools");
1611
- var pingInputSchema = z8.object({
1612
- routerId: z8.string().describe("Target router identifier from the router registry"),
1613
- address: z8.string().describe("Target IP address or hostname to ping"),
1614
- count: z8.number().int().min(1).max(20).default(4).describe("Number of ICMP echo requests (1\u201320)"),
1615
- size: z8.number().int().min(14).max(65535).default(56).describe("Packet size in bytes (14\u201365535)"),
1616
- routingTable: z8.string().optional().describe("Routing table to use for the ping")
1572
+ var pingInputSchema = z9.object({
1573
+ routerId,
1574
+ address: z9.string().describe("Target IP address or hostname to ping"),
1575
+ count: z9.number().int().min(1).max(20).default(4).describe("Number of ICMP echo requests (1\u201320)"),
1576
+ size: z9.number().int().min(14).max(65535).default(56).describe("Packet size in bytes (14\u201365535)"),
1577
+ routingTable: z9.string().optional().describe("Routing table to use for the ping")
1617
1578
  }).strict();
1618
1579
  var pingTool = {
1619
1580
  name: "ping",
@@ -1658,15 +1619,15 @@ var pingTool = {
1658
1619
  }
1659
1620
  };
1660
1621
  } catch (err) {
1661
- throw enrichError(err, { routerId: context.routerId, tool: "ping" });
1622
+ throw toolError(err, context, "ping");
1662
1623
  }
1663
1624
  }
1664
1625
  };
1665
- var tracerouteInputSchema = z8.object({
1666
- routerId: z8.string().describe("Target router identifier from the router registry"),
1667
- address: z8.string().describe("Target IP address or hostname to trace"),
1668
- count: z8.number().int().min(1).max(5).default(3).describe("Probes per hop (1\u20135)"),
1669
- maxHops: z8.number().int().min(1).max(30).default(15).describe("Maximum number of hops (1\u201330)")
1626
+ var tracerouteInputSchema = z9.object({
1627
+ routerId,
1628
+ address: z9.string().describe("Target IP address or hostname to trace"),
1629
+ count: z9.number().int().min(1).max(5).default(3).describe("Probes per hop (1\u20135)"),
1630
+ maxHops: z9.number().int().min(1).max(30).default(15).describe("Maximum number of hops (1\u201330)")
1670
1631
  }).strict();
1671
1632
  var tracerouteTool = {
1672
1633
  name: "traceroute",
@@ -1713,16 +1674,16 @@ var tracerouteTool = {
1713
1674
  }
1714
1675
  };
1715
1676
  } catch (err) {
1716
- throw enrichError(err, { routerId: context.routerId, tool: "traceroute" });
1677
+ throw toolError(err, context, "traceroute");
1717
1678
  }
1718
1679
  }
1719
1680
  };
1720
- var torchInputSchema = z8.object({
1721
- routerId: z8.string().describe("Target router identifier from the router registry"),
1722
- interface: z8.string().describe("Interface name to monitor (e.g. ether1, bridge1)"),
1723
- duration: z8.number().int().min(1).max(30).default(5).describe("Capture duration in seconds (1\u201330)"),
1724
- srcAddress: z8.string().optional().describe("Filter by source IP address"),
1725
- dstAddress: z8.string().optional().describe("Filter by destination IP address")
1681
+ var torchInputSchema = z9.object({
1682
+ routerId,
1683
+ interface: z9.string().describe("Interface name to monitor (e.g. ether1, bridge1)"),
1684
+ duration: z9.number().int().min(1).max(30).default(5).describe("Capture duration in seconds (1\u201330)"),
1685
+ srcAddress: z9.string().optional().describe("Filter by source IP address"),
1686
+ dstAddress: z9.string().optional().describe("Filter by destination IP address")
1726
1687
  }).strict();
1727
1688
  function parseTorchOutput(output) {
1728
1689
  const flows = [];
@@ -1778,19 +1739,19 @@ var torchTool = {
1778
1739
  }
1779
1740
  };
1780
1741
  } catch (err) {
1781
- throw enrichError(err, { routerId: context.routerId, tool: "torch" });
1742
+ throw toolError(err, context, "torch");
1782
1743
  }
1783
1744
  }
1784
1745
  };
1785
- var getLogInputSchema = z8.object({
1786
- routerId: z8.string().describe("Target router identifier from the router registry"),
1787
- limit: z8.number().int().min(1).max(500).default(100).describe("Maximum entries to return (1\u2013500)"),
1788
- offset: z8.number().int().min(0).default(0).describe("Pagination offset"),
1789
- topics: z8.array(z8.string()).optional().describe(
1746
+ var getLogInputSchema = z9.object({
1747
+ routerId,
1748
+ limit,
1749
+ offset,
1750
+ topics: z9.array(z9.string()).optional().describe(
1790
1751
  'Filter entries whose topics field contains any of these strings (e.g. ["firewall", "dhcp"])'
1791
1752
  ),
1792
- prefix: z8.string().optional().describe("Substring to match against the log message"),
1793
- sinceMinutes: z8.number().int().min(1).max(1440).optional().describe("Only return entries from the last N minutes (1\u20131440)")
1753
+ prefix: z9.string().optional().describe("Substring to match against the log message"),
1754
+ sinceMinutes: z9.number().int().min(1).max(1440).optional().describe("Only return entries from the last N minutes (1\u20131440)")
1794
1755
  }).strict();
1795
1756
  function parseRouterOsTimestamp(ts, now) {
1796
1757
  const timeOnly = /^(\d{2}):(\d{2}):(\d{2})$/.exec(ts);
@@ -1871,8 +1832,7 @@ var getLogTool = {
1871
1832
  return parsedTs >= cutoff;
1872
1833
  });
1873
1834
  }
1874
- const total = filtered.length;
1875
- const paginated = filtered.slice(parsed.offset, parsed.offset + parsed.limit);
1835
+ const { items: paginated, total } = paginate(filtered, parsed.offset, parsed.limit);
1876
1836
  const lines = [
1877
1837
  `Log on ${context.routerId}: ${total} matching entries, showing ${paginated.length} (offset ${parsed.offset})`
1878
1838
  ];
@@ -1891,14 +1851,14 @@ var getLogTool = {
1891
1851
  }
1892
1852
  };
1893
1853
  } catch (err) {
1894
- throw enrichError(err, { routerId: context.routerId, tool: "get_log" });
1854
+ throw toolError(err, context, "get_log");
1895
1855
  }
1896
1856
  }
1897
1857
  };
1898
1858
  var diagnosticTools = [pingTool, tracerouteTool, torchTool, getLogTool];
1899
1859
 
1900
1860
  // src/domain/tools/system-ops-tools.ts
1901
- import { z as z9 } from "zod";
1861
+ import { z as z10 } from "zod";
1902
1862
 
1903
1863
  // src/domain/tools/command-guard.ts
1904
1864
  var BUILTIN_DENY_PATTERNS = [
@@ -1961,8 +1921,8 @@ function checkCommand(command, policy) {
1961
1921
 
1962
1922
  // src/domain/tools/system-ops-tools.ts
1963
1923
  var log8 = createLogger("system-ops-tools");
1964
- var getSystemClockInputSchema = z9.object({
1965
- routerId: z9.string().describe("Target router identifier from the router registry")
1924
+ var getSystemClockInputSchema = z10.object({
1925
+ routerId
1966
1926
  }).strict();
1967
1927
  var getSystemClockTool = {
1968
1928
  name: "get_system_clock",
@@ -1992,16 +1952,16 @@ var getSystemClockTool = {
1992
1952
  }
1993
1953
  };
1994
1954
  } catch (err) {
1995
- throw enrichError(err, { routerId: context.routerId, tool: "get_system_clock" });
1955
+ throw toolError(err, context, "get_system_clock");
1996
1956
  }
1997
1957
  }
1998
1958
  };
1999
- var setSystemClockInputSchema = z9.object({
2000
- routerId: z9.string().describe("Target router identifier from the router registry"),
2001
- date: z9.string().optional().describe("Date in RouterOS format: mon/dd/yyyy (e.g. jan/02/2006)"),
2002
- time: z9.string().optional().describe("Time in RouterOS format: hh:mm:ss (e.g. 15:04:05)"),
2003
- timeZoneName: z9.string().optional().describe("IANA timezone name (e.g. Europe/London, UTC)"),
2004
- dryRun: z9.boolean().default(false).describe("Preview changes without applying")
1959
+ var setSystemClockInputSchema = z10.object({
1960
+ routerId,
1961
+ date: z10.string().optional().describe("Date in RouterOS format: mon/dd/yyyy (e.g. jan/02/2006)"),
1962
+ time: z10.string().optional().describe("Time in RouterOS format: hh:mm:ss (e.g. 15:04:05)"),
1963
+ timeZoneName: z10.string().optional().describe("IANA timezone name (e.g. Europe/London, UTC)"),
1964
+ dryRun
2005
1965
  }).strict();
2006
1966
  var setSystemClockTool = {
2007
1967
  name: "set_system_clock",
@@ -2059,15 +2019,14 @@ var setSystemClockTool = {
2059
2019
  structuredContent: { action: "updated", routerId: context.routerId, diff }
2060
2020
  };
2061
2021
  } catch (err) {
2062
- if (err instanceof MikroMCPError) throw err;
2063
- throw enrichError(err, { routerId: context.routerId, tool: "set_system_clock" });
2022
+ throw toolError(err, context, "set_system_clock");
2064
2023
  }
2065
2024
  }
2066
2025
  };
2067
- var rebootInputSchema = z9.object({
2068
- routerId: z9.string().describe("Target router identifier from the router registry"),
2069
- delay: z9.number().int().min(0).max(3600).default(0).describe("Seconds before rebooting (0\u20133600)"),
2070
- dryRun: z9.boolean().default(false).describe("Preview the reboot without executing")
2026
+ var rebootInputSchema = z10.object({
2027
+ routerId,
2028
+ delay: z10.number().int().min(0).max(3600).default(0).describe("Seconds before rebooting (0\u20133600)"),
2029
+ dryRun: z10.boolean().default(false).describe("Preview the reboot without executing")
2071
2030
  }).strict();
2072
2031
  var rebootTool = {
2073
2032
  name: "reboot",
@@ -2099,20 +2058,20 @@ var rebootTool = {
2099
2058
  structuredContent: { action: "rebooting", delay: parsed.delay, routerId: context.routerId }
2100
2059
  };
2101
2060
  } catch (err) {
2102
- throw enrichError(err, { routerId: context.routerId, tool: "reboot" });
2061
+ throw toolError(err, context, "reboot");
2103
2062
  }
2104
2063
  }
2105
2064
  };
2106
2065
  var OUTPUT_MAX_CHARS = 4e3;
2107
- var runCommandInputSchema = z9.object({
2108
- routerId: z9.string().describe("Target router identifier from the router registry"),
2109
- command: z9.string().min(1).describe("RouterOS console command to execute"),
2110
- dryRun: z9.boolean().default(false).describe("Preview the command without executing (validates allow/deny policy only)")
2066
+ var runCommandInputSchema = z10.object({
2067
+ routerId,
2068
+ command: z10.string().min(1).describe("RouterOS console command to execute"),
2069
+ dryRun: z10.boolean().default(false).describe("Preview the command without executing (validates allow/deny policy only)")
2111
2070
  }).strict();
2112
2071
  var runCommandTool = {
2113
2072
  name: "run_command",
2114
2073
  title: "Run Command",
2115
- description: "Execute an arbitrary RouterOS console command via SSH. Protected by a configurable allow/deny policy \u2014 built-in deny list blocks destructive commands; optionally restrict further with an explicit allow list (cmdAllow in routers.yaml or MIKROMCP_CMD_ALLOW). Use dedicated tools (reboot, etc.) for controlled operations. Output capped at 4000 characters.",
2074
+ description: "Execute an arbitrary RouterOS console command via SSH. Guarded by an allow/deny policy (built-in deny list blocks destructive commands; tighten via cmdAllow in routers.yaml or MIKROMCP_CMD_ALLOW). Prefer dedicated tools (reboot, etc.) where available. Output capped at 4000 characters.",
2116
2075
  inputSchema: runCommandInputSchema,
2117
2076
  annotations: {
2118
2077
  readOnlyHint: false,
@@ -2158,8 +2117,7 @@ ${output}`;
2158
2117
  }
2159
2118
  };
2160
2119
  } catch (err) {
2161
- if (err instanceof MikroMCPError) throw err;
2162
- throw enrichError(err, { routerId: context.routerId, tool: "run_command" });
2120
+ throw toolError(err, context, "run_command");
2163
2121
  }
2164
2122
  }
2165
2123
  };
@@ -2171,12 +2129,12 @@ var systemOpsTools = [
2171
2129
  ];
2172
2130
 
2173
2131
  // src/domain/tools/bridge-tools.ts
2174
- import { z as z10 } from "zod";
2132
+ import { z as z11 } from "zod";
2175
2133
  var log9 = createLogger("bridge-tools");
2176
- var listBridgesInputSchema = z10.object({
2177
- routerId: z10.string().describe("Target router identifier from the router registry"),
2178
- limit: z10.number().int().min(1).max(500).default(100).describe("Maximum number of bridges to return"),
2179
- offset: z10.number().int().min(0).default(0).describe("Offset for pagination")
2134
+ var listBridgesInputSchema = z11.object({
2135
+ routerId,
2136
+ limit,
2137
+ offset
2180
2138
  }).strict();
2181
2139
  var listBridgesTool = {
2182
2140
  name: "list_bridges",
@@ -2208,19 +2166,9 @@ var listBridgesTool = {
2208
2166
  const rec = b;
2209
2167
  return { ...rec, ports: portsByBridge[rec.name] ?? [] };
2210
2168
  });
2211
- const total = enriched.length;
2212
- const paginated = enriched.slice(parsed.offset, parsed.offset + parsed.limit);
2213
- const hasMore = parsed.offset + parsed.limit < total;
2214
- const lines = [`Bridges on ${context.routerId}: ${total} total`];
2215
- for (const b of paginated) {
2216
- const bRec = b;
2217
- const portList = b.ports.map((p) => p.interface).join(", ");
2218
- lines.push(
2219
- ` ${String(bRec.name)} [${b.ports.length} ports${portList ? ": " + portList : ""}]`
2220
- );
2221
- }
2169
+ const { items: paginated, total, hasMore } = paginate(enriched, parsed.offset, parsed.limit);
2222
2170
  return {
2223
- content: lines.join("\n"),
2171
+ content: listSummary("Bridges", context.routerId, paginated.length, total, parsed.offset),
2224
2172
  structuredContent: {
2225
2173
  routerId: context.routerId,
2226
2174
  bridges: paginated,
@@ -2231,17 +2179,17 @@ var listBridgesTool = {
2231
2179
  }
2232
2180
  };
2233
2181
  } catch (err) {
2234
- throw enrichError(err, { routerId: context.routerId, tool: "list_bridges" });
2182
+ throw toolError(err, context, "list_bridges");
2235
2183
  }
2236
2184
  }
2237
2185
  };
2238
- var manageBridgeInputSchema = z10.object({
2239
- routerId: z10.string().describe("Target router identifier from the router registry"),
2240
- action: z10.enum(["create", "remove"]).describe("Action to perform"),
2241
- name: z10.string().regex(/^[a-zA-Z0-9_-]+$/).max(15).describe("Bridge interface name"),
2242
- comment: z10.string().max(255).optional().describe("Optional comment"),
2243
- disabled: z10.boolean().default(false).describe("Whether the bridge should be disabled"),
2244
- dryRun: z10.boolean().default(false).describe("Preview changes without applying")
2186
+ var manageBridgeInputSchema = z11.object({
2187
+ routerId,
2188
+ action: z11.enum(["create", "remove"]).describe("Action to perform"),
2189
+ name: z11.string().regex(/^[a-zA-Z0-9_-]+$/).max(15).describe("Bridge interface name"),
2190
+ comment: z11.string().max(255).optional().describe("Optional comment"),
2191
+ disabled: z11.boolean().default(false).describe("Whether the bridge should be disabled"),
2192
+ dryRun
2245
2193
  }).strict();
2246
2194
  var manageBridgeTool = {
2247
2195
  name: "manage_bridge",
@@ -2325,17 +2273,16 @@ var manageBridgeTool = {
2325
2273
  structuredContent: { action: "removed", name: parsed.name, id: rec[".id"] }
2326
2274
  };
2327
2275
  } catch (err) {
2328
- if (err instanceof MikroMCPError) throw err;
2329
- throw enrichError(err, { routerId: context.routerId, tool: "manage_bridge" });
2276
+ throw toolError(err, context, "manage_bridge");
2330
2277
  }
2331
2278
  }
2332
2279
  };
2333
- var manageBridgePortInputSchema = z10.object({
2334
- routerId: z10.string().describe("Target router identifier from the router registry"),
2335
- action: z10.enum(["add", "remove"]).describe("Action to perform"),
2336
- bridge: z10.string().describe("Bridge interface name"),
2337
- interface: z10.string().describe("Interface to add or remove as a bridge port"),
2338
- dryRun: z10.boolean().default(false).describe("Preview changes without applying")
2280
+ var manageBridgePortInputSchema = z11.object({
2281
+ routerId,
2282
+ action: z11.enum(["add", "remove"]).describe("Action to perform"),
2283
+ bridge: z11.string().describe("Bridge interface name"),
2284
+ interface: z11.string().describe("Interface to add or remove as a bridge port"),
2285
+ dryRun
2339
2286
  }).strict();
2340
2287
  var manageBridgePortTool = {
2341
2288
  name: "manage_bridge_port",
@@ -2427,8 +2374,7 @@ var manageBridgePortTool = {
2427
2374
  }
2428
2375
  };
2429
2376
  } catch (err) {
2430
- if (err instanceof MikroMCPError) throw err;
2431
- throw enrichError(err, { routerId: context.routerId, tool: "manage_bridge_port" });
2377
+ throw toolError(err, context, "manage_bridge_port");
2432
2378
  }
2433
2379
  }
2434
2380
  };
@@ -2439,7 +2385,7 @@ var bridgeTools = [
2439
2385
  ];
2440
2386
 
2441
2387
  // src/domain/tools/wifi-tools.ts
2442
- import { z as z11 } from "zod";
2388
+ import { z as z12 } from "zod";
2443
2389
  var log10 = createLogger("wifi-tools");
2444
2390
  function wifiPath(rosVersion) {
2445
2391
  return rosVersion.startsWith("7") ? "interface/wifi" : "interface/wireless";
@@ -2447,10 +2393,10 @@ function wifiPath(rosVersion) {
2447
2393
  function wifiRegistrationPath(rosVersion) {
2448
2394
  return rosVersion.startsWith("7") ? "interface/wifi/registration-table" : "interface/wireless/registration-table";
2449
2395
  }
2450
- var listWifiInputSchema = z11.object({
2451
- routerId: z11.string().describe("Target router identifier from the router registry"),
2452
- limit: z11.number().int().min(1).max(500).default(100).describe("Maximum number of interfaces to return"),
2453
- offset: z11.number().int().min(0).default(0).describe("Offset for pagination")
2396
+ var listWifiInputSchema = z12.object({
2397
+ routerId,
2398
+ limit,
2399
+ offset
2454
2400
  }).strict();
2455
2401
  var listWifiTool = {
2456
2402
  name: "list_wifi_interfaces",
@@ -2472,17 +2418,9 @@ var listWifiTool = {
2472
2418
  limit: void 0,
2473
2419
  offset: void 0
2474
2420
  });
2475
- const total = interfaces.length;
2476
- const paginated = interfaces.slice(parsed.offset, parsed.offset + parsed.limit);
2477
- const hasMore = parsed.offset + parsed.limit < total;
2478
- const lines = [`WiFi interfaces on ${context.routerId}: ${total} total`];
2479
- for (const iface of paginated) {
2480
- const rec = iface;
2481
- const status = rec.disabled === "true" ? "DISABLED" : "ENABLED";
2482
- lines.push(` ${rec.name} [${rec.ssid ?? "no-ssid"}] ${status}`);
2483
- }
2421
+ const { items: paginated, total, hasMore } = paginate(interfaces, parsed.offset, parsed.limit);
2484
2422
  return {
2485
- content: lines.join("\n"),
2423
+ content: listSummary("WiFi interfaces", context.routerId, paginated.length, total, parsed.offset),
2486
2424
  structuredContent: {
2487
2425
  routerId: context.routerId,
2488
2426
  interfaces: paginated,
@@ -2493,15 +2431,15 @@ var listWifiTool = {
2493
2431
  }
2494
2432
  };
2495
2433
  } catch (err) {
2496
- throw enrichError(err, { routerId: context.routerId, tool: "list_wifi_interfaces" });
2434
+ throw toolError(err, context, "list_wifi_interfaces");
2497
2435
  }
2498
2436
  }
2499
2437
  };
2500
- var listWifiClientsInputSchema = z11.object({
2501
- routerId: z11.string().describe("Target router identifier from the router registry"),
2502
- interface: z11.string().optional().describe("Filter by WiFi interface name"),
2503
- limit: z11.number().int().min(1).max(500).default(100).describe("Maximum number of clients to return"),
2504
- offset: z11.number().int().min(0).default(0).describe("Offset for pagination")
2438
+ var listWifiClientsInputSchema = z12.object({
2439
+ routerId,
2440
+ interface: z12.string().optional().describe("Filter by WiFi interface name"),
2441
+ limit,
2442
+ offset
2505
2443
  }).strict();
2506
2444
  var listWifiClientsTool = {
2507
2445
  name: "list_wifi_clients",
@@ -2521,18 +2459,9 @@ var listWifiClientsTool = {
2521
2459
  const path = wifiRegistrationPath(context.routerConfig.rosVersion);
2522
2460
  const filter = parsed.interface ? { interface: parsed.interface } : void 0;
2523
2461
  const allClients = await context.routerClient.get(path, { filter });
2524
- const total = allClients.length;
2525
- const clients = allClients.slice(parsed.offset, parsed.offset + parsed.limit);
2526
- const hasMore = parsed.offset + parsed.limit < total;
2527
- const lines = [`WiFi clients on ${context.routerId}: ${total} total`];
2528
- for (const c of clients) {
2529
- const rec = c;
2530
- lines.push(
2531
- ` ${rec["mac-address"] ?? rec[".id"]} on ${rec.interface ?? "unknown"} signal=${rec["signal-strength"] ?? "?"}`
2532
- );
2533
- }
2462
+ const { items: clients, total, hasMore } = paginate(allClients, parsed.offset, parsed.limit);
2534
2463
  return {
2535
- content: lines.join("\n"),
2464
+ content: listSummary("WiFi clients", context.routerId, clients.length, total, parsed.offset),
2536
2465
  structuredContent: {
2537
2466
  routerId: context.routerId,
2538
2467
  clients,
@@ -2543,16 +2472,16 @@ var listWifiClientsTool = {
2543
2472
  }
2544
2473
  };
2545
2474
  } catch (err) {
2546
- throw enrichError(err, { routerId: context.routerId, tool: "list_wifi_clients" });
2475
+ throw toolError(err, context, "list_wifi_clients");
2547
2476
  }
2548
2477
  }
2549
2478
  };
2550
- var manageWifiInputSchema = z11.object({
2551
- routerId: z11.string().describe("Target router identifier from the router registry"),
2552
- name: z11.string().describe("WiFi interface name (e.g. wifi1, wlan1)"),
2553
- disabled: z11.boolean().optional().describe("Set to true to disable, false to enable"),
2554
- ssid: z11.string().max(32).optional().describe("New SSID to set"),
2555
- dryRun: z11.boolean().default(false).describe("Preview changes without applying")
2479
+ var manageWifiInputSchema = z12.object({
2480
+ routerId,
2481
+ name: z12.string().describe("WiFi interface name (e.g. wifi1, wlan1)"),
2482
+ disabled: z12.boolean().optional().describe("Set to true to disable, false to enable"),
2483
+ ssid: z12.string().max(32).optional().describe("New SSID to set"),
2484
+ dryRun
2556
2485
  }).strict();
2557
2486
  var manageWifiTool = {
2558
2487
  name: "manage_wifi_interface",
@@ -2629,20 +2558,19 @@ var manageWifiTool = {
2629
2558
  structuredContent: { action: "updated", name: parsed.name, changes: diff }
2630
2559
  };
2631
2560
  } catch (err) {
2632
- if (err instanceof MikroMCPError) throw err;
2633
- throw enrichError(err, { routerId: context.routerId, tool: "manage_wifi_interface" });
2561
+ throw toolError(err, context, "manage_wifi_interface");
2634
2562
  }
2635
2563
  }
2636
2564
  };
2637
2565
  var wifiTools = [listWifiTool, listWifiClientsTool, manageWifiTool];
2638
2566
 
2639
2567
  // src/domain/tools/wireguard-tools.ts
2640
- import { z as z12 } from "zod";
2568
+ import { z as z13 } from "zod";
2641
2569
  var log11 = createLogger("wireguard-tools");
2642
- var listWgInputSchema = z12.object({
2643
- routerId: z12.string().describe("Target router identifier from the router registry"),
2644
- limit: z12.number().int().min(1).max(500).default(100).describe("Maximum number of interfaces to return"),
2645
- offset: z12.number().int().min(0).default(0).describe("Offset for pagination")
2570
+ var listWgInputSchema = z13.object({
2571
+ routerId,
2572
+ limit,
2573
+ offset
2646
2574
  }).strict();
2647
2575
  var listWgTool = {
2648
2576
  name: "list_wireguard_interfaces",
@@ -2663,17 +2591,15 @@ var listWgTool = {
2663
2591
  limit: void 0,
2664
2592
  offset: void 0
2665
2593
  });
2666
- const total = interfaces.length;
2667
- const paginated = interfaces.slice(parsed.offset, parsed.offset + parsed.limit);
2668
- const hasMore = parsed.offset + parsed.limit < total;
2669
- const lines = [`WireGuard interfaces on ${context.routerId}: ${total} total`];
2670
- for (const iface of paginated) {
2671
- const rec = iface;
2672
- const running = rec.running === "true" ? "UP" : "DOWN";
2673
- lines.push(` ${rec.name} [port=${rec["listen-port"] ?? "?"}] ${running}`);
2674
- }
2594
+ const { items: paginated, total, hasMore } = paginate(interfaces, parsed.offset, parsed.limit);
2675
2595
  return {
2676
- content: lines.join("\n"),
2596
+ content: listSummary(
2597
+ "WireGuard interfaces",
2598
+ context.routerId,
2599
+ paginated.length,
2600
+ total,
2601
+ parsed.offset
2602
+ ),
2677
2603
  structuredContent: {
2678
2604
  routerId: context.routerId,
2679
2605
  interfaces: paginated,
@@ -2684,15 +2610,15 @@ var listWgTool = {
2684
2610
  }
2685
2611
  };
2686
2612
  } catch (err) {
2687
- throw enrichError(err, { routerId: context.routerId, tool: "list_wireguard_interfaces" });
2613
+ throw toolError(err, context, "list_wireguard_interfaces");
2688
2614
  }
2689
2615
  }
2690
2616
  };
2691
- var listPeersInputSchema = z12.object({
2692
- routerId: z12.string().describe("Target router identifier from the router registry"),
2693
- interface: z12.string().optional().describe("Filter by WireGuard interface name"),
2694
- limit: z12.number().int().min(1).max(500).default(100).describe("Maximum number of peers to return"),
2695
- offset: z12.number().int().min(0).default(0).describe("Offset for pagination")
2617
+ var listPeersInputSchema = z13.object({
2618
+ routerId,
2619
+ interface: z13.string().optional().describe("Filter by WireGuard interface name"),
2620
+ limit,
2621
+ offset
2696
2622
  }).strict();
2697
2623
  var listPeersTool = {
2698
2624
  name: "list_wireguard_peers",
@@ -2716,19 +2642,9 @@ var listPeersTool = {
2716
2642
  const allPeers = await context.routerClient.get("interface/wireguard/peers", {
2717
2643
  filter
2718
2644
  });
2719
- const total = allPeers.length;
2720
- const peers = allPeers.slice(parsed.offset, parsed.offset + parsed.limit);
2721
- const hasMore = parsed.offset + parsed.limit < total;
2722
- const lines = [`WireGuard peers on ${context.routerId}: ${total} total`];
2723
- for (const peer of peers) {
2724
- const rec = peer;
2725
- const key2 = rec["public-key"] ? rec["public-key"].slice(0, 8) + "\u2026" : "unknown";
2726
- lines.push(
2727
- ` [${rec.interface ?? "?"}] ${key2} last-handshake=${rec["last-handshake"] ?? "never"}`
2728
- );
2729
- }
2645
+ const { items: peers, total, hasMore } = paginate(allPeers, parsed.offset, parsed.limit);
2730
2646
  return {
2731
- content: lines.join("\n"),
2647
+ content: listSummary("WireGuard peers", context.routerId, peers.length, total, parsed.offset),
2732
2648
  structuredContent: {
2733
2649
  routerId: context.routerId,
2734
2650
  peers,
@@ -2739,19 +2655,19 @@ var listPeersTool = {
2739
2655
  }
2740
2656
  };
2741
2657
  } catch (err) {
2742
- throw enrichError(err, { routerId: context.routerId, tool: "list_wireguard_peers" });
2658
+ throw toolError(err, context, "list_wireguard_peers");
2743
2659
  }
2744
2660
  }
2745
2661
  };
2746
- var managePeerInputSchema = z12.object({
2747
- routerId: z12.string().describe("Target router identifier from the router registry"),
2748
- action: z12.enum(["add", "remove"]).describe("Action to perform"),
2749
- interface: z12.string().describe("WireGuard interface name (e.g. wg0)"),
2750
- publicKey: z12.string().min(1).describe("Peer public key in base64 format (44 characters)"),
2751
- allowedAddress: z12.string().optional().describe("Allowed IP address/CIDR for this peer (e.g. 10.0.0.2/32)"),
2752
- endpoint: z12.string().optional().describe("Peer endpoint as IP:port (e.g. 1.2.3.4:51820)"),
2753
- comment: z12.string().max(255).optional().describe("Optional comment"),
2754
- dryRun: z12.boolean().default(false).describe("Preview changes without applying")
2662
+ var managePeerInputSchema = z13.object({
2663
+ routerId,
2664
+ action: z13.enum(["add", "remove"]).describe("Action to perform"),
2665
+ interface: z13.string().describe("WireGuard interface name (e.g. wg0)"),
2666
+ publicKey: z13.string().min(1).describe("Peer public key in base64 format (44 characters)"),
2667
+ allowedAddress: z13.string().optional().describe("Allowed IP address/CIDR for this peer (e.g. 10.0.0.2/32)"),
2668
+ endpoint: z13.string().optional().describe("Peer endpoint as IP:port (e.g. 1.2.3.4:51820)"),
2669
+ comment: z13.string().max(255).optional().describe("Optional comment"),
2670
+ dryRun
2755
2671
  }).strict();
2756
2672
  var managePeerTool = {
2757
2673
  name: "manage_wireguard_peer",
@@ -2837,19 +2753,18 @@ var managePeerTool = {
2837
2753
  structuredContent: { action: "removed", interface: parsed.interface, id: rec[".id"] }
2838
2754
  };
2839
2755
  } catch (err) {
2840
- if (err instanceof MikroMCPError) throw err;
2841
- throw enrichError(err, { routerId: context.routerId, tool: "manage_wireguard_peer" });
2756
+ throw toolError(err, context, "manage_wireguard_peer");
2842
2757
  }
2843
2758
  }
2844
2759
  };
2845
- var manageWgIfaceInputSchema = z12.object({
2846
- routerId: z12.string().describe("Target router identifier from the router registry"),
2847
- action: z12.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
2848
- name: z12.string().describe("Interface name \u2014 idempotency key (e.g. wg0)"),
2849
- listenPort: z12.number().int().min(1).max(65535).optional().describe("UDP listen port (RouterOS picks one if omitted)"),
2850
- mtu: z12.number().int().min(1280).max(65535).default(1420).describe("MTU (default 1420)"),
2851
- comment: z12.string().optional().describe("Optional comment"),
2852
- dryRun: z12.boolean().default(false).describe("Preview changes without applying")
2760
+ var manageWgIfaceInputSchema = z13.object({
2761
+ routerId,
2762
+ action: z13.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
2763
+ name: z13.string().describe("Interface name \u2014 idempotency key (e.g. wg0)"),
2764
+ listenPort: z13.number().int().min(1).max(65535).optional().describe("UDP listen port (RouterOS picks one if omitted)"),
2765
+ mtu: z13.number().int().min(1280).max(65535).default(1420).describe("MTU (default 1420)"),
2766
+ comment: z13.string().optional().describe("Optional comment"),
2767
+ dryRun
2853
2768
  }).strict();
2854
2769
  var manageWgIfaceTool = {
2855
2770
  name: "manage_wireguard_interface",
@@ -2973,8 +2888,7 @@ var manageWgIfaceTool = {
2973
2888
  structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
2974
2889
  };
2975
2890
  } catch (err) {
2976
- if (err instanceof MikroMCPError) throw err;
2977
- throw enrichError(err, { routerId: context.routerId, tool: "manage_wireguard_interface" });
2891
+ throw toolError(err, context, "manage_wireguard_interface");
2978
2892
  }
2979
2893
  }
2980
2894
  };
@@ -2986,14 +2900,14 @@ var wireguardTools = [
2986
2900
  ];
2987
2901
 
2988
2902
  // src/domain/tools/dns-tools.ts
2989
- import { z as z13 } from "zod";
2903
+ import { z as z14 } from "zod";
2990
2904
  var log12 = createLogger("dns-tools");
2991
- var listDnsInputSchema = z13.object({
2992
- routerId: z13.string().describe("Target router identifier from the router registry"),
2993
- name: z13.string().optional().describe("Filter by hostname (partial match)"),
2994
- type: z13.enum(["A", "CNAME", "TXT", "all"]).default("all").describe("Filter by record type"),
2995
- limit: z13.number().int().min(1).max(500).default(100).describe("Maximum number of entries to return"),
2996
- offset: z13.number().int().min(0).default(0).describe("Offset for pagination")
2905
+ var listDnsInputSchema = z14.object({
2906
+ routerId,
2907
+ name: z14.string().optional().describe("Filter by hostname (partial match)"),
2908
+ type: z14.enum(["A", "CNAME", "TXT", "all"]).default("all").describe("Filter by record type"),
2909
+ limit,
2910
+ offset
2997
2911
  }).strict();
2998
2912
  var listDnsTool = {
2999
2913
  name: "list_dns_entries",
@@ -3023,17 +2937,9 @@ var listDnsTool = {
3023
2937
  (e) => (e.name ?? "").toLowerCase().includes(needle)
3024
2938
  );
3025
2939
  }
3026
- const total = entries.length;
3027
- const paginated = entries.slice(parsed.offset, parsed.offset + parsed.limit);
3028
- const hasMore = parsed.offset + parsed.limit < total;
3029
- const lines = [`DNS entries on ${context.routerId}: ${total} total`];
3030
- for (const entry of paginated) {
3031
- const rec = entry;
3032
- const value = rec.address ?? rec.cname ?? rec.text ?? "?";
3033
- lines.push(` ${rec.name} ${rec.type ?? "A"} \u2192 ${value}`);
3034
- }
2940
+ const { items: paginated, total, hasMore } = paginate(entries, parsed.offset, parsed.limit);
3035
2941
  return {
3036
- content: lines.join("\n"),
2942
+ content: listSummary("DNS entries", context.routerId, paginated.length, total, parsed.offset),
3037
2943
  structuredContent: {
3038
2944
  routerId: context.routerId,
3039
2945
  entries: paginated,
@@ -3044,22 +2950,22 @@ var listDnsTool = {
3044
2950
  }
3045
2951
  };
3046
2952
  } catch (err) {
3047
- throw enrichError(err, { routerId: context.routerId, tool: "list_dns_entries" });
3048
- }
3049
- }
3050
- };
3051
- var manageDnsInputSchema = z13.object({
3052
- routerId: z13.string().describe("Target router identifier from the router registry"),
3053
- action: z13.enum(["add", "remove"]).describe("Action to perform"),
3054
- name: z13.string().min(1).describe("Hostname for the DNS record (e.g. server.example.com)"),
3055
- type: z13.enum(["A", "CNAME", "TXT"]).default("A").describe("DNS record type"),
3056
- address: z13.string().optional().describe("IP address (required for A records)"),
3057
- cname: z13.string().optional().describe("Target hostname (required for CNAME records)"),
3058
- text: z13.string().optional().describe("Text value (required for TXT records)"),
3059
- ttl: z13.string().optional().describe("TTL value (e.g. 1d, 00:05:00)"),
3060
- comment: z13.string().max(255).optional().describe("Optional comment"),
3061
- disabled: z13.boolean().default(false).describe("Whether the entry should be disabled"),
3062
- dryRun: z13.boolean().default(false).describe("Preview changes without applying")
2953
+ throw toolError(err, context, "list_dns_entries");
2954
+ }
2955
+ }
2956
+ };
2957
+ var manageDnsInputSchema = z14.object({
2958
+ routerId,
2959
+ action: z14.enum(["add", "remove"]).describe("Action to perform"),
2960
+ name: z14.string().min(1).describe("Hostname for the DNS record (e.g. server.example.com)"),
2961
+ type: z14.enum(["A", "CNAME", "TXT"]).default("A").describe("DNS record type"),
2962
+ address: z14.string().optional().describe("IP address (required for A records)"),
2963
+ cname: z14.string().optional().describe("Target hostname (required for CNAME records)"),
2964
+ text: z14.string().optional().describe("Text value (required for TXT records)"),
2965
+ ttl: z14.string().optional().describe("TTL value (e.g. 1d, 00:05:00)"),
2966
+ comment: z14.string().max(255).optional().describe("Optional comment"),
2967
+ disabled: z14.boolean().default(false).describe("Whether the entry should be disabled"),
2968
+ dryRun
3063
2969
  }).strict();
3064
2970
  var manageDnsTool = {
3065
2971
  name: "manage_dns_entry",
@@ -3178,13 +3084,12 @@ var manageDnsTool = {
3178
3084
  }
3179
3085
  };
3180
3086
  } catch (err) {
3181
- if (err instanceof MikroMCPError) throw err;
3182
- throw enrichError(err, { routerId: context.routerId, tool: "manage_dns_entry" });
3087
+ throw toolError(err, context, "manage_dns_entry");
3183
3088
  }
3184
3089
  }
3185
3090
  };
3186
- var getDnsSettingsInputSchema = z13.object({
3187
- routerId: z13.string().describe("Target router identifier from the router registry")
3091
+ var getDnsSettingsInputSchema = z14.object({
3092
+ routerId
3188
3093
  }).strict();
3189
3094
  var getDnsSettingsTool = {
3190
3095
  name: "get_dns_settings",
@@ -3218,18 +3123,18 @@ var getDnsSettingsTool = {
3218
3123
  structuredContent: { routerId: context.routerId, settings }
3219
3124
  };
3220
3125
  } catch (err) {
3221
- throw enrichError(err, { routerId: context.routerId, tool: "get_dns_settings" });
3126
+ throw toolError(err, context, "get_dns_settings");
3222
3127
  }
3223
3128
  }
3224
3129
  };
3225
- var manageDnsSettingsInputSchema = z13.object({
3226
- routerId: z13.string().describe("Target router identifier from the router registry"),
3227
- servers: z13.string().optional().describe("Comma-separated upstream DNS server IPs (e.g. '8.8.8.8,1.1.1.1')"),
3228
- allowRemoteRequests: z13.boolean().optional().describe("Allow router to answer DNS queries from the network"),
3229
- maxUdpPacketSize: z13.number().int().min(512).max(65535).optional().describe("Maximum UDP packet size in bytes"),
3230
- cacheMaxTtl: z13.string().optional().describe("Maximum cache TTL (e.g. '1d', '00:30:00')"),
3231
- cacheSize: z13.number().int().min(1).optional().describe("DNS cache size in KiB"),
3232
- dryRun: z13.boolean().default(false).describe("Preview changes without applying")
3130
+ var manageDnsSettingsInputSchema = z14.object({
3131
+ routerId,
3132
+ servers: z14.string().optional().describe("Comma-separated upstream DNS server IPs (e.g. '8.8.8.8,1.1.1.1')"),
3133
+ allowRemoteRequests: z14.boolean().optional().describe("Allow router to answer DNS queries from the network"),
3134
+ maxUdpPacketSize: z14.number().int().min(512).max(65535).optional().describe("Maximum UDP packet size in bytes"),
3135
+ cacheMaxTtl: z14.string().optional().describe("Maximum cache TTL (e.g. '1d', '00:30:00')"),
3136
+ cacheSize: z14.number().int().min(1).optional().describe("DNS cache size in KiB"),
3137
+ dryRun
3233
3138
  }).strict();
3234
3139
  var manageDnsSettingsTool = {
3235
3140
  name: "manage_dns_settings",
@@ -3300,14 +3205,14 @@ var manageDnsSettingsTool = {
3300
3205
  structuredContent: { action: "updated", routerId: context.routerId, diff }
3301
3206
  };
3302
3207
  } catch (err) {
3303
- throw enrichError(err, { routerId: context.routerId, tool: "manage_dns_settings" });
3208
+ throw toolError(err, context, "manage_dns_settings");
3304
3209
  }
3305
3210
  }
3306
3211
  };
3307
3212
  var dnsTools = [listDnsTool, manageDnsTool, getDnsSettingsTool, manageDnsSettingsTool];
3308
3213
 
3309
3214
  // src/domain/tools/mangle-tools.ts
3310
- import { z as z14 } from "zod";
3215
+ import { z as z15 } from "zod";
3311
3216
  var log13 = createLogger("mangle-tools");
3312
3217
  var MANGLE_PATH = "ip/firewall/mangle";
3313
3218
  async function findMangleRuleByComment(context, comment) {
@@ -3316,11 +3221,11 @@ async function findMangleRuleByComment(context, comment) {
3316
3221
  });
3317
3222
  return results2.length > 0 ? results2[0] : void 0;
3318
3223
  }
3319
- var listMangleRulesInputSchema = z14.object({
3320
- routerId: z14.string().describe("Target router identifier from the router registry"),
3321
- chain: z14.string().optional().describe("Filter by chain name (e.g. prerouting, forward, postrouting)"),
3322
- action: z14.string().optional().describe("Filter by mangle action (e.g. mark-routing, mark-connection)"),
3323
- disabled: z14.boolean().optional().describe("Filter by disabled state")
3224
+ var listMangleRulesInputSchema = z15.object({
3225
+ routerId,
3226
+ chain: z15.string().optional().describe("Filter by chain name (e.g. prerouting, forward, postrouting)"),
3227
+ action: z15.string().optional().describe("Filter by mangle action (e.g. mark-routing, mark-connection)"),
3228
+ disabled: z15.boolean().optional().describe("Filter by disabled state")
3324
3229
  }).strict();
3325
3230
  var listMangleRulesTool = {
3326
3231
  name: "list_mangle_rules",
@@ -3359,30 +3264,29 @@ var listMangleRulesTool = {
3359
3264
  structuredContent: { routerId: context.routerId, rules, total: rules.length }
3360
3265
  };
3361
3266
  } catch (err) {
3362
- if (err instanceof MikroMCPError) throw err;
3363
- throw enrichError(err, { routerId: context.routerId, tool: "list_mangle_rules" });
3364
- }
3365
- }
3366
- };
3367
- var manageMangleRuleInputSchema = z14.object({
3368
- routerId: z14.string().describe("Target router identifier from the router registry"),
3369
- action: z14.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
3370
- comment: z14.string().describe("Idempotency key \u2014 uniquely identifies this mangle rule"),
3371
- chain: z14.string().optional().describe("Mangle chain (required on add): prerouting, input, forward, output, postrouting"),
3372
- dryRun: z14.boolean().default(false).describe("Preview changes without applying"),
3373
- srcAddress: z14.string().optional().describe("Source IP/CIDR to match"),
3374
- dstAddress: z14.string().optional().describe("Destination IP/CIDR to match"),
3375
- srcAddressList: z14.string().optional().describe("Source address list name to match"),
3376
- dstAddressList: z14.string().optional().describe("Destination address list name to match"),
3377
- protocol: z14.string().optional().describe("Protocol to match (e.g. tcp, udp)"),
3378
- srcPort: z14.string().optional().describe("Source port or range"),
3379
- dstPort: z14.string().optional().describe("Destination port or range"),
3380
- inInterface: z14.string().optional().describe("Incoming interface to match"),
3381
- outInterface: z14.string().optional().describe("Outgoing interface to match"),
3382
- newRoutingMark: z14.string().optional().describe("Routing mark to set"),
3383
- newConnectionMark: z14.string().optional().describe("Connection mark to set"),
3384
- newDscpValue: z14.number().int().min(0).max(63).optional().describe("DSCP value to set (0\u201363)"),
3385
- passthrough: z14.boolean().optional().describe("Whether to continue matching subsequent rules")
3267
+ throw toolError(err, context, "list_mangle_rules");
3268
+ }
3269
+ }
3270
+ };
3271
+ var manageMangleRuleInputSchema = z15.object({
3272
+ routerId,
3273
+ action: z15.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
3274
+ comment: z15.string().describe("Idempotency key \u2014 uniquely identifies this mangle rule"),
3275
+ chain: z15.string().optional().describe("Mangle chain (required on add): prerouting, input, forward, output, postrouting"),
3276
+ dryRun,
3277
+ srcAddress: z15.string().optional().describe("Source IP/CIDR to match"),
3278
+ dstAddress: z15.string().optional().describe("Destination IP/CIDR to match"),
3279
+ srcAddressList: z15.string().optional().describe("Source address list name to match"),
3280
+ dstAddressList: z15.string().optional().describe("Destination address list name to match"),
3281
+ protocol: z15.string().optional().describe("Protocol to match (e.g. tcp, udp)"),
3282
+ srcPort: z15.string().optional().describe("Source port or range"),
3283
+ dstPort: z15.string().optional().describe("Destination port or range"),
3284
+ inInterface: z15.string().optional().describe("Incoming interface to match"),
3285
+ outInterface: z15.string().optional().describe("Outgoing interface to match"),
3286
+ newRoutingMark: z15.string().optional().describe("Routing mark to set"),
3287
+ newConnectionMark: z15.string().optional().describe("Connection mark to set"),
3288
+ newDscpValue: z15.number().int().min(0).max(63).optional().describe("DSCP value to set (0\u201363)"),
3289
+ passthrough: z15.boolean().optional().describe("Whether to continue matching subsequent rules")
3386
3290
  }).strict();
3387
3291
  var manageMangleRuleTool = {
3388
3292
  name: "manage_mangle_rule",
@@ -3567,21 +3471,20 @@ var manageMangleRuleTool = {
3567
3471
  }
3568
3472
  });
3569
3473
  } catch (err) {
3570
- if (err instanceof MikroMCPError) throw err;
3571
- throw enrichError(err, { routerId: context.routerId, tool: "manage_mangle_rule" });
3474
+ throw toolError(err, context, "manage_mangle_rule");
3572
3475
  }
3573
3476
  }
3574
3477
  };
3575
3478
  var mangleTools = [listMangleRulesTool, manageMangleRuleTool];
3576
3479
 
3577
3480
  // src/domain/tools/address-list-tools.ts
3578
- import { z as z15 } from "zod";
3481
+ import { z as z16 } from "zod";
3579
3482
  var log14 = createLogger("address-list-tools");
3580
3483
  var ADDRESS_LIST_PATH = "ip/firewall/address-list";
3581
- var listAddressListInputSchema = z15.object({
3582
- routerId: z15.string().describe("Target router identifier from the router registry"),
3583
- list: z15.string().optional().describe("Filter by address list name"),
3584
- address: z15.string().optional().describe("Filter by address (IP or CIDR)")
3484
+ var listAddressListInputSchema = z16.object({
3485
+ routerId,
3486
+ list: z16.string().optional().describe("Filter by address list name"),
3487
+ address: z16.string().optional().describe("Filter by address (IP or CIDR)")
3585
3488
  }).strict();
3586
3489
  var listAddressListTool = {
3587
3490
  name: "list_address_list_entries",
@@ -3611,19 +3514,18 @@ var listAddressListTool = {
3611
3514
  structuredContent: { routerId: context.routerId, entries, total: entries.length }
3612
3515
  };
3613
3516
  } catch (err) {
3614
- if (err instanceof MikroMCPError) throw err;
3615
- throw enrichError(err, { routerId: context.routerId, tool: "list_address_list_entries" });
3517
+ throw toolError(err, context, "list_address_list_entries");
3616
3518
  }
3617
3519
  }
3618
3520
  };
3619
- var manageAddressListInputSchema = z15.object({
3620
- routerId: z15.string().describe("Target router identifier from the router registry"),
3621
- action: z15.enum(["add", "remove"]).describe("Action to perform"),
3622
- list: z15.string().describe("Address list name"),
3623
- address: z15.string().describe("IP address or CIDR to add/remove"),
3624
- comment: z15.string().optional().describe("Optional comment for the entry"),
3625
- timeout: z15.string().optional().describe("Expiry timeout (e.g. 1d, 2h30m) \u2014 omit for permanent"),
3626
- dryRun: z15.boolean().default(false).describe("Preview changes without applying")
3521
+ var manageAddressListInputSchema = z16.object({
3522
+ routerId,
3523
+ action: z16.enum(["add", "remove"]).describe("Action to perform"),
3524
+ list: z16.string().describe("Address list name"),
3525
+ address: z16.string().describe("IP address or CIDR to add/remove"),
3526
+ comment: z16.string().optional().describe("Optional comment for the entry"),
3527
+ timeout: z16.string().optional().describe("Expiry timeout (e.g. 1d, 2h30m) \u2014 omit for permanent"),
3528
+ dryRun
3627
3529
  }).strict();
3628
3530
  var manageAddressListTool = {
3629
3531
  name: "manage_address_list_entry",
@@ -3716,22 +3618,21 @@ var manageAddressListTool = {
3716
3618
  recoverability: { retryable: false, suggestedAction: "Use one of: add, remove." }
3717
3619
  });
3718
3620
  } catch (err) {
3719
- if (err instanceof MikroMCPError) throw err;
3720
- throw enrichError(err, { routerId: context.routerId, tool: "manage_address_list_entry" });
3621
+ throw toolError(err, context, "manage_address_list_entry");
3721
3622
  }
3722
3623
  }
3723
3624
  };
3724
3625
  var addressListTools = [listAddressListTool, manageAddressListTool];
3725
3626
 
3726
3627
  // src/domain/tools/policy-routing-tools.ts
3727
- import { z as z16 } from "zod";
3628
+ import { z as z17 } from "zod";
3728
3629
  var log15 = createLogger("policy-routing-tools");
3729
3630
  var ROUTING_RULE_PATH = "routing/rule";
3730
3631
  var ROUTING_TABLE_PATH = "routing/table";
3731
- var listRoutingRulesInputSchema = z16.object({
3732
- routerId: z16.string().describe("Target router identifier from the router registry"),
3733
- table: z16.string().optional().describe("Filter by routing table name"),
3734
- disabled: z16.boolean().optional().describe("Filter by disabled state")
3632
+ var listRoutingRulesInputSchema = z17.object({
3633
+ routerId,
3634
+ table: z17.string().optional().describe("Filter by routing table name"),
3635
+ disabled: z17.boolean().optional().describe("Filter by disabled state")
3735
3636
  }).strict();
3736
3637
  var listRoutingRulesTool = {
3737
3638
  name: "list_routing_rules",
@@ -3767,22 +3668,21 @@ var listRoutingRulesTool = {
3767
3668
  structuredContent: { routerId: context.routerId, rules, total: rules.length }
3768
3669
  };
3769
3670
  } catch (err) {
3770
- if (err instanceof MikroMCPError) throw err;
3771
- throw enrichError(err, { routerId: context.routerId, tool: "list_routing_rules" });
3671
+ throw toolError(err, context, "list_routing_rules");
3772
3672
  }
3773
3673
  }
3774
3674
  };
3775
- var manageRoutingRuleInputSchema = z16.object({
3776
- routerId: z16.string().describe("Target router identifier from the router registry"),
3777
- action: z16.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
3778
- table: z16.string().describe(
3675
+ var manageRoutingRuleInputSchema = z17.object({
3676
+ routerId,
3677
+ action: z17.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
3678
+ table: z17.string().describe(
3779
3679
  "Routing table name \u2014 part of the composite idempotency key; required for all actions"
3780
3680
  ),
3781
- srcAddress: z16.string().optional().describe("Source CIDR to match"),
3782
- dstAddress: z16.string().optional().describe("Destination CIDR to match"),
3783
- interface: z16.string().optional().describe("Incoming interface to match"),
3784
- priority: z16.number().int().min(0).max(4294967295).optional().describe("Rule priority (0\u20134294967295)"),
3785
- dryRun: z16.boolean().default(false).describe("Preview changes without applying")
3681
+ srcAddress: z17.string().optional().describe("Source CIDR to match"),
3682
+ dstAddress: z17.string().optional().describe("Destination CIDR to match"),
3683
+ interface: z17.string().optional().describe("Incoming interface to match"),
3684
+ priority: z17.number().int().min(0).max(4294967295).optional().describe("Rule priority (0\u20134294967295)"),
3685
+ dryRun
3786
3686
  }).strict();
3787
3687
  function findRoutingRule(rules, srcAddress, dstAddress, iface, table) {
3788
3688
  return rules.find((r) => {
@@ -3938,13 +3838,12 @@ var manageRoutingRuleTool = {
3938
3838
  }
3939
3839
  });
3940
3840
  } catch (err) {
3941
- if (err instanceof MikroMCPError) throw err;
3942
- throw enrichError(err, { routerId: context.routerId, tool: "manage_routing_rule" });
3841
+ throw toolError(err, context, "manage_routing_rule");
3943
3842
  }
3944
3843
  }
3945
3844
  };
3946
- var listRoutingTablesInputSchema = z16.object({
3947
- routerId: z16.string().describe("Target router identifier from the router registry")
3845
+ var listRoutingTablesInputSchema = z17.object({
3846
+ routerId
3948
3847
  }).strict();
3949
3848
  var listRoutingTablesTool = {
3950
3849
  name: "list_routing_tables",
@@ -3970,17 +3869,16 @@ var listRoutingTablesTool = {
3970
3869
  structuredContent: { routerId: context.routerId, tables, total: tables.length }
3971
3870
  };
3972
3871
  } catch (err) {
3973
- if (err instanceof MikroMCPError) throw err;
3974
- throw enrichError(err, { routerId: context.routerId, tool: "list_routing_tables" });
3872
+ throw toolError(err, context, "list_routing_tables");
3975
3873
  }
3976
3874
  }
3977
3875
  };
3978
- var manageRoutingTableInputSchema = z16.object({
3979
- routerId: z16.string().describe("Target router identifier from the router registry"),
3980
- action: z16.enum(["add", "remove"]).describe("Action to perform"),
3981
- name: z16.string().describe("Routing table name (idempotency key)"),
3982
- fib: z16.boolean().default(false).describe("Whether to sync this table with the FIB"),
3983
- dryRun: z16.boolean().default(false).describe("Preview changes without applying")
3876
+ var manageRoutingTableInputSchema = z17.object({
3877
+ routerId,
3878
+ action: z17.enum(["add", "remove"]).describe("Action to perform"),
3879
+ name: z17.string().describe("Routing table name (idempotency key)"),
3880
+ fib: z17.boolean().default(false).describe("Whether to sync this table with the FIB"),
3881
+ dryRun
3984
3882
  }).strict();
3985
3883
  var manageRoutingTableTool = {
3986
3884
  name: "manage_routing_table",
@@ -4062,8 +3960,7 @@ var manageRoutingTableTool = {
4062
3960
  recoverability: { retryable: false, suggestedAction: "Use one of: add, remove." }
4063
3961
  });
4064
3962
  } catch (err) {
4065
- if (err instanceof MikroMCPError) throw err;
4066
- throw enrichError(err, { routerId: context.routerId, tool: "manage_routing_table" });
3963
+ throw toolError(err, context, "manage_routing_table");
4067
3964
  }
4068
3965
  }
4069
3966
  };
@@ -4075,11 +3972,11 @@ var policyRoutingTools = [
4075
3972
  ];
4076
3973
 
4077
3974
  // src/domain/tools/routing-protocol-tools.ts
4078
- import { z as z17 } from "zod";
3975
+ import { z as z18 } from "zod";
4079
3976
  var log16 = createLogger("routing-protocol-tools");
4080
- var listBgpPeersInputSchema = z17.object({
4081
- routerId: z17.string().describe("Target router identifier from the router registry"),
4082
- state: z17.string().optional().describe("Filter by session state (e.g. established, active, idle)")
3977
+ var listBgpPeersInputSchema = z18.object({
3978
+ routerId,
3979
+ state: z18.string().optional().describe("Filter by session state (e.g. established, active, idle)")
4083
3980
  }).strict();
4084
3981
  var listBgpPeersTool = {
4085
3982
  name: "list_bgp_peers",
@@ -4108,14 +4005,13 @@ var listBgpPeersTool = {
4108
4005
  structuredContent: { routerId: context.routerId, sessions, total: sessions.length }
4109
4006
  };
4110
4007
  } catch (err) {
4111
- if (err instanceof MikroMCPError) throw err;
4112
- throw enrichError(err, { routerId: context.routerId, tool: "list_bgp_peers" });
4008
+ throw toolError(err, context, "list_bgp_peers");
4113
4009
  }
4114
4010
  }
4115
4011
  };
4116
- var listOspfNeighborsInputSchema = z17.object({
4117
- routerId: z17.string().describe("Target router identifier from the router registry"),
4118
- state: z17.string().optional().describe("Filter by neighbor state (e.g. full, 2-way, init)")
4012
+ var listOspfNeighborsInputSchema = z18.object({
4013
+ routerId,
4014
+ state: z18.string().optional().describe("Filter by neighbor state (e.g. full, 2-way, init)")
4119
4015
  }).strict();
4120
4016
  var listOspfNeighborsTool = {
4121
4017
  name: "list_ospf_neighbors",
@@ -4144,20 +4040,19 @@ var listOspfNeighborsTool = {
4144
4040
  structuredContent: { routerId: context.routerId, neighbors, total: neighbors.length }
4145
4041
  };
4146
4042
  } catch (err) {
4147
- if (err instanceof MikroMCPError) throw err;
4148
- throw enrichError(err, { routerId: context.routerId, tool: "list_ospf_neighbors" });
4043
+ throw toolError(err, context, "list_ospf_neighbors");
4149
4044
  }
4150
4045
  }
4151
4046
  };
4152
4047
  var routingProtocolTools = [listBgpPeersTool, listOspfNeighborsTool];
4153
4048
 
4154
4049
  // src/domain/tools/scripts-tools.ts
4155
- import { z as z18 } from "zod";
4050
+ import { z as z19 } from "zod";
4156
4051
  var log17 = createLogger("scripts-tools");
4157
4052
  var SCRIPT_PATH = "system/script";
4158
- var listScriptsInputSchema = z18.object({
4159
- routerId: z18.string().describe("Target router identifier from the router registry"),
4160
- name: z18.string().optional().describe("Filter by script name (substring match)")
4053
+ var listScriptsInputSchema = z19.object({
4054
+ routerId,
4055
+ name: z19.string().optional().describe("Filter by script name (substring match)")
4161
4056
  }).strict();
4162
4057
  var listScriptsTool = {
4163
4058
  name: "list_scripts",
@@ -4189,19 +4084,18 @@ var listScriptsTool = {
4189
4084
  structuredContent: { routerId: context.routerId, scripts, total: scripts.length }
4190
4085
  };
4191
4086
  } catch (err) {
4192
- if (err instanceof MikroMCPError) throw err;
4193
- throw enrichError(err, { routerId: context.routerId, tool: "list_scripts" });
4087
+ throw toolError(err, context, "list_scripts");
4194
4088
  }
4195
4089
  }
4196
4090
  };
4197
- var manageScriptInputSchema = z18.object({
4198
- routerId: z18.string().describe("Target router identifier from the router registry"),
4199
- action: z18.enum(["add", "update", "remove"]).describe("Action to perform"),
4200
- name: z18.string().describe("Script name \u2014 idempotency key"),
4201
- source: z18.string().optional().describe("Script body (required for add and update)"),
4202
- comment: z18.string().optional().describe("Optional comment"),
4203
- dontRequirePermissions: z18.boolean().optional().describe("Allow script to run without elevated permissions"),
4204
- dryRun: z18.boolean().default(false).describe("Preview changes without applying")
4091
+ var manageScriptInputSchema = z19.object({
4092
+ routerId,
4093
+ action: z19.enum(["add", "update", "remove"]).describe("Action to perform"),
4094
+ name: z19.string().describe("Script name \u2014 idempotency key"),
4095
+ source: z19.string().optional().describe("Script body (required for add and update)"),
4096
+ comment: z19.string().optional().describe("Optional comment"),
4097
+ dontRequirePermissions: z19.boolean().optional().describe("Allow script to run without elevated permissions"),
4098
+ dryRun
4205
4099
  }).strict();
4206
4100
  var manageScriptTool = {
4207
4101
  name: "manage_script",
@@ -4340,14 +4234,13 @@ var manageScriptTool = {
4340
4234
  recoverability: { retryable: false, suggestedAction: "Use one of: add, update, remove." }
4341
4235
  });
4342
4236
  } catch (err) {
4343
- if (err instanceof MikroMCPError) throw err;
4344
- throw enrichError(err, { routerId: context.routerId, tool: "manage_script" });
4237
+ throw toolError(err, context, "manage_script");
4345
4238
  }
4346
4239
  }
4347
4240
  };
4348
- var runScriptInputSchema = z18.object({
4349
- routerId: z18.string().describe("Target router identifier from the router registry"),
4350
- name: z18.string().describe("Name of the script to execute")
4241
+ var runScriptInputSchema = z19.object({
4242
+ routerId,
4243
+ name: z19.string().describe("Name of the script to execute")
4351
4244
  }).strict();
4352
4245
  var runScriptTool = {
4353
4246
  name: "run_script",
@@ -4389,20 +4282,19 @@ var runScriptTool = {
4389
4282
  structuredContent: { action: "executed", name: parsed.name }
4390
4283
  };
4391
4284
  } catch (err) {
4392
- if (err instanceof MikroMCPError) throw err;
4393
- throw enrichError(err, { routerId: context.routerId, tool: "run_script" });
4285
+ throw toolError(err, context, "run_script");
4394
4286
  }
4395
4287
  }
4396
4288
  };
4397
4289
  var scriptsTools = [listScriptsTool, manageScriptTool, runScriptTool];
4398
4290
 
4399
4291
  // src/domain/tools/scheduler-tools.ts
4400
- import { z as z19 } from "zod";
4292
+ import { z as z20 } from "zod";
4401
4293
  var log18 = createLogger("scheduler-tools");
4402
4294
  var SCHEDULER_PATH = "system/scheduler";
4403
- var listScheduledJobsInputSchema = z19.object({
4404
- routerId: z19.string().describe("Target router identifier from the router registry"),
4405
- name: z19.string().optional().describe("Filter by job name (exact match)")
4295
+ var listScheduledJobsInputSchema = z20.object({
4296
+ routerId,
4297
+ name: z20.string().optional().describe("Filter by job name (exact match)")
4406
4298
  }).strict();
4407
4299
  var listScheduledJobsTool = {
4408
4300
  name: "list_scheduled_jobs",
@@ -4431,21 +4323,20 @@ var listScheduledJobsTool = {
4431
4323
  structuredContent: { routerId: context.routerId, jobs, total: jobs.length }
4432
4324
  };
4433
4325
  } catch (err) {
4434
- if (err instanceof MikroMCPError) throw err;
4435
- throw enrichError(err, { routerId: context.routerId, tool: "list_scheduled_jobs" });
4326
+ throw toolError(err, context, "list_scheduled_jobs");
4436
4327
  }
4437
4328
  }
4438
4329
  };
4439
- var manageScheduledJobInputSchema = z19.object({
4440
- routerId: z19.string().describe("Target router identifier from the router registry"),
4441
- action: z19.enum(["add", "update", "remove", "enable", "disable"]).describe("Action to perform"),
4442
- name: z19.string().describe("Job name \u2014 idempotency key"),
4443
- onEvent: z19.string().optional().describe("Script name or inline command to run (required on add)"),
4444
- startDate: z19.string().optional().describe("Start date (e.g. jan/01/2000)"),
4445
- startTime: z19.string().optional().describe("Start time (e.g. 00:00:00)"),
4446
- interval: z19.string().optional().describe("Run interval (e.g. 00:05:00 for every 5 minutes)"),
4447
- comment: z19.string().optional().describe("Optional comment"),
4448
- dryRun: z19.boolean().default(false).describe("Preview changes without applying")
4330
+ var manageScheduledJobInputSchema = z20.object({
4331
+ routerId,
4332
+ action: z20.enum(["add", "update", "remove", "enable", "disable"]).describe("Action to perform"),
4333
+ name: z20.string().describe("Job name \u2014 idempotency key"),
4334
+ onEvent: z20.string().optional().describe("Script name or inline command to run (required on add)"),
4335
+ startDate: z20.string().optional().describe("Start date (e.g. jan/01/2000)"),
4336
+ startTime: z20.string().optional().describe("Start time (e.g. 00:00:00)"),
4337
+ interval: z20.string().optional().describe("Run interval (e.g. 00:05:00 for every 5 minutes)"),
4338
+ comment: z20.string().optional().describe("Optional comment"),
4339
+ dryRun
4449
4340
  }).strict();
4450
4341
  var manageScheduledJobTool = {
4451
4342
  name: "manage_scheduled_job",
@@ -4637,20 +4528,19 @@ var manageScheduledJobTool = {
4637
4528
  }
4638
4529
  });
4639
4530
  } catch (err) {
4640
- if (err instanceof MikroMCPError) throw err;
4641
- throw enrichError(err, { routerId: context.routerId, tool: "manage_scheduled_job" });
4531
+ throw toolError(err, context, "manage_scheduled_job");
4642
4532
  }
4643
4533
  }
4644
4534
  };
4645
4535
  var schedulerTools = [listScheduledJobsTool, manageScheduledJobTool];
4646
4536
 
4647
4537
  // src/domain/tools/packages-tools.ts
4648
- import { z as z20 } from "zod";
4538
+ import { z as z21 } from "zod";
4649
4539
  var log19 = createLogger("packages-tools");
4650
4540
  var PACKAGE_PATH = "system/package";
4651
- var listPackagesInputSchema = z20.object({
4652
- routerId: z20.string().describe("Target router identifier from the router registry"),
4653
- name: z20.string().optional().describe("Filter by package name (exact match)")
4541
+ var listPackagesInputSchema = z21.object({
4542
+ routerId,
4543
+ name: z21.string().optional().describe("Filter by package name (exact match)")
4654
4544
  }).strict();
4655
4545
  var listPackagesTool = {
4656
4546
  name: "list_packages",
@@ -4679,16 +4569,15 @@ var listPackagesTool = {
4679
4569
  structuredContent: { routerId: context.routerId, packages, total: packages.length }
4680
4570
  };
4681
4571
  } catch (err) {
4682
- if (err instanceof MikroMCPError) throw err;
4683
- throw enrichError(err, { routerId: context.routerId, tool: "list_packages" });
4572
+ throw toolError(err, context, "list_packages");
4684
4573
  }
4685
4574
  }
4686
4575
  };
4687
- var managePackageInputSchema = z20.object({
4688
- routerId: z20.string().describe("Target router identifier from the router registry"),
4689
- action: z20.enum(["enable", "disable"]).describe("Action to perform"),
4690
- name: z20.string().describe("Package name"),
4691
- dryRun: z20.boolean().default(false).describe("Preview changes without applying")
4576
+ var managePackageInputSchema = z21.object({
4577
+ routerId,
4578
+ action: z21.enum(["enable", "disable"]).describe("Action to perform"),
4579
+ name: z21.string().describe("Package name"),
4580
+ dryRun
4692
4581
  }).strict();
4693
4582
  var managePackageTool = {
4694
4583
  name: "manage_package",
@@ -4754,21 +4643,20 @@ var managePackageTool = {
4754
4643
  structuredContent: { action: "updated", name: parsed.name, id }
4755
4644
  };
4756
4645
  } catch (err) {
4757
- if (err instanceof MikroMCPError) throw err;
4758
- throw enrichError(err, { routerId: context.routerId, tool: "manage_package" });
4646
+ throw toolError(err, context, "manage_package");
4759
4647
  }
4760
4648
  }
4761
4649
  };
4762
4650
  var packagesTools = [listPackagesTool, managePackageTool];
4763
4651
 
4764
4652
  // src/domain/tools/files-tools.ts
4765
- import { z as z21 } from "zod";
4653
+ import { z as z22 } from "zod";
4766
4654
  var log20 = createLogger("files-tools");
4767
4655
  var FILE_PATH = "file";
4768
- var listFilesInputSchema = z21.object({
4769
- routerId: z21.string().describe("Target router identifier from the router registry"),
4770
- name: z21.string().optional().describe("Filter by file name (substring match)"),
4771
- type: z21.string().optional().describe("Filter by file type (e.g. script, backup, package)")
4656
+ var listFilesInputSchema = z22.object({
4657
+ routerId,
4658
+ name: z22.string().optional().describe("Filter by file name (substring match)"),
4659
+ type: z22.string().optional().describe("Filter by file type (e.g. script, backup, package)")
4772
4660
  }).strict();
4773
4661
  var listFilesTool = {
4774
4662
  name: "list_files",
@@ -4803,14 +4691,13 @@ var listFilesTool = {
4803
4691
  structuredContent: { routerId: context.routerId, files, total: files.length }
4804
4692
  };
4805
4693
  } catch (err) {
4806
- if (err instanceof MikroMCPError) throw err;
4807
- throw enrichError(err, { routerId: context.routerId, tool: "list_files" });
4694
+ throw toolError(err, context, "list_files");
4808
4695
  }
4809
4696
  }
4810
4697
  };
4811
- var getFileContentInputSchema = z21.object({
4812
- routerId: z21.string().describe("Target router identifier from the router registry"),
4813
- name: z21.string().describe("Exact file name on the router (e.g. flash/script.rsc)")
4698
+ var getFileContentInputSchema = z22.object({
4699
+ routerId,
4700
+ name: z22.string().describe("Exact file name on the router (e.g. flash/script.rsc)")
4814
4701
  }).strict();
4815
4702
  var getFileContentTool = {
4816
4703
  name: "get_file_content",
@@ -4854,21 +4741,20 @@ ${contents}`,
4854
4741
  structuredContent: { routerId: context.routerId, name: parsed.name, contents }
4855
4742
  };
4856
4743
  } catch (err) {
4857
- if (err instanceof MikroMCPError) throw err;
4858
- throw enrichError(err, { routerId: context.routerId, tool: "get_file_content" });
4744
+ throw toolError(err, context, "get_file_content");
4859
4745
  }
4860
4746
  }
4861
4747
  };
4862
- var uploadFileInputSchema = z21.object({
4863
- routerId: z21.string().describe("Target router identifier from the router registry"),
4864
- name: z21.string().describe("Target filename on the router (e.g. flash/my-script.rsc)"),
4865
- content: z21.string().describe("File content to upload (text)"),
4866
- dryRun: z21.boolean().default(false).describe("Validate FTP connectivity without writing the file")
4748
+ var uploadFileInputSchema = z22.object({
4749
+ routerId,
4750
+ name: z22.string().describe("Target filename on the router (e.g. flash/my-script.rsc)"),
4751
+ content: z22.string().describe("File content to upload (text)"),
4752
+ dryRun: z22.boolean().default(false).describe("Validate FTP connectivity without writing the file")
4867
4753
  }).strict();
4868
4754
  var uploadFileTool = {
4869
4755
  name: "upload_file",
4870
4756
  title: "Upload File",
4871
- description: "Upload a text file to a MikroTik router via FTP using the router's credentials. Overwrites any existing file with the same name. Requires FTP permission on the router user \u2014 see config/routers.example.yaml for setup instructions. Supports dry-run (tests FTP connectivity only).",
4757
+ description: "Upload a text file to a router via FTP using the router's credentials, overwriting any existing file of the same name. Requires FTP permission on the router user (see config/routers.example.yaml). Dry-run tests FTP connectivity only.",
4872
4758
  inputSchema: uploadFileInputSchema,
4873
4759
  annotations: {
4874
4760
  readOnlyHint: false,
@@ -4895,7 +4781,6 @@ var uploadFileTool = {
4895
4781
  structuredContent: { action: "uploaded", name: parsed.name, routerId: context.routerId }
4896
4782
  };
4897
4783
  } catch (err) {
4898
- if (err instanceof MikroMCPError) throw err;
4899
4784
  if (err instanceof Error && err.code === "ECONNREFUSED") {
4900
4785
  throw new MikroMCPError({
4901
4786
  category: "CONFIGURATION" /* CONFIGURATION */,
@@ -4908,14 +4793,14 @@ var uploadFileTool = {
4908
4793
  }
4909
4794
  });
4910
4795
  }
4911
- throw enrichError(err, { routerId: context.routerId, tool: "upload_file" });
4796
+ throw toolError(err, context, "upload_file");
4912
4797
  }
4913
4798
  }
4914
4799
  };
4915
- var deleteFileInputSchema = z21.object({
4916
- routerId: z21.string().describe("Target router identifier from the router registry"),
4917
- name: z21.string().describe("Exact file name on the router (e.g. flash/backup.backup)"),
4918
- dryRun: z21.boolean().default(false).describe("Preview deletion without removing the file")
4800
+ var deleteFileInputSchema = z22.object({
4801
+ routerId,
4802
+ name: z22.string().describe("Exact file name on the router (e.g. flash/backup.backup)"),
4803
+ dryRun: z22.boolean().default(false).describe("Preview deletion without removing the file")
4919
4804
  }).strict();
4920
4805
  var deleteFileTool = {
4921
4806
  name: "delete_file",
@@ -4966,8 +4851,7 @@ var deleteFileTool = {
4966
4851
  }
4967
4852
  };
4968
4853
  } catch (err) {
4969
- if (err instanceof MikroMCPError) throw err;
4970
- throw enrichError(err, { routerId: context.routerId, tool: "delete_file" });
4854
+ throw toolError(err, context, "delete_file");
4971
4855
  }
4972
4856
  }
4973
4857
  };
@@ -4979,11 +4863,11 @@ var filesTools = [
4979
4863
  ];
4980
4864
 
4981
4865
  // src/domain/tools/container-tools.ts
4982
- import { z as z22 } from "zod";
4866
+ import { z as z23 } from "zod";
4983
4867
  var log21 = createLogger("container-tools");
4984
4868
  var CONTAINER_PATH = "container";
4985
- var listContainersInputSchema = z22.object({
4986
- routerId: z22.string().describe("Target router identifier from the router registry")
4869
+ var listContainersInputSchema = z23.object({
4870
+ routerId
4987
4871
  }).strict();
4988
4872
  var listContainersTool = {
4989
4873
  name: "list_containers",
@@ -5009,28 +4893,27 @@ var listContainersTool = {
5009
4893
  structuredContent: { routerId: context.routerId, containers, total: containers.length }
5010
4894
  };
5011
4895
  } catch (err) {
5012
- if (err instanceof MikroMCPError) throw err;
5013
- throw enrichError(err, { routerId: context.routerId, tool: "list_containers" });
4896
+ throw toolError(err, context, "list_containers");
5014
4897
  }
5015
4898
  }
5016
4899
  };
5017
- var manageContainerInputSchema = z22.object({
5018
- routerId: z22.string().describe("Target router identifier from the router registry"),
5019
- action: z22.enum(["create", "start", "stop", "remove"]).describe("Action to perform"),
5020
- name: z22.string().describe("Container name \u2014 idempotency key"),
5021
- remoteImage: z22.string().optional().describe("Docker image to pull (required on create, e.g. alpine:latest)"),
5022
- interface: z22.string().optional().describe(
4900
+ var manageContainerInputSchema = z23.object({
4901
+ routerId,
4902
+ action: z23.enum(["create", "start", "stop", "remove"]).describe("Action to perform"),
4903
+ name: z23.string().describe("Container name \u2014 idempotency key"),
4904
+ remoteImage: z23.string().optional().describe("Docker image to pull (required on create, e.g. alpine:latest)"),
4905
+ interface: z23.string().optional().describe(
5023
4906
  "veth interface name to attach the container to (required on create \u2014 must be pre-configured in RouterOS)"
5024
4907
  ),
5025
- rootDir: z22.string().optional().describe("Root directory for container files"),
5026
- envlist: z22.string().optional().describe("RouterOS environment list name for container env vars"),
5027
- comment: z22.string().optional().describe("Optional comment"),
5028
- dryRun: z22.boolean().default(false).describe("Preview changes without applying")
4908
+ rootDir: z23.string().optional().describe("Root directory for container files"),
4909
+ envlist: z23.string().optional().describe("RouterOS environment list name for container env vars"),
4910
+ comment: z23.string().optional().describe("Optional comment"),
4911
+ dryRun
5029
4912
  }).strict();
5030
4913
  var manageContainerTool = {
5031
4914
  name: "manage_container",
5032
4915
  title: "Manage Container",
5033
- description: "Create, start, stop, or remove a RouterOS container. create requires a pre-configured veth interface. start/stop are no-ops if the container is already in the target state. remove throws NOT_FOUND if the container does not exist. Supports dry-run.",
4916
+ description: "Create, start, stop, or remove a RouterOS container. create needs a pre-configured veth interface; start/stop are no-ops when already in the target state; remove throws NOT_FOUND when absent. Supports dry-run.",
5034
4917
  inputSchema: manageContainerInputSchema,
5035
4918
  annotations: {
5036
4919
  readOnlyHint: false,
@@ -5194,15 +5077,14 @@ var manageContainerTool = {
5194
5077
  }
5195
5078
  });
5196
5079
  } catch (err) {
5197
- if (err instanceof MikroMCPError) throw err;
5198
- throw enrichError(err, { routerId: context.routerId, tool: "manage_container" });
5080
+ throw toolError(err, context, "manage_container");
5199
5081
  }
5200
5082
  }
5201
5083
  };
5202
5084
  var containerTools = [listContainersTool, manageContainerTool];
5203
5085
 
5204
5086
  // src/domain/tools/change-management-tools.ts
5205
- import { z as z23 } from "zod";
5087
+ import { z as z24 } from "zod";
5206
5088
  import { readFileSync } from "fs";
5207
5089
 
5208
5090
  // src/domain/snapshot/snapshot-engine.ts
@@ -5215,16 +5097,16 @@ function pathToSlug(rosPath) {
5215
5097
  function timestampPrefix() {
5216
5098
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 15);
5217
5099
  }
5218
- async function takeSnapshot(client, routerId, path, snapshotDir) {
5100
+ async function takeSnapshot(client, routerId2, path, snapshotDir) {
5219
5101
  const records = await client.get(path, {});
5220
5102
  const id = `${timestampPrefix()}-${pathToSlug(path)}-${nanoid(6)}`;
5221
- const dir = join2(snapshotDir, routerId);
5103
+ const dir = join2(snapshotDir, routerId2);
5222
5104
  const filePath = join2(dir, `${id}.json`);
5223
5105
  const ts = (/* @__PURE__ */ new Date()).toISOString();
5224
5106
  await mkdir(dir, { recursive: true });
5225
- const stored = { id, routerId, path, ts, records };
5107
+ const stored = { id, routerId: routerId2, path, ts, records };
5226
5108
  await writeFile(filePath, JSON.stringify(stored, null, 2));
5227
- return { id, routerId, path, ts, filePath, recordCount: records.length };
5109
+ return { id, routerId: routerId2, path, ts, filePath, recordCount: records.length };
5228
5110
  }
5229
5111
  async function loadSnapshot(filePath) {
5230
5112
  return JSON.parse(await readFile(filePath, "utf-8"));
@@ -5339,15 +5221,20 @@ var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
5339
5221
  "apikey",
5340
5222
  "confirmationsecret"
5341
5223
  ]);
5224
+ function redactValue(value) {
5225
+ if (Array.isArray(value)) {
5226
+ return value.map(redactValue);
5227
+ }
5228
+ if (value !== null && typeof value === "object") {
5229
+ return redactParams(value);
5230
+ }
5231
+ return value;
5232
+ }
5342
5233
  function redactParams(params) {
5343
5234
  const out = {};
5344
5235
  for (const [key2, value] of Object.entries(params)) {
5345
5236
  if (SENSITIVE_KEYS.has(key2.toLowerCase())) continue;
5346
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
5347
- out[key2] = redactParams(value);
5348
- } else {
5349
- out[key2] = value;
5350
- }
5237
+ out[key2] = redactValue(value);
5351
5238
  }
5352
5239
  return out;
5353
5240
  }
@@ -5398,23 +5285,23 @@ function recordOutcome(args) {
5398
5285
 
5399
5286
  // src/domain/tools/change-management-tools.ts
5400
5287
  var log24 = createLogger("change-management");
5401
- var stepSchema = z23.object({
5402
- tool: z23.string().min(1).describe("Name of the MikroMCP tool to invoke"),
5403
- params: z23.record(z23.unknown()).describe("Tool parameters (dryRun is injected automatically)")
5288
+ var stepSchema = z24.object({
5289
+ tool: z24.string().min(1).describe("Name of the MikroMCP tool to invoke"),
5290
+ params: z24.record(z24.unknown()).describe("Tool parameters (dryRun is injected automatically)")
5404
5291
  }).strict();
5405
- var planChangesInputSchema = z23.object({
5406
- routerId: z23.string().describe("Target router identifier"),
5407
- steps: z23.array(stepSchema).min(1).max(10).describe("Ordered list of write operations to preview (up to 10)")
5292
+ var planChangesInputSchema = z24.object({
5293
+ routerId,
5294
+ steps: z24.array(stepSchema).min(1).max(10).describe("Ordered list of write operations to preview (up to 10)")
5408
5295
  }).strict();
5409
- var applyPlanInputSchema = z23.object({
5410
- routerId: z23.string().describe("Target router identifier"),
5411
- steps: z23.array(stepSchema).min(1).max(10).describe("Ordered list of write operations to apply in sequence"),
5412
- confirmationToken: z23.string().optional()
5296
+ var applyPlanInputSchema = z24.object({
5297
+ routerId,
5298
+ steps: z24.array(stepSchema).min(1).max(10).describe("Ordered list of write operations to apply in sequence"),
5299
+ confirmationToken: z24.string().optional()
5413
5300
  }).strict();
5414
- var rollbackChangeInputSchema = z23.object({
5415
- routerId: z23.string().describe("Target router identifier"),
5416
- journalId: z23.string().describe("Journal entry ID from write-journal.ndjson to roll back"),
5417
- dryRun: z23.boolean().default(false).describe("Preview the restore plan without applying changes")
5301
+ var rollbackChangeInputSchema = z24.object({
5302
+ routerId,
5303
+ journalId: z24.string().describe("Journal entry ID from write-journal.ndjson to roll back"),
5304
+ dryRun: z24.boolean().default(false).describe("Preview the restore plan without applying changes")
5418
5305
  }).strict();
5419
5306
  function createChangeManagementTools(baseTools2) {
5420
5307
  const toolMap = new Map(baseTools2.map((t) => [t.name, t]));
@@ -5436,7 +5323,7 @@ function createChangeManagementTools(baseTools2) {
5436
5323
  const planChangesTool = {
5437
5324
  name: "plan_changes",
5438
5325
  title: "Plan Changes",
5439
- description: "Preview a sequence of write operations without applying them. Each step is run with dryRun=true against the live router state. Returns the current state of affected RouterOS paths plus the predicted action for each step. Use apply_plan to execute the same steps for real.",
5326
+ description: "Preview a sequence of write operations: each step runs with dryRun=true against live state, returning affected paths and the predicted action per step. Use apply_plan to execute the same steps for real.",
5440
5327
  inputSchema: planChangesInputSchema,
5441
5328
  annotations: {
5442
5329
  readOnlyHint: false,
@@ -5489,7 +5376,7 @@ function createChangeManagementTools(baseTools2) {
5489
5376
  const applyPlanTool = {
5490
5377
  name: "apply_plan",
5491
5378
  title: "Apply Plan",
5492
- description: "Execute a sequence of write operations in order. Stops on the first failure. Each step is snapshotted and journaled individually. Requires a confirmationToken for non-admin identities (same two-step flow as other destructive tools). Use rollback_change with any resulting journal IDs to undo individual steps.",
5379
+ description: "Execute write operations in order, stopping on first failure. Each step is snapshotted and journaled individually. Non-admin identities need a confirmationToken (same two-step flow as other destructive tools). Undo individual steps via rollback_change with the returned journal IDs.",
5493
5380
  inputSchema: applyPlanInputSchema,
5494
5381
  snapshotPaths: [],
5495
5382
  annotations: {
@@ -5586,7 +5473,7 @@ function createChangeManagementTools(baseTools2) {
5586
5473
  const rollbackChangeTool = {
5587
5474
  name: "rollback_change",
5588
5475
  title: "Rollback Change",
5589
- description: "Restore the RouterOS state to what it was before a write, identified by its journal ID. Reads the before-snapshot from disk, computes the diff against live state, and applies the reverse diff. Use dryRun=true to preview the restore plan without applying. Requires MIKROMCP_DATA_DIR (or defaults to data/) to be configured.",
5476
+ description: "Restore RouterOS state to before a write, identified by its journal ID: reads the before-snapshot, diffs against live state, and applies the reverse. Use dryRun=true to preview the restore plan. Requires MIKROMCP_DATA_DIR (defaults to data/).",
5590
5477
  inputSchema: rollbackChangeInputSchema,
5591
5478
  annotations: {
5592
5479
  readOnlyHint: false,
@@ -5637,12 +5524,12 @@ function createChangeManagementTools(baseTools2) {
5637
5524
  }
5638
5525
 
5639
5526
  // src/domain/tools/ipsec-tools.ts
5640
- import { z as z24 } from "zod";
5527
+ import { z as z25 } from "zod";
5641
5528
  var log25 = createLogger("ipsec-tools");
5642
- var listIpsecPeersInputSchema = z24.object({
5643
- routerId: z24.string().describe("Target router identifier from the router registry"),
5644
- address: z24.string().optional().describe("Filter by remote address (substring match)"),
5645
- limit: z24.number().int().min(1).max(500).default(100).describe("Maximum number of peers to return")
5529
+ var listIpsecPeersInputSchema = z25.object({
5530
+ routerId,
5531
+ address: z25.string().optional().describe("Filter by remote address (substring match)"),
5532
+ limit
5646
5533
  }).strict();
5647
5534
  var listIpsecPeersTool = {
5648
5535
  name: "list_ipsec_peers",
@@ -5677,15 +5564,15 @@ var listIpsecPeersTool = {
5677
5564
  }
5678
5565
  };
5679
5566
  } catch (err) {
5680
- throw enrichError(err, { routerId: context.routerId, tool: "list_ipsec_peers" });
5567
+ throw toolError(err, context, "list_ipsec_peers");
5681
5568
  }
5682
5569
  }
5683
5570
  };
5684
- var listIpsecPoliciesInputSchema = z24.object({
5685
- routerId: z24.string().describe("Target router identifier from the router registry"),
5686
- srcAddress: z24.string().optional().describe("Filter by source address (substring match)"),
5687
- dstAddress: z24.string().optional().describe("Filter by destination address (substring match)"),
5688
- limit: z24.number().int().min(1).max(500).default(100).describe("Maximum number of policies to return")
5571
+ var listIpsecPoliciesInputSchema = z25.object({
5572
+ routerId,
5573
+ srcAddress: z25.string().optional().describe("Filter by source address (substring match)"),
5574
+ dstAddress: z25.string().optional().describe("Filter by destination address (substring match)"),
5575
+ limit
5689
5576
  }).strict();
5690
5577
  var listIpsecPoliciesTool = {
5691
5578
  name: "list_ipsec_policies",
@@ -5722,20 +5609,20 @@ var listIpsecPoliciesTool = {
5722
5609
  }
5723
5610
  };
5724
5611
  } catch (err) {
5725
- throw enrichError(err, { routerId: context.routerId, tool: "list_ipsec_policies" });
5612
+ throw toolError(err, context, "list_ipsec_policies");
5726
5613
  }
5727
5614
  }
5728
5615
  };
5729
- var manageIpsecPeerInputSchema = z24.object({
5730
- routerId: z24.string().describe("Target router identifier from the router registry"),
5731
- action: z24.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
5732
- name: z24.string().describe("Peer name \u2014 idempotency key"),
5733
- address: z24.string().optional().describe("Remote gateway address (required for add)"),
5734
- localAddress: z24.string().optional().describe("Local address"),
5735
- exchange: z24.enum(["ike1", "ike2"]).default("ike2").describe("IKE exchange mode"),
5736
- profile: z24.string().optional().describe("IKE profile name"),
5737
- comment: z24.string().optional().describe("Optional comment"),
5738
- dryRun: z24.boolean().default(false).describe("Preview changes without applying")
5616
+ var manageIpsecPeerInputSchema = z25.object({
5617
+ routerId,
5618
+ action: z25.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
5619
+ name: z25.string().describe("Peer name \u2014 idempotency key"),
5620
+ address: z25.string().optional().describe("Remote gateway address (required for add)"),
5621
+ localAddress: z25.string().optional().describe("Local address"),
5622
+ exchange: z25.enum(["ike1", "ike2"]).default("ike2").describe("IKE exchange mode"),
5623
+ profile: z25.string().optional().describe("IKE profile name"),
5624
+ comment: z25.string().optional().describe("Optional comment"),
5625
+ dryRun
5739
5626
  }).strict();
5740
5627
  var manageIpsecPeerTool = {
5741
5628
  name: "manage_ipsec_peer",
@@ -5885,22 +5772,21 @@ var manageIpsecPeerTool = {
5885
5772
  structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
5886
5773
  };
5887
5774
  } catch (err) {
5888
- if (err instanceof MikroMCPError) throw err;
5889
- throw enrichError(err, { routerId: context.routerId, tool: "manage_ipsec_peer" });
5890
- }
5891
- }
5892
- };
5893
- var manageIpsecPolicyInputSchema = z24.object({
5894
- routerId: z24.string().describe("Target router identifier from the router registry"),
5895
- action: z24.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
5896
- srcAddress: z24.string().describe("Source CIDR \u2014 part of composite idempotency key"),
5897
- dstAddress: z24.string().describe("Destination CIDR \u2014 part of composite idempotency key"),
5898
- tunnel: z24.boolean().default(false).describe("Tunnel mode \u2014 part of composite idempotency key"),
5899
- ipsecAction: z24.enum(["encrypt", "discard", "none"]).optional().describe("IPSec action (required for add)"),
5900
- level: z24.enum(["require", "use", "unique"]).default("require").describe("SA level"),
5901
- saSourceAddress: z24.string().optional().describe("SA source IP for tunnel mode"),
5902
- saDstAddress: z24.string().optional().describe("SA destination IP for tunnel mode"),
5903
- dryRun: z24.boolean().default(false).describe("Preview changes without applying")
5775
+ throw toolError(err, context, "manage_ipsec_peer");
5776
+ }
5777
+ }
5778
+ };
5779
+ var manageIpsecPolicyInputSchema = z25.object({
5780
+ routerId,
5781
+ action: z25.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
5782
+ srcAddress: z25.string().describe("Source CIDR \u2014 part of composite idempotency key"),
5783
+ dstAddress: z25.string().describe("Destination CIDR \u2014 part of composite idempotency key"),
5784
+ tunnel: z25.boolean().default(false).describe("Tunnel mode \u2014 part of composite idempotency key"),
5785
+ ipsecAction: z25.enum(["encrypt", "discard", "none"]).optional().describe("IPSec action (required for add)"),
5786
+ level: z25.enum(["require", "use", "unique"]).default("require").describe("SA level"),
5787
+ saSourceAddress: z25.string().optional().describe("SA source IP for tunnel mode"),
5788
+ saDstAddress: z25.string().optional().describe("SA destination IP for tunnel mode"),
5789
+ dryRun
5904
5790
  }).strict();
5905
5791
  var manageIpsecPolicyTool = {
5906
5792
  name: "manage_ipsec_policy",
@@ -6037,8 +5923,7 @@ var manageIpsecPolicyTool = {
6037
5923
  structuredContent: { action: resultAction, id: existing[".id"] }
6038
5924
  };
6039
5925
  } catch (err) {
6040
- if (err instanceof MikroMCPError) throw err;
6041
- throw enrichError(err, { routerId: context.routerId, tool: "manage_ipsec_policy" });
5926
+ throw toolError(err, context, "manage_ipsec_policy");
6042
5927
  }
6043
5928
  }
6044
5929
  };
@@ -6050,13 +5935,13 @@ var ipsecTools = [
6050
5935
  ];
6051
5936
 
6052
5937
  // src/domain/tools/certificate-tools.ts
6053
- import { z as z25 } from "zod";
5938
+ import { z as z26 } from "zod";
6054
5939
  var log26 = createLogger("certificate-tools");
6055
- var listCertificatesInputSchema = z25.object({
6056
- routerId: z25.string().describe("Target router identifier from the router registry"),
6057
- name: z25.string().optional().describe("Filter by certificate name (substring match)"),
6058
- expired: z25.boolean().optional().describe("Filter by expiry status; omit to return all"),
6059
- limit: z25.number().int().min(1).max(500).default(100).describe("Maximum number of certificates to return")
5940
+ var listCertificatesInputSchema = z26.object({
5941
+ routerId,
5942
+ name: z26.string().optional().describe("Filter by certificate name (substring match)"),
5943
+ expired: z26.boolean().optional().describe("Filter by expiry status; omit to return all"),
5944
+ limit
6060
5945
  }).strict();
6061
5946
  var listCertificatesTool = {
6062
5947
  name: "list_certificates",
@@ -6098,15 +5983,15 @@ var listCertificatesTool = {
6098
5983
  }
6099
5984
  };
6100
5985
  } catch (err) {
6101
- throw enrichError(err, { routerId: context.routerId, tool: "list_certificates" });
5986
+ throw toolError(err, context, "list_certificates");
6102
5987
  }
6103
5988
  }
6104
5989
  };
6105
- var manageCertificateInputSchema = z25.object({
6106
- routerId: z25.string().describe("Target router identifier from the router registry"),
6107
- action: z25.enum(["remove", "trust", "untrust"]).describe("Action to perform"),
6108
- name: z25.string().describe("Certificate name \u2014 idempotency key"),
6109
- dryRun: z25.boolean().default(false).describe("Preview changes without applying")
5990
+ var manageCertificateInputSchema = z26.object({
5991
+ routerId,
5992
+ action: z26.enum(["remove", "trust", "untrust"]).describe("Action to perform"),
5993
+ name: z26.string().describe("Certificate name \u2014 idempotency key"),
5994
+ dryRun
6110
5995
  }).strict();
6111
5996
  var manageCertificateTool = {
6112
5997
  name: "manage_certificate",
@@ -6203,20 +6088,19 @@ var manageCertificateTool = {
6203
6088
  structuredContent: { action: "untrusted", name: parsed.name }
6204
6089
  };
6205
6090
  } catch (err) {
6206
- if (err instanceof MikroMCPError) throw err;
6207
- throw enrichError(err, { routerId: context.routerId, tool: "manage_certificate" });
6091
+ throw toolError(err, context, "manage_certificate");
6208
6092
  }
6209
6093
  }
6210
6094
  };
6211
6095
  var certificateTools = [listCertificatesTool, manageCertificateTool];
6212
6096
 
6213
6097
  // src/domain/tools/user-tools.ts
6214
- import { z as z26 } from "zod";
6098
+ import { z as z27 } from "zod";
6215
6099
  var log27 = createLogger("user-tools");
6216
- var listUsersInputSchema = z26.object({
6217
- routerId: z26.string().describe("Target router identifier from the router registry"),
6218
- group: z26.string().optional().describe("Filter by group name (exact match)"),
6219
- limit: z26.number().int().min(1).max(500).default(100).describe("Maximum number of users to return")
6100
+ var listUsersInputSchema = z27.object({
6101
+ routerId,
6102
+ group: z27.string().optional().describe("Filter by group name (exact match)"),
6103
+ limit
6220
6104
  }).strict();
6221
6105
  var listUsersTool = {
6222
6106
  name: "list_users",
@@ -6258,19 +6142,19 @@ var listUsersTool = {
6258
6142
  }
6259
6143
  };
6260
6144
  } catch (err) {
6261
- throw enrichError(err, { routerId: context.routerId, tool: "list_users" });
6145
+ throw toolError(err, context, "list_users");
6262
6146
  }
6263
6147
  }
6264
6148
  };
6265
- var manageUserInputSchema = z26.object({
6266
- routerId: z26.string().describe("Target router identifier from the router registry"),
6267
- action: z26.enum(["add", "remove", "enable", "disable", "set-password"]).describe("Action to perform"),
6268
- name: z26.string().describe("Username \u2014 idempotency key"),
6269
- group: z26.string().optional().describe("Group name (required for add; e.g. 'read', 'write', 'full')"),
6270
- password: z26.string().optional().describe("Password (required for add and set-password)"),
6271
- address: z26.string().optional().describe("Allowed source address or range"),
6272
- comment: z26.string().optional().describe("Optional comment"),
6273
- dryRun: z26.boolean().default(false).describe("Preview changes without applying")
6149
+ var manageUserInputSchema = z27.object({
6150
+ routerId,
6151
+ action: z27.enum(["add", "remove", "enable", "disable", "set-password"]).describe("Action to perform"),
6152
+ name: z27.string().describe("Username \u2014 idempotency key"),
6153
+ group: z27.string().optional().describe("Group name (required for add; e.g. 'read', 'write', 'full')"),
6154
+ password: z27.string().optional().describe("Password (required for add and set-password)"),
6155
+ address: z27.string().optional().describe("Allowed source address or range"),
6156
+ comment: z27.string().optional().describe("Optional comment"),
6157
+ dryRun
6274
6158
  }).strict();
6275
6159
  var manageUserTool = {
6276
6160
  name: "manage_user",
@@ -6490,19 +6374,18 @@ var manageUserTool = {
6490
6374
  structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
6491
6375
  };
6492
6376
  } catch (err) {
6493
- if (err instanceof MikroMCPError) throw err;
6494
- throw enrichError(err, { routerId: context.routerId, tool: "manage_user" });
6377
+ throw toolError(err, context, "manage_user");
6495
6378
  }
6496
6379
  }
6497
6380
  };
6498
6381
  var userTools = [listUsersTool, manageUserTool];
6499
6382
 
6500
6383
  // src/domain/tools/user-group-tools.ts
6501
- import { z as z27 } from "zod";
6384
+ import { z as z28 } from "zod";
6502
6385
  var log28 = createLogger("user-group-tools");
6503
- var listUserGroupsInputSchema = z27.object({
6504
- routerId: z27.string().describe("Target router identifier from the router registry"),
6505
- limit: z27.number().int().min(1).max(500).default(100).describe("Maximum number of groups to return")
6386
+ var listUserGroupsInputSchema = z28.object({
6387
+ routerId,
6388
+ limit
6506
6389
  }).strict();
6507
6390
  var listUserGroupsTool = {
6508
6391
  name: "list_user_groups",
@@ -6534,17 +6417,17 @@ var listUserGroupsTool = {
6534
6417
  }
6535
6418
  };
6536
6419
  } catch (err) {
6537
- throw enrichError(err, { routerId: context.routerId, tool: "list_user_groups" });
6420
+ throw toolError(err, context, "list_user_groups");
6538
6421
  }
6539
6422
  }
6540
6423
  };
6541
- var manageUserGroupInputSchema = z27.object({
6542
- routerId: z27.string().describe("Target router identifier from the router registry"),
6543
- action: z27.enum(["add", "update", "remove"]).describe("Action to perform"),
6544
- name: z27.string().describe("Group name \u2014 idempotency key"),
6545
- policy: z27.string().optional().describe("Comma-separated policy list (e.g. 'read,write,ftp')"),
6546
- skin: z27.string().optional().describe("Optional skin name for the group"),
6547
- dryRun: z27.boolean().default(false).describe("Preview changes without applying")
6424
+ var manageUserGroupInputSchema = z28.object({
6425
+ routerId,
6426
+ action: z28.enum(["add", "update", "remove"]).describe("Action to perform"),
6427
+ name: z28.string().describe("Group name \u2014 idempotency key"),
6428
+ policy: z28.string().optional().describe("Comma-separated policy list (e.g. 'read,write,ftp')"),
6429
+ skin: z28.string().optional().describe("Optional skin name for the group"),
6430
+ dryRun
6548
6431
  }).strict();
6549
6432
  var manageUserGroupTool = {
6550
6433
  name: "manage_user_group",
@@ -6700,21 +6583,20 @@ var manageUserGroupTool = {
6700
6583
  structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
6701
6584
  };
6702
6585
  } catch (err) {
6703
- if (err instanceof MikroMCPError) throw err;
6704
- throw enrichError(err, { routerId: context.routerId, tool: "manage_user_group" });
6586
+ throw toolError(err, context, "manage_user_group");
6705
6587
  }
6706
6588
  }
6707
6589
  };
6708
6590
  var userGroupTools = [listUserGroupsTool, manageUserGroupTool];
6709
6591
 
6710
6592
  // src/domain/tools/dhcp-server-tools.ts
6711
- import { z as z28 } from "zod";
6593
+ import { z as z29 } from "zod";
6712
6594
  var log29 = createLogger("dhcp-server-tools");
6713
- var listDhcpServersInputSchema = z28.object({
6714
- routerId: z28.string().describe("Target router identifier from the router registry"),
6715
- interface: z28.string().optional().describe("Filter by interface name (exact match)"),
6716
- limit: z28.number().int().min(1).max(500).default(100).describe("Maximum number of DHCP servers to return"),
6717
- offset: z28.number().int().min(0).default(0).describe("Offset for pagination")
6595
+ var listDhcpServersInputSchema = z29.object({
6596
+ routerId,
6597
+ interface: z29.string().optional().describe("Filter by interface name (exact match)"),
6598
+ limit,
6599
+ offset
6718
6600
  }).strict();
6719
6601
  var listDhcpServersTool = {
6720
6602
  name: "list_dhcp_servers",
@@ -6736,9 +6618,7 @@ var listDhcpServersTool = {
6736
6618
  offset: void 0
6737
6619
  });
6738
6620
  let filtered = parsed.interface ? allServers.filter((s) => s.interface === parsed.interface) : allServers;
6739
- const total = filtered.length;
6740
- const servers = filtered.slice(parsed.offset, parsed.offset + parsed.limit);
6741
- const hasMore = parsed.offset + parsed.limit < total;
6621
+ const { items: servers, total, hasMore } = paginate(filtered, parsed.offset, parsed.limit);
6742
6622
  return {
6743
6623
  content: `DHCP servers on ${context.routerId}: ${total} total, showing ${servers.length} (offset ${parsed.offset})`,
6744
6624
  structuredContent: {
@@ -6751,19 +6631,19 @@ var listDhcpServersTool = {
6751
6631
  }
6752
6632
  };
6753
6633
  } catch (err) {
6754
- throw enrichError(err, { routerId: context.routerId, tool: "list_dhcp_servers" });
6634
+ throw toolError(err, context, "list_dhcp_servers");
6755
6635
  }
6756
6636
  }
6757
6637
  };
6758
- var manageDhcpServerInputSchema = z28.object({
6759
- routerId: z28.string().describe("Target router identifier from the router registry"),
6760
- action: z28.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
6761
- name: z28.string().describe("Server name \u2014 idempotency key"),
6762
- interface: z28.string().optional().describe("Interface to serve DHCP on (required for add)"),
6763
- addressPool: z28.string().optional().describe("IP pool name (required for add)"),
6764
- leaseTime: z28.string().optional().describe("Lease duration (e.g. '1d', '12h')"),
6765
- comment: z28.string().optional().describe("Optional comment"),
6766
- dryRun: z28.boolean().default(false).describe("Preview changes without applying")
6638
+ var manageDhcpServerInputSchema = z29.object({
6639
+ routerId,
6640
+ action: z29.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
6641
+ name: z29.string().describe("Server name \u2014 idempotency key"),
6642
+ interface: z29.string().optional().describe("Interface to serve DHCP on (required for add)"),
6643
+ addressPool: z29.string().optional().describe("IP pool name (required for add)"),
6644
+ leaseTime: z29.string().optional().describe("Lease duration (e.g. '1d', '12h')"),
6645
+ comment: z29.string().optional().describe("Optional comment"),
6646
+ dryRun
6767
6647
  }).strict();
6768
6648
  var manageDhcpServerTool = {
6769
6649
  name: "manage_dhcp_server",
@@ -6933,21 +6813,20 @@ var manageDhcpServerTool = {
6933
6813
  structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
6934
6814
  };
6935
6815
  } catch (err) {
6936
- if (err instanceof MikroMCPError) throw err;
6937
- throw enrichError(err, { routerId: context.routerId, tool: "manage_dhcp_server" });
6816
+ throw toolError(err, context, "manage_dhcp_server");
6938
6817
  }
6939
6818
  }
6940
6819
  };
6941
6820
  var dhcpServerTools = [listDhcpServersTool, manageDhcpServerTool];
6942
6821
 
6943
6822
  // src/domain/tools/ip-pool-tools.ts
6944
- import { z as z29 } from "zod";
6823
+ import { z as z30 } from "zod";
6945
6824
  var log30 = createLogger("ip-pool-tools");
6946
- var listIpPoolsInputSchema = z29.object({
6947
- routerId: z29.string().describe("Target router identifier from the router registry"),
6948
- name: z29.string().optional().describe("Filter by pool name (substring match)"),
6949
- limit: z29.number().int().min(1).max(500).default(100).describe("Maximum number of pools to return"),
6950
- offset: z29.number().int().min(0).default(0).describe("Offset for pagination")
6825
+ var listIpPoolsInputSchema = z30.object({
6826
+ routerId,
6827
+ name: z30.string().optional().describe("Filter by pool name (substring match)"),
6828
+ limit,
6829
+ offset
6951
6830
  }).strict();
6952
6831
  var listIpPoolsTool = {
6953
6832
  name: "list_ip_pools",
@@ -6972,9 +6851,7 @@ var listIpPoolsTool = {
6972
6851
  if (parsed.name) {
6973
6852
  filtered = filtered.filter((p) => (p.name ?? "").includes(parsed.name));
6974
6853
  }
6975
- const total = filtered.length;
6976
- const pools = filtered.slice(parsed.offset, parsed.offset + parsed.limit);
6977
- const hasMore = parsed.offset + parsed.limit < total;
6854
+ const { items: pools, total, hasMore } = paginate(filtered, parsed.offset, parsed.limit);
6978
6855
  return {
6979
6856
  content: `IP pools on ${context.routerId}: ${total} total, showing ${pools.length} (offset ${parsed.offset})`,
6980
6857
  structuredContent: {
@@ -6987,17 +6864,17 @@ var listIpPoolsTool = {
6987
6864
  }
6988
6865
  };
6989
6866
  } catch (err) {
6990
- throw enrichError(err, { routerId: context.routerId, tool: "list_ip_pools" });
6867
+ throw toolError(err, context, "list_ip_pools");
6991
6868
  }
6992
6869
  }
6993
6870
  };
6994
- var manageIpPoolInputSchema = z29.object({
6995
- routerId: z29.string().describe("Target router identifier from the router registry"),
6996
- action: z29.enum(["add", "remove"]).describe("Action to perform"),
6997
- name: z29.string().describe("Pool name \u2014 idempotency key"),
6998
- ranges: z29.string().optional().describe("IP range (e.g. '192.168.1.100-192.168.1.200'; required for add)"),
6999
- nextPool: z29.string().optional().describe("Next pool name for overflow"),
7000
- dryRun: z29.boolean().default(false).describe("Preview changes without applying")
6871
+ var manageIpPoolInputSchema = z30.object({
6872
+ routerId,
6873
+ action: z30.enum(["add", "remove"]).describe("Action to perform"),
6874
+ name: z30.string().describe("Pool name \u2014 idempotency key"),
6875
+ ranges: z30.string().optional().describe("IP range (e.g. '192.168.1.100-192.168.1.200'; required for add)"),
6876
+ nextPool: z30.string().optional().describe("Next pool name for overflow"),
6877
+ dryRun
7001
6878
  }).strict();
7002
6879
  var manageIpPoolTool = {
7003
6880
  name: "manage_ip_pool",
@@ -7091,20 +6968,19 @@ var manageIpPoolTool = {
7091
6968
  structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
7092
6969
  };
7093
6970
  } catch (err) {
7094
- if (err instanceof MikroMCPError) throw err;
7095
- throw enrichError(err, { routerId: context.routerId, tool: "manage_ip_pool" });
6971
+ throw toolError(err, context, "manage_ip_pool");
7096
6972
  }
7097
6973
  }
7098
6974
  };
7099
6975
  var ipPoolTools = [listIpPoolsTool, manageIpPoolTool];
7100
6976
 
7101
6977
  // src/domain/tools/queue-tools.ts
7102
- import { z as z30 } from "zod";
6978
+ import { z as z31 } from "zod";
7103
6979
  var log31 = createLogger("queue-tools");
7104
- var listQueuesInputSchema = z30.object({
7105
- routerId: z30.string().describe("Target router identifier from the router registry"),
7106
- target: z30.string().optional().describe("Filter by target address (substring match)"),
7107
- limit: z30.number().int().min(1).max(500).default(100).describe("Maximum number of queues to return")
6980
+ var listQueuesInputSchema = z31.object({
6981
+ routerId,
6982
+ target: z31.string().optional().describe("Filter by target address (substring match)"),
6983
+ limit
7108
6984
  }).strict();
7109
6985
  var listQueuesTool = {
7110
6986
  name: "list_queues",
@@ -7139,19 +7015,19 @@ var listQueuesTool = {
7139
7015
  }
7140
7016
  };
7141
7017
  } catch (err) {
7142
- throw enrichError(err, { routerId: context.routerId, tool: "list_queues" });
7018
+ throw toolError(err, context, "list_queues");
7143
7019
  }
7144
7020
  }
7145
7021
  };
7146
- var manageQueueInputSchema = z30.object({
7147
- routerId: z30.string().describe("Target router identifier from the router registry"),
7148
- action: z30.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
7149
- name: z30.string().describe("Queue name \u2014 idempotency key"),
7150
- target: z30.string().optional().describe("Target address (required for add; e.g. '192.168.1.0/24')"),
7151
- maxLimit: z30.string().optional().describe("Max upload/download limit (e.g. '10M/10M')"),
7152
- limitAt: z30.string().optional().describe("Guaranteed rate (e.g. '1M/1M')"),
7153
- comment: z30.string().optional().describe("Optional comment"),
7154
- dryRun: z30.boolean().default(false).describe("Preview changes without applying")
7022
+ var manageQueueInputSchema = z31.object({
7023
+ routerId,
7024
+ action: z31.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
7025
+ name: z31.string().describe("Queue name \u2014 idempotency key"),
7026
+ target: z31.string().optional().describe("Target address (required for add; e.g. '192.168.1.0/24')"),
7027
+ maxLimit: z31.string().optional().describe("Max upload/download limit (e.g. '10M/10M')"),
7028
+ limitAt: z31.string().optional().describe("Guaranteed rate (e.g. '1M/1M')"),
7029
+ comment: z31.string().optional().describe("Optional comment"),
7030
+ dryRun
7155
7031
  }).strict();
7156
7032
  var manageQueueTool = {
7157
7033
  name: "manage_queue",
@@ -7297,20 +7173,19 @@ var manageQueueTool = {
7297
7173
  structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
7298
7174
  };
7299
7175
  } catch (err) {
7300
- if (err instanceof MikroMCPError) throw err;
7301
- throw enrichError(err, { routerId: context.routerId, tool: "manage_queue" });
7176
+ throw toolError(err, context, "manage_queue");
7302
7177
  }
7303
7178
  }
7304
7179
  };
7305
7180
  var queueTools = [listQueuesTool, manageQueueTool];
7306
7181
 
7307
7182
  // src/domain/tools/vrrp-tools.ts
7308
- import { z as z31 } from "zod";
7183
+ import { z as z32 } from "zod";
7309
7184
  var log32 = createLogger("vrrp-tools");
7310
- var listVrrpInstancesInputSchema = z31.object({
7311
- routerId: z31.string().describe("Target router identifier from the router registry"),
7312
- interface: z31.string().optional().describe("Filter by master interface name (exact match)"),
7313
- limit: z31.number().int().min(1).max(500).default(100).describe("Maximum number of instances to return")
7185
+ var listVrrpInstancesInputSchema = z32.object({
7186
+ routerId,
7187
+ interface: z32.string().optional().describe("Filter by master interface name (exact match)"),
7188
+ limit
7314
7189
  }).strict();
7315
7190
  var listVrrpInstancesTool = {
7316
7191
  name: "list_vrrp_instances",
@@ -7345,21 +7220,21 @@ var listVrrpInstancesTool = {
7345
7220
  }
7346
7221
  };
7347
7222
  } catch (err) {
7348
- throw enrichError(err, { routerId: context.routerId, tool: "list_vrrp_instances" });
7223
+ throw toolError(err, context, "list_vrrp_instances");
7349
7224
  }
7350
7225
  }
7351
7226
  };
7352
- var manageVrrpInstanceInputSchema = z31.object({
7353
- routerId: z31.string().describe("Target router identifier from the router registry"),
7354
- action: z31.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
7355
- name: z31.string().describe("VRRP interface name \u2014 idempotency key"),
7356
- interface: z31.string().optional().describe("Master interface (required for add)"),
7357
- vrid: z31.number().int().min(1).max(255).optional().describe("Virtual router ID (required for add)"),
7358
- priority: z31.number().int().min(1).max(254).default(100).describe("Router priority (1\u2013254)"),
7359
- interval: z31.number().int().min(1).optional().describe("Advertisement interval in seconds"),
7360
- version: z31.enum(["2", "3"]).default("3").describe("VRRP protocol version"),
7361
- comment: z31.string().optional().describe("Optional comment"),
7362
- dryRun: z31.boolean().default(false).describe("Preview changes without applying")
7227
+ var manageVrrpInstanceInputSchema = z32.object({
7228
+ routerId,
7229
+ action: z32.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
7230
+ name: z32.string().describe("VRRP interface name \u2014 idempotency key"),
7231
+ interface: z32.string().optional().describe("Master interface (required for add)"),
7232
+ vrid: z32.number().int().min(1).max(255).optional().describe("Virtual router ID (required for add)"),
7233
+ priority: z32.number().int().min(1).max(254).default(100).describe("Router priority (1\u2013254)"),
7234
+ interval: z32.number().int().min(1).optional().describe("Advertisement interval in seconds"),
7235
+ version: z32.enum(["2", "3"]).default("3").describe("VRRP protocol version"),
7236
+ comment: z32.string().optional().describe("Optional comment"),
7237
+ dryRun
7363
7238
  }).strict();
7364
7239
  var manageVrrpInstanceTool = {
7365
7240
  name: "manage_vrrp_instance",
@@ -7527,18 +7402,17 @@ var manageVrrpInstanceTool = {
7527
7402
  structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
7528
7403
  };
7529
7404
  } catch (err) {
7530
- if (err instanceof MikroMCPError) throw err;
7531
- throw enrichError(err, { routerId: context.routerId, tool: "manage_vrrp_instance" });
7405
+ throw toolError(err, context, "manage_vrrp_instance");
7532
7406
  }
7533
7407
  }
7534
7408
  };
7535
7409
  var vrrpTools = [listVrrpInstancesTool, manageVrrpInstanceTool];
7536
7410
 
7537
7411
  // src/domain/tools/network-services-tools.ts
7538
- import { z as z32 } from "zod";
7412
+ import { z as z33 } from "zod";
7539
7413
  var log33 = createLogger("network-services-tools");
7540
- var getSnmpSettingsInputSchema = z32.object({
7541
- routerId: z32.string().describe("Target router identifier from the router registry")
7414
+ var getSnmpSettingsInputSchema = z33.object({
7415
+ routerId
7542
7416
  }).strict();
7543
7417
  var getSnmpSettingsTool = {
7544
7418
  name: "get_snmp_settings",
@@ -7577,13 +7451,12 @@ var getSnmpSettingsTool = {
7577
7451
  }
7578
7452
  };
7579
7453
  } catch (err) {
7580
- if (err instanceof MikroMCPError) throw err;
7581
- throw enrichError(err, { routerId: context.routerId, tool: "get_snmp_settings" });
7454
+ throw toolError(err, context, "get_snmp_settings");
7582
7455
  }
7583
7456
  }
7584
7457
  };
7585
- var getNtpSettingsInputSchema = z32.object({
7586
- routerId: z32.string().describe("Target router identifier from the router registry")
7458
+ var getNtpSettingsInputSchema = z33.object({
7459
+ routerId
7587
7460
  }).strict();
7588
7461
  var getNtpSettingsTool = {
7589
7462
  name: "get_ntp_settings",
@@ -7622,16 +7495,15 @@ var getNtpSettingsTool = {
7622
7495
  }
7623
7496
  };
7624
7497
  } catch (err) {
7625
- if (err instanceof MikroMCPError) throw err;
7626
- throw enrichError(err, { routerId: context.routerId, tool: "get_ntp_settings" });
7498
+ throw toolError(err, context, "get_ntp_settings");
7627
7499
  }
7628
7500
  }
7629
7501
  };
7630
- var listNetwatchEntriesInputSchema = z32.object({
7631
- routerId: z32.string().describe("Target router identifier from the router registry"),
7632
- host: z32.string().optional().describe("Filter by host (substring match)"),
7633
- status: z32.enum(["up", "down", "unknown"]).optional().describe("Filter by current status"),
7634
- limit: z32.number().int().min(1).max(500).default(100).describe("Maximum number of entries to return")
7502
+ var listNetwatchEntriesInputSchema = z33.object({
7503
+ routerId,
7504
+ host: z33.string().optional().describe("Filter by host (substring match)"),
7505
+ status: z33.enum(["up", "down", "unknown"]).optional().describe("Filter by current status"),
7506
+ limit
7635
7507
  }).strict();
7636
7508
  var listNetwatchEntriesTool = {
7637
7509
  name: "list_netwatch_entries",
@@ -7664,19 +7536,19 @@ var listNetwatchEntriesTool = {
7664
7536
  }
7665
7537
  };
7666
7538
  } catch (err) {
7667
- throw enrichError(err, { routerId: context.routerId, tool: "list_netwatch_entries" });
7539
+ throw toolError(err, context, "list_netwatch_entries");
7668
7540
  }
7669
7541
  }
7670
7542
  };
7671
- var manageNetwatchEntryInputSchema = z32.object({
7672
- routerId: z32.string().describe("Target router identifier from the router registry"),
7673
- action: z32.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
7674
- host: z32.string().describe("Host to monitor \u2014 idempotency key"),
7675
- port: z32.number().int().min(1).max(65535).optional().describe("TCP port (optional; ICMP if omitted)"),
7676
- interval: z32.string().optional().describe("Check interval (e.g. '1m'; default '1m')"),
7677
- timeout: z32.string().optional().describe("Probe timeout (e.g. '500ms')"),
7678
- comment: z32.string().optional().describe("Optional comment"),
7679
- dryRun: z32.boolean().default(false).describe("Preview changes without applying")
7543
+ var manageNetwatchEntryInputSchema = z33.object({
7544
+ routerId,
7545
+ action: z33.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
7546
+ host: z33.string().describe("Host to monitor \u2014 idempotency key"),
7547
+ port: z33.number().int().min(1).max(65535).optional().describe("TCP port (optional; ICMP if omitted)"),
7548
+ interval: z33.string().optional().describe("Check interval (e.g. '1m'; default '1m')"),
7549
+ timeout: z33.string().optional().describe("Probe timeout (e.g. '500ms')"),
7550
+ comment: z33.string().optional().describe("Optional comment"),
7551
+ dryRun
7680
7552
  }).strict();
7681
7553
  var manageNetwatchEntryTool = {
7682
7554
  name: "manage_netwatch_entry",
@@ -7819,15 +7691,14 @@ var manageNetwatchEntryTool = {
7819
7691
  structuredContent: { action: resultAction, host: parsed.host, id: existing[".id"] }
7820
7692
  };
7821
7693
  } catch (err) {
7822
- if (err instanceof MikroMCPError) throw err;
7823
- throw enrichError(err, { routerId: context.routerId, tool: "manage_netwatch_entry" });
7694
+ throw toolError(err, context, "manage_netwatch_entry");
7824
7695
  }
7825
7696
  }
7826
7697
  };
7827
- var listNeighborsInputSchema = z32.object({
7828
- routerId: z32.string().describe("Target router identifier from the router registry"),
7829
- interface: z32.string().optional().describe("Filter by interface name (substring match)"),
7830
- limit: z32.number().int().min(1).max(500).default(100).describe("Maximum number of neighbors to return")
7698
+ var listNeighborsInputSchema = z33.object({
7699
+ routerId,
7700
+ interface: z33.string().optional().describe("Filter by interface name (substring match)"),
7701
+ limit
7831
7702
  }).strict();
7832
7703
  var listNeighborsTool = {
7833
7704
  name: "list_neighbors",
@@ -7862,16 +7733,16 @@ var listNeighborsTool = {
7862
7733
  }
7863
7734
  };
7864
7735
  } catch (err) {
7865
- throw enrichError(err, { routerId: context.routerId, tool: "list_neighbors" });
7736
+ throw toolError(err, context, "list_neighbors");
7866
7737
  }
7867
7738
  }
7868
7739
  };
7869
- var listArpEntriesInputSchema = z32.object({
7870
- routerId: z32.string().describe("Target router identifier from the router registry"),
7871
- interface: z32.string().optional().describe("Filter by interface (substring match)"),
7872
- address: z32.string().optional().describe("Filter by IP address (substring match)"),
7873
- macAddress: z32.string().optional().describe("Filter by MAC address (substring match)"),
7874
- limit: z32.number().int().min(1).max(500).default(100).describe("Maximum number of ARP entries to return")
7740
+ var listArpEntriesInputSchema = z33.object({
7741
+ routerId,
7742
+ interface: z33.string().optional().describe("Filter by interface (substring match)"),
7743
+ address: z33.string().optional().describe("Filter by IP address (substring match)"),
7744
+ macAddress: z33.string().optional().describe("Filter by MAC address (substring match)"),
7745
+ limit
7875
7746
  }).strict();
7876
7747
  var listArpEntriesTool = {
7877
7748
  name: "list_arp_entries",
@@ -7906,17 +7777,17 @@ var listArpEntriesTool = {
7906
7777
  }
7907
7778
  };
7908
7779
  } catch (err) {
7909
- throw enrichError(err, { routerId: context.routerId, tool: "list_arp_entries" });
7780
+ throw toolError(err, context, "list_arp_entries");
7910
7781
  }
7911
7782
  }
7912
7783
  };
7913
- var manageNtpClientInputSchema = z32.object({
7914
- routerId: z32.string().describe("Target router identifier from the router registry"),
7915
- enabled: z32.boolean().optional().describe("Enable or disable the NTP client"),
7916
- mode: z32.enum(["unicast", "broadcast", "multicast", "manycast"]).optional().describe("NTP client mode"),
7917
- servers: z32.string().optional().describe("Comma-separated NTP server addresses"),
7918
- vlanInterface: z32.string().optional().describe("VLAN interface for NTP communication"),
7919
- dryRun: z32.boolean().default(false).describe("Preview changes without applying")
7784
+ var manageNtpClientInputSchema = z33.object({
7785
+ routerId,
7786
+ enabled: z33.boolean().optional().describe("Enable or disable the NTP client"),
7787
+ mode: z33.enum(["unicast", "broadcast", "multicast", "manycast"]).optional().describe("NTP client mode"),
7788
+ servers: z33.string().optional().describe("Comma-separated NTP server addresses"),
7789
+ vlanInterface: z33.string().optional().describe("VLAN interface for NTP communication"),
7790
+ dryRun
7920
7791
  }).strict();
7921
7792
  var manageNtpClientTool = {
7922
7793
  name: "manage_ntp_client",
@@ -7978,7 +7849,7 @@ var manageNtpClientTool = {
7978
7849
  structuredContent: { action: "updated", routerId: context.routerId, diff }
7979
7850
  };
7980
7851
  } catch (err) {
7981
- throw enrichError(err, { routerId: context.routerId, tool: "manage_ntp_client" });
7852
+ throw toolError(err, context, "manage_ntp_client");
7982
7853
  }
7983
7854
  }
7984
7855
  };
@@ -7993,22 +7864,22 @@ var networkServicesTools = [
7993
7864
  ];
7994
7865
 
7995
7866
  // src/domain/tools/vlan-tools.ts
7996
- import { z as z33 } from "zod";
7867
+ import { z as z34 } from "zod";
7997
7868
  var log34 = createLogger("vlan-tools");
7998
7869
  function sanitizeComment4(comment) {
7999
7870
  if (comment === void 0) return void 0;
8000
7871
  return comment.replace(/[\x00-\x1f\x7f]/g, "");
8001
7872
  }
8002
- var manageVlanInputSchema = z33.object({
8003
- routerId: z33.string().describe("Target router identifier from the router registry"),
8004
- action: z33.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
8005
- name: z33.string().regex(/^[a-zA-Z0-9_-]+$/).max(15).describe("VLAN interface name \u2014 idempotency key (alphanumeric, hyphens, underscores, max 15 chars)"),
8006
- vlanId: z33.number().int().min(1).max(4094).optional().describe("VLAN ID (1-4094; required for add)"),
8007
- parentInterface: z33.string().optional().describe("Parent interface name (e.g., ether1, bridge1; required for add)"),
8008
- mtu: z33.number().int().min(68).max(9e3).default(1500).describe("MTU size (add only)"),
8009
- disabled: z33.boolean().default(false).describe("Whether to create the VLAN disabled (add only)"),
8010
- comment: z33.string().max(255).optional().describe("Optional comment (add only)"),
8011
- dryRun: z33.boolean().default(false).describe("Preview changes without applying")
7873
+ var manageVlanInputSchema = z34.object({
7874
+ routerId,
7875
+ action: z34.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
7876
+ name: z34.string().regex(/^[a-zA-Z0-9_-]+$/).max(15).describe("VLAN interface name \u2014 idempotency key (alphanumeric, hyphens, underscores, max 15 chars)"),
7877
+ vlanId: z34.number().int().min(1).max(4094).optional().describe("VLAN ID (1-4094; required for add)"),
7878
+ parentInterface: z34.string().optional().describe("Parent interface name (e.g., ether1, bridge1; required for add)"),
7879
+ mtu: z34.number().int().min(68).max(9e3).default(1500).describe("MTU size (add only)"),
7880
+ disabled: z34.boolean().default(false).describe("Whether to create the VLAN disabled (add only)"),
7881
+ comment: z34.string().max(255).optional().describe("Optional comment (add only)"),
7882
+ dryRun
8012
7883
  }).strict();
8013
7884
  var manageVlanTool = {
8014
7885
  name: "manage_vlan",
@@ -8154,22 +8025,21 @@ var manageVlanTool = {
8154
8025
  structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
8155
8026
  };
8156
8027
  } catch (err) {
8157
- if (err instanceof MikroMCPError) throw err;
8158
- throw enrichError(err, { routerId: context.routerId, tool: "manage_vlan" });
8028
+ throw toolError(err, context, "manage_vlan");
8159
8029
  }
8160
8030
  }
8161
8031
  };
8162
8032
  var vlanTools = [manageVlanTool];
8163
8033
 
8164
8034
  // src/domain/tools/dhcp-client-tools.ts
8165
- import { z as z34 } from "zod";
8035
+ import { z as z35 } from "zod";
8166
8036
  var log35 = createLogger("dhcp-client-tools");
8167
- var listDhcpClientsInputSchema = z34.object({
8168
- routerId: z34.string().describe("Target router identifier from the router registry"),
8169
- interface: z34.string().optional().describe("Filter by interface name (exact match)"),
8170
- status: z34.enum(["bound", "searching", "requesting", "init", "all"]).default("all").describe("Filter by DHCP client status"),
8171
- limit: z34.number().int().min(1).max(500).default(100).describe("Maximum number of clients to return"),
8172
- offset: z34.number().int().min(0).default(0).describe("Offset for pagination")
8037
+ var listDhcpClientsInputSchema = z35.object({
8038
+ routerId,
8039
+ interface: z35.string().optional().describe("Filter by interface name (exact match)"),
8040
+ status: z35.enum(["bound", "searching", "requesting", "init", "all"]).default("all").describe("Filter by DHCP client status"),
8041
+ limit,
8042
+ offset
8173
8043
  }).strict();
8174
8044
  var listDhcpClientsTool = {
8175
8045
  name: "list_dhcp_clients",
@@ -8197,22 +8067,9 @@ var listDhcpClientsTool = {
8197
8067
  if (parsed.status !== "all") {
8198
8068
  filtered = filtered.filter((c) => c.status === parsed.status);
8199
8069
  }
8200
- const total = filtered.length;
8201
- const clients = filtered.slice(parsed.offset, parsed.offset + parsed.limit);
8202
- const hasMore = parsed.offset + parsed.limit < total;
8203
- const lines = [
8204
- `DHCP clients on ${context.routerId}: ${total} total, showing ${clients.length} (offset ${parsed.offset})`
8205
- ];
8206
- for (const c of clients) {
8207
- const rec = c;
8208
- const iface = rec.interface ?? "unknown";
8209
- const status = rec.status ?? "unknown";
8210
- const address = rec.address ?? rec["active-address"] ?? "-";
8211
- const isDisabled = rec.disabled === true || rec.disabled === "true";
8212
- lines.push(` ${iface} [${status}] ${address}${isDisabled ? " DISABLED" : ""}`);
8213
- }
8070
+ const { items: clients, total, hasMore } = paginate(filtered, parsed.offset, parsed.limit);
8214
8071
  return {
8215
- content: lines.join("\n"),
8072
+ content: listSummary("DHCP clients", context.routerId, clients.length, total, parsed.offset),
8216
8073
  structuredContent: {
8217
8074
  routerId: context.routerId,
8218
8075
  clients,
@@ -8223,19 +8080,19 @@ var listDhcpClientsTool = {
8223
8080
  }
8224
8081
  };
8225
8082
  } catch (err) {
8226
- throw enrichError(err, { routerId: context.routerId, tool: "list_dhcp_clients" });
8083
+ throw toolError(err, context, "list_dhcp_clients");
8227
8084
  }
8228
8085
  }
8229
8086
  };
8230
- var manageDhcpClientInputSchema = z34.object({
8231
- routerId: z34.string().describe("Target router identifier from the router registry"),
8232
- action: z34.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
8233
- interface: z34.string().describe("Interface name \u2014 idempotency key (e.g. ether1, ether2)"),
8234
- usePeerDns: z34.boolean().default(true).describe("Use DNS servers provided by DHCP server (add only)"),
8235
- usePeerNtp: z34.boolean().default(false).describe("Use NTP servers provided by DHCP server (add only)"),
8236
- addDefaultRoute: z34.boolean().default(true).describe("Add default route from DHCP (add only)"),
8237
- comment: z34.string().max(255).optional().describe("Optional comment (add only)"),
8238
- dryRun: z34.boolean().default(false).describe("Preview changes without applying")
8087
+ var manageDhcpClientInputSchema = z35.object({
8088
+ routerId,
8089
+ action: z35.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
8090
+ interface: z35.string().describe("Interface name \u2014 idempotency key (e.g. ether1, ether2)"),
8091
+ usePeerDns: z35.boolean().default(true).describe("Use DNS servers provided by DHCP server (add only)"),
8092
+ usePeerNtp: z35.boolean().default(false).describe("Use NTP servers provided by DHCP server (add only)"),
8093
+ addDefaultRoute: z35.boolean().default(true).describe("Add default route from DHCP (add only)"),
8094
+ comment: z35.string().max(255).optional().describe("Optional comment (add only)"),
8095
+ dryRun
8239
8096
  }).strict();
8240
8097
  var manageDhcpClientTool = {
8241
8098
  name: "manage_dhcp_client",
@@ -8353,21 +8210,20 @@ var manageDhcpClientTool = {
8353
8210
  structuredContent: { action: resultAction, interface: parsed.interface, id: existing[".id"] }
8354
8211
  };
8355
8212
  } catch (err) {
8356
- if (err instanceof MikroMCPError) throw err;
8357
- throw enrichError(err, { routerId: context.routerId, tool: "manage_dhcp_client" });
8213
+ throw toolError(err, context, "manage_dhcp_client");
8358
8214
  }
8359
8215
  }
8360
8216
  };
8361
8217
  var dhcpClientTools = [listDhcpClientsTool, manageDhcpClientTool];
8362
8218
 
8363
8219
  // src/domain/tools/ip-service-tools.ts
8364
- import { z as z35 } from "zod";
8220
+ import { z as z36 } from "zod";
8365
8221
  var log36 = createLogger("ip-service-tools");
8366
8222
  var SERVICE_NAMES = ["api", "api-ssl", "ssh", "telnet", "www", "www-ssl", "winbox", "ftp"];
8367
- var listIpServicesInputSchema = z35.object({
8368
- routerId: z35.string().describe("Target router identifier from the router registry"),
8369
- name: z35.enum(SERVICE_NAMES).optional().describe("Filter by service name (api, api-ssl, ssh, telnet, www, www-ssl, winbox, ftp)"),
8370
- enabled: z35.boolean().optional().describe("When true, return only enabled services; when false, only disabled services")
8223
+ var listIpServicesInputSchema = z36.object({
8224
+ routerId,
8225
+ name: z36.enum(SERVICE_NAMES).optional().describe("Filter by service name (api, api-ssl, ssh, telnet, www, www-ssl, winbox, ftp)"),
8226
+ enabled: z36.boolean().optional().describe("When true, return only enabled services; when false, only disabled services")
8371
8227
  }).strict();
8372
8228
  var listIpServicesTool = {
8373
8229
  name: "list_ip_services",
@@ -8398,14 +8254,8 @@ var listIpServicesTool = {
8398
8254
  return parsed.enabled ? !isDisabled : isDisabled;
8399
8255
  });
8400
8256
  }
8401
- const lines = [`IP services on ${context.routerId}: ${filtered.length} returned`];
8402
- for (const s of filtered) {
8403
- const rec = s;
8404
- const isDisabled = rec.disabled === true || rec.disabled === "true";
8405
- lines.push(` ${rec.name} port:${rec.port ?? "-"} [${isDisabled ? "DISABLED" : "enabled"}]`);
8406
- }
8407
8257
  return {
8408
- content: lines.join("\n"),
8258
+ content: `IP services on ${context.routerId}: ${filtered.length} returned. Full records in structuredContent.`,
8409
8259
  structuredContent: {
8410
8260
  routerId: context.routerId,
8411
8261
  services: filtered,
@@ -8413,17 +8263,17 @@ var listIpServicesTool = {
8413
8263
  }
8414
8264
  };
8415
8265
  } catch (err) {
8416
- throw enrichError(err, { routerId: context.routerId, tool: "list_ip_services" });
8266
+ throw toolError(err, context, "list_ip_services");
8417
8267
  }
8418
8268
  }
8419
8269
  };
8420
- var manageIpServiceInputSchema = z35.object({
8421
- routerId: z35.string().describe("Target router identifier from the router registry"),
8422
- action: z35.enum(["enable", "disable"]).describe(
8270
+ var manageIpServiceInputSchema = z36.object({
8271
+ routerId,
8272
+ action: z36.enum(["enable", "disable"]).describe(
8423
8273
  "Action to perform \u2014 only enable/disable to prevent accidental lockout from changing ports"
8424
8274
  ),
8425
- name: z35.enum(SERVICE_NAMES).describe("Service name to manage (api, api-ssl, ssh, telnet, www, www-ssl, winbox, ftp)"),
8426
- dryRun: z35.boolean().default(false).describe("Preview changes without applying")
8275
+ name: z36.enum(SERVICE_NAMES).describe("Service name to manage (api, api-ssl, ssh, telnet, www, www-ssl, winbox, ftp)"),
8276
+ dryRun
8427
8277
  }).strict();
8428
8278
  var manageIpServiceTool = {
8429
8279
  name: "manage_ip_service",
@@ -8491,23 +8341,22 @@ var manageIpServiceTool = {
8491
8341
  structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
8492
8342
  };
8493
8343
  } catch (err) {
8494
- if (err instanceof MikroMCPError) throw err;
8495
- throw enrichError(err, { routerId: context.routerId, tool: "manage_ip_service" });
8344
+ throw toolError(err, context, "manage_ip_service");
8496
8345
  }
8497
8346
  }
8498
8347
  };
8499
8348
  var ipServiceTools = [listIpServicesTool, manageIpServiceTool];
8500
8349
 
8501
8350
  // src/domain/tools/pppoe-tools.ts
8502
- import { z as z36 } from "zod";
8351
+ import { z as z37 } from "zod";
8503
8352
  var log37 = createLogger("pppoe-tools");
8504
8353
  var PPPOE_PATH = "interface/pppoe-client";
8505
- var listPppoeClientsInputSchema = z36.object({
8506
- routerId: z36.string().describe("Target router identifier from the router registry"),
8507
- interface: z36.string().optional().describe("Filter by parent interface name (exact match)"),
8508
- status: z36.enum(["connected", "disconnected", "all"]).default("all").describe("Filter by running status"),
8509
- limit: z36.number().int().min(1).max(500).default(100).describe("Maximum number of clients to return"),
8510
- offset: z36.number().int().min(0).default(0).describe("Offset for pagination")
8354
+ var listPppoeClientsInputSchema = z37.object({
8355
+ routerId,
8356
+ interface: z37.string().optional().describe("Filter by parent interface name (exact match)"),
8357
+ status: z37.enum(["connected", "disconnected", "all"]).default("all").describe("Filter by running status"),
8358
+ limit,
8359
+ offset
8511
8360
  }).strict();
8512
8361
  function isRunning(record) {
8513
8362
  return record.running === true || record.running === "true";
@@ -8540,21 +8389,9 @@ var listPppoeClientsTool = {
8540
8389
  } else if (parsed.status === "disconnected") {
8541
8390
  filtered = filtered.filter((c) => !isRunning(c));
8542
8391
  }
8543
- const total = filtered.length;
8544
- const clients = filtered.slice(parsed.offset, parsed.offset + parsed.limit);
8545
- const hasMore = parsed.offset + parsed.limit < total;
8546
- const lines = [
8547
- `PPPoE clients on ${context.routerId}: ${total} total, showing ${clients.length} (offset ${parsed.offset})`
8548
- ];
8549
- for (const c of clients) {
8550
- const name = c.name ?? "unknown";
8551
- const iface = c.interface ?? "unknown";
8552
- const user = c.user ?? "-";
8553
- const status = isRunning(c) ? "connected" : "disconnected";
8554
- lines.push(` ${name} [${iface}] user=${user} ${status}`);
8555
- }
8392
+ const { items: clients, total, hasMore } = paginate(filtered, parsed.offset, parsed.limit);
8556
8393
  return {
8557
- content: lines.join("\n"),
8394
+ content: listSummary("PPPoE clients", context.routerId, clients.length, total, parsed.offset),
8558
8395
  structuredContent: {
8559
8396
  routerId: context.routerId,
8560
8397
  clients,
@@ -8565,26 +8402,26 @@ var listPppoeClientsTool = {
8565
8402
  }
8566
8403
  };
8567
8404
  } catch (err) {
8568
- throw enrichError(err, { routerId: context.routerId, tool: "list_pppoe_clients" });
8405
+ throw toolError(err, context, "list_pppoe_clients");
8569
8406
  }
8570
8407
  }
8571
8408
  };
8572
- var managePppoeClientInputSchema = z36.object({
8573
- routerId: z36.string().describe("Target router identifier from the router registry"),
8574
- action: z36.enum(["add", "update", "remove"]).describe("Action to perform"),
8575
- name: z36.string().describe("PPPoE client interface name \u2014 idempotency key (e.g. pppoe-wan)"),
8576
- interface: z36.string().optional().describe("Parent interface (required for add)"),
8577
- user: z36.string().optional().describe("PPPoE username (required for add)"),
8578
- password: z36.string().optional().describe("PPPoE password (never logged)"),
8579
- serviceName: z36.string().optional().describe("PPPoE service name filter (leave empty to match any)"),
8580
- addDefaultRoute: z36.boolean().optional().describe("Add default route via PPPoE (yes/no)"),
8581
- dialOnDemand: z36.boolean().optional().describe("Dial on demand instead of always-on (yes/no)"),
8582
- dryRun: z36.boolean().default(false).describe("Preview changes without applying")
8409
+ var managePppoeClientInputSchema = z37.object({
8410
+ routerId,
8411
+ action: z37.enum(["add", "update", "remove"]).describe("Action to perform"),
8412
+ name: z37.string().describe("PPPoE client interface name \u2014 idempotency key (e.g. pppoe-wan)"),
8413
+ interface: z37.string().optional().describe("Parent interface (required for add)"),
8414
+ user: z37.string().optional().describe("PPPoE username (required for add)"),
8415
+ password: z37.string().optional().describe("PPPoE password (never logged)"),
8416
+ serviceName: z37.string().optional().describe("PPPoE service name filter (leave empty to match any)"),
8417
+ addDefaultRoute: z37.boolean().optional().describe("Add default route via PPPoE (yes/no)"),
8418
+ dialOnDemand: z37.boolean().optional().describe("Dial on demand instead of always-on (yes/no)"),
8419
+ dryRun
8583
8420
  }).strict();
8584
8421
  var managePppoeClientTool = {
8585
8422
  name: "manage_pppoe_client",
8586
8423
  title: "Manage PPPoE Client",
8587
- description: "Add, update, or remove a PPPoE client interface. Idempotent by name: add returns already_exists if same name+interface+user exists; throws CONFLICT if same name exists with different config. Update builds a diff of changed fields and returns no_change when nothing differs. Password is always written when provided because RouterOS does not expose it in GET.",
8424
+ description: "Add, update, or remove a PPPoE client interface. Idempotent by name (already_exists on matching name+interface+user; CONFLICT on differing config; no_change when an update differs in nothing). Password is always written when provided since RouterOS does not return it on GET.",
8588
8425
  inputSchema: managePppoeClientInputSchema,
8589
8426
  annotations: {
8590
8427
  readOnlyHint: false,
@@ -8775,22 +8612,21 @@ var managePppoeClientTool = {
8775
8612
  structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
8776
8613
  };
8777
8614
  } catch (err) {
8778
- if (err instanceof MikroMCPError) throw err;
8779
- throw enrichError(err, { routerId: context.routerId, tool: "manage_pppoe_client" });
8615
+ throw toolError(err, context, "manage_pppoe_client");
8780
8616
  }
8781
8617
  }
8782
8618
  };
8783
8619
  var pppoeTools = [listPppoeClientsTool, managePppoeClientTool];
8784
8620
 
8785
8621
  // src/domain/tools/ovpn-tools.ts
8786
- import { z as z37 } from "zod";
8622
+ import { z as z38 } from "zod";
8787
8623
  var log38 = createLogger("ovpn-tools");
8788
8624
  var OVPN_CLIENT_PATH = "interface/ovpn-client";
8789
8625
  var OVPN_SERVER_PATH = "interface/ovpn-server/server";
8790
- var listOvpnClientsInputSchema = z37.object({
8791
- routerId: z37.string().describe("Target router identifier from the router registry"),
8792
- limit: z37.number().int().min(1).max(500).default(100).describe("Maximum number of clients to return"),
8793
- offset: z37.number().int().min(0).default(0).describe("Offset for pagination")
8626
+ var listOvpnClientsInputSchema = z38.object({
8627
+ routerId,
8628
+ limit,
8629
+ offset
8794
8630
  }).strict();
8795
8631
  var listOvpnClientsTool = {
8796
8632
  name: "list_ovpn_clients",
@@ -8811,9 +8647,7 @@ var listOvpnClientsTool = {
8811
8647
  limit: void 0,
8812
8648
  offset: void 0
8813
8649
  });
8814
- const total = allClients.length;
8815
- const clients = allClients.slice(parsed.offset, parsed.offset + parsed.limit);
8816
- const hasMore = parsed.offset + parsed.limit < total;
8650
+ const { items: clients, total, hasMore } = paginate(allClients, parsed.offset, parsed.limit);
8817
8651
  return {
8818
8652
  content: `OpenVPN clients on ${context.routerId}: ${total} total, showing ${clients.length} (offset ${parsed.offset})`,
8819
8653
  structuredContent: {
@@ -8826,27 +8660,27 @@ var listOvpnClientsTool = {
8826
8660
  }
8827
8661
  };
8828
8662
  } catch (err) {
8829
- throw enrichError(err, { routerId: context.routerId, tool: "list_ovpn_clients" });
8663
+ throw toolError(err, context, "list_ovpn_clients");
8830
8664
  }
8831
8665
  }
8832
8666
  };
8833
- var manageOvpnClientInputSchema = z37.object({
8834
- routerId: z37.string().describe("Target router identifier from the router registry"),
8835
- action: z37.enum(["add", "update", "remove"]).describe("Action to perform"),
8836
- name: z37.string().describe("OpenVPN client interface name \u2014 idempotency key"),
8837
- connectTo: z37.string().optional().describe("Remote server address (required for add)"),
8838
- port: z37.number().int().min(1).max(65535).optional().describe("Remote port number"),
8839
- mode: z37.enum(["ip", "ethernet"]).optional().describe("Tunnel mode"),
8840
- protocol: z37.enum(["tcp-client", "udp"]).optional().describe("Transport protocol"),
8841
- certificate: z37.string().optional().describe("Client certificate name"),
8842
- user: z37.string().optional().describe("VPN username"),
8843
- password: z37.string().optional().describe("VPN password (never logged)"),
8844
- dryRun: z37.boolean().default(false).describe("Preview changes without applying")
8667
+ var manageOvpnClientInputSchema = z38.object({
8668
+ routerId,
8669
+ action: z38.enum(["add", "update", "remove"]).describe("Action to perform"),
8670
+ name: z38.string().describe("OpenVPN client interface name \u2014 idempotency key"),
8671
+ connectTo: z38.string().optional().describe("Remote server address (required for add)"),
8672
+ port: z38.number().int().min(1).max(65535).optional().describe("Remote port number"),
8673
+ mode: z38.enum(["ip", "ethernet"]).optional().describe("Tunnel mode"),
8674
+ protocol: z38.enum(["tcp-client", "udp"]).optional().describe("Transport protocol"),
8675
+ certificate: z38.string().optional().describe("Client certificate name"),
8676
+ user: z38.string().optional().describe("VPN username"),
8677
+ password: z38.string().optional().describe("VPN password (never logged)"),
8678
+ dryRun
8845
8679
  }).strict();
8846
8680
  var manageOvpnClientTool = {
8847
8681
  name: "manage_ovpn_client",
8848
8682
  title: "Manage OpenVPN Client",
8849
- description: "Add, update, or remove an OpenVPN client interface. Idempotent by name: add returns already_exists if same name+connectTo exists; throws CONFLICT if same name exists with different connectTo. Update builds a diff of changed fields and returns no_change when nothing differs. Password is always written when provided because RouterOS does not expose it in GET.",
8683
+ description: "Add, update, or remove an OpenVPN client interface. Idempotent by name (already_exists on matching name+connectTo; CONFLICT on differing connectTo; no_change when an update differs in nothing). Password is always written when provided since RouterOS does not return it on GET.",
8850
8684
  inputSchema: manageOvpnClientInputSchema,
8851
8685
  annotations: {
8852
8686
  readOnlyHint: false,
@@ -9013,21 +8847,20 @@ var manageOvpnClientTool = {
9013
8847
  structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
9014
8848
  };
9015
8849
  } catch (err) {
9016
- if (err instanceof MikroMCPError) throw err;
9017
- throw enrichError(err, { routerId: context.routerId, tool: "manage_ovpn_client" });
8850
+ throw toolError(err, context, "manage_ovpn_client");
9018
8851
  }
9019
8852
  }
9020
8853
  };
9021
- var getOvpnServerInputSchema = z37.object({
9022
- routerId: z37.string().describe("Target router identifier from the router registry")
8854
+ var getOvpnServerInputSchema = z38.object({
8855
+ routerId
9023
8856
  }).strict();
9024
- function fetchOvpnServer(allRecords, routerId) {
8857
+ function fetchOvpnServer(allRecords, routerId2) {
9025
8858
  if (allRecords.length === 0) {
9026
8859
  throw new MikroMCPError({
9027
8860
  category: "NOT_FOUND" /* NOT_FOUND */,
9028
8861
  code: "OVPN_SERVER_NOT_FOUND",
9029
8862
  message: "OpenVPN server not found. The OpenVPN package may not be installed on this router.",
9030
- details: { routerId },
8863
+ details: { routerId: routerId2 },
9031
8864
  recoverability: {
9032
8865
  retryable: false,
9033
8866
  suggestedAction: "Install the OpenVPN package via manage_package.",
@@ -9049,39 +8882,38 @@ var getOvpnServerTool = {
9049
8882
  openWorldHint: false
9050
8883
  },
9051
8884
  async handler(params, context) {
9052
- const parsed = getOvpnServerInputSchema.parse(params);
8885
+ getOvpnServerInputSchema.parse(params);
9053
8886
  log38.info({ routerId: context.routerId }, "Getting OpenVPN server configuration");
9054
8887
  try {
9055
8888
  const records = await context.routerClient.get(OVPN_SERVER_PATH, {
9056
8889
  limit: void 0,
9057
8890
  offset: void 0
9058
8891
  });
9059
- const server = fetchOvpnServer(records, parsed.routerId);
8892
+ const server = fetchOvpnServer(records, context.routerId);
9060
8893
  return {
9061
8894
  content: `OpenVPN server on ${context.routerId}: enabled=${server.enabled}, port=${server.port}, protocol=${server.protocol}`,
9062
8895
  structuredContent: { routerId: context.routerId, server }
9063
8896
  };
9064
8897
  } catch (err) {
9065
- if (err instanceof MikroMCPError) throw err;
9066
- throw enrichError(err, { routerId: context.routerId, tool: "get_ovpn_server" });
8898
+ throw toolError(err, context, "get_ovpn_server");
9067
8899
  }
9068
8900
  }
9069
8901
  };
9070
- var manageOvpnServerInputSchema = z37.object({
9071
- routerId: z37.string().describe("Target router identifier from the router registry"),
9072
- action: z37.enum(["enable", "disable", "set"]).describe("Action to perform"),
9073
- port: z37.number().int().min(1).max(65535).optional().describe("Listening port (set action only)"),
9074
- mode: z37.enum(["ip", "ethernet"]).optional().describe("Tunnel mode (set action only)"),
9075
- protocol: z37.enum(["tcp-server", "udp"]).optional().describe("Transport protocol (set action only)"),
9076
- certificate: z37.string().optional().describe("Server certificate name (set action only)"),
9077
- cipher: z37.enum(["blowfish128", "aes128-cbc", "aes192-cbc", "aes256-cbc", "aes128-gcm", "aes256-gcm", "none"]).optional().describe("Encryption cipher (set action only)"),
9078
- auth: z37.enum(["md5", "sha1", "sha256", "sha512", "null"]).optional().describe("Authentication algorithm (set action only)"),
9079
- dryRun: z37.boolean().default(false).describe("Preview changes without applying")
8902
+ var manageOvpnServerInputSchema = z38.object({
8903
+ routerId,
8904
+ action: z38.enum(["enable", "disable", "set"]).describe("Action to perform"),
8905
+ port: z38.number().int().min(1).max(65535).optional().describe("Listening port (set action only)"),
8906
+ mode: z38.enum(["ip", "ethernet"]).optional().describe("Tunnel mode (set action only)"),
8907
+ protocol: z38.enum(["tcp-server", "udp"]).optional().describe("Transport protocol (set action only)"),
8908
+ certificate: z38.string().optional().describe("Server certificate name (set action only)"),
8909
+ cipher: z38.enum(["blowfish128", "aes128-cbc", "aes192-cbc", "aes256-cbc", "aes128-gcm", "aes256-gcm", "none"]).optional().describe("Encryption cipher (set action only)"),
8910
+ auth: z38.enum(["md5", "sha1", "sha256", "sha512", "null"]).optional().describe("Authentication algorithm (set action only)"),
8911
+ dryRun
9080
8912
  }).strict();
9081
8913
  var manageOvpnServerTool = {
9082
8914
  name: "manage_ovpn_server",
9083
8915
  title: "Manage OpenVPN Server",
9084
- description: "Enable, disable, or configure the OpenVPN server on a MikroTik router. The server is a singleton \u2014 there is only one per router. Throws NOT_FOUND if the OpenVPN package is not installed. For set action, at least one configuration field must be provided.",
8916
+ description: "Enable, disable, or configure the OpenVPN server (a per-router singleton). Throws NOT_FOUND if the OpenVPN package is not installed. The set action requires at least one configuration field.",
9085
8917
  inputSchema: manageOvpnServerInputSchema,
9086
8918
  annotations: {
9087
8919
  readOnlyHint: false,
@@ -9098,7 +8930,7 @@ var manageOvpnServerTool = {
9098
8930
  limit: void 0,
9099
8931
  offset: void 0
9100
8932
  });
9101
- const server = fetchOvpnServer(records, parsed.routerId);
8933
+ const server = fetchOvpnServer(records, context.routerId);
9102
8934
  const serverId = server[".id"];
9103
8935
  if (parsed.action === "enable" || parsed.action === "disable") {
9104
8936
  const desiredEnabled = parsed.action === "enable";
@@ -9186,8 +9018,7 @@ var manageOvpnServerTool = {
9186
9018
  structuredContent: { action: "updated", diff }
9187
9019
  };
9188
9020
  } catch (err) {
9189
- if (err instanceof MikroMCPError) throw err;
9190
- throw enrichError(err, { routerId: context.routerId, tool: "manage_ovpn_server" });
9021
+ throw toolError(err, context, "manage_ovpn_server");
9191
9022
  }
9192
9023
  }
9193
9024
  };
@@ -9199,12 +9030,12 @@ var ovpnTools = [
9199
9030
  ];
9200
9031
 
9201
9032
  // src/domain/tools/ppp-tools.ts
9202
- import { z as z38 } from "zod";
9033
+ import { z as z39 } from "zod";
9203
9034
  var log39 = createLogger("ppp-tools");
9204
- var listPppProfilesInputSchema = z38.object({
9205
- routerId: z38.string().describe("Target router identifier from the router registry"),
9206
- name: z38.string().optional().describe("Filter by profile name (exact match)"),
9207
- limit: z38.number().int().min(1).max(500).default(100).describe("Maximum profiles to return")
9035
+ var listPppProfilesInputSchema = z39.object({
9036
+ routerId,
9037
+ name: z39.string().optional().describe("Filter by profile name (exact match)"),
9038
+ limit
9208
9039
  }).strict();
9209
9040
  var listPppProfilesTool = {
9210
9041
  name: "list_ppp_profiles",
@@ -9224,22 +9055,21 @@ var listPppProfilesTool = {
9224
9055
  structuredContent: { routerId: context.routerId, profiles, total: all.length, returned: profiles.length }
9225
9056
  };
9226
9057
  } catch (err) {
9227
- if (err instanceof MikroMCPError) throw err;
9228
- throw enrichError(err, { routerId: context.routerId, tool: "list_ppp_profiles" });
9058
+ throw toolError(err, context, "list_ppp_profiles");
9229
9059
  }
9230
9060
  }
9231
9061
  };
9232
- var managePppProfileInputSchema = z38.object({
9233
- routerId: z38.string().describe("Target router identifier from the router registry"),
9234
- action: z38.enum(["add", "update", "remove"]).describe("Action to perform"),
9235
- name: z38.string().describe("Profile name \u2014 idempotency key"),
9236
- localAddress: z38.string().optional().describe("Local IP assigned to router end of PPP link"),
9237
- remoteAddress: z38.string().optional().describe("IP or pool name assigned to client"),
9238
- dnsServer: z38.string().optional().describe("DNS server IP pushed to client"),
9239
- rateLimit: z38.string().optional().describe("Rate limit string (e.g. '10M/10M')"),
9240
- sessionTimeout: z38.string().optional().describe("Session timeout duration string (e.g. '1h')"),
9241
- comment: z38.string().optional().describe("Optional comment"),
9242
- dryRun: z38.boolean().default(false).describe("Preview changes without applying")
9062
+ var managePppProfileInputSchema = z39.object({
9063
+ routerId,
9064
+ action: z39.enum(["add", "update", "remove"]).describe("Action to perform"),
9065
+ name: z39.string().describe("Profile name \u2014 idempotency key"),
9066
+ localAddress: z39.string().optional().describe("Local IP assigned to router end of PPP link"),
9067
+ remoteAddress: z39.string().optional().describe("IP or pool name assigned to client"),
9068
+ dnsServer: z39.string().optional().describe("DNS server IP pushed to client"),
9069
+ rateLimit: z39.string().optional().describe("Rate limit string (e.g. '10M/10M')"),
9070
+ sessionTimeout: z39.string().optional().describe("Session timeout duration string (e.g. '1h')"),
9071
+ comment: z39.string().optional().describe("Optional comment"),
9072
+ dryRun
9243
9073
  }).strict();
9244
9074
  var managePppProfileTool = {
9245
9075
  name: "manage_ppp_profile",
@@ -9364,18 +9194,17 @@ var managePppProfileTool = {
9364
9194
  structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
9365
9195
  };
9366
9196
  } catch (err) {
9367
- if (err instanceof MikroMCPError) throw err;
9368
- throw enrichError(err, { routerId: context.routerId, tool: "manage_ppp_profile" });
9197
+ throw toolError(err, context, "manage_ppp_profile");
9369
9198
  }
9370
9199
  }
9371
9200
  };
9372
9201
  var pppTools = [listPppProfilesTool, managePppProfileTool];
9373
9202
 
9374
9203
  // src/domain/tools/upgrade-tools.ts
9375
- import { z as z39 } from "zod";
9204
+ import { z as z40 } from "zod";
9376
9205
  var log40 = createLogger("upgrade-tools");
9377
- var getUpgradeStatusInputSchema = z39.object({
9378
- routerId: z39.string().describe("Target router identifier from the router registry")
9206
+ var getUpgradeStatusInputSchema = z40.object({
9207
+ routerId
9379
9208
  }).strict();
9380
9209
  var getUpgradeStatusTool = {
9381
9210
  name: "get_upgrade_status",
@@ -9415,16 +9244,16 @@ var getUpgradeStatusTool = {
9415
9244
  }
9416
9245
  };
9417
9246
  } catch (err) {
9418
- throw enrichError(err, { routerId: context.routerId, tool: "get_upgrade_status" });
9247
+ throw toolError(err, context, "get_upgrade_status");
9419
9248
  }
9420
9249
  }
9421
9250
  };
9422
- var manageUpgradeInputSchema = z39.object({
9423
- routerId: z39.string().describe("Target router identifier from the router registry"),
9424
- action: z39.enum(["check", "install"]).describe(
9251
+ var manageUpgradeInputSchema = z40.object({
9252
+ routerId,
9253
+ action: z40.enum(["check", "install"]).describe(
9425
9254
  "check \u2014 trigger a check for available updates; install \u2014 download and install the latest update (triggers reboot)"
9426
9255
  ),
9427
- dryRun: z39.boolean().default(false).describe("Preview the action without executing")
9256
+ dryRun: z40.boolean().default(false).describe("Preview the action without executing")
9428
9257
  }).strict();
9429
9258
  var manageUpgradeTool = {
9430
9259
  name: "manage_upgrade",
@@ -9466,20 +9295,20 @@ var manageUpgradeTool = {
9466
9295
  structuredContent: { action: "install_triggered", routerId: parsed.routerId }
9467
9296
  };
9468
9297
  } catch (err) {
9469
- throw enrichError(err, { routerId: context.routerId, tool: "manage_upgrade" });
9298
+ throw toolError(err, context, "manage_upgrade");
9470
9299
  }
9471
9300
  }
9472
9301
  };
9473
9302
  var upgradeTools = [getUpgradeStatusTool, manageUpgradeTool];
9474
9303
 
9475
9304
  // src/domain/tools/backup-tools.ts
9476
- import { z as z40 } from "zod";
9305
+ import { z as z41 } from "zod";
9477
9306
  var log41 = createLogger("backup-tools");
9478
- var createBackupInputSchema = z40.object({
9479
- routerId: z40.string().describe("Target router identifier from the router registry"),
9480
- name: z40.string().default("backup").describe("Backup file name (without extension)"),
9481
- password: z40.string().optional().describe("Password to encrypt the backup file"),
9482
- dryRun: z40.boolean().default(false).describe("Preview the backup without creating it")
9307
+ var createBackupInputSchema = z41.object({
9308
+ routerId,
9309
+ name: z41.string().default("backup").describe("Backup file name (without extension)"),
9310
+ password: z41.string().optional().describe("Password to encrypt the backup file"),
9311
+ dryRun: z41.boolean().default(false).describe("Preview the backup without creating it")
9483
9312
  }).strict();
9484
9313
  var createBackupTool = {
9485
9314
  name: "create_backup",
@@ -9517,15 +9346,14 @@ var createBackupTool = {
9517
9346
  }
9518
9347
  };
9519
9348
  } catch (err) {
9520
- if (err instanceof MikroMCPError) throw err;
9521
- throw enrichError(err, { routerId: context.routerId, tool: "create_backup" });
9349
+ throw toolError(err, context, "create_backup");
9522
9350
  }
9523
9351
  }
9524
9352
  };
9525
- var exportConfigInputSchema = z40.object({
9526
- routerId: z40.string().describe("Target router identifier from the router registry"),
9527
- compact: z40.boolean().default(false).describe("Export only non-default settings (compact format)"),
9528
- file: z40.string().optional().describe("Save the export to a file on the router (without extension); omit to return inline")
9353
+ var exportConfigInputSchema = z41.object({
9354
+ routerId,
9355
+ compact: z41.boolean().default(false).describe("Export only non-default settings (compact format)"),
9356
+ file: z41.string().optional().describe("Save the export to a file on the router (without extension); omit to return inline")
9529
9357
  }).strict();
9530
9358
  var exportConfigTool = {
9531
9359
  name: "export_config",
@@ -9566,24 +9394,23 @@ ${scriptText}`,
9566
9394
  }
9567
9395
  };
9568
9396
  } catch (err) {
9569
- if (err instanceof MikroMCPError) throw err;
9570
- throw enrichError(err, { routerId: context.routerId, tool: "export_config" });
9397
+ throw toolError(err, context, "export_config");
9571
9398
  }
9572
9399
  }
9573
9400
  };
9574
9401
  var backupTools = [createBackupTool, exportConfigTool];
9575
9402
 
9576
9403
  // src/domain/tools/log-tools.ts
9577
- import { z as z41 } from "zod";
9404
+ import { z as z42 } from "zod";
9578
9405
  var log42 = createLogger("log-tools");
9579
9406
  var LOG_RULES_PATH = "system/logging";
9580
9407
  var LOG_ACTIONS_PATH = "system/logging/action";
9581
- var logActionTypeEnum = z41.enum(["memory", "disk", "remote", "echo", "email"]);
9582
- var listLogRulesInputSchema = z41.object({
9583
- routerId: z41.string().describe("Target router identifier from the router registry"),
9584
- topics: z41.string().optional().describe("Filter by topics (substring match)"),
9585
- logAction: z41.string().optional().describe("Filter by log action target (exact match on action field)"),
9586
- limit: z41.number().int().min(1).max(500).default(100).describe("Maximum number of rules to return (1-500, default 100)")
9408
+ var logActionTypeEnum = z42.enum(["memory", "disk", "remote", "echo", "email"]);
9409
+ var listLogRulesInputSchema = z42.object({
9410
+ routerId,
9411
+ topics: z42.string().optional().describe("Filter by topics (substring match)"),
9412
+ logAction: z42.string().optional().describe("Filter by log action target (exact match on action field)"),
9413
+ limit
9587
9414
  }).strict();
9588
9415
  var listLogRulesTool = {
9589
9416
  name: "list_log_rules",
@@ -9622,23 +9449,22 @@ var listLogRulesTool = {
9622
9449
  }
9623
9450
  };
9624
9451
  } catch (err) {
9625
- if (err instanceof MikroMCPError) throw err;
9626
- throw enrichError(err, { routerId: context.routerId, tool: "list_log_rules" });
9452
+ throw toolError(err, context, "list_log_rules");
9627
9453
  }
9628
9454
  }
9629
9455
  };
9630
- var manageLogRuleInputSchema = z41.object({
9631
- routerId: z41.string().describe("Target router identifier from the router registry"),
9632
- action: z41.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
9633
- topics: z41.string().describe("Log topics (e.g. firewall, system, info) \u2014 idempotency key"),
9634
- logAction: z41.string().describe("Log action target name (RouterOS action field) \u2014 idempotency key"),
9635
- prefix: z41.string().optional().describe("Optional prefix to prepend to log messages"),
9636
- dryRun: z41.boolean().default(false).describe("Preview changes without applying")
9456
+ var manageLogRuleInputSchema = z42.object({
9457
+ routerId,
9458
+ action: z42.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
9459
+ topics: z42.string().describe("Log topics (e.g. firewall, system, info) \u2014 idempotency key"),
9460
+ logAction: z42.string().describe("Log action target name (RouterOS action field) \u2014 idempotency key"),
9461
+ prefix: z42.string().optional().describe("Optional prefix to prepend to log messages"),
9462
+ dryRun
9637
9463
  }).strict();
9638
9464
  var manageLogRuleTool = {
9639
9465
  name: "manage_log_rule",
9640
9466
  title: "Manage Log Rule",
9641
- description: "Add, remove, enable, or disable a RouterOS logging rule. Idempotent by topics+logAction pair. add returns already_exists if matching rule found. remove returns not_found gracefully. enable/disable throw NOT_FOUND if rule not found. Supports dry-run.",
9467
+ description: "Add, remove, enable, or disable a RouterOS logging rule. Idempotent by topics+logAction (add \u2192 already_exists on match; remove \u2192 not_found handled gracefully; enable/disable throw NOT_FOUND when absent). Supports dry-run.",
9642
9468
  inputSchema: manageLogRuleInputSchema,
9643
9469
  annotations: {
9644
9470
  readOnlyHint: false,
@@ -9771,15 +9597,14 @@ var manageLogRuleTool = {
9771
9597
  }
9772
9598
  });
9773
9599
  } catch (err) {
9774
- if (err instanceof MikroMCPError) throw err;
9775
- throw enrichError(err, { routerId: context.routerId, tool: "manage_log_rule" });
9600
+ throw toolError(err, context, "manage_log_rule");
9776
9601
  }
9777
9602
  }
9778
9603
  };
9779
- var listLogActionsInputSchema = z41.object({
9780
- routerId: z41.string().describe("Target router identifier from the router registry"),
9604
+ var listLogActionsInputSchema = z42.object({
9605
+ routerId,
9781
9606
  type: logActionTypeEnum.optional().describe("Filter by action type (exact match): memory, disk, remote, echo, email"),
9782
- limit: z41.number().int().min(1).max(500).default(100).describe("Maximum number of actions to return (1-500, default 100)")
9607
+ limit
9783
9608
  }).strict();
9784
9609
  var listLogActionsTool = {
9785
9610
  name: "list_log_actions",
@@ -9815,20 +9640,19 @@ var listLogActionsTool = {
9815
9640
  }
9816
9641
  };
9817
9642
  } catch (err) {
9818
- if (err instanceof MikroMCPError) throw err;
9819
- throw enrichError(err, { routerId: context.routerId, tool: "list_log_actions" });
9643
+ throw toolError(err, context, "list_log_actions");
9820
9644
  }
9821
9645
  }
9822
9646
  };
9823
- var manageLogActionInputSchema = z41.object({
9824
- routerId: z41.string().describe("Target router identifier from the router registry"),
9825
- action: z41.enum(["add", "remove"]).describe("Action to perform"),
9826
- name: z41.string().describe("Log action name \u2014 idempotency key"),
9647
+ var manageLogActionInputSchema = z42.object({
9648
+ routerId,
9649
+ action: z42.enum(["add", "remove"]).describe("Action to perform"),
9650
+ name: z42.string().describe("Log action name \u2014 idempotency key"),
9827
9651
  type: logActionTypeEnum.optional().describe("Action type: memory, disk, remote, echo, email (required on add)"),
9828
- remote: z41.string().optional().describe("Remote syslog server address (for type=remote)"),
9829
- remotePort: z41.number().int().min(1).max(65535).optional().describe("Remote syslog server port (for type=remote)"),
9830
- diskFileName: z41.string().optional().describe("Disk log file name without extension (for type=disk)"),
9831
- dryRun: z41.boolean().default(false).describe("Preview changes without applying")
9652
+ remote: z42.string().optional().describe("Remote syslog server address (for type=remote)"),
9653
+ remotePort: z42.number().int().min(1).max(65535).optional().describe("Remote syslog server port (for type=remote)"),
9654
+ diskFileName: z42.string().optional().describe("Disk log file name without extension (for type=disk)"),
9655
+ dryRun
9832
9656
  }).strict();
9833
9657
  var manageLogActionTool = {
9834
9658
  name: "manage_log_action",
@@ -9944,8 +9768,7 @@ var manageLogActionTool = {
9944
9768
  }
9945
9769
  });
9946
9770
  } catch (err) {
9947
- if (err instanceof MikroMCPError) throw err;
9948
- throw enrichError(err, { routerId: context.routerId, tool: "manage_log_action" });
9771
+ throw toolError(err, context, "manage_log_action");
9949
9772
  }
9950
9773
  }
9951
9774
  };
@@ -9957,15 +9780,15 @@ var logTools = [
9957
9780
  ];
9958
9781
 
9959
9782
  // src/domain/tools/network-test-tools.ts
9960
- import { z as z42 } from "zod";
9783
+ import { z as z43 } from "zod";
9961
9784
  var log43 = createLogger("network-test-tools");
9962
9785
  var BODY_CAP = 65536;
9963
- var bandwidthTestInputSchema = z42.object({
9964
- routerId: z42.string().describe("Target router identifier from the router registry"),
9965
- address: z42.string().describe("Remote host running RouterOS btest server"),
9966
- protocol: z42.enum(["tcp", "udp"]).default("tcp").describe("Test protocol"),
9967
- direction: z42.enum(["send", "receive", "both"]).default("both").describe("Test direction"),
9968
- duration: z42.number().int().min(1).max(30).default(5).describe("Test duration in seconds (max 30)")
9786
+ var bandwidthTestInputSchema = z43.object({
9787
+ routerId,
9788
+ address: z43.string().describe("Remote host running RouterOS btest server"),
9789
+ protocol: z43.enum(["tcp", "udp"]).default("tcp").describe("Test protocol"),
9790
+ direction: z43.enum(["send", "receive", "both"]).default("both").describe("Test direction"),
9791
+ duration: z43.number().int().min(1).max(30).default(5).describe("Test duration in seconds (max 30)")
9969
9792
  }).strict();
9970
9793
  var bandwidthTestTool = {
9971
9794
  name: "bandwidth_test",
@@ -10000,16 +9823,16 @@ var bandwidthTestTool = {
10000
9823
  }
10001
9824
  };
10002
9825
  } catch (err) {
10003
- throw enrichError(err, { routerId: context.routerId, tool: "bandwidth_test" });
9826
+ throw toolError(err, context, "bandwidth_test");
10004
9827
  }
10005
9828
  }
10006
9829
  };
10007
- var fetchUrlInputSchema = z42.object({
10008
- routerId: z42.string().describe("Target router identifier from the router registry"),
10009
- url: z42.string().url().describe("URL to fetch from the router"),
10010
- method: z42.enum(["GET", "POST"]).default("GET").describe("HTTP method"),
10011
- httpData: z42.string().optional().describe("Request body for POST"),
10012
- outputFile: z42.string().optional().describe("Save response body to this router file path instead of returning inline")
9830
+ var fetchUrlInputSchema = z43.object({
9831
+ routerId,
9832
+ url: z43.string().url().describe("URL to fetch from the router"),
9833
+ method: z43.enum(["GET", "POST"]).default("GET").describe("HTTP method"),
9834
+ httpData: z43.string().optional().describe("Request body for POST"),
9835
+ outputFile: z43.string().optional().describe("Save response body to this router file path instead of returning inline")
10013
9836
  }).strict();
10014
9837
  var fetchUrlTool = {
10015
9838
  name: "fetch_url",
@@ -10047,16 +9870,16 @@ var fetchUrlTool = {
10047
9870
  structuredContent: { routerId: context.routerId, url: parsed.url, statusCode, body: responseBody }
10048
9871
  };
10049
9872
  } catch (err) {
10050
- throw enrichError(err, { routerId: context.routerId, tool: "fetch_url" });
9873
+ throw toolError(err, context, "fetch_url");
10051
9874
  }
10052
9875
  }
10053
9876
  };
10054
- var listConnectionsInputSchema = z42.object({
10055
- routerId: z42.string().describe("Target router identifier from the router registry"),
10056
- srcAddress: z42.string().optional().describe("Filter by source address (substring match)"),
10057
- dstAddress: z42.string().optional().describe("Filter by destination address (substring match)"),
10058
- protocol: z42.string().optional().describe("Filter by protocol (exact match, e.g. tcp, udp, icmp)"),
10059
- limit: z42.number().int().min(1).max(500).default(100).describe("Maximum connections to return")
9877
+ var listConnectionsInputSchema = z43.object({
9878
+ routerId,
9879
+ srcAddress: z43.string().optional().describe("Filter by source address (substring match)"),
9880
+ dstAddress: z43.string().optional().describe("Filter by destination address (substring match)"),
9881
+ protocol: z43.string().optional().describe("Filter by protocol (exact match, e.g. tcp, udp, icmp)"),
9882
+ limit
10060
9883
  }).strict();
10061
9884
  var listConnectionsTool = {
10062
9885
  name: "list_connections",
@@ -10079,16 +9902,16 @@ var listConnectionsTool = {
10079
9902
  structuredContent: { routerId: context.routerId, connections, total: all.length, returned: connections.length }
10080
9903
  };
10081
9904
  } catch (err) {
10082
- throw enrichError(err, { routerId: context.routerId, tool: "list_connections" });
9905
+ throw toolError(err, context, "list_connections");
10083
9906
  }
10084
9907
  }
10085
9908
  };
10086
9909
  var networkTestTools = [bandwidthTestTool, fetchUrlTool, listConnectionsTool];
10087
9910
 
10088
9911
  // src/domain/tools/container-config-tools.ts
10089
- import { z as z43 } from "zod";
9912
+ import { z as z44 } from "zod";
10090
9913
  var log44 = createLogger("container-config-tools");
10091
- var getContainerConfigInputSchema = z43.object({ routerId: z43.string().describe("Target router identifier from the router registry") }).strict();
9914
+ var getContainerConfigInputSchema = z44.object({ routerId }).strict();
10092
9915
  var getContainerConfigTool = {
10093
9916
  name: "get_container_config",
10094
9917
  title: "Get Container Config",
@@ -10111,16 +9934,16 @@ var getContainerConfigTool = {
10111
9934
  }
10112
9935
  };
10113
9936
  } catch (err) {
10114
- throw enrichError(err, { routerId: context.routerId, tool: "get_container_config" });
9937
+ throw toolError(err, context, "get_container_config");
10115
9938
  }
10116
9939
  }
10117
9940
  };
10118
- var manageContainerConfigInputSchema = z43.object({
10119
- routerId: z43.string().describe("Target router identifier from the router registry"),
10120
- registryUrl: z43.string().optional().describe("Container registry URL"),
10121
- ramHighMb: z43.number().int().min(1).optional().describe("RAM high-water mark in MB"),
10122
- vethInterface: z43.string().optional().describe("Veth interface name for container networking"),
10123
- dryRun: z43.boolean().default(false).describe("Preview changes without applying")
9941
+ var manageContainerConfigInputSchema = z44.object({
9942
+ routerId,
9943
+ registryUrl: z44.string().optional().describe("Container registry URL"),
9944
+ ramHighMb: z44.number().int().min(1).optional().describe("RAM high-water mark in MB"),
9945
+ vethInterface: z44.string().optional().describe("Veth interface name for container networking"),
9946
+ dryRun
10124
9947
  }).strict();
10125
9948
  var manageContainerConfigTool = {
10126
9949
  name: "manage_container_config",
@@ -10172,14 +9995,14 @@ var manageContainerConfigTool = {
10172
9995
  structuredContent: { action: "updated", routerId: context.routerId, diff }
10173
9996
  };
10174
9997
  } catch (err) {
10175
- throw enrichError(err, { routerId: context.routerId, tool: "manage_container_config" });
9998
+ throw toolError(err, context, "manage_container_config");
10176
9999
  }
10177
10000
  }
10178
10001
  };
10179
- var listContainerEnvsInputSchema = z43.object({
10180
- routerId: z43.string().describe("Target router identifier from the router registry"),
10181
- name: z43.string().optional().describe("Filter by container name (exact match)"),
10182
- limit: z43.number().int().min(1).max(500).default(100).describe("Maximum entries to return")
10002
+ var listContainerEnvsInputSchema = z44.object({
10003
+ routerId,
10004
+ name: z44.string().optional().describe("Filter by container name (exact match)"),
10005
+ limit
10183
10006
  }).strict();
10184
10007
  var listContainerEnvsTool = {
10185
10008
  name: "list_container_envs",
@@ -10202,17 +10025,17 @@ var listContainerEnvsTool = {
10202
10025
  structuredContent: { routerId: context.routerId, envs, total: all.length, returned: envs.length }
10203
10026
  };
10204
10027
  } catch (err) {
10205
- throw enrichError(err, { routerId: context.routerId, tool: "list_container_envs" });
10028
+ throw toolError(err, context, "list_container_envs");
10206
10029
  }
10207
10030
  }
10208
10031
  };
10209
- var manageContainerEnvInputSchema = z43.object({
10210
- routerId: z43.string().describe("Target router identifier from the router registry"),
10211
- action: z43.enum(["add", "remove"]).describe("Action to perform"),
10212
- name: z43.string().describe("Container name"),
10213
- key: z43.string().describe("Environment variable name \u2014 part of idempotency key"),
10214
- value: z43.string().optional().describe("Environment variable value (required for add)"),
10215
- dryRun: z43.boolean().default(false).describe("Preview changes without applying")
10032
+ var manageContainerEnvInputSchema = z44.object({
10033
+ routerId,
10034
+ action: z44.enum(["add", "remove"]).describe("Action to perform"),
10035
+ name: z44.string().describe("Container name"),
10036
+ key: z44.string().describe("Environment variable name \u2014 part of idempotency key"),
10037
+ value: z44.string().optional().describe("Environment variable value (required for add)"),
10038
+ dryRun
10216
10039
  }).strict();
10217
10040
  var manageContainerEnvTool = {
10218
10041
  name: "manage_container_env",
@@ -10309,15 +10132,14 @@ var manageContainerEnvTool = {
10309
10132
  structuredContent: { action: "removed", name: parsed.name, key: parsed.key }
10310
10133
  };
10311
10134
  } catch (err) {
10312
- if (err instanceof MikroMCPError) throw err;
10313
- throw enrichError(err, { routerId: context.routerId, tool: "manage_container_env" });
10135
+ throw toolError(err, context, "manage_container_env");
10314
10136
  }
10315
10137
  }
10316
10138
  };
10317
- var listContainerMountsInputSchema = z43.object({
10318
- routerId: z43.string().describe("Target router identifier from the router registry"),
10319
- name: z43.string().optional().describe("Filter by mount name (exact match)"),
10320
- limit: z43.number().int().min(1).max(500).default(100).describe("Maximum entries to return")
10139
+ var listContainerMountsInputSchema = z44.object({
10140
+ routerId,
10141
+ name: z44.string().optional().describe("Filter by mount name (exact match)"),
10142
+ limit
10321
10143
  }).strict();
10322
10144
  var listContainerMountsTool = {
10323
10145
  name: "list_container_mounts",
@@ -10340,17 +10162,17 @@ var listContainerMountsTool = {
10340
10162
  structuredContent: { routerId: context.routerId, mounts, total: all.length, returned: mounts.length }
10341
10163
  };
10342
10164
  } catch (err) {
10343
- throw enrichError(err, { routerId: context.routerId, tool: "list_container_mounts" });
10165
+ throw toolError(err, context, "list_container_mounts");
10344
10166
  }
10345
10167
  }
10346
10168
  };
10347
- var manageContainerMountInputSchema = z43.object({
10348
- routerId: z43.string().describe("Target router identifier from the router registry"),
10349
- action: z43.enum(["add", "remove"]).describe("Action to perform"),
10350
- name: z43.string().describe("Mount name \u2014 idempotency key"),
10351
- src: z43.string().optional().describe("Host source path (required for add)"),
10352
- dst: z43.string().optional().describe("Container destination path (required for add)"),
10353
- dryRun: z43.boolean().default(false).describe("Preview changes without applying")
10169
+ var manageContainerMountInputSchema = z44.object({
10170
+ routerId,
10171
+ action: z44.enum(["add", "remove"]).describe("Action to perform"),
10172
+ name: z44.string().describe("Mount name \u2014 idempotency key"),
10173
+ src: z44.string().optional().describe("Host source path (required for add)"),
10174
+ dst: z44.string().optional().describe("Container destination path (required for add)"),
10175
+ dryRun
10354
10176
  }).strict();
10355
10177
  var manageContainerMountTool = {
10356
10178
  name: "manage_container_mount",
@@ -10440,8 +10262,7 @@ var manageContainerMountTool = {
10440
10262
  structuredContent: { action: "removed", name: parsed.name }
10441
10263
  };
10442
10264
  } catch (err) {
10443
- if (err instanceof MikroMCPError) throw err;
10444
- throw enrichError(err, { routerId: context.routerId, tool: "manage_container_mount" });
10265
+ throw toolError(err, context, "manage_container_mount");
10445
10266
  }
10446
10267
  }
10447
10268
  };
@@ -10455,11 +10276,11 @@ var containerConfigTools = [
10455
10276
  ];
10456
10277
 
10457
10278
  // src/domain/tools/interface-list-tools.ts
10458
- import { z as z44 } from "zod";
10279
+ import { z as z45 } from "zod";
10459
10280
  var log45 = createLogger("interface-list-tools");
10460
- var listInterfaceListsInputSchema = z44.object({
10461
- routerId: z44.string().describe("Target router identifier from the router registry"),
10462
- limit: z44.number().int().min(1).max(500).default(100).describe("Maximum lists to return")
10281
+ var listInterfaceListsInputSchema = z45.object({
10282
+ routerId,
10283
+ limit
10463
10284
  }).strict();
10464
10285
  var listInterfaceListsTool = {
10465
10286
  name: "list_interface_lists",
@@ -10481,17 +10302,16 @@ var listInterfaceListsTool = {
10481
10302
  structuredContent: { routerId: context.routerId, lists, total: all.length, returned: lists.length }
10482
10303
  };
10483
10304
  } catch (err) {
10484
- if (err instanceof MikroMCPError) throw err;
10485
- throw enrichError(err, { routerId: context.routerId, tool: "list_interface_lists" });
10305
+ throw toolError(err, context, "list_interface_lists");
10486
10306
  }
10487
10307
  }
10488
10308
  };
10489
- var manageInterfaceListInputSchema = z44.object({
10490
- routerId: z44.string().describe("Target router identifier from the router registry"),
10491
- action: z44.enum(["add", "remove"]).describe("Action to perform"),
10492
- name: z44.string().describe("Interface list name \u2014 idempotency key"),
10493
- comment: z44.string().optional().describe("Optional comment"),
10494
- dryRun: z44.boolean().default(false).describe("Preview changes without applying")
10309
+ var manageInterfaceListInputSchema = z45.object({
10310
+ routerId,
10311
+ action: z45.enum(["add", "remove"]).describe("Action to perform"),
10312
+ name: z45.string().describe("Interface list name \u2014 idempotency key"),
10313
+ comment: z45.string().optional().describe("Optional comment"),
10314
+ dryRun
10495
10315
  }).strict();
10496
10316
  var manageInterfaceListTool = {
10497
10317
  name: "manage_interface_list",
@@ -10555,18 +10375,17 @@ var manageInterfaceListTool = {
10555
10375
  structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
10556
10376
  };
10557
10377
  } catch (err) {
10558
- if (err instanceof MikroMCPError) throw err;
10559
- throw enrichError(err, { routerId: context.routerId, tool: "manage_interface_list" });
10378
+ throw toolError(err, context, "manage_interface_list");
10560
10379
  }
10561
10380
  }
10562
10381
  };
10563
- var manageInterfaceListMemberInputSchema = z44.object({
10564
- routerId: z44.string().describe("Target router identifier from the router registry"),
10565
- action: z44.enum(["add", "remove"]).describe("Action to perform"),
10566
- list: z44.string().describe("Interface list name \u2014 part of composite idempotency key"),
10567
- interface: z44.string().describe("Interface name to add/remove \u2014 part of composite idempotency key"),
10568
- comment: z44.string().optional().describe("Optional comment"),
10569
- dryRun: z44.boolean().default(false).describe("Preview changes without applying")
10382
+ var manageInterfaceListMemberInputSchema = z45.object({
10383
+ routerId,
10384
+ action: z45.enum(["add", "remove"]).describe("Action to perform"),
10385
+ list: z45.string().describe("Interface list name \u2014 part of composite idempotency key"),
10386
+ interface: z45.string().describe("Interface name to add/remove \u2014 part of composite idempotency key"),
10387
+ comment: z45.string().optional().describe("Optional comment"),
10388
+ dryRun
10570
10389
  }).strict();
10571
10390
  var manageInterfaceListMemberTool = {
10572
10391
  name: "manage_interface_list_member",
@@ -10640,8 +10459,7 @@ var manageInterfaceListMemberTool = {
10640
10459
  }
10641
10460
  };
10642
10461
  } catch (err) {
10643
- if (err instanceof MikroMCPError) throw err;
10644
- throw enrichError(err, { routerId: context.routerId, tool: "manage_interface_list_member" });
10462
+ throw toolError(err, context, "manage_interface_list_member");
10645
10463
  }
10646
10464
  }
10647
10465
  };
@@ -10652,7 +10470,7 @@ var interfaceListTools = [
10652
10470
  ];
10653
10471
 
10654
10472
  // src/domain/tools/fleet-tools.ts
10655
- import { z as z45 } from "zod";
10473
+ import { z as z46 } from "zod";
10656
10474
 
10657
10475
  // src/middleware/authz.ts
10658
10476
  function matchesPattern(name, pattern) {
@@ -10660,16 +10478,16 @@ function matchesPattern(name, pattern) {
10660
10478
  const prefix = pattern.slice(0, pattern.indexOf("*"));
10661
10479
  return name.startsWith(prefix);
10662
10480
  }
10663
- function checkAuthz(identity, toolName, routerId) {
10664
- if (identity.allowedRouters.length > 0 && !identity.allowedRouters.includes(routerId)) {
10481
+ function checkAuthz(identity, toolName, routerId2) {
10482
+ if (identity.allowedRouters.length > 0 && !identity.allowedRouters.includes(routerId2)) {
10665
10483
  throw new MikroMCPError({
10666
10484
  category: "PERMISSION_DENIED" /* PERMISSION_DENIED */,
10667
10485
  code: "ROUTER_NOT_ALLOWED",
10668
- message: `Identity "${identity.id}" is not allowed to access router "${routerId}".`,
10669
- details: { identityId: identity.id, routerId, allowedRouters: identity.allowedRouters },
10486
+ message: `Identity "${identity.id}" is not allowed to access router "${routerId2}".`,
10487
+ details: { identityId: identity.id, routerId: routerId2, allowedRouters: identity.allowedRouters },
10670
10488
  recoverability: {
10671
10489
  retryable: false,
10672
- suggestedAction: `Add "${routerId}" to allowedRouters for identity "${identity.id}" in identities.yaml.`
10490
+ suggestedAction: `Add "${routerId2}" to allowedRouters for identity "${identity.id}" in identities.yaml.`
10673
10491
  }
10674
10492
  });
10675
10493
  }
@@ -10952,16 +10770,16 @@ function checkFleetConfirmation(args, secret) {
10952
10770
 
10953
10771
  // src/domain/tools/fleet-tools.ts
10954
10772
  var log47 = createLogger("fleet-tools");
10955
- var checkHealthInputSchema = z45.object({
10956
- routerId: z45.string().describe("Target router to health-check")
10773
+ var checkHealthInputSchema = z46.object({
10774
+ routerId
10957
10775
  }).strict();
10958
- var bulkExecuteInputSchema = z45.object({
10959
- toolName: z45.string().describe("Name of the tool to fan out (must be a single-router tool)"),
10960
- routerIds: z45.array(z45.string()).optional().describe("Explicit list of router IDs to target"),
10961
- tags: z45.array(z45.string()).optional().describe("Target all routers with ALL of these tags (mutually exclusive with routerIds)"),
10962
- params: z45.record(z45.string(), z45.unknown()).describe("Params to pass to the tool (omit routerId \u2014 injected per router)"),
10963
- concurrency: z45.number().int().min(1).max(20).default(5).describe("Max simultaneous router calls"),
10964
- confirmationToken: z45.string().optional().describe(
10776
+ var bulkExecuteInputSchema = z46.object({
10777
+ toolName: z46.string().describe("Name of the tool to fan out (must be a single-router tool)"),
10778
+ routerIds: z46.array(z46.string()).optional().describe("Explicit list of router IDs to target"),
10779
+ tags: z46.array(z46.string()).optional().describe("Target all routers with ALL of these tags (mutually exclusive with routerIds)"),
10780
+ params: z46.record(z46.string(), z46.unknown()).describe("Params to pass to the tool (omit routerId \u2014 injected per router)"),
10781
+ concurrency: z46.number().int().min(1).max(20).default(5).describe("Max simultaneous router calls"),
10782
+ confirmationToken: z46.string().optional().describe(
10965
10783
  "Fleet confirmation token from a prior APPROVAL_REQUIRED response. Required to fan out a destructive tool."
10966
10784
  )
10967
10785
  }).strict();
@@ -11018,7 +10836,7 @@ function createFleetTools(baseTools2) {
11018
10836
  const bulkExecuteTool = {
11019
10837
  name: "bulk_execute",
11020
10838
  title: "Bulk Execute",
11021
- description: "Fan out a single-router tool to multiple routers in parallel (up to `concurrency` at a time). Target routers via explicit routerIds or by tag. Non-destructive tools fan out immediately. Destructive tools require a two-step confirmation: call without `confirmationToken` to receive a fleet token (requires `MIKROMCP_CONFIRMATION_SECRET`), then re-call with that token to proceed. Returns per-router results with succeeded/failed counts.",
10839
+ description: "Fan out a single-router tool to many routers in parallel (up to `concurrency`), targeted by routerIds or tag. Destructive tools need two-step confirmation: call without `confirmationToken` to get a fleet token (needs MIKROMCP_CONFIRMATION_SECRET), then re-call with it. Writes snapshot+journal each router for rollback. Returns per-router results with succeeded/failed counts.",
11022
10840
  inputSchema: bulkExecuteInputSchema,
11023
10841
  annotations: {
11024
10842
  readOnlyHint: false,
@@ -11150,8 +10968,12 @@ function createFleetTools(baseTools2) {
11150
10968
  }
11151
10969
  };
11152
10970
  }
10971
+ const isWrite = !targetTool.annotations.readOnlyHint;
10972
+ const snapshotDir = context.appConfig.snapshotDir;
10973
+ const journalPath = context.appConfig.journalPath;
11153
10974
  async function runForRouter(router) {
11154
10975
  const start = Date.now();
10976
+ let journalId;
11155
10977
  try {
11156
10978
  checkAuthz(context.identity, parsed.toolName, router.id);
11157
10979
  const routerContext = buildRouterToolContext({
@@ -11163,11 +10985,44 @@ function createFleetTools(baseTools2) {
11163
10985
  registry: context.routerRegistry
11164
10986
  });
11165
10987
  const toolParams = { ...parsed.params, routerId: router.id };
10988
+ const snapshotIds = [];
10989
+ if (isWrite && targetTool.snapshotPaths && targetTool.snapshotPaths.length > 0) {
10990
+ for (const path of targetTool.snapshotPaths) {
10991
+ try {
10992
+ const meta = await takeSnapshot(
10993
+ routerContext.routerClient,
10994
+ router.id,
10995
+ path,
10996
+ snapshotDir
10997
+ );
10998
+ snapshotIds.push(meta.id);
10999
+ } catch (err) {
11000
+ log47.warn(
11001
+ { err, path, routerId: router.id },
11002
+ "bulk_execute snapshot failed \u2014 proceeding without snapshot"
11003
+ );
11004
+ }
11005
+ }
11006
+ }
11007
+ if (isWrite && journalPath) {
11008
+ journalId = recordAttempt({
11009
+ journalPath,
11010
+ identityId: context.identity.id,
11011
+ role: context.identity.role,
11012
+ tool: parsed.toolName,
11013
+ routerId: router.id,
11014
+ params: toolParams,
11015
+ snapshotIds
11016
+ });
11017
+ }
11166
11018
  const result = await targetTool.handler(
11167
11019
  toolParams,
11168
11020
  routerContext
11169
11021
  );
11170
11022
  const elapsed = Date.now() - start;
11023
+ if (journalId) {
11024
+ recordOutcome({ journalPath, journalId, phase: "success", durationMs: elapsed });
11025
+ }
11171
11026
  auditLog({
11172
11027
  type: "audit",
11173
11028
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11184,6 +11039,9 @@ function createFleetTools(baseTools2) {
11184
11039
  } catch (err) {
11185
11040
  const elapsed = Date.now() - start;
11186
11041
  const message = err instanceof Error ? err.message : String(err);
11042
+ if (journalId) {
11043
+ recordOutcome({ journalPath, journalId, phase: "failure", outcome: message, durationMs: elapsed });
11044
+ }
11187
11045
  auditLog({
11188
11046
  type: "audit",
11189
11047
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11378,8 +11236,8 @@ function computeParamHash(params) {
11378
11236
  const { confirmationToken: _, ...rest } = params;
11379
11237
  return createHash3("sha256").update(JSON.stringify(rest)).digest("hex");
11380
11238
  }
11381
- function computeToken(tool, routerId, identityId, paramHash, expiresAt, secret) {
11382
- const payload = JSON.stringify({ tool, routerId, paramHash, identityId, expiresAt });
11239
+ function computeToken(tool, routerId2, identityId, paramHash, expiresAt, secret) {
11240
+ const payload = JSON.stringify({ tool, routerId: routerId2, paramHash, identityId, expiresAt });
11383
11241
  return createHmac2("sha256", secret).update(payload).digest("hex");
11384
11242
  }
11385
11243
  function evictExpired2() {
@@ -11388,7 +11246,7 @@ function evictExpired2() {
11388
11246
  if (entry.expiresAt <= now) pendingConfirmations.delete(token);
11389
11247
  }
11390
11248
  }
11391
- async function checkConfirmation(toolName, routerId, params, identity, secret) {
11249
+ async function checkConfirmation(toolName, routerId2, params, identity, secret) {
11392
11250
  if (BYPASS_ROLES.includes(identity.role)) return;
11393
11251
  evictExpired2();
11394
11252
  const submittedToken = typeof params.confirmationToken === "string" ? params.confirmationToken : null;
@@ -11406,7 +11264,7 @@ async function checkConfirmation(toolName, routerId, params, identity, secret) {
11406
11264
  });
11407
11265
  }
11408
11266
  const currentParamHash = computeParamHash(params);
11409
- if (entry.tool !== toolName || entry.routerId !== routerId || entry.identityId !== identity.id || entry.paramHash !== currentParamHash) {
11267
+ if (entry.tool !== toolName || entry.routerId !== routerId2 || entry.identityId !== identity.id || entry.paramHash !== currentParamHash) {
11410
11268
  throw new MikroMCPError({
11411
11269
  category: "PERMISSION_DENIED" /* PERMISSION_DENIED */,
11412
11270
  code: "CONFIRMATION_MISMATCH",
@@ -11422,11 +11280,11 @@ async function checkConfirmation(toolName, routerId, params, identity, secret) {
11422
11280
  }
11423
11281
  const expiresAt = new Date(Date.now() + CONFIRMATION_TTL_MS2).toISOString();
11424
11282
  const paramHash = computeParamHash(params);
11425
- const token = computeToken(toolName, routerId, identity.id, paramHash, expiresAt, secret);
11283
+ const token = computeToken(toolName, routerId2, identity.id, paramHash, expiresAt, secret);
11426
11284
  pendingConfirmations.set(token, {
11427
11285
  expiresAt: new Date(expiresAt),
11428
11286
  tool: toolName,
11429
- routerId,
11287
+ routerId: routerId2,
11430
11288
  identityId: identity.id,
11431
11289
  paramHash
11432
11290
  });
@@ -11438,7 +11296,7 @@ async function checkConfirmation(toolName, routerId, params, identity, secret) {
11438
11296
  confirmationToken: token,
11439
11297
  expiresAt,
11440
11298
  tool: toolName,
11441
- routerId
11299
+ routerId: routerId2
11442
11300
  },
11443
11301
  recoverability: {
11444
11302
  retryable: true,
@@ -11578,8 +11436,8 @@ var DEFAULTS2 = {
11578
11436
  cooldownMs: 3e4
11579
11437
  };
11580
11438
  var CircuitBreaker = class {
11581
- constructor(routerId, options) {
11582
- this.routerId = routerId;
11439
+ constructor(routerId2, options) {
11440
+ this.routerId = routerId2;
11583
11441
  this.options = { ...DEFAULTS2, ...options };
11584
11442
  }
11585
11443
  routerId;
@@ -11588,15 +11446,22 @@ var CircuitBreaker = class {
11588
11446
  consecutiveFailures = 0;
11589
11447
  lastFailureTime = 0;
11590
11448
  probeInFlight = false;
11449
+ /** Effective state, accounting for an elapsed cooldown. Pure — does not mutate. */
11591
11450
  get state() {
11592
- if (this.currentState === "open") {
11593
- const elapsed = Date.now() - this.lastFailureTime;
11594
- if (elapsed >= this.options.cooldownMs) {
11595
- this.currentState = "half-open";
11596
- }
11451
+ if (this.currentState === "open" && this.cooldownElapsed()) {
11452
+ return "half-open";
11597
11453
  }
11598
11454
  return this.currentState;
11599
11455
  }
11456
+ cooldownElapsed() {
11457
+ return Date.now() - this.lastFailureTime >= this.options.cooldownMs;
11458
+ }
11459
+ /** Apply the time-based open→half-open transition. Call before acting on state. */
11460
+ transitionIfCooldownElapsed() {
11461
+ if (this.currentState === "open" && this.cooldownElapsed()) {
11462
+ this.currentState = "half-open";
11463
+ }
11464
+ }
11600
11465
  /**
11601
11466
  * Execute `fn` through the circuit breaker.
11602
11467
  *
@@ -11605,7 +11470,8 @@ var CircuitBreaker = class {
11605
11470
  * - **half-open**: execute as a probe; success closes, failure reopens.
11606
11471
  */
11607
11472
  async execute(fn) {
11608
- const currentState = this.state;
11473
+ this.transitionIfCooldownElapsed();
11474
+ const currentState = this.currentState;
11609
11475
  if (currentState === "open") {
11610
11476
  throw new MikroMCPError({
11611
11477
  category: "ROUTER_BUSY" /* ROUTER_BUSY */,
@@ -11677,6 +11543,26 @@ var CircuitBreaker = class {
11677
11543
 
11678
11544
  // src/mcp/tool-executor.ts
11679
11545
  var log48 = createLogger("tool-executor");
11546
+ function resolveRouterId(explicit, registry, config) {
11547
+ if (explicit) {
11548
+ return explicit;
11549
+ }
11550
+ const fallback = config.defaultRouter ?? registry.soleRouterId();
11551
+ if (fallback) {
11552
+ return fallback;
11553
+ }
11554
+ const available = registry.routerIds();
11555
+ throw new MikroMCPError({
11556
+ category: "VALIDATION" /* VALIDATION */,
11557
+ code: "MISSING_ROUTER_ID",
11558
+ message: available.length > 0 ? `routerId is required: more than one router is configured. Available: ${available.join(", ")}.` : "routerId is required, but no routers are configured.",
11559
+ details: { availableRouters: available },
11560
+ recoverability: {
11561
+ retryable: false,
11562
+ suggestedAction: available.length > 0 ? "Provide a routerId, or set MIKROMCP_DEFAULT_ROUTER to choose a default." : "Configure at least one router in routers.yaml."
11563
+ }
11564
+ });
11565
+ }
11680
11566
  async function executeToolCall(tool, args, deps) {
11681
11567
  const { registry, pool, circuitBreakers, config, identityRegistry } = deps;
11682
11568
  const correlationId = nanoid4();
@@ -11702,27 +11588,17 @@ async function executeToolCall(tool, args, deps) {
11702
11588
  recordToolCall(tool.name, "success");
11703
11589
  return formatToolResult(fleetResult);
11704
11590
  }
11705
- const routerId = args.routerId;
11706
- if (!routerId) {
11707
- throw new MikroMCPError({
11708
- category: "VALIDATION" /* VALIDATION */,
11709
- code: "MISSING_ROUTER_ID",
11710
- message: "routerId is required",
11711
- recoverability: {
11712
- retryable: false,
11713
- suggestedAction: "Provide a valid routerId parameter."
11714
- }
11715
- });
11716
- }
11717
- checkAuthz(identity, tool.name, routerId);
11718
- const routerConfig = registry.getRouter(routerId);
11591
+ const routerId2 = resolveRouterId(args.routerId, registry, config);
11592
+ args.routerId = routerId2;
11593
+ checkAuthz(identity, tool.name, routerId2);
11594
+ const routerConfig = registry.getRouter(routerId2);
11719
11595
  if (tool.annotations.destructiveHint) {
11720
11596
  const windows = routerConfig.maintenanceWindows;
11721
11597
  if (windows && windows.length > 0 && !isWithinMaintenanceWindow(windows, /* @__PURE__ */ new Date())) {
11722
11598
  throw new MikroMCPError({
11723
11599
  category: "PERMISSION_DENIED" /* PERMISSION_DENIED */,
11724
11600
  code: "OUTSIDE_MAINTENANCE_WINDOW",
11725
- message: `Router "${routerId}" has maintenance windows configured. Destructive operations are only permitted during scheduled windows.`,
11601
+ message: `Router "${routerId2}" has maintenance windows configured. Destructive operations are only permitted during scheduled windows.`,
11726
11602
  details: { maintenanceWindows: windows },
11727
11603
  recoverability: {
11728
11604
  retryable: true,
@@ -11732,7 +11608,7 @@ async function executeToolCall(tool, args, deps) {
11732
11608
  }
11733
11609
  }
11734
11610
  if (tool.annotations.destructiveHint && config.confirmationSecret) {
11735
- await checkConfirmation(tool.name, routerId, args, identity, config.confirmationSecret);
11611
+ await checkConfirmation(tool.name, routerId2, args, identity, config.confirmationSecret);
11736
11612
  }
11737
11613
  const shouldAudit = tool.annotations.destructiveHint || !tool.annotations.readOnlyHint;
11738
11614
  if (shouldAudit) {
@@ -11743,7 +11619,7 @@ async function executeToolCall(tool, args, deps) {
11743
11619
  identityId: identity.id,
11744
11620
  role: identity.role,
11745
11621
  tool: tool.name,
11746
- routerId,
11622
+ routerId: routerId2,
11747
11623
  phase: "attempt",
11748
11624
  params: args
11749
11625
  }, config.auditLogPath);
@@ -11756,13 +11632,13 @@ async function executeToolCall(tool, args, deps) {
11756
11632
  config,
11757
11633
  registry
11758
11634
  });
11759
- let cb = circuitBreakers.get(routerId);
11635
+ let cb = circuitBreakers.get(routerId2);
11760
11636
  if (!cb) {
11761
- cb = new CircuitBreaker(routerId, {
11637
+ cb = new CircuitBreaker(routerId2, {
11762
11638
  failureThreshold: config.circuitBreaker.failureThreshold,
11763
11639
  cooldownMs: config.circuitBreaker.cooldownMs
11764
11640
  });
11765
- circuitBreakers.set(routerId, cb);
11641
+ circuitBreakers.set(routerId2, cb);
11766
11642
  }
11767
11643
  toolContext.circuitBreaker = cb;
11768
11644
  const { confirmationToken: _ct, ...handlerArgs } = args;
@@ -11770,11 +11646,11 @@ async function executeToolCall(tool, args, deps) {
11770
11646
  if (tool.snapshotPaths && tool.snapshotPaths.length > 0 && !tool.annotations.readOnlyHint) {
11771
11647
  for (const path of tool.snapshotPaths) {
11772
11648
  try {
11773
- const meta = await takeSnapshot(toolContext.routerClient, routerId, path, config.snapshotDir);
11649
+ const meta = await takeSnapshot(toolContext.routerClient, routerId2, path, config.snapshotDir);
11774
11650
  snapshotIds.push(meta.id);
11775
11651
  log48.debug({ snapshotId: meta.id, path }, "Snapshot taken");
11776
11652
  } catch (err) {
11777
- log48.warn({ err, path, routerId }, "Snapshot failed \u2014 proceeding without snapshot");
11653
+ log48.warn({ err, path, routerId: routerId2 }, "Snapshot failed \u2014 proceeding without snapshot");
11778
11654
  }
11779
11655
  }
11780
11656
  }
@@ -11784,7 +11660,7 @@ async function executeToolCall(tool, args, deps) {
11784
11660
  identityId: identity.id,
11785
11661
  role: identity.role,
11786
11662
  tool: tool.name,
11787
- routerId,
11663
+ routerId: routerId2,
11788
11664
  params: handlerArgs,
11789
11665
  snapshotIds
11790
11666
  });
@@ -11810,13 +11686,13 @@ async function executeToolCall(tool, args, deps) {
11810
11686
  identityId: identity.id,
11811
11687
  role: identity.role,
11812
11688
  tool: tool.name,
11813
- routerId,
11689
+ routerId: routerId2,
11814
11690
  phase: "success",
11815
11691
  params: args,
11816
11692
  durationMs: Date.now() - startMs
11817
11693
  }, config.auditLogPath);
11818
11694
  }
11819
- log48.info({ tool: tool.name, routerId, correlationId }, "Tool executed successfully");
11695
+ log48.info({ tool: tool.name, routerId: routerId2, correlationId }, "Tool executed successfully");
11820
11696
  recordToolCall(tool.name, "success");
11821
11697
  return formatToolResult(result);
11822
11698
  } catch (err) {
@@ -11839,7 +11715,7 @@ async function executeToolCall(tool, args, deps) {
11839
11715
  }
11840
11716
  const shouldAudit = tool.annotations.destructiveHint || !tool.annotations.readOnlyHint;
11841
11717
  if (shouldAudit && error.category !== "APPROVAL_REQUIRED" /* APPROVAL_REQUIRED */) {
11842
- const routerId = args.routerId ?? "unknown";
11718
+ const routerId2 = args.routerId ?? "unknown";
11843
11719
  const identity = getCurrentIdentity() ?? getStdioIdentity(config.stdioIdentity, identityRegistry);
11844
11720
  auditLog({
11845
11721
  type: "audit",
@@ -11848,7 +11724,7 @@ async function executeToolCall(tool, args, deps) {
11848
11724
  identityId: identity.id,
11849
11725
  role: identity.role,
11850
11726
  tool: tool.name,
11851
- routerId,
11727
+ routerId: routerId2,
11852
11728
  phase: "failure",
11853
11729
  params: args,
11854
11730
  outcome: error.code,
@@ -11868,7 +11744,7 @@ function registerAllTools(server, registry, pool, circuitBreakers, config, ident
11868
11744
  const deps = { registry, pool, circuitBreakers, config, identityRegistry };
11869
11745
  for (const tool of allTools) {
11870
11746
  const registrationSchema = tool.annotations.destructiveHint ? tool.inputSchema.extend({
11871
- confirmationToken: z46.string().optional().describe(
11747
+ confirmationToken: z47.string().optional().describe(
11872
11748
  "Token from a prior APPROVAL_REQUIRED response. Re-submit the identical call with this token to confirm the destructive action."
11873
11749
  )
11874
11750
  }) : tool.inputSchema;
@@ -11890,41 +11766,41 @@ function registerAllTools(server, registry, pool, circuitBreakers, config, ident
11890
11766
  // src/config/router-registry.ts
11891
11767
  import { readFileSync as readFileSync2, existsSync } from "fs";
11892
11768
  import { parse } from "yaml";
11893
- import { z as z47 } from "zod";
11769
+ import { z as z48 } from "zod";
11894
11770
  var log50 = createLogger("router-registry");
11895
- var RouterConfigSchema = z47.object({
11896
- host: z47.string().min(1, "host is required"),
11897
- port: z47.number().int().min(1).max(65535),
11898
- tls: z47.object({
11899
- enabled: z47.boolean(),
11900
- rejectUnauthorized: z47.boolean(),
11901
- ca: z47.string().optional(),
11902
- fingerprint: z47.string().optional()
11771
+ var RouterConfigSchema = z48.object({
11772
+ host: z48.string().min(1, "host is required"),
11773
+ port: z48.number().int().min(1).max(65535),
11774
+ tls: z48.object({
11775
+ enabled: z48.boolean(),
11776
+ rejectUnauthorized: z48.boolean(),
11777
+ ca: z48.string().optional(),
11778
+ fingerprint: z48.string().optional()
11903
11779
  }).strict(),
11904
- credentials: z47.object({
11905
- source: z47.enum(["env", "vault"]),
11906
- envPrefix: z47.string().optional(),
11907
- vaultPath: z47.string().optional()
11780
+ credentials: z48.object({
11781
+ source: z48.enum(["env", "vault"]),
11782
+ envPrefix: z48.string().optional(),
11783
+ vaultPath: z48.string().optional()
11908
11784
  }).strict(),
11909
- tags: z47.array(z47.string()).default([]),
11910
- rosVersion: z47.string().min(1),
11911
- sshPort: z47.number().int().min(1).max(65535).optional(),
11912
- sshFingerprint: z47.string().optional(),
11913
- cmdAllow: z47.array(z47.string()).optional(),
11914
- cmdDeny: z47.array(z47.string()).optional(),
11915
- maintenanceWindows: z47.array(
11916
- z47.object({
11917
- days: z47.array(
11918
- z47.enum(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"])
11785
+ tags: z48.array(z48.string()).default([]),
11786
+ rosVersion: z48.string().min(1),
11787
+ sshPort: z48.number().int().min(1).max(65535).optional(),
11788
+ sshFingerprint: z48.string().optional(),
11789
+ cmdAllow: z48.array(z48.string()).optional(),
11790
+ cmdDeny: z48.array(z48.string()).optional(),
11791
+ maintenanceWindows: z48.array(
11792
+ z48.object({
11793
+ days: z48.array(
11794
+ z48.enum(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"])
11919
11795
  ),
11920
- startTime: z47.string().regex(/^\d{2}:\d{2}$/, "startTime must be HH:MM"),
11921
- endTime: z47.string().regex(/^\d{2}:\d{2}$/, "endTime must be HH:MM"),
11922
- timezone: z47.string().min(1)
11796
+ startTime: z48.string().regex(/^\d{2}:\d{2}$/, "startTime must be HH:MM"),
11797
+ endTime: z48.string().regex(/^\d{2}:\d{2}$/, "endTime must be HH:MM"),
11798
+ timezone: z48.string().min(1)
11923
11799
  }).strict()
11924
11800
  ).optional()
11925
11801
  }).strict();
11926
- var ConfigFileSchema = z47.object({
11927
- routers: z47.record(z47.string(), RouterConfigSchema)
11802
+ var ConfigFileSchema = z48.object({
11803
+ routers: z48.record(z48.string(), RouterConfigSchema)
11928
11804
  }).strict();
11929
11805
  var RouterRegistry = class {
11930
11806
  routers;
@@ -11969,6 +11845,14 @@ ${issues}`);
11969
11845
  hasRouter(id) {
11970
11846
  return this.routers.has(id);
11971
11847
  }
11848
+ /** The id of the only configured router, or undefined when zero or more than one exist. */
11849
+ soleRouterId() {
11850
+ return this.routers.size === 1 ? this.routers.keys().next().value : void 0;
11851
+ }
11852
+ /** All configured router ids, for error messages and discovery. */
11853
+ routerIds() {
11854
+ return Array.from(this.routers.keys());
11855
+ }
11972
11856
  };
11973
11857
 
11974
11858
  // src/adapter/rest-client.ts
@@ -12064,10 +11948,10 @@ function buildListQuery(options) {
12064
11948
  }
12065
11949
  return { method: "GET" };
12066
11950
  }
12067
- function applyPagination(items, limit, offset) {
11951
+ function applyPagination(items, limit2, offset2) {
12068
11952
  const total = items.length;
12069
- const start = offset ?? 0;
12070
- const sliced = limit !== void 0 ? items.slice(start, start + limit) : items.slice(start);
11953
+ const start = offset2 ?? 0;
11954
+ const sliced = limit2 !== void 0 ? items.slice(start, start + limit2) : items.slice(start);
12071
11955
  return {
12072
11956
  items: sliced,
12073
11957
  total,
@@ -12207,8 +12091,8 @@ var ConnectionPool = class {
12207
12091
  * Perform a lightweight health check against a router by fetching
12208
12092
  * `/rest/system/resource`. Returns `true` if the request succeeds.
12209
12093
  */
12210
- async healthCheck(routerId) {
12211
- const client = this.clients.get(routerId);
12094
+ async healthCheck(routerId2) {
12095
+ const client = this.clients.get(routerId2);
12212
12096
  if (!client) {
12213
12097
  return false;
12214
12098
  }
@@ -12220,11 +12104,11 @@ var ConnectionPool = class {
12220
12104
  }
12221
12105
  }
12222
12106
  /** Remove and close a single client by router ID. */
12223
- removeClient(routerId) {
12224
- const client = this.clients.get(routerId);
12107
+ removeClient(routerId2) {
12108
+ const client = this.clients.get(routerId2);
12225
12109
  if (client) {
12226
12110
  client.close();
12227
- this.clients.delete(routerId);
12111
+ this.clients.delete(routerId2);
12228
12112
  }
12229
12113
  }
12230
12114
  /** Close and remove all clients. */
@@ -12237,18 +12121,30 @@ var ConnectionPool = class {
12237
12121
  };
12238
12122
 
12239
12123
  // src/version.ts
12240
- var VERSION = "1.5.0";
12124
+ var VERSION = "1.6.0";
12241
12125
 
12242
12126
  // src/mcp/server.ts
12127
+ var SERVER_INSTRUCTIONS = [
12128
+ "MikroMCP manages MikroTik RouterOS devices.",
12129
+ "Reads are safe; treat writes as consequential \u2014 preview a write with dryRun:true and review the diff before applying.",
12130
+ "Destructive operations may require a two-step confirmation token.",
12131
+ "Writes are idempotent: already_exists and no_change are success, not errors.",
12132
+ "Prefer dedicated tools over run_command.",
12133
+ "routerId is optional when a default router is configured.",
12134
+ "For the full operating playbook, install the MikroMCP usage skill."
12135
+ ].join(" ");
12243
12136
  function createServerFactory(config, identityRegistry) {
12244
12137
  const registry = new RouterRegistry(config.configPath);
12245
12138
  const pool = new ConnectionPool();
12246
12139
  const circuitBreakers = /* @__PURE__ */ new Map();
12247
12140
  const makeServer = () => {
12248
- const server = new McpServer({
12249
- name: "mikromcp",
12250
- version: VERSION
12251
- });
12141
+ const server = new McpServer(
12142
+ {
12143
+ name: "mikromcp",
12144
+ version: VERSION
12145
+ },
12146
+ { instructions: SERVER_INSTRUCTIONS }
12147
+ );
12252
12148
  registerAllTools(server, registry, pool, circuitBreakers, config, identityRegistry);
12253
12149
  return server;
12254
12150
  };
@@ -12478,17 +12374,17 @@ async function handleMcpRequest(pathname, req, res, url, port, maxBodyBytes, mak
12478
12374
  // src/config/identity-registry.ts
12479
12375
  import { readFileSync as readFileSync4, existsSync as existsSync2 } from "fs";
12480
12376
  import { parse as parse2 } from "yaml";
12481
- import { z as z48 } from "zod";
12377
+ import { z as z49 } from "zod";
12482
12378
  import bcrypt from "bcryptjs";
12483
12379
  var log52 = createLogger("identity-registry");
12484
- var IdentityConfigSchema = z48.object({
12485
- token: z48.string().min(1, "token hash is required"),
12486
- role: z48.enum(["readonly", "operator", "admin", "superadmin"]),
12487
- allowedRouters: z48.array(z48.string()).default([]),
12488
- allowedToolPatterns: z48.array(z48.string()).default([])
12380
+ var IdentityConfigSchema = z49.object({
12381
+ token: z49.string().min(1, "token hash is required"),
12382
+ role: z49.enum(["readonly", "operator", "admin", "superadmin"]),
12383
+ allowedRouters: z49.array(z49.string()).default([]),
12384
+ allowedToolPatterns: z49.array(z49.string()).default([])
12489
12385
  }).strict();
12490
- var ConfigFileSchema2 = z48.object({
12491
- identities: z48.record(z48.string(), IdentityConfigSchema)
12386
+ var ConfigFileSchema2 = z49.object({
12387
+ identities: z49.record(z49.string(), IdentityConfigSchema)
12492
12388
  }).strict();
12493
12389
  var IdentityRegistry = class {
12494
12390
  identities = [];
@@ -12636,7 +12532,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
12636
12532
  import { homedir as homedir2 } from "os";
12637
12533
  import * as net from "net";
12638
12534
  import chalk from "chalk";
12639
- var localVersion = "1.5.0";
12535
+ var localVersion = "1.6.0";
12640
12536
  var results = [];
12641
12537
  function ok(msg) {
12642
12538
  console.log(chalk.green("\u2705 ") + msg);
@@ -12729,6 +12625,26 @@ function checkEnvVars(routers, transport2) {
12729
12625
  warn("MIKROMCP_CONFIRMATION_SECRET not set \u2014 required in HTTP mode");
12730
12626
  }
12731
12627
  }
12628
+ function checkDefaultRouter(defaultRouter, routers) {
12629
+ const ids = routers.map((r) => r.id);
12630
+ if (defaultRouter) {
12631
+ if (ids.includes(defaultRouter)) {
12632
+ ok(`Default router: ${defaultRouter} (MIKROMCP_DEFAULT_ROUTER; used when routerId is omitted)`);
12633
+ } else {
12634
+ fail(
12635
+ `MIKROMCP_DEFAULT_ROUTER="${defaultRouter}" does not match any configured router` + (ids.length > 0 ? ` (available: ${ids.join(", ")})` : "")
12636
+ );
12637
+ }
12638
+ return;
12639
+ }
12640
+ if (routers.length === 1) {
12641
+ ok(`Default router: ${ids[0]} (sole configured router; used when routerId is omitted)`);
12642
+ } else if (routers.length > 1) {
12643
+ warn(
12644
+ `No MIKROMCP_DEFAULT_ROUTER set and ${routers.length} routers configured \u2014 tool calls must specify routerId (set MIKROMCP_DEFAULT_ROUTER to choose a default)`
12645
+ );
12646
+ }
12647
+ }
12732
12648
  async function checkRouter(router) {
12733
12649
  console.log();
12734
12650
  console.log(chalk.bold(`Router: ${router.id} (${router.host})`));
@@ -12846,6 +12762,16 @@ function checkClaudeDesktop() {
12846
12762
  warn(`Could not parse Claude Desktop config: ${err instanceof Error ? err.message : String(err)}`);
12847
12763
  }
12848
12764
  }
12765
+ function checkSkill() {
12766
+ const skillPath = `${homedir2()}/.claude/skills/mikromcp/SKILL.md`;
12767
+ if (existsSync3(skillPath)) {
12768
+ ok("Usage skill: installed (~/.claude/skills/mikromcp)");
12769
+ } else {
12770
+ warn(
12771
+ "Usage skill not installed at ~/.claude/skills/mikromcp \u2014 your AI assistant will lack MikroMCP operating guidance. See docs/wiki/Using-the-Skill.md"
12772
+ );
12773
+ }
12774
+ }
12849
12775
  async function checkForUpdates() {
12850
12776
  try {
12851
12777
  const response = await fetch("https://registry.npmjs.org/mikromcp/latest", {
@@ -12883,12 +12809,16 @@ async function runDoctor() {
12883
12809
  if (routers.length > 0) {
12884
12810
  checkEnvVars(routers, appConfig.transport);
12885
12811
  }
12812
+ if (routers.length > 0) {
12813
+ checkDefaultRouter(appConfig.defaultRouter, routers);
12814
+ }
12886
12815
  for (const router of routers) {
12887
12816
  await checkRouter(router);
12888
12817
  }
12889
12818
  console.log();
12890
12819
  checkIdentities(appConfig.identitiesPath, appConfig.transport);
12891
12820
  checkClaudeDesktop();
12821
+ checkSkill();
12892
12822
  await checkForUpdates();
12893
12823
  console.log();
12894
12824
  console.log(chalk.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
@@ -12940,8 +12870,8 @@ function toYaml(obj) {
12940
12870
  });
12941
12871
  return doc.toString();
12942
12872
  }
12943
- function suggestEnvPrefix(routerId) {
12944
- return "ROUTER_" + routerId.toUpperCase().replace(/-/g, "_");
12873
+ function suggestEnvPrefix(routerId2) {
12874
+ return "ROUTER_" + routerId2.toUpperCase().replace(/-/g, "_");
12945
12875
  }
12946
12876
  function validateRouterId(val) {
12947
12877
  if (/^[a-zA-Z0-9-]+$/.test(val)) return true;
@@ -12959,7 +12889,7 @@ function findClaudeDesktopConfig() {
12959
12889
  }
12960
12890
  async function collectRouterInfo() {
12961
12891
  console.log(chalk2.bold("\n\u2500\u2500 Router configuration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
12962
- const routerId = await input({
12892
+ const routerId2 = await input({
12963
12893
  message: "Router ID (e.g. core-01):",
12964
12894
  validate: validateRouterId
12965
12895
  });
@@ -12991,7 +12921,7 @@ async function collectRouterInfo() {
12991
12921
  );
12992
12922
  }
12993
12923
  }
12994
- const suggestedPrefix = suggestEnvPrefix(routerId);
12924
+ const suggestedPrefix = suggestEnvPrefix(routerId2);
12995
12925
  const envPrefix = await input({
12996
12926
  message: "Credential env prefix:",
12997
12927
  default: suggestedPrefix,
@@ -13015,7 +12945,13 @@ async function collectRouterInfo() {
13015
12945
  default: "7",
13016
12946
  validate: (v) => v.trim().length > 0 ? true : "Version is required"
13017
12947
  });
13018
- return { routerId, host, port, tlsEnabled, rejectUnauthorized, envPrefix, routerUser, routerPass, tags, rosVersion };
12948
+ return { routerId: routerId2, host, port, tlsEnabled, rejectUnauthorized, envPrefix, routerUser, routerPass, tags, rosVersion };
12949
+ }
12950
+ async function collectDefaultRouterPreference(routerId2) {
12951
+ return confirm({
12952
+ message: `Set "${routerId2}" as the default router? (tools may then omit routerId)`,
12953
+ default: true
12954
+ });
13019
12955
  }
13020
12956
  async function collectIdentityInfo() {
13021
12957
  console.log(chalk2.bold("\n\u2500\u2500 Identity / HTTP transport \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
@@ -13165,6 +13101,10 @@ function writeDotEnv(data) {
13165
13101
  `MIKROMCP_CONFIG_PATH=${join4(data.configDir, "routers.yaml")}`,
13166
13102
  `MIKROMCP_IDENTITIES_PATH=${join4(data.configDir, "identities.yaml")}`,
13167
13103
  "",
13104
+ "# \u2500\u2500 Default router \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13105
+ "# Router used when a tool call omits routerId (handy for single-router setups).",
13106
+ data.setAsDefault ? `MIKROMCP_DEFAULT_ROUTER=${data.routerId}` : `# MIKROMCP_DEFAULT_ROUTER=${data.routerId}`,
13107
+ "",
13168
13108
  "# \u2500\u2500 Logging \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13169
13109
  "# Levels: trace | debug | info | warn | error",
13170
13110
  "MIKROMCP_LOG_LEVEL=info",
@@ -13231,6 +13171,7 @@ async function runInit() {
13231
13171
  }
13232
13172
  }
13233
13173
  const routerInfo = await collectRouterInfo();
13174
+ const setAsDefault = await collectDefaultRouterPreference(routerInfo.routerId);
13234
13175
  const identityInfo = await collectIdentityInfo();
13235
13176
  const transport2 = await collectTransport();
13236
13177
  const writeEnv = await collectEnvPreference();
@@ -13241,6 +13182,7 @@ async function runInit() {
13241
13182
  transport: transport2,
13242
13183
  envPath,
13243
13184
  configDir,
13185
+ setAsDefault,
13244
13186
  writeEnv,
13245
13187
  writeRoutersYaml: true,
13246
13188
  writeIdentitiesYaml: identityInfo.createIdentity,
@@ -13262,6 +13204,16 @@ async function runInit() {
13262
13204
  writeDotEnv(data);
13263
13205
  summary.push(`.env written to ${data.envPath}`);
13264
13206
  console.log(chalk2.green(` \u2714 ${data.envPath}`));
13207
+ if (data.setAsDefault) {
13208
+ summary.push(`default router: ${data.routerId}`);
13209
+ console.log(chalk2.green(` \u2714 Default router: ${data.routerId} (MIKROMCP_DEFAULT_ROUTER)`));
13210
+ }
13211
+ } else if (data.setAsDefault) {
13212
+ console.log(
13213
+ chalk2.yellow(
13214
+ ` \u26A0 Default router not persisted \u2014 set MIKROMCP_DEFAULT_ROUTER=${data.routerId} in your environment to use it.`
13215
+ )
13216
+ );
13265
13217
  }
13266
13218
  let desktopRegistered = false;
13267
13219
  if (data.registerClaudeDesktop) {
@@ -13284,11 +13236,15 @@ async function runInit() {
13284
13236
  console.log(chalk2.bold("\nNext steps:"));
13285
13237
  console.log(chalk2.dim(" 1. Run: mikromcp doctor"));
13286
13238
  console.log(chalk2.dim(" 2. Restart Claude Desktop to load the MCP server"));
13239
+ console.log(
13240
+ chalk2.dim(" 3. (Optional) Install the usage skill so your AI assistant drives MikroMCP safely")
13241
+ );
13242
+ console.log(chalk2.dim(" See: docs/wiki/Using-the-Skill.md"));
13287
13243
  console.log();
13288
13244
  }
13289
13245
 
13290
13246
  // src/cli/index.ts
13291
- var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.5.0");
13247
+ var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.6.0");
13292
13248
  program.command("serve", { isDefault: true }).description("Start the MCP server (default)").action(runServe);
13293
13249
  program.command("doctor").description("Check config, connectivity, and permissions").action(runDoctor);
13294
13250
  program.command("init").description("Interactive setup wizard").action(runInit);