euenergy-mcp
README.md
<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](https://modelcontextprotocol.io) server that wraps the
**[euenergy.live](https://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 `null` with 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:
```bash
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](https://docs.docker.com/get-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:
```dotenv
EUENERGY_TOKEN=your-euenergy-token
```
## Running
### Docker Compose (recommended)
```bash
docker compose up -d --build
```
The MCP endpoint is then available at **`http://localhost:3000/mcp`**. Override the host port with
`HOST_PORT`:
```bash
HOST_PORT=8080 docker compose up -d --build
```
### Docker
```bash
docker build -t euenergy-mcp .
docker run --rm -p 3000:3000 --env-file secret.env euenergy-mcp
```
### Local (Python)
```bash
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:
```json
{
"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 a `price_eur_kwh` convenience 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.000Z` and 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 UTC `ts` yourself using the zone's offset (CET = UTC+1, CEST = UTC+2).
## Development
```bash
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_hours` and `get_current_price` cover **today** only; use `get_prices(zone, "tomorrow")`
or `price_history` for other days.
- `price_change_pct` in `price_summary` is 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.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues