@usex/mikrotik-mcp 5.5.0 → 5.6.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 +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.js +1 -1
- package/dist/shared/{cli-cvq8pxvn.js → cli-gyqfq9j6.js} +268 -76
- package/dist/shared/{cli-qqwjbjnv.js → cli-h41j14aw.js} +1 -1
- package/dist/shared/{library-5p5mk963.js → library-135yzct0.js} +268 -76
- package/dist/shared/{library-241wsfpw.js → library-v52e871r.js} +1 -1
- package/package.json +1 -1
- package/schemas/tool-catalog.json +17 -5
- package/schemas/tools/add_ospf_interface_template.json +9 -1
- package/schemas/tools/update_wireguard_peer.json +4 -0
|
@@ -42,6 +42,7 @@ var logger = {
|
|
|
42
42
|
|
|
43
43
|
// src/ssh/client.ts
|
|
44
44
|
var RUN_IDLE_TIMEOUT_MS = 60000;
|
|
45
|
+
var RUN_HARD_TIMEOUT_MS = 120000;
|
|
45
46
|
function decodeOutput(data) {
|
|
46
47
|
if (!data || data.length === 0)
|
|
47
48
|
return "";
|
|
@@ -158,11 +159,14 @@ class MikroTikSSHClient {
|
|
|
158
159
|
let settled = false;
|
|
159
160
|
let timer;
|
|
160
161
|
let idleTimer;
|
|
162
|
+
let hardTimer;
|
|
161
163
|
const clearTimers = () => {
|
|
162
164
|
if (timer)
|
|
163
165
|
clearTimeout(timer);
|
|
164
166
|
if (idleTimer)
|
|
165
167
|
clearTimeout(idleTimer);
|
|
168
|
+
if (hardTimer)
|
|
169
|
+
clearTimeout(hardTimer);
|
|
166
170
|
};
|
|
167
171
|
const finish = () => {
|
|
168
172
|
if (settled)
|
|
@@ -204,6 +208,14 @@ class MikroTikSSHClient {
|
|
|
204
208
|
} catch {}
|
|
205
209
|
finish();
|
|
206
210
|
}, opts.maxMs);
|
|
211
|
+
} else {
|
|
212
|
+
hardTimer = setTimeout(() => {
|
|
213
|
+
try {
|
|
214
|
+
stream.signal("INT");
|
|
215
|
+
} catch {}
|
|
216
|
+
const got = Buffer.concat(stdout).length;
|
|
217
|
+
fail(new Error(`MikroTik command exceeded the ${RUN_HARD_TIMEOUT_MS / 1000}s hard timeout and was ` + `aborted (${got} bytes received, output discarded as incomplete). ` + `Command: ${command.slice(0, 120)}`));
|
|
218
|
+
}, RUN_HARD_TIMEOUT_MS);
|
|
207
219
|
}
|
|
208
220
|
stream.on("close", finish).on("data", (d) => {
|
|
209
221
|
stdout.push(d);
|
|
@@ -766,20 +778,36 @@ function deviceLabels() {
|
|
|
766
778
|
}
|
|
767
779
|
return out;
|
|
768
780
|
}
|
|
769
|
-
function
|
|
770
|
-
if (name) {
|
|
771
|
-
if (name in active.devices && isEnabled(active.devices[name]))
|
|
772
|
-
return name;
|
|
773
|
-
const byLabel = deviceKeyForLabel(name);
|
|
774
|
-
if (byLabel)
|
|
775
|
-
return byLabel;
|
|
776
|
-
}
|
|
781
|
+
function defaultDeviceKey() {
|
|
777
782
|
if (active.defaultDevice in active.devices && isEnabled(active.devices[active.defaultDevice])) {
|
|
778
783
|
return active.defaultDevice;
|
|
779
784
|
}
|
|
780
785
|
const firstEnabled = Object.entries(active.devices).find(([, dc]) => isEnabled(dc));
|
|
781
786
|
return firstEnabled ? firstEnabled[0] : active.defaultDevice;
|
|
782
787
|
}
|
|
788
|
+
function tryResolveDeviceName(name) {
|
|
789
|
+
if (!name)
|
|
790
|
+
return;
|
|
791
|
+
if (name in active.devices && isEnabled(active.devices[name]))
|
|
792
|
+
return name;
|
|
793
|
+
return deviceKeyForLabel(name);
|
|
794
|
+
}
|
|
795
|
+
function resolveDeviceName(name) {
|
|
796
|
+
if (!name)
|
|
797
|
+
return defaultDeviceKey();
|
|
798
|
+
const resolved = tryResolveDeviceName(name);
|
|
799
|
+
if (resolved)
|
|
800
|
+
return resolved;
|
|
801
|
+
throw new Error(unknownDeviceMessage(name));
|
|
802
|
+
}
|
|
803
|
+
function unknownDeviceMessage(name) {
|
|
804
|
+
const enabled = listDevices().names;
|
|
805
|
+
const disabled = name in active.devices && !isEnabled(active.devices[name]);
|
|
806
|
+
if (disabled) {
|
|
807
|
+
return `Device '${name}' is disabled. Enable it from the dashboard or config file. Enabled devices: ${enabled.join(", ")}`;
|
|
808
|
+
}
|
|
809
|
+
return `Unknown device '${name}'. Enabled devices: ${enabled.join(", ") || "(none)"}. ` + "Call list_mikrotik_devices for the authoritative current set \u2014 this was NOT run against " + "the default device.";
|
|
810
|
+
}
|
|
783
811
|
function deviceTarget(dc) {
|
|
784
812
|
if (!dc)
|
|
785
813
|
return "?";
|
|
@@ -799,9 +827,6 @@ function resolvedTarget(name) {
|
|
|
799
827
|
return { key, label: dc?.description?.trim() || undefined, target: deviceTarget(dc) };
|
|
800
828
|
}
|
|
801
829
|
function getDevice(name) {
|
|
802
|
-
if (name && !(name in active.devices) && !deviceKeyForLabel(name)) {
|
|
803
|
-
throw new Error(`Unknown device '${name}'. Configured devices: ${Object.keys(active.devices).join(", ")}`);
|
|
804
|
-
}
|
|
805
830
|
const key = resolveDeviceName(name);
|
|
806
831
|
const dc = active.devices[key];
|
|
807
832
|
if (!dc)
|
|
@@ -2187,6 +2212,27 @@ function parseDetailRecords(lines) {
|
|
|
2187
2212
|
flush();
|
|
2188
2213
|
return rows;
|
|
2189
2214
|
}
|
|
2215
|
+
function filterDetailBlocks(text, predicate) {
|
|
2216
|
+
const lines = text.split(/\r?\n/);
|
|
2217
|
+
const firstRecord = lines.findIndex((l) => INDEX_LINE.test(l));
|
|
2218
|
+
if (firstRecord === -1)
|
|
2219
|
+
return text;
|
|
2220
|
+
const preamble = lines.slice(0, firstRecord);
|
|
2221
|
+
const blocks = [];
|
|
2222
|
+
for (const line of lines.slice(firstRecord)) {
|
|
2223
|
+
if (INDEX_LINE.test(line) || blocks.length === 0)
|
|
2224
|
+
blocks.push([]);
|
|
2225
|
+
blocks[blocks.length - 1].push(line);
|
|
2226
|
+
}
|
|
2227
|
+
const kept = blocks.filter((block) => {
|
|
2228
|
+
const [row] = parseDetailRecords(block);
|
|
2229
|
+
return row ? predicate(row) : false;
|
|
2230
|
+
});
|
|
2231
|
+
if (kept.length === 0)
|
|
2232
|
+
return "";
|
|
2233
|
+
return [...preamble, ...kept.flat()].join(`
|
|
2234
|
+
`);
|
|
2235
|
+
}
|
|
2190
2236
|
function parseColumnarRecords(lines) {
|
|
2191
2237
|
const headerIdx = lines.findIndex((l) => /\S/.test(l) && !l.includes("=") && /^[\s#]*[#A-Z]/.test(l) && /[A-Z]/.test(l));
|
|
2192
2238
|
if (headerIdx === -1)
|
|
@@ -2557,8 +2603,20 @@ function portConflictError(result, port) {
|
|
|
2557
2603
|
if (!/configured elsewhere/i.test(result))
|
|
2558
2604
|
return;
|
|
2559
2605
|
const other = result.match(/\*\d+\s*=\s*([A-Za-z][\w-]*)/)?.[1];
|
|
2560
|
-
|
|
2561
|
-
|
|
2606
|
+
if (port == null) {
|
|
2607
|
+
return `the device rejected this change with 'configured elsewhere' \u2014 the command targeted more ` + `than one row, or the value is owned by another item${other ? ` (RouterOS named '${other}')` : ""}. No port was being changed, so this is not a port collision.`;
|
|
2608
|
+
}
|
|
2609
|
+
return other ? `port ${port} is already used by the '${other}' service \u2014 two services can't share a port. Pick a different port, or change/disable '${other}' first (disable_ip_service / set_ip_service).` : `port ${port} is already in use elsewhere \u2014 two items can't share it. Pick a different port, or free it on the conflicting item first.`;
|
|
2610
|
+
}
|
|
2611
|
+
function interfaceValueError(result, fields) {
|
|
2612
|
+
if (!/input does not match any value of interface/i.test(result))
|
|
2613
|
+
return;
|
|
2614
|
+
const sent = fields.filter((f) => f.value !== undefined && f.value !== "");
|
|
2615
|
+
if (sent.length === 0)
|
|
2616
|
+
return;
|
|
2617
|
+
const which = sent.map((f) => `${f.name}='${f.value ?? ""}'`).join(", ");
|
|
2618
|
+
const listHint = sent.map((f) => `${f.name}_list`).join(" / ");
|
|
2619
|
+
return `the device rejected an interface value (${which}) \u2014 it is not the name of an interface ` + `on this router. If you meant an interface LIST (e.g. WAN, LAN), use ${listHint} instead; ` + "list_interfaces shows the valid interface names.";
|
|
2562
2620
|
}
|
|
2563
2621
|
function commandUnsupported(result) {
|
|
2564
2622
|
const t = result.toLowerCase();
|
|
@@ -4317,6 +4375,18 @@ function gatingDecision(requires, multiDevice) {
|
|
|
4317
4375
|
}
|
|
4318
4376
|
return { prefix: `[unavailable on this device: ${unmet}]` };
|
|
4319
4377
|
}
|
|
4378
|
+
var HTML_ENTITIES = /&(amp|lt|gt|quot|#0?39|apos);/i;
|
|
4379
|
+
function htmlEscapedArgument(args) {
|
|
4380
|
+
for (const [key, value] of Object.entries(args)) {
|
|
4381
|
+
if (typeof value !== "string")
|
|
4382
|
+
continue;
|
|
4383
|
+
const hit = value.match(HTML_ENTITIES);
|
|
4384
|
+
if (!hit)
|
|
4385
|
+
continue;
|
|
4386
|
+
return `'${key}' contains HTML-escaped text (${hit[0]}). RouterOS stores it literally, so the ` + `value would be saved with "${hit[0]}" in it and every later exact lookup ` + `(e.g. [find comment="\u2026"]) would fail to match. Send the character itself ` + `(&, <, >, ", ') \u2014 quoting and escaping for the console is handled automatically.`;
|
|
4387
|
+
}
|
|
4388
|
+
return;
|
|
4389
|
+
}
|
|
4320
4390
|
function defineTool(def) {
|
|
4321
4391
|
return {
|
|
4322
4392
|
name: def.name,
|
|
@@ -4350,6 +4420,21 @@ function defineTool(def) {
|
|
|
4350
4420
|
const callback = async (args) => {
|
|
4351
4421
|
const { device, reason, ...rest } = args;
|
|
4352
4422
|
const deviceName = typeof device === "string" ? device : undefined;
|
|
4423
|
+
if (!def.noDevice && deviceName !== undefined && !tryResolveDeviceName(deviceName)) {
|
|
4424
|
+
const text = `Error: ${unknownDeviceMessage(deviceName)}`;
|
|
4425
|
+
logger.error(text);
|
|
4426
|
+
sendLog?.("error", text);
|
|
4427
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
4428
|
+
}
|
|
4429
|
+
if (risk !== "READ") {
|
|
4430
|
+
const escaped = htmlEscapedArgument(rest);
|
|
4431
|
+
if (escaped) {
|
|
4432
|
+
const text = `Error: ${escaped}`;
|
|
4433
|
+
logger.error(text);
|
|
4434
|
+
sendLog?.("error", text);
|
|
4435
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4353
4438
|
const policy = getAccessPolicy();
|
|
4354
4439
|
if (policy.enabled) {
|
|
4355
4440
|
const resolvedDevice = def.noDevice ? undefined : resolveDeviceName(deviceName);
|
|
@@ -8209,7 +8294,12 @@ var complianceAuditTools = [
|
|
|
8209
8294
|
results.push({ device: resolved, report, text });
|
|
8210
8295
|
for (const e of report.evaluatedChecks) {
|
|
8211
8296
|
if (e.result.status === "fail" || e.result.status === "warn") {
|
|
8212
|
-
|
|
8297
|
+
const prev = failTally.get(e.check.id);
|
|
8298
|
+
failTally.set(e.check.id, {
|
|
8299
|
+
count: (prev?.count ?? 0) + 1,
|
|
8300
|
+
label: e.result.label,
|
|
8301
|
+
status: prev?.status === "fail" ? "fail" : e.result.status
|
|
8302
|
+
});
|
|
8213
8303
|
}
|
|
8214
8304
|
}
|
|
8215
8305
|
} catch (err) {
|
|
@@ -8255,11 +8345,12 @@ Error: ${err instanceof Error ? err.message : String(err)}`
|
|
|
8255
8345
|
lines.push("");
|
|
8256
8346
|
}
|
|
8257
8347
|
if (failTally.size > 0) {
|
|
8258
|
-
const sorted = [...failTally.entries()].sort((a2, b) => b[1] - a2[1]).slice(0, 10);
|
|
8348
|
+
const sorted = [...failTally.entries()].sort((a2, b) => b[1].count - a2[1].count).slice(0, 10);
|
|
8259
8349
|
lines.push("\u2500\u2500 MOST COMMON FAILURES \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8260
|
-
for (const [id, count] of sorted) {
|
|
8350
|
+
for (const [id, { count, label, status }] of sorted) {
|
|
8261
8351
|
const check = ALL_CHECKS.find((c) => c.id === id);
|
|
8262
|
-
|
|
8352
|
+
const want = check?.title ? ` (should be: ${check.title})` : "";
|
|
8353
|
+
lines.push(` ${String(count).padStart(2)}/${targets.length} devices ${status.toUpperCase().padEnd(4)} ${id.padEnd(30)} ${label}${want}`);
|
|
8263
8354
|
}
|
|
8264
8355
|
lines.push("");
|
|
8265
8356
|
}
|
|
@@ -10785,7 +10876,7 @@ var cache2 = null;
|
|
|
10785
10876
|
async function gateway() {
|
|
10786
10877
|
if (cache2)
|
|
10787
10878
|
return cache2;
|
|
10788
|
-
const { moduleCatalog } = await import("./library-
|
|
10879
|
+
const { moduleCatalog } = await import("./library-v52e871r.js");
|
|
10789
10880
|
const forIndex = [];
|
|
10790
10881
|
const byName = new Map;
|
|
10791
10882
|
for (const mod of moduleCatalog) {
|
|
@@ -16289,20 +16380,36 @@ Management access may be partially restricted \u2014 review immediately.`;
|
|
|
16289
16380
|
import { z as z35 } from "zod";
|
|
16290
16381
|
|
|
16291
16382
|
// src/tools/_resolve-rule-id.ts
|
|
16383
|
+
var VALID_ID = /^(?:\*[0-9a-fA-F]+|\d+)$/;
|
|
16384
|
+
function isValidItemId(id) {
|
|
16385
|
+
return VALID_ID.test(id.trim());
|
|
16386
|
+
}
|
|
16387
|
+
async function listItemIds(scope, ctx) {
|
|
16388
|
+
const idsRaw = await executeMikrotikCommand(`:foreach i in=[${scope} find] do={:put $i}`, ctx);
|
|
16389
|
+
if (isEmpty(idsRaw))
|
|
16390
|
+
return [];
|
|
16391
|
+
return idsRaw.trim().split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
16392
|
+
}
|
|
16292
16393
|
function ruleResolver(scope) {
|
|
16293
16394
|
return async function resolveRuleId(ruleId, ctx) {
|
|
16294
|
-
|
|
16295
|
-
|
|
16296
|
-
|
|
16395
|
+
const id = ruleId.trim();
|
|
16396
|
+
if (!isValidItemId(id))
|
|
16397
|
+
return null;
|
|
16398
|
+
if (/^\d+$/.test(id)) {
|
|
16399
|
+
const ids = await listItemIds(scope, ctx);
|
|
16400
|
+
if (ids.length === 0)
|
|
16297
16401
|
return null;
|
|
16298
|
-
const
|
|
16299
|
-
const pos = Number.parseInt(ruleId, 10);
|
|
16402
|
+
const pos = Number.parseInt(id, 10);
|
|
16300
16403
|
return pos >= 0 && pos < ids.length ? ids[pos] : null;
|
|
16301
16404
|
}
|
|
16302
|
-
const count = await executeMikrotikCommand(`${scope} print count-only where .id=${
|
|
16303
|
-
return count.trim() !== "0" ?
|
|
16405
|
+
const count = await executeMikrotikCommand(`${scope} print count-only where .id=${id}`, ctx);
|
|
16406
|
+
return count.trim() !== "0" ? id : null;
|
|
16304
16407
|
};
|
|
16305
16408
|
}
|
|
16409
|
+
function notFoundMessage(what, id, listTool) {
|
|
16410
|
+
const shape = isValidItemId(id) ? "" : ` '${id}' is not a valid reference \u2014 pass the \`.id\` from ${listTool} (e.g. "*1F") or a bare row position (e.g. "3").`;
|
|
16411
|
+
return `${what} '${id}' not found.${shape}`;
|
|
16412
|
+
}
|
|
16306
16413
|
|
|
16307
16414
|
// src/tools/firewall-filter.ts
|
|
16308
16415
|
var isDigits = (s) => /^\d+$/.test(s);
|
|
@@ -16395,8 +16502,13 @@ async function updateFilterRule(a, ctx) {
|
|
|
16395
16502
|
return `Firewall filter rule '${a.rule_id}' not found.`;
|
|
16396
16503
|
const cmd = `/ip firewall filter set ${id} ${updates.join(" ")}`;
|
|
16397
16504
|
const result = await executeMikrotikCommand(cmd, ctx);
|
|
16398
|
-
if (looksLikeError(result))
|
|
16399
|
-
|
|
16505
|
+
if (looksLikeError(result)) {
|
|
16506
|
+
const hint = interfaceValueError(result, [
|
|
16507
|
+
{ name: "in_interface", value: a.in_interface },
|
|
16508
|
+
{ name: "out_interface", value: a.out_interface }
|
|
16509
|
+
]);
|
|
16510
|
+
return `Failed to update firewall filter rule: ${hint ?? result}`;
|
|
16511
|
+
}
|
|
16400
16512
|
const details = await executeMikrotikCommand(`/ip firewall filter print detail where .id=${id}`, ctx);
|
|
16401
16513
|
return `Firewall filter rule updated successfully:
|
|
16402
16514
|
|
|
@@ -16485,7 +16597,10 @@ var firewallFilterTools = [
|
|
|
16485
16597
|
const result = await executeMikrotikCommand(cmd, ctx);
|
|
16486
16598
|
const trimmed = result.trim();
|
|
16487
16599
|
if (looksLikeError(trimmed)) {
|
|
16488
|
-
const hint = placeBeforeError(trimmed, a.place_before)
|
|
16600
|
+
const hint = placeBeforeError(trimmed, a.place_before) ?? interfaceValueError(trimmed, [
|
|
16601
|
+
{ name: "in_interface", value: a.in_interface },
|
|
16602
|
+
{ name: "out_interface", value: a.out_interface }
|
|
16603
|
+
]);
|
|
16489
16604
|
return `Failed to create firewall filter rule: ${hint ?? trimmed}`;
|
|
16490
16605
|
}
|
|
16491
16606
|
const createdId = extractCreatedId(trimmed);
|
|
@@ -16533,18 +16648,31 @@ ${details}`;
|
|
|
16533
16648
|
filters.push(`src-address~"${a.src_address_filter}"`);
|
|
16534
16649
|
if (a.dst_address_filter)
|
|
16535
16650
|
filters.push(`dst-address~"${a.dst_address_filter}"`);
|
|
16536
|
-
if (a.protocol_filter)
|
|
16537
|
-
filters.push(`protocol=${a.protocol_filter}`);
|
|
16538
|
-
if (a.interface_filter) {
|
|
16539
|
-
filters.push(`(in-interface~"${a.interface_filter}" or out-interface~"${a.interface_filter}")`);
|
|
16540
|
-
}
|
|
16541
16651
|
if (a.disabled_only)
|
|
16542
16652
|
filters.push("disabled=yes");
|
|
16543
16653
|
if (a.invalid_only)
|
|
16544
16654
|
filters.push("invalid=yes");
|
|
16545
16655
|
if (a.dynamic_only)
|
|
16546
16656
|
filters.push("dynamic=yes");
|
|
16547
|
-
const
|
|
16657
|
+
const raw = await executeMikrotikCommand(`/ip firewall filter print detail${whereClause(filters)}`, ctx);
|
|
16658
|
+
const protocolWanted = a.protocol_filter?.toLowerCase();
|
|
16659
|
+
const interfaceWanted = a.interface_filter?.toLowerCase();
|
|
16660
|
+
const result = protocolWanted || interfaceWanted ? filterDetailBlocks(raw, (row) => {
|
|
16661
|
+
if (protocolWanted && (row.protocol ?? "").toLowerCase() !== protocolWanted) {
|
|
16662
|
+
return false;
|
|
16663
|
+
}
|
|
16664
|
+
if (interfaceWanted) {
|
|
16665
|
+
const hit = [
|
|
16666
|
+
row["in-interface"],
|
|
16667
|
+
row["out-interface"],
|
|
16668
|
+
row["in-interface-list"],
|
|
16669
|
+
row["out-interface-list"]
|
|
16670
|
+
].some((v) => (v ?? "").toLowerCase().includes(interfaceWanted));
|
|
16671
|
+
if (!hit)
|
|
16672
|
+
return false;
|
|
16673
|
+
}
|
|
16674
|
+
return true;
|
|
16675
|
+
}) : raw;
|
|
16548
16676
|
return isEmpty(result) ? "No firewall filter rules found matching the criteria." : `FIREWALL FILTER RULES:
|
|
16549
16677
|
|
|
16550
16678
|
${result}`;
|
|
@@ -16554,7 +16682,7 @@ ${result}`;
|
|
|
16554
16682
|
name: "get_filter_rule",
|
|
16555
16683
|
title: "Get Firewall Filter Rule",
|
|
16556
16684
|
annotations: READ,
|
|
16557
|
-
description: "Gets the full detail of one IPv4 firewall FILTER rule (`/ip firewall filter`) by id \u2014 " + "every matcher, action, counter and flag. For IPv6 use get_ipv6_filter_rule. " + 'rule_id: preferably the `.id` from list_filter_rules e.g. "*1F". A bare number like "3" ' + "is
|
|
16685
|
+
description: "Gets the full detail of one IPv4 firewall FILTER rule (`/ip firewall filter`) by id \u2014 " + "every matcher, action, counter and flag. For IPv6 use get_ipv6_filter_rule. " + 'rule_id: preferably the `.id` from list_filter_rules e.g. "*1F". A bare number like "3" ' + "is read as the positional ROW INDEX and resolved to whatever `.id` currently sits there \u2014 " + "it is never treated as `.id=*3`, since a `*N` id is hex and reassigned over time. Row " + "positions shift whenever a rule is added, removed or moved, so prefer the `.id`.",
|
|
16558
16686
|
inputSchema: {
|
|
16559
16687
|
rule_id: z35.string().describe('Rule .id e.g. "*1F", or bare position number e.g. "3"')
|
|
16560
16688
|
},
|
|
@@ -16662,10 +16790,23 @@ ${result}`;
|
|
|
16662
16790
|
const id = await resolveFilterRuleId(a.rule_id, ctx);
|
|
16663
16791
|
if (!id)
|
|
16664
16792
|
return `Firewall filter rule '${a.rule_id}' not found.`;
|
|
16793
|
+
const before = await listItemIds("/ip firewall filter", ctx);
|
|
16665
16794
|
const result = await executeMikrotikCommand(`/ip firewall filter move ${id} destination=${a.destination}`, ctx);
|
|
16666
16795
|
if (looksLikeError(result))
|
|
16667
16796
|
return `Failed to move firewall filter rule: ${result}`;
|
|
16668
|
-
|
|
16797
|
+
const after = await listItemIds("/ip firewall filter", ctx);
|
|
16798
|
+
const from = before.indexOf(id);
|
|
16799
|
+
const to = after.indexOf(id);
|
|
16800
|
+
if (to === -1) {
|
|
16801
|
+
return `Firewall filter rule ${id} moved, but it could not be located afterwards \u2014 verify with list_filter_rules.`;
|
|
16802
|
+
}
|
|
16803
|
+
if (to === a.destination) {
|
|
16804
|
+
return `Firewall filter rule '${a.rule_id}' (${id}) moved to position ${to}.`;
|
|
16805
|
+
}
|
|
16806
|
+
if (to === from) {
|
|
16807
|
+
return `NO CHANGE: firewall filter rule '${a.rule_id}' (${id}) is still at position ${to} \u2014 ` + `RouterOS ignored the move to ${a.destination}. Destination is 0-based and counts ` + `ALL rules in the menu (${after.length} total), not just this rule's chain; a ` + "destination at or past the end, or equal to the current position, does nothing.";
|
|
16808
|
+
}
|
|
16809
|
+
return `Firewall filter rule '${a.rule_id}' (${id}) moved from position ${from} to ${to} ` + `(requested ${a.destination} \u2014 RouterOS places the rule BEFORE the item currently at ` + "the destination index, so the final index can differ when moving downward).";
|
|
16669
16810
|
}
|
|
16670
16811
|
}),
|
|
16671
16812
|
defineTool({
|
|
@@ -18405,6 +18546,7 @@ ${result}`;
|
|
|
18405
18546
|
|
|
18406
18547
|
// src/tools/ip-service.ts
|
|
18407
18548
|
import { z as z43 } from "zod";
|
|
18549
|
+
var staticService = (name) => `[find name="${name}" and !dynamic]`;
|
|
18408
18550
|
var ipServiceTools = [
|
|
18409
18551
|
defineTool({
|
|
18410
18552
|
name: "list_ip_services",
|
|
@@ -18450,7 +18592,7 @@ ${result}`;
|
|
|
18450
18592
|
},
|
|
18451
18593
|
async handler(a, ctx) {
|
|
18452
18594
|
ctx.info(`Updating IP service: name=${a.name}`);
|
|
18453
|
-
const cmd = new Cmd(`/ip service set
|
|
18595
|
+
const cmd = new Cmd(`/ip service set ${staticService(a.name)}`).opt("port", a.port).opt("address", a.address).bool("disabled", a.disabled).opt("certificate", a.certificate).opt("vrf", a.vrf).build();
|
|
18454
18596
|
if (!cmd.includes("=", cmd.indexOf("]")))
|
|
18455
18597
|
return "No updates specified.";
|
|
18456
18598
|
const result = await executeMikrotikCommand(cmd, ctx);
|
|
@@ -18474,7 +18616,7 @@ ${details}`;
|
|
|
18474
18616
|
},
|
|
18475
18617
|
async handler(a, ctx) {
|
|
18476
18618
|
ctx.info(`Enabling IP service: name=${a.name}`);
|
|
18477
|
-
const result = await executeMikrotikCommand(`/ip service enable
|
|
18619
|
+
const result = await executeMikrotikCommand(`/ip service enable ${staticService(a.name)}`, ctx);
|
|
18478
18620
|
if (looksLikeError(result))
|
|
18479
18621
|
return `Failed to enable IP service: ${result}`;
|
|
18480
18622
|
return `IP service '${a.name}' enabled successfully.`;
|
|
@@ -18490,7 +18632,7 @@ ${details}`;
|
|
|
18490
18632
|
},
|
|
18491
18633
|
async handler(a, ctx) {
|
|
18492
18634
|
ctx.info(`Disabling IP service: name=${a.name}`);
|
|
18493
|
-
const result = await executeMikrotikCommand(`/ip service disable
|
|
18635
|
+
const result = await executeMikrotikCommand(`/ip service disable ${staticService(a.name)}`, ctx);
|
|
18494
18636
|
if (looksLikeError(result))
|
|
18495
18637
|
return `Failed to disable IP service: ${result}`;
|
|
18496
18638
|
return `IP service '${a.name}' disabled successfully.`;
|
|
@@ -24987,7 +25129,7 @@ var speedTestTools = [
|
|
|
24987
25129
|
},
|
|
24988
25130
|
async handler(a, ctx) {
|
|
24989
25131
|
ctx.info(`Speed test to ${a.address} (test-duration=${a.duration}s)`);
|
|
24990
|
-
const cmd = new Cmd(
|
|
25132
|
+
const cmd = new Cmd("/tool speed-test").set("address", a.address).set("test-duration", `${a.duration}s`).opt("connection-count", a.connection_count).opt("user", a.user).opt("password", a.password).build();
|
|
24991
25133
|
const result = flattenLiveOutput(await executeMikrotikCommand(cmd, ctx, { maxMs: a.duration * 1000 + 15000 }));
|
|
24992
25134
|
if (looksLikeError(result))
|
|
24993
25135
|
return `Failed to run speed test to ${a.address}: ${result}`;
|
|
@@ -29643,10 +29785,11 @@ async function collectRoutes(ctx) {
|
|
|
29643
29785
|
dst: r["dst-address"] ?? "",
|
|
29644
29786
|
gateway: r.gateway ?? "",
|
|
29645
29787
|
distance: num(r.distance),
|
|
29646
|
-
active: (r.flags ?? "").includes("A") || r.active
|
|
29647
|
-
dynamic: (r.flags ?? "").includes("D") || r.dynamic
|
|
29788
|
+
active: (r.flags ?? "").includes("A") || isYes(r.active),
|
|
29789
|
+
dynamic: (r.flags ?? "").includes("D") || isYes(r.dynamic)
|
|
29648
29790
|
}));
|
|
29649
|
-
const
|
|
29791
|
+
const isDefaultDst = (dst) => dst === "0.0.0.0/0" || dst === "0.0.0.0/0*";
|
|
29792
|
+
const hasDefault = routes.some((r) => isDefaultDst(r.dst) && r.active);
|
|
29650
29793
|
return { routes, count: routes.length, hasDefault };
|
|
29651
29794
|
}
|
|
29652
29795
|
async function collectOspfNeighbors(ctx) {
|
|
@@ -30157,6 +30300,8 @@ var multiwanTools = [
|
|
|
30157
30300
|
|
|
30158
30301
|
// src/tools/routes.ts
|
|
30159
30302
|
import { z as z99 } from "zod";
|
|
30303
|
+
var resolveRouteId = ruleResolver("/ip route");
|
|
30304
|
+
var routeNotFound = (id) => notFoundMessage("Route", id, "list_routes");
|
|
30160
30305
|
var RouteType2 = z99.enum(["unicast", "blackhole"]);
|
|
30161
30306
|
async function addRoute(a, ctx) {
|
|
30162
30307
|
ctx.info(`Adding route: dst=${a.dst_address}, gateway=${a.gateway}`);
|
|
@@ -30179,10 +30324,13 @@ ${details}` : "Route addition completed but unable to verify.";
|
|
|
30179
30324
|
}
|
|
30180
30325
|
async function setRouteDisabled(routeId, disabled, ctx) {
|
|
30181
30326
|
ctx.info(`Updating route: route_id=${routeId}`);
|
|
30182
|
-
const
|
|
30327
|
+
const id = await resolveRouteId(routeId, ctx);
|
|
30328
|
+
if (!id)
|
|
30329
|
+
return routeNotFound(routeId);
|
|
30330
|
+
const result = await executeMikrotikCommand(`/ip route set ${id} disabled=${yesno(disabled)}`, ctx);
|
|
30183
30331
|
if (looksLikeError(result))
|
|
30184
30332
|
return `Failed to update route: ${result}`;
|
|
30185
|
-
const details = await executeMikrotikCommand(`/ip route print detail where .id=${
|
|
30333
|
+
const details = await executeMikrotikCommand(`/ip route print detail where .id=${id}`, ctx);
|
|
30186
30334
|
return `Route updated successfully:
|
|
30187
30335
|
|
|
30188
30336
|
${details}`;
|
|
@@ -30265,8 +30413,11 @@ ${result}`;
|
|
|
30265
30413
|
},
|
|
30266
30414
|
async handler(a, ctx) {
|
|
30267
30415
|
ctx.info(`Getting route details: route_id=${a.route_id}`);
|
|
30268
|
-
const
|
|
30269
|
-
|
|
30416
|
+
const id = await resolveRouteId(a.route_id, ctx);
|
|
30417
|
+
if (!id)
|
|
30418
|
+
return routeNotFound(a.route_id);
|
|
30419
|
+
const result = await executeMikrotikCommand(`/ip route print detail where .id=${id}`, ctx);
|
|
30420
|
+
return isEmpty(result) ? routeNotFound(a.route_id) : `ROUTE DETAILS:
|
|
30270
30421
|
|
|
30271
30422
|
${result}`;
|
|
30272
30423
|
}
|
|
@@ -30295,7 +30446,10 @@ ${result}`;
|
|
|
30295
30446
|
},
|
|
30296
30447
|
async handler(a, ctx) {
|
|
30297
30448
|
ctx.info(`Updating route: route_id=${a.route_id}`);
|
|
30298
|
-
const
|
|
30449
|
+
const id = await resolveRouteId(a.route_id, ctx);
|
|
30450
|
+
if (!id)
|
|
30451
|
+
return routeNotFound(a.route_id);
|
|
30452
|
+
const base = `/ip route set ${id}`;
|
|
30299
30453
|
const cmd = new Cmd(base);
|
|
30300
30454
|
if (a.dst_address)
|
|
30301
30455
|
cmd.set("dst-address", a.dst_address);
|
|
@@ -30333,8 +30487,8 @@ ${result}`;
|
|
|
30333
30487
|
const result = await executeMikrotikCommand(built, ctx);
|
|
30334
30488
|
if (looksLikeError(result))
|
|
30335
30489
|
return `Failed to update route: ${result}`;
|
|
30336
|
-
const details = await executeMikrotikCommand(`/ip route print detail where .id=${
|
|
30337
|
-
return `Route updated successfully:
|
|
30490
|
+
const details = await executeMikrotikCommand(`/ip route print detail where .id=${id}`, ctx);
|
|
30491
|
+
return `Route ${id} updated successfully:
|
|
30338
30492
|
|
|
30339
30493
|
${details}`;
|
|
30340
30494
|
}
|
|
@@ -30349,13 +30503,16 @@ ${details}`;
|
|
|
30349
30503
|
},
|
|
30350
30504
|
async handler(a, ctx) {
|
|
30351
30505
|
ctx.info(`Removing route: route_id=${a.route_id}`);
|
|
30352
|
-
const
|
|
30353
|
-
if (
|
|
30354
|
-
return
|
|
30355
|
-
const
|
|
30506
|
+
const id = await resolveRouteId(a.route_id, ctx);
|
|
30507
|
+
if (!id)
|
|
30508
|
+
return routeNotFound(a.route_id);
|
|
30509
|
+
const doomed = await executeMikrotikCommand(`/ip route print detail where .id=${id}`, ctx);
|
|
30510
|
+
const result = await executeMikrotikCommand(`/ip route remove ${id}`, ctx);
|
|
30356
30511
|
if (looksLikeError(result))
|
|
30357
30512
|
return `Failed to remove route: ${result}`;
|
|
30358
|
-
return `Route
|
|
30513
|
+
return `Route ${id} removed successfully. Removed entry was:
|
|
30514
|
+
|
|
30515
|
+
${doomed}`;
|
|
30359
30516
|
}
|
|
30360
30517
|
}),
|
|
30361
30518
|
defineTool({
|
|
@@ -31883,14 +32040,22 @@ ${result}`;
|
|
|
31883
32040
|
name: "add_ospf_interface_template",
|
|
31884
32041
|
title: "Add OSPF Interface Template",
|
|
31885
32042
|
annotations: WRITE,
|
|
31886
|
-
description: "Bind interfaces or network prefixes to an OSPF area (`/routing ospf interface-template add`). " + "Match links via `interfaces` (interface or interface-list name) and/or `networks` (prefix, " + "e.g. '10.0.0.0/24'). `type` sets the link model: broadcast (LAN), ptp (point-to-point), " + "ptmp, ptmp-broadcast, nbma, or virtual-link. `passive=true` advertises the subnet without forming OSPF adjacencies " + "(for stub networks). `auth`/`auth_id`/`auth_key` enable per-interface authentication; `hello_interval` " + "e.g. '10s', `dead_interval` e.g. '40s'. " + "Requires the area to already exist (add_ospf_area). " + "Returns the new template id (e.g. '*2') if RouterOS echoes one; use list_ospf_interface_templates to verify.",
|
|
32043
|
+
description: "Bind interfaces or network prefixes to an OSPF area (`/routing ospf interface-template add`). " + "Match links via `interfaces` (interface or interface-list name) and/or `networks` (prefix, " + "e.g. '10.0.0.0/24'). `type` sets the link model: broadcast (LAN), ptp (point-to-point), " + "ptp-unnumbered (point-to-point over an unnumbered link), ptmp, ptmp-broadcast, nbma, or " + "virtual-link. `passive=true` advertises the subnet without forming OSPF adjacencies " + "(for stub networks). `auth`/`auth_id`/`auth_key` enable per-interface authentication; `hello_interval` " + "e.g. '10s', `dead_interval` e.g. '40s'. " + "Requires the area to already exist (add_ospf_area). " + "Returns the new template id (e.g. '*2') if RouterOS echoes one; use list_ospf_interface_templates to verify.",
|
|
31887
32044
|
inputSchema: {
|
|
31888
32045
|
area: z107.string().describe("OSPF area name to attach matched interfaces to"),
|
|
31889
32046
|
interfaces: z107.string().optional().describe("Interface or interface-list name"),
|
|
31890
32047
|
networks: z107.string().optional().describe('Network prefix(es) to enable OSPF on, e.g. "10.0.0.0/24"'),
|
|
31891
32048
|
cost: z107.number().int().optional().describe("Output cost / metric"),
|
|
31892
32049
|
priority: z107.number().int().optional().describe("DR election priority (0 = never DR)"),
|
|
31893
|
-
type: z107.enum([
|
|
32050
|
+
type: z107.enum([
|
|
32051
|
+
"broadcast",
|
|
32052
|
+
"ptp",
|
|
32053
|
+
"ptp-unnumbered",
|
|
32054
|
+
"ptmp",
|
|
32055
|
+
"ptmp-broadcast",
|
|
32056
|
+
"nbma",
|
|
32057
|
+
"virtual-link"
|
|
32058
|
+
]).optional(),
|
|
31894
32059
|
passive: z107.boolean().optional(),
|
|
31895
32060
|
hello_interval: z107.string().optional().describe('e.g. "10s"'),
|
|
31896
32061
|
dead_interval: z107.string().optional().describe('e.g. "40s"'),
|
|
@@ -34528,7 +34693,7 @@ function resolveTargets(targets) {
|
|
|
34528
34693
|
const cfg = getConfig();
|
|
34529
34694
|
const notes = [];
|
|
34530
34695
|
if (Array.isArray(targets)) {
|
|
34531
|
-
const unknown = targets.filter((t) => !(
|
|
34696
|
+
const unknown = targets.filter((t) => !tryResolveDeviceName(t));
|
|
34532
34697
|
if (unknown.length > 0) {
|
|
34533
34698
|
return { devices: [], notes, error: `unknown device(s): ${unknown.join(", ")}` };
|
|
34534
34699
|
}
|
|
@@ -35455,7 +35620,7 @@ var riskByTool = null;
|
|
|
35455
35620
|
async function riskIndex() {
|
|
35456
35621
|
if (riskByTool)
|
|
35457
35622
|
return riskByTool;
|
|
35458
|
-
const { moduleCatalog } = await import("./library-
|
|
35623
|
+
const { moduleCatalog } = await import("./library-v52e871r.js");
|
|
35459
35624
|
const index = new Map;
|
|
35460
35625
|
for (const mod of moduleCatalog) {
|
|
35461
35626
|
for (const tool of mod.tools)
|
|
@@ -42307,6 +42472,8 @@ ${config}`;
|
|
|
42307
42472
|
|
|
42308
42473
|
// src/tools/wireguard.ts
|
|
42309
42474
|
import { z as z138 } from "zod";
|
|
42475
|
+
var resolvePeerId = ruleResolver("/interface wireguard peers");
|
|
42476
|
+
var peerNotFound = (id) => notFoundMessage("WireGuard peer", id, "list_wireguard_peers");
|
|
42310
42477
|
var wireguardTools = [
|
|
42311
42478
|
defineTool({
|
|
42312
42479
|
name: "create_wireguard_interface",
|
|
@@ -42431,17 +42598,26 @@ ${details}`;
|
|
|
42431
42598
|
name: "remove_wireguard_interface",
|
|
42432
42599
|
title: "Remove WireGuard Interface",
|
|
42433
42600
|
annotations: DESTRUCTIVE,
|
|
42434
|
-
description: "Permanently deletes a WireGuard tunnel interface (`/interface wireguard remove`) by name
|
|
42601
|
+
description: "Permanently deletes a WireGuard tunnel interface (`/interface wireguard remove`) by name," + " together with every peer bound to it. Verifies existence first with count-only." + " For removing only a specific peer without touching the interface use remove_wireguard_peer." + " Reports how many peers were removed alongside the interface.",
|
|
42435
42602
|
inputSchema: { name: z138.string() },
|
|
42436
42603
|
async handler(a, ctx) {
|
|
42437
42604
|
ctx.info(`Removing WireGuard interface: name=${a.name}`);
|
|
42438
42605
|
const count = await executeMikrotikCommand(`/interface wireguard print count-only where name="${a.name}"`, ctx);
|
|
42439
42606
|
if (count.trim() === "0")
|
|
42440
42607
|
return `WireGuard interface '${a.name}' not found.`;
|
|
42608
|
+
const peerCount = (await executeMikrotikCommand(`/interface wireguard peers print count-only where interface="${a.name}"`, ctx)).trim();
|
|
42609
|
+
if (peerCount !== "0" && !looksLikeError(peerCount)) {
|
|
42610
|
+
const peerResult = await executeMikrotikCommand(`/interface wireguard peers remove [find interface="${a.name}"]`, ctx);
|
|
42611
|
+
if (looksLikeError(peerResult)) {
|
|
42612
|
+
return `Failed to remove the peers bound to '${a.name}': ${peerResult}
|
|
42613
|
+
` + "The interface was NOT removed \u2014 removing it now would orphan those peers.";
|
|
42614
|
+
}
|
|
42615
|
+
}
|
|
42441
42616
|
const result = await executeMikrotikCommand(`/interface wireguard remove [find name="${a.name}"]`, ctx);
|
|
42442
42617
|
if (looksLikeError(result))
|
|
42443
42618
|
return `Failed to remove WireGuard interface: ${result}`;
|
|
42444
|
-
|
|
42619
|
+
const peers = peerCount === "0" ? "no peers were bound to it" : `${peerCount} peer(s) removed`;
|
|
42620
|
+
return `WireGuard interface '${a.name}' removed successfully (${peers}).`;
|
|
42445
42621
|
}
|
|
42446
42622
|
}),
|
|
42447
42623
|
defineTool({
|
|
@@ -42548,8 +42724,11 @@ ${result}`;
|
|
|
42548
42724
|
},
|
|
42549
42725
|
async handler(a, ctx) {
|
|
42550
42726
|
ctx.info(`Getting WireGuard peer details: peer_id=${a.peer_id}`);
|
|
42551
|
-
const
|
|
42552
|
-
|
|
42727
|
+
const id = await resolvePeerId(a.peer_id, ctx);
|
|
42728
|
+
if (!id)
|
|
42729
|
+
return peerNotFound(a.peer_id);
|
|
42730
|
+
const result = await executeMikrotikCommand(`/interface wireguard peers print detail where .id=${id}`, ctx);
|
|
42731
|
+
return isEmpty(result) ? peerNotFound(a.peer_id) : `WIREGUARD PEER DETAILS:
|
|
42553
42732
|
|
|
42554
42733
|
${result}`;
|
|
42555
42734
|
}
|
|
@@ -42567,6 +42746,7 @@ ${result}`;
|
|
|
42567
42746
|
` + ' Pass "" for endpoint_address or preshared_key to clear them.',
|
|
42568
42747
|
inputSchema: {
|
|
42569
42748
|
peer_id: z138.string().describe('"*N" or "N" from list output e.g. "*2"'),
|
|
42749
|
+
public_key: z138.string().optional().describe("The peer's base64 public key \u2014 re-key a peer in place instead of removing and " + "re-adding it (which would lose the rest of its configuration)."),
|
|
42570
42750
|
allowed_address: z138.string().optional(),
|
|
42571
42751
|
endpoint_address: z138.string().optional(),
|
|
42572
42752
|
endpoint_port: z138.number().int().optional(),
|
|
@@ -42585,15 +42765,18 @@ ${result}`;
|
|
|
42585
42765
|
},
|
|
42586
42766
|
async handler(a, ctx) {
|
|
42587
42767
|
ctx.info(`Updating WireGuard peer: peer_id=${a.peer_id}`);
|
|
42588
|
-
const
|
|
42589
|
-
|
|
42768
|
+
const id = await resolvePeerId(a.peer_id, ctx);
|
|
42769
|
+
if (!id)
|
|
42770
|
+
return peerNotFound(a.peer_id);
|
|
42771
|
+
const base = `/interface wireguard peers set ${id}`;
|
|
42772
|
+
const cmd = new Cmd(base).raw(a.public_key !== undefined ? `public-key=${quoteValue(a.public_key)}` : null).raw(a.allowed_address !== undefined ? `allowed-address=${quoteValue(a.allowed_address)}` : null).raw(a.endpoint_address !== undefined ? a.endpoint_address === "" ? "!endpoint-address" : `endpoint-address=${quoteValue(a.endpoint_address)}` : null).opt("endpoint-port", a.endpoint_port).raw(a.preshared_key !== undefined ? a.preshared_key === "" ? "!preshared-key" : `preshared-key=${quoteValue(a.preshared_key)}` : null).raw(a.persistent_keepalive !== undefined ? `persistent-keepalive=${a.persistent_keepalive}` : null).opt("name", a.name).opt("private-key", a.private_key).bool("responder", a.responder).raw(a.client_address !== undefined ? a.client_address === "" ? "!client-address" : `client-address=${quoteValue(a.client_address)}` : null).raw(a.client_dns !== undefined ? a.client_dns === "" ? "!client-dns" : `client-dns=${quoteValue(a.client_dns)}` : null).raw(a.client_endpoint !== undefined ? a.client_endpoint === "" ? "!client-endpoint" : `client-endpoint=${quoteValue(a.client_endpoint)}` : null).opt("client-keepalive", a.client_keepalive).opt("client-listen-port", a.client_listen_port).raw(a.comment !== undefined ? `comment=${quoteValue(a.comment)}` : null).bool("disabled", a.disabled).build();
|
|
42590
42773
|
if (cmd === base)
|
|
42591
42774
|
return "No updates specified.";
|
|
42592
42775
|
const result = await executeMikrotikCommand(cmd, ctx);
|
|
42593
42776
|
if (looksLikeError(result))
|
|
42594
42777
|
return `Failed to update WireGuard peer: ${result}`;
|
|
42595
|
-
const details = await executeMikrotikCommand(`/interface wireguard peers print detail where .id=${
|
|
42596
|
-
return `WireGuard peer updated successfully:
|
|
42778
|
+
const details = await executeMikrotikCommand(`/interface wireguard peers print detail where .id=${id}`, ctx);
|
|
42779
|
+
return `WireGuard peer ${id} updated successfully:
|
|
42597
42780
|
|
|
42598
42781
|
${details}`;
|
|
42599
42782
|
}
|
|
@@ -42611,13 +42794,16 @@ ${details}`;
|
|
|
42611
42794
|
},
|
|
42612
42795
|
async handler(a, ctx) {
|
|
42613
42796
|
ctx.info(`Removing WireGuard peer: peer_id=${a.peer_id}`);
|
|
42614
|
-
const
|
|
42615
|
-
if (
|
|
42616
|
-
return
|
|
42617
|
-
const
|
|
42797
|
+
const id = await resolvePeerId(a.peer_id, ctx);
|
|
42798
|
+
if (!id)
|
|
42799
|
+
return peerNotFound(a.peer_id);
|
|
42800
|
+
const doomed = await executeMikrotikCommand(`/interface wireguard peers print detail where .id=${id}`, ctx);
|
|
42801
|
+
const result = await executeMikrotikCommand(`/interface wireguard peers remove ${id}`, ctx);
|
|
42618
42802
|
if (looksLikeError(result))
|
|
42619
42803
|
return `Failed to remove WireGuard peer: ${result}`;
|
|
42620
|
-
return `WireGuard peer
|
|
42804
|
+
return `WireGuard peer ${id} removed successfully. Removed entry was:
|
|
42805
|
+
|
|
42806
|
+
${doomed}`;
|
|
42621
42807
|
}
|
|
42622
42808
|
}),
|
|
42623
42809
|
defineTool({
|
|
@@ -42633,10 +42819,13 @@ ${details}`;
|
|
|
42633
42819
|
},
|
|
42634
42820
|
async handler(a, ctx) {
|
|
42635
42821
|
ctx.info(`Enabling WireGuard peer: peer_id=${a.peer_id}`);
|
|
42636
|
-
const
|
|
42822
|
+
const id = await resolvePeerId(a.peer_id, ctx);
|
|
42823
|
+
if (!id)
|
|
42824
|
+
return peerNotFound(a.peer_id);
|
|
42825
|
+
const result = await executeMikrotikCommand(`/interface wireguard peers enable ${id}`, ctx);
|
|
42637
42826
|
if (looksLikeError(result))
|
|
42638
42827
|
return `Failed to enable WireGuard peer: ${result}`;
|
|
42639
|
-
return `WireGuard peer
|
|
42828
|
+
return `WireGuard peer ${id} enabled successfully.`;
|
|
42640
42829
|
}
|
|
42641
42830
|
}),
|
|
42642
42831
|
defineTool({
|
|
@@ -42652,10 +42841,13 @@ ${details}`;
|
|
|
42652
42841
|
},
|
|
42653
42842
|
async handler(a, ctx) {
|
|
42654
42843
|
ctx.info(`Disabling WireGuard peer: peer_id=${a.peer_id}`);
|
|
42655
|
-
const
|
|
42844
|
+
const id = await resolvePeerId(a.peer_id, ctx);
|
|
42845
|
+
if (!id)
|
|
42846
|
+
return peerNotFound(a.peer_id);
|
|
42847
|
+
const result = await executeMikrotikCommand(`/interface wireguard peers disable ${id}`, ctx);
|
|
42656
42848
|
if (looksLikeError(result))
|
|
42657
42849
|
return `Failed to disable WireGuard peer: ${result}`;
|
|
42658
|
-
return `WireGuard peer
|
|
42850
|
+
return `WireGuard peer ${id} disabled successfully.`;
|
|
42659
42851
|
}
|
|
42660
42852
|
}),
|
|
42661
42853
|
defineTool({
|