swiss-weather-mcp is a JSON-RPC 2.0 MCP server over stdio that exposes Swiss weather, pollen, radiation, UV, and air-quality data.
The server reads one JSON-RPC request per line from stdin and writes one JSON-RPC response per line to stdout.
Install from crates.io:
cargo install swiss-weather-mcpswiss-weather-mcp is a stdio MCP server. There is no separate config file, no required environment variables, and no command-line arguments to set up. Configure your MCP client to launch the installed binary directly.
Example MCP client config:
{
"mcpServers": {
"swiss-weather": {
"command": "swiss-weather-mcp"
}
}
}If you want to run it from source instead of an installed binary:
{
"mcpServers": {
"swiss-weather": {
"command": "cargo",
"args": [
"run",
"--quiet",
"--manifest-path",
"/absolute/path/to/swiss-weather-mcp/Cargo.toml"
]
}
}
}Repository and homepage:
License:
- Apache-2.0
This server currently exposes these tools:
list_weather_stationsget_precipitationget_wind_speed_10minget_wind_gusts_10minget_sunshineget_global_radiationlist_pollen_stationsget_pollen_measurementget_local_forecast
The implementation currently pulls data from these upstream sources:
- MeteoSwiss / geo.admin current measurement layers for precipitation, wind, sunshine, and global radiation
- MeteoSwiss OGD local forecasting dataset for point forecast data
- MeteoSwiss OGD pollen dataset for pollen station discovery
- Copernicus CAMS via ECMWF public WMS for UV daily maximum values
- FOEN NABEL current table for current pollutant measurements
- Transport is stdio only. There is no HTTP server.
air_quality_aqiis not implemented as a verified AQI feed. The server returns current pollutant measurements separately and marks AQI unavailable.air_quality_currentis mapped to the nearest official NABEL station using static station metadata embedded in the code. The pollutant values themselves are live.- A live
cargo runend-to-end check forget_local_forecastmay depend on external upstream latency.
Requirements:
- Rust stable
Common commands:
cargo build
cargo run
cargo test
cargo clippy --all-targets --all-features
cargo fmtTo run the server manually:
cargo runThen write JSON-RPC requests, one per line, to stdin.
The server supports:
initializenotifications/initializedtools/listtools/call
Protocol version:
2025-03-26
Request:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}Response shape:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": {
"listChanged": false
}
},
"serverInfo": {
"name": "swiss-weather-mcp",
"version": "0.1.0"
}
}
}Request:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_local_forecast",
"arguments": {
"point_query": "Bettwil",
"hours": 12
}
}
}Tool results are returned as MCP content blocks containing pretty-printed JSON text:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "{\n ... tool JSON ...\n}"
}
]
}
}Lists MeteoSwiss current-weather stations based on the latest 10-minute precipitation layer.
Arguments:
- none
Returns:
source_url: upstream JSON file URLupdated_at: source timestampstation_countstations: array of:idstation_namecoordinates_lv95.eastcoordinates_lv95.north
Returns precipitation measurements from official MeteoSwiss current layers.
Arguments:
interval: one of10min,1h,24h,48hstation_id: optional station filter
Returns:
source_urllayertitleupdated_atrecord_countrecords
Returns the MeteoSwiss 10-minute mean wind speed layer.
Arguments:
station_id: optional station filter
Returns the same top-level shape as get_precipitation.
Returns the MeteoSwiss 10-minute maximum 1-second wind gust layer.
Arguments:
station_id: optional station filter
Returns the same top-level shape as get_precipitation.
Returns sunshine duration measurements.
Arguments:
interval: one of10min,1dstation_id: optional station filter
Returns the same top-level shape as get_precipitation.
Returns global radiation measurements.
Arguments:
interval: one of10min,1dstation_id: optional station filter
Returns the same top-level shape as get_precipitation.
Lists MeteoSwiss pollen stations from the pollen OGD dataset.
Arguments:
- none
Returns:
source_urlstation_countstations: array of:idstation_name
Returns the latest hourly measurement layer for a specific pollen species.
Arguments:
species: one ofalder,ash,beech,birch,grass,hazel,oakstation_id: optional station filter
Returns the same top-level shape as get_precipitation.
Notes:
- The pollen layers use a missing-value sentinel (
99999) which is converted tonull.
Returns the latest local forecast for a point, with daily summary rows, hourly rows, computed summary blocks, UV, current pollen, and current air quality.
Arguments:
point_query: point ID, station abbreviation, postal code, or point namehours: optional integer from1to96, default24
Top-level return shape:
source_collectionissued_atpointdaily_parametershourly_parametersdaily_summary_counthourly_breakdown_countdaily_summaryhourly_breakdownsummaryunsupported
These tools return the same general shape:
get_precipitationget_wind_speed_10minget_wind_gusts_10minget_sunshineget_global_radiationget_pollen_measurement
Top-level fields:
source_urllayertitleupdated_atrecord_countrecords
Each records item contains:
station_idstation_namevalueunitreference_timestampaltitudemeasurement_heightcoordinates_lv95wind_direction_degreeswind_direction_radian- optional
value_relative
Notes:
- Some fields may be
nulldepending on the source layer. station_idmatching accepts exact station ID, exact station name, or substring match on station name.
get_local_forecast resolves point_query in this order:
- exact
point_id - exact
station_abbr - exact
postal_code - exact
point_name - partial match against
point_name,station_abbr, orpostal_code
Ambiguous partial matches return an error.
The selected point object contains:
point_idpoint_namepoint_typestation_abbrpostal_codealtitude_maslcoordinates_wgs84.latcoordinates_wgs84.loncoordinates_lv95.eastcoordinates_lv95.north
Each item contains:
parameter_codefield_namelabelunit
The current hourly parameter set is:
temperature_2m_cprecipitation_hourly_mmprecipitation_probability_3h_percentwind_direction_degreeswind_speed_kmhwind_gust_kmhweather_icon_codelow_cloud_covermedium_cloud_coverhigh_cloud_coversunshine_duration_minutesglobal_radiation_w_m2diffuse_radiation_w_m2
Each item contains the same metadata fields as hourly_parameters.
The current daily parameter set is:
temperature_min_ctemperature_max_cprecipitation_total_mmprecipitation_total_10_percentile_mmprecipitation_total_90_percentile_mmweather_icon_code
An array of timeline rows. Each row contains:
forecast_time- zero or more hourly fields from the parameter set above
The server only includes values that were present in the source series for that timestamp.
An array of daily rows. Each row contains:
forecast_date- zero or more daily fields from the daily parameter set above
summary is a derived rollup built from hourly_breakdown plus external UV, pollen, and air-quality lookups.
Fields:
temperature_2m_cprecipitation_hourly_mmprecipitation_probability_3h_percentwind_speed_kmhwind_gust_kmhsunshine_duration_minutesglobal_radiation_w_m2diffuse_radiation_w_m2cloud_cover_fractionuv_indexpollen_currentair_quality_currentair_quality_aqi
These fields use:
{
"min": 0.0,
"max": 0.0,
"mean": 0.0
}Used for:
temperature_2m_cprecipitation_probability_3h_percentwind_speed_kmhwind_gust_kmhglobal_radiation_w_m2diffuse_radiation_w_m2cloud_cover_fraction.lowcloud_cover_fraction.mediumcloud_cover_fraction.high
These fields use:
{
"total": 0.0,
"max": 0.0
}Used for:
precipitation_hourly_mmsunshine_duration_minutes
Current behavior:
- Source: Copernicus CAMS via ECMWF public WMS
- Uses the first up to
3daily forecast dates
Available shape:
{
"available": true,
"source": {
"provider": "Copernicus CAMS",
"service": "ECMWF public WMS",
"layer": "composition_uvindex_daily_max",
"url": "https://eccharts.ecmwf.int/wms/"
},
"daily_max_summary": {
"min": 0.0,
"max": 0.0,
"mean": 0.0
},
"daily_max_forecast": [
{
"forecast_date": "20260420",
"time_utc": "2026-04-20T00:00:00Z",
"value": 4.1,
"nearest_grid_point": {
"latitude": 47.2,
"longitude": 8.4,
"distance_km": 14.6
}
}
]
}Unavailable shape:
{
"available": false,
"reason": "Copernicus CAMS UV lookup is unavailable for this point or time window. ..."
}Current behavior:
- Source: FOEN NABEL current table
- The server chooses the nearest official NABEL station using static station metadata and the forecast point LV95 coordinates
Available shape:
{
"available": true,
"reported_at": "Data from: 19.04.2026 23:00",
"source": {
"provider": "FOEN NABEL",
"service": "Current situation table",
"url": "https://bafu.meteotest.ch/nabel/tables/show/english"
},
"nearest_station": {
"id": "BER",
"station_name": "Bern-Bollwerk",
"site_type": "Urban, traffic",
"distance_km": 42.7,
"coordinates_lv95": {
"east": 2600170.0,
"north": 1199990.0
}
},
"measurements_ug_m3": {
"o3": 68.0,
"o3_daily_max": 88.0,
"no2": 10.0,
"pm10": 13.0,
"pm2_5": 6.0
}
}Unavailable shape:
{
"available": false,
"reason": "Official FOEN NABEL current pollutant measurements are unavailable for this point or time window. ..."
}Current behavior:
- Source: MeteoSwiss official pollen station catalog and hourly pollen measurement layers
- The server chooses the nearest official pollen station using the forecast point WGS84 coordinates
Available shape:
{
"available": true,
"source": {
"provider": "MeteoSwiss",
"service": "Official pollen measurement layers"
},
"nearest_station": {
"id": "PBE",
"station_name": "Bern",
"distance_km": 14.2,
"coordinates_wgs84": {
"lat": 46.948,
"lon": 7.4474
}
},
"reference_timestamp": "2026-04-20T10:00:00Z",
"measurements": {
"alder": {
"value": 12.0,
"unit": "grains/m3",
"reference_timestamp": "2026-04-20T10:00:00Z"
},
"grass": {
"value": 31.0,
"unit": "grains/m3",
"reference_timestamp": "2026-04-20T10:00:00Z"
}
}
}Unavailable shape:
{
"available": false,
"reason": "Official MeteoSwiss pollen measurements are unavailable for this point or time window. ..."
}Current behavior:
- There is no verified AQI feed integrated
- The field is currently returned as unavailable
Shape:
{
"available": false,
"reason": "No verified official AQI feed has been integrated. Current official NABEL pollutant measurements are available separately in summary.air_quality_current."
}unsupported is an object containing only the unavailable derived sub-features.
Possible keys:
uv_indexpollen_currentair_quality_currentair_quality_aqi
Each value is the corresponding reason string.
If all derived features are available, unsupported is an empty object.
Common failure cases:
- missing required tool argument
- unsupported interval
- unsupported pollen species
- unknown tool name
- unsupported method
- no matching station or point
- ambiguous point query
- upstream request failure
- upstream decode failure
For tool calls, application errors are returned inside the tool content as:
Error: ...
JSON-RPC argument and method errors are returned as MCP error objects, for example:
-32601for unknown method or unknown tool-32602for missing required arguments
- Formatting:
cargo fmt - Typecheck/build validation:
cargo check - Tests:
cargo test - Linting:
cargo clippy --all-targets --all-features
The current implementation has unit tests for:
- MCP request/response serialization
- CSV parsing
- forecast asset parsing
- forecast summary derivation
- CAMS UV parsing
- compact CAMS date conversion
- NABEL table parsing
- station-name normalization
- ambiguous point resolution
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_local_forecast","arguments":{"point_query":"Bettwil","hours":12}}}
src/main.rs: request handling, tool implementations, forecast parsing, UV and air-quality integration, testssrc/mcp_engine.rs: MCP protocol types, tool metadata, shared CSV and source typesCargo.toml: dependencies and crate metadataREADME.md: package readme and user-facing project documentation