mikromcp 1.0.1 → 1.0.2

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 +89 -62
  3. package/package.json +2 -2
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.0.0-EA580C)](package.json)
15
+ [![Version](https://img.shields.io/badge/version-v1.0.2-EA580C)](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
@@ -2,6 +2,8 @@
2
2
 
3
3
  // src/main.ts
4
4
  import { config as loadDotenv } from "dotenv";
5
+ import { join as join3 } from "path";
6
+ import { homedir as homedir3 } from "os";
5
7
 
6
8
  // src/cli/index.ts
7
9
  import { Command } from "commander";
@@ -8604,7 +8606,7 @@ function createServerFactory(config, identityRegistry) {
8604
8606
  const makeServer = () => {
8605
8607
  const server = new McpServer({
8606
8608
  name: "mikromcp",
8607
- version: "1.0.1"
8609
+ version: "1.0.2"
8608
8610
  });
8609
8611
  registerAllTools(server, registry, pool, circuitBreakers, config, identityRegistry);
8610
8612
  return server;
@@ -8927,7 +8929,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
8927
8929
  import { homedir } from "os";
8928
8930
  import * as net from "net";
8929
8931
  import chalk from "chalk";
8930
- var localVersion = "1.0.1";
8932
+ var localVersion = "1.0.2";
8931
8933
  var results = [];
8932
8934
  function ok(msg) {
8933
8935
  console.log(chalk.green("\u2705 ") + msg);
@@ -9211,6 +9213,12 @@ import { join as join2 } from "path";
9211
9213
  import { stringify as yamlStringify, parse as yamlParse } from "yaml";
9212
9214
  import bcrypt2 from "bcryptjs";
9213
9215
  import chalk2 from "chalk";
9216
+ function mikromcpDir() {
9217
+ return join2(homedir2(), ".mikromcp");
9218
+ }
9219
+ function defaultAuditLogPath() {
9220
+ return join2(mikromcpDir(), "audit.ndjson");
9221
+ }
9214
9222
  function suggestEnvPrefix(routerId) {
9215
9223
  return "ROUTER_" + routerId.toUpperCase().replace(/-/g, "_");
9216
9224
  }
@@ -9228,17 +9236,6 @@ function findClaudeDesktopConfig() {
9228
9236
  ];
9229
9237
  return candidates.find((p) => existsSync4(p)) ?? null;
9230
9238
  }
9231
- function appendToGitignore(projectRoot, entry) {
9232
- const gitignorePath = join2(projectRoot, ".gitignore");
9233
- if (existsSync4(gitignorePath)) {
9234
- const content = readFileSync7(gitignorePath, "utf-8");
9235
- if (!content.split("\n").some((line) => line.trim() === entry)) {
9236
- writeFileSync2(gitignorePath, content.endsWith("\n") ? content + entry + "\n" : content + "\n" + entry + "\n");
9237
- }
9238
- } else {
9239
- writeFileSync2(gitignorePath, entry + "\n");
9240
- }
9241
- }
9242
9239
  async function collectRouterInfo() {
9243
9240
  console.log(chalk2.bold("\n\u2500\u2500 Router configuration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
9244
9241
  const routerId = await input({
@@ -9246,19 +9243,20 @@ async function collectRouterInfo() {
9246
9243
  validate: validateRouterId
9247
9244
  });
9248
9245
  const host = await input({
9249
- message: "Host / IP (e.g. 192.168.1.1):",
9246
+ message: "Host / IP (e.g. 192.168.88.1):",
9250
9247
  validate: (v) => v.trim().length > 0 ? true : "Host is required"
9251
9248
  });
9252
9249
  const portStr = await input({
9253
9250
  message: "Port:",
9254
- default: "80",
9251
+ default: "443",
9255
9252
  validate: (v) => {
9256
9253
  const n = Number(v);
9257
9254
  return Number.isInteger(n) && n >= 1 && n <= 65535 ? true : "Port must be 1\u201365535";
9258
9255
  }
9259
9256
  });
9260
9257
  const port = Number(portStr);
9261
- const tlsEnabled = await confirm({ message: "Enable TLS?", default: false });
9258
+ const tlsDefault = port === 443 || port === 8443;
9259
+ const tlsEnabled = await confirm({ message: "Enable TLS?", default: tlsDefault });
9262
9260
  let rejectUnauthorized = true;
9263
9261
  if (tlsEnabled) {
9264
9262
  rejectUnauthorized = await confirm({
@@ -9345,16 +9343,18 @@ async function collectIdentityInfo() {
9345
9343
  tokenHash
9346
9344
  };
9347
9345
  }
9348
- async function collectEnvPreference(_data) {
9349
- console.log(chalk2.bold("\n\u2500\u2500 Environment file \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
9350
- return confirm({ message: "Write env vars to .env?", default: true });
9346
+ async function collectEnvPreference() {
9347
+ return confirm({
9348
+ message: "Write credentials to ~/.mikromcp/.env? (No = inject env vars at runtime)",
9349
+ default: true
9350
+ });
9351
9351
  }
9352
9352
  async function collectClaudeDesktopPreference() {
9353
9353
  console.log(chalk2.bold("\n\u2500\u2500 Claude Desktop integration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
9354
9354
  return confirm({ message: "Register with Claude Desktop?", default: true });
9355
9355
  }
9356
- function writeRoutersYaml(data, configDir) {
9357
- const filePath = join2(configDir, "routers.yaml");
9356
+ function writeRoutersYaml(data) {
9357
+ const filePath = join2(data.configDir, "routers.yaml");
9358
9358
  const newEntry = {
9359
9359
  host: data.host,
9360
9360
  port: data.port,
@@ -9376,12 +9376,12 @@ function writeRoutersYaml(data, configDir) {
9376
9376
  if (!parsed.routers) parsed.routers = {};
9377
9377
  }
9378
9378
  parsed.routers[data.routerId] = newEntry;
9379
- mkdirSync3(configDir, { recursive: true });
9379
+ mkdirSync3(data.configDir, { recursive: true });
9380
9380
  writeFileSync2(filePath, yamlStringify(parsed, { lineWidth: 0 }));
9381
9381
  return true;
9382
9382
  }
9383
- function writeIdentitiesYaml(data, configDir) {
9384
- const filePath = join2(configDir, "identities.yaml");
9383
+ function writeIdentitiesYaml(data) {
9384
+ const filePath = join2(data.configDir, "identities.yaml");
9385
9385
  const newEntry = {
9386
9386
  token: data.tokenHash,
9387
9387
  role: data.role,
@@ -9395,32 +9395,55 @@ function writeIdentitiesYaml(data, configDir) {
9395
9395
  if (!parsed.identities) parsed.identities = {};
9396
9396
  }
9397
9397
  parsed.identities[data.identityId] = newEntry;
9398
- mkdirSync3(configDir, { recursive: true });
9398
+ mkdirSync3(data.configDir, { recursive: true });
9399
9399
  writeFileSync2(filePath, yamlStringify(parsed, { lineWidth: 0 }));
9400
9400
  return true;
9401
9401
  }
9402
- function writeDotEnv(data, projectRoot) {
9403
- const envPath = join2(projectRoot, ".env");
9404
- const lines = [];
9405
- lines.push(`ROUTER_${data.envPrefix.replace(/^ROUTER_/, "")}_USER=`);
9406
- lines.push(`ROUTER_${data.envPrefix.replace(/^ROUTER_/, "")}_PASS=`);
9402
+ function writeDotEnv(data) {
9407
9403
  const userKey = `${data.envPrefix}_USER`;
9408
9404
  const passKey = `${data.envPrefix}_PASS`;
9409
- if (data.createIdentity) {
9410
- lines.push("MIKROMCP_TRANSPORT=http");
9411
- lines.push("MIKROMCP_CONFIRMATION_SECRET=");
9412
- }
9413
- const block = [`${userKey}=`, `${passKey}=`, ...data.createIdentity ? ["MIKROMCP_TRANSPORT=http", "MIKROMCP_CONFIRMATION_SECRET="] : []].join("\n") + "\n";
9414
- if (existsSync4(envPath)) {
9415
- const existing = readFileSync7(envPath, "utf-8");
9416
- writeFileSync2(envPath, existing.endsWith("\n") ? existing + block : existing + "\n" + block);
9405
+ const auditLogPath = defaultAuditLogPath();
9406
+ const transport2 = data.createIdentity ? "http" : "stdio";
9407
+ const confirmationSecret = data.createIdentity ? randomBytes(32).toString("hex") : "";
9408
+ const lines = [
9409
+ "# \u2500\u2500 Router credentials \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
9410
+ `${userKey}=`,
9411
+ `${passKey}=`,
9412
+ "",
9413
+ "# \u2500\u2500 Transport \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
9414
+ `# stdio \u2192 launched directly by Claude Desktop or another MCP client`,
9415
+ `# http \u2192 standalone server mode with bearer-token auth`,
9416
+ `MIKROMCP_TRANSPORT=${transport2}`,
9417
+ "",
9418
+ "# \u2500\u2500 Config paths \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
9419
+ `MIKROMCP_CONFIG_PATH=${join2(data.configDir, "routers.yaml")}`,
9420
+ `MIKROMCP_IDENTITIES_PATH=${join2(data.configDir, "identities.yaml")}`,
9421
+ "",
9422
+ "# \u2500\u2500 Logging \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
9423
+ "# Levels: trace | debug | info | warn | error",
9424
+ "MIKROMCP_LOG_LEVEL=info",
9425
+ `MIKROMCP_AUDIT_LOG_PATH=${auditLogPath}`,
9426
+ "",
9427
+ "# \u2500\u2500 HTTP transport (only used when MIKROMCP_TRANSPORT=http) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
9428
+ "MIKROMCP_PORT=3000",
9429
+ `MIKROMCP_CONFIRMATION_SECRET=${confirmationSecret}`,
9430
+ "",
9431
+ "# \u2500\u2500 RBAC (optional, for stdio with identity enforcement) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
9432
+ "# MIKROMCP_STDIO_IDENTITY="
9433
+ ];
9434
+ const content = lines.join("\n") + "\n";
9435
+ mkdirSync3(data.configDir, { recursive: true });
9436
+ if (existsSync4(data.envPath)) {
9437
+ const existing = readFileSync7(data.envPath, "utf-8");
9438
+ writeFileSync2(data.envPath, existing.endsWith("\n") ? existing + "\n" + content : existing + "\n\n" + content);
9417
9439
  } else {
9418
- writeFileSync2(envPath, block);
9440
+ writeFileSync2(data.envPath, content);
9419
9441
  }
9420
- appendToGitignore(projectRoot, ".env");
9421
- console.log(chalk2.dim(` Edit .env and fill in your router credentials`));
9442
+ console.log(
9443
+ chalk2.dim(` Fill in ${userKey} and ${passKey} in ${data.envPath}`)
9444
+ );
9422
9445
  }
9423
- function registerClaudeDesktop(_projectRoot) {
9446
+ function registerClaudeDesktop() {
9424
9447
  const configPath = findClaudeDesktopConfig();
9425
9448
  const entry = {
9426
9449
  command: "mikromcp",
@@ -9452,17 +9475,17 @@ function registerClaudeDesktop(_projectRoot) {
9452
9475
  }
9453
9476
  async function runInit() {
9454
9477
  console.log(chalk2.bold.cyan("\n\u{1F527} MikroMCP \u2014 Interactive Setup Wizard\n"));
9455
- const projectRoot = process.cwd();
9456
- const configDir = join2(projectRoot, "config");
9457
9478
  const routerInfo = await collectRouterInfo();
9458
9479
  const identityInfo = await collectIdentityInfo();
9459
- const writeEnv = await collectEnvPreference({ ...routerInfo, ...identityInfo });
9480
+ const configDir = mikromcpDir();
9481
+ const envPath = join2(configDir, ".env");
9482
+ const writeEnv = await collectEnvPreference();
9460
9483
  let writeRouters = true;
9461
9484
  const routersYamlPath = join2(configDir, "routers.yaml");
9462
9485
  if (existsSync4(routersYamlPath)) {
9463
9486
  console.log(chalk2.bold("\n\u2500\u2500 Config files \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
9464
9487
  writeRouters = await confirm({
9465
- message: "config/routers.yaml already exists. Add this router to it?",
9488
+ message: "routers.yaml already exists. Add this router to it?",
9466
9489
  default: true
9467
9490
  });
9468
9491
  }
@@ -9470,6 +9493,8 @@ async function runInit() {
9470
9493
  const data = {
9471
9494
  ...routerInfo,
9472
9495
  ...identityInfo,
9496
+ envPath,
9497
+ configDir,
9473
9498
  writeEnv,
9474
9499
  writeRoutersYaml: writeRouters,
9475
9500
  writeIdentitiesYaml: identityInfo.createIdentity,
@@ -9478,23 +9503,23 @@ async function runInit() {
9478
9503
  console.log(chalk2.bold("\n\u2500\u2500 Writing files \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
9479
9504
  const summary = [];
9480
9505
  if (data.writeRoutersYaml) {
9481
- writeRoutersYaml(data, configDir);
9482
- summary.push(`config/routers.yaml updated`);
9483
- console.log(chalk2.green(" \u2714 config/routers.yaml written"));
9506
+ writeRoutersYaml(data);
9507
+ summary.push(`routers.yaml written to ${data.configDir}`);
9508
+ console.log(chalk2.green(` \u2714 ${join2(data.configDir, "routers.yaml")}`));
9484
9509
  }
9485
9510
  if (data.writeIdentitiesYaml) {
9486
- writeIdentitiesYaml(data, configDir);
9487
- summary.push(`config/identities.yaml updated`);
9488
- console.log(chalk2.green(" \u2714 config/identities.yaml written"));
9511
+ writeIdentitiesYaml(data);
9512
+ summary.push(`identities.yaml written to ${data.configDir}`);
9513
+ console.log(chalk2.green(` \u2714 ${join2(data.configDir, "identities.yaml")}`));
9489
9514
  }
9490
9515
  if (data.writeEnv) {
9491
- writeDotEnv(data, projectRoot);
9492
- summary.push(`.env created (fill in ${data.envPrefix}_USER and ${data.envPrefix}_PASS)`);
9493
- console.log(chalk2.green(" \u2714 .env written"));
9516
+ writeDotEnv(data);
9517
+ summary.push(`.env written to ${data.envPath}`);
9518
+ console.log(chalk2.green(` \u2714 ${data.envPath}`));
9494
9519
  }
9495
9520
  let desktopRegistered = false;
9496
9521
  if (data.registerClaudeDesktop) {
9497
- const result = registerClaudeDesktop(projectRoot);
9522
+ const result = registerClaudeDesktop();
9498
9523
  desktopRegistered = result.registered;
9499
9524
  if (desktopRegistered) {
9500
9525
  summary.push("Claude Desktop registered");
@@ -9506,22 +9531,24 @@ async function runInit() {
9506
9531
  console.log(chalk2.green(` \u2705 ${item}`));
9507
9532
  }
9508
9533
  if (data.createIdentity && data.rawToken) {
9509
- console.log(chalk2.bold.yellow(` \u2705 Bearer token: ${data.rawToken} (save this \u2014 it won't be shown again)`));
9534
+ console.log(
9535
+ chalk2.bold.yellow(` \u2705 Bearer token: ${data.rawToken} (save this \u2014 it won't be shown again)`)
9536
+ );
9510
9537
  }
9511
9538
  console.log(chalk2.bold("\nNext steps:"));
9512
- console.log(chalk2.dim(` 1. Edit .env and fill in router credentials`));
9513
- console.log(chalk2.dim(" 2. Run: npm start"));
9514
- console.log(chalk2.dim(" 3. Verify: mikromcp doctor"));
9539
+ console.log(chalk2.dim(` 1. Fill in router credentials in ${data.envPath}`));
9540
+ console.log(chalk2.dim(" 2. Run: mikromcp doctor"));
9541
+ console.log(chalk2.dim(" 3. Restart Claude Desktop to load the MCP server"));
9515
9542
  console.log();
9516
9543
  }
9517
9544
 
9518
9545
  // src/cli/index.ts
9519
- var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.0.1");
9546
+ var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.0.2");
9520
9547
  program.command("serve", { isDefault: true }).description("Start the MCP server (default)").action(runServe);
9521
9548
  program.command("doctor").description("Check config, connectivity, and permissions").action(runDoctor);
9522
9549
  program.command("init").description("Interactive setup wizard").action(runInit);
9523
9550
 
9524
9551
  // src/main.ts
9525
- loadDotenv({ override: false });
9552
+ loadDotenv({ path: join3(homedir3(), ".mikromcp", ".env"), override: false });
9526
9553
  program.parse();
9527
9554
  //# sourceMappingURL=main.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikromcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Production-grade MCP server for MikroTik RouterOS",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -19,7 +19,7 @@
19
19
  "dev": "tsx watch src/main.ts",
20
20
  "build": "tsup",
21
21
  "clean": "rm -rf dist",
22
- "test": "vitest run",
22
+ "test": "vitest run && tsc --noEmit && eslint src/",
23
23
  "test:watch": "vitest",
24
24
  "lint": "eslint src/",
25
25
  "format": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",