Making MikroTik talk - a WiFi device API for humans (and AIs)
Anyone running complex wifi setups on MikroTik knows the pain of tuning it up. You’ve got multiple access points, devices roaming between them, and when you want to tune your wireless config (say, adjust channel widths or check which AP a specific device landed on) you’re clicking through WinBox or the web UI, jumping between registration tables, trying to piece together the full picture.
I got tired of that pretty quickly. But the real trigger was Home Assistant. I wanted device presence and connection data available as entities: signal strength, which AP a phone is connected to, that kind of thing. RouterOS does have a REST API, but the data comes back scattered across multiple tables (WiFi registrations, DHCP leases, ARP entries) and as Mikrotik is great on backward-compatibility it has its drawbacks. Field names look like they were designed to save bytes on a floppy disk. Not exactly user-friendly.
So I built an API wrapper that stitches it all together.
What it actually solves
Here’s a real example. I have 3 APs managed by WiFiWave2/CAPsMAN, spread across rooms separated by old 100-year-old walls. Devices were sticking to the wrong APs instead of roaming. My iPhone in the living room was hanging onto the office AP at -60 dBm, and an NSPanel sitting right next to its local AP was connecting to one two rooms away.
The root cause? No transition threshold configured, no band steering, and 2.4GHz TX power cranked to full. Too much cell overlap through the walls, making distant APs appear “good enough.”
The fix involved a handful of CAPsMAN settings:
steering.transition-threshold=-60- nudge devices to roam when signal drops below -60 dBmsteering.2g-probe-delay=yes- delay 2.4GHz probe responses to encourage 5GHz- 802.11k/v/r enabled (
steering.rrm,steering.wnm,security.ft) - neighbor reports, BSS transition management, fast roaming configuration.tx-power=10on 2.4GHz radios - reduced cell overlap
That -60 dBm threshold wasn’t a guess. I picked it by querying signal strengths of devices at known locations through the API. A device sitting directly under an AP read -57 dBm on 5GHz, so -60 was the tightest safe value.
The TX power reduction turned out to be more effective than steering alone, especially for IoT devices that don’t support 802.11k/r/v. After the change, the NSPanel reconnected to its correct local AP at -36 dBm.
Without querying device data programmatically I’d just be guessing what to tweak and wasting time switching between WinBox views comparing data manually.
What it does
mikrotik_wifi_users is a FastAPI application that sits between you and your MikroTik router. It talks to RouterOS via its REST API and gives you back clean, enriched data.
The core endpoints are straightforward:
GET /devices- list all connected WiFi devices, with optional filtering by name, MAC, or interfaceGET /devices/{device_id}- detailed info about a specific deviceGET /interfaces/wifi- list WiFi interfacesGET /system/info- router system informationGET /health- connection status check
In the background I do simple data combination. A single device query actually pulls data from the wireless registration table, and cross-references it for IP address. All those RouterOS field names like mac-address get normalized to proper mac_address Python conventions.
The MCP part
This is where it gets fun. The app also exposes an MCP server over SSE at /mcp, which means you can plug it into Claude (or any MCP-compatible client) and just ask about your network.
“What devices are connected to the 5GHz network?” That kind of thing.
The MCP tools mirror the REST endpoints: list_devices, get_device, list_wifi_interfaces, get_system_info, and health_check. Separate API key from the REST side, because mixing auth contexts is never a good idea.
Tech stack
Nothing fancy:
- FastAPI because I simply like design choices @tiangolo did.
- Pydantic v2 for data validation
- Requests with retry strategy and exponential backoff for RouterOS communication
- Bearer token auth with HMAC constant-time comparison (most of you don’t care about security, I at least try to)
- Docker I mean, it’s kinda obvious at this stage.
The whole thing is managed with uv, which at this point I use for everything Python-related.
Running it
Docker Compose makes this trivial. Set your RouterOS credentials and API keys in the environment, docker compose up, and you’re done. The API runs on port 8490 by default.
For development, uv run uvicorn app.main:app --reload does the trick.
Why I did it?
This started because I wanted to tune my CAPsMAN setup without clicking through WinBox for every adjustment, and to get device data into Home Assistant. Having a proper API in front of my router opens up more than I’d expect. Monitoring dashboards, device alerts, presence detection automations, all become much easier when I don’t have to scrape a web UI.
The MCP part turned out to be a nice bonus on top. Being able to ask “which AP is my laptop connected to?” without opening anything is surprisingly convenient.