MCP server for controlling and monitoring a Gleanvolt EV charging installation with hybrid inverter and home battery integration
Gleanvolt demonstrates strong naming conventions (verb_noun pattern: gleanvolt_overview, gleanvolt_forecast, gleanvolt_start), comprehensive descriptions (avg 180 chars, well above 10-char minimum), and explicit parameter documentation. All 13 tools have descriptions and input schemas with type definitions. Tool annotations (Destructive, Idempotent, OpenWorld) are present in source code. However, output schemas are not formally documented in the visible code, parameter descriptions lack some constraint details (e.g., ISO-8601 format validation rules), and error handling guidance is minimal. Composition is excellent: tools chain logically (quote_plan → start_targeted), and parameter naming is consistent (departBy, energyKWh, targetSocPercent). Security-conscious: destructive tools gated behind GLEANVOLT_MCP_ALLOW_WRITES environment variable.
A whole local day added up: generation, consumption, import, export, and what went into the car. Stated in kilowatt-hours, over the installation's own local day — not UTC — so this is the figure that matches a bill or a "how did yesterday go" question. Prefer this over the interval series whenever the question is about a day rather than about a moment within one.
The energy series at recording resolution, in kilowatt-hours per interval — the shape of a day rather than its total. Use it to answer "when did the export happen", not "how much was there". The window may not exceed 31 days and a longer one is refused, so ask day by day for detail and use gleanvolt_energy_day for totals. Omitting both bounds returns the most recent window.
The solar forecast the controller is planning from, as a series of expected PV power over the coming period, plus the daily totals it derives. This is the forecast the controller itself uses, not a fresh third-party lookup — so it is the right thing to quote when explaining why a plan was shaped the way it was.
Whether the controller is alive and what it can currently see: how long it has been up, and whether the inverter, the charger, the database, the forecast and the car are each reporting. Call this when another tool returns stale-looking or missing data, to tell "the controller is down" apart from "the controller is fine and the car simply has not phoned home".
Output schemas not formally documented. Tool descriptions explain what is returned (e.g., 'Returns the site description... together with the live state'), but JSON Schema for response objects is not visible in source code. LLMs cannot reliably extract fields or plan downstream calls without explicit output schemas.
Parameter constraints lack formal specification. Descriptions mention 'ISO-8601 with an offset' and 'GUID' but do not declare regex patterns, length limits, or enum values in schema. LLMs cannot validate input format before calling and may pass invalid values.
Inferred effective spec: 2026-07-28+.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 65 | 2026-07-28+ | v2 |
What this installation is, and what it is doing right now — call this first, before any other tool, because everything else assumes its numbers. Returns the site description (inverter, battery capacity, charger, array, time zone, the configured car) together with the live state (PV, battery, grid and charger power, state of charge, the active charge-control mode, whether the battery discharge hold is armed). Powers are instantaneous watts signed as the hardware reports them: grid positive is import, battery positive is charging. Energies are watt-hours. Timestamps are ISO-8601 with an offset.
Quote a targeted charge WITHOUT starting it: how much sun the plan expects to catch, how much grid it would have to buy and when, whether the target is reachable by the deadline at all, and what it would fall short by if not. Always call this before gleanvolt_start_targeted, and show the person the numbers. The two take the same arguments, so what is quoted is exactly what would be committed. Say the amount ONE way: either energyKWh or targetSocPercent, never both. A state-of-charge target is converted using what the car last reported, so check gleanvolt_vehicle first — a stale reading makes a percentage target wrong by however much has happened since.
One charging session in full, by its id — the detail behind a row from gleanvolt_sessions, including how the session ended and, for a targeted charge, what was promised against what was delivered.
Charging sessions in a range, newest first: when each ran, how much was delivered, how much of it was solar rather than grid, which mode produced it, and what started it. The window may not exceed 31 days and at most 500 sessions come back at once. The summary here is usually enough; call gleanvolt_session only when a specific session needs its detail.
Arm or release the home battery's discharge hold. Armed, the pack stops serving house load, so the car charges from PV and grid while the pack still charges from surplus. Orthogonal to the charge mode: either can be on without the other. This MOVES REAL HARDWARE. The result includes a 'verification' block read back from the inverter a moment after the write. Report what that block says, NOT merely that the call succeeded: on some installations the hold is accepted and then ignored, and a battery still discharging while the hold is armed is the observable symptom. Judge by the read-back power, never by the acknowledgement.
Start controlled charging in a mode. This MOVES REAL HARDWARE — confirm with the person before calling it, and say which mode and why. Modes: 'solar' follows live surplus once the home battery is full. 'forecasted' lets today's forecast decide how much of the sun the car may have, so the home battery still reaches 100% by its evening deadline. 'fastNoBattery' charges flat out from PV and grid with the home battery held out of it, and ends itself when the car is full. 'solarGrid' follows the surplus while it clears the owner-set minimum, tops a surplus below the charger's 6 A floor up from the grid with the home battery held out of it, and ends itself once today's forecast has no such sun left. For a targeted charge use gleanvolt_quote_plan then gleanvolt_start_targeted instead — this tool refuses 'targeted'. To stop, use gleanvolt_stop rather than passing 'off'.
Start a targeted charge — deliver a stated amount by a stated time. This MOVES REAL HARDWARE. Call gleanvolt_quote_plan FIRST with the same arguments and show the person what it says, including any shortfall and any grid it expects to buy. Then call this with those arguments unchanged, plus the planId the quote returned: the response will say whether the forecast has moved since the quote. The planId is advisory — it never blocks a start.
Stop controlled charging: return the mode to 'off' and leave the charger's setpoint where it is. This MOVES REAL HARDWARE. Safe to call when nothing is running.
What the car last said about itself: state of charge, whether it is plugged in, and — the field that matters most — when that reading was captured. A parked car reports when it feels like it, so treat the capture time as part of the answer and say how old the reading is rather than presenting it as current. If the reading is hours old, a state-of-charge target computed from it will be wrong by however much has happened since.
Error handling guidance absent. Tools do not document what errors can occur, how to recover, or what the LLM should do next. E.g., gleanvolt_session with invalid GUID returns nothing visible; gleanvolt_quote_plan with unreachable target should guide the agent to adjust parameters.
Pagination limits documented in descriptions but not enforced in schema. gleanvolt_energy_intervals and gleanvolt_sessions state '31 days max' and '500 sessions max' in text, but no maxItems or pattern constraint in JSON Schema. LLMs may ignore text limits and pass invalid ranges.
Mutually exclusive parameters not formally declared. gleanvolt_quote_plan and gleanvolt_start_targeted accept both energyKWh and targetSocPercent; descriptions say 'Omit when asking in state of charge' but schema does not enforce mutual exclusion. LLMs may pass both, causing ambiguous behavior.