@usex/mikrotik-mcp 3.7.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -56,6 +56,12 @@ var DashboardConfigSchema = z.object({
56
56
  maxBodyBytes: z.coerce.number().int().nonnegative().default(16384),
57
57
  token: z.string().optional()
58
58
  });
59
+ var ToolFilterSchema = z.object({
60
+ enabledModules: z.array(z.string()).default([]),
61
+ disabledModules: z.array(z.string()).default([]),
62
+ enabledGroups: z.array(z.string()).default([]),
63
+ disabledGroups: z.array(z.string()).default([])
64
+ });
59
65
  var MikrotikConfigSchema = z.object({
60
66
  devices: z.record(z.string(), DeviceConfigSchema).default(() => ({ default: DeviceConfigSchema.parse({}) })),
61
67
  defaultDevice: z.string().default("default"),
@@ -63,6 +69,7 @@ var MikrotikConfigSchema = z.object({
63
69
  s3: S3ConfigSchema.optional(),
64
70
  dashboard: DashboardConfigSchema.default(() => DashboardConfigSchema.parse({})),
65
71
  readOnly: z.boolean().default(false),
72
+ tools: ToolFilterSchema.default(() => ToolFilterSchema.parse({})),
66
73
  backupDir: z.string().optional()
67
74
  });
68
75
  function env(...names) {
@@ -108,7 +115,8 @@ function parseDevicesSource(raw, fromFile) {
108
115
  const defaultDevice = typeof obj.defaultDevice === "string" ? obj.defaultDevice : undefined;
109
116
  const s3 = structured && obj.s3 && typeof obj.s3 === "object" ? obj.s3 : undefined;
110
117
  const dashboard = structured && obj.dashboard && typeof obj.dashboard === "object" ? obj.dashboard : undefined;
111
- return { devices, defaultDevice, s3, dashboard };
118
+ const tools = structured && obj.tools && typeof obj.tools === "object" ? obj.tools : undefined;
119
+ return { devices, defaultDevice, s3, dashboard, tools };
112
120
  }
113
121
  var configSource = { path: DEFAULT_CONFIG_FILE, fromFile: false };
114
122
  function loadConfig(argv = process.argv.slice(2)) {
@@ -140,6 +148,7 @@ function loadConfig(argv = process.argv.slice(2)) {
140
148
  const devicesInline = flags.devices ?? env("MIKROTIK_DEVICES");
141
149
  let fileS3 = {};
142
150
  let fileDashboard = {};
151
+ let fileTools;
143
152
  if (configFile || devicesInline) {
144
153
  const src = configFile ? parseDevicesSource(configFile, true) : parseDevicesSource(devicesInline, false);
145
154
  for (const [name, dc] of Object.entries(src.devices))
@@ -150,6 +159,7 @@ function loadConfig(argv = process.argv.slice(2)) {
150
159
  defaultDevice = Object.keys(src.devices)[0];
151
160
  fileS3 = src.s3;
152
161
  fileDashboard = src.dashboard;
162
+ fileTools = src.tools;
153
163
  }
154
164
  const s3 = {
155
165
  accessKeyId: pick("s3-access-key-id", "S3_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID"),
@@ -172,6 +182,14 @@ function loadConfig(argv = process.argv.slice(2)) {
172
182
  };
173
183
  const isTruthy = (v) => /^(1|true|yes|on)$/i.test(v ?? "");
174
184
  const readOnly = isTruthy(pick("read-only", "MIKROTIK_READ_ONLY"));
185
+ const csv = (v) => v === undefined ? undefined : v.split(",").map((s) => s.trim()).filter(Boolean);
186
+ const tools = {
187
+ enabledModules: csv(pick("tools-enabled-modules", "MIKROTIK_TOOLS__ENABLED_MODULES")),
188
+ disabledModules: csv(pick("tools-disabled-modules", "MIKROTIK_TOOLS__DISABLED_MODULES")),
189
+ enabledGroups: csv(pick("tools-enabled-groups", "MIKROTIK_TOOLS__ENABLED_GROUPS")),
190
+ disabledGroups: csv(pick("tools-disabled-groups", "MIKROTIK_TOOLS__DISABLED_GROUPS")),
191
+ ...fileTools
192
+ };
175
193
  const boolOpt = (v) => v === undefined ? undefined : isTruthy(v);
176
194
  const dashboard = {
177
195
  enabled: boolOpt(pick("dashboard", "MIKROTIK_DASHBOARD__ENABLED", "MIKROTIK_DASHBOARD")),
@@ -192,6 +210,7 @@ function loadConfig(argv = process.argv.slice(2)) {
192
210
  mcp,
193
211
  dashboard,
194
212
  readOnly,
213
+ tools,
195
214
  ...hasS3 ? { s3 } : {}
196
215
  };
197
216
  const pruned = JSON.parse(JSON.stringify(raw));
@@ -1033,6 +1052,16 @@ function isEmpty(result) {
1033
1052
  const t = result.trim();
1034
1053
  return t === "" || t === "no such item" || t === "no such item (4)";
1035
1054
  }
1055
+ function extractCreatedId(output) {
1056
+ const star = output.match(/\*[0-9A-Fa-f]+/);
1057
+ if (star)
1058
+ return star[0];
1059
+ const num = output.trim().match(/^\d+$/);
1060
+ return num ? num[0] : undefined;
1061
+ }
1062
+ function readBackUnavailable(details) {
1063
+ return isEmpty(details) || /no such item/i.test(details) || looksLikeError(details);
1064
+ }
1036
1065
  function looksLikeError(result) {
1037
1066
  const t = result.toLowerCase();
1038
1067
  return t.includes("failure:") || t.includes("syntax error") || t.includes("bad command") || t.includes("bad parameter") || t.includes("expected end of command") || t.includes("invalid value") || t.includes("input does not match") || t.includes("ambiguous value") || t.startsWith("error");
@@ -6056,16 +6085,17 @@ var firewallFilterTools = [
6056
6085
  ctx.info(`Creating firewall filter rule: chain=${a.chain}, action=${a.action}`);
6057
6086
  const cmd = new Cmd("/ip firewall filter add").set("chain", a.chain).set("action", a.action).opt("src-address", a.src_address).opt("dst-address", a.dst_address).opt("src-port", a.src_port).opt("dst-port", a.dst_port).opt("protocol", a.protocol).opt("in-interface", a.in_interface).opt("out-interface", a.out_interface).opt("connection-state", a.connection_state).opt("connection-nat-state", a.connection_nat_state).opt("src-address-list", a.src_address_list).opt("dst-address-list", a.dst_address_list).opt("limit", a.limit).opt("tcp-flags", a.tcp_flags).opt("comment", a.comment).flag("disabled", a.disabled).flag("log", a.log).opt("log-prefix", a.log ? a.log_prefix : undefined).opt("place-before", a.place_before).build();
6058
6087
  const result = await executeMikrotikCommand(cmd, ctx);
6059
- if (result.trim()) {
6060
- const trimmed = result.trim();
6061
- if (trimmed.includes("*") || isDigits(trimmed)) {
6062
- const details = await executeMikrotikCommand(`/ip firewall filter print detail where .id=${trimmed}`, ctx);
6063
- return details.trim() ? `Firewall filter rule created successfully:
6088
+ const trimmed = result.trim();
6089
+ if (looksLikeError(trimmed)) {
6090
+ const hint = placeBeforeError(trimmed, a.place_before);
6091
+ return `Failed to create firewall filter rule: ${hint ?? trimmed}`;
6092
+ }
6093
+ const createdId = extractCreatedId(trimmed);
6094
+ if (createdId) {
6095
+ const details = await executeMikrotikCommand(`/ip firewall filter print detail where .id=${createdId}`, ctx);
6096
+ return readBackUnavailable(details) ? `Firewall filter rule created (id ${createdId}).` : `Firewall filter rule created successfully:
6064
6097
 
6065
- ${details}` : `Firewall filter rule created with ID: ${result}`;
6066
- }
6067
- const hint = placeBeforeError(result, a.place_before);
6068
- return `Failed to create firewall filter rule: ${hint ?? result}`;
6098
+ ${details}`;
6069
6099
  }
6070
6100
  const count = await executeMikrotikCommand("/ip firewall filter print detail count-only", ctx);
6071
6101
  const c = count.trim();
@@ -6353,16 +6383,17 @@ var firewallNatTools = [
6353
6383
  }
6354
6384
  const cmd = new Cmd("/ip firewall nat add").set("chain", a.chain).set("action", a.action).opt("src-address", a.src_address).opt("dst-address", a.dst_address).opt("src-port", a.src_port).opt("dst-port", a.dst_port).opt("protocol", a.protocol).opt("in-interface", a.in_interface).opt("out-interface", a.out_interface).opt("to-addresses", a.to_addresses).opt("to-ports", a.to_ports).opt("comment", a.comment).flag("disabled", a.disabled).flag("log", a.log).opt("log-prefix", a.log ? a.log_prefix : undefined).opt("place-before", a.place_before).build();
6355
6385
  const result = await executeMikrotikCommand(cmd, ctx);
6356
- if (result.trim()) {
6357
- const trimmed = result.trim();
6358
- if (trimmed.includes("*") || isDigits2(trimmed)) {
6359
- const details = await executeMikrotikCommand(`/ip firewall nat print detail where .id=${trimmed}`, ctx);
6360
- return details.trim() ? `NAT rule created successfully:
6386
+ const trimmed = result.trim();
6387
+ if (looksLikeError(trimmed)) {
6388
+ const hint = placeBeforeError(trimmed, a.place_before);
6389
+ return `Failed to create NAT rule: ${hint ?? trimmed}`;
6390
+ }
6391
+ const createdId = extractCreatedId(trimmed);
6392
+ if (createdId) {
6393
+ const details = await executeMikrotikCommand(`/ip firewall nat print detail where .id=${createdId}`, ctx);
6394
+ return readBackUnavailable(details) ? `NAT rule created (id ${createdId}).` : `NAT rule created successfully:
6361
6395
 
6362
- ${details}` : `NAT rule created with ID: ${result}`;
6363
- }
6364
- const hint = placeBeforeError(result, a.place_before);
6365
- return `Failed to create NAT rule: ${hint ?? result}`;
6396
+ ${details}`;
6366
6397
  }
6367
6398
  const count = await executeMikrotikCommand("/ip firewall nat print detail count-only", ctx);
6368
6399
  const c = count.trim();
@@ -8025,15 +8056,16 @@ var ipv6FirewallFilterTools = [
8025
8056
  ctx.info(`Creating IPv6 firewall filter rule: chain=${a.chain}, action=${a.action}`);
8026
8057
  const cmd = new Cmd("/ipv6 firewall filter add").set("chain", a.chain).set("action", a.action).opt("src-address", a.src_address).opt("dst-address", a.dst_address).opt("src-port", a.src_port).opt("dst-port", a.dst_port).opt("protocol", a.protocol).opt("in-interface", a.in_interface).opt("out-interface", a.out_interface).opt("in-interface-list", a.in_interface_list).opt("out-interface-list", a.out_interface_list).opt("connection-state", a.connection_state).opt("src-address-list", a.src_address_list).opt("dst-address-list", a.dst_address_list).opt("hop-limit", a.hop_limit).opt("limit", a.limit).opt("tcp-flags", a.tcp_flags).opt("comment", a.comment).flag("disabled", a.disabled).flag("log", a.log).opt("log-prefix", a.log ? a.log_prefix : undefined).opt("place-before", a.place_before).build();
8027
8058
  const result = await executeMikrotikCommand(cmd, ctx);
8028
- if (result.trim()) {
8029
- const trimmed = result.trim();
8030
- if (trimmed.includes("*") || isDigits3(trimmed)) {
8031
- const details = await executeMikrotikCommand(`/ipv6 firewall filter print detail where .id=${trimmed}`, ctx);
8032
- return details.trim() ? `IPv6 firewall filter rule created successfully:
8059
+ const trimmed = result.trim();
8060
+ if (looksLikeError(trimmed)) {
8061
+ return `Failed to create IPv6 firewall filter rule: ${trimmed}`;
8062
+ }
8063
+ const createdId = extractCreatedId(trimmed);
8064
+ if (createdId) {
8065
+ const details = await executeMikrotikCommand(`/ipv6 firewall filter print detail where .id=${createdId}`, ctx);
8066
+ return readBackUnavailable(details) ? `IPv6 firewall filter rule created (id ${createdId}).` : `IPv6 firewall filter rule created successfully:
8033
8067
 
8034
- ${details}` : `IPv6 firewall filter rule created with ID: ${result}`;
8035
- }
8036
- return `Failed to create IPv6 firewall filter rule: ${result}`;
8068
+ ${details}`;
8037
8069
  }
8038
8070
  const count = await executeMikrotikCommand("/ipv6 firewall filter print detail count-only", ctx);
8039
8071
  const c = count.trim();
@@ -8286,15 +8318,16 @@ var ipv6FirewallNatTools = [
8286
8318
  ctx.info(`Creating IPv6 firewall NAT rule: chain=${a.chain}, action=${a.action}`);
8287
8319
  const cmd = new Cmd("/ipv6 firewall nat add").set("chain", a.chain).set("action", a.action).opt("src-address", a.src_address).opt("dst-address", a.dst_address).opt("src-port", a.src_port).opt("dst-port", a.dst_port).opt("protocol", a.protocol).opt("in-interface", a.in_interface).opt("out-interface", a.out_interface).opt("to-address", a.to_address).opt("to-ports", a.to_ports).opt("src-address-list", a.src_address_list).opt("dst-address-list", a.dst_address_list).opt("comment", a.comment).flag("disabled", a.disabled).flag("log", a.log).opt("log-prefix", a.log ? a.log_prefix : undefined).opt("place-before", a.place_before).build();
8288
8320
  const result = await executeMikrotikCommand(cmd, ctx);
8289
- if (result.trim()) {
8290
- const trimmed = result.trim();
8291
- if (trimmed.includes("*") || isDigits4(trimmed)) {
8292
- const details = await executeMikrotikCommand(`/ipv6 firewall nat print detail where .id=${trimmed}`, ctx);
8293
- return details.trim() ? `IPv6 firewall NAT rule created successfully:
8321
+ const trimmed = result.trim();
8322
+ if (looksLikeError(trimmed)) {
8323
+ return `Failed to create IPv6 firewall NAT rule: ${trimmed}`;
8324
+ }
8325
+ const createdId = extractCreatedId(trimmed);
8326
+ if (createdId) {
8327
+ const details = await executeMikrotikCommand(`/ipv6 firewall nat print detail where .id=${createdId}`, ctx);
8328
+ return readBackUnavailable(details) ? `IPv6 firewall NAT rule created (id ${createdId}).` : `IPv6 firewall NAT rule created successfully:
8294
8329
 
8295
- ${details}` : `IPv6 firewall NAT rule created with ID: ${result}`;
8296
- }
8297
- return `Failed to create IPv6 firewall NAT rule: ${result}`;
8330
+ ${details}`;
8298
8331
  }
8299
8332
  const count = await executeMikrotikCommand("/ipv6 firewall nat print detail count-only", ctx);
8300
8333
  const c = count.trim();
@@ -8550,15 +8583,16 @@ var ipv6FirewallMangleTools = [
8550
8583
  ctx.info(`Creating IPv6 firewall mangle rule: chain=${a.chain}, action=${a.action}`);
8551
8584
  const cmd = new Cmd("/ipv6 firewall mangle add").set("chain", a.chain).set("action", a.action).opt("src-address", a.src_address).opt("dst-address", a.dst_address).opt("src-port", a.src_port).opt("dst-port", a.dst_port).opt("protocol", a.protocol).opt("in-interface", a.in_interface).opt("out-interface", a.out_interface).opt("connection-mark", a.connection_mark).opt("packet-mark", a.packet_mark).opt("routing-mark", a.routing_mark).opt("new-connection-mark", a.new_connection_mark).opt("new-packet-mark", a.new_packet_mark).opt("new-routing-mark", a.new_routing_mark).opt("new-dscp", a.new_dscp).opt("new-hop-limit", a.new_hop_limit).bool("passthrough", a.passthrough).opt("comment", a.comment).flag("disabled", a.disabled).flag("log", a.log).opt("log-prefix", a.log ? a.log_prefix : undefined).opt("place-before", a.place_before).build();
8552
8585
  const result = await executeMikrotikCommand(cmd, ctx);
8553
- if (result.trim()) {
8554
- const trimmed = result.trim();
8555
- if (trimmed.includes("*") || isDigits5(trimmed)) {
8556
- const details = await executeMikrotikCommand(`/ipv6 firewall mangle print detail where .id=${trimmed}`, ctx);
8557
- return details.trim() ? `IPv6 firewall mangle rule created successfully:
8586
+ const trimmed = result.trim();
8587
+ if (looksLikeError(trimmed)) {
8588
+ return `Failed to create IPv6 firewall mangle rule: ${trimmed}`;
8589
+ }
8590
+ const createdId = extractCreatedId(trimmed);
8591
+ if (createdId) {
8592
+ const details = await executeMikrotikCommand(`/ipv6 firewall mangle print detail where .id=${createdId}`, ctx);
8593
+ return readBackUnavailable(details) ? `IPv6 firewall mangle rule created (id ${createdId}).` : `IPv6 firewall mangle rule created successfully:
8558
8594
 
8559
- ${details}` : `IPv6 firewall mangle rule created with ID: ${result}`;
8560
- }
8561
- return `Failed to create IPv6 firewall mangle rule: ${result}`;
8595
+ ${details}`;
8562
8596
  }
8563
8597
  const count = await executeMikrotikCommand("/ipv6 firewall mangle print detail count-only", ctx);
8564
8598
  const c = count.trim();
@@ -8789,15 +8823,16 @@ var ipv6FirewallRawTools = [
8789
8823
  ctx.info(`Creating IPv6 firewall raw rule: chain=${a.chain}, action=${a.action}`);
8790
8824
  const cmd = new Cmd("/ipv6 firewall raw add").set("chain", a.chain).set("action", a.action).opt("src-address", a.src_address).opt("dst-address", a.dst_address).opt("src-port", a.src_port).opt("dst-port", a.dst_port).opt("protocol", a.protocol).opt("in-interface", a.in_interface).opt("out-interface", a.out_interface).opt("src-address-list", a.src_address_list).opt("dst-address-list", a.dst_address_list).opt("comment", a.comment).flag("disabled", a.disabled).flag("log", a.log).opt("log-prefix", a.log ? a.log_prefix : undefined).opt("place-before", a.place_before).build();
8791
8825
  const result = await executeMikrotikCommand(cmd, ctx);
8792
- if (result.trim()) {
8793
- const trimmed = result.trim();
8794
- if (trimmed.includes("*") || isDigits6(trimmed)) {
8795
- const details = await executeMikrotikCommand(`/ipv6 firewall raw print detail where .id=${trimmed}`, ctx);
8796
- return details.trim() ? `IPv6 firewall raw rule created successfully:
8826
+ const trimmed = result.trim();
8827
+ if (looksLikeError(trimmed)) {
8828
+ return `Failed to create IPv6 firewall raw rule: ${trimmed}`;
8829
+ }
8830
+ const createdId = extractCreatedId(trimmed);
8831
+ if (createdId) {
8832
+ const details = await executeMikrotikCommand(`/ipv6 firewall raw print detail where .id=${createdId}`, ctx);
8833
+ return readBackUnavailable(details) ? `IPv6 firewall raw rule created (id ${createdId}).` : `IPv6 firewall raw rule created successfully:
8797
8834
 
8798
- ${details}` : `IPv6 firewall raw rule created with ID: ${result}`;
8799
- }
8800
- return `Failed to create IPv6 firewall raw rule: ${result}`;
8835
+ ${details}`;
8801
8836
  }
8802
8837
  const count = await executeMikrotikCommand("/ipv6 firewall raw print detail count-only", ctx);
8803
8838
  const c = count.trim();
@@ -17710,15 +17745,16 @@ var switchRuleTools = [
17710
17745
  ctx.info(`Adding switch rule: switch=${a.switch}, ports=${a.ports}`);
17711
17746
  const cmd = new Cmd("/interface ethernet switch rule add").set("switch", a.switch).set("ports", a.ports).opt("src-address", a.src_address).opt("dst-address", a.dst_address).opt("src-mac-address", a.src_mac_address).opt("dst-mac-address", a.dst_mac_address).opt("src-port", a.src_port).opt("dst-port", a.dst_port).opt("protocol", a.protocol).opt("mac-protocol", a.mac_protocol).opt("vlan-id", a.vlan_id).opt("vlan-priority", a.vlan_priority).opt("dscp", a.dscp).opt("flow-label", a.flow_label).opt("new-dst-ports", a.new_dst_ports).opt("new-vlan-id", a.new_vlan_id).opt("new-vlan-priority", a.new_vlan_priority).bool("redirect-to-cpu", a.redirect_to_cpu).bool("copy-to-cpu", a.copy_to_cpu).bool("mirror", a.mirror).opt("rate", a.rate).opt("comment", a.comment).flag("disabled", a.disabled).build();
17712
17747
  const result = await executeMikrotikCommand(cmd, ctx);
17713
- if (result.trim()) {
17714
- const trimmed = result.trim();
17715
- if (trimmed.includes("*") || isDigits7(trimmed)) {
17716
- const details = await executeMikrotikCommand(`/interface ethernet switch rule print detail where .id=${trimmed}`, ctx);
17717
- return details.trim() ? `Switch rule added successfully:
17748
+ const trimmed = result.trim();
17749
+ if (looksLikeError(trimmed)) {
17750
+ return `Failed to add switch rule: ${trimmed}`;
17751
+ }
17752
+ const createdId = extractCreatedId(trimmed);
17753
+ if (createdId) {
17754
+ const details = await executeMikrotikCommand(`/interface ethernet switch rule print detail where .id=${createdId}`, ctx);
17755
+ return readBackUnavailable(details) ? `Switch rule added (id ${createdId}).` : `Switch rule added successfully:
17718
17756
 
17719
- ${details}` : `Switch rule added with ID: ${result}`;
17720
- }
17721
- return `Failed to add switch rule: ${result}`;
17757
+ ${details}`;
17722
17758
  }
17723
17759
  const count = await executeMikrotikCommand("/interface ethernet switch rule print detail count-only", ctx);
17724
17760
  const c = count.trim();
@@ -20014,7 +20050,7 @@ ${details}` : "WireGuard interface created successfully.";
20014
20050
  name: "list_wireguard_interfaces",
20015
20051
  title: "List WireGuard Interfaces",
20016
20052
  annotations: READ,
20017
- description: "Lists WireGuard tunnel interfaces (`/interface wireguard print`). Returns each interface's" + " name, listen-port, public-key, MTU, and running/disabled status." + " Supports filtering by name substring (name_filter), disabled-only, or running-only." + " For the peer table use list_wireguard_peers." + " Use the name from this output with get_wireguard_interface or update_wireguard_interface.",
20053
+ description: "`list_wireguard_interfaces` \u2014 READ / list / show / inspect all WireGuard tunnel interfaces" + " (`/interface wireguard print`). The go-to tool to read the current WireGuard state on a" + " device. Returns each interface's name, listen-port, public-key, MTU, and running/disabled" + " status. Filter by name substring (name_filter), disabled-only, or running-only." + " For one interface's full detail use get_wireguard_interface; for the peer table use" + " list_wireguard_peers; for interfaces AND peers in one call use get_wireguard_status.",
20018
20054
  inputSchema: {
20019
20055
  name_filter: z109.string().optional(),
20020
20056
  disabled_only: z109.boolean().default(false),
@@ -20039,7 +20075,7 @@ ${result}`;
20039
20075
  name: "get_wireguard_interface",
20040
20076
  title: "Get WireGuard Interface Details",
20041
20077
  annotations: READ,
20042
- description: "Retrieves full detail of a single WireGuard tunnel interface (`/interface wireguard print detail`)" + " looked up by name. Returns listen-port, private-key, public-key, MTU, running state, and comment." + " For the peer table use get_wireguard_peer." + " Use list_wireguard_interfaces to discover available interface names.",
20078
+ description: "`get_wireguard_interface` \u2014 READ / get / show the full detail of ONE WireGuard tunnel interface" + " (`/interface wireguard print detail`) looked up by name. Returns listen-port, private-key," + " public-key, MTU, running state, and comment. To list all interfaces use" + " list_wireguard_interfaces; for that interface's peers use list_wireguard_peers; for" + " interfaces AND peers in one call use get_wireguard_status.",
20043
20079
  inputSchema: { name: z109.string() },
20044
20080
  async handler(a, ctx) {
20045
20081
  ctx.info(`Getting WireGuard interface details: name=${a.name}`);
@@ -20049,6 +20085,31 @@ ${result}`;
20049
20085
  ${result}`;
20050
20086
  }
20051
20087
  }),
20088
+ defineTool({
20089
+ name: "get_wireguard_status",
20090
+ title: "Get WireGuard Status (Interfaces + Peers)",
20091
+ annotations: READ,
20092
+ description: "`get_wireguard_status` \u2014 READ the current WireGuard state on a device in ONE call: ALL tunnel" + " interfaces AND ALL peers together. Use this FIRST to inspect / show / check WireGuard before" + " changing anything (e.g. to read public keys, see which side initiates, or confirm the tunnel" + " is up). Interfaces report name, listen-port, public-key, MTU and running state; peers report" + " .id, interface, public-key, allowed-address, endpoint/current-endpoint, last-handshake time" + " and rx/tx bytes. Combines list_wireguard_interfaces + list_wireguard_peers; optionally filter" + " both to one interface with interface_filter.",
20093
+ inputSchema: {
20094
+ interface_filter: z109.string().optional().describe("Limit to one interface name, e.g. 'wg-mesh'")
20095
+ },
20096
+ async handler(a, ctx) {
20097
+ ctx.info("Reading WireGuard status (interfaces + peers)");
20098
+ const ifFilters = a.interface_filter ? [`name="${a.interface_filter}"`] : [];
20099
+ const peerFilters = a.interface_filter ? [`interface="${a.interface_filter}"`] : [];
20100
+ const interfaces = await executeMikrotikCommand(`/interface wireguard print${whereClause(ifFilters)}`, ctx);
20101
+ const peers = await executeMikrotikCommand(`/interface wireguard peers print${whereClause(peerFilters)}`, ctx);
20102
+ const ifBlock = isEmpty(interfaces) ? "(none)" : interfaces;
20103
+ const peerBlock = isEmpty(peers) ? "(none)" : peers;
20104
+ return `WIREGUARD INTERFACES:
20105
+
20106
+ ${ifBlock}
20107
+
20108
+ WIREGUARD PEERS:
20109
+
20110
+ ${peerBlock}`;
20111
+ }
20112
+ }),
20052
20113
  defineTool({
20053
20114
  name: "update_wireguard_interface",
20054
20115
  title: "Update WireGuard Interface",
@@ -20161,7 +20222,7 @@ ${details}` : "WireGuard peer added successfully.";
20161
20222
  name: "list_wireguard_peers",
20162
20223
  title: "List WireGuard Peers",
20163
20224
  annotations: READ,
20164
- description: "Lists all WireGuard peer entries (`/interface wireguard peers print`)." + " Returns each peer's .id, interface, public-key, allowed-address, endpoint, last-handshake time, and rx/tx byte counters." + " Filter by interface name (interface_filter) or disabled-only." + " For the interface table use list_wireguard_interfaces." + " Use the .id from this output with get_wireguard_peer, update_wireguard_peer, remove_wireguard_peer," + " enable_wireguard_peer, or disable_wireguard_peer.",
20225
+ description: "`list_wireguard_peers` \u2014 READ / list / show / inspect all WireGuard peers" + " (`/interface wireguard peers print`). Use this to read which peers are configured and" + " whether the tunnel is up (handshake). Returns each peer's .id, interface, public-key," + " allowed-address, endpoint/current-endpoint, last-handshake time, and rx/tx byte counters." + " Filter by interface name (interface_filter) or disabled-only. For the interface table use" + " list_wireguard_interfaces; for one peer's full detail use get_wireguard_peer; for interfaces" + " AND peers in one call use get_wireguard_status. Use the .id from this output with" + " update_wireguard_peer, remove_wireguard_peer, enable_wireguard_peer, or disable_wireguard_peer.",
20165
20226
  inputSchema: {
20166
20227
  interface_filter: z109.string().optional(),
20167
20228
  disabled_only: z109.boolean().default(false)
@@ -20183,7 +20244,7 @@ ${result}`;
20183
20244
  name: "get_wireguard_peer",
20184
20245
  title: "Get WireGuard Peer Details",
20185
20246
  annotations: READ,
20186
- description: "Retrieves full detail of a single WireGuard peer (`/interface wireguard peers print detail`) by its .id." + " Returns public-key, allowed-address, endpoint, preshared-key presence, last-handshake time, and rx/tx bytes." + " For the interface table use get_wireguard_interface." + ` Use list_wireguard_peers to obtain peer .id values.
20247
+ description: "`get_wireguard_peer` \u2014 READ / get / show the full detail of ONE WireGuard peer" + " (`/interface wireguard peers print detail`) by its .id. Returns public-key, allowed-address," + " endpoint/current-endpoint, preshared-key presence, last-handshake time, and rx/tx bytes." + " To list all peers use list_wireguard_peers; for interfaces AND peers in one call use" + ` get_wireguard_status.
20187
20248
 
20188
20249
  ` + `Notes:
20189
20250
  ` + ' peer_id: the .id from list_wireguard_peers, format "*N" or "N" e.g. "*2"',
@@ -21623,10 +21684,27 @@ var moduleCatalog = [
21623
21684
  }
21624
21685
  ];
21625
21686
  var allToolModules = moduleCatalog.map((m) => m.tools);
21687
+ function selectToolModules(filter = {}, catalog = moduleCatalog) {
21688
+ const lc = (xs) => new Set((xs ?? []).map((s) => s.toLowerCase()));
21689
+ const enabledModules = lc(filter.enabledModules);
21690
+ const disabledModules = lc(filter.disabledModules);
21691
+ const enabledGroups = lc(filter.enabledGroups);
21692
+ const disabledGroups = lc(filter.disabledGroups);
21693
+ const hasAllow = enabledModules.size > 0 || enabledGroups.size > 0;
21694
+ return catalog.filter((m) => {
21695
+ const slug = m.slug.toLowerCase();
21696
+ const group = m.group.toLowerCase();
21697
+ if (disabledModules.has(slug) || disabledGroups.has(group))
21698
+ return false;
21699
+ if (hasAllow && !(enabledModules.has(slug) || enabledGroups.has(group)))
21700
+ return false;
21701
+ return true;
21702
+ }).map((m) => m.tools);
21703
+ }
21626
21704
  // package.json
21627
21705
  var package_default = {
21628
21706
  name: "@usex/mikrotik-mcp",
21629
- version: "3.7.0",
21707
+ version: "3.9.0",
21630
21708
  description: "MCP server for MikroTik RouterOS \u2014 660+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
21631
21709
  keywords: [
21632
21710
  "ai",
@@ -21818,7 +21896,8 @@ function createServer(opts = {}) {
21818
21896
  server.server.sendLoggingMessage({ level, data: message }).catch(() => {});
21819
21897
  } catch {}
21820
21898
  });
21821
- const toolCount = registerTools(server, allToolModules, {
21899
+ const toolModules = selectToolModules(getConfig().tools);
21900
+ const toolCount = registerTools(server, toolModules, {
21822
21901
  sendLog,
21823
21902
  deviceNames: names,
21824
21903
  readOnly