Back to Discover

swiss-electricity-mcp

connector

malkreide

BFE energy dashboard, ElCom tariffs, public consumption data

View on GitHub
0 starsSynced Aug 2, 2026

Install to Claude Code

/plugin marketplace add malkreide/swiss-electricity-mcp

README

swiss-electricity-mcp

MCP server for Swiss electricity data β€” three official sources, twelve tools, zero authentication.

CI PyPI Python License: MIT

🌍 Read this in your language: πŸ‡©πŸ‡ͺ Deutsch

Part of the Swiss Public Data MCP Portfolio β€” a coordinated set of MCP servers for Swiss public administration.


Anchor demo query

"How have ewz electricity tariffs for a typical school building (consumption category C3, β‰ˆ150'000 kWh/a) developed since 2019, and how do they compare to the Swiss median?"

A single conversation calls tariff_get_by_municipality (bfs_nr=261, category="C3") + tariff_get_median_swiss and returns a year-by-year comparison with full provenance β€” ready for a GeschΓ€ftsleitung slide.

Demo

Demo: Claude using tariff_get_by_municipality and tariff_get_median_swiss


What's inside

Three official Swiss data sources combined into one MCP server, each with its own dedicated tool group:

SourceWhat it providesProvenance
Energiedashboard.ch (Bundesamt fΓΌr Energie)National production mix, consumption forecast, storage-lake fill, consumer price indexlive_api
ElCom electricity-price cubes (via LINDAS SPARQL)Tariffs per municipality, category, year, with full breakdown (energy + grid usage + KEV + Abgaben)sparql
opendata.swiss + Stadt ZΓΌrich OGD (CKAN)Dataset discovery for raw time series (e.g. quarter-hour NE5/NE7 consumption)live_api

No authentication required. All endpoints are public Swiss OGD.


Tools (12)

dashboard_* β€” Energiedashboard.ch (BFE)

  • dashboard_get_production_mix β€” Production mix by year (TWh + %): Kernkraft, Wasserkraft, PV, Wind, thermal.
  • dashboard_get_consumption_forecast β€” Current consumption forecast + 5-day outlook + 5-year envelope.
  • dashboard_get_storage_lakes β€” Speichersee fill level (CH or per region: Wallis, Tessin, GraubΓΌnden, Zentral/Ost) β€” critical winter-supply indicator.
  • dashboard_get_consumer_price_index β€” Endverbraucher-Strompreis-Index (2020-01-01 = 100).

tariff_* β€” ElCom (via LINDAS SPARQL)

  • tariff_list_categories β€” H1–H8 (households) and C1–C7 (commercial). C3 β‰ˆ 150'000 kWh/a is the typical reference for school buildings.
  • tariff_get_by_municipality β€” Tariffs for a BFS-Nr + category + year range, broken into energy / grid usage / KEV / Abgaben.
  • tariff_get_median_swiss β€” National median benchmark.
  • tariff_get_median_canton β€” Cantonal median (e.g. for Kanton ZΓΌrich).
  • tariff_compare_municipalities β€” Compare up to 20 municipalities side-by-side.

consumption_* β€” opendata.swiss + Stadt ZΓΌrich OGD

  • consumption_search_bfe_datasets β€” CKAN search across BFE-published datasets.
  • consumption_search_zurich β€” CKAN search across Stadt ZΓΌrich OGD (includes quarter-hour NE5/NE7 consumption).

Status

  • electricity_check_status β€” Liveness probe across all four upstreams (HTTP status + latency + overall-healthy flag).

Installation

From PyPI

pip install swiss-electricity-mcp

From source

git clone https://github.com/malkreide/swiss-electricity-mcp.git
cd swiss-electricity-mcp
pip install -e ".[dev]"

Use with Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "swiss-electricity": {
      "command": "swiss-electricity-mcp"
    }
  }
}

Cloud deployment (Streamable HTTP)

SWISS_ELECTRICITY_TRANSPORT=streamable-http \
SWISS_ELECTRICITY_HOST=0.0.0.0 \
SWISS_ELECTRICITY_PORT=8000 \
swiss-electricity-mcp

Works on Render.com, Railway, Fly.io.

Host binding (security). In HTTP mode the host defaults to 127.0.0.1 (loopback only). Bind to all interfaces with SWISS_ELECTRICITY_HOST=0.0.0.0 only inside a container, where the network boundary is the container, not the host. Setting 0.0.0.0 on a developer machine exposes the server to the local network (NeighborJack).

Docker

A multi-stage Dockerfile is provided. It runs as a non-root user (UID 10001) and sets SWISS_ELECTRICITY_HOST=0.0.0.0 explicitly for the containerised case.

docker build -t swiss-electricity-mcp .
docker run --rm -p 8000:8000 swiss-electricity-mcp

Observability & configuration

Env varDefaultPurpose
SWISS_ELECTRICITY_TRANSPORTstdiostdio or streamable-http
SWISS_ELECTRICITY_HOST127.0.0.1HTTP bind host (0.0.0.0 in containers only)
SWISS_ELECTRICITY_PORT8000HTTP port
SWISS_ELECTRICITY_LOG_LEVELINFOLog level (DEBUG/INFO/WARNING/ERROR)
SWISS_ELECTRICITY_CORS_ORIGINS(empty)Comma-separated allowed CORS origins (browser clients); never *
OTEL_EXPORTER_OTLP_ENDPOINT(unset)Enables OpenTelemetry tracing when set
SWISS_ELECTRICITY_ENVunknowndeployment.environment resource attribute for traces
  • Logging is structured JSON on stderr (stdout is reserved for the stdio JSON-RPC channel). Upstream failures are logged in full server-side but masked in client-facing responses.

  • Tracing is opt-in. Install the extra and point it at a collector:

    pip install "swiss-electricity-mcp[otel]"
    OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 swiss-electricity-mcp
    

    You get one span per tool call (mcp.tool.<name>) plus automatic httpx child spans for each upstream request. No argument values or PII are recorded.


Architecture

          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ MCP client (Claude etc.) ──────────────────────────┐
          β”‚                          stdio  or  Streamable HTTP                           β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                   β”‚  12 read-only tools (annotated)
                                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                          β”‚  FastMCP server   β”‚  egress allow-list + HTTPS gate
                                          β”‚  + structlog/OTel β”‚  per-source TTL cache + retry
                                          β””β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”˜
                  dashboard_* β”‚ tariff_*      β”‚        β”‚   β”‚   consumption_*
                              β–Ό               β–Ό        β–Ό   β–Ό
                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β”‚ Energiedashboard  β”‚ β”‚  LINDAS   β”‚ β”‚ opendata.swissβ”‚ β”‚ data.stadt-zuerich.chβ”‚
                  β”‚ .admin.ch (BFE)   β”‚ β”‚  SPARQL   β”‚ β”‚   CKAN        β”‚ β”‚   CKAN (OGD)         β”‚
                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Hybrid (live API + SPARQL + CKAN discovery), no authentication. Three reasons this is the right shape:

  1. Different latency profiles per source: Energiedashboard responds in ~200 ms (great live); LINDAS SPARQL is slower and occasionally returns 504 (longer timeout + 3 retries); CKAN is metadata-only and inherently safe.
  2. Different update cadences: Dashboard updates intraday; ElCom tariffs update once per year; OGD datasets are stable for months. Per-source TTL caching (600 s / 3600 s) reflects this.
  3. Domain separation from swiss-energy-mcp: that server covers geo and infrastructure data (power plants, grid lines). swiss-electricity-mcp covers time-series and tariffs. Both compose cleanly.

Provenance discipline

Every tool response is a Pydantic envelope carrying:

  • source β€” full attribution string (e.g. "Daten: Bundesamt fΓΌr Energie (BFE)…").
  • provenance β€” exactly one of live_api / sparql / cached / weekly_dump / stale_cache_fallback.
  • retrieved_at β€” ISO-8601 UTC timestamp.

This makes accidental misattribution structurally impossible.

Resilience

  • Retry: 3 attempts with exponential backoff (2 s / 4 s / 8 s).
  • 5xx + 429: retried. 4xx (except 429): raised immediately (permanent client error).
  • In-memory TTL cache: per-source TTLs reduce upstream load and round-trip during multi-step agent workflows.

MCP primitives β€” why Tools only

This server intentionally exposes only Tools, not Resources or Prompts. The data is parametric and query-driven (a municipality BFS number, a category, a year), which maps naturally to tool calls; there is no stable, enumerable set of documents to expose as Resources, and no curated prompt templates to ship. If a future use case needs, say, a fixed "national production mix" document, the read-only dashboard_* tools are the obvious Resource-migration candidates.

Project phase

Phase 1 β€” read-only. All 12 tools are read-only (readOnlyHint=true) with no write or destructive operations. Phase-transition criteria and the longer-term plan live in docs/roadmap.md. Security posture (egress, supply-chain, lethal-trifecta assessment) is documented in docs/security-posture.md.


Testing

# Unit tests (mocked, fast, CI default) β€” tests/test_unit.py + tests/test_security.py
PYTHONPATH=src pytest -m "not live" -v

# Live tests (hits real upstreams) β€” tests/test_live.py
PYTHONPATH=src pytest -m live -v

Unit tests cover the contract layers: Happy (response parsing), Retry (5xx, 429, 4xx), Timeout (network errors β†’ clean UpstreamUnreachableError), envelope/attribution invariants, plus security (egress allow-list, SPARQL escaping, tool-definition lock). CI runs ruff + pytest -m "not live" on Python 3.11–3.13.


MCP protocol version

This server is built on the official MCP Python SDK (mcp[cli]), pinned to >=1.2.0,<2.0.0. The MCP protocol version is negotiated by the SDK at the initialize handshake; the supported spec version tracks the pinned SDK (currently MCP spec 2025-11-25).

Update policy: SDK updates arrive as weekly Dependabot PRs. A protocol-spec bump is only adopted via an explicit SDK minor/major bump, recorded in CHANGELOG.md, and verified against the tool-definition lock (tool-definitions.lock.json).


Known limitations

  • LINDAS SPARQL 504 timeouts: the LINDAS public endpoint occasionally returns 504 under load. The 3-retry policy handles transient cases; persistent unavailability surfaces as UpstreamUnreachableError.
  • No historical PV/wind detail: Energiedashboard exposes only aggregated production mix at year level. For sub-yearly PV or wind, use consumption_search_bfe_datasets.
  • No FHIR or smart-meter data: out of scope. Future work may add a swiss-prosumer-mcp or similar.
  • Year coverage: ElCom tariff data starts in 2009. Energiedashboard mix starts in 2014.

Portfolio synergy

This server composes naturally with other portfolio servers:

  • + swiss-energy-mcp β€” combine geo/asset data (power plants) with time-series and tariffs for full energy-infrastructure analysis.
  • + meteoswiss-mcp β€” correlate consumption forecasts with weather (temperature drives heating/cooling load).
  • + fedlex-mcp β€” pair tariff data with the Stromversorgungsgesetz (StromVG) for compliance/legal context.
  • + zh-education-mcp β€” Schulamt-relevant queries combining tariffs, school counts, infrastructure budgets.

Data sources & licensing

All upstream data is Open Government Data Switzerland (OGD-CH):

  • Energiedashboard.ch Β© Bundesamt fΓΌr Energie BFE β€” Open data, free to use.
  • ElCom / LINDAS Β© EidgenΓΆssische ElektrizitΓ€tskommission ElCom β€” CC BY 4.0.
  • opendata.swiss Β© Various Swiss public bodies β€” Mostly CC0 / CC BY 4.0.
  • Stadt ZΓΌrich OGD Β© Stadt ZΓΌrich β€” CC0.

This MCP server is MIT-licensed (see LICENSE). Always cite the original data source β€” the response envelope includes the proper attribution string automatically.


Contributing

See CONTRIBUTING.md.

Security

See SECURITY.md for the security policy and how to report a vulnerability.

License

MIT License β€” see LICENSE. The upstream data keeps the licences listed under Data sources & licensing above.

Author

Hayal Oezkan Β· github.com/malkreide

Changelog

See CHANGELOG.md.

Installation

Run via uv's uvx β€” no clone or manual install needed. Add to your MCP client config (mcpServers for Claude Desktop, Cursor and Windsurf; use a top-level servers key for VS Code in .vscode/mcp.json):

{
  "mcpServers": {
    "swiss-electricity-mcp": {
      "command": "uvx",
      "args": [
        "swiss-electricity-mcp"
      ]
    }
  }
}

Rendered live from malkreide/swiss-electricity-mcp's GitHub README β€” not stored, always reflects the source repo.

1 Install Method

NameDescriptionCategorySource
pypi packageInstall via pypi (stdio transport)mcp-serverswiss-electricity-mcp

0 Comments

Login required
Log in to post a comment or update on this repo.

No comments yet β€” be the first to share an update.