@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,12 +436,34 @@ 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 loadConfig(argv = process.argv.slice(2)) {
443
458
  const flags = parseFlags(argv);
444
459
  const pick = (flag, ...envNames) => flags[flag] ?? env(...envNames);
460
+ const overlay = (file, over) => {
461
+ const out = { ...file };
462
+ for (const [k, v] of Object.entries(over))
463
+ if (v !== undefined)
464
+ out[k] = v;
465
+ return out;
466
+ };
445
467
  const jumpHostName = pick("jump-host", "MIKROTIK_JUMP_HOST");
446
468
  const jumpHost = jumpHostName ? {
447
469
  host: jumpHostName,
@@ -482,6 +504,9 @@ function loadConfig(argv = process.argv.slice(2)) {
482
504
  let fileMcp;
483
505
  let fileSsh;
484
506
  let fileMemory;
507
+ let fileReadOnly;
508
+ let fileDisableUpdateCheck;
509
+ let fileBackupDir;
485
510
  if (configFile || devicesInline) {
486
511
  const src = configFile ? parseDevicesSource(configFile, true) : parseDevicesSource(devicesInline, false);
487
512
  for (const [name, dc] of Object.entries(src.devices))
@@ -496,8 +521,11 @@ function loadConfig(argv = process.argv.slice(2)) {
496
521
  fileMcp = src.mcp;
497
522
  fileSsh = src.ssh;
498
523
  fileMemory = src.memory;
524
+ fileReadOnly = src.readOnly;
525
+ fileDisableUpdateCheck = src.disableUpdateCheck;
526
+ fileBackupDir = src.backupDir;
499
527
  }
500
- const s3 = {
528
+ const s3 = overlay(fileS3, {
501
529
  accessKeyId: pick("s3-access-key-id", "S3_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID"),
502
530
  secretAccessKey: pick("s3-secret-access-key", "S3_SECRET_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY"),
503
531
  sessionToken: pick("s3-session-token", "S3_SESSION_TOKEN", "AWS_SESSION_TOKEN"),
@@ -505,12 +533,11 @@ function loadConfig(argv = process.argv.slice(2)) {
505
533
  endpoint: pick("s3-endpoint", "S3_ENDPOINT", "AWS_ENDPOINT"),
506
534
  bucket: pick("s3-bucket", "S3_BUCKET", "AWS_BUCKET"),
507
535
  prefix: pick("s3-prefix", "MIKROTIK_S3_PREFIX"),
508
- presignExpiresIn: pick("s3-presign-expires-in", "MIKROTIK_S3_PRESIGN_EXPIRES_IN"),
509
- ...fileS3
510
- };
536
+ presignExpiresIn: pick("s3-presign-expires-in", "MIKROTIK_S3_PRESIGN_EXPIRES_IN")
537
+ });
511
538
  const appViewsRaw = pick("app-views", "MIKROTIK_MCP__APP_VIEWS");
512
539
  const appViewsEnv = appViewsRaw === undefined ? undefined : !/^(0|false|no|off)$/i.test(appViewsRaw);
513
- const mcp = {
540
+ const mcp = overlay(fileMcp, {
514
541
  transport: pick("transport", "MIKROTIK_MCP__TRANSPORT", "MCP_TRANSPORT"),
515
542
  host: pick("mcp-host", "MIKROTIK_MCP__HOST"),
516
543
  port: pick("mcp-port", "MIKROTIK_MCP__PORT"),
@@ -518,22 +545,22 @@ function loadConfig(argv = process.argv.slice(2)) {
518
545
  allowedOrigins: pick("mcp-allowed-origins", "MIKROTIK_MCP__ALLOWED_ORIGINS"),
519
546
  corsOrigins: pick("mcp-cors-origins", "MIKROTIK_MCP__CORS_ORIGINS"),
520
547
  toolPageSize: pick("tool-page-size", "MIKROTIK_MCP__TOOL_PAGE_SIZE"),
521
- appViews: appViewsEnv,
522
- ...fileMcp
523
- };
548
+ appViews: appViewsEnv
549
+ });
524
550
  const isTruthy = (v) => /^(1|true|yes|on)$/i.test(v ?? "");
525
- const readOnly = isTruthy(pick("read-only", "MIKROTIK_READ_ONLY"));
526
- const disableUpdateCheck = isTruthy(pick("disable-update-check", "MIKROTIK_DISABLE_UPDATE_CHECK"));
551
+ const boolFileFlag = (flagVal, fileVal) => flagVal !== undefined ? isTruthy(flagVal) : fileVal;
552
+ const readOnly = boolFileFlag(pick("read-only", "MIKROTIK_READ_ONLY"), fileReadOnly);
553
+ const disableUpdateCheck = boolFileFlag(pick("disable-update-check", "MIKROTIK_DISABLE_UPDATE_CHECK"), fileDisableUpdateCheck);
554
+ const backupDir = pick("backup-dir", "MIKROTIK_BACKUP_DIR") ?? fileBackupDir;
527
555
  const csv = (v) => v === undefined ? undefined : v.split(",").map((s) => s.trim()).filter(Boolean);
528
- const tools = {
556
+ const tools = overlay(fileTools, {
529
557
  enabledModules: csv(pick("tools-enabled-modules", "MIKROTIK_TOOLS__ENABLED_MODULES")),
530
558
  disabledModules: csv(pick("tools-disabled-modules", "MIKROTIK_TOOLS__DISABLED_MODULES")),
531
559
  enabledGroups: csv(pick("tools-enabled-groups", "MIKROTIK_TOOLS__ENABLED_GROUPS")),
532
- disabledGroups: csv(pick("tools-disabled-groups", "MIKROTIK_TOOLS__DISABLED_GROUPS")),
533
- ...fileTools
534
- };
560
+ disabledGroups: csv(pick("tools-disabled-groups", "MIKROTIK_TOOLS__DISABLED_GROUPS"))
561
+ });
535
562
  const boolOpt = (v) => v === undefined ? undefined : isTruthy(v);
536
- const dashboard = {
563
+ const dashboard = overlay(fileDashboard, {
537
564
  enabled: boolOpt(pick("dashboard", "MIKROTIK_DASHBOARD__ENABLED", "MIKROTIK_DASHBOARD")),
538
565
  host: pick("dashboard-host", "MIKROTIK_DASHBOARD__HOST"),
539
566
  port: pick("dashboard-port", "MIKROTIK_DASHBOARD__PORT"),
@@ -542,20 +569,17 @@ function loadConfig(argv = process.argv.slice(2)) {
542
569
  captureBody: boolOpt(pick("dashboard-capture-body", "MIKROTIK_DASHBOARD__CAPTURE_BODY")),
543
570
  redactInput: boolOpt(pick("dashboard-redact-input", "MIKROTIK_DASHBOARD__REDACT_INPUT")),
544
571
  maxBodyBytes: pick("dashboard-max-body-bytes", "MIKROTIK_DASHBOARD__MAX_BODY_BYTES"),
545
- token: pick("dashboard-token", "MIKROTIK_DASHBOARD__TOKEN"),
546
- ...fileDashboard
547
- };
548
- const ssh = {
572
+ token: pick("dashboard-token", "MIKROTIK_DASHBOARD__TOKEN")
573
+ });
574
+ const ssh = overlay(fileSsh, {
549
575
  keepAlive: boolOpt(pick("ssh-keep-alive", "MIKROTIK_SSH__KEEP_ALIVE")),
550
576
  keepAliveInterval: pick("ssh-keepalive-interval", "MIKROTIK_SSH__KEEPALIVE_INTERVAL"),
551
- idleTimeout: pick("ssh-idle-timeout", "MIKROTIK_SSH__IDLE_TIMEOUT"),
552
- ...fileSsh
553
- };
554
- const memory = {
577
+ idleTimeout: pick("ssh-idle-timeout", "MIKROTIK_SSH__IDLE_TIMEOUT")
578
+ });
579
+ const memory = overlay(fileMemory, {
555
580
  enabled: boolOpt(pick("memory-enabled", "MIKROTIK_MEMORY__ENABLED")),
556
- dbPath: pick("memory-db", "MIKROTIK_MEMORY__DB_PATH"),
557
- ...fileMemory
558
- };
581
+ dbPath: pick("memory-db", "MIKROTIK_MEMORY__DB_PATH")
582
+ });
559
583
  const hasS3 = !!(s3.accessKeyId || s3.bucket || s3.endpoint);
560
584
  const raw = {
561
585
  devices: Object.keys(devices).length ? devices : { default: {} },
@@ -564,6 +588,7 @@ function loadConfig(argv = process.argv.slice(2)) {
564
588
  dashboard,
565
589
  readOnly,
566
590
  disableUpdateCheck,
591
+ backupDir,
567
592
  tools,
568
593
  ssh,
569
594
  memory,
@@ -1189,6 +1214,8 @@ function stripAnsi(text) {
1189
1214
  return text.replace(ANSI_RE, "");
1190
1215
  }
1191
1216
  var CTRL_X = "\x18";
1217
+ var IDLE_TIMEOUT_MS = 15000;
1218
+ var HARD_CAP_MS = 120000;
1192
1219
  function isSafeModeActivated(response) {
1193
1220
  if (response.includes("<SAFE>"))
1194
1221
  return true;
@@ -1264,7 +1291,7 @@ class SafeModeManager {
1264
1291
  `);
1265
1292
  const { text, timedOut } = await this.readUntilPrompt();
1266
1293
  if (timedOut) {
1267
- 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).");
1294
+ 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).");
1268
1295
  }
1269
1296
  return this.extractOutput(text, command);
1270
1297
  });
@@ -1318,25 +1345,32 @@ class SafeModeManager {
1318
1345
  status() {
1319
1346
  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.";
1320
1347
  }
1321
- readUntilPrompt(timeoutMs = 15000, isDone = (c) => PROMPT_RE.test(c)) {
1348
+ readUntilPrompt(idleMs = IDLE_TIMEOUT_MS, isDone = (c) => PROMPT_RE.test(c), hardCapMs = HARD_CAP_MS) {
1322
1349
  const channel = this.channel;
1323
1350
  if (!channel)
1324
1351
  return Promise.resolve({ text: "", timedOut: false });
1325
1352
  return new Promise((resolve2) => {
1326
1353
  let buf = "";
1327
- let timer;
1354
+ let idleTimer;
1355
+ const hardTimer = setTimeout(() => finish(stripAnsi(buf), true), hardCapMs);
1356
+ function armIdle2() {
1357
+ clearTimeout(idleTimer);
1358
+ idleTimer = setTimeout(() => finish(stripAnsi(buf), true), idleMs);
1359
+ }
1328
1360
  function onData(chunk) {
1329
1361
  buf += decodeOutput(chunk);
1362
+ armIdle2();
1330
1363
  const cleaned = stripAnsi(buf);
1331
1364
  if (isDone(cleaned))
1332
1365
  finish(cleaned, false);
1333
1366
  }
1334
1367
  function finish(result, timedOut) {
1335
- clearTimeout(timer);
1368
+ clearTimeout(idleTimer);
1369
+ clearTimeout(hardTimer);
1336
1370
  channel.removeListener("data", onData);
1337
1371
  resolve2({ text: result, timedOut });
1338
1372
  }
1339
- timer = setTimeout(() => finish(stripAnsi(buf), true), timeoutMs);
1373
+ armIdle2();
1340
1374
  channel.on("data", onData);
1341
1375
  });
1342
1376
  }
@@ -5194,7 +5228,7 @@ var changePlanTools = [
5194
5228
  const enabled2 = await safe.enable();
5195
5229
  if (enabled2.startsWith("Error"))
5196
5230
  throw new Error(enabled2);
5197
- const APPLY_BUDGET_MS = 90000;
5231
+ const APPLY_BUDGET_MS = 180000;
5198
5232
  const startedAt = Date.now();
5199
5233
  const overBudget = () => Date.now() - startedAt > APPLY_BUDGET_MS;
5200
5234
  const applyAndDiff = async () => {
@@ -8138,7 +8172,7 @@ var rawCommandTools = [
8138
8172
  if (!cmd)
8139
8173
  return "Provide a RouterOS command to run.";
8140
8174
  ctx.info(`Running raw RouterOS command: ${cmd}`);
8141
- const result = await executeMikrotikCommand(cmd, ctx);
8175
+ const result = await executeMikrotikCommand(cmd, ctx, { maxMs: 30000 });
8142
8176
  if (looksLikeError(result))
8143
8177
  return `RouterOS command error: ${result}`;
8144
8178
  return result.trim() ? result : "(command completed with no output)";
@@ -8260,7 +8294,7 @@ var cache = null;
8260
8294
  async function gateway() {
8261
8295
  if (cache)
8262
8296
  return cache;
8263
- const { moduleCatalog } = await import("./library-0zd2tej9.js");
8297
+ const { moduleCatalog } = await import("./library-4fpy49qb.js");
8264
8298
  const forIndex = [];
8265
8299
  const byName = new Map;
8266
8300
  for (const mod of moduleCatalog) {
@@ -10457,7 +10491,30 @@ ${results.join(`
10457
10491
 
10458
10492
  // src/tools/firewall-nat.ts
10459
10493
  import { z as z29 } from "zod";
10494
+
10495
+ // src/tools/_resolve-rule-id.ts
10496
+ function ruleResolver(scope) {
10497
+ return async function resolveRuleId(ruleId, ctx) {
10498
+ if (/^\d+$/.test(ruleId)) {
10499
+ const id = `*${ruleId}`;
10500
+ const byId = await executeMikrotikCommand(`${scope} print count-only where .id=${id}`, ctx);
10501
+ if (byId.trim() !== "0")
10502
+ return id;
10503
+ const idsRaw = await executeMikrotikCommand(`:foreach i in=[${scope} find] do={:put $i}`, ctx);
10504
+ if (isEmpty(idsRaw))
10505
+ return null;
10506
+ const ids = idsRaw.trim().split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
10507
+ const pos = Number.parseInt(ruleId, 10);
10508
+ return pos >= 0 && pos < ids.length ? ids[pos] : null;
10509
+ }
10510
+ const count = await executeMikrotikCommand(`${scope} print count-only where .id=${ruleId}`, ctx);
10511
+ return count.trim() !== "0" ? ruleId : null;
10512
+ };
10513
+ }
10514
+
10515
+ // src/tools/firewall-nat.ts
10460
10516
  var isDigits2 = (s) => /^\d+$/.test(s);
10517
+ var resolveNatRuleId = ruleResolver("/ip firewall nat");
10461
10518
  async function updateNatRule(a, ctx) {
10462
10519
  ctx.info(`Updating NAT rule: rule_id=${a.rule_id}`);
10463
10520
  const updates = [];
@@ -10533,11 +10590,14 @@ async function updateNatRule(a, ctx) {
10533
10590
  }
10534
10591
  if (updates.length === 0)
10535
10592
  return "No updates specified.";
10536
- const cmd = `/ip firewall nat set ${a.rule_id} ${updates.join(" ")}`;
10593
+ const id = await resolveNatRuleId(a.rule_id, ctx);
10594
+ if (!id)
10595
+ return `NAT rule '${a.rule_id}' not found.`;
10596
+ const cmd = `/ip firewall nat set ${id} ${updates.join(" ")}`;
10537
10597
  const result = await executeMikrotikCommand(cmd, ctx);
10538
10598
  if (looksLikeError(result))
10539
10599
  return `Failed to update NAT rule: ${result}`;
10540
- const details = await executeMikrotikCommand(`/ip firewall nat print detail where .id=${a.rule_id}`, ctx);
10600
+ const details = await executeMikrotikCommand(`/ip firewall nat print detail where .id=${id}`, ctx);
10541
10601
  return `NAT rule updated successfully:
10542
10602
 
10543
10603
  ${details}`;
@@ -10715,8 +10775,11 @@ ${result}`;
10715
10775
  },
10716
10776
  async handler(a, ctx) {
10717
10777
  ctx.info(`Getting NAT rule details: rule_id=${a.rule_id}`);
10718
- const result = await executeMikrotikCommand(`/ip firewall nat print detail where .id=${a.rule_id}`, ctx);
10719
- return isEmpty(result) ? `NAT rule with ID '${a.rule_id}' not found.` : `NAT RULE DETAILS:
10778
+ const id = await resolveNatRuleId(a.rule_id, ctx);
10779
+ if (!id)
10780
+ return `NAT rule '${a.rule_id}' not found.`;
10781
+ const result = await executeMikrotikCommand(`/ip firewall nat print detail where .id=${id}`, ctx);
10782
+ return isEmpty(result) ? `NAT rule '${a.rule_id}' not found.` : `NAT RULE DETAILS:
10720
10783
 
10721
10784
  ${result}`;
10722
10785
  }
@@ -10798,13 +10861,13 @@ ${result}`;
10798
10861
  inputSchema: { rule_id: z29.string() },
10799
10862
  async handler(a, ctx) {
10800
10863
  ctx.info(`Removing NAT rule: rule_id=${a.rule_id}`);
10801
- const count = await executeMikrotikCommand(`/ip firewall nat print count-only where .id=${a.rule_id}`, ctx);
10802
- if (count.trim() === "0")
10803
- return `NAT rule with ID '${a.rule_id}' not found.`;
10804
- const result = await executeMikrotikCommand(`/ip firewall nat remove ${a.rule_id}`, ctx);
10864
+ const id = await resolveNatRuleId(a.rule_id, ctx);
10865
+ if (!id)
10866
+ return `NAT rule '${a.rule_id}' not found.`;
10867
+ const result = await executeMikrotikCommand(`/ip firewall nat remove ${id}`, ctx);
10805
10868
  if (looksLikeError(result))
10806
10869
  return `Failed to remove NAT rule: ${result}`;
10807
- return `NAT rule with ID '${a.rule_id}' removed successfully.`;
10870
+ return `NAT rule '${a.rule_id}' (${id}) removed successfully.`;
10808
10871
  }
10809
10872
  }),
10810
10873
  defineTool({
@@ -10818,13 +10881,13 @@ ${result}`;
10818
10881
  },
10819
10882
  async handler(a, ctx) {
10820
10883
  ctx.info(`Moving NAT rule: rule_id=${a.rule_id} to position ${a.destination}`);
10821
- const count = await executeMikrotikCommand(`/ip firewall nat print count-only where .id=${a.rule_id}`, ctx);
10822
- if (count.trim() === "0")
10823
- return `NAT rule with ID '${a.rule_id}' not found.`;
10824
- const result = await executeMikrotikCommand(`/ip firewall nat move ${a.rule_id} destination=${a.destination}`, ctx);
10884
+ const id = await resolveNatRuleId(a.rule_id, ctx);
10885
+ if (!id)
10886
+ return `NAT rule '${a.rule_id}' not found.`;
10887
+ const result = await executeMikrotikCommand(`/ip firewall nat move ${id} destination=${a.destination}`, ctx);
10825
10888
  if (looksLikeError(result))
10826
10889
  return `Failed to move NAT rule: ${result}`;
10827
- return `NAT rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
10890
+ return `NAT rule '${a.rule_id}' (${id}) moved to position ${a.destination}.`;
10828
10891
  }
10829
10892
  }),
10830
10893
  defineTool({
@@ -10852,6 +10915,7 @@ ${result}`;
10852
10915
  // src/tools/firewall-mangle.ts
10853
10916
  import { z as z30 } from "zod";
10854
10917
  var isDigits3 = (s) => /^\d+$/.test(s);
10918
+ var resolveMangleRuleId = ruleResolver("/ip firewall mangle");
10855
10919
  async function updateMangleRule(a, ctx) {
10856
10920
  ctx.info(`Updating mangle rule: rule_id=${a.rule_id}`);
10857
10921
  const updates = [];
@@ -10937,11 +11001,14 @@ async function updateMangleRule(a, ctx) {
10937
11001
  }
10938
11002
  if (updates.length === 0)
10939
11003
  return "No updates specified.";
10940
- const cmd = `/ip firewall mangle set ${a.rule_id} ${updates.join(" ")}`;
11004
+ const id = await resolveMangleRuleId(a.rule_id, ctx);
11005
+ if (!id)
11006
+ return `Mangle rule '${a.rule_id}' not found.`;
11007
+ const cmd = `/ip firewall mangle set ${id} ${updates.join(" ")}`;
10941
11008
  const result = await executeMikrotikCommand(cmd, ctx);
10942
11009
  if (looksLikeError(result))
10943
11010
  return `Failed to update mangle rule: ${result}`;
10944
- const details = await executeMikrotikCommand(`/ip firewall mangle print detail where .id=${a.rule_id}`, ctx);
11011
+ const details = await executeMikrotikCommand(`/ip firewall mangle print detail where .id=${id}`, ctx);
10945
11012
  return `Mangle rule updated successfully:
10946
11013
 
10947
11014
  ${details}`;
@@ -11116,8 +11183,11 @@ ${result}`;
11116
11183
  },
11117
11184
  async handler(a, ctx) {
11118
11185
  ctx.info(`Getting mangle rule details: rule_id=${a.rule_id}`);
11119
- const result = await executeMikrotikCommand(`/ip firewall mangle print detail where .id=${a.rule_id}`, ctx);
11120
- return isEmpty(result) ? `Mangle rule with ID '${a.rule_id}' not found.` : `MANGLE RULE DETAILS:
11186
+ const id = await resolveMangleRuleId(a.rule_id, ctx);
11187
+ if (!id)
11188
+ return `Mangle rule '${a.rule_id}' not found.`;
11189
+ const result = await executeMikrotikCommand(`/ip firewall mangle print detail where .id=${id}`, ctx);
11190
+ return isEmpty(result) ? `Mangle rule '${a.rule_id}' not found.` : `MANGLE RULE DETAILS:
11121
11191
 
11122
11192
  ${result}`;
11123
11193
  }
@@ -11208,13 +11278,13 @@ ${result}`;
11208
11278
  inputSchema: { rule_id: z30.string() },
11209
11279
  async handler(a, ctx) {
11210
11280
  ctx.info(`Removing mangle rule: rule_id=${a.rule_id}`);
11211
- const count = await executeMikrotikCommand(`/ip firewall mangle print count-only where .id=${a.rule_id}`, ctx);
11212
- if (count.trim() === "0")
11213
- return `Mangle rule with ID '${a.rule_id}' not found.`;
11214
- const result = await executeMikrotikCommand(`/ip firewall mangle remove ${a.rule_id}`, ctx);
11281
+ const id = await resolveMangleRuleId(a.rule_id, ctx);
11282
+ if (!id)
11283
+ return `Mangle rule '${a.rule_id}' not found.`;
11284
+ const result = await executeMikrotikCommand(`/ip firewall mangle remove ${id}`, ctx);
11215
11285
  if (looksLikeError(result))
11216
11286
  return `Failed to remove mangle rule: ${result}`;
11217
- return `Mangle rule with ID '${a.rule_id}' removed successfully.`;
11287
+ return `Mangle rule '${a.rule_id}' (${id}) removed successfully.`;
11218
11288
  }
11219
11289
  }),
11220
11290
  defineTool({
@@ -11228,13 +11298,13 @@ ${result}`;
11228
11298
  },
11229
11299
  async handler(a, ctx) {
11230
11300
  ctx.info(`Moving mangle rule: rule_id=${a.rule_id} to position ${a.destination}`);
11231
- const count = await executeMikrotikCommand(`/ip firewall mangle print count-only where .id=${a.rule_id}`, ctx);
11232
- if (count.trim() === "0")
11233
- return `Mangle rule with ID '${a.rule_id}' not found.`;
11234
- const result = await executeMikrotikCommand(`/ip firewall mangle move ${a.rule_id} destination=${a.destination}`, ctx);
11301
+ const id = await resolveMangleRuleId(a.rule_id, ctx);
11302
+ if (!id)
11303
+ return `Mangle rule '${a.rule_id}' not found.`;
11304
+ const result = await executeMikrotikCommand(`/ip firewall mangle move ${id} destination=${a.destination}`, ctx);
11235
11305
  if (looksLikeError(result))
11236
11306
  return `Failed to move mangle rule: ${result}`;
11237
- return `Mangle rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
11307
+ return `Mangle rule '${a.rule_id}' (${id}) moved to position ${a.destination}.`;
11238
11308
  }
11239
11309
  }),
11240
11310
  defineTool({
@@ -13370,6 +13440,7 @@ ${result}`;
13370
13440
  // src/tools/ipv6-firewall-filter.ts
13371
13441
  import { z as z44 } from "zod";
13372
13442
  var isDigits4 = (s) => /^\d+$/.test(s);
13443
+ var resolveRuleId = ruleResolver("/ipv6 firewall filter");
13373
13444
  async function updateFilterRule2(a, ctx) {
13374
13445
  ctx.info(`Updating IPv6 firewall filter rule: rule_id=${a.rule_id}`);
13375
13446
  const updates = [];
@@ -13438,11 +13509,14 @@ async function updateFilterRule2(a, ctx) {
13438
13509
  }
13439
13510
  if (updates.length === 0)
13440
13511
  return "No updates specified.";
13441
- const cmd = `/ipv6 firewall filter set ${a.rule_id} ${updates.join(" ")}`;
13512
+ const id = await resolveRuleId(a.rule_id, ctx);
13513
+ if (!id)
13514
+ return `IPv6 firewall filter rule '${a.rule_id}' not found.`;
13515
+ const cmd = `/ipv6 firewall filter set ${id} ${updates.join(" ")}`;
13442
13516
  const result = await executeMikrotikCommand(cmd, ctx);
13443
13517
  if (looksLikeError(result))
13444
13518
  return `Failed to update IPv6 firewall filter rule: ${result}`;
13445
- const details = await executeMikrotikCommand(`/ipv6 firewall filter print detail where .id=${a.rule_id}`, ctx);
13519
+ const details = await executeMikrotikCommand(`/ipv6 firewall filter print detail where .id=${id}`, ctx);
13446
13520
  return `IPv6 firewall filter rule updated successfully:
13447
13521
 
13448
13522
  ${details}`;
@@ -13599,8 +13673,11 @@ ${result}`;
13599
13673
  },
13600
13674
  async handler(a, ctx) {
13601
13675
  ctx.info(`Getting IPv6 firewall filter rule details: rule_id=${a.rule_id}`);
13602
- const result = await executeMikrotikCommand(`/ipv6 firewall filter print detail where .id=${a.rule_id}`, ctx);
13603
- return isEmpty(result) ? `IPv6 firewall filter rule with ID '${a.rule_id}' not found.` : `IPV6 FIREWALL FILTER RULE DETAILS:
13676
+ const id = await resolveRuleId(a.rule_id, ctx);
13677
+ if (!id)
13678
+ return `IPv6 firewall filter rule '${a.rule_id}' not found.`;
13679
+ const result = await executeMikrotikCommand(`/ipv6 firewall filter print detail where .id=${id}`, ctx);
13680
+ return isEmpty(result) ? `IPv6 firewall filter rule '${a.rule_id}' not found.` : `IPV6 FIREWALL FILTER RULE DETAILS:
13604
13681
 
13605
13682
  ${result}`;
13606
13683
  }
@@ -13676,13 +13753,13 @@ ${result}`;
13676
13753
  inputSchema: { rule_id: z44.string() },
13677
13754
  async handler(a, ctx) {
13678
13755
  ctx.info(`Removing IPv6 firewall filter rule: rule_id=${a.rule_id}`);
13679
- const count = await executeMikrotikCommand(`/ipv6 firewall filter print count-only where .id=${a.rule_id}`, ctx);
13680
- if (count.trim() === "0")
13681
- return `IPv6 firewall filter rule with ID '${a.rule_id}' not found.`;
13682
- const result = await executeMikrotikCommand(`/ipv6 firewall filter remove ${a.rule_id}`, ctx);
13756
+ const id = await resolveRuleId(a.rule_id, ctx);
13757
+ if (!id)
13758
+ return `IPv6 firewall filter rule '${a.rule_id}' not found.`;
13759
+ const result = await executeMikrotikCommand(`/ipv6 firewall filter remove ${id}`, ctx);
13683
13760
  if (looksLikeError(result))
13684
13761
  return `Failed to remove IPv6 firewall filter rule: ${result}`;
13685
- return `IPv6 firewall filter rule with ID '${a.rule_id}' removed successfully.`;
13762
+ return `IPv6 firewall filter rule '${a.rule_id}' (${id}) removed successfully.`;
13686
13763
  }
13687
13764
  }),
13688
13765
  defineTool({
@@ -13696,13 +13773,13 @@ ${result}`;
13696
13773
  },
13697
13774
  async handler(a, ctx) {
13698
13775
  ctx.info(`Moving IPv6 firewall filter rule: rule_id=${a.rule_id} to position ${a.destination}`);
13699
- const count = await executeMikrotikCommand(`/ipv6 firewall filter print count-only where .id=${a.rule_id}`, ctx);
13700
- if (count.trim() === "0")
13701
- return `IPv6 firewall filter rule with ID '${a.rule_id}' not found.`;
13702
- const result = await executeMikrotikCommand(`/ipv6 firewall filter move ${a.rule_id} destination=${a.destination}`, ctx);
13776
+ const id = await resolveRuleId(a.rule_id, ctx);
13777
+ if (!id)
13778
+ return `IPv6 firewall filter rule '${a.rule_id}' not found.`;
13779
+ const result = await executeMikrotikCommand(`/ipv6 firewall filter move ${id} destination=${a.destination}`, ctx);
13703
13780
  if (looksLikeError(result))
13704
13781
  return `Failed to move IPv6 firewall filter rule: ${result}`;
13705
- return `IPv6 firewall filter rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
13782
+ return `IPv6 firewall filter rule '${a.rule_id}' (${id}) moved to position ${a.destination}.`;
13706
13783
  }
13707
13784
  }),
13708
13785
  defineTool({
@@ -13730,6 +13807,7 @@ ${result}`;
13730
13807
  // src/tools/ipv6-firewall-nat.ts
13731
13808
  import { z as z45 } from "zod";
13732
13809
  var isDigits5 = (s) => /^\d+$/.test(s);
13810
+ var resolveRuleId2 = ruleResolver("/ipv6 firewall nat");
13733
13811
  async function updateNatRule2(a, ctx) {
13734
13812
  ctx.info(`Updating IPv6 firewall NAT rule: rule_id=${a.rule_id}`);
13735
13813
  const updates = [];
@@ -13797,11 +13875,14 @@ async function updateNatRule2(a, ctx) {
13797
13875
  }
13798
13876
  if (updates.length === 0)
13799
13877
  return "No updates specified.";
13800
- const cmd = `/ipv6 firewall nat set ${a.rule_id} ${updates.join(" ")}`;
13878
+ const id = await resolveRuleId2(a.rule_id, ctx);
13879
+ if (!id)
13880
+ return `IPv6 NAT rule '${a.rule_id}' not found.`;
13881
+ const cmd = `/ipv6 firewall nat set ${id} ${updates.join(" ")}`;
13801
13882
  const result = await executeMikrotikCommand(cmd, ctx);
13802
13883
  if (looksLikeError(result))
13803
13884
  return `Failed to update IPv6 firewall NAT rule: ${result}`;
13804
- const details = await executeMikrotikCommand(`/ipv6 firewall nat print detail where .id=${a.rule_id}`, ctx);
13885
+ const details = await executeMikrotikCommand(`/ipv6 firewall nat print detail where .id=${id}`, ctx);
13805
13886
  return `IPv6 firewall NAT rule updated successfully:
13806
13887
 
13807
13888
  ${details}`;
@@ -13952,8 +14033,11 @@ ${result}`;
13952
14033
  },
13953
14034
  async handler(a, ctx) {
13954
14035
  ctx.info(`Getting IPv6 firewall NAT rule details: rule_id=${a.rule_id}`);
13955
- const result = await executeMikrotikCommand(`/ipv6 firewall nat print detail where .id=${a.rule_id}`, ctx);
13956
- return isEmpty(result) ? `IPv6 firewall NAT rule with ID '${a.rule_id}' not found.` : `IPV6 FIREWALL NAT RULE DETAILS:
14036
+ const id = await resolveRuleId2(a.rule_id, ctx);
14037
+ if (!id)
14038
+ return `IPv6 firewall NAT rule '${a.rule_id}' not found.`;
14039
+ const result = await executeMikrotikCommand(`/ipv6 firewall nat print detail where .id=${id}`, ctx);
14040
+ return isEmpty(result) ? `IPv6 firewall NAT rule '${a.rule_id}' not found.` : `IPV6 FIREWALL NAT RULE DETAILS:
13957
14041
 
13958
14042
  ${result}`;
13959
14043
  }
@@ -14028,13 +14112,13 @@ ${result}`;
14028
14112
  inputSchema: { rule_id: z45.string() },
14029
14113
  async handler(a, ctx) {
14030
14114
  ctx.info(`Removing IPv6 firewall NAT rule: rule_id=${a.rule_id}`);
14031
- const count = await executeMikrotikCommand(`/ipv6 firewall nat print count-only where .id=${a.rule_id}`, ctx);
14032
- if (count.trim() === "0")
14033
- return `IPv6 firewall NAT rule with ID '${a.rule_id}' not found.`;
14034
- const result = await executeMikrotikCommand(`/ipv6 firewall nat remove ${a.rule_id}`, ctx);
14115
+ const id = await resolveRuleId2(a.rule_id, ctx);
14116
+ if (!id)
14117
+ return `IPv6 firewall NAT rule '${a.rule_id}' not found.`;
14118
+ const result = await executeMikrotikCommand(`/ipv6 firewall nat remove ${id}`, ctx);
14035
14119
  if (looksLikeError(result))
14036
14120
  return `Failed to remove IPv6 firewall NAT rule: ${result}`;
14037
- return `IPv6 firewall NAT rule with ID '${a.rule_id}' removed successfully.`;
14121
+ return `IPv6 firewall NAT rule '${a.rule_id}' (${id}) removed successfully.`;
14038
14122
  }
14039
14123
  }),
14040
14124
  defineTool({
@@ -14048,13 +14132,13 @@ ${result}`;
14048
14132
  },
14049
14133
  async handler(a, ctx) {
14050
14134
  ctx.info(`Moving IPv6 firewall NAT rule: rule_id=${a.rule_id} to position ${a.destination}`);
14051
- const count = await executeMikrotikCommand(`/ipv6 firewall nat print count-only where .id=${a.rule_id}`, ctx);
14052
- if (count.trim() === "0")
14053
- return `IPv6 firewall NAT rule with ID '${a.rule_id}' not found.`;
14054
- const result = await executeMikrotikCommand(`/ipv6 firewall nat move ${a.rule_id} destination=${a.destination}`, ctx);
14135
+ const id = await resolveRuleId2(a.rule_id, ctx);
14136
+ if (!id)
14137
+ return `IPv6 firewall NAT rule '${a.rule_id}' not found.`;
14138
+ const result = await executeMikrotikCommand(`/ipv6 firewall nat move ${id} destination=${a.destination}`, ctx);
14055
14139
  if (looksLikeError(result))
14056
14140
  return `Failed to move IPv6 firewall NAT rule: ${result}`;
14057
- return `IPv6 firewall NAT rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
14141
+ return `IPv6 firewall NAT rule '${a.rule_id}' (${id}) moved to position ${a.destination}.`;
14058
14142
  }
14059
14143
  }),
14060
14144
  defineTool({
@@ -14082,6 +14166,7 @@ ${result}`;
14082
14166
  // src/tools/ipv6-firewall-mangle.ts
14083
14167
  import { z as z46 } from "zod";
14084
14168
  var isDigits6 = (s) => /^\d+$/.test(s);
14169
+ var resolveRuleId3 = ruleResolver("/ipv6 firewall mangle");
14085
14170
  async function updateMangleRule2(a, ctx) {
14086
14171
  ctx.info(`Updating IPv6 firewall mangle rule: rule_id=${a.rule_id}`);
14087
14172
  const updates = [];
@@ -14157,11 +14242,14 @@ async function updateMangleRule2(a, ctx) {
14157
14242
  }
14158
14243
  if (updates.length === 0)
14159
14244
  return "No updates specified.";
14160
- const cmd = `/ipv6 firewall mangle set ${a.rule_id} ${updates.join(" ")}`;
14245
+ const id = await resolveRuleId3(a.rule_id, ctx);
14246
+ if (!id)
14247
+ return `IPv6 mangle rule '${a.rule_id}' not found.`;
14248
+ const cmd = `/ipv6 firewall mangle set ${id} ${updates.join(" ")}`;
14161
14249
  const result = await executeMikrotikCommand(cmd, ctx);
14162
14250
  if (looksLikeError(result))
14163
14251
  return `Failed to update IPv6 firewall mangle rule: ${result}`;
14164
- const details = await executeMikrotikCommand(`/ipv6 firewall mangle print detail where .id=${a.rule_id}`, ctx);
14252
+ const details = await executeMikrotikCommand(`/ipv6 firewall mangle print detail where .id=${id}`, ctx);
14165
14253
  return `IPv6 firewall mangle rule updated successfully:
14166
14254
 
14167
14255
  ${details}`;
@@ -14320,8 +14408,11 @@ ${result}`;
14320
14408
  },
14321
14409
  async handler(a, ctx) {
14322
14410
  ctx.info(`Getting IPv6 firewall mangle rule details: rule_id=${a.rule_id}`);
14323
- const result = await executeMikrotikCommand(`/ipv6 firewall mangle print detail where .id=${a.rule_id}`, ctx);
14324
- return isEmpty(result) ? `IPv6 firewall mangle rule with ID '${a.rule_id}' not found.` : `IPV6 FIREWALL MANGLE RULE DETAILS:
14411
+ const id = await resolveRuleId3(a.rule_id, ctx);
14412
+ if (!id)
14413
+ return `IPv6 mangle rule '${a.rule_id}' not found.`;
14414
+ const result = await executeMikrotikCommand(`/ipv6 firewall mangle print detail where .id=${id}`, ctx);
14415
+ return isEmpty(result) ? `IPv6 mangle rule '${a.rule_id}' not found.` : `IPV6 FIREWALL MANGLE RULE DETAILS:
14325
14416
 
14326
14417
  ${result}`;
14327
14418
  }
@@ -14403,13 +14494,13 @@ ${result}`;
14403
14494
  inputSchema: { rule_id: z46.string() },
14404
14495
  async handler(a, ctx) {
14405
14496
  ctx.info(`Removing IPv6 firewall mangle rule: rule_id=${a.rule_id}`);
14406
- const count = await executeMikrotikCommand(`/ipv6 firewall mangle print count-only where .id=${a.rule_id}`, ctx);
14407
- if (count.trim() === "0")
14408
- return `IPv6 firewall mangle rule with ID '${a.rule_id}' not found.`;
14409
- const result = await executeMikrotikCommand(`/ipv6 firewall mangle remove ${a.rule_id}`, ctx);
14497
+ const id = await resolveRuleId3(a.rule_id, ctx);
14498
+ if (!id)
14499
+ return `IPv6 mangle rule '${a.rule_id}' not found.`;
14500
+ const result = await executeMikrotikCommand(`/ipv6 firewall mangle remove ${id}`, ctx);
14410
14501
  if (looksLikeError(result))
14411
14502
  return `Failed to remove IPv6 firewall mangle rule: ${result}`;
14412
- return `IPv6 firewall mangle rule with ID '${a.rule_id}' removed successfully.`;
14503
+ return `IPv6 mangle rule '${a.rule_id}' (${id}) removed successfully.`;
14413
14504
  }
14414
14505
  }),
14415
14506
  defineTool({
@@ -14423,13 +14514,13 @@ ${result}`;
14423
14514
  },
14424
14515
  async handler(a, ctx) {
14425
14516
  ctx.info(`Moving IPv6 firewall mangle rule: rule_id=${a.rule_id} to position ${a.destination}`);
14426
- const count = await executeMikrotikCommand(`/ipv6 firewall mangle print count-only where .id=${a.rule_id}`, ctx);
14427
- if (count.trim() === "0")
14428
- return `IPv6 firewall mangle rule with ID '${a.rule_id}' not found.`;
14429
- const result = await executeMikrotikCommand(`/ipv6 firewall mangle move ${a.rule_id} destination=${a.destination}`, ctx);
14517
+ const id = await resolveRuleId3(a.rule_id, ctx);
14518
+ if (!id)
14519
+ return `IPv6 mangle rule '${a.rule_id}' not found.`;
14520
+ const result = await executeMikrotikCommand(`/ipv6 firewall mangle move ${id} destination=${a.destination}`, ctx);
14430
14521
  if (looksLikeError(result))
14431
14522
  return `Failed to move IPv6 firewall mangle rule: ${result}`;
14432
- return `IPv6 firewall mangle rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
14523
+ return `IPv6 mangle rule '${a.rule_id}' (${id}) moved to position ${a.destination}.`;
14433
14524
  }
14434
14525
  }),
14435
14526
  defineTool({
@@ -14457,6 +14548,7 @@ ${result}`;
14457
14548
  // src/tools/ipv6-firewall-raw.ts
14458
14549
  import { z as z47 } from "zod";
14459
14550
  var isDigits7 = (s) => /^\d+$/.test(s);
14551
+ var resolveRuleId4 = ruleResolver("/ipv6 firewall raw");
14460
14552
  async function updateRawRule(a, ctx) {
14461
14553
  ctx.info(`Updating IPv6 firewall raw rule: rule_id=${a.rule_id}`);
14462
14554
  const updates = [];
@@ -14502,11 +14594,14 @@ async function updateRawRule(a, ctx) {
14502
14594
  }
14503
14595
  if (updates.length === 0)
14504
14596
  return "No updates specified.";
14505
- const cmd = `/ipv6 firewall raw set ${a.rule_id} ${updates.join(" ")}`;
14597
+ const id = await resolveRuleId4(a.rule_id, ctx);
14598
+ if (!id)
14599
+ return `IPv6 raw rule '${a.rule_id}' not found.`;
14600
+ const cmd = `/ipv6 firewall raw set ${id} ${updates.join(" ")}`;
14506
14601
  const result = await executeMikrotikCommand(cmd, ctx);
14507
14602
  if (looksLikeError(result))
14508
14603
  return `Failed to update IPv6 firewall raw rule: ${result}`;
14509
- const details = await executeMikrotikCommand(`/ipv6 firewall raw print detail where .id=${a.rule_id}`, ctx);
14604
+ const details = await executeMikrotikCommand(`/ipv6 firewall raw print detail where .id=${id}`, ctx);
14510
14605
  return `IPv6 firewall raw rule updated successfully:
14511
14606
 
14512
14607
  ${details}`;
@@ -14621,7 +14716,10 @@ ${result}`;
14621
14716
  },
14622
14717
  async handler(a, ctx) {
14623
14718
  ctx.info(`Getting IPv6 firewall raw rule details: rule_id=${a.rule_id}`);
14624
- const result = await executeMikrotikCommand(`/ipv6 firewall raw print detail where .id=${a.rule_id}`, ctx);
14719
+ const id = await resolveRuleId4(a.rule_id, ctx);
14720
+ if (!id)
14721
+ return `IPv6 firewall raw rule with ID '${a.rule_id}' not found.`;
14722
+ const result = await executeMikrotikCommand(`/ipv6 firewall raw print detail where .id=${id}`, ctx);
14625
14723
  return isEmpty(result) ? `IPv6 firewall raw rule with ID '${a.rule_id}' not found.` : `IPV6 FIREWALL RAW RULE DETAILS:
14626
14724
 
14627
14725
  ${result}`;
@@ -14675,13 +14773,13 @@ ${result}`;
14675
14773
  inputSchema: { rule_id: z47.string() },
14676
14774
  async handler(a, ctx) {
14677
14775
  ctx.info(`Removing IPv6 firewall raw rule: rule_id=${a.rule_id}`);
14678
- const count = await executeMikrotikCommand(`/ipv6 firewall raw print count-only where .id=${a.rule_id}`, ctx);
14679
- if (count.trim() === "0")
14776
+ const id = await resolveRuleId4(a.rule_id, ctx);
14777
+ if (!id)
14680
14778
  return `IPv6 firewall raw rule with ID '${a.rule_id}' not found.`;
14681
- const result = await executeMikrotikCommand(`/ipv6 firewall raw remove ${a.rule_id}`, ctx);
14779
+ const result = await executeMikrotikCommand(`/ipv6 firewall raw remove ${id}`, ctx);
14682
14780
  if (looksLikeError(result))
14683
14781
  return `Failed to remove IPv6 firewall raw rule: ${result}`;
14684
- return `IPv6 firewall raw rule with ID '${a.rule_id}' removed successfully.`;
14782
+ return `IPv6 firewall raw rule '${a.rule_id}' (${id}) removed successfully.`;
14685
14783
  }
14686
14784
  }),
14687
14785
  defineTool({
@@ -14695,13 +14793,13 @@ ${result}`;
14695
14793
  },
14696
14794
  async handler(a, ctx) {
14697
14795
  ctx.info(`Moving IPv6 firewall raw rule: rule_id=${a.rule_id} to position ${a.destination}`);
14698
- const count = await executeMikrotikCommand(`/ipv6 firewall raw print count-only where .id=${a.rule_id}`, ctx);
14699
- if (count.trim() === "0")
14796
+ const id = await resolveRuleId4(a.rule_id, ctx);
14797
+ if (!id)
14700
14798
  return `IPv6 firewall raw rule with ID '${a.rule_id}' not found.`;
14701
- const result = await executeMikrotikCommand(`/ipv6 firewall raw move ${a.rule_id} destination=${a.destination}`, ctx);
14799
+ const result = await executeMikrotikCommand(`/ipv6 firewall raw move ${id} destination=${a.destination}`, ctx);
14702
14800
  if (looksLikeError(result))
14703
14801
  return `Failed to move IPv6 firewall raw rule: ${result}`;
14704
- return `IPv6 firewall raw rule with ID '${a.rule_id}' moved to position ${a.destination}.`;
14802
+ return `IPv6 firewall raw rule '${a.rule_id}' (${id}) moved to position ${a.destination}.`;
14705
14803
  }
14706
14804
  }),
14707
14805
  defineTool({
@@ -22047,7 +22145,7 @@ ${result}`;
22047
22145
  name: "check_route_path",
22048
22146
  title: "Check IPv4 Route Path",
22049
22147
  annotations: READ,
22050
- 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.",
22148
+ 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.",
22051
22149
  inputSchema: {
22052
22150
  destination: z88.string(),
22053
22151
  routing_table: z88.string().optional().describe('Policy-routing table name (v7) or routing-mark (v6), e.g. "VPN"')
@@ -22062,8 +22160,8 @@ ${result}`;
22062
22160
 
22063
22161
  ${v6Result}`;
22064
22162
  }
22065
- const where = [`dst-address in ${a.destination}`, "active=yes"];
22066
- if (table)
22163
+ const where = [`${a.destination} in dst-address`, "active=yes"];
22164
+ if (table && table !== "main")
22067
22165
  where.push(`routing-table=${quoteValue(table)}`);
22068
22166
  const v7Cmd = `/ip route print detail where ${where.join(" ")}`;
22069
22167
  const v7Result = await executeMikrotikCommand(v7Cmd, ctx);
@@ -24856,13 +24954,15 @@ async function fetchLatestRelease() {
24856
24954
  throw new Error(`GitHub API ${res.status}`);
24857
24955
  const gh = await res.json();
24858
24956
  const latestVersion = gh.tag_name.replace(/^v/, "");
24957
+ const cmp = compareVersions(latestVersion, VERSION);
24859
24958
  const data = {
24860
24959
  version: latestVersion,
24861
24960
  name: gh.name || `v${latestVersion}`,
24862
24961
  body: gh.body || "",
24863
24962
  publishedAt: gh.published_at,
24864
24963
  url: gh.html_url,
24865
- isNewer: compareVersions(latestVersion, VERSION) > 0,
24964
+ isNewer: cmp > 0,
24965
+ isAhead: cmp < 0,
24866
24966
  currentVersion: VERSION
24867
24967
  };
24868
24968
  memoryCache = { data, fetchedAt: Date.now() };
@@ -24970,14 +25070,18 @@ var serverPulseTools = [
24970
25070
  const sep = "\u2500".repeat(50);
24971
25071
  sections.push(`SERVER PULSE \u2014 ${SERVER_TITLE} v${VERSION}`, sep, "Package: @usex/mikrotik-mcp", `Version: ${VERSION}`, `Uptime: ${formatUptime(uptime)}`, `Website: ${WEBSITE_URL}`);
24972
25072
  if (result.release) {
24973
- const freshness = assessFreshness(VERSION, result.release.version);
24974
- const label = freshnessLabel(freshness);
24975
25073
  const age = timeAgo(result.release.publishedAt);
24976
- sections.push("", `UPDATE STATUS: ${label}`, sep, `Current: v${VERSION}`, `Latest: v${result.release.version} (${result.release.name})`, `Published: ${age}`, `Freshness: ${freshness.toUpperCase()}`);
24977
- if (result.release.isNewer) {
24978
- sections.push("", ">>> A newer version is available! <<<");
25074
+ if (result.release.isAhead) {
25075
+ 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.");
24979
25076
  } else {
24980
- sections.push("", "You are running the latest version.");
25077
+ const freshness = assessFreshness(VERSION, result.release.version);
25078
+ const label = freshnessLabel(freshness);
25079
+ sections.push("", `UPDATE STATUS: ${label}`, sep, `Current: v${VERSION}`, `Latest: v${result.release.version} (${result.release.name})`, `Published: ${age}`, `Freshness: ${freshness.toUpperCase()}`);
25080
+ if (result.release.isNewer) {
25081
+ sections.push("", ">>> A newer version is available! <<<");
25082
+ } else {
25083
+ sections.push("", "You are running the latest version.");
25084
+ }
24981
25085
  }
24982
25086
  if (result.release.isNewer) {
24983
25087
  sections.push("", "UPGRADE", sep);