Skip to main content
Glama
oliveres

chirpstack-mcp-server

by oliveres
README.md
# chirpstack-mcp-server

<!-- mcp-name: io.github.oliveres/chirpstack-mcp-server -->

An [MCP](https://modelcontextprotocol.io) server for [ChirpStack](https://www.chirpstack.io) 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.

![Claude Code fixes a payload decoder and verifies it on the next live uplink](docs/demo.gif)

*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

```bash
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

```bash
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

```json
{
  "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:

1. `capture_start(target, kind)` opens a background stream (`events` or `frames` for a device,
   `gateway_frames` for a gateway) into a 500-item ring buffer and returns a `session_id`.
2. `device_enqueue(dev_eui, f_port, data_hex=...)` queues the downlink.
3. `capture_read(session_id, since_seq)` returns everything that arrived since the last read —
   decoded uplinks (`f_port`, `f_cnt`, `data_hex`, codec `object`, per-gateway `rssi`/`snr`),
   `ack`/`txack` for the downlink, `log` entries when something went wrong.
4. `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_get`
  hides root keys unless asked with `include_keys=true`.
- `device_get`/`multicast_get` hide session keys unless `include_keys=true`.
- `integration_get` redacts stored credentials unless `include_secrets=true`.
- `<redacted>` is reserved: `integration_set`/`multicast_update` keep the stored value
  wherever it appears (so an edited `_get` result can be handed straight back), and
  `integration_set`/`multicast_create` refuse 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-http` has no authentication of its own and binds to `127.0.0.1`. Do not expose it
  on a public interface.
- The HTTP transport validates `Host`/`Origin` headers (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 `tenants` toolset lets the agent mint API keys; `api_key_create` returns the new
  token once, in its result.

## Development

```bash
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

TDQS

A3.5/5.0

Scored across 40 tools

Disambiguation5/5

Each tool maps to a distinct resource/action: CRUD for devices, applications, profiles, and gateways are clearly separated, and session/queue/capture operations have unique names. Where two tools could overlap (wait_for_event vs device_recent_events, device_activate vs device_set_keys), the descriptions explicitly distinguish them.

Naming Consistency4/5

The overwhelming majority follow a consistent lowercase snake_case resource_action pattern (device_list, application_update, gateway_delete, capture_start). Minor deviations such as server_info and wait_for_event are understandable but break the strict verb_noun/resource_action convention enough to prevent a perfect score.

Tool Count2/5

40 tools is well beyond the heavy range and will impose a significant selection burden on an agent, even though the tools are organized by domain. The breadth is justified by ChirpStack's API surface, but for an MCP tool set it is too many to be considered well-scoped.

Completeness4/5

The set covers full CRUD/lifecycle for devices, applications, profiles, and gateways plus key device operations (activation, queueing, metrics, captures, events). Minor gaps exist such as tenant listing and per-item downlink queue deletion, but core workflows are not blocked.

Maintenance

ActivityMaintained
ResponsivenessNo issues