mikromcp 1.0.0 → 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 -67
  3. package/package.json +3 -3
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,10 +2,11 @@
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";
8
- import { createRequire as createRequire2 } from "module";
9
10
 
10
11
  // src/config/app-config.ts
11
12
  function loadAppConfig() {
@@ -8605,7 +8606,7 @@ function createServerFactory(config, identityRegistry) {
8605
8606
  const makeServer = () => {
8606
8607
  const server = new McpServer({
8607
8608
  name: "mikromcp",
8608
- version: "1.0.0"
8609
+ version: "1.0.2"
8609
8610
  });
8610
8611
  registerAllTools(server, registry, pool, circuitBreakers, config, identityRegistry);
8611
8612
  return server;
@@ -8927,10 +8928,8 @@ async function runServe() {
8927
8928
  import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
8928
8929
  import { homedir } from "os";
8929
8930
  import * as net from "net";
8930
- import { createRequire } from "module";
8931
8931
  import chalk from "chalk";
8932
- var require2 = createRequire(import.meta.url);
8933
- var { version: localVersion } = require2("../../package.json");
8932
+ var localVersion = "1.0.2";
8934
8933
  var results = [];
8935
8934
  function ok(msg) {
8936
8935
  console.log(chalk.green("\u2705 ") + msg);
@@ -9214,6 +9213,12 @@ import { join as join2 } from "path";
9214
9213
  import { stringify as yamlStringify, parse as yamlParse } from "yaml";
9215
9214
  import bcrypt2 from "bcryptjs";
9216
9215
  import chalk2 from "chalk";
9216
+ function mikromcpDir() {
9217
+ return join2(homedir2(), ".mikromcp");
9218
+ }
9219
+ function defaultAuditLogPath() {
9220
+ return join2(mikromcpDir(), "audit.ndjson");
9221
+ }
9217
9222
  function suggestEnvPrefix(routerId) {
9218
9223
  return "ROUTER_" + routerId.toUpperCase().replace(/-/g, "_");
9219
9224
  }
@@ -9231,17 +9236,6 @@ function findClaudeDesktopConfig() {
9231
9236
  ];
9232
9237
  return candidates.find((p) => existsSync4(p)) ?? null;
9233
9238
  }
9234
- function appendToGitignore(projectRoot, entry) {
9235
- const gitignorePath = join2(projectRoot, ".gitignore");
9236
- if (existsSync4(gitignorePath)) {
9237
- const content = readFileSync7(gitignorePath, "utf-8");
9238
- if (!content.split("\n").some((line) => line.trim() === entry)) {
9239
- writeFileSync2(gitignorePath, content.endsWith("\n") ? content + entry + "\n" : content + "\n" + entry + "\n");
9240
- }
9241
- } else {
9242
- writeFileSync2(gitignorePath, entry + "\n");
9243
- }
9244
- }
9245
9239
  async function collectRouterInfo() {
9246
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"));
9247
9241
  const routerId = await input({
@@ -9249,19 +9243,20 @@ async function collectRouterInfo() {
9249
9243
  validate: validateRouterId
9250
9244
  });
9251
9245
  const host = await input({
9252
- message: "Host / IP (e.g. 192.168.1.1):",
9246
+ message: "Host / IP (e.g. 192.168.88.1):",
9253
9247
  validate: (v) => v.trim().length > 0 ? true : "Host is required"
9254
9248
  });
9255
9249
  const portStr = await input({
9256
9250
  message: "Port:",
9257
- default: "80",
9251
+ default: "443",
9258
9252
  validate: (v) => {
9259
9253
  const n = Number(v);
9260
9254
  return Number.isInteger(n) && n >= 1 && n <= 65535 ? true : "Port must be 1\u201365535";
9261
9255
  }
9262
9256
  });
9263
9257
  const port = Number(portStr);
9264
- 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 });
9265
9260
  let rejectUnauthorized = true;
9266
9261
  if (tlsEnabled) {
9267
9262
  rejectUnauthorized = await confirm({
@@ -9348,16 +9343,18 @@ async function collectIdentityInfo() {
9348
9343
  tokenHash
9349
9344
  };
9350
9345
  }
9351
- async function collectEnvPreference(_data) {
9352
- 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"));
9353
- 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
+ });
9354
9351
  }
9355
9352
  async function collectClaudeDesktopPreference() {
9356
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"));
9357
9354
  return confirm({ message: "Register with Claude Desktop?", default: true });
9358
9355
  }
9359
- function writeRoutersYaml(data, configDir) {
9360
- const filePath = join2(configDir, "routers.yaml");
9356
+ function writeRoutersYaml(data) {
9357
+ const filePath = join2(data.configDir, "routers.yaml");
9361
9358
  const newEntry = {
9362
9359
  host: data.host,
9363
9360
  port: data.port,
@@ -9379,12 +9376,12 @@ function writeRoutersYaml(data, configDir) {
9379
9376
  if (!parsed.routers) parsed.routers = {};
9380
9377
  }
9381
9378
  parsed.routers[data.routerId] = newEntry;
9382
- mkdirSync3(configDir, { recursive: true });
9379
+ mkdirSync3(data.configDir, { recursive: true });
9383
9380
  writeFileSync2(filePath, yamlStringify(parsed, { lineWidth: 0 }));
9384
9381
  return true;
9385
9382
  }
9386
- function writeIdentitiesYaml(data, configDir) {
9387
- const filePath = join2(configDir, "identities.yaml");
9383
+ function writeIdentitiesYaml(data) {
9384
+ const filePath = join2(data.configDir, "identities.yaml");
9388
9385
  const newEntry = {
9389
9386
  token: data.tokenHash,
9390
9387
  role: data.role,
@@ -9398,32 +9395,55 @@ function writeIdentitiesYaml(data, configDir) {
9398
9395
  if (!parsed.identities) parsed.identities = {};
9399
9396
  }
9400
9397
  parsed.identities[data.identityId] = newEntry;
9401
- mkdirSync3(configDir, { recursive: true });
9398
+ mkdirSync3(data.configDir, { recursive: true });
9402
9399
  writeFileSync2(filePath, yamlStringify(parsed, { lineWidth: 0 }));
9403
9400
  return true;
9404
9401
  }
9405
- function writeDotEnv(data, projectRoot) {
9406
- const envPath = join2(projectRoot, ".env");
9407
- const lines = [];
9408
- lines.push(`ROUTER_${data.envPrefix.replace(/^ROUTER_/, "")}_USER=`);
9409
- lines.push(`ROUTER_${data.envPrefix.replace(/^ROUTER_/, "")}_PASS=`);
9402
+ function writeDotEnv(data) {
9410
9403
  const userKey = `${data.envPrefix}_USER`;
9411
9404
  const passKey = `${data.envPrefix}_PASS`;
9412
- if (data.createIdentity) {
9413
- lines.push("MIKROMCP_TRANSPORT=http");
9414
- lines.push("MIKROMCP_CONFIRMATION_SECRET=");
9415
- }
9416
- const block = [`${userKey}=`, `${passKey}=`, ...data.createIdentity ? ["MIKROMCP_TRANSPORT=http", "MIKROMCP_CONFIRMATION_SECRET="] : []].join("\n") + "\n";
9417
- if (existsSync4(envPath)) {
9418
- const existing = readFileSync7(envPath, "utf-8");
9419
- 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);
9420
9439
  } else {
9421
- writeFileSync2(envPath, block);
9440
+ writeFileSync2(data.envPath, content);
9422
9441
  }
9423
- appendToGitignore(projectRoot, ".env");
9424
- 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
+ );
9425
9445
  }
9426
- function registerClaudeDesktop(_projectRoot) {
9446
+ function registerClaudeDesktop() {
9427
9447
  const configPath = findClaudeDesktopConfig();
9428
9448
  const entry = {
9429
9449
  command: "mikromcp",
@@ -9455,17 +9475,17 @@ function registerClaudeDesktop(_projectRoot) {
9455
9475
  }
9456
9476
  async function runInit() {
9457
9477
  console.log(chalk2.bold.cyan("\n\u{1F527} MikroMCP \u2014 Interactive Setup Wizard\n"));
9458
- const projectRoot = process.cwd();
9459
- const configDir = join2(projectRoot, "config");
9460
9478
  const routerInfo = await collectRouterInfo();
9461
9479
  const identityInfo = await collectIdentityInfo();
9462
- const writeEnv = await collectEnvPreference({ ...routerInfo, ...identityInfo });
9480
+ const configDir = mikromcpDir();
9481
+ const envPath = join2(configDir, ".env");
9482
+ const writeEnv = await collectEnvPreference();
9463
9483
  let writeRouters = true;
9464
9484
  const routersYamlPath = join2(configDir, "routers.yaml");
9465
9485
  if (existsSync4(routersYamlPath)) {
9466
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"));
9467
9487
  writeRouters = await confirm({
9468
- message: "config/routers.yaml already exists. Add this router to it?",
9488
+ message: "routers.yaml already exists. Add this router to it?",
9469
9489
  default: true
9470
9490
  });
9471
9491
  }
@@ -9473,6 +9493,8 @@ async function runInit() {
9473
9493
  const data = {
9474
9494
  ...routerInfo,
9475
9495
  ...identityInfo,
9496
+ envPath,
9497
+ configDir,
9476
9498
  writeEnv,
9477
9499
  writeRoutersYaml: writeRouters,
9478
9500
  writeIdentitiesYaml: identityInfo.createIdentity,
@@ -9481,23 +9503,23 @@ async function runInit() {
9481
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"));
9482
9504
  const summary = [];
9483
9505
  if (data.writeRoutersYaml) {
9484
- writeRoutersYaml(data, configDir);
9485
- summary.push(`config/routers.yaml updated`);
9486
- 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")}`));
9487
9509
  }
9488
9510
  if (data.writeIdentitiesYaml) {
9489
- writeIdentitiesYaml(data, configDir);
9490
- summary.push(`config/identities.yaml updated`);
9491
- 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")}`));
9492
9514
  }
9493
9515
  if (data.writeEnv) {
9494
- writeDotEnv(data, projectRoot);
9495
- summary.push(`.env created (fill in ${data.envPrefix}_USER and ${data.envPrefix}_PASS)`);
9496
- 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}`));
9497
9519
  }
9498
9520
  let desktopRegistered = false;
9499
9521
  if (data.registerClaudeDesktop) {
9500
- const result = registerClaudeDesktop(projectRoot);
9522
+ const result = registerClaudeDesktop();
9501
9523
  desktopRegistered = result.registered;
9502
9524
  if (desktopRegistered) {
9503
9525
  summary.push("Claude Desktop registered");
@@ -9509,24 +9531,24 @@ async function runInit() {
9509
9531
  console.log(chalk2.green(` \u2705 ${item}`));
9510
9532
  }
9511
9533
  if (data.createIdentity && data.rawToken) {
9512
- 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
+ );
9513
9537
  }
9514
9538
  console.log(chalk2.bold("\nNext steps:"));
9515
- console.log(chalk2.dim(` 1. Edit .env and fill in router credentials`));
9516
- console.log(chalk2.dim(" 2. Run: npm start"));
9517
- 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"));
9518
9542
  console.log();
9519
9543
  }
9520
9544
 
9521
9545
  // src/cli/index.ts
9522
- var require3 = createRequire2(import.meta.url);
9523
- var { version } = require3("../../package.json");
9524
- var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version(version);
9546
+ var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.0.2");
9525
9547
  program.command("serve", { isDefault: true }).description("Start the MCP server (default)").action(runServe);
9526
9548
  program.command("doctor").description("Check config, connectivity, and permissions").action(runDoctor);
9527
9549
  program.command("init").description("Interactive setup wizard").action(runInit);
9528
9550
 
9529
9551
  // src/main.ts
9530
- loadDotenv({ override: false });
9552
+ loadDotenv({ path: join3(homedir3(), ".mikromcp", ".env"), override: false });
9531
9553
  program.parse();
9532
9554
  //# sourceMappingURL=main.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikromcp",
3
- "version": "1.0.0",
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'",
@@ -38,7 +38,7 @@
38
38
  ],
39
39
  "repository": {
40
40
  "type": "git",
41
- "url": "https://github.com/AliKarami/MikroMCP"
41
+ "url": "git+https://github.com/AliKarami/MikroMCP.git"
42
42
  },
43
43
  "author": "Ali Karami",
44
44
  "license": "MIT",