mikromcp 1.3.0 → 1.4.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 +5 -5
- package/dist/main.js +1200 -328
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -60,7 +60,7 @@ function loadAppConfig() {
|
|
|
60
60
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
61
61
|
|
|
62
62
|
// src/mcp/tool-registry.ts
|
|
63
|
-
import { z as
|
|
63
|
+
import { z as z42 } from "zod";
|
|
64
64
|
|
|
65
65
|
// src/domain/tools/system-tools.ts
|
|
66
66
|
import { z } from "zod";
|
|
@@ -6057,14 +6057,224 @@ var manageUserTool = {
|
|
|
6057
6057
|
};
|
|
6058
6058
|
var userTools = [listUsersTool, manageUserTool];
|
|
6059
6059
|
|
|
6060
|
-
// src/domain/tools/
|
|
6060
|
+
// src/domain/tools/user-group-tools.ts
|
|
6061
6061
|
import { z as z27 } from "zod";
|
|
6062
|
-
var log28 = createLogger("
|
|
6063
|
-
var
|
|
6062
|
+
var log28 = createLogger("user-group-tools");
|
|
6063
|
+
var listUserGroupsInputSchema = z27.object({
|
|
6064
|
+
routerId: z27.string().describe("Target router identifier from the router registry"),
|
|
6065
|
+
limit: z27.number().int().min(1).max(500).default(100).describe("Maximum number of groups to return")
|
|
6066
|
+
}).strict();
|
|
6067
|
+
var listUserGroupsTool = {
|
|
6068
|
+
name: "list_user_groups",
|
|
6069
|
+
title: "List User Groups",
|
|
6070
|
+
description: "List local user groups on a MikroTik router.",
|
|
6071
|
+
inputSchema: listUserGroupsInputSchema,
|
|
6072
|
+
annotations: {
|
|
6073
|
+
readOnlyHint: true,
|
|
6074
|
+
destructiveHint: false,
|
|
6075
|
+
idempotentHint: true,
|
|
6076
|
+
openWorldHint: false
|
|
6077
|
+
},
|
|
6078
|
+
async handler(params, context) {
|
|
6079
|
+
const parsed = listUserGroupsInputSchema.parse(params);
|
|
6080
|
+
log28.info({ routerId: context.routerId }, "Listing user groups");
|
|
6081
|
+
try {
|
|
6082
|
+
const all = await context.routerClient.get("user/group", {
|
|
6083
|
+
limit: void 0,
|
|
6084
|
+
offset: void 0
|
|
6085
|
+
});
|
|
6086
|
+
const groups = all.slice(0, parsed.limit);
|
|
6087
|
+
return {
|
|
6088
|
+
content: `User groups on ${context.routerId}: ${groups.length} returned (${all.length} total)`,
|
|
6089
|
+
structuredContent: {
|
|
6090
|
+
routerId: context.routerId,
|
|
6091
|
+
groups,
|
|
6092
|
+
total: all.length,
|
|
6093
|
+
returned: groups.length
|
|
6094
|
+
}
|
|
6095
|
+
};
|
|
6096
|
+
} catch (err) {
|
|
6097
|
+
throw enrichError(err, { routerId: context.routerId, tool: "list_user_groups" });
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
};
|
|
6101
|
+
var manageUserGroupInputSchema = z27.object({
|
|
6064
6102
|
routerId: z27.string().describe("Target router identifier from the router registry"),
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6103
|
+
action: z27.enum(["add", "update", "remove"]).describe("Action to perform"),
|
|
6104
|
+
name: z27.string().describe("Group name \u2014 idempotency key"),
|
|
6105
|
+
policy: z27.string().optional().describe("Comma-separated policy list (e.g. 'read,write,ftp')"),
|
|
6106
|
+
skin: z27.string().optional().describe("Optional skin name for the group"),
|
|
6107
|
+
dryRun: z27.boolean().default(false).describe("Preview changes without applying")
|
|
6108
|
+
}).strict();
|
|
6109
|
+
var manageUserGroupTool = {
|
|
6110
|
+
name: "manage_user_group",
|
|
6111
|
+
title: "Manage User Group",
|
|
6112
|
+
description: "Add, update, or remove a local RouterOS user group. Idempotent by name: add returns already_exists if a group with the same name and policy already exists.",
|
|
6113
|
+
inputSchema: manageUserGroupInputSchema,
|
|
6114
|
+
annotations: {
|
|
6115
|
+
readOnlyHint: false,
|
|
6116
|
+
destructiveHint: false,
|
|
6117
|
+
idempotentHint: true,
|
|
6118
|
+
openWorldHint: false
|
|
6119
|
+
},
|
|
6120
|
+
snapshotPaths: ["user/group"],
|
|
6121
|
+
async handler(params, context) {
|
|
6122
|
+
const parsed = manageUserGroupInputSchema.parse(params);
|
|
6123
|
+
log28.info(
|
|
6124
|
+
{ routerId: context.routerId, action: parsed.action, name: parsed.name },
|
|
6125
|
+
"Managing user group"
|
|
6126
|
+
);
|
|
6127
|
+
try {
|
|
6128
|
+
if (parsed.action === "add") {
|
|
6129
|
+
const all2 = await context.routerClient.get("user/group", {
|
|
6130
|
+
limit: void 0,
|
|
6131
|
+
offset: void 0
|
|
6132
|
+
});
|
|
6133
|
+
const existing2 = all2.find((g) => g.name === parsed.name);
|
|
6134
|
+
if (existing2) {
|
|
6135
|
+
if (parsed.policy === void 0 || existing2.policy === parsed.policy) {
|
|
6136
|
+
return {
|
|
6137
|
+
content: `User group "${parsed.name}" already exists. No changes made.`,
|
|
6138
|
+
structuredContent: { action: "already_exists", name: parsed.name, policy: existing2.policy }
|
|
6139
|
+
};
|
|
6140
|
+
}
|
|
6141
|
+
throw new MikroMCPError({
|
|
6142
|
+
category: "CONFLICT" /* CONFLICT */,
|
|
6143
|
+
code: "USER_GROUP_CONFLICT",
|
|
6144
|
+
message: `Group "${parsed.name}" exists with a different policy.`,
|
|
6145
|
+
details: { existingPolicy: existing2.policy, requestedPolicy: parsed.policy },
|
|
6146
|
+
recoverability: {
|
|
6147
|
+
retryable: false,
|
|
6148
|
+
suggestedAction: "Remove the existing group first or use a different name.",
|
|
6149
|
+
alternativeTools: ["manage_user_group"]
|
|
6150
|
+
}
|
|
6151
|
+
});
|
|
6152
|
+
}
|
|
6153
|
+
if (parsed.dryRun) {
|
|
6154
|
+
return {
|
|
6155
|
+
content: `Dry run: Would add group "${parsed.name}".`,
|
|
6156
|
+
structuredContent: {
|
|
6157
|
+
action: "dry_run",
|
|
6158
|
+
diff: [
|
|
6159
|
+
{ property: "name", before: null, after: parsed.name },
|
|
6160
|
+
{ property: "policy", before: null, after: parsed.policy ?? "" }
|
|
6161
|
+
]
|
|
6162
|
+
}
|
|
6163
|
+
};
|
|
6164
|
+
}
|
|
6165
|
+
const body = { name: parsed.name };
|
|
6166
|
+
if (parsed.policy !== void 0) body.policy = parsed.policy;
|
|
6167
|
+
if (parsed.skin !== void 0) body.skin = parsed.skin;
|
|
6168
|
+
const created = await context.routerClient.create("user/group", body);
|
|
6169
|
+
log28.info({ name: parsed.name, id: created[".id"] }, "User group added");
|
|
6170
|
+
return {
|
|
6171
|
+
content: `Added group "${parsed.name}".`,
|
|
6172
|
+
structuredContent: {
|
|
6173
|
+
action: "created",
|
|
6174
|
+
routerId: context.routerId,
|
|
6175
|
+
name: parsed.name,
|
|
6176
|
+
id: created[".id"]
|
|
6177
|
+
}
|
|
6178
|
+
};
|
|
6179
|
+
}
|
|
6180
|
+
if (parsed.action === "update") {
|
|
6181
|
+
const all2 = await context.routerClient.get("user/group", {
|
|
6182
|
+
limit: void 0,
|
|
6183
|
+
offset: void 0
|
|
6184
|
+
});
|
|
6185
|
+
const existing2 = all2.find((g) => g.name === parsed.name);
|
|
6186
|
+
if (!existing2) {
|
|
6187
|
+
throw new MikroMCPError({
|
|
6188
|
+
category: "NOT_FOUND" /* NOT_FOUND */,
|
|
6189
|
+
code: "USER_GROUP_NOT_FOUND",
|
|
6190
|
+
message: `Group "${parsed.name}" not found.`,
|
|
6191
|
+
details: { name: parsed.name },
|
|
6192
|
+
recoverability: {
|
|
6193
|
+
retryable: false,
|
|
6194
|
+
suggestedAction: "Verify the group name with list_user_groups.",
|
|
6195
|
+
alternativeTools: ["list_user_groups"]
|
|
6196
|
+
}
|
|
6197
|
+
});
|
|
6198
|
+
}
|
|
6199
|
+
if (parsed.policy !== void 0 && existing2.policy === parsed.policy) {
|
|
6200
|
+
return {
|
|
6201
|
+
content: `Group "${parsed.name}" already has the requested policy. No changes made.`,
|
|
6202
|
+
structuredContent: { action: "no_change", name: parsed.name }
|
|
6203
|
+
};
|
|
6204
|
+
}
|
|
6205
|
+
if (parsed.dryRun) {
|
|
6206
|
+
return {
|
|
6207
|
+
content: `Dry run: Would update group "${parsed.name}".`,
|
|
6208
|
+
structuredContent: {
|
|
6209
|
+
action: "dry_run",
|
|
6210
|
+
diff: [
|
|
6211
|
+
{
|
|
6212
|
+
property: "policy",
|
|
6213
|
+
before: existing2.policy ?? null,
|
|
6214
|
+
after: parsed.policy ?? null
|
|
6215
|
+
}
|
|
6216
|
+
]
|
|
6217
|
+
}
|
|
6218
|
+
};
|
|
6219
|
+
}
|
|
6220
|
+
const changes = {};
|
|
6221
|
+
if (parsed.policy !== void 0) changes.policy = parsed.policy;
|
|
6222
|
+
if (parsed.skin !== void 0) changes.skin = parsed.skin;
|
|
6223
|
+
if (Object.keys(changes).length === 0) {
|
|
6224
|
+
return {
|
|
6225
|
+
content: `User group "${parsed.name}" already has the requested configuration. No changes made.`,
|
|
6226
|
+
structuredContent: { action: "no_change", name: parsed.name }
|
|
6227
|
+
};
|
|
6228
|
+
}
|
|
6229
|
+
await context.routerClient.update("user/group", existing2[".id"], changes);
|
|
6230
|
+
log28.info({ name: parsed.name }, "User group updated");
|
|
6231
|
+
return {
|
|
6232
|
+
content: `Updated group "${parsed.name}".`,
|
|
6233
|
+
structuredContent: { action: "updated", routerId: context.routerId, name: parsed.name }
|
|
6234
|
+
};
|
|
6235
|
+
}
|
|
6236
|
+
const all = await context.routerClient.get("user/group", {
|
|
6237
|
+
limit: void 0,
|
|
6238
|
+
offset: void 0
|
|
6239
|
+
});
|
|
6240
|
+
const existing = all.find((g) => g.name === parsed.name);
|
|
6241
|
+
if (!existing) {
|
|
6242
|
+
return {
|
|
6243
|
+
content: `Group "${parsed.name}" not found. Nothing to remove.`,
|
|
6244
|
+
structuredContent: { action: "not_found", name: parsed.name }
|
|
6245
|
+
};
|
|
6246
|
+
}
|
|
6247
|
+
if (parsed.dryRun) {
|
|
6248
|
+
return {
|
|
6249
|
+
content: `Dry run: Would remove group "${parsed.name}".`,
|
|
6250
|
+
structuredContent: {
|
|
6251
|
+
action: "dry_run",
|
|
6252
|
+
diff: [{ property: "name", before: parsed.name, after: null }]
|
|
6253
|
+
}
|
|
6254
|
+
};
|
|
6255
|
+
}
|
|
6256
|
+
await context.routerClient.remove("user/group", existing[".id"]);
|
|
6257
|
+
log28.info({ name: parsed.name }, "User group removed");
|
|
6258
|
+
return {
|
|
6259
|
+
content: `Removed group "${parsed.name}".`,
|
|
6260
|
+
structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
|
|
6261
|
+
};
|
|
6262
|
+
} catch (err) {
|
|
6263
|
+
if (err instanceof MikroMCPError) throw err;
|
|
6264
|
+
throw enrichError(err, { routerId: context.routerId, tool: "manage_user_group" });
|
|
6265
|
+
}
|
|
6266
|
+
}
|
|
6267
|
+
};
|
|
6268
|
+
var userGroupTools = [listUserGroupsTool, manageUserGroupTool];
|
|
6269
|
+
|
|
6270
|
+
// src/domain/tools/dhcp-server-tools.ts
|
|
6271
|
+
import { z as z28 } from "zod";
|
|
6272
|
+
var log29 = createLogger("dhcp-server-tools");
|
|
6273
|
+
var listDhcpServersInputSchema = z28.object({
|
|
6274
|
+
routerId: z28.string().describe("Target router identifier from the router registry"),
|
|
6275
|
+
interface: z28.string().optional().describe("Filter by interface name (exact match)"),
|
|
6276
|
+
limit: z28.number().int().min(1).max(500).default(100).describe("Maximum number of DHCP servers to return"),
|
|
6277
|
+
offset: z28.number().int().min(0).default(0).describe("Offset for pagination")
|
|
6068
6278
|
}).strict();
|
|
6069
6279
|
var listDhcpServersTool = {
|
|
6070
6280
|
name: "list_dhcp_servers",
|
|
@@ -6079,7 +6289,7 @@ var listDhcpServersTool = {
|
|
|
6079
6289
|
},
|
|
6080
6290
|
async handler(params, context) {
|
|
6081
6291
|
const parsed = listDhcpServersInputSchema.parse(params);
|
|
6082
|
-
|
|
6292
|
+
log29.info({ routerId: context.routerId }, "Listing DHCP servers");
|
|
6083
6293
|
try {
|
|
6084
6294
|
const allServers = await context.routerClient.get("ip/dhcp-server", {
|
|
6085
6295
|
limit: void 0,
|
|
@@ -6105,15 +6315,15 @@ var listDhcpServersTool = {
|
|
|
6105
6315
|
}
|
|
6106
6316
|
}
|
|
6107
6317
|
};
|
|
6108
|
-
var manageDhcpServerInputSchema =
|
|
6109
|
-
routerId:
|
|
6110
|
-
action:
|
|
6111
|
-
name:
|
|
6112
|
-
interface:
|
|
6113
|
-
addressPool:
|
|
6114
|
-
leaseTime:
|
|
6115
|
-
comment:
|
|
6116
|
-
dryRun:
|
|
6318
|
+
var manageDhcpServerInputSchema = z28.object({
|
|
6319
|
+
routerId: z28.string().describe("Target router identifier from the router registry"),
|
|
6320
|
+
action: z28.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
|
|
6321
|
+
name: z28.string().describe("Server name \u2014 idempotency key"),
|
|
6322
|
+
interface: z28.string().optional().describe("Interface to serve DHCP on (required for add)"),
|
|
6323
|
+
addressPool: z28.string().optional().describe("IP pool name (required for add)"),
|
|
6324
|
+
leaseTime: z28.string().optional().describe("Lease duration (e.g. '1d', '12h')"),
|
|
6325
|
+
comment: z28.string().optional().describe("Optional comment"),
|
|
6326
|
+
dryRun: z28.boolean().default(false).describe("Preview changes without applying")
|
|
6117
6327
|
}).strict();
|
|
6118
6328
|
var manageDhcpServerTool = {
|
|
6119
6329
|
name: "manage_dhcp_server",
|
|
@@ -6129,7 +6339,7 @@ var manageDhcpServerTool = {
|
|
|
6129
6339
|
snapshotPaths: ["ip/dhcp-server"],
|
|
6130
6340
|
async handler(params, context) {
|
|
6131
6341
|
const parsed = manageDhcpServerInputSchema.parse(params);
|
|
6132
|
-
|
|
6342
|
+
log29.info(
|
|
6133
6343
|
{ routerId: context.routerId, action: parsed.action, name: parsed.name },
|
|
6134
6344
|
"Managing DHCP server"
|
|
6135
6345
|
);
|
|
@@ -6215,7 +6425,7 @@ var manageDhcpServerTool = {
|
|
|
6215
6425
|
if (parsed.leaseTime) body["lease-time"] = parsed.leaseTime;
|
|
6216
6426
|
if (parsed.comment) body.comment = parsed.comment;
|
|
6217
6427
|
const created = await context.routerClient.create("ip/dhcp-server", body);
|
|
6218
|
-
|
|
6428
|
+
log29.info({ name: parsed.name, id: created[".id"] }, "DHCP server added");
|
|
6219
6429
|
return {
|
|
6220
6430
|
content: `Added DHCP server "${parsed.name}".`,
|
|
6221
6431
|
structuredContent: { action: "created", server: created }
|
|
@@ -6238,7 +6448,7 @@ var manageDhcpServerTool = {
|
|
|
6238
6448
|
};
|
|
6239
6449
|
}
|
|
6240
6450
|
await context.routerClient.remove("ip/dhcp-server", existing[".id"]);
|
|
6241
|
-
|
|
6451
|
+
log29.info({ name: parsed.name }, "DHCP server removed");
|
|
6242
6452
|
return {
|
|
6243
6453
|
content: `Removed DHCP server "${parsed.name}".`,
|
|
6244
6454
|
structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
|
|
@@ -6277,7 +6487,7 @@ var manageDhcpServerTool = {
|
|
|
6277
6487
|
disabled: disabledValue
|
|
6278
6488
|
});
|
|
6279
6489
|
const resultAction = parsed.action === "disable" ? "disabled" : "enabled";
|
|
6280
|
-
|
|
6490
|
+
log29.info({ name: parsed.name, action: resultAction }, "DHCP server updated");
|
|
6281
6491
|
return {
|
|
6282
6492
|
content: `DHCP server "${parsed.name}" ${resultAction}.`,
|
|
6283
6493
|
structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
|
|
@@ -6291,13 +6501,13 @@ var manageDhcpServerTool = {
|
|
|
6291
6501
|
var dhcpServerTools = [listDhcpServersTool, manageDhcpServerTool];
|
|
6292
6502
|
|
|
6293
6503
|
// src/domain/tools/ip-pool-tools.ts
|
|
6294
|
-
import { z as
|
|
6295
|
-
var
|
|
6296
|
-
var listIpPoolsInputSchema =
|
|
6297
|
-
routerId:
|
|
6298
|
-
name:
|
|
6299
|
-
limit:
|
|
6300
|
-
offset:
|
|
6504
|
+
import { z as z29 } from "zod";
|
|
6505
|
+
var log30 = createLogger("ip-pool-tools");
|
|
6506
|
+
var listIpPoolsInputSchema = z29.object({
|
|
6507
|
+
routerId: z29.string().describe("Target router identifier from the router registry"),
|
|
6508
|
+
name: z29.string().optional().describe("Filter by pool name (substring match)"),
|
|
6509
|
+
limit: z29.number().int().min(1).max(500).default(100).describe("Maximum number of pools to return"),
|
|
6510
|
+
offset: z29.number().int().min(0).default(0).describe("Offset for pagination")
|
|
6301
6511
|
}).strict();
|
|
6302
6512
|
var listIpPoolsTool = {
|
|
6303
6513
|
name: "list_ip_pools",
|
|
@@ -6312,7 +6522,7 @@ var listIpPoolsTool = {
|
|
|
6312
6522
|
},
|
|
6313
6523
|
async handler(params, context) {
|
|
6314
6524
|
const parsed = listIpPoolsInputSchema.parse(params);
|
|
6315
|
-
|
|
6525
|
+
log30.info({ routerId: context.routerId }, "Listing IP pools");
|
|
6316
6526
|
try {
|
|
6317
6527
|
const allPools = await context.routerClient.get("ip/pool", {
|
|
6318
6528
|
limit: void 0,
|
|
@@ -6341,13 +6551,13 @@ var listIpPoolsTool = {
|
|
|
6341
6551
|
}
|
|
6342
6552
|
}
|
|
6343
6553
|
};
|
|
6344
|
-
var manageIpPoolInputSchema =
|
|
6345
|
-
routerId:
|
|
6346
|
-
action:
|
|
6347
|
-
name:
|
|
6348
|
-
ranges:
|
|
6349
|
-
nextPool:
|
|
6350
|
-
dryRun:
|
|
6554
|
+
var manageIpPoolInputSchema = z29.object({
|
|
6555
|
+
routerId: z29.string().describe("Target router identifier from the router registry"),
|
|
6556
|
+
action: z29.enum(["add", "remove"]).describe("Action to perform"),
|
|
6557
|
+
name: z29.string().describe("Pool name \u2014 idempotency key"),
|
|
6558
|
+
ranges: z29.string().optional().describe("IP range (e.g. '192.168.1.100-192.168.1.200'; required for add)"),
|
|
6559
|
+
nextPool: z29.string().optional().describe("Next pool name for overflow"),
|
|
6560
|
+
dryRun: z29.boolean().default(false).describe("Preview changes without applying")
|
|
6351
6561
|
}).strict();
|
|
6352
6562
|
var manageIpPoolTool = {
|
|
6353
6563
|
name: "manage_ip_pool",
|
|
@@ -6363,7 +6573,7 @@ var manageIpPoolTool = {
|
|
|
6363
6573
|
snapshotPaths: ["ip/pool"],
|
|
6364
6574
|
async handler(params, context) {
|
|
6365
6575
|
const parsed = manageIpPoolInputSchema.parse(params);
|
|
6366
|
-
|
|
6576
|
+
log30.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing IP pool");
|
|
6367
6577
|
try {
|
|
6368
6578
|
const allPools = await context.routerClient.get("ip/pool", {
|
|
6369
6579
|
limit: void 0,
|
|
@@ -6416,7 +6626,7 @@ var manageIpPoolTool = {
|
|
|
6416
6626
|
const body = { name: parsed.name, ranges: parsed.ranges };
|
|
6417
6627
|
if (parsed.nextPool) body["next-pool"] = parsed.nextPool;
|
|
6418
6628
|
const created = await context.routerClient.create("ip/pool", body);
|
|
6419
|
-
|
|
6629
|
+
log30.info({ name: parsed.name, id: created[".id"] }, "IP pool added");
|
|
6420
6630
|
return {
|
|
6421
6631
|
content: `Added IP pool "${parsed.name}" with ranges ${parsed.ranges}.`,
|
|
6422
6632
|
structuredContent: { action: "created", pool: created }
|
|
@@ -6435,7 +6645,7 @@ var manageIpPoolTool = {
|
|
|
6435
6645
|
};
|
|
6436
6646
|
}
|
|
6437
6647
|
await context.routerClient.remove("ip/pool", existing[".id"]);
|
|
6438
|
-
|
|
6648
|
+
log30.info({ name: parsed.name }, "IP pool removed");
|
|
6439
6649
|
return {
|
|
6440
6650
|
content: `Removed IP pool "${parsed.name}".`,
|
|
6441
6651
|
structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
|
|
@@ -6449,12 +6659,12 @@ var manageIpPoolTool = {
|
|
|
6449
6659
|
var ipPoolTools = [listIpPoolsTool, manageIpPoolTool];
|
|
6450
6660
|
|
|
6451
6661
|
// src/domain/tools/queue-tools.ts
|
|
6452
|
-
import { z as
|
|
6453
|
-
var
|
|
6454
|
-
var listQueuesInputSchema =
|
|
6455
|
-
routerId:
|
|
6456
|
-
target:
|
|
6457
|
-
limit:
|
|
6662
|
+
import { z as z30 } from "zod";
|
|
6663
|
+
var log31 = createLogger("queue-tools");
|
|
6664
|
+
var listQueuesInputSchema = z30.object({
|
|
6665
|
+
routerId: z30.string().describe("Target router identifier from the router registry"),
|
|
6666
|
+
target: z30.string().optional().describe("Filter by target address (substring match)"),
|
|
6667
|
+
limit: z30.number().int().min(1).max(500).default(100).describe("Maximum number of queues to return")
|
|
6458
6668
|
}).strict();
|
|
6459
6669
|
var listQueuesTool = {
|
|
6460
6670
|
name: "list_queues",
|
|
@@ -6469,7 +6679,7 @@ var listQueuesTool = {
|
|
|
6469
6679
|
},
|
|
6470
6680
|
async handler(params, context) {
|
|
6471
6681
|
const parsed = listQueuesInputSchema.parse(params);
|
|
6472
|
-
|
|
6682
|
+
log31.info({ routerId: context.routerId }, "Listing queues");
|
|
6473
6683
|
try {
|
|
6474
6684
|
const allQueues = await context.routerClient.get("queue/simple", {
|
|
6475
6685
|
limit: void 0,
|
|
@@ -6493,15 +6703,15 @@ var listQueuesTool = {
|
|
|
6493
6703
|
}
|
|
6494
6704
|
}
|
|
6495
6705
|
};
|
|
6496
|
-
var manageQueueInputSchema =
|
|
6497
|
-
routerId:
|
|
6498
|
-
action:
|
|
6499
|
-
name:
|
|
6500
|
-
target:
|
|
6501
|
-
maxLimit:
|
|
6502
|
-
limitAt:
|
|
6503
|
-
comment:
|
|
6504
|
-
dryRun:
|
|
6706
|
+
var manageQueueInputSchema = z30.object({
|
|
6707
|
+
routerId: z30.string().describe("Target router identifier from the router registry"),
|
|
6708
|
+
action: z30.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
|
|
6709
|
+
name: z30.string().describe("Queue name \u2014 idempotency key"),
|
|
6710
|
+
target: z30.string().optional().describe("Target address (required for add; e.g. '192.168.1.0/24')"),
|
|
6711
|
+
maxLimit: z30.string().optional().describe("Max upload/download limit (e.g. '10M/10M')"),
|
|
6712
|
+
limitAt: z30.string().optional().describe("Guaranteed rate (e.g. '1M/1M')"),
|
|
6713
|
+
comment: z30.string().optional().describe("Optional comment"),
|
|
6714
|
+
dryRun: z30.boolean().default(false).describe("Preview changes without applying")
|
|
6505
6715
|
}).strict();
|
|
6506
6716
|
var manageQueueTool = {
|
|
6507
6717
|
name: "manage_queue",
|
|
@@ -6517,7 +6727,7 @@ var manageQueueTool = {
|
|
|
6517
6727
|
snapshotPaths: ["queue/simple"],
|
|
6518
6728
|
async handler(params, context) {
|
|
6519
6729
|
const parsed = manageQueueInputSchema.parse(params);
|
|
6520
|
-
|
|
6730
|
+
log31.info(
|
|
6521
6731
|
{ routerId: context.routerId, action: parsed.action, name: parsed.name },
|
|
6522
6732
|
"Managing queue"
|
|
6523
6733
|
);
|
|
@@ -6579,7 +6789,7 @@ var manageQueueTool = {
|
|
|
6579
6789
|
if (parsed.limitAt) body["limit-at"] = parsed.limitAt;
|
|
6580
6790
|
if (parsed.comment) body.comment = parsed.comment;
|
|
6581
6791
|
const created = await context.routerClient.create("queue/simple", body);
|
|
6582
|
-
|
|
6792
|
+
log31.info({ name: parsed.name, id: created[".id"] }, "Queue added");
|
|
6583
6793
|
return {
|
|
6584
6794
|
content: `Added queue "${parsed.name}".`,
|
|
6585
6795
|
structuredContent: { action: "created", queue: created }
|
|
@@ -6602,7 +6812,7 @@ var manageQueueTool = {
|
|
|
6602
6812
|
};
|
|
6603
6813
|
}
|
|
6604
6814
|
await context.routerClient.remove("queue/simple", existing[".id"]);
|
|
6605
|
-
|
|
6815
|
+
log31.info({ name: parsed.name }, "Queue removed");
|
|
6606
6816
|
return {
|
|
6607
6817
|
content: `Removed queue "${parsed.name}".`,
|
|
6608
6818
|
structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
|
|
@@ -6641,7 +6851,7 @@ var manageQueueTool = {
|
|
|
6641
6851
|
disabled: disabledValue
|
|
6642
6852
|
});
|
|
6643
6853
|
const resultAction = parsed.action === "disable" ? "disabled" : "enabled";
|
|
6644
|
-
|
|
6854
|
+
log31.info({ name: parsed.name, action: resultAction }, "Queue updated");
|
|
6645
6855
|
return {
|
|
6646
6856
|
content: `Queue "${parsed.name}" ${resultAction}.`,
|
|
6647
6857
|
structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
|
|
@@ -6655,12 +6865,12 @@ var manageQueueTool = {
|
|
|
6655
6865
|
var queueTools = [listQueuesTool, manageQueueTool];
|
|
6656
6866
|
|
|
6657
6867
|
// src/domain/tools/vrrp-tools.ts
|
|
6658
|
-
import { z as
|
|
6659
|
-
var
|
|
6660
|
-
var listVrrpInstancesInputSchema =
|
|
6661
|
-
routerId:
|
|
6662
|
-
interface:
|
|
6663
|
-
limit:
|
|
6868
|
+
import { z as z31 } from "zod";
|
|
6869
|
+
var log32 = createLogger("vrrp-tools");
|
|
6870
|
+
var listVrrpInstancesInputSchema = z31.object({
|
|
6871
|
+
routerId: z31.string().describe("Target router identifier from the router registry"),
|
|
6872
|
+
interface: z31.string().optional().describe("Filter by master interface name (exact match)"),
|
|
6873
|
+
limit: z31.number().int().min(1).max(500).default(100).describe("Maximum number of instances to return")
|
|
6664
6874
|
}).strict();
|
|
6665
6875
|
var listVrrpInstancesTool = {
|
|
6666
6876
|
name: "list_vrrp_instances",
|
|
@@ -6675,7 +6885,7 @@ var listVrrpInstancesTool = {
|
|
|
6675
6885
|
},
|
|
6676
6886
|
async handler(params, context) {
|
|
6677
6887
|
const parsed = listVrrpInstancesInputSchema.parse(params);
|
|
6678
|
-
|
|
6888
|
+
log32.info({ routerId: context.routerId }, "Listing VRRP instances");
|
|
6679
6889
|
try {
|
|
6680
6890
|
const allInstances = await context.routerClient.get("interface/vrrp", {
|
|
6681
6891
|
limit: void 0,
|
|
@@ -6699,17 +6909,17 @@ var listVrrpInstancesTool = {
|
|
|
6699
6909
|
}
|
|
6700
6910
|
}
|
|
6701
6911
|
};
|
|
6702
|
-
var manageVrrpInstanceInputSchema =
|
|
6703
|
-
routerId:
|
|
6704
|
-
action:
|
|
6705
|
-
name:
|
|
6706
|
-
interface:
|
|
6707
|
-
vrid:
|
|
6708
|
-
priority:
|
|
6709
|
-
interval:
|
|
6710
|
-
version:
|
|
6711
|
-
comment:
|
|
6712
|
-
dryRun:
|
|
6912
|
+
var manageVrrpInstanceInputSchema = z31.object({
|
|
6913
|
+
routerId: z31.string().describe("Target router identifier from the router registry"),
|
|
6914
|
+
action: z31.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
|
|
6915
|
+
name: z31.string().describe("VRRP interface name \u2014 idempotency key"),
|
|
6916
|
+
interface: z31.string().optional().describe("Master interface (required for add)"),
|
|
6917
|
+
vrid: z31.number().int().min(1).max(255).optional().describe("Virtual router ID (required for add)"),
|
|
6918
|
+
priority: z31.number().int().min(1).max(254).default(100).describe("Router priority (1\u2013254)"),
|
|
6919
|
+
interval: z31.number().int().min(1).optional().describe("Advertisement interval in seconds"),
|
|
6920
|
+
version: z31.enum(["2", "3"]).default("3").describe("VRRP protocol version"),
|
|
6921
|
+
comment: z31.string().optional().describe("Optional comment"),
|
|
6922
|
+
dryRun: z31.boolean().default(false).describe("Preview changes without applying")
|
|
6713
6923
|
}).strict();
|
|
6714
6924
|
var manageVrrpInstanceTool = {
|
|
6715
6925
|
name: "manage_vrrp_instance",
|
|
@@ -6725,7 +6935,7 @@ var manageVrrpInstanceTool = {
|
|
|
6725
6935
|
snapshotPaths: ["interface/vrrp"],
|
|
6726
6936
|
async handler(params, context) {
|
|
6727
6937
|
const parsed = manageVrrpInstanceInputSchema.parse(params);
|
|
6728
|
-
|
|
6938
|
+
log32.info(
|
|
6729
6939
|
{ routerId: context.routerId, action: parsed.action, name: parsed.name },
|
|
6730
6940
|
"Managing VRRP instance"
|
|
6731
6941
|
);
|
|
@@ -6809,7 +7019,7 @@ var manageVrrpInstanceTool = {
|
|
|
6809
7019
|
if (parsed.interval) body.interval = String(parsed.interval);
|
|
6810
7020
|
if (parsed.comment) body.comment = parsed.comment;
|
|
6811
7021
|
const created = await context.routerClient.create("interface/vrrp", body);
|
|
6812
|
-
|
|
7022
|
+
log32.info({ name: parsed.name, id: created[".id"] }, "VRRP instance added");
|
|
6813
7023
|
return {
|
|
6814
7024
|
content: `Added VRRP instance "${parsed.name}".`,
|
|
6815
7025
|
structuredContent: { action: "created", instance: created }
|
|
@@ -6832,7 +7042,7 @@ var manageVrrpInstanceTool = {
|
|
|
6832
7042
|
};
|
|
6833
7043
|
}
|
|
6834
7044
|
await context.routerClient.remove("interface/vrrp", existing[".id"]);
|
|
6835
|
-
|
|
7045
|
+
log32.info({ name: parsed.name }, "VRRP instance removed");
|
|
6836
7046
|
return {
|
|
6837
7047
|
content: `Removed VRRP instance "${parsed.name}".`,
|
|
6838
7048
|
structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
|
|
@@ -6871,7 +7081,7 @@ var manageVrrpInstanceTool = {
|
|
|
6871
7081
|
disabled: disabledValue
|
|
6872
7082
|
});
|
|
6873
7083
|
const resultAction = parsed.action === "disable" ? "disabled" : "enabled";
|
|
6874
|
-
|
|
7084
|
+
log32.info({ name: parsed.name, action: resultAction }, "VRRP instance updated");
|
|
6875
7085
|
return {
|
|
6876
7086
|
content: `VRRP instance "${parsed.name}" ${resultAction}.`,
|
|
6877
7087
|
structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
|
|
@@ -6885,10 +7095,10 @@ var manageVrrpInstanceTool = {
|
|
|
6885
7095
|
var vrrpTools = [listVrrpInstancesTool, manageVrrpInstanceTool];
|
|
6886
7096
|
|
|
6887
7097
|
// src/domain/tools/network-services-tools.ts
|
|
6888
|
-
import { z as
|
|
6889
|
-
var
|
|
6890
|
-
var getSnmpSettingsInputSchema =
|
|
6891
|
-
routerId:
|
|
7098
|
+
import { z as z32 } from "zod";
|
|
7099
|
+
var log33 = createLogger("network-services-tools");
|
|
7100
|
+
var getSnmpSettingsInputSchema = z32.object({
|
|
7101
|
+
routerId: z32.string().describe("Target router identifier from the router registry")
|
|
6892
7102
|
}).strict();
|
|
6893
7103
|
var getSnmpSettingsTool = {
|
|
6894
7104
|
name: "get_snmp_settings",
|
|
@@ -6903,7 +7113,7 @@ var getSnmpSettingsTool = {
|
|
|
6903
7113
|
},
|
|
6904
7114
|
async handler(params, context) {
|
|
6905
7115
|
const parsed = getSnmpSettingsInputSchema.parse(params);
|
|
6906
|
-
|
|
7116
|
+
log33.info({ routerId: context.routerId }, "Fetching SNMP settings");
|
|
6907
7117
|
try {
|
|
6908
7118
|
const results2 = await context.routerClient.get("snmp");
|
|
6909
7119
|
if (!results2 || results2.length === 0) {
|
|
@@ -6932,8 +7142,8 @@ var getSnmpSettingsTool = {
|
|
|
6932
7142
|
}
|
|
6933
7143
|
}
|
|
6934
7144
|
};
|
|
6935
|
-
var getNtpSettingsInputSchema =
|
|
6936
|
-
routerId:
|
|
7145
|
+
var getNtpSettingsInputSchema = z32.object({
|
|
7146
|
+
routerId: z32.string().describe("Target router identifier from the router registry")
|
|
6937
7147
|
}).strict();
|
|
6938
7148
|
var getNtpSettingsTool = {
|
|
6939
7149
|
name: "get_ntp_settings",
|
|
@@ -6948,7 +7158,7 @@ var getNtpSettingsTool = {
|
|
|
6948
7158
|
},
|
|
6949
7159
|
async handler(params, context) {
|
|
6950
7160
|
const parsed = getNtpSettingsInputSchema.parse(params);
|
|
6951
|
-
|
|
7161
|
+
log33.info({ routerId: context.routerId }, "Fetching NTP settings");
|
|
6952
7162
|
try {
|
|
6953
7163
|
const results2 = await context.routerClient.get("system/ntp/client");
|
|
6954
7164
|
if (!results2 || results2.length === 0) {
|
|
@@ -6977,11 +7187,11 @@ var getNtpSettingsTool = {
|
|
|
6977
7187
|
}
|
|
6978
7188
|
}
|
|
6979
7189
|
};
|
|
6980
|
-
var listNetwatchEntriesInputSchema =
|
|
6981
|
-
routerId:
|
|
6982
|
-
host:
|
|
6983
|
-
status:
|
|
6984
|
-
limit:
|
|
7190
|
+
var listNetwatchEntriesInputSchema = z32.object({
|
|
7191
|
+
routerId: z32.string().describe("Target router identifier from the router registry"),
|
|
7192
|
+
host: z32.string().optional().describe("Filter by host (substring match)"),
|
|
7193
|
+
status: z32.enum(["up", "down", "unknown"]).optional().describe("Filter by current status"),
|
|
7194
|
+
limit: z32.number().int().min(1).max(500).default(100).describe("Maximum number of entries to return")
|
|
6985
7195
|
}).strict();
|
|
6986
7196
|
var listNetwatchEntriesTool = {
|
|
6987
7197
|
name: "list_netwatch_entries",
|
|
@@ -6996,7 +7206,7 @@ var listNetwatchEntriesTool = {
|
|
|
6996
7206
|
},
|
|
6997
7207
|
async handler(params, context) {
|
|
6998
7208
|
const parsed = listNetwatchEntriesInputSchema.parse(params);
|
|
6999
|
-
|
|
7209
|
+
log33.info({ routerId: context.routerId }, "Listing Netwatch entries");
|
|
7000
7210
|
try {
|
|
7001
7211
|
const allEntries = await context.routerClient.get("tool/netwatch", {
|
|
7002
7212
|
limit: void 0,
|
|
@@ -7018,15 +7228,15 @@ var listNetwatchEntriesTool = {
|
|
|
7018
7228
|
}
|
|
7019
7229
|
}
|
|
7020
7230
|
};
|
|
7021
|
-
var manageNetwatchEntryInputSchema =
|
|
7022
|
-
routerId:
|
|
7023
|
-
action:
|
|
7024
|
-
host:
|
|
7025
|
-
port:
|
|
7026
|
-
interval:
|
|
7027
|
-
timeout:
|
|
7028
|
-
comment:
|
|
7029
|
-
dryRun:
|
|
7231
|
+
var manageNetwatchEntryInputSchema = z32.object({
|
|
7232
|
+
routerId: z32.string().describe("Target router identifier from the router registry"),
|
|
7233
|
+
action: z32.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
|
|
7234
|
+
host: z32.string().describe("Host to monitor \u2014 idempotency key"),
|
|
7235
|
+
port: z32.number().int().min(1).max(65535).optional().describe("TCP port (optional; ICMP if omitted)"),
|
|
7236
|
+
interval: z32.string().optional().describe("Check interval (e.g. '1m'; default '1m')"),
|
|
7237
|
+
timeout: z32.string().optional().describe("Probe timeout (e.g. '500ms')"),
|
|
7238
|
+
comment: z32.string().optional().describe("Optional comment"),
|
|
7239
|
+
dryRun: z32.boolean().default(false).describe("Preview changes without applying")
|
|
7030
7240
|
}).strict();
|
|
7031
7241
|
var manageNetwatchEntryTool = {
|
|
7032
7242
|
name: "manage_netwatch_entry",
|
|
@@ -7042,7 +7252,7 @@ var manageNetwatchEntryTool = {
|
|
|
7042
7252
|
snapshotPaths: ["tool/netwatch"],
|
|
7043
7253
|
async handler(params, context) {
|
|
7044
7254
|
const parsed = manageNetwatchEntryInputSchema.parse(params);
|
|
7045
|
-
|
|
7255
|
+
log33.info(
|
|
7046
7256
|
{ routerId: context.routerId, action: parsed.action, host: parsed.host },
|
|
7047
7257
|
"Managing Netwatch entry"
|
|
7048
7258
|
);
|
|
@@ -7099,7 +7309,7 @@ var manageNetwatchEntryTool = {
|
|
|
7099
7309
|
if (parsed.timeout) body.timeout = parsed.timeout;
|
|
7100
7310
|
if (parsed.comment) body.comment = parsed.comment;
|
|
7101
7311
|
const created = await context.routerClient.create("tool/netwatch", body);
|
|
7102
|
-
|
|
7312
|
+
log33.info({ host: parsed.host, id: created[".id"] }, "Netwatch entry added");
|
|
7103
7313
|
return {
|
|
7104
7314
|
content: `Added Netwatch entry for "${parsed.host}".`,
|
|
7105
7315
|
structuredContent: { action: "created", entry: created }
|
|
@@ -7123,7 +7333,7 @@ var manageNetwatchEntryTool = {
|
|
|
7123
7333
|
};
|
|
7124
7334
|
}
|
|
7125
7335
|
await context.routerClient.remove("tool/netwatch", existing2[".id"]);
|
|
7126
|
-
|
|
7336
|
+
log33.info({ host: parsed.host }, "Netwatch entry removed");
|
|
7127
7337
|
return {
|
|
7128
7338
|
content: `Removed Netwatch entry for "${parsed.host}".`,
|
|
7129
7339
|
structuredContent: { action: "removed", host: parsed.host, id: existing2[".id"] }
|
|
@@ -7163,7 +7373,7 @@ var manageNetwatchEntryTool = {
|
|
|
7163
7373
|
disabled: disabledValue
|
|
7164
7374
|
});
|
|
7165
7375
|
const resultAction = parsed.action === "disable" ? "disabled" : "enabled";
|
|
7166
|
-
|
|
7376
|
+
log33.info({ host: parsed.host, action: resultAction }, "Netwatch entry updated");
|
|
7167
7377
|
return {
|
|
7168
7378
|
content: `Netwatch entry for "${parsed.host}" ${resultAction}.`,
|
|
7169
7379
|
structuredContent: { action: resultAction, host: parsed.host, id: existing[".id"] }
|
|
@@ -7174,10 +7384,10 @@ var manageNetwatchEntryTool = {
|
|
|
7174
7384
|
}
|
|
7175
7385
|
}
|
|
7176
7386
|
};
|
|
7177
|
-
var listNeighborsInputSchema =
|
|
7178
|
-
routerId:
|
|
7179
|
-
interface:
|
|
7180
|
-
limit:
|
|
7387
|
+
var listNeighborsInputSchema = z32.object({
|
|
7388
|
+
routerId: z32.string().describe("Target router identifier from the router registry"),
|
|
7389
|
+
interface: z32.string().optional().describe("Filter by interface name (substring match)"),
|
|
7390
|
+
limit: z32.number().int().min(1).max(500).default(100).describe("Maximum number of neighbors to return")
|
|
7181
7391
|
}).strict();
|
|
7182
7392
|
var listNeighborsTool = {
|
|
7183
7393
|
name: "list_neighbors",
|
|
@@ -7192,7 +7402,7 @@ var listNeighborsTool = {
|
|
|
7192
7402
|
},
|
|
7193
7403
|
async handler(params, context) {
|
|
7194
7404
|
const parsed = listNeighborsInputSchema.parse(params);
|
|
7195
|
-
|
|
7405
|
+
log33.info({ routerId: context.routerId }, "Listing neighbors");
|
|
7196
7406
|
try {
|
|
7197
7407
|
const allNeighbors = await context.routerClient.get("ip/neighbor", {
|
|
7198
7408
|
limit: void 0,
|
|
@@ -7216,12 +7426,12 @@ var listNeighborsTool = {
|
|
|
7216
7426
|
}
|
|
7217
7427
|
}
|
|
7218
7428
|
};
|
|
7219
|
-
var listArpEntriesInputSchema =
|
|
7220
|
-
routerId:
|
|
7221
|
-
interface:
|
|
7222
|
-
address:
|
|
7223
|
-
macAddress:
|
|
7224
|
-
limit:
|
|
7429
|
+
var listArpEntriesInputSchema = z32.object({
|
|
7430
|
+
routerId: z32.string().describe("Target router identifier from the router registry"),
|
|
7431
|
+
interface: z32.string().optional().describe("Filter by interface (substring match)"),
|
|
7432
|
+
address: z32.string().optional().describe("Filter by IP address (substring match)"),
|
|
7433
|
+
macAddress: z32.string().optional().describe("Filter by MAC address (substring match)"),
|
|
7434
|
+
limit: z32.number().int().min(1).max(500).default(100).describe("Maximum number of ARP entries to return")
|
|
7225
7435
|
}).strict();
|
|
7226
7436
|
var listArpEntriesTool = {
|
|
7227
7437
|
name: "list_arp_entries",
|
|
@@ -7236,7 +7446,7 @@ var listArpEntriesTool = {
|
|
|
7236
7446
|
},
|
|
7237
7447
|
async handler(params, context) {
|
|
7238
7448
|
const parsed = listArpEntriesInputSchema.parse(params);
|
|
7239
|
-
|
|
7449
|
+
log33.info({ routerId: context.routerId }, "Listing ARP entries");
|
|
7240
7450
|
try {
|
|
7241
7451
|
const allEntries = await context.routerClient.get("ip/arp", {
|
|
7242
7452
|
limit: void 0,
|
|
@@ -7260,32 +7470,105 @@ var listArpEntriesTool = {
|
|
|
7260
7470
|
}
|
|
7261
7471
|
}
|
|
7262
7472
|
};
|
|
7473
|
+
var manageNtpClientInputSchema = z32.object({
|
|
7474
|
+
routerId: z32.string().describe("Target router identifier from the router registry"),
|
|
7475
|
+
enabled: z32.boolean().optional().describe("Enable or disable the NTP client"),
|
|
7476
|
+
mode: z32.enum(["unicast", "broadcast", "multicast", "manycast"]).optional().describe("NTP client mode"),
|
|
7477
|
+
servers: z32.string().optional().describe("Comma-separated NTP server addresses"),
|
|
7478
|
+
vlanInterface: z32.string().optional().describe("VLAN interface for NTP communication"),
|
|
7479
|
+
dryRun: z32.boolean().default(false).describe("Preview changes without applying")
|
|
7480
|
+
}).strict();
|
|
7481
|
+
var manageNtpClientTool = {
|
|
7482
|
+
name: "manage_ntp_client",
|
|
7483
|
+
title: "Manage NTP Client",
|
|
7484
|
+
description: "Update NTP client settings on a MikroTik router. Idempotent: returns already_set if no changes are needed.",
|
|
7485
|
+
inputSchema: manageNtpClientInputSchema,
|
|
7486
|
+
annotations: {
|
|
7487
|
+
readOnlyHint: false,
|
|
7488
|
+
destructiveHint: false,
|
|
7489
|
+
idempotentHint: true,
|
|
7490
|
+
openWorldHint: false
|
|
7491
|
+
},
|
|
7492
|
+
snapshotPaths: ["system/ntp/client"],
|
|
7493
|
+
async handler(params, context) {
|
|
7494
|
+
const parsed = manageNtpClientInputSchema.parse(params);
|
|
7495
|
+
log33.info({ routerId: context.routerId }, "Managing NTP client settings");
|
|
7496
|
+
try {
|
|
7497
|
+
const results2 = await context.routerClient.get("system/ntp/client");
|
|
7498
|
+
const current = Array.isArray(results2) && results2.length > 0 ? results2[0] : results2;
|
|
7499
|
+
const id = current[".id"];
|
|
7500
|
+
const changes = {};
|
|
7501
|
+
const diff = [];
|
|
7502
|
+
if (parsed.enabled !== void 0 && current.enabled !== String(parsed.enabled)) {
|
|
7503
|
+
changes.enabled = String(parsed.enabled);
|
|
7504
|
+
diff.push({ property: "enabled", before: current.enabled, after: String(parsed.enabled) });
|
|
7505
|
+
}
|
|
7506
|
+
if (parsed.mode !== void 0 && current.mode !== parsed.mode) {
|
|
7507
|
+
changes.mode = parsed.mode;
|
|
7508
|
+
diff.push({ property: "mode", before: current.mode, after: parsed.mode });
|
|
7509
|
+
}
|
|
7510
|
+
if (parsed.servers !== void 0 && current.servers !== parsed.servers) {
|
|
7511
|
+
changes.servers = parsed.servers;
|
|
7512
|
+
diff.push({ property: "servers", before: current.servers, after: parsed.servers });
|
|
7513
|
+
}
|
|
7514
|
+
if (parsed.vlanInterface !== void 0 && current["vlan-interface"] !== parsed.vlanInterface) {
|
|
7515
|
+
changes["vlan-interface"] = parsed.vlanInterface;
|
|
7516
|
+
diff.push({
|
|
7517
|
+
property: "vlan-interface",
|
|
7518
|
+
before: current["vlan-interface"],
|
|
7519
|
+
after: parsed.vlanInterface
|
|
7520
|
+
});
|
|
7521
|
+
}
|
|
7522
|
+
if (diff.length === 0) {
|
|
7523
|
+
return {
|
|
7524
|
+
content: `NTP client on ${context.routerId} already matches requested configuration.`,
|
|
7525
|
+
structuredContent: { action: "already_set", routerId: context.routerId }
|
|
7526
|
+
};
|
|
7527
|
+
}
|
|
7528
|
+
if (parsed.dryRun) {
|
|
7529
|
+
return {
|
|
7530
|
+
content: `Dry run: Would update NTP client on ${context.routerId}.`,
|
|
7531
|
+
structuredContent: { action: "dry_run", diff }
|
|
7532
|
+
};
|
|
7533
|
+
}
|
|
7534
|
+
await context.routerClient.update("system/ntp/client", id, changes);
|
|
7535
|
+
log33.info({ routerId: context.routerId }, "NTP client updated");
|
|
7536
|
+
return {
|
|
7537
|
+
content: `NTP client on ${context.routerId} updated.`,
|
|
7538
|
+
structuredContent: { action: "updated", routerId: context.routerId, diff }
|
|
7539
|
+
};
|
|
7540
|
+
} catch (err) {
|
|
7541
|
+
throw enrichError(err, { routerId: context.routerId, tool: "manage_ntp_client" });
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7544
|
+
};
|
|
7263
7545
|
var networkServicesTools = [
|
|
7264
7546
|
getSnmpSettingsTool,
|
|
7265
7547
|
getNtpSettingsTool,
|
|
7266
7548
|
listNetwatchEntriesTool,
|
|
7267
7549
|
manageNetwatchEntryTool,
|
|
7268
7550
|
listNeighborsTool,
|
|
7269
|
-
listArpEntriesTool
|
|
7551
|
+
listArpEntriesTool,
|
|
7552
|
+
manageNtpClientTool
|
|
7270
7553
|
];
|
|
7271
7554
|
|
|
7272
7555
|
// src/domain/tools/vlan-tools.ts
|
|
7273
|
-
import { z as
|
|
7274
|
-
var
|
|
7556
|
+
import { z as z33 } from "zod";
|
|
7557
|
+
var log34 = createLogger("vlan-tools");
|
|
7275
7558
|
function sanitizeComment4(comment) {
|
|
7276
7559
|
if (comment === void 0) return void 0;
|
|
7277
7560
|
return comment.replace(/[\x00-\x1f\x7f]/g, "");
|
|
7278
7561
|
}
|
|
7279
|
-
var manageVlanInputSchema =
|
|
7280
|
-
routerId:
|
|
7281
|
-
action:
|
|
7282
|
-
name:
|
|
7283
|
-
vlanId:
|
|
7284
|
-
parentInterface:
|
|
7285
|
-
mtu:
|
|
7286
|
-
disabled:
|
|
7287
|
-
comment:
|
|
7288
|
-
dryRun:
|
|
7562
|
+
var manageVlanInputSchema = z33.object({
|
|
7563
|
+
routerId: z33.string().describe("Target router identifier from the router registry"),
|
|
7564
|
+
action: z33.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
|
|
7565
|
+
name: z33.string().regex(/^[a-zA-Z0-9_-]+$/).max(15).describe("VLAN interface name \u2014 idempotency key (alphanumeric, hyphens, underscores, max 15 chars)"),
|
|
7566
|
+
vlanId: z33.number().int().min(1).max(4094).optional().describe("VLAN ID (1-4094; required for add)"),
|
|
7567
|
+
parentInterface: z33.string().optional().describe("Parent interface name (e.g., ether1, bridge1; required for add)"),
|
|
7568
|
+
mtu: z33.number().int().min(68).max(9e3).default(1500).describe("MTU size (add only)"),
|
|
7569
|
+
disabled: z33.boolean().default(false).describe("Whether to create the VLAN disabled (add only)"),
|
|
7570
|
+
comment: z33.string().max(255).optional().describe("Optional comment (add only)"),
|
|
7571
|
+
dryRun: z33.boolean().default(false).describe("Preview changes without applying")
|
|
7289
7572
|
}).strict();
|
|
7290
7573
|
var manageVlanTool = {
|
|
7291
7574
|
name: "manage_vlan",
|
|
@@ -7302,7 +7585,7 @@ var manageVlanTool = {
|
|
|
7302
7585
|
async handler(params, context) {
|
|
7303
7586
|
const parsed = manageVlanInputSchema.parse(params);
|
|
7304
7587
|
const comment = sanitizeComment4(parsed.comment);
|
|
7305
|
-
|
|
7588
|
+
log34.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing VLAN");
|
|
7306
7589
|
try {
|
|
7307
7590
|
const allVlans = await context.routerClient.get("interface/vlan", {
|
|
7308
7591
|
limit: void 0,
|
|
@@ -7330,7 +7613,7 @@ var manageVlanTool = {
|
|
|
7330
7613
|
}
|
|
7331
7614
|
if (existing) {
|
|
7332
7615
|
if (existing["vlan-id"] === String(parsed.vlanId) && existing.interface === parsed.parentInterface) {
|
|
7333
|
-
|
|
7616
|
+
log34.info({ name: parsed.name }, "VLAN already exists with matching config");
|
|
7334
7617
|
return {
|
|
7335
7618
|
content: `VLAN "${parsed.name}" already exists with VLAN ID ${parsed.vlanId} on ${parsed.parentInterface}. No changes made.`,
|
|
7336
7619
|
structuredContent: { action: "already_exists", vlan: existing }
|
|
@@ -7374,7 +7657,7 @@ var manageVlanTool = {
|
|
|
7374
7657
|
};
|
|
7375
7658
|
if (comment) body.comment = comment;
|
|
7376
7659
|
const created = await context.routerClient.create("interface/vlan", body);
|
|
7377
|
-
|
|
7660
|
+
log34.info({ name: parsed.name, id: created[".id"] }, "VLAN created");
|
|
7378
7661
|
return {
|
|
7379
7662
|
content: `Created VLAN "${parsed.name}" (ID ${parsed.vlanId}) on ${parsed.parentInterface} with MTU ${parsed.mtu}.`,
|
|
7380
7663
|
structuredContent: { action: "created", vlan: created }
|
|
@@ -7394,7 +7677,7 @@ var manageVlanTool = {
|
|
|
7394
7677
|
};
|
|
7395
7678
|
}
|
|
7396
7679
|
await context.routerClient.remove("interface/vlan", existing[".id"]);
|
|
7397
|
-
|
|
7680
|
+
log34.info({ name: parsed.name }, "VLAN removed");
|
|
7398
7681
|
return {
|
|
7399
7682
|
content: `Removed VLAN "${parsed.name}".`,
|
|
7400
7683
|
structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
|
|
@@ -7425,7 +7708,7 @@ var manageVlanTool = {
|
|
|
7425
7708
|
const disabledValue = parsed.action === "disable" ? "true" : "false";
|
|
7426
7709
|
await context.routerClient.update("interface/vlan", existing[".id"], { disabled: disabledValue });
|
|
7427
7710
|
const resultAction = parsed.action === "disable" ? "disabled" : "enabled";
|
|
7428
|
-
|
|
7711
|
+
log34.info({ name: parsed.name, action: resultAction }, "VLAN updated");
|
|
7429
7712
|
return {
|
|
7430
7713
|
content: `VLAN "${parsed.name}" ${resultAction}.`,
|
|
7431
7714
|
structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
|
|
@@ -7439,14 +7722,14 @@ var manageVlanTool = {
|
|
|
7439
7722
|
var vlanTools = [manageVlanTool];
|
|
7440
7723
|
|
|
7441
7724
|
// src/domain/tools/dhcp-client-tools.ts
|
|
7442
|
-
import { z as
|
|
7443
|
-
var
|
|
7444
|
-
var listDhcpClientsInputSchema =
|
|
7445
|
-
routerId:
|
|
7446
|
-
interface:
|
|
7447
|
-
status:
|
|
7448
|
-
limit:
|
|
7449
|
-
offset:
|
|
7725
|
+
import { z as z34 } from "zod";
|
|
7726
|
+
var log35 = createLogger("dhcp-client-tools");
|
|
7727
|
+
var listDhcpClientsInputSchema = z34.object({
|
|
7728
|
+
routerId: z34.string().describe("Target router identifier from the router registry"),
|
|
7729
|
+
interface: z34.string().optional().describe("Filter by interface name (exact match)"),
|
|
7730
|
+
status: z34.enum(["bound", "searching", "requesting", "init", "all"]).default("all").describe("Filter by DHCP client status"),
|
|
7731
|
+
limit: z34.number().int().min(1).max(500).default(100).describe("Maximum number of clients to return"),
|
|
7732
|
+
offset: z34.number().int().min(0).default(0).describe("Offset for pagination")
|
|
7450
7733
|
}).strict();
|
|
7451
7734
|
var listDhcpClientsTool = {
|
|
7452
7735
|
name: "list_dhcp_clients",
|
|
@@ -7461,7 +7744,7 @@ var listDhcpClientsTool = {
|
|
|
7461
7744
|
},
|
|
7462
7745
|
async handler(params, context) {
|
|
7463
7746
|
const parsed = listDhcpClientsInputSchema.parse(params);
|
|
7464
|
-
|
|
7747
|
+
log35.info({ routerId: context.routerId }, "Listing DHCP clients");
|
|
7465
7748
|
try {
|
|
7466
7749
|
const allClients = await context.routerClient.get("ip/dhcp-client", {
|
|
7467
7750
|
limit: void 0,
|
|
@@ -7504,15 +7787,15 @@ var listDhcpClientsTool = {
|
|
|
7504
7787
|
}
|
|
7505
7788
|
}
|
|
7506
7789
|
};
|
|
7507
|
-
var manageDhcpClientInputSchema =
|
|
7508
|
-
routerId:
|
|
7509
|
-
action:
|
|
7510
|
-
interface:
|
|
7511
|
-
usePeerDns:
|
|
7512
|
-
usePeerNtp:
|
|
7513
|
-
addDefaultRoute:
|
|
7514
|
-
comment:
|
|
7515
|
-
dryRun:
|
|
7790
|
+
var manageDhcpClientInputSchema = z34.object({
|
|
7791
|
+
routerId: z34.string().describe("Target router identifier from the router registry"),
|
|
7792
|
+
action: z34.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
|
|
7793
|
+
interface: z34.string().describe("Interface name \u2014 idempotency key (e.g. ether1, ether2)"),
|
|
7794
|
+
usePeerDns: z34.boolean().default(true).describe("Use DNS servers provided by DHCP server (add only)"),
|
|
7795
|
+
usePeerNtp: z34.boolean().default(false).describe("Use NTP servers provided by DHCP server (add only)"),
|
|
7796
|
+
addDefaultRoute: z34.boolean().default(true).describe("Add default route from DHCP (add only)"),
|
|
7797
|
+
comment: z34.string().max(255).optional().describe("Optional comment (add only)"),
|
|
7798
|
+
dryRun: z34.boolean().default(false).describe("Preview changes without applying")
|
|
7516
7799
|
}).strict();
|
|
7517
7800
|
var manageDhcpClientTool = {
|
|
7518
7801
|
name: "manage_dhcp_client",
|
|
@@ -7528,7 +7811,7 @@ var manageDhcpClientTool = {
|
|
|
7528
7811
|
snapshotPaths: ["ip/dhcp-client"],
|
|
7529
7812
|
async handler(params, context) {
|
|
7530
7813
|
const parsed = manageDhcpClientInputSchema.parse(params);
|
|
7531
|
-
|
|
7814
|
+
log35.info({ routerId: context.routerId, action: parsed.action, interface: parsed.interface }, "Managing DHCP client");
|
|
7532
7815
|
try {
|
|
7533
7816
|
const allClients = await context.routerClient.get("ip/dhcp-client", {
|
|
7534
7817
|
limit: void 0,
|
|
@@ -7564,7 +7847,7 @@ var manageDhcpClientTool = {
|
|
|
7564
7847
|
};
|
|
7565
7848
|
if (parsed.comment) body.comment = parsed.comment;
|
|
7566
7849
|
const created = await context.routerClient.create("ip/dhcp-client", body);
|
|
7567
|
-
|
|
7850
|
+
log35.info({ interface: parsed.interface, id: created[".id"] }, "DHCP client added");
|
|
7568
7851
|
return {
|
|
7569
7852
|
content: `Added DHCP client on "${parsed.interface}".`,
|
|
7570
7853
|
structuredContent: { action: "created", client: created }
|
|
@@ -7587,7 +7870,7 @@ var manageDhcpClientTool = {
|
|
|
7587
7870
|
};
|
|
7588
7871
|
}
|
|
7589
7872
|
await context.routerClient.remove("ip/dhcp-client", existing[".id"]);
|
|
7590
|
-
|
|
7873
|
+
log35.info({ interface: parsed.interface }, "DHCP client removed");
|
|
7591
7874
|
return {
|
|
7592
7875
|
content: `Removed DHCP client on "${parsed.interface}".`,
|
|
7593
7876
|
structuredContent: { action: "removed", interface: parsed.interface, id: existing[".id"] }
|
|
@@ -7624,7 +7907,7 @@ var manageDhcpClientTool = {
|
|
|
7624
7907
|
const disabledValue = parsed.action === "disable" ? "true" : "false";
|
|
7625
7908
|
await context.routerClient.update("ip/dhcp-client", existing[".id"], { disabled: disabledValue });
|
|
7626
7909
|
const resultAction = parsed.action === "disable" ? "disabled" : "enabled";
|
|
7627
|
-
|
|
7910
|
+
log35.info({ interface: parsed.interface, action: resultAction }, "DHCP client updated");
|
|
7628
7911
|
return {
|
|
7629
7912
|
content: `DHCP client on "${parsed.interface}" ${resultAction}.`,
|
|
7630
7913
|
structuredContent: { action: resultAction, interface: parsed.interface, id: existing[".id"] }
|
|
@@ -7638,13 +7921,13 @@ var manageDhcpClientTool = {
|
|
|
7638
7921
|
var dhcpClientTools = [listDhcpClientsTool, manageDhcpClientTool];
|
|
7639
7922
|
|
|
7640
7923
|
// src/domain/tools/ip-service-tools.ts
|
|
7641
|
-
import { z as
|
|
7642
|
-
var
|
|
7924
|
+
import { z as z35 } from "zod";
|
|
7925
|
+
var log36 = createLogger("ip-service-tools");
|
|
7643
7926
|
var SERVICE_NAMES = ["api", "api-ssl", "ssh", "telnet", "www", "www-ssl", "winbox", "ftp"];
|
|
7644
|
-
var listIpServicesInputSchema =
|
|
7645
|
-
routerId:
|
|
7646
|
-
name:
|
|
7647
|
-
enabled:
|
|
7927
|
+
var listIpServicesInputSchema = z35.object({
|
|
7928
|
+
routerId: z35.string().describe("Target router identifier from the router registry"),
|
|
7929
|
+
name: z35.enum(SERVICE_NAMES).optional().describe("Filter by service name (api, api-ssl, ssh, telnet, www, www-ssl, winbox, ftp)"),
|
|
7930
|
+
enabled: z35.boolean().optional().describe("When true, return only enabled services; when false, only disabled services")
|
|
7648
7931
|
}).strict();
|
|
7649
7932
|
var listIpServicesTool = {
|
|
7650
7933
|
name: "list_ip_services",
|
|
@@ -7659,7 +7942,7 @@ var listIpServicesTool = {
|
|
|
7659
7942
|
},
|
|
7660
7943
|
async handler(params, context) {
|
|
7661
7944
|
const parsed = listIpServicesInputSchema.parse(params);
|
|
7662
|
-
|
|
7945
|
+
log36.info({ routerId: context.routerId }, "Listing IP services");
|
|
7663
7946
|
try {
|
|
7664
7947
|
const allServices = await context.routerClient.get("ip/service", {
|
|
7665
7948
|
limit: void 0,
|
|
@@ -7694,13 +7977,13 @@ var listIpServicesTool = {
|
|
|
7694
7977
|
}
|
|
7695
7978
|
}
|
|
7696
7979
|
};
|
|
7697
|
-
var manageIpServiceInputSchema =
|
|
7698
|
-
routerId:
|
|
7699
|
-
action:
|
|
7980
|
+
var manageIpServiceInputSchema = z35.object({
|
|
7981
|
+
routerId: z35.string().describe("Target router identifier from the router registry"),
|
|
7982
|
+
action: z35.enum(["enable", "disable"]).describe(
|
|
7700
7983
|
"Action to perform \u2014 only enable/disable to prevent accidental lockout from changing ports"
|
|
7701
7984
|
),
|
|
7702
|
-
name:
|
|
7703
|
-
dryRun:
|
|
7985
|
+
name: z35.enum(SERVICE_NAMES).describe("Service name to manage (api, api-ssl, ssh, telnet, www, www-ssl, winbox, ftp)"),
|
|
7986
|
+
dryRun: z35.boolean().default(false).describe("Preview changes without applying")
|
|
7704
7987
|
}).strict();
|
|
7705
7988
|
var manageIpServiceTool = {
|
|
7706
7989
|
name: "manage_ip_service",
|
|
@@ -7715,7 +7998,7 @@ var manageIpServiceTool = {
|
|
|
7715
7998
|
},
|
|
7716
7999
|
async handler(params, context) {
|
|
7717
8000
|
const parsed = manageIpServiceInputSchema.parse(params);
|
|
7718
|
-
|
|
8001
|
+
log36.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing IP service");
|
|
7719
8002
|
try {
|
|
7720
8003
|
const allServices = await context.routerClient.get("ip/service", {
|
|
7721
8004
|
limit: void 0,
|
|
@@ -7762,7 +8045,7 @@ var manageIpServiceTool = {
|
|
|
7762
8045
|
const disabledValue = parsed.action === "disable" ? "true" : "false";
|
|
7763
8046
|
await context.routerClient.update("ip/service", existing[".id"], { disabled: disabledValue });
|
|
7764
8047
|
const resultAction = parsed.action === "disable" ? "disabled" : "enabled";
|
|
7765
|
-
|
|
8048
|
+
log36.info({ name: parsed.name, action: resultAction }, "IP service updated");
|
|
7766
8049
|
return {
|
|
7767
8050
|
content: `IP service "${parsed.name}" ${resultAction}.`,
|
|
7768
8051
|
structuredContent: { action: resultAction, name: parsed.name, id: existing[".id"] }
|
|
@@ -7776,16 +8059,16 @@ var manageIpServiceTool = {
|
|
|
7776
8059
|
var ipServiceTools = [listIpServicesTool, manageIpServiceTool];
|
|
7777
8060
|
|
|
7778
8061
|
// src/domain/tools/pppoe-tools.ts
|
|
7779
|
-
import { z as
|
|
7780
|
-
var
|
|
8062
|
+
import { z as z36 } from "zod";
|
|
8063
|
+
var log37 = createLogger("pppoe-tools");
|
|
7781
8064
|
var PPPOE_PATH = "interface/pppoe-client";
|
|
7782
|
-
var listPppoeClientsInputSchema =
|
|
7783
|
-
routerId:
|
|
7784
|
-
interface:
|
|
7785
|
-
status:
|
|
7786
|
-
limit:
|
|
7787
|
-
offset:
|
|
7788
|
-
}).strict();
|
|
8065
|
+
var listPppoeClientsInputSchema = z36.object({
|
|
8066
|
+
routerId: z36.string().describe("Target router identifier from the router registry"),
|
|
8067
|
+
interface: z36.string().optional().describe("Filter by parent interface name (exact match)"),
|
|
8068
|
+
status: z36.enum(["connected", "disconnected", "all"]).default("all").describe("Filter by running status"),
|
|
8069
|
+
limit: z36.number().int().min(1).max(500).default(100).describe("Maximum number of clients to return"),
|
|
8070
|
+
offset: z36.number().int().min(0).default(0).describe("Offset for pagination")
|
|
8071
|
+
}).strict();
|
|
7789
8072
|
function isRunning(record) {
|
|
7790
8073
|
return record.running === true || record.running === "true";
|
|
7791
8074
|
}
|
|
@@ -7802,7 +8085,7 @@ var listPppoeClientsTool = {
|
|
|
7802
8085
|
},
|
|
7803
8086
|
async handler(params, context) {
|
|
7804
8087
|
const parsed = listPppoeClientsInputSchema.parse(params);
|
|
7805
|
-
|
|
8088
|
+
log37.info({ routerId: context.routerId }, "Listing PPPoE clients");
|
|
7806
8089
|
try {
|
|
7807
8090
|
const allClients = await context.routerClient.get(PPPOE_PATH, {
|
|
7808
8091
|
limit: void 0,
|
|
@@ -7846,17 +8129,17 @@ var listPppoeClientsTool = {
|
|
|
7846
8129
|
}
|
|
7847
8130
|
}
|
|
7848
8131
|
};
|
|
7849
|
-
var managePppoeClientInputSchema =
|
|
7850
|
-
routerId:
|
|
7851
|
-
action:
|
|
7852
|
-
name:
|
|
7853
|
-
interface:
|
|
7854
|
-
user:
|
|
7855
|
-
password:
|
|
7856
|
-
serviceName:
|
|
7857
|
-
addDefaultRoute:
|
|
7858
|
-
dialOnDemand:
|
|
7859
|
-
dryRun:
|
|
8132
|
+
var managePppoeClientInputSchema = z36.object({
|
|
8133
|
+
routerId: z36.string().describe("Target router identifier from the router registry"),
|
|
8134
|
+
action: z36.enum(["add", "update", "remove"]).describe("Action to perform"),
|
|
8135
|
+
name: z36.string().describe("PPPoE client interface name \u2014 idempotency key (e.g. pppoe-wan)"),
|
|
8136
|
+
interface: z36.string().optional().describe("Parent interface (required for add)"),
|
|
8137
|
+
user: z36.string().optional().describe("PPPoE username (required for add)"),
|
|
8138
|
+
password: z36.string().optional().describe("PPPoE password (never logged)"),
|
|
8139
|
+
serviceName: z36.string().optional().describe("PPPoE service name filter (leave empty to match any)"),
|
|
8140
|
+
addDefaultRoute: z36.boolean().optional().describe("Add default route via PPPoE (yes/no)"),
|
|
8141
|
+
dialOnDemand: z36.boolean().optional().describe("Dial on demand instead of always-on (yes/no)"),
|
|
8142
|
+
dryRun: z36.boolean().default(false).describe("Preview changes without applying")
|
|
7860
8143
|
}).strict();
|
|
7861
8144
|
var managePppoeClientTool = {
|
|
7862
8145
|
name: "manage_pppoe_client",
|
|
@@ -7872,7 +8155,7 @@ var managePppoeClientTool = {
|
|
|
7872
8155
|
snapshotPaths: [PPPOE_PATH],
|
|
7873
8156
|
async handler(params, context) {
|
|
7874
8157
|
const parsed = managePppoeClientInputSchema.parse(params);
|
|
7875
|
-
|
|
8158
|
+
log37.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing PPPoE client");
|
|
7876
8159
|
try {
|
|
7877
8160
|
const allClients = await context.routerClient.get(PPPOE_PATH, {
|
|
7878
8161
|
limit: void 0,
|
|
@@ -7959,7 +8242,7 @@ var managePppoeClientTool = {
|
|
|
7959
8242
|
if (parsed.addDefaultRoute !== void 0) body["add-default-route"] = parsed.addDefaultRoute ? "yes" : "no";
|
|
7960
8243
|
if (parsed.dialOnDemand !== void 0) body["dial-on-demand"] = parsed.dialOnDemand ? "yes" : "no";
|
|
7961
8244
|
const created = await context.routerClient.create(PPPOE_PATH, body);
|
|
7962
|
-
|
|
8245
|
+
log37.info({ name: parsed.name, id: created[".id"] }, "PPPoE client created");
|
|
7963
8246
|
return {
|
|
7964
8247
|
content: `Created PPPoE client "${parsed.name}" on "${parsed.interface}".`,
|
|
7965
8248
|
structuredContent: { action: "created", client: created }
|
|
@@ -8024,7 +8307,7 @@ var managePppoeClientTool = {
|
|
|
8024
8307
|
};
|
|
8025
8308
|
}
|
|
8026
8309
|
await context.routerClient.update(PPPOE_PATH, existing[".id"], updates);
|
|
8027
|
-
|
|
8310
|
+
log37.info({ name: parsed.name }, "PPPoE client updated");
|
|
8028
8311
|
return {
|
|
8029
8312
|
content: `Updated PPPoE client "${parsed.name}".`,
|
|
8030
8313
|
structuredContent: { action: "updated", name: parsed.name, diff }
|
|
@@ -8046,7 +8329,7 @@ var managePppoeClientTool = {
|
|
|
8046
8329
|
};
|
|
8047
8330
|
}
|
|
8048
8331
|
await context.routerClient.remove(PPPOE_PATH, existing[".id"]);
|
|
8049
|
-
|
|
8332
|
+
log37.info({ name: parsed.name }, "PPPoE client removed");
|
|
8050
8333
|
return {
|
|
8051
8334
|
content: `Removed PPPoE client "${parsed.name}".`,
|
|
8052
8335
|
structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
|
|
@@ -8060,14 +8343,14 @@ var managePppoeClientTool = {
|
|
|
8060
8343
|
var pppoeTools = [listPppoeClientsTool, managePppoeClientTool];
|
|
8061
8344
|
|
|
8062
8345
|
// src/domain/tools/ovpn-tools.ts
|
|
8063
|
-
import { z as
|
|
8064
|
-
var
|
|
8346
|
+
import { z as z37 } from "zod";
|
|
8347
|
+
var log38 = createLogger("ovpn-tools");
|
|
8065
8348
|
var OVPN_CLIENT_PATH = "interface/ovpn-client";
|
|
8066
8349
|
var OVPN_SERVER_PATH = "interface/ovpn-server/server";
|
|
8067
|
-
var listOvpnClientsInputSchema =
|
|
8068
|
-
routerId:
|
|
8069
|
-
limit:
|
|
8070
|
-
offset:
|
|
8350
|
+
var listOvpnClientsInputSchema = z37.object({
|
|
8351
|
+
routerId: z37.string().describe("Target router identifier from the router registry"),
|
|
8352
|
+
limit: z37.number().int().min(1).max(500).default(100).describe("Maximum number of clients to return"),
|
|
8353
|
+
offset: z37.number().int().min(0).default(0).describe("Offset for pagination")
|
|
8071
8354
|
}).strict();
|
|
8072
8355
|
var listOvpnClientsTool = {
|
|
8073
8356
|
name: "list_ovpn_clients",
|
|
@@ -8082,7 +8365,7 @@ var listOvpnClientsTool = {
|
|
|
8082
8365
|
},
|
|
8083
8366
|
async handler(params, context) {
|
|
8084
8367
|
const parsed = listOvpnClientsInputSchema.parse(params);
|
|
8085
|
-
|
|
8368
|
+
log38.info({ routerId: context.routerId }, "Listing OpenVPN clients");
|
|
8086
8369
|
try {
|
|
8087
8370
|
const allClients = await context.routerClient.get(OVPN_CLIENT_PATH, {
|
|
8088
8371
|
limit: void 0,
|
|
@@ -8107,18 +8390,18 @@ var listOvpnClientsTool = {
|
|
|
8107
8390
|
}
|
|
8108
8391
|
}
|
|
8109
8392
|
};
|
|
8110
|
-
var manageOvpnClientInputSchema =
|
|
8111
|
-
routerId:
|
|
8112
|
-
action:
|
|
8113
|
-
name:
|
|
8114
|
-
connectTo:
|
|
8115
|
-
port:
|
|
8116
|
-
mode:
|
|
8117
|
-
protocol:
|
|
8118
|
-
certificate:
|
|
8119
|
-
user:
|
|
8120
|
-
password:
|
|
8121
|
-
dryRun:
|
|
8393
|
+
var manageOvpnClientInputSchema = z37.object({
|
|
8394
|
+
routerId: z37.string().describe("Target router identifier from the router registry"),
|
|
8395
|
+
action: z37.enum(["add", "update", "remove"]).describe("Action to perform"),
|
|
8396
|
+
name: z37.string().describe("OpenVPN client interface name \u2014 idempotency key"),
|
|
8397
|
+
connectTo: z37.string().optional().describe("Remote server address (required for add)"),
|
|
8398
|
+
port: z37.number().int().min(1).max(65535).optional().describe("Remote port number"),
|
|
8399
|
+
mode: z37.enum(["ip", "ethernet"]).optional().describe("Tunnel mode"),
|
|
8400
|
+
protocol: z37.enum(["tcp-client", "udp"]).optional().describe("Transport protocol"),
|
|
8401
|
+
certificate: z37.string().optional().describe("Client certificate name"),
|
|
8402
|
+
user: z37.string().optional().describe("VPN username"),
|
|
8403
|
+
password: z37.string().optional().describe("VPN password (never logged)"),
|
|
8404
|
+
dryRun: z37.boolean().default(false).describe("Preview changes without applying")
|
|
8122
8405
|
}).strict();
|
|
8123
8406
|
var manageOvpnClientTool = {
|
|
8124
8407
|
name: "manage_ovpn_client",
|
|
@@ -8134,7 +8417,7 @@ var manageOvpnClientTool = {
|
|
|
8134
8417
|
snapshotPaths: [OVPN_CLIENT_PATH],
|
|
8135
8418
|
async handler(params, context) {
|
|
8136
8419
|
const parsed = manageOvpnClientInputSchema.parse(params);
|
|
8137
|
-
|
|
8420
|
+
log38.info({ routerId: context.routerId, action: parsed.action, name: parsed.name }, "Managing OpenVPN client");
|
|
8138
8421
|
try {
|
|
8139
8422
|
const allClients = await context.routerClient.get(OVPN_CLIENT_PATH, {
|
|
8140
8423
|
limit: void 0,
|
|
@@ -8199,7 +8482,7 @@ var manageOvpnClientTool = {
|
|
|
8199
8482
|
if (parsed.user !== void 0) body.user = parsed.user;
|
|
8200
8483
|
if (parsed.password !== void 0) body.password = parsed.password;
|
|
8201
8484
|
const created = await context.routerClient.create(OVPN_CLIENT_PATH, body);
|
|
8202
|
-
|
|
8485
|
+
log38.info({ name: parsed.name, id: created[".id"] }, "OpenVPN client created");
|
|
8203
8486
|
return {
|
|
8204
8487
|
content: `Created OpenVPN client "${parsed.name}" connecting to "${parsed.connectTo}".`,
|
|
8205
8488
|
structuredContent: { action: "created", client: created }
|
|
@@ -8262,7 +8545,7 @@ var manageOvpnClientTool = {
|
|
|
8262
8545
|
};
|
|
8263
8546
|
}
|
|
8264
8547
|
await context.routerClient.update(OVPN_CLIENT_PATH, existing[".id"], updates);
|
|
8265
|
-
|
|
8548
|
+
log38.info({ name: parsed.name }, "OpenVPN client updated");
|
|
8266
8549
|
return {
|
|
8267
8550
|
content: `Updated OpenVPN client "${parsed.name}".`,
|
|
8268
8551
|
structuredContent: { action: "updated", name: parsed.name, diff }
|
|
@@ -8284,7 +8567,7 @@ var manageOvpnClientTool = {
|
|
|
8284
8567
|
};
|
|
8285
8568
|
}
|
|
8286
8569
|
await context.routerClient.remove(OVPN_CLIENT_PATH, existing[".id"]);
|
|
8287
|
-
|
|
8570
|
+
log38.info({ name: parsed.name }, "OpenVPN client removed");
|
|
8288
8571
|
return {
|
|
8289
8572
|
content: `Removed OpenVPN client "${parsed.name}".`,
|
|
8290
8573
|
structuredContent: { action: "removed", name: parsed.name, id: existing[".id"] }
|
|
@@ -8295,8 +8578,8 @@ var manageOvpnClientTool = {
|
|
|
8295
8578
|
}
|
|
8296
8579
|
}
|
|
8297
8580
|
};
|
|
8298
|
-
var getOvpnServerInputSchema =
|
|
8299
|
-
routerId:
|
|
8581
|
+
var getOvpnServerInputSchema = z37.object({
|
|
8582
|
+
routerId: z37.string().describe("Target router identifier from the router registry")
|
|
8300
8583
|
}).strict();
|
|
8301
8584
|
function fetchOvpnServer(allRecords, routerId) {
|
|
8302
8585
|
if (allRecords.length === 0) {
|
|
@@ -8327,7 +8610,7 @@ var getOvpnServerTool = {
|
|
|
8327
8610
|
},
|
|
8328
8611
|
async handler(params, context) {
|
|
8329
8612
|
const parsed = getOvpnServerInputSchema.parse(params);
|
|
8330
|
-
|
|
8613
|
+
log38.info({ routerId: context.routerId }, "Getting OpenVPN server configuration");
|
|
8331
8614
|
try {
|
|
8332
8615
|
const records = await context.routerClient.get(OVPN_SERVER_PATH, {
|
|
8333
8616
|
limit: void 0,
|
|
@@ -8344,16 +8627,16 @@ var getOvpnServerTool = {
|
|
|
8344
8627
|
}
|
|
8345
8628
|
}
|
|
8346
8629
|
};
|
|
8347
|
-
var manageOvpnServerInputSchema =
|
|
8348
|
-
routerId:
|
|
8349
|
-
action:
|
|
8350
|
-
port:
|
|
8351
|
-
mode:
|
|
8352
|
-
protocol:
|
|
8353
|
-
certificate:
|
|
8354
|
-
cipher:
|
|
8355
|
-
auth:
|
|
8356
|
-
dryRun:
|
|
8630
|
+
var manageOvpnServerInputSchema = z37.object({
|
|
8631
|
+
routerId: z37.string().describe("Target router identifier from the router registry"),
|
|
8632
|
+
action: z37.enum(["enable", "disable", "set"]).describe("Action to perform"),
|
|
8633
|
+
port: z37.number().int().min(1).max(65535).optional().describe("Listening port (set action only)"),
|
|
8634
|
+
mode: z37.enum(["ip", "ethernet"]).optional().describe("Tunnel mode (set action only)"),
|
|
8635
|
+
protocol: z37.enum(["tcp-server", "udp"]).optional().describe("Transport protocol (set action only)"),
|
|
8636
|
+
certificate: z37.string().optional().describe("Server certificate name (set action only)"),
|
|
8637
|
+
cipher: z37.enum(["blowfish128", "aes128-cbc", "aes192-cbc", "aes256-cbc", "aes128-gcm", "aes256-gcm", "none"]).optional().describe("Encryption cipher (set action only)"),
|
|
8638
|
+
auth: z37.enum(["md5", "sha1", "sha256", "sha512", "null"]).optional().describe("Authentication algorithm (set action only)"),
|
|
8639
|
+
dryRun: z37.boolean().default(false).describe("Preview changes without applying")
|
|
8357
8640
|
}).strict();
|
|
8358
8641
|
var manageOvpnServerTool = {
|
|
8359
8642
|
name: "manage_ovpn_server",
|
|
@@ -8369,7 +8652,7 @@ var manageOvpnServerTool = {
|
|
|
8369
8652
|
snapshotPaths: [OVPN_SERVER_PATH],
|
|
8370
8653
|
async handler(params, context) {
|
|
8371
8654
|
const parsed = manageOvpnServerInputSchema.parse(params);
|
|
8372
|
-
|
|
8655
|
+
log38.info({ routerId: context.routerId, action: parsed.action }, "Managing OpenVPN server");
|
|
8373
8656
|
try {
|
|
8374
8657
|
const records = await context.routerClient.get(OVPN_SERVER_PATH, {
|
|
8375
8658
|
limit: void 0,
|
|
@@ -8398,7 +8681,7 @@ var manageOvpnServerTool = {
|
|
|
8398
8681
|
await context.routerClient.update(OVPN_SERVER_PATH, serverId, {
|
|
8399
8682
|
enabled: desiredEnabled ? "yes" : "no"
|
|
8400
8683
|
});
|
|
8401
|
-
|
|
8684
|
+
log38.info({ routerId: context.routerId, action: parsed.action }, "OpenVPN server updated");
|
|
8402
8685
|
return {
|
|
8403
8686
|
content: `OpenVPN server on ${context.routerId} ${parsed.action}d.`,
|
|
8404
8687
|
structuredContent: { action: parsed.action === "enable" ? "enabled" : "disabled", server }
|
|
@@ -8457,7 +8740,7 @@ var manageOvpnServerTool = {
|
|
|
8457
8740
|
};
|
|
8458
8741
|
}
|
|
8459
8742
|
await context.routerClient.update(OVPN_SERVER_PATH, serverId, updates);
|
|
8460
|
-
|
|
8743
|
+
log38.info({ routerId: context.routerId }, "OpenVPN server configuration updated");
|
|
8461
8744
|
return {
|
|
8462
8745
|
content: `Updated OpenVPN server configuration on ${context.routerId}.`,
|
|
8463
8746
|
structuredContent: { action: "updated", diff }
|
|
@@ -8475,8 +8758,593 @@ var ovpnTools = [
|
|
|
8475
8758
|
manageOvpnServerTool
|
|
8476
8759
|
];
|
|
8477
8760
|
|
|
8761
|
+
// src/domain/tools/upgrade-tools.ts
|
|
8762
|
+
import { z as z38 } from "zod";
|
|
8763
|
+
var log39 = createLogger("upgrade-tools");
|
|
8764
|
+
var getUpgradeStatusInputSchema = z38.object({
|
|
8765
|
+
routerId: z38.string().describe("Target router identifier from the router registry")
|
|
8766
|
+
}).strict();
|
|
8767
|
+
var getUpgradeStatusTool = {
|
|
8768
|
+
name: "get_upgrade_status",
|
|
8769
|
+
title: "Get Upgrade Status",
|
|
8770
|
+
description: "Read the current RouterOS package upgrade status and routerboard firmware versions. Shows installed version, latest available version, update channel, and firmware upgrade availability.",
|
|
8771
|
+
inputSchema: getUpgradeStatusInputSchema,
|
|
8772
|
+
annotations: {
|
|
8773
|
+
readOnlyHint: true,
|
|
8774
|
+
destructiveHint: false,
|
|
8775
|
+
idempotentHint: true,
|
|
8776
|
+
openWorldHint: false
|
|
8777
|
+
},
|
|
8778
|
+
async handler(params, context) {
|
|
8779
|
+
const parsed = getUpgradeStatusInputSchema.parse(params);
|
|
8780
|
+
log39.info({ routerId: context.routerId }, "Getting upgrade status");
|
|
8781
|
+
try {
|
|
8782
|
+
const [updateResults, routerboardResults] = await Promise.all([
|
|
8783
|
+
context.routerClient.get("system/package/update"),
|
|
8784
|
+
context.routerClient.get("system/routerboard")
|
|
8785
|
+
]);
|
|
8786
|
+
const update = updateResults[0] ?? {};
|
|
8787
|
+
const routerboard = routerboardResults.length > 0 ? routerboardResults[0] : null;
|
|
8788
|
+
return {
|
|
8789
|
+
content: `Upgrade status on ${parsed.routerId}: installed=${update["installed-version"] ?? "?"} latest=${update["latest-version"] ?? "?"} (${update.status ?? "unknown"})`,
|
|
8790
|
+
structuredContent: {
|
|
8791
|
+
routerId: parsed.routerId,
|
|
8792
|
+
channel: update.channel ?? null,
|
|
8793
|
+
installedVersion: update["installed-version"] ?? null,
|
|
8794
|
+
latestVersion: update["latest-version"] ?? null,
|
|
8795
|
+
status: update.status ?? null,
|
|
8796
|
+
routerboard: routerboard ? {
|
|
8797
|
+
model: routerboard.model ?? null,
|
|
8798
|
+
currentFirmware: routerboard["current-firmware"] ?? null,
|
|
8799
|
+
upgradeFirmware: routerboard["upgrade-firmware"] ?? null,
|
|
8800
|
+
factoryFirmware: routerboard["factory-firmware"] ?? null
|
|
8801
|
+
} : null
|
|
8802
|
+
}
|
|
8803
|
+
};
|
|
8804
|
+
} catch (err) {
|
|
8805
|
+
throw enrichError(err, { routerId: context.routerId, tool: "get_upgrade_status" });
|
|
8806
|
+
}
|
|
8807
|
+
}
|
|
8808
|
+
};
|
|
8809
|
+
var manageUpgradeInputSchema = z38.object({
|
|
8810
|
+
routerId: z38.string().describe("Target router identifier from the router registry"),
|
|
8811
|
+
action: z38.enum(["check", "install"]).describe(
|
|
8812
|
+
"check \u2014 trigger a check for available updates; install \u2014 download and install the latest update (triggers reboot)"
|
|
8813
|
+
),
|
|
8814
|
+
dryRun: z38.boolean().default(false).describe("Preview the action without executing")
|
|
8815
|
+
}).strict();
|
|
8816
|
+
var manageUpgradeTool = {
|
|
8817
|
+
name: "manage_upgrade",
|
|
8818
|
+
title: "Manage Upgrade",
|
|
8819
|
+
description: "Trigger a RouterOS package update check or install. 'check' queries the update server for new packages. 'install' downloads and applies the update \u2014 the router will reboot automatically. Supports dry-run.",
|
|
8820
|
+
inputSchema: manageUpgradeInputSchema,
|
|
8821
|
+
annotations: {
|
|
8822
|
+
readOnlyHint: false,
|
|
8823
|
+
destructiveHint: true,
|
|
8824
|
+
idempotentHint: false,
|
|
8825
|
+
openWorldHint: false
|
|
8826
|
+
},
|
|
8827
|
+
async handler(params, context) {
|
|
8828
|
+
const parsed = manageUpgradeInputSchema.parse(params);
|
|
8829
|
+
log39.info({ routerId: context.routerId, action: parsed.action }, "Managing upgrade");
|
|
8830
|
+
try {
|
|
8831
|
+
if (parsed.dryRun) {
|
|
8832
|
+
return {
|
|
8833
|
+
content: `Dry run: Would execute '${parsed.action}' on ${parsed.routerId}.`,
|
|
8834
|
+
structuredContent: {
|
|
8835
|
+
action: "dry_run",
|
|
8836
|
+
requestedAction: parsed.action,
|
|
8837
|
+
routerId: parsed.routerId
|
|
8838
|
+
}
|
|
8839
|
+
};
|
|
8840
|
+
}
|
|
8841
|
+
if (parsed.action === "check") {
|
|
8842
|
+
await context.routerClient.execute("system/package/update/check-for-updates");
|
|
8843
|
+
log39.info({ routerId: context.routerId }, "Update check triggered");
|
|
8844
|
+
return {
|
|
8845
|
+
content: `Update check triggered on ${parsed.routerId}.`,
|
|
8846
|
+
structuredContent: { action: "check_triggered", routerId: parsed.routerId }
|
|
8847
|
+
};
|
|
8848
|
+
}
|
|
8849
|
+
await context.routerClient.execute("system/package/update/install");
|
|
8850
|
+
log39.info({ routerId: context.routerId }, "Package install triggered");
|
|
8851
|
+
return {
|
|
8852
|
+
content: `Package install triggered on ${parsed.routerId}. Router will reboot automatically.`,
|
|
8853
|
+
structuredContent: { action: "install_triggered", routerId: parsed.routerId }
|
|
8854
|
+
};
|
|
8855
|
+
} catch (err) {
|
|
8856
|
+
throw enrichError(err, { routerId: context.routerId, tool: "manage_upgrade" });
|
|
8857
|
+
}
|
|
8858
|
+
}
|
|
8859
|
+
};
|
|
8860
|
+
var upgradeTools = [getUpgradeStatusTool, manageUpgradeTool];
|
|
8861
|
+
|
|
8862
|
+
// src/domain/tools/backup-tools.ts
|
|
8863
|
+
import { z as z39 } from "zod";
|
|
8864
|
+
var log40 = createLogger("backup-tools");
|
|
8865
|
+
var createBackupInputSchema = z39.object({
|
|
8866
|
+
routerId: z39.string().describe("Target router identifier from the router registry"),
|
|
8867
|
+
name: z39.string().default("backup").describe("Backup file name (without extension)"),
|
|
8868
|
+
password: z39.string().optional().describe("Password to encrypt the backup file"),
|
|
8869
|
+
dryRun: z39.boolean().default(false).describe("Preview the backup without creating it")
|
|
8870
|
+
}).strict();
|
|
8871
|
+
var createBackupTool = {
|
|
8872
|
+
name: "create_backup",
|
|
8873
|
+
title: "Create Backup",
|
|
8874
|
+
description: "Create a binary configuration backup on a MikroTik router. The backup is saved as <name>.backup on the router's filesystem. Supports optional encryption via password and dry-run mode.",
|
|
8875
|
+
inputSchema: createBackupInputSchema,
|
|
8876
|
+
annotations: {
|
|
8877
|
+
readOnlyHint: false,
|
|
8878
|
+
destructiveHint: false,
|
|
8879
|
+
idempotentHint: false,
|
|
8880
|
+
openWorldHint: false
|
|
8881
|
+
},
|
|
8882
|
+
async handler(params, context) {
|
|
8883
|
+
const parsed = createBackupInputSchema.parse(params);
|
|
8884
|
+
log40.info({ routerId: context.routerId, name: parsed.name }, "Creating backup");
|
|
8885
|
+
if (parsed.dryRun) {
|
|
8886
|
+
return {
|
|
8887
|
+
content: `Dry run: Would create backup "${parsed.name}.backup" on ${context.routerId}.`,
|
|
8888
|
+
structuredContent: { action: "dry_run", name: parsed.name, routerId: context.routerId }
|
|
8889
|
+
};
|
|
8890
|
+
}
|
|
8891
|
+
try {
|
|
8892
|
+
const body = { name: parsed.name };
|
|
8893
|
+
if (parsed.password !== void 0) {
|
|
8894
|
+
body.password = parsed.password;
|
|
8895
|
+
}
|
|
8896
|
+
await context.routerClient.execute("system/backup/save", body);
|
|
8897
|
+
log40.info({ routerId: context.routerId, name: parsed.name }, "Backup created");
|
|
8898
|
+
return {
|
|
8899
|
+
content: `Backup "${parsed.name}.backup" created on ${context.routerId}.`,
|
|
8900
|
+
structuredContent: {
|
|
8901
|
+
action: "created",
|
|
8902
|
+
routerId: context.routerId,
|
|
8903
|
+
filePath: `${parsed.name}.backup`
|
|
8904
|
+
}
|
|
8905
|
+
};
|
|
8906
|
+
} catch (err) {
|
|
8907
|
+
if (err instanceof MikroMCPError) throw err;
|
|
8908
|
+
throw enrichError(err, { routerId: context.routerId, tool: "create_backup" });
|
|
8909
|
+
}
|
|
8910
|
+
}
|
|
8911
|
+
};
|
|
8912
|
+
var exportConfigInputSchema = z39.object({
|
|
8913
|
+
routerId: z39.string().describe("Target router identifier from the router registry"),
|
|
8914
|
+
compact: z39.boolean().default(false).describe("Export only non-default settings (compact format)"),
|
|
8915
|
+
file: z39.string().optional().describe("Save the export to a file on the router (without extension); omit to return inline")
|
|
8916
|
+
}).strict();
|
|
8917
|
+
var exportConfigTool = {
|
|
8918
|
+
name: "export_config",
|
|
8919
|
+
title: "Export Config",
|
|
8920
|
+
description: "Export the router configuration as a RouterOS script. When no file is specified, returns the script text inline. When a file is specified, saves it as <file>.rsc on the router's filesystem. Supports compact mode to show only non-default values.",
|
|
8921
|
+
inputSchema: exportConfigInputSchema,
|
|
8922
|
+
annotations: {
|
|
8923
|
+
readOnlyHint: true,
|
|
8924
|
+
destructiveHint: false,
|
|
8925
|
+
idempotentHint: true,
|
|
8926
|
+
openWorldHint: false
|
|
8927
|
+
},
|
|
8928
|
+
async handler(params, context) {
|
|
8929
|
+
const parsed = exportConfigInputSchema.parse(params);
|
|
8930
|
+
log40.info({ routerId: context.routerId, compact: parsed.compact }, "Exporting config");
|
|
8931
|
+
try {
|
|
8932
|
+
if (parsed.file !== void 0) {
|
|
8933
|
+
const body = { file: parsed.file };
|
|
8934
|
+
if (parsed.compact) body.compact = "yes";
|
|
8935
|
+
await context.routerClient.execute("system/export", body);
|
|
8936
|
+
return {
|
|
8937
|
+
content: `Config exported to "${parsed.file}.rsc" on ${context.routerId}.`,
|
|
8938
|
+
structuredContent: {
|
|
8939
|
+
routerId: context.routerId,
|
|
8940
|
+
filePath: `${parsed.file}.rsc`
|
|
8941
|
+
}
|
|
8942
|
+
};
|
|
8943
|
+
}
|
|
8944
|
+
const sshCommand = parsed.compact ? "export compact" : "export";
|
|
8945
|
+
const scriptText = await context.sshClient.execute(sshCommand);
|
|
8946
|
+
return {
|
|
8947
|
+
content: `Config export from ${context.routerId}:
|
|
8948
|
+
${scriptText}`,
|
|
8949
|
+
structuredContent: {
|
|
8950
|
+
routerId: context.routerId,
|
|
8951
|
+
script: scriptText,
|
|
8952
|
+
compact: parsed.compact
|
|
8953
|
+
}
|
|
8954
|
+
};
|
|
8955
|
+
} catch (err) {
|
|
8956
|
+
if (err instanceof MikroMCPError) throw err;
|
|
8957
|
+
throw enrichError(err, { routerId: context.routerId, tool: "export_config" });
|
|
8958
|
+
}
|
|
8959
|
+
}
|
|
8960
|
+
};
|
|
8961
|
+
var backupTools = [createBackupTool, exportConfigTool];
|
|
8962
|
+
|
|
8963
|
+
// src/domain/tools/log-tools.ts
|
|
8964
|
+
import { z as z40 } from "zod";
|
|
8965
|
+
var log41 = createLogger("log-tools");
|
|
8966
|
+
var LOG_RULES_PATH = "system/logging";
|
|
8967
|
+
var LOG_ACTIONS_PATH = "system/logging/action";
|
|
8968
|
+
var logActionTypeEnum = z40.enum(["memory", "disk", "remote", "echo", "email"]);
|
|
8969
|
+
var listLogRulesInputSchema = z40.object({
|
|
8970
|
+
routerId: z40.string().describe("Target router identifier from the router registry"),
|
|
8971
|
+
topics: z40.string().optional().describe("Filter by topics (substring match)"),
|
|
8972
|
+
logAction: z40.string().optional().describe("Filter by log action target (exact match on action field)"),
|
|
8973
|
+
limit: z40.number().int().min(1).max(500).default(100).describe("Maximum number of rules to return (1-500, default 100)")
|
|
8974
|
+
}).strict();
|
|
8975
|
+
var listLogRulesTool = {
|
|
8976
|
+
name: "list_log_rules",
|
|
8977
|
+
title: "List Log Rules",
|
|
8978
|
+
description: "List RouterOS logging rules (system/logging) with optional topic substring and action exact-match filtering.",
|
|
8979
|
+
inputSchema: listLogRulesInputSchema,
|
|
8980
|
+
annotations: {
|
|
8981
|
+
readOnlyHint: true,
|
|
8982
|
+
destructiveHint: false,
|
|
8983
|
+
idempotentHint: true,
|
|
8984
|
+
openWorldHint: false
|
|
8985
|
+
},
|
|
8986
|
+
async handler(params, context) {
|
|
8987
|
+
const parsed = listLogRulesInputSchema.parse(params);
|
|
8988
|
+
log41.info({ routerId: context.routerId }, "Listing log rules");
|
|
8989
|
+
try {
|
|
8990
|
+
const all = await context.routerClient.get(LOG_RULES_PATH, {
|
|
8991
|
+
limit: void 0,
|
|
8992
|
+
offset: void 0
|
|
8993
|
+
});
|
|
8994
|
+
let rules = all;
|
|
8995
|
+
if (parsed.topics !== void 0) {
|
|
8996
|
+
rules = rules.filter((r) => r.topics?.includes(parsed.topics));
|
|
8997
|
+
}
|
|
8998
|
+
if (parsed.logAction !== void 0) {
|
|
8999
|
+
rules = rules.filter((r) => r.action === parsed.logAction);
|
|
9000
|
+
}
|
|
9001
|
+
const returned = rules.slice(0, parsed.limit);
|
|
9002
|
+
return {
|
|
9003
|
+
content: `Log rules on ${context.routerId}: ${returned.length} rule(s) (${all.length} total).`,
|
|
9004
|
+
structuredContent: {
|
|
9005
|
+
routerId: context.routerId,
|
|
9006
|
+
rules: returned,
|
|
9007
|
+
total: all.length,
|
|
9008
|
+
returned: returned.length
|
|
9009
|
+
}
|
|
9010
|
+
};
|
|
9011
|
+
} catch (err) {
|
|
9012
|
+
if (err instanceof MikroMCPError) throw err;
|
|
9013
|
+
throw enrichError(err, { routerId: context.routerId, tool: "list_log_rules" });
|
|
9014
|
+
}
|
|
9015
|
+
}
|
|
9016
|
+
};
|
|
9017
|
+
var manageLogRuleInputSchema = z40.object({
|
|
9018
|
+
routerId: z40.string().describe("Target router identifier from the router registry"),
|
|
9019
|
+
action: z40.enum(["add", "remove", "enable", "disable"]).describe("Action to perform"),
|
|
9020
|
+
topics: z40.string().describe("Log topics (e.g. firewall, system, info) \u2014 idempotency key"),
|
|
9021
|
+
logAction: z40.string().describe("Log action target name (RouterOS action field) \u2014 idempotency key"),
|
|
9022
|
+
prefix: z40.string().optional().describe("Optional prefix to prepend to log messages"),
|
|
9023
|
+
dryRun: z40.boolean().default(false).describe("Preview changes without applying")
|
|
9024
|
+
}).strict();
|
|
9025
|
+
var manageLogRuleTool = {
|
|
9026
|
+
name: "manage_log_rule",
|
|
9027
|
+
title: "Manage Log Rule",
|
|
9028
|
+
description: "Add, remove, enable, or disable a RouterOS logging rule. Idempotent by topics+logAction pair. add returns already_exists if matching rule found. remove returns not_found gracefully. enable/disable throw NOT_FOUND if rule not found. Supports dry-run.",
|
|
9029
|
+
inputSchema: manageLogRuleInputSchema,
|
|
9030
|
+
annotations: {
|
|
9031
|
+
readOnlyHint: false,
|
|
9032
|
+
destructiveHint: false,
|
|
9033
|
+
idempotentHint: true,
|
|
9034
|
+
openWorldHint: false
|
|
9035
|
+
},
|
|
9036
|
+
snapshotPaths: ["system/logging"],
|
|
9037
|
+
async handler(params, context) {
|
|
9038
|
+
const parsed = manageLogRuleInputSchema.parse(params);
|
|
9039
|
+
log41.info(
|
|
9040
|
+
{ routerId: context.routerId, action: parsed.action, topics: parsed.topics },
|
|
9041
|
+
"Managing log rule"
|
|
9042
|
+
);
|
|
9043
|
+
try {
|
|
9044
|
+
const all = await context.routerClient.get(LOG_RULES_PATH, {
|
|
9045
|
+
limit: void 0,
|
|
9046
|
+
offset: void 0
|
|
9047
|
+
});
|
|
9048
|
+
const existing = all.find(
|
|
9049
|
+
(r) => r.topics === parsed.topics && r.action === parsed.logAction
|
|
9050
|
+
);
|
|
9051
|
+
if (parsed.action === "add") {
|
|
9052
|
+
if (existing) {
|
|
9053
|
+
return {
|
|
9054
|
+
content: `Log rule for topics "${parsed.topics}" \u2192 "${parsed.logAction}" already exists.`,
|
|
9055
|
+
structuredContent: {
|
|
9056
|
+
action: "already_exists",
|
|
9057
|
+
routerId: context.routerId,
|
|
9058
|
+
id: existing[".id"]
|
|
9059
|
+
}
|
|
9060
|
+
};
|
|
9061
|
+
}
|
|
9062
|
+
const body = {
|
|
9063
|
+
topics: parsed.topics,
|
|
9064
|
+
action: parsed.logAction
|
|
9065
|
+
};
|
|
9066
|
+
if (parsed.prefix !== void 0) body.prefix = parsed.prefix;
|
|
9067
|
+
if (parsed.dryRun) {
|
|
9068
|
+
return {
|
|
9069
|
+
content: `Dry run: Would add log rule topics="${parsed.topics}" action="${parsed.logAction}".`,
|
|
9070
|
+
structuredContent: {
|
|
9071
|
+
action: "dry_run",
|
|
9072
|
+
diff: Object.entries(body).map(([property, after]) => ({
|
|
9073
|
+
property,
|
|
9074
|
+
before: null,
|
|
9075
|
+
after
|
|
9076
|
+
}))
|
|
9077
|
+
}
|
|
9078
|
+
};
|
|
9079
|
+
}
|
|
9080
|
+
const created = await context.routerClient.create(LOG_RULES_PATH, body);
|
|
9081
|
+
log41.info({ topics: parsed.topics, logAction: parsed.logAction, id: created[".id"] }, "Log rule added");
|
|
9082
|
+
return {
|
|
9083
|
+
content: `Added log rule topics="${parsed.topics}" action="${parsed.logAction}".`,
|
|
9084
|
+
structuredContent: {
|
|
9085
|
+
action: "created",
|
|
9086
|
+
routerId: context.routerId,
|
|
9087
|
+
id: created[".id"]
|
|
9088
|
+
}
|
|
9089
|
+
};
|
|
9090
|
+
}
|
|
9091
|
+
if (parsed.action === "remove") {
|
|
9092
|
+
if (!existing) {
|
|
9093
|
+
return {
|
|
9094
|
+
content: `Log rule for topics "${parsed.topics}" \u2192 "${parsed.logAction}" not found. No changes made.`,
|
|
9095
|
+
structuredContent: {
|
|
9096
|
+
action: "not_found",
|
|
9097
|
+
routerId: context.routerId
|
|
9098
|
+
}
|
|
9099
|
+
};
|
|
9100
|
+
}
|
|
9101
|
+
const id = existing[".id"];
|
|
9102
|
+
if (parsed.dryRun) {
|
|
9103
|
+
return {
|
|
9104
|
+
content: `Dry run: Would remove log rule topics="${parsed.topics}" action="${parsed.logAction}".`,
|
|
9105
|
+
structuredContent: { action: "dry_run", id }
|
|
9106
|
+
};
|
|
9107
|
+
}
|
|
9108
|
+
await context.routerClient.remove(LOG_RULES_PATH, id);
|
|
9109
|
+
log41.info({ topics: parsed.topics, logAction: parsed.logAction, id }, "Log rule removed");
|
|
9110
|
+
return {
|
|
9111
|
+
content: `Removed log rule topics="${parsed.topics}" action="${parsed.logAction}".`,
|
|
9112
|
+
structuredContent: { action: "removed", id }
|
|
9113
|
+
};
|
|
9114
|
+
}
|
|
9115
|
+
if (parsed.action === "enable" || parsed.action === "disable") {
|
|
9116
|
+
if (!existing) {
|
|
9117
|
+
throw new MikroMCPError({
|
|
9118
|
+
category: "NOT_FOUND" /* NOT_FOUND */,
|
|
9119
|
+
code: "LOG_RULE_NOT_FOUND",
|
|
9120
|
+
message: `Log rule with topics "${parsed.topics}" and action "${parsed.logAction}" not found.`,
|
|
9121
|
+
details: { topics: parsed.topics, logAction: parsed.logAction },
|
|
9122
|
+
recoverability: {
|
|
9123
|
+
retryable: false,
|
|
9124
|
+
suggestedAction: "Verify with list_log_rules or use action=add to create it.",
|
|
9125
|
+
alternativeTools: ["list_log_rules", "manage_log_rule with action=add"]
|
|
9126
|
+
}
|
|
9127
|
+
});
|
|
9128
|
+
}
|
|
9129
|
+
const id = existing[".id"];
|
|
9130
|
+
const wantDisabled = parsed.action === "disable";
|
|
9131
|
+
if (parsed.dryRun) {
|
|
9132
|
+
return {
|
|
9133
|
+
content: `Dry run: Would ${parsed.action} log rule topics="${parsed.topics}" action="${parsed.logAction}".`,
|
|
9134
|
+
structuredContent: {
|
|
9135
|
+
action: "dry_run",
|
|
9136
|
+
id,
|
|
9137
|
+
diff: [{ property: "disabled", before: existing.disabled ?? "false", after: String(wantDisabled) }]
|
|
9138
|
+
}
|
|
9139
|
+
};
|
|
9140
|
+
}
|
|
9141
|
+
await context.routerClient.update(LOG_RULES_PATH, id, {
|
|
9142
|
+
disabled: wantDisabled ? "true" : "false"
|
|
9143
|
+
});
|
|
9144
|
+
const resultAction = wantDisabled ? "disabled" : "enabled";
|
|
9145
|
+
log41.info({ topics: parsed.topics, id, action: resultAction }, "Log rule toggled");
|
|
9146
|
+
return {
|
|
9147
|
+
content: `${wantDisabled ? "Disabled" : "Enabled"} log rule topics="${parsed.topics}" action="${parsed.logAction}".`,
|
|
9148
|
+
structuredContent: { action: resultAction, id }
|
|
9149
|
+
};
|
|
9150
|
+
}
|
|
9151
|
+
throw new MikroMCPError({
|
|
9152
|
+
category: "VALIDATION" /* VALIDATION */,
|
|
9153
|
+
code: "INVALID_ACTION",
|
|
9154
|
+
message: `Unknown action: ${parsed.action}`,
|
|
9155
|
+
recoverability: {
|
|
9156
|
+
retryable: false,
|
|
9157
|
+
suggestedAction: "Use one of: add, remove, enable, disable."
|
|
9158
|
+
}
|
|
9159
|
+
});
|
|
9160
|
+
} catch (err) {
|
|
9161
|
+
if (err instanceof MikroMCPError) throw err;
|
|
9162
|
+
throw enrichError(err, { routerId: context.routerId, tool: "manage_log_rule" });
|
|
9163
|
+
}
|
|
9164
|
+
}
|
|
9165
|
+
};
|
|
9166
|
+
var listLogActionsInputSchema = z40.object({
|
|
9167
|
+
routerId: z40.string().describe("Target router identifier from the router registry"),
|
|
9168
|
+
type: logActionTypeEnum.optional().describe("Filter by action type (exact match): memory, disk, remote, echo, email"),
|
|
9169
|
+
limit: z40.number().int().min(1).max(500).default(100).describe("Maximum number of actions to return (1-500, default 100)")
|
|
9170
|
+
}).strict();
|
|
9171
|
+
var listLogActionsTool = {
|
|
9172
|
+
name: "list_log_actions",
|
|
9173
|
+
title: "List Log Actions",
|
|
9174
|
+
description: "List RouterOS logging action targets (system/logging/action) with optional type filter.",
|
|
9175
|
+
inputSchema: listLogActionsInputSchema,
|
|
9176
|
+
annotations: {
|
|
9177
|
+
readOnlyHint: true,
|
|
9178
|
+
destructiveHint: false,
|
|
9179
|
+
idempotentHint: true,
|
|
9180
|
+
openWorldHint: false
|
|
9181
|
+
},
|
|
9182
|
+
async handler(params, context) {
|
|
9183
|
+
const parsed = listLogActionsInputSchema.parse(params);
|
|
9184
|
+
log41.info({ routerId: context.routerId }, "Listing log actions");
|
|
9185
|
+
try {
|
|
9186
|
+
const all = await context.routerClient.get(LOG_ACTIONS_PATH, {
|
|
9187
|
+
limit: void 0,
|
|
9188
|
+
offset: void 0
|
|
9189
|
+
});
|
|
9190
|
+
let actions = all;
|
|
9191
|
+
if (parsed.type !== void 0) {
|
|
9192
|
+
actions = actions.filter((a) => a.type === parsed.type);
|
|
9193
|
+
}
|
|
9194
|
+
const returned = actions.slice(0, parsed.limit);
|
|
9195
|
+
return {
|
|
9196
|
+
content: `Log actions on ${context.routerId}: ${returned.length} action(s) (${all.length} total).`,
|
|
9197
|
+
structuredContent: {
|
|
9198
|
+
routerId: context.routerId,
|
|
9199
|
+
actions: returned,
|
|
9200
|
+
total: all.length,
|
|
9201
|
+
returned: returned.length
|
|
9202
|
+
}
|
|
9203
|
+
};
|
|
9204
|
+
} catch (err) {
|
|
9205
|
+
if (err instanceof MikroMCPError) throw err;
|
|
9206
|
+
throw enrichError(err, { routerId: context.routerId, tool: "list_log_actions" });
|
|
9207
|
+
}
|
|
9208
|
+
}
|
|
9209
|
+
};
|
|
9210
|
+
var manageLogActionInputSchema = z40.object({
|
|
9211
|
+
routerId: z40.string().describe("Target router identifier from the router registry"),
|
|
9212
|
+
action: z40.enum(["add", "remove"]).describe("Action to perform"),
|
|
9213
|
+
name: z40.string().describe("Log action name \u2014 idempotency key"),
|
|
9214
|
+
type: logActionTypeEnum.optional().describe("Action type: memory, disk, remote, echo, email (required on add)"),
|
|
9215
|
+
remote: z40.string().optional().describe("Remote syslog server address (for type=remote)"),
|
|
9216
|
+
remotePort: z40.number().int().min(1).max(65535).optional().describe("Remote syslog server port (for type=remote)"),
|
|
9217
|
+
diskFileName: z40.string().optional().describe("Disk log file name without extension (for type=disk)"),
|
|
9218
|
+
dryRun: z40.boolean().default(false).describe("Preview changes without applying")
|
|
9219
|
+
}).strict();
|
|
9220
|
+
var manageLogActionTool = {
|
|
9221
|
+
name: "manage_log_action",
|
|
9222
|
+
title: "Manage Log Action",
|
|
9223
|
+
description: "Add or remove a RouterOS logging action target. Idempotent by name. add throws VALIDATION if type is missing; returns already_exists if name found. remove returns not_found gracefully. Supports dry-run.",
|
|
9224
|
+
inputSchema: manageLogActionInputSchema,
|
|
9225
|
+
annotations: {
|
|
9226
|
+
readOnlyHint: false,
|
|
9227
|
+
destructiveHint: false,
|
|
9228
|
+
idempotentHint: true,
|
|
9229
|
+
openWorldHint: false
|
|
9230
|
+
},
|
|
9231
|
+
snapshotPaths: ["system/logging/action"],
|
|
9232
|
+
async handler(params, context) {
|
|
9233
|
+
const parsed = manageLogActionInputSchema.parse(params);
|
|
9234
|
+
log41.info(
|
|
9235
|
+
{ routerId: context.routerId, action: parsed.action, name: parsed.name },
|
|
9236
|
+
"Managing log action"
|
|
9237
|
+
);
|
|
9238
|
+
try {
|
|
9239
|
+
const all = await context.routerClient.get(LOG_ACTIONS_PATH, {
|
|
9240
|
+
limit: void 0,
|
|
9241
|
+
offset: void 0
|
|
9242
|
+
});
|
|
9243
|
+
const existing = all.find((a) => a.name === parsed.name);
|
|
9244
|
+
if (parsed.action === "add") {
|
|
9245
|
+
if (parsed.type === void 0) {
|
|
9246
|
+
throw new MikroMCPError({
|
|
9247
|
+
category: "VALIDATION" /* VALIDATION */,
|
|
9248
|
+
code: "LOG_ACTION_TYPE_REQUIRED",
|
|
9249
|
+
message: "type is required when action is add",
|
|
9250
|
+
recoverability: {
|
|
9251
|
+
retryable: false,
|
|
9252
|
+
suggestedAction: "Provide the type field: memory, disk, remote, echo, or email."
|
|
9253
|
+
}
|
|
9254
|
+
});
|
|
9255
|
+
}
|
|
9256
|
+
if (existing) {
|
|
9257
|
+
return {
|
|
9258
|
+
content: `Log action "${parsed.name}" already exists.`,
|
|
9259
|
+
structuredContent: {
|
|
9260
|
+
action: "already_exists",
|
|
9261
|
+
routerId: context.routerId,
|
|
9262
|
+
name: parsed.name,
|
|
9263
|
+
id: existing[".id"]
|
|
9264
|
+
}
|
|
9265
|
+
};
|
|
9266
|
+
}
|
|
9267
|
+
const body = {
|
|
9268
|
+
name: parsed.name,
|
|
9269
|
+
type: parsed.type
|
|
9270
|
+
};
|
|
9271
|
+
if (parsed.remote !== void 0) body.remote = parsed.remote;
|
|
9272
|
+
if (parsed.remotePort !== void 0) body["remote-port"] = String(parsed.remotePort);
|
|
9273
|
+
if (parsed.diskFileName !== void 0) body["disk-file-name"] = parsed.diskFileName;
|
|
9274
|
+
if (parsed.dryRun) {
|
|
9275
|
+
return {
|
|
9276
|
+
content: `Dry run: Would add log action "${parsed.name}" (type=${parsed.type}).`,
|
|
9277
|
+
structuredContent: {
|
|
9278
|
+
action: "dry_run",
|
|
9279
|
+
diff: Object.entries(body).map(([property, after]) => ({
|
|
9280
|
+
property,
|
|
9281
|
+
before: null,
|
|
9282
|
+
after
|
|
9283
|
+
}))
|
|
9284
|
+
}
|
|
9285
|
+
};
|
|
9286
|
+
}
|
|
9287
|
+
const created = await context.routerClient.create(LOG_ACTIONS_PATH, body);
|
|
9288
|
+
log41.info({ name: parsed.name, type: parsed.type, id: created[".id"] }, "Log action added");
|
|
9289
|
+
return {
|
|
9290
|
+
content: `Added log action "${parsed.name}" (type=${parsed.type}).`,
|
|
9291
|
+
structuredContent: {
|
|
9292
|
+
action: "created",
|
|
9293
|
+
routerId: context.routerId,
|
|
9294
|
+
name: parsed.name,
|
|
9295
|
+
id: created[".id"]
|
|
9296
|
+
}
|
|
9297
|
+
};
|
|
9298
|
+
}
|
|
9299
|
+
if (parsed.action === "remove") {
|
|
9300
|
+
if (!existing) {
|
|
9301
|
+
return {
|
|
9302
|
+
content: `Log action "${parsed.name}" not found. No changes made.`,
|
|
9303
|
+
structuredContent: {
|
|
9304
|
+
action: "not_found",
|
|
9305
|
+
routerId: context.routerId,
|
|
9306
|
+
name: parsed.name
|
|
9307
|
+
}
|
|
9308
|
+
};
|
|
9309
|
+
}
|
|
9310
|
+
const id = existing[".id"];
|
|
9311
|
+
if (parsed.dryRun) {
|
|
9312
|
+
return {
|
|
9313
|
+
content: `Dry run: Would remove log action "${parsed.name}".`,
|
|
9314
|
+
structuredContent: { action: "dry_run", name: parsed.name, id }
|
|
9315
|
+
};
|
|
9316
|
+
}
|
|
9317
|
+
await context.routerClient.remove(LOG_ACTIONS_PATH, id);
|
|
9318
|
+
log41.info({ name: parsed.name, id }, "Log action removed");
|
|
9319
|
+
return {
|
|
9320
|
+
content: `Removed log action "${parsed.name}".`,
|
|
9321
|
+
structuredContent: { action: "removed", name: parsed.name, id }
|
|
9322
|
+
};
|
|
9323
|
+
}
|
|
9324
|
+
throw new MikroMCPError({
|
|
9325
|
+
category: "VALIDATION" /* VALIDATION */,
|
|
9326
|
+
code: "INVALID_ACTION",
|
|
9327
|
+
message: `Unknown action: ${parsed.action}`,
|
|
9328
|
+
recoverability: {
|
|
9329
|
+
retryable: false,
|
|
9330
|
+
suggestedAction: "Use one of: add, remove."
|
|
9331
|
+
}
|
|
9332
|
+
});
|
|
9333
|
+
} catch (err) {
|
|
9334
|
+
if (err instanceof MikroMCPError) throw err;
|
|
9335
|
+
throw enrichError(err, { routerId: context.routerId, tool: "manage_log_action" });
|
|
9336
|
+
}
|
|
9337
|
+
}
|
|
9338
|
+
};
|
|
9339
|
+
var logTools = [
|
|
9340
|
+
listLogRulesTool,
|
|
9341
|
+
manageLogRuleTool,
|
|
9342
|
+
listLogActionsTool,
|
|
9343
|
+
manageLogActionTool
|
|
9344
|
+
];
|
|
9345
|
+
|
|
8478
9346
|
// src/domain/tools/fleet-tools.ts
|
|
8479
|
-
import { z as
|
|
9347
|
+
import { z as z41 } from "zod";
|
|
8480
9348
|
|
|
8481
9349
|
// src/middleware/authz.ts
|
|
8482
9350
|
function matchesPattern(name, pattern) {
|
|
@@ -8644,7 +9512,7 @@ var SshClient = class {
|
|
|
8644
9512
|
// src/adapter/ftp-client.ts
|
|
8645
9513
|
import { Client as Client2 } from "basic-ftp";
|
|
8646
9514
|
import { Readable } from "stream";
|
|
8647
|
-
var
|
|
9515
|
+
var log42 = createLogger("ftp-client");
|
|
8648
9516
|
var FtpClient = class {
|
|
8649
9517
|
#host;
|
|
8650
9518
|
#port;
|
|
@@ -8657,7 +9525,7 @@ var FtpClient = class {
|
|
|
8657
9525
|
this.#password = password;
|
|
8658
9526
|
}
|
|
8659
9527
|
async upload(remoteName, content) {
|
|
8660
|
-
|
|
9528
|
+
log42.info({ host: this.#host, remoteName }, "Uploading file via FTP");
|
|
8661
9529
|
const client = new Client2();
|
|
8662
9530
|
try {
|
|
8663
9531
|
await client.access({
|
|
@@ -8667,7 +9535,7 @@ var FtpClient = class {
|
|
|
8667
9535
|
password: this.#password
|
|
8668
9536
|
});
|
|
8669
9537
|
await client.uploadFrom(Readable.from(Buffer.from(content, "utf-8")), remoteName);
|
|
8670
|
-
|
|
9538
|
+
log42.info({ host: this.#host, remoteName }, "FTP upload complete");
|
|
8671
9539
|
} finally {
|
|
8672
9540
|
client.close();
|
|
8673
9541
|
}
|
|
@@ -8775,17 +9643,17 @@ function checkFleetConfirmation(args, secret) {
|
|
|
8775
9643
|
}
|
|
8776
9644
|
|
|
8777
9645
|
// src/domain/tools/fleet-tools.ts
|
|
8778
|
-
var
|
|
8779
|
-
var checkHealthInputSchema =
|
|
8780
|
-
routerId:
|
|
9646
|
+
var log43 = createLogger("fleet-tools");
|
|
9647
|
+
var checkHealthInputSchema = z41.object({
|
|
9648
|
+
routerId: z41.string().describe("Target router to health-check")
|
|
8781
9649
|
}).strict();
|
|
8782
|
-
var bulkExecuteInputSchema =
|
|
8783
|
-
toolName:
|
|
8784
|
-
routerIds:
|
|
8785
|
-
tags:
|
|
8786
|
-
params:
|
|
8787
|
-
concurrency:
|
|
8788
|
-
confirmationToken:
|
|
9650
|
+
var bulkExecuteInputSchema = z41.object({
|
|
9651
|
+
toolName: z41.string().describe("Name of the tool to fan out (must be a single-router tool)"),
|
|
9652
|
+
routerIds: z41.array(z41.string()).optional().describe("Explicit list of router IDs to target"),
|
|
9653
|
+
tags: z41.array(z41.string()).optional().describe("Target all routers with ALL of these tags (mutually exclusive with routerIds)"),
|
|
9654
|
+
params: z41.record(z41.string(), z41.unknown()).describe("Params to pass to the tool (omit routerId \u2014 injected per router)"),
|
|
9655
|
+
concurrency: z41.number().int().min(1).max(20).default(5).describe("Max simultaneous router calls"),
|
|
9656
|
+
confirmationToken: z41.string().optional().describe(
|
|
8789
9657
|
"Fleet confirmation token from a prior APPROVAL_REQUIRED response. Required to fan out a destructive tool."
|
|
8790
9658
|
)
|
|
8791
9659
|
}).strict();
|
|
@@ -8804,7 +9672,7 @@ function createFleetTools(baseTools2) {
|
|
|
8804
9672
|
},
|
|
8805
9673
|
async handler(params, context) {
|
|
8806
9674
|
checkHealthInputSchema.parse(params);
|
|
8807
|
-
|
|
9675
|
+
log43.info({ routerId: context.routerId }, "Checking router health");
|
|
8808
9676
|
const startMs = Date.now();
|
|
8809
9677
|
try {
|
|
8810
9678
|
const resources = await context.routerClient.get("system/resource");
|
|
@@ -8866,7 +9734,7 @@ function createFleetTools(baseTools2) {
|
|
|
8866
9734
|
}
|
|
8867
9735
|
});
|
|
8868
9736
|
}
|
|
8869
|
-
|
|
9737
|
+
log43.info(
|
|
8870
9738
|
{ toolName: parsed.toolName, concurrency: parsed.concurrency },
|
|
8871
9739
|
"bulk_execute invoked"
|
|
8872
9740
|
);
|
|
@@ -8913,7 +9781,7 @@ function createFleetTools(baseTools2) {
|
|
|
8913
9781
|
try {
|
|
8914
9782
|
resolved.push(context.routerRegistry.getRouter(id));
|
|
8915
9783
|
} catch {
|
|
8916
|
-
|
|
9784
|
+
log43.warn({ routerId: id }, "bulk_execute: router not found");
|
|
8917
9785
|
preErrors.push({
|
|
8918
9786
|
routerId: id,
|
|
8919
9787
|
status: "error",
|
|
@@ -9084,6 +9952,7 @@ var baseTools = [
|
|
|
9084
9952
|
...ipsecTools,
|
|
9085
9953
|
...certificateTools,
|
|
9086
9954
|
...userTools,
|
|
9955
|
+
...userGroupTools,
|
|
9087
9956
|
...dhcpServerTools,
|
|
9088
9957
|
...ipPoolTools,
|
|
9089
9958
|
...queueTools,
|
|
@@ -9093,7 +9962,10 @@ var baseTools = [
|
|
|
9093
9962
|
...dhcpClientTools,
|
|
9094
9963
|
...ipServiceTools,
|
|
9095
9964
|
...pppoeTools,
|
|
9096
|
-
...ovpnTools
|
|
9965
|
+
...ovpnTools,
|
|
9966
|
+
...upgradeTools,
|
|
9967
|
+
...backupTools,
|
|
9968
|
+
...logTools
|
|
9097
9969
|
];
|
|
9098
9970
|
var allTools = [
|
|
9099
9971
|
...baseTools,
|
|
@@ -9492,7 +10364,7 @@ var CircuitBreaker = class {
|
|
|
9492
10364
|
};
|
|
9493
10365
|
|
|
9494
10366
|
// src/mcp/tool-executor.ts
|
|
9495
|
-
var
|
|
10367
|
+
var log44 = createLogger("tool-executor");
|
|
9496
10368
|
async function executeToolCall(tool, args, deps) {
|
|
9497
10369
|
const { registry, pool, circuitBreakers, config, identityRegistry } = deps;
|
|
9498
10370
|
const correlationId = nanoid4();
|
|
@@ -9588,9 +10460,9 @@ async function executeToolCall(tool, args, deps) {
|
|
|
9588
10460
|
try {
|
|
9589
10461
|
const meta = await takeSnapshot(toolContext.routerClient, routerId, path, config.snapshotDir);
|
|
9590
10462
|
snapshotIds.push(meta.id);
|
|
9591
|
-
|
|
10463
|
+
log44.debug({ snapshotId: meta.id, path }, "Snapshot taken");
|
|
9592
10464
|
} catch (err) {
|
|
9593
|
-
|
|
10465
|
+
log44.warn({ err, path, routerId }, "Snapshot failed \u2014 proceeding without snapshot");
|
|
9594
10466
|
}
|
|
9595
10467
|
}
|
|
9596
10468
|
}
|
|
@@ -9632,7 +10504,7 @@ async function executeToolCall(tool, args, deps) {
|
|
|
9632
10504
|
durationMs: Date.now() - startMs
|
|
9633
10505
|
}, config.auditLogPath);
|
|
9634
10506
|
}
|
|
9635
|
-
|
|
10507
|
+
log44.info({ tool: tool.name, routerId, correlationId }, "Tool executed successfully");
|
|
9636
10508
|
recordToolCall(tool.name, "success");
|
|
9637
10509
|
return formatToolResult(result);
|
|
9638
10510
|
} catch (err) {
|
|
@@ -9641,7 +10513,7 @@ async function executeToolCall(tool, args, deps) {
|
|
|
9641
10513
|
const failedRouterId = args.routerId;
|
|
9642
10514
|
if (failedRouterId) {
|
|
9643
10515
|
pool.removeClient(failedRouterId);
|
|
9644
|
-
|
|
10516
|
+
log44.info({ routerId: failedRouterId }, "Evicted pooled client after auth failure");
|
|
9645
10517
|
}
|
|
9646
10518
|
}
|
|
9647
10519
|
if (journalId) {
|
|
@@ -9671,7 +10543,7 @@ async function executeToolCall(tool, args, deps) {
|
|
|
9671
10543
|
durationMs: Date.now() - startMs
|
|
9672
10544
|
}, config.auditLogPath);
|
|
9673
10545
|
}
|
|
9674
|
-
|
|
10546
|
+
log44.error({ err: error, tool: tool.name, correlationId }, "Tool execution failed");
|
|
9675
10547
|
recordToolCall(tool.name, "error");
|
|
9676
10548
|
return formatError(error);
|
|
9677
10549
|
}
|
|
@@ -9679,12 +10551,12 @@ async function executeToolCall(tool, args, deps) {
|
|
|
9679
10551
|
}
|
|
9680
10552
|
|
|
9681
10553
|
// src/mcp/tool-registry.ts
|
|
9682
|
-
var
|
|
10554
|
+
var log45 = createLogger("tool-registry");
|
|
9683
10555
|
function registerAllTools(server, registry, pool, circuitBreakers, config, identityRegistry) {
|
|
9684
10556
|
const deps = { registry, pool, circuitBreakers, config, identityRegistry };
|
|
9685
10557
|
for (const tool of allTools) {
|
|
9686
10558
|
const registrationSchema = tool.annotations.destructiveHint ? tool.inputSchema.extend({
|
|
9687
|
-
confirmationToken:
|
|
10559
|
+
confirmationToken: z42.string().optional().describe(
|
|
9688
10560
|
"Token from a prior APPROVAL_REQUIRED response. Re-submit the identical call with this token to confirm the destructive action."
|
|
9689
10561
|
)
|
|
9690
10562
|
}) : tool.inputSchema;
|
|
@@ -9698,56 +10570,56 @@ function registerAllTools(server, registry, pool, circuitBreakers, config, ident
|
|
|
9698
10570
|
},
|
|
9699
10571
|
(args) => executeToolCall(tool, args, deps)
|
|
9700
10572
|
);
|
|
9701
|
-
|
|
10573
|
+
log45.debug({ tool: tool.name }, "Registered tool");
|
|
9702
10574
|
}
|
|
9703
|
-
|
|
10575
|
+
log45.info({ count: allTools.length }, "All tools registered");
|
|
9704
10576
|
}
|
|
9705
10577
|
|
|
9706
10578
|
// src/config/router-registry.ts
|
|
9707
10579
|
import { readFileSync as readFileSync2, existsSync } from "fs";
|
|
9708
10580
|
import { parse } from "yaml";
|
|
9709
|
-
import { z as
|
|
9710
|
-
var
|
|
9711
|
-
var RouterConfigSchema =
|
|
9712
|
-
host:
|
|
9713
|
-
port:
|
|
9714
|
-
tls:
|
|
9715
|
-
enabled:
|
|
9716
|
-
rejectUnauthorized:
|
|
9717
|
-
ca:
|
|
9718
|
-
fingerprint:
|
|
10581
|
+
import { z as z43 } from "zod";
|
|
10582
|
+
var log46 = createLogger("router-registry");
|
|
10583
|
+
var RouterConfigSchema = z43.object({
|
|
10584
|
+
host: z43.string().min(1, "host is required"),
|
|
10585
|
+
port: z43.number().int().min(1).max(65535),
|
|
10586
|
+
tls: z43.object({
|
|
10587
|
+
enabled: z43.boolean(),
|
|
10588
|
+
rejectUnauthorized: z43.boolean(),
|
|
10589
|
+
ca: z43.string().optional(),
|
|
10590
|
+
fingerprint: z43.string().optional()
|
|
9719
10591
|
}).strict(),
|
|
9720
|
-
credentials:
|
|
9721
|
-
source:
|
|
9722
|
-
envPrefix:
|
|
9723
|
-
vaultPath:
|
|
10592
|
+
credentials: z43.object({
|
|
10593
|
+
source: z43.enum(["env", "vault"]),
|
|
10594
|
+
envPrefix: z43.string().optional(),
|
|
10595
|
+
vaultPath: z43.string().optional()
|
|
9724
10596
|
}).strict(),
|
|
9725
|
-
tags:
|
|
9726
|
-
rosVersion:
|
|
9727
|
-
sshPort:
|
|
9728
|
-
sshFingerprint:
|
|
9729
|
-
cmdAllow:
|
|
9730
|
-
cmdDeny:
|
|
9731
|
-
maintenanceWindows:
|
|
9732
|
-
|
|
9733
|
-
days:
|
|
9734
|
-
|
|
10597
|
+
tags: z43.array(z43.string()).default([]),
|
|
10598
|
+
rosVersion: z43.string().min(1),
|
|
10599
|
+
sshPort: z43.number().int().min(1).max(65535).optional(),
|
|
10600
|
+
sshFingerprint: z43.string().optional(),
|
|
10601
|
+
cmdAllow: z43.array(z43.string()).optional(),
|
|
10602
|
+
cmdDeny: z43.array(z43.string()).optional(),
|
|
10603
|
+
maintenanceWindows: z43.array(
|
|
10604
|
+
z43.object({
|
|
10605
|
+
days: z43.array(
|
|
10606
|
+
z43.enum(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"])
|
|
9735
10607
|
),
|
|
9736
|
-
startTime:
|
|
9737
|
-
endTime:
|
|
9738
|
-
timezone:
|
|
10608
|
+
startTime: z43.string().regex(/^\d{2}:\d{2}$/, "startTime must be HH:MM"),
|
|
10609
|
+
endTime: z43.string().regex(/^\d{2}:\d{2}$/, "endTime must be HH:MM"),
|
|
10610
|
+
timezone: z43.string().min(1)
|
|
9739
10611
|
}).strict()
|
|
9740
10612
|
).optional()
|
|
9741
10613
|
}).strict();
|
|
9742
|
-
var ConfigFileSchema =
|
|
9743
|
-
routers:
|
|
10614
|
+
var ConfigFileSchema = z43.object({
|
|
10615
|
+
routers: z43.record(z43.string(), RouterConfigSchema)
|
|
9744
10616
|
}).strict();
|
|
9745
10617
|
var RouterRegistry = class {
|
|
9746
10618
|
routers;
|
|
9747
10619
|
constructor(configPath) {
|
|
9748
10620
|
this.routers = /* @__PURE__ */ new Map();
|
|
9749
10621
|
if (!existsSync(configPath)) {
|
|
9750
|
-
|
|
10622
|
+
log46.warn({ configPath }, "Router config file not found; starting with empty registry");
|
|
9751
10623
|
return;
|
|
9752
10624
|
}
|
|
9753
10625
|
const raw = readFileSync2(configPath, "utf-8");
|
|
@@ -9761,14 +10633,14 @@ ${issues}`);
|
|
|
9761
10633
|
for (const [id, config] of Object.entries(result.data.routers)) {
|
|
9762
10634
|
const routerConfig = { ...config, id };
|
|
9763
10635
|
if (routerConfig.tls.enabled && !routerConfig.tls.rejectUnauthorized) {
|
|
9764
|
-
|
|
10636
|
+
log46.warn(
|
|
9765
10637
|
{ routerId: id },
|
|
9766
10638
|
"TLS certificate validation is DISABLED (rejectUnauthorized=false). Set tls.fingerprint to pin the server certificate instead."
|
|
9767
10639
|
);
|
|
9768
10640
|
}
|
|
9769
10641
|
this.routers.set(id, routerConfig);
|
|
9770
10642
|
}
|
|
9771
|
-
|
|
10643
|
+
log46.info({ count: this.routers.size }, "Loaded routers from config");
|
|
9772
10644
|
}
|
|
9773
10645
|
getRouter(id) {
|
|
9774
10646
|
const router = this.routers.get(id);
|
|
@@ -10053,7 +10925,7 @@ var ConnectionPool = class {
|
|
|
10053
10925
|
};
|
|
10054
10926
|
|
|
10055
10927
|
// src/version.ts
|
|
10056
|
-
var VERSION = "1.
|
|
10928
|
+
var VERSION = "1.4.0";
|
|
10057
10929
|
|
|
10058
10930
|
// src/mcp/server.ts
|
|
10059
10931
|
function createServerFactory(config, identityRegistry) {
|
|
@@ -10083,7 +10955,7 @@ import { randomUUID } from "crypto";
|
|
|
10083
10955
|
import { createServer } from "http";
|
|
10084
10956
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
10085
10957
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
10086
|
-
var
|
|
10958
|
+
var log47 = createLogger("transport-http");
|
|
10087
10959
|
var BodyTooLargeError = class extends Error {
|
|
10088
10960
|
constructor() {
|
|
10089
10961
|
super("Request body exceeds maximum allowed size");
|
|
@@ -10175,7 +11047,7 @@ async function connectHttp(makeServer, config, identityRegistry) {
|
|
|
10175
11047
|
return;
|
|
10176
11048
|
}
|
|
10177
11049
|
if (!checkRateLimit(ip)) {
|
|
10178
|
-
|
|
11050
|
+
log47.warn({ ip }, "Rate limit exceeded");
|
|
10179
11051
|
res.writeHead(429, { "Content-Type": "application/json" });
|
|
10180
11052
|
res.end(JSON.stringify({ error: "Too many requests. Retry after 60 seconds." }));
|
|
10181
11053
|
return;
|
|
@@ -10216,7 +11088,7 @@ async function connectHttp(makeServer, config, identityRegistry) {
|
|
|
10216
11088
|
}
|
|
10217
11089
|
return;
|
|
10218
11090
|
}
|
|
10219
|
-
|
|
11091
|
+
log47.error({ err }, "HTTP transport error");
|
|
10220
11092
|
if (!res.headersSent) {
|
|
10221
11093
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10222
11094
|
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
@@ -10226,7 +11098,7 @@ async function connectHttp(makeServer, config, identityRegistry) {
|
|
|
10226
11098
|
httpServer.on("close", () => clearInterval(sweepTimer));
|
|
10227
11099
|
await new Promise((resolve, reject) => {
|
|
10228
11100
|
httpServer.listen(port, bindHost, () => {
|
|
10229
|
-
|
|
11101
|
+
log47.info(
|
|
10230
11102
|
{ port, bindHost },
|
|
10231
11103
|
"HTTP transport listening (POST /mcp for Streamable HTTP, GET /sse for legacy SSE)"
|
|
10232
11104
|
);
|
|
@@ -10250,13 +11122,13 @@ async function handleMcpRequest(pathname, req, res, url, port, maxBodyBytes, mak
|
|
|
10250
11122
|
sessionIdGenerator: () => randomUUID(),
|
|
10251
11123
|
onsessioninitialized: (id) => {
|
|
10252
11124
|
streamableSessions.set(id, transport2);
|
|
10253
|
-
|
|
11125
|
+
log47.info({ sessionId: id }, "Streamable HTTP session created");
|
|
10254
11126
|
}
|
|
10255
11127
|
});
|
|
10256
11128
|
transport2.onclose = () => {
|
|
10257
11129
|
if (transport2.sessionId) {
|
|
10258
11130
|
streamableSessions.delete(transport2.sessionId);
|
|
10259
|
-
|
|
11131
|
+
log47.info({ sessionId: transport2.sessionId }, "Streamable HTTP session closed");
|
|
10260
11132
|
}
|
|
10261
11133
|
};
|
|
10262
11134
|
await makeServer().connect(transport2);
|
|
@@ -10273,7 +11145,7 @@ async function handleMcpRequest(pathname, req, res, url, port, maxBodyBytes, mak
|
|
|
10273
11145
|
sseTransport.onclose = () => {
|
|
10274
11146
|
sseTransports.delete(sseTransport.sessionId);
|
|
10275
11147
|
};
|
|
10276
|
-
|
|
11148
|
+
log47.info({ sessionId: sseTransport.sessionId }, "SSE client connected");
|
|
10277
11149
|
await sseTransport.start();
|
|
10278
11150
|
return;
|
|
10279
11151
|
}
|
|
@@ -10294,23 +11166,23 @@ async function handleMcpRequest(pathname, req, res, url, port, maxBodyBytes, mak
|
|
|
10294
11166
|
// src/config/identity-registry.ts
|
|
10295
11167
|
import { readFileSync as readFileSync4, existsSync as existsSync2 } from "fs";
|
|
10296
11168
|
import { parse as parse2 } from "yaml";
|
|
10297
|
-
import { z as
|
|
11169
|
+
import { z as z44 } from "zod";
|
|
10298
11170
|
import bcrypt from "bcryptjs";
|
|
10299
|
-
var
|
|
10300
|
-
var IdentityConfigSchema =
|
|
10301
|
-
token:
|
|
10302
|
-
role:
|
|
10303
|
-
allowedRouters:
|
|
10304
|
-
allowedToolPatterns:
|
|
11171
|
+
var log48 = createLogger("identity-registry");
|
|
11172
|
+
var IdentityConfigSchema = z44.object({
|
|
11173
|
+
token: z44.string().min(1, "token hash is required"),
|
|
11174
|
+
role: z44.enum(["readonly", "operator", "admin", "superadmin"]),
|
|
11175
|
+
allowedRouters: z44.array(z44.string()).default([]),
|
|
11176
|
+
allowedToolPatterns: z44.array(z44.string()).default([])
|
|
10305
11177
|
}).strict();
|
|
10306
|
-
var ConfigFileSchema2 =
|
|
10307
|
-
identities:
|
|
11178
|
+
var ConfigFileSchema2 = z44.object({
|
|
11179
|
+
identities: z44.record(z44.string(), IdentityConfigSchema)
|
|
10308
11180
|
}).strict();
|
|
10309
11181
|
var IdentityRegistry = class {
|
|
10310
11182
|
identities = [];
|
|
10311
11183
|
constructor(configPath) {
|
|
10312
11184
|
if (!existsSync2(configPath)) {
|
|
10313
|
-
|
|
11185
|
+
log48.warn(
|
|
10314
11186
|
{ configPath },
|
|
10315
11187
|
"Identity config file not found; starting with empty registry (stdio callers do not require it)"
|
|
10316
11188
|
);
|
|
@@ -10333,7 +11205,7 @@ ${issues}`);
|
|
|
10333
11205
|
allowedToolPatterns: config.allowedToolPatterns
|
|
10334
11206
|
});
|
|
10335
11207
|
}
|
|
10336
|
-
|
|
11208
|
+
log48.info({ count: this.identities.length }, "Loaded identities from config");
|
|
10337
11209
|
}
|
|
10338
11210
|
getIdentities() {
|
|
10339
11211
|
return this.identities.map(({ id, role, allowedRouters, allowedToolPatterns }) => ({
|
|
@@ -10362,7 +11234,7 @@ ${issues}`);
|
|
|
10362
11234
|
// src/domain/snapshot/retention.ts
|
|
10363
11235
|
import { readdir, stat, unlink } from "fs/promises";
|
|
10364
11236
|
import { join as join3 } from "path";
|
|
10365
|
-
var
|
|
11237
|
+
var log49 = createLogger("snapshot-retention");
|
|
10366
11238
|
async function pruneSnapshots(snapshotDir, maxAgeDays) {
|
|
10367
11239
|
const cutoffMs = Date.now() - maxAgeDays * 864e5;
|
|
10368
11240
|
let removed = 0;
|
|
@@ -10389,21 +11261,21 @@ async function pruneSnapshots(snapshotDir, maxAgeDays) {
|
|
|
10389
11261
|
removed++;
|
|
10390
11262
|
}
|
|
10391
11263
|
} catch (err) {
|
|
10392
|
-
|
|
11264
|
+
log49.warn({ err, filePath }, "Failed to evaluate snapshot for retention");
|
|
10393
11265
|
}
|
|
10394
11266
|
}
|
|
10395
11267
|
}
|
|
10396
11268
|
if (removed > 0) {
|
|
10397
|
-
|
|
11269
|
+
log49.info({ removed, maxAgeDays }, "Pruned expired snapshots");
|
|
10398
11270
|
}
|
|
10399
11271
|
return removed;
|
|
10400
11272
|
}
|
|
10401
11273
|
|
|
10402
11274
|
// src/cli/serve.ts
|
|
10403
|
-
var
|
|
11275
|
+
var log50 = createLogger("main");
|
|
10404
11276
|
async function runServe() {
|
|
10405
11277
|
const config = loadAppConfig();
|
|
10406
|
-
|
|
11278
|
+
log50.info({ transport: config.transport, logLevel: config.logLevel }, "Starting MikroMCP server");
|
|
10407
11279
|
void pruneSnapshots(config.snapshotDir, config.retention.snapshotMaxAgeDays);
|
|
10408
11280
|
const identityRegistry = new IdentityRegistry(config.identitiesPath);
|
|
10409
11281
|
if (config.transport === "http") {
|
|
@@ -10411,7 +11283,7 @@ async function runServe() {
|
|
|
10411
11283
|
(i) => i.role === "readonly" || i.role === "operator"
|
|
10412
11284
|
);
|
|
10413
11285
|
if (hasLimitedRoles && !config.confirmationSecret) {
|
|
10414
|
-
|
|
11286
|
+
log50.error(
|
|
10415
11287
|
"MIKROMCP_CONFIRMATION_SECRET is required in HTTP mode when identities with role readonly or operator are configured"
|
|
10416
11288
|
);
|
|
10417
11289
|
process.exit(1);
|
|
@@ -10421,7 +11293,7 @@ async function runServe() {
|
|
|
10421
11293
|
if (config.transport === "stdio") {
|
|
10422
11294
|
const stdioIdentity = getStdioIdentity(config.stdioIdentity, identityRegistry);
|
|
10423
11295
|
await withIdentity(stdioIdentity, () => connectStdio(makeServer()));
|
|
10424
|
-
|
|
11296
|
+
log50.info("MikroMCP server running via stdio");
|
|
10425
11297
|
} else if (config.transport === "http") {
|
|
10426
11298
|
await connectHttp(
|
|
10427
11299
|
makeServer,
|
|
@@ -10433,13 +11305,13 @@ async function runServe() {
|
|
|
10433
11305
|
},
|
|
10434
11306
|
identityRegistry
|
|
10435
11307
|
);
|
|
10436
|
-
|
|
11308
|
+
log50.info(
|
|
10437
11309
|
{ port: config.port, bindHost: config.bindHost },
|
|
10438
11310
|
"MikroMCP server running via HTTP/SSE"
|
|
10439
11311
|
);
|
|
10440
11312
|
}
|
|
10441
11313
|
const shutdown = () => {
|
|
10442
|
-
|
|
11314
|
+
log50.info("Shutting down MikroMCP server");
|
|
10443
11315
|
pool.closeAll();
|
|
10444
11316
|
process.exit(0);
|
|
10445
11317
|
};
|
|
@@ -10452,7 +11324,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
|
10452
11324
|
import { homedir as homedir2 } from "os";
|
|
10453
11325
|
import * as net from "net";
|
|
10454
11326
|
import chalk from "chalk";
|
|
10455
|
-
var localVersion = "1.
|
|
11327
|
+
var localVersion = "1.4.0";
|
|
10456
11328
|
var results = [];
|
|
10457
11329
|
function ok(msg) {
|
|
10458
11330
|
console.log(chalk.green("\u2705 ") + msg);
|
|
@@ -11104,7 +11976,7 @@ async function runInit() {
|
|
|
11104
11976
|
}
|
|
11105
11977
|
|
|
11106
11978
|
// src/cli/index.ts
|
|
11107
|
-
var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.
|
|
11979
|
+
var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.4.0");
|
|
11108
11980
|
program.command("serve", { isDefault: true }).description("Start the MCP server (default)").action(runServe);
|
|
11109
11981
|
program.command("doctor").description("Check config, connectivity, and permissions").action(runDoctor);
|
|
11110
11982
|
program.command("init").description("Interactive setup wizard").action(runInit);
|