omada-mcp
This server provides read-only inspection of a TP-Link Omada controller; the provided schema exposes no write operations.
Check configured connection state without logging in or making a network call.
List accessible sites.
List adopted devices for a site.
List active clients for a site.
Read LAN network, VLAN, subnet, and DHCP settings without SSID passwords.
Read one fixed configuration dataset, including DHCP reservations, groups, gateway ACLs, port forwards, mDNS/Bonjour, site/internet/bandwidth/QoS/DNS/firewall/IDS/remote logging/IGMP settings, clients, and LAN networks.
Rely on closed enums and required site identifiers rather than arbitrary paths or methods.
Provides read-only and optional write access to TP-Link Omada controllers, allowing inspection and management of sites, devices, clients, VLANs, gateway ACLs, NAT, mDNS, QoS, DNS, DHCP reservations, groups, port forwards, and LAN networks.
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., "@omada-mcpshow me all clients on my Omada network"
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.
omada-mcp
A Model Context Protocol server for TP-Link Omada controllers (SDN / Omada Controller 5.x–6.x).
It lets an MCP client (OpenCode, Claude, Cursor, etc.) inspect an Omada network — sites, devices, clients, VLANs, gateway ACLs, NAT, mDNS, QoS, DNS and more.
Read-only by default. Set OMADA_ENABLE_WRITES=true to additionally register 17 write tools
(Gateway ACL, DHCP reservation, group, port-forward, mDNS, LAN network create/update/delete plus
ACL reorder). See Write mode — even when enabled, every write is dry-run until you
pass confirm:true, and only a fixed whitelist of endpoints is reachable.
Why read-only by default?
Most Omada MCP servers can write unconditionally. That's powerful, but risky: a model can delete an ACL, disable a VLAN, or reboot a gateway from a mistyped prompt. Here, write capability is an explicit opt-in rather than an assumption, and it stays constrained even when on:
Flag-gated registration. Without
OMADA_ENABLE_WRITES=true, write tools are never registered — they don't appear in the tool list at all.Dry-run default. When registered, every write tool validates and returns the request without sending it unless you pass
confirm: true. Two independent gates.No arbitrary writers. There is no generic
create/update/delete/requestescape hatch. Writes go through onewrite(operation, …)entry point bound to a fixed operation whitelist.Method + path guard. Every mutating request is re-checked against the whitelist's exact verb, path pattern, API version, and controller ID before any socket opens.
Strict identifiers. Dynamic path segments must match
[A-Za-z0-9_-]+, so an identifier cannot traverse out of its path segment (..,/,//are all rejected).Schema validation. Write bodies require the fields Omada's own Open API marks required; unknown top-level fields are rejected by a strict input schema.
Read-back. Each write returns Omada's acknowledgement (
errorCode 0= accepted); verify with the read tools before and after.
Write access is available here — it's just opt-in and bounded. Turn it on only when you intend to change configuration; leave it off for routine inspection. See Write mode.
Related MCP server: Unimus MCP Server
Safety properties
TLS stays verified. The controller's leaf certificate is pinned by SHA-256. A changed certificate fails closed rather than falling back to "ignore errors".
No secrets in output. Tool results are recursively redacted before they're returned (tokens, secrets, cookies, passwords, keys).
No empty-on-error. Paginated reads refuse malformed, truncated or repeated responses and surface an error instead of reporting "0 results".
No network calls on setup.
getConnectionStatusreports what's configured without logging in.
Install
git clone <this-repo>
cd omada-mcp
npm installQuick start
1. Discover your controller and pin its certificate:
node setup.mjs https://<controller-host>:8043This reads the public /api/info endpoint, writes controller.json + controller.pem, and prints
the certificate's SHA-256. Verify that fingerprint against the controller UI or browser padlock
before entering credentials — it's trust-on-first-observation on your own LAN, not PKI.
2. Provide credentials. Either set environment variables (recommended) or store them locally:
# Option A — pre-fetched access token (no API app needed)
export OMADA_ACCESS_TOKEN="<token>"
# Option B — Open API application in Client mode
export OMADA_CLIENT_ID="<client-id>"
export OMADA_CLIENT_SECRET="<client-secret>"Or store them in an owner-only .secrets/credentials.json (never committed):
python3 configure.py # Client ID + Secret
python3 configure.py --token # or a pre-fetched access tokenCreate the application under Omada → Settings → Platform Integration → Open API, in Client mode, with the narrowest role your read-only needs allow. Don't reuse your admin password.
3. Run:
npm startAdd to OpenCode
opencode mcp add omada --global -- /usr/bin/node /path/to/omada-mcp/src/index.mjsOr with credentials in the MCP config:
{
"mcp": {
"servers": {
"omada": {
"type": "local",
"command": ["node", "/path/to/omada-mcp/src/index.mjs"],
"environment": {
"OMADA_BASE_URL": "https://<controller-host>:8043",
"OMADA_OMADAC_ID": "<omadacId>",
"OMADA_CERT_SHA256": "<sha256>",
"OMADA_ACCESS_TOKEN": "{env:OMADA_ACCESS_TOKEN}"
}
}
}
}
}setup.mjs already writes baseUrl, controllerId and certificateSha256 into controller.json,
so if you're running next to the repo you only need to supply credentials.
Tools
Read tools (always available):
Tool | Purpose |
| Local config readiness; no login, no network call |
| Sites accessible to the credential |
| Adopted devices for a site |
| Active clients for a site |
| VLAN/subnet/DHCP settings (no SSID passwords) |
| Read one fixed dataset from a fixed registry |
| Recent site alerts (severity, category, message, timestamp) |
| Paginated site event log, newest first |
| One device by MAC: model, status, uptime, counts, type-specific detail |
| Switch port status: link, speed, PoE, errors, and the client on each port |
readSiteConfiguration accepts one of a fixed set of dataset names — no arbitrary paths or HTTP
methods are reachable:
dhcpReservations, groups, gatewayAcls, gatewayAclMode, customGatewayAcls, portForwards,
mdns, bonjourServices, siteSpecification, internet, bandwidthControl, bandwidthRules,
gatewayBandwidthRules, qosWans, qosClasses, dnsProxy, dnsCache, firewall, idsIps,
remoteLogging, igmp, clients, lanNetworks.
Endpoint paths were checked against the Omada Open API reference. Endpoints can vary by controller version: an unsupported or unauthorized endpoint returns a visible error, never an empty list.
Site logs
getAlerts and getEvents read the two site log endpoints. Both take a siteId, an optional
since/until window (ISO-8601 timestamp or date), and a limit (default 50, max 200). Records
come back newest first.
Tool | Arguments | Returns |
|
|
|
|
|
|
categoryisSystem,DeviceorClient(the controller'sfilters.module).severityisInformation,Warning,ErrororCritical— case sensitive. The controller matchesWarning, notwarning.resolvedistrueorfalseand applies to alerts only.The event log has no severity and no resolved state, so those filters are rejected there rather than silently ignored — the controller accepts a filter it does not implement and returns an unfiltered log that looks filtered.
Filter values are validated against a closed list. The controller accepts an unknown value (
warning,NotALevel) and matches nothing, which is indistinguishable from "no such alerts" — so a typo is an error instead of a silent empty result.
Both tools return an envelope: {kind, siteId, window, limit, returned, totalRows, limited, records}.
totalRows is the controller's own count for the query and limited says whether the read was
bounded — so "nothing matched" is never confused with "there was more than the limit".
What the controller does not expose, and the tools therefore do not claim:
There is no separate first/last-seen timestamp. The controller stores one occurrence time per row, so
firstSeenandlastSeenboth mirrortime.There is no per-row device or client id. The related hardware is named inside the message (
[ap:28-87-BA-A2-73-62],[client:B4-22-00-0B-23-77 ]), sodeviceMacsandclientMacsare parsed out of the message text and normalised to the same uppercase-dashed MAC form the device and client inventories use.Event rows carry no severity field at all.
The default window is the last 7 days. The log endpoints require a time window and bounded pagination server-side, so the reads are always windowed and paginated — a malformed, truncated or repeated response is a visible error, never "0 results".
Device and switch-port status
getDevice and getSwitchPorts read one device and one switch's ports. Both take a siteId and
an optional deviceId — a MAC address as listDevices reports it. The id is uppercased for
comparison and is otherwise the controller's own value, so 28-87-BA-C0-4E-4A,
28-87-ba-c0-4e-4a and 2887BAC04E4A all resolve to the same device. The id must be one of
[A-Za-z0-9_-] and is rejected before any controller call.
Tool | Arguments | Returns |
|
|
|
|
|
|
deviceId is required on getDevice and optional on getSwitchPorts: with it, only that
switch's ports come back; without it, every switch in the site is read.
getDevice resolves the id against the site's real device list before issuing a detail
request, so an unknown id is an error naming the id — and listing a few valid ones — rather than a
404 or an empty body. An unsupported device type is likewise an error that names the type.
There is no generic device-detail endpoint. The controller has no /devices/{mac} route —
it answers 404. Each device type has its own detail route, so getDevice reads
switches/{mac}, aps/{mac} or gateways/{mac} depending on what the id actually is.
device is {deviceId, name, type, model, modelName, modelVersion, hardwareVersion, ip, firmwareVersion, firmwareVersionShort, status, statusCode, statusCategory, statusDetail, statusDetailCode, uptimeSeconds, uptime, cpuUtil, memUtil, temperature, sn, lastSeen, portCount}:
statusisonline/offline/unknown, derived from the device's own code in the controller's own space and not reinterpreted.statusDetailandstatusDetailCodekeep the detail route's separate status space intact — the list route and the detail route do not share one.statusCategoryis the detail route's own coarser category, reported as the raw code.The Open API does not report last-seen for a device, so
lastSeenmirrorsuptimewhen the device is connected and isnullwhen it is not.An unadopted device has no uptime or firmware in the list row. Those fields are
null, never0— an uptime of 0 seconds and no uptime at all are different facts, and a controller that omits a field is not being reported as "just now".cpuUtil/memUtilare null on an AP,temperature/portCountare gateway/switch-only. The tools never guess a value the controller does not expose for that device type.A device id is not validated against the write path parameters.
getDeviceproves the id exists in the site's device list, which is the read-only question; it never touchesdeviceParamPath.
getSwitchPorts joins three read-only endpoints in parallel — switches/ports/counters (link
state, errors and byte counters), switches/ports/poe-info (PoE) and clients (what is plugged
in) — narrowed server-side by filters.switchMac when a deviceId was given, and paginated until
every row is in hand. switches gives the per-switch tally {mac, name, ports, linksUp, clients}, so a filtered read of one switch is distinguishable from a whole-site read without
recounting.
Each port is {switchMac, switchName, port, portName, linkUp, linkSpeedCode, linkSpeedMbps, linkSpeed, duplex, duplexCode, poeSupported, poeMode, poeActive, poePowerW, poeVoltage, poeCurrent, poeClass, txBytes, rxBytes, txErrors, rxErrors, stp, clientConnected, clients, clientMac, clientName, clientIp, clientType}:
clientConnectedis the port's link flag, not a count of end devices. An uplink carrying another switch isclientConnected: true, clients: 0, and itsclientMac/clientName/clientIp/clientTypeare null.linkSpeedMbpsandlinkSpeedcome from a code map. Codes1/2/3(10M/100M/1G) are corroborated by the controller's own data; any other code keepslinkSpeedCodeand reports the two derived fields as null rather than guessing a rate. SeeLINK_SPEEDSinsrc/devices.mjsfor why code 5 is deliberately left unmapped.PoE is reported in two separate pieces, deliberately.
poeSupported(this port is a PoE port) andpoeMode(PoE enabled on this port) come from the counters row's own flags, so they are present even where the controller exposes no live telemetry. Thepoe-infojoin supplies only the live figures, so a port with no telemetry row gets nullpoePowerW/poeVoltage/poeCurrent/poeClassrather than a fabricated 0 W — a switch with no PoE ports still reports its ports, each correctly marked unsupported.poeClassis null when the controller reports'--', its sentinel for a PoE port with no powered device attached. That is "nothing is drawing power", not class 0.Port names come from the counters rows themselves; the tools read no port configuration.
Only the
switches/{mac}detail route used bygetDevicereturns a port list at all, and that is configuration — profile, PoE mode, enabled flag — not link status.getSwitchPortsdoes not merge it in.
Write mode
Off by default. To enable:
OMADA_ENABLE_WRITES=true npm startThis registers 17 additional tools:
Group | Tools |
Gateway ACL |
|
DHCP reservation |
|
Group profile |
|
Port forwarding |
|
mDNS |
|
LAN network |
|
The two gates
OMADA_ENABLE_WRITES=true— registers the tools. Without it they don't exist.confirm: trueon each call — actually dispatches. Omit it (or setfalse) and the call is a dry-run: it validates the body, builds the exact path, and returns what would be sent, without touching the network.
So a cautious workflow is: call normally to preview → inspect path and body → re-send with
confirm: true.
Preview → { "dryRun": true, "sent": false, "path": "/sites/<id>/acls/osg-acls", ... }
Apply → { "sent": true, "controllerAck": { ... } }What write mode cannot do
Even with the flag on, these remain blocked:
Anything not on the whitelist. Only the verbs/paths in the table above. ACL delete is at
/acls/{id}while update is at/acls/osg-acls/{id}— both allowlisted precisely; a delete to the update path is rejected, as isPUT /acls/modifyIndex.Wrong API version.
updateLanNetworkis v2 only; using it at v1 is rejected.A different controller. Paths are checked against the configured
omadacId.Path traversal. Identifiers must match
[A-Za-z0-9_-]+.Schema-invalid bodies. Required fields enforced; unknown top-level keys rejected.
Reboots, firmware upgrades, client blocking, arbitrary HTTP. Not in the whitelist.
Recommendation
Keep write mode off for routine/agent-driven inspection. Turn it on only when you intend to change configuration, and preview first. Give the Omada credential the narrowest role that still permits the operations you need.
Configuration reference
Variable | Required | Description |
| one of* | Controller base URL |
| one of* | Controller ID from |
| one of* | SHA-256 of the controller's leaf certificate |
| one of | Pre-fetched OAuth access token |
| one of | Open API Client-mode application |
| no |
|
| no | Path to |
| no | Path to the pinned |
| no | Path to a credentials JSON file |
* OMADA_BASE_URL, OMADA_OMADAC_ID and OMADA_CERT_SHA256 can instead come from
controller.json, which setup.mjs generates.
OMADA_ACCESS_TOKEN takes precedence over Client ID/Secret when both are set.
Access token vs Client ID/Secret
Access token — simplest, if you already have a short-lived OAuth token. Set
OMADA_ACCESS_TOKEN. Note tokens expire; refresh requires the credentials flow or a new token.Client ID/Secret — the server obtains and refreshes its own token automatically. Best for long-running sessions.
Tests
npm testCovers: the read-only method guard, absence of bypass writers on the client, the Omada
AccessToken header format, secret redaction, pagination completeness/failure, path construction
safety, certificate pinning (including refusal of a mismatched fingerprint), the write flag
defaulting off, the operation whitelist's exact verb/path/version matching, path-traversal
rejection in identifiers, write-tool registration only when enabled, and dry-run by default with
confirm:true required to dispatch.
Not affiliated
Unofficial. TP-Link and Omada are trademarks of TP-Link Technologies Co., Ltd.
License
MIT
Available Tools
6 toolsgetConnectionStatusARead-onlyIdempotent
Report configured connection state without logging in or making a network call.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only, non-destructive, idempotent, closed-world behavior, so the bar is lower. The description adds a genuinely useful behavioral fact beyond the annotations: no authentication and no network call, which tells the agent this is a safe, fast, offline check.
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?
A single front-loaded sentence with no filler. Every clause ('report configured connection state', 'without logging in', 'without making a network call') carries distinct 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?
For a zero-parameter status tool with no output schema, the main missing piece is what 'configured connection state' actually returns (boolean, status string, structured object). The description is otherwise adequate for a low-complexity read-only check.
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 the baseline is 4; there is nothing for the description to clarify or omit.
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 ('Report') and resource ('configured connection state') and clarifies the operation happens without login or network I/O. This distinguishes it from the sibling list/get tools, which presumably fetch remote data, though the differentiation is implicit rather than stated.
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 phrase 'without logging in or making a network call' implies a use case (cheap local pre-flight check), but there is no explicit when-to-use or when-not-to-use guidance relative to siblings like readSiteConfiguration.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
getLanNetworksARead-onlyIdempotent
Read VLAN/subnet/DHCP settings for the selected site. Contains no SSID passwords.
| Name | Required | Description | Default |
|---|---|---|---|
| siteId | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive, closed-world behavior, so the safety profile is covered. The description adds a real content disclosure beyond the annotations — that returned data contains no SSID passwords — which tells the agent what sensitive fields will and won't be present. It stops short of describing return shape or pagination.
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 short sentences, purpose front-loaded, with the non-obvious security detail second. No redundant restatement of the tool name or annotations.
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 single-parameter read tool with full annotation coverage and no output schema, the definition provides enough to call it correctly. The one remaining gap is the meaning/format of siteId, which neither the schema nor description resolves.
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 schema gives no meaning for siteId beyond type and minLength. The phrase 'the selected site' loosely ties the parameter to a site identifier, but the description never names siteId or says whether it accepts a numeric or string ID. It partially compensates for the coverage gap without fully closing it.
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 (read) and resource (VLAN/subnet/DHCP settings for a site), which is concrete enough for an agent to know what it returns. It doesn't explicitly name itself relative to siblings, but the siblings listed (listSites, listDevices, getConnectionStatus) cover different resources, so confusion risk is low.
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?
'For the selected site' implies the tool is site-scoped and requires a site context, which is the only usage hint. There is no explicit when-to-use, when-not-to-use, or alternative named among the siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
listClientsBRead-onlyIdempotent
List active clients for the selected site.
| Name | Required | Description | Default |
|---|---|---|---|
| siteId | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false, and openWorldHint=false, so the safety profile is covered. The description adds one useful behavioral detail: it returns only 'active' clients, implying inactive clients are excluded. It does not disclose pagination, return format, or how site selection is determined.
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?
A single front-loaded sentence with no wasted words. It is efficient, though its brevity leaves gaps that a slightly longer description could have closed.
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 simple (one required parameter, rich annotations, no output schema), so the description does not need extensive detail. Still, it should explain how to obtain siteId and whether results are paginated or filtered beyond 'active,' which it does not.
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%, so the description must compensate for the single required siteId parameter. It only says 'for the selected site,' which loosely maps to siteId but gives no format, source (e.g., from listSites), or constraints. This does not sufficiently compensate for the undocumented parameter.
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 active clients') with a clear scope ('for the selected site'). It is distinguishable from siblings like listDevices and listSites, though it does not explicitly name the boundary between clients and devices.
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 phrase 'for the selected site,' which signals that a site must already be selected. However, there is no explicit guidance on when to use this versus listDevices or listSites, nor any exclusions or preconditions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
listDevicesBRead-onlyIdempotent
List adopted devices for the selected site.
| Name | Required | Description | Default |
|---|---|---|---|
| siteId | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive, and closed-world, so the safety profile is covered. The description adds one piece of real behavioral context — only *adopted* devices are returned, implying unadopted/pending ones are excluded — but says nothing about pagination, result volume, or ordering.
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?
A single short sentence, front-loaded with the action and resource, with zero filler. Nothing could be trimmed without losing 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?
For a simple, well-annotated read tool this is close to adequate: the filter and the adopted-only constraint are conveyed. However, with 0% parameter description coverage and no output schema, the definition should say at minimum that siteId is required and how to obtain 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?
There is a single required parameter with 0% schema description coverage; the description gestures at it via "the selected site" but never names siteId or explains its expected format or provenance. This partially compensates for the coverage gap but leaves the one input underspecified.
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 adopted devices") plus a scope ("for the selected site"). The word "adopted" is a meaningful domain qualifier that distinguishes these from unprovisioned devices, though it never explicitly contrasts with siblings like listClients.
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?
There is no explicit when-to-use, no when-not-to-use, and no named alternative among the siblings (listClients, getLanNetworks). Only a weak implication that a site must already be selected before calling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
listSitesARead-onlyIdempotent
List sites available to the configured credential.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false and openWorldHint=false, so the safety profile is fully covered. The description's only added value is the credential-scoping note, which tells the agent results are limited to what the configured credential can see. It says nothing about result size, ordering, or pagination behavior.
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?
A single ten-word sentence with no filler, and the credential-scoping qualifier — the most decision-relevant part — is included rather than buried. 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?
For a zero-parameter read-only list tool with full annotation coverage, the definition is nearly sufficient. The gap is that there is no output schema and the description does not hint at what a returned site looks like or roughly how many to expect, which would help an agent plan subsequent calls.
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 no parameter semantics to explain — baseline 4 applies. The empty schema with additionalProperties=false is self-evident and the description correctly implies no inputs are needed.
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 sites") plus a scope qualifier ("available to the configured credential"). It is distinguishable from siblings like listDevices and listClients by resource, though it never explicitly contrasts itself with readSiteConfiguration.
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 only implied: an agent can infer this is the discovery step before readSiteConfiguration, but the description never says when to call it, what precedes it, or what to do with the result. No alternatives or exclusions are named.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
readSiteConfigurationCRead-onlyIdempotent
Read one fixed configuration dataset. No arbitrary paths or methods; unknown datasets are rejected.
| Name | Required | Description | Default |
|---|---|---|---|
| siteId | No | ||
| dataset | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive, closed-world, so safety is covered. The description adds one genuinely useful trait beyond them: unknown datasets are rejected and there is no path/method passthrough, implying strict validation errors. It says nothing about permissions, defaults, or response 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 short, front-loaded sentences with no filler; the constraint follows immediately after the purpose. Brevity is a virtue here, though the second sentence could be folded into a clause.
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?
With no output schema and 0% parameter documentation, the description should compensate and does not: it omits siteId semantics, never indicates what a returned dataset looks like, and gives no size/shape expectations. For a 23-way enum read tool this leaves real gaps.
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%, so the description must carry parameter meaning, and it does not. The optional siteId parameter is never mentioned — an agent cannot tell whether omitting it means "current site" or something else — and the 23-value dataset enum is left entirely to the schema.
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 verb+resource ("Read one fixed configuration dataset") is identifiable, but "dataset" stays abstract and the description never says this is site configuration data keyed by the schema's enum. It does not differentiate itself from siblings such as getLanNetworks, which appears to overlap with the lanNetworks dataset.
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?
No when-to-use or when-not-to-use guidance beyond the implicit "read config" framing. There is no mention of how this relates to getLanNetworks or the list* siblings, which an agent would need to route correctly.
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.
6 tool updates
v0.1.0- First observed
getConnectionStatus - First observed
getLanNetworks - First observed
listClients - First observed
listDevices - First observed
listSites - First observed
readSiteConfiguration
TDQS
Scored across 6 tools
Most tools have clear, distinct targets: connection status, sites, devices, clients, LAN networks, and site configuration. The only mild ambiguity is between getLanNetworks and readSiteConfiguration, since both read configuration-like data, but the former is explicitly VLAN/subnet/DHCP-specific.
All tool names use camelCase and begin with readable verbs such as get, list, and read. The verb choice varies slightly, but the pattern remains predictable and understandable.
Six tools is a reasonable minimum for a read-only Omada surface, but the set feels thin for practical exploration and management. It covers basic listing and status reads without becoming bloated.
The surface is intentionally read-only and covers core list/status/configuration reads, but it lacks detail-oriented operations such as getting a specific device or client, reading SSID/wireless configuration, or any write/update lifecycle operations. Agents can perform basic inspection but may hit dead ends for deeper troubleshooting.
Maintenance
Related MCP Connectors
Ovanek MCP: Model Context Protocol server for Ovanek platform tools; writes are dry-run by default.
An MCP server that provides read access to your cloud storage providers, bank accounts and more.
Read-only MCP server for interior design studios: projects, overviews, weekly activity. No writes.
Model Context Protocol server for the Apideck Unified API. Connect any MCP-compatible agent framework to 100+ accounting systems, HRIS platforms, file storage providers, and more through one integration. More information https://www.apideck.com/mcp-server
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA Model Context Protocol server that provides programmatic access to ONOS SDN controller's network management capabilities, enabling device control, topology management, and analytics through ONOS's REST API.6GPL 3.0
- AlicenseNot gradedqualityNot gradedmaintenanceA read-only Model Context Protocol server that enables interaction with Unimus network configuration management system directly via LLMs, providing access to device data, backups, and configuration analysis.MIT
- 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.111MIT
- AlicenseAqualityCmaintenanceRead-only MCP server for TP-Link Omada SDN controllers, enabling querying controller, site, device, and WiFi state.8Apache 2.0