tplink-easysmart-mcp
Allows interaction with TP-Link Easy Smart managed PoE switches, providing tools to read system, port, statistics, PoE, and VLAN status, and to perform gated writes such as setting PoE/port state and power-cycling PoE devices.
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., "@tplink-easysmart-mcp cam_hall is frozen — power-cycle its PoE port"
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.
tplink-easysmart-mcp
A local, browser-free MCP server for the
TP-Link Easy Smart family of managed PoE switches (developed against the
TL-SG1016PE). It speaks the switch's HTML/*.cgi web protocol directly — no
headless browser at runtime — so an MCP client (Claude, or any MCP host) can read
port, statistics and PoE status and power-cycle a wedged PoE camera without
anyone opening the switch's web UI.
It is read-first, write-gated and lockout-aware: it never retries a failed login, gates every write behind two independent keys, restores power on every failure path, and refuses to act when the switch is in factory-reset mode.
Status: 0.1.0. The authenticated client, the typed read tools and the write / PoE power-cycle tools are implemented and covered by an offline test suite against synthetic fixtures. Live on-device verification (phase S4) is the last step before a tagged release; see
docs/specs/.
Why
The Easy Smart line has no JSON API. Each page is HTML with an inline
var NAME = VALUE; state block, and changes are made with *.cgi form posts over
a cleartext HTTP login on port 80 — there is no TLS on the device. This server
re-implements that protocol as a set of typed, safety-gated MCP tools, so an agent
can answer "is the gate camera drawing power?" or "the hallway camera is frozen,
power-cycle it" over the LAN.
Related MCP server: mcp-omada
Supported hardware
Hardware | Status |
TL-SG1016PE V1 / V3 (16-port, PoE+ on 1–8) | Primary target. Protocol logic matches the MIT reference client, which lists V1 and V3 as fully supported including PoE. Live capture (S0) pending. |
Other Easy Smart models (SG108E, SG105E, SG1016PE siblings, SG1428PE, …) | Likely to work — they share the page names, inline-variable layout and PoE encodings — but each needs an S0 live capture to confirm array padding, the auto power-limit field and the VLAN page names before it is called "tested". |
Newer encrypted-login firmware (salted-XOR password + | Refused, fail-closed. The login probe detects the encrypted variant and raises |
The switch's hardware revision and firmware are only readable after login, so for
an unknown unit run tplink-easysmart-mcp check-auth --login once and compare.
Features
Thirteen tools, all prefixed switch_, all returning the same
{success, data, error} envelope and never raising:
System info — model, hardware revision, firmware, MAC, IP, netmask, gateway.
Ports — per-port admin state, link, speed, flow control, LAG, name, protected flag.
Statistics — tx/rx good/bad packet counters, with derived
error_ports.PoE — budget totals and per-port state/priority/limit/class/W/mA/V/status, with derived
fault_portsandunpowered_enabled_ports.VLANs — the 802.1Q table and per-port PVIDs (or
NOT_SUPPORTED).Resolve port — preview how a name or number maps to a physical port.
Writes (gated):
switch_set_poe,switch_set_port, andswitch_poe_cycle(power-cycle a PoE camera off → wait → on → confirm power).
Install
Requires Python 3.11+.
pip install git+https://github.com/ebenezer-isaac/tplink-easysmart-mcp
# or, from a clone:
pip install .Register it with your MCP client (stdio transport) — for example in a Claude Desktop / Claude Code config:
{
"mcpServers": {
"tplink-switch": {
"command": "tplink-easysmart-mcp",
"env": {
"EASYSMART_HOST": "192.0.2.10",
"EASYSMART_PASSWORD": "your-switch-password",
"EASYSMART_PORT_MAP": "cam_hall=1;cam_gate=2"
}
}
}
}Leave EASYSMART_ALLOW_WRITES unset (the default) to run read-only. See the next
section for every variable.
Configuration
Copy .env.example and fill it in, or pass the variables through
your MCP client's env. The switch address, password and port map live only in
that file — never in the repo.
Variable | Default | Meaning |
| — (required) | Switch IP or hostname. Scheme is always |
|
| HTTP port. |
|
| 1–16 printable-ASCII chars. |
| — (required) | 6–16 chars, no spaces (the switch's own limit). Validated at startup so a bad value never reaches the device and trips its lockout. |
|
| Per-request timeout (1–120). The web server is single-threaded; keep it short. |
|
| Master write switch. Half of the write gate. |
|
| A write reads the live page and returns the exact form it would send, sending nothing. |
|
| Freezes authentication: no login attempt is ever made. |
|
| Where the breaker, cooldown and cycle-reservation files live. |
|
| PoE-capable ports (ranges and commas, e.g. |
| — | Ports no write may ever touch: the uplink and the server's own port. Required and non-empty whenever |
| — | Friendly names: |
|
| Minimum gap between a failed/unconfirmed login and the next attempt. |
|
| Bounds for a power-cycle's |
|
| How long to wait for |
|
| If true, read tools also log out. Use it when a human works in the switch web UI often (see session eviction below). |
|
|
|
|
| HTTP transport bind. Keep it on loopback; the server has no auth of its own. |
VERIFY_TLS / TLS_FINGERPRINT_SHA256 are rejected if set — the device is
HTTP-only, so they could do nothing but mislead.
Running
tplink-easysmart-mcp # serve over EASYSMART_MCP_TRANSPORT
tplink-easysmart-mcp --list-tools # print the 13 tool names; no device I/O
tplink-easysmart-mcp check-auth # probe only (one GET, no login)
tplink-easysmart-mcp check-auth --login # exactly one login, confirm, then log out
tplink-easysmart-mcp breaker --show # print the persistent breaker state
tplink-easysmart-mcp breaker --clear # clear the breaker after fixing the causeSafety model
This device makes safety the whole point of the project. See
SECURITY.md for the full threat model.
Cleartext login → LAN host only. The password crosses the wire in cleartext on every login. Run this server on the wired LAN host that reaches the switch — never across WiFi or a Tailscale/SSH hop. The HTTP transport binds to loopback; reach the MCP over Tailscale/SSH, but keep the switch traffic local.
Writes are double-gated. A mutating tool acts only when
EASYSMART_ALLOW_WRITES=trueon the server andconfirm_write=trueon the call. Miss either and the tool returns a refusal before any network call.Dry-run.
EASYSMART_DRY_RUN=truemakes a write read the live page and return the exact bytes it would send, sending nothing.Read-modify-write + verify. Every write reads the current page, changes only the one field, re-sends the rest verbatim, then re-reads to confirm — so a PoE on/off never silently resets priority or limit (
WRITE_VERIFY_FAILEDif it did).Protected ports.
EASYSMART_PROTECTED_PORTS(required when writes are on) names the uplink and the server's own port; no write or power-cycle touches them.Single-session eviction. The switch is effectively single-session: logging in evicts a human's web-UI session and vice versa. The server logs in lazily, re-logs in at most once per read, and logs out after every write.
Restored-account / factory-reset refusal. In factory-reset mode a login POST would set the admin password. The server detects the "New Password / Confirm" page, refuses to POST, and trips the breaker.
Lockout breaker + cooldown. A failed login (bad credentials, locked account, restored mode) trips a persistent breaker that blocks further logins until a human runs
breaker --clear; session-busy / timeout responses set a cooldown instead. The login is never retried automatically.One cycle per port, crash-visible. Only one power-cycle runs per port at a time, reserved through a cross-process state file so a crashed cycle stays visible. Once the port is off, every failure path still tries to turn it back on and reports both outcomes; power is restored on every path.
Exit codes (for
check-auth/breakerin scripts):0success ·1auth failed ·2config error ·3breaker open / cooldown / login disabled / state unavailable ·4transport error.
Tools
A port argument is an int or a case-insensitive EASYSMART_PORT_MAP name
(switch_resolve_port previews the mapping; resolution is type-stable, so 3 and
"3" are always the same physical port).
Tool | R/W | What it does / refuses |
| R | Healthcheck: config summary, one credential-free reachability probe, breaker/cooldown/cycle state. Never logs in. |
| R | Probe only (one GET): session model, auth variant, login mode. |
| R | Exactly one login, confirm, then logout. Returns hw/fw; never cookies. |
| R | End any lingering session (no-op if not logged in). |
| R | Model, hw revision, firmware, MAC, IP, netmask, gateway, session model. |
| R | Per-port state, link, speed, flow control, LAG, name, protected. |
| R | tx/rx good/bad counters for one port or all, plus |
| R | Budget totals + per-port state/priority/limit/class/W/mA/V/status, plus |
| R | 802.1Q table + per-port PVIDs, or |
| R | Resolve a name/number to |
| W | Enable/disable PoE on one port (RMW; verifies priority/limit survive). Refuses |
| W | Enable/disable one port's link (RMW; verifies speed/flow-control survive). Refuses |
| W | Power-cycle a PoE camera: off → wait |
Recipes
Power-cycle a wedged camera by name. With writes enabled and the camera in
EASYSMART_PORT_MAP:
switch_poe_cycle(port_or_name="cam_hall", off_seconds=10, confirm_write=true)It turns PoE off, waits, turns it back on, and polls until the camera draws power
again — restoring power and reporting the outcome even if a step fails. Dry-run it
first (EASYSMART_DRY_RUN=true) to see the exact off/on forms.
Check the PoE budget.
switch_get_poe()Returns the system budget (limit / consumption / remaining) and every port's
draw, plus fault_ports (overload/short/voltage/thermal) and
unpowered_enabled_ports (PoE enabled but no power — often a dead or unplugged PD).
Limitations
Live verification pending. The protocol is implemented from the MIT reference client and synthetic fixtures; the on-device S0 capture and S4 cycle test have not been run. Items a live unit must confirm are tagged
# S0: confirminswitch/constants.py(the auto power-limit wire value, the per-port array padding, the disabled-PoE read value, whetherLogout.htmends the session, and whether the 802.1Q pages exist under these names).VLAN writes are not exposed. The VLAN read layout is inferred from sibling models; VLAN writes have two incompatible firmware shapes and are intentionally not implemented.
Cable diagnostics are unknown. No reference client implements the cable-test page; it is not a tool.
Hardware revision needs a login. It is only on
SystemInfoRpm.htm.
Troubleshooting
AUTH_FAILED/LOCKED_OUTand the breaker is now open. A login was rejected (errType 1/2). The server will not retry. Fix the credentials, thentplink-easysmart-mcp breaker --clear. Do not loop on login — the switch locks the admin account.LOGIN_NOT_ACCEPTED. The login POST returned errType 0 but the confirming GET came back as the login page (a known silent-ignore on cookie firmware). The credentials may be fine; a cooldown is recorded rather than tripping the breaker.SESSION_BUSY. Another process on your IP (usually a browser tab on the switch) already holds the session. Close it and retry.RESTORED_ACCOUNT_MODE. The switch is in factory-reset "set a new password" mode. Set the admin password in the web UI first; the server will not do it.AUTH_VARIANT_UNSUPPORTED. The firmware uses the encrypted login variant, which is not yet supported.OUTCOME_UNKNOWN. A write's connection was reset. The server re-reads rather than resending; checkswitch_get_poe/switch_get_portsfor the real state.CYCLE_IN_PROGRESSthat never clears. A previous cycle crashed and left a reservation. A marker older than its window is reported stale and overwritten; if it persists, remove the cycle marker underEASYSMART_STATE_DIR.errType meanings: 0 ok/silently-ignored · 1 bad credentials · 2 user blocked · 3/4 session slots full · 5 session timeout · 6 restored-account mode.
Prior art and credits
vmakeev/hass_tplink_easy_smart(MIT, © 2022 Vladimir Makeev) — the request flow and the inline-variable / PoE / port parsing logic are ported from this Home Assistant integration (logic re-implemented, no files copied). SeeNOTICE.t0mer/SwitchDeck(Apache-2.0) — read for SG108E page/field names and connection-reset behaviour.pklaus/smrt— reference for the Easy Smart web protocol and page conventions.
Development
python -m venv .venv && .venv/Scripts/python.exe -m pip install -e ".[dev]" # Windows
# or: python -m venv .venv && source .venv/bin/activate && pip install -e ".[dev]"
python scripts/gate.py # ruff + pytest (coverage) + secret scan + stub scan + --list-toolsscripts/gate.py must print PASS before any commit. See
CONTRIBUTING.md for the workflow, the breaker protocol, and
the core-identity rule (core/ is canonical in vigi-nvr-mcp and copied verbatim).
Protocol notes live in
docs/protocol/easysmart-switch.md; the full
specs and endpoint map are in docs/specs/.
License
Available Tools
13 toolsswitch_check_authA
Probe the switch without logging in: report the session model, auth variant and login mode plus breaker/cooldown state. Makes one GET, no POST.
Inputs: no named parameters.
switch_get_poeA
Read-only: PoE budget totals and per-port state, priority, power limit, PD class ("--" when none), watts, milliamps, volts and status, plus derived fault_ports and unpowered_enabled_ports. Does not mutate.
Inputs: no named parameters.
switch_get_portsA
Read-only: per-port admin state, link, configured/actual speed, flow control, LAG id, friendly name and whether the port is protected. Set only_linked=true to list only ports with a live link. Does not mutate.
Inputs: only_linked.
switch_get_port_statsA
Read-only: tx/rx good/bad packet counters. Pass a port number or a PORT_MAP name for one port, or omit it for all ports plus error_ports (any port with rx_bad+tx_bad > 0). Does not mutate.
Inputs: port.
switch_get_system_infoA
Read-only: model, hardware revision, firmware, MAC, IP, netmask, gateway and the session model. Logs in lazily; does not mutate anything.
Inputs: no named parameters.
switch_get_vlansA
Read-only: the 802.1Q VLAN table and per-port PVIDs. Returns NOT_SUPPORTED if this firmware does not serve a VLAN page. Does not mutate.
Inputs: no named parameters.
switch_loginA
Perform exactly one login, confirm it with a data GET, then log out. Returns the session model and hardware/firmware. Never returns cookies. Refuses (no POST) in restored-account or encrypted-variant modes, and the breaker blocks it after a prior failure until a human clears it.
Inputs: no named parameters.
switch_logoutA
End any lingering session with GET /Logout.htm. A no-op (no network) if not currently logged in. Does not mutate switch settings.
Inputs: no named parameters.
switch_poe_cycleA
MUTATES: power-cycle one PoE camera (off, wait off_seconds, on, wait for power). Resolve a PORT_MAP camera name or port number. Refuses non-PoE (NOT_POE_PORT), protected (PROTECTED_PORT), an already-off port (ALREADY_OFF) and a second concurrent cycle (CYCLE_IN_PROGRESS). If it cannot restore power it reports CYCLE_INCOMPLETE/POWER_NOT_RESTORED naming the port that may be UNPOWERED. Requires both write gates; honours EASYSMART_DRY_RUN (returns both planned forms, sends nothing).
Inputs: port_or_name (required), off_seconds, confirm_write.
switch_resolve_portA
Read-only helper: resolve a port number or PORT_MAP name to {port, name, is_poe, protected, max_port}. Resolution is type-stable: an integer or a string of digits is ALWAYS a port number (never a name), so 3 and "3" mean the same physical port and a numeric name can never shadow one. A non-digit string is a case-insensitive name. UNKNOWN_PORT lists the known names; AMBIGUOUS_NAME if a name maps to more than one port; an out-of-range number is INVALID_PORT. Does not mutate.
Inputs: name_or_port (required).
switch_set_poeA
MUTATES PoE on one port. Reads the live PoE page, re-sends the port's current priority and power limit, and changes only on/off, then verifies (WRITE_VERIFY_FAILED if priority/limit were clobbered). Refuses a non-PoE port (NOT_POE_PORT) or a protected port (PROTECTED_PORT). Requires EASYSMART_ALLOW_WRITES=true and confirm_write=true; otherwise no network call. Honours EASYSMART_DRY_RUN (returns the exact form, sends nothing).
Inputs: port (required), enabled (required), confirm_write.
switch_set_portA
MUTATES one port's admin state (enable/disable the link). Reads the live page, re-sends the port's current speed and flow control, changes only the state, then verifies. Refuses a protected port (PROTECTED_PORT). Requires both write gates; otherwise no network call. Honours EASYSMART_DRY_RUN.
Inputs: port (required), enabled (required), confirm_write.
switch_statusA
Healthcheck: policy, one credential-free reachability GET, session-model guess and breaker state. Never logs in, never POSTs, never returns the password or cookies.
Inputs: no named parameters.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 13 tools
Most tools target distinct resources or actions, but switch_status and switch_check_auth both perform a credential-free probe and report session/auth/breaker state, creating one confusing pair. switch_login and switch_get_system_info also overlap in session handling, though their descriptions clarify the difference.
All tools use snake_case with a consistent switch_ prefix. Verbs like get_, set_, resolve_, check_, login, and logout are conventional and readable throughout, with no mixed conventions.
The 13 tools are well-scoped for a switch management server. They cover status, authentication, read operations, safe writes, a helper, and a specialized PoE cycle without excessive bloat.
The surface covers read-heavy diagnostics and limited safe mutations, but it lacks VLAN configuration, port speed/flow-control changes, LAG management, and a way to clear the auth breaker. These are notable gaps for full switch management, though core read and safe write workflows exist.
Maintenance
Related MCP Connectors
REST-to-MCP for UK hospitality. Safety proxy: circuit-breakers, rate limits, whitelists. Apache 2.0.
Tailscale device, route, DNS, key, user, and ACL management over MCP and CLI.
Scans remote MCP servers for protocol, security, and TLS issues; exposes scan tools via MCP.
Pingstack monitors cron jobs and scheduled tasks with heartbeat checks, and checks websites, TCP ports, DNS records, SSL certificates and domain expiry. This MCP server lets your agent create, update, pause and delete checks, build dashboards and publish them as status pages, manage notification groups, and read incidents with their causes, uptime, run history and latency. Pause, delete and unpublish stay off until you turn them on. Browser sign-in, no API key needed.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceMCP server for managing TP-Link and Mercury Easy Smart switches via their web interface, enabling read-only status checks and VLAN/trunk configuration with dry-run support.1-
- AlicenseAqualityCmaintenanceRead-only MCP server for TP-Link Omada SDN controllers, enabling querying controller, site, device, and WiFi state.8Apache 2.0
- FlicenseNot gradedqualityDmaintenanceMCP server for Topaz switches: searches documentation, performs network diagnostics (ping, traceroute, port scanning, etc.), calculates subnets, and generates VLAN/base configurations. Also includes a web UI for use without an MCP client.1-
- AlicenseCqualityBmaintenanceMCP server that lets AI assistants monitor and manage TP-Link Omada controllers. It provides analytics and read-only queries plus write operations such as rebooting devices, blocking clients, and changing network or firewall settings.84MIT