Skip to main content
Glama
J-X0
by J-X0

eastbournestreaming

Streaming inference MCP server for utility meter telemetry. It folds a stream of meter readings into fixed (tumbling) time windows with exactly-once semantics and runs an inference provider over each closed window.

Project codename: Eastbourne. Client: Quenby Metering.

The constraint that shapes the design

Regulated meter data cannot leave the customer's boundary. Two things follow, and both are enforced by tests:

  • No network at runtime. The server transport is stdio (newline-delimited JSON-RPC 2.0), not a socket. tests/boundary.test.ts scans src/ and fails the build if any module imports node:net, node:http(s), node:tls, node:dgram, WebSocket, or calls fetch.

  • In-process inference. All model behaviour goes through InferenceProvider (src/providers/base.ts). The bundled StubInferenceProvider (src/providers/stub.ts) is deterministic and runs offline with no API key. A production model ships on-premises behind the same interface.

Related MCP server: SEOSiri Data Pipeline MCP

Exactly-once semantics

For a fixed set of distinct eventIds the emitted aggregates are identical regardless of how often or in what order events are delivered, as long as a duplicate arrives before its window closes. This holds because:

  • duplicates are dropped via an eventId dedup set scoped to open windows, and

  • window closure is a deterministic function of event-time via a watermark (maxEventTime - allowedLateness), not wall-clock.

checkpoint() / WindowedAggregator.restore() carry the open windows and dedup set across a restart so the guarantee survives process recycling.

Architecture

stdin (JSON-RPC lines) ──► server.ts ──► WindowedAggregator ──► InferenceProvider
                              │                │                      │
                           Logger          watermark +            StubInferenceProvider
                           (stderr)        dedup + windows        (deterministic, offline)

The server is a thin envelope: it parses one request per line, routes to the aggregator, runs the provider over any windows that closed, and writes one response per line to stdout. State lives in WindowedAggregator; all model behaviour lives behind InferenceProvider. Nothing opens a socket — see the ADR on the stdio transport. Design decisions with a real alternative are recorded in docs/adr/.

Layout

  • src/types.ts — domain types (Reading, WindowAggregate, ...)

  • src/aggregator.tsWindowedAggregator, the core algorithm

  • src/providers/base.tsInferenceProvider interface

  • src/providers/stub.ts — deterministic offline provider (z-score anomaly)

  • src/config.ts — env-var configuration, validated at startup

  • src/logger.ts — structured JSON logging to stderr, with timed()

  • src/errors.tsConfigError, WindowLimitError

  • src/server.ts — stdio JSON-RPC entry point wiring the pieces together

  • tests/*.test.ts — behavioural tests, node:test

Configuration

All configuration is read from environment variables at startup and validated; a malformed value aborts with exit code 78 (EX_CONFIG) and a logged reason rather than failing later on a bad window.

Variable

Default

Meaning

EB_WINDOW_MS

60000

Tumbling window size (ms), must be > 0

EB_LATENESS_MS

30000

Allowed lateness (ms), must be >= 0

EB_MAX_OPEN_WINDOWS

100000

Ceiling on concurrent open windows (back-pressure)

EB_BASELINE_MEAN

1.0

Expected healthy mean per reading

EB_BASELINE_STDDEV

0.25

Baseline standard deviation, must be > 0

EB_Z_THRESHOLD

3.0

Absolute z-score to flag an anomaly, must be > 0

EB_LOG_LEVEL

info

debug | info | warn | error

Operational behaviour

  • Logs are newline-delimited JSON on stderr (stdout is the JSON-RPC channel). flush timing is logged at debug.

  • Back-pressure: a reading that would open a window past EB_MAX_OPEN_WINDOWS is refused with JSON-RPC error -32000 (busy). The stream stays alive; the client retries once windows drain. Bounds memory against unbounded meters or lateness.

  • Graceful degradation: if the inference provider throws, the affected window is still emitted with label: "degraded" and the error is logged. A model fault never stalls the pipeline or drops a computed aggregate.

  • Graceful shutdown: on SIGINT/SIGTERM the server flushes open windows so no aggregate is lost, logs the closed windows, and exits 0.

  • Bad input: malformed readings return JSON-RPC -32602 (invalid params); unparseable lines return -32700 and are dropped without crashing.

Requirements

Node 22+. Node runs the TypeScript sources directly; there is no build step and no dist/.

Install and test

npm ci
npm test          # runs tests/ with the built-in runner
npm run typecheck # tsc --noEmit

Running the server

npm run server

It reads one JSON-RPC request per line on stdin and writes one response per line on stdout. Pipe a session straight in (windows are 60s, allowed lateness 30s):

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
  '{"jsonrpc":"2.0","id":2,"method":"ingest","params":{"reading":{"eventId":"e1","meterId":"m1","timestampMs":0,"value":1.0}}}' \
  '{"jsonrpc":"2.0","id":3,"method":"flush"}' | npm run --silent server 2>/dev/null

The third line closes the window opened by the second and returns its inference:

{"jsonrpc":"2.0","id":3,"result":{"inferences":[{"key":{"meterId":"m1","windowStartMs":0,"windowEndMs":60000},"label":"normal","score":0.047426,"reason":"mean=1 z=0 threshold=3"}]}}

Logs go to stderr; the 2>/dev/null above hides them so only JSON-RPC responses show. Drop it (or set EB_LOG_LEVEL=debug) to see the structured log.

ingest returns { rejected: "duplicate" | "late" | null, inferences: [...] }. flush force-closes all open windows and returns their inferences. stats returns { openWindows }. Inferences are produced only for windows that close.

Known limitations / deferred work

  • The dedup set is scoped to open windows; a duplicate that arrives after its window has closed is treated as a late reading and dropped rather than being recognised as a duplicate. For the supported lateness horizon this is equivalent, but a longer-horizon dedup would need a persistent seen-id log.

  • The stub provider is a single-feature z-score detector — enough to exercise the boundary and the window pipeline, not a production anomaly model.


Quenby Metering is an illustrative client; this repository is a self-directed reference implementation built to work end to end.

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

ActivitySlowing
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
    A
    quality
    D
    maintenance
    An MCP server that exposes GPU-accelerated anomaly detection to AI assistants via the Model Context Protocol. Provides two MCP tools: waveguard_scan (send training + test data in one call, returns per-sample anomaly scores and top explanatory features) and waveguard_health (check API and GPU status). Works on time series, JSON, numbers, text, and images — fully stateless.
    3
    4
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Exposes live industrial IoT telemetry to any MCP client, streaming simulated sensor data from a fleet of machines and detecting anomalies, with the ability to inject faults on demand.
    4
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Provides telemetry, rule-based anomaly detection, and maintenance recommendations for a synthetic connected-vehicle fleet through five narrow MCP tools, enabling fleet monitoring and analysis without external APIs.
    5
    MIT

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/J-X0/quenby-metering-streaming-inference-mcp'

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