Swiss Electricity Mcp

io.github.malkreide由 GitHub 账号 malkreide 发布v0.3.0更新于 Oct 25, 2026

BFE energy dashboard, ElCom tariffs, public consumption data

已验证STDIO仅桌面金融数据与分析

概览

AI 生成的概览

只读 MCP 服务器,提供瑞士官方电力数据:BFE 能源仪表盘指标、ElCom 各市镇电价以及公开用电数据集。

可以做什么
从 BFE 能源仪表盘获取全国发电结构、用电预测、蓄水湖蓄水率和消费者价格指数,每条响应都带来源标注。按市镇、类别和年份查询 ElCom 电价,并拆分能源、电网使用、KEV 和附加费。与全国或州中位数对比,并最多并排比较 20 个市镇。在 BFE 和苏黎世市开放数据目录中检索原始用电时间序列。检查上游数据源的健康状态和延迟。
适用场景
适合比较瑞士各市镇或州历年电价、将某市镇与全国中位数对标,或获取带明确来源标注的全国发电、用电和蓄水数据。
运行要求
作为 Python 包在本地运行,通常用 uvx 启动或通过 pip 安装;无需认证、API 密钥或环境变量。需要访问瑞士公开开放数据端点的网络连接。HTTP 传输模式为可选项,默认仅绑定回环地址。
安装前请注意
全部十二个工具均为只读,不会写入或修改数据。在 HTTP 模式下将主机绑定到所有网卡会把服务器暴露给本地网络,只应在容器内这样做。查询会发往瑞士政府公开端点,因此市镇和类别参数会发送给这些第三方。

安装

在 SourceWeft 中

  1. 打开 控制台中的 Swiss Electricity Mcp,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 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

bash
pip install swiss-electricity-mcp

From source

bash
git clone https://github.com/malkreide/swiss-electricity-mcp.gitcd swiss-electricity-mcppip install -e ".[dev]"

Use with Claude Desktop

Add to claude_desktop_config.json:

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

Cloud deployment (Streamable HTTP)

bash
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.

bash
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:

    bash
    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)                                          ┌────────▼──────────┐                                          │  MCPServer (mcp)  │  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: up to 3 retries, i.e. at most 4 attempts. Waits grow exponentially (base 2 s / 4 s / 8 s), each jittered to 0.5–1.5× so that clients do not return in lockstep after an outage. No single wait exceeds 20 s. A Retry-After sent with 429 or 503 takes precedence (plus 0–25 %, also capped at 20 s).
  • Time budget: 25 s for the whole call, all attempts and waits together. A wait that would overrun the budget ends the call instead, so there may be fewer than 4 attempts.
  • 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.


MCP Protocol Version

This server speaks two protocol eras over the same endpoint. The client's first request on a connection decides which one applies; a later claim from the other era is refused.

EraRevisionWho reaches it
initialize handshake2024-11-05 … 2025-11-25What today's clients speak. The server answers with the revision asked for, or with the 2025-11-25 ceiling when the request asks for something newer.
Per-request envelope2026-07-28A request carrying the 2026-07-28 _meta envelope opens a modern connection.

Both revisions are pinned in tests/test_protocol_version.py and asserted against the installed SDK, so a Dependabot bump of mcp cannot move either one silently. The handshake ceiling is measured against a live initialize through the assembled ASGI stack, not read off a constant name.

Note that the SDK's LATEST_PROTOCOL_VERSION is an alias for the modern era, not for the handshake era — pinning against it alone would leave the era that current clients actually negotiate free to drift.

Update policy. When the gate fails, do not edit the constant blindly: read the spec changelog between the two revisions, verify the server still behaves, then move the constant, this section, README.de.md and CHANGELOG.md together. A spec bump is adopted only through an explicit mcp minor/major bump, recorded in CHANGELOG.md and verified against the tool-definition lock (tool-definitions.lock.json).

What the server sets natively for 2026-07-28

The SDK reaching a revision is not the same as a server speaking it. Two surfaces the SDK leaves to the server, and what happens when it is left alone:

SurfaceWhat this server setsWhat the SDK does without it
serverInfo — stamped into the _meta of every result, not just the initialize replyname, title, version, websiteUrlSubstitutes nothing. An unversioned server reports "version": "" to every caller, on both eras and both transports.
ttlMs / cacheScope on the cacheable methods (SEP-2549)300000 ms, scope public, on tools/list, server/discover, prompts/list, resources/list, resources/templates/listCacheHint() defaults to ttl_ms=0, scope="private" — the wire form of "already stale, never share". Every client then re-lists on every connection.

2026-07-28 moved serverInfo from a once-per-connection handshake footnote to a stamp on the running traffic, which is what makes the empty version worth a gate rather than a shrug.

The three empty directories carry a hint on purpose. MCPServer registers their handlers unconditionally and server/discover lists prompts and resources among its capabilities, so the surface exists on the wire — it is just empty. This server has no way to register a prompt or a resource at runtime, so it stays empty for the life of the process, which makes it the safest thing here to cache.

Both are measured off a real response rather than read back off the constructor: tests/test_server_identity.py checks each identity field separately on each era, and tests/test_cache_hints.py reads the hints out of a live client session. Each has a negative control against a bare MCPServer("kontrolle") — without it, an assertion that the SDK one day starts satisfying by itself would keep reading as proof that this server sets it.


Testing

bash
# Unit tests (mocked, fast, CI default) — tests/test_unit.py + tests/test_security.pyPYTHONPATH=src pytest -m "not live" -v
# Live tests (hits real upstreams) — tests/test_live.pyPYTHONPATH=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.

Auditing the ruff pin across the portfolio

scripts/pin_audit.py checks whether a server's own pin guards actually hold. It is not a CI gate — it needs the sibling repositories on disk — but it is worth running whenever a pin convention changes or a new server joins:

bash
python scripts/pin_audit.py ../*-mcp

It measures black-box: prepend an ordinary second pre-commit hook with its own rev:, run the guard, read the exit code, restore the file. Two guards in the portfolio used to report that hook's version as the ruff pin, turning CI red with a number nobody had written. A positive control (misconfigure the ruff hook's own rev) separates "correctly scoped" from "never reads the file" — without it, a guard that ignores the config looks like a clean bill of health.

Where the test data comes from

The fixtures under tests/fixtures/ are recorded from the live sources and dated. Source, retrieval date, selection rule and SHA-256 for every file: tests/fixtures/PROVENANCE.md.

bash
python scripts/record_fixtures.py   # re-record

The requests are built by the production code. The script calls ElComSparqlClient and EnergyDashboardClient and captures the answer through an httpx transport, rather than retyping the SPARQL alongside. A fixture that answers a slightly different question than the server asks proves the wrong answer — quietly, because it looks plausible. At 40 lines of SPARQL, "slightly different" is the normal case, not the exception.

Two selection rules are deliberately more than "the first N":

  • The storage-lake series runs into the future. After the last measured day come rows with a null measurement — 94 of them on the recording day. They are kept on purpose: without them, no test could show that the tool skips them.
  • What counts as a measurement is named per file, not guessed. The first version of this used "any field other than date is non-null", which is wrong: those future rows do carry values — the five-year reference curves — just no measurement.

Where a search is trimmed, count keeps its real value: it says how much is not in the file.


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):

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

来源:README.md,提交 2c4cbd6

工具

0
SourceWeft 尚未在沙箱中启动过该软件包,因此其工具未知。

版本历史

2
  1. v0.3.0最新Sep 27, 2026
  2. v0.2.5Sep 16, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分