netatmo-mcp/README.md
mineracks 9efe05eca0 Initial commit: Netatmo MCP + REST bridge for DC environmental monitoring
Deployed on integr8 VM (pve01/700) at environment.mineracks.com.
Polls Netatmo every 5 min, stores in SQLite, exposes via FastAPI REST + MCP.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 12:14:18 +00:00

162 lines
6.1 KiB
Markdown

# netatmo-mcp
Self-hosted bridge that exposes a Netatmo Weather Station to Claude (via MCP)
**and** to anything else (via REST), with a local SQLite store for history and
threshold-based alerts. Designed to run as a Docker container on Proxmox.
## What you get
One process serving three things on port `8088`:
| Path | What it is |
|-------------|-----------------------------------------------------------------|
| `/mcp/` | MCP server (Streamable HTTP transport). Add to Claude as a connector. |
| `/api/...` | REST API — same data, for n8n / Grafana / cron / curl. |
| `/health` | Unauthenticated liveness + poller status. |
A background poller hits Netatmo every 5 minutes (configurable), stores every
new reading in SQLite, and evaluates any alerts you've configured.
## Capabilities
- **Current readings** for every station and module (`current_readings` tool / `GET /api/current`)
- **Local historical queries** at native ~5min resolution (`history` tool / `GET /api/history`)
- **Direct Netatmo historical pulls** for longer windows or pre-aggregated scales — 1hour, 1day, 1week, 1month (`history_from_netatmo` tool)
- **Threshold alerts** on any metric — e.g. CO₂ > 1000 ppm, temp > 28 °C (`create_alert` tool / `POST /api/alerts`)
- **Multi-station / multi-module** — handled natively; everything is keyed by `module_id`
## One-time Netatmo setup
1. Go to **https://dev.netatmo.com/apps** and create an app. Note the
**client_id** and **client_secret**.
2. On the app page, scroll to **Token generator**. Tick the `read_station`
scope and click "Generate token". Copy the **refresh token** (you don't
need the access token — it only lasts 3 hours).
3. Make sure your Netatmo account has the data centre station added.
> **Important:** Netatmo rotates the refresh token on every refresh call. This
> service persists the rotated token to `/data/tokens.json` so it survives
> restarts. Don't share that file or the refresh chain breaks.
## Deploy on Proxmox
On your Ubuntu VM with Docker:
```bash
git clone <this repo> netatmo-mcp && cd netatmo-mcp
cp .env.example .env
# fill in NETATMO_CLIENT_ID, NETATMO_CLIENT_SECRET, NETATMO_REFRESH_TOKEN,
# and NETATMO_MCP_API_KEY=$(openssl rand -hex 32)
docker compose up -d --build
docker compose logs -f
```
You should see `Poller starting` and, within a minute or so, `Refreshing
Netatmo access token` followed by the first poll. Hit `http://<vm>:8088/health`
to confirm.
After ~10 minutes there'll be a few rows in the DB and you can query history.
### Putting it behind your reverse proxy
For Claude (web/app) connectors you need a **public HTTPS URL**. If you're on
Caddy:
```
netatmo.mineracks.ai {
reverse_proxy <vm-ip>:8088
}
```
Or stick it on Cloudflare Tunnel — same deal. Bearer auth is enforced at the
app, so exposing it is fine as long as your `NETATMO_MCP_API_KEY` is strong.
## Connecting Claude
In **claude.ai → Settings → Connectors → Add custom connector**:
- **Name:** Netatmo Datacentre
- **URL:** `https://netatmo.mineracks.ai/mcp/`
- **Auth:** Bearer token → paste your `NETATMO_MCP_API_KEY`
For **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`
on macOS) or Claude Code (`~/.claude/settings.json`):
```json
{
"mcpServers": {
"netatmo": {
"transport": {
"type": "streamable-http",
"url": "https://netatmo.mineracks.ai/mcp/",
"headers": {
"Authorization": "Bearer YOUR_NETATMO_MCP_API_KEY"
}
}
}
}
}
```
Then ask Claude: "What's the current CO2 in the data centre?" or "Plot the
last 48 hours of temperature for the rack module."
## Using the REST API
```bash
KEY=your_api_key
BASE=https://netatmo.mineracks.ai
# All modules (find your module_id here)
curl -H "Authorization: Bearer $KEY" $BASE/api/modules
# Current snapshot
curl -H "Authorization: Bearer $KEY" $BASE/api/current
# Last 24h of CO2 for a specific module
curl -H "Authorization: Bearer $KEY" \
"$BASE/api/history?module_id=02:00:00:aa:bb:cc&metric=CO2&hours=24"
# Create an alert: CO2 > 1000 ppm on the rack module
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"name":"dc_co2_high","module_id":"02:00:00:aa:bb:cc","metric":"CO2","op":">","threshold":1000}' \
$BASE/api/alerts
```
## Metrics reference
The fields exposed depend on module type:
| Module | Metrics |
|------------------|----------------------------------------------------------------|
| Indoor (NAMain / NAModule4) | `Temperature`, `Humidity`, `CO2`, `Noise`, `Pressure` |
| Outdoor (NAModule1) | `Temperature`, `Humidity` |
| Rain (NAModule3) | `Rain`, `sum_rain_1`, `sum_rain_24` |
| Wind (NAModule2) | `WindStrength`, `WindAngle`, `GustStrength`, `GustAngle` |
For a data centre you almost certainly care about indoor `Temperature`,
`Humidity`, and `CO2` — the noise sensor is also a surprisingly useful
"is the AC running differently?" canary.
## Files / state
- `./data/netatmo.db` — SQLite history + alerts. Back it up.
- `./data/tokens.json` — rotating refresh token. **Do not delete** without
generating a new bootstrap refresh token.
## Notes & gotchas
- **Netatmo rate limits:** 50 requests / 10 seconds and 500 / hour per user.
At 5-minute poll interval you use ~12/hour, so you're fine — but don't
drop the interval below 60s.
- **First poll is slow** because it does a token refresh. Subsequent polls
reuse the access token until it nears expiry.
- **History granularity:** the local store records whatever Netatmo returns,
which is roughly one point per metric per ~5 min. For longer windows
(weeks/months) use `history_from_netatmo` with `scale="1day"` to get
Netatmo's pre-aggregated values.
- **Alerts are evaluated each poll**, so worst-case detection latency =
`POLL_INTERVAL_SEC`. To wire alerts to email/Slack/ntfy, point an n8n
workflow at `GET /api/alert-events`, or extend `Poller._tick` to POST
to a webhook.