Releases: malkreide/bag-epl-mcp
Release list
v1.0.3
[1.0.3] - 2026-08-02
Behoben
-
structloghatte keine Obergrenze, und der Index fuehrt bereits einen Major
oberhalb der Untergrenze. Deklariert warstructlog>=25.5.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>=25.5.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.
v1.0.2
[1.0.2] - 2026-07-31
Hinzugefuegt
-
Der Server nennt jetzt seinen Namen. Bisher ging gegenueber jedem
Upstream der httpx-Default hinaus: der Betreiber der Datenquelle sah
eine Bibliothek, nicht uns, und hatte keinen Weg, uns bei Fehlverhalten
zu erreichen. Neu traegt den HTTP-Client
bag-epl-mcp/<version> (+github.com/malkreide/bag-epl-mcp).Die Version stammt aus
importlib.metadataund kann nicht getrennt vom
Paket driften.
Fixed
-
Streamable-HTTP wies unter jedem echten Hostnamen mit 421 ab (SEC-005).
_build_http_app()riefmcp.streamable_http_app()ohnehostauf. Unter
mcp 2.x ist das kein neutraler Default: das SDK leitet daraus seine
Host-Allow-List ab und aktiviert bei loopback-artigem Wert automatisch
127.0.0.1:*. Da das Argument selbst auf127.0.0.1defaultet, traf das jedes
Cloud-Deployment mitMCP_HOST=0.0.0.0. Vor der Migration ginghostan den
FastMCP-Konstruktor, wo dieselbe Logik den echten Bind sah und den Schutz
korrekt ausliess.Besonders tückisch hier:
/healthzist bewusst von der Transport-Prüfung
ausgenommen (SCALE-004) und antwortete weiter mit 200. Ein Load-Balancer sah
also einen gesunden Server, der keine einzige MCP-Anfrage bedienen konnte.Der Bind reist jetzt in die App, und eine echte Allow-List wird aus dem neuen
MCP_ALLOWED_HOSTSgebaut. Ohne diese Variable bleibt der Schutz auf einem
Nicht-Loopback-Bind bewusst aus und der Aufrufer warnt — eine geratene Liste
wäre genau der 421-Fall.https://claude.ai(CORS-Default) wird in die
Origin-Liste übernommen, sonst weist der Transport genau den Browser-Client ab,
für den die CORS-Konfiguration existiert.13 neue Tests, darunter der tragende Fall „richtiger Hostname, falscher Port"
und einer, der die/healthz-Asymmetrie festhält — sie ist gewollt, war aber
auch der Grund, warum der Fehler verdeckt blieb. Mutationsgetestet: nimmt man
denhost-Kwarg wieder weg, reproduziert der Test das 421.Geprüft mit den wörtlichen CI-Kommandos: 70 passed / 8 deselected,
ruff check src/ tests/clean, Versions-Sync OK.
Fixed
- Capped
mcpat<2.mcp2.0.0, published 2026-07-28, removed
mcp.server.fastmcp— the module this server imports. With the previous
unbounded>=1.28.1every fresh resolve picked 2.0.0 and failed at import
withModuleNotFoundError, in CI and for anyone runningpip installalike.
Verified in both directions: 2.0.0 fails,<2resolves to 1.29.0 and imports
cleanly. Migrating to the 2.x API (mcp.server.mcpserver) stays a separate,
deliberate piece of work.
v0.2.0 — Production hardening (audit remediation)
v0.2.0 — Production hardening (audit remediation)
This release closes the security/production-readiness audit: from
11 pass / 26 partial / 7 fail at baseline to 39 pass / 5 partial / 0 fail
across the 44 applicable checks — all failed checks resolved, 0 blocking findings.
Highlights
Cloud transport, for real
- Env-driven transport selection (
MCP_TRANSPORT,MCP_HOST,MCP_PORT); the
advertised Streamable-HTTP transport is now actually implemented (default stays
stdio). CORS allow-list (MCP_CORS_ORIGINS) exposingMcp-Session-Id. - Multi-stage
Dockerfile(non-root UID 10001,HEALTHCHECK),render.yaml
blueprint,/healthzprobe,$PORTfallback.
Security (SSRF / egress / transport)
- Egress allow-list guard (
ALLOWED_HOSTS+_assert_safe_url): HTTPS-only,
host allow-list, resolved-IP blocklist before any outbound request. - DNS-pinned HTTP transport (
_PinnedNetworkBackend): host resolved exactly
once, IP validated and the TCP connection pinned to it, TLS SNI/cert still
verified against the hostname — eliminates the resolve/connect TOCTOU (SEC-005). - Pydantic
strict=Trueinput models; errors surface as sanitized MCPisError
results (no raw exception text to the model).docs/SECURITY.mdthreat model.
MCP / SDK
- Typed structured tool output (SDK-002): all 6 tools declare Pydantic output
schemas and return a hybridCallToolResult— curated Markdown/JSON in
contentplus validatedstructuredContent. Machine-readable, no UX loss. ctx: Contextinjection in every tool; tool annotations
(readOnlyHint/openWorldHint);protocolVersionconstant + policy.
Observability
- Structured JSON logging (
structlog) to stderr (stdout reserved for the
stdio JSON-RPC stream); per-call bound context (tool,correlation_id). - OpenTelemetry on by default (OBS-006):
MCP_OTEL_ENABLEDdefaults on;
silent no-op without the[otel]extra. TracerProvider + OTLP + Starlette/httpx
auto-instrumentation.MCP_OTEL_ENABLED=0to disable.
Ops / supply chain
- Pooled lifespan-managed
httpx.AsyncClient; unit/live test split;
scripts/snapshot_tool_hashes.pyrecords SHA-256 of tool definitions (SEC-022);
deploy/haproxy.cfgsticky-session reference for horizontal scaling.
Remaining (non-blocking, by design)
SEC-009 (no-auth, accepted-risk), SCALE-002/003 (multi-instance, deferred),
SEC-014/015 (enterprise gateway, accepted-risk). See
docs/audit/2026-06-01-reaudit/.
Full changelog: v0.1.0...v0.2.0
v1.0.0 – Initial Release
Add GitHub Actions workflow for publishing to PyPI