Skip to main content
Glama
ebenezer-isaac

archer-router-mcp

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

AX53v1_1.11.0

Certification profile

["US FCC", "SG CLS L1 STAGE2"] (the SG-hardened variant)

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

ARCHER_ROUTER_HOST

— (required)

Router IP or hostname.

ARCHER_ROUTER_PORT

443

Admin HTTPS port (80 if you use HTTP).

ARCHER_ROUTER_PASSWORD

— (required)

Router admin password.

ARCHER_ROUTER_VERIFY_TLS

false

Verify the TLS cert chain. Archer ships a self-signed cert, so this is off by default — pin instead (below).

ARCHER_ROUTER_TLS_FINGERPRINT_SHA256

unset

Pin the router cert by SHA-256 fingerprint (hex, no colons). See TLS pinning.

ARCHER_ROUTER_TIMEOUT_SECONDS

10

Per-request timeout.

ARCHER_ROUTER_ENVELOPE

auto

Wire envelope: auto (plain over HTTPS, AES+sign over HTTP), https-plain, or http-encrypted. Leave auto unless an R0 capture says otherwise.

Safety switches (ARCHER_ROUTER_*)

Variable

Default

Meaning

ARCHER_ROUTER_ALLOW_WRITES

false

Master write switch. Writes also need confirm_write=true per call.

ARCHER_ROUTER_PROTECTED_MACS

empty

Comma/space-separated MAC list of devices that may never be blocked, limited, isolated, un-reserved or woken. Required (non-empty) whenever ALLOW_WRITES=true — the server refuses to start otherwise.

ARCHER_ROUTER_DRY_RUN

false

Return the exact request body for every write and send nothing.

ARCHER_ROUTER_REBOOT_MIN_INTERVAL_S

900

Minimum seconds between accepted reboots (persisted under the state dir).

ARCHER_ROUTER_FORCE_SESSION_TAKEOVER

false

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 force_takeover argument too.

ARCHER_ROUTER_LOGIN_DISABLED

false

Freeze authentication: no login request is ever sent while true.

ARCHER_ROUTER_MAX_LOGIN_FAILURES

1

Failed logins tolerated per process before all logins are refused (1–5).

ARCHER_ROUTER_STATE_DIR

~/.local/state/archer-router-mcp

Where the login breaker, reboot rate-limit, and presence log live.

Presence history (ARCHER_ROUTER_*)

Variable

Default

Meaning

ARCHER_ROUTER_PRESENCE_MAX_MB

20

Size cap for the JSON-lines presence log (rotates once).

ARCHER_ROUTER_PRESENCE_INTERVAL_S

0

In-server background sampler interval; 0 disables it (call router_poll_presence on your own schedule instead).

MCP server (ARCHER_MCP_*)

Variable

Default

Meaning

ARCHER_MCP_TRANSPORT

stdio

stdio or streamable-http.

ARCHER_MCP_HOST

127.0.0.1

Bind address for streamable-HTTP (keep it on loopback).

ARCHER_MCP_PORT

8770

Bind port for streamable-HTTP.

ARCHER_MCP_LOG_LEVEL

INFO

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 serve

stdio (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 serve

Reach 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 / attemptsAllowed in the error;

  • makes a exceeded max attempts lock a first-class cooldown the breaker honours (it refuses until the ~2 h window elapses);

  • offers ARCHER_ROUTER_LOGIN_DISABLED=true as a hard freeze, and archer-router-mcp breaker --clear as 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

router_add_dhcp_reservation

Verified

router_remove_dhcp_reservation

Verified

router_block_device

Verified

router_unblock_device

Verified

router_set_access_mode

Verified

router_reboot

Verified

router_set_wifi

Inferred → R0

router_set_client_speed_limit

Inferred → R0

router_set_wifi_schedule

Inferred → R0

router_isolate_device

Inferred → R0

router_wol

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

0

Success

1

Auth failed (wrong password) / other error

2

Config error

3

Lockout / breaker open / cooldown / login disabled / state unavailable

4

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

router_status

R

Model, UI version, certification flags, observed TLS fingerprint, breaker state. No login.

router_check_auth

R

Capabilities + breaker counters; optional one login.

router_login

—

Exactly one explicit login. Returns mode/flags, never the token.

router_logout

—

End the current session.

Reads

Tool

R/W

What it does

router_get_status

R

Model, firmware, uptime, WAN/LAN addresses, radios, client count.

router_get_wan

R

WAN IPv4: connection type, IP/mask/gateway, DNS, uptime.

router_list_clients

R

Connected clients, merged across the five device lists, one per MAC (include_offline optional).

router_list_dhcp_leases

R

Active DHCP leases: mac, ip, name, lease time.

router_list_dhcp_reservations

R

DHCP address reservations + max-rules limit.

router_get_wifi

R

Per band: enable, SSID, encryption, hidden, channel. The PSK is never returned.

router_list_port_forwards

R

Virtual-server port-forward rules.

router_list_mesh_nodes

R

EasyMesh nodes: name, model, ip, mac, firmware.

router_get_access_control

R

Enabled, mode, black/white lists and devices.

router_get_speed_limits

R

Per-client bandwidth limits.

router_get_wifi_schedule

R

Scheduled Wi-Fi on/off rules.

router_get_iot_isolation

R

IoT isolation state and isolated devices.

router_poll_presence

R

One presence sample → append join/leave/rename events to the local log. Read-only against the router.

router_client_history

R

Query the local presence log (since, mac, limit). No router I/O.

AX53 (UI 1.11.0) client-list note. router_list_clients merges five device lists, but four of those callbacks — smart_network?form=game_accelerator, easymesh_network?form=mesh_sclient_list_all, status?form=network_map and nat?form=client_list — are absent on this firmware (the router answers no such callback); only dhcps?form=client is present. router_list_dhcp_leases is the reliable client source on the AX53. router_get_access_control likewise reads access_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 verified access_control write path and are unaffected.

Writes (double-gated)

Tool

R/W

Body

What it does

router_add_dhcp_reservation

W

Verified

Add a reservation (mac, ip, name); refuses duplicate/conflict/out-of-subnet.

router_remove_dhcp_reservation

W

Verified

Remove a reservation by MAC.

router_block_device

W

Verified

Block a client (access-control black list).

router_unblock_device

W

Verified

Unblock a client.

router_set_access_mode

W

Verified

Set access-control mode: off / blacklist / whitelist.

router_reboot

W

Verified

Reboot the router (rate-limited).

router_set_wifi

W

Inferred

Turn a Wi-Fi band (2g/5g) radio on/off.

router_set_client_speed_limit

W

Inferred

Set a per-client kbps limit (0 = unlimited).

router_set_wifi_schedule

W

Inferred

Replace the Wi-Fi on/off schedule.

router_isolate_device

W

Inferred

Isolate / un-isolate a device.

router_wol

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 set ARCHER_ROUTER_FORCE_SESSION_TAKEOVER=true and pass force_takeover=true to evict it deliberately.

  • login failed with counters — wrong password. The error carries the router's failureCount and attemptsAllowed (attempts left before the lock). The breaker also refuses further logins after MAX_LOGIN_FAILURES; fix the password, then archer-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 wrong ARCHER_ROUTER_ENVELOPE for your transport.

  • Envelope selection — auto sends 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, pin ARCHER_ROUTER_ENVELOPE to https-plain or http-encrypted to match what an R0 capture showed your unit accepts.

Prior art and credits

Development

pip install -e '.[dev]'
python scripts/gate.py   # ruff + pytest(+coverage) + secret-scan + stub-scan + --list-tools

The 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

MIT. See LICENSE and NOTICE.

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

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to manage TP-Link routers by listing clients, checking status, controlling Wi-Fi, and rebooting via natural language.
    2
    MIT
  • F
    license
    B
    quality
    A
    maintenance
    Enables 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
    -
  • A
    license
    A
    quality
    C
    maintenance
    Lets 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.
    9
    4
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables 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