Sign inSign up

edudele/cumulusmx-mcp

By edudele

•Updated less than a minute ago

Full Model Context Protocol (MCP) server for CumulusMX weather stations.

Image
1

1.2K

edudele/cumulusmx-mcp repository overview

⁠cumulusmx-mcp

Build and push to Docker Hub Docker Pulls Docker Image Size

MCP server that exposes a CumulusMX⁠ weather station to any LLM via the Model Context Protocol⁠.

Built with FastMCP⁠ and deployed as a Docker container. Designed to sit behind a reverse proxy (e.g. Pangolin⁠) so your station is never exposed directly.


⁠Features

  • Current conditions — temperature, humidity, pressure, wind, rain, solar/UV in real time.
  • Today / yesterday — highs, lows and times for all variables.
  • Historical data & Analytics — daily summaries from dayfile across all years, flexible column alias filtering (date, mintemp, rain, etc.) and aggregated statistical summaries (summary_only).
  • Agronomy & climate tools — climatology (monthly normals), frost_dates (last spring / first autumn frost, frost-free period), degree_days (growing degree days), wind_rose (16-point rose) and daily_briefing (live conditions + yesterday in context + month vs normal + streaks in progress).
  • Full CumulusMX API coverage — read and write: every settings section (get_settings / update_settings with merge, dry_run preview and no blind writes), dedicated MySQL and MQTT tools (get_/update_mysql_settings, mysql_table, get_/update_mqtt_settings, mysql_cache), history corrections (edit_dayfile, edit_datalog, edit_recent_data, edit_records), diary import/export, interval_data / daily_data, records_query, custom charts, WebSocket live_sample and HTTP station input. See API coverage⁠.
  • Safety net for the write tools — every write is audited (log + optional JSON-Lines file, secrets masked), the previous state is backed up automatically and can be undone with restore_backup, and an optional read-only token lets others query the station without being able to change anything (it does not even see the write tools). See Security model⁠.
  • Alerts and reports — weather_alerts (frost, wind, rain, heat, UV, pressure drop...) and period_report (a shareable Markdown weekly / monthly / yearly report compared with the station's own normals).
  • Observable — GET /metrics exposes Prometheus metrics: tool calls and latency, writes by outcome, requests to CumulusMX, whether the station is reachable, stored backups. See Monitoring⁠.
  • Runs anywhere — Docker, pip/uvx, or stdio for local clients such as Claude Desktop. Pinned dependencies, tested on FastMCP 3.x and 4.x and Python 3.10 - 3.13.
  • Resilient — GET requests to CumulusMX are retried with backoff on transient failures; timeouts are configurable.
  • Agent-ready — the server sends usage instructions to MCP clients (which tool for which question, units, decimal conventions, write-tool etiquette).
  • Climate analytics — count_days, find_streaks, anomaly and data_quality answer "how many days above 35 °C?", "longest dry spell?", "was this July warmer than normal?" and "can I trust the history?" from the station's own data, with no extra CumulusMX endpoints.
  • Historical rankings & Annual records — competition rankings (1, 2, 2, 4) for extremes (heat, cold, rain, wind gusts) and yearly records summaries.
  • MCP Resources — declarative weather:// resources for direct context retrieval in MCP clients.
  • MCP Prompts — ready-to-use guided prompts for daily analysis, extreme events, annual summaries, and period comparisons.
  • Records — all-time, this year, this month, by calendar month or custom date range.
  • Graphs — recent and daily graph data series with statistical downsampling (graph_summary).
  • NOAA reports — monthly and annual plain-text reports.
  • Webtags — query any CumulusMX webtag by name or evaluate webtag templates.
  • Health check — unauthenticated /health endpoint for Docker and monitoring orchestrators.
  • Bearer token auth — all MCP endpoints require Authorization: Bearer ***.
  • Prefix stripping — configurable middleware for reverse proxies that don't rewrite paths.
  • Agent skill — ready-to-use skills/cumulusmx-weather⁠ that teaches an agent when and how to use these tools (see Agent skill⁠).
  • MCP Tool Annotations — full readOnlyHint and destructiveHint annotations for client permission grouping (read-only vs write/delete in Claude and other clients).

⁠Quick start

⁠Docker

The image is multi-architecture (linux/amd64 and linux/arm64), so it runs the same on a PC, a NAS or a Raspberry Pi.

docker run -d \
  -e MCP_AUTH_TOKEN=your-secret-token \
  -e CUMULUSMX_URL=http://your-cumulusmx-host:8998 \
  -p 8000:8000 \
  edudele/cumulusmx-mcp
⁠Python package (pip / uvx) and stdio
pip install cumulusmx-mcp      # or: uvx cumulusmx-mcp   (development version: pip install git+https://github.com/edudele/cumulusmx-mcp)
CUMULUSMX_URL=http://your-cumulusmx-host:8998 MCP_AUTH_TOKEN=secret cumulusmx-mcp           # HTTP on :8000
cumulusmx-mcp --transport stdio                                                          # local MCP client (no HTTP, no token)

Claude Desktop (or any stdio client):

{
  "mcpServers": {
    "cumulusmx": {
      "command": "cumulusmx-mcp",
      "args": ["--transport", "stdio"],
      "env": { "CUMULUSMX_URL": "http://192.168.1.10:8998" }
    }
  }
}

In stdio mode the process is started by your client and nobody else can reach it, so there is no Bearer token.

⁠Docker Compose
services:
  cumulusmx-mcp:
    image: edudele/cumulusmx-mcp
    environment:
      MCP_AUTH_TOKEN: your-secret-token
      CUMULUSMX_URL: http://cumulusmx:8998   # adjust to your CumulusMX host
      PORT: 8000
      MCP_STRIP_PREFIX: ""                   # set if behind a prefix-routing proxy
      CUMULUS_DATA_START_YEAR: 2023          # start year for historical rankings
      CUMULUS_FROST_THRESHOLD: 0.0           # threshold in °C for frost day counts
      CUMULUS_CACHE_TTL: 600                 # dayfile cache TTL in seconds
    ports:
      - "8000:8000"
    restart: unless-stopped

See .env.example⁠ for all variables.


⁠Environment variables

VariableRequiredDefaultDescription
MCP_AUTH_TOKEN✅—Bearer token clients must send
CUMULUSMX_URLhttp://cumulusmx:8998Base URL of the CumulusMX HTTP API
PORT8000Port the server listens on
MCP_STRIP_PREFIX/mcp-resPath prefix to strip (set to "" to disable)
CUMULUS_DATA_START_YEAR2023First year of station data for caching and default rankings range
CUMULUS_FROST_THRESHOLD0.0Temperature threshold (°C) to count frost days
CUMULUS_CACHE_TTL600In-memory cache TTL in seconds for current year dayfile data
CUMULUS_TIMEZONE—IANA time zone of the station (e.g. Europe/Madrid). Needed only to give readable dates (2025-01-31 14:00) to interval_data / daily_data; Unix seconds always work
CUMULUS_MASK_SECRETSfalsetrue masks passwords and keys (pass, password, phpsecret, API keys...) in get_settings, get_mysql_settings and get_mqtt_settings. Off by default: the server returns the configuration as CumulusMX does
MCP_METRICStrueServes Prometheus metrics at GET /metrics (same Bearer token as /mcp). false removes the endpoint
MCP_AUTH_TOKEN_READONLY—Second Bearer token that can query but not write: write tools refuse to run with it and settings are returned with passwords masked
CUMULUS_BACKUP_DIRsystem temp dirWhere the pre-write backups are kept (none disables them). Mount a volume (the image uses /data/backups) to keep them across restarts
CUMULUS_BACKUP_KEEP200Maximum number of backups kept (the oldest are deleted)
CUMULUS_AUDIT_FILE—Also append every write (JSON Lines) to this file; the audit trail always goes to the log (logger cumulusmx-mcp.audit). The image uses /data/audit.jsonl
CUMULUS_TIMEOUT30Timeout in seconds for each request to CumulusMX
CUMULUS_RETRIES2Retries (exponential backoff) of GET requests on connection errors, timeouts and HTTP 502/504. POST requests are never retried
CUMULUS_USER—HTTP Basic Auth username for CumulusMX API (optional)
CUMULUS_PASSWORD—HTTP Basic Auth password for CumulusMX API (optional)
ENABLE_WRITE_TOOLStruePublish the 29 tools that modify data or station state. With false they are not even listed to the client (read-only server)
LOG_LEVELinfoLog verbosity: info, debug, warning, error

Security tip: write tools are enabled by default. If you only need to query your station, set ENABLE_WRITE_TOOLS=false: the 29 write tools are then not published at all (clients only see the 60 read-only tools), and they would refuse to run even if called directly.


⁠Security model

LayerWhat it does
Bearer token (MCP_AUTH_TOKEN)Required on every request except /health. Full access.
Read-only token (MCP_AUTH_TOKEN_READONLY)Optional second token. It can use every read tool; tools/list does not include the write tools for it, any attempt to run one is refused ("este token es de solo lectura") and audited, and settings come back with passwords masked.
ENABLE_WRITE_TOOLS=falseThe 29 write tools are not published at all.
AuditEach write is recorded (tool, token scope, arguments with secrets masked, result ok / fallo / denegado / error) in the log and, with CUMULUS_AUDIT_FILE, in a JSON-Lines file. get_audit_log shows the recent ones.
Backups and undoBefore changing a settings section, a day-file line, an interval-log line, a recent-data record, a MySQL-cache statement or a record, the previous state is saved (CUMULUS_BACKUP_DIR, files 0600). list_backups / get_backup / restore_backup undo it, and a restore is itself undoable.
MetricsGET /metrics (Prometheus text format) needs a Bearer token, unlike /health. It exposes counters and durations only (tool names and outcomes, requests to CumulusMX), never arguments or station data.
No blind writesSettings updates read the current section first and refuse to write if they cannot (the old classic updaters used to overwrite with only the fragment). dry_run=true previews the diff.
SecretsPasswords are returned by the settings tools unless CUMULUS_MASK_SECRETS=true or the read-only token is used. They never appear in the audit log. Backups can contain them, hence the 0600 permissions.
No outbound callsThe server only talks to CumulusMX. FastMCP's update check against PyPI is disabled by default.

Keep the server behind HTTPS (a reverse proxy) when it is reachable from outside your network.


⁠Health check

An unauthenticated health check endpoint is exposed at http://localhost:8000/health (or /health on your host/proxy). It returns:

{"status": "healthy", "service": "cumulusmx-mcp", "version": "1.8.1"}

The Docker container includes a native HEALTHCHECK directive verifying this endpoint with Python's standard urllib.request.


⁠Reverse proxy

The server is designed to work with proxies that use path-prefix routing (e.g. Pangolin with clip prefix / rewrite mode). Set MCP_STRIP_PREFIX to the prefix your proxy uses so the server sees clean paths.

Example with Pangolin routing https://your-domain.com/mcp-res → http://cumulusmx-mcp:8000:

MCP_STRIP_PREFIX=/mcp-res

⁠MCP endpoint

https://your-domain.com/mcp-res/mcp

Add it to your MCP client (e.g. Hermes, Claude Desktop) with:

{
  "mcpServers": {
    "cumulusmx": {
      "url": "https://your-domain.com/mcp-res/mcp",
      "headers": {
        "Authorization": "Bearer your-secret-token"
      }
    }
  }
}

⁠Available tools

ToolDescription
pingFast check that the MCP server responds
server_statusMCP server and CumulusMX version diagnostic
station_infoVersion, units, alarms, time format, display settings
current_dataFull current conditions (JSON)
recent_dataRecent log entries (last hours)
today_yesterdayToday/yesterday highs & lows by variable (temp, hum, rain, wind, pressure, solar)
today_allAll today/yesterday variables in one single call
graph_dataRecent graph series (~24 h)
daily_graph_dataDaily graph series (all history)
graph_summaryStatistical summary of a graph series (min, max, avg, last, optional downsampled series)
dayfilePaginated daily history (dayfile.txt)
dayfile_rangeDaily history filtered by date range, with column alias support and summary_only mode
rankingsHistorical competition rankings (1, 2, 2, 4) of extremes (temp_min, temp_max, lluvia_total, racha_max, temp_media, horas_sol)
year_recordsAnnual records and stats (extremes, rain, frost/warm days) for a given year
count_daysCount days matching a condition (e.g. temp_max >= 35) with a per-year breakdown and optional date list
find_streaksLongest runs of consecutive days matching a condition (dry spells, heatwaves, frost runs) and any run in progress
anomalyCompare a period with the station's own climatology (same calendar window in other years): normal, difference, percentile, rank
data_qualityAudit the daily history: missing days, days without temperature, incoherent values
climatologyMonthly normals (Jan-Dec): mean temperatures, rain, rain days, sun hours, extremes and record years
frost_datesPer year: last spring frost, first autumn frost, frost days and frost-free period; average dates
degree_daysGrowing degree days (base/upper temperature, average or modified method) per month and year
wind_roseDays per dominant wind direction (16 sectors) and peak gust per gust direction
daily_briefingOne-call daily overview: now, yesterday vs same month in other years, month-to-date anomaly, streaks in progress
logfileInterval log for a date range (standard or extra sensors)
recordsRecords by scope (alltime, thismonth, thisyear, month) and variable
records_allAll record categories in one single call
records_periodRecords calculated for a custom date range
noaa_reportNOAA monthly or annual plain-text report
monthly_summaryMonth-by-month climate summary for a given year
climate_summaryMulti-day or multi-year aggregate climate summary
compare_periodsSide-by-side comparison of two date ranges
snapshotQuick station snapshot (temperatures, extremes, wind, rain, sun/moon, status)
get_tagsQuery CumulusMX webtags by name; numbers use a decimal point (rc format, optional)
render_templateEvaluate custom webtag templates with formatting modifiers
diaryWeather diary entries or summary
simple_reportCumulusMX summary reports over the whole history: temperature, rainfall, wind run, sunshine, ET, dry/wet days
extra_sensorsCurrent values of extra sensors: soil, leaf wetness, AirLink air quality, lightning, CO2, laser depth, snow...
api_getLow-level whitelisted read-only access to CumulusMX GET endpoints
⁠System & Maintenance (/utils/*)
ToolDescription
reloaddayfileReload dayfile.txt on CumulusMX and invalidate local in-memory dayfile cache
clear_alarmsReset a specific alarm (e.g. BatteryLow, Sensor) or all alarms (alarm="all")
clear_error_logClear the accumulated error log in CumulusMX
trigger_ftpTrigger an immediate FTP / web upload for daily graphs, NOAA reports, graphs, or logfiles
purgemysqlPurge pending/failed command queue and MySQL cache
⁠Reports & Upload (/genreports/, /uploadreport/)
ToolDescription
generate_reportsGenerate annual or monthly NOAA reports on the server (year or month)
upload_reportsUpload annual or monthly NOAA reports via configured FTP/web transfer
⁠Diagnostics (/data/errorlog.json)
ToolDescription
get_error_logRetrieve system warning and error log entries
⁠Alerts, reports and safety net
ToolDescription
weather_alertsActive alerts right now: frost (today / in progress / night risk), strong wind, intense rain, heat, high UV, fast pressure drop, very dry hot air, station without data. Thresholds can be overridden
period_reportShareable Markdown report for a week, month or year: temperatures, rain and streaks, wind, pressure, sun and a comparison with the station's own normals
list_backups / get_backupBackups taken before each write (settings, day-file and interval-log lines, recent data, MySQL cache, records)
restore_backupUndo any edit from its backup: settings, day-file / interval-log lines, recent data, MySQL-cache statements and records (dry_run=true by default; the restore is undoable too). Deletions cannot be re-inserted through CumulusMX's API; their backup keeps the content
get_audit_logRecent writes done through this server
⁠Configuration, MySQL & MQTT
ToolDescription
get_settingsRead any of the 18 settings sections: program, station, internet, thirdparty, extrasensors, extrawebfiles, calibration, language, noaa, mysql, alarms, useralarms, wizard, customlogs_interval, customlogs_daily, display, httpfiles, mqtt
update_settingsChange any section: reads the current one, deep-merges only your fields (or replace=true), sends it in the format the endpoint expects. dry_run=true shows the diff without applying. Never writes if the current settings cannot be read
get_mysql_settings / update_mysql_settingsMySQL connection, monthly/dayfile/realtime tables, retention and custom commands
mysql_tableCreate or update the monthly, dayfile or realtime MySQL table
mysql_cache / edit_mysql_cacheInspect and edit/delete the pending SQL commands that failed to reach MySQL
get_mqtt_settings / update_mqtt_settingsMQTT broker, credentials, TLS, topics and templates
update_noaa_settingsNOAA report settings (counterpart of get_noaa_settings)
⁠Data access, queries and real time
ToolDescription
interval_dataInterval logs for a range with only the fields you ask for (main log and extra-sensor log), no paging
daily_dataDay-file fields by their official CumulusMX names for a range
records_queryDay-file query computed by CumulusMX (min/max/sum/avg/count, optional condition and period)
graph_selectCustom "Select-a-Chart" / "Select-a-Period" series for a date range
live_sampleListen to the /ws/ WebSocket for a few seconds and return the live messages
records_edit_dataRecord-editing screen data (current values, or candidates from the day file / logs)
diary_exportWhole diary as CSV
get_current_conditions_textCurrent-conditions text
⁠History corrections, diary and device input (write)
ToolDescription
edit_dayfileEdit or delete one dayfile.txt line (date-checked; updates SQLite and MySQL if enabled)
edit_datalogEdit or delete one line of a monthly interval log (or extra-sensor log)
edit_recent_dataEdit or delete a row of the recent-data table
edit_recordsCorrect an all-time, monthly, this-month or this-year record (sends the value with the station's decimal separator)
diary_upload / diary_automateImport diary entries from CSV / choose the laser snow sensor that fills the diary
station_inputSend a reading to the HTTP station endpoints (ecowitt, json, wunderground, ambient...) to test a station setup
generate_reportsAlso accepts report_type="all" to generate every missing report

Back up before editing history. The edit_* tools rewrite dayfile.txt, log files and record files.

⁠Data Editing (/edit/*)
ToolDescription
edit_current_conditionsUpdate current weather condition text description
edit_rain_todayAdjust today's rain accumulation and midnight rain values
edit_diaryCreate or update a weather diary entry with observations and snow/storm flags
delete_diaryDelete a weather diary entry for a specific date
⁠Configuration (/settings/, /setsettings/)
ToolDescription
get_station_settingsGet station configuration (Alpaca schema)
update_station_settingsUpdate station configuration settings (partial merge supported)
get_calibration_settingsGet sensor calibration offsets and multipliers
update_calibration_settingsUpdate sensor calibration factors and offsets
get_alarm_settingsGet alarm limits and notification configuration
update_alarm_settingsUpdate alarm thresholds and notification settings
get_noaa_settingsGet NOAA report generation configuration
test_emailSend a test email to verify alarm SMTP settings

⁠API coverage

Checked against the Api.cs controllers of CumulusMX 5.2.0 (217 named endpoints):

AreaStatus
Data, logs, diary, graphs, records, today/yesterday, extra sensors, tags, reports, infoCovered (read)
Settings: all 18 sections, read and writeCovered (get_settings, update_settings + dedicated MySQL/MQTT/NOAA tools)
MySQL tables (create*sql, update*sql) and failed-command cacheCovered
Utilities (reload day file, purge MySQL, FTP now, clear alarms/error log), report generation and uploadCovered
Data editor: day file, interval logs, recent data, MySQL cache, records, rain today, current conditions, diaryCovered
intervaldata / dailydata, records/query, selectachart / selectaperiod, WebSocket /ws/Covered (interval_data, daily_data, records_query, graph_select, live_sample)
HTTP station input (/station/ecowitt, json, wunderground, ambient...)Covered (station_input)
Web dashboard pages, static assets and localisation files (/, /js, /json, /ai2)Not exposed: they are the CumulusMX web UI, not data

The write operations were verified against the CumulusMX source and with mocked tests; they are not exercised against a live station by the test suite. Settings and editors take effect on the real station, so use dry_run for settings and keep backups before editing history.


⁠MCP Resources

FastMCP declarative resources available for direct context reading:

Resource URIDescription
weather://currentReal-time current weather conditions
weather://todaySummary of today and yesterday extremes and values
weather://stationStation info, version and units configuration
weather://records/alltimeAll-time historical records of the station

⁠MCP Prompts

Ready-to-use guided prompts for AI assistants:

PromptArgumentsDescription
resumen_diariodate (optional)Daily weather report and anomaly analysis
analisis_extremosvariable, topHistorical extreme events comparison and ranking analysis
comparativa_periodosstart_a, end_a, start_b, end_bPeriod A vs Period B climate comparison
resumen_anualyearComprehensive annual climate summary and milestone review
resumen_mensualyear, monthMonth report versus the station's own climatology
heladas_y_cultivobase_temp (optional)Frost dates and growing degree days for garden planning
auditoria_datos—Data-quality audit of the history before analysing it

⁠Agent skill

The repository ships an agent skill in skills/cumulusmx-weather/⁠. The MCP server provides the tools; the skill tells the agent when to use each one (for example `r

Tag summary

Content type

Image

Digest

sha256:a021a4445…

Size

69.2 MB

Last updated

less than a minute ago

docker pull edudele/cumulusmx-mcp