<p align="center"> <img src="assets/banner.svg" alt="euenergy-mcp — European day-ahead electricity prices, a read-only MCP server for AI agents" width="100%"> </p>
<p align="center"> <a href="#"><img alt="Python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-3776AB?logo=python&logoColor=white"></a> <a href="https://modelcontextprotocol.io"><img alt="MCP" src="https://img.shields.io/badge/MCP-streamable--http-22C55E"></a> <a href="Dockerfile"><img alt="Docker" src="https://img.shields.io/badge/docker-ready-2496ED?logo=docker&logoColor=white"></a> <a href="#"><img alt="Access: read-only" src="https://img.shields.io/badge/access-read--only-475569"></a> </p>
euenergy-mcp
A small Model Context Protocol server that wraps the euenergy.live day-ahead electricity price API as curated, read-only tools for AI agents — hourly prices, the cheapest hours, the current price, and a cross-zone snapshot for ~41 European bidding zones.
Day-ahead market results (sourced from ENTSO-E) are published around midday CEST for the next delivery day and are static thereafter — so the data only changes about once per day. The server caches upstream responses with a long TTL (default 1 hour) keyed by endpoint + params.
Features
- Six read-only tools covering the whole API surface: zones, prices (today/tomorrow), the
current hour, cheapest hours, a cross-zone summary, and a historical date range.
- EUR/MWh and EUR/kWh — every price is surfaced as the raw
price_eur_mwh(exactly as
published) and a price_eur_kwh convenience value (price / 1000).
- Never fakes a zero — a missing or unparseable upstream price/load is surfaced as
nullwith a
warnings entry and partial: true, never silently coerced to 0.
- Honest about time — hour timestamps are passed through as UTC, never silently shifted (see
the timezone caveat below).
- Long-TTL cache keyed by zone + date, appropriate for once-a-day data.
- Structured errors — every tool returns
{"error": …, "error_type": …}instead of throwing
across the tool boundary.
Tools
| Tool | What it answers | |------|-----------------| | list_zones() | The ≈41 bidding zones: code, ENTSO-E eic, country, label, resolution, currency, lat/lon. | | get_prices(zone, day?) | Hourly day-ahead prices for a zone. day = today (default) or tomorrow. | | get_current_price(zone) | The price for the current UTC hour (and the next hour). | | cheapest_hours(zone, hours?) | The cheapest individual hours today (default 4; upstream clamps to 24) — for load-shifting. | | price_summary() | Cross-zone snapshot: per-zone average price, average load (MW), and day-over-day change. | | price_history(zone, date_from, date_to) | Hourly prices over a date range (YYYY-MM-DD); pass equal dates for a single past day. |
All prices are EUR/MWh on the wire (plus a price_eur_kwh convenience value). All timestamps and day boundaries are UTC. The server is strictly read-only — no write/control endpoints exist.
Prerequisites
- A free euenergy.live API token, minted with your email:
curl -X POST 'https://euenergy.live/api/v1/keys?email=you@example.com&label=euenergy-mcp'
The token is returned/emailed; pass it as a Bearer token (the server does this for you). When using this data publicly, attribute "Data via euenergy.live (CC-BY-4.0)".
- Docker (recommended) or Python 3.10+.
Configuration
Configuration is entirely via environment variables:
| Variable | Required | Default | Description | |----------|:--------:|---------|-------------| | EUENERGY_TOKEN | ✅ | — | Your euenergy.live API token. | | EUENERGY_BASE_URL | | https://euenergy.live/api/v1 | API base URL (override only for testing/proxying). | | EUENERGY_TIMEOUT | | 20 | HTTP timeout, seconds. | | EUENERGY_CACHE_TTL | | 3600 | Response cache TTL, seconds. Long by design — data changes ~once/day. | | MCP_PORT | | 3000 | Port the server listens on. | | TZ | | UTC | Only affects log readability — prices and timestamps are always UTC. |
The token is read lazily: importing the module never requires it; only making a request does. A missing token fails fast with a clear message.
Put the secret in a secret.env file (git-ignored) for Docker Compose:
EUENERGY_TOKEN=your-euenergy-token
Running
Docker Compose (recommended)
docker compose up -d --build
The MCP endpoint is then available at http://localhost:3000/mcp. Override the host port with HOST_PORT:
HOST_PORT=8080 docker compose up -d --build
Docker
docker build -t euenergy-mcp .
docker run --rm -p 3000:3000 --env-file secret.env euenergy-mcp
Local (Python)
pip install -r requirements.txt
export EUENERGY_TOKEN=your-euenergy-token
python server.py
Connecting an MCP client
The server speaks streamable HTTP. Point any MCP client at the /mcp endpoint:
{
"mcpServers": {
"euenergy": {
"url": "http://localhost:3000/mcp"
}
}
}
How it works
MCP client ──HTTP /mcp──> euenergy MCP (FastMCP) ──> euenergy.live API (v1)
GET /zones
GET /summary/latest
GET /prices/{today,tomorrow}
GET /prices?zone&from&to
GET /cheapest-hours
Units & timezone caveats
[!IMPORTANT] EUR/MWh → EUR/kWh. Prices are published in EUR/MWh. Each tool returns the raw
price_eur_mwh(the source of truth, unrounded) plus aprice_eur_kwhconvenience value (price / 1000, rounded to 5 decimal places, banker's rounding). To compare against a home electricity bill, use the kWh value.
[!IMPORTANT] Everything is UTC. Hour timestamps look like
2026-06-22T10:00:00.000Zand the API's "day" is a UTC calendar day (00:00Z–23:00Z). The day-ahead market itself clears in CET/CEST, so a zone's local midnight does not line up with hour 0 of the returned day. Timestamps are surfaced exactly as received — never silently converted. If you need local-hour reasoning, convert from the UTCtsyourself using the zone's offset (CET = UTC+1, CEST = UTC+2).
Development
pip install -r requirements.txt pytest
pytest -q # network-free unit tests
EUENERGY_TOKEN=... python live_smoke.py # paced end-to-end smoke test against the real API
live_smoke.py exercises every tool and underlying endpoint against the live API and exits non-zero if any call fails.
Notes & limitations
- Read-only by design — no writes, no control endpoints.
cheapest_hoursandget_current_pricecover today only; useget_prices(zone, "tomorrow")
or price_history for other days.
price_change_pctinprice_summaryis the fractional change vs the previous published day,
as provided by the API (e.g. 0.5 ≈ +50%) — passed through verbatim.
- Data is CC-BY-4.0: attribute "Data via euenergy.live (CC-BY-4.0)" when publishing it.
- Not affiliated with euenergy.live.











