Skip to main content
Glama

REE MCP Server

by ESJavadex
CLAUDE.md18.1 kB
# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview REE MCP Server: A production-ready MCP (Model Context Protocol) server for accessing Red Eléctrica Española (REE) electricity data through the eSios API. Built with strict Domain-Driven Design (DDD) principles, Clean Architecture, and comprehensive testing. **Key Constraint**: This codebase follows a NO MOCKING policy in the domain layer. Domain logic must remain pure and testable without mocks. ## Development Commands ### Environment Setup ```bash # Using uv (recommended) uv venv source .venv/bin/activate # On Windows: .venv\Scripts\activate uv pip install -e ".[dev]" # Using standard pip python -m venv venv source venv/bin/activate pip install -e ".[dev]" ``` ### Testing ```bash # Run all tests pytest # Run only unit tests (domain layer) pytest tests/unit/ # Run integration tests (infrastructure with mocked HTTP) pytest tests/integration/ # Run e2e tests pytest tests/e2e/ # Run with coverage report pytest --cov=src/ree_mcp --cov-report=html # Run specific test file pytest tests/unit/domain/test_value_objects.py # Run specific test function pytest tests/unit/domain/test_value_objects.py::test_indicator_id_validation ``` ### Type Checking ```bash # Type check entire codebase (mypy strict mode enabled) mypy src/ree_mcp/ # Type check specific module mypy src/ree_mcp/domain/ ``` ### Linting and Formatting ```bash # Check code style ruff check . # Auto-fix issues ruff check --fix . # Format code (line length: 100) ruff format . ``` ### Running the Server ```bash # STDIO mode (default for MCP) python -m ree_mcp # HTTP mode for testing/debugging python -c "from ree_mcp.interface.mcp_server import mcp; mcp.run(transport='http', port=8000)" ``` ## Architecture Overview This project implements **Clean Architecture** with **Domain-Driven Design (DDD)** in four distinct layers: ### 1. Domain Layer (`src/ree_mcp/domain/`) **Pure business logic with ZERO external dependencies. No I/O, no frameworks, no mocking in tests.** - **Value Objects** (`value_objects/`): Immutable domain concepts - `IndicatorId`: Strongly-typed IDs with validation (must be > 0) - `DateTimeRange`: Date ranges with business rules (max 366 days, start < end) - `TimeGranularity`: Enum for aggregation levels (raw/hour/day/fifteen_minutes) - `MeasurementUnit`: Power (MW), Price (€/MWh), Emissions (tCO₂eq) - `GeographicScope`: Peninsular, National, Canarias, etc. - **Entities** (`entities/`): Business objects with identity - `Indicator`: Electricity indicator with metadata + helper methods (is_demand, is_generation) - `IndicatorValue`: Single time-series data point - `IndicatorData`: **Aggregate Root** - indicator + values + computed statistics - **Repository Interface** (`repositories/`): Abstract contract defined in domain - `IndicatorRepository`: Interface for data access (implemented in infrastructure) - **Domain Exceptions** (`exceptions.py`): Business rule violations - `InvalidIndicatorIdError`, `InvalidDateRangeError` - `IndicatorNotFoundError`, `NoDataAvailableError` **Critical Rule**: Domain tests (`tests/unit/domain/`) test pure logic without mocks, HTTP, or external dependencies. ### 2. Application Layer (`src/ree_mcp/application/`) **Orchestrates domain objects to fulfill use cases. Depends only on domain.** - **Use Cases** (`use_cases/`): Business workflows - `GetIndicatorDataUseCase`: Fetch time-series data via repository - `ListIndicatorsUseCase`: Retrieve all indicators - `SearchIndicatorsUseCase`: Find indicators by keyword - **DTOs** (`dtos/`): Data Transfer Objects for boundaries - `GetIndicatorDataRequest`: Input validation with Pydantic - `IndicatorDataResponse`: Structured output - Uses Pydantic for validation and serialization ### 3. Infrastructure Layer (`src/ree_mcp/infrastructure/`) **Implements domain interfaces. Handles external dependencies.** - **HTTP Client** (`http/ree_api_client.py`): - Async httpx client with context manager - **Exponential backoff retry** for transient failures (HTTP 500, timeouts) - Configurable via Settings (timeout, max_retries, backoff_factor) - **Repository Implementation** (`repositories/ree_indicator_repository.py`): - Implements `IndicatorRepository` interface - Maps API responses to domain entities - Handles data parsing and transformation - **Configuration** (`config/settings.py`): - Pydantic-settings for type-safe env config - Loads from `.env` file with defaults - Required: `REE_API_TOKEN` - Optional: `REE_API_BASE_URL`, `REQUEST_TIMEOUT`, `MAX_RETRIES`, `RETRY_BACKOFF_FACTOR` **Integration Tests** (`tests/integration/`): Test infrastructure with mocked HTTP (pytest-httpx). ### 4. Interface Layer (`src/ree_mcp/interface/`) **Exposes domain functionality via MCP protocol. Refactored for maintainability following DRY, KISS, and SOLID principles.** - **MCP Server** (`mcp_server.py`): FastMCP integration (923 lines, 30% reduction from original) - **15 MCP Tools**: Low-level (`get_indicator_data`, `list_indicators`, `search_indicators`) + High-level convenience tools (demand, generation, renewables, carbon, pricing, storage, grid stability, forecasting) - **2 MCP Resources**: `ree://indicators`, `ree://indicators/{id}` - Uses helper classes and services for clean separation of concerns - Comprehensive error handling (DomainException → JSON errors) - **Indicator Configuration** (`indicator_config.py`): Centralized indicator metadata - `IndicatorMetadata`: Frozen dataclass with ID, name, category, description - `IndicatorCategory`: Enum for categorizing indicators (DEMAND, GENERATION, PRICE, etc.) - `IndicatorIDs`: Repository of 40+ commonly used indicators with typed access - Helper methods: `get_generation_mix_sources()`, `get_renewable_sources()`, `get_international_exchanges()` - Eliminates magic numbers throughout the codebase - **Tool Helpers** (`tool_helpers.py`): Reusable utility classes - `DateTimeHelper`: Date/time operations (build ranges, validate formats) - `ResponseFormatter`: Consistent JSON formatting (success, error, domain exceptions) - `ToolExecutor`: Dependency injection container with async context management - Eliminates code duplication across all tools - **Tool Services** (`tool_services.py`): Complex multi-indicator analysis - `DataFetcher`: Multi-indicator data fetching service - `GenerationMixService`: Generation breakdown and timeline analysis - `RenewableAnalysisService`: Renewable energy calculations and percentages - `GridStabilityService`: Synchronous vs variable renewable balance - `InternationalExchangeService`: Cross-border flow analysis with net balance - Encapsulates complex business logic for high-level tools ## Key Design Principles ### Dependency Inversion Principle (DIP) - Domain defines `IndicatorRepository` interface - Infrastructure implements the interface - Application depends on domain abstraction, NOT concrete implementation - High-level modules (domain) never depend on low-level modules (infrastructure) ### Repository Pattern - Domain defines "what" (interface) - Infrastructure defines "how" (implementation) - Enables swapping implementations (e.g., switch from REE API to database) ### Value Objects - Immutable (frozen dataclasses) - Self-validating (validation in `__post_init__`) - Examples: `IndicatorId(42)` raises `InvalidIndicatorIdError` if < 1 ### Aggregate Roots - `IndicatorData` is an aggregate root - Ensures consistency boundary for indicator + values + statistics - Single entry point for data access ## Testing Strategy ### Unit Tests (`tests/unit/`) **Test domain logic and helpers in isolation. NO mocking allowed in domain tests.** **Domain Tests** (`tests/unit/domain/`): - `test_value_objects.py`: 26 tests - Validation (invalid IDs raise errors) - Conversions (to_api_params, from_iso_string) - Factory methods (last_n_days, last_n_hours) - Enum parsing - `test_entities.py`: 15 tests - Entity equality (by ID) - Business methods (is_demand_indicator, is_generation_indicator) - Statistics (min, max, avg) - Geographic filtering **Interface Tests** (`tests/unit/interface/`): - `test_tool_helpers.py`: 29 tests - DateTimeHelper: datetime range building, validation - ResponseFormatter: success/error formatting, domain exceptions - Focus on pure utility functions - `test_indicator_config.py`: 15 tests - IndicatorMetadata creation and immutability - IndicatorIDs: verify all constants exist - Helper methods: generation mix, renewables, exchanges, synchronous sources - Category verification ### Integration Tests (`tests/integration/infrastructure/`) **Test infrastructure with mocked HTTP (pytest-httpx).** - `test_ree_api_client.py`: 10 tests - Successful API calls - Error handling (404, 500, timeouts) - Retry logic with exponential backoff - Context manager usage ### E2E Tests (`tests/e2e/`) **Test complete workflows end-to-end.** - `test_mcp_server.py`: 16 tests - All 15 MCP tool invocations - Error scenarios (invalid IDs, date ranges) - Real API tests (marked for manual execution) **Coverage**: 96 tests covering all layers with 90% overall code coverage. ## Configuration Management ### Environment Variables (`.env` file) ```env REE_API_TOKEN=your_token_here # Required REE_API_BASE_URL=https://api.esios.ree.es # Optional REQUEST_TIMEOUT=30 # Optional MAX_RETRIES=3 # Optional RETRY_BACKOFF_FACTOR=0.5 # Optional ``` **Security**: `.env` is gitignored. Use `.env.example` as template. ### Settings Class (`settings.py`) - Uses Pydantic-settings for type-safe config - Cached via `@lru_cache` in `get_settings()` - Field validation (e.g., timeout: 1-300 seconds) ## Common Patterns ### Adding a New MCP Tool (Refactored Pattern) **Follow this pattern to maintain DRY, KISS, and SOLID principles:** 1. **Use existing indicator IDs** from `IndicatorIDs` in `indicator_config.py`, or add new ones if needed 2. **Use helper classes** from `tool_helpers.py`: ```python from interface.tool_helpers import DateTimeHelper, ResponseFormatter, ToolExecutor @mcp.tool() async def my_new_tool(date: str, hour: str = "12") -> str: try: # Use DateTimeHelper for date operations start_datetime, end_datetime = DateTimeHelper.build_datetime_range(date, hour) # Use ToolExecutor for dependency injection async with ToolExecutor() as executor: use_case = executor.create_get_indicator_data_use_case() # ... tool logic ... # Use ResponseFormatter for consistent output return ResponseFormatter.success(result) except DomainException as e: return ResponseFormatter.domain_exception(e) except Exception as e: return ResponseFormatter.unexpected_error(e, context="my tool context") ``` 3. **For complex multi-indicator operations**, create a service in `tool_services.py`: ```python class MyAnalysisService: def __init__(self, data_fetcher: DataFetcher): self.data_fetcher = data_fetcher async def analyze(self, start_date: str, end_date: str) -> dict[str, Any]: # Use IndicatorIDs for constants indicators = { "demand": IndicatorIDs.REAL_DEMAND_PENINSULAR, "generation": IndicatorIDs.NUCLEAR, } # Use DataFetcher to get data data = await self.data_fetcher.fetch_multiple_indicators( indicators, start_date, end_date ) # Process and return return {"result": data} ``` 4. **Write tests**: - Unit tests for services in `tests/unit/interface/` - E2E test for the MCP tool in `tests/e2e/test_mcp_server.py` ### Adding a New Use Case 1. Define DTO in `application/dtos/` (request + response) 2. Create use case in `application/use_cases/` (depends on repository interface) 3. Add MCP tool in `interface/mcp_server.py` using the refactored pattern above 4. Write unit tests (domain logic) + e2e test (tool invocation) ### Adding a New Domain Concept 1. Create value object or entity in `domain/` 2. Add validation in `__post_init__` 3. Write unit tests (validation, equality, methods) 4. NO external dependencies allowed in domain ### Adding New Indicator Constants Add to `indicator_config.py`: ```python class IndicatorIDs: # Add new indicator MY_NEW_INDICATOR = IndicatorMetadata( id=12345, name="My Indicator Name", category=IndicatorCategory.GENERATION, description="Detailed description" ) # If needed, add to helper methods @classmethod def get_my_indicator_group(cls) -> dict[str, IndicatorMetadata]: return { "my_indicator": cls.MY_NEW_INDICATOR, # ... } ``` ### Error Handling Pattern (Updated) **Always use `ResponseFormatter` for consistent error handling:** ```python from interface.tool_helpers import ResponseFormatter from domain.exceptions import DomainException try: # Use case execution response = await use_case.execute(request) return ResponseFormatter.success(response.model_dump()) except DomainException as e: return ResponseFormatter.domain_exception(e) except Exception as e: return ResponseFormatter.unexpected_error(e, context="Operation context") ``` ## Important Indicator IDs Commonly used indicators from the REE API (1,967+ total available): ### Demand - `1293`: Real Demand (Peninsular) - MW, 5 min - `2037`: Real National Demand - MW, 5 min - `1292`: Demand Forecast - MW, hourly ### Generation - `549`: Nuclear - MW, 5 min - `2038`: Wind (National) - MW, 5 min - `1295`: Solar PV (Peninsular) - MW, 5 min - `2041`: Combined Cycle (National) - MW, 5 min - `2042`: Hydroelectric (National) - MW, 5 min ### Prices - `600`: SPOT Market Price - €/MWh, 15 min - `1013`: PVPC Rate - €/MWh, hourly ### Emissions - `10355`: CO₂ Emissions - tCO₂eq, 5 min See `ree_docs.md` for complete indicator reference (gitignored). ## MCP Tools Reference ### Core Tools (Low-Level API Access) #### `get_indicator_data` Fetch time-series data for any indicator. ```python await get_indicator_data( indicator_id=1293, # Required: REE indicator ID start_date="2025-10-08T00:00", # Required: ISO format end_date="2025-10-08T23:59", # Required: ISO format time_granularity="hour" # Optional: raw/hour/day/fifteen_minutes ) ``` #### `list_indicators` List all 1,967+ available indicators with pagination. ```python await list_indicators(limit=50, offset=0) ``` #### `search_indicators` Search indicators by keyword. ```python await search_indicators("demanda", limit=20) ``` ### Convenience Tools (High-Level Analysis) #### Demand & Generation **`get_demand_summary`**: Quick demand overview ```python await get_demand_summary("2025-10-08") ``` **`get_generation_mix`**: Generation breakdown at specific time ```python await get_generation_mix(date="2025-10-08", hour="12") ``` **`get_generation_mix_timeline`**: Generation breakdown over time ```python await get_generation_mix_timeline(date="2025-10-08", time_granularity="hour") ``` #### Renewable Energy & Sustainability **`get_renewable_summary`**: Renewable generation analysis ```python await get_renewable_summary(date="2025-10-08", hour="12") ``` Returns: Total renewable MW, variable vs synchronous breakdown, percentage of demand **`get_carbon_intensity`**: CO₂ emissions per kWh ```python await get_carbon_intensity(start_date="2025-10-08T00:00", end_date="2025-10-08T23:59", time_granularity="hour") ``` Returns: gCO₂/kWh time series + interpretation (excellent/good/moderate/poor) #### Grid Operations & Stability **`get_grid_stability`**: Synchronous vs variable renewable balance ```python await get_grid_stability(date="2025-10-08", hour="12") ``` Returns: Inertia analysis, stability level assessment **`get_storage_operations`**: Pumped storage analysis ```python await get_storage_operations(date="2025-10-08") ``` Returns: Pumping/turbining operations, efficiency metrics **`get_international_exchanges`**: Cross-border electricity flows ```python await get_international_exchanges(date="2025-10-08", hour="12") ``` Returns: Import/export by country (Andorra, Morocco, Portugal, France) #### Market & Forecasting **`get_price_analysis`**: SPOT market price analysis ```python await get_price_analysis(start_date="2025-10-08T00:00", end_date="2025-10-08T23:59") ``` Returns: Multi-country price comparison, statistics **`compare_forecast_actual`**: Demand forecast accuracy ```python await compare_forecast_actual(date="2025-10-08") ``` Returns: MAE, RMSE, MAPE, bias analysis **`get_peak_analysis`**: Peak demand patterns ```python await get_peak_analysis(start_date="2025-10-01", end_date="2025-10-31") ``` Returns: Daily max/min, load factors, efficiency metrics ## Code Quality Standards - **Type Safety**: mypy strict mode (100% type-annotated) - **Line Length**: 100 characters (ruff configured) - **Testing**: All new code requires tests (unit + integration/e2e) - **No Mocking**: Domain tests must be pure (no mocks/stubs) - **Immutability**: Value objects are frozen dataclasses - **Validation**: All inputs validated (Pydantic for DTOs, domain for value objects) ## Tools Configuration ### pytest (`pyproject.toml`) - `asyncio_mode = "auto"`: Automatic async test detection - Coverage enabled by default - Testpaths: `["tests"]` ### mypy - Strict mode enabled - Python 3.11+ target - `fastmcp` imports ignored (no type stubs) ### ruff - Selects: pycodestyle, pyflakes, isort, flake8-bugbear, comprehensions, pyupgrade - Per-file ignores: tests can use asserts (S101) ## Installation Script Use `./INSTALL_COMMAND.sh` to add server to Claude Code: - Reads `REE_API_TOKEN` from `.env` - Runs `claude mcp add-json` with proper configuration - Verifies token exists before proceeding

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ESJavadex/ree-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server