mikromcp 1.14.0 → 1.14.1

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/main.js +101 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  [![CI](https://github.com/AliKarami/MikroMCP/actions/workflows/ci.yml/badge.svg)](https://github.com/AliKarami/MikroMCP/actions/workflows/ci.yml)
14
14
  [![Release](https://github.com/AliKarami/MikroMCP/actions/workflows/release.yml/badge.svg)](https://github.com/AliKarami/MikroMCP/actions/workflows/release.yml)
15
- [![Version](https://img.shields.io/badge/version-v1.14.0-2DD4BF)](package.json)
15
+ [![Version](https://img.shields.io/badge/version-v1.14.1-2DD4BF)](package.json)
16
16
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
17
17
  [![Node.js >= 22](https://img.shields.io/badge/node-%3E%3D22-339933)](package.json)
18
18
  [![RouterOS 7.x](https://img.shields.io/badge/RouterOS-7.x-293239)](https://help.mikrotik.com/docs/display/ROS/REST+API)
package/dist/main.js CHANGED
@@ -3270,6 +3270,7 @@ var wifiTools = [listWifiTool, listWifiClientsTool, manageWifiTool];
3270
3270
 
3271
3271
  // src/domain/tools/wireguard-tools.ts
3272
3272
  import { z as z13 } from "zod";
3273
+ import { isIP } from "net";
3273
3274
  var log11 = createLogger("wireguard-tools");
3274
3275
  var listWgInputSchema = z13.object({
3275
3276
  routerId,
@@ -3363,6 +3364,7 @@ var listPeersTool = {
3363
3364
  "interface",
3364
3365
  "public-key",
3365
3366
  "endpoint-address",
3367
+ "endpoint-port",
3366
3368
  "allowed-address",
3367
3369
  "disabled"
3368
3370
  ])
@@ -3381,20 +3383,71 @@ var listPeersTool = {
3381
3383
  }
3382
3384
  }
3383
3385
  };
3386
+ function parsePeerEndpoint(endpoint) {
3387
+ if (/\s/.test(endpoint) || endpoint.includes("%")) return void 0;
3388
+ if (isIP(endpoint) === 6) return { "endpoint-address": endpoint };
3389
+ const ipv6 = /^\[([^\]]+)\](?::(\d+))?$/.exec(endpoint);
3390
+ const host = /^([^:]+)(?::(\d+))?$/.exec(endpoint);
3391
+ const match = ipv6 ?? host;
3392
+ if (!match) return void 0;
3393
+ const [, address, port] = match;
3394
+ if (ipv6) {
3395
+ if (isIP(address) !== 6) return void 0;
3396
+ } else if (isIP(address) !== 4) {
3397
+ const hostname = address.endsWith(".") ? address.slice(0, -1) : address;
3398
+ if (hostname.length > 253 || /^[\d.]+$/.test(hostname) || !hostname.split(".").every((label) => /^[a-z\d_](?:[a-z\d_-]{0,61}[a-z\d_])?$/i.test(label))) {
3399
+ return void 0;
3400
+ }
3401
+ }
3402
+ if (port !== void 0 && (Number(port) < 1 || Number(port) > 65535)) return void 0;
3403
+ return {
3404
+ "endpoint-address": address,
3405
+ ...port === void 0 ? {} : { "endpoint-port": String(Number(port)) }
3406
+ };
3407
+ }
3408
+ var peerEndpointSchema = z13.string().transform((endpoint, ctx) => {
3409
+ const fields = parsePeerEndpoint(endpoint);
3410
+ if (fields) return fields;
3411
+ ctx.addIssue({
3412
+ code: z13.ZodIssueCode.custom,
3413
+ message: "Use IPv4:port, hostname:port or [IPv6]:port with port 1\u201365535, or an address without a port"
3414
+ });
3415
+ return z13.NEVER;
3416
+ });
3417
+ function canonicalPeerAddress(value) {
3418
+ const address = String(value ?? "");
3419
+ if (isIP(address) === 6) return new URL(`http://[${address}]/`).hostname;
3420
+ return address.toLowerCase().replace(/\.$/, "");
3421
+ }
3422
+ function canonicalAllowedAddresses(value) {
3423
+ const addresses = String(value ?? "").split(",").map((item) => {
3424
+ const cidr2 = item.trim();
3425
+ const [address, prefix] = cidr2.split("/");
3426
+ const family = isIP(address);
3427
+ if (!family || prefix !== void 0 && !/^\d+$/.test(prefix)) return cidr2;
3428
+ const bits = family === 4 ? 32 : 128;
3429
+ const length = Number(prefix ?? bits);
3430
+ if (length > bits || cidr2.split("/").length > 2) return cidr2;
3431
+ return `${canonicalPeerAddress(address)}/${length}`;
3432
+ });
3433
+ return [...new Set(addresses)].sort().join(",");
3434
+ }
3384
3435
  var managePeerInputSchema = z13.object({
3385
3436
  routerId,
3386
3437
  action: z13.enum(["add", "remove"]).describe("Action to perform"),
3387
3438
  interface: z13.string().describe("WireGuard interface name (e.g. wg0)"),
3388
3439
  publicKey: z13.string().min(1).describe("Peer public key in base64 format (44 characters)"),
3389
3440
  allowedAddress: z13.string().optional().describe("Allowed IP address/CIDR for this peer (e.g. 10.0.0.2/32)"),
3390
- endpoint: z13.string().optional().describe("Peer endpoint as IP:port (e.g. 1.2.3.4:51820)"),
3441
+ endpoint: z13.string().optional().describe(
3442
+ "Add only: IPv4:port, hostname:port or [IPv6]:port (port 1\u201365535); hostname labels may contain underscores. Address-only IPv4/hostname/IPv6 (bare or bracketed) leaves the RouterOS port default unchanged, which may not allow initiation. Ignored for remove; omit for a roaming peer."
3443
+ ),
3391
3444
  comment: z13.string().max(255).optional().describe("Optional comment"),
3392
3445
  dryRun
3393
3446
  }).strict();
3394
3447
  var managePeerTool = {
3395
3448
  name: "manage_wireguard_peer",
3396
3449
  title: "Manage WireGuard Peer",
3397
- description: "Add or remove a WireGuard peer. Idempotent by public key: add returns already_exists if a peer with the same public key already exists on the interface.",
3450
+ description: "Add or remove a WireGuard peer. Add returns already_exists when the public key and explicitly requested endpoint/allowed addresses match; different settings produce CONFLICT without changing the peer. Omitted settings are left unchanged.",
3398
3451
  inputSchema: managePeerInputSchema,
3399
3452
  annotations: {
3400
3453
  readOnlyHint: false,
@@ -3410,11 +3463,44 @@ var managePeerTool = {
3410
3463
  "Managing WireGuard peer"
3411
3464
  );
3412
3465
  try {
3466
+ const endpointFields = parsed.action === "add" && parsed.endpoint !== void 0 ? peerEndpointSchema.parse(parsed.endpoint) : {};
3413
3467
  const existing = await context.routerClient.get("interface/wireguard/peers", {
3414
3468
  filter: { interface: parsed.interface, "public-key": parsed.publicKey }
3415
3469
  });
3416
3470
  if (parsed.action === "add") {
3417
3471
  if (existing.length > 0) {
3472
+ const rec2 = existing[0];
3473
+ const requestedFields = {
3474
+ ...endpointFields,
3475
+ ...parsed.allowedAddress === void 0 ? {} : { "allowed-address": parsed.allowedAddress }
3476
+ };
3477
+ const mismatched = Object.entries(requestedFields).filter(([property, desired]) => {
3478
+ if (property === "endpoint-address") {
3479
+ return canonicalPeerAddress(rec2[property]) !== canonicalPeerAddress(desired);
3480
+ }
3481
+ if (property === "allowed-address") {
3482
+ return canonicalAllowedAddresses(rec2[property]) !== canonicalAllowedAddresses(desired);
3483
+ }
3484
+ return !sameValue(rec2[property], desired);
3485
+ });
3486
+ if (mismatched.length > 0) {
3487
+ throw new MikroMCPError({
3488
+ category: "CONFLICT" /* CONFLICT */,
3489
+ code: "WIREGUARD_PEER_CONFLICT",
3490
+ message: `WireGuard peer already exists on "${parsed.interface}" with different requested settings.`,
3491
+ details: {
3492
+ existing: Object.fromEntries(
3493
+ mismatched.map(([property]) => [property, rec2[property]])
3494
+ ),
3495
+ requested: Object.fromEntries(mismatched)
3496
+ },
3497
+ recoverability: {
3498
+ retryable: false,
3499
+ suggestedAction: "Inspect the peer with list_wireguard_peers, then remove it before adding different settings.",
3500
+ alternativeTools: ["list_wireguard_peers", "manage_wireguard_peer"]
3501
+ }
3502
+ });
3503
+ }
3418
3504
  return {
3419
3505
  content: `WireGuard peer with this public key already exists on "${parsed.interface}". No changes made.`,
3420
3506
  structuredContent: { action: "already_exists", peer: existing[0] }
@@ -3424,8 +3510,12 @@ var managePeerTool = {
3424
3510
  const diff = [
3425
3511
  { property: "interface", before: null, after: parsed.interface },
3426
3512
  { property: "public-key", before: null, after: parsed.publicKey },
3427
- ...parsed.allowedAddress ? [{ property: "allowed-address", before: null, after: parsed.allowedAddress }] : [],
3428
- ...parsed.endpoint ? [{ property: "endpoint-address", before: null, after: parsed.endpoint }] : []
3513
+ ...parsed.allowedAddress !== void 0 ? [{ property: "allowed-address", before: null, after: parsed.allowedAddress }] : [],
3514
+ ...Object.entries(endpointFields).map(([property, after]) => ({
3515
+ property,
3516
+ before: null,
3517
+ after
3518
+ }))
3429
3519
  ];
3430
3520
  return {
3431
3521
  content: `Dry run: Would add WireGuard peer on "${parsed.interface}".`,
@@ -3434,10 +3524,10 @@ var managePeerTool = {
3434
3524
  }
3435
3525
  const body = {
3436
3526
  interface: parsed.interface,
3437
- "public-key": parsed.publicKey
3527
+ "public-key": parsed.publicKey,
3528
+ ...endpointFields
3438
3529
  };
3439
- if (parsed.allowedAddress) body["allowed-address"] = parsed.allowedAddress;
3440
- if (parsed.endpoint) body["endpoint-address"] = parsed.endpoint;
3530
+ if (parsed.allowedAddress !== void 0) body["allowed-address"] = parsed.allowedAddress;
3441
3531
  if (parsed.comment) body.comment = parsed.comment.replace(/[\x00-\x1f\x7f]/g, "");
3442
3532
  const created = await context.routerClient.create("interface/wireguard/peers", body);
3443
3533
  log11.info({ interface: parsed.interface, id: created[".id"] }, "WireGuard peer added");
@@ -12236,7 +12326,7 @@ var manageIpServiceInputSchema = z37.object({
12236
12326
  var manageIpServiceTool = {
12237
12327
  name: "manage_ip_service",
12238
12328
  title: "Manage IP Service",
12239
- description: "Enable or disable a RouterOS IP service (api, api-ssl, ssh, telnet, www, www-ssl, winbox, ftp). Port number changes are intentionally not supported to prevent accidental lockout.",
12329
+ description: "Enable or disable a RouterOS IP service (api, api-ssl, ssh, telnet, www, www-ssl, winbox, ftp). Port number changes are intentionally not supported to prevent accidental lockout. MikroMCP reaches RouterOS through the REST API on www-ssl (or www for plaintext): disabling the service its connection uses cuts MikroMCP off from the router, including any later call to re-enable it.",
12240
12330
  inputSchema: manageIpServiceInputSchema,
12241
12331
  annotations: {
12242
12332
  readOnlyHint: false,
@@ -18308,7 +18398,7 @@ var ConnectionPool = class {
18308
18398
  };
18309
18399
 
18310
18400
  // src/version.ts
18311
- var VERSION2 = "1.14.0";
18401
+ var VERSION2 = "1.14.1";
18312
18402
 
18313
18403
  // src/mcp/server.ts
18314
18404
  var SERVER_INSTRUCTIONS = [
@@ -18786,7 +18876,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync7 } from "fs";
18786
18876
  import { homedir as homedir2 } from "os";
18787
18877
  import * as net from "net";
18788
18878
  import chalk from "chalk";
18789
- var localVersion = "1.14.0";
18879
+ var localVersion = "1.14.1";
18790
18880
  var results = [];
18791
18881
  function ok(msg) {
18792
18882
  console.log(chalk.green("\u2705 ") + msg);
@@ -19602,7 +19692,7 @@ async function runInit() {
19602
19692
  }
19603
19693
 
19604
19694
  // src/cli/index.ts
19605
- var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.14.0");
19695
+ var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.14.1");
19606
19696
  program.command("serve", { isDefault: true }).description("Start the MCP server (default)").action(runServe);
19607
19697
  program.command("doctor").description("Check config, connectivity, and permissions").action(runDoctor);
19608
19698
  program.command("init").description("Interactive setup wizard").action(runInit);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikromcp",
3
- "version": "1.14.0",
3
+ "version": "1.14.1",
4
4
  "mcpName": "io.github.AliKarami/mikromcp",
5
5
  "description": "Production-grade MCP server for MikroTik RouterOS routers and SwOS switches",
6
6
  "type": "module",