Skip to main content
Glama

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:

  1. Flag-gated registration. Without OMADA_ENABLE_WRITES=true, write tools are never registered — they don't appear in the tool list at all.

  2. Dry-run default. When registered, every write tool validates and returns the request without sending it unless you pass confirm: true. Two independent gates.

  3. No arbitrary writers. There is no generic create/update/delete/request escape hatch. Writes go through one write(operation, …) entry point bound to a fixed operation whitelist.

  4. 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.

  5. Strict identifiers. Dynamic path segments must match [A-Za-z0-9_-]+, so an identifier cannot traverse out of its path segment (.., /, // are all rejected).

  6. 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.

  7. 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. getConnectionStatus reports what's configured without logging in.

Install

git clone <this-repo>
cd omada-mcp
npm install

Quick start

1. Discover your controller and pin its certificate:

node setup.mjs https://<controller-host>:8043

This 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 token

Create 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 start

Add to OpenCode

opencode mcp add omada --global -- /usr/bin/node /path/to/omada-mcp/src/index.mjs

Or 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

getConnectionStatus

Local config readiness; no login, no network call

listSites

Sites accessible to the credential

listDevices

Adopted devices for a site

listClients

Active clients for a site

getLanNetworks

VLAN/subnet/DHCP settings (no SSID passwords)

readSiteConfiguration

Read one fixed dataset from a fixed registry

getAlerts

Recent site alerts (severity, category, message, timestamp)

getEvents

Paginated site event log, newest first

getDevice

One device by MAC: model, status, uptime, counts, type-specific detail

getSwitchPorts

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

getAlerts

siteId, limit, since, until, category, severity, resolved

{id, key, severity, category, message, time, firstSeen, lastSeen, deviceMacs, clientMacs}

getEvents

siteId, limit, since, until, category

{id, key, category, message, time, deviceMacs, clientMacs}

  • category is System, Device or Client (the controller's filters.module).

  • severity is Information, Warning, Error or Critical — case sensitive. The controller matches Warning, not warning.

  • resolved is true or false and 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 firstSeen and lastSeen both mirror time.

  • 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 ]), so deviceMacs and clientMacs are 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

getDevice

siteId, deviceId

{siteId, device}

getSwitchPorts

siteId, deviceId (optional)

{siteId, deviceId, portCount, switchCount, switches, ports}

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}:

  • status is online/offline/unknown, derived from the device's own code in the controller's own space and not reinterpreted. statusDetail and statusDetailCode keep the detail route's separate status space intact — the list route and the detail route do not share one. statusCategory is the detail route's own coarser category, reported as the raw code.

  • The Open API does not report last-seen for a device, so lastSeen mirrors uptime when the device is connected and is null when it is not.

  • An unadopted device has no uptime or firmware in the list row. Those fields are null, never 0 — 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/memUtil are null on an AP, temperature/portCount are 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. getDevice proves the id exists in the site's device list, which is the read-only question; it never touches deviceParamPath.

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}:

  • clientConnected is the port's link flag, not a count of end devices. An uplink carrying another switch is clientConnected: true, clients: 0, and its clientMac/clientName/ clientIp/clientType are null.

  • linkSpeedMbps and linkSpeed come from a code map. Codes 1/2/3 (10M/100M/1G) are corroborated by the controller's own data; any other code keeps linkSpeedCode and reports the two derived fields as null rather than guessing a rate. See LINK_SPEEDS in src/devices.mjs for why code 5 is deliberately left unmapped.

  • PoE is reported in two separate pieces, deliberately. poeSupported (this port is a PoE port) and poeMode (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. The poe-info join supplies only the live figures, so a port with no telemetry row gets null poePowerW/poeVoltage/ poeCurrent/poeClass rather than a fabricated 0 W — a switch with no PoE ports still reports its ports, each correctly marked unsupported.

  • poeClass is 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 by getDevice returns a port list at all, and that is configuration — profile, PoE mode, enabled flag — not link status. getSwitchPorts does not merge it in.

Write mode

Off by default. To enable:

OMADA_ENABLE_WRITES=true npm start

This registers 17 additional tools:

Group

Tools

Gateway ACL

createGatewayAcl, updateGatewayAcl, deleteGatewayAcl, reorderGatewayAcls

DHCP reservation

createDhcpReservation, updateDhcpReservation, deleteDhcpReservation

Group profile

createGroupProfile, updateGroupProfile, deleteGroupProfile

Port forwarding

createPortForward, updatePortForward, deletePortForward

mDNS

createMdnsRule, updateMdnsRule, deleteMdnsRule

LAN network

updateLanNetwork

The two gates

  1. OMADA_ENABLE_WRITES=true — registers the tools. Without it they don't exist.

  2. confirm: true on each call — actually dispatches. Omit it (or set false) 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 is PUT /acls/modifyIndex.

  • Wrong API version. updateLanNetwork is 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

OMADA_BASE_URL

one of*

Controller base URL

OMADA_OMADAC_ID

one of*

Controller ID from /api/info

OMADA_CERT_SHA256

one of*

SHA-256 of the controller's leaf certificate

OMADA_ACCESS_TOKEN

one of

Pre-fetched OAuth access token

OMADA_CLIENT_ID / OMADA_CLIENT_SECRET

one of

Open API Client-mode application

OMADA_ENABLE_WRITES

no

true registers the write tools; omitted/false = read-only (default)

OMADA_CONFIG

no

Path to controller.json

OMADA_PEM

no

Path to the pinned controller.pem

OMADA_CREDENTIALS

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 test

Covers: 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 tools
getConnectionStatusA
Read-onlyIdempotent

Report configured connection state without logging in or making a network call.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.8/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.

getLanNetworksA
Read-onlyIdempotent

Read VLAN/subnet/DHCP settings for the selected site. Contains no SSID passwords.

NameRequiredDescriptionDefault
siteIdYes

TDQS

A3.8/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.

listClientsB
Read-onlyIdempotent

List active clients for the selected site.

NameRequiredDescriptionDefault
siteIdYes

TDQS

B3.2/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.

listDevicesB
Read-onlyIdempotent

List adopted devices for the selected site.

NameRequiredDescriptionDefault
siteIdYes

TDQS

B3.3/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

listSitesA
Read-onlyIdempotent

List sites available to the configured credential.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.7/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.

readSiteConfigurationC
Read-onlyIdempotent

Read one fixed configuration dataset. No arbitrary paths or methods; unknown datasets are rejected.

NameRequiredDescriptionDefault
siteIdNo
datasetYes

TDQS

C2.7/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness2/5

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.

Parameters2/5

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.

Purpose3/5

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.

Usage Guidelines2/5

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.

  1. 6 tool updatesv0.1.0
    • First observedgetConnectionStatus
    • First observedgetLanNetworks
    • First observedlistClients
    • First observedlistDevices
    • First observedlistSites
    • First observedreadSiteConfiguration

TDQS

B3.2/5.0

Scored across 6 tools

Disambiguation4/5

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.

Naming Consistency4/5

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.

Tool Count3/5

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.

Completeness3/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A 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.
    6
    GPL 3.0
  • A
    license
    Not graded
    quality
    Not graded
    maintenance
    A 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
  • A
    license
    A
    quality
    D
    maintenance
    A 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.
    11
    1
    MIT