Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
MIKROTIK_TIMEOUTNoFallback connect timeout (seconds) for devices without their own timeout10
MIKROTIK_LOG_LEVELNoLog level for the server process (stderr); invalid values fall back to INFO with a warningINFO
MIKROTIK_ALLOW_WRITENoEnable write tools (see Security model)false
MIKROTIK_DEVICES_FILENoPath to the devices YAML filedevices.yaml

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
list_devicesA

List configured MikroTik devices. Read-only; passwords are never included.

system_infoB

Get RouterOS identity + resource info (board, version, uptime, CPU/memory).

interfacesC

List network interfaces on a device.

list_vlansA

List VLAN interfaces (/interface/vlan): name, vlan-id, interface (the parent interface it rides on top of), mtu, running, disabled, comment (if set).

Excludes disabled VLAN interfaces by default, like interfaces; pass include_disabled=True to see them too.

ip_addressesC

List IPv4 addresses configured on a device.

ip_routesB

List the IPv4 routing table of a device.

limit, if given, caps the number of rows returned (capped at 500); omit it to get the full table, mirroring logs' limit parameter.

neighborsB

List neighbors discovered via RouterOS neighbor discovery (CDP/MNDP/LLDP).

dhcp_leasesC

List DHCP server leases (address, mac, host-name, status, server, comment).

simple_queuesA

List Simple Queue entries (/queue/simple): name, target, max-limit, limit-at, bytes counters, disabled. Use this to see which clients already have a bandwidth limit and how much traffic they've moved (the bytes counter), before deciding who to limit with set_client_bandwidth.

address_listsA

List firewall address-list entries (/ip/firewall/address-list): list, address, timeout, dynamic, disabled. See who's currently in which named list (e.g. a "blocked-clients" list a firewall rule drops), before adding/removing entries with add_to_address_list / remove_from_address_list.

firewall_natA

List IPv4 firewall NAT rules (/ip/firewall/nat): chain, action, to-addresses, etc. Read-only - does not add/modify/remove rules.

schedulerB

List scheduled tasks (/system/scheduler): name, on-event, interval, next-run, disabled. Read-only.

ip_poolsB

List IP pools (/ip/pool): name, ranges. Read-only.

wireless_registrationsA

List wireless clients currently associated to the device (mac, signal, interface, uptime).

RouterOS exposes this under two different paths depending on generation: ROS7's wifi package (/interface/wifi/registration-table) or ROS6's wireless package (/interface/wireless/registration-table). This tries ROS7 first, falls back to ROS6, and returns an empty list

  • rather than raising - for a device with no wireless radio at all (or the relevant package not installed), since that is a completely normal, expected state for a wired-only device.

RAW rows, as RouterOS returns them - see get_wireless_link_quality (v1.11) for the same registration-table data normalized into a fixed CCQ/rate/distance shape for PtP/PtMP diagnosis.

get_wireless_link_qualityA

PtP/PtMP link-quality diagnosis: the same registration-table data wireless_registrations reads, normalized per peer into interface, mac_address, signal_strength, signal_to_noise, tx_ccq, rx_ccq, tx_rate, rx_rate, distance, uptime (see formatting.normalize_wireless_registration).

This is the read to run before AND after set_wireless_channel/ set_wireless_tx_power/set_wireless_tuning to judge whether an RF change actually helped - CCQ (Client Connection Quality) and signal-to-noise are the two numbers that matter most for a PtP link; tx_rate/rx_rate dropping right after a channel/power change is expected for a few seconds while rates re-adapt, not a failure (see set_wireless_tx_power's docstring).

Optional interface filters to peers associated on that one local interface (validated for shape like every other interface_name parameter in this package).

Field availability genuinely differs by generation: ROS7's newer /interface/wifi/registration-table does not publish signal-to-noise/tx-ccq/rx-ccq/distance at all (per MikroTik's own docs) - those fields come back None rather than fabricated. /interface/wireless (confirmed against real hardware today - see docs/api-notes-wireless-rf.md) publishes all of them. Empty list (not an error) for a device with no wireless radio, same convention as wireless_registrations.

wireguard_peersA

List WireGuard VPN peers (/interface/wireguard/peers): name, interface, public-key, endpoint-address/endpoint-port, current-endpoint-address/current-endpoint-port, last-handshake, rx/tx byte counters, allowed-address, disabled.

SECURITY: a private-key field never appears in RouterOS's own /interface/wireguard/peers reply (only /interface/wireguard - the tunnel interfaces themselves - carries one; see wireguard_interfaces below), but this strips it defensively anyway, along with any preshared-key a configured peer may genuinely carry (a real, if optional, RouterOS field on this menu) - see formatting.WIREGUARD_SENSITIVE_FIELDS and test_wireguard_peers_never_exposes_private_key/ test_wireguard_peers_never_exposes_preshared_key.

Returns an empty list (never an error) for a device with no WireGuard package/interfaces at all - same "empty, not an error" convention as wireless_registrations/system_health for optional features.

wireguard_interfacesA

List WireGuard tunnel interfaces (/interface/wireguard): name, listen-port, public-key, running, disabled, mtu.

SECURITY: RouterOS's own /interface/wireguard reply carries the interface's private-key - this is ALWAYS stripped before returning (formatting.strip_sensitive_fields with formatting.WIREGUARD_SENSITIVE_FIELDS), the same mechanism wireguard_peers (v0.8) already used defensively for a peer's private-key/preshared-key. A private-key never leaves this process. See test_wireguard_interfaces_never_exposes_private_key.

Returns an empty list (never an error) for a device with no WireGuard package/interfaces at all - same convention as wireguard_peers.

ppp_activeA

List active PPP sessions (/ppp/active): name, service (l2tp/pptp/sstp/ovpn/pppoe), caller-id, address, uptime - VPN server sessions currently connected to this device.

Returns an empty list (never an error) for a device with no PPP server configured, or simply no sessions active right now.

ppp_secretsA

List CONFIGURED PPP/PPPoE secrets (/ppp/secret): name, service (pppoe/pptp/l2tp/ovpn/sstp/any), profile, remote-address, local-address (if set), disabled, comment, last-logged-out (if set)

  • the dial-in credentials themselves, as opposed to ppp_active's currently-CONNECTED sessions. See "PPP/PPPoE secrets" below.

SECURITY: RouterOS's own /ppp/secret reply carries each secret's plaintext password - this is ALWAYS stripped before returning (formatting.strip_sensitive_fields), the same mechanism wireguard_interfaces uses for a tunnel interface's private-key. A secret's password never leaves this process via this tool. See test_ppp_secrets_never_exposes_password.

Returns an empty list (never an error) for a device with no PPP package configured at all - same convention as ppp_active.

ipsec_active_peersA

List active IPsec peers (/ip/ipsec/active-peers): remote-address, state, uptime, rx/tx byte counters, side (initiator/responder).

Returns an empty list (never an error) for a device that doesn't use IPsec at all - a completely normal state.

bgp_sessionsA

List BGP session status: remote-address/remote-as, state (established/idle/...), uptime, prefix-count.

RouterOS exposes this under two different paths depending on generation, the same split wireless_registrations already handles for wifi: ROS7's routing package (/routing/bgp/session) or ROS6's (/routing/bgp/peer). This tries ROS7 first, falls back to ROS6, and returns an empty list - rather than raising - for a device that doesn't run BGP at all.

ospf_neighborsA

List OSPF neighbor adjacencies (/routing/ospf/neighbor): address, state (Full/Down/...), router-id, adjacency.

Returns an empty list (never an error) for a device that doesn't run OSPF at all.

netwatchA

List Netwatch host monitors (/tool/netwatch): host, status (up/down), interval, since, comment, disabled, plus has-up-script/has-down-script booleans.

Netwatch is the usual way a RouterOS device itself watches a gateway or peer's reachability (e.g. to drive a failover script on down/up) - this is read-only groundwork for future failover tooling; see README's "VPN & routing diagnostics".

The up-script/down-script fields are surfaced only as presence booleans (has-up-script/has-down-script), never as the raw script body, which can contain arbitrary RouterOS commands (e.g. route/credential changes) that don't belong in a read tool's output.

Returns an empty list (never an error) for a device with no Netwatch entries configured.

dns_cacheB

List cached DNS records on the device (name, type, data, ttl).

firewall_filterB

List IPv4 firewall filter rules (chain, action, etc). Read-only - does not add/modify/remove rules.

firewall_mangleA

List IPv4 firewall mangle rules (/ip/firewall/mangle): chain (e.g. prerouting/postrouting/forward/input/output, or a custom jump-target chain), action (e.g. mark-connection/mark-packet/ mark-routing), comment, disabled, plus whatever other fields RouterOS returns for a given rule (protocol, src/dst-address, etc - these vary per rule/action, same as firewall_filter/firewall_nat

  • not every rule has every field, and this returns each row as-is rather than assuming a fixed shape). Read-only - does not add/ modify/remove rules. See "NAT & mangle rule toggle (by comment)" below for the guarded enable_mangle_rule/disable_mangle_rule pair.

connection_trackingA

List active connections from RouterOS's connection tracking table (/ip/firewall/connection) - FILTERED. At least ONE of src_address, dst_address, dst_port, protocol is REQUIRED.

WHY a filter is mandatory (unlike every other read tool in this package): on a production router, the full connection-tracking table can be large enough to blow past an LLM caller's context/token budget on its own. Calling this with no filter at all raises a ValidationError instead of returning the whole table.

Filtering happens in Python after reading the table - the same reasoning logs' topics filter already documents (RouterOS's structured API doesn't expose a query-by-field read here either). src_address/dst_address match a row's IP, ignoring the port RouterOS packs into the same field (e.g. "192.0.2.1:80" -> address "192.0.2.1"); dst_port matches the destination's port component. protocol is a RouterOS protocol name (e.g. "tcp"/"udp"/"icmp", case-insensitive) or a numeric IP protocol number (0-255).

Regardless of how many rows match, the result is capped at MAX_CONNTRACK_LIMIT (100) entries - truncated is true whenever more rows matched than were returned, and total_matched always reports the real (pre-truncation) match count, so a caller always knows whether it's seeing everything that matched.

Each returned entry: protocol, src-address/src-port, dst-address/dst-port (address and port split apart - see formatting.split_address_port), tcp-state (populated for TCP connections), timeout, and the assured/confirmed/seen-reply flags - RouterOS's own closest equivalent to a generic "connection state" for this table.

system_healthB

Read system health metrics (e.g. voltage, temperature), if the device exposes them.

Not every RouterOS device/board type has health sensors (e.g. some CHR/virtual instances have none) - in that case this returns an empty list instead of raising.

logsA

Read recent RouterOS log entries (most recent last).

limit must be positive and is capped at 500. topics, if given, is matched as a plain substring against each entry's topics field - no regex, no unbounded scans - and is applied BEFORE the limit cut: the full log is filtered by topics first, then the last limit matching entries are returned (not the last limit raw entries, then filtered - that would silently drop matches).

R1: this reads the whole /log table via librouteros' path().select() and slices in Python rather than asking RouterOS for only the last limit rows. librouteros' structured API doesn't expose a clean "give me only the tail" query for /log (RouterOS's own count-only print flags aren't reachable through path().select() the way a .limit()/offset would be), so a "request fewer rows" optimization here would mean building a fragile ad-hoc workaround for a table that is small in practice (a few hundred to low thousands of rows on RouterOS's own ring buffer). Left as-is; revisit if a real device turns out to have a much larger log buffer than expected.

pingB

Ping an address from a device. address must be a valid IPv4/IPv6 address or hostname.

tracerouteA

Traceroute to an address from a device; returns the list of hops.

address must be a valid IPv4/IPv6 address or hostname (validated exactly like ping's). count (probes per hop) and max_hops are both capped low (see MAX_TRACEROUTE_COUNT/MAX_TRACEROUTE_MAX_HOPS) and a fixed short per-hop timeout is used internally, so the command can't run long enough to hit RouterOS's own ~60s API command timeout.

Diagnostic only - this never changes device state, so it is not gated by MIKROTIK_ALLOW_WRITE and needs no confirm/preview.

arp_tableA

List the IPv4 ARP table (/ip/arp): address, mac-address, interface, dynamic, complete.

Use this to cross-reference an IP to a MAC (or vice versa) for a statically-addressed device that never shows up in dhcp_leases (it never requested a DHCP lease, so it has no lease entry - but it does get an ARP entry once it has exchanged traffic with the device).

bridge_hostsA

List /interface/bridge/host entries: mac-address, on-interface (the physical bridge port), bridge, dynamic, local.

Use this to find which physical port of a bridge a given MAC is currently learned on - e.g. to identify which PoE-capable ethernet port a locked-up device is plugged into, before using poe_status/ set_poe_out on it (see "Physical layer & PoE control" in the README).

interface_trafficA

Current rx/tx traffic rate of one interface (/interface/monitor-traffic interface= once=yes).

interface is validated for shape (validate_interface_name) before it is ever sent to the device - existence isn't checked separately here (unlike the guarded write tools), so a typo'd/unknown interface name simply produces whatever error RouterOS itself returns.

Returns a single reply dict - typically rx-bits-per-second/ tx-bits-per-second and rx-packets-per-second/tx-packets-per-second - or an empty dict if the device returned nothing. once=yes makes this a single instantaneous reading, not a continuous stream, so the call always returns promptly.

poe_statusA

PoE status/consumption per port, for every PoE-capable ethernet port on the device.

Reads /interface/ethernet and keeps only rows that have a poe-out field (i.e. are PoE-capable on this hardware - e.g. the CRS318-16P's high/low PoE ports), then reads /interface/ethernet/poe/monitor once=yes for each one to get its live voltage/current/power/poe-out-status. Each entry looks like {"interface", "poe-out" (configured mode), "poe-out-status", "voltage", "current", "power"} - the monitor fields are omitted for a port whose live monitor call fails (kept resilient rather than failing the whole tool for one bad port).

voltage/current/power are normalized to int | float | None via formatting.coerce_ros_number, never passed through raw: real hardware (CRS318-16P-2S+, ROS6.49.20) mixes int and string-decimal (e.g. "4.7") types for these same three fields within a single monitor reply, and across different ports of the same device - see that helper's docstring. Without this, a caller comparing voltage > 0 (see README's PoE power-cycle walkthrough) could get a string on one port and a number on another.

Returns an empty list (never an error) for a device with no PoE hardware at all - that's a completely normal state, same as wireless_registrations for a wired-only device.

lte_statusA

Signal/status of one LTE/5G modem interface (/interface/lte/monitor <interface> once=yes).

Returns a single reply dict - typically operator (current-operator), technology (access-technology: 3G/LTE/5G), signal (rsrp/rsrq/sinr/rssi), band, registration-status, cell-id - or an empty dict if the device has no LTE hardware/package at all, or interface doesn't match one (same "empty, not an error" convention as poe_status/system_health for optional hardware).

interface is validated for shape (validate_interface_name) before it is ever sent to the device.

lte_interfacesB

List LTE/5G modem interfaces (/interface/lte): name, running, disabled, apn-profiles, etc. Returns an empty list (never an error) for a device with no LTE hardware/package at all.

containersB

List containers (/container): name/tag, status, ram-usage, root-dir, interface, os, etc. Returns an empty list (never an error) for a device with no container package/hardware support at all.

container_configB

Container subsystem configuration (/container/config): registry-url, tmpdir, ram-high, etc - a single-row menu. Returns an empty dict (never an error) for a device with no container package.

usb_devicesA

USB hardware on a device: physical USB ports (/system/routerboard/usb, if the board exposes them) plus attached storage (/disk - USB flash drives, and USB LTE/5G modems that surface as a disk rather than under routerboard/usb). Combined into one read since which of the two a given USB device shows up under depends on the hardware.

Returns {"usb_ports": [...], "disks": [...]} - either or both lists empty (never an error) if the board doesn't expose that menu/hardware at all.

interface_monitorA

Link/optical status of one ethernet interface (/interface/ethernet/monitor once=yes) - status (link-ok/ no-link), rate, full-duplex (coerced to bool | None - see formatting.coerce_ros_bool), auto-negotiation (RouterOS's raw value - a "done"/"incomplete"-style state, not a strict boolean, so left as-is), plus SFP/DDM optics fields WHEN the port has an SFP cage and a module is present: sfp-temperature, sfp-supply-voltage, sfp-tx-power, sfp-rx-power, sfp-tx-bias-current, sfp-vendor-name, sfp-vendor-part-number, sfp-wavelength, sfp-module-present (also coerced to bool | None).

A plain copper port (the vast majority) has NONE of the sfp-* fields in RouterOS's reply at all - each is only added to the result when the device's reply actually carries it (.get/in checks throughout, nothing invented). The reference hardware this project was verified against (a mANTBox) has no SFP cage, so the command path itself is confirmed but the DDM field VALUES are not yet verified against real SFP optics - see ROADMAP.md.

interface is validated for shape (validate_interface_name) before it is ever sent to the device - existence isn't checked separately, so a typo'd/unknown interface name simply produces whatever error RouterOS itself returns. Returns an empty dict if the device answers with nothing (same "once" convention as interface_traffic/poe_status/lte_status).

dhcp_serversA

List DHCP server CONFIG (/ip/dhcp-server) - as opposed to dhcp_leases, which lists the leases a server has handed out. Each entry keeps every field RouterOS returns (name, interface, address-pool, lease-time, authoritative, comment, ...), with disabled normalized to bool | None (formatting.coerce_ros_bool

  • never a == "true" string-equality trap, see that helper's docstring). authoritative is left as RouterOS's own raw value (it can be "yes"/"no"/"after-2sec-delay"/... - not a strict boolean - so it is not coerced).

dhcp_networksA

List DHCP server networks (/ip/dhcp-server/network): address, gateway, dns-server, netmask, domain, comment - the per-subnet options a DHCP server (see dhcp_servers) hands out to clients on lease. No boolean fields here to normalize.

bridge_portsA

List bridge port membership (/interface/bridge/port): bridge, interface, pvid, disabled, edge, horizon, learn, comment. Only disabled is normalized to bool | None (formatting.coerce_ros_bool) - edge/learn are RouterOS enums (e.g. auto/yes/no/yes-discover/no-discover), not strict booleans, so they are left as RouterOS's own raw value rather than coerced.

Use this (with bridge_vlans below) to see which physical port of a managed switch's bridge a VLAN actually applies to - bridge_hosts only shows the MAC table, not port/VLAN configuration.

bridge_vlansA

List bridge VLAN filtering table entries (/interface/bridge/vlan): bridge, vlan-ids, tagged, untagged, comment, plus current-tagged/current-untagged when the device's reply carries them (RouterOS's own computed effective port lists - not present on every ROS6/ROS7 version, so only added when actually present, never invented).

This is the honest completion of the VLAN story for a MANAGED SWITCH (CRS/hEX-style hardware): the v1.2 list_vlans/add_vlan/ remove_vlan tools operate on standalone /interface/vlan interfaces (router-on-a-stick style routing), which is a DIFFERENT RouterOS mechanism from bridge VLAN filtering - a switch that segments traffic by VLAN across bridge ports needs THIS table, not /interface/vlan. See bridge_ports above for per-port pvid/edge/learn config.

ntp_clientB

NTP client configuration/status (/system/ntp/client) - a single-row menu, same shape as container_config. Every field RouterOS's own reply carries is returned as-is (.get-based, nothing invented) - only enabled is normalized to bool | None (formatting.coerce_ros_bool).

ROS7 fields: enabled, mode, servers (a comma-joined string of every configured NTP server - left exactly as RouterOS sends it, NOT split into a list; same "no invented shape" convention bridge_vlans' tagged/untagged fields already follow), freq-drift, status, synced-server, synced-stratum.

ROS6 fields: enabled, primary-ntp, secondary-ntp, server-dns-names, mode. See set_ntp_servers for how a write detects and targets whichever of these two shapes a device actually speaks.

Returns an empty dict (never an error) for a device with no NTP client menu at all.

system_clockA

Device clock (/system/clock): time, date, time-zone-name, time-zone-autodetect (normalized to bool | None - formatting.coerce_ros_bool), gmt-offset, dst-active (also normalized) - a single-row menu, same shape as container_config/ ntp_client.

Clock drift breaks certificate validation (see certificates' daysUntilExpiry), log timestamps, and scheduler timing - check this alongside ntp_client's status/synced-server when diagnosing any of those. Returns an empty dict (never an error) for a device with no /system/clock menu at all (not expected on any real RouterOS device, but handled the same defensive way every other single-row read here is).

hotspot_activeA

List clients currently logged into the RouterOS hotspot (/ip/hotspot/active): user, address, mac-address, uptime, bytes-in/bytes-out - who is on the hotspot right now, and how much they've each moved this session.

Returns an empty list (never an error) for a device with no hotspot server configured, or simply no one logged in right now - same convention as ppp_active/ipsec_active_peers for an optional feature with no active sessions.

torchA

Live traffic snapshot of one interface (/tool/torch interface=<interface> once=yes) - RouterOS's own real-time traffic monitor, useful to answer "who is consuming bandwidth on this link RIGHT NOW". interface is validated for shape (validate_interface_name) before it is ever sent to the device - existence isn't checked separately, so a typo'd/unknown interface name simply produces whatever error RouterOS itself returns.

src_address/dst_address (plain IPv4/IPv6 addresses - validate_ip_address) and port (1-65535 - validate_conntrack_dst_port, reused here for the same "TCP/UDP port" shape) are all optional filters forwarded to RouterOS itself, narrowing the snapshot BEFORE it ever leaves the device - use them to cut down volume on a busy interface rather than fetching every flow and filtering client-side.

once=yes makes this a single instantaneous snapshot, not a continuous stream (same "once" convention as interface_traffic/ poe_status/lte_status - see client.MikrotikClient.torch), so the call always returns promptly instead of opening RouterOS's normal interactive torch stream.

VOLUME CAP: regardless of how many flows RouterOS reports for this snapshot, the result's flows list is sorted by total traffic (tx+rx bytes, biggest first - the "top talkers") and hard-capped at MAX_TORCH_LIMIT (50) entries - truncated is true whenever more flows matched than were returned, and total_matched always reports the real (pre-cap) count. RouterOS's own torch field names for a flow's traffic volume aren't perfectly uniform across RouterOS versions/hardware - this sorts by whichever of tx/rx (bits- or bytes-per-second, depending on version) the device actually returned, defaulting a flow with neither to 0 (sorted last) rather than failing the whole call over one unexpected row shape.

list_backupsA

List backup files stored on the device (/file, filtered to names ending in .backup): name, size, creation-time.

Reads the same /file menu create_backup's duplicate-name check uses - call this after create_backup to confirm a new backup landed and see its real size. Returns an empty list (never an error) if the device has no backup files at all.

list_write_operationsA

List every guarded write operation and the RouterOS path/action it maps to.

Read-only: this only surfaces guard.ALLOWLIST's metadata (D3) - it does not perform or preview a write, and is not gated by MIKROTIK_ALLOW_WRITE.

certificatesA

List certificates (/certificate): name, common-name, subject/issuer fields (if present), invalid-before/invalid-after (raw RouterOS date strings, kept as-is), key-size/key-type, fingerprint, and RouterOS's own flags (expired, trusted) - all returned exactly as the device sends them (booleans may come back as Python bool or be omitted entirely; see formatting.coerce_ros_bool for a caller that needs to branch on expired/trusted rather than just display them).

Adds a computed daysUntilExpiry (int, negative once past due) from invalid-after whenever it can be parsed - see formatting.parse_ros_datetime's docstring for the two RouterOS date shapes handled ("2027-01-15 12:00:00" and "jan/15/2027 12:00:00"). RouterOS's own date rendering varies by version/locale; parsing is DEFENSIVE and never raises - a row whose invalid-after doesn't match either known shape simply has no daysUntilExpiry key added, with the raw invalid-after string left untouched so a caller can still see it.

SECURITY: /certificate's own API reply never carries a private key (RouterOS only returns certificate metadata over the API) - a private-key field is nonetheless stripped defensively before returning, in case a future RouterOS version or firmware quirk ever adds one (same strip_sensitive_fields mechanism ppp_secrets/ wireguard_interfaces use). See test_certificates_strips_private_key_defensively.

See also security_audit's certificate-expiry check (v1.6), which flags an expired or soon-to-expire (<=30 days) certificate as a finding using this same expiry logic.

Returns an empty list (never an error) for a device with no certificates configured.

usersA

List RouterOS login accounts (/user): name, group, address (an allowed-source restriction, if the account has one), last-logged-in (if RouterOS exposes it), disabled, comment.

/user's own API reply never carries a password at all - RouterOS doesn't expose it over the API, so there is nothing to strip here (unlike ppp_secrets/radius, whose underlying menus DO carry a secret). This is a READ only: creating/editing a /user login stays deliberately out of scope for this package (see ROADMAP.md's "Explicitly NOT on the roadmap" - a router login is a device/API credential, a different risk class from a service credential like a PPP secret or hotspot user).

Returns an empty list (never an error) if /user is unavailable for some reason - /user always exists on RouterOS in practice, but this keeps the same "empty, not an error" convention every other optional read in this package uses.

user_activeA

List currently active RouterOS login sessions (/user/active): name, address, via (e.g. api/winbox/ssh/web), when (session start time) - who is logged into the device's own management right now, as opposed to users' CONFIGURED accounts.

Returns an empty list (never an error) for a device with nobody currently logged in, or if /user/active is unavailable.

radiusA

List RADIUS server configuration (/radius): service, address, timeout, accounting-port, authentication-port, and whatever other fields RouterOS returns for a given entry.

SECURITY: RouterOS's own /radius reply carries the plaintext shared secret - this is ALWAYS stripped before returning (formatting.strip_sensitive_fields), the same mechanism ppp_secrets uses for /ppp/secret's password and wireguard_interfaces uses for a tunnel interface's private-key. A RADIUS shared secret never leaves this process via this tool. See test_radius_never_exposes_secret.

Returns an empty list (never an error) for a device with no RADIUS servers configured.

security_auditA

Read-only security audit of a device's configuration - gives an LLM caller (or operator) a structured list of findings to review, so it can "look at the security of this router" without an operator manually walking every menu.

Aggregates several independent, defensive checks - see src/mcp_mikrotik/security.py for the full list and reasoning: insecure management services (/ip/service: telnet/ftp/www/api enabled, and whether they're open to any address), whether the firewall's input chain ends in a drop/reject rule (heuristic), SNMP community exposure (/snmp/community), an open DNS resolver (/ip/dns allow-remote-requests), outdated RouterOS (/system/package/update), open wireless/wifi networks (no security profile / no passphrase), a count of users with a write/full policy, and (v1.6) an expired or soon-to-expire (<=30 days) certificate (/certificate).

Each check reads its own menu(s) and skips itself (contributing no findings) if that menu doesn't exist on this device/RouterOS generation - one missing/unsupported menu never fails the whole audit. NEVER a scanner, NEVER definitive - this is a heuristic, best-effort read meant to prompt a human decision, not to replace one; see README's "Security audit" section for the full disclaimer.

READ-ONLY: does not change anything on the device, and is not gated by MIKROTIK_ALLOW_WRITE.

NO SECRET IS EVER RETURNED: no finding ever includes a password, passphrase, or SNMP community string - see security.py's module docstring for exactly how each check avoids that.

Returns {"findings": [{"severity", "category", "title", "detail", "recommendation"}, ...], "summary": {"high", "medium", "low", "info"}} - findings sorted by severity (high first), summary always including all four keys (0 for a severity with no findings).

security_eventsA

Recent RouterOS log entries filtered down to security-relevant ones - login/logout/authentication-failure events (topic "account"), "critical"/"error" topic entries, and generic "system,info" rows whose message looks like a login/logout - so a caller can correlate access attempts/anomalies without reading the entire (often much larger) unfiltered log via logs.

Filtering happens in Python, same reasoning logs' topics filter already documents (RouterOS's structured API doesn't expose a query-by-field read here either) - and is applied BEFORE the limit cut, so this returns the most recent limit MATCHING entries (not the last limit raw entries filtered afterward, which would silently drop matches on a busy log).

limit must be positive and is capped at 500 (default 50), the same shape as logs' own limit.

READ-ONLY: not gated by MIKROTIK_ALLOW_WRITE.

set_identityA

Set a device's RouterOS identity (hostname).

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it.

enable_interfaceA

Enable a network interface by name (sets disabled=no).

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly if interface_name does not exist on the device - it is never created.

disable_interfaceA

Disable a network interface by name (sets disabled=yes).

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly if interface_name does not exist on the device - it is never created.

set_wifi_ssidA

Set a wireless interface's SSID.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Works against either RouterOS generation - it looks for interface_name under the ROS7 wifi package first, then the ROS6 wireless package - and errors clearly if it isn't found under either; it is never created.

On ROS7, a wifi interface running the standard production layout (a named configuration) has no ssid field of its own - the actual write lands on the referenced /interface/wifi/configuration profile instead, resolved automatically. The before/after preview always reflects the real location the ssid is read from and written to.

set_client_bandwidthA

Limit a client's bandwidth via a RouterOS Simple Queue (/queue/simple).

target is the client's IP address or subnet (e.g. "10.0.0.5" or "10.0.0.0/24"). max_limit is a RouterOS rate pair in "upload/download" form (e.g. "10M/5M"); limit_at is the optional guaranteed-rate (CIR) pair in the same form. If a Simple Queue already targets target, its max-limit/limit-at is UPDATED; otherwise a new one is CREATED with a name derived from target - the returned operation field ("set_client_bandwidth_update" vs "set_client_bandwidth_add") tells you which happened (or would happen, with confirm=False).

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it.

GOTCHA - FastTrack: if the device has a FastTrack rule in its firewall (common on RouterOS's own quick-set wizards), fasttracked connections bypass queueing entirely, so this queue may have no visible effect on a client whose traffic is already fasttracked - see README's "Security model" section.

add_static_dhcp_leaseA

Create a static DHCP lease (/ip/dhcp-server/lease), pinning address to mac_address. Useful to give a client a stable, predictable IP - e.g. before limiting it with set_client_bandwidth.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly (without creating anything) if a lease for mac_address already exists on the device - it never creates a duplicate.

remove_simple_queueA

Remove a Simple Queue by target or by name - undoes a bandwidth limit previously set with set_client_bandwidth. At least one of target/name must be given and must match an existing queue.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a preview of what would be removed; call again with confirm=True to actually remove it.

add_to_address_listA

Add address (an IP or subnet) to a named firewall address-list (/ip/firewall/address-list).

IMPORTANT: this only manages the list - it does NOT create or modify any firewall rule. Adding an address here only blocks or allows traffic if a /ip/firewall/filter (or NAT) rule on the device already references list_name (e.g. src-address-list=list_name, action=drop). See README's "Blocking/allowing a client via address lists" section.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly (without creating anything) if this exact list_name+address pair already exists on the device - it never creates a duplicate.

remove_from_address_listA

Remove the entry matching list_name+address from a firewall address-list (/ip/firewall/address-list).

Like add_to_address_list, this only manages the list - see that tool's docstring and README's "Blocking/allowing a client via address lists" section for why this alone doesn't guarantee a change in blocking/allowing behavior.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a preview of what would be removed; call again with confirm=True to actually remove it. Errors clearly if no entry matches list_name+address.

set_poe_outA

Set a PoE-capable ethernet port's PoE output mode (/interface/ethernet set [interface_name] poe-out=).

poe_out must be one of "auto-on", "forced-on", "off".

Primary use case: reset a locked-up antenna/camera/AP powered over PoE by cycling its power - call with poe_out="off" (confirm=true), wait for it to actually power down, then call again with poe_out="auto-on" to bring it back up.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly (without changing anything) if interface_name doesn't exist on the device, or exists but isn't PoE-capable - it never creates or coerces anything.

start_containerA

Start a container by name or tag (/container/start).

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly (without creating anything) if container doesn't match any /container row's name or tag on the device - it is never created.

The after.status in the preview is the status RouterOS sets immediately ("starting"), not a guaranteed final state - the container transitions to "running" asynchronously; use containers again afterward to see the settled status.

stop_containerA

Stop a container by name or tag (/container/stop).

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly (without changing anything) if container doesn't match any /container row's name or tag on the device - it is never created.

The after.status in the preview is the status RouterOS sets immediately ("stopping"), not a guaranteed final state - use containers again afterward to see the settled status.

set_route_distanceA

Adjust an existing route's distance (failover priority - lower distance wins) via /ip/route set distance=<distance>.

Resolved by the STABLE (dst_address, gateway) pair - never a dynamic .id/list index, which can silently shift as routes are added/removed elsewhere on the device between a preview and the confirmed apply. Errors clearly (without changing anything) if no route matches that pair, or if more than one still does (AmbiguousResourceError) - this never guesses which route to touch.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. See README's "Failover control" section for the recommended step-by-step flow.

enable_routeA

Enable a route (/ip/route set disabled=no), resolved by dst_address - narrowed by gateway/comment when more than one route shares that dst_address (e.g. two default routes to different gateways, the standard failover shape). Errors clearly if nothing matches, or if the match is still ambiguous after narrowing.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it.

disable_routeA

Disable a route (/ip/route set disabled=yes), resolved by dst_address - narrowed by gateway/comment when more than one route shares that dst_address. Errors clearly if nothing matches, or if the match is still ambiguous after narrowing.

RISK: disabling the default route (dst_address="0.0.0.0/0" or "::/0") cuts all outbound traffic that relies on this gateway. The returned preview's warning field is non-null whenever this is the case - always check it before calling again with confirm=true.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview (including the warning field) without changing anything; call again with confirm=True to actually apply it.

add_routeA

Add a static route (/ip/route add): dst_address and gateway are required, distance (failover priority - lower wins) and comment are optional. Never refuses a duplicate dst_address

  • multiple routes sharing one is the normal failover shape.

RISK: adding/overriding the default route (dst_address="0.0.0.0/0" or "::/0") redirects all outbound traffic through the new gateway. The returned preview's warning field is non-null whenever this is the case - always check it before calling again with confirm=true.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview (including the warning field) without changing anything; call again with confirm=True to actually apply it.

remove_routeA

Remove a static route (/ip/route remove), resolved by dst_address - narrowed by gateway when more than one route shares that dst_address. Errors clearly if nothing matches, or if the match is still ambiguous after narrowing.

SAFETY: refuses outright (raises an error, does not remove anything) if the resolved route is dynamic (dynamic=true - a connected/DHCP/OSPF/BGP-installed route). Only static, admin-created routes can be removed by this tool - removing a device's connected/dynamic route can sever the network.

The returned preview's warning field is non-null (but not blocking) whenever the resolved route's dst_address is the default route (0.0.0.0/0/::/0) - check it before calling again with confirm=true.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview (including the warning field) without changing anything; call again with confirm=True to actually apply it.

add_netwatchA

Create a Netwatch host monitor (/tool/netwatch add): host (a plain IPv4/IPv6 address), optional interval (a RouterOS duration, e.g. "10s"/"00:00:10") and optional comment.

SECURITY: this tool does NOT accept an up-script/down-script - a Netwatch script body can run arbitrary RouterOS commands (route or credential changes, ...), so it is deliberately outside what this guarded write tool will ever send to a device. Configure up/down scripts manually on the device once the monitor exists (WinBox/CLI)

  • see README's "Failover control" section. The read-only netwatch tool already only ever surfaces has-up-script/has-down-script as presence booleans, never a script body, for the same reason.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly (without creating anything) if a monitor for host already exists - it never creates a duplicate.

remove_netwatchA

Remove a Netwatch host monitor by host or comment (/tool/netwatch remove). At least one of host/comment must be given and must match an existing monitor (host is tried first if both are given).

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a preview of what would be removed; call again with confirm=True to actually remove it.

add_static_dnsA

Create a static DNS entry (/ip/dns/static add) resolving name to address.

record_type is "A" (default) or "CNAME": for "A", address is a literal IPv4/IPv6 address; for "CNAME", address is itself another hostname (the alias target), written to RouterOS's cname field. Useful to block a malicious domain (point it at 0.0.0.0) or set up an internal DNS override.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly (without creating anything) if a row already matches this exact name+record_type pair - it never creates a duplicate.

remove_static_dnsA

Remove a static DNS entry (/ip/dns/static remove) by name, optionally narrowed by record_type ("A"/"CNAME").

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a preview of what would be removed; call again with confirm=True to actually remove it. Errors clearly if nothing matches name (narrowed by record_type), or if more than one row still matches after narrowing (AmbiguousResourceError) - never guesses which one to remove.

clear_dns_cacheA

Flush the device's DNS resolver cache (/ip/dns/cache/flush) - no arguments, clears every cached DNS answer at once.

Benign (only cached answers are cleared - repopulated on the next resolution - never device configuration), but still guarded/ confirm-gated like every other write tool here.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to preview the current cached-entry count without changing anything; call again with confirm=True to actually flush it.

remove_dhcp_leaseA

Remove a DHCP lease (/ip/dhcp-server/lease remove) by address or mac_address - typically to force a client to renew its IP. At least one of address/mac_address must be given and must match an existing lease (mac_address is tried first if both are given).

Removes EITHER a dynamic or a static lease. If the resolved lease is STATIC (dynamic=false - i.e. it was pinned with add_static_dhcp_lease), the returned preview's warning field is non-null: removing it deletes the pinned IP<->MAC mapping itself, not just a renewable cache entry. Always check warning before calling again with confirm=true. No warning for a dynamic lease - that is this tool's ordinary use case.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a preview (including the warning field) without changing anything; call again with confirm=True to actually remove it. Errors clearly if nothing matches.

wake_on_lanA

Send a Wake-on-LAN magic packet (/tool/wol) for mac_address, out interface.

Benign - it never changes device configuration and targets no existing RouterOS row - but still guarded/confirm-gated like every other write tool here, so an LLM caller can't wake a machine "by accident". Does NOT verify interface exists on the device first; RouterOS itself rejects an unknown interface name at send time.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a preview of what would be sent without changing anything; call again with confirm=True to actually send it.

enable_firewall_ruleA

Enable an EXISTING firewall filter rule (/ip/firewall/filter set disabled=no), resolved by its comment - optionally narrowed by chain if more than one rule shares that comment.

SAFE BY DESIGN: this NEVER creates a rule. Intended workflow: an admin creates a rule ahead of time on the device with a descriptive comment (e.g. comment="Bloqueio_Ataque_X"), reviews it once, and leaves it disabled; an LLM caller later enables it via this tool when it detects the condition the rule exists to guard against. If it goes wrong, the admin knows exactly which rule was toggled - the same one they already wrote and reviewed. See README's "Firewall rule toggle (by comment)" section.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview - the FULL matched rule, not just its disabled field, so you can confirm WHICH rule this is - without changing anything; call again with confirm=True to actually apply it. Errors clearly if no rule matches comment (narrowed by chain), or if more than one still does (AmbiguousResourceError)

  • never guesses which one to toggle.

disable_firewall_ruleA

Disable an EXISTING firewall filter rule (/ip/firewall/filter set disabled=yes), resolved by its comment - optionally narrowed by chain if more than one rule shares that comment.

Same "never creates a rule" guarantee and comment-based resolution as enable_firewall_rule - see its docstring and README's "Firewall rule toggle (by comment)" section.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly if no rule matches comment (narrowed by chain), or if more than one still does (AmbiguousResourceError).

enable_nat_ruleA

Enable an EXISTING firewall NAT rule (/ip/firewall/nat set disabled=no), resolved by its comment - optionally narrowed by chain (srcnat/dstnat) if more than one rule shares that comment.

Same "never creates a rule" guarantee and comment-based resolution as enable_firewall_rule (see its docstring and README's "Firewall rule toggle (by comment)" section) - extended to /ip/firewall/nat.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview - the FULL matched rule, not just its disabled field - without changing anything; call again with confirm=True to actually apply it. Errors clearly if no rule matches comment (narrowed by chain), or if more than one still does (AmbiguousResourceError) - never guesses which one to toggle.

disable_nat_ruleA

Disable an EXISTING firewall NAT rule (/ip/firewall/nat set disabled=yes), resolved by its comment - optionally narrowed by chain (srcnat/dstnat).

Same "never creates a rule" guarantee and comment-based resolution as enable_nat_rule.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly if no rule matches comment (narrowed by chain), or if more than one still does (AmbiguousResourceError).

enable_mangle_ruleA

Enable an EXISTING firewall mangle rule (/ip/firewall/mangle set disabled=no), resolved by its comment - optionally narrowed by chain (e.g. prerouting/postrouting/forward/input/output) if more than one rule shares that comment.

Same "never creates a rule" guarantee and comment-based resolution as enable_firewall_rule - extended to /ip/firewall/mangle.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview - the FULL matched rule, not just its disabled field - without changing anything; call again with confirm=True to actually apply it. Errors clearly if no rule matches comment (narrowed by chain), or if more than one still does (AmbiguousResourceError) - never guesses which one to toggle.

disable_mangle_ruleA

Disable an EXISTING firewall mangle rule (/ip/firewall/mangle set disabled=yes), resolved by its comment - optionally narrowed by chain.

Same "never creates a rule" guarantee and comment-based resolution as enable_mangle_rule.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly if no rule matches comment (narrowed by chain), or if more than one still does (AmbiguousResourceError).

add_wireguard_interfaceA

Create a WireGuard tunnel interface (/interface/wireguard add).

RouterOS generates the interface's private-key internally - this tool never accepts (or returns) one. The confirm=False preview's after only describes what will be created (name, listen-port if given) - it does not invent a public-key, since RouterOS hasn't generated the key pair yet at preview time. The confirm=True applied result re-reads the created interface and reports its real public-key, with private-key always stripped. See README's "WireGuard management" section.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to preview without changing anything; call again with confirm=True to actually create it. Errors clearly if name already exists - never creates a duplicate.

add_wireguard_peerA

Add a WireGuard peer (/interface/wireguard/peers add) to an existing tunnel interface.

public_key is the REMOTE peer's own public key (base64, 44 chars). allowed_address is a comma-separated list of CIDR ranges routed through this peer (e.g. "10.0.0.2/32,10.0.0.3/32"). endpoint_address/endpoint_port (the peer's reachable address/port, if any) and persistent_keepalive (a RouterOS duration, e.g. "25s") are optional.

Does NOT accept a private-key or preshared-key parameter - the remote peer's own private key, and any preshared key, are entirely out of this tool's scope.

interface must already exist - create it first with add_wireguard_interface; errors clearly if it doesn't. Refuses to add a duplicate peer (same public_key already registered on the same interface) - never creates a duplicate.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to preview without changing anything; call again with confirm=True to actually add it.

remove_wireguard_peerA

Remove a WireGuard peer (/interface/wireguard/peers remove) from interface, resolved by public_key or comment (public_key tried first if both are given). At least one of the two must be given.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to preview what would be removed without changing anything; call again with confirm=True to actually remove it. Errors clearly if nothing matches, or if more than one peer still matches (AmbiguousResourceError) - never guesses which one to remove.

add_hotspot_userA

Create a hotspot voucher user (/ip/hotspot/user add) for a visitor - name/password are the login credentials; profile (an existing hotspot user profile), limit_uptime (a RouterOS duration, e.g. "01:00:00"), and limit_bytes_total (a positive integer byte quota) are all optional.

QR/VOUCHER: the result always also includes username, password (the plaintext voucher credentials - THIS IS THE POINT: the visitor needs them), and qr_payload - a plain STRING the caller renders as a QR code itself; this package deliberately does NOT generate a QR IMAGE (no extra imaging dependency). Format chosen for qr_payload: "<username>:<password>" - a plain, self-describing credential pair, NOT a login URL and NOT a WIFI: payload. Two alternatives were considered and rejected: a login URL (http://<hotspot>/login?username=..&password=..) would need a reliably-known hotspot LAN address, which this package has no way to determine for an arbitrary device/deployment - and RouterOS's actual captive-portal login is normally a POST with additional session-specific tokens (chap-id/chap-challenge), so a bare GET URL with a plaintext password wouldn't even reliably work; a WIFI: payload is for auto-joining a WPA network, which is a different credential than a hotspot LOGIN (walled-garden HTTP auth) entirely. username:password makes no claim about network topology or login mechanics that could be wrong for a given deployment - a caller integrating with a specific captive portal can build its own login URL from these two fields plus its own known portal address.

PASSWORD IN THE JOURNAL: unlike every secret this package has handled before (a device password, a WireGuard private-key), this tool's password is DELIBERATELY present in its result - but still never written to the audit journal. See guard.add_hotspot_user's docstring for exactly how that asymmetry holds.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a preview (including qr_payload) without changing anything; call again with confirm=True to actually create it. Errors clearly (without creating anything) if name already exists on the device - it never creates a duplicate or resets an existing voucher's password.

create_backupA

Create a RouterOS system backup file (/system/backup/save name=<name>) - captures the device's full configuration into one binary .backup file on its own storage. Use list_backups afterward to confirm it landed and see its real size/creation-time.

password, if given, is RouterOS's own backup-FILE encryption option (unrelated to any device/API credential) - forwarded to the device, but NEVER included in the returned preview, and never journaled. See guard.create_backup's docstring for how that redaction is enforced.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a preview of the file that would be created without changing anything; call again with confirm=True to actually create it. Errors clearly (without creating anything) if a .backup file matching name already exists on the device - it never silently overwrites one.

add_vlanA

Create a VLAN interface (/interface/vlan add): name (the new RouterOS interface name, e.g. "vlan100"), vlan_id (1-4094, the IEEE 802.1Q tag), interface (the parent interface it rides on top of, e.g. "bridge1"/"ether2" - not verified to exist here; RouterOS itself rejects an unknown one at write time). mtu/comment are optional.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually create it. Errors clearly (without creating anything) if name already exists on the device - it never creates a duplicate.

remove_vlanA

Remove a VLAN interface (/interface/vlan remove) by name.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually remove it. Errors clearly if no VLAN interface matches name.

move_firewall_ruleA

Reorder an EXISTING firewall filter rule (/ip/firewall/filter move), resolved by its comment - optionally narrowed by chain if more than one rule shares that comment, same resolution enable_firewall_rule/disable_firewall_rule use.

SAFE BY DESIGN: this NEVER creates or otherwise edits a rule's fields - only its position in the chain's evaluation order changes.

Exactly one of before_comment (move the rule to appear immediately before the EXISTING rule with this comment) or position (move the rule to this 0-based index among the OTHER rules - a value at or beyond the end of that list moves it to the very end) must be given.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview (the rule's comment/chain plus its current vs. new position) without changing anything; call again with confirm=True to actually apply it. Errors clearly if no rule matches comment (narrowed by chain), if before_comment is given but matches no rule, or if either still matches more than one rule (AmbiguousResourceError) - never guesses.

add_ppp_secretA

Create a PPP/PPPoE secret (/ppp/secret add) - name/password are the dial-in login credentials for a PPPoE/PPTP/L2TP/OpenVPN/SSTP service. service (default "any") restricts which PPP service the secret may authenticate for - one of "pppoe"/"pptp"/"l2tp"/"ovpn"/ "sstp"/"any". profile (an existing /ppp/profile name, e.g. to assign an address pool or rate limit), remote_address (a literal IP handed to the client on connect), and comment are all optional.

PASSWORD IN THE RESULT: like add_hotspot_user's voucher password, this tool's password is DELIBERATELY present in its result (the caller supplied it and gets it echoed back as confirmation) - but still never written to the audit journal. See guard.add_ppp_secret's docstring for exactly how that asymmetry holds.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a preview without changing anything; call again with confirm=True to actually create it. Errors clearly (without creating anything) if name already exists on the device - it never creates a duplicate or resets an existing secret's password.

remove_ppp_secretA

Remove a PPP/PPPoE secret (/ppp/secret remove) by name.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually remove it. Errors clearly if no secret matches name (ResourceNotFoundError), or if more than one somehow does (AmbiguousResourceError) - never guesses which one to remove. The returned preview's before never includes the secret's password - redacted before the preview is ever built (see guard.remove_ppp_secret's docstring).

set_ntp_serversA

Set the NTP server(s) a device syncs its clock against (/system/ntp/client). servers is a list of one or more IPv4/ IPv6 addresses or hostnames.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Never enables/disables the NTP client itself - only the server list changes; the returned preview's warning says so if the client is currently disabled.

Works against either RouterOS generation, detected by reading /system/ntp/client first: ROS7 writes the full servers list; ROS6 has no such list - servers[0]/servers[1] map onto its fixed primary-ntp/secondary-ntp slots instead (extras beyond two are dropped, called out in warning). A hostname destined for one of those two ROS6 slots is folded into server-dns-names if the device has that field, otherwise it is not applied - warning says so. See guard.set_ntp_servers's docstring for the full detection/mapping rules.

ipv6_addressesA

List IPv6 addresses configured on a device (/ipv6/address): address, interface, advertise, disabled, dynamic, plus whatever other fields RouterOS's own reply carries (e.g. its global/ link-local classification). Mirrors ip_addresses for IPv6.

Returns an empty list (never an error) if the ipv6 package is disabled on the device - see "IPv6 read parity (v1.9)" in the README.

ipv6_routesA

List the IPv6 routing table of a device (/ipv6/route): dst-address, gateway, distance, active, dynamic, disabled. Mirrors ip_routes for IPv6, including its optional limit (capped at 500, same MAX_ROUTE_LIMIT); omit it to get the full table.

Returns an empty list (never an error) if the ipv6 package is disabled on the device - see "IPv6 read parity (v1.9)" in the README.

ipv6_firewall_filterA

List IPv6 firewall filter rules (/ipv6/firewall/filter): chain, action, etc. Mirrors firewall_filter for IPv6. Read-only - does not add/modify/remove rules.

Returns an empty list (never an error) if the ipv6 package is disabled on the device - see "IPv6 read parity (v1.9)" in the README.

ipv6_neighborsA

List the IPv6 neighbor discovery table (/ipv6/neighbor): address, mac-address, interface, status, dynamic. IPv6's neighbor- discovery equivalent of arp_table's IPv4 ARP table.

Returns an empty list (never an error) if the ipv6 package is disabled on the device - see "IPv6 read parity (v1.9)" in the README.

ipv6_firewall_address_listsA

List IPv6 firewall address-list entries (/ipv6/firewall/address-list): list, address, dynamic, disabled. Mirrors address_lists for IPv6.

Returns an empty list (never an error) if the ipv6 package is disabled on the device - see "IPv6 read parity (v1.9)" in the README.

enable_ipv6_firewall_ruleA

Enable an EXISTING IPv6 firewall filter rule (/ipv6/firewall/filter set disabled=no), resolved by its comment - optionally narrowed by chain if more than one rule shares that comment. Mirrors enable_firewall_rule on the IPv6 menu - see its docstring and README's "Firewall rule toggle (by comment)" section for the full admin-creates/LLM-enables workflow.

SAFE BY DESIGN: this NEVER creates a rule.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview - the FULL matched rule, not just its disabled field - without changing anything; call again with confirm=True to actually apply it. Errors clearly if no rule matches comment (narrowed by chain), or if more than one still does (AmbiguousResourceError) - never guesses which one to toggle. Also errors clearly (does not return an empty result) if the ipv6 package is disabled on the device - see "IPv6 write parity (v1.10)" in the README.

disable_ipv6_firewall_ruleA

Disable an EXISTING IPv6 firewall filter rule (/ipv6/firewall/ filter set disabled=yes), resolved by its comment - optionally narrowed by chain if more than one rule shares that comment. Mirrors disable_firewall_rule on the IPv6 menu.

Same "never creates a rule" guarantee and comment-based resolution as enable_ipv6_firewall_rule.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly if no rule matches comment (narrowed by chain), or if more than one still does (AmbiguousResourceError). Also errors clearly if the ipv6 package is disabled on the device.

add_ipv6_routeA

Add a static IPv6 route (/ipv6/route add): dst_address and gateway are required, distance (failover priority - lower wins) and comment are optional. Mirrors add_route on the IPv6 menu - never refuses a duplicate dst_address, multiple routes sharing one is the normal failover shape.

dst_address/gateway must be IPv6 (an IPv4 address/subnet in either is rejected before the device is ever touched - /ipv6/route has no IPv4 concept).

RISK: adding/overriding the default route (dst_address="::/0") redirects all outbound IPv6 traffic through the new gateway. The returned preview's warning field is non-null whenever this is the case - always check it before calling again with confirm=true.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview (including the warning field) without changing anything; call again with confirm=True to actually apply it. Also errors clearly if the ipv6 package is disabled on the device.

remove_ipv6_routeA

Remove a static IPv6 route (/ipv6/route remove), resolved by dst_address - narrowed by gateway when more than one route shares that dst_address. Mirrors remove_route on the IPv6 menu, INCLUDING its most important safety property. Errors clearly if nothing matches, or if the match is still ambiguous after narrowing.

dst_address/gateway must be IPv6 (an IPv4 address/subnet in either is rejected before the device is ever touched).

SAFETY: refuses outright (raises an error, does not remove anything) if the resolved route is dynamic (dynamic=true - a connected/DHCP/router-advertisement-installed route). Only static, admin-created IPv6 routes can be removed by this tool.

RISK: removing the default route (dst_address="::/0") cuts all outbound IPv6 traffic that relies on this gateway. The returned preview's warning field is non-null whenever this is the case (not blocking - removing a static default route is legitimate).

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview (including the warning field) without changing anything; call again with confirm=True to actually apply it. Also errors clearly if the ipv6 package is disabled on the device.

add_to_ipv6_address_listA

Add address (an IPv6 address or subnet) to a named IPv6 firewall address-list (/ipv6/firewall/address-list). Mirrors add_to_address_list on the IPv6 menu.

IMPORTANT: this only manages the list - it does NOT create or modify any firewall rule. Adding an address here only blocks or allows traffic if an /ipv6/firewall/filter rule on the device already references list_name. See README's "Blocking/allowing a client via address lists" section.

address must be IPv6 (an IPv4 address/subnet is rejected before the device is ever touched).

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors clearly (without creating anything) if this exact list_name+address pair already exists on the device - it never creates a duplicate. Also errors clearly if the ipv6 package is disabled on the device.

remove_from_ipv6_address_listA

Remove the entry matching list_name+address from an IPv6 firewall address-list (/ipv6/firewall/address-list). Mirrors remove_from_address_list on the IPv6 menu.

address must be IPv6 (an IPv4 address/subnet is rejected before the device is ever touched).

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a preview of what would be removed; call again with confirm=True to actually remove it. Errors clearly if no entry matches list_name+address. Also errors clearly if the ipv6 package is disabled on the device.

set_wireless_channelA

Set a /interface/wireless interface's frequency (MHz) and optionally channel_width.

LOCKOUT-RISK on a PtP link that is itself the management path to the far end: a bad frequency (or a DFS Channel Availability Check stall) can cut the only route back to the device. By default (arm_deadman=True), a confirm=True apply FIRST arms a dead-man (arm_dead_man) that restores the interface's prior frequency/ channel-width after deadman_minutes (1-60, default 3) unless cancelled - call cancel_dead_man(device_name, dead_man["name"]) once the new channel is confirmed good. Set arm_deadman=False only for an interface known NOT to be a management path.

The returned preview's warning ALWAYS reports whether frequency needs a DFS Channel Availability Check under this interface's CURRENT frequency-mode (superchannel: none, instant; otherwise ~60s in the general DFS range 5250-5725MHz, ~600s in the 5600-5650MHz weather-radar sub-band - verified against real hardware). See docs/api-notes-wireless-rf.md.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview (including the DFS warning, without arming anything) without changing anything; call again with confirm=True to actually apply it. Errors if interface_name doesn't exist on /interface/wireless - never creates one. This targets /interface/wireless only - see guard.py's module note for why ROS7's newer /interface/wifi package is out of scope this round.

set_wireless_tx_powerA

Set a /interface/wireless interface's tx_power (dBm), forcing tx-power-mode=all-rates-fixed.

CONFIRMED AGAINST REAL HARDWARE TODAY: on a short link, the default (maximum) tx-power SATURATES the receiver and PRODUCES A WORSE CCQ than a lower power (measured: -27dBm/CCQ 34 at default, -47dBm/CCQ 94 at ~8dBm). There is no single "right" power for every link - use get_wireless_link_quality before/after to judge the effect.

LOCKOUT-RISK for the same reason as set_wireless_channel - same arm_deadman/deadman_minutes dead-man behavior (default armed), restoring BOTH tx-power-mode and tx-power on revert.

The returned preview's warning always notes that CCQ/rate briefly re-adapt (a few seconds) right after a power change - expected, not a failure.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview without changing anything; call again with confirm=True to actually apply it. Errors if interface_name doesn't exist on /interface/wireless - never creates one.

set_wireless_tuningA

Set a /interface/wireless interface's adaptive_noise_immunity ("none"/"client-mode"/"ap-and-client-mode") and/or distance ("dynamic"/"indoors"/an integer number of km). At least one must be given.

adaptive_noise_immunity alone is reception-only tuning - CONFIRMED SAFE against real hardware today (does not drop an already- associated link), never arms a dead-man. CONFIRMED TODAY: with good signal but poor CCQ (interference, not distance), adaptive_noise_immunity="ap-and-client-mode" measurably helped.

A NUMERIC distance (unlike the named "dynamic"/"indoors" modes) directly changes the ACK-timeout/TDMA timing - LOCKOUT-RISK, CONFIRMED LIVE it can silently drop an already-associated link on a mismatch (e.g. too short for the real link length). By default (arm_deadman=True), a numeric distance arms a dead-man (arm_dead_man) that restores the interface's prior distance after deadman_minutes (1-60, default 3) unless cancelled - same mechanism as set_wireless_channel/set_wireless_tx_power. For a long verified PtP link, an explicit distance (e.g. 9 for ~9km) gave a better ACK timeout than leaving it on "dynamic".

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview (including the LOCKOUT-RISK warning for a numeric distance, without arming anything) without changing anything; call again with confirm=True to actually apply it. Errors if interface_name doesn't exist on /interface/wireless

  • never creates one.

arm_dead_manA

Arm a local, self-removing RouterOS scheduler on device_name that reverts a change after minutes (1-60) unless cancelled first (cancel_dead_man) - the anti-lockout primitive behind every LOCKOUT-RISK write in this package (set_wireless_channel/ set_wireless_tx_power use it automatically by default). See README's "Dead-man / lockout-proof writes" section for the full design and the real-hardware incident that validated it.

NOT wireless-specific: revert_commands is any non-empty list (max 10 items) of RouterOS script statements that restore a known-good prior state - a route, a bridge port, a firewall rule, anything - run in order when the dead-man fires, after logging a warning (visible in logs/security_events) and before the scheduler removes itself. Build each command from state you already read from the device, not free-form text.

The returned preview's after["name"] (also after more broadly) is the exact scheduler that would be armed (or was armed, if confirm=True) - pass that name to cancel_dead_man once the change it guards is confirmed good.

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to preview the exact scheduler that would be armed without touching the device; call again with confirm=True to actually arm it.

cancel_dead_manA

Cancel a dead-man scheduler armed by arm_dead_man (or automatically by set_wireless_channel/set_wireless_tx_power), once the change it guards is confirmed good - removes it from /system/scheduler before it can fire and revert.

name MUST be the exact "deadman-" handle arm_dead_man returned (after["name"], or a wireless write's dead_man["name"])

  • by construction this can never target an unrelated scheduler entry (e.g. an admin's own "backup-daily" task).

WRITE tool, guarded: blocked entirely unless the server is running with MIKROTIK_ALLOW_WRITE=true. Call with confirm=False (the default) to preview what would be cancelled; call again with confirm=True to actually cancel it. Errors clearly if name doesn't match an armed scheduler - which can mean it already fired and self-removed (it wasn't cancelled in time), or was already cancelled.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

B3.4/5.0

Scored across 114 tools

Disambiguation3/5

Most tools target clearly distinct resources, but the firewall rule toggles overlap heavily: enable_firewall_rule/disable_firewall_rule/enable_nat_rule/disable_nat_rule/enable_mangle_rule/disable_mangle_rule/enable_ipv6_firewall_rule/disable_ipv6_firewall_rule are nine near-identical tools distinguished only by menu, and the IPv6 mirror set (ipv6_addresses vs ip_addresses, add_route vs add_ipv6_route, etc.) requires reading descriptions carefully to pick correctly.

Naming Consistency4/5

Overwhelmingly consistent verb_noun snake_case (add_route, remove_vlan, set_wifi_ssid, list_backups), but several read tools drop the verb entirely (interfaces, neighbors, logs, certificates, users, scheduler, radius, torch, ping, traceroute), mixing noun-only names with the dominant verb_noun pattern.

Tool Count1/5

114 tools is an extreme mismatch for a single LLM-facing server: the nine enumerate/disable rule toggles, the full IPv4/IPv6 duplicated read-and-write surface, and per-feature reads (wireless, wireguard, lte, ppp, container, bgp, ospf, netwatch) collectively make the surface far larger than an agent can reliably navigate.

Completeness4/5

Coverage is genuinely broad - reads for nearly every RouterOS subsystem plus guarded CRUD for routes, VLANs, firewall rules, DNS, DHCP, WireGuard, PPP, hotspot, containers, and netwatch, with IPv6 read/write parity. Gaps exist (no interface creation, no user management, no hotspot profile creation) but these appear intentional and documented.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive