@usex/mikrotik-mcp 3.60.0 → 4.1.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/README.md +1 -1
- package/dist/cli.js +27 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +21 -1
- package/dist/shared/{cli-ghatyv7e.js → cli-2dtt405d.js} +84 -44
- package/dist/shared/{cli-7yjsmkwg.js → cli-kq9jry12.js} +1 -1
- package/dist/shared/{library-1hs18tmh.js → library-0d02nsc7.js} +1 -1
- package/dist/shared/{library-7j22z9a8.js → library-pqnndx9v.js} +84 -44
- package/dist/ui/aaa.html +1 -1
- package/dist/ui/connected-devices.html +1 -1
- package/dist/ui/dashboard.html +1 -1
- package/dist/ui/firewall-audit.html +1 -1
- package/dist/ui/firewall.html +1 -1
- package/dist/ui/interfaces.html +1 -1
- package/dist/ui/observability.html +47 -47
- package/dist/ui/records.html +1 -1
- package/package.json +24 -21
- package/schemas/tool-catalog.json +2 -2
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ precedence over a password. Full configuration reference:
|
|
|
113
113
|
### From source
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
|
-
git clone https://github.com/
|
|
116
|
+
git clone https://github.com/mikrotik-mcp/mikrotik-mcp && cd mikrotik-mcp
|
|
117
117
|
bun install
|
|
118
118
|
bun run start # serve from source
|
|
119
119
|
bun run build # bundle to dist/
|
package/dist/cli.js
CHANGED
|
@@ -106,7 +106,7 @@ import {
|
|
|
106
106
|
updateAaaEntity,
|
|
107
107
|
updateSummaryLine,
|
|
108
108
|
writeBackup
|
|
109
|
-
} from "./shared/cli-
|
|
109
|
+
} from "./shared/cli-2dtt405d.js";
|
|
110
110
|
|
|
111
111
|
// src/cli.ts
|
|
112
112
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -1564,9 +1564,14 @@ function configPayload() {
|
|
|
1564
1564
|
devices: cfg.devices,
|
|
1565
1565
|
defaultDevice: cfg.defaultDevice,
|
|
1566
1566
|
mcp: cfg.mcp,
|
|
1567
|
+
s3: cfg.s3,
|
|
1567
1568
|
dashboard: cfg.dashboard,
|
|
1569
|
+
ssh: cfg.ssh,
|
|
1568
1570
|
readOnly: cfg.readOnly,
|
|
1569
|
-
|
|
1571
|
+
tools: cfg.tools,
|
|
1572
|
+
memory: cfg.memory,
|
|
1573
|
+
backupDir: cfg.backupDir,
|
|
1574
|
+
disableUpdateCheck: cfg.disableUpdateCheck
|
|
1570
1575
|
});
|
|
1571
1576
|
}
|
|
1572
1577
|
function sseResponse(transportLabel) {
|
|
@@ -2718,11 +2723,31 @@ accepts an optional "device" argument to choose which router it runs on; omit it
|
|
|
2718
2723
|
to use the default. Use list_mikrotik_devices to see them. For cross-device work
|
|
2719
2724
|
(e.g. a tunnel between two routers) configure each side by passing the matching
|
|
2720
2725
|
"device", then verify reachability with ping/traceroute from each end.`;
|
|
2726
|
+
var MEMORY_INSTRUCTIONS = `
|
|
2727
|
+
|
|
2728
|
+
Persistent memory \u2014 you have a knowledge graph that survives across sessions
|
|
2729
|
+
(entities, observations, relations in a local SQLite database). USE IT on every
|
|
2730
|
+
task so context is never lost between conversations:
|
|
2731
|
+
1. AT THE START of a task, recall what you already know before touching the
|
|
2732
|
+
device: call \`memory_search_nodes\` for the device/subject at hand (or
|
|
2733
|
+
\`memory_read_graph\` for the whole picture on a fresh device). Apply what you
|
|
2734
|
+
find \u2014 do not re-discover facts you already recorded.
|
|
2735
|
+
2. WHILE WORKING, when you learn a durable fact about the network, a device, a
|
|
2736
|
+
user, or a config pattern (RouterOS version, port layout, VLAN scheme, WAN
|
|
2737
|
+
uplink, owner, recurring fix), record it: \`memory_create_entities\` for new
|
|
2738
|
+
subjects, \`memory_add_observations\` for facts about existing ones, and
|
|
2739
|
+
\`memory_create_relations\` to link them (e.g. router --provides_dhcp_for-->
|
|
2740
|
+
subnet). Every device you touch is auto-added as an entity, so attach
|
|
2741
|
+
observations to it by name.
|
|
2742
|
+
3. Prefer specific, reusable facts over transient state. Skip one-off command
|
|
2743
|
+
output; record what will still be true next session.`;
|
|
2721
2744
|
function createServer(opts = {}) {
|
|
2722
2745
|
process.title = `Mikrotik MCP Server v${VERSION}`;
|
|
2723
2746
|
const { names, default: defaultDevice } = listDevices();
|
|
2724
2747
|
const readOnly = getConfig().readOnly;
|
|
2725
2748
|
let instructions = names.length > 1 ? INSTRUCTIONS + MULTI_DEVICE_INSTRUCTIONS.replace("{{names}}", names.join(", ")).replace("{{default}}", defaultDevice) : INSTRUCTIONS;
|
|
2749
|
+
if (getConfig().memory.enabled)
|
|
2750
|
+
instructions += MEMORY_INSTRUCTIONS;
|
|
2726
2751
|
if (!getConfig().disableUpdateCheck) {
|
|
2727
2752
|
const cached = loadFileCacheSync();
|
|
2728
2753
|
if (cached) {
|
package/dist/index.d.ts
CHANGED
|
@@ -308,10 +308,20 @@ declare class SafeModeManager {
|
|
|
308
308
|
rollback(): Promise<string>;
|
|
309
309
|
status(): string;
|
|
310
310
|
/**
|
|
311
|
-
* Read from the channel until `isDone(cleaned)` is satisfied or the
|
|
312
|
-
*
|
|
311
|
+
* Read from the channel until `isDone(cleaned)` is satisfied or the shell goes
|
|
312
|
+
* SILENT. Defaults to "any RouterOS prompt appeared". Mode transitions pass a
|
|
313
313
|
* stricter predicate so they wait for the prompt that reflects the NEW state
|
|
314
314
|
* (safe-mode marker present/absent), not merely the first prompt-shaped line.
|
|
315
|
+
*
|
|
316
|
+
* The timeout is an IDLE timeout, not a total budget: every data chunk resets
|
|
317
|
+
* it, so a legitimately slow-but-streaming command (`/terse` on a large
|
|
318
|
+
* config can emit for tens of seconds) never trips it — only a genuine wedge,
|
|
319
|
+
* where no byte has arrived for `idleMs`, does. A separate hard cap (`hardCapMs`)
|
|
320
|
+
* bounds the pathological "dribbles a byte forever" case so the read can't hang
|
|
321
|
+
* indefinitely. Either expiry resolves with `timedOut: true` so callers can tell
|
|
322
|
+
* "the prompt appeared" from "the shell stopped answering" — the latter means the
|
|
323
|
+
* interactive Safe-Mode session is wedged and must abort rather than letting every
|
|
324
|
+
* subsequent command burn its own timeout.
|
|
315
325
|
*/
|
|
316
326
|
private readUntilPrompt;
|
|
317
327
|
/**
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
selectToolModules,
|
|
30
30
|
setConfig,
|
|
31
31
|
updateSummaryLine
|
|
32
|
-
} from "./shared/library-
|
|
32
|
+
} from "./shared/library-pqnndx9v.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";
|
|
@@ -201,11 +201,31 @@ accepts an optional "device" argument to choose which router it runs on; omit it
|
|
|
201
201
|
to use the default. Use list_mikrotik_devices to see them. For cross-device work
|
|
202
202
|
(e.g. a tunnel between two routers) configure each side by passing the matching
|
|
203
203
|
"device", then verify reachability with ping/traceroute from each end.`;
|
|
204
|
+
var MEMORY_INSTRUCTIONS = `
|
|
205
|
+
|
|
206
|
+
Persistent memory \u2014 you have a knowledge graph that survives across sessions
|
|
207
|
+
(entities, observations, relations in a local SQLite database). USE IT on every
|
|
208
|
+
task so context is never lost between conversations:
|
|
209
|
+
1. AT THE START of a task, recall what you already know before touching the
|
|
210
|
+
device: call \`memory_search_nodes\` for the device/subject at hand (or
|
|
211
|
+
\`memory_read_graph\` for the whole picture on a fresh device). Apply what you
|
|
212
|
+
find \u2014 do not re-discover facts you already recorded.
|
|
213
|
+
2. WHILE WORKING, when you learn a durable fact about the network, a device, a
|
|
214
|
+
user, or a config pattern (RouterOS version, port layout, VLAN scheme, WAN
|
|
215
|
+
uplink, owner, recurring fix), record it: \`memory_create_entities\` for new
|
|
216
|
+
subjects, \`memory_add_observations\` for facts about existing ones, and
|
|
217
|
+
\`memory_create_relations\` to link them (e.g. router --provides_dhcp_for-->
|
|
218
|
+
subnet). Every device you touch is auto-added as an entity, so attach
|
|
219
|
+
observations to it by name.
|
|
220
|
+
3. Prefer specific, reusable facts over transient state. Skip one-off command
|
|
221
|
+
output; record what will still be true next session.`;
|
|
204
222
|
function createServer(opts = {}) {
|
|
205
223
|
process.title = `Mikrotik MCP Server v${VERSION}`;
|
|
206
224
|
const { names, default: defaultDevice } = listDevices();
|
|
207
225
|
const readOnly = getConfig().readOnly;
|
|
208
226
|
let instructions = names.length > 1 ? INSTRUCTIONS + MULTI_DEVICE_INSTRUCTIONS.replace("{{names}}", names.join(", ")).replace("{{default}}", defaultDevice) : INSTRUCTIONS;
|
|
227
|
+
if (getConfig().memory.enabled)
|
|
228
|
+
instructions += MEMORY_INSTRUCTIONS;
|
|
209
229
|
if (!getConfig().disableUpdateCheck) {
|
|
210
230
|
const cached = loadFileCacheSync();
|
|
211
231
|
if (cached) {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
526
|
-
};
|
|
551
|
+
appViews: appViewsEnv
|
|
552
|
+
});
|
|
527
553
|
const isTruthy = (v) => /^(1|true|yes|on)$/i.test(v ?? "");
|
|
528
|
-
const
|
|
529
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
1387
|
+
clearTimeout(idleTimer);
|
|
1388
|
+
clearTimeout(hardTimer);
|
|
1355
1389
|
channel.removeListener("data", onData);
|
|
1356
1390
|
resolve2({ text: result, timedOut });
|
|
1357
1391
|
}
|
|
1358
|
-
|
|
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 =
|
|
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 () => {
|
|
@@ -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-
|
|
8367
|
+
const { moduleCatalog } = await import("./cli-kq9jry12.js");
|
|
8334
8368
|
const forIndex = [];
|
|
8335
8369
|
const byName = new Map;
|
|
8336
8370
|
for (const mod of moduleCatalog) {
|
|
@@ -22181,7 +22215,7 @@ ${result}`;
|
|
|
22181
22215
|
name: "check_route_path",
|
|
22182
22216
|
title: "Check IPv4 Route Path",
|
|
22183
22217
|
annotations: READ,
|
|
22184
|
-
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
|
|
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.",
|
|
22185
22219
|
inputSchema: {
|
|
22186
22220
|
destination: z88.string(),
|
|
22187
22221
|
routing_table: z88.string().optional().describe('Policy-routing table name (v7) or routing-mark (v6), e.g. "VPN"')
|
|
@@ -22196,7 +22230,7 @@ ${result}`;
|
|
|
22196
22230
|
|
|
22197
22231
|
${v6Result}`;
|
|
22198
22232
|
}
|
|
22199
|
-
const where = [
|
|
22233
|
+
const where = [`${a.destination} in dst-address`, "active=yes"];
|
|
22200
22234
|
if (table && table !== "main")
|
|
22201
22235
|
where.push(`routing-table=${quoteValue(table)}`);
|
|
22202
22236
|
const v7Cmd = `/ip route print detail where ${where.join(" ")}`;
|
|
@@ -24933,7 +24967,7 @@ var SERVER_NAME = "mikrotik-mcp";
|
|
|
24933
24967
|
var PKG_META = pkg;
|
|
24934
24968
|
|
|
24935
24969
|
// src/core/update-check.ts
|
|
24936
|
-
var GITHUB_API = "https://api.github.com/repos/
|
|
24970
|
+
var GITHUB_API = "https://api.github.com/repos/mikrotik-mcp/mikrotik-mcp/releases/latest";
|
|
24937
24971
|
var MEMORY_CACHE_TTL = 15 * 60 * 1000;
|
|
24938
24972
|
var FILE_CACHE_TTL = 6 * 60 * 60 * 1000;
|
|
24939
24973
|
var CACHE_PATH = join6(homedir2(), ".mikrotik-mcp", "update-check.json");
|
|
@@ -24991,13 +25025,15 @@ async function fetchLatestRelease() {
|
|
|
24991
25025
|
throw new Error(`GitHub API ${res.status}`);
|
|
24992
25026
|
const gh = await res.json();
|
|
24993
25027
|
const latestVersion = gh.tag_name.replace(/^v/, "");
|
|
25028
|
+
const cmp = compareVersions(latestVersion, VERSION);
|
|
24994
25029
|
const data = {
|
|
24995
25030
|
version: latestVersion,
|
|
24996
25031
|
name: gh.name || `v${latestVersion}`,
|
|
24997
25032
|
body: gh.body || "",
|
|
24998
25033
|
publishedAt: gh.published_at,
|
|
24999
25034
|
url: gh.html_url,
|
|
25000
|
-
isNewer:
|
|
25035
|
+
isNewer: cmp > 0,
|
|
25036
|
+
isAhead: cmp < 0,
|
|
25001
25037
|
currentVersion: VERSION
|
|
25002
25038
|
};
|
|
25003
25039
|
memoryCache = { data, fetchedAt: Date.now() };
|
|
@@ -25105,14 +25141,18 @@ var serverPulseTools = [
|
|
|
25105
25141
|
const sep = "\u2500".repeat(50);
|
|
25106
25142
|
sections.push(`SERVER PULSE \u2014 ${SERVER_TITLE} v${VERSION}`, sep, "Package: @usex/mikrotik-mcp", `Version: ${VERSION}`, `Uptime: ${formatUptime(uptime)}`, `Website: ${WEBSITE_URL}`);
|
|
25107
25143
|
if (result.release) {
|
|
25108
|
-
const freshness = assessFreshness(VERSION, result.release.version);
|
|
25109
|
-
const label = freshnessLabel(freshness);
|
|
25110
25144
|
const age = timeAgo(result.release.publishedAt);
|
|
25111
|
-
|
|
25112
|
-
|
|
25113
|
-
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.");
|
|
25114
25147
|
} else {
|
|
25115
|
-
|
|
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
|
+
}
|
|
25116
25156
|
}
|
|
25117
25157
|
if (result.release.isNewer) {
|
|
25118
25158
|
sections.push("", "UPGRADE", sep);
|