rms-mcp
by GlassOnTin
README.md
# rms-mcp
[](LICENSE)
A resident [MCP](https://modelcontextprotocol.io) server for an **RMS / Global Meteor
Network** station. It lets Claude Code (or any MCP client) inspect the station over an
SSH/WireGuard tunnel — "how did last night go?", "is capture healthy?", "show me the
detections" — without SSHing in and re-deriving the answer each time.
It runs **beside** RMS, not inside it: a small pure-Python daemon in its own virtualenv
that reads RMS's own outputs (FTPdetectinfo, the recalibrated platepars, the logs) and
resolves every path from the live `.config`, so a station on an NVMe (`data_dir:
/mnt/nvme/RMS_data`) or with renamed subdirs Just Works. No `import RMS`, so it can't be
broken by — and can't break — the capture environment.
## Tools
**Read** (safe inspection):
| Tool | Returns |
|---|---|
| `rms_status` | capture/service states, disk, current capture night, `ml_filter`, last upload |
| `rms_list_nights` | recent archived nights, newest first (sorted by date, not name) |
| `rms_night_summary` | meteor count, FOV, recalibration status, upload bundle — for a night, or the latest |
| `rms_detections` | per-detection kinematics (frame span, peak magnitude, meteor-vs-streak hint) |
| `rms_logs` | tail the current RMS log, optionally filtered |
**Write** (gated — see Scoped access):
| Tool | Does | Scope |
|---|---|---|
| `rms_config_get` | read one `.config` value | `config.<key>` |
| `rms_config_set` | change one `.config` value — backs up first, preserves comments, never appends, does **not** restart capture | `config.<key>` |
| `rms_reprocess` | launch RMS reprocessing of a captured night under `~/vRMS` — refuses the live night, won't stack jobs (`dry_run` to preview) | `reprocess` |
## Security & scoped access
Loopback-bound (`127.0.0.1:8740`), every request carries a bearer token that maps to a
**paired client**, every call is authorised against that client's standing policy (deny by
default) and audited to `~/.config/rms-mcp/audit.log`. Reach it from another machine over an
SSH `-L` tunnel or WireGuard — never expose the port directly.
Grant management is CLI-only (no MCP tool can alter policy, so nothing can self-escalate):
```sh
rms-mcp pair phone # prints a token once
rms-mcp allow phone rms_status,rms_night_summary,rms_detections '' 90d # read-only
rms-mcp allow ops rms_config_set,rms_reprocess 'config.ml_filter reprocess' 30d
rms-mcp policies # show grants
rms-mcp unpair phone
```
A refusal names the exact `allow` line that would grant it. The single bearer minted on
first run is adopted as a full-access `local` client, so an already-connected client keeps
working while you add narrower ones.
## Run
```sh
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python -m rms_mcp # serve
.venv/bin/python -m rms_mcp token # print the bearer token
```
As a persistent user service:
```sh
systemctl --user enable --now rms-mcp
loginctl enable-linger "$USER" # survive logout/reboot
```
## Connect a client
```sh
# from the client machine, tunnel to the station's loopback port:
ssh -N -f -L 8740:127.0.0.1:8740 ian@meteor
claude mcp add --transport http rms http://127.0.0.1:8740/mcp \
--header "Authorization: Bearer $(ssh ian@meteor '~/source/rms-mcp/.venv/bin/python -m rms_mcp token')"
```
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues