@usex/mikrotik-mcp 4.18.0 → 4.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +13 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.js +1 -1
- package/dist/shared/{cli-7jxnfmp0.js → cli-cdxrs0vz.js} +1 -1
- package/dist/shared/{cli-dv2kmsh2.js → cli-fa29149s.js} +32 -2
- package/dist/shared/{library-77r1j24d.js → library-g5p25m9e.js} +1 -1
- package/dist/shared/{library-7m2c1g1y.js → library-ypdm9bkx.js} +28 -1
- package/dist/ui/observability.html +59 -59
- package/package.json +3 -3
- package/schemas/tool-catalog.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
captureSnapshot,
|
|
41
41
|
checkForUpdate,
|
|
42
42
|
closeAll,
|
|
43
|
+
closeDevice,
|
|
43
44
|
closeMemoryStore,
|
|
44
45
|
commandUnsupported,
|
|
45
46
|
configureRecorder,
|
|
@@ -123,7 +124,7 @@ import {
|
|
|
123
124
|
updateAaaEntity,
|
|
124
125
|
updateSummaryLine,
|
|
125
126
|
writeBackup
|
|
126
|
-
} from "./shared/cli-
|
|
127
|
+
} from "./shared/cli-fa29149s.js";
|
|
127
128
|
|
|
128
129
|
// src/cli.ts
|
|
129
130
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -2997,6 +2998,17 @@ async function runDashboard(cfg, transportLabel) {
|
|
|
2997
2998
|
...devicesPayload(db)
|
|
2998
2999
|
});
|
|
2999
3000
|
}
|
|
3001
|
+
if ((url.pathname === "/api/devices/test" || url.pathname === "/api/devices/reconnect") && req.method === "POST") {
|
|
3002
|
+
const b = await readJson(req);
|
|
3003
|
+
const name = typeof b?.device === "string" ? b.device : "";
|
|
3004
|
+
const cfg2 = getConfig();
|
|
3005
|
+
if (!(name in cfg2.devices))
|
|
3006
|
+
return json3({ error: `unknown device: ${name}` }, 404);
|
|
3007
|
+
if (url.pathname === "/api/devices/reconnect")
|
|
3008
|
+
closeDevice(name);
|
|
3009
|
+
const status = await probeDevice(name, cfg2.devices[name]);
|
|
3010
|
+
return json3({ ok: true, status, ...devicesPayload(db) });
|
|
3011
|
+
}
|
|
3000
3012
|
if (url.pathname === "/api/topology") {
|
|
3001
3013
|
return json3(topologyPayload());
|
|
3002
3014
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -280,6 +280,14 @@ declare class SafeModeManager {
|
|
|
280
280
|
private ssh;
|
|
281
281
|
private channel;
|
|
282
282
|
private active;
|
|
283
|
+
/**
|
|
284
|
+
* Set when the persistent shell drops WHILE Safe Mode was active — i.e. the
|
|
285
|
+
* session died before an explicit commit, so RouterOS has already auto-reverted
|
|
286
|
+
* every staged change. Distinguishes "never enabled / cleanly closed" from
|
|
287
|
+
* "died with your changes still pending" so commit/status can report the revert
|
|
288
|
+
* instead of a reassuring false success. Cleared on the next enable().
|
|
289
|
+
*/
|
|
290
|
+
private droppedUnexpectedly;
|
|
283
291
|
/** Serializes channel access so concurrent tool calls don't interleave I/O. */
|
|
284
292
|
private queue;
|
|
285
293
|
/** The device this Safe Mode session belongs to (a configured device name). */
|
|
@@ -308,6 +316,15 @@ declare class SafeModeManager {
|
|
|
308
316
|
rollback(): Promise<string>;
|
|
309
317
|
status(): string;
|
|
310
318
|
/**
|
|
319
|
+
* Fired by the persistent shell's `close`/`error` events. Arrow-bound so the
|
|
320
|
+
* same reference is used for add/removeListener. Acts ONLY when we still think
|
|
321
|
+
* Safe Mode is active — a drop then means the session died with staged changes
|
|
322
|
+
* pending, which RouterOS has auto-reverted, so we flag it and tear the zombie
|
|
323
|
+
* handles down. A close during our own cleanup() (which clears `active` first)
|
|
324
|
+
* is a no-op: it is an intentional teardown, not a lost session.
|
|
325
|
+
*/
|
|
326
|
+
private readonly handleUnexpectedDrop;
|
|
327
|
+
/**
|
|
311
328
|
* Read from the channel until `isDone(cleaned)` is satisfied or the shell goes
|
|
312
329
|
* SILENT. Defaults to "any RouterOS prompt appeared". Mode transitions pass a
|
|
313
330
|
* stricter predicate so they wait for the prompt that reflects the NEW state
|
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-ypdm9bkx.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";
|
|
@@ -1219,6 +1219,9 @@ function closeAll() {
|
|
|
1219
1219
|
removeEntry(name);
|
|
1220
1220
|
}
|
|
1221
1221
|
}
|
|
1222
|
+
function closeDevice(deviceName) {
|
|
1223
|
+
removeEntry(deviceName);
|
|
1224
|
+
}
|
|
1222
1225
|
function poolStatus() {
|
|
1223
1226
|
return Array.from(entries, ([name, e]) => ({
|
|
1224
1227
|
device: name,
|
|
@@ -1258,6 +1261,7 @@ class SafeModeManager {
|
|
|
1258
1261
|
ssh = null;
|
|
1259
1262
|
channel = null;
|
|
1260
1263
|
active = false;
|
|
1264
|
+
droppedUnexpectedly = false;
|
|
1261
1265
|
queue = Promise.resolve();
|
|
1262
1266
|
constructor(deviceName) {
|
|
1263
1267
|
this.deviceName = deviceName;
|
|
@@ -1278,6 +1282,7 @@ class SafeModeManager {
|
|
|
1278
1282
|
return this.lock(async () => {
|
|
1279
1283
|
if (this.active)
|
|
1280
1284
|
return "Safe mode is already active.";
|
|
1285
|
+
this.droppedUnexpectedly = false;
|
|
1281
1286
|
const dc = getDevice(this.deviceName);
|
|
1282
1287
|
if (dc.mac) {
|
|
1283
1288
|
return "Error: Safe Mode is not supported for a MAC-Telnet device " + `('${this.deviceName}' is reached by MAC ${dc.mac}). ` + "Connect over SSH (configure host/credentials) to use Safe Mode.";
|
|
@@ -1288,6 +1293,8 @@ class SafeModeManager {
|
|
|
1288
1293
|
}
|
|
1289
1294
|
this.ssh = ssh;
|
|
1290
1295
|
this.channel = await ssh.shell({ term: "dumb", cols: 220, rows: 50 });
|
|
1296
|
+
this.channel.on("close", this.handleUnexpectedDrop);
|
|
1297
|
+
this.channel.on("error", this.handleUnexpectedDrop);
|
|
1291
1298
|
const initial = (await this.readUntilPrompt(20000)).text;
|
|
1292
1299
|
if (!PROMPT_RE.test(initial)) {
|
|
1293
1300
|
this.cleanup();
|
|
@@ -1305,6 +1312,9 @@ class SafeModeManager {
|
|
|
1305
1312
|
}
|
|
1306
1313
|
execute(command) {
|
|
1307
1314
|
return this.lock(async () => {
|
|
1315
|
+
if (this.droppedUnexpectedly) {
|
|
1316
|
+
throw new Error("Safe Mode session dropped \u2014 RouterOS auto-reverted every staged change; nothing " + "was saved. Re-enable Safe Mode and re-apply, or apply the change directly.");
|
|
1317
|
+
}
|
|
1308
1318
|
if (!this.active || !this.channel) {
|
|
1309
1319
|
throw new Error("Safe mode session is not active.");
|
|
1310
1320
|
}
|
|
@@ -1319,6 +1329,13 @@ class SafeModeManager {
|
|
|
1319
1329
|
}
|
|
1320
1330
|
commit() {
|
|
1321
1331
|
return this.lock(async () => {
|
|
1332
|
+
if (this.droppedUnexpectedly) {
|
|
1333
|
+
this.droppedUnexpectedly = false;
|
|
1334
|
+
return {
|
|
1335
|
+
ok: false,
|
|
1336
|
+
message: "Commit FAILED \u2014 the Safe Mode session dropped before this commit, so RouterOS " + "automatically reverted ALL staged changes and NOTHING was saved. Re-enable Safe Mode " + "and re-apply the changes (or apply them directly and verify each with a read)."
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1322
1339
|
if (!this.active || !this.channel) {
|
|
1323
1340
|
return { ok: true, message: "Safe mode is not active. Nothing to commit." };
|
|
1324
1341
|
}
|
|
@@ -1357,6 +1374,10 @@ class SafeModeManager {
|
|
|
1357
1374
|
}
|
|
1358
1375
|
rollback() {
|
|
1359
1376
|
return this.lock(async () => {
|
|
1377
|
+
if (this.droppedUnexpectedly) {
|
|
1378
|
+
this.droppedUnexpectedly = false;
|
|
1379
|
+
return "Safe Mode session had already dropped \u2014 RouterOS auto-reverted all staged changes; " + "nothing was left to roll back.";
|
|
1380
|
+
}
|
|
1360
1381
|
if (!this.active)
|
|
1361
1382
|
return "Safe mode is not active. Nothing to roll back.";
|
|
1362
1383
|
this.cleanup();
|
|
@@ -1364,8 +1385,17 @@ class SafeModeManager {
|
|
|
1364
1385
|
});
|
|
1365
1386
|
}
|
|
1366
1387
|
status() {
|
|
1388
|
+
if (this.droppedUnexpectedly) {
|
|
1389
|
+
return "Safe mode session DROPPED unexpectedly \u2014 RouterOS auto-reverted all staged changes; they " + "were NOT saved. Re-enable Safe Mode and re-apply, or apply changes directly (verify each with a read).";
|
|
1390
|
+
}
|
|
1367
1391
|
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.";
|
|
1368
1392
|
}
|
|
1393
|
+
handleUnexpectedDrop = () => {
|
|
1394
|
+
if (!this.active)
|
|
1395
|
+
return;
|
|
1396
|
+
this.droppedUnexpectedly = true;
|
|
1397
|
+
this.cleanup();
|
|
1398
|
+
};
|
|
1369
1399
|
readUntilPrompt(idleMs = IDLE_TIMEOUT_MS, isDone = (c) => PROMPT_RE.test(c), hardCapMs = HARD_CAP_MS) {
|
|
1370
1400
|
const channel = this.channel;
|
|
1371
1401
|
if (!channel)
|
|
@@ -9029,7 +9059,7 @@ var cache = null;
|
|
|
9029
9059
|
async function gateway() {
|
|
9030
9060
|
if (cache)
|
|
9031
9061
|
return cache;
|
|
9032
|
-
const { moduleCatalog } = await import("./cli-
|
|
9062
|
+
const { moduleCatalog } = await import("./cli-cdxrs0vz.js");
|
|
9033
9063
|
const forIndex = [];
|
|
9034
9064
|
const byName = new Map;
|
|
9035
9065
|
for (const mod of moduleCatalog) {
|
|
@@ -33305,4 +33335,4 @@ function selectToolModules(filter = {}, catalog = moduleCatalog) {
|
|
|
33305
33335
|
}).map((m) => m.tools);
|
|
33306
33336
|
}
|
|
33307
33337
|
|
|
33308
|
-
export { __require, DEFAULT_SNAPSHOT_DB, DEFAULT_CONFIG_HISTORY_DIR, DeviceConfigSchema, ToolFilterSchema, MikrotikConfigSchema, getConfigSource, loadConfig, logger, setConfig, getConfig, listDevices, deviceLabels, resolveDeviceName, deviceDirectory, isMacTelnetDevice, createDeviceClient, describeTransport, isPoolEnabled, closeAll, poolStatus, getMemoryStore, closeMemoryStore, reopenMemoryStore, executeMikrotikCommand, createContext, Cmd, isEmpty, looksLikeError, commandUnsupported, parseKeyValues, parseRouterosDate, parseSize, parseSystemResource, parseRecords, parseLeadingNumber, parseDisks, riskOf, REDACTED, redact, configureRecorder, getEventStore, subscribe, subscriberCount, registerTools, fetchDevices, sampleDeviceTraffic, sampleAllTraffic, setDeviceLimits, blockDevice, allowDevice, makeDeviceStatic, setDeviceIp, setDeviceLabel, removeDeviceLease, devicesView, PROMPTS_DIR, UI_DIST_DIR, registerUiResources, backupDir, listBackups, readBackup, writeBackup, deleteBackup, renameBackup, createLocalBackup, restoreLocalBackup, isS3Configured, getS3Client, presignExpiresIn, s3Target, splitCommands, buildChangePlan, renderPlan, diffLines, normalizeExport, analyzeDrift, attributeChanges, openSnapshotStore, applyWritesSafely, captureSnapshot, DEFAULT_TZSP_PORT, capture2 as capture, AAA_ENTITIES, listAaaEntity, addAaaEntity, updateAaaEntity, removeAaaEntity, toggleAaaEntity, getRadiusIncoming, setRadiusIncoming, resetRadiusCounters, getUmSettings, setUmSettings, VERSION, WEBSITE_URL, LOGO_URL, SERVER_TITLE, SERVER_DESCRIPTION, SERVER_NAME, PKG_META, loadFileCacheSync, fetchLatestRelease, checkForUpdate, updateSummaryLine, fetchAllReleases, buildChannelPlanCommands, reportWeakClients, runCapsmanAudit, steerAlreadyPresent, buildSteerCommands, loadBalancePlan, buildLoadBalanceCommands, buildFtCommands, buildHaCommands, haGuidance, capsmanOverview, fetchCapsmanState, moduleCatalog, allToolModules, ALWAYS_ON_MODULES, selectToolModules };
|
|
33338
|
+
export { __require, DEFAULT_SNAPSHOT_DB, DEFAULT_CONFIG_HISTORY_DIR, DeviceConfigSchema, ToolFilterSchema, MikrotikConfigSchema, getConfigSource, loadConfig, logger, setConfig, getConfig, listDevices, deviceLabels, resolveDeviceName, deviceDirectory, isMacTelnetDevice, createDeviceClient, describeTransport, isPoolEnabled, closeAll, closeDevice, poolStatus, getMemoryStore, closeMemoryStore, reopenMemoryStore, executeMikrotikCommand, createContext, Cmd, isEmpty, looksLikeError, commandUnsupported, parseKeyValues, parseRouterosDate, parseSize, parseSystemResource, parseRecords, parseLeadingNumber, parseDisks, riskOf, REDACTED, redact, configureRecorder, getEventStore, subscribe, subscriberCount, registerTools, fetchDevices, sampleDeviceTraffic, sampleAllTraffic, setDeviceLimits, blockDevice, allowDevice, makeDeviceStatic, setDeviceIp, setDeviceLabel, removeDeviceLease, devicesView, PROMPTS_DIR, UI_DIST_DIR, registerUiResources, backupDir, listBackups, readBackup, writeBackup, deleteBackup, renameBackup, createLocalBackup, restoreLocalBackup, isS3Configured, getS3Client, presignExpiresIn, s3Target, splitCommands, buildChangePlan, renderPlan, diffLines, normalizeExport, analyzeDrift, attributeChanges, openSnapshotStore, applyWritesSafely, captureSnapshot, DEFAULT_TZSP_PORT, capture2 as capture, AAA_ENTITIES, listAaaEntity, addAaaEntity, updateAaaEntity, removeAaaEntity, toggleAaaEntity, getRadiusIncoming, setRadiusIncoming, resetRadiusCounters, getUmSettings, setUmSettings, VERSION, WEBSITE_URL, LOGO_URL, SERVER_TITLE, SERVER_DESCRIPTION, SERVER_NAME, PKG_META, loadFileCacheSync, fetchLatestRelease, checkForUpdate, updateSummaryLine, fetchAllReleases, buildChannelPlanCommands, reportWeakClients, runCapsmanAudit, steerAlreadyPresent, buildSteerCommands, loadBalancePlan, buildLoadBalanceCommands, buildFtCommands, buildHaCommands, haGuidance, capsmanOverview, fetchCapsmanState, moduleCatalog, allToolModules, ALWAYS_ON_MODULES, selectToolModules };
|
|
@@ -1239,6 +1239,7 @@ class SafeModeManager {
|
|
|
1239
1239
|
ssh = null;
|
|
1240
1240
|
channel = null;
|
|
1241
1241
|
active = false;
|
|
1242
|
+
droppedUnexpectedly = false;
|
|
1242
1243
|
queue = Promise.resolve();
|
|
1243
1244
|
constructor(deviceName) {
|
|
1244
1245
|
this.deviceName = deviceName;
|
|
@@ -1259,6 +1260,7 @@ class SafeModeManager {
|
|
|
1259
1260
|
return this.lock(async () => {
|
|
1260
1261
|
if (this.active)
|
|
1261
1262
|
return "Safe mode is already active.";
|
|
1263
|
+
this.droppedUnexpectedly = false;
|
|
1262
1264
|
const dc = getDevice(this.deviceName);
|
|
1263
1265
|
if (dc.mac) {
|
|
1264
1266
|
return "Error: Safe Mode is not supported for a MAC-Telnet device " + `('${this.deviceName}' is reached by MAC ${dc.mac}). ` + "Connect over SSH (configure host/credentials) to use Safe Mode.";
|
|
@@ -1269,6 +1271,8 @@ class SafeModeManager {
|
|
|
1269
1271
|
}
|
|
1270
1272
|
this.ssh = ssh;
|
|
1271
1273
|
this.channel = await ssh.shell({ term: "dumb", cols: 220, rows: 50 });
|
|
1274
|
+
this.channel.on("close", this.handleUnexpectedDrop);
|
|
1275
|
+
this.channel.on("error", this.handleUnexpectedDrop);
|
|
1272
1276
|
const initial = (await this.readUntilPrompt(20000)).text;
|
|
1273
1277
|
if (!PROMPT_RE.test(initial)) {
|
|
1274
1278
|
this.cleanup();
|
|
@@ -1286,6 +1290,9 @@ class SafeModeManager {
|
|
|
1286
1290
|
}
|
|
1287
1291
|
execute(command) {
|
|
1288
1292
|
return this.lock(async () => {
|
|
1293
|
+
if (this.droppedUnexpectedly) {
|
|
1294
|
+
throw new Error("Safe Mode session dropped \u2014 RouterOS auto-reverted every staged change; nothing " + "was saved. Re-enable Safe Mode and re-apply, or apply the change directly.");
|
|
1295
|
+
}
|
|
1289
1296
|
if (!this.active || !this.channel) {
|
|
1290
1297
|
throw new Error("Safe mode session is not active.");
|
|
1291
1298
|
}
|
|
@@ -1300,6 +1307,13 @@ class SafeModeManager {
|
|
|
1300
1307
|
}
|
|
1301
1308
|
commit() {
|
|
1302
1309
|
return this.lock(async () => {
|
|
1310
|
+
if (this.droppedUnexpectedly) {
|
|
1311
|
+
this.droppedUnexpectedly = false;
|
|
1312
|
+
return {
|
|
1313
|
+
ok: false,
|
|
1314
|
+
message: "Commit FAILED \u2014 the Safe Mode session dropped before this commit, so RouterOS " + "automatically reverted ALL staged changes and NOTHING was saved. Re-enable Safe Mode " + "and re-apply the changes (or apply them directly and verify each with a read)."
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1303
1317
|
if (!this.active || !this.channel) {
|
|
1304
1318
|
return { ok: true, message: "Safe mode is not active. Nothing to commit." };
|
|
1305
1319
|
}
|
|
@@ -1338,6 +1352,10 @@ class SafeModeManager {
|
|
|
1338
1352
|
}
|
|
1339
1353
|
rollback() {
|
|
1340
1354
|
return this.lock(async () => {
|
|
1355
|
+
if (this.droppedUnexpectedly) {
|
|
1356
|
+
this.droppedUnexpectedly = false;
|
|
1357
|
+
return "Safe Mode session had already dropped \u2014 RouterOS auto-reverted all staged changes; " + "nothing was left to roll back.";
|
|
1358
|
+
}
|
|
1341
1359
|
if (!this.active)
|
|
1342
1360
|
return "Safe mode is not active. Nothing to roll back.";
|
|
1343
1361
|
this.cleanup();
|
|
@@ -1345,8 +1363,17 @@ class SafeModeManager {
|
|
|
1345
1363
|
});
|
|
1346
1364
|
}
|
|
1347
1365
|
status() {
|
|
1366
|
+
if (this.droppedUnexpectedly) {
|
|
1367
|
+
return "Safe mode session DROPPED unexpectedly \u2014 RouterOS auto-reverted all staged changes; they " + "were NOT saved. Re-enable Safe Mode and re-apply, or apply changes directly (verify each with a read).";
|
|
1368
|
+
}
|
|
1348
1369
|
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.";
|
|
1349
1370
|
}
|
|
1371
|
+
handleUnexpectedDrop = () => {
|
|
1372
|
+
if (!this.active)
|
|
1373
|
+
return;
|
|
1374
|
+
this.droppedUnexpectedly = true;
|
|
1375
|
+
this.cleanup();
|
|
1376
|
+
};
|
|
1350
1377
|
readUntilPrompt(idleMs = IDLE_TIMEOUT_MS, isDone = (c) => PROMPT_RE.test(c), hardCapMs = HARD_CAP_MS) {
|
|
1351
1378
|
const channel = this.channel;
|
|
1352
1379
|
if (!channel)
|
|
@@ -8811,7 +8838,7 @@ var cache = null;
|
|
|
8811
8838
|
async function gateway() {
|
|
8812
8839
|
if (cache)
|
|
8813
8840
|
return cache;
|
|
8814
|
-
const { moduleCatalog } = await import("./library-
|
|
8841
|
+
const { moduleCatalog } = await import("./library-g5p25m9e.js");
|
|
8815
8842
|
const forIndex = [];
|
|
8816
8843
|
const byName = new Map;
|
|
8817
8844
|
for (const mod of moduleCatalog) {
|