3.8 KiB
name, description, version, author, license, platforms, prerequisites, metadata
| name | description | version | author | license | platforms | prerequisites | metadata | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| saiop-mikrotik-mcp | MikroTik (RouterOS) read-only MCP access for rt-alpha — TLS debugging notes and the read-only verification method. | 1.0.0 | Claude Code (SAIOP ops session) | MIT |
|
|
|
MikroTik (rt-alpha) read-only MCP access
When to use
Querying the ATU network's MikroTik router (rt-alpha,
169.239.248.166) for read-only diagnostics (interfaces, routes, DHCP
leases, firewall rules) via Hermes.
What's wired up
A custom single-tool MCP server (/opt/ai-stack/hermes/scripts/mikrotik_mcp.py,
run via uv run --with mcp --with librouteros) connects via the RouterOS
API over TLS (port 8729) as a dedicated claudeuser account.
Two independent layers, same pattern as postgres-mcp/proxmox-mcp:
- The
claudeuserRouterOS account itself is read-only — independently verified, not assumed: a real write attempt (/interface/setto change a comment) returnedTrapError: not enough permissions (9)directly from the device's permission system. - The MCP tool also only accepts paths ending in
/print,/getall, or/export(rejects anything else before even attempting the call) — defense-in-depth on top of #1, not the primary boundary.
TLS gotchas hit getting here (useful if this recurs for other network gear)
- Initial connection attempts failed with a TLS handshake failure. Root
cause: RouterOS's
api-sslservice had no certificate assigned, so it fell back to anonymous Diffie-Hellman ciphers (ADH-AES256-SHA256) — meaning no server authentication at all. Confirmed via rawopenssl s_client -cipher "ALL:@SECLEVEL=0" -tls1_2, which negotiated successfully where every normal client library failed (modern TLS stacks don't enable anonymous ciphers the same permissive way the CLI does, even with matching settings — spent real effort confirming this was the device's config, not a bug in our approach, before escalating to ask for a router-side fix). - The actual fix was on the device, not the client: assigning a real
certificate to the
api-sslservice (/certificate add+/certificate sign+/ip service set api-ssl certificate=...). Once that landed, standard TLS negotiated cleanly — no special cipher/version pinning needed in the client at all. - Even with a real cert, it had its own separate issues (expired,
CN mismatch vs. the hostname used to connect) — handled with
verify_mode = ssl.CERT_NONEin the client, since we already have independent assurance of which device we're talking to (the ~1ms ping latency confirms it's on a directly-attached local segment, not reachable for interception the way a public-internet path would be). Worth a routine cert renewal on the network side regardless. librouteros'sApi.__call__takes the RouterOS path positionally, not as acmd=keyword (api("/interface/print"), notapi(cmd="/interface/print")) — easy mistake, produces a confusingTypeError: missing 1 required positional argument.
Why a custom server instead of mcp-server-mikrotik
That package exists and is actively maintained, but it's the wrong shape
for this need: it connects via SSH and an interactive shell, uses
RouterOS's own "Safe Mode" (a revert-on-disconnect safety net for live
config changes, not a read-only restriction), and exposes broad
read-write scope across firewall/DHCP/DNS/NAT/backup management. Writing
~70 lines against the already-verified API connection was less work and
matches the actual need (read-only diagnostics) far more precisely — same
reasoning as the proxmox-mcp custom server earlier in this project.