Skip to main content
Glama

omada-mcp

A Model Context Protocol server for TP-Link Omada SDN controllers — zero dependencies, read-only by default, and honest about every failure.

CI License: MIT Python

Your Omada controller already knows which AP is overloaded, which client is sitting on the guest VLAN, and which switch dropped a port. omada-mcp hands that to Claude, Cursor, Hermes or any other MCP host — as 35 read-only tools that return clean JSON (plus 47 optional write tools behind an explicit allowlist, plus --watch and --export-metrics monitoring modes), with a self-test that tells you exactly what to fix when something is wrong.

uvx omada-mcp --doctor
omada-mcp doctor v1.2.1 on Linux-6.8 (Python 3.12.7)
------------------------------------------------------------------
PASS env      /home/you/.config/omada-mcp/.env | url=https://192.0.2.10:8043 user=omada-readonly password=set (24 chars)
PASS tls      verified against /home/you/.config/omada-mcp/controller-ca.pem (serverName=Omada) | reachable, omadacId=0123456789abcdef01234567, controllerVer=6.2.10.17, family=software-6.x
PASS login    token accepted for omada-readonly (role=Viewer, scope=all sites, cookie=TPOMADA_SESSIONID), Csrf-Token round-trip OK
PASS site     Home (89abcdef0123456789abcdef) site 1 of 1; matched OMADA_SITE_NAME=Home
PASS tools    list_devices -> 7 device(s); 35 read-only tools registered (0 write tool(s) enabled), 23 client(s)
PASS write    disabled (OMADA_WRITE_TOOLS is empty; read-only tools only)
------------------------------------------------------------------
6/6 checks passed
Ready: `omada-mcp` will serve 35 read-only tools on stdio.

Add --json (omada-mcp --doctor --json) for machine-readable output validated against schemas/doctor.schema.json. JSON output redacts local .env and OMADA_CA_BUNDLE filesystem paths to origin labels (OMADA_ENV_FILE, cwd, appdata, xdg, environment) so reports are safe to paste into issues.


Why this exists

Omada exposes two unrelated APIs and the documentation blurs them:

Web API v2 (/{omadacId}/api/v2/...)

Open API (/openapi/v1/...)

Auth

username + password (Csrf-Token + TPOMADA_SESSIONID)

OAuth2 client-credentials

Setup on controller

none — it is the API the web UI calls

"Platform Integration" must be configured

Account roles

Viewer is enough

Client ID/secret per integration

Works on OC200/OC300

yes

firmware dependent

omada-mcp speaks the Web API. That means: create a local Viewer account, put three values in a dotenv file, and you are done. No OAuth app, no client secret rotation, no per-firmware integration screen.

Three properties make it safe to hand to an LLM:

  • Read-only tools only. Every tool is annotated readOnlyHint: true, and OMADA_READ_ONLY=true fails closed: a call to anything not declared read-only is refused with -32003 before lookup, so a future write tool cannot leak past the switch.

  • No secret echo. The password never appears in output (only set (N chars)), the session token is never logged, and WLAN pre-shared keys are redacted — list_wlans reports hasPresharedKey: true instead.

  • Errors that carry the fix. Controller error codes are translated (-1505, -1600, -33726, -41011, -44116, …) and every message ends with the action to take.

Related MCP server: mcp-omada

Install

# from git (works today)
uvx --from git+https://github.com/falehsub/omada-mcp omada-mcp --doctor

# from PyPI
uvx omada-mcp --doctor

# or install the command
uv tool install omada-mcp     # or: pipx install omada-mcp
pip install omada-mcp         # plain pip

# from a local checkout:
git clone https://github.com/falehsub/omada-mcp && cd omada-mcp
uv tool install .             # installs the `omada-mcp` command from the checkout

Requires Python 3.10+ and nothing else. There are no runtime dependencies — urllib, ssl, json and argparse are the whole stack, which keeps the supply chain at zero.

Configure

mkdir -p ~/.config/omada-mcp
cp .env.example ~/.config/omada-mcp/.env
chmod 600 ~/.config/omada-mcp/.env
$EDITOR ~/.config/omada-mcp/.env

The minimum is three lines:

OMADA_BASE_URL=https://192.0.2.10:8043
OMADA_USERNAME=omada-readonly
OMADA_PASSWORD=...

Where configuration is read from

Highest priority first — the first hit wins per variable, and the process environment always overrides a file:

  1. process environment (export OMADA_BASE_URL=...)

  2. $OMADA_ENV_FILE — an explicit path (missing file = hard error, so a typo cannot silently fall through)

  3. ./.env — relative to the working directory (handy for per-project setups)

  4. %APPDATA%\omada-mcp\.env — Windows only, where a Windows user expects a per-user config

  5. ~/.config/omada-mcp/.env (or $XDG_CONFIG_HOME/omada-mcp/.env)

Windows

Everything is supported natively — no WSL, no MSYS. The console script installs as omada-mcp.exe, and uvx / uv tool install behave exactly as on POSIX.

New-Item -ItemType Directory -Force "$env:APPDATA\omada-mcp" | Out-Null
Copy-Item .env.example "$env:APPDATA\omada-mcp\.env"
notepad "$env:APPDATA\omada-mcp\.env"

Windows-specific behaviour, handled for you:

  • Config location. %APPDATA%\omada-mcp\.env is picked up before ~/.config/omada-mcp/.env, so the usual Windows location just works.

  • Encoding. Stdio is forced to UTF-8. The console code page (cp1252, cp1256, cp437 …) cannot encode an Arabic or CJK SSID, and the failure mode is a UnicodeEncodeError in the middle of a session — this never reaches you.

  • Line endings. Frames are written with a single \n; text mode would otherwise emit \r\n and strict clients can mis-frame. Incoming \r\n frames are accepted too.

  • Proxies. System/WinINET proxies are ignored by default (OMADA_USE_SYSTEM_PROXY=true to opt in).

  • File permissions. API keys live in your user profile; --doctor skips the POSIX chmod advice on Windows rather than handing you a command that does nothing. To restrict the file to your account: icacls "%APPDATA%\omada-mcp\.env" /inheritance:r /grant:r "%USERNAME%:F".

get_device/get_client accept a MAC in any format (AA-BB-CC-DD-EE-FF, aabbccddeeff, aa:bb:cc:dd:ee:ff) as well as a hostname or IP, so PowerShell output pastes straight in.

All variables

Variable

Default

Notes

OMADA_BASE_URL

— required

https://host:8043. Scheme-less values get https://…:8043; http:// defaults to port 8088. Must not embed credentials.

OMADA_USERNAME

— required

Local controller account. Viewer is sufficient for reads; use a dedicated Admin account for writes (every login evicts an existing session sharing that username).

OMADA_PASSWORD

— required

Never printed, never logged.

OMADA_SITE_NAME

first site

Display name, e.g. Home.

OMADA_SITE_ID

first site

Hex id or name — names are resolved, because the controller only accepts the hex id in URLs (that mismatch is exactly what -1505 reports).

OMADA_CA_BUNDLE

system store

PEM bundle with the controller's CA.

OMADA_VERIFY_SSL

true

false disables chain + hostname checks. Not for production.

OMADA_STRICT_X509

true

false clears Python 3.13+'s VERIFY_X509_STRICT while keeping chain and hostname checks — the fix for "self-signed certificate" errors on 3.13/3.14.

OMADA_TLS_SERVER_NAME

—

Optional TLS server_hostname override (e.g. Omada) when connecting by IP against the controller's built-in cert (SAN=DNS:Omada) so OMADA_CA_BUNDLE still verifies the chain and SAN.

OMADA_READ_ONLY

true

Default true fails closed and refuses every non-read-only tool (-32003) before argument validation; set false to enable allowlisted writes.

OMADA_WRITE_TOOLS

empty

Comma-separated exact write tool names to enable (wildcards and all are rejected). Both OMADA_READ_ONLY=false and this allowlist are required to expose or invoke any write tool.

OMADA_SNAPSHOT_DIR

platform state dir

Override directory for pre-write JSON snapshots and audit.jsonl (POSIX 0700/0600). Defaults to $XDG_STATE_HOME/omada-mcp/snapshots (%LOCALAPPDATA%\omada-mcp\snapshots on Windows).

OMADA_MANAGEMENT_CIDRS

empty

Comma-separated IPv4 hosts/CIDRs (in addition to controller host IP) protected from lockouts during VLAN isolation, DHCP pool, or deny-ACL writes.

OMADA_USE_SYSTEM_PROXY

false

System/registry proxies are ignored by default. On Windows, urllib inherits the WinINET proxy, which makes a one-hop controller look unreachable; set true only when a proxy is genuinely required.

OMADA_TIMEOUT_SECONDS

30

Per-request timeout.

OMADA_MIN_INTERVAL_SECONDS

0.25

Minimum spacing between controller calls; raise to 0.8 on large sites.

OMADA_WRITE_INTERVAL_SECONDS

1.0

Minimum serialized spacing between write requests (writes never auto-retry -1600).

OMADA_SCHEMA_MODE

slim

tools/list schema detail: slim (default, ~48% smaller payload while keeping inputSchema constraints and MCP 2025-06-18 outputSchema invariants intact) or full.

OMADA_TOOL_CATEGORIES

all

Optional comma-separated tool categories (core,diagnostics,switch,wifi,routing,security,rollback) to expose in tools/list.

OMADA_COMPACT_OUTPUT

true

Emit single-line compact JSON in tools/call content[0].text (structuredContent stays a typed JSON object). Set false for 2-space indented JSON.

omada-mcp --show-config prints the resolved values with the password masked; omada-mcp --doctor validates them.

Connect an MCP host

The server is plain stdio: a host runs uvx omada-mcp, then speaks JSON-RPC on stdin/stdout. The OMADA_ENV_FILE environment variable lets several hosts share one configuration file.

Hermes Agent

hermes mcp add omada --command uvx --args omada-mcp \
  --env OMADA_ENV_FILE=$HOME/.config/omada-mcp/.env

Or in ~/.hermes/config.yaml:

mcp_servers:
  omada:
    command: uvx
    args: ["omada-mcp"]
    env:
      OMADA_ENV_FILE: /home/you/.config/omada-mcp/.env

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "omada": {
      "command": "uvx",
      "args": ["omada-mcp"],
      "env": {
        "OMADA_ENV_FILE": "/home/you/.config/omada-mcp/.env"
      }
    }
  }
}

A copy lives at examples/claude_desktop_config.json.

Claude Code

claude mcp add omada --env OMADA_ENV_FILE=$HOME/.config/omada-mcp/.env -- uvx omada-mcp
claude mcp list

Cursor

.cursor/mcp.json in the project, or the global ~/.cursor/mcp.json:

{
  "mcpServers": {
    "omada": {
      "command": "uvx",
      "args": ["omada-mcp"],
      "env": { "OMADA_ENV_FILE": "/home/you/.config/omada-mcp/.env" }
    }
  }
}

Anything else

Any host that can spawn a process and speak newline-delimited JSON-RPC 2.0 works:

{
  "mcpServers": {
    "omada": {
      "command": "uvx",
      "args": ["omada-mcp"],
      "env": { "OMADA_ENV_FILE": "/home/you/.config/omada-mcp/.env" }
    }
  }
}

From a git checkout instead of PyPI:

uvx --from git+https://github.com/falehsub/omada-mcp omada-mcp --doctor

Tools

Thirty-five read-only tools are always registered; 47 optional write tools stay hidden and disabled until both OMADA_READ_ONLY=false and OMADA_WRITE_TOOLS allowlist them explicitly. All site-scoped tools also accept an optional per-call site name or ID (strictly pinned when OMADA_SITE_NAME/ OMADA_SITE_ID is set unless OMADA_ALLOW_CROSS_SITE=true). list_clients windowing is bounded by limit (default 500, max 5000) and de-duplicated by MAC.

Read-only tools (always available)

Full generated parameter & safety reference across all 82 tools: docs/tools-reference.md.

Tool

Arguments

Returns

list_sites

—

Sites visible to the account (id, name, connectivity flags, default indicator)

get_site_info

site?

Site name/id, WAN/LAN/WLAN state, device and client counts, controller version

list_devices

—

MAC, name, type, model, firmware, IP, uptime, client count, status, needUpgrade

list_clients

limit?

MAC, name, hostname, IP, wireless, guest, SSID, AP, network, VLAN, rx/tx, duration, last seen (UTC ISO-8601)

list_wifi_clients

limit?

Same projection, wireless only

list_wired_clients

limit?

Same projection, wired only

search_clients

query?, active?, wireless?, guest?, limit?

Filtered client search (clients for Admin; automatic insight/clients fallback on Viewer -1007/-41011)

get_device

mac (or name)

One device

get_client

mac (or hostname/IP)

One client

list_wlans

—

SSID, band, VLAN, security, guest, hasPresharedKey (expands v6.x WLAN groups; key never returned)

list_wlan_groups

—

Wireless WLAN group IDs/names and member SSID summaries (secrets stripped)

list_lan_networks

—

Merged setting/lan/networks + /interface view (VLAN, subnet, DHCP ipRangePool, leasetime, isolation, accessControlRule)

list_port_profiles

—

Switch port profiles (setting/lan/profiles) with native/tagged VLANs, PoE mode, and prohibitModify flag

list_dhcp_reservations

vlan?

Offline/static DHCP reservations from setting/service/dhcp

list_acls

aclType?

Gateway (?type=0), switch (?type=1), or EAP (?type=2) ACL rules and advisory aclDisable flag

list_ip_groups

groupType?

IP/port/profile groups (setting/profiles/groups) with CIDRs / entries

list_port_forwards

—

Port-forwarding NAT rules (setting/transmission/portForwardings)

list_static_routes

—

Static/interface routes (setting/transmission/staticRoutings)

list_policy_routes

—

Policy routing rules (setting/routing/policy), degrading cleanly when no gateway is present

list_lan_dns_rules

—

Local LAN DNS records (setting/dns/local), degrading cleanly on older firmware

get_network_services

—

UPnP, DNS proxy, mDNS rules & built-in profiles, DDNS, and WAN port state

list_logs

category?, hours?, level?, limit?

Controller alerts, events, or audit logs within time window

get_switch_ports

mac

Per-switch port link/speed/duplex, PoE budget/power, LAG membership, uplink & controller-port flags, RX/TX errors, and cable tests

get_ap_radios

mac

Per-AP 2g/5g/5g2/6g channel (resolved from channelInfo + actualChannel), width, TX power, utilization %, interference, and retry/drop rates

get_rf_scan

mac

Latest RF scan status and per-band interference summary for an AP

get_wan_status

maskWanIp?, includeSpeedtest?

Live WAN link/internet state, failover/load-balance mode, gateway/DNS, latency/loss, and latest speedtest

list_vpn_tunnels

—

WireGuard, OpenVPN, IPsec, and L2TP tunnel & peer state (all private keys, PSKs, and PEM blocks redacted)

get_topology

—

Compact uplink tree (Gateway -> Switch[port] -> AP/Switch) using topology-v2 with v5 device-list fallback

get_security_status

—

IPS/IDS status and recent threat/WIDS alerts with graceful missing-hardware warnings

diagnose_client

mac, hours?

Composite client troubleshooter correlating client RSSI/SNR, pastConnections & roam/disconnect logs (roamingSummary, telemetryAvailability), parent AP radio or switch port counters, and recent logs

diagnose_wan

maskWanIp?, hours?

Composite WAN troubleshooter correlating link/failover, latency/loss, VPN state, and WAN log events

network_audit

—

Composite one-call health audit: pending firmware, offline devices, PoE budget >= 85%, uplink speed mismatches, AP retry spikes, WAN loss/failover, unexpected VLAN clients, unisolated VLANs, DHCP pool utilization, and stable finding codes

explain_error

code, endpoint?

Structured decoder for Omada errorCode values (-1600, -33717, -33726, -1505, -1001, -30109, -41011, -44116, …) with ambiguity notes and fixes

get_config_snapshot

objects?

Read-only snapshot merging networks, acls, groups, wlans, devices, and optional clients, dhcpReservations, portForwards, staticRoutes, mdnsRules, services

diff_config_snapshots

baseline, current?, objects?

Deterministic drift diff between two get_config_snapshot payloads (or baseline vs live site when current is omitted), stripping volatile telemetry

Opt-in write tools (OMADA_READ_ONLY=false + OMADA_WRITE_TOOLS)

Every write tool defaults dryRun=true, captures a pre-mutation JSON snapshot under OMADA_SNAPSHOT_DIR, requires confirm="<tool>:<target>" when dryRun=false, verifies state on read-back, and never auto-retries on -1600.

Category

Tools

Behaviour & Safety

Snapshot rollback

rollback_from_snapshot

Loads snapshot strictly inside OMADA_SNAPSHOT_DIR, previews drift on dryRun=true, and restores pre-mutation config on dryRun=false

Switch ports & profiles

update_switch_port, cycle_poe_port, upsert_port_profile, delete_port_profile

Port enable/PoE mode/profile updates, custom port profile CRUD, and PoE power cycling; refuses built-in profile deletion or stranding uplink, controller-hosting, or LAG member ports unless allowUplinkMutation=true

AP radios, RF scan & WLAN groups

update_ap_radio, start_rf_scan, assign_ap_wlan_group

Preserves sibling band configs during config/radios PUT; pre-validates channel & width against availableChannels

Live client controls & fixed IP

set_client_fixed_ip, release_client_fixed_ip, set_client_blocked, reconnect_client, update_client, set_client_lock_to_ap, set_client_rate_limit

Full-object stripped client PATCH (netId preserved, 20 volatile keys stripped), Lock-to-AP (clientLockToApSetting), and rate limits

Offline DHCP reservations

upsert_dhcp_reservation, delete_dhcp_reservation

Create/update/delete reservations in setting/service/dhcp with subnet & IP-overlap validation

LAN & VLAN management

create_lan_network, update_lan_network, delete_lan_network, set_vlan_dhcp_pools, set_vlan_isolation

Two-endpoint LAN RMW, normalizes lanNetworkIpv6Config + dhcpGuard/dhcpv6Guard, uses priDns/sndDns, checks ipRangePool, refuses management/primary VLAN deletion

IP groups & ACLs

create_ip_group, replace_ip_group, delete_ip_group, create_gateway_acl, update_gateway_acl, set_acl_rule_status, delete_gateway_acl, create_switch_acl, create_eap_acl

Aligned CIDR packing (<=16, -33726), typed gateway/switch/eap ACL toggle/update/delete + auto 6-step swap (-33717), and management-host anti-stranding

Wireless SSIDs

create_ssid, update_ssid, delete_ssid

Manage SSIDs under setting/wlans/{groupId}/ssids; PSK/secret keys scrubbed recursively from diffs, outputs, and snapshots

Routing, DNS, NAT & services

upsert_port_forward, delete_port_forward, upsert_static_route, delete_static_route, upsert_policy_route, delete_policy_route, upsert_lan_dns_rule, delete_lan_dns_rule, upsert_mdns_rule, delete_mdns_rule, set_upnp

NAT port forwards, static & policy routing, local LAN DNS records, mDNS repeater rules, and UPnP toggle

Device operations

update_device, reboot_device

Rename / LED / locate / reboot EAPs, switches, and gateways (destructive=True on reboot; firmware upgrade remains unshipped)

omada-mcp --list-tools prints active descriptors without touching the network (add --all to inspect all 82 tools including non-allowlisted writes).

Every descriptor in tools/list declares MCP annotations and an MCP 2025-06-18 outputSchema, so hosts can auto-approve reads and validate structured results:

{
  "name": "list_devices",
  "title": "List Omada devices",
  "outputSchema": {
    "type": "object",
    "properties": {
      "items": { "type": "array" },
      "count": { "type": "integer", "minimum": 0 }
    },
    "required": ["items", "count"]
  },
  "annotations": {
    "title": "List Omada devices",
    "readOnlyHint": true,
    "destructiveHint": false,
    "idempotentHint": true,
    "openWorldHint": true
  }
}

Output shape (content vs structuredContent)

  • List tools (list_devices, list_clients, list_wifi_clients, list_wired_clients, search_clients, list_wlans, list_wlan_groups, list_lan_networks, list_port_profiles, list_dhcp_reservations, list_acls, list_ip_groups, list_port_forwards, list_static_routes, list_policy_routes, list_lan_dns_rules, list_vpn_tunnels, list_logs): content[0].text is serialized as a bare JSON array ([{...}, ...]), while structuredContent is an object matching outputSchema: {"items": [...], "count": <int>}. Callers parsing content[0].text (for example migrating from wrapper objects like {"devices": [...]}) receive a JSON list directly, whereas MCP 2025-06-18 hosts read structuredContent.items and structuredContent.count.

  • Object tools (get_device, get_client, get_site_info, get_network_services, get_switch_ports, get_ap_radios, get_rf_scan, get_wan_status, get_topology, get_security_status, diagnose_client, diagnose_wan, network_audit, explain_error, get_config_snapshot, and write envelopes): both content[0].text and structuredContent contain the same JSON object matching outputSchema.

omada-mcp --list-tools prints active descriptors without touching the network; omada-mcp --list-tools --all prints all 82 descriptors (add --compact for single-line JSON). Direct controller connections reuse persistent HTTP/1.1 keep-alive TLS sockets (UrllibTransport), and composite troubleshooters (network_audit, diagnose_client, diagnose_wan, get_config_snapshot, --watch / --export-metrics) deduplicate repeated site-wide GETs within a single invocation via an ephemeral per-call cache (OmadaClient.scoped_cache()).

Watch mode, Prometheus metrics & Docker

Both monitoring modes reuse one persistent authenticated OmadaClient session and enforce >= 0.8 s pacing so they never evict shared sessions:

# Stream JSON telemetry snapshot + event diffs (wan_failover, ap_retry_spike, weak_client_rssi, dhcp_pool_utilization)
omada-mcp --watch --interval 30

# One-shot Prometheus text exposition
omada-mcp --export-metrics

# Persistent /metrics HTTP endpoint (cached with >= 5 s scrape TTL + /healthz)
omada-mcp --export-metrics --metrics-port 9108 --metrics-host 127.0.0.1

Build and run the non-root container (OMADA_READ_ONLY=true by default):

docker build -t omada-mcp .
docker run --rm -i --env-file ~/.config/omada-mcp/.env omada-mcp --doctor
docker run --rm -p 9108:9108 --env-file ~/.config/omada-mcp/.env omada-mcp --export-metrics --metrics-port 9108 --metrics-host 0.0.0.0

Safety model

Control

Behaviour

OMADA_READ_ONLY=true

Refuses any tool not declared read-only (-32003), before argument validation or transport.

OMADA_WRITE_TOOLS allowlist

Defaults empty. Wildcards and all are rejected; write tools remain hidden and return -32003 unless explicitly named.

Two-phase apply (dryRun / confirm)

Every write defaults dryRun=true (zero writes; returns diff + confirmToken). Applying requires boolean dryRun=false and exact confirm="<tool>:<target-id>".

Snapshot before & verify after

Saves pre-write JSON snapshot + audit.jsonl record (0700/0600 on POSIX), re-reads state afterwards, and fails on silent controller no-ops or collateral field drift.

Anti-stranding guards

Refuses VLAN isolation, DHCP pool overlaps, or deny ACL rules that isolate/conflict with controller IP, gateway, device IPs, or OMADA_MANAGEMENT_CIDRS.

Credential handling

Password only ever rendered as set (N chars); tokens never logged; stdout is reserved for JSON-RPC, diagnostics go to stderr.

Secret redaction

list_wlans and get_config_snapshot omit securityKey / pskSetting; only hasPresharedKey is exposed.

Session recovery

A 200 response containing the web UI shell triggers one re-login: reads replay once, writes re-read live state first and never auto-retry -1600.

Pacing

Reads spaced by OMADA_MIN_INTERVAL_SECONDS; writes serialized at OMADA_WRITE_INTERVAL_SECONDS (default 1.0 s).

Dotenv hygiene

--doctor warns when the dotenv file is readable by group/other, and flags Viewer accounts when write tools are enabled.

Troubleshooting

Run omada-mcp --doctor first — it names the failing phase and the fix. The table below maps the symptoms it cannot see.

Symptom

Cause

Fix

-1505 on a site call

Site name used where the hex site id is required

Set OMADA_SITE_NAME (or OMADA_SITE_ID) and let the server resolve it

-1600 everywhere

Wrong path shape (GET /networks/{id}, missing /{type} on group DELETE, no PATCH on groups) or burst rate-limiting

Reads retry once after 0.8 s; writes never auto-retry -1600

-33717 on group delete

Group is referenced by one or more ACL rules

Use replace_ip_group, which performs the 6-step temp-group ACL swap automatically

-33726 on an IP group write

The group holds at most 16 entries

Store ranges as network-aligned CIDRs (packed automatically by create_ip_group/replace_ip_group)

-1001 "bean validation"

A mandatory field missing from a PATCH body — for LAN networks it is lanNetworkIpv6Config (proto: "none", enable: 0)

Send the whole object (handled automatically by LAN write tools)

-1007 "no permission"

Current user role lacks write permission (e.g. Viewer account on Omada 6.2.x)

Use a dedicated Admin account for writes; keep Viewer for read-only mode

-33598 "network isolation"

No adopted gateway/switch on the site supports Network Isolation

Adopt an isolation-capable gateway/switch or use gateway ACLs instead

-33601 "invalid destination type"

destinationType: 0 (network) used on a LAN->WAN gateway ACL

Use an IP group (e.g. IPGroup_Any) or port group on LAN->WAN rules

-41011

Client does not exist, MAC belongs to a managed device, or Viewer role on /clients/*

Use insight/clients or list_devices to inspect; use a dedicated Admin account for writes

-44116

You are on the Open API without Platform Integration

Use this server (Web API) instead

HTTP 200 with HTML in response

The controller's server-side session died (cookie is still valid for 15 days) — another login for the same account, or a restart

Automatic: the client re-authenticates once and replays

CERTIFICATE_VERIFY_FAILED

Self-signed controller cert, IP/SAN mismatch, or Python 3.13+ strict X509

Set OMADA_CA_BUNDLE to the controller CA, OMADA_STRICT_X509=false, and OMADA_TLS_SERVER_NAME=Omada when connecting by IP; OMADA_VERIFY_SSL=false only as a last resort

cannot reach …

Wrong port or unroutable network

Controller web UI is https://host:8043 (or http://host:8088)

Host starts but every call fails

No credentials at server start

Expected: initialize/tools/list never touch the controller; run --doctor

Windows: timeouts although the controller pings

WinINET proxy inherited by urllib

Proxies are off by default; if you need one, OMADA_USE_SYSTEM_PROXY=true

Windows: omada-mcp not found after pip install --user

%APPDATA%\Python\Python3x\Scripts not on PATH

Use uvx/uv tool install, or add the Scripts directory to PATH

Compatibility

See docs/compatibility.md for the full compatibility table.

  • Software controller v6.x — read-only path (--doctor --json and all 35 read-only tools) verified live on v6.2.10.17 (TPOMADA_SESSIONID), and 20 baseline read-only tools plus controller-native write CRUD (create/update/delete_lan_network with explicit ipaddrStart/ipaddrEnd pools, set_vlan_dhcp_pools, upsert/delete_dhcp_reservation, create/replace/delete_ip_group, create/update/set_acl_rule_status/delete_gateway_acl including the 6-step swap, create_switch_acl/create_eap_acl with typed toggle/update/delete lifecycle, create/update/delete_ssid with PSK redaction, upsert/delete_port_forward, upsert/delete_static_route, upsert/delete_mdns_rule, set_upnp dry-run + live disable + -33001 gateway check, -33598 on hardwareless isolation, Viewer -1007 -> insight/clients fallback, and -41011/not-found probes) verified live against an isolated Docker Omada Controller v6.2.14.11; client fixed-IP lease-delayed behavior (set_client_fixed_ip/release_client_fixed_ip), client actions, and device controls (update_device, reboot_device) are covered by the stateful mock controller.

  • Software controller v5.x — expected (v5.13.30.8 core auth/list shapes supported; awaiting a --doctor --json capture via the issue template).

  • OC200 / OC300 hardware & TPEAP_SESSIONID builds — expected (--doctor detects both TPOMADA_SESSIONID and TPEAP_SESSIONID, handles legacy result.omadacId login shapes, and reports hardware-5.x / hardware-6.x). Run omada-mcp --doctor --json and submit a Controller Compatibility Report.

Development

git clone https://github.com/falehsub/omada-mcp && cd omada-mcp
python -m unittest discover -s tests -v     # stdlib tests; loopback HTTP only
python -m omada_mcp.cli --help              # after: pip install -e .
uvx --from . omada-mcp --doctor             # what an external user does

# uv caches the build of a local *path* and does not rebuild when the source
# changes (`--refresh` does not help). Force a fresh environment while iterating:
UV_CACHE_DIR=$(mktemp -d) uvx --from . omada-mcp --doctor

For a repeatable live read-only sweep, use an explicit isolated lab configuration:

python scripts/read_selftest.py --env-file /path/to/isolated/viewer.env

The sweep forces read-only mode, blocks mutations at the transport boundary, and reports pass/fail/skip, elapsed time and request counts without controller payloads. Hardware-dependent checks are skipped when the site has no matching devices or clients. Configuration, service and routing exports expose known fields only; diagnostic snapshots are not full controller backups.

See the local review and prioritized improvements for measured results, limitations and the next development priorities.

Layout:

src/omada_mcp/config.py         dotenv discovery, validation, base-URL normalisation, write allowlist
src/omada_mcp/errors.py         OmadaError taxonomy, ERROR_CATALOG, explain_error_code, redaction
src/omada_mcp/transport.py      pooled keep-alive HTTP/1.1 transport, TLS context, SiteInfo/AuthInfo
src/omada_mcp/client.py         Web API v2 client: auth, pagination, write gate, snapshots/audit
src/omada_mcp/domains/          cohesive read, write, diagnostic, rollback & schema modules
src/omada_mcp/tools.py          tool registry facade (35 read-only + 47 write = 82 tools)
src/omada_mcp/write_tools.py    backward-compatible write handler & validator facade
src/omada_mcp/_helpers.py       backward-compatible shared diagnostic helper facade
src/omada_mcp/monitor.py        single-session --watch JSON diff streamer & --export-metrics Prometheus exporter
src/omada_mcp/server.py         JSON-RPC stdio server, fail-closed gates, strict arg/bool checks
src/omada_mcp/doctor.py         6-phase self-test (env, tls, login, site, tools, write) with --json
Dockerfile                      minimal non-root container (stdio default, --watch, --export-metrics)
schemas/doctor.schema.json      JSON Schema for --doctor --json
docs/tools-reference.md          auto-generated 82-tool parameter & safety reference
docs/compatibility.md           controller family and cookie compatibility matrix
docs/write-support.md           write-support contract, verified endpoints, and controller traps
server.json                     MCP Registry manifest (PyPI + GHCR OCI metadata)
SECURITY.md                     vulnerability disclosure policy & credential safety model
CONTRIBUTING.md                 contributor setup, mock/Docker lab rules & zero-drift checks
tests/mock_controller.py        stateful Web API v2 mock controller with fault injection
tests/fake_controller.py        lightweight FakeController & offline unit fixtures
tests/test_*.py                 domain-split read, protocol, monitor, write & regression tests
skills/omada-controller/        agent skill: field notes for the Omada API itself

The API notes in skills/omada-controller/SKILL.md document the parts of the controller that are not in any public reference: session expiry semantics, CIDR packing limits, dual-pool DHCP bodies, and the endpoints that are Admin-only.

Contributing & releasing

See CONTRIBUTING.md and SECURITY.md for local setup, mock/Docker lab testing rules, and vulnerability reporting.

  • Pull requests only: Develop changes on a branch and open a Pull Request targeting main rather than pushing directly to main. Every PR runs the CI matrix across Ubuntu, Windows, and macOS (Python 3.10–3.14), zero-drift doc checks (python scripts/generate_docs.py --check), and Docker container smoke tests via .github/workflows/ci.yml.

  • PyPI, GHCR & Attestations: Releases publish to PyPI via .github/workflows/publish.yml using GitHub Actions OIDC Trusted Publishing (pypi environment, no stored tokens), push multi-arch (linux/amd64, linux/arm64) containers to ghcr.io/falehsub/omada-mcp, and attach SLSA build provenance attestations plus SHA256SUMS.txt.

Write support

Read-only mode remains the default (OMADA_WRITE_TOOLS defaults empty and write tools are hidden). Opt-in writes follow the contract in docs/write-support.md — explicit OMADA_WRITE_TOOLS allowlist, dryRun=true default, confirm="<tool>:<target-id>", pre-write snapshots + audit.jsonl, whole-object verification, and anti-stranding checks. Use a dedicated Admin local account separate from your read bridge and web UI to avoid session eviction. Firmware upgrades remain unshipped pending a maintenance-window policy.

Roadmap

Read-only inspection, composite diagnostics (get_switch_ports, get_ap_radios, get_wan_status, get_topology, diagnose_client, diagnose_wan), --doctor --json, network_audit, the 82-tool control surface (rollback_from_snapshot + anti-stranding switch/AP/routing/DNS/ACL/client controls), multi-site workflows (list_sites + per-call site), diff_config_snapshots, --watch/--export-metrics, non-root multi-arch Docker packaging, and MCP Registry metadata (server.json) are implemented. See ROADMAP.md for historical milestones and future ideas. PyPI installation is available.

License

MIT — see LICENSE.

Not affiliated with TP-Link. Omada is a trademark of TP-Link Technologies. This server calls an undocumented internal API; a controller firmware update can change it, which is exactly why --doctor exists.

Available Tools

35 tools
diagnose_clientDiagnose a WiFi or wired clientC
Read-onlyIdempotent

Correlate client signal/link state, connection history, parent AP radio or switch port telemetry, and recent…

Inputs: mac (required), site, hours. Returns: client, findings, warnings, nextSteps, +4 more

diagnose_wanDiagnose gateway WAN & VPN healthC
Read-onlyIdempotent

Correlate live WAN link state, latency/packet loss, speedtest, VPN tunnel health, and gateway alerts/events…

Inputs: site, hours, includeSpeedtest. Returns: wan, findings, warnings, nextSteps, +2 more

diff_config_snapshotsDiff site configuration snapshotsC
Read-onlyIdempotent

Deterministically compare two get_config_snapshot payloads…

Inputs: baseline (required), site, current, objects. Returns: summary, sections, crossSite, hasChanges, +2 more

explain_errorExplain Omada errorCodeC
Read-onlyIdempotent

Decode an Omada Web API errorCode (e.g. -1600, -33717, -33726, -1505, -1001, -30109, -41011, -44116) into its…

Inputs: code (required), endpoint. Returns: fix, code, known, title, +5 more

get_ap_radiosGet AP radio & channel stateC
Read-onlyIdempotent

Inspect an AP's 2.4G/5G/5G2/6G radios: channel, width, TX power, channel utilization, interference…

Inputs: mac (required), site, includeRfScan, includeAvailableChannels. Returns: mac, name, radios, warnings

get_clientGet clientB
Read-onlyIdempotent

Get one client by MAC address, hostname or IP address.

Inputs: mac (required), site. Returns: ip, mac, name, ssid, +12 more

get_config_snapshotGet site configuration snapshotC
Read-onlyIdempotent

Read-only snapshot of LAN networks (both collection and /interface endpoints), gateway ACLs, IP/port groups…

Inputs: site, objects. Returns: site, objects, warnings

get_deviceGet deviceC
Read-onlyIdempotent

Get one device by MAC address or device name.

Inputs: mac (required), site. Returns: ip, mac, name, type, +6 more

get_network_servicesGet network servicesC
Read-onlyIdempotent

Summarize UPnP, DNS proxy, mDNS reflector rules and profiles, secret-redacted DDNS configuration, and…

Inputs: site. Returns: wan, ddns, mdns, upnp, +2 more

get_rf_scanGet AP RF scan resultsC
Read-onlyIdempotent

Read the latest cached RF scan status/neighbor results (empty channels list if no scan was triggered) and…

Inputs: mac (required), site. Returns: mac, name, channels, warnings, +2 more

get_security_statusGet IPS/IDS, WIDS & rogue AP statusC
Read-onlyIdempotent

Summarize IPS/IDS enablement, recent threat events, attack defense limits, WIDS/WIPS, and rogue AP detections…

Inputs: site, hours, limit. Returns: ips, counts, rogueAps, warnings, +1 more

get_site_infoGet site summaryB
Read-onlyIdempotent

Site summary: name, id, WAN/LAN/WLAN state, device and client counts, controller version.

Inputs: site. Returns: id, name, siteUrl, clientCount, +8 more

get_switch_portsGet switch ports, PoE & diagnosticsC
Read-onlyIdempotent

Inspect per-port link status, speed/duplex, PoE delivery & switch budget, active port profile/VLANs…

Inputs: mac (required), site, compact, includeCounters, +2 more. Returns: mac, poe, name, ports, +2 more

get_topologyGet site uplink topology treeB
Read-onlyIdempotent

Build the site's Gateway -> Switch(port) -> AP/Switch uplink tree with device counts.

Inputs: site, includeClients. Returns: roots, counts, warnings, unattachedDevices

get_wan_statusGet live WAN link & health statusB
Read-onlyIdempotent

Inspect gateway WAN link state, gateway/DNS/WAN IP, failover/load-balance mode, latency/packet loss health…

Inputs: site, maskWanIp, includeSpeedtest. Returns: ports, wanMode, warnings, hasGateway, +3 more

list_aclsList firewall ACL rulesC
Read-onlyIdempotent

List gateway, switch, or EAP ACL rules (setting/firewall/acls?type=0|1|2) with policy, source/destination…

Inputs: site, aclType. Returns: count, items

list_clientsList clientsC
Read-onlyIdempotent

List connected clients (WiFi and wired) with MAC, IP, SSID, AP, VLAN, traffic counters, session duration and…

Inputs: site, limit. Returns: count, items

list_devicesList Omada devicesC
Read-onlyIdempotent

List all managed network devices (APs, switches, gateways) on the site with MAC, model, firmware, IP, uptime…

Inputs: site. Returns: count, items

list_dhcp_reservationsList static DHCP reservationsB
Read-onlyIdempotent

List static DHCP reservations configured on the site (works for offline clients too).

Inputs: site, vlan. Returns: count, items

list_ip_groupsList IP/port/MAC profile groupsB
Read-onlyIdempotent

List profile groups (IP groups type=0, port groups type=1, MAC groups type=2) with entry counts and ACL…

Inputs: site, groupType. Returns: count, items

list_lan_dns_rulesList local LAN DNS rulesB
Read-onlyIdempotent

List local LAN DNS records (gracefully warns on controller versions without LAN DNS).

Inputs: site. Returns: count, items, warnings, supported

list_lan_networksList LAN and VLAN networksC
Read-onlyIdempotent

List configured LAN/VLAN networks merging setting/lan/networks and /interface…

Inputs: site. Returns: count, items

list_logsList controller alerts, events, or audit logsB
Read-onlyIdempotent

Query controller site logs ('alerts', 'events', or 'audit') with time window, optional level filter, and row…

Inputs: site, hours, level, limit, +1 more. Returns: count, items

list_policy_routesList gateway policy routing rulesB
Read-onlyIdempotent

List policy routing rules (gracefully reports unavailability if no gateway exists).

Inputs: site. Returns: count, items, warnings, supported

list_port_forwardsList port forwarding rulesB
Read-onlyIdempotent

List gateway NAT port forwarding and DMZ-mapped rules.

Inputs: site. Returns: count, items

list_port_profilesList switch LAN port profilesB
Read-onlyIdempotent

List switch port profiles (All, Disable, custom VLAN/PoE profiles).

Inputs: site. Returns: count, items

list_sitesList visible controller sitesB
Read-onlyIdempotent

List sites visible to the authenticated Viewer or Admin account with site IDs, names, connectivity flags, and…

Inputs: no named parameters. Returns: count, items

list_static_routesList static routesB
Read-onlyIdempotent

List configured gateway static routes (destinations, next hop, interface, metric).

Inputs: site. Returns: count, items

list_vpn_tunnelsList VPN tunnels & secret-redacted configsB
Read-onlyIdempotent

List WireGuard, OpenVPN, IPsec, and SSL/L2TP VPN tunnel state and configured profiles…

Inputs: site. Returns: counts, tunnels, warnings, configuredVpns

list_wifi_clientsList WiFi clientsB
Read-onlyIdempotent

List only wireless clients, same fields as list_clients.

Inputs: site, limit. Returns: count, items

list_wired_clientsList wired clientsB
Read-onlyIdempotent

List only wired clients, same fields as list_clients.

Inputs: site, limit. Returns: count, items

list_wlan_groupsList WLAN groups & SSID bindingsC
Read-onlyIdempotent

List configured WLAN groups and their SSIDs for assigning APs cleanly.

Inputs: site. Returns: count, items

list_wlansList WLANsB
Read-onlyIdempotent

List configured WLANs/SSIDs with band, VLAN, security type and guest flag.

Inputs: site. Returns: count, items

network_auditAudit site network healthB
Read-onlyIdempotent

One-call read-only health audit checking WAN state/packet loss, offline devices, pending firmware upgrades…

Inputs: site. Returns: site, counts, healthy, findings, +2 more

search_clientsSearch clientsB
Read-onlyIdempotent

Search online and offline/historical clients by MAC, name, hostname, IP, SSID, or VLAN/network, with…

Inputs: site, ssid, vlan, guest, +4 more. Returns: count, items

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3/5.0

    Scored across 35 tools

    Disambiguation4/5

    Most tools target clearly distinct resources (devices, clients, WLANs, ACLs, VPN tunnels, WAN status, logs), and the get_* vs list_* vs diagnose_* split is meaningful. Overlap exists between list_clients and the list_wifi_clients/list_wired_clients subsets, and between network_audit/get_config_snapshot/diagnose_wan which all report WAN health from different angles, but descriptions make the intended use reasonably clear.

    Naming Consistency4/5

    Almost all names follow a consistent snake_case verb_noun pattern (list_*, get_*, diagnose_*, search_*, diff_*), with list_/get_ used predictably for collections vs single records. The one deviation is the noun-style 'network_audit', and 'explain_error'/'diff_config_snapshots' are slightly off-pattern but still readable.

    Tool Count3/5

    35 tools is heavy and exceeds a comfortable range, though the breadth reflects a genuinely wide network-controller domain (sites, devices, clients, WiFi, ACLs, VPN, routing, DNS, RF, logs). Some tools are thin convenience wrappers (list_wifi_clients, list_wired_clients, get_rf_scan) that could be parameters of existing tools rather than separate entries.

    Completeness4/5

    The read-only observability surface is thorough: inventory, topology, health audits, config snapshots with diffing, diagnostics, and error decoding cover the monitoring lifecycle well. The main gap is that the server is entirely read-only—no mutations for DHCP reservations, ACLs, WLANs, or firmware upgrades—so flows like 'detected pending firmware' dead-end without an action tool.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers