MCP Server for Swiss weather & climate data – MeteoSwiss SwissMetNet, ICON forecasts, school planning
- ✓Open-source license (MIT)
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Topics declared
claude mcp add meteoswiss-mcp -- python -m meteoswiss-mcp{
"mcpServers": {
"meteoswiss-mcp": {
"command": "python",
"args": ["-m", "meteoswiss-mcp"],
"env": {
"MCP_HOST": "<mcp_host>",
"MCP_ALLOW_ANY_HOST": "<mcp_allow_any_host>",
"MCP_API_KEY": "<mcp_api_key>"
}
}
}
}MCP_HOSTMCP_ALLOW_ANY_HOSTMCP_API_KEYMCP Servers overview
# 🌦️ meteoswiss-mcp
[](https://github.com/malkreide/meteoswiss-mcp/actions/workflows/ci.yml)
[](https://pypi.org/project/meteoswiss-mcp/)
[](https://pypi.org/project/meteoswiss-mcp/)
[](LICENSE)
[](https://github.com/malkreide/swiss-public-data-mcp)
**MCP server for Swiss weather and climate data from MeteoSwiss.**
Connects AI models to the SwissMetNet measurement network (160+ stations, 10-minute interval), MeteoSwiss ICON-CH1/CH2-EPS forecasts and climate normals 1991–2020. Part of the [swiss-public-data-mcp](https://github.com/malkreide/swiss-public-data-mcp) portfolio.
🇩🇪 [Deutsche Version](README.de.md)
---
## Demo query (anchor example)
<img src="assets/demo.png" width="720" alt="Demo: Claude asks about sports-day suitability → meteo_school_check tool call → structured weather traffic-light response">
```
How suitable is next Wednesday for the sports day at Leutschenbach school?
```
→ `meteo_school_check(location="Zürich Oerlikon", activity="Sporttag")` returns a 🟢/🟡/🔴 traffic light for each day of the coming week — straight from the MeteoSwiss ICON model.
**Combined with [swiss-environment-mcp](https://github.com/malkreide/swiss-environment-mcp):**
```
How were air quality and weather at Leutschenbach school yesterday?
```
→ `meteo_current(station='REH')` + `env_nabel_current(station='ZUE')` = a complete environmental picture.
→ [More use cases by audience](EXAMPLES.md) →
---
## Tools (6)
| Tool | Description | Data source |
|------|-------------|-------------|
| `meteo_stations` | List SwissMetNet stations (filterable by canton) | Embedded |
| `meteo_current` | Current 10-min observations for a station | BGDI STAC API |
| `meteo_forecast` | 1–16 day forecast for a place or coordinates | Open-Meteo / MeteoSwiss ICON |
| `meteo_school_check` | 🟢/🟡/🔴 traffic light for outdoor school events | Open-Meteo / MeteoSwiss ICON |
| `meteo_climate_normals` | Monthly climate normals 1991–2020 | Embedded (KLO, SMA, BER, LUG, GVE) |
| `meteo_warnings` | Active official weather warnings (storm, thunderstorm, heat, forest fire, …) — nationwide, by canton, or by PLZ | MeteoSwiss App-API + opendata.swiss |
### Tool annotations (MCP hints)
All tools carry explicit [MCP annotations](https://modelcontextprotocol.io/specification/draft/server/tools#tool-annotations) — relevant for the client approval UI and for the LLM's safety decisions.
| Tool | `readOnlyHint` | `destructiveHint` | `idempotentHint` | `openWorldHint` |
|------|---|---|---|---|
| `meteo_stations` | ✅ | ✗ | ✅ | ✗ (curated list) |
| `meteo_current` | ✅ | ✗ | ✗ (live data) | ✅ (upstream STAC) |
| `meteo_forecast` | ✅ | ✗ | ✗ (live data) | ✅ (upstream Open-Meteo) |
| `meteo_school_check` | ✅ | ✗ | ✗ (live data) | ✅ (geocoding + forecast) |
| `meteo_climate_normals` | ✅ | ✗ | ✅ | ✗ (embedded normals) |
| `meteo_warnings` | ✅ | ✗ | ✗ (live data) | ✅ (MeteoSwiss App-API) |
**Read rules**: all 6 tools are `readOnly + non-destructive` — the server fundamentally cannot write or delete anything. `idempotentHint=False` marks tools that return different values depending on when they are called.
### MCP protocol version
| Aspect | Value |
|---|---|
| Tested spec versions | `2024-11-05`, `2025-03-26`, `2025-06-18` (via the `mcp[cli]` SDK) |
| FastMCP SDK version | see `pyproject.toml` → `mcp[cli]>=1.0.0` |
| Update policy | Dependabot watches `mcp[cli]`; spec bumps are documented in the CHANGELOG with a "Tool Definition Changes" marker |
→ Full roadmap & update strategy: [`docs/roadmap.md`](docs/roadmap.md)
---
## Quick start
### Claude Desktop
```json
{
"mcpServers": {
"meteoswiss": {
"command": "uvx",
"args": ["meteoswiss-mcp"]
}
}
}
```
### Claude Desktop (local development)
```json
{
"mcpServers": {
"meteoswiss": {
"command": "uv",
"args": ["run", "--directory", "/path/to/meteoswiss-mcp", "meteoswiss-mcp"]
}
}
}
```
### Cloud / Render.com (Streamable HTTP)
Configuration via ENV variables (the CLI flags `--http` / `--port N` still work as an override):
| Variable | Default | Meaning |
|---|---|---|
| `MCP_TRANSPORT` | `stdio` | `stdio` or `streamable-http` |
| `MCP_HOST` | `127.0.0.1` | Bind address — **never change locally** |
| `MCP_PORT` | `8000` | Port |
| `MCP_ALLOW_ANY_HOST` | _unset_ | Must be set to `1` to allow the server to bind to `0.0.0.0` (containers/cloud only) |
| `MCP_LOG_LEVEL` | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR` — structured JSON logs on stderr |
| `MCP_ALLOWED_ORIGINS` | _unset_ | Comma-separated list of allowed origins for CORS. Empty = CORS disabled (same-origin only). `Mcp-Session-Id` is exposed automatically. |
| `MCP_API_KEY` | _unset_ | If set: every request except `/health` requires `X-API-Key: <key>` or `Authorization: Bearer <key>`. Constant-time comparison. |
| `MCP_STATELESS_HTTP` | `0` | `1` enables FastMCP stateless mode → each HTTP request opens a new session. Prerequisite for multi-replica deploys without sticky sessions (SCALE-002/003). |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | _unset_ | If set + `pip install meteoswiss-mcp[otel]`: OpenTelemetry spans per tool call + automatic httpx instrumentation are sent as OTLP-HTTP to the collector. |
| `OTEL_SERVICE_NAME` | `meteoswiss_mcp` | Service name in the OTel resources |
| `MCP_CACHE_ENABLED` | `1` | `0` disables the TTL cache entirely (e.g. for end-to-end tests) |
| `MCP_CACHE_TTL_STAC` | `300` | TTL in seconds for STAC SMN observations (default 5 min) |
| `MCP_CACHE_TTL_OPEN_METEO` | `600` | TTL for ICON forecasts (default 10 min) |
| `MCP_CACHE_TTL_GEOCODING` | `3600` | TTL for geocoding lookups (default 1 h) |
| `MCP_CACHE_TTL_OPENDATA` | `3600` | TTL for the opendata.swiss catalogue (default 1 h) |
| `MCP_CACHE_TTL_WARNINGS` | `300` | TTL for warnings (MeteoSwiss App-API / structured override; default 5 min) |
| `MCP_CLIMATE_NORMALS_PATH` | _unset_ | Path to a JSON file with additional climate normals — see `data/climate-normals.example.json` |
| `MCP_WARNINGS_API_URL` | _unset_ | **Override** for the default MeteoSwiss App-API source: URL of a structured MeteoSwiss warnings API (e.g. the future OGD warnings REST endpoint). The host must be on the egress allow-list. Schema-tolerant (GeoJSON `features`, a `warnings` array or `items`). Unset → live App-API. |
| `MCP_CLIMATE_NORMALS_URL_TEMPLATE` | _unset_ | URL template for runtime lookup of climate normals (for stations without embedded or JSON values). Tokens: `{station}` (lowercase), `{STATION}` (uppercase), `{param}` (MeteoSwiss code `tre200m0`/`rre150m0`/`sre000m0`). Example: `https://data.geo.admin.ch/.../{station}/{param}.txt`. The host must be on the egress allow-list. |
```bash
# Local test (safe, loopback only)
MCP_TRANSPORT=streamable-http meteoswiss-mcp
# Container / Render
MCP_TRANSPORT=streamable-http MCP_HOST=0.0.0.0 MCP_ALLOW_ANY_HOST=1 meteoswiss-mcp
```
#### Docker / Render
The repo includes a production-ready **multi-stage Dockerfile** (non-root user, HEALTHCHECK) and a **`render.yaml`** blueprint:
```bash
# Build + test locally
docker build -t meteoswiss-mcp .
docker run --rm -p 8000:8000 meteoswiss-mcp
curl http://127.0.0.1:8000/health # → {"status":"ok","service":"meteoswiss-mcp"}
```
On Render: "New → Blueprint" → select the repo. Defaults (plan `starter`, Frankfurt, single instance) are set in `render.yaml`.
**Important:** `numInstances: 1` is set deliberately — sticky-session routing for multi-replica (audit SCALE-002/003) is not yet implemented.
#### Structured logging
All tool invocations, upstream failures and egress blocks are emitted as JSON events on `stderr` (stdio-transport safe). Example:
```json
{"tool": "meteo_forecast", "days": 7, "has_coords": false, "event": "tool_invoked", "level": "info", "timestamp": "2026-05-20T07:00:00Z"}
{"tool": "meteo_forecast", "endpoint": "geocoding", "error_type": "HTTPStatusError", "event": "upstream_failed", "level": "warning", "timestamp": "..."}
{"url": "https://evil.example.com/", "method": "GET", "reason": "host not in allow-list", "event": "egress_blocked", "level": "warning", "timestamp": "..."}
```
#### HTTP-mode security
- `MCP_HOST` deliberately defaults to `127.0.0.1` so that `--http` on a dev laptop is not accidentally exposed to the local subnet (audit finding SEC-016).
- All outgoing HTTP calls (including redirect follows) are validated against an allow-list: `data.geo.admin.ch`, `api.open-meteo.com`, `geocoding-api.open-meteo.com`, `opendata.swiss`. Other hosts and IP literals (in particular `169.254.169.254`, RFC1918) are rejected with `EgressBlocked` (SEC-004 / SEC-021).
- **CORS**: disabled by default (same-origin only). Browser clients (e.g. claude.ai web) need `MCP_ALLOWED_ORIGINS=<csv>` — the `Mcp-Session-Id` header is then automatically in `Access-Control-Expose-Headers` (SDK-004).
- **API-key auth**: disabled by default. In a production HTTP setup, always set `MCP_API_KEY=<random>` — requests without a valid `X-API-Key` or `Authorization: Bearer …` are rejected with 401 (SEC-009 / SEC-013). `/health` stays open for container health probes.
#### Example: production HTTP stack
```bash
# 32 bytes of randomness as the auth key
export MCP_API_KEY=$(python -c "import secrets; print(secrets.token_urlsafe(32))")
MCP_TRANSPORT=streamable-http \
MCP_HOST=0.0.0.0 \
MCP_ALLOW_ANY_HOST=1 \
MCP_ALLOWED_ORIGINS=https://app.example.com \
MCP_API_KEY="$MCP_API_KEY" \
meteoswiss-mcp
```
---
## Example queries
### School planning
```
Which days next week are suitable for a sports day in Zürich?
→ meteo_school_check(location="Zürich", activity="Sporttag")
What will the weather be at LeutschenWhat people ask about meteoswiss-mcp
What is malkreide/meteoswiss-mcp?
+
malkreide/meteoswiss-mcp is mcp servers for the Claude AI ecosystem. MCP Server for Swiss weather & climate data – MeteoSwiss SwissMetNet, ICON forecasts, school planning It has 0 GitHub stars and was last updated today.
How do I install meteoswiss-mcp?
+
You can install meteoswiss-mcp by cloning the repository (https://github.com/malkreide/meteoswiss-mcp) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.
Is malkreide/meteoswiss-mcp safe to use?
+
Our security agent has analyzed malkreide/meteoswiss-mcp and assigned a Trust Score of 87/100 (tier: Trusted). See the full breakdown of passed checks and flags on this page.
Who maintains malkreide/meteoswiss-mcp?
+
malkreide/meteoswiss-mcp is maintained by malkreide. The last recorded GitHub activity is from today, with 1 open issues.
Are there alternatives to meteoswiss-mcp?
+
Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.
Deploy meteoswiss-mcp to your cloud
Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.
Maintain this repo? Add a badge to your README
Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.
[](https://claudewave.com/repo/malkreide-meteoswiss-mcp)<a href="https://claudewave.com/repo/malkreide-meteoswiss-mcp"><img src="https://claudewave.com/api/badge/malkreide-meteoswiss-mcp" alt="Featured on ClaudeWave: malkreide/meteoswiss-mcp" width="320" height="64" /></a>More MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
The fastest path to AI-powered full stack observability, even for lean teams.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!