Skip to main content
Glama
michaelkrasa

Alpha ESS MCP Server

by michaelkrasa

get_one_date_energy_data

Retrieve energy data for a specific date from Alpha ESS solar and battery systems. Automatically selects the system if only one exists, or specify a serial number for targeted queries.

Instructions

Get energy data for a specific date and Alpha ESS system.
If no serial provided, auto-selects if only one system exists.

Args:
    query_date: Date in YYYY-MM-DD format
    serial: The serial number of the Alpha ESS system (optional)
    
Returns:
    dict: Energy data for the specified date with success status

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
query_dateYes
serialNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • main.py:592-663 (handler)
    Primary handler for the MCP tool 'get_one_date_energy_data'. Fetches daily power data from Alpha ESS API, structures it into TimeSeries summary, maps to energy metrics (epv, eCharge, etc.), handles serial auto-detection and errors.
    @mcp.tool()
    async def get_one_date_energy_data(query_date: str, serial: Optional[str] = None) -> dict[str, Any]:
        """
        Get energy data for a specific date and Alpha ESS system.
        If no serial provided, auto-selects if only one system exists.
        
        Args:
            query_date: Date in YYYY-MM-DD format
            serial: The serial number of the Alpha ESS system (optional)
            
        Returns:
            dict: Energy data for the specified date with success status
        """
        client = None
        try:
            # Auto-discover serial if not provided
            if not serial:
                serial_info = await get_default_serial()
                if not serial_info['success'] or not serial_info['serial']:
                    return {
                        "success": False,
                        "message": f"Serial auto-discovery failed: {serial_info['message']}",
                        "data": None,
                        "available_systems": serial_info.get('systems', [])
                    }
                serial = serial_info['serial']
    
            app_id, app_secret = get_alpha_credentials()
            client = alphaess(app_id, app_secret)
    
            # Get one day power data
            power_data = await client.getOneDayPowerBySn(serial, query_date)
    
            # Structure the timeseries data
            structured = structure_timeseries_data(power_data, serial)
            
            summary = structured.summary
            
            # Map the summary to the expected output format
            energy_data = {
                "eCharge": summary.battery.get('total_charge_kwh', 0),
                "epv": summary.solar.get('total_generation_kwh', 0),
                "eOutput": summary.grid.get('total_feedin_kwh', 0),
                "eInput": summary.grid.get('total_import_kwh', 0),
                "eGridCharge": summary.grid.get('total_charge_from_grid_kwh', 0),
                "eDischarge": summary.battery.get('total_discharge_kwh', 0),
                "eChargingPile": 0 # This data is not available in the summary
            }
    
            return {
                "success": True,
                "message": f"Successfully retrieved energy data for {serial} on {query_date}",
                "data": energy_data,
                "serial_used": serial
            }
    
        except ValueError as e:
            return {
                "success": False,
                "message": f"Configuration or parameter error: {str(e)}",
                "data": None
            }
        except Exception as e:
            return {
                "success": False,
                "message": f"Error retrieving energy data: {str(e)}",
                "data": None
            }
        finally:
            if client:
                await client.close()
  • Key helper function that aggregates raw API timeseries data into hourly averages and computes summary statistics (e.g., total_generation_kwh for solar), directly used by the handler to process power data into energy metrics.
    def structure_timeseries_data(raw_data: List[Dict], serial: str) -> TimeSeries:
        """Convert inefficient timeseries to structured format with hourly aggregation"""
        if not raw_data:
            return TimeSeries(series=[], summary=TimeSeriesSummary(total_records=0, interval="1 hour", time_span_hours=0, solar={}, battery={}, grid={}, load={}))
    
        # Group data by hour
        hourly_data = {}
        for record in raw_data:
            timestamp = record.get('uploadTime', '')
            if not timestamp:
                continue
    
            # Extract hour from timestamp (assumes format like "2024-03-21 14:30:00")
            hour = timestamp[:13] + ":00:00"  # Truncate to hour
    
            if hour not in hourly_data:
                hourly_data[hour] = {
                    "solar_power": [],
                    "load_power": [],
                    "battery_soc": [],
                    "grid_feedin": [],
                    "grid_import": [],
                    "ev_charging": []
                }
    
            # Collect all values for this hour
            hourly_data[hour]["solar_power"].append(record.get('ppv', 0))
            hourly_data[hour]["load_power"].append(record.get('load', 0))
            hourly_data[hour]["battery_soc"].append(record.get('cbat', 0))
            hourly_data[hour]["grid_feedin"].append(record.get('feedIn', 0))
            hourly_data[hour]["grid_import"].append(record.get('gridCharge', 0))
            hourly_data[hour]["ev_charging"].append(record.get('pchargingPile', 0))
    
        # Convert hourly data to averages
        series_entries = []
        for hour, data in sorted(hourly_data.items()):
            series_entries.append(TimeSeriesEntry(
                timestamp=hour,
                solar_power=round(sum(data["solar_power"]) / len(data["solar_power"])) if data["solar_power"] else 0,
                load_power=round(sum(data["load_power"]) / len(data["load_power"])) if data["load_power"] else 0,
                battery_soc=round(sum(data["battery_soc"]) / len(data["battery_soc"]), 1) if data["battery_soc"] else 0,
                grid_feedin=round(sum(data["grid_feedin"]) / len(data["grid_feedin"])) if data["grid_feedin"] else 0,
                grid_import=round(sum(data["grid_import"]) / len(data["grid_import"])) if data["grid_import"] else 0,
                ev_charging=round(sum(data["ev_charging"]) / len(data["ev_charging"])) if data["ev_charging"] else 0
            ))
    
        # Calculate summary statistics using hourly averages
        solar_values = [r.solar_power for r in series_entries]
        load_values = [r.load_power for r in series_entries]
        battery_values = [r.battery_soc for r in series_entries]
        feedin_values = [r.grid_feedin for r in series_entries]
    
        summary = TimeSeriesSummary(
            total_records=len(series_entries),
            interval="1 hour",
            time_span_hours=len(series_entries),
            solar={
                "peak_power": max(solar_values) if solar_values else 0,
                "avg_power": round(sum(solar_values) / len(solar_values)) if solar_values else 0,
                "total_generation_kwh": round(sum(solar_values) / 1000, 2)  # Convert W to kWh
            },
            battery={
                "max_soc": max(battery_values) if battery_values else 0,
                "min_soc": min(battery_values) if battery_values else 0,
                "avg_soc": round(sum(battery_values) / len(battery_values), 1) if battery_values else 0
            },
            grid={
                "total_feedin_kwh": round(sum(feedin_values) / 1000, 2),
                "peak_feedin": max(feedin_values) if feedin_values else 0
            },
            load={
                "peak_power": max(load_values) if load_values else 0,
                "avg_power": round(sum(load_values) / len(load_values)) if load_values else 0,
                "total_consumption_kwh": round(sum(load_values) / 1000, 2)
            }
        )
    
        return TimeSeries(series=series_entries, summary=summary)
  • Helper function for auto-detecting the default system serial number by querying the ESS list, used when serial parameter is not provided in the tool call.
    async def get_default_serial() -> dict[str, Any]:
        """
        Get the default serial number to use. If only one system is registered,
        returns that serial. If multiple systems, returns list for user to choose.
        
        Returns:
            dict: Result with serial info
        """
        client = None
        try:
            app_id, app_secret = get_alpha_credentials()
            client = alphaess(app_id, app_secret)
    
            # Get ESS list
            ess_list = await client.getESSList()
    
            if not ess_list or len(ess_list) == 0:
                return {
                    "success": False,
                    "message": "No Alpha ESS systems found in your account",
                    "serial": None,
                    "systems": []
                }
    
            if len(ess_list) == 1:
                # Auto-select the only system
                system = ess_list[0]
                serial = system.get('sysSn') if isinstance(system, dict) else getattr(system, 'sysSn', None)
                return {
                    "success": True,
                    "message": f"Auto-selected single system: {serial}",
                    "serial": serial,
                    "systems": ess_list
                }
            else:
                # Multiple systems - return list for user choice
                systems_info = []
                for system in ess_list:
                    if isinstance(system, dict):
                        systems_info.append({
                            "serial": system.get('sysSn'),
                            "name": system.get('sysName', 'Unknown'),
                            "status": system.get('sysStatus', 'Unknown')
                        })
                    else:
                        systems_info.append({
                            "serial": getattr(system, 'sysSn', 'Unknown'),
                            "name": getattr(system, 'sysName', 'Unknown'),
                            "status": getattr(system, 'sysStatus', 'Unknown')
                        })
    
                return {
                    "success": True,
                    "message": f"Found {len(ess_list)} systems. Please specify which serial to use.",
                    "serial": None,
                    "systems": systems_info
                }
    
        except Exception as e:
            return {
                "success": False,
                "message": f"Error getting system list: {str(e)}",
                "serial": None,
                "systems": []
            }
        finally:
            if client:
                await client.close()
  • Dataclass defining the structured TimeSeries format used internally by the handler's helper to represent processed timeseries data with series entries and summary.
    @dataclass
    class TimeSeries:
        series: List[TimeSeriesEntry]
        summary: TimeSeriesSummary
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden. It discloses the auto-selection behavior for the serial parameter, which is useful context beyond basic functionality. However, it lacks details on authentication needs, error handling, rate limits, or data format specifics.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences with zero waste: purpose statement, parameter behavior, and return value. Each sentence adds essential information, and the structure is front-loaded with the core functionality.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 2 parameters, no annotations, and an output schema exists (so return values are documented elsewhere), the description is reasonably complete. It covers purpose, parameter semantics, and basic behavior, but could improve by mentioning authentication prerequisites or error cases.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It adds meaning by specifying the date format (YYYY-MM-DD) and explaining the optional serial's auto-selection logic. This covers both parameters adequately, though it doesn't detail constraints like valid date ranges.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Get' and resource 'energy data', specifies the scope 'for a specific date and Alpha ESS system', and distinguishes from siblings like 'get_alpha_ess_data' (general) and 'get_one_day_power_data' (power vs energy).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It provides clear context about auto-selection when no serial is provided and only one system exists, which helps determine when to omit the optional parameter. However, it doesn't explicitly state when to use this tool versus alternatives like 'get_alpha_ess_data' or 'get_one_day_power_data'.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

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/michaelkrasa/alpha-ess-mcp-server'

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