| 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 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) 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 |
| 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 |
| 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 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) 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) |
| 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 |
| 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"])
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. |