chirpstack-mcp-server
An MCP server for ChirpStack v4. It lets an AI agent (Claude Code, Claude Desktop, or any MCP client) manage a LoRaWAN network and — the part that matters while you are building a device application — debug devices live: queue a downlink, watch the uplinks and events as they arrive, iterate a payload codec, and inspect link quality, all from the coding session.
The server talks to ChirpStack's native gRPC API with a single API key. It carries no device- or vendor-specific logic.

Real, unedited Claude Code session (2.5× speed, only the ChirpStack MCP tools): the agent reads the device profile's codec, spots the disconnected-probe sentinel in a raw uplink, deploys a fix with profile_set_codec, then waits for the device's next uplink with wait_for_event and confirms the decoded object.
Install
uvx chirpstack-mcp-server # run directly (needs uv: https://docs.astral.sh/uv/)
# or
pip install chirpstack-mcp-server
Configure
| Variable | Required | Default | Meaning |
|---|---|---|---|
CHIRPSTACK_SERVER | yes | — | host:port of the ChirpStack API (the web-UI port, usually 8080) |
CHIRPSTACK_API_KEY | yes | — | API key from ChirpStack → API keys (tenant or global admin) |
CHIRPSTACK_TOOLSETS | no | devices, debug, applications, profiles, gateways | comma-separated toolsets, or all |
CHIRPSTACK_TLS | no | false | use TLS instead of plain HTTP/2 |
CHIRPSTACK_TRANSPORT | no | stdio | stdio or streamable-http |
CHIRPSTACK_HTTP_PORT | no | 8000 | port for streamable-http (bound to 127.0.0.1) |
Claude Code
claude mcp add chirpstack -e CHIRPSTACK_SERVER=192.168.1.10:8080 -e CHIRPSTACK_API_KEY=eyJ... -- uvx chirpstack-mcp-server
Claude Desktop / generic MCP config
{
"mcpServers": {
"chirpstack": {
"command": "uvx",
"args": ["chirpstack-mcp-server"],
"env": {
"CHIRPSTACK_SERVER": "192.168.1.10:8080",
"CHIRPSTACK_API_KEY": "eyJ..."
}
}
}
}
Toolsets
Tools are grouped so an agent only sees what it needs. Names are <toolset>_<verb>.
| Toolset | Default | Tools |
|---|---|---|
devices | yes | list, get, create, update, delete, set_keys, activate, deactivate, flush_dev_nonces, enqueue, queue_get, queue_flush, metrics |
debug | yes | server_info, capture_start, capture_read, capture_stop, capture_list, wait_for_event, device_recent_events |
applications | yes | list, get, create, update, delete, list_device_tags |
profiles | yes | list, get, create, update, delete, profile_set_codec, list_vendors, list_adr_algorithms |
gateways | yes | list, get, create, update, delete, metrics |
multicast | no | group CRUD, add/remove device, enqueue, queue_list, queue_flush |
fuota | no | deployment CRUD, start, add/remove/list devices, list_jobs |
integrations | no | integration_list/get/set/delete — one generic set for all ten ChirpStack integration kinds; integration_get redacts stored credentials unless include_secrets=true |
tenants | no | tenant CRUD, tenant users, API keys |
relay | no | relay devices and relay gateways |
Enable more with CHIRPSTACK_TOOLSETS=devices,debug,profiles,multicast or CHIRPSTACK_TOOLSETS=all.
server_info is the first call an agent should make to check the connection and the API key;
its chirpstack_version/regions fields may come back null/empty since ChirpStack only serves
those to a logged-in user session, never to an API key.
Live debugging
ChirpStack keeps the last ~10 events per device and streams new ones. The debug toolset
turns that into something an agent can use between tool calls:
capture_start(target, kind)opens a background stream (eventsorframesfor a device,gateway_framesfor a gateway) into a 500-item ring buffer and returns asession_id.device_enqueue(dev_eui, f_port, data_hex=...)queues the downlink.capture_read(session_id, since_seq)returns everything that arrived since the last read — decoded uplinks (f_port,f_cnt,data_hex, codecobject, per-gatewayrssi/snr),ack/txackfor the downlink,logentries when something went wrong.capture_stop(session_id)when done. Idle sessions expire after 30 minutes.
For quick looks: wait_for_event(dev_eui, timeout_s) blocks up to 60 s for the next live event —
it only returns events newer than the moment it was called, never the history ChirpStack replays;
device_recent_events(dev_eui) returns that history without keeping a session.
Class A devices only receive a downlink after their next uplink; Class C devices get it right away.
Security notes
- The API key is read from the environment and never appears in tool output.
device_gethides root keys unless asked withinclude_keys=true. device_get/multicast_gethide session keys unlessinclude_keys=true.integration_getredacts stored credentials unlessinclude_secrets=true.<redacted>is reserved:integration_set/multicast_updatekeep the stored value wherever it appears (so an edited_getresult can be handed straight back), andintegration_set/multicast_createrefuse it when there is nothing to keep.- Plain HTTP/2 (h2c) is the default because ChirpStack's API port is plain by default. Plain
h2c sends the API key as a cleartext bearer token on the wire; use it only on a trusted LAN,
and set
CHIRPSTACK_TLS=true(behind a TLS-terminating proxy that speaks gRPC) or a VPN elsewhere. streamable-httphas no authentication of its own and binds to127.0.0.1. Do not expose it on a public interface.- The HTTP transport validates
Host/Originheaders (DNS-rebinding protection), so a web page in the operator's browser cannot open an MCP session against the loopback listener. - Destructive tools are annotated (
destructiveHint) so MCP clients can ask before running them. - Enabling the
tenantstoolset lets the agent mint API keys;api_key_createreturns the new token once, in its result.
Development
uv sync
uv run pytest # unit tests
uv run ruff check . && uv run pyright
tests/integration/up.sh # throwaway ChirpStack in Docker + API key
set -a; . .integration/env; set +a
uv run pytest -m integration
tests/integration/down.sh
Design notes live in docs/design.md.
License
MIT © Oldřich Švéda