Skip to main content
Glama
iancumes

energyops-mcp

by iancumes

energyops-mcp

A hand-rolled implementation of the Model Context Protocol over JSON-RPC 2.0 — no MCP SDK — plus a non-trivial local MCP server for building energy analysis: anomaly detection, flexible-load schedule optimization, and battery/solar outage-autonomy simulation.

This package is fully independent: it does not require any other repository, any LLM API key, or any building's real data. Everything it analyzes is a reproducible synthetic dataset generated from a fixed seed.

What's in here

  • energyops_mcp.protocol — the MCP transport and lifecycle layer, built directly against JSON-RPC 2.0 and the MCP 2025-06-18 spec:

    • jsonrpc.py: message parsing/serialization, standard error codes.

    • client.py: an asyncio MCP client — initialize, ping, tools/list (with pagination), tools/call, timeouts with notifications/cancelled, clean shutdown. Protocol errors (JsonRpcError) are kept structurally distinct from tool execution failures (CallToolResult(isError=True)).

    • server.py: a synchronous stdio MCP server base class (McpServer) — register tools with a decorator, it handles initialize/ping/tools/list/tools/call framing.

    • stdio_transport.py: child-process transport for the client side (newline-delimited JSON over stdin/stdout). Includes a fix for a real Windows issue — asyncio.create_subprocess_exec cannot launch a .cmd/.bat shim (like npx) directly; this is routed through cmd.exe /c automatically.

    • http_transport.py: a Streamable HTTP client transport (POST/GET to a single endpoint, Mcp-Session-Id handling, JSON and text/event-stream response modes), for talking to a remote MCP server over HTTPS.

  • energyops_mcp.energy — the analytical engine, exposed as seven MCP tools by energy/server.py:

Tool

Purpose

list_buildings

List configured buildings and their meters

import_readings

Validate and idempotently import a canonical CSV (timestamp,meter_id,energy_kwh)

get_energy_summary

Consumption, generation, peak/average demand and data quality for a period

detect_anomalies

Median-absolute-deviation anomaly detection against the historical same-time-of-day/day-type baseline

optimize_schedule

Brute-force 15-minute-resolution search for the cheapest feasible start time of each flexible load

simulate_outage

Interval-by-interval battery+solar autonomy simulation during a simulated outage, comparing the full building against critical loads only

prepare_report

Assemble selected analysis results into a Markdown report with provenance

A demo building auto-seeds on first use: five independent consumption circuits (lighting, HVAC, critical services, a pump, and general equipment) plus a solar generation meter, eight weeks of 15-minute-resolution history and one target day, generated from a fixed random seed — fully reproducible, and never double-counting energy (consumption and generation are always summed separately). A known anomaly is deliberately injected into the target day so detect_anomalies has something real to find.

Related MCP server: bim2sim-mcp

Install

Requires Python 3.13+.

git clone https://github.com/iancumes/energyops-mcp.git
cd energyops-mcp
python -m venv .venv
# Windows: .venv\Scripts\activate      macOS/Linux: source .venv/bin/activate
pip install -e ".[dev]"

No API key, no environment variables, and no other repository are required for anything in this package.

Run the server

energyops-mcp-server --db-path energyops.sqlite3
# or:
python -m energyops_mcp.energy.server --db-path energyops.sqlite3

This starts a stdio MCP server: it speaks newline-delimited JSON-RPC on stdin/stdout and writes diagnostics to stderr, so it is meant to be launched by an MCP client/host as a child process, not run interactively. --import-dir (default data/imports) sets the only directory import_readings is allowed to read a CSV from.

Connecting from your own client

Any MCP client that speaks JSON-RPC 2.0 over stdio can talk to this server. Using the client included in this package:

import asyncio
from energyops_mcp.protocol.client import McpClient
from energyops_mcp.protocol.stdio_transport import StdioTransport

async def main():
    transport = StdioTransport("python", ["-m", "energyops_mcp.energy.server"])
    client = McpClient(transport, server_name="energyops-mcp")
    await client.start()
    await client.initialize()

    tools = await client.list_tools()
    print([t.name for t in tools])

    result = await client.call_tool("list_buildings", {})
    print(result.text())

    await client.aclose()

asyncio.run(main())

Example: importing readings

result = await client.call_tool("import_readings", {"csv_path": "data/imports/january.csv"})

The CSV must have exactly the header timestamp,meter_id,energy_kwh, with UTC ISO-8601 timestamps. Re-importing the same file is a no-op (rows are keyed by (meter_id, timestamp)); unknown meters and malformed rows are reported back, not silently dropped or fatal.

Example: detecting anomalies

result = await client.call_tool(
    "detect_anomalies",
    {"building_id": "demo-building", "target_date": "2026-03-02"},
)
print(result.structured_content["incidents"])

Example: optimizing a flexible load schedule

tariff_bands = [{"start_minute": 0, "end_minute": 24 * 60, "price_per_kwh": 0.2}]
result = await client.call_tool(
    "optimize_schedule",
    {"building_id": "demo-building", "date": "2026-03-02", "tariff_bands": tariff_bands},
)
print(result.structured_content["proposed"])

Design notes

  • Protocol errors vs. tool failures are structurally distinct. A malformed message, an unknown method, or a transport failure raises JsonRpcError. A tool that runs but fails (bad input, no data for the period, an infeasible schedule) returns a normal result with isError: true instead — this is what lets a host tell "the protocol is broken" apart from "the tool reported a failure."

  • The server-side I/O loop is deliberately synchronous, not asyncio. A local tool server talks to exactly one client over one pipe and processes one request at a time, so blocking line I/O is simpler and sidesteps a well-known fragility of piping a process's own stdin/stdout through asyncio on Windows. The client side is asyncio-based because a host application typically needs to manage the child subprocess concurrently with everything else it's doing (an LLM call, a UI) — which is the well-supported case on every platform via asyncio.create_subprocess_exec.

  • Every reported figure comes from a real calculation, never from an LLM guessing. The MCP layer only decides which tool to call; energyops_mcp.energy does the arithmetic.

Testing

pip install -e ".[dev]"
pytest -v
ruff check src tests

The test suite spawns this server as a real subprocess and drives it over actual stdio pipes — not just mocks — including a hand-checkable acceptance scenario (a 10 kWh battery at 100% state of charge with a 20% reserve, ideal efficiency, a steady 2 kW load and no solar must yield exactly 4 hours of autonomy) and detection of the deliberately injected anomaly through the full protocol round trip.

License

MIT — see LICENSE.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    A lightweight MCP server that connects LLM agents to BACnet devices for building automation, enabling real-time monitoring, actuation, and task orchestration.
    5
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP server for BIM-to-building-energy simulation workflows with IFC extraction, TEASER integration, scenario modeling, weather binding, and results export for downstream WAT/ROI analysis.
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    MCP server for querying and simulating the dispatch plan of a solar PV + battery system in the Chilean electricity market, using deterministic optimization and optional DRL.
    -

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/iancumes/energyops-mcp'

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