| Parameter | Type | Description |
|---|---|---|
| refresh | bool | Force a live refresh (default false). |
tapo-mcp-server
A local-first MCP server blueprint that exposes your Tapo smart lights to MCP-compatible clients. It keeps device credentials and control on the local network, and ships a clean separation between MCP tools, device adapters, an effects engine, and configuration.
This site documents a proposed, reference implementation. It is not a claim of live device connectivity or of support for every Tapo model. Exact library and API compatibility should be verified against your own devices and firmware.
Overview
Local-first
The server runs on your machine and talks to lights over your LAN. Credentials and control never leave the local network.
MCP-native
Typed tools are exposed over stdio transport, so any MCP-compatible client can discover and control the lights.
Blueprint
A clean, production-minded reference layout you can adapt, with clear seams between tools, devices, effects, and config.
The goal is a small, solid server you can drop into a local codebase or run directly on a Windows machine. It targets five lights: three LED strips and two bulbs. Everything below is the proposed design - treat it as a starting point, not a finished product.
Hardware
The blueprint is scoped to exactly five devices: three L900 LED strips and two L530 bulbs, at the addresses below.
Inventory
| Device | Kind | Host / IP | Role |
|---|---|---|---|
| L900-3 | Strip | 192.168.1.139 | LED strip |
| L900 | Strip | 192.168.1.179 | LED strip |
| L900-2 | Strip | 192.168.1.82 | LED strip |
| L530-2 | Bulb | 192.168.1.129 | Smart bulb |
| L530 | Bulb | 192.168.1.97 | Smart bulb |
Topology
MCP client
Claude, Cursor, etc.
tapo-mcp-server
Python · stdio transport
Tapo devices
3 strips + 2 bulbs
The client talks to the server over stdio (local JSON-RPC). The server talks to the lights over the LAN using the chosen device library. No cloud round-trip for control, and credentials stay on the machine.
Architecture
Layers are kept separate so you can swap a backend or extend an effect without touching the rest.
MCP tools
FastMCP exposes typed functions as tools over stdio. Thin layer - no device logic here.
Controller
Orchestrates devices and effects, validates inputs, and maps errors to clean results.
Device adapters
A common adapter interface with swappable backends: kasa, tapo, or an in-memory mock.
Effects engine
Runs strobe, pulse, and BPM sync as cancellable tasks with rate limiting.
Cross-cutting
Config validation, secret-redacting logs, retries, timeouts, and graceful shutdown.
Every device call passes through a bounded-concurrency limiter, a per-second rate limiter, retries with backoff, and a timeout, so even a fast effect cannot overwhelm the lights or the network.
Setup
Example steps for getting the server running locally. IPs and credentials are placeholders - never hardcode secrets.
Create an environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activateInstall dependencies
pip install -r requirements.txt
# Backend libraries (pick the one you use):
# pip install python-kasa # or python-tapoConfigure environment
Set device hostnames/IPs and credentials through environment variables. Keep secrets out of committed files.
# Copy to .env and fill in your real values
TAPO_USERNAME=you@example.com
TAPO_PASSWORD=change-me
# Device addresses
L900_3_IP=192.168.1.139
L900_IP=192.168.1.179
L900_2_IP=192.168.1.82
L530_2_IP=192.168.1.129
L530_IP=192.168.1.97Discover devices
The server reads addresses from config. To find them, check your router's DHCP client list, or use the library's discovery helper on the LAN.
# python-kasa discovery on the local network
python -m kasa discoverRun the server
python -m tapo_mcp
# Without hardware, try the in-memory mock backend:
python -m tapo_mcp --mockMCP Tools
The proposed tool contracts. These are reference signatures, not a claim of a shipped client - map them to your five devices by alias.
| Parameter | Type | Description |
|---|---|---|
| device_id * | string | Alias, e.g. L900-3 or L530. |
| Parameter | Type | Description |
|---|---|---|
| device_id * | string | Alias, or a group name. |
| on * | bool | true to power on, false to power off. |
| Parameter | Type | Description |
|---|---|---|
| device_id * | string | Alias or group. |
| hue * | int | 0-360. |
| saturation * | int | 0-100. |
| value * | int | 0-100. |
| Parameter | Type | Description |
|---|---|---|
| device_id * | string | Alias or group. |
| brightness * | int | 1-100. |
| Parameter | Type | Description |
|---|---|---|
| device_id * | string | Alias or group. |
| effect * | string | strobe | pulse | bpm. |
| params | object | Mode-specific options. |
| Parameter | Type | Description |
|---|---|---|
| effect | string? | Optional effect name to stop. |
Device aliases (L900-3, L900, L900-2, L530-2, L530) are resolved from configuration, so tools stay readable and never need raw IPs.
Party Modes
Three distinct effects. Timing is best-effort on a local network and device capabilities vary - treat these as the blueprint's defaults.
bpm_sync
Beat-syncedFlash on the beat, driven by an external BPM or beat pulses from a local source. No fake audio capture.
Controls
- bpmBeats per minute (validated range).
- paletteColor palette for the pulse.
- brightnessPulse brightness.
- offsetPhase/beat offset for staggered strips.
while beat := next_beat():
set_color(palette[beat % len(palette)])
wait(60 / bpm)strobe
Fast flashRapid on/off flashing at a fixed rate. Intense - use with care.
Controls
- fpsFlash rate (flashes per second).
- brightnessFlash brightness.
- durationMax runtime, then auto-stop.
while running:
turn_on()
wait(1 / fps)
turn_off()
wait(1 / fps)Strobe can trigger photosensitivity in some people. Keep rates low, limit duration, and provide an immediate stop.
pulse
Smooth pulseA slow, smooth breathing effect between two colors or brightness levels.
Controls
- color_aStart color.
- color_bEnd color.
- periodSeconds per full cycle.
- brightnessPeak brightness.
for t in range(period):
k = 0.5 * (1 + sin(2 * pi * t / period))
set_color(lerp(color_a, color_b, k))
wait(frame)Configuration
A readable example config. Values are placeholders - set your own.
backend: kasa # kasa | tapo | mock
devices:
- id: L900-3
alias: L900-3
host: 192.168.1.139
protocol: kasa # library/adapter for this device
- id: L900
alias: L900
host: 192.168.1.179
protocol: kasa
- id: L900-2
alias: L900-2
host: 192.168.1.82
protocol: kasa
- id: L530-2
alias: L530-2
host: 192.168.1.129
protocol: kasa
- id: L530
alias: L530
host: 192.168.1.97
protocol: kasa
defaults:
brightness: 60 # default brightness
max_brightness: 90 # hard ceiling, never exceeded
party_modes:
bpm:
min: 40
max: 240
strobe:
max_fps: 12
max_duration_s: 30The brightness ceiling is a safety floor: no tool or effect can exceed it, even if a client requests more.
Safety
The blueprint treats safety as part of the design, not an afterthought.
Secrets
Treat Tapo credentials like passwords. Keep them in a git-ignored .env, never in committed config or chat.
LAN-only
The server talks to private addresses only and refuses public IPs. Do not expose it to the internet.
Rate limiting
Bounded concurrency and a per-second cap prevent effects from flooding the network or the devices.
Brightness ceiling
A configured max brightness is enforced server-side as a hard cap for all tools and effects.
Strobe warning
Strobe can trigger photosensitivity. Keep rates low, cap duration, and provide an immediate stop.
No secrets in logs
Logs are scrubbed of credential-looking values before they are written.
Roadmap
What comes next for the blueprint, in rough priority order.
Adapter verification
Validate kasa and tapo adapters against real devices and firmware.
Persistence
Persist device state and preferences across restarts.
Richer effects
More effect presets, palettes, and per-device choreography.
Beat source integration
Wiring for MIDI clock or DJ software as the beat source.
Tests
Expand the suite to cover adapters and effects against mocks.
Packaging
Publish as a pip package with a documented entry point.