Metadata-Version: 2.5
Name: chirpstack-mcp-server
Version: 0.1.1
Summary: MCP server for ChirpStack v4 - manage and live-debug LoRaWAN devices from an AI agent
Project-URL: Homepage, https://github.com/oliveres/chirpstack-mcp-server
Project-URL: Repository, https://github.com/oliveres/chirpstack-mcp-server
Project-URL: Changelog, https://github.com/oliveres/chirpstack-mcp-server/blob/main/CHANGELOG.md
Author: Oldřich Švéda
License-Expression: MIT
License-File: LICENSE
Keywords: chirpstack,iot,lorawan,mcp,model-context-protocol
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: System :: Networking
Requires-Python: >=3.10
Requires-Dist: chirpstack-api<5,>=4.19
Requires-Dist: fastmcp<4,>=3.4
Description-Content-Type: text/markdown

# 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.

![An agent watches a LoRaWAN soil sensor report in, live](docs/demo.gif)

*Real recording: `device_recent_events` shows the backlog, `wait_for_event` returns the next uplink the moment ChirpStack receives it, decoded by the device profile's codec.*

## 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
