mikromcp 1.0.2 → 1.0.4

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 +136 -132
  2. package/dist/main.js +77 -46
  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.0.2-EA580C)](package.json)
15
+ [![Version](https://img.shields.io/badge/version-v1.0.4-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)
@@ -37,8 +37,6 @@ MikroMCP exists because raw router CLI access is the wrong abstraction for AI ag
37
37
  | Raw network access for every assistant | RBAC identities, bearer tokens for HTTP mode, tool allowlists, and audit trails |
38
38
  | Fragile troubleshooting workflows | Router-originated ping, traceroute, torch, logs, interfaces, DHCP, firewall, routes, WiFi, WireGuard, and more |
39
39
 
40
- MikroMCP is especially useful when you want AI agents to help with network operations without giving them unchecked terminal access.
41
-
42
40
  ---
43
41
 
44
42
  ## Feature Showcase
@@ -56,8 +54,6 @@ MikroMCP is especially useful when you want AI agents to help with network opera
56
54
  | 🤖 **AI-agent fit** | Human-readable responses plus structured JSON content for reasoning, chaining, and automation |
57
55
  | 🧩 **MCP compatibility** | stdio for desktop clients, Streamable HTTP and legacy SSE for remote or service-style clients |
58
56
 
59
- **Best-in-class strengths:** MikroMCP is not just a thin REST wrapper. It models operational safety around RouterOS: typed tool contracts, safe write patterns, router-aware credentials, destructive-action gates, and rollback-oriented workflows.
60
-
61
57
  ---
62
58
 
63
59
  ## Demo
@@ -78,10 +74,9 @@ MikroMCP is especially useful when you want AI agents to help with network opera
78
74
 
79
75
  ### Prerequisites
80
76
 
81
- - Node.js 22 or newer
77
+ - Node.js 22 or newer (for npm install) — or use a standalone binary below
82
78
  - MikroTik RouterOS 7.x with the REST API enabled
83
- - A RouterOS user with the policies required by the tools you plan to use
84
- - Git and npm
79
+ - A RouterOS user with the policies your tools require
85
80
 
86
81
  Recommended RouterOS policies for full tool coverage:
87
82
 
@@ -89,29 +84,65 @@ Recommended RouterOS policies for full tool coverage:
89
84
  read, write, api, rest-api, test, ssh, sniff, ftp
90
85
  ```
91
86
 
92
- Notes:
87
+ > `ssh` is required for `ping`, `traceroute`, `torch`, and `run_command`. `sniff` is required by `torch`. `ftp` is required only for `upload_file`.
88
+
89
+ ---
90
+
91
+ ### Install
92
+
93
+ **npm (recommended)**
94
+
95
+ ```bash
96
+ npm install -g mikromcp
97
+ ```
98
+
99
+ **Standalone binaries**
93
100
 
94
- - `ssh` is required for `ping`, `traceroute`, `torch`, and `run_command`.
95
- - `sniff` is required by `torch`.
96
- - `ftp` is required only for `upload_file`.
101
+ Download the binary for your platform from the [latest GitHub release](https://github.com/AliKarami/MikroMCP/releases/latest) — no Node.js required.
97
102
 
98
- ### Install Locally
103
+ **Docker**
99
104
 
100
105
  ```bash
101
- git clone https://github.com/AliKarami/MikroMCP.git
102
- cd MikroMCP
103
- npm install
104
- npm run build
105
- cp config/routers.example.yaml config/routers.yaml
106
- cp config/identities.example.yaml config/identities.yaml
106
+ docker pull ghcr.io/alikarami/mikromcp:latest
107
+ ```
108
+
109
+ ---
110
+
111
+ ### Set Up With the Init Wizard
112
+
113
+ Run the interactive setup wizard:
114
+
115
+ ```bash
116
+ mikromcp init
107
117
  ```
108
118
 
109
- Edit `config/routers.yaml`:
119
+ The wizard will ask for your router details and write everything to `~/.mikromcp/`:
120
+
121
+ ```
122
+ ~/.mikromcp/
123
+ ├── routers.yaml # router registry
124
+ ├── identities.yaml # RBAC identities (HTTP mode)
125
+ └── .env # credentials and runtime settings
126
+ ```
127
+
128
+ `~/.mikromcp/.env` is loaded automatically every time MikroMCP starts — no shell exports or Claude Desktop `env` blocks needed. Fill in the credentials it generates:
129
+
130
+ ```bash
131
+ # ~/.mikromcp/.env (generated by mikromcp init)
132
+ ROUTER_CORE01_USER=
133
+ ROUTER_CORE01_PASS=
134
+ ```
135
+
136
+ To edit your router registry directly:
137
+
138
+ ```bash
139
+ nano ~/.mikromcp/routers.yaml
140
+ ```
110
141
 
111
142
  ```yaml
112
143
  routers:
113
144
  core-01:
114
- host: "10.0.0.1"
145
+ host: "192.168.88.1"
115
146
  port: 443
116
147
  tls:
117
148
  enabled: true
@@ -119,59 +150,34 @@ routers:
119
150
  credentials:
120
151
  source: "env"
121
152
  envPrefix: "ROUTER_CORE01"
122
- tags: ["core", "production"]
123
- rosVersion: "7.14"
153
+ tags: ["core"]
154
+ rosVersion: "7"
124
155
  ```
125
156
 
126
- Provide router credentials through environment variables:
127
-
128
- ```bash
129
- export ROUTER_CORE01_USER="mcp-api"
130
- export ROUTER_CORE01_PASS="your-router-password"
131
- npm start
132
- ```
133
-
134
- ### Docker
157
+ ---
135
158
 
136
- #### Pull and run
159
+ ### Verify With Doctor
137
160
 
138
161
  ```bash
139
- docker pull ghcr.io/alikarami/mikromcp:latest
162
+ mikromcp doctor
140
163
  ```
141
164
 
142
- One-liner with environment variables:
165
+ Doctor checks Node version, config files, router reachability, Claude Desktop registration, and whether a newer version is available.
143
166
 
144
- ```bash
145
- docker run --rm \
146
- -e MIKROMCP_TRANSPORT=http \
147
- -e MIKROMCP_PORT=3000 \
148
- -e MIKROMCP_CONFIRMATION_SECRET="$(openssl rand -hex 32)" \
149
- -e ROUTER_CORE01_USER=mcp-api \
150
- -e ROUTER_CORE01_PASS=your-router-password \
151
- -v "$(pwd)/config:/app/config:ro" \
152
- -p 3000:3000 \
153
- ghcr.io/alikarami/mikromcp:latest
154
- ```
167
+ ---
155
168
 
156
- #### Docker Compose
169
+ ### Run
157
170
 
158
- Copy the example file and fill in your `.env`:
171
+ **stdio (for Claude Desktop and other desktop MCP clients)**
159
172
 
160
173
  ```bash
161
- cp docker-compose.example.yml docker-compose.yml
162
- cp config/routers.example.yaml config/routers.yaml
163
- cp config/identities.example.yaml config/identities.yaml
164
- # edit config/routers.yaml and set MIKROMCP_CONFIRMATION_SECRET + router credentials in .env
165
- docker compose up -d
174
+ mikromcp serve
166
175
  ```
167
176
 
168
- See `docker-compose.example.yml` for the full service definition.
169
-
170
- #### Build from source
177
+ **HTTP mode (for service deployments)**
171
178
 
172
179
  ```bash
173
- docker build -t mikromcp .
174
- docker run --rm -e MIKROMCP_TRANSPORT=http -p 3000:3000 mikromcp
180
+ MIKROMCP_TRANSPORT=http mikromcp serve
175
181
  ```
176
182
 
177
183
  ---
@@ -180,25 +186,22 @@ docker run --rm -e MIKROMCP_TRANSPORT=http -p 3000:3000 mikromcp
180
186
 
181
187
  ### Claude Desktop
182
188
 
183
- Add MikroMCP to `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS:
189
+ Run `mikromcp init` and choose **Register with Claude Desktop** — it patches `claude_desktop_config.json` automatically.
190
+
191
+ Or add it manually to `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS:
184
192
 
185
193
  ```json
186
194
  {
187
195
  "mcpServers": {
188
- "mikrotik": {
189
- "command": "node",
190
- "args": ["/absolute/path/to/MikroMCP/dist/main.js"],
191
- "env": {
192
- "MIKROMCP_CONFIG_PATH": "/absolute/path/to/MikroMCP/config/routers.yaml",
193
- "ROUTER_CORE01_USER": "mcp-api",
194
- "ROUTER_CORE01_PASS": "your-router-password"
195
- }
196
+ "mikromcp": {
197
+ "command": "mikromcp",
198
+ "args": ["serve"]
196
199
  }
197
200
  }
198
201
  }
199
202
  ```
200
203
 
201
- Restart Claude Desktop, then ask:
204
+ No `env` block needed — credentials are loaded from `~/.mikromcp/.env` at startup. Restart Claude Desktop, then ask:
202
205
 
203
206
  ```text
204
207
  Use MikroMCP to show CPU, memory, uptime, active interfaces, and warning logs for core-01.
@@ -206,48 +209,72 @@ Use MikroMCP to show CPU, memory, uptime, active interfaces, and warning logs fo
206
209
 
207
210
  ### HTTP / SSE Mode
208
211
 
209
- HTTP mode is useful for service deployments and MCP clients that connect over a local or private network endpoint.
212
+ HTTP mode is useful for service deployments and MCP clients that connect over a network endpoint.
213
+
214
+ Set in `~/.mikromcp/.env`:
215
+
216
+ ```bash
217
+ MIKROMCP_TRANSPORT=http
218
+ MIKROMCP_PORT=3000
219
+ MIKROMCP_CONFIRMATION_SECRET=<openssl rand -hex 32>
220
+ ```
221
+
222
+ Then run:
210
223
 
211
224
  ```bash
212
- export MIKROMCP_TRANSPORT=http
213
- export MIKROMCP_PORT=3000
214
- export MIKROMCP_BIND_HOST=127.0.0.1
215
- export MIKROMCP_CONFIRMATION_SECRET="$(openssl rand -hex 32)"
216
- export ROUTER_CORE01_USER="mcp-api"
217
- export ROUTER_CORE01_PASS="your-router-password"
218
- npm start
225
+ mikromcp serve
219
226
  ```
220
227
 
221
- Every HTTP/SSE request must include:
228
+ Every HTTP request must include:
222
229
 
223
230
  ```text
224
231
  Authorization: Bearer <token>
225
232
  ```
226
233
 
227
- Tokens are configured as bcrypt hashes in `config/identities.yaml`.
234
+ Tokens are configured as bcrypt hashes in `~/.mikromcp/identities.yaml`. Use `mikromcp init` to generate them.
235
+
236
+ ### Docker
237
+
238
+ ```bash
239
+ docker run --rm \
240
+ -e MIKROMCP_TRANSPORT=http \
241
+ -e MIKROMCP_PORT=3000 \
242
+ -e MIKROMCP_CONFIRMATION_SECRET="$(openssl rand -hex 32)" \
243
+ -e ROUTER_CORE01_USER=mcp-api \
244
+ -e ROUTER_CORE01_PASS=your-router-password \
245
+ -e MIKROMCP_CONFIG_PATH=/config/routers.yaml \
246
+ -v "$HOME/.mikromcp:/config:ro" \
247
+ -p 3000:3000 \
248
+ ghcr.io/alikarami/mikromcp:latest
249
+ ```
250
+
251
+ Pass `MIKROMCP_CONFIG_PATH` and `MIKROMCP_IDENTITIES_PATH` explicitly when running in Docker since `~/.mikromcp/` inside the container refers to the container's home directory.
228
252
 
229
253
  ---
230
254
 
231
255
  ## Configuration Reference
232
256
 
233
- | Variable | Default | Purpose |
234
- | --------------------------------- | ------------------------ | ------------------------------------------------------ |
235
- | `MIKROMCP_TRANSPORT` | `stdio` | `stdio` or `http` |
236
- | `MIKROMCP_CONFIG_PATH` | `config/routers.yaml` | Router registry path |
237
- | `MIKROMCP_IDENTITIES_PATH` | `config/identities.yaml` | Identity and bearer-token registry |
238
- | `MIKROMCP_STDIO_IDENTITY` | built-in superadmin | Named identity for stdio mode |
239
- | `MIKROMCP_PORT` | `3000` | HTTP transport port |
240
- | `MIKROMCP_BIND_HOST` | `127.0.0.1` | HTTP bind address |
241
- | `MIKROMCP_CONFIRMATION_SECRET` | unset | HMAC secret for destructive-action confirmation tokens |
242
- | `MIKROMCP_AUDIT_LOG_PATH` | unset | Optional NDJSON audit log file path |
243
- | `MIKROMCP_HTTP_MAX_BODY_BYTES` | `1048576` | HTTP request body cap |
244
- | `MIKROMCP_HTTP_RATE_LIMIT_RPM` | `60` | Requests per minute per IP; `0` disables rate limiting |
245
- | `MIKROMCP_SSH_COMMAND_TIMEOUT_MS` | `30000` | SSH command timeout |
246
- | `MIKROMCP_SSH_MAX_OUTPUT_BYTES` | `524288` | SSH output cap |
247
- | `MIKROMCP_CMD_ALLOW` | unset | Global allowlist patterns for `run_command` |
248
- | `MIKROMCP_CMD_DENY` | unset | Global denylist patterns for `run_command` |
249
- | `ROUTER_<PREFIX>_USER` | unset | Router username from `envPrefix` |
250
- | `ROUTER_<PREFIX>_PASS` | unset | Router password from `envPrefix` |
257
+ All settings can be placed in `~/.mikromcp/.env` or passed as environment variables. Values in `~/.mikromcp/.env` are loaded at startup; explicit environment variables always take precedence.
258
+
259
+ | Variable | Default | Purpose |
260
+ | --------------------------------- | -------------------------------- | ------------------------------------------------------ |
261
+ | `MIKROMCP_TRANSPORT` | `stdio` | `stdio` or `http` |
262
+ | `MIKROMCP_CONFIG_PATH` | `~/.mikromcp/routers.yaml` | Router registry path |
263
+ | `MIKROMCP_IDENTITIES_PATH` | `~/.mikromcp/identities.yaml` | Identity and bearer-token registry |
264
+ | `MIKROMCP_STDIO_IDENTITY` | built-in superadmin | Named identity for stdio mode |
265
+ | `MIKROMCP_PORT` | `3000` | HTTP transport port |
266
+ | `MIKROMCP_BIND_HOST` | `127.0.0.1` | HTTP bind address |
267
+ | `MIKROMCP_CONFIRMATION_SECRET` | unset | HMAC secret for destructive-action confirmation tokens |
268
+ | `MIKROMCP_AUDIT_LOG_PATH` | unset | Optional NDJSON audit log file path |
269
+ | `MIKROMCP_DATA_DIR` | `~/.mikromcp/data` | Snapshots and write-journal directory |
270
+ | `MIKROMCP_HTTP_MAX_BODY_BYTES` | `1048576` | HTTP request body cap |
271
+ | `MIKROMCP_HTTP_RATE_LIMIT_RPM` | `60` | Requests per minute per IP; `0` disables rate limiting |
272
+ | `MIKROMCP_SSH_COMMAND_TIMEOUT_MS` | `30000` | SSH command timeout |
273
+ | `MIKROMCP_SSH_MAX_OUTPUT_BYTES` | `524288` | SSH output cap |
274
+ | `MIKROMCP_CMD_ALLOW` | unset | Global allowlist patterns for `run_command` |
275
+ | `MIKROMCP_CMD_DENY` | unset | Global denylist patterns for `run_command` |
276
+ | `ROUTER_<PREFIX>_USER` | unset | Router username from `envPrefix` |
277
+ | `ROUTER_<PREFIX>_PASS` | unset | Router password from `envPrefix` |
251
278
 
252
279
  ---
253
280
 
@@ -353,14 +380,6 @@ to 1.1.1.1. If packet loss is present, use torch on the WAN interface for a
353
380
  short traffic snapshot.
354
381
  ```
355
382
 
356
- ### Configuration Backup And Audit
357
-
358
- ```text
359
- List files, scripts, scheduled jobs, firewall rules, DNS static records, and
360
- routes on core-01. Produce an audit summary with change-risk notes and suggested
361
- cleanup tasks.
362
- ```
363
-
364
383
  ### Plan / Apply / Rollback Workflow
365
384
 
366
385
  ```text
@@ -383,15 +402,13 @@ AI assistants can use MikroMCP to:
383
402
  - Preview changes before mutation and explain exactly what would happen.
384
403
  - Respect tool-level authorization, router scoping, maintenance windows, and confirmation gates.
385
404
 
386
- This makes MikroMCP a practical bridge between MikroTik networks and the emerging AI infrastructure ecosystem: Claude MCP, LLM tooling, infrastructure automation, DevOps workflows, and network operations copilots.
387
-
388
405
  ---
389
406
 
390
407
  ## Documentation
391
408
 
392
409
  | Resource | Use it for |
393
410
  | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
394
- | [ROADMAP.md](ROADMAP.md) | Shipped milestones and planned v1.0 work |
411
+ | [ROADMAP.md](ROADMAP.md) | Shipped milestones and planned work |
395
412
  | [Architecture](https://github.com/AliKarami/MikroMCP/wiki/Architecture) | System layers and request pipeline |
396
413
  | [Setup Guide](https://github.com/AliKarami/MikroMCP/wiki/Setup-Guide) | RouterOS REST setup and end-to-end onboarding |
397
414
  | [Configuration](https://github.com/AliKarami/MikroMCP/wiki/Configuration) | Router registry, TLS, SSH, credentials, HTTP mode |
@@ -401,7 +418,6 @@ This makes MikroMCP a practical bridge between MikroTik networks and the emergin
401
418
  | [Error Handling](https://github.com/AliKarami/MikroMCP/wiki/Error-Handling) | Typed errors, retry behavior, circuit breaker behavior |
402
419
  | [Development](https://github.com/AliKarami/MikroMCP/wiki/Development) | Project structure, tests, MCP Inspector workflow |
403
420
  | [Contributing](https://github.com/AliKarami/MikroMCP/wiki/Contributing) | Adding tools, coding conventions, PR checklist |
404
- | [Roadmap](https://github.com/AliKarami/MikroMCP/wiki/Roadmap) | Wiki mirror of the milestone roadmap |
405
421
 
406
422
  ---
407
423
 
@@ -411,30 +427,23 @@ This makes MikroMCP a practical bridge between MikroTik networks and the emergin
411
427
  npm run dev # tsx watch hot-reload
412
428
  npm run build # build ESM output to dist/main.js
413
429
  npm start # run built server
414
- npm test # run Vitest once
415
- npm run typecheck # TypeScript type checking
416
- npm run lint # ESLint
430
+ npm test # vitest + tsc + eslint
417
431
  npm run format # Prettier
418
432
  ```
419
433
 
420
- Run this before committing:
421
-
422
- ```bash
423
- npm test
424
- npm run typecheck
425
- ```
426
-
427
434
  Key project paths:
428
435
 
429
436
  | Path | Purpose |
430
437
  | -------------------------------- | ------------------------------------------------------------------------------------------- |
431
- | `src/main.ts` | Loads config and starts stdio or HTTP transport |
438
+ | `src/main.ts` | Loads `~/.mikromcp/.env` and starts stdio or HTTP transport |
432
439
  | `src/mcp/tool-registry.ts` | Registers tools and applies auth, retry, circuit breaker, audit, snapshots, and credentials |
433
440
  | `src/domain/tools/` | Tool definitions and handlers |
434
441
  | `src/domain/snapshot/` | Snapshot, diff, and write-journal support |
435
442
  | `src/adapter/rest-client.ts` | RouterOS REST API client |
436
443
  | `src/adapter/ssh-client.ts` | SSH execution adapter for diagnostics and guarded commands |
437
444
  | `src/config/router-registry.ts` | Router inventory loader |
445
+ | `src/cli/init.ts` | Interactive setup wizard (`mikromcp init`) |
446
+ | `src/cli/doctor.ts` | Health check command (`mikromcp doctor`) |
438
447
  | `config/routers.example.yaml` | Example multi-router registry |
439
448
  | `config/identities.example.yaml` | Example RBAC identity registry |
440
449
 
@@ -448,7 +457,7 @@ Key project paths:
448
457
  | v0.7 | ✅ Shipped | Identity, bearer auth, RBAC, audit log, confirmation gate |
449
458
  | v0.8 | ✅ Shipped | Snapshots, write journal, plan/apply, rollback, maintenance windows |
450
459
  | v0.9 | ✅ Shipped | Fleet operations, IPSec, certificates, users, DHCP servers/pools, queues/QoS, VRRP, SNMP/NTP, Netwatch, discovery, ARP, health checks |
451
- | v1.0 | 🔜 Planned | Docker/npm/systemd distribution, Prometheus metrics, CHR integration tests, doctor CLI, stability policy |
460
+ | v1.0 | ✅ Shipped | npm package, standalone binaries, Docker images, `mikromcp init` wizard, `mikromcp doctor`, `~/.mikromcp/` convention |
452
461
 
453
462
  See [ROADMAP.md](ROADMAP.md) for the complete milestone plan.
454
463
 
@@ -468,12 +477,9 @@ Good first contributions:
468
477
 
469
478
  Development standards:
470
479
 
471
- - TypeScript strict mode
472
- - ESM imports with `.js` extensions
473
- - Zod schemas with `.strict()`
474
- - Idempotency and `dryRun` for write tools
475
- - `MikroMCPError` for domain errors
476
- - Focused Vitest coverage for every tool
480
+ - TypeScript strict mode, ESM imports with `.js` extensions
481
+ - Zod schemas with `.strict()`, idempotency and `dryRun` for write tools
482
+ - `MikroMCPError` for domain errors, focused Vitest coverage for every tool
477
483
 
478
484
  Please open an issue before large changes so maintainers can align on scope.
479
485
 
@@ -481,18 +487,17 @@ Please open an issue before large changes so maintainers can align on scope.
481
487
 
482
488
  ## Security
483
489
 
484
- MikroMCP is designed for sensitive infrastructure, but it still controls real network devices. Treat it like an operations system.
490
+ MikroMCP controls real network devices. Treat it like an operations system.
485
491
 
486
492
  - Use least-privilege RouterOS users.
487
493
  - Prefer TLS verification and certificate fingerprint pinning.
488
- - Pin SSH host-key fingerprints for SSH-enabled tools.
489
- - Keep router credentials in environment variables or a secrets system, not YAML.
494
+ - Keep router credentials in `~/.mikromcp/.env`, not in YAML or shell history.
490
495
  - Use HTTP mode behind a trusted network boundary.
491
496
  - Configure identities with the smallest practical `allowedRouters` and `allowedToolPatterns`.
492
- - Enable audit logging for shared or production use.
497
+ - Enable audit logging (`MIKROMCP_AUDIT_LOG_PATH`) for shared or production use.
493
498
  - Test write tools with `dryRun: true` before applying changes.
494
499
 
495
- For vulnerabilities or unsafe behavior, open a private security advisory if available, or contact the maintainer before publishing exploit details.
500
+ For vulnerabilities or unsafe behavior, open a private security advisory or contact the maintainer before publishing details.
496
501
 
497
502
  ---
498
503
 
@@ -501,7 +506,6 @@ For vulnerabilities or unsafe behavior, open a private security advisory if avai
501
506
  - ⭐ Star the repository if MikroMCP helps your MikroTik or MCP workflow.
502
507
  - 🍴 Fork it to add RouterOS surfaces your network depends on.
503
508
  - 🧵 Open an issue for bugs, feature requests, compatibility notes, or documentation gaps.
504
- - 🛣️ Follow the [roadmap](ROADMAP.md) for upcoming fleet, distribution, and production-readiness work.
505
509
 
506
510
  ---
507
511
 
package/dist/main.js CHANGED
@@ -2,28 +2,33 @@
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
+ import { join as join5 } from "path";
6
+ import { homedir as homedir5 } from "os";
7
7
 
8
8
  // src/cli/index.ts
9
9
  import { Command } from "commander";
10
10
 
11
11
  // src/config/app-config.ts
12
+ import { join } from "path";
13
+ import { homedir } from "os";
14
+ function mikromcpDir() {
15
+ return join(homedir(), ".mikromcp");
16
+ }
12
17
  function loadAppConfig() {
13
18
  const env = process.env;
14
- const dataDir = env.MIKROMCP_DATA_DIR ?? "data";
19
+ const dataDir = env.MIKROMCP_DATA_DIR ?? join(mikromcpDir(), "data");
15
20
  return {
16
21
  transport: env.MIKROMCP_TRANSPORT === "http" ? "http" : "stdio",
17
22
  port: parseInt(env.MIKROMCP_PORT ?? "3000", 10),
18
23
  bindHost: env.MIKROMCP_BIND_HOST ?? "127.0.0.1",
19
24
  logLevel: env.MIKROMCP_LOG_LEVEL ?? "info",
20
- configPath: env.MIKROMCP_CONFIG_PATH ?? "config/routers.yaml",
25
+ configPath: env.MIKROMCP_CONFIG_PATH ?? join(mikromcpDir(), "routers.yaml"),
21
26
  dataDir,
22
27
  snapshotDir: `${dataDir}/snapshots`,
23
28
  journalPath: `${dataDir}/write-journal.ndjson`,
24
29
  cmdAllow: (env.MIKROMCP_CMD_ALLOW ?? "").split(",").map((s) => s.trim()).filter(Boolean),
25
30
  cmdDeny: (env.MIKROMCP_CMD_DENY ?? "").split(",").map((s) => s.trim()).filter(Boolean),
26
- identitiesPath: env.MIKROMCP_IDENTITIES_PATH ?? "config/identities.yaml",
31
+ identitiesPath: env.MIKROMCP_IDENTITIES_PATH ?? join(mikromcpDir(), "identities.yaml"),
27
32
  stdioIdentity: env.MIKROMCP_STDIO_IDENTITY || void 0,
28
33
  confirmationSecret: env.MIKROMCP_CONFIRMATION_SECRET || void 0,
29
34
  auditLogPath: env.MIKROMCP_AUDIT_LOG_PATH || void 0,
@@ -4900,10 +4905,12 @@ var containerTools = [listContainersTool, manageContainerTool];
4900
4905
  // src/domain/tools/change-management-tools.ts
4901
4906
  import { z as z23 } from "zod";
4902
4907
  import { readFileSync as readFileSync2 } from "fs";
4908
+ import { join as join3 } from "path";
4909
+ import { homedir as homedir2 } from "os";
4903
4910
 
4904
4911
  // src/domain/snapshot/snapshot-engine.ts
4905
4912
  import { mkdirSync, writeFileSync, readFileSync } from "fs";
4906
- import { join } from "path";
4913
+ import { join as join2 } from "path";
4907
4914
  import { nanoid } from "nanoid";
4908
4915
  function pathToSlug(rosPath) {
4909
4916
  return rosPath.replace(/\//g, "-");
@@ -4914,8 +4921,8 @@ function timestampPrefix() {
4914
4921
  async function takeSnapshot(client, routerId, path, snapshotDir) {
4915
4922
  const records = await client.get(path, {});
4916
4923
  const id = `${timestampPrefix()}-${pathToSlug(path)}-${nanoid(6)}`;
4917
- const dir = join(snapshotDir, routerId);
4918
- const filePath = join(dir, `${id}.json`);
4924
+ const dir = join2(snapshotDir, routerId);
4925
+ const filePath = join2(dir, `${id}.json`);
4919
4926
  const ts = (/* @__PURE__ */ new Date()).toISOString();
4920
4927
  mkdirSync(dir, { recursive: true });
4921
4928
  const stored = { id, routerId, path, ts, records };
@@ -5177,7 +5184,7 @@ function createChangeManagementTools(baseTools2) {
5177
5184
  async handler(params, context) {
5178
5185
  const parsed = applyPlanInputSchema.parse(params);
5179
5186
  log24.info({ routerId: context.routerId, stepCount: parsed.steps.length }, "Applying plan");
5180
- const dataDir = process.env.MIKROMCP_DATA_DIR ?? "data";
5187
+ const dataDir = process.env.MIKROMCP_DATA_DIR ?? join3(homedir2(), ".mikromcp", "data");
5181
5188
  const journalPath = `${dataDir}/write-journal.ndjson`;
5182
5189
  const snapshotDir = `${dataDir}/snapshots`;
5183
5190
  const results2 = [];
@@ -5271,7 +5278,7 @@ function createChangeManagementTools(baseTools2) {
5271
5278
  },
5272
5279
  async handler(params, context) {
5273
5280
  const parsed = rollbackChangeInputSchema.parse(params);
5274
- const dataDir = process.env.MIKROMCP_DATA_DIR ?? "data";
5281
+ const dataDir = process.env.MIKROMCP_DATA_DIR ?? join3(homedir2(), ".mikromcp", "data");
5275
5282
  const journalPath = `${dataDir}/write-journal.ndjson`;
5276
5283
  const snapshotDir = `${dataDir}/snapshots`;
5277
5284
  log24.info({ routerId: context.routerId, journalId: parsed.journalId }, "Rolling back change");
@@ -7935,7 +7942,7 @@ function getStdioIdentity(stdioIdentityName, registry) {
7935
7942
  details: { stdioIdentityName },
7936
7943
  recoverability: {
7937
7944
  retryable: false,
7938
- suggestedAction: `Add an identity named "${stdioIdentityName}" to config/identities.yaml or unset MIKROMCP_STDIO_IDENTITY.`
7945
+ suggestedAction: `Add an identity named "${stdioIdentityName}" to ~/.mikromcp/identities.yaml or unset MIKROMCP_STDIO_IDENTITY.`
7939
7946
  }
7940
7947
  });
7941
7948
  }
@@ -8606,7 +8613,7 @@ function createServerFactory(config, identityRegistry) {
8606
8613
  const makeServer = () => {
8607
8614
  const server = new McpServer({
8608
8615
  name: "mikromcp",
8609
- version: "1.0.2"
8616
+ version: "1.0.4"
8610
8617
  });
8611
8618
  registerAllTools(server, registry, pool, circuitBreakers, config, identityRegistry);
8612
8619
  return server;
@@ -8926,10 +8933,10 @@ async function runServe() {
8926
8933
 
8927
8934
  // src/cli/doctor.ts
8928
8935
  import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
8929
- import { homedir } from "os";
8936
+ import { homedir as homedir3 } from "os";
8930
8937
  import * as net from "net";
8931
8938
  import chalk from "chalk";
8932
- var localVersion = "1.0.2";
8939
+ var localVersion = "1.0.4";
8933
8940
  var results = [];
8934
8941
  function ok(msg) {
8935
8942
  console.log(chalk.green("\u2705 ") + msg);
@@ -9085,7 +9092,7 @@ async function checkRouter(router) {
9085
9092
  }
9086
9093
  }
9087
9094
  function checkIdentities(identitiesPath, transport2) {
9088
- if (!process.env.MIKROMCP_IDENTITIES_PATH && identitiesPath === "config/identities.yaml") {
9095
+ if (!process.env.MIKROMCP_IDENTITIES_PATH && identitiesPath.endsWith(".mikromcp/identities.yaml")) {
9089
9096
  if (!existsSync3(identitiesPath)) {
9090
9097
  return;
9091
9098
  }
@@ -9109,8 +9116,8 @@ function checkIdentities(identitiesPath, transport2) {
9109
9116
  }
9110
9117
  function checkClaudeDesktop() {
9111
9118
  const candidatePaths = [
9112
- `${homedir()}/Library/Application Support/Claude/claude_desktop_config.json`,
9113
- `${homedir()}/.config/Claude/claude_desktop_config.json`
9119
+ `${homedir3()}/Library/Application Support/Claude/claude_desktop_config.json`,
9120
+ `${homedir3()}/.config/Claude/claude_desktop_config.json`
9114
9121
  ];
9115
9122
  const configPath = candidatePaths.find((p) => existsSync3(p));
9116
9123
  if (!configPath) {
@@ -9208,16 +9215,16 @@ import {
9208
9215
  mkdirSync as mkdirSync3,
9209
9216
  copyFileSync
9210
9217
  } from "fs";
9211
- import { homedir as homedir2 } from "os";
9212
- import { join as join2 } from "path";
9218
+ import { homedir as homedir4 } from "os";
9219
+ import { join as join4 } from "path";
9213
9220
  import { stringify as yamlStringify, parse as yamlParse } from "yaml";
9214
9221
  import bcrypt2 from "bcryptjs";
9215
9222
  import chalk2 from "chalk";
9216
- function mikromcpDir() {
9217
- return join2(homedir2(), ".mikromcp");
9223
+ function mikromcpDir2() {
9224
+ return join4(homedir4(), ".mikromcp");
9218
9225
  }
9219
9226
  function defaultAuditLogPath() {
9220
- return join2(mikromcpDir(), "audit.ndjson");
9227
+ return join4(mikromcpDir2(), "audit.ndjson");
9221
9228
  }
9222
9229
  function suggestEnvPrefix(routerId) {
9223
9230
  return "ROUTER_" + routerId.toUpperCase().replace(/-/g, "_");
@@ -9231,8 +9238,8 @@ function splitTags(val) {
9231
9238
  }
9232
9239
  function findClaudeDesktopConfig() {
9233
9240
  const candidates = [
9234
- join2(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json"),
9235
- join2(homedir2(), ".config", "Claude", "claude_desktop_config.json")
9241
+ join4(homedir4(), "Library", "Application Support", "Claude", "claude_desktop_config.json"),
9242
+ join4(homedir4(), ".config", "Claude", "claude_desktop_config.json")
9236
9243
  ];
9237
9244
  return candidates.find((p) => existsSync4(p)) ?? null;
9238
9245
  }
@@ -9255,8 +9262,7 @@ async function collectRouterInfo() {
9255
9262
  }
9256
9263
  });
9257
9264
  const port = Number(portStr);
9258
- const tlsDefault = port === 443 || port === 8443;
9259
- const tlsEnabled = await confirm({ message: "Enable TLS?", default: tlsDefault });
9265
+ const tlsEnabled = await confirm({ message: "Enable TLS?", default: false });
9260
9266
  let rejectUnauthorized = true;
9261
9267
  if (tlsEnabled) {
9262
9268
  rejectUnauthorized = await confirm({
@@ -9277,6 +9283,14 @@ async function collectRouterInfo() {
9277
9283
  default: suggestedPrefix,
9278
9284
  validate: (v) => v.trim().length > 0 ? true : "Env prefix is required"
9279
9285
  });
9286
+ const routerUser = await input({
9287
+ message: `Router username (${envPrefix}_USER):`,
9288
+ validate: (v) => v.trim().length > 0 ? true : "Username is required"
9289
+ });
9290
+ const routerPass = await input({
9291
+ message: `Router password (${envPrefix}_PASS):`,
9292
+ validate: (v) => v.trim().length > 0 ? true : "Password is required"
9293
+ });
9280
9294
  const tagsRaw = await input({
9281
9295
  message: "Tags (comma-separated, optional):",
9282
9296
  default: ""
@@ -9287,7 +9301,7 @@ async function collectRouterInfo() {
9287
9301
  default: "7",
9288
9302
  validate: (v) => v.trim().length > 0 ? true : "Version is required"
9289
9303
  });
9290
- return { routerId, host, port, tlsEnabled, rejectUnauthorized, envPrefix, tags, rosVersion };
9304
+ return { routerId, host, port, tlsEnabled, rejectUnauthorized, envPrefix, routerUser, routerPass, tags, rosVersion };
9291
9305
  }
9292
9306
  async function collectIdentityInfo() {
9293
9307
  console.log(chalk2.bold("\n\u2500\u2500 Identity / HTTP 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"));
@@ -9343,6 +9357,25 @@ async function collectIdentityInfo() {
9343
9357
  tokenHash
9344
9358
  };
9345
9359
  }
9360
+ async function collectTransport() {
9361
+ console.log(chalk2.bold("\n\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"));
9362
+ const transport2 = await select({
9363
+ message: "Transport mode:",
9364
+ choices: [
9365
+ { value: "stdio", name: "stdio \u2014 launched by Claude Desktop or an MCP client (recommended)" },
9366
+ { value: "http", name: "http \u2014 standalone HTTP server with bearer-token auth" }
9367
+ ],
9368
+ default: "stdio"
9369
+ });
9370
+ if (transport2 === "http") {
9371
+ console.log(
9372
+ chalk2.yellow(
9373
+ "\n \u26A0 HTTP mode requires every request to include:\n Authorization: Bearer <token>\n Set MIKROMCP_CONFIRMATION_SECRET in ~/.mikromcp/.env and create an identity above."
9374
+ )
9375
+ );
9376
+ }
9377
+ return transport2;
9378
+ }
9346
9379
  async function collectEnvPreference() {
9347
9380
  return confirm({
9348
9381
  message: "Write credentials to ~/.mikromcp/.env? (No = inject env vars at runtime)",
@@ -9354,7 +9387,7 @@ async function collectClaudeDesktopPreference() {
9354
9387
  return confirm({ message: "Register with Claude Desktop?", default: true });
9355
9388
  }
9356
9389
  function writeRoutersYaml(data) {
9357
- const filePath = join2(data.configDir, "routers.yaml");
9390
+ const filePath = join4(data.configDir, "routers.yaml");
9358
9391
  const newEntry = {
9359
9392
  host: data.host,
9360
9393
  port: data.port,
@@ -9381,7 +9414,7 @@ function writeRoutersYaml(data) {
9381
9414
  return true;
9382
9415
  }
9383
9416
  function writeIdentitiesYaml(data) {
9384
- const filePath = join2(data.configDir, "identities.yaml");
9417
+ const filePath = join4(data.configDir, "identities.yaml");
9385
9418
  const newEntry = {
9386
9419
  token: data.tokenHash,
9387
9420
  role: data.role,
@@ -9403,21 +9436,20 @@ function writeDotEnv(data) {
9403
9436
  const userKey = `${data.envPrefix}_USER`;
9404
9437
  const passKey = `${data.envPrefix}_PASS`;
9405
9438
  const auditLogPath = defaultAuditLogPath();
9406
- const transport2 = data.createIdentity ? "http" : "stdio";
9407
- const confirmationSecret = data.createIdentity ? randomBytes(32).toString("hex") : "";
9439
+ const confirmationSecret = data.transport === "http" ? randomBytes(32).toString("hex") : "";
9408
9440
  const lines = [
9409
9441
  "# \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}=`,
9442
+ `${userKey}=${data.routerUser}`,
9443
+ `${passKey}=${data.routerPass}`,
9412
9444
  "",
9413
9445
  "# \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
9446
  `# stdio \u2192 launched directly by Claude Desktop or another MCP client`,
9415
9447
  `# http \u2192 standalone server mode with bearer-token auth`,
9416
- `MIKROMCP_TRANSPORT=${transport2}`,
9448
+ `MIKROMCP_TRANSPORT=${data.transport}`,
9417
9449
  "",
9418
9450
  "# \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")}`,
9451
+ `MIKROMCP_CONFIG_PATH=${join4(data.configDir, "routers.yaml")}`,
9452
+ `MIKROMCP_IDENTITIES_PATH=${join4(data.configDir, "identities.yaml")}`,
9421
9453
  "",
9422
9454
  "# \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
9455
  "# Levels: trace | debug | info | warn | error",
@@ -9439,9 +9471,6 @@ function writeDotEnv(data) {
9439
9471
  } else {
9440
9472
  writeFileSync2(data.envPath, content);
9441
9473
  }
9442
- console.log(
9443
- chalk2.dim(` Fill in ${userKey} and ${passKey} in ${data.envPath}`)
9444
- );
9445
9474
  }
9446
9475
  function registerClaudeDesktop() {
9447
9476
  const configPath = findClaudeDesktopConfig();
@@ -9477,11 +9506,12 @@ async function runInit() {
9477
9506
  console.log(chalk2.bold.cyan("\n\u{1F527} MikroMCP \u2014 Interactive Setup Wizard\n"));
9478
9507
  const routerInfo = await collectRouterInfo();
9479
9508
  const identityInfo = await collectIdentityInfo();
9480
- const configDir = mikromcpDir();
9481
- const envPath = join2(configDir, ".env");
9509
+ const configDir = mikromcpDir2();
9510
+ const envPath = join4(configDir, ".env");
9511
+ const transport2 = await collectTransport();
9482
9512
  const writeEnv = await collectEnvPreference();
9483
9513
  let writeRouters = true;
9484
- const routersYamlPath = join2(configDir, "routers.yaml");
9514
+ const routersYamlPath = join4(configDir, "routers.yaml");
9485
9515
  if (existsSync4(routersYamlPath)) {
9486
9516
  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"));
9487
9517
  writeRouters = await confirm({
@@ -9493,6 +9523,7 @@ async function runInit() {
9493
9523
  const data = {
9494
9524
  ...routerInfo,
9495
9525
  ...identityInfo,
9526
+ transport: transport2,
9496
9527
  envPath,
9497
9528
  configDir,
9498
9529
  writeEnv,
@@ -9505,12 +9536,12 @@ async function runInit() {
9505
9536
  if (data.writeRoutersYaml) {
9506
9537
  writeRoutersYaml(data);
9507
9538
  summary.push(`routers.yaml written to ${data.configDir}`);
9508
- console.log(chalk2.green(` \u2714 ${join2(data.configDir, "routers.yaml")}`));
9539
+ console.log(chalk2.green(` \u2714 ${join4(data.configDir, "routers.yaml")}`));
9509
9540
  }
9510
9541
  if (data.writeIdentitiesYaml) {
9511
9542
  writeIdentitiesYaml(data);
9512
9543
  summary.push(`identities.yaml written to ${data.configDir}`);
9513
- console.log(chalk2.green(` \u2714 ${join2(data.configDir, "identities.yaml")}`));
9544
+ console.log(chalk2.green(` \u2714 ${join4(data.configDir, "identities.yaml")}`));
9514
9545
  }
9515
9546
  if (data.writeEnv) {
9516
9547
  writeDotEnv(data);
@@ -9543,12 +9574,12 @@ async function runInit() {
9543
9574
  }
9544
9575
 
9545
9576
  // src/cli/index.ts
9546
- var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.0.2");
9577
+ var program = new Command().name("mikromcp").description("MikroTik RouterOS MCP server").version("1.0.4");
9547
9578
  program.command("serve", { isDefault: true }).description("Start the MCP server (default)").action(runServe);
9548
9579
  program.command("doctor").description("Check config, connectivity, and permissions").action(runDoctor);
9549
9580
  program.command("init").description("Interactive setup wizard").action(runInit);
9550
9581
 
9551
9582
  // src/main.ts
9552
- loadDotenv({ path: join3(homedir3(), ".mikromcp", ".env"), override: false });
9583
+ loadDotenv({ path: join5(homedir5(), ".mikromcp", ".env"), override: false });
9553
9584
  program.parse();
9554
9585
  //# sourceMappingURL=main.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikromcp",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Production-grade MCP server for MikroTik RouterOS",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",