HTTP+WS
The custom HTTP+WebSocket entry point, run standalone or in Docker, with a reverse proxy in front.
The custom HTTP entry point (src/index.ts) is the production stack: Express, WebSocket, JWT auth, rate limiting, and a /mcp/ws streaming endpoint. This page is how to run it.
Standalone
Build first, then run:
bun run build:all
PORT=4000 \
HASS_HOST=http://your-ha-instance:8123 \
HASS_TOKEN=your_long_lived_token \
JWT_SECRET="$(openssl rand -base64 48)" \
node dist/index.cjs
The server prints MCP Server listening on http://0.0.0.0:4000 (or whatever PORT you set). Hit GET /health to confirm:
curl http://localhost:4000/health
# {"status":"ok","version":"0.1.0"}
Behind a reverse proxy (recommended for production)
Put a Caddy, nginx, or Cloudflare Tunnel in front. The custom stack assumes TLS is terminated upstream.
Caddy:
mcp.example.com {
reverse_proxy localhost:4000
}
nginx (with real_ip so the rate limiter sees the real client):
server {
listen 443 ssl http2;
server_name mcp.example.com;
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
location / {
proxy_pass http://127.0.0.1:4000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
proxy_set_header Upgrade and Connection: upgrade are what enable the WebSocket upgrade at /mcp/ws. Without them, the tools/call subscribe_events call will hang.
Docker
The repo includes a Dockerfile that builds all three entry points in a multi-stage Bun image.
Pull the prebuilt image
docker pull ghcr.io/jango-blockchained/advanced-homeassistant-mcp:latest
Run
docker run -d \
--name homeassistant-mcp \
--restart unless-stopped \
-e HASS_HOST=http://your-ha-instance:8123 \
-e HASS_TOKEN=your_long_lived_token \
-e JWT_SECRET="$(openssl rand -base64 48)" \
-e PORT=4000 \
-e HOST=0.0.0.0 \
-e LOG_LEVEL=info \
-p 4000:4000 \
-v mcp-logs:/app/logs \
ghcr.io/jango-blockchained/advanced-homeassistant-mcp:latest
The image:
- Uses
oven/bun:1-slim(Debian-slim base). - Runs as the unprivileged
bunjsuser (uid 1001). - Has
curlinstalled (for theHEALTHCHECK). - Does not include Python or audio libraries. Speech features are opt-in via a separate compose file (see below).
- Exposes the port from
PORT(default 7123, the Smithery default; set to 4000 to match the rest of this page).
Health checks
The image declares:
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:${PORT}/health || exit 1
The host orchestrator (docker compose, Kubernetes) sees this as a healthy / unhealthy state. Don’t override HEALTHCHECK without also updating the port.
docker compose
services:
mcp:
image: ghcr.io/jango-blockchained/advanced-homeassistant-mcp:latest
container_name: homeassistant-mcp
restart: unless-stopped
environment:
HASS_HOST: http://192.168.1.50:8123
HASS_TOKEN: ${HASS_TOKEN}
JWT_SECRET: ${JWT_SECRET}
PORT: 4000
HOST: 0.0.0.0
LOG_LEVEL: info
ports:
- "4000:4000"
volumes:
- mcp-logs:/app/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
interval: 30s
timeout: 10s
retries: 3
volumes:
mcp-logs:
A .env file alongside holds the secrets:
HASS_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
JWT_SECRET=$(openssl rand -base64 48)
Ports and networking
- Default port: 7123 (the Smithery default) in the Docker image, 4000 in standalone. Override with
PORT. - Default host: 0.0.0.0 in Docker, 127.0.0.1 in standalone. Override with
HOST. - The server binds a single port for both HTTP and WebSocket; the WS upgrade is negotiated on
/mcp/ws.
Logs
Logs are written to stdout (so docker logs works) and to /app/logs/ (rotated by winston-daily-rotate-file):
mcp-YYYY-MM-DD.log— info levelmcp-error-YYYY-MM-DD.log— error level- Both files are rotated when they hit
LOG_MAX_SIZE(default 20 MB) and pruned afterLOG_MAX_DAYS(default 14 d).
To attach to logs:
docker logs -f homeassistant-mcp
Persistent state
The server is stateless between restarts. The state cache in the HA client rebuilds on every start by reading the initial state from HA. The only persistent file is the log directory. Mount it as a volume if you want to keep logs across container restarts (the docker run and compose snippets above do this).
Adding speech features
The docker-compose.speech.yml file in the repo adds a second container with the speech runtime (Porcupine for wake word, Whisper for STT). See the file for the full setup.