@usex/mikrotik-mcp 3.59.0 → 4.0.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.
@@ -436,7 +436,22 @@ function parseDevicesSource(raw, fromFile) {
436
436
  const mcp = structured && obj.mcp && typeof obj.mcp === "object" ? obj.mcp : undefined;
437
437
  const ssh = structured && obj.ssh && typeof obj.ssh === "object" ? obj.ssh : undefined;
438
438
  const memory = structured && obj.memory && typeof obj.memory === "object" ? obj.memory : undefined;
439
- return { devices, defaultDevice, s3, dashboard, tools, mcp, ssh, memory };
439
+ const readOnly = structured && typeof obj.readOnly === "boolean" ? obj.readOnly : undefined;
440
+ const disableUpdateCheck = structured && typeof obj.disableUpdateCheck === "boolean" ? obj.disableUpdateCheck : undefined;
441
+ const backupDir = structured && typeof obj.backupDir === "string" ? obj.backupDir : undefined;
442
+ return {
443
+ devices,
444
+ defaultDevice,
445
+ s3,
446
+ dashboard,
447
+ tools,
448
+ mcp,
449
+ ssh,
450
+ memory,
451
+ readOnly,
452
+ disableUpdateCheck,
453
+ backupDir
454
+ };
440
455
  }
441
456
  var configSource = { path: DEFAULT_CONFIG_FILE, fromFile: false };
442
457
  function getConfigSource() {
@@ -445,6 +460,13 @@ function getConfigSource() {
445
460
  function loadConfig(argv = process.argv.slice(2)) {
446
461
  const flags = parseFlags(argv);
447
462
  const pick = (flag, ...envNames) => flags[flag] ?? env(...envNames);
463
+ const overlay = (file, over) => {
464
+ const out = { ...file };
465
+ for (const [k, v] of Object.entries(over))
466
+ if (v !== undefined)
467
+ out[k] = v;
468
+ return out;
469
+ };
448
470
  const jumpHostName = pick("jump-host", "MIKROTIK_JUMP_HOST");
449
471
  const jumpHost = jumpHostName ? {
450
472
  host: jumpHostName,
@@ -485,6 +507,9 @@ function loadConfig(argv = process.argv.slice(2)) {
485
507
  let fileMcp;
486
508
  let fileSsh;
487
509
  let fileMemory;
510
+ let fileReadOnly;
511
+ let fileDisableUpdateCheck;
512
+ let fileBackupDir;
488
513
  if (configFile || devicesInline) {
489
514
  const src = configFile ? parseDevicesSource(configFile, true) : parseDevicesSource(devicesInline, false);
490
515
  for (const [name, dc] of Object.entries(src.devices))
@@ -499,8 +524,11 @@ function loadConfig(argv = process.argv.slice(2)) {
499
524
  fileMcp = src.mcp;
500
525
  fileSsh = src.ssh;
501
526
  fileMemory = src.memory;
527
+ fileReadOnly = src.readOnly;
528
+ fileDisableUpdateCheck = src.disableUpdateCheck;
529
+ fileBackupDir = src.backupDir;
502
530
  }
503
- const s3 = {
531
+ const s3 = overlay(fileS3, {
504
532
  accessKeyId: pick("s3-access-key-id", "S3_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID"),
505
533
  secretAccessKey: pick("s3-secret-access-key", "S3_SECRET_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY"),
506
534
  sessionToken: pick("s3-session-token", "S3_SESSION_TOKEN", "AWS_SESSION_TOKEN"),
@@ -508,12 +536,11 @@ function loadConfig(argv = process.argv.slice(2)) {
508
536
  endpoint: pick("s3-endpoint", "S3_ENDPOINT", "AWS_ENDPOINT"),
509
537
  bucket: pick("s3-bucket", "S3_BUCKET", "AWS_BUCKET"),
510
538
  prefix: pick("s3-prefix", "MIKROTIK_S3_PREFIX"),
511
- presignExpiresIn: pick("s3-presign-expires-in", "MIKROTIK_S3_PRESIGN_EXPIRES_IN"),
512
- ...fileS3
513
- };
539
+ presignExpiresIn: pick("s3-presign-expires-in", "MIKROTIK_S3_PRESIGN_EXPIRES_IN")
540
+ });
514
541
  const appViewsRaw = pick("app-views", "MIKROTIK_MCP__APP_VIEWS");
515
542
  const appViewsEnv = appViewsRaw === undefined ? undefined : !/^(0|false|no|off)$/i.test(appViewsRaw);
516
- const mcp = {
543
+ const mcp = overlay(fileMcp, {
517
544
  transport: pick("transport", "MIKROTIK_MCP__TRANSPORT", "MCP_TRANSPORT"),
518
545
  host: pick("mcp-host", "MIKROTIK_MCP__HOST"),
519
546
  port: pick("mcp-port", "MIKROTIK_MCP__PORT"),
@@ -521,22 +548,22 @@ function loadConfig(argv = process.argv.slice(2)) {
521
548
  allowedOrigins: pick("mcp-allowed-origins", "MIKROTIK_MCP__ALLOWED_ORIGINS"),
522
549
  corsOrigins: pick("mcp-cors-origins", "MIKROTIK_MCP__CORS_ORIGINS"),
523
550
  toolPageSize: pick("tool-page-size", "MIKROTIK_MCP__TOOL_PAGE_SIZE"),
524
- appViews: appViewsEnv,
525
- ...fileMcp
526
- };
551
+ appViews: appViewsEnv
552
+ });
527
553
  const isTruthy = (v) => /^(1|true|yes|on)$/i.test(v ?? "");
528
- const readOnly = isTruthy(pick("read-only", "MIKROTIK_READ_ONLY"));
529
- const disableUpdateCheck = isTruthy(pick("disable-update-check", "MIKROTIK_DISABLE_UPDATE_CHECK"));
554
+ const boolFileFlag = (flagVal, fileVal) => flagVal !== undefined ? isTruthy(flagVal) : fileVal;
555
+ const readOnly = boolFileFlag(pick("read-only", "MIKROTIK_READ_ONLY"), fileReadOnly);
556
+ const disableUpdateCheck = boolFileFlag(pick("disable-update-check", "MIKROTIK_DISABLE_UPDATE_CHECK"), fileDisableUpdateCheck);
557
+ const backupDir = pick("backup-dir", "MIKROTIK_BACKUP_DIR") ?? fileBackupDir;
530
558
  const csv = (v) => v === undefined ? undefined : v.split(",").map((s) => s.trim()).filter(Boolean);
531
- const tools = {
559
+ const tools = overlay(fileTools, {
532
560
  enabledModules: csv(pick("tools-enabled-modules", "MIKROTIK_TOOLS__ENABLED_MODULES")),
533
561
  disabledModules: csv(pick("tools-disabled-modules", "MIKROTIK_TOOLS__DISABLED_MODULES")),
534
562
  enabledGroups: csv(pick("tools-enabled-groups", "MIKROTIK_TOOLS__ENABLED_GROUPS")),
535
- disabledGroups: csv(pick("tools-disabled-groups", "MIKROTIK_TOOLS__DISABLED_GROUPS")),
536
- ...fileTools
537
- };
563
+ disabledGroups: csv(pick("tools-disabled-groups", "MIKROTIK_TOOLS__DISABLED_GROUPS"))
564
+ });
538
565
  const boolOpt = (v) => v === undefined ? undefined : isTruthy(v);
539
- const dashboard = {
566
+ const dashboard = overlay(fileDashboard, {
540
567
  enabled: boolOpt(pick("dashboard", "MIKROTIK_DASHBOARD__ENABLED", "MIKROTIK_DASHBOARD")),
541
568
  host: pick("dashboard-host", "MIKROTIK_DASHBOARD__HOST"),
542
569
  port: pick("dashboard-port", "MIKROTIK_DASHBOARD__PORT"),
@@ -545,20 +572,17 @@ function loadConfig(argv = process.argv.slice(2)) {
545
572
  captureBody: boolOpt(pick("dashboard-capture-body", "MIKROTIK_DASHBOARD__CAPTURE_BODY")),
546
573
  redactInput: boolOpt(pick("dashboard-redact-input", "MIKROTIK_DASHBOARD__REDACT_INPUT")),
547
574
  maxBodyBytes: pick("dashboard-max-body-bytes", "MIKROTIK_DASHBOARD__MAX_BODY_BYTES"),
548
- token: pick("dashboard-token", "MIKROTIK_DASHBOARD__TOKEN"),
549
- ...fileDashboard
550
- };
551
- const ssh = {
575
+ token: pick("dashboard-token", "MIKROTIK_DASHBOARD__TOKEN")
576
+ });
577
+ const ssh = overlay(fileSsh, {
552
578
  keepAlive: boolOpt(pick("ssh-keep-alive", "MIKROTIK_SSH__KEEP_ALIVE")),
553
579
  keepAliveInterval: pick("ssh-keepalive-interval", "MIKROTIK_SSH__KEEPALIVE_INTERVAL"),
554
- idleTimeout: pick("ssh-idle-timeout", "MIKROTIK_SSH__IDLE_TIMEOUT"),
555
- ...fileSsh
556
- };
557
- const memory = {
580
+ idleTimeout: pick("ssh-idle-timeout", "MIKROTIK_SSH__IDLE_TIMEOUT")
581
+ });
582
+ const memory = overlay(fileMemory, {
558
583
  enabled: boolOpt(pick("memory-enabled", "MIKROTIK_MEMORY__ENABLED")),
559
- dbPath: pick("memory-db", "MIKROTIK_MEMORY__DB_PATH"),
560
- ...fileMemory
561
- };
584
+ dbPath: pick("memory-db", "MIKROTIK_MEMORY__DB_PATH")
585
+ });
562
586
  const hasS3 = !!(s3.accessKeyId || s3.bucket || s3.endpoint);
563
587
  const raw = {
564
588
  devices: Object.keys(devices).length ? devices : { default: {} },
@@ -567,6 +591,7 @@ function loadConfig(argv = process.argv.slice(2)) {
567
591
  dashboard,
568
592
  readOnly,
569
593
  disableUpdateCheck,
594
+ backupDir,
570
595
  tools,
571
596
  ssh,
572
597
  memory,
@@ -1208,6 +1233,8 @@ function stripAnsi(text) {
1208
1233
  return text.replace(ANSI_RE, "");
1209
1234
  }
1210
1235
  var CTRL_X = "\x18";
1236
+ var IDLE_TIMEOUT_MS = 15000;
1237
+ var HARD_CAP_MS = 120000;
1211
1238
  function isSafeModeActivated(response) {
1212
1239
  if (response.includes("<SAFE>"))
1213
1240
  return true;
@@ -1283,7 +1310,7 @@ class SafeModeManager {
1283
1310
  `);
1284
1311
  const { text, timedOut } = await this.readUntilPrompt();
1285
1312
  if (timedOut) {
1286
- throw new Error(`Safe Mode shell did not return a prompt within 15s (command: ${command}). The interactive ` + "session appears wedged \u2014 some RouterOS builds/terminals don't support Safe Mode over SSH. " + "Apply the change with the direct write tools instead (verify each with a read).");
1313
+ throw new Error(`Safe Mode shell went silent for ${IDLE_TIMEOUT_MS / 1000}s (command: ${command}). The ` + "interactive session appears wedged \u2014 some RouterOS builds/terminals don't support Safe " + "Mode over SSH. Apply the change with the direct write tools instead (verify each with a read).");
1287
1314
  }
1288
1315
  return this.extractOutput(text, command);
1289
1316
  });
@@ -1337,25 +1364,32 @@ class SafeModeManager {
1337
1364
  status() {
1338
1365
  return this.active ? "Safe mode is ACTIVE. Changes are pending \u2014 they are NOT yet persisted. " + "Call commit_safe_mode to persist or rollback_safe_mode to revert." : "Safe mode is NOT active. Changes take effect and persist immediately.";
1339
1366
  }
1340
- readUntilPrompt(timeoutMs = 15000, isDone = (c) => PROMPT_RE.test(c)) {
1367
+ readUntilPrompt(idleMs = IDLE_TIMEOUT_MS, isDone = (c) => PROMPT_RE.test(c), hardCapMs = HARD_CAP_MS) {
1341
1368
  const channel = this.channel;
1342
1369
  if (!channel)
1343
1370
  return Promise.resolve({ text: "", timedOut: false });
1344
1371
  return new Promise((resolve2) => {
1345
1372
  let buf = "";
1346
- let timer;
1373
+ let idleTimer;
1374
+ const hardTimer = setTimeout(() => finish(stripAnsi(buf), true), hardCapMs);
1375
+ function armIdle2() {
1376
+ clearTimeout(idleTimer);
1377
+ idleTimer = setTimeout(() => finish(stripAnsi(buf), true), idleMs);
1378
+ }
1347
1379
  function onData(chunk) {
1348
1380
  buf += decodeOutput(chunk);
1381
+ armIdle2();
1349
1382
  const cleaned = stripAnsi(buf);
1350
1383
  if (isDone(cleaned))
1351
1384
  finish(cleaned, false);
1352
1385
  }
1353
1386
  function finish(result, timedOut) {
1354
- clearTimeout(timer);
1387
+ clearTimeout(idleTimer);
1388
+ clearTimeout(hardTimer);
1355
1389
  channel.removeListener("data", onData);
1356
1390
  resolve2({ text: result, timedOut });
1357
1391
  }
1358
- timer = setTimeout(() => finish(stripAnsi(buf), true), timeoutMs);
1392
+ armIdle2();
1359
1393
  channel.on("data", onData);
1360
1394
  });
1361
1395
  }
@@ -5264,7 +5298,7 @@ var changePlanTools = [
5264
5298
  const enabled2 = await safe.enable();
5265
5299
  if (enabled2.startsWith("Error"))
5266
5300
  throw new Error(enabled2);
5267
- const APPLY_BUDGET_MS = 90000;
5301
+ const APPLY_BUDGET_MS = 180000;
5268
5302
  const startedAt = Date.now();
5269
5303
  const overBudget = () => Date.now() - startedAt > APPLY_BUDGET_MS;
5270
5304
  const applyAndDiff = async () => {
@@ -8208,7 +8242,7 @@ var rawCommandTools = [
8208
8242
  if (!cmd)
8209
8243
  return "Provide a RouterOS command to run.";
8210
8244
  ctx.info(`Running raw RouterOS command: ${cmd}`);
8211
- const result = await executeMikrotikCommand(cmd, ctx);
8245
+ const result = await executeMikrotikCommand(cmd, ctx, { maxMs: 30000 });
8212
8246
  if (looksLikeError(result))
8213
8247
  return `RouterOS command error: ${result}`;
8214
8248
  return result.trim() ? result : "(command completed with no output)";
@@ -8330,7 +8364,7 @@ var cache = null;
8330
8364
  async function gateway() {
8331
8365
  if (cache)
8332
8366
  return cache;
8333
- const { moduleCatalog } = await import("./cli-2dyvnv9v.js");
8367
+ const { moduleCatalog } = await import("./cli-cjrk8g04.js");
8334
8368
  const forIndex = [];
8335
8369
  const byName = new Map;
8336
8370
  for (const mod of moduleCatalog) {
@@ -10527,7 +10561,30 @@ ${results.join(`
10527
10561
 
10528
10562
  // src/tools/firewall-nat.ts
10529
10563
  import { z as z29 } from "zod";
10564
+
10565
+ // src/tools/_resolve-rule-id.ts
10566
+ function ruleResolver(scope) {
10567
+ return async function resolveRuleId(ruleId, ctx) {
10568
+ if (/^\d+$/.test(ruleId)) {
10569
+ const id = `*${ruleId}`;
10570
+ const byId = await executeMikrotikCommand(`${scope} print count-only where .id=${id}`, ctx);
10571
+ if (byId.trim() !== "0")
10572
+ return id;
10573
+ const idsRaw = await executeMikrotikCommand(`:foreach i in=[${scope} find] do={:put $i}`, ctx);
10574
+ if (isEmpty(idsRaw))
10575
+ return null;
10576
+ const ids = idsRaw.trim().split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
10577
+ const pos = Number.parseInt(ruleId, 10);
10578
+ return pos >= 0 && pos < ids.length ? ids[pos] : null;
10579
+ }
10580
+ const count = await executeMikrotikCommand(`${scope} print count-only where .id=${ruleId}`, ctx);
10581
+ return count.trim() !== "0" ? ruleId : null;
10582
+ };
10583
+ }
10584
+
10585
+ // src/tools/firewall-nat.ts
10530
10586
  var isDigits2 = (s) => /^\d+$/.test(s);
10587
+ var resolveNatRuleId = ruleResolver("/ip firewall nat");
10531
10588
  async function updateNatRule(a, ctx) {
10532
10589
  ctx.info(`Updating NAT rule: rule_id=${a.rule_id}`);
10533
10590
  const updates = [];
@@ -10603,11 +10660,14 @@ async function updateNatRule(a, ctx) {
10603
10660
  }
10604
10661
  if (updates.length === 0)
10605
10662
  return "No updates specified.";
10606
- const cmd = `/ip firewall nat set ${a.rule_id} ${updates.join(" ")}`;
10663
+ const id = await resolveNatRuleId(a.rule_id, ctx);
10664
+ if (!id)
10665
+ return `NAT rule '${a.rule_id}' not found.`;
10666
+ const cmd = `/ip firewall nat set ${id} ${updates.join(" ")}`;
10607
10667
  const result = await executeMikrotikCommand(cmd, ctx);
10608
10668
  if (looksLikeError(result))
10609
10669
  return `Failed to update NAT rule: ${result}`;
10610
- const details = await executeMikrotikCommand(`/ip firewall nat print detail where .id=${a.rule_id}`, ctx);
10670
+ const details = await executeMikrotikCommand(`/ip firewall nat print detail where .id=${id}`, ctx);
10611
10671
  return `NAT rule updated successfully:
10612
10672
 
10613
10673
  ${details}`;
@@ -10785,8 +10845,11 @@ ${result}`;
10785
10845
  },
10786
10846
  async handler(a, ctx) {
10787
10847
  ctx.info(`Getting NAT rule details: rule_id=${a.rule_id}`);
10788
- const result = await executeMikrotikCommand(`/ip firewall nat print detail where .id=${a.rule_id}`, ctx);
10789
- return isEmpty(result) ? `NAT rule with ID '${a.rule_id}' not found.` : `NAT RULE DETAILS:
10848
+ const id = await resolveNatRuleId(a.rule_id, ctx);
10849
+ if (!id)
10850
+ return `NAT rule '${a.rule_id}' not found.`;
10851
+ const result = await executeMikrotikCommand(`/ip firewall nat print detail where .id=${id}`, ctx);
10852
+ return isEmpty(result) ? `NAT rule '${a.rule_id}' not found.` : `NAT RULE DETAILS:
10790
10853
 
10791
10854
  ${result}`;
10792
10855
  }
@@ -10868,13 +10931,13 @@ ${result}`;
10868
10931
  inputSchema: { rule_id: z29.string() },
10869
10932
  async handler(a, ctx) {
10870
10933
  ctx.info(`Removing NAT rule: rule_id=${a.rule_id}`);
10871
- const count = await executeMikrotikCommand(`/ip firewall nat print count-only where .id=${a.rule_id}`, ctx);
10872
- if (count.trim() === "0")
10873
- return `NAT rule with ID '${a.rule_id}' not found.`;
10874
- const result = await executeMikrotikCommand(`/ip firewall nat remove ${a.rule_id}`, ctx);
10934
+ const id = await resolveNatRuleId(a.rule_id, ctx);
10935
+ if (!id)
10936
+ return `NAT rule '${a.rule_id}' not found.`;
10937
+ const result = await executeMikrotikCommand(`/ip firewall nat remove ${id}`, ctx);
10875
10938
  if (looksLikeError(result))
10876
10939
  return `Failed to remove NAT rule: ${result}`;
10877
- return `NAT rule with ID '${a.rule_id}' removed successfully.`;
10940
+ return `NAT rule '${a.rule_id}' (${id}) removed successfully.`;
10878
10941
  }
10879
10942
  }),
10880
10943
  defineTool({
@@ -10888,13 +10951,13 @@ ${result}`;
10888
10951
  },
10889
10952
  async handler(a, ctx) {
10890
10953
  ctx.info(`Moving NAT rule: rule_id=${a.rule_id} to position ${a.destination}`);
10891
- const count = await executeMikrotikCommand(`/ip firewall nat print count-only where .id=${a.rule_id}`, ctx);
10892
- if (count.trim() === "0")
10893
- return `NAT rule with ID '${a.rule_id}' not found.`;
10894
- const result = await executeMikrotikCommand(`/ip firewall nat move ${a.rule_id} destination=${a.destination}`, ctx);
10954
+ const id = await resolveNatRuleId(a.rule_id, ctx);
10955
+ if (!id)
10956
+ return `NAT rule '${a.rule_id}' not found.`;
10957
+ const result = await executeMikrotikCommand(`/ip firewall nat move ${id} destination=${a.destination}`, ctx);
10895
10958
  if (looksLikeError(result))
10896
10959
  return `Failed to move NAT rule: ${result}`;
10897
- return `NAT rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
10960
+ return `NAT rule '${a.rule_id}' (${id}) moved to position ${a.destination}.`;
10898
10961
  }
10899
10962
  }),
10900
10963
  defineTool({
@@ -10922,6 +10985,7 @@ ${result}`;
10922
10985
  // src/tools/firewall-mangle.ts
10923
10986
  import { z as z30 } from "zod";
10924
10987
  var isDigits3 = (s) => /^\d+$/.test(s);
10988
+ var resolveMangleRuleId = ruleResolver("/ip firewall mangle");
10925
10989
  async function updateMangleRule(a, ctx) {
10926
10990
  ctx.info(`Updating mangle rule: rule_id=${a.rule_id}`);
10927
10991
  const updates = [];
@@ -11007,11 +11071,14 @@ async function updateMangleRule(a, ctx) {
11007
11071
  }
11008
11072
  if (updates.length === 0)
11009
11073
  return "No updates specified.";
11010
- const cmd = `/ip firewall mangle set ${a.rule_id} ${updates.join(" ")}`;
11074
+ const id = await resolveMangleRuleId(a.rule_id, ctx);
11075
+ if (!id)
11076
+ return `Mangle rule '${a.rule_id}' not found.`;
11077
+ const cmd = `/ip firewall mangle set ${id} ${updates.join(" ")}`;
11011
11078
  const result = await executeMikrotikCommand(cmd, ctx);
11012
11079
  if (looksLikeError(result))
11013
11080
  return `Failed to update mangle rule: ${result}`;
11014
- const details = await executeMikrotikCommand(`/ip firewall mangle print detail where .id=${a.rule_id}`, ctx);
11081
+ const details = await executeMikrotikCommand(`/ip firewall mangle print detail where .id=${id}`, ctx);
11015
11082
  return `Mangle rule updated successfully:
11016
11083
 
11017
11084
  ${details}`;
@@ -11186,8 +11253,11 @@ ${result}`;
11186
11253
  },
11187
11254
  async handler(a, ctx) {
11188
11255
  ctx.info(`Getting mangle rule details: rule_id=${a.rule_id}`);
11189
- const result = await executeMikrotikCommand(`/ip firewall mangle print detail where .id=${a.rule_id}`, ctx);
11190
- return isEmpty(result) ? `Mangle rule with ID '${a.rule_id}' not found.` : `MANGLE RULE DETAILS:
11256
+ const id = await resolveMangleRuleId(a.rule_id, ctx);
11257
+ if (!id)
11258
+ return `Mangle rule '${a.rule_id}' not found.`;
11259
+ const result = await executeMikrotikCommand(`/ip firewall mangle print detail where .id=${id}`, ctx);
11260
+ return isEmpty(result) ? `Mangle rule '${a.rule_id}' not found.` : `MANGLE RULE DETAILS:
11191
11261
 
11192
11262
  ${result}`;
11193
11263
  }
@@ -11278,13 +11348,13 @@ ${result}`;
11278
11348
  inputSchema: { rule_id: z30.string() },
11279
11349
  async handler(a, ctx) {
11280
11350
  ctx.info(`Removing mangle rule: rule_id=${a.rule_id}`);
11281
- const count = await executeMikrotikCommand(`/ip firewall mangle print count-only where .id=${a.rule_id}`, ctx);
11282
- if (count.trim() === "0")
11283
- return `Mangle rule with ID '${a.rule_id}' not found.`;
11284
- const result = await executeMikrotikCommand(`/ip firewall mangle remove ${a.rule_id}`, ctx);
11351
+ const id = await resolveMangleRuleId(a.rule_id, ctx);
11352
+ if (!id)
11353
+ return `Mangle rule '${a.rule_id}' not found.`;
11354
+ const result = await executeMikrotikCommand(`/ip firewall mangle remove ${id}`, ctx);
11285
11355
  if (looksLikeError(result))
11286
11356
  return `Failed to remove mangle rule: ${result}`;
11287
- return `Mangle rule with ID '${a.rule_id}' removed successfully.`;
11357
+ return `Mangle rule '${a.rule_id}' (${id}) removed successfully.`;
11288
11358
  }
11289
11359
  }),
11290
11360
  defineTool({
@@ -11298,13 +11368,13 @@ ${result}`;
11298
11368
  },
11299
11369
  async handler(a, ctx) {
11300
11370
  ctx.info(`Moving mangle rule: rule_id=${a.rule_id} to position ${a.destination}`);
11301
- const count = await executeMikrotikCommand(`/ip firewall mangle print count-only where .id=${a.rule_id}`, ctx);
11302
- if (count.trim() === "0")
11303
- return `Mangle rule with ID '${a.rule_id}' not found.`;
11304
- const result = await executeMikrotikCommand(`/ip firewall mangle move ${a.rule_id} destination=${a.destination}`, ctx);
11371
+ const id = await resolveMangleRuleId(a.rule_id, ctx);
11372
+ if (!id)
11373
+ return `Mangle rule '${a.rule_id}' not found.`;
11374
+ const result = await executeMikrotikCommand(`/ip firewall mangle move ${id} destination=${a.destination}`, ctx);
11305
11375
  if (looksLikeError(result))
11306
11376
  return `Failed to move mangle rule: ${result}`;
11307
- return `Mangle rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
11377
+ return `Mangle rule '${a.rule_id}' (${id}) moved to position ${a.destination}.`;
11308
11378
  }
11309
11379
  }),
11310
11380
  defineTool({
@@ -13440,6 +13510,7 @@ ${result}`;
13440
13510
  // src/tools/ipv6-firewall-filter.ts
13441
13511
  import { z as z44 } from "zod";
13442
13512
  var isDigits4 = (s) => /^\d+$/.test(s);
13513
+ var resolveRuleId = ruleResolver("/ipv6 firewall filter");
13443
13514
  async function updateFilterRule2(a, ctx) {
13444
13515
  ctx.info(`Updating IPv6 firewall filter rule: rule_id=${a.rule_id}`);
13445
13516
  const updates = [];
@@ -13508,11 +13579,14 @@ async function updateFilterRule2(a, ctx) {
13508
13579
  }
13509
13580
  if (updates.length === 0)
13510
13581
  return "No updates specified.";
13511
- const cmd = `/ipv6 firewall filter set ${a.rule_id} ${updates.join(" ")}`;
13582
+ const id = await resolveRuleId(a.rule_id, ctx);
13583
+ if (!id)
13584
+ return `IPv6 firewall filter rule '${a.rule_id}' not found.`;
13585
+ const cmd = `/ipv6 firewall filter set ${id} ${updates.join(" ")}`;
13512
13586
  const result = await executeMikrotikCommand(cmd, ctx);
13513
13587
  if (looksLikeError(result))
13514
13588
  return `Failed to update IPv6 firewall filter rule: ${result}`;
13515
- const details = await executeMikrotikCommand(`/ipv6 firewall filter print detail where .id=${a.rule_id}`, ctx);
13589
+ const details = await executeMikrotikCommand(`/ipv6 firewall filter print detail where .id=${id}`, ctx);
13516
13590
  return `IPv6 firewall filter rule updated successfully:
13517
13591
 
13518
13592
  ${details}`;
@@ -13669,8 +13743,11 @@ ${result}`;
13669
13743
  },
13670
13744
  async handler(a, ctx) {
13671
13745
  ctx.info(`Getting IPv6 firewall filter rule details: rule_id=${a.rule_id}`);
13672
- const result = await executeMikrotikCommand(`/ipv6 firewall filter print detail where .id=${a.rule_id}`, ctx);
13673
- return isEmpty(result) ? `IPv6 firewall filter rule with ID '${a.rule_id}' not found.` : `IPV6 FIREWALL FILTER RULE DETAILS:
13746
+ const id = await resolveRuleId(a.rule_id, ctx);
13747
+ if (!id)
13748
+ return `IPv6 firewall filter rule '${a.rule_id}' not found.`;
13749
+ const result = await executeMikrotikCommand(`/ipv6 firewall filter print detail where .id=${id}`, ctx);
13750
+ return isEmpty(result) ? `IPv6 firewall filter rule '${a.rule_id}' not found.` : `IPV6 FIREWALL FILTER RULE DETAILS:
13674
13751
 
13675
13752
  ${result}`;
13676
13753
  }
@@ -13746,13 +13823,13 @@ ${result}`;
13746
13823
  inputSchema: { rule_id: z44.string() },
13747
13824
  async handler(a, ctx) {
13748
13825
  ctx.info(`Removing IPv6 firewall filter rule: rule_id=${a.rule_id}`);
13749
- const count = await executeMikrotikCommand(`/ipv6 firewall filter print count-only where .id=${a.rule_id}`, ctx);
13750
- if (count.trim() === "0")
13751
- return `IPv6 firewall filter rule with ID '${a.rule_id}' not found.`;
13752
- const result = await executeMikrotikCommand(`/ipv6 firewall filter remove ${a.rule_id}`, ctx);
13826
+ const id = await resolveRuleId(a.rule_id, ctx);
13827
+ if (!id)
13828
+ return `IPv6 firewall filter rule '${a.rule_id}' not found.`;
13829
+ const result = await executeMikrotikCommand(`/ipv6 firewall filter remove ${id}`, ctx);
13753
13830
  if (looksLikeError(result))
13754
13831
  return `Failed to remove IPv6 firewall filter rule: ${result}`;
13755
- return `IPv6 firewall filter rule with ID '${a.rule_id}' removed successfully.`;
13832
+ return `IPv6 firewall filter rule '${a.rule_id}' (${id}) removed successfully.`;
13756
13833
  }
13757
13834
  }),
13758
13835
  defineTool({
@@ -13766,13 +13843,13 @@ ${result}`;
13766
13843
  },
13767
13844
  async handler(a, ctx) {
13768
13845
  ctx.info(`Moving IPv6 firewall filter rule: rule_id=${a.rule_id} to position ${a.destination}`);
13769
- const count = await executeMikrotikCommand(`/ipv6 firewall filter print count-only where .id=${a.rule_id}`, ctx);
13770
- if (count.trim() === "0")
13771
- return `IPv6 firewall filter rule with ID '${a.rule_id}' not found.`;
13772
- const result = await executeMikrotikCommand(`/ipv6 firewall filter move ${a.rule_id} destination=${a.destination}`, ctx);
13846
+ const id = await resolveRuleId(a.rule_id, ctx);
13847
+ if (!id)
13848
+ return `IPv6 firewall filter rule '${a.rule_id}' not found.`;
13849
+ const result = await executeMikrotikCommand(`/ipv6 firewall filter move ${id} destination=${a.destination}`, ctx);
13773
13850
  if (looksLikeError(result))
13774
13851
  return `Failed to move IPv6 firewall filter rule: ${result}`;
13775
- return `IPv6 firewall filter rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
13852
+ return `IPv6 firewall filter rule '${a.rule_id}' (${id}) moved to position ${a.destination}.`;
13776
13853
  }
13777
13854
  }),
13778
13855
  defineTool({
@@ -13800,6 +13877,7 @@ ${result}`;
13800
13877
  // src/tools/ipv6-firewall-nat.ts
13801
13878
  import { z as z45 } from "zod";
13802
13879
  var isDigits5 = (s) => /^\d+$/.test(s);
13880
+ var resolveRuleId2 = ruleResolver("/ipv6 firewall nat");
13803
13881
  async function updateNatRule2(a, ctx) {
13804
13882
  ctx.info(`Updating IPv6 firewall NAT rule: rule_id=${a.rule_id}`);
13805
13883
  const updates = [];
@@ -13867,11 +13945,14 @@ async function updateNatRule2(a, ctx) {
13867
13945
  }
13868
13946
  if (updates.length === 0)
13869
13947
  return "No updates specified.";
13870
- const cmd = `/ipv6 firewall nat set ${a.rule_id} ${updates.join(" ")}`;
13948
+ const id = await resolveRuleId2(a.rule_id, ctx);
13949
+ if (!id)
13950
+ return `IPv6 NAT rule '${a.rule_id}' not found.`;
13951
+ const cmd = `/ipv6 firewall nat set ${id} ${updates.join(" ")}`;
13871
13952
  const result = await executeMikrotikCommand(cmd, ctx);
13872
13953
  if (looksLikeError(result))
13873
13954
  return `Failed to update IPv6 firewall NAT rule: ${result}`;
13874
- const details = await executeMikrotikCommand(`/ipv6 firewall nat print detail where .id=${a.rule_id}`, ctx);
13955
+ const details = await executeMikrotikCommand(`/ipv6 firewall nat print detail where .id=${id}`, ctx);
13875
13956
  return `IPv6 firewall NAT rule updated successfully:
13876
13957
 
13877
13958
  ${details}`;
@@ -14022,8 +14103,11 @@ ${result}`;
14022
14103
  },
14023
14104
  async handler(a, ctx) {
14024
14105
  ctx.info(`Getting IPv6 firewall NAT rule details: rule_id=${a.rule_id}`);
14025
- const result = await executeMikrotikCommand(`/ipv6 firewall nat print detail where .id=${a.rule_id}`, ctx);
14026
- return isEmpty(result) ? `IPv6 firewall NAT rule with ID '${a.rule_id}' not found.` : `IPV6 FIREWALL NAT RULE DETAILS:
14106
+ const id = await resolveRuleId2(a.rule_id, ctx);
14107
+ if (!id)
14108
+ return `IPv6 firewall NAT rule '${a.rule_id}' not found.`;
14109
+ const result = await executeMikrotikCommand(`/ipv6 firewall nat print detail where .id=${id}`, ctx);
14110
+ return isEmpty(result) ? `IPv6 firewall NAT rule '${a.rule_id}' not found.` : `IPV6 FIREWALL NAT RULE DETAILS:
14027
14111
 
14028
14112
  ${result}`;
14029
14113
  }
@@ -14098,13 +14182,13 @@ ${result}`;
14098
14182
  inputSchema: { rule_id: z45.string() },
14099
14183
  async handler(a, ctx) {
14100
14184
  ctx.info(`Removing IPv6 firewall NAT rule: rule_id=${a.rule_id}`);
14101
- const count = await executeMikrotikCommand(`/ipv6 firewall nat print count-only where .id=${a.rule_id}`, ctx);
14102
- if (count.trim() === "0")
14103
- return `IPv6 firewall NAT rule with ID '${a.rule_id}' not found.`;
14104
- const result = await executeMikrotikCommand(`/ipv6 firewall nat remove ${a.rule_id}`, ctx);
14185
+ const id = await resolveRuleId2(a.rule_id, ctx);
14186
+ if (!id)
14187
+ return `IPv6 firewall NAT rule '${a.rule_id}' not found.`;
14188
+ const result = await executeMikrotikCommand(`/ipv6 firewall nat remove ${id}`, ctx);
14105
14189
  if (looksLikeError(result))
14106
14190
  return `Failed to remove IPv6 firewall NAT rule: ${result}`;
14107
- return `IPv6 firewall NAT rule with ID '${a.rule_id}' removed successfully.`;
14191
+ return `IPv6 firewall NAT rule '${a.rule_id}' (${id}) removed successfully.`;
14108
14192
  }
14109
14193
  }),
14110
14194
  defineTool({
@@ -14118,13 +14202,13 @@ ${result}`;
14118
14202
  },
14119
14203
  async handler(a, ctx) {
14120
14204
  ctx.info(`Moving IPv6 firewall NAT rule: rule_id=${a.rule_id} to position ${a.destination}`);
14121
- const count = await executeMikrotikCommand(`/ipv6 firewall nat print count-only where .id=${a.rule_id}`, ctx);
14122
- if (count.trim() === "0")
14123
- return `IPv6 firewall NAT rule with ID '${a.rule_id}' not found.`;
14124
- const result = await executeMikrotikCommand(`/ipv6 firewall nat move ${a.rule_id} destination=${a.destination}`, ctx);
14205
+ const id = await resolveRuleId2(a.rule_id, ctx);
14206
+ if (!id)
14207
+ return `IPv6 firewall NAT rule '${a.rule_id}' not found.`;
14208
+ const result = await executeMikrotikCommand(`/ipv6 firewall nat move ${id} destination=${a.destination}`, ctx);
14125
14209
  if (looksLikeError(result))
14126
14210
  return `Failed to move IPv6 firewall NAT rule: ${result}`;
14127
- return `IPv6 firewall NAT rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
14211
+ return `IPv6 firewall NAT rule '${a.rule_id}' (${id}) moved to position ${a.destination}.`;
14128
14212
  }
14129
14213
  }),
14130
14214
  defineTool({
@@ -14152,6 +14236,7 @@ ${result}`;
14152
14236
  // src/tools/ipv6-firewall-mangle.ts
14153
14237
  import { z as z46 } from "zod";
14154
14238
  var isDigits6 = (s) => /^\d+$/.test(s);
14239
+ var resolveRuleId3 = ruleResolver("/ipv6 firewall mangle");
14155
14240
  async function updateMangleRule2(a, ctx) {
14156
14241
  ctx.info(`Updating IPv6 firewall mangle rule: rule_id=${a.rule_id}`);
14157
14242
  const updates = [];
@@ -14227,11 +14312,14 @@ async function updateMangleRule2(a, ctx) {
14227
14312
  }
14228
14313
  if (updates.length === 0)
14229
14314
  return "No updates specified.";
14230
- const cmd = `/ipv6 firewall mangle set ${a.rule_id} ${updates.join(" ")}`;
14315
+ const id = await resolveRuleId3(a.rule_id, ctx);
14316
+ if (!id)
14317
+ return `IPv6 mangle rule '${a.rule_id}' not found.`;
14318
+ const cmd = `/ipv6 firewall mangle set ${id} ${updates.join(" ")}`;
14231
14319
  const result = await executeMikrotikCommand(cmd, ctx);
14232
14320
  if (looksLikeError(result))
14233
14321
  return `Failed to update IPv6 firewall mangle rule: ${result}`;
14234
- const details = await executeMikrotikCommand(`/ipv6 firewall mangle print detail where .id=${a.rule_id}`, ctx);
14322
+ const details = await executeMikrotikCommand(`/ipv6 firewall mangle print detail where .id=${id}`, ctx);
14235
14323
  return `IPv6 firewall mangle rule updated successfully:
14236
14324
 
14237
14325
  ${details}`;
@@ -14390,8 +14478,11 @@ ${result}`;
14390
14478
  },
14391
14479
  async handler(a, ctx) {
14392
14480
  ctx.info(`Getting IPv6 firewall mangle rule details: rule_id=${a.rule_id}`);
14393
- const result = await executeMikrotikCommand(`/ipv6 firewall mangle print detail where .id=${a.rule_id}`, ctx);
14394
- return isEmpty(result) ? `IPv6 firewall mangle rule with ID '${a.rule_id}' not found.` : `IPV6 FIREWALL MANGLE RULE DETAILS:
14481
+ const id = await resolveRuleId3(a.rule_id, ctx);
14482
+ if (!id)
14483
+ return `IPv6 mangle rule '${a.rule_id}' not found.`;
14484
+ const result = await executeMikrotikCommand(`/ipv6 firewall mangle print detail where .id=${id}`, ctx);
14485
+ return isEmpty(result) ? `IPv6 mangle rule '${a.rule_id}' not found.` : `IPV6 FIREWALL MANGLE RULE DETAILS:
14395
14486
 
14396
14487
  ${result}`;
14397
14488
  }
@@ -14473,13 +14564,13 @@ ${result}`;
14473
14564
  inputSchema: { rule_id: z46.string() },
14474
14565
  async handler(a, ctx) {
14475
14566
  ctx.info(`Removing IPv6 firewall mangle rule: rule_id=${a.rule_id}`);
14476
- const count = await executeMikrotikCommand(`/ipv6 firewall mangle print count-only where .id=${a.rule_id}`, ctx);
14477
- if (count.trim() === "0")
14478
- return `IPv6 firewall mangle rule with ID '${a.rule_id}' not found.`;
14479
- const result = await executeMikrotikCommand(`/ipv6 firewall mangle remove ${a.rule_id}`, ctx);
14567
+ const id = await resolveRuleId3(a.rule_id, ctx);
14568
+ if (!id)
14569
+ return `IPv6 mangle rule '${a.rule_id}' not found.`;
14570
+ const result = await executeMikrotikCommand(`/ipv6 firewall mangle remove ${id}`, ctx);
14480
14571
  if (looksLikeError(result))
14481
14572
  return `Failed to remove IPv6 firewall mangle rule: ${result}`;
14482
- return `IPv6 firewall mangle rule with ID '${a.rule_id}' removed successfully.`;
14573
+ return `IPv6 mangle rule '${a.rule_id}' (${id}) removed successfully.`;
14483
14574
  }
14484
14575
  }),
14485
14576
  defineTool({
@@ -14493,13 +14584,13 @@ ${result}`;
14493
14584
  },
14494
14585
  async handler(a, ctx) {
14495
14586
  ctx.info(`Moving IPv6 firewall mangle rule: rule_id=${a.rule_id} to position ${a.destination}`);
14496
- const count = await executeMikrotikCommand(`/ipv6 firewall mangle print count-only where .id=${a.rule_id}`, ctx);
14497
- if (count.trim() === "0")
14498
- return `IPv6 firewall mangle rule with ID '${a.rule_id}' not found.`;
14499
- const result = await executeMikrotikCommand(`/ipv6 firewall mangle move ${a.rule_id} destination=${a.destination}`, ctx);
14587
+ const id = await resolveRuleId3(a.rule_id, ctx);
14588
+ if (!id)
14589
+ return `IPv6 mangle rule '${a.rule_id}' not found.`;
14590
+ const result = await executeMikrotikCommand(`/ipv6 firewall mangle move ${id} destination=${a.destination}`, ctx);
14500
14591
  if (looksLikeError(result))
14501
14592
  return `Failed to move IPv6 firewall mangle rule: ${result}`;
14502
- return `IPv6 firewall mangle rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
14593
+ return `IPv6 mangle rule '${a.rule_id}' (${id}) moved to position ${a.destination}.`;
14503
14594
  }
14504
14595
  }),
14505
14596
  defineTool({
@@ -14527,6 +14618,7 @@ ${result}`;
14527
14618
  // src/tools/ipv6-firewall-raw.ts
14528
14619
  import { z as z47 } from "zod";
14529
14620
  var isDigits7 = (s) => /^\d+$/.test(s);
14621
+ var resolveRuleId4 = ruleResolver("/ipv6 firewall raw");
14530
14622
  async function updateRawRule(a, ctx) {
14531
14623
  ctx.info(`Updating IPv6 firewall raw rule: rule_id=${a.rule_id}`);
14532
14624
  const updates = [];
@@ -14572,11 +14664,14 @@ async function updateRawRule(a, ctx) {
14572
14664
  }
14573
14665
  if (updates.length === 0)
14574
14666
  return "No updates specified.";
14575
- const cmd = `/ipv6 firewall raw set ${a.rule_id} ${updates.join(" ")}`;
14667
+ const id = await resolveRuleId4(a.rule_id, ctx);
14668
+ if (!id)
14669
+ return `IPv6 raw rule '${a.rule_id}' not found.`;
14670
+ const cmd = `/ipv6 firewall raw set ${id} ${updates.join(" ")}`;
14576
14671
  const result = await executeMikrotikCommand(cmd, ctx);
14577
14672
  if (looksLikeError(result))
14578
14673
  return `Failed to update IPv6 firewall raw rule: ${result}`;
14579
- const details = await executeMikrotikCommand(`/ipv6 firewall raw print detail where .id=${a.rule_id}`, ctx);
14674
+ const details = await executeMikrotikCommand(`/ipv6 firewall raw print detail where .id=${id}`, ctx);
14580
14675
  return `IPv6 firewall raw rule updated successfully:
14581
14676
 
14582
14677
  ${details}`;
@@ -14691,7 +14786,10 @@ ${result}`;
14691
14786
  },
14692
14787
  async handler(a, ctx) {
14693
14788
  ctx.info(`Getting IPv6 firewall raw rule details: rule_id=${a.rule_id}`);
14694
- const result = await executeMikrotikCommand(`/ipv6 firewall raw print detail where .id=${a.rule_id}`, ctx);
14789
+ const id = await resolveRuleId4(a.rule_id, ctx);
14790
+ if (!id)
14791
+ return `IPv6 firewall raw rule with ID '${a.rule_id}' not found.`;
14792
+ const result = await executeMikrotikCommand(`/ipv6 firewall raw print detail where .id=${id}`, ctx);
14695
14793
  return isEmpty(result) ? `IPv6 firewall raw rule with ID '${a.rule_id}' not found.` : `IPV6 FIREWALL RAW RULE DETAILS:
14696
14794
 
14697
14795
  ${result}`;
@@ -14745,13 +14843,13 @@ ${result}`;
14745
14843
  inputSchema: { rule_id: z47.string() },
14746
14844
  async handler(a, ctx) {
14747
14845
  ctx.info(`Removing IPv6 firewall raw rule: rule_id=${a.rule_id}`);
14748
- const count = await executeMikrotikCommand(`/ipv6 firewall raw print count-only where .id=${a.rule_id}`, ctx);
14749
- if (count.trim() === "0")
14846
+ const id = await resolveRuleId4(a.rule_id, ctx);
14847
+ if (!id)
14750
14848
  return `IPv6 firewall raw rule with ID '${a.rule_id}' not found.`;
14751
- const result = await executeMikrotikCommand(`/ipv6 firewall raw remove ${a.rule_id}`, ctx);
14849
+ const result = await executeMikrotikCommand(`/ipv6 firewall raw remove ${id}`, ctx);
14752
14850
  if (looksLikeError(result))
14753
14851
  return `Failed to remove IPv6 firewall raw rule: ${result}`;
14754
- return `IPv6 firewall raw rule with ID '${a.rule_id}' removed successfully.`;
14852
+ return `IPv6 firewall raw rule '${a.rule_id}' (${id}) removed successfully.`;
14755
14853
  }
14756
14854
  }),
14757
14855
  defineTool({
@@ -14765,13 +14863,13 @@ ${result}`;
14765
14863
  },
14766
14864
  async handler(a, ctx) {
14767
14865
  ctx.info(`Moving IPv6 firewall raw rule: rule_id=${a.rule_id} to position ${a.destination}`);
14768
- const count = await executeMikrotikCommand(`/ipv6 firewall raw print count-only where .id=${a.rule_id}`, ctx);
14769
- if (count.trim() === "0")
14866
+ const id = await resolveRuleId4(a.rule_id, ctx);
14867
+ if (!id)
14770
14868
  return `IPv6 firewall raw rule with ID '${a.rule_id}' not found.`;
14771
- const result = await executeMikrotikCommand(`/ipv6 firewall raw move ${a.rule_id} destination=${a.destination}`, ctx);
14869
+ const result = await executeMikrotikCommand(`/ipv6 firewall raw move ${id} destination=${a.destination}`, ctx);
14772
14870
  if (looksLikeError(result))
14773
14871
  return `Failed to move IPv6 firewall raw rule: ${result}`;
14774
- return `IPv6 firewall raw rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
14872
+ return `IPv6 firewall raw rule '${a.rule_id}' (${id}) moved to position ${a.destination}.`;
14775
14873
  }
14776
14874
  }),
14777
14875
  defineTool({
@@ -22117,7 +22215,7 @@ ${result}`;
22117
22215
  name: "check_route_path",
22118
22216
  title: "Check IPv4 Route Path",
22119
22217
  annotations: READ,
22120
- description: "Resolves which nexthop RouterOS would use for a given IPv4 destination " + '\u2014 answers "which gateway will this packet take?" without sending any traffic. ' + "Version-aware: uses `/ip route check` on v6, falls back to " + "`/ip route print where dst-address in <dest> active=yes` on v7+ where the check " + "command was removed. Optionally scoped by `routing_table` (v7) / `routing_mark` (v6) " + "for policy-routing table lookups. " + "For listing all known routes use list_routes; for a named-table view use get_routing_table. " + "Returns the resolved nexthop and interface detail.",
22218
+ description: "Resolves which nexthop RouterOS would use for a given IPv4 destination " + '\u2014 answers "which gateway will this packet take?" without sending any traffic. ' + "Version-aware: uses `/ip route check` on v6, falls back to " + "`/ip route print where <dest> in dst-address active=yes` on v7+ where the check " + "command was removed. Optionally scoped by `routing_table` (v7) / `routing_mark` (v6) " + "for policy-routing table lookups. " + "For listing all known routes use list_routes; for a named-table view use get_routing_table. " + "Returns the resolved nexthop and interface detail.",
22121
22219
  inputSchema: {
22122
22220
  destination: z88.string(),
22123
22221
  routing_table: z88.string().optional().describe('Policy-routing table name (v7) or routing-mark (v6), e.g. "VPN"')
@@ -22132,8 +22230,8 @@ ${result}`;
22132
22230
 
22133
22231
  ${v6Result}`;
22134
22232
  }
22135
- const where = [`dst-address in ${a.destination}`, "active=yes"];
22136
- if (table)
22233
+ const where = [`${a.destination} in dst-address`, "active=yes"];
22234
+ if (table && table !== "main")
22137
22235
  where.push(`routing-table=${quoteValue(table)}`);
22138
22236
  const v7Cmd = `/ip route print detail where ${where.join(" ")}`;
22139
22237
  const v7Result = await executeMikrotikCommand(v7Cmd, ctx);
@@ -24927,13 +25025,15 @@ async function fetchLatestRelease() {
24927
25025
  throw new Error(`GitHub API ${res.status}`);
24928
25026
  const gh = await res.json();
24929
25027
  const latestVersion = gh.tag_name.replace(/^v/, "");
25028
+ const cmp = compareVersions(latestVersion, VERSION);
24930
25029
  const data = {
24931
25030
  version: latestVersion,
24932
25031
  name: gh.name || `v${latestVersion}`,
24933
25032
  body: gh.body || "",
24934
25033
  publishedAt: gh.published_at,
24935
25034
  url: gh.html_url,
24936
- isNewer: compareVersions(latestVersion, VERSION) > 0,
25035
+ isNewer: cmp > 0,
25036
+ isAhead: cmp < 0,
24937
25037
  currentVersion: VERSION
24938
25038
  };
24939
25039
  memoryCache = { data, fetchedAt: Date.now() };
@@ -25041,14 +25141,18 @@ var serverPulseTools = [
25041
25141
  const sep = "\u2500".repeat(50);
25042
25142
  sections.push(`SERVER PULSE \u2014 ${SERVER_TITLE} v${VERSION}`, sep, "Package: @usex/mikrotik-mcp", `Version: ${VERSION}`, `Uptime: ${formatUptime(uptime)}`, `Website: ${WEBSITE_URL}`);
25043
25143
  if (result.release) {
25044
- const freshness = assessFreshness(VERSION, result.release.version);
25045
- const label = freshnessLabel(freshness);
25046
25144
  const age = timeAgo(result.release.publishedAt);
25047
- sections.push("", `UPDATE STATUS: ${label}`, sep, `Current: v${VERSION}`, `Latest: v${result.release.version} (${result.release.name})`, `Published: ${age}`, `Freshness: ${freshness.toUpperCase()}`);
25048
- if (result.release.isNewer) {
25049
- sections.push("", ">>> A newer version is available! <<<");
25145
+ if (result.release.isAhead) {
25146
+ sections.push("", "UPDATE STATUS: AHEAD OF LATEST RELEASE", sep, `Current: v${VERSION}`, `Published: v${result.release.version} (${result.release.name})`, `Released: ${age}`, `Freshness: DEV BUILD`, "", "You are running ahead of the latest published release.");
25050
25147
  } else {
25051
- sections.push("", "You are running the latest version.");
25148
+ const freshness = assessFreshness(VERSION, result.release.version);
25149
+ const label = freshnessLabel(freshness);
25150
+ sections.push("", `UPDATE STATUS: ${label}`, sep, `Current: v${VERSION}`, `Latest: v${result.release.version} (${result.release.name})`, `Published: ${age}`, `Freshness: ${freshness.toUpperCase()}`);
25151
+ if (result.release.isNewer) {
25152
+ sections.push("", ">>> A newer version is available! <<<");
25153
+ } else {
25154
+ sections.push("", "You are running the latest version.");
25155
+ }
25052
25156
  }
25053
25157
  if (result.release.isNewer) {
25054
25158
  sections.push("", "UPGRADE", sep);