@partme.ai/openclaw-mqtt
v0.1.13
Published
MQTT protocol bridge for OpenClaw — IoT device integration via embedded Aedes MQTT broker
Downloads
315
Maintainers
Readme
OpenClaw MQTT
OpenClaw plugin — MQTT channel bridge with multi-topic routing and explicit topic bindings
📖 Introduction
@partme.ai/openclaw-mqtt is an OpenClaw channel plugin for OpenClaw that embeds an MQTT broker (Aedes) and bridges MQTT devices to OpenClaw agents. The plugin uses defineChannelPluginEntry / ChannelPlugin per the OpenClaw channel plugin guide (not definePluginEntry, which is for non-channel plugins). It supports:
- Explicit
topicPattern -> agentIdbindings - Multi-topic allowlist (
subscribeTopics) - Inbound payload parsing strategy (
JSON.textfirst, fallback plain text) - Runtime reply dispatch via OpenClaw
dispatchReplyFromConfig
🎯 Core Capabilities
- Embedded broker: no external broker required for basic integration.
- Explicit routing first:
topicBindingshas higher priority than standard topic fallback. - Standard fallback:
openclaw/agent/<agentId>/instill works when no binding matches. - Reply topic control: use
replyTopicper binding, otherwise derive from inbound topic. - Session context mapping: each MQTT session records agent/account/reply topic context.
- Enterprise security baseline (RabbitMQ-inspired): optional MQTT over TLS, user-level topic ACL (
publishAllow/subscribeAllow), anonymous control, and payload size limits.
Plugin lifecycle
- The embedded broker starts when the Gateway runs
gateway.startAccountfor the MQTT channel (single accountdefaultin this release). - HTTP
GET /mqtt/statusis registered inregisterFull(plugin-authenticated route) and exposes broker stats, activechannels.mqttsnapshot, and policy hot-reload metadata (policy.version,policy.updatedAt, summary fields). - Session key scope follows OpenClaw global
session.dmScope(for exampleper-channel-peer), instead of a channel-localchannels.mqtt.session.dmScope. - If
channels.mqtt.session.dmScopeis present, the plugin logs a warning and ignores it.\n-package.json→openclaw.setupEntrypoints todist/setup-entry.js, a lightweight entry that only exports theChannelPluginviadefineSetupPluginEntry. OpenClaw can load it during setup or when the channel is disabled/unconfigured, instead of the fullindexentry (see Setup entry).
Scaling note (Aedes)
Default deployment is single-process in-memory. For multi-gateway horizontal scale you would need a shared MQEmitter (for example mqemitter-redis) and optional persistence backends from the Aedes ecosystem; this package does not wire those in yet—documented here for future extension.
🏗️ Message Flow
- Device publishes MQTT message.
- Plugin checks
subscribeTopicsallowlist. - Plugin resolves route:
- First:
topicBindings - Fallback: standard
openclaw/agent/<agentId>/in
- First:
- Plugin parses payload (
JSON.text-> plain text fallback). - Plugin dispatches to OpenClaw runtime.
- Reply is published to
replyTopicor derived/outtopic.
🚀 Quick Start
Prerequisites
- OpenClaw
>= 2026.4.0 - Node.js
20+
Install
openclaw plugins install @partme.ai/openclaw-mqttMinimal config (openclaw.json)
{
"channels": {
"mqtt": {
"port": 1883,
"maxConnections": 1000,
"subscribeTopics": [
"devices/+/in",
"openclaw/agent/+/in"
],
"topicBindings": [
{
"topicPattern": "devices/+/in",
"agentId": "iot-agent",
"accountId": "default",
"replyTopic": "devices/reply"
}
],
"payload": {
"mode": "jsonTextOrPlain"
},
"auth": {
"enabled": true,
"allowAnonymous": false,
"users": [
{
"username": "device-01",
"passwordHash": "e3b0c44298fc1c149afbf4c8996fb924...",
"hashAlgorithm": "sha256",
"publishAllow": ["devices/device-01/in"],
"subscribeAllow": ["devices/device-01/out"],
"aclRules": [
{ "action": "inbound", "topicPattern": "devices/device-01/in", "effect": "allow", "accountId": "default" },
{ "action": "outbound", "topicPattern": "devices/device-01/out", "effect": "allow", "accountId": "default" },
{ "action": "publish", "topicPattern": "devices/+/admin/#", "effect": "deny" }
]
}
]
},
"tls": {
"enabled": false,
"port": 8883,
"certFile": "/etc/openclaw/certs/server.pem",
"keyFile": "/etc/openclaw/certs/server.key",
"caFile": "/etc/openclaw/certs/ca.pem"
},
"limits": {
"maxPayloadBytes": 1048576
},
"session": {
"maxExpirySeconds": 86400,
"persistentAcrossReconnect": true
},
"qos0": {
"mailboxSoftLimit": 200
},
"retain": {
"allowInboundRetain": true,
"outboundRetain": false
},
"audit": {
"enabled": true,
"format": "json"
},
"will": {
"allow": true,
"allowedTopicPatterns": ["devices/+/will"]
}
}
}
,
"session": {
"dmScope": "per-channel-peer"
}
}🧭 Topic Rules
- Standard inbound:
openclaw/agent/<agentId>/in - Standard outbound:
openclaw/agent/<agentId>/out - Explicit mapping: configured by
topicBindings.topicPattern
Priority:
topicBindingsmatch- Standard inbound parsing
- Drop message when neither matches
🧪 Testing
Unit tests
npm testIntegration test client
npm run test:clientscripts/test-client.ts will:
- connect to broker (default
mqtt://127.0.0.1:1883) - subscribe multiple reply topics
- publish JSON payload and plain text payload
- fail on timeout when no reply is received
Environment variables:
MQTT_BROKER_URLMQTT_CLIENT_IDMQTT_TEST_SUBSCRIBE_TOPICS— comma-separated list (overrides default reply topics)MQTT_TEST_PUBLISH_CASES— optional JSON array of{ "topic": "...", "payload": "..." }(overrides default publish cases)MQTT_TEST_TOPIC_JSONMQTT_TEST_TOPIC_PLAINMQTT_TEST_REPLY_TOPICMQTT_TEST_REPLY_TOPIC_2MQTT_TEST_TIMEOUT_MS
🤖 GitHub Actions
| Workflow | Trigger | Purpose |
| --- | --- | --- |
| .github/workflows/ci.yml | Push / PR to main or master | Install, typecheck, build, test, upload dist/ |
| .github/workflows/release.yml | Tag v* / manual dispatch | Build, test, publish npm package |
📦 Publishing
- Package:
@partme.ai/openclaw-mqtt - Required secret:
NPM_TOKEN - Release guide:
RELEASING.md
Tag release example:
npm version patch
git push origin main --follow-tags📁 Project Structure
openclaw-mqtt/
├── src/
│ ├── index.ts # defineChannelPluginEntry + registerFull (HTTP)
│ ├── setup-entry.ts # defineSetupPluginEntry(mqttPlugin) — openclaw.setupEntry
│ ├── mqtt-plugin.ts # ChannelPlugin
│ ├── gateway-mqtt.ts # Gateway startAccount / broker lifecycle
│ ├── outbound.ts # ChannelOutboundAdapter
│ ├── inbound.ts # dispatch to OpenClaw runtime
│ ├── broker.ts # Aedes TCP server
│ ├── topic-router.ts
│ ├── session-mapper.ts
│ ├── mqtt-config.ts
│ ├── runtime.ts
│ └── openclaw-peer.d.ts # types when openclaw is not installed locally
├── scripts/
│ └── test-client.ts
├── openclaw.plugin.json
├── package.json
└── README.md / README_CN.mdOpenClaw documentation
Official docs for plugins, the SDK, and this channel’s building blocks:
Plugins
Building plugins
- Building plugins
- SDK — Channel plugins (this package is a channel plugin)
- SDK — Provider plugins
- SDK — Migration
SDK reference
- SDK overview
- SDK entry points (
defineChannelPluginEntry,registerFull, etc.) - SDK runtime
- SDK setup
- SDK testing
- Manifest (
openclaw.plugin.json,package.jsonopenclawfield) - Architecture
❓ FAQ
Does this plugin require an external MQTT broker?
No. It embeds aedes and starts a local broker service.
How is payload parsed?
Default mode is jsonTextOrPlain: parse JSON.text first, otherwise use raw text.
How do I bind one topic to one agent?
Use topicBindings with topicPattern and agentId; optionally set replyTopic.
📄 License
MIT
