OpenClaw Integration¶
Two independent integrations, useful separately:
- Ask about the network — register the MCP server with OpenClaw so you can ask "any microcuts today?" from Telegram and get a real answer. This works against a stock OpenClaw gateway.
- Be told about the network — have the alerter push incidents into a chat.
This works against a stock gateway too, via its
POST /tools/invokeendpoint; it needs the Gateway token and themessagetool permitted by tool policy. See Alert delivery.
The short way¶
If OpenClaw runs on the same machine as the stack, the mechanical part of section 1 is one command:
smoking-pi openclaw
It generates MCP_API_TOKEN if there is none, records the mcp profile in
the env file, starts the server, checks that the port refuses an
unauthenticated request, registers smokeping with OpenClaw and installs the
skill. Then, after you start a new chat session:
smoking-pi openclaw --check
which asks the agent a question and reads the MCP server's log — not the answer. See Verify with evidence for why that distinction is the whole point.
On a package install, run both with sudo: the env file is root's. The
OpenClaw steps still run as you, the user who ran sudo, because the
gateway, its ~/.openclaw and usually its openclaw command (under
~/.nvm, which sudo drops from PATH) are yours. The command looks for
openclaw on PATH, then in nvm's default Node version, then in
~/.local/bin, ~/.npm-global/bin, ~/.volta/bin and ~/.bun/bin, and
says which one it used.
The rest of this page is what those steps do, which is worth reading when something does not work, and is the only path if OpenClaw is on another machine (remote-openclaw.md first, then section 1 by hand against the tunnelled port).
All commands below use placeholders. Nothing machine-specific is committed to this repository — generate your own tokens and substitute your own ids.
Everything here assumes OpenClaw and Smoking Pi share a machine. If OpenClaw lives elsewhere, read remote-openclaw.md first: it connects the two loopbacks with a tunnel so that this page then applies unchanged.
1. MCP server registration¶
Secure the port first¶
The MCP server exposes the whole tool surface, including mutations (add and
remove targets, restart SmokePing). Compose binds it to 127.0.0.1:8090, so
the boundary is the host — but any process on that host can reach it. Give it
its own credential:
openssl rand -hex 32 # put the result in editions/pro/.env
# editions/pro/.env
MCP_API_TOKEN=<your-mcp-token>
sudo smoking-pi enable mcp
With MCP_API_TOKEN set, every HTTP request needs
Authorization: Bearer <token>; without it the transport is unauthenticated
exactly as before. The stdio transport is never gated — the caller already had
to be able to spawn the process.
Verify both paths:
curl -s -o /dev/null -w '%{http_code}\n' -XPOST http://127.0.0.1:8090/mcp
# 401
curl -s -o /dev/null -w '%{http_code}\n' -XPOST http://127.0.0.1:8090/mcp \
-H "Authorization: Bearer $MCP_API_TOKEN"
# not 401
Register with OpenClaw¶
openclaw mcp set smokeping '{
"url": "http://127.0.0.1:8090/mcp",
"transport": "streamable-http",
"headers": {"Authorization": "Bearer <your-mcp-token>"},
"connectionTimeoutMs": 5000,
"requestTimeoutMs": 30000
}'
openclaw mcp doctor smokeping # static config problems
openclaw mcp probe smokeping # connect and list capabilities
openclaw mcp reload # pick up the new config
The full tool surface — including mutations — is available by default, on the assumption that OpenClaw's agent confirms destructive actions with you in chat before running them. To expose only read tools instead:
openclaw mcp tools smokeping --exclude 'add_target,remove_target,restart_smokeping'
Use openclaw mcp show smokeping to check what ended up in the config, and
openclaw mcp status for transport state without connecting.
Install the skill — do not skip this¶
Registering the MCP server is not enough. An agent that also has shell
access will answer "how is my internet?" by running ping and curl, because
that is the obvious move and nothing has told it otherwise. It will sound
confident and it will be describing one instant, not your recorded history.
The skill is what redirects it. Symptom of a missing skill: the agent gives
you live speed-test numbers and never mentions your targets.
./shared/scripts/install-openclaw-skill.sh # copy + next steps
./shared/scripts/install-openclaw-skill.sh --reload # and reload the gateway
With --reload, it then asks the gateway whether it answers
(openclaw gateway status --require-rpc). If none answers (not running,
or it refused the probe), it says so and exits 3: the skill is installed,
and the gateway reads it when it starts. smoking-pi openclaw reports that case in its step 5 instead of
"reloaded the gateway".
Re-run it after any change to the skill. A stale copy is the failure mode
worth guarding against, because it is invisible — the agent keeps answering,
just in the old shape, and the install looks like it silently did nothing.
The skill carries the answering guide, which changes in most releases, so
smoking-pi upgrade does this for you: when the installed skill differs
from the new release's, it refreshes it, reloads the gateway, and says so
(Upgrades). When it cannot, it says to run
smoking-pi openclaw (with sudo on a package install), which installs
the current skill as its step 5. To check by hand:
./shared/scripts/install-openclaw-skill.sh --check # exits non-zero if stale
It backs up an existing skill to SKILL.md.bak before overwriting, in case
you tuned it in place. The skill is the answering guide every assistant gets
from the server (shared/modules/common/guide.py) plus a short OpenClaw
header; the one value worth tuning is the trigger description:, in
openclaw-header.md — then rebuild with
python3 shared/scripts/build-openclaw-skill.py. The manual
equivalent, if you prefer it:
mkdir -p ~/.openclaw/skills/smokeping-monitoring
cp examples/openclaw/smokeping-monitoring/SKILL.md \
~/.openclaw/skills/smokeping-monitoring/
openclaw skills list # confirm smokeping-monitoring is listed
The skill's description is the part that matters most: OpenClaw uses it to
decide whether to load the skill at all, so it is written to trigger on the
words people actually use ("how is my connection", "cómo está mi conexión",
the host name) and to say explicitly what it is not for. If the agent still
reaches for the shell, widen that description rather than the body.
The skill is written in English and does not make the answers English. It
carries a report template — traffic lights, bold sections, one fact per
bullet, both links — and directs the agent to answer in whatever language the
question arrived in, translating the headings and keeping what is not prose:
target names (database keys — a translated CloudflareDNS cannot be looked up
or muted), numbers with their units, the lights, and the URLs. Translating the
skill file itself would pin every reply to that one language; leave it alone
and ask in yours.
Re-copy the file after any change to it, and start a new chat session: skills, like the tool set, are cached per session.
Reload after registering — the gateway caches the tool set¶
Registering an MCP server does not reach a gateway that is already running. The Codex runtime fingerprints the server set per thread, so a long-lived gateway and existing chat sessions keep the tool list they started with. This cost four days of a working server that no agent could see:
openclaw mcp reload # drop cached MCP runtimes
systemctl --user restart openclaw-gateway # or your service manager
# then start a NEW chat session — existing threads keep the stale set
Symptom of skipping it: the agent says the tools "aren't exposed to it", or
quietly answers with live pings while openclaw mcp probe reports the server
healthy. Both can be true at once — a separate poller reads the config fresh,
so probe and doctor pass while agent sessions get nothing.
Verify with evidence, not with the answer¶
Do not judge this by reading the agent's reply. A well-primed agent produces a fluent, accurate-sounding answer — correct target names, sensible caveats — entirely from its shell, while the MCP server sits untouched. That false positive is exactly how the breakage above went unnoticed.
Ask the question, then check the server:
openclaw agent --agent main --session-key smokeping-check \
-m "how has my connection been in the last 6 hours?"
cd editions/pro
docker compose logs mcp-server --since 5m | grep 'tool='
A working integration logs the calls it made:
tool=system_status args=- -> ok in 83ms
tool=get_latency_stats args=hours=6 -> 19 stats in 40ms
tool=get_microcut_stats args=hours=6 -> 1 stats in 42ms
No tool= line means it is not wired, no matter how good the answer reads.
That is the only check that distinguishes the two cases.
The web admin shows the same evidence without a terminal. Step 4 of the
welcome tour (the dashboard's Welcome tour button reopens it) reads the
MCP server's tool= lines through config-manager's GET /assistant. It
says Not set up, Stopped, Not used yet, or Connected with the last
tool called and when. Container logs start over when the container is
recreated, so Not used yet means no call since the server last started,
and the step shows that time.
2. Alert delivery¶
What a stock gateway actually provides¶
The gateway listens on 127.0.0.1:18789 and multiplexes both WebSocket and
HTTP on that port. The HTTP surface includes POST /tools/invoke, which is
always enabled, plus the OpenAI-compatible /v1/* routes. Verified on
2026.7.1-2.
This document previously claimed the gateway was WebSocket-only and served no HTTP at all. That was wrong, and it cost the alerting engine a week of being unable to deliver. The claim came from probing
/hooks/agent— a path that exists on no OpenClaw build — getting a 404, and generalising from one missing route to the entire surface. The correct conclusion from a single 404 is that one path is absent, not that a protocol is.
openclaw hooksreally does manage internal agent lifecycle hooks (boot-md,command-logger, …) and has nothing to do with inbound HTTP — that part was right, and is what made the wrong inference look plausible.
NOTIFY_MODE=openclaw sends by invoking the message tool:
curl -s -X POST http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $(python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.openclaw/openclaw.json')))['gateway']['auth']['token'])")" \
-H 'Content-Type: application/json' \
-d '{"name":"message","args":{"action":"send","channel":"telegram",
"to":"telegram:<chat-id>","message":"hello from the alerter"}}'
A successful call returns {"ok":true,"result":{...,"messageId":"1544"}}.
Two requirements beyond the token:
- Tool policy must permit
message. The endpoint is gated by Gateway auth and tool policy. A filtered tool answers HTTP 404 with{"ok":false,"error":{"message":"Tool not available: message"}}— allow it viatools.allowinopenclaw.json.
That 404 is the same status a missing route returns, which is why the
preflight reads the body rather than the status: Tool not available means
the gateway is reachable and the path is right, and only the policy is in
the way. Diagnosing it as a bad OPENCLAW_HOOK_PATH sends you to the wrong
file.
2. OPENCLAW_TO must be the OpenClaw address form, e.g.
telegram:123456789. Find it with openclaw gateway call sessions-list and
read deliveryContext.to.
The alerter runs network_mode: host, so the loopback default needs no extra
plumbing.
Verifying delivery, not guessing at it¶
The alerter probes /tools/invoke at startup by invoking message with no
arguments. A reachable, permitted tool rejects that with its own validation
error — which is the proof we want: endpoint answered, token accepted, tool not
filtered. Healthy output:
INFO alerter.notifier: Delivery preflight: http://127.0.0.1:18789/tools/invoke
reachable, 'message' tool permitted (HTTP 200)
The preflight distinguishes three failures that would otherwise look identical:
401 (wrong Gateway token), Tool not available (policy blocks the tool), and
a connection error (gateway down).
Note the endpoint answers HTTP 200 with {"ok": false} when a tool fails.
A notifier that trusts the status code alone counts every refused send as a
delivered alert, so the alerter inspects the body and retries on ok: false.
The portable alternative¶
NOTIFY_MODE=webhook posts a documented JSON payload to any endpoint, with an
optional bearer token. It works with ntfy, a Slack bridge, Home Assistant, or
the bridge script above, and it has no OpenClaw version dependency:
NOTIFY_MODE=webhook
ALERT_WEBHOOK_URL=https://your-endpoint.example/hook
ALERT_WEBHOOK_TOKEN=<optional-bearer-token>
See docs/alerting.md for the payload shape and the rule set.
Security notes¶
- Separate tokens.
MCP_API_TOKEN,CONFIG_API_TOKEN, any alert-delivery token, and OpenClaw's owngateway.auth.tokenshould all be different values. Never reuse the gateway token for an application integration: it is the credential for the gateway's full control surface, and a token that leaks from one place should not compromise the others. - Loopback only. Both the MCP port and the OpenClaw gateway bind to
127.0.0.1. Keep it that way; reach them remotely over Tailscale or an SSH tunnel rather than by binding to0.0.0.0. - Nothing real in git.
.envis gitignored and every value in.env.templateships empty. The skill inexamples/uses placeholders. Keep your actual tokens, chat ids, and hostnames out of commits. - Mutations are real. The MCP tool surface can change monitoring config and
restart SmokePing. If the chat channel is shared, or you would rather not
rely on the agent's confirmation step, use the
--excludefilter above.