Releases: malkreide/meteoswiss-mcp
Release list
v0.6.1
[0.6.1] - 2026-08-02
Behoben
-
structloghatte keine Obergrenze, und der Index fuehrt bereits einen Major
oberhalb der Untergrenze. Deklariert warstructlog>=24.0.0; auf PyPI liegt
26.1.0. Das Artefakt aendert sich nicht — die Antwort des Resolvers auf
die naechste frische Installation schon, und genau so wurde
swiss-energy-mcp0.3.3 uninstallierbar, alsmcp2.0.0 das Modul entfernt
hat, das es importierte.Neu
structlog>=24.0.0,<27. Die Grenze ist gemessen, nicht geraten: dieses Paket installiert
und importiert heute gegenstructlog 26.1.0, die Obergrenze laesst also zu,
was nachweislich funktioniert, und stoppt nur den naechsten, unbekannten
Major.
Ein Abhaengigkeitsbereich erreicht die Nutzenden nur ueber ein neues
Release, daher der Versions-Bump. Am Code aendert sich nichts.
v0.6.0
[0.6.0] - 2026-07-30
Reparatur-Release. In 0.5.0 lieferten drei der sechs Tools nichts —
meteo_current für jede Station einen 404, meteo_forecast und
meteo_school_check gar keine Daten. Drei unabhängige Ursachen, zwei davon
Änderungen bei Datenquellen, eine ein Fehler im Server selbst. Alle drei sind
live gegen die echten APIs verifiziert.
Für Clients nicht breaking: Tool-Namen, Parameter und Rückgabetypen sind
unverändert. Einzig die Beschreibung von meteo_forecast wurde präzisiert
(siehe «Changed»), was tool-hashes.json ändert.
Behoben
-
Ortsnamen mit Zusatz waren gar nicht auflösbar
(#37). Die
Geocoding-API kennt nur einzelne Ortsnamen und liefert für
«Schulhaus Leutschenbach Zürich» nichts._geocode()schickte den vollen
String in beiden Versuchen — der «Fuzzy»-Retry liess nur die
Sprachrestriktion weg und kürzte die Anfrage nie. Damit scheiterten auch die
Beispiele, die die Tools selbst dokumentieren (meteo_forecast-Docstring und
dielocation-Beschreibung vonmeteo_school_check).Neu werden bei Misserfolg führende Tokens nach und nach weggelassen —
Schweizer Ortsangaben sind konventionellGattungswort… Ort Stadt, das
verallgemeinert also von spezifisch nach allgemein und endet bei der Stadt.
Zusätzlich wird jedes führende Token einzeln probiert, aber nur mit
Namensprüfung: «Leutschenbach» →Leutschenbachwird angenommen,
«Schulhaus» →Dübendorf / Schulhaus Wilverworfen. Ohne diese Prüfung
bekäme eine Zürcher Anfrage stillschweigend Wetter aus einer anderen
Gemeinde — schlimmer als der bisherige harte Fehler.Live verifiziert:
Anfrage Ergebnis Schulhaus Leutschenbach ZürichLeutschenbach, ZH (47.4175, 8.5648) Sportanlage Heerenschürli ZürichZurich, ZH (47.3667, 8.5500) Zürich/Bernunverändert exact, ein einziger Requestmatch_typekennt dafür neu"shortened"— Aufrufende sehen damit, dass die
Antwort allgemeiner ist als die Frage. Ein Volltreffer löst weiterhin genau
einen Request aus; gekürzt wird nur, wenn der volle String scheitert. -
meteo_forecastundmeteo_school_checklieferten gar nichts mehr
(#35). Open-Meteo hat
die provider-eigenen Pfade abgeschafft;/v1/meteoswissantwortet mit 404.
Modelle werden neu übermodels=auf/v1/forecastgewählt.Ein reiner URL-Tausch reicht nicht, denn keines der MeteoSwiss-Modelle
liefert, was die beiden Tools zusagen. Live gemessen für
meteoswiss_icon_seamless(ICON-CH1 + ICON-CH2):- Reichweite 5 Tage.
forecast_days=16liefert darüber hinaus nur
Nullwerte — das ist der ICON-CH2-Horizont, kein Parameterproblem.
meteo_forecastverspricht aber bis zu 16 Tage. - Kein UV-Index, auch nicht stündlich (0 von 72 Werten über 3 Tage).
Open-Meteo bezieht UV aus CAMS, nicht aus dem Modelloutput.
meteo_school_checkwarnt aber ab UV 6.
Der Server holt deshalb neu beide Modelle und mischt sie entlang der
Zeitachse: MeteoSwiss ICON gewinnt überall, wo es einen Wert hat,None
fällt aufbest_matchzurück. Dieselbe Regel erledigt die 5-Tage-Grenze und
die UV-Lücke. Gemischt wird bewusst über Zeitstempel statt Listenindizes —
der ICON-Block ist kürzer, und ein Indexversatz würde Werte still auf den
falschen Tag schieben.Die Herkunft steht in jeder Antwort, statt im Ungefähren zu bleiben: die
Markdown-Fussnote und die JSON-Feldermodell/modell_detailsweisen aus,
welche Tage aus MeteoSwiss ICON stammen, ab wannbest_matchübernimmt und
dass UV durchgehend von dort kommt. Fällt der ICON-Request aus, trägt
best_matchdie Antwort allein — und sagt das ebenfalls, statt einen
Totalausfall zu produzieren.Kein Test hätte das finden können: die Unit-Tests deckten ausschliesslich
Geocoding-Fehlerpfade ab, der erfolgreiche Prognosepfad war komplett
ungetestet. Neu 11 Tests für Merge-Logik (inklusive Zeitachsen-Versatz),
Provenance-Label, den vollen Hybrid-Pfad in Markdown und JSON, den
ICON-Ausfall und die UV-Herkunft inmeteo_school_check. - Reichweite 5 Tage.
Changed
-
meteo_forecast-Beschreibung präzisiert — sie nannte pauschal
«MeteoSwiss ICON-CH1/CH2-EPS» und verschwieg damit, dass Tage jenseits von 5
und der UV-Index ausbest_matchstammen.tool-hashes.jsonentsprechend neu
generiert (Rug-Pull-Signal, SEC-022). Für Clients nicht breaking: Parameter
und Rückgabetyp sind unverändert,daysakzeptiert weiterhin 1–16. -
meteo_currentlieferte für jede Station 404
(#33). Die STAC-Item-ID
ist der nackte Stationscode in Kleinschreibung (…/items/klo); der Server
stellte ihm die Collection-ID voran (…/items/ch.meteoschweiz.ogd-smn-klo).
Live gegen die BGDI-API verifiziert:/items/klo→ 200, die Präfix-Variante
und die Grossschreibung → je 404. Die URL wurde an drei Stellen dupliziert
(Fetch, Fehlermeldung, JSON-Provenance) — jetzt einmal in
_smn_stac_item_url().Beim Beheben kamen drei weitere Fehler zum Vorschein, die der 404 verdeckt
hatte — sie hätten nach dem URL-Fix plausibel aussehende, aber falsche Daten
geliefert:- Die Asset-Auswahl konnte nie greifen. Gesucht wurde nach
/now/im
Pfad, doch die Granularität steckt im Dateinamen
(ogd-smn_klo_t_now.csv); ein Verzeichnis/now/existiert nicht. Der
Fallback nahm daraufhin das erste CSV im Item —d_historical, also
Tageswerte ab 1980, ausgegeben als «aktuelle Beobachtung». Neu wird gezielt
_t_nowgewählt, ersatzweise_t_recent; gibt es beides nicht, ist das ein
Fehler statt eines stillen Griffs ins Archiv. - Der Zeitstempel war immer
–. Gelesen wurdetime/Date/datum, die
OGD-CSV führt aberreference_timestamp. - Die Zeile «Luftdruck (reduziert auf Meeresniveau)» blieb immer leer.
prestah0gibt es in der CSV nicht; der QNH-Wert heisstpp0qnhs0.
- Die Asset-Auswahl konnte nie greifen. Gesucht wurde nach
Tests
Alle drei Fehler oben hatten einen Test, der sie hätte finden müssen, aber so
formuliert war, dass auch der Fehlerfall ihn erfüllte. Das ist das eigentliche
Thema dieses Releases:
-
meteo_current(live): prüfte"KLO" in result or "Zürich" in result—
beides steht auch in der Fallback-Fehlermeldung. Der Test lief also durch
einen Totalausfall hindurch grün. Verlangt jetzt echte Messwerte. -
meteo_forecast/meteo_school_check: der erfolgreiche Prognosepfad war
komplett ungetestet, abgedeckt waren nur Geocoding-Fehlerpfade. Zudem mockten
die Tests die eigene Endpoint-Konstante — sie konnten das Abschalten upstream
gar nicht bemerken. -
_geocode(live): prüfte nur einen Koordinatenbereich (8.4–8.7), den
Dübendorf mit 8.62 ebenfalls erfüllt — der Fehlgriff auf eine andere Gemeinde
wäre durchgewinkt worden. Verlangt jetzt den Ortsnamen im Ergebnis. -
Cache-Isolation zwischen Tests. Der TTL-Cache ist modul-global und
überlebte den einzelnen Test. Ein Test sah dadurch Einträge eines früheren,
umging sein eigenes respx-Mock und schlug je nach Ausführungsreihenfolge fehl
— oder bestand aus dem falschen Grund. Eine autouse-Fixture räumt jetzt vor
und nach jedem Test auf.
v0.5.0
[0.5.0] - 2026-07-30
Reparatur-Release für einen kaputten PyPI-Stand. mcp 2.0.0 erschien am
28.07.2026 und entfernte mcp.server.fastmcp — genau das Modul, das v0.4.0
importierte. Weil v0.4.0 keine Obergrenze auf mcp hatte, zog jede frische
Installation aus PyPI die 2.0.0 und scheiterte sofort beim Import. Das
betrifft uvx meteoswiss-mcp genauso wie pip install meteoswiss-mcp
(#31).
Der Code auf main war bereits migriert; dieses Release bringt die Korrektur
zu den Nutzenden. Für Clients ist nichts breaking — die 6 Tools, ihre Schemata
und das Wire-Format sind unverändert (tool-hashes.json bleibt gleich).
Behoben
- Frische Installationen starten wieder. Der
ModuleNotFoundError: No module named 'mcp.server.fastmcp'beim Start vonuvx meteoswiss-mcpist
weg. Ursache war nicht der Code, sondern die Release-Lücke: der Fix lag seit
#29/#30 aufmain, aber PyPI führte weiterhin v0.4.0.
Changed
-
Migration auf das
mcp-Python-SDK 2.x (#30). Der Server-Import wechselt
vonmcp.server.fastmcpaufmcp.server.mcpserver,FastMCPheisst neu
MCPServer. Ohne Kompatibilitäts-Shim im SDK ist der Boden hart: der Pin
lautet jetztmcp[cli]>=2.0.0,<3statt>=1.28.1. Weitere Anpassungen im
SDK, die mitgezogen werden mussten:mcp_typessnake_cased sämtliche Modell-Attribute (inputSchema→
input_schema,isError→is_error, …). Das sind Pydantic-Aliase, das
Wire-Format bleibt identisch.McpErrorheisstMCPErrorund nimmt(code, message, data=None)direkt
entgegen statt eineErrorData-Instanz zu umschliessen.call_tool()liefert einCallToolResultstatt des 1.x-Tupels
(content, structured).MCPServer.__init__akzeptierthost/port/stateless_http/
transport_securitynicht mehr — das sind neurun()- bzw. App-Kwargs.- Der Lowlevel-Server ist als
_lowlevel_servererreichbar und exponiert
keinrequest_handlers-Mapping mehr.
Gegen eine aufgezeichnete 1.x-Baseline verifiziert: die Suite besteht vor
und nach der Migration exakt dieselben Tests, ohne neue Fehlschläge und ohne
stillschweigend übersprungene Tests.
Added
serverInfo.versionim initialize-Handshake. Der Server meldete bisher
einen leeren Version-String — ausgerechnet die Angabe, die bei einem
Bug-Report als Erstes gebraucht wird. Sie kommt neu aus der installierten
Distribution (importlib.metadata), kann also nicht gegenüber dem Paket
veralten; im Source-Checkout ohne Installation lautet sie0.0.0+unknown.
Docs
- README (DE/EN) und CONTRIBUTING (DE/EN) sprachen weiterhin von «FastMCP» und
nanntenmcp[cli]>=1.0.0als SDK-Version — beides aufMCPServerbzw.
mcp[cli]>=2.0.0,<3korrigiert.
v0.4.0
[0.4.0] - 2026-07-26
Feature-Release: meteo_warnings liefert neu echte Live-Warnungen aus dem
öffentlichen MeteoSwiss-App-Backend statt nur eines Linkstacks. Kein Breaking
Change — bestehende Aufrufe funktionieren unverändert (neue Parameter plz /
language sind optional).
Added
Live-Wetterwarnungen (meteo_warnings)
meteo_warningsliefert jetzt echte, aktive Warnungen statt nur eines
Linkstacks. Live-Quelle ist das öffentliche MeteoSwiss-App-Backend
(app-prod-ws.meteoswiss-app.ch/v1/plzDetail) — öffentlich und ohne Auth.
Aggregierte Wetterwarnungen (Sturm, Gewitter, Hitze, Waldbrand, Frost,
Schnee, …) mit Typ- und Stufen-Label, betroffener Warnregion, Gültig-ab und
offiziellem Handlungslink; zusätzlich eineVorausschau-Sektion für noch
nicht aktive Warnungen.- Drei Abfrage-Granularitäten:
plz="8001"(ortsgenau),canton="TI"
(Kanton via Hauptort-PLZ) oder ohne Filter → landesweite Aggregation über je
eine Kantonshauptort-PLZ pro Kanton (26 Abfragen, dedupliziert, nach Typ
gruppiert). Einzelne fehlgeschlagene PLZ-Abfragen degradieren den Aufruf
nicht (Teilergebnis + Hinweis). - Mehrsprachige Warntexte via neuen Parameter
language(de/fr/it/
en, Defaultde) — wird alsAccept-Languagean die App-API durchgereicht. - Neuer Egress-Host
app-prod-ws.meteoswiss-app.chin der Allow-List.
MCP_WARNINGS_API_URLüberschreibt die App-Quelle weiterhin (Vorbereitung
auf die künftige offizielle OGD-Warnings-REST-API). warnType-Mapping (7=Hitze, 10=Waldbrand gegen die natural-hazards.ch-Slugs
verifiziert) mit Slug-Fallback für unbekannte Codes; Warnstufen 1–5.
Changed
meteo_warnings-Tool-Definition erweitert (neue Parameterplz,
language; aktualisierte Description) →tool-hashes.jsonneu generiert
(Rug-Pull-Signal, SEC-022). Für Clients nicht breaking: bestehende Aufrufe
ohneplz/languagefunktionieren unverändert.
Tests
- 12 neue Tests für die App-API-Warnungen (respx-gemockt, kein Netzwerk):
PLZ-Detailansicht, landesweite Aggregation, JSON-Schema, unbekannter Kanton,
Fehler-Degradation, PLZ-/Sprach-Validierung sowie Unit-Tests für
_warn_type_label,_epoch_millis_to_iso,_dedupe_warningsund die
Egress-Allow-List.
v0.3.0 — Phase 2: Caching + Climate Data + Warnings Hook
Phase-2-Release. Kein Breaking Change gegenüber 0.2.0 — alle neuen Features sind opt-in via ENV.
Highlights
- Performance: TTL-Cache für alle Upstream-Calls (STAC, Open-Meteo, Geocoding, opendata.swiss). Asyncio-safe, per-Endpoint-TTLs via ENV, default 5 min für Live-Daten / 1 h für Stammdaten.
- Klimanormwerte 1991–2020 für 19 SMN-Stationen in
data/climate-normals.json, ingested aus der offiziellen MeteoSwiss-NBCN-Publikation: BAS, BER, CHU, DAV, GVE, INT, JUN, KLO, LUG, LUZ, PIL, PUY, REH, SAE, SIO, SMA, STG, TAE, WAE (REC nicht in NBCN). Vorher: nur 5 eingebettete Stationen. - Runtime-Lookup für Klimadaten via
MCP_CLIMATE_NORMALS_URL_TEMPLATE— fängt Stationen ab, die weder eingebettet noch in der JSON sind (z.B. via STAC). - Strukturierte Warnings-API via
MCP_WARNINGS_API_URL— Hook für die geplante MeteoSwiss-OGD-Phase-2-API. Schema-tolerant (GeoJSON /warnings-Array /items). - Ingest-Skript:
python scripts/ingest_climate_normals.py --dir <path>scannt einen MeteoSwiss-NBCN-Dump-Ordner, filtert relevante Parameter, schreibtdata/climate-normals.json(Tab-separated TSV, cp1252-encoded).
Neue ENV-Variablen
| Variable | Default | Zweck |
|---|---|---|
MCP_CACHE_ENABLED |
1 |
TTL-Cache an/aus |
MCP_CACHE_TTL_STAC |
300 |
SMN-Now-Beobachtungen |
MCP_CACHE_TTL_OPEN_METEO |
600 |
ICON-Prognosen |
MCP_CACHE_TTL_GEOCODING |
3600 |
Geocoding |
MCP_CACHE_TTL_OPENDATA |
3600 |
opendata.swiss-Katalog |
MCP_CACHE_TTL_WARNINGS |
300 |
Strukturierte Warnings-API |
MCP_CACHE_TTL_STAC_CLIMATE |
86400 |
Climate-Runtime-Lookup |
MCP_CLIMATE_NORMALS_PATH |
unset | Pfad auf climate-normals.json |
MCP_CLIMATE_NORMALS_URL_TEMPLATE |
unset | URL-Template mit {station} / {STATION} / {param} |
MCP_WARNINGS_API_URL |
unset | URL der strukturierten Warnings-API |
Vollständiges CHANGELOG
Siehe CHANGELOG.md.
Upgrade-Pfad
# uvx
uvx meteoswiss-mcp@0.3.0
# pip
pip install -U meteoswiss-mcp==0.3.0Release v0.2.1 — Audit-Remediation komplett (0 critical/high/medium offen)
Komplette Umsetzung des mcp-audit-skill-Reviews. 0 critical / 0 high / 0 medium Findings offen.
Production-ready für:
- stdio (Claude Desktop, lokal)
- Single-instance HTTP auf Render via
render.yaml-Blueprint - Multi-Replica HTTP mit
MCP_STATELESS_HTTP=1
⚠️ Breaking Changes
- JSON-Output-Format: Tools mit
response_format="json"liefern jetzt einen Envelope{ "payload": ..., "provenance": { source, license, attribution, retrieved_at, data_source_url } }statt eines flachen Dicts. Markdown-Outputs unverändert. Konsumenten, die JSON parsen, müssen aufresult["payload"]zugreifen. - Entry-Point:
meteoswiss_mcp.server:mainstattmcp.run. Der neue Wrapper liestMCP_TRANSPORT/MCP_HOST/MCP_PORT/MCP_ALLOW_ANY_HOSTaus ENV. CLI-Flags--http/--portbleiben als Override.
Sicherheit
- SSRF-Prevention (SEC-004/SEC-021): Egress-Allow-List validiert alle Requests inkl. Redirect-Targets. Blockiert RFC1918 / Loopback / Link-Local-IPs (z.B.
169.254.169.254). - NeighborJack-Hardening (SEC-016):
MCP_HOSTdefaultet auf127.0.0.1;0.0.0.0-Binding nur mit explizitemMCP_ALLOW_ANY_HOST=1. - Container-Sandboxing (SEC-007): Multi-Stage-Dockerfile mit non-root user
mcp:10001. - Optionaler API-Key-Auth via
MCP_API_KEY(X-API-Key / Bearer, constant-time-Vergleich).
Neue Features (opt-in)
- CORS-Middleware via
MCP_ALLOWED_ORIGINS— claude.ai Web kann jetzt Sessions aufbauen. - Stateless-HTTP-Mode via
MCP_STATELESS_HTTP=1für Multi-Replica. - OpenTelemetry-Tracing via
OTEL_EXPORTER_OTLP_ENDPOINT+pip install meteoswiss-mcp[otel]. - Structured Logging (
structlog→ JSON auf stderr):tool_invoked,upstream_failed,egress_blocked,auth_rejected, ... - Health-Endpoint
GET /health(ohne Upstream-Pings). - OGD-Provenance-Envelope in allen JSON-Responses (CC BY 4.0 Attribution).
- Fuzzy-Geocoding-Fallback: nicht-deutsche Ortsnamen werden via Zweitversuch ohne language-Restriktion aufgelöst.
- Tool-Hash-Pinning (
tool-hashes.json+ CI-Guard) gegen Rug-Pull.
Infrastruktur
- Multi-Stage-Dockerfile +
render.yaml-Blueprint (plan starter, healthCheckPath, numInstances=1). - Dependabot für pip / github-actions / docker.
docs/roadmap.mdmit expliziter Phasen-Statustabelle.
Audit-Findings geschlossen
ARCH-002, ARCH-003, ARCH-009, ARCH-012, OPS-003, OBS-001, OBS-002, OBS-003, OBS-004, OBS-006, SDK-001, SDK-002, SDK-003, SDK-004, SCALE-001, SCALE-002, SCALE-003, SCALE-004, SCALE-006, SEC-004, SEC-005, SEC-006, SEC-007, SEC-009, SEC-013, SEC-016, SEC-021, SEC-022, CH-004.
Vollständiges CHANGELOG
Siehe CHANGELOG.md.
Upgrade-Pfad
# uvx
uvx meteoswiss-mcp@0.2.0
# pip
pip install -U meteoswiss-mcp==0.2.0
# Docker
docker pull ghcr.io/malkreide/meteoswiss-mcp:v0.2.0 # falls Image-Publish konfiguriertv0.2.0 — Initial PyPI Release
fix: use StrEnum instead of (str, Enum) to pass UP042 lint rule Python 3.11+ provides enum.StrEnum natively. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>