About Tapo MCP
Tapo MCP exposes TP-Link Tapo smart-home devices to AI agents through an HTTP MCP server. It provides device discovery and state or control tools, along with camera snapshots and supported sensor and energy readings.
Key features
- 01Discover Tapo devices
- 02Read device state and capabilities
- 03Control supported device settings
- 04Capture camera snapshots
- 05Read sensor, event, and energy data
- 06HTTP authentication and host controls
Use cases
- 01Find devices on a home network
- 02Switch lights or adjust brightness
- 03Check camera snapshots and detection events
- 04Review temperature or energy readings
MCP support
The server exposes Tapo devices through MCP tools for discovery, state checks, control, and camera snapshots, plus the `tapo://devices` resource. MCP clients can use the capabilities supported by each device, including available sensor and energy readings.
Configuration
Local server config is the same for every client. Copy it into your client’s MCP config file.
docker run -i --rm -e TAPO_MCP_USERNAME -e TAPO_MCP_PASSWORD -e TAPO_MCP_CAMERA_USERNAME -e TAPO_MCP_CAMERA_PASSWORD -e TAPO_MCP_DISCOVERY_TARGET -e TAPO_MCP_API_KEY -e TAPO_MCP_ALLOWED_HOSTS -e TAPO_MCP_PUBLIC_URL -e TAPO_MCP_DISCOVERY_TIMEOUT -e TAPO_MCP_DISCOVERY_INTERVAL -e OTEL_EXPORTER_OTLP_ENDPOINT ghcr.io/mihai-dinculescu/tapo-mcp:0.6.0TAPO_MCP_USERNAMESecretTP-Link Tapo account emailTAPO_MCP_PASSWORDSecretTP-Link Tapo account passwordTAPO_MCP_CAMERA_USERNAMESecretOptional camera account username; required for taking camera snapshotsTAPO_MCP_CAMERA_PASSWORDSecretOptional camera account password; required for taking camera snapshotsTAPO_MCP_DISCOVERY_TARGETNetwork target for device discovery (e.g. 192.168.1.255)TAPO_MCP_API_KEYSecretOptional Bearer token for API authenticationTAPO_MCP_ALLOWED_HOSTSOptional comma-separated Host header allowlist; replaces the loopback-only defaultTAPO_MCP_PUBLIC_URLOptional base URL clients reach the server at; enables short-lived snapshot linksTAPO_MCP_DISCOVERY_TIMEOUTDevice discovery timeout in seconds; must be between 1 and 60TAPO_MCP_DISCOVERY_INTERVALSeconds between background device discoveries that refresh the device list; must be at least TAPO_MCP_DISCOVERY_TIMEOUT and at most 86400 (one day); tool calls wait while a discovery runs, so a short interval delays them more oftenOTEL_EXPORTER_OTLP_ENDPOINTOptional OpenTelemetry collector endpoint for tracingLocal servers need a runtime on your machine (e.g. Node.js 18+). Config file locations differ by client; see your client’s docs.
ghcr.io/mihai-dinculescu/tapo-mcp:0.6.0Streamable HTTPTools
Local servers expose tools only once running on your machine, and the maintainer has not provided a tool list.
Details
From the project READMEtapo-mcp
MCP server that exposes Tapo smart-home devices as AI-callable tools and resources via the Model Context Protocol.
Built on the tapo crate and the rmcp SDK. Runs as an HTTP server (Streamable HTTP transport).
Example Prompts
"List all my Tapo devices"
"Turn off the office light"
"Turn on smart plug 4 on the power strip"
"Is the office light on?"
"Set the bedroom light to 50% brightness"
"Change the living room light to Coral"
"Take a snapshot from the baby monitor"
"What's the temperature in the kitchen?"
"Show me the last 24 hours of temperature from the living room temperature and humidity sensor"
"List the 5 most recent events on the smart button"
"How much energy did the washing machine plug use each day this month?"
"Show me the TV's power draw over the last 6 hours"
Tools
| Tool | Description |
|---|---|
list_devices | List available Tapo devices on the network (includes set and get capabilities). |
check_device | Verify a device ID matches at a given IP. |
get_device_state | Get a device's current state (e.g. {"type": "DeviceInfo"}). Checks the capability's parameters, then runs check_device. |
control_device | Control a device by applying 1 to 12 set capabilities, in order. Checks the count and values, then runs check_device; a capability the device does not support fails before any is applied. |
take_snapshot | Capture a still JPEG snapshot from a Tapo camera (~640x360), with the device id, capture time and size as text, plus a short-lived link when TAPO_MCP_PUBLIC_URL is set (see Snapshot links). Runs check_device first. |
Resources
| URI | Description |
|---|---|
tapo://devices | JSON list of discovered Tapo devices. |
Capabilities
Devices and child devices expose separate lists of set and get capabilities they support. Child devices have no IP of their own; their ip is their parent's, which is the one to pass with the child's id.
Set Capabilities
| Capability | Description |
|---|---|
Brightness | Set the device brightness (1-100) |
Color | Set the device color using a preset name |
OnOff | Turn the device on or off |
Get Capabilities
| Capability | Description |
|---|---|
Detections | Read the detections stored on a camera's SD card, with the events detected (person, motion, ...), for up to 24 hours (C120, C210, C220, C225, C325WB, C520WS, TC40, TC70) |
DeviceInfo | Read the device's current state |
EnergyDataHourly | Read the energy used per hour, in Wh, for up to 8 days (P110, P110M, P115, P304M, P316M) |
EnergyDataDaily | Read the energy used per day, in Wh, for a quarter of a year (P110, P110M, P115, P304M, P316M) |
EnergyDataMonthly | Read the energy used per month, in Wh, for a year (P110, P110M, P115, P304M, P316M) |
PowerDataEvery5Minutes | Read the power draw every 5 minutes, in W, for up to 12 hours (P110, P110M, P115, P304M, P316M) |
PowerDataHourly | Read the power draw every hour, in W, for up to 6 days (P110, P110M, P115, P304M, P316M) |
Snapshot | Capture a still JPEG snapshot. Served by the dedicated take_snapshot tool (binary, not JSON state) |
TemperatureHumidityRecords | Read the last 24 hours of temperature and humidity records (T310, T315) at 15 minute intervals |
TriggerLogs | Read paginated trigger logs from a hub child sensor (S200, T100, T110, T300) |
Configuration
All configuration is via environment variables prefixed with TAPO_MCP_:
| Variable | Required | Default | Description |
|---|---|---|---|
TAPO_MCP_USERNAME | Yes | — | Tapo account email |
TAPO_MCP_PASSWORD | Yes | — | Tapo account password |
TAPO_MCP_CAMERA_USERNAME | No | — | Camera account username1. Required by take_snapshot. |
TAPO_MCP_CAMERA_PASSWORD | No | — | Camera account password1. Required by take_snapshot. |
TAPO_MCP_DISCOVERY_TARGET | Yes | — | Network target for device discovery (e.g. 192.168.1.255) |
TAPO_MCP_HTTP_ADDR | No | 127.0.0.1:3000 | Address the server listens on |
TAPO_MCP_DISCOVERY_TIMEOUT | No | 5 | Discovery timeout in seconds. Must be between 1 and 602 |
TAPO_MCP_DISCOVERY_INTERVAL | No | 600 | Seconds between background discoveries that refresh the device list. Must be at least TAPO_MCP_DISCOVERY_TIMEOUT and at most 86400 (one day)2 |
TAPO_MCP_API_KEY | No | — | Bearer token for HTTP authentication (see below) |
TAPO_MCP_ALLOWED_HOSTS | No | loopback only | Comma-separated Host header allowlist (see Network exposure) |
TAPO_MCP_PUBLIC_URL | No | — | Base URL clients reach the server at (e.g. https://tapo.example.com). Enables short-lived snapshot links (see Snapshot links) |
Authentication
When TAPO_MCP_API_KEY is set, the server requires all HTTP requests to include an Authorization: Bearer <key> header. Requests with a missing or invalid token receive a 401 Unauthorized response.
When the variable is unset (or empty/whitespace-only), the server runs without authentication.
Network exposure
The server enforces the MCP Streamable HTTP DNS-rebinding protection. By default only loopback Host headers (localhost, 127.0.0.1, ::1) are accepted, which prevents a malicious web page from reaching a locally running server via DNS rebinding. Requests to the MCP endpoint with any other Host receive a 403 Forbidden response. Snapshot links are not covered by this check (see Snapshot links).
To reach the server over the LAN or from another host, set TAPO_MCP_ALLOWED_HOSTS to the exact hostname(s) or host:port authorities clients connect to, for example TAPO_MCP_ALLOWED_HOSTS="tapo-mcp.lan:3000,192.168.1.50:3000". This replaces the loopback default, so include loopback entries as well if you still need them.
To avoid shipping unauthenticated smart-home control, the server refuses to start when it binds to a non-loopback address (for example 0.0.0.0:3000) without TAPO_MCP_API_KEY set. Set an API key, or bind to a loopback address.
Snapshot links
Some MCP clients don't pass image results on to the model, and others show the image to the model but give it nothing it can hand to the user. When TAPO_MCP_PUBLIC_URL is set, take_snapshot also returns a url (and its expires_at) pointing at <TAPO_MCP_PUBLIC_URL>/snapshots/<token>.jpg, so the agent can share the link or download the file.
- The link is served without the API key, so a browser can open it directly. The
Hostallowlist fromTAPO_MCP_ALLOWED_HOSTSdoes not apply to it either. The random token in the path is the only credential: anyone holding the link can view that frame until it expires. - Each link is valid for 5 minutes. Snapshots are kept in memory only and never written to disk. Expired snapshots are cleared out within a minute, and at most the 1024 most recent are kept.
- Behind an authenticating reverse proxy (Cloudflare Access and similar), links opened in a browser that is already signed in to the proxy work. Non-browser fetches (
curl, an agent downloading the file) need the proxy's own credential or a policy exception for/snapshots/.
When TAPO_MCP_PUBLIC_URL is unset, no link is returned and the /snapshots/ route is not served.
OpenTelemetry
Set OTEL_EXPORTER_OTLP_ENDPOINT to the full URL of an OTLP/HTTP traces endpoint, including the path (e.g. http://alloy.monitoring:4318/v1/traces), to export traces. OTEL_RESOURCE_ATTRIBUTES adds resource attributes. RUST_LOG (default tapo_mcp=info) also decides which spans are exported.
What gets traced:
- Tool calls. Each
tools/callrequest gets one server span namedtools/call <tool>, covering the whole call, following the OpenTelemetry MCP semantic conventions. It carriesmcp.method.name,gen_ai.tool.name(only for tools the server provides),gen_ai.operation.name,jsonrpc.request.id,mcp.session.idandmcp.protocol.version, plustapo.device.idandtapo.device.ipwhere the tool takes them. A failed call has error status anderror.type(the JSON-RPC error code, ortool_error). Inside it,check_devicecovers the device verification, andget_capability,set_capabilityorcapture_snapshotcovers the device request.get_capabilityandset_capabilitycarrytapo.capability, andcontrol_devicehas oneset_capabilityspan for each capability it applies. A failed step has error status anderror.typeset to the kind of failure, such asDeviceNotFoundorWrongDeviceType. - Background jobs.
refresh_devicesfor each device discovery, andprune_snapshotsfor each sweep of expired snapshot links.
Credentials, the API key and image data are never recorded. Tool arguments are not recorded either, except id and ip, and a failed call's error message, which can repeat the value that was rejected.
Incoming W3C trace context is honoured. When the request's params._meta has a traceparent (and optionally tracestate), as MCP clients that trace their calls send, the tool call span becomes its child and joins that trace. A traceparent header on the HTTP request that carries the call, for example from a tracing reverse proxy, is then recorded as a span link. Without _meta context, the HTTP header is used as the parent instead. Without either, the span starts a new trace.
Deployment
Docker
docker run --rm \
--network host \
-e TAPO_MCP_USERNAME="[email protected]" \
-e TAPO_MCP_PASSWORD="<YOUR_TAPO_PASSWORD>" \
-e TAPO_MCP_CAMERA_USERNAME="<YOUR_CAMERA_ACCOUNT_USERNAME>" \
-e TAPO_MCP_CAMERA_PASSWORD="<YOUR_CAMERA_ACCOUNT_PASSWORD>" \
-e TAPO_MCP_DISCOVERY_TARGET="192.168.1.255" \
-e TAPO_MCP_API_KEY="<YOUR_TAPO_MCP_API_KEY>" \
ghcr.io/mihai-dinculescu/tapo-mcp:latest
Note: The image binds to
0.0.0.0:3000, soTAPO_MCP_API_KEYis required — the server refuses to start on a non-loopback address without it (see Network exposure). To reach the server by hostname or LAN IP rather than loopback, also setTAPO_MCP_ALLOWED_HOSTS. To get snapshot links, add-e TAPO_MCP_PUBLIC_URL="https://tapo.example.com".
Note:
--network hostis required so the container can reach Tapo devices on your local network via UDP broadcast for discovery. On macOS and Windows,--network hostis not supported — you can use-p 3000:3000instead, but device discovery won't work as Docker Desktop runs containers inside a VM without LAN access.
Kubernetes
Create the Secret and ConfigMap first:
kubectl create secret generic tapo-mcp-secrets \
--from-literal=TAPO_MCP_USERNAME="[email protected]" \
--from-literal=TAPO_MCP_PASSWORD="<YOUR_TAPO_PASSWORD>" \
--from-literal=TAPO_MCP_CAMERA_USERNAME="<YOUR_CAMERA_ACCOUNT_USERNAME>" \
--from-literal=TAPO_MCP_CAMERA_PASSWORD="<YOUR_CAMERA_ACCOUNT_PASSWORD>" \
--from-literal=TAPO_MCP_API_KEY="<YOUR_TAPO_MCP_API_KEY>"
kubectl create configmap tapo-mcp-config \
--from-literal=TAPO_MCP_DISCOVERY_TARGET="192.168.1.255"
Then apply the Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: tapo-mcp
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: tapo-mcp
template:
metadata:
labels:
app: tapo-mcp
spec:
hostNetwork: true
containers:
- name: tapo-mcp
image: ghcr.io/mihai-dinculescu/tapo-mcp:latest
env:
- name: TAPO_MCP_USERNAME
valueFrom:
secretKeyRef:
name: tapo-mcp-secrets
key: TAPO_MCP_USERNAME
- name: TAPO_MCP_PASSWORD
valueFrom:
secretKeyRef:
name: tapo-mcp-secrets
key: TAPO_MCP_PASSWORD
- name: TAPO_MCP_CAMERA_USERNAME
valueFrom:
secretKeyRef:
name: tapo-mcp-secrets
key: TAPO_MCP_CAMERA_USERNAME
- name: TAPO_MCP_CAMERA_PASSWORD
valueFrom:
secretKeyRef:
name: tapo-mcp-secrets
key: TAPO_MCP_CAMERA_PASSWORD
- name: TAPO_MCP_API_KEY
valueFrom:
secretKeyRef:
name: tapo-mcp-secrets
key: TAPO_MCP_API_KEY
- name: TAPO_MCP_DISCOVERY_TARGET
valueFrom:
configMapKeyRef:
name: tapo-mcp-config
key: TAPO_MCP_DISCOVERY_TARGET
Note:
hostNetwork: trueis required for UDP broadcast discovery, similar to--network hostin Docker. Because clients reach the server by node IP or hostname rather than loopback, setTAPO_MCP_ALLOWED_HOSTSaccordingly (see Network exposure). To get snapshot links, addTAPO_MCP_PUBLIC_URLto the ConfigMap and the Deployment'senv.
OpenClaw
The tapo skill for OpenClaw makes it easy to use a deployed tapo MCP server from OpenClaw agents.
Install it with:
npx clawhub install tapo
Contributing
Contributions are welcome and encouraged! See /tapo-mcp/CONTRIBUTING.md.
Footnotes
-
Set on each camera in the Tapo app under Camera Settings > Advanced Settings > Camera Account. Distinct from your TP-Link cloud account. ↩ ↩2
-
While a background discovery runs, tool calls that contact a device wait for it to finish, and the discovery waits for calls already in progress. A run takes about
TAPO_MCP_DISCOVERY_TIMEOUTseconds plus a few more to log in to the devices, and longer if a device is slow to respond. A short interval means these pauses happen often: at15with the default timeout, tool calls can be held up for several seconds out of every 15.list_devicesand thetapo://devicesresource never wait. ↩ ↩2
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.