Skip to main content
Glama

vor

Vor is the governed reader of the Yggdrasil IIoT project. It is an MCP server (stdio) exposing exactly one tool, read_governed(question_id). It embeds no LLM; the MCP host does the reasoning. It answers with a value bound to a published Bifrost mapping release and a live Sparkplug B sample from Muninn, or with a deterministic refusal. Question ids are fixed in the configuration; a question id that is not configured is refused (binding-invalid, rule question-unknown). The host cannot ask for an arbitrary node or metric. Vor shares no code with Bifrost or Muninn: it reads only their published bytes (the Bifrost registry directory on disk, and Sparkplug B messages on an MQTT broker).

What an answer contains

An answer contains the following items, each with its source (a release file path plus its raw-byte SHA-256, or the MQTT topic the sample came on):

  • Semantic pin (stream_id, release_id, version, release_sha256, file, file_sha256, field_id, digest_rule "raw-bytes-sha256")

  • Verified head (headVersion, signedAt, expires, publishedBy, approvedBy)

  • Meaning (semanticId, name, description, lifecycle state, revision)

  • UCUM unit code

  • ISA-95 equipment path and its levels

  • Sparkplug binding (group, edge, device, metric, governed definition reference)

  • Temporal policy

  • Sample (value, quality good/uncertain, OPC UA status code, source and server timestamps, age)

  • Verdict (usable or usable-uncertain; carries a meaning-deprecated warning with the replacement semanticId when the meaning is deprecated)

A refusal has answer null and a verdict with usable false, a reason code, and evidence. A refusal never carries the sample value.

Related MCP server: RecoSearch

Refusals

Checks run in this fixed order; the first failing check decides the reason. Vor verifies the DSSE (in-toto Statement v1) head attestation of a stream with Ed25519 public keys from the trusted keys file outside the registry; publisher and approver must be different principals with different keys. Vor replays semantic lifecycle events from releases at every stream head in the registry; every head must verify.

Reason code

When it fires

head-unverified

Head file or DSSE envelope missing, publisher and approver are the same principal, a signature does not verify against a trusted key, both signatures from the same key, statement subjects do not match the head and release.json, or the head is signed in the future.

head-rollback

Head version below the stored floor, or the same version with a different statement.

head-expired

The current time is at or past the head's expires.

provenance-mismatch

release.json, the proposal, or a payload file does not match its recorded SHA-256 or identity.

binding-invalid

The read binding is missing or does not resolve (fields, projection other than identity, stream, mapping set, target, semantic definition, equipment, mapping, member path, semantic id, metric, source node, unit, hierarchy path, device not equal to the ISA-95 path), or the question is not configured.

meaning-retired

The meaning is retired in the semantic lifecycle.

unit-unknown

The output unit is not a known UCUM code.

policy-missing

The mapping has no valid temporalPolicy.

no-sample

No sample for the bound metric in the current Sparkplug session.

sample-unknown

The sample lacks a status code or source timestamp (or the timestamp the policy uses), or its type does not match the field's numeric type.

bad-quality

OPC UA status severity is bad.

uncertain-rejected

Severity is uncertain and the policy does not propagate uncertain input.

future-sample

The evaluated timestamp is after now.

stale

Sample age exceeds maxAgeMs.

skew-exceeded

The spread of source timestamps exceeds maxSkewMs (zero for a single scalar input).

location-mismatch

The Sparkplug device id is not the ISA-95 path from the release.

(future-sample and stale cannot both apply to one sample.)

Configuration

The configuration file is JSON, named by the environment variable VOR_CONFIG. Relative paths resolve against the config file's directory.

{
  "registry": "registry",
  "trustedKeys": "trusted-keys.jsonl",
  "stateDir": "state",
  "mqtt": "tcp://localhost:1883",
  "questions": {
    "mixer1-temp": {"stream": "line1", "binding": "vor-binding-mixer1-temp",
                    "description": "Mixer1 process temperature in Cel"}
  }
}
  • registry: The Bifrost registry directory (it contains mappings/).

  • trustedKeys: JSON-lines file of trusted Ed25519 public keys.

  • stateDir: Where rollback floors (head version and statement digest) are written per stream.

  • mqtt: Broker URL.

  • questions: Question id to stream, read-binding id, and a description shown to the host in the tool description.

Run

Requirements: Python 3.11+. Dependencies: mcp (>=1.2,<2, FastMCP), paho-mqtt (>=2.0), cryptography (>=42).

Install:

pip install -e .
# for tests:
uv pip install -e ".[dev]"

Run:

VOR_CONFIG=path/to/vor.json vor
# or:
VOR_CONFIG=path/to/vor.json python -m vor.server

An MCP host launches it as a stdio server with command vor (or python -m vor.server) and the VOR_CONFIG environment variable set.

Joining a running edge node

Vor subscribes to spBv1.0/# with QoS 1 and publishes nothing. A session starts with an NBIRTH at seq 0; sequence numbers must continue modulo 256. A sequence gap, an NDEATH, a DDEATH, or an undecodable payload drops the cached samples until the next birth. Each data message replaces a sample whole; metadata is never carried over from an earlier message. Muninn puts the OPC UA source time in the metric timestamp and adds the properties Quality, ua_statuscode, and ua_server_timestamp to each metric.

Vor caches nothing until it sees a birth. If Vor starts after the edge node's NBIRTH, read_governed answers no-sample until the edge node births again. Vor does not request a rebirth itself, because it publishes nothing. A Sparkplug primary host application or an operator sends the rebirth request (an NCMD with Node Control/Rebirth = true); Muninn's own CLI has:

java -jar muninn.jar host <mqttUrl> rebirth <group> <edge>

Verified live: started after the birth, Vor answered no-sample; after one rebirth request it answered usable with a sample about 1.3 s old.

Tests and fixtures

Run tests:

pytest

Tests include a refusal case for every reason, provenance tampering of release.json and each payload file, real MCP stdio round trips, and NBIRTH/DBIRTH/DDATA bytes captured from a real Muninn session (tests/fixtures/muninn-wire).

Every registry byte the tests verify was written by Bifrost's own CLI. Regenerate with:

(cd ../bifrost && mvn -pl gates -am package -DskipTests)
python scripts/make_fixtures.py [--bifrost ../bifrost]

This requires /gates/target/bifrost-gates.jar. It writes tests/fixtures/registries/{active,deprecated,retired} and tests/fixtures/trusted-keys.jsonl; private keys are created in a temporary directory and deleted. tests/fixtures is marked -text in .gitattributes so line endings never change hash-pinned bytes.

Design

The design specification is located at docs/superpowers/specs/2026-11-01-vor-m1-governed-read-design.md.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    This MCP server enables AI agents to read MQTT and Sparkplug B data via tools like list_topics, get_latest, and read_all, providing read-only access to latest sensor values.
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    A deterministic MCP server that governs read-only queries across multiple data sources, returning answers with full provenance (every row cited) or a typed refusal, ensuring LLM answers are traceable and contract-enforced.
    1
    Apache 2.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    A governed context layer for internal data exposed over MCP, enabling agents to query governed definitions, retrieve grounded data, and execute scoped actions with citations and auditing.
    Apache 2.0
  • A
    license
    B
    quality
    B
    maintenance
    Exposes a deterministic IoT edge runtime — Modbus/CAN/J1939 acquisition, local rules, alarms, and history — to AI assistants as typed, governed MCP tools. Reads are safe by default; device control stays deny-by-default, explicitly confirmed, and audited.
    23
    3
    Apache 2.0