Skip to main content
Glama
TMFNK

Meter MCP Server

by TMFNK

meter-mcp-server

A small server that answers one question about energy meters: "What was this meter doing on a given day?" An AI assistant such as Claude can ask it directly, get back one of four labels with a reason, and show that answer to a person instead of guessing.

Headline result: 90.0% correct on a sealed 30-day test, against 86.7% for a hand-written rule, with an explicit "unsure" whenever the data is too thin to call. Section 6 explains how small that gap really is.

1. The problem

Anyone can now ask an AI assistant "was this machine running last Tuesday?" and get a fluent, confident answer. The assistant has no built-in way to read a meter, so it will still produce an answer. That answer may be wrong and look exactly like a right one, and someone may act on it: send an engineer to a machine that was off, or ignore a compressor that ran all night.

The safer setup is to let the assistant hand the question to a small program that was built and tested for this one job, and to pass its answer back unchanged. This project is that program. It keeps the checking outside the assistant, where it can be measured and audited.

Related MCP server: gridhub-mcp

2. What it does

Energy meters record consumption every 15 minutes. A full day is 96 numbers (or 92 or 100 on the days the clocks change). The server takes those numbers plus a meter reference value (the meter's typical high reading) and classifies the day into one of four labels:

Label

What it means

active

The meter was actively consuming energy

standby

The meter was in a low-power standby state

off

The meter was off (all zeros or near-zero)

unsure

The model declines to make a forced call

unsure is a first-class answer, not a failure. When the evidence is thin, the server says so instead of guessing.

A concrete example

You send 96 numbers representing a Monday's 15-minute kWh readings, plus the meter reference (the meter's typical p95 consumption; p95 is the level 95% of readings stay under, a way to ignore brief spikes). The server replies with a structured answer like this (illustrative values):

{
  "label": "standby",
  "reason": "label is standby because night median 0.12 kWh vs meter reference 2.45 kWh; low-band share 78% of valid intervals; max zero run 12; baseline p95 2.45 kWh.",
  "confidence": 0.89,
  "abstained": false,
  "model_version": "classical_logreg_v1",
  "date": "2026-01-05"
}

The reason cites the actual numbers the model looked at, so a human can check the logic.

The three tools

A "tool" here is one action the assistant is allowed to ask the server to perform.

Tool

What you send

What you get

classify_day

One day of 92/96/100 kWh values + meter reference

Label, reason, confidence, abstained flag, model version

classify_batch

Up to 31 days in one call

Same output per day, in order

model_info

Nothing

Version, eval numbers, feature list, input limits

3. How it works

What is MCP?

MCP (Model Context Protocol) is a standard way for AI assistants to call external tools. Think of it like a USB port for AI: any assistant that speaks MCP can plug into any server that speaks MCP, without custom glue code.

When an assistant uses a tool this way, it is doing tool calling: it writes down which tool it wants and what to send, a program runs it, and the result comes back for the assistant to use. The assistant does not compute the answer itself. This project is one such server. It wraps a frozen machine-learning model behind three MCP tools, so an assistant can ask "was this meter active last Tuesday?" and get a structured answer with a reason and a confidence score.

The four layers

The server has four layers, each with one job:

src/meter_mcp/
  classifier.py     The brain: loads the frozen model, computes 26 evidence
                    features from raw values, applies the argmax-unsure rule.
                    No MCP dependency, pure Python + sklearn.

  audit.py          The notebook: appends one JSONL line per call with
                    timestamps, labels, and abstention flags. Never stores
                    raw meter values, so audit files stay small and private.

  server_stdio.py   The STDIO wrapper: exposes the three tools over
                    standard input/output for Claude Desktop, Claude Code,
                    and MCP Inspector. Adds typed inputs and audit logging.

  server_http.py    The HTTP wrapper: same three tools over Streamable HTTP
                    for shared or remote use. Adds API key auth and a
                    public /healthz endpoint.

Two plain-language notes on that list. STDIO means the assistant and the server talk through a local pipe on one computer. Streamable HTTP means they talk over a network connection, so several people can share one server. sklearn is scikit-learn, a standard library for classic statistical models.

Key design choice: the classifier is a pure library with no MCP dependency. The servers are thin wrappers that add typed inputs, audit logging, and transport. This means the same model logic runs identically over STDIO and HTTP: there is only one implementation.

The 26 evidence features

The model does not look at raw numbers directly. The classifier first computes 26 summary features (numbers that describe the shape of the day) that capture how the day looked:

  • Missing data: how many intervals are missing, what percentage

  • Zero analysis: how many zeros, the longest run of zeros, trailing zeros

  • Positive stats: p10, median, p95, max, min of non-zero values

  • Ratios: flatness (median/p95), floor-to-reference, low-positive share

  • Time buckets: weekday night, weekday day, weekend (each with low-positive share and median)

  • Context: meter reference, low threshold (35% of reference), borderline-floor flag, weekend flag

These features are the same ones used in the sibling model repo. They were chosen because they capture the difference between "active", "standby", and "off" without needing the model to learn from raw sequences.

The abstention rule

Abstention means declining to answer. The model outputs a probability for each of the four labels. The server applies a simple rule:

  1. If the top prediction is unsure, the answer is unsure.

  2. If the margin between the top two predictions is below a threshold, the answer is unsure.

  3. Otherwise, the answer is the top prediction.

The threshold is frozen at 0.0, meaning only the first clause fires. It was set in the sibling repo on 30 human-labeled training days, not on the sealed eval days. On the separate sealed eval the model declined 36.7% of days at precision 0.727 (see Section 5): it would rather say "unsure" than make a confident error.

Guardrails on the input

A guardrail is a check that stops bad input before it reaches the model.

Parameter

Type

Constraint

values

list of float or null

92, 96, or 100 values (must match the Europe/Berlin local day)

meter_reference_kwh

float

0.1 to 150.0 (the meter's whole-period p95)

date

string or null

ISO YYYY-MM-DD; defaults to 2026-01-05 (a Monday)

Values are kWh per 15-minute interval. The server rejects inputs outside the published envelope instead of treating them as predictions. This prevents unit mistakes (sending Wh as kWh) from producing confident garbage.

4. What this teaches you

This repo demonstrates tool calling: how an AI assistant hands a question to a checked program and why that is more trustworthy than letting the assistant answer alone. It also shows the abstention lesson from the sibling classifier.

  • The assistant is only as good as the tool behind it. The assistant decides what to ask. The server decides the answer, and gives the same answer every time for the same input.

  • A tool can refuse. The server rejects inputs outside its limits and answers unsure when the evidence is thin. A tool that can say no is easier to trust than one that always produces something.

  • You can check the answer. Every reply carries the numbers it was based on, and every call leaves an audit line, so a person can review what the assistant was told.

To see how the underlying model compares with a hand-written rule and with a general-purpose AI model, read the sibling repo, which holds the tests.

5. Results

The numbers below are measured in the sibling repo TMFNK/meter-day-classifier, in its outputs/results.csv, on a sealed set of 30 human-labeled days. "Sealed" means the model never saw these days and they were used only for this one measurement. The model was trained on synthetic data and frozen; no retraining happens in this repo.

System

Accuracy

Note

rules_floor_v1 (deterministic rules)

86.7% (26/30)

Pre-registered baseline from the sibling repo

classical_logreg_v1 (this server)

90.0% (27/30)

Decline 36.7% at precision 0.727, validity 1.0, p50 0.77 ms/day, zero confident errors

What the terms mean.

  • Rules floor. A hand-written set of if-then rules, fixed before the model was tested. It is the score any learned model has to beat to justify existing.

  • Accuracy. Share of the 30 sealed days labeled correctly.

  • Decline rate. Share of days the model answered unsure instead of committing: 36.7%, which is 11 of 30 days.

  • Precision (of declines). Of the days it declined, how many were genuinely ambiguous by the human labels: 0.727, which is 8 of 11.

  • Validity. Share of replies that are a well-formed answer: 1.0.

  • p50 ms/day. Median time to classify one day inside the model: 0.77 milliseconds. The server adds transport time on top, which is not measured here.

  • Zero confident errors. No day got a wrong active, standby, or off label. All 3 wrong days were unsure calls.

What the gap means. 3.3 points is one day: 27 correct against 26 out of 30. Read it as "at least as good as the rules, with a measured way to say I do not know", not as "clearly better". The value of this server is making that model reachable from an assistant, with limits and an audit trail, not a new accuracy record.

6. Where it fails

What the numbers do not show: day labels are the benchmark, not a customer savings claim. The server states what a meter did; it does not calculate kWh savings. Segment triage and a real pilot come later.

Limits, stated plainly:

  • The win over the rules is one day. With 30 test days, a 3.3-point gap is within what a single relabeled day could erase.

  • It declines a lot. 11 of 30 sealed days came back unsure, and 3 of those were days a person could label. Over a third of days would go to human review.

  • This repo tests the plumbing, not the accuracy. The tests here check that inputs, outputs, limits, and authentication behave as documented. The accuracy numbers are copied from the sibling repo and were not re-measured here.

  • Day labels only. This server classifies what a meter did. It does not recommend actions or calculate savings.

  • kWh, not Wh. Inputs are kWh per 15-minute interval. Values outside 0.1-150 kWh reference or 0-1000 kWh per interval are rejected. The server can catch a unit slip that lands outside those ranges, but cannot tell whether the assistant sent the right meter's data.

  • Europe/Berlin only. The interval count must match the local day (92/96/100). Other timezones are not supported.

  • Frozen model. The weights and threshold are vendored (copied in unchanged) byte-identical from the sibling repo. No retraining, no online learning.

  • Template reasons. The reason string cites printed numbers and nothing more. It is a communication artifact, not a second prediction.

  • Shared-key auth. HTTP auth is a shared API key, not per-user identity. It keeps casual traffic out; it does not attribute calls to individual users.

  • Best-effort audit. Audit write failures emit a warning and do not change the classification response. Deployments must mount the writable audit volume if they need persistence.

7. Try it yourself

What you need. Python (the Docker image uses 3.12) and the packages in requirements-dev.txt. No GPU. The model is a small classic statistical model, so an ordinary laptop is enough.

pip install -r requirements-dev.txt
export PYTHONPATH="$PWD/src"
python3 -m pytest -q
bash reproduce.sh

reproduce.sh runs compile checks, contract tests, a STDIO tool list, an artifact freeze check (confirming the model files are unchanged), and an HTTP health/auth check. It exits 0 only if everything passes.

Run the server over STDIO (Claude Desktop, Claude Code, MCP Inspector)

export PYTHONPATH="$PWD/src"
python3 -m meter_mcp.server_stdio

The server reads JSON-RPC messages (a simple request and response format) from stdin and writes responses to stdout. Claude Desktop and Claude Code discover it as an MCP server automatically.

Run the server over HTTP (shared or remote use)

export MCP_API_KEY='replace-with-a-long-random-key'
python3 -m meter_mcp.server_http --port 8000
curl -s localhost:8000/healthz

The HTTP server adds two things on top of STDIO:

  • /healthz: a public liveness probe that returns status, model version, and tool list. No auth needed.

  • /mcp: the MCP protocol endpoint. Requires X-API-Key or Authorization: Bearer header. Without a key, it returns 401.

The API key is a shared secret, not per-user identity. It keeps casual traffic out; it does not attribute calls to individual users.

Docker / Apple containers

# Docker
docker build -t meter-mcp .
docker run -d --name meter-mcp -p 8000:8000 -e MCP_API_KEY="$MCP_API_KEY" meter-mcp

# Apple containers (Davit, no Docker)
container build -t meter-mcp .
container run -d --name meter-mcp -p 8000:8000 -e MCP_API_KEY="$MCP_API_KEY" meter-mcp

The Dockerfile uses python:3.12-slim, runs as non-root, and mounts a writable volume for audit logs. The health check hits /healthz every 30 seconds.

Project layout

src/meter_mcp/
  __init__.py        Version, model constants, label list
  classifier.py      Frozen model + 26-feature engineering + argmax-unsure rule
  audit.py           JSONL audit writer (summaries only, never raw values)
  server_stdio.py    FastMCP STDIO server (3 tools, typed inputs, audit)
  server_http.py     Streamable HTTP server (API key, /healthz, same tools)

artifacts/
  model.joblib           Frozen sklearn logreg (C=1.0), vendored from sibling
  operating_point.json   Frozen threshold, medians, feature list, CV tables

tests/
  test_contract.py       Contract tests + STDIO integration + HTTP auth tests

Dockerfile              python:3.12-slim, non-root, healthcheck, audit volume
docker-compose.yml      Local HTTP run with MCP_API_KEY
reproduce.sh            Compile + pytest + STDIO list + HTTP health/auth + artifact freeze
requirements.txt        Pinned runtime dependencies
requirements-dev.txt    Pinned test dependencies
docs/SOLUTION.md        Business framing, buyer personas, engagement shapes

8. Privacy and cost

  • The server itself sends nothing anywhere. The model runs locally and needs no network for the classification. Over STDIO everything stays on one computer.

  • The assistant is a separate party. If you connect a cloud assistant such as Claude, the meter values you ask about pass through that assistant and its provider. Check that this fits your data rules before connecting real meters.

  • The audit log keeps summaries, not raw values. One line per call with timestamp, label, and abstention flag. Audit files are excluded from git.

  • No per-call cost from this server. No tokens are used by the model. Any usage charges from the assistant you connect are separate.

Need this applied to your own meters? Email info@mbitai.com.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI-powered analysis and prediction of household energy consumption through machine learning models, providing historical consumption breakdowns, price queries from Spanish electricity markets, and personalized energy optimization recommendations.
    -
  • A
    license
    A
    quality
    B
    maintenance
    Live and historical electricity prices, demand, generation mix and carbon intensity for 25 grid zones (US, Europe, GB, Australia). Hosted endpoint plus local stdio bridge; free sample mode, free API key, or x402 pay-per-call.
    6
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables streaming inference over utility meter telemetry with exactly-once tumbling windows and offline, in-process anomaly detection via stdio JSON-RPC, keeping regulated meter data within the customer boundary.
    -