@usex/mikrotik-mcp 5.0.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -230,7 +230,7 @@ import {
230
230
  validatePolicyText,
231
231
  worstSeverity,
232
232
  writeBackup
233
- } from "./shared/cli-2dj736ma.js";
233
+ } from "./shared/cli-mqb3rqfy.js";
234
234
 
235
235
  // src/cli.ts
236
236
  import { existsSync as existsSync2 } from "fs";
@@ -2703,12 +2703,27 @@ async function attackRoutes(req, url) {
2703
2703
  }, 501);
2704
2704
  }
2705
2705
  if (p === "/api/attacks/scan" && req.method === "POST") {
2706
- logger.info("attack sweep requested from the dashboard");
2707
- const result = await sweep({ respond: false });
2706
+ const body = await req.json().catch(() => ({}));
2707
+ const scope = body.devices?.length ? body.devices.join(", ") : body.onlineOnly ? "reachable devices" : "every device";
2708
+ logger.info(`attack sweep requested from the dashboard (${scope})`);
2709
+ const result = await sweep({
2710
+ devices: body.devices?.length ? body.devices : undefined,
2711
+ onlineOnly: body.onlineOnly,
2712
+ respond: false
2713
+ });
2708
2714
  return json9({
2709
2715
  incidents: result.incidents,
2710
2716
  unavailable: result.unavailable,
2711
- devices: result.devices
2717
+ devices: result.devices,
2718
+ skipped: result.skipped
2719
+ });
2720
+ }
2721
+ if (p === "/api/attacks/devices" && req.method === "GET") {
2722
+ return json9({
2723
+ devices: listDevices().names.map((name) => ({
2724
+ name,
2725
+ reachable: getDeviceStatus(name).reachable
2726
+ }))
2712
2727
  });
2713
2728
  }
2714
2729
  const rest = p.slice("/api/attacks/".length).split("/").filter(Boolean);
@@ -3252,6 +3267,11 @@ function configPayload() {
3252
3267
  s3: cfg.s3,
3253
3268
  dashboard: cfg.dashboard,
3254
3269
  ssh: cfg.ssh,
3270
+ alerts: cfg.alerts,
3271
+ flows: cfg.flows,
3272
+ policy: cfg.policy,
3273
+ schedules: cfg.schedules,
3274
+ attacks: cfg.attacks,
3255
3275
  readOnly: cfg.readOnly,
3256
3276
  tools: cfg.tools,
3257
3277
  memory: cfg.memory,
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  selectToolModules,
30
30
  setConfig,
31
31
  updateSummaryLine
32
- } from "./shared/library-13dhfdnq.js";
32
+ } from "./shared/library-tq8phch8.js";
33
33
  // src/server.ts
34
34
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
35
35
  import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
@@ -10832,7 +10832,7 @@ var cache2 = null;
10832
10832
  async function gateway() {
10833
10833
  if (cache2)
10834
10834
  return cache2;
10835
- const { moduleCatalog } = await import("./cli-vvy7yfyg.js");
10835
+ const { moduleCatalog } = await import("./cli-s9fc4c7j.js");
10836
10836
  const forIndex = [];
10837
10837
  const byName = new Map;
10838
10838
  for (const mod of moduleCatalog) {
@@ -34834,7 +34834,7 @@ var riskByTool = null;
34834
34834
  async function riskIndex() {
34835
34835
  if (riskByTool)
34836
34836
  return riskByTool;
34837
- const { moduleCatalog } = await import("./cli-vvy7yfyg.js");
34837
+ const { moduleCatalog } = await import("./cli-s9fc4c7j.js");
34838
34838
  const index = new Map;
34839
34839
  for (const mod of moduleCatalog) {
34840
34840
  for (const tool of mod.tools)
@@ -37566,11 +37566,28 @@ function policyFromConfig() {
37566
37566
  autoRespondTo: cfg.autoRespondTo
37567
37567
  };
37568
37568
  }
37569
+ function resolveTargets2(spec) {
37570
+ const all = spec.devices ?? listDevices().names;
37571
+ if (!spec.onlineOnly)
37572
+ return { targets: all, skipped: [] };
37573
+ const targets = [];
37574
+ const skipped = [];
37575
+ for (const device of all) {
37576
+ if (getDeviceStatus(device).reachable === false)
37577
+ skipped.push(device);
37578
+ else
37579
+ targets.push(device);
37580
+ }
37581
+ return { targets, skipped };
37582
+ }
37569
37583
  async function sweep(options = {}) {
37570
37584
  const cfg = getConfig().attacks;
37571
37585
  const now = options.now ?? Date.now();
37572
37586
  const windowMinutes = options.windowMinutes ?? cfg.windowMinutes;
37573
- const targets = options.devices ?? listDevices().names;
37587
+ const { targets, skipped } = resolveTargets2({
37588
+ devices: options.devices,
37589
+ onlineOnly: options.onlineOnly
37590
+ });
37574
37591
  const store3 = await attackStore().catch(() => null);
37575
37592
  const signals = [];
37576
37593
  const unavailable = [];
@@ -37674,7 +37691,7 @@ async function sweep(options = {}) {
37674
37691
  });
37675
37692
  }
37676
37693
  }
37677
- return { incidents, unavailable, devices: deviceResults, responses };
37694
+ return { incidents, unavailable, devices: deviceResults, responses, skipped };
37678
37695
  }
37679
37696
  var timer3 = null;
37680
37697
  var running2 = false;
@@ -37726,15 +37743,17 @@ var attackTools = [
37726
37743
  title: "Scan for Attacks Now",
37727
37744
  annotations: READ,
37728
37745
  noDevice: true,
37729
- description: "Reads recent logs from every configured device, runs the attack detectors, and correlates " + "what they find into incidents \u2014 brute force, credential spraying, a login that SUCCEEDED " + "after failures, port scans, management ports answering the internet, and unexplained " + "changes to security-relevant configuration. Read-only: it changes nothing on any device. " + "One source hitting several routers is reported as ONE incident, which is the pattern no " + "single router can see. Detectors whose input is missing say so and name the fix, rather " + "than reporting a quiet network.",
37746
+ description: "Reads recent logs from every configured device, runs the attack detectors, and correlates " + "what they find into incidents \u2014 brute force, credential spraying, a login that SUCCEEDED " + "after failures, port scans, management ports answering the internet, and unexplained " + "changes to security-relevant configuration. Read-only: it changes nothing on any device. " + "One source hitting several routers is reported as ONE incident, which is the pattern no " + "single router can see. Detectors whose input is missing say so and name the fix, rather " + "than reporting a quiet network. Pass `devices` for one router, or `online_only` to skip " + "the ones currently unreachable \u2014 any device left out is named in the result.",
37730
37747
  inputSchema: {
37731
37748
  devices: z116.array(z116.string()).optional().describe("Limit to these devices (default: all)."),
37749
+ online_only: z116.boolean().default(false).describe("Skip devices the health probe currently reports as unreachable. A device not yet " + "probed is still scanned \u2014 never probed is not the same as known-offline."),
37732
37750
  window_minutes: z116.coerce.number().int().positive().max(1440).optional().describe("How far back to read. Default from config (10).")
37733
37751
  },
37734
37752
  async handler(a, ctx) {
37735
37753
  ctx.info("Scanning the fleet for attacks");
37736
37754
  const result = await sweep({
37737
37755
  devices: a.devices,
37756
+ onlineOnly: a.online_only,
37738
37757
  windowMinutes: a.window_minutes,
37739
37758
  respond: false
37740
37759
  });
@@ -37743,6 +37762,9 @@ var attackTools = [
37743
37762
  lines.push(`Scanned ${result.devices.length} device(s), ${result.devices.reduce((n, d) => n + d.events, 0)} log event(s).`);
37744
37763
  for (const f of failed)
37745
37764
  lines.push(` \u2717 ${f.device}: ${f.error}`);
37765
+ if (result.skipped.length > 0) {
37766
+ lines.push(` Skipped as unreachable: ${result.skipped.join(", ")}`);
37767
+ }
37746
37768
  lines.push("");
37747
37769
  if (result.incidents.length === 0) {
37748
37770
  lines.push("No attack incidents found in this window.");
@@ -4,7 +4,7 @@ import {
4
4
  allToolModules,
5
5
  moduleCatalog,
6
6
  selectToolModules
7
- } from "./cli-2dj736ma.js";
7
+ } from "./cli-mqb3rqfy.js";
8
8
  export {
9
9
  selectToolModules,
10
10
  moduleCatalog,
@@ -10607,7 +10607,7 @@ var cache2 = null;
10607
10607
  async function gateway() {
10608
10608
  if (cache2)
10609
10609
  return cache2;
10610
- const { moduleCatalog } = await import("./library-syt5fmtj.js");
10610
+ const { moduleCatalog } = await import("./library-xgy70fsf.js");
10611
10611
  const forIndex = [];
10612
10612
  const byName = new Map;
10613
10613
  for (const mod of moduleCatalog) {
@@ -32930,6 +32930,10 @@ var HISTORY_CAP = 60;
32930
32930
  var statuses = new Map;
32931
32931
  var histories = new Map;
32932
32932
  var neighbors = new Map;
32933
+ var UNKNOWN = { reachable: null, checkedAt: null, latencyMs: null };
32934
+ function getDeviceStatus(name) {
32935
+ return statuses.get(name) ?? UNKNOWN;
32936
+ }
32933
32937
  function pushHistory(name, sample) {
32934
32938
  const arr = histories.get(name) ?? [];
32935
32939
  arr.push(sample);
@@ -34330,7 +34334,7 @@ var riskByTool = null;
34330
34334
  async function riskIndex() {
34331
34335
  if (riskByTool)
34332
34336
  return riskByTool;
34333
- const { moduleCatalog } = await import("./library-syt5fmtj.js");
34337
+ const { moduleCatalog } = await import("./library-xgy70fsf.js");
34334
34338
  const index = new Map;
34335
34339
  for (const mod of moduleCatalog) {
34336
34340
  for (const tool of mod.tools)
@@ -37004,11 +37008,28 @@ function policyFromConfig() {
37004
37008
  autoRespondTo: cfg.autoRespondTo
37005
37009
  };
37006
37010
  }
37011
+ function resolveTargets2(spec) {
37012
+ const all = spec.devices ?? listDevices().names;
37013
+ if (!spec.onlineOnly)
37014
+ return { targets: all, skipped: [] };
37015
+ const targets = [];
37016
+ const skipped = [];
37017
+ for (const device of all) {
37018
+ if (getDeviceStatus(device).reachable === false)
37019
+ skipped.push(device);
37020
+ else
37021
+ targets.push(device);
37022
+ }
37023
+ return { targets, skipped };
37024
+ }
37007
37025
  async function sweep(options = {}) {
37008
37026
  const cfg = getConfig().attacks;
37009
37027
  const now = options.now ?? Date.now();
37010
37028
  const windowMinutes = options.windowMinutes ?? cfg.windowMinutes;
37011
- const targets = options.devices ?? listDevices().names;
37029
+ const { targets, skipped } = resolveTargets2({
37030
+ devices: options.devices,
37031
+ onlineOnly: options.onlineOnly
37032
+ });
37012
37033
  const store3 = await attackStore().catch(() => null);
37013
37034
  const signals = [];
37014
37035
  const unavailable = [];
@@ -37112,7 +37133,7 @@ async function sweep(options = {}) {
37112
37133
  });
37113
37134
  }
37114
37135
  }
37115
- return { incidents, unavailable, devices: deviceResults, responses };
37136
+ return { incidents, unavailable, devices: deviceResults, responses, skipped };
37116
37137
  }
37117
37138
 
37118
37139
  // src/tools/attack.ts
@@ -37137,15 +37158,17 @@ var attackTools = [
37137
37158
  title: "Scan for Attacks Now",
37138
37159
  annotations: READ,
37139
37160
  noDevice: true,
37140
- description: "Reads recent logs from every configured device, runs the attack detectors, and correlates " + "what they find into incidents \u2014 brute force, credential spraying, a login that SUCCEEDED " + "after failures, port scans, management ports answering the internet, and unexplained " + "changes to security-relevant configuration. Read-only: it changes nothing on any device. " + "One source hitting several routers is reported as ONE incident, which is the pattern no " + "single router can see. Detectors whose input is missing say so and name the fix, rather " + "than reporting a quiet network.",
37161
+ description: "Reads recent logs from every configured device, runs the attack detectors, and correlates " + "what they find into incidents \u2014 brute force, credential spraying, a login that SUCCEEDED " + "after failures, port scans, management ports answering the internet, and unexplained " + "changes to security-relevant configuration. Read-only: it changes nothing on any device. " + "One source hitting several routers is reported as ONE incident, which is the pattern no " + "single router can see. Detectors whose input is missing say so and name the fix, rather " + "than reporting a quiet network. Pass `devices` for one router, or `online_only` to skip " + "the ones currently unreachable \u2014 any device left out is named in the result.",
37141
37162
  inputSchema: {
37142
37163
  devices: z116.array(z116.string()).optional().describe("Limit to these devices (default: all)."),
37164
+ online_only: z116.boolean().default(false).describe("Skip devices the health probe currently reports as unreachable. A device not yet " + "probed is still scanned \u2014 never probed is not the same as known-offline."),
37143
37165
  window_minutes: z116.coerce.number().int().positive().max(1440).optional().describe("How far back to read. Default from config (10).")
37144
37166
  },
37145
37167
  async handler(a, ctx) {
37146
37168
  ctx.info("Scanning the fleet for attacks");
37147
37169
  const result = await sweep({
37148
37170
  devices: a.devices,
37171
+ onlineOnly: a.online_only,
37149
37172
  windowMinutes: a.window_minutes,
37150
37173
  respond: false
37151
37174
  });
@@ -37154,6 +37177,9 @@ var attackTools = [
37154
37177
  lines.push(`Scanned ${result.devices.length} device(s), ${result.devices.reduce((n, d) => n + d.events, 0)} log event(s).`);
37155
37178
  for (const f of failed)
37156
37179
  lines.push(` \u2717 ${f.device}: ${f.error}`);
37180
+ if (result.skipped.length > 0) {
37181
+ lines.push(` Skipped as unreachable: ${result.skipped.join(", ")}`);
37182
+ }
37157
37183
  lines.push("");
37158
37184
  if (result.incidents.length === 0) {
37159
37185
  lines.push("No attack incidents found in this window.");
@@ -4,7 +4,7 @@ import {
4
4
  allToolModules,
5
5
  moduleCatalog,
6
6
  selectToolModules
7
- } from "./library-13dhfdnq.js";
7
+ } from "./library-tq8phch8.js";
8
8
  export {
9
9
  selectToolModules,
10
10
  moduleCatalog,