Control4 MCP Server

by randybritsch

Not rated
GitHub

About

A safe-by-default MCP server that exposes your Control4 home automation (lights, scenes, locks, thermostats, and media) as structured tools over HTTP and Claude Desktop STDIO for reliable AI-powered control on your local network.

Details

Author
randybritsch
Categories
Other

Install from PyPI (recommended for most users)

If you just want to use the server (not hack on the repo), you can install it from PyPI:

- STDIO (for Claude Desktop / stdio MCP clients):c4-mcp
- HTTP (for scripts / curl / local dev):c4-mcp-http

You still need to provide Control4 config viaC4_CONFIG_PATH(recommended) orC4_HOST/C4_USERNAME/C4_PASSWORD.

If you haveNode.js + npminstalled, you can bootstrap the Python venv + dependencies with one command:

-

Install Node.js (includes npm):](https://github.com/randybritsch/c4-mcp)https://nodejs.org/

- Start HTTP server:npm run start
- Start STDIO server (Claude-style):npm run start:stdio
- Run end-to-end checks:npm run e2e

This is just a convenience wrapper around the existing Python setup steps (it creates.venvand installsrequirements.txt).

This project is intended to work with any Control4 system. Nothing in the server is hard-coded to a specific home.

This repo usesrequirements.txtas the source of truth.

- python -m venv .venv
- .\.venv\Scripts\Activate.ps1
- python -m pip install -r requirements.txt

- python3 -m venv .venv
- source .venv/bin/activate
- python -m pip install -r requirements.txt
- Provide Control4 connection config via either:

- Environment variables:

- C4_HOST(Director/Controller IP or hostname; scheme optional)
- C4_USERNAME(Control4 account email)
- C4_PASSWORD(Control4 account password)

- A local config file (not committed): copyconfig.example.jsontoconfig.jsonand fill in values.

- Python(ms-python.python)
- Pylance(ms-python.vscode-pylance)

- Ctrl+Shift+PPython: Create Environment→ choosevenv→ select your Python 3.12 interpreter.

- python -m venv .venv
- Activate it (see Setup section above)
- Ctrl+Shift+PPython: Select Interpreter→ choose.venv

- python -m pip install -r requirements.txt
- Provide Control4 config while developing

- Config file: copyconfig.example.jsonconfig.json(kept local-only; ignored by git)

- Environment variables: setC4_HOST,C4_USERNAME,C4_PASSWORD

Tip (VS Code-friendly): create a local.envfile in the repo root (ignored by git) and use it from a debug config.

Turn your Control4 system into aModel Context Protocol (MCP)toolset, so any MCP-capable client (Claude Desktop, custom agents, scripts) canquery rooms/devicesandsafely run automations.

- Works with real MCP clients: HTTP transport for dev/scripts +STDIO JSON-RPCfor clients likeClaude Desktop.
- One integration point for many clients: use the same toolset from Claude Desktop, scripts, or your own agents without rewriting Control4 logic.
- Structured tool schemas = fewer mistakes: explicit inputs/outputs (room/device IDs, levels, setpoints, etc.) reduce ambiguity vs. prompt-only automations.
- Safe-by-default controls: optional write guardrails, read-only mode, and allow/deny lists for state-changing tools.
- Session memory for follow-ups: enables natural multi-step flows like “turn on the basement lights… now dim those lights”.
- Smarter “lights” semantics: room-based lighting ops avoid accidentally targeting fans/heaters/outlets.
- One-command validation: an end-to-end runner exercises HTTP + STDIO so you can ship changes with confidence.
- Tunable performance: inventory caching + env-configurable timeouts for slower Control4 projects.
- Credentials stay local: keepconfig.jsonon your machine (gitignored) and choose local-only STDIO or LAN HTTP based on your risk tolerance.

- Discover rooms/devices by name, category, and room (plus resolvers for “best-effort” name-based calls).
- Activate scenes, control shades, query variables/commands, and (optionally) change state (lights/locks/thermostat/media).
- Use it as a local “home automation brain” for chat + agents without hard-coding your project’s device IDs.
-

c4-mcpmust remain decoupled from any specific client app (includingc4-mcp-app).

- Integration is via MCP over HTTP/STDIO only.
- No shared code or cross-repo imports; clients should treatc4-mcpas an external dependency.

The client (AI/app) owns command interpretation;c4-mcpowns execution + safety.

- The client decides which tools to call and with what arguments (and in what sequence).
- c4-mcpvalidates inputs, enforces guardrails, executes tool calls, and returns structured results/ambiguity.

These work well in MCP clients like Claude Desktop (the client will call tools under the hood):

List all rooms. Show me the lights in the Basement. Turn on the basement lights. Now dim those lights to 30%. Activate the "Movie Time" scene in the Living Room. Which doors are currently unlocked?

Advanced prompts (hard in the stock Control4 app UI)

These are examples of the kind ofcross-device,conditional, andmulti-steprequests that are awkward (or not possible) to do purely in the standard Control4 app UI without building custom automation logic elsewhere.

Note: prompts thatchange state(lights/locks/thermostat/media) requireC4_WRITES_ENABLED=true. Read-only prompts (inventory/status/reporting) work fine with the safe defaultC4_WRITES_ENABLED=false.

Run a “Good Night” sweep: turn off all lights except Hallway (10%), lock all exterior doors, set Downstairs thermostat to 68°F, then report what succeeded/failed. If any door is unlocked, lock it — but do NOT lock the Garage door. Find anything in the Basement that is currently on (lights, outlets), list it, then turn off everything except the dehumidifier outlet. I’m leaving: turn off all AV devices, activate the “Away” scene, and confirm the house is secured (all locks locked). The Basement lights are on — tell me which specific loads are on and turn off only the ones that are above 50%. Compare the Living Room lights vs. Kitchen lights: which room is brighter right now? Then set them to match. Do a safety check: list any unlocked doors, any lights left on in the Basement, and the current thermostat setpoints for each zone.

Tip: If you run withC4_WRITE_GUARDRAILS=trueandC4_WRITES_ENABLED=false, you’ll get a safe read-only experience until you explicitly enable writes.

Ambiguity & disambiguation (recommended)

Name-based tools can legitimately returnmultiple matches(e.g., “Basement” might match several rooms). In that case,c4-mcpreturns a structured failure with anambiguousmarker and a candidate list.
- Call the name-based tool withinclude_candidates=true(or accept the default if the tool always includes them).
- If the response indicates ambiguity, show the candidates to the user and let them pick.
- Re-call the tool withrequire_unique=trueand a more specific scope (e.g.,room_id/room_name, or exactdevice_name).

This is how higher-level apps can support natural commands like “turn on the basement lights” while still being deterministic and safe.

Direct HTTP examples (no MCP client required)

If you’re not using an MCP client yet, you can still call the server directly.

- Inside Docker/Compose,c4-mcpcommonly listens on:3333.
- On the NAS/LAN, it’s often published as host port:3334→ container:3333.

- Example:GET http://<NAS_IP>:3334/mcp/list

$base = 'http://127.0.0.1:3333' # or: http://<NAS_IP>:3334 Invoke-RestMethod -Method Post -Uri ($base + '/mcp/call') -ContentType 'application/json' -Body ( @{ kind = 'tool'; name = 'c4_list_rooms'; args = @{} } | ConvertTo-Json -Depth 10 )
curl -s http://127.0.0.1:3333/mcp/call \ -H "Content-Type: application/json" \ -d '{"kind":"tool","name":"c4_list_rooms","args":{}}'

1)/mcp/listreturns a toolmap(not an array).

In PowerShell,toolsis aPSCustomObjectwhere each property name is a tool name.

$r = Invoke-RestMethod -Method Get -Uri 'http://127.0.0.1:3333/mcp/list' -TimeoutSec 10 $toolNames = $r.tools.PSObject.Properties.Name | Sort-Object "tools_count=$($r.tools.PSObject.Properties.Count)" $toolNames | Select-Object -First 25

2) Quick start/stop on Windows (detached, logs captured).

This avoids confusion around multiple terminals / Ctrl+C and makes it easy to inspect server logs.

# Safe-by-default: guardrails on, writes off $env:C4_WRITE_GUARDRAILS='true' $env:C4_WRITES_ENABLED='false' $env:PYTHONUTF8='1' New-Item -ItemType Directory -Force -Path logs | Out-Null $p = Start-Process -FilePath .\.venv\Scripts\python.exe -ArgumentList @('app.py') -PassThru -WindowStyle Hidden  -RedirectStandardOutput 'logs\http_server_out.txt' -RedirectStandardError 'logs\http_server_err.txt' $p.Id | Set-Content -Encoding ascii 'logs\http_server.pid' "started_pid=$($p.Id)" # Sanity check Test-NetConnection 127.0.0.1 -Port 3333 | Select-Object TcpTestSucceeded
Stop-Process -Id (Get-Content .\logs\http_server.pid)

If/mcp/listhangs or errors, checklogs/http_server_err.txt.

The HTTP server is designed to run locally. To run it on a NAS and reach it from other machines on your LAN:

- Bind to all interfaces withC4_BIND_HOST=0.0.0.0(default is localhost-only).
- Keep it LAN-only using Synology firewall rules (recommended) or a VPN (for remote access later).

This repo includes aDockerfileanddocker-compose.yml.

On Synology (Container Manager), run a compose project that:

- Publishes port3333to your LAN.
- Mounts your real
config.json(keep credentials off git).
- Keeps writes off by default:
C4_WRITES_ENABLED=false.

Before you start the compose project, create your local config file:

- Copyconfig.example.jsonconfig.jsonand fill in values (this repo ignoresconfig.json).

docker-compose.ymlalready setsC4_BIND_HOST=0.0.0.0.

LAN-only note: donotexpose port 3333 to the internet. Use Synology Firewall to allow only your LAN subnet (e.g.192.168.0.0/16) to reach TCP 3333.

If Container Manager fails with an error like:

unable to prepare context: unable to evaluate symlinks in Dockerfile path: lstat /volume1/...

That means Docker cannot find or access the folder you selected as thebuild context(the folder that should contain yourDockerfileand source code).

- Put the repo files on the NAS under a real shared-folder path, e.g./volume1/docker/c4-mcp/.
- Ensure that folder contains at least:
Dockerfile,docker-compose.yml,requirements.txt,app.py, and the Python modules.
- In Container Manager, create the Compose project using that exact folder as the project path (don’t point it at just
/volume1/docker/unless the files are actually there).
- If your shared folder isn’t on
volume1, use the correct volume (e.g./volume2/...).

- C4_BIND_HOST(default127.0.0.1)
-
C4_PORT(default3333)

Example (LAN):C4_BIND_HOST=0.0.0.0andC4_PORT=3333

This project talks to your Control4 system using credentials (and often a local controller IP).

- Never commit real credentials. Keepconfig.jsonlocal-only (it is ignored by.gitignore).
- If you accidentally committed credentials at any point, rotate them immediately and rewrite git history before making the repo public.

Public GitHub checklist (do this before you publish)

- Ensureconfig.jsonis not in git history. At minimum it should not be tracked in your current tree.

- Quick check:git ls-files config.jsonshould return nothing.
- If it was ever committed: rotate your Control4 password and rewrite history (e.g.,
git filter-repo), then force-push.

This section is meant to becopy/paste-friendlyfor MCP registries and "server list" directories.

- Name:c4-mcp
- Category: Home Automation / Control4
- Repo:https://github.com/randybritsch/c4-mcp
- License: MIT
- Transports:

- STDIO (JSON-RPC):claude_stdio_server.py(for Claude Desktop and other stdio-based MCP clients)

- HTTP:app.py(defaults tohttp://127.0.0.1:3333; override withC4_BIND_HOST/C4_PORT; endpoints:/mcp/list,/mcp/call)

- Recommended: setC4_CONFIG_PATHto a localconfig.jsonthat containshost,username,password(keep this file gitignored)
- Optional: set
C4_HOST(non-secret) to overridehostfromconfig.json
- Optional: set
C4_USERNAME/C4_PASSWORD(secret) via OS env vars (must be provided together)

- For read-only-by-default runs:C4_WRITE_GUARDRAILS=true+C4_WRITES_ENABLED=false
- Optional filters:
C4_WRITE_ALLOWLIST/C4_WRITE_DENYLIST

- Scheduler Agent writes are additionally gated:c4_scheduler_set_enabledrequiresC4_SCHEDULER_WRITES_ENABLED=true

This project depends onflask-mcp-server, which in turn depends onpydantic/pydantic-core. At the time of writing,Python 3.14 will not work out-of-the-box on Windowsbecausepydantic-coredoes not ship wheels for it yet.

UsePython 3.12(recommended) or another version withpydantic-corewheels available.

Install from PyPI (recommended for most users)

If you just want to use the server (not hack on the repo), you can install it from PyPI:

- STDIO (for Claude Desktop / stdio MCP clients):c4-mcp
- HTTP (for scripts / curl / local dev):
c4-mcp-http

You still need to provide Control4 config viaC4_CONFIG_PATH(recommended) orC4_HOST/C4_USERNAME/C4_PASSWORD.

If you haveNode.js + npminstalled, you can bootstrap the Python venv + dependencies with one command:

-

Install Node.js (includes npm):https://nodejs.org/

- Start HTTP server:npm run start
- Start STDIO server (Claude-style):
npm run start:stdio
- Run end-to-end checks:
npm run e2e

This is just a convenience wrapper around the existing Python setup steps (it creates.venvand installsrequirements.txt).

This project is intended to work with any Control4 system. Nothing in the server is hard-coded to a specific home.

This repo usesrequirements.txtas the source of truth.

- python -m venv .venv
-
.\.venv\Scripts\Activate.ps1
-
python -m pip install -r requirements.txt

- python3 -m venv .venv
-
source .venv/bin/activate
-
python -m pip install -r requirements.txt
- Provide Control4 connection config via either:

- Environment variables:

- C4_HOST(Director/Controller IP or hostname; scheme optional)
-
C4_USERNAME(Control4 account email)
-
C4_PASSWORD(Control4 account password)

- A local config file (not committed): copyconfig.example.jsontoconfig.jsonand fill in values.

- Python(ms-python.python)
- Pylance(ms-python.vscode-pylance)

- Ctrl+Shift+PPython: Create Environment→ choosevenv→ select your Python 3.12 interpreter.

- python -m venv .venv
- Activate it (see Setup section above)
-
Ctrl+Shift+PPython: Select Interpreter→ choose.venv

- python -m pip install -r requirements.txt
- Provide Control4 config while developing

- Config file: copyconfig.example.jsonconfig.json(kept local-only; ignored by git)

- Environment variables: setC4_HOST,C4_USERNAME,C4_PASSWORD

Tip (VS Code-friendly): create a local.env`file in the repo root (ignored by git) and use it from a debug config.

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.