MQTT channel plugin for Claude Code. Bridges MQTT messages directly into Claude Code sessions — enabling cross-session communication, Home Assistant integration, and IoT-triggered workflows.
If you're running multiple Claude Code sessions, they can't talk to each other. If you have Home Assistant, Frigate, or other IoT systems, they can't trigger Claude directly. MQTT solves both — it's the universal pub/sub protocol that every smart home device and automation tool already speaks.
This plugin connects a Claude Code session to any standard MQTT broker. Messages arrive as tagged events in Claude's context. Claude can read them, act on them, and publish back.
An MQTT broker on a busy network can have hundreds of messages per second. If all of that landed in Claude's context, the session would be overwhelmed. So messages are filtered through a gating model:
| Tier | Behavior |
|---|---|
| Admitted | Flows into Claude's context in real time |
| Watched | Buffered silently — Claude pulls via inbox when it wants |
| Muted | Silently dropped, never buffered |
| Everything else | Discarded |
Agents stay lean by default. They only see what they've explicitly opted into.
Admission, watch, and mute lists persist across session restarts in a JSON config file. Each session gets its own config — the email session admits different things than the coding session.
Topic matching: Patterns support standard MQTT wildcards — # for multi-level (e.g., homeassistant/sensor/# matches all subtopics) and + for single-level (e.g., homeassistant/+/temperature matches any device's temperature topic).
By default, publish wraps messages in a JSON envelope:
{"sender": "session-name", "ts": "2026-03-27T...", "content": "your message"}Set raw: true to send the text as-is — useful for publishing to systems that expect plain strings (e.g., Home Assistant command topics):
publish topic="homeassistant/switch/office/set" text="ON" raw=true
The plugin accepts both formats:
- JSON envelope —
{"sender": "name", "content": "..."}— sender and content are extracted - Plain string — any non-JSON payload is used as content directly, with sender set to
unknown
This means you can receive messages from systems that don't know about the envelope format (IoT devices, Home Assistant, other MQTT clients).
When you admit or watch a topic, the plugin automatically subscribes to it on the broker. When you unadmit or unwatch, it unsubscribes (unless the other list still needs it). Persisted topics are re-subscribed on reconnect.
Each session publishes a retained status message to claude/sessions/<name>/status with a lastSeen timestamp updated every 60 seconds (configurable). On clean shutdown, status flips to offline. On crash, MQTT's Last Will & Testament does it automatically.
Any coordinator can passively monitor agent health by reading retained status messages — no ping/response protocol needed:
status: online+ recentlastSeen→ healthystatus: offline→ graceful shutdown or LWT firedstatus: online+ stalelastSeen→ frozen/hung, needs attention
Note: The Last Will timestamp is set when the client connects (an MQTT protocol limitation). If the broker delivers the LWT hours later after a crash, the ts field will reflect connection time, not actual disconnect time. Use lastSeen from the most recent heartbeat for accurate timing.
# 1. Install Bun if you don't have it
curl -fsSL https://bun.sh/install | bash
# 2. Install the plugin
claude plugin install mqtt@mattstein111/claude-code-mqtt
# 3. Create the config directory and .env file
mkdir -p ~/.claude/channels/mqtt
cat > ~/.claude/channels/mqtt/.env << 'EOF'
MQTT_BROKER_URL=mqtt://localhost:1883
# MQTT_USERNAME=your_user
# MQTT_PASSWORD=your_pass
EOF
# 4. Launch Claude Code with the MQTT channel
SESSION_NAME=primary claude --channels plugin:mqtt@mattstein111/claude-code-mqtt- Bun runtime
- An MQTT broker (e.g., Mosquitto) — if you run Home Assistant, you probably already have one
- Claude Code CLI
claude plugin install mqtt@mattstein111/claude-code-mqttTo install for a specific project only (keeps it out of your other sessions):
claude plugin install mqtt@mattstein111/claude-code-mqtt --scope localUse --scope local when you only need MQTT in certain projects — for example, a home automation repo that talks to your broker but not your other coding sessions.
SESSION_NAME=primary claude --channels plugin:mqtt@mattstein111/claude-code-mqttThe session name identifies this agent on the network. Other sessions (or anything that can publish to MQTT) can send messages to claude/sessions/primary/inbox.
The plugin reads broker settings from ~/.claude/channels/mqtt/.env:
MQTT_BROKER_URL=mqtt://localhost:1883
MQTT_USERNAME=your_user
MQTT_PASSWORD=your_passgit clone https://github.com/mattstein111/claude-code-mqtt.git
cd claude-code-mqtt
bun installRun from source using the dev channels flag:
SESSION_NAME=primary claude --dangerously-load-development-channels server:mqtt| Tool | Description |
|---|---|
publish |
Send a message to any MQTT topic (supports raw flag to skip JSON envelope) |
reply |
Reply to a message on an MQTT topic (same as publish, with optional correlation_id for request/reply flows) |
request |
Send a message and wait for a correlated response |
admit |
Allow a sender/topic to flow directly into context (persists) |
mute |
Silently drop messages from a sender/topic (persists) |
watch |
Buffer messages from a topic for on-demand reading |
inbox |
Read buffered messages from watched topics |
unadmit |
Remove from admitted list |
unmute |
Remove from muted list |
unwatch |
Stop watching, clear buffer |
config |
View or update session settings |
subscribe |
Subscribe to a new MQTT topic at runtime (admit/watch auto-subscribe, so this is rarely needed) |
unsubscribe |
Unsubscribe from a topic |
status |
Show current session state: admitted/watched/muted lists, subscriptions, and buffer stats |
Session A can message session B by publishing to claude/sessions/B/inbox. Session B admits session A, and messages flow in real time. They coordinate without human involvement.
Session A (email) → publish to claude/sessions/coding/inbox
↓
Session B (coding) ← receives message, acts on it, replies back
Subscribe to HA topics and admit/watch what's relevant:
# In Claude's session:
subscribe homeassistant/sensor/#
watch homeassistant/sensor/temperature
admit homeassistant/binary_sensor/front_door
Temperature readings buffer silently for periodic review. Front door events flow in real time.
All configuration lives in ~/.claude/channels/mqtt/:
.env # Broker connection
sessions/<name>.json # Per-session config (auto-created)
| Variable | Default | Description |
|---|---|---|
MQTT_BROKER_URL |
mqtt://localhost:1883 |
Broker connection URL |
MQTT_USERNAME |
— | Broker auth username |
MQTT_PASSWORD |
— | Broker auth password |
SESSION_NAME |
default |
This session's identity |
MQTT_TOPIC_PREFIX |
claude |
Prefix for all session topics (inbox, status) |
QOS |
1 |
MQTT QoS level (0, 1, or 2) |
HEARTBEAT_INTERVAL |
60 |
Seconds between status heartbeats |
MQTT_REQUEST_TIMEOUT |
120 |
Seconds to wait for a correlated response |
MQTT_MAX_PAYLOAD_BYTES |
262144 |
Max inbound payload size (256KB) |
MQTT_MAX_PENDING_REQUESTS |
50 |
Max concurrent pending request/reply operations |
MQTT_STATE_DIR |
~/.claude/channels/mqtt |
Directory for .env and session config files |
{
"admitted": ["claude/sessions/primary/inbox"],
"muted": [],
"watched": [],
"bufferMaxAge": 3600,
"bufferMaxPerTopic": 50
}Messages not appearing?
- Check that the topic is admitted (
admit) or watched (watch). Messages to unmatched topics are silently discarded. - Verify the broker is reachable:
mosquitto_pub -h <broker-host> -t test -m hello - Check Claude Code's stderr output for connection errors (run with
--verboseto see MCP logs).
Broker connection fails silently?
- The plugin logs to stderr, not to Claude's context. If the broker is unreachable, tools will appear to work but messages won't be delivered.
- Verify your
.envfile is at~/.claude/channels/mqtt/.env(or the path set byMQTT_STATE_DIR).
Session name was changed?
SESSION_NAMEis sanitized to[a-zA-Z0-9_-]only. Characters like.or/are silently stripped. SetSESSION_NAME=my-session(hyphens and underscores are fine).
Using a TLS broker?
- Set
MQTT_BROKER_URL=mqtts://broker.example.com:8883for TLS connections. The plugin uses the mqtt.js library which supportsmqtts://URLs. For custom CA certificates or client certificates, you'll need to modify themqtt.connect()options inserver.ts.
This plugin bridges an MQTT broker into an LLM's context window. Any message that passes the gating filters (admitted or watched) reaches the agent. This means:
- Broker authentication is your perimeter. The plugin does not add its own auth layer — it trusts whatever the broker allows. Use broker-level ACLs, username/password, or TLS client certificates to control who can publish.
- Admitted messages flow verbatim into the agent's context. A malicious publisher on an admitted topic could attempt prompt injection. Mitigations:
- Only admit topics you fully control or trust
- Use the
watch+inboxpull model for untrusted sources — the agent explicitly requests these messages and can inspect them with more scrutiny - Keep admission lists narrow (specific topics, not broad wildcards)
- Content size — inbound payloads are capped at 256KB by default (
MQTT_MAX_PAYLOAD_BYTES). Adjust this if your use case requires larger messages, but be aware that large payloads consume context window space.
For security issues, please use GitHub's private vulnerability reporting rather than opening a public issue.
MIT