huawei-ont-mcp
by Erenn0989
README.md
# huawei-ont-mcp
An [MCP](https://modelcontextprotocol.io) server for reading status off a **Huawei GPON ONT** (fiber modem) — connected devices and WAN status — in plain language.
Inspired by [Karya Boyraz's `keenetic-mcp`](https://github.com/karyaboyraz/keenetic-mcp), but for a different device family. Reverse-engineered from a **TTNET2-branded HG8245X6** (Türk Telekom fiber modem) web UI's own JavaScript, by capturing the real network requests a logged-in browser session makes (via Chrome DevTools MCP) and matching them against the router's own `RndSecurityFormat.js`/`safelogin.js` source.
## Login (solved 2026-07-16)
Unlike Keenetic's documented, stateless RCI auth flow, this ONT's `/login.cgi` flow (token from `/asp/GetRandCount.asp` + `base64(password)`) looked simple on paper but rejected every faithful reimplementation for a while — headers matched byte-for-byte against a real successful browser request, still failed. The missing piece: **the one-time token is bound to the specific TCP connection it was issued on.** `GetRandCount.asp` and the `login.cgi` submission have to be sent on the exact same socket (`http.client.HTTPConnection` kept open across both calls) — separate `requests.post()` calls, even inside the same `requests.Session()`, open a new connection often enough that the router silently rejects the login. Confirmed by capturing a real browser login with Chrome DevTools MCP (a genuine `<form>` navigation, not an XHR — that's also why it carries navigation-only headers like `Upgrade-Insecure-Requests: 1` and a full `Accept: text/html,...` string) and then replicating it on one persistent connection: worked on the first try.
The web UI still enforces a 5-strikes lockout on failed logins — but per the device's own `login.asp` source (`errloginlockNum`/`LockLeftTime`, and the Turkish string `"Fazla deneme, lütfen N sn bekleyin."`), it's a **temporary, seconds-scale cooldown on the admin panel only**, not a device reset and not an internet outage. `server.py`'s `_login()` deliberately does not retry in a loop, out of respect for that lockout — a failed login raises immediately.
No write tools yet — this is still read-only, just no longer login-blocked.
## Tools
| Tool | Type | What it does |
|---|---|---|
| `list_devices()` | read | LAN devices (DHCP + static): IP, MAC, hostname, ETH/WIFI, Online/Offline |
| `wan_status()` | read | Each WAN connection (INTERNET/VOICE/IPTV/MANAGEMENT/MOBILE BACKUP): connected?, public IPv4, PPPoE username |
## Install
Requires Python 3.9+.
```bash
cd Agents/mcp-servers/huawei-ont-mcp
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
```
## Setting the admin credential
The server logs in automatically on first use and re-logs-in automatically if the session expires — no manual browser login or cookie copying needed anymore. It just needs the admin password, stored as `base64(password)` (exactly the form the router's own login form sends, so the raw plaintext never has to pass through this script either):
```bash
security add-generic-password -a "admin" -s "huawei-ont-password-b64" -w "<base64 of your admin password>" -U
```
(never in a file, never in shell history where it lingers). On non-macOS, set `HUAWEI_ONT_PASSWORD_B64` as a fallback env var instead.
## Run
```bash
.venv/bin/python server.py
```
Starts a streamable-HTTP MCP server on `http://0.0.0.0:8906/mcp` (configurable via `HUAWEI_ONT_HOST`/`HUAWEI_ONT_PORT`).
## Connecting an MCP client
### Claude Code
```json
{
"mcpServers": {
"huawei-ont": {
"type": "http",
"url": "http://127.0.0.1:8906/mcp"
}
}
}
```
Restart Claude Code after adding (MCP config isn't hot-reloaded). Ask: *"list devices on my router"* or *"is my internet connected?"*.
## How the login actually works
TTNET2 config mode uses the scheme in `login.asp`'s `Submit()`, confirmed by capturing a real successful browser login:
1. `POST /asp/GetRandCount.asp` (no body) → response text is a one-time token (strip a leading BOM byte).
2. On the **same TCP connection**: `POST /login.cgi` with `UserName=admin&PassWord=<base64(password)>&Language=turkish&x.X_HW_Token=<token>`, header `Cookie: Cookie=body:Language:turkish:id=-1` (**only** on this second request, not the token fetch).
3. Success sets `Set-Cookie: Cookie=sid=<hex>:Language:turkish:id=1`.
Step 2's connection reuse is the part that isn't optional -- see `_login()` in `server.py`.
## Security notes
- The server has **read access** to router status. No write tools exist yet.
- Only `base64(admin password)` lives in Keychain; the session cookie itself is cached in memory for the running process only, never written to disk.
- Nothing is sent anywhere except your own router.
- This is not affiliated with or endorsed by Huawei or Türk Telekom. Use at your own risk.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues