v2.0.0
[2.0.0] - 2026-08-01
Sammelrelease. Diese Version liefert alles aus, was seit 1.0.3 auf
main
aufgelaufen ist — 22 Commits, darunter die beiden Stände, die bis zum
01.08.2026 fälschlich als[1.1.0]und[1.2.0]in diesem CHANGELOG standen,
obwohl keiner von beiden je ausgeliefert wurde: kein Tag, kein PyPI-Upload,
kein GitHub-Release. Ihr Inhalt ist unverändert übernommen und unten als
eigene Blöcke erhalten, damit nachvollziehbar bleibt, was wann entstanden ist.Warum 2.0.0 und nicht das vorbereitete 1.2.0: die Migration auf das
mcp-SDK 2.x hebt die Abhängigkeit von<2auf>=2.0.0,<3. In einer
Umgebung, diemcpauf 1.x festhält, bricht die Installation — ein Breaking
Change, den auch der Commit selbst so markiert (feat!). Der MCP-Tool-Vertrag
ist davon nicht betroffen: 12 Tools, 2 Resources,tool-definitions.lock.json
unverändert. Wer den Server wie dokumentiert überuvxstartet, merkt vom
Major-Sprung nichts.
Fixed
-
Streamable-HTTP wies unter jedem echten Hostnamen mit 421 ab (SEC-005).
_run_http()baute die App mitmcp.streamable_http_app()ohnehost— und
zwar bevor Host und Port überhaupt aufgelöst waren. Der Bind konnte also
gar nicht ankommen. Unter mcp 2.x ist das kein neutraler Default: das SDK
leitet aus dem App-Argument seine Host-Allow-List ab und aktiviert bei
loopback-artigem Wert automatisch127.0.0.1:*. Da der Default127.0.0.1
ist, traf das jeden Start mitFEDLEX_HOST=0.0.0.0.Der Bind wird jetzt zuerst ermittelt (
resolve_http_bind()) und dann an beide
Abnehmer gegeben — uvicorn und die App. Eine echte Allow-List entsteht aus dem
neuenFEDLEX_ALLOWED_HOSTS; ohne diese Variable bleibt der Schutz auf einem
Nicht-Loopback-Bind bewusst aus und der Aufrufer warnt.13 neue Tests, darunter der tragende Fall „richtiger Hostname, falscher Port"
und einer, der die Bind-Auflösung festnagelt (PORTschlägt--port,--port
schlägt die Settings) — sonst könnte die App später wieder einen anderen Wert
sehen als uvicorn. Mutationsgetestet: nimmt man denhost-Kwarg wieder weg,
reproduziert der Test das 421.Geprüft mit den wörtlichen CI-Kommandos: 94 passed / 3 deselected,
ruff check src/ tests/clean.
Geändert — Breaking
-
Migration auf das
mcp-Python-SDK 2.x. Der Server importiert nicht mehr
mcp.server.fastmcp, sondernmcp.server.mcpserver; ausFastMCPwird
MCPServer. Die Abhängigkeit lautet damitmcp[cli]>=2.0.0,<3statt
>=1.28.1,<2— das ist der Breaking-Anteil: werfedlex-mcpin eine Umgebung
installiert, diemcpauf 1.x festhält, bekommt jetzt einen
Auflösungskonflikt. Unteruvx, dem dokumentierten Installationsweg, ist die
Umgebung isoliert und die Änderung unsichtbar.Der Tool-Vertrag bleibt bewusst unverändert.
mcp_types2.x hat die
Python-Felder auf snake_case umbenannt (inputSchema→input_schema,
outputSchema→output_schema);compute_tool_signature_hash()liest jetzt
die neuen Namen und dumpt die Annotations mitby_alias=True, damit die
Draht-Schreibweise (readOnlyHint, …) erhalten bleibt. Ohne diesesby_alias
hätte sich der Signatur-Hash geändert, ohne dass sich am Vertrag etwas ändert —
die Drift-Erkennung aus SEC-022 hätte falsch angeschlagen.
tool-definitions.lock.jsonist entsprechend unverändert geblieben; die 12
Tools und 2 Resources sind identisch.Protokollseitig bringt 2.x eine neuere Revision mit
(LATEST_PROTOCOL_VERSION=2026-07-28). Sie wird beiminitialize-Handshake
ausgehandelt, ältere Clients bleiben also bedient.
Behoben
-
mcpauf<2begrenzt.mcp2.0.0, veröffentlicht am 28.07.2026, hat
mcp.server.fastmcpentfernt — genau das Modul, das dieser Server importiert.
Mit dem bisherigen offenen>=1.28.1wählte jede frische Auflösung 2.0.0 und
scheiterte beim Import mitModuleNotFoundError, in der CI ebenso wie bei
jedempip install. In beide Richtungen verifiziert: 2.0.0 scheitert,<2
löst auf 1.29.0 auf und importiert sauber. Die Migration auf die 2.x-API
(mcp.server.mcpserver) bleibt eine eigene, bewusste Aufgabe.Zwischenstand, inzwischen überholt. Die Migration ist im selben
Release-Zyklus erfolgt (siehe «Geändert — Breaking»). Ausgeliefert wird
mcp[cli]>=2.0.0,<3, nicht<2. Der Eintrag bleibt stehen, weil er
erklärt, warum diese Abhängigkeit überhaupt eine Obergrenze trägt. -
mcp-Untergrenze auf>=1.28.1angehoben (CVE-2026-59950). Vorher stand
dort ein offenes>=1.3.0, unter dem eine frische Auflösung eine verwundbare
SDK-Version hätte wählen können. Die heute ausgelieferte Untergrenze
(>=2.0.0) schliesst den betroffenen Bereich weiterhin aus.
Hinzugefügt — Verteilung und Installation
server.jsonergänzt: Registry-Metadaten für die MCP Registry
(io.github.malkreide/fedlex-mcp,registryType: pypi,runtimeHint: uvx,
Kategorie «Legal, Courts & Regulatory»). Bis dahin existierte die Datei nicht,
obwohl dermcp-name-Marker im README seit 1.0.3 auf sie vorbereitet war.## Installationim README mit demuvx-Client-Snippet für die
mcpServers-Konfiguration (Claude Desktop, Cursor, Windsurf; Hinweis auf den
Top-Level-Schlüsselserversfür VS Code). Der generierte Block war ans
Dateiende gehängt worden — hinter## Authorund## Credits, und als
zweite## Installation-Überschrift. Er steht jetzt an der richtigen
Stelle (nach## Prerequisites), die MarkerBEGIN/END GENERATED: install
sind erhalten, und der bisherige Abschnitt heisst zur Unterscheidung
## Installation from source. Damit löst der Anker#installationwieder auf
den empfohlenen Weg auf und die Schluss-Sektionen stehen wieder am Schluss.- Publish in die MCP Registry als eigener Workflow-Job (
publish-mcp),
nachgelagert zum PyPI-Upload, authentifiziert über GitHub-OIDC
(mcp-publisher login github-oidc). Der Job ziehtversionund
packages[0].versioninserver.jsonaus dem Release-Tag nach, damit
Tag, Paket und Registry-Eintrag nicht auseinanderlaufen können.
Geändert — Dokumentation und Interna
- README (beide Sprachfassungen): Schluss-Sektionen in der Reihenfolge
Contributing → Security statt Security → Contributing. - README (EN): Die Sektion «MCP Protocol Version» nannte die SDK-Grenze
>=1.3.0und war damit über drei Abhängigkeitsänderungen hinweg falsch; sie
verweist jetzt aufpyproject.tomlstatt eine Version zu wiederholen. ruffinpyproject.tomlmit Obergrenze gepinnt (>=0.15.15,<0.17). Ohne
Obergrenze installiert die CI die jeweils neuste Version; ein geänderter
Default-Regelsatz färbt den Lauf dann rot, ohne dass sich eine Zeile Code
geändert hat.actions/checkoutinci.yml,publish.ymlundsecurity.ymlvon v6 auf v7
angehoben (Dependabot).
Aus der nie ausgelieferten 1.2.0 (dokumentiert am 20.07.2026)
Vertieft die Vernehmlassungs-Schicht auf ein fristenzentriertes Produkt: die
Restfrist wird zur Kernaussage. Keine neuen Tools (bleibt bei 12); die drei
bestehenden fedlex_*consultation*-Tools werden gehärtet. Vollständig
rückwärtskompatibel für SR/AS/BBl/TERMDAT.
Added
days_remainingin jeder Vernehmlassungs-Antwort, zur Laufzeit berechnet
(nie gecacht, nie geschätzt) gegen Europe/Zurich. Kalendertag-Semantik:
0= Frist endet heute, negativ = abgelaufen. Zentral in einer testbaren
Funktion (consultations.days_until/deadline_status).- Abgeleiteter
status— die Frist gewinnt. Sagt die Quelle «Laufend», die
Frist liegt aber in der Vergangenheit, wird der StatusAbgeschlossen; das
rohe Quell-Label bleibt alsstatus_sourcesichtbar, der Widerspruch als
status_conflict: true. Eine abgelaufene Vernehmlassung erscheint nie als
laufend. - Typisiertes
Consultation-Pydantic-v2-Modell mit den Pflichtfeldern
title, status, opened_on, deadline, days_remaining, lead_office, source_url, retrieved_at, language.retrieved_at(UTC) in jeder Antwort. - Thematischer Filter
topic="education"— ausgewiesene Stichwort-Union
(Freitext im Titel; Fedlex hat keine Sachgebiets-Taxonomie). Die Antwort nennt
die tatsächlich gesuchten Begriffe (message+ Markdown), damit klar ist,
wonach nicht gesucht wurde. Sortierung nach kürzester Restfrist als Default. - Isoliertes
consultations-Modul — Vernehmlassungs-Logik (Uhr,
Fristenberechnung, Modell, Query-Bausteine, Themenfilter) getrennt von der
SR-/AS-/BBl-Schicht inserver.py. - Neue Tests (respx + injizierbare Uhr): korrektes
days_remaining,
Frist heute →days_remaining == 0, Frist gestern → «Abgeschlossen», nicht
in der Liste laufender Verfahren (SPARQL-Frist-Grenze + Sprach-Dedupe im
Query geprüft), Quelle «laufend» vs. Datum → Datum gewinnt, Themenfilter
findet Bildungsvorlage inkl. ausgewiesener Strategie, Endpoint nicht
erreichbar → erklärender Fehler statt leerem Resultat.
Known findings (live verifiziert 2026-07-20)
jolux:Consultationhat keine Sachgebiets-/Klassifikations-Taxonomie —
thematische Filterung ist ausschliesslich Freitext. Der Ankerbegriff
«Volksschule» kommt in 0 Titeln vor (bildung44,topic="education"-Union
66; 42 aktuell offen).- SPARQL-Quirk:
REGEX(LCASE(...), "a|b")liefert auf dem Fedlex-Endpoint
still 0 Treffer — Alternation daher über OR-verkettetesCONTAINS. eventEndDateistxsd:date(reiner Kalendertag, keine Uhrzeit/TZ) →
Vergleich gegen «heute in Europe/Zurich».- Scope-Grenze dokumentiert: nur Bund, keine kantonalen Vernehmlassungen;
kein Push-Mechanismus (MCP ist Pull-basiert).
Refactor
- Geteilter SPARQL-/JSON-Client extrahiert (
sparql_client.py, vendored
Portfolio-Baustein). Der bisherige_execute_sparql-Retry-Kern ist jetzt eine
dünne Bindung an das wiederverwendbare Modul;sparql_escape/valdaraus
re-exportiert,RETRYABLE_STATUSentfernt. Verhalten unverändert
(Retry/Backoff, Egress-Guard,sparql_retry-Log via Callback erhalten),
öffentliche Namen stabil, 76 Tests grün. Die Datei ist byte-identisch zur
Kopie inswiss-environment-mcp— bis ein installierbaresswiss-mcp-commons
(PyPI/OIDC) existiert, sind die Kopien synchron zu halten.
Aus der nie ausgelieferten 1.1.0 (dokumentiert am 18.07.2026)
Erweitert den Server um zwei zusätzliche, ebenfalls SPARQL-basierte Datenquellen
— Vernehmlassungen (Fedlex) und die Terminologiedatenbank TERMDAT (via LINDAS).
Von 7 auf 12 Tools; 2 Resources unverändert. Vollständig rückwärtskompatibel:
die bestehenden fedlex_*-Tools und beide Resources bleiben unangetastet.
Hinweis zur Version: Der zugrunde liegende Auftrag nannte
0.2.0; da das Repo
bereits auf1.0.3stand, wäre das ein Downgrade gewesen. Als
semver-konformer Minor-Bump für eine rückwärtskompatible Funktionserweiterung
ist es1.1.0.
Added
- Vernehmlassungen (Fedlex,
jolux:Consultation) — drei neue Tools:fedlex_get_open_consultations— Fristen-Monitoring; filtert primär über
eventEndDate >= heute, nicht über den Status (die beiden Signale sind
unabhängig). Sortiert nach Frist aufsteigend, optionalerkeyword-Filter.
Leeres Resultat liefert eine explizite Sachaussage inkl. Prüfzeitpunkt statt
einer nackten leeren Liste.fedlex_search_consultations— Volltextsuche übereventTitle/
eventDescriptionmit Filtern für Status, Zeitraum (Frist) und
federführendes Amt.fedlex_get_consultation— Detail zu einereventId: Fristen, Departement/
Amt, Status, Vernehmlassungsunterlagen (opinionHasDraftRelatedDocument),
betroffene Rechtsressource (foreseenImpactToLegalResource).
- TERMDAT (LINDAS, schema.org) — zwei neue Tools:
termdat_lookup_term— Begriff → Entsprechungen in de/fr/it/rm/en samt
Definition.termdat_get_concept— vollständiger Eintrag zu einer ID oder URI; akzeptiert
ID, Konzept-URI und Term-URI mit Sprachsuffix und normalisiert intern.
- Zweiter, isolierter SPARQL-Endpoint (LINDAS) mit eigenem httpx-Client und
eigenem Timeout (ARCH A / Isolationspflicht): Ein LINDAS-Ausfall lässt die
fedlex_*-Tools unbeeinträchtigt und umgekehrt — getrennte Fehler-/Statusmeldung. - Retry-Logik für transiente Fehler (HTTP 429/502/503/504, Timeout, Netzwerk)
mit exponentiellem Backoff; deterministische Fehler (z.B. HTTP 400) werden
nicht wiederholt. - TERMDAT-Attribution in jeder Response (
source-Feld + Markdown-Footer),
inkl. des Teilbestand-Hinweises (77'692 von ~400'000 Einträgen). - Neue Tests (respx-gemockt): Happy-Path je Tool, Retry bei 503, Timeout-/
Netzwerk-Masking, Endpoint-Isolation, Status-Frist-Konflikt (Quirk 1),
Consultation ohnehasSubTask, SPARQL-Escaping. Live-Tests gegen beide
Endpoints unter-m live(aus CI ausgeschlossen).
Changed
fedlex://info-Resource: Version, zweiter Endpoint, alle 12 Tools und die
Isolationsnotiz ergänzt (Resource-URI unverändert).handle_errorund der Response-Envelope tragen jetzt den betroffenen Dienst
(Fedlexvs.TERMDAT (LINDAS)), damit ein isolierter LINDAS-Fehler nicht als
Fedlex-Fehler gelesen wird.
Known findings
Live verifiziert am 18.07.2026:
Vernehmlassungen (Fedlex)
- Gesamtbestand
jolux:Consultation: 2 553. - 48 von 2 553 Consultations haben keine
eventStartDate/eventEndDate
(keinhasSubTaskmit Fristen) →deadline: null, kein Tool-Fehler. - Quirk 1: Status «Laufend» (
consultation-status/2) und eine Frist in der
Zukunft sind unabhängige Signale. Es gibt «laufende» Einträge mit
abgelaufener Frist. Die Tools filtern über die Frist und markieren
Widersprüche mitstatus_conflict: truestatt sie stillschweigend aufzulösen. previousConsultationStatusist mit nur 234 Werten dünn besetzt und wird
nicht als Filter verwendet.
TERMDAT (LINDAS)
- Quirk 2 (Reality-Check): Die Bundeskanzlei kommuniziert ~400 000 TERMDAT-
Einträge; als Linked Data auf LINDAS liegen 77 692 (schema.ld.admin.ch/Term).
Die Differenz ist nicht erklärt — vermutlich ist nur der validierte/freigegebene
Teilbestand publiziert. Ein Negativtreffer bedeutet daher nicht, dass der
Begriff in TERMDAT fehlt, sondern nur, dass er nicht im LINDAS-Teilbestand liegt.
Dieser Hinweis steht in jeder TERMDAT-Response. - Quirk 3 (zwei URI-Ebenen): Konzept-URIs (
…/termdat/40109) tragen die
bevorzugten Benennungen je Sprache, die Definition und viaschema:hasPartdie
Synonym-/Varianten-Term-URIs mit Sprach- und Positionssuffix (…/40109/3/de).
Beide Eingaben werden akzeptiert und intern auf die Konzept-ID normalisiert. rm(Rätoromanisch) ist im LINDAS-Teilbestand faktisch nicht besetzt
(0 Namen) — als Zielsprache erlaubt, liefert aber in aller Regel keinen Treffer.