@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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
523
|
-
};
|
|
548
|
+
appViews: appViewsEnv
|
|
549
|
+
});
|
|
524
550
|
const isTruthy = (v) => /^(1|true|yes|on)$/i.test(v ?? "");
|
|
525
|
-
const
|
|
526
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
1368
|
+
clearTimeout(idleTimer);
|
|
1369
|
+
clearTimeout(hardTimer);
|
|
1336
1370
|
channel.removeListener("data", onData);
|
|
1337
1371
|
resolve2({ text: result, timedOut });
|
|
1338
1372
|
}
|
|
1339
|
-
|
|
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 =
|
|
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 () => {
|
|
@@ -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-
|
|
8297
|
+
const { moduleCatalog } = await import("./library-0d02nsc7.js");
|
|
8264
8298
|
const forIndex = [];
|
|
8265
8299
|
const byName = new Map;
|
|
8266
8300
|
for (const mod of moduleCatalog) {
|
|
@@ -22111,7 +22145,7 @@ ${result}`;
|
|
|
22111
22145
|
name: "check_route_path",
|
|
22112
22146
|
title: "Check IPv4 Route Path",
|
|
22113
22147
|
annotations: READ,
|
|
22114
|
-
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
|
|
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.",
|
|
22115
22149
|
inputSchema: {
|
|
22116
22150
|
destination: z88.string(),
|
|
22117
22151
|
routing_table: z88.string().optional().describe('Policy-routing table name (v7) or routing-mark (v6), e.g. "VPN"')
|
|
@@ -22126,7 +22160,7 @@ ${result}`;
|
|
|
22126
22160
|
|
|
22127
22161
|
${v6Result}`;
|
|
22128
22162
|
}
|
|
22129
|
-
const where = [
|
|
22163
|
+
const where = [`${a.destination} in dst-address`, "active=yes"];
|
|
22130
22164
|
if (table && table !== "main")
|
|
22131
22165
|
where.push(`routing-table=${quoteValue(table)}`);
|
|
22132
22166
|
const v7Cmd = `/ip route print detail where ${where.join(" ")}`;
|
|
@@ -24862,7 +24896,7 @@ var SERVER_DESCRIPTION = pkg.description ?? "";
|
|
|
24862
24896
|
var SERVER_NAME = "mikrotik-mcp";
|
|
24863
24897
|
|
|
24864
24898
|
// src/core/update-check.ts
|
|
24865
|
-
var GITHUB_API = "https://api.github.com/repos/
|
|
24899
|
+
var GITHUB_API = "https://api.github.com/repos/mikrotik-mcp/mikrotik-mcp/releases/latest";
|
|
24866
24900
|
var MEMORY_CACHE_TTL = 15 * 60 * 1000;
|
|
24867
24901
|
var FILE_CACHE_TTL = 6 * 60 * 60 * 1000;
|
|
24868
24902
|
var CACHE_PATH = join6(homedir2(), ".mikrotik-mcp", "update-check.json");
|
|
@@ -24920,13 +24954,15 @@ async function fetchLatestRelease() {
|
|
|
24920
24954
|
throw new Error(`GitHub API ${res.status}`);
|
|
24921
24955
|
const gh = await res.json();
|
|
24922
24956
|
const latestVersion = gh.tag_name.replace(/^v/, "");
|
|
24957
|
+
const cmp = compareVersions(latestVersion, VERSION);
|
|
24923
24958
|
const data = {
|
|
24924
24959
|
version: latestVersion,
|
|
24925
24960
|
name: gh.name || `v${latestVersion}`,
|
|
24926
24961
|
body: gh.body || "",
|
|
24927
24962
|
publishedAt: gh.published_at,
|
|
24928
24963
|
url: gh.html_url,
|
|
24929
|
-
isNewer:
|
|
24964
|
+
isNewer: cmp > 0,
|
|
24965
|
+
isAhead: cmp < 0,
|
|
24930
24966
|
currentVersion: VERSION
|
|
24931
24967
|
};
|
|
24932
24968
|
memoryCache = { data, fetchedAt: Date.now() };
|
|
@@ -25034,14 +25070,18 @@ var serverPulseTools = [
|
|
|
25034
25070
|
const sep = "\u2500".repeat(50);
|
|
25035
25071
|
sections.push(`SERVER PULSE \u2014 ${SERVER_TITLE} v${VERSION}`, sep, "Package: @usex/mikrotik-mcp", `Version: ${VERSION}`, `Uptime: ${formatUptime(uptime)}`, `Website: ${WEBSITE_URL}`);
|
|
25036
25072
|
if (result.release) {
|
|
25037
|
-
const freshness = assessFreshness(VERSION, result.release.version);
|
|
25038
|
-
const label = freshnessLabel(freshness);
|
|
25039
25073
|
const age = timeAgo(result.release.publishedAt);
|
|
25040
|
-
|
|
25041
|
-
|
|
25042
|
-
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.");
|
|
25043
25076
|
} else {
|
|
25044
|
-
|
|
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
|
+
}
|
|
25045
25085
|
}
|
|
25046
25086
|
if (result.release.isNewer) {
|
|
25047
25087
|
sections.push("", "UPGRADE", sep);
|