mcp-omada
Provides read-only tools for interacting with TP-Link Omada SDN controllers, including retrieving controller info, listing sites, devices, device details, and per-AP WiFi summaries.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-omadaList all devices on the default site"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-omada
A Model Context Protocol server for TP-Link Omada SDN controllers - read controller/site/device/WiFi state and, for one guarded write, change it, from an MCP client such as Claude Code.
This is a from-scratch implementation, sibling to mcp-mikrotik: same philosophy (structured API calls only, no generic "run any command" tool, tests against an in-memory fake instead of a real device, 100% test coverage), applied to a very different transport (HTTP + JSON instead of RouterOS's binary API) and a controller with two separate, non-interchangeable authentication mechanisms - see "Verified against real hardware" below.
Status
v0.2: read tools + the first guarded write. Seven read tools (controller
identity, sites, devices, device detail, per-AP WiFi summary, Insight
clients, alerts) plus one write tool, set_radio_channel, gated by the same
read-only-by-default + central allowlist + confirm/preview model
mcp-mikrotik established -
see src/mcp_omada/guard.py and "Security model" below.
Related MCP server: safe-omada-mcp
Installation
Requires Python >= 3.11.
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"Configuration
Configuration comes entirely from environment variables (v0.1 targets a
single controller - there is no multi-controller fleet file, unlike
mcp-mikrotik's devices.yaml).
Copy the example:
cp .env.example .envEdit
.env(or export the variables another way):Variable
Default
Meaning
OMADA_BASE_URL(required)
Controller base URL, e.g.
https://192.168.1.2:8043OMADA_OMADAC_ID(auto)
Controller ID; auto-discovered via
GET /api/infoif unsetOMADA_SITE_ID(auto)
Site to operate on; auto-selected if the controller manages exactly one site (legacy auth only - see below)
OMADA_USER/OMADA_PASS-
Legacy local-user login (preferred - richer field set). A Viewer role is enough for all read tools;
set_radio_channel(write) requires an Administrator-role user - the controller's own RBAC returns-1007 "user does not have permissions"for a Viewer even whenOMADA_ALLOW_WRITE=true(verified live: a defense-in-depth layer on top of this server's write guard).OMADA_CLIENT_ID/OMADA_CLIENT_SECRET-
Open API
client_credentials(reduced field set)OMADA_VERIFY_TLSfalseVerify the controller's TLS certificate
OMADA_TIMEOUT15HTTP request timeout, in seconds
OMADA_LOG_LEVELINFOLog level for the server process (stderr)
OMADA_ALLOW_WRITEfalseEnable write tools (
set_radio_channel) - see "Security model"OMADA_AUDIT_LOG(unset - stderr)
File path for the write audit journal (JSON lines) - see "Security model"
Set either
OMADA_USER+OMADA_PASSorOMADA_CLIENT_ID+OMADA_CLIENT_SECRET- not partially, and if both pairs happen to be set, legacy wins (see "Verified against real hardware" for why it's the richer path). The Open API app itself is created in the controller UI: Global View > Settings > Platform Integration > Open API, mode Client, role Viewer.OMADA_VERIFY_TLSdefaults tofalse(with a startup warning) because an OC200 commonly serves a self-signed certificate on its LAN management port - strict verification would refuse to connect out of the box. Set it totrueonce the controller has a certificate you can actually validate.
Running
The server speaks MCP over stdio - it is meant to be launched by an MCP client (e.g. configured as a command in Claude Code), not run as a network service:
mcp-omada
# or, without installing the console script:
python -m mcp_omada.serverThere is no HTTP transport in v0.1. If one is added later, it must default
to binding 127.0.0.1 (never 0.0.0.0) and require a bearer token from an
environment variable - see the TODO(http-transport) note at the top of
src/mcp_omada/server.py.
Tools
Read-only
Tool | Description |
| Controller identity: version, |
| Sites managed by this controller (id + name). Requires legacy auth. |
| Devices on a site, normalized to one consistent shape regardless of auth mode - see below. |
| Richest available detail for one device, by MAC (any common format accepted). |
| Per-AP WiFi summary: parsed 2.4GHz/5GHz channel, client counts per band, radio utilization. Requires legacy auth. |
| Insight/known clients on a site: mac, name, download/upload bytes, duration, last_seen, guest/wireless flags, VLAN, block/manager flags. Requires legacy auth. |
| Active alerts on a site. Pagination envelope confirmed against real hardware; individual alert row shape is a documented best-effort guess ( |
Write (guarded)
Tool | Description |
| Set an AP's 2.4GHz or 5GHz radio channel. Requires |
Normalization
list_devices/get_device_detail return the same field names regardless
of which auth mode is active - a field unavailable in the current mode is
null rather than omitted, so a caller never has to branch on auth mode.
The normalization itself encodes three confirmed real-hardware gotchas
(full detail in docs/api-notes.md):
connected:statusCategory == 1(primary) with fallbackstatus == 14on the legacy path;status == 1on the Open API path - the same field name (status) means something different on each path.uptime_seconds: prefersuptimeLong(legacy-only, already seconds); falls back to parsing theuptimedisplay string (e.g."1h 43m") whenuptimeLongis absent - notably, always, on the Open API path.WiFi channel:
actualChannelis a string like"11 / 2462MHz"(irregular whitespace) - parsed into{"channel": 11, "freq_mhz": 2462}. On the 5GHz radio,channelis an internal index, not the operator-recognizable channel number -freq_mhzis the reliable value.
Verified against real hardware (OC200 v5.13.30.20)
Everything below was confirmed against a real OC200 running firmware
v5.13.30.20, across two verification passes (2026-07-12 reads, 2026-07-13
set_radio_channel/get_clients/get_alerts - the latter while correcting
the channels of a real EAP fleet) - not assumed from public docs (which are
thin, and in places silent about exactly these details). Full write-up,
including the write endpoint's silent-discard gotcha, in
docs/api-notes.md.
Capability | Legacy ( | Open API ( |
Controller identity ( | Yes (unauthenticated either way) | Yes (unauthenticated either way) |
List sites | Yes | Not verified - no Open API sites-list endpoint was exercised; set |
List devices | Yes, rich fields | Yes, reduced fields |
Per-device detail | Yes for AP/EAP devices ( | No separate endpoint verified - returns the list row |
Per-radio WiFi detail ( | Yes | Absent entirely |
|
|
|
Insight/known clients | Yes | Not verified |
Alerts | Yes (envelope only - row shape unverified) | Not verified |
Set AP radio channel (write) | Yes ( | Not verified |
Device/system logs | Not found - every path tried returned | Not attempted |
The two auth mechanisms (legacy session + CSRF token vs. Open API access
token) are not interchangeable - a session from one is rejected (empty
response) by the other's endpoints. See src/mcp_omada/client.py's module
docstring and docs/api-notes.md for the full login flows.
Security model
src/mcp_omada/guard.py follows the security model mcp-mikrotik's own
guard.py established, studied first - not a claim of an exact mirror
(see docs/api-notes.md's "Design decisions" for where the two genuinely
diverge and why). Four independent controls apply to set_radio_channel:
Read-only by default.
OMADA_ALLOW_WRITEdefaults tofalse. With writes disabled,set_radio_channelreturns a clearWriteDisabledErrorand never touches the device - the gate is checked before any read or write call is made, regardless ofconfirm.Central allowlist, no generic command tool. There is no tool that accepts an arbitrary API path or request body. The one write operation this package exposes is a dedicated, named function (
guard.set_radio_channel) mapped to exactly one fixed endpoint inguard.ALLOWLIST. There is no code path by which a caller can reach an API path outside that table -OmadaClient._patch_v2(the underlying write primitive) is never called anywhere except that one function.Explicit confirm with before/after preview.
set_radio_channeltakes aconfirm: boolparameter. Withconfirm=False(the default), it reads the device's current radio configuration and returns what would change - abefore/afterstructure - without applying anything. Onlyconfirm=Trueapplies the change.Empirical re-read verification - mcp-omada's own addition, not something mcp-mikrotik needs. A confirmed write's
errorCode 0is never trusted on its own:set_radio_channelre-reads the device afterward and compares the resultingfreqagainst what was requested.applied=Trueis returned ONLY when they match - a controller that accepts a write but doesn't actually apply it (an uncharacterized rejection - a DFS channel the firmware refuses, say - beyond the two silent-discard causes already ruled out by construction) is reported asapplied=Falsewith a clearmessage, never a false positive. This exists because Omada's controller CAN answer "success" for a write it didn't apply - RouterOS's own API doesn't, so mcp-mikrotik has no equivalent control.
Every call also carries a warning: changing a channel restarts the radio
(clients on that band briefly disconnect and reassociate), plus, on 5GHz,
the confirmed channel-persists-as-internal-index caveat (see
docs/api-notes.md) - so a caller reading only applied/after can't
miss either.
set_radio_channel is registered unconditionally (like mcp-mikrotik's
set_identity) - the tool is always callable; OMADA_ALLOW_WRITE=false
makes every call cleanly refuse rather than making the tool disappear,
which would be harder to diagnose.
Audit journal. Every set_radio_channel call - previewed, applied,
rejected by the re-read check, or errored - is recorded as one structured
JSON-lines event (src/mcp_omada/audit.py, following the model
mcp-mikrotik's audit.py/correlation.py established): a per-call
correlation id, the target MAC, before/after, warning/message, and outcome.
Written to OMADA_AUDIT_LOG if set, otherwise a stderr INFO line. Never
includes a controller credential, in any outcome - see
docs/api-notes.md's "Audit journal" section for the full shape and the
fourth outcome ("rejected") mcp-mikrotik's own three-outcome journal has
no equivalent of.
On top of the write guard:
Structured HTTP, not shell commands. All controller communication goes through
httpxwith structured URL path segments, query parameters, and JSON bodies. Nothing in this codebase builds a request by concatenating strings from caller-supplied input, so injection through a MAC address or site ID is ruled out by construction rather than by input filtering.Input validation on top, for its own sake.
get_device_detail/get_wifi_summary/set_radio_channel'smacargument is still validated and normalized before use (src/mcp_omada/validation.py), andset_radio_channel'sband/channelare validated against a fixed channel/frequency table (src/mcp_omada/channels.py) before any device is touched - purely to reject garbage input early with a clear error, not as an injection defense (see previous point).No secrets in output or logs. Password, client secret, CSRF token, session cookie, and Open API access token are never included in a log message or an exception's own text - exceptions carry only what the controller told us (an
errorCode/msg), never the request that was sent.Settings' credential fields are allrepr=False.TLS verification is explicit, not silently bypassed.
OMADA_VERIFY_TLSdefaults tofalsewith a loud startup warning (not a silent downgrade) - see "Configuration" above for why an OC200 in LAN needs this by default.Structured errors. All errors raised inside the package derive from
OmadaMCPError(src/mcp_omada/exceptions.py) and are caught at the tool boundary inserver.py, which returns a clean, structured result. Unexpected exceptions are logged server-side and returned to the caller as a generic internal-error message, never as a raw traceback.
Development
pip install -e ".[dev]"
pytest --cov=mcp_omada --cov-report=term-missing --cov-fail-under=100
ruff check .
ruff format --check .
mypy src/mcp_omadaThe test suite never talks to a real controller: tests/fakes.py provides
an httpx.MockTransport-backed fake that reproduces both auth flows and
the exact JSON shapes (including the documented gotchas) confirmed against
real hardware, injected via a client_factory parameter on
build_server() - the same dependency-injection shape mcp-mikrotik's
tests/fakes.py uses for its RouterOS connection.
Roadmap
v0.2 - delivered.
set_radio_channel, the first guarded write, following mcp-mikrotik'sguard.pymodel (a named, reviewable write operation; a read-only gate checked before anything is touched; explicitconfirm/before-after preview; an audit journal) plus, specific to this package, empirical re-read verification of the write - andget_clients(Insight/known clients) andget_alerts(envelope verified, row shape honestly flagged as unverified). Seedocs/api-notes.md.v0.3 -
get_logs+ more guarded writes.get_logsis NOT in v0.2: every device/system log endpoint path tried (log,logs,logs/queryLog,setting/logs/logs,insight/logs) returnederrorCode -1600against real hardware - deferred until a working endpoint is found (seedocs/api-notes.md). Additional guarded writes under consideration: AP reboot (needs its own confirmation/cooldown policy - no meaningful before/after preview for a reboot, mirroring mcp-mikrotik's own reasoning for excluding it from its v0 allowlist), LED control.v3-controller compatibility. The pre-v5 controller UI uses a different login call and session cookie name entirely - recorded as a historical note (not independently verified) in
docs/api-notes.md, for whoever picks this up.
License
Apache-2.0 - see LICENSE.
Related projects
No official TP-Link MCP server exists as of 2026-07; TP-Link's official
offering is the Omada Open API
(OAuth, /openapi/v1, reduced field set). Community MCP servers we know of:
MiguelTVMS/tplink-omada-mcp — TypeScript; includes a generic "invoke arbitrary endpoint" tool.
realtydev/omada-mcp — fork with full CRUD (60+ read/write tools).
gaspareduard/Omada-mcp — Open-API-based, capability-gated.
How this project differs: read-only by default with no generic endpoint
escape hatch (every write lands behind an explicit, reviewable allowlist -
OMADA_ALLOW_WRITE + guard.py, mirroring
mcp-mikrotik), and the
legacy /api/v2 path — which Open-API-only clients cannot reach (the
Open API token is rejected there; verified against real hardware) — for the
rich per-radio/per-client data, with every field-shape gotcha documented in
docs/api-notes.md.
Available Tools
8 toolsget_alertsA
List active alerts on a site.
The pagination envelope is confirmed against real hardware; the
shape of an individual alert row is NOT (no alert was active during
verification) - each entry includes a best-effort module/level/
content/time guess AND the untouched raw row, so nothing is lost
if the guess is wrong. See docs/api-notes.md.
Requires legacy login (OMADA_USER/OMADA_PASS) - no Open API equivalent has been verified against real hardware yet.
| Name | Required | Description | Default |
|---|---|---|---|
| site_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and delivers: it discloses that the pagination envelope is verified but the per-row shape is not, that entries contain best-effort guesses plus an untouched `raw` row, and that legacy authentication is required. This is unusually candid behavioral context an agent could not get from the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded in one line, followed by detail blocks that each add real information (data-shape caveat, auth requirement). It is slightly verbose with parenthetical asides but nothing is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values needn't be explained, and the description supplies valuable caveats about unverified row shape and auth. The only meaningful omission is the semantics of the site_id parameter, which leaves the definition slightly short for a 1-param tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single parameter site_id has 0% schema description coverage, so the description is responsible for clarifying it, yet it never mentions site_id at all. It is unclear whether omitting site_id lists alerts across all sites or behaves some other way, leaving a real gap for the only input.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence gives a specific verb and resource ('List active alerts on a site'), making the tool's function immediately clear. Although siblings like list_devices exist, the alerts resource is distinct enough that no explicit differentiation is needed. An agent can identify this tool's job from the first line.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description states a prerequisite (legacy OMADA_USER/OMADA_PASS login) but never says when to choose this tool over alternatives or under what conditions alert listing is appropriate. There is no when-to-use or when-not-to-use framing at all.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_clientsA
List Insight/known clients on a site: mac, name, download/upload (bytes), duration_seconds, last_seen_ms, guest/wireless flags, vid (VLAN), and block/manager flags. This is the controller's "Insight" view (historical + known clients), not just currently-associated ones.
Requires legacy login (OMADA_USER/OMADA_PASS) - no Open API equivalent has been verified against real hardware yet.
| Name | Required | Description | Default |
|---|---|---|---|
| site_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the behavioral burden and does disclose two important traits: the authentication method required (legacy login, no verified Open API path) and the historical/known scope of the data. It omits pagination, rate limits, and how fresh last_seen_ms is, which are secondary for a read tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose and scope clarification, followed by the auth caveat. The field enumeration is long but informative rather than redundant, and no sentence is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is low-complexity (one optional param) and an output schema exists, so return-value explanation isn't required. The auth requirement and scope caveat are the key operational facts and are present; only the site_id default semantics are left uncovered.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% for the single site_id parameter, and the description never mentions it or explains default behavior when omitted. The description adds no parameter meaning beyond the bare schema name, so it fails to compensate for the coverage gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb (List) plus resource (Insight/known clients on a site), with the return fields enumerated. It explicitly distinguishes itself from a plain 'currently-associated clients' listing, giving the agent a clear mental model of the scope.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clarifies when this view applies (historical + known clients, not just currently-associated), which is a meaningful selection cue. It also states the auth prerequisite. It doesn't name a sibling as the alternative for live-only listings, so it stops just short of full routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_controller_infoA
Get the Omada controller's own identity: controller_version, omadac_id, configured. Unauthenticated (GET /api/info) - works regardless of which auth mode this server is configured with.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does disclose the key behavioral trait: this endpoint is unauthenticated and independent of the server's auth mode, plus the underlying route GET /api/info. It omits rate-limit or failure-mode notes, but the safety-relevant auth behavior is covered and an output schema exists for the return shape.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two compact sentences, front-loading the identity/fields and then the auth property. Every clause adds information, with no padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, read-only identity probe with an output schema already documenting the return values, the description supplies everything needed: what it returns, the underlying route, and that no authentication is required.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing for the description to disambiguate beyond what the empty schema already shows; the baseline for a no-param tool applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Get the Omada controller's own identity') and enumerates the returned fields (controller_version, omadac_id, configured), which cleanly separates it from siblings like list_sites and get_clients. An agent can distinguish it without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives a clear usage condition: the call is unauthenticated and works regardless of the server's configured auth mode, which tells the agent it is safe to call at any time. It stops short of naming alternatives or explicit exclusions, but the context is unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_device_detailA
Get the richest available detail for one device by MAC address (any common separator - colon, hyphen, Cisco-dotted, or bare - is accepted and normalized).
Legacy auth: full detail (ssidOverrides, lanPortSettings, ledSetting, ...) for AP/EAP devices via /eaps/{MAC}; other device types fall back to their grid/devices summary row (no richer verified endpoint yet - see README). Open API auth: the matching (reduced-field) row from the device list.
The normalized fields (see list_devices) are included, plus the
complete raw response under raw so nothing the controller
returned is lost.
| Name | Required | Description | Default |
|---|---|---|---|
| mac | Yes | ||
| site_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses that output depth varies by auth mode (legacy full detail for AP/EAP, summary rows otherwise, reduced fields on Open API), that fields are normalized plus a raw passthrough, and that MAC separators are normalized. It stops short of permission/rate-limit or error behavior, so it is strong but not exhaustive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose and the MAC-format detail in the first sentence, then auth-mode behavior, then return shape. Dense and mostly waste-free, though the parenthetical asides and trailing 'so nothing the controller returned is lost' add mild length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, yet the description still usefully characterizes the return shape (normalized fields plus raw), and it documents the auth-dependent behavioral split, which the schema cannot convey. The unaddressed `site_id` parameter is the notable gap preventing a 5.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate for both parameters. It compensates excellently for `mac` (accepted separators: colon, hyphen, Cisco-dotted, bare; normalized), but `site_id` is never mentioned, leaving half the parameters undocumented in both schema and description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('get the richest available detail for one device') plus the keying mechanism (MAC address), which sharply distinguishes it from the sibling list_devices. An agent can tell what the tool returns and how it is identified without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the contrast with list_devices (one device vs. a list) and by the noted fallback behavior, but there is no explicit 'use this when / not when' statement or named alternative. The auth-mode caveat hints at when richer data is available, which is useful but not a routing rule.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_wifi_summaryA
Per-AP WiFi summary: 2.4GHz/5GHz channel (parsed from the
controller's irregularly-formatted actualChannel string), client
counts per band, and radio utilization. One entry per AP on the
site, or just the one matching mac if given.
Requires legacy login (OMADA_USER/OMADA_PASS) - the Open API device list has no per-radio fields at all (confirmed against real hardware; see README's auth x endpoint matrix).
| Name | Required | Description | Default |
|---|---|---|---|
| mac | No | ||
| site_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so well: it discloses that legacy login (OMADA_USER/OMADA_PASS) is required, explains why (the Open API device list has no per-radio fields, verified against real hardware), and warns that actualChannel is irregularly formatted and must be parsed. These are exactly the operational caveats an agent needs before calling.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the returned payload, followed by scope and then the auth caveat; every clause carries information. The parenthetical on the parser and the README reference are slightly verbose but justified.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value documentation is unnecessary, and the description covers the data source, auth path, and scoping. The only real gap is the unaddressed `site_id` parameter and its default behavior.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% for two parameters, so the description must compensate. It clarifies `mac` as an optional single-AP filter, but says nothing about `site_id` or what a null/default value implies, leaving half the parameters undocumented in both places.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific resource and enumerates the exact fields returned (2.4/5GHz channel, per-band client counts, radio utilization), so an agent knows precisely what it gets. It doesn't explicitly contrast itself with siblings like get_device_detail or get_clients, which keeps it short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states the scoping behavior clearly — one entry per AP on the site, or a single AP when `mac` is given — and notes the legacy-login prerequisite. However, it never says when to prefer this over the sibling get_device_detail or list_devices, so usage routing is only implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_devicesA
List devices on a site, normalized to one consistent shape regardless of auth mode (see formatting.normalize_device): connected, uptime_seconds, per-radio wifi_2g/wifi_5g channel info, and every raw field this package knows how to normalize - fields unavailable in the active auth mode are present as null rather than omitted, so a caller never has to branch on auth mode.
site_id defaults to OMADA_SITE_ID, or auto-selects if the
controller manages exactly one site (legacy auth only).
| Name | Required | Description | Default |
|---|---|---|---|
| site_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden and does so well for return-shape behavior: fields unavailable under the active auth mode appear as null rather than being omitted, so callers never branch on auth mode. It omits read-only/pagination/rate-limit disclosure, which is a gap for a list tool, but the null-filling contract is genuinely non-obvious behavioral information.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the purpose, then the normalization contract, then the parameter default. Two short paragraphs, no filler sentences. The detail on return shape is somewhat long for a one-parameter tool but each clause carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so raw return values need not be explained, yet the description usefully adds the null-semantics contract that the schema likely cannot express. It leaves out pagination/result-size behavior and any explicit read-only statement, which are the only notable gaps for a zero-required-arg list tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% (the schema only says 'Site Id', default null), so the description must compensate — and it does: site_id falls back to OMADA_SITE_ID or auto-selects when the controller manages exactly one site (legacy auth only). That is real added meaning beyond the schema, including an auth-mode caveat.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List devices on a site') and immediately qualifies the scope with the normalization guarantee. It does not name or contrast with any sibling (e.g. get_device_detail or get_clients), so the agent must infer boundaries itself, but the core purpose is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied rather than stated: the site_id defaulting rules (OMADA_SITE_ID, single-site auto-select) tell the agent it can call this with no arguments, which is useful context. However, there is no explicit when-to-use-this vs get_device_detail/get_clients guidance and no exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_sitesA
List sites managed by this controller: id + name.
Requires legacy login (OMADA_USER/OMADA_PASS) - see README's auth x endpoint matrix for why the Open API path can't serve this in v0.1.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations exist, so the description carries the full burden and does so well. It discloses a critical behavioral trait: this endpoint requires legacy login (OMADA_USER/OMADA_PASS) and the Open API path cannot serve it in v0.1, pointing to README for the auth x endpoint matrix. This is exactly the kind of context an agent needs to know before attempting the call.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with what it does and what it returns, then immediately covers the auth caveat. Every part earns its place; no fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite no output schema details needed (output schema exists), the description covers the key gaps: no annotations means it must disclose auth requirements, which it does thoroughly. For a zero-param list tool, this is complete – it tells the agent what to expect and what's required to call it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so baseline is 4. The description correctly adds no parameter details, but the auth prerequisite is effectively an implicit input requirement (needs OMADA_USER/OMADA_PASS credentials), which is useful semantic context.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('List') and resource ('sites') and even specifies the exact output fields ('id + name'). An agent can immediately distinguish this from siblings like list_devices or get_controller_info, which target different resources.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage ('List sites managed by this controller') but provides no explicit when-to-use vs alternatives guidance. There's no sibling differentiation or condition named. The auth note is a prerequisite, not usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
set_radio_channelA
Set an AP's 2.4GHz ("2g") or 5GHz ("5g") radio channel.
WRITE tool, guarded: blocked entirely unless the server is running with OMADA_ALLOW_WRITE=true. Call with confirm=False (the default) to get a before/after preview (channel + freq) without changing anything; call again with confirm=True to actually apply it.
applied=True is only ever returned once a post-write re-read
confirms the change (comparing freq, the one reliable round-trip
value) - a bare "the controller said Success" is never trusted on
its own. If the controller accepted the write but the re-read shows
no change, applied=False and message explains why - see
guard.set_radio_channel's docstring and docs/api-notes.md. Every
call also carries a warning: changing a channel restarts the
radio (clients on that band briefly disconnect and reassociate),
plus, on 5GHz, the confirmed channel-persists-as-internal-index
caveat.
Legacy auth only. channel is the operator-facing channel number
(e.g. 11 on 2.4GHz, 149 on 5GHz) - this tool always derives the
matching frequency itself (channels.py) and resends the complete
current radio configuration with only channel/freq changed, so the
confirmed real-hardware silent-discard gotcha (int channel, or a
missing freq) can't be hit by construction - see docs/api-notes.md.
| Name | Required | Description | Default |
|---|---|---|---|
| mac | Yes | ||
| band | Yes | ||
| channel | Yes | ||
| confirm | No | ||
| site_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are present, so the description carries the full burden and does so: it discloses the write guard, the preview/apply flow, that applied=True requires a post-write re-read of freq, the applied=False outcome with an explanatory message, the legacy-auth requirement, and the warning that changing a channel briefly drops clients on that band.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the operation, then the guard, then confirm semantics, then return/warning details in a logical order. It is dense with implementation references to guard docstrings and docs/api-notes.md that could be trimmed, but little of the content is truly redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a guarded mutation with an output schema, the description covers the guard, the confirmation protocol, what applied/message mean, and the side effects. Returns are already documented by the output schema, so nothing an agent needs to invoke this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate, and it does for the two non-obvious params: channel is the operator-facing number (11 on 2.4GHz, 149 on 5GHz) with freq derived internally, and confirm's preview-vs-apply semantics are spelled out. mac and site_id are left unexplained, and band only gains the '2g'/'5g' hint, so the coverage is strong but not complete.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Set) plus the exact resource (an AP's 2.4GHz or 5GHz radio channel) and names the band tokens '2g'/'5g' that the agent must supply. All siblings are read-only tools, so this write tool is unambiguously distinguished.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states the write guard (blocked unless OMADA_ALLOW_WRITE=true) and the two-step flow: call with confirm=False for a preview, then with confirm=True to apply. This is exactly the when-and-how guidance an agent needs before invoking.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
8 tool updates
v0.2.0- First observed
get_alerts - First observed
get_clients - First observed
get_controller_info - First observed
get_device_detail - First observed
get_wifi_summary - First observed
list_devices - First observed
list_sites - First observed
set_radio_channel
TDQS
Scored across 8 tools
Most tools have clearly distinct purposes (list vs detail vs summary vs info), and get_clients/get_alerts are unambiguous. However list_devices and get_wifi_summary overlap on per-AP channel/radio data, so an agent could reasonably confuse them for WiFi-channel queries.
All tool names use consistent snake_case verb_noun patterns: list_sites, list_devices, get_device_detail, get_wifi_summary, get_controller_info, get_clients, get_alerts, set_radio_channel. The verbs list/get/set are applied predictably.
Eight tools is a well-scoped size for an Omada controller server: it covers sites, devices, clients, alerts, controller info, WiFi summaries, and one guarded write without feeling bloated or thin. Each tool maps to a distinct data need.
The read surface covers common monitoring needs but lacks broader management operations: no SSID/VLAN configuration, no client block/unblock, no device reboot, and no update/delete beyond a single radio-channel write. Notable gaps remain for a server named mcp-omada.
Maintenance
Related MCP Connectors
MCP server wrapping the Tesla Fleet API and TeslaMate API
Read-only MCP server for ClassQuill, a tutoring-business-management platform.
Read-only MCP server for The Quiet Protocol's engines, benchmarks, proof, and business data.
Read-only MCP server for AIStatusDashboard status, incidents, metrics, and fallback recommendations.
Related MCP Servers
- AlicenseAqualityDmaintenanceA Model Context Protocol server that enables AI assistants to read and safely modify TP-Link Omada networks through capability-gated tools, with a default read-only profile and dry-run writes for security.11MIT
- AlicenseBqualityFmaintenanceSecurity-focused MCP server for TP-Link Omada Open API workflows, enabling network management via natural language.8721MIT
- FlicenseNot gradedqualityBmaintenanceRead-only MCP server that surfaces UniFi network data from a UCG-Fiber console to Claude, enabling monitoring of hosts, sites, devices, clients, ISP metrics, and device stats via read-only API tools.-
- AlicenseBqualityCmaintenanceA Model Context Protocol server that exposes TP-Link Omada controller APIs to AI copilots and automation workflows, enabling listing sites, devices, clients, and invoking arbitrary Omada endpoints.84MIT