{
  "markdown": "# claude-code-mqtt\n\nMQTT channel plugin for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Bridges MQTT messages directly into Claude Code sessions — enabling cross-session communication, Home Assistant integration, and IoT-triggered workflows.\n\n## Why\n\nIf 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.\n\nThis 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.\n\n## The gating model\n\nAn 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:\n\n| Tier | Behavior |\n|---|---|\n| **Admitted** | Flows into Claude's context in real time |\n| **Watched** | Buffered silently — Claude pulls via `inbox` when it wants |\n| **Muted** | Silently dropped, never buffered |\n| **Everything else** | Discarded |\n\nAgents stay lean by default. They only see what they've explicitly opted into.\n\nAdmission, 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.\n\n**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).\n\n## Message format\n\n### Outbound (publish)\n\nBy default, `publish` wraps messages in a JSON envelope:\n\n```json\n{\"sender\": \"session-name\", \"ts\": \"2026-03-27T...\", \"content\": \"your message\"}\n```\n\nSet `raw: true` to send the text as-is — useful for publishing to systems that expect plain strings (e.g., Home Assistant command topics):\n\n```\npublish topic=\"homeassistant/switch/office/set\" text=\"ON\" raw=true\n```\n\n### Inbound (receive)\n\nThe plugin accepts both formats:\n\n- **JSON envelope** — `{\"sender\": \"name\", \"content\": \"...\"}` — sender and content are extracted\n- **Plain string** — any non-JSON payload is used as content directly, with sender set to `unknown`\n\nThis means you can receive messages from systems that don't know about the envelope format (IoT devices, Home Assistant, other MQTT clients).\n\n### Broker subscriptions\n\nWhen 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.\n\n## Health monitoring\n\nEach 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.\n\nAny coordinator can passively monitor agent health by reading retained status messages — no ping/response protocol needed:\n\n- `status: online` + recent `lastSeen` → healthy\n- `status: offline` → graceful shutdown or LWT fired\n- `status: online` + stale `lastSeen` → frozen/hung, needs attention\n\n**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.\n\n## Setup\n\n### Quickstart\n\n```bash\n# 1. Install Bun if you don't have it\ncurl -fsSL https://bun.sh/install | bash\n\n# 2. Install the plugin\nclaude plugin install mqtt@mattstein111/claude-code-mqtt\n\n# 3. Create the config directory and .env file\nmkdir -p ~/.claude/channels/mqtt\ncat > ~/.claude/channels/mqtt/.env << 'EOF'\nMQTT_BROKER_URL=mqtt://localhost:1883\n# MQTT_USERNAME=your_user\n# MQTT_PASSWORD=your_pass\nEOF\n\n# 4. Launch Claude Code with the MQTT channel\nSESSION_NAME=primary claude --channels plugin:mqtt@mattstein111/claude-code-mqtt\n```\n\n### Prerequisites\n\n- [Bun](https://bun.sh) runtime\n- An MQTT broker (e.g., [Mosquitto](https://mosquitto.org/)) — if you run Home Assistant, you probably already have one\n- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI\n\n### Install as Claude Code plugin\n\n```bash\nclaude plugin install mqtt@mattstein111/claude-code-mqtt\n```\n\nTo install for a specific project only (keeps it out of your other sessions):\n\n```bash\nclaude plugin install mqtt@mattstein111/claude-code-mqtt --scope local\n```\n\nUse `--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.\n\n### Run\n\n```bash\nSESSION_NAME=primary claude --channels plugin:mqtt@mattstein111/claude-code-mqtt\n```\n\nThe 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`.\n\n### Configure\n\nThe plugin reads broker settings from `~/.claude/channels/mqtt/.env`:\n\n```bash\nMQTT_BROKER_URL=mqtt://localhost:1883\nMQTT_USERNAME=your_user\nMQTT_PASSWORD=your_pass\n```\n\n### Install from source (development)\n\n```bash\ngit clone https://github.com/mattstein111/claude-code-mqtt.git\ncd claude-code-mqtt\nbun install\n```\n\nRun from source using the dev channels flag:\n\n```bash\nSESSION_NAME=primary claude --dangerously-load-development-channels server:mqtt\n```\n\n## Tools\n\n| Tool | Description |\n|---|---|\n| `publish` | Send a message to any MQTT topic (supports `raw` flag to skip JSON envelope) |\n| `reply` | Reply to a message on an MQTT topic (same as publish, with optional correlation_id for request/reply flows) |\n| `request` | Send a message and wait for a correlated response |\n| `admit` | Allow a sender/topic to flow directly into context (persists) |\n| `mute` | Silently drop messages from a sender/topic (persists) |\n| `watch` | Buffer messages from a topic for on-demand reading |\n| `inbox` | Read buffered messages from watched topics |\n| `unadmit` | Remove from admitted list |\n| `unmute` | Remove from muted list |\n| `unwatch` | Stop watching, clear buffer |\n| `config` | View or update session settings |\n| `subscribe` | Subscribe to a new MQTT topic at runtime (admit/watch auto-subscribe, so this is rarely needed) |\n| `unsubscribe` | Unsubscribe from a topic |\n| `status` | Show current session state: admitted/watched/muted lists, subscriptions, and buffer stats |\n\n## Cross-session messaging\n\nSession 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.\n\n```\nSession A (email)  →  publish to claude/sessions/coding/inbox\n                          ↓\nSession B (coding) ←  receives message, acts on it, replies back\n```\n\n## Home Assistant integration\n\nSubscribe to HA topics and admit/watch what's relevant:\n\n```\n# In Claude's session:\nsubscribe homeassistant/sensor/#\nwatch homeassistant/sensor/temperature\nadmit homeassistant/binary_sensor/front_door\n```\n\nTemperature readings buffer silently for periodic review. Front door events flow in real time.\n\n## Configuration\n\nAll configuration lives in `~/.claude/channels/mqtt/`:\n\n```\n.env                        # Broker connection\nsessions/<name>.json        # Per-session config (auto-created)\n```\n\n### Environment variables\n\n| Variable | Default | Description |\n|---|---|---|\n| `MQTT_BROKER_URL` | `mqtt://localhost:1883` | Broker connection URL |\n| `MQTT_USERNAME` | — | Broker auth username |\n| `MQTT_PASSWORD` | — | Broker auth password |\n| `SESSION_NAME` | `default` | This session's identity |\n| `MQTT_TOPIC_PREFIX` | `claude` | Prefix for all session topics (inbox, status) |\n| `QOS` | `1` | MQTT QoS level (0, 1, or 2) |\n| `HEARTBEAT_INTERVAL` | `60` | Seconds between status heartbeats |\n| `MQTT_REQUEST_TIMEOUT` | `120` | Seconds to wait for a correlated response |\n| `MQTT_MAX_PAYLOAD_BYTES` | `262144` | Max inbound payload size (256KB) |\n| `MQTT_MAX_PENDING_REQUESTS` | `50` | Max concurrent pending request/reply operations |\n| `MQTT_STATE_DIR` | `~/.claude/channels/mqtt` | Directory for .env and session config files |\n\n### Session config (JSON)\n\n```json\n{\n  \"admitted\": [\"claude/sessions/primary/inbox\"],\n  \"muted\": [],\n  \"watched\": [],\n  \"bufferMaxAge\": 3600,\n  \"bufferMaxPerTopic\": 50\n}\n```\n\n## Troubleshooting\n\n**Messages not appearing?**\n- Check that the topic is admitted (`admit`) or watched (`watch`). Messages to unmatched topics are silently discarded.\n- Verify the broker is reachable: `mosquitto_pub -h <broker-host> -t test -m hello`\n- Check Claude Code's stderr output for connection errors (run with `--verbose` to see MCP logs).\n\n**Broker connection fails silently?**\n- 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.\n- Verify your `.env` file is at `~/.claude/channels/mqtt/.env` (or the path set by `MQTT_STATE_DIR`).\n\n**Session name was changed?**\n- `SESSION_NAME` is sanitized to `[a-zA-Z0-9_-]` only. Characters like `.` or `/` are silently stripped. Set `SESSION_NAME=my-session` (hyphens and underscores are fine).\n\n**Using a TLS broker?**\n- Set `MQTT_BROKER_URL=mqtts://broker.example.com:8883` for TLS connections. The plugin uses the mqtt.js library which supports `mqtts://` URLs. For custom CA certificates or client certificates, you'll need to modify the `mqtt.connect()` options in `server.ts`.\n\n## Security considerations\n\n### Threat model\n\nThis 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:\n\n- **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.\n- **Admitted messages flow verbatim into the agent's context.** A malicious publisher on an admitted topic could attempt prompt injection. Mitigations:\n  - Only admit topics you fully control or trust\n  - Use the `watch` + `inbox` pull model for untrusted sources — the agent explicitly requests these messages and can inspect them with more scrutiny\n  - Keep admission lists narrow (specific topics, not broad wildcards)\n- **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.\n\n### Reporting vulnerabilities\n\nFor security issues, please use [GitHub's private vulnerability reporting](https://github.com/mattstein111/claude-code-mqtt/security/advisories/new) rather than opening a public issue.\n\n## License\n\nMIT\n",
  "bytes": 11005,
  "sha": "b6aa716e40938d619a6ade2130f4920a84b4ffb90973608586a59f844a5092ad",
  "repo_slug": "mattstein111/claude-code-mqtt",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_mattstein111_claude_code_mqtt_mqtt_7072ab07/readme"
}