archer-router-mcp
Provides tools for reading and safely modifying settings on a TP-Link Archer AX53 Wi-Fi router over its local web protocol. Read tools cover router and WAN status, connected clients, DHCP leases and reservations, per-band Wi-Fi, port-forwarding rules, EasyMesh nodes, access control, per-client speed limits, Wi-Fi schedules, and IoT isolation, plus a file-backed client join/leave/rename presence history. Double-gated write tools (requiring an explicit allow-writes switch, per-call confirmation, and protected-MAC safeguards) can add or remove DHCP reservations, toggle Wi-Fi bands, reboot, block/unblock clients, set access-control mode, set per-client speed limits, replace the Wi-Fi schedule, isolate devices, and send Wake-on-LAN.
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., "@archer-router-mcpwho's connected to my Wi-Fi right now?"
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.
archer-router-mcp
A local, browser-free MCP server for the TP-Link Archer AX53 Wi-Fi router. It lets an LLM client (Claude Desktop, Claude Code, or any MCP host) read and — carefully — change your router's settings by speaking the router's own web protocol directly: no headless browser at runtime, no cloud account, nothing leaves your LAN.
It is built read-first, write-gated, and lockout-aware so that an automated client cannot brick the admin account, kick you out of the router UI, or silently rewire your network.
you ──▶ MCP host (LLM) ──▶ archer-router-mcp ──▶ https://<router>/cgi-bin/luci/...
(this server) (your Archer, on the LAN)Why this exists
The Archer web UI is a single-page app that signs and encrypts every request with a per-session scheme. There is no documented local API. This server is a clean-room reimplementation of that scheme, wrapped in 29 MCP tools with typed, normalised output, so an assistant can answer "who's on my Wi-Fi?", "reserve an IP for the hallway camera", or "block that unknown device" without you opening a browser — and without it ever spending a login attempt you didn't ask for.
Related MCP server: keenetic
Supported and tested hardware
Model | TP-Link Archer AX53 v1 (AX3000) |
Web UI |
|
Certification profile |
|
The router's login and request-signing scheme is gated on the device's certification flags. On the SG-hardened profile the UI bundle turns on six flags, two of which change the crypto:
13_rsa_pad_with_pkcs1_oaep— the login signature uses RSA-OAEP, not HMAC.12_replace_hash— every post-login request carries a rolling SHA-256 hash.
The pre-login tool router_status reads device_config?form=config (no
login, no credentials) and reports exactly which flags your unit has:
{ "model": "...", "ui_version": "...",
"certification": ["US FCC", "SG CLS L1 STAGE2"],
"feature_flags": { "2_login_SHA256": true, "13_rsa_pad_with_pkcs1_oaep": true, ... } }Run router_status (or archer-router-mcp --check-auth) first. If your unit
reports a different certification set, the login/sign path may differ and
the server has not been verified against it — treat it as unverified and do an
R0 capture (see Safety model and
deploy/install.md) before enabling writes. Other Archer
models (AX23, AX55, …) share the family protocol but are not tested here;
they may need the same R0 verification step.
Clean-room note
This project is a clean-room MIT reimplementation. The protocol was
recovered by reading and executing the router's own static JavaScript bundle
offline (documented in docs/specs/archer-ax53-auth-flow.md and pinned by the
byte-exact vectors in tests/fixtures/router_auth_vectors.json). The GPL-3.0
project tplinkrouterc6u
(client/sg.py) was consulted only to cross-check observed behaviour; no
code, comments, structure, or other expression was copied. This repository
contains no GPL-licensed code and ships under MIT. See NOTICE.
Features
29 MCP tools (all under the router_ prefix):
Reads (no changes): router status, WAN status, connected clients (merged across the router's five device lists), DHCP leases, DHCP reservations, Wi-Fi per band, port-forward (virtual-server) rules, EasyMesh nodes, access control, per-client speed limits, Wi-Fi schedule, IoT isolation.
Presence history: an honest, file-backed join/leave/rename log the router itself does not keep — a one-shot sampler (
router_poll_presence) plus a query tool (router_client_history), optionally driven by a background sampler inside the server.Writes (double-gated, dry-runnable): add/remove a DHCP reservation, turn a Wi-Fi band on/off, reboot, block/unblock a client, set the access-control mode, set a per-client speed limit, replace the Wi-Fi schedule, isolate/un-isolate a device, and Wake-on-LAN.
See the full tools table below.
Install
Requires Python 3.11+.
git clone https://github.com/ebenezer-isaac/archer-router-mcp
cd archer-router-mcp
pip install .
# or, for development:
pip install -e '.[dev]'This installs the archer-router-mcp console script.
Configuration
All configuration is environment variables. Copy .env.example
to .env (never commit it) and edit. 192.0.2.1 is an RFC 5737 documentation
placeholder — replace it with your router's address.
Router connection (ARCHER_ROUTER_*)
Variable | Default | Meaning |
| — (required) | Router IP or hostname. |
|
| Admin HTTPS port ( |
| — (required) | Router admin password. |
|
| Verify the TLS cert chain. Archer ships a self-signed cert, so this is off by default — pin instead (below). |
| unset | Pin the router cert by SHA-256 fingerprint (hex, no colons). See TLS pinning. |
|
| Per-request timeout. |
|
| Wire envelope: |
Safety switches (ARCHER_ROUTER_*)
Variable | Default | Meaning |
|
| Master write switch. Writes also need |
| empty | Comma/space-separated MAC list of devices that may never be blocked, limited, isolated, un-reserved or woken. Required (non-empty) whenever |
|
| Return the exact request body for every write and send nothing. |
|
| Minimum seconds between accepted reboots (persisted under the state dir). |
|
| Allow evicting another logged-in admin (e.g. the Tether app / your browser). Off by default so a login never kicks you out without intent. Still requires the per-call |
|
| Freeze authentication: no login request is ever sent while true. |
|
| Failed logins tolerated per process before all logins are refused (1–5). |
|
| Where the login breaker, reboot rate-limit, and presence log live. |
Presence history (ARCHER_ROUTER_*)
Variable | Default | Meaning |
|
| Size cap for the JSON-lines presence log (rotates once). |
|
| In-server background sampler interval; |
MCP server (ARCHER_MCP_*)
Variable | Default | Meaning |
|
|
|
|
| Bind address for streamable-HTTP (keep it on loopback). |
|
| Bind port for streamable-HTTP. |
|
| Log level (logs go to stderr). |
Running
# Inspect capabilities — no login:
archer-router-mcp --check-auth
# Perform exactly one login attempt (counts against the breaker):
archer-router-mcp --check-auth --login
# Inspect / reset the persistent login breaker:
archer-router-mcp breaker --show
archer-router-mcp breaker --clear
# List the registered tools:
archer-router-mcp --list-tools
# Run the MCP server:
archer-router-mcp servestdio (for Claude Desktop / Claude Code), an MCP client config entry:
{
"mcpServers": {
"archer-router": {
"command": "archer-router-mcp",
"args": ["serve"],
"env": {
"ARCHER_ROUTER_HOST": "192.0.2.1",
"ARCHER_ROUTER_PASSWORD": "your-router-password"
}
}
}
}streamable-HTTP (for a long-running service), bound to loopback:
ARCHER_MCP_TRANSPORT=streamable-http ARCHER_MCP_HOST=127.0.0.1 ARCHER_MCP_PORT=8770 \
archer-router-mcp serveReach it across machines only over Tailscale/SSH — never expose the port to the
LAN or the internet. For a systemd unit, see deploy/.
Safety model
The Archer firmware is unforgiving about logins and admin sessions. This server is designed around that.
Single admin session
The router allows one admin session at a time. Logging in evicts whatever
else holds it (your browser, the TP-Link Tether app) — but only if the login
sends confirm=true. By default this server refuses rather than evict: a
user conflict surfaces as a SESSION_CONFLICT error and your session is left
alone. To deliberately take over, set ARCHER_ROUTER_FORCE_SESSION_TAKEOVER=true
and pass force_takeover=true on the call (double-gated).
Lockout breaker
On this firmware, a handful of failed logins (≈5–7) locks the admin account for ~2 hours. So the server:
never auto-retries a failed login;
consults a persistent breaker (a lock-protected, schema-validated ledger file under the state dir) before any login request leaves the process — the admission and the attempt-count increment are one atomic, cross-process step, so two processes can't both spend the last attempt;
surfaces the device's own
failureCount/attemptsAllowedin the error;makes a
exceeded max attemptslock a first-class cooldown the breaker honours (it refuses until the ~2 h window elapses);offers
ARCHER_ROUTER_LOGIN_DISABLED=trueas a hard freeze, andarcher-router-mcp breaker --clearas the human reset.
A crashed login leaves the attempt counted (fail closed); recovery is a
deliberate breaker --clear.
Writes are gated twice (and dry-runnable)
A mutating tool does nothing unless both ARCHER_ROUTER_ALLOW_WRITES=true
(env) and confirm_write=true (per call) are set; otherwise it returns a
WRITE_REFUSED envelope and makes no network call. With
ARCHER_ROUTER_DRY_RUN=true, every write instead returns the exact
{path, form, operation, params, body} it would send and sends nothing — use
this to review a body before enabling writes.
Protected MACs
Every MAC in ARCHER_ROUTER_PROTECTED_MACS (your own and the server's devices)
is refused by every MAC-targeting write — block/unblock, speed limit,
isolate/un-isolate, reservation-remove and Wake-on-LAN — with PROTECTED_TARGET,
before any network call. Switching access control to whitelist mode is
refused unless every protected MAC is already whitelisted, so you can't lock
yourself off your own network. The list is required when writes are enabled.
R0-unconfirmed writes
Some write bodies are field-for-field verified from the router's own write
DTOs; others are still inferred from the read shapes and need one live
capture ("R0") to confirm. Inferred writes refuse a live write with
R0_UNCONFIRMED until that capture is recorded — DRY_RUN still shows the
planned body.
Write tool | Body status |
| Verified |
| Verified |
| Verified |
| Verified |
| Verified |
| Verified |
| Inferred → R0 |
| Inferred → R0 |
| Inferred → R0 |
| Inferred → R0 |
| Inferred → R0 |
To unlock the inferred writes, perform the R0 capture (see
deploy/install.md) and record each confirmed tool as a
row in docs/protocol/archer-ax53-verified.md:
| router_set_wifi | 2026-10-05 | write_spf enable-only body confirmed over HTTPS |The file format (a Markdown table with the header
| tool | confirmed_on | note |) is documented in that file's template. A row
whose first column is a known inferred tool name unlocks that tool's live write;
a missing or unreadable file unlocks nothing (fail closed).
No-replay + read-back for writes
Writes are sent non-idempotent: if the reply is lost (a session timeout),
the server does not resend. Instead it re-authenticates, reads the live state
back, and reports the outcome as landed, not_landed, or unknown
(WRITE_OUTCOME_UNKNOWN) — it never doubles a write it isn't sure about. Reboot
and Wake-on-LAN, which have no state to read back, surface the lost reply
directly. The reboot limiter reserves the cooldown slot before sending, so an
unwritable state dir refuses the reboot rather than firing unthrottled.
TLS pinning (TOFU)
Archer ships a self-signed certificate, so chain verification is off by default.
Set ARCHER_ROUTER_TLS_FINGERPRINT_SHA256 to pin the cert by its SHA-256
fingerprint; the pin is enforced on the server's own connection at TLS
handshake. The observed fingerprint is reported by router_status
(tls_fingerprint_observed) — a trust-on-first-use flow: read it once over a
trusted link, then pin it.
Exit codes
The CLI maps failures to shell exit codes so scripts can branch on the kind:
Code | Meaning |
| Success |
| Auth failed (wrong password) / other error |
| Config error |
| Lockout / breaker open / cooldown / login disabled / state unavailable |
| Transport error / TLS pin mismatch |
Every tool itself returns the {success, data, error} envelope and never
raises; credential-bearing fields are redacted from all output.
Tools
R = read-only · W = mutating (double-gated). Inferred write bodies are gated by
R0_UNCONFIRMED until verified (see above).
Session & health
Tool | R/W | What it does |
| R | Model, UI version, certification flags, observed TLS fingerprint, breaker state. No login. |
| R | Capabilities + breaker counters; optional one login. |
| — | Exactly one explicit login. Returns mode/flags, never the token. |
| — | End the current session. |
Reads
Tool | R/W | What it does |
| R | Model, firmware, uptime, WAN/LAN addresses, radios, client count. |
| R | WAN IPv4: connection type, IP/mask/gateway, DNS, uptime. |
| R | Connected clients, merged across the five device lists, one per MAC ( |
| R | Active DHCP leases: mac, ip, name, lease time. |
| R | DHCP address reservations + max-rules limit. |
| R | Per band: enable, SSID, encryption, hidden, channel. The PSK is never returned. |
| R | Virtual-server port-forward rules. |
| R | EasyMesh nodes: name, model, ip, mac, firmware. |
| R | Enabled, mode, black/white lists and devices. |
| R | Per-client bandwidth limits. |
| R | Scheduled Wi-Fi on/off rules. |
| R | IoT isolation state and isolated devices. |
| R | One presence sample → append join/leave/rename events to the local log. Read-only against the router. |
| R | Query the local presence log ( |
AX53 (UI 1.11.0) client-list note.
router_list_clientsmerges five device lists, but four of those callbacks —smart_network?form=game_accelerator,easymesh_network?form=mesh_sclient_list_all,status?form=network_mapandnat?form=client_list— are absent on this firmware (the router answersno such callback); onlydhcps?form=clientis present.router_list_dhcp_leasesis the reliable client source on the AX53.router_get_access_controllikewise readsaccess_control?form=*callbacks that this firmware does not expose under that module name. These were confirmed by reversing the router's own web-UI JS (the four form strings appear in no shipped JS chunk). The block/unblock/mode writes use the verifiedaccess_controlwrite path and are unaffected.
Writes (double-gated)
Tool | R/W | Body | What it does |
| W | Verified | Add a reservation (mac, ip, name); refuses duplicate/conflict/out-of-subnet. |
| W | Verified | Remove a reservation by MAC. |
| W | Verified | Block a client (access-control black list). |
| W | Verified | Unblock a client. |
| W | Verified | Set access-control mode: off / blacklist / whitelist. |
| W | Verified | Reboot the router (rate-limited). |
| W | Inferred | Turn a Wi-Fi band (2g/5g) radio on/off. |
| W | Inferred | Set a per-client kbps limit (0 = unlimited). |
| W | Inferred | Replace the Wi-Fi on/off schedule. |
| W | Inferred | Isolate / un-isolate a device. |
| W | Inferred | Send a Wake-on-LAN magic packet. |
Recipes
New-device alert. Schedule router_poll_presence (or set
ARCHER_ROUTER_PRESENCE_INTERVAL_S), then have the assistant call
router_client_history(since=<last check>) and surface any join events for
MACs it doesn't recognise, via its own notification channel. Nothing is
fabricated — first_seen/last_seen come only from real samples.
Block a device. router_list_clients → confirm the MAC with the user →
router_block_device(mac, confirm_write=true) (with ALLOW_WRITES=true) →
router_get_access_control to verify it's on the black list. Protected MACs are
refused.
Reserve an IP for a camera. router_list_clients or
router_list_dhcp_leases to find the camera's MAC → run
router_add_dhcp_reservation(mac, ip, name) with DRY_RUN=true to review the
exact body → then with ALLOW_WRITES=true and confirm_write=true → confirm
with router_list_dhcp_reservations.
Limitations
Parental controls / website filtering are cloud-only. HomeShield parental controls and web filtering are not in the router's local API, so this server cannot read or change them. A local alternative is a DNS filter (e.g. AdGuard Home on a LAN host); the only router-side change is the DHCP DNS setting.
No usage history on-device. The router keeps no per-client traffic/usage history, so there is none to read. The presence log here is the only history, and it is built from samples this server takes — not back-filled.
No client notifications. The router cannot push notifications to clients; any alerting must come from the MCP client's own channel.
Troubleshooting
user conflict/SESSION_CONFLICT— another admin (your browser or the Tether app) holds the single session. Log out there, or setARCHER_ROUTER_FORCE_SESSION_TAKEOVER=trueand passforce_takeover=trueto evict it deliberately.login failedwith counters — wrong password. The error carries the router'sfailureCountandattemptsAllowed(attempts left before the lock). The breaker also refuses further logins afterMAX_LOGIN_FAILURES; fix the password, thenarcher-router-mcp breaker --clear.exceeded max attempts/LOCKED_OUT— the account is locked for ~2 hours. Wait it out; the breaker honours the cooldown. Do not keep trying.timeout— a session expired or a response could not be decrypted. Reads re-login once automatically; writes do not resend (they read back the state). Persistent timeouts usually mean a wrongARCHER_ROUTER_ENVELOPEfor your transport.Envelope selection —
autosends a plain body over HTTPS and an AES+signed body over HTTP (mirroring the UI). If logins succeed over one transport but not the other, pinARCHER_ROUTER_ENVELOPEtohttps-plainorhttp-encryptedto match what an R0 capture showed your unit accepts.
Prior art and credits
tplinkrouterc6u(GPL-3.0) — used as a cross-check reference only (itsclient/sg.pySG login flow), never copied. SeeNOTICE.tplinkctl— prior art for TP-Link web-UI control.taroru5358/tplink-router-mcp— an earlier TP-Link router MCP server.sharozdawa/tplink-archer-mcp— Archer MCP endpoint list.
Development
pip install -e '.[dev]'
python scripts/gate.py # ruff + pytest(+coverage) + secret-scan + stub-scan + --list-toolsThe gate must pass (and the secret scan must be clean) before every commit. CI
runs it on Ubuntu and Windows across Python 3.11–3.13. The device-agnostic
core/ directory is canonical in a sibling project and copied verbatim here —
see CONTRIBUTING.md before touching it.
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Remote streamable-HTTP MCP server running on a single Cloudflare Worker. Your assistant gets live Airbnb, Amazon, Booking.com, Google Flights, Maps and Reddit data, social search on X, Instagram and TikTok, the Meta Ad Library, and image/video generation without any keys. Connect your own accounts to let it send WhatsApp or Telegram messages, work an IMAP inbox, manage Meta Ads campaigns and publish to X and LinkedIn. OAuth 2.1 with PKCE; stored credentials are AES-256-GCM encrypted.
AXL MCP lets AI assistants create and manage landing pages, courses, email campaigns, CRM records, and marketing workflows inside AXL. Built for growing expert businesses, it turns chat requests into real work across sales, marketing, and course delivery. An AXL account is required. Sign in securely with OAuth 2.1. Website: https://axl.tech/developers/mcp . Setup guide: https://docs.axl.tech/mcp . Watch AXL in 77 seconds: pages, courses, CRM, and automation. Product overview: https://www.youtube.com/watch?v=jlhR9CafIww
Zero-setup MCP gateway securely connecting AI to your tools with authentication and workflows
The OpenRouter for tools. One MCP connection gives any AI agent 254 hosted tools, pay per call.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to manage TP-Link routers by listing clients, checking status, controlling Wi-Fi, and rebooting via natural language.2MIT
- FlicenseBqualityAmaintenanceEnables AI agents to manage Keenetic routers through the same RCI API used by the router's web interface, working directly over the local network without cloud involvement. It supports reading device statuses and executing configuration changes, with confirm, dry-run, and destructive-action safeguards.23-
- AlicenseAqualityCmaintenanceLets an AI assistant inspect and adjust TP-Link Omada WiFi networks by searching, describing, and calling any of roughly 1,650 Omada Open API operations through nine tools. It is read-only by default, refusing every non-GET request unless writes are explicitly unlocked, and even then returning a dry-run preview until a change is confirmed.94MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to interact with Xiaomi/Redmi routers, providing read-only queries and management capabilities such as device listing, port forwarding, DHCP reservations, and reboot control.MIT