omada-mcp 1.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omada_mcp-1.2.2/LICENSE +21 -0
- omada_mcp-1.2.2/PKG-INFO +569 -0
- omada_mcp-1.2.2/README.md +537 -0
- omada_mcp-1.2.2/pyproject.toml +76 -0
- omada_mcp-1.2.2/setup.cfg +4 -0
- omada_mcp-1.2.2/src/omada_mcp/__init__.py +41 -0
- omada_mcp-1.2.2/src/omada_mcp/__main__.py +10 -0
- omada_mcp-1.2.2/src/omada_mcp/_helpers.py +1691 -0
- omada_mcp-1.2.2/src/omada_mcp/cli.py +218 -0
- omada_mcp-1.2.2/src/omada_mcp/client.py +1729 -0
- omada_mcp-1.2.2/src/omada_mcp/config.py +669 -0
- omada_mcp-1.2.2/src/omada_mcp/doctor.py +391 -0
- omada_mcp-1.2.2/src/omada_mcp/monitor.py +652 -0
- omada_mcp-1.2.2/src/omada_mcp/py.typed +0 -0
- omada_mcp-1.2.2/src/omada_mcp/server.py +467 -0
- omada_mcp-1.2.2/src/omada_mcp/tools.py +4907 -0
- omada_mcp-1.2.2/src/omada_mcp/write_tools.py +7992 -0
- omada_mcp-1.2.2/src/omada_mcp.egg-info/PKG-INFO +569 -0
- omada_mcp-1.2.2/src/omada_mcp.egg-info/SOURCES.txt +22 -0
- omada_mcp-1.2.2/src/omada_mcp.egg-info/dependency_links.txt +1 -0
- omada_mcp-1.2.2/src/omada_mcp.egg-info/entry_points.txt +2 -0
- omada_mcp-1.2.2/src/omada_mcp.egg-info/top_level.txt +1 -0
- omada_mcp-1.2.2/tests/test_bridge.py +2270 -0
- omada_mcp-1.2.2/tests/test_write_support.py +2271 -0
omada_mcp-1.2.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Faleh Alsubaie
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
omada_mcp-1.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: omada-mcp
|
|
3
|
+
Version: 1.2.2
|
|
4
|
+
Summary: Model Context Protocol server for TP-Link Omada SDN controllers (Web API v2, zero dependencies)
|
|
5
|
+
Author: Faleh Alsubaie
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/falehsub/omada-mcp
|
|
8
|
+
Project-URL: Repository, https://github.com/falehsub/omada-mcp
|
|
9
|
+
Project-URL: Issues, https://github.com/falehsub/omada-mcp/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/falehsub/omada-mcp/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: mcp,model-context-protocol,omada,tplink,tp-link,sdn,network,eap,er605,oc200
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Operating System :: MacOS
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: System :: Networking
|
|
26
|
+
Classifier: Topic :: System :: Systems Administration
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.10
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# omada-mcp
|
|
34
|
+
|
|
35
|
+
**A Model Context Protocol server for TP-Link Omada SDN controllers — zero dependencies, read-only by default, and honest about every failure.**
|
|
36
|
+
|
|
37
|
+
[](https://github.com/falehsub/omada-mcp/actions/workflows/ci.yml)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
[](pyproject.toml)
|
|
40
|
+
|
|
41
|
+
Your Omada controller already knows which AP is overloaded, which client is
|
|
42
|
+
sitting on the guest VLAN, and which switch dropped a port. `omada-mcp` hands
|
|
43
|
+
that to Claude, Cursor, Hermes or any other MCP host — as 33 read-only tools
|
|
44
|
+
that return clean JSON (plus 46 optional write tools behind an explicit allowlist,
|
|
45
|
+
plus `--watch` and `--export-metrics` monitoring modes),
|
|
46
|
+
with a self-test that tells you exactly what to fix when something is wrong.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
uvx omada-mcp --doctor
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
omada-mcp doctor v1.2.1 on Linux-6.8 (Python 3.12.7)
|
|
54
|
+
------------------------------------------------------------------
|
|
55
|
+
PASS env /home/you/.config/omada-mcp/.env | url=https://192.0.2.10:8043 user=omada-readonly password=set (24 chars)
|
|
56
|
+
PASS tls verified against /home/you/.config/omada-mcp/controller-ca.pem (serverName=Omada) | reachable, omadacId=0123456789abcdef01234567, controllerVer=6.2.10.17, family=software-6.x
|
|
57
|
+
PASS login token accepted for omada-readonly (role=Viewer, scope=all sites, cookie=TPOMADA_SESSIONID), Csrf-Token round-trip OK
|
|
58
|
+
PASS site Home (89abcdef0123456789abcdef) site 1 of 1; matched OMADA_SITE_NAME=Home
|
|
59
|
+
PASS tools list_devices -> 7 device(s); 33 read-only tools registered (0 write tool(s) enabled), 23 client(s)
|
|
60
|
+
PASS write disabled (OMADA_WRITE_TOOLS is empty; read-only tools only)
|
|
61
|
+
------------------------------------------------------------------
|
|
62
|
+
6/6 checks passed
|
|
63
|
+
Ready: `omada-mcp` will serve 33 read-only tools on stdio.
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Add `--json` (`omada-mcp --doctor --json`) for machine-readable output validated against [`schemas/doctor.schema.json`](schemas/doctor.schema.json). JSON output redacts local `.env` and `OMADA_CA_BUNDLE` filesystem paths to origin labels (`OMADA_ENV_FILE`, `cwd`, `appdata`, `xdg`, `environment`) so reports are safe to paste into issues.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Why this exists
|
|
71
|
+
|
|
72
|
+
Omada exposes two unrelated APIs and the documentation blurs them:
|
|
73
|
+
|
|
74
|
+
| | **Web API v2** (`/{omadacId}/api/v2/...`) | **Open API** (`/openapi/v1/...`) |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| Auth | username + password (`Csrf-Token` + `TPOMADA_SESSIONID`) | OAuth2 client-credentials |
|
|
77
|
+
| Setup on controller | none — it is the API the web UI calls | "Platform Integration" must be configured |
|
|
78
|
+
| Account roles | Viewer is enough | Client ID/secret per integration |
|
|
79
|
+
| Works on OC200/OC300 | yes | firmware dependent |
|
|
80
|
+
|
|
81
|
+
`omada-mcp` speaks the **Web API**. That means: create a local Viewer account,
|
|
82
|
+
put three values in a dotenv file, and you are done. No OAuth app, no client
|
|
83
|
+
secret rotation, no per-firmware integration screen.
|
|
84
|
+
|
|
85
|
+
Three properties make it safe to hand to an LLM:
|
|
86
|
+
|
|
87
|
+
* **Read-only tools only.** Every tool is annotated `readOnlyHint: true`, and
|
|
88
|
+
`OMADA_READ_ONLY=true` fails closed: a call to anything not declared read-only
|
|
89
|
+
is refused with `-32003` *before* lookup, so a future write tool cannot leak
|
|
90
|
+
past the switch.
|
|
91
|
+
* **No secret echo.** The password never appears in output (only `set (N chars)`),
|
|
92
|
+
the session token is never logged, and WLAN pre-shared keys are redacted —
|
|
93
|
+
`list_wlans` reports `hasPresharedKey: true` instead.
|
|
94
|
+
* **Errors that carry the fix.** Controller error codes are translated
|
|
95
|
+
(`-1505`, `-1600`, `-33726`, `-41011`, `-44116`, …) and every message ends with
|
|
96
|
+
the action to take.
|
|
97
|
+
|
|
98
|
+
## Install
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# from git (works today)
|
|
102
|
+
uvx --from git+https://github.com/falehsub/omada-mcp omada-mcp --doctor
|
|
103
|
+
|
|
104
|
+
# from PyPI (once v1.2.1 is published there -- the name `omada-mcp` is reserved-free)
|
|
105
|
+
uvx omada-mcp --doctor
|
|
106
|
+
|
|
107
|
+
# or install the command (once published to PyPI)
|
|
108
|
+
uv tool install omada-mcp # or: pipx install omada-mcp
|
|
109
|
+
pip install omada-mcp # plain pip
|
|
110
|
+
|
|
111
|
+
# today, without PyPI:
|
|
112
|
+
git clone https://github.com/falehsub/omada-mcp && cd omada-mcp
|
|
113
|
+
uv tool install . # installs the `omada-mcp` command from the checkout
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Requires Python 3.10+ and nothing else. There are **no runtime dependencies** —
|
|
117
|
+
`urllib`, `ssl`, `json` and `argparse` are the whole stack, which keeps the
|
|
118
|
+
supply chain at zero.
|
|
119
|
+
|
|
120
|
+
## Configure
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
mkdir -p ~/.config/omada-mcp
|
|
124
|
+
cp .env.example ~/.config/omada-mcp/.env
|
|
125
|
+
chmod 600 ~/.config/omada-mcp/.env
|
|
126
|
+
$EDITOR ~/.config/omada-mcp/.env
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The minimum is three lines:
|
|
130
|
+
|
|
131
|
+
```dotenv
|
|
132
|
+
OMADA_BASE_URL=https://192.0.2.10:8043
|
|
133
|
+
OMADA_USERNAME=omada-readonly
|
|
134
|
+
OMADA_PASSWORD=...
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Where configuration is read from
|
|
138
|
+
|
|
139
|
+
Highest priority first — the first hit wins per variable, and the process
|
|
140
|
+
environment always overrides a file:
|
|
141
|
+
|
|
142
|
+
1. **process environment** (`export OMADA_BASE_URL=...`)
|
|
143
|
+
2. **`$OMADA_ENV_FILE`** — an explicit path (missing file = hard error, so a typo
|
|
144
|
+
cannot silently fall through)
|
|
145
|
+
3. **`./.env`** — relative to the working directory (handy for per-project setups)
|
|
146
|
+
4. **`%APPDATA%\omada-mcp\.env`** — Windows only, where a Windows user expects a per-user config
|
|
147
|
+
5. **`~/.config/omada-mcp/.env`** (or `$XDG_CONFIG_HOME/omada-mcp/.env`)
|
|
148
|
+
|
|
149
|
+
### Windows
|
|
150
|
+
|
|
151
|
+
Everything is supported natively — no WSL, no MSYS. The console script installs as
|
|
152
|
+
`omada-mcp.exe`, and `uvx` / `uv tool install` behave exactly as on POSIX.
|
|
153
|
+
|
|
154
|
+
```powershell
|
|
155
|
+
New-Item -ItemType Directory -Force "$env:APPDATA\omada-mcp" | Out-Null
|
|
156
|
+
Copy-Item .env.example "$env:APPDATA\omada-mcp\.env"
|
|
157
|
+
notepad "$env:APPDATA\omada-mcp\.env"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Windows-specific behaviour, handled for you:
|
|
161
|
+
|
|
162
|
+
* **Config location.** `%APPDATA%\omada-mcp\.env` is picked up before
|
|
163
|
+
`~/.config/omada-mcp/.env`, so the usual Windows location just works.
|
|
164
|
+
* **Encoding.** Stdio is forced to UTF-8. The console code page (cp1252, cp1256,
|
|
165
|
+
cp437 …) cannot encode an Arabic or CJK SSID, and the failure mode is a
|
|
166
|
+
`UnicodeEncodeError` in the middle of a session — this never reaches you.
|
|
167
|
+
* **Line endings.** Frames are written with a single `\n`; text mode would
|
|
168
|
+
otherwise emit `\r\n` and strict clients can mis-frame. Incoming `\r\n`
|
|
169
|
+
frames are accepted too.
|
|
170
|
+
* **Proxies.** System/WinINET proxies are ignored by default
|
|
171
|
+
(`OMADA_USE_SYSTEM_PROXY=true` to opt in).
|
|
172
|
+
* **File permissions.** API keys live in your user profile; `--doctor` skips the
|
|
173
|
+
POSIX `chmod` advice on Windows rather than handing you a command that does
|
|
174
|
+
nothing. To restrict the file to your account:
|
|
175
|
+
`icacls "%APPDATA%\omada-mcp\.env" /inheritance:r /grant:r "%USERNAME%:F"`.
|
|
176
|
+
|
|
177
|
+
`get_device`/`get_client` accept a MAC in any format (`AA-BB-CC-DD-EE-FF`,
|
|
178
|
+
`aabbccddeeff`, `aa:bb:cc:dd:ee:ff`) as well as a hostname or IP, so PowerShell
|
|
179
|
+
output pastes straight in.
|
|
180
|
+
|
|
181
|
+
### All variables
|
|
182
|
+
|
|
183
|
+
| Variable | Default | Notes |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| `OMADA_BASE_URL` | — **required** | `https://host:8043`. Scheme-less values get `https://…:8043`; `http://` defaults to port 8088. Must not embed credentials. |
|
|
186
|
+
| `OMADA_USERNAME` | — **required** | Local controller account. `Viewer` is sufficient for reads; use a **dedicated Admin account** for writes (every login evicts an existing session sharing that username). |
|
|
187
|
+
| `OMADA_PASSWORD` | — **required** | Never printed, never logged. |
|
|
188
|
+
| `OMADA_SITE_NAME` | first site | Display name, e.g. `Home`. |
|
|
189
|
+
| `OMADA_SITE_ID` | first site | Hex id **or** name — names are resolved, because the controller only accepts the hex id in URLs (that mismatch is exactly what `-1505` reports). |
|
|
190
|
+
| `OMADA_CA_BUNDLE` | system store | PEM bundle with the controller's CA. |
|
|
191
|
+
| `OMADA_VERIFY_SSL` | `true` | `false` disables chain + hostname checks. Not for production. |
|
|
192
|
+
| `OMADA_STRICT_X509` | `true` | `false` clears Python 3.13+'s `VERIFY_X509_STRICT` while keeping chain and hostname checks — the fix for "self-signed certificate" errors on 3.13/3.14. |
|
|
193
|
+
| `OMADA_TLS_SERVER_NAME` | — | Optional TLS `server_hostname` override (e.g. `Omada`) when connecting by IP against the controller's built-in cert (`SAN=DNS:Omada`) so `OMADA_CA_BUNDLE` still verifies the chain and SAN. |
|
|
194
|
+
| `OMADA_READ_ONLY` | `true` | Default `true` fails closed and refuses every non-read-only tool (`-32003`) before argument validation; set `false` to enable allowlisted writes. |
|
|
195
|
+
| `OMADA_WRITE_TOOLS` | empty | Comma-separated exact write tool names to enable (wildcards and `all` are rejected). Both `OMADA_READ_ONLY=false` and this allowlist are required to expose or invoke any write tool. |
|
|
196
|
+
| `OMADA_SNAPSHOT_DIR` | platform state dir | Override directory for pre-write JSON snapshots and `audit.jsonl` (POSIX `0700`/`0600`). Defaults to `$XDG_STATE_HOME/omada-mcp/snapshots` (`%LOCALAPPDATA%\omada-mcp\snapshots` on Windows). |
|
|
197
|
+
| `OMADA_MANAGEMENT_CIDRS` | empty | Comma-separated IPv4 hosts/CIDRs (in addition to controller host IP) protected from lockouts during VLAN isolation, DHCP pool, or deny-ACL writes. |
|
|
198
|
+
| `OMADA_USE_SYSTEM_PROXY` | `false` | System/registry proxies are ignored by default. On Windows, `urllib` inherits the WinINET proxy, which makes a one-hop controller look unreachable; set `true` only when a proxy is genuinely required. |
|
|
199
|
+
| `OMADA_TIMEOUT_SECONDS` | `30` | Per-request timeout. |
|
|
200
|
+
| `OMADA_MIN_INTERVAL_SECONDS` | `0.25` | Minimum spacing between controller calls; raise to `0.8` on large sites. |
|
|
201
|
+
| `OMADA_WRITE_INTERVAL_SECONDS` | `1.0` | Minimum serialized spacing between write requests (writes never auto-retry `-1600`). |
|
|
202
|
+
| `OMADA_SCHEMA_MODE` | `slim` | `tools/list` schema detail: `slim` (default, ~48% smaller payload while keeping `inputSchema` constraints and MCP `2025-06-18` `outputSchema` invariants intact) or `full`. |
|
|
203
|
+
| `OMADA_TOOL_CATEGORIES` | all | Optional comma-separated tool categories (`core,diagnostics,switch,wifi,routing,security,rollback`) to expose in `tools/list`. |
|
|
204
|
+
| `OMADA_COMPACT_OUTPUT` | `true` | Emit single-line compact JSON in `tools/call` `content[0].text` (`structuredContent` stays a typed JSON object). Set `false` for 2-space indented JSON. |
|
|
205
|
+
|
|
206
|
+
`omada-mcp --show-config` prints the resolved values with the password masked;
|
|
207
|
+
`omada-mcp --doctor` validates them.
|
|
208
|
+
|
|
209
|
+
## Connect an MCP host
|
|
210
|
+
|
|
211
|
+
The server is plain stdio: a host runs `uvx omada-mcp`, then speaks JSON-RPC on
|
|
212
|
+
stdin/stdout. The `OMADA_ENV_FILE` environment variable lets several hosts share
|
|
213
|
+
one configuration file.
|
|
214
|
+
|
|
215
|
+
### Hermes Agent
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
hermes mcp add omada --command uvx --args omada-mcp \
|
|
219
|
+
--env OMADA_ENV_FILE=$HOME/.config/omada-mcp/.env
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Or in `~/.hermes/config.yaml`:
|
|
223
|
+
|
|
224
|
+
```yaml
|
|
225
|
+
mcp_servers:
|
|
226
|
+
omada:
|
|
227
|
+
command: uvx
|
|
228
|
+
args: ["omada-mcp"]
|
|
229
|
+
env:
|
|
230
|
+
OMADA_ENV_FILE: /home/you/.config/omada-mcp/.env
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Claude Desktop
|
|
234
|
+
|
|
235
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or
|
|
236
|
+
`%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"mcpServers": {
|
|
241
|
+
"omada": {
|
|
242
|
+
"command": "uvx",
|
|
243
|
+
"args": ["omada-mcp"],
|
|
244
|
+
"env": {
|
|
245
|
+
"OMADA_ENV_FILE": "/home/you/.config/omada-mcp/.env"
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
A copy lives at [`examples/claude_desktop_config.json`](examples/claude_desktop_config.json).
|
|
253
|
+
|
|
254
|
+
### Claude Code
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
claude mcp add omada --env OMADA_ENV_FILE=$HOME/.config/omada-mcp/.env -- uvx omada-mcp
|
|
258
|
+
claude mcp list
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Cursor
|
|
262
|
+
|
|
263
|
+
`.cursor/mcp.json` in the project, or the global `~/.cursor/mcp.json`:
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
{
|
|
267
|
+
"mcpServers": {
|
|
268
|
+
"omada": {
|
|
269
|
+
"command": "uvx",
|
|
270
|
+
"args": ["omada-mcp"],
|
|
271
|
+
"env": { "OMADA_ENV_FILE": "/home/you/.config/omada-mcp/.env" }
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Anything else
|
|
278
|
+
|
|
279
|
+
Any host that can spawn a process and speak newline-delimited JSON-RPC 2.0 works:
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"mcpServers": {
|
|
284
|
+
"omada": {
|
|
285
|
+
"command": "uvx",
|
|
286
|
+
"args": ["omada-mcp"],
|
|
287
|
+
"env": { "OMADA_ENV_FILE": "/home/you/.config/omada-mcp/.env" }
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
From a git checkout instead of PyPI:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
uvx --from git+https://github.com/falehsub/omada-mcp omada-mcp --doctor
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Tools
|
|
300
|
+
|
|
301
|
+
Thirty-three read-only tools (~11.3k tokens in `tools/list`) are always registered;
|
|
302
|
+
46 optional write tools stay hidden and disabled until both `OMADA_READ_ONLY=false`
|
|
303
|
+
and `OMADA_WRITE_TOOLS` allowlist them explicitly. `list_clients` windowing is bounded
|
|
304
|
+
by `limit` (default 500, max 5000) and de-duplicated by MAC.
|
|
305
|
+
|
|
306
|
+
### Read-only tools (always available)
|
|
307
|
+
|
|
308
|
+
| Tool | Arguments | Returns |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| `get_site_info` | — | Site name/id, WAN/LAN/WLAN state, device and client counts, controller version |
|
|
311
|
+
| `list_devices` | — | MAC, name, type, model, firmware, IP, uptime, client count, status, `needUpgrade` |
|
|
312
|
+
| `list_clients` | `limit?` | MAC, name, hostname, IP, wireless, guest, SSID, AP, network, VLAN, rx/tx, duration, last seen (UTC ISO-8601) |
|
|
313
|
+
| `list_wifi_clients` | `limit?` | Same projection, wireless only |
|
|
314
|
+
| `list_wired_clients` | `limit?` | Same projection, wired only |
|
|
315
|
+
| `search_clients` | `query?`, `active?`, `wireless?`, `guest?`, `limit?` | Filtered client search (`clients` for Admin; automatic `insight/clients` fallback on Viewer `-1007`/`-41011`) |
|
|
316
|
+
| `get_device` | `mac` (or name) | One device |
|
|
317
|
+
| `get_client` | `mac` (or hostname/IP) | One client |
|
|
318
|
+
| `list_wlans` | — | SSID, band, VLAN, security, guest, `hasPresharedKey` (expands v6.x WLAN groups; key never returned) |
|
|
319
|
+
| `list_wlan_groups` | — | Wireless WLAN group IDs/names and member SSID summaries (secrets stripped) |
|
|
320
|
+
| `list_lan_networks` | — | Merged `setting/lan/networks` + `/interface` view (VLAN, subnet, DHCP `ipRangePool`, `leasetime`, `isolation`, `accessControlRule`) |
|
|
321
|
+
| `list_port_profiles` | — | Switch port profiles (`setting/lan/profiles`) with native/tagged VLANs, PoE mode, and `prohibitModify` flag |
|
|
322
|
+
| `list_dhcp_reservations` | `vlan?` | Offline/static DHCP reservations from `setting/service/dhcp` |
|
|
323
|
+
| `list_acls` | `aclType?` | Gateway (`?type=0`), switch (`?type=1`), or EAP (`?type=2`) ACL rules and advisory `aclDisable` flag |
|
|
324
|
+
| `list_ip_groups` | `groupType?` | IP/port/profile groups (`setting/profiles/groups`) with CIDRs / entries |
|
|
325
|
+
| `list_port_forwards` | — | Port-forwarding NAT rules (`setting/transmission/portForwardings`) |
|
|
326
|
+
| `list_static_routes` | — | Static/interface routes (`setting/transmission/staticRoutings`) |
|
|
327
|
+
| `list_policy_routes` | — | Policy routing rules (`setting/routing/policy`), degrading cleanly when no gateway is present |
|
|
328
|
+
| `list_lan_dns_rules` | — | Local LAN DNS records (`setting/dns/local`), degrading cleanly on older firmware |
|
|
329
|
+
| `get_network_services` | — | UPnP, DNS proxy, mDNS rules & built-in profiles, DDNS, and WAN port state |
|
|
330
|
+
| `list_logs` | `category?`, `hours?`, `level?`, `limit?` | Controller `alerts`, `events`, or `audit` logs within time window |
|
|
331
|
+
| `get_switch_ports` | `mac` | Per-switch port link/speed/duplex, PoE budget/power, LAG membership, uplink & controller-port flags, RX/TX errors, and cable tests |
|
|
332
|
+
| `get_ap_radios` | `mac` | Per-AP `2g`/`5g`/`5g2`/`6g` channel (resolved from `channelInfo` + `actualChannel`), width, TX power, utilization %, interference, and retry/drop rates |
|
|
333
|
+
| `get_rf_scan` | `mac` | Latest RF scan status and per-band interference summary for an AP |
|
|
334
|
+
| `get_wan_status` | `maskWanIp?`, `includeSpeedtest?` | Live WAN link/internet state, failover/load-balance mode, gateway/DNS, latency/loss, and latest speedtest |
|
|
335
|
+
| `list_vpn_tunnels` | — | WireGuard, OpenVPN, IPsec, and L2TP tunnel & peer state (all private keys, PSKs, and PEM blocks redacted) |
|
|
336
|
+
| `get_topology` | — | Compact uplink tree (Gateway -> Switch[port] -> AP/Switch) using `topology-v2` with v5 device-list fallback |
|
|
337
|
+
| `get_security_status` | — | IPS/IDS status and recent threat/WIDS alerts with graceful missing-hardware warnings |
|
|
338
|
+
| `diagnose_client` | `macOrQuery`, `hours?` | Composite client troubleshooter correlating client RSSI/SNR, parent AP radio or switch port counters, and recent logs |
|
|
339
|
+
| `diagnose_wan` | `maskWanIp?`, `hours?` | Composite WAN troubleshooter correlating link/failover, latency/loss, VPN state, and WAN log events |
|
|
340
|
+
| `network_audit` | — | Composite one-call health audit: pending firmware, offline devices, PoE budget >= 85%, uplink speed mismatches, AP retry spikes, WAN loss/failover, unexpected VLAN clients, unisolated VLANs, DHCP pool utilization, and stable finding codes |
|
|
341
|
+
| `explain_error` | `code`, `endpoint?` | Structured decoder for Omada `errorCode` values (`-1600`, `-33717`, `-33726`, `-1505`, `-1001`, `-30109`, `-41011`, `-44116`, …) with ambiguity notes and fixes |
|
|
342
|
+
| `get_config_snapshot` | `objects?` | Read-only snapshot merging `networks`, `acls`, `groups`, `wlans`, `devices`, and optional `clients`, `dhcpReservations`, `portForwards`, `staticRoutes`, `mdnsRules`, `services` |
|
|
343
|
+
|
|
344
|
+
### Opt-in write tools (`OMADA_READ_ONLY=false` + `OMADA_WRITE_TOOLS`)
|
|
345
|
+
|
|
346
|
+
Every write tool defaults `dryRun=true`, captures a pre-mutation JSON snapshot under `OMADA_SNAPSHOT_DIR`, requires `confirm="<tool>:<target>"` when `dryRun=false`, verifies state on read-back, and never auto-retries on `-1600`.
|
|
347
|
+
|
|
348
|
+
| Category | Tools | Behaviour & Safety |
|
|
349
|
+
|---|---|---|
|
|
350
|
+
| Snapshot rollback | `rollback_from_snapshot` | Loads snapshot strictly inside `OMADA_SNAPSHOT_DIR`, previews drift on `dryRun=true`, and restores pre-mutation config on `dryRun=false` |
|
|
351
|
+
| Switch ports & profiles | `update_switch_port`, `cycle_poe_port`, `upsert_port_profile` | Port enable/PoE mode/profile updates and PoE power cycling; refuses stranding uplink, controller-hosting, or LAG member ports unless `allowUplinkMutation=true` |
|
|
352
|
+
| AP radios, RF scan & WLAN groups | `update_ap_radio`, `start_rf_scan`, `assign_ap_wlan_group` | Preserves sibling band configs during `config/radios` PUT; pre-validates channel & width against `availableChannels` |
|
|
353
|
+
| Live client controls & fixed IP | `set_client_fixed_ip`, `release_client_fixed_ip`, `set_client_blocked`, `reconnect_client`, `update_client`, `set_client_lock_to_ap`, `set_client_rate_limit` | Full-object stripped client PATCH (`netId` preserved, 20 volatile keys stripped), Lock-to-AP (`clientLockToApSetting`), and rate limits |
|
|
354
|
+
| Offline DHCP reservations | `upsert_dhcp_reservation`, `delete_dhcp_reservation` | Create/update/delete reservations in `setting/service/dhcp` with subnet & IP-overlap validation |
|
|
355
|
+
| LAN & VLAN management | `create_lan_network`, `update_lan_network`, `delete_lan_network`, `set_vlan_dhcp_pools`, `set_vlan_isolation` | Two-endpoint LAN RMW, normalizes `lanNetworkIpv6Config` + `dhcpGuard`/`dhcpv6Guard`, uses `priDns`/`sndDns`, checks `ipRangePool`, refuses management/primary VLAN deletion |
|
|
356
|
+
| IP groups & ACLs | `create_ip_group`, `replace_ip_group`, `delete_ip_group`, `create_gateway_acl`, `update_gateway_acl`, `set_acl_rule_status`, `delete_gateway_acl`, `create_switch_acl`, `create_eap_acl` | Aligned CIDR packing (`<=16`, `-33726`), typed `gateway`/`switch`/`eap` ACL toggle/update/delete + auto 6-step swap (`-33717`), and management-host anti-stranding |
|
|
357
|
+
| Wireless SSIDs | `create_ssid`, `update_ssid`, `delete_ssid` | Manage SSIDs under `setting/wlans/{groupId}/ssids`; PSK/secret keys scrubbed recursively from diffs, outputs, and snapshots |
|
|
358
|
+
| Routing, DNS, NAT & services | `upsert_port_forward`, `delete_port_forward`, `upsert_static_route`, `delete_static_route`, `upsert_policy_route`, `delete_policy_route`, `upsert_lan_dns_rule`, `delete_lan_dns_rule`, `upsert_mdns_rule`, `delete_mdns_rule`, `set_upnp` | NAT port forwards, static & policy routing, local LAN DNS records, mDNS repeater rules, and UPnP toggle |
|
|
359
|
+
| Device operations | `update_device`, `reboot_device` | Rename / LED / locate / reboot EAPs, switches, and gateways (`destructive=True` on reboot; firmware upgrade remains unshipped) |
|
|
360
|
+
|
|
361
|
+
`omada-mcp --list-tools` prints active descriptors without touching the network
|
|
362
|
+
(add `--all` to inspect all 79 tools including non-allowlisted writes).
|
|
363
|
+
|
|
364
|
+
Every descriptor in `tools/list` declares MCP annotations and an MCP `2025-06-18`
|
|
365
|
+
`outputSchema`, so hosts can auto-approve reads and validate structured results:
|
|
366
|
+
|
|
367
|
+
```json
|
|
368
|
+
{
|
|
369
|
+
"name": "list_devices",
|
|
370
|
+
"title": "List Omada devices",
|
|
371
|
+
"outputSchema": {
|
|
372
|
+
"type": "object",
|
|
373
|
+
"properties": {
|
|
374
|
+
"items": { "type": "array" },
|
|
375
|
+
"count": { "type": "integer", "minimum": 0 }
|
|
376
|
+
},
|
|
377
|
+
"required": ["items", "count"]
|
|
378
|
+
},
|
|
379
|
+
"annotations": {
|
|
380
|
+
"title": "List Omada devices",
|
|
381
|
+
"readOnlyHint": true,
|
|
382
|
+
"destructiveHint": false,
|
|
383
|
+
"idempotentHint": true,
|
|
384
|
+
"openWorldHint": true
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Output shape (`content` vs `structuredContent`)
|
|
390
|
+
|
|
391
|
+
* **List tools** (`list_devices`, `list_clients`, `list_wifi_clients`,
|
|
392
|
+
`list_wired_clients`, `search_clients`, `list_wlans`, `list_wlan_groups`,
|
|
393
|
+
`list_lan_networks`, `list_port_profiles`, `list_dhcp_reservations`, `list_acls`,
|
|
394
|
+
`list_ip_groups`, `list_port_forwards`, `list_static_routes`,
|
|
395
|
+
`list_policy_routes`, `list_lan_dns_rules`, `list_vpn_tunnels`, `list_logs`):
|
|
396
|
+
`content[0].text` is serialized as a
|
|
397
|
+
**bare JSON array** (`[{...}, ...]`), while `structuredContent` is an object
|
|
398
|
+
matching `outputSchema`: `{"items": [...], "count": <int>}`. Callers parsing
|
|
399
|
+
`content[0].text` (for example migrating from wrapper objects like
|
|
400
|
+
`{"devices": [...]}`) receive a JSON list directly, whereas MCP `2025-06-18`
|
|
401
|
+
hosts read `structuredContent.items` and `structuredContent.count`.
|
|
402
|
+
* **Object tools** (`get_device`, `get_client`, `get_site_info`,
|
|
403
|
+
`get_network_services`, `get_switch_ports`, `get_ap_radios`, `get_rf_scan`,
|
|
404
|
+
`get_wan_status`, `get_topology`, `get_security_status`, `diagnose_client`,
|
|
405
|
+
`diagnose_wan`, `network_audit`, `explain_error`, `get_config_snapshot`,
|
|
406
|
+
and write envelopes): both `content[0].text` and `structuredContent` contain
|
|
407
|
+
the same JSON object matching `outputSchema`.
|
|
408
|
+
|
|
409
|
+
`omada-mcp --list-tools` prints active descriptors without touching the network;
|
|
410
|
+
`omada-mcp --list-tools --all` prints all 79 descriptors (add `--compact` for single-line JSON).
|
|
411
|
+
Direct controller connections reuse persistent HTTP/1.1 keep-alive TLS sockets (`UrllibTransport`),
|
|
412
|
+
and composite troubleshooters (`network_audit`, `diagnose_client`, `diagnose_wan`,
|
|
413
|
+
`get_config_snapshot`, `--watch` / `--export-metrics`) deduplicate repeated site-wide GETs within
|
|
414
|
+
a single invocation via an ephemeral per-call cache (`OmadaClient.scoped_cache()`).
|
|
415
|
+
|
|
416
|
+
## Watch mode, Prometheus metrics & Docker
|
|
417
|
+
|
|
418
|
+
Both monitoring modes reuse **one persistent authenticated `OmadaClient` session**
|
|
419
|
+
and enforce `>= 0.8 s` pacing so they never evict shared sessions:
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
# Stream JSON telemetry snapshot + event diffs (wan_failover, ap_retry_spike, weak_client_rssi, dhcp_pool_utilization)
|
|
423
|
+
omada-mcp --watch --interval 30
|
|
424
|
+
|
|
425
|
+
# One-shot Prometheus text exposition
|
|
426
|
+
omada-mcp --export-metrics
|
|
427
|
+
|
|
428
|
+
# Persistent /metrics HTTP endpoint (cached with >= 5 s scrape TTL + /healthz)
|
|
429
|
+
omada-mcp --export-metrics --metrics-port 9108 --metrics-host 127.0.0.1
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Build and run the non-root container (`OMADA_READ_ONLY=true` by default):
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
docker build -t omada-mcp .
|
|
436
|
+
docker run --rm -i --env-file ~/.config/omada-mcp/.env omada-mcp --doctor
|
|
437
|
+
docker run --rm -p 9108:9108 --env-file ~/.config/omada-mcp/.env omada-mcp --export-metrics --metrics-port 9108 --metrics-host 0.0.0.0
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## Safety model
|
|
441
|
+
|
|
442
|
+
| Control | Behaviour |
|
|
443
|
+
|---|---|
|
|
444
|
+
| `OMADA_READ_ONLY=true` | Refuses any tool not declared read-only (`-32003`), before argument validation or transport. |
|
|
445
|
+
| `OMADA_WRITE_TOOLS` allowlist | Defaults empty. Wildcards and `all` are rejected; write tools remain hidden and return `-32003` unless explicitly named. |
|
|
446
|
+
| Two-phase apply (`dryRun` / `confirm`) | Every write defaults `dryRun=true` (zero writes; returns diff + `confirmToken`). Applying requires boolean `dryRun=false` and exact `confirm="<tool>:<target-id>"`. |
|
|
447
|
+
| Snapshot before & verify after | Saves pre-write JSON snapshot + `audit.jsonl` record (`0700`/`0600` on POSIX), re-reads state afterwards, and fails on silent controller no-ops or collateral field drift. |
|
|
448
|
+
| Anti-stranding guards | Refuses VLAN isolation, DHCP pool overlaps, or deny ACL rules that isolate/conflict with controller IP, gateway, device IPs, or `OMADA_MANAGEMENT_CIDRS`. |
|
|
449
|
+
| Credential handling | Password only ever rendered as `set (N chars)`; tokens never logged; stdout is reserved for JSON-RPC, diagnostics go to stderr. |
|
|
450
|
+
| Secret redaction | `list_wlans` and `get_config_snapshot` omit `securityKey` / `pskSetting`; only `hasPresharedKey` is exposed. |
|
|
451
|
+
| Session recovery | A 200 response containing the web UI shell triggers **one** re-login: reads replay once, writes re-read live state first and never auto-retry `-1600`. |
|
|
452
|
+
| Pacing | Reads spaced by `OMADA_MIN_INTERVAL_SECONDS`; writes serialized at `OMADA_WRITE_INTERVAL_SECONDS` (default 1.0 s). |
|
|
453
|
+
| Dotenv hygiene | `--doctor` warns when the dotenv file is readable by group/other, and flags Viewer accounts when write tools are enabled. |
|
|
454
|
+
|
|
455
|
+
## Troubleshooting
|
|
456
|
+
|
|
457
|
+
Run `omada-mcp --doctor` first — it names the failing phase and the fix. The
|
|
458
|
+
table below maps the symptoms it cannot see.
|
|
459
|
+
|
|
460
|
+
| Symptom | Cause | Fix |
|
|
461
|
+
|---|---|---|
|
|
462
|
+
| `-1505` on a site call | Site **name** used where the hex site id is required | Set `OMADA_SITE_NAME` (or `OMADA_SITE_ID`) and let the server resolve it |
|
|
463
|
+
| `-1600` everywhere | Wrong path shape (`GET /networks/{id}`, missing `/{type}` on group DELETE, no PATCH on groups) **or** burst rate-limiting | Reads retry once after 0.8 s; writes never auto-retry `-1600` |
|
|
464
|
+
| `-33717` on group delete | Group is referenced by one or more ACL rules | Use `replace_ip_group`, which performs the 6-step temp-group ACL swap automatically |
|
|
465
|
+
| `-33726` on an IP group write | The group holds at most **16** entries | Store ranges as network-aligned CIDRs (packed automatically by `create_ip_group`/`replace_ip_group`) |
|
|
466
|
+
| `-1001` "bean validation" | A mandatory field missing from a PATCH body — for LAN networks it is `lanNetworkIpv6Config` (`proto: "none"`, `enable: 0`) | Send the whole object (handled automatically by LAN write tools) |
|
|
467
|
+
| `-1007` "no permission" | Current user role lacks write permission (e.g. Viewer account on Omada 6.2.x) | Use a dedicated Admin account for writes; keep Viewer for read-only mode |
|
|
468
|
+
| `-33598` "network isolation" | No adopted gateway/switch on the site supports Network Isolation | Adopt an isolation-capable gateway/switch or use gateway ACLs instead |
|
|
469
|
+
| `-33601` "invalid destination type" | `destinationType: 0` (`network`) used on a `LAN->WAN` gateway ACL | Use an IP group (e.g. `IPGroup_Any`) or port group on `LAN->WAN` rules |
|
|
470
|
+
| `-41011` | Client does not exist, MAC belongs to a managed device, **or** Viewer role on `/clients/*` | Use `insight/clients` or `list_devices` to inspect; use a dedicated Admin account for writes |
|
|
471
|
+
| `-44116` | You are on the Open API without Platform Integration | Use this server (Web API) instead |
|
|
472
|
+
| HTTP 200 with HTML in response | The controller's **server-side** session died (cookie is still valid for 15 days) — another login for the same account, or a restart | Automatic: the client re-authenticates once and replays |
|
|
473
|
+
| `CERTIFICATE_VERIFY_FAILED` | Self-signed controller cert, IP/SAN mismatch, or Python 3.13+ strict X509 | Set `OMADA_CA_BUNDLE` to the controller CA, `OMADA_STRICT_X509=false`, and `OMADA_TLS_SERVER_NAME=Omada` when connecting by IP; `OMADA_VERIFY_SSL=false` only as a last resort |
|
|
474
|
+
| `cannot reach …` | Wrong port or unroutable network | Controller web UI is `https://host:8043` (or `http://host:8088`) |
|
|
475
|
+
| Host starts but every call fails | No credentials at server start | Expected: `initialize`/`tools/list` never touch the controller; run `--doctor` |
|
|
476
|
+
| Windows: timeouts although the controller pings | WinINET proxy inherited by `urllib` | Proxies are off by default; if you need one, `OMADA_USE_SYSTEM_PROXY=true` |
|
|
477
|
+
| Windows: `omada-mcp` not found after `pip install --user` | `%APPDATA%\Python\Python3x\Scripts` not on `PATH` | Use `uvx`/`uv tool install`, or add the Scripts directory to `PATH` |
|
|
478
|
+
|
|
479
|
+
## Compatibility
|
|
480
|
+
|
|
481
|
+
See [`docs/compatibility.md`](docs/compatibility.md) for the full compatibility table.
|
|
482
|
+
|
|
483
|
+
* **Software controller v6.x** — read-only path (`--doctor --json` and all 33
|
|
484
|
+
read-only tools) verified live on v6.2.10.17 (`TPOMADA_SESSIONID`), and 20 baseline
|
|
485
|
+
read-only tools plus controller-native write CRUD (`create/update/delete_lan_network`
|
|
486
|
+
with explicit `ipaddrStart`/`ipaddrEnd` pools, `set_vlan_dhcp_pools`,
|
|
487
|
+
`upsert/delete_dhcp_reservation`, `create/replace/delete_ip_group`,
|
|
488
|
+
`create/update/set_acl_rule_status/delete_gateway_acl` including the 6-step swap,
|
|
489
|
+
`create_switch_acl`/`create_eap_acl` with typed toggle/update/delete lifecycle,
|
|
490
|
+
`create/update/delete_ssid` with PSK redaction, `upsert/delete_port_forward`,
|
|
491
|
+
`upsert/delete_static_route`, `upsert/delete_mdns_rule`, `set_upnp` dry-run + live
|
|
492
|
+
disable + `-33001` gateway check, `-33598` on hardwareless isolation, Viewer
|
|
493
|
+
`-1007 -> insight/clients` fallback, and `-41011`/not-found probes) verified live
|
|
494
|
+
against an isolated Docker Omada Controller v6.2.14.11; client fixed-IP lease-delayed
|
|
495
|
+
behavior (`set_client_fixed_ip`/`release_client_fixed_ip`), client actions, and device
|
|
496
|
+
controls (`update_device`, `reboot_device`) are covered by the stateful mock controller.
|
|
497
|
+
* **Software controller v5.x** — expected (v5.13.30.8 core auth/list shapes
|
|
498
|
+
supported; awaiting a `--doctor --json` capture via the issue template).
|
|
499
|
+
* **OC200 / OC300 hardware & `TPEAP_SESSIONID` builds** — expected (`--doctor`
|
|
500
|
+
detects both `TPOMADA_SESSIONID` and `TPEAP_SESSIONID`, handles legacy
|
|
501
|
+
`result.omadacId` login shapes, and reports `hardware-5.x` / `hardware-6.x`).
|
|
502
|
+
Run `omada-mcp --doctor --json` and submit a
|
|
503
|
+
[Controller Compatibility Report](https://github.com/falehsub/omada-mcp/issues/new?template=controller-report.yml).
|
|
504
|
+
|
|
505
|
+
## Development
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
git clone https://github.com/falehsub/omada-mcp && cd omada-mcp
|
|
509
|
+
python -m unittest discover -s tests -v # 148 tests, no network, no deps
|
|
510
|
+
python -m omada_mcp.cli --help # after: pip install -e .
|
|
511
|
+
uvx --from . omada-mcp --doctor # what an external user does
|
|
512
|
+
|
|
513
|
+
# uv caches the build of a local *path* and does not rebuild when the source
|
|
514
|
+
# changes (`--refresh` does not help). Force a fresh environment while iterating:
|
|
515
|
+
UV_CACHE_DIR=$(mktemp -d) uvx --from . omada-mcp --doctor
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Layout:
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
src/omada_mcp/config.py dotenv discovery, validation, base-URL normalisation, write allowlist
|
|
522
|
+
src/omada_mcp/client.py Web API v2 client: auth, pagination, write gate, snapshots/audit, errors
|
|
523
|
+
src/omada_mcp/_helpers.py shared v5/v6 diagnostics, secret redaction, topology & capability helpers
|
|
524
|
+
src/omada_mcp/tools.py 33 read-only tools + 46 write descriptors, projections, outputSchema
|
|
525
|
+
src/omada_mcp/write_tools.py read-modify-write handlers, CIDR packer, anti-stranding, rollback, 6-step swap
|
|
526
|
+
src/omada_mcp/monitor.py single-session --watch JSON diff streamer & --export-metrics Prometheus exporter
|
|
527
|
+
src/omada_mcp/server.py JSON-RPC stdio server, fail-closed gates, strict arg/bool checks
|
|
528
|
+
src/omada_mcp/doctor.py 6-phase self-test (env, tls, login, site, tools, write) with --json
|
|
529
|
+
Dockerfile minimal non-root container (stdio default, --watch, --export-metrics)
|
|
530
|
+
schemas/doctor.schema.json JSON Schema for --doctor --json
|
|
531
|
+
docs/compatibility.md controller family and cookie compatibility matrix
|
|
532
|
+
docs/write-support.md write-support contract, verified endpoints, and controller traps
|
|
533
|
+
tests/mock_controller.py stateful Web API v2 mock controller with fault injection
|
|
534
|
+
tests/test_bridge.py read-only bridge, M1 diagnostics, M3 watch/metrics & protocol tests
|
|
535
|
+
tests/test_write_support.py write-gate, RMW flows, anti-stranding, rollback & fault-injection tests
|
|
536
|
+
skills/omada-controller/ agent skill: field notes for the Omada API itself
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
The API notes in [`skills/omada-controller/SKILL.md`](skills/omada-controller/SKILL.md)
|
|
540
|
+
document the parts of the controller that are not in any public reference:
|
|
541
|
+
session expiry semantics, CIDR packing limits, dual-pool DHCP bodies, and the
|
|
542
|
+
endpoints that are Admin-only.
|
|
543
|
+
|
|
544
|
+
## Write support
|
|
545
|
+
|
|
546
|
+
Read-only mode remains the default (`OMADA_WRITE_TOOLS` defaults empty and write
|
|
547
|
+
tools are hidden). Opt-in writes follow the contract in
|
|
548
|
+
[`docs/write-support.md`](docs/write-support.md) — explicit `OMADA_WRITE_TOOLS`
|
|
549
|
+
allowlist, `dryRun=true` default, `confirm="<tool>:<target-id>"`, pre-write snapshots +
|
|
550
|
+
`audit.jsonl`, whole-object verification, and anti-stranding checks. Use a **dedicated
|
|
551
|
+
Admin local account** separate from your read bridge and web UI to avoid session
|
|
552
|
+
eviction. Firmware upgrades remain unshipped pending a maintenance-window policy.
|
|
553
|
+
|
|
554
|
+
## Roadmap
|
|
555
|
+
|
|
556
|
+
Read-only inspection, composite diagnostics (`get_switch_ports`, `get_ap_radios`,
|
|
557
|
+
`get_wan_status`, `get_topology`, `diagnose_client`, `diagnose_wan`), `--doctor --json`,
|
|
558
|
+
`network_audit`, the 79-tool control surface (`rollback_from_snapshot` + anti-stranding
|
|
559
|
+
switch/AP/routing/DNS/ACL/client controls), `--watch`/`--export-metrics`, and non-root
|
|
560
|
+
Docker packaging are implemented on `main`. Next items in [ROADMAP.md](ROADMAP.md)
|
|
561
|
+
include MCP Registry `server.json` / PyPI publishing and multi-version live controller captures.
|
|
562
|
+
|
|
563
|
+
## License
|
|
564
|
+
|
|
565
|
+
MIT — see [LICENSE](LICENSE).
|
|
566
|
+
|
|
567
|
+
Not affiliated with TP-Link. Omada is a trademark of TP-Link Technologies.
|
|
568
|
+
This server calls an undocumented internal API; a controller firmware update can
|
|
569
|
+
change it, which is exactly why `--doctor` exists.
|