Docs
Install it, watch the demo, run a gateway, then connect a device and an agent. Everything here works with simulated devices first.
Install
OpenMHS ships two programs: openmhs (the command line, which can also run
the gateway) and openmhsd (the gateway only). Both are one self-contained
binary with the virtual lab built in.
You need Rust 1.89 or newer from rustup.rs, and Git. Python 3.10+ is needed for the Python SDK, examples and Python adapters.
git clone https://github.com/KNambiarDJsc/Open-MHS
cd Open-MHS
cargo install --locked --path crates/openmhs-cli # installs openmhs and openmhsd
openmhs --version
To work on OpenMHS rather than use it, build in place and run from
target/:
cargo build -p openmhs-cli
./target/debug/openmhs --version # Windows: .\target\debug\openmhs.exe
| Platform | Status |
|---|---|
| Windows 11 x86_64 | Built and tested locally and in CI |
| Linux x86_64 (Ubuntu) | Built and tested in CI |
| macOS arm64 | Configured in CI and the release build; not yet confirmed |
| macOS x86_64 | In the release build; untested |
link command hides Microsoft's link.exe and
the build fails with link: extra operand.Full guide: docs/installation.md.
Quickstart
1. See it work (about 10 seconds). A guided run of a real gateway over its API:
openmhs demo
2. Run the gateway yourself. In terminal 1:
openmhs serve --sim
In terminal 2, as the operator, allow an agent to use the simulated power supply and create its credential. The token is printed once.
openmhs devices list
openmhs grants add --id agent-psu --principal agent:demo --device sim.psu-01 --capability '*' --ops read,write --effect allow
openmhs tokens create agent:demo --any-scope
Then act as the agent.
# Linux / macOS
export OPENMHS_TOKEN=omhs_...
openmhs exec sim.psu-01 power.output.voltage.setpoint --value 5V # refused: jumps too fast
openmhs exec sim.psu-01 power.output.voltage.setpoint --value 2V # applied and read back
# PowerShell
$env:OPENMHS_TOKEN = "omhs_..."
openmhs exec sim.psu-01 power.output.voltage.setpoint --value 5V
openmhs exec sim.psu-01 power.output.voltage.setpoint --value 2V
The 5 V request ends DENIED command.slew_exceeded and nothing is sent to
the device. The 2 V request ends VERIFIED -> succeeded (confirmed by
readback). Last, as the operator (without the token):
openmhs audit verify
The simulated devices are sim.thermo-01 (temperature sensor),
sim.psu-01 (power supply) and sim.axis-01 (linear stage). State
resets when the gateway restarts.
Connect your own device
The gateway knows a device from a profile (a JSON file) or an adapter (a small program). Nothing reaches it unless a grant allows it.
1. Pick how OpenMHS talks to it
| Your device | Use | Start with |
|---|---|---|
| Speaks Modbus TCP (PLCs, meters, drives, chambers) | a profile | openmhs profile new modbus |
| Speaks MQTT (ESP32, Raspberry Pi, IoT nodes) | a profile | openmhs profile new mqtt |
| Speaks SCPI over TCP or serial (power supplies, meters, scopes) | a profile | openmhs profile new scpi |
| Serves a JSON HTTP API (your own firmware, a small web service) | a profile | openmhs profile new http |
| Has its own API or SDK (for example OctoPrint) | a Python adapter | Python adapter SDK |
| Needs Rust, or a protocol not listed | a Rust adapter | openmhs adapter new my-device |
| OPC UA server, ROS 2 topics | the bundled adapters (read-only, observe-only) | OPC UA, ROS 2 |
A bare microcontroller has to run firmware that speaks one of these first;
firmware/esp32-mqtt-node is a complete ESP32 example with its profile.
2. Describe it
openmhs profile new mqtt --id lab.node-01 # a working starter profile
openmhs profile check lab.node-01.json # checks the file; connects to nothing
Edit three parts: connection (where the device is),
descriptor.capabilities (what it can do: type, unit,
range, side effect, readback, freshness; be strict, these are enforced),
and mapping (register, topic, command or URL for each capability).
3. Start the gateway, simulator first
openmhs serve --profile-sim lab.node-01.json # simulated: safe to experiment
openmhs serve --profile lab.node-01.json # the real device at "connection"
4. Decide what is allowed
Nothing is allowed at first, not even for the operator. Give an agent exactly what it needs:
openmhs grants add --id agent-node --principal agent:demo --device lab.node-01 \
--capability '*' --ops read,write --effect allow --max-side-effect reversible --reason "first device"
openmhs tokens create agent:demo --scope 'lab.node-01:*:read' --scope 'lab.node-01:*:write' --ttl 7d
Use --effect require_approval when a person should confirm each command
(openmhs approve <id>). The token's scopes are a second ceiling: it can
do nothing the grants do not also allow.
5. Use it
openmhs read lab.node-01 air.temperature
openmhs exec lab.node-01 fan.on --value true # VERIFIED, confirmed by readback
openmhs exec lab.node-01 vent.position --value 140% # DENIED, outside 0..100 %
Full guide: docs/connect-a-device.md.
Connect an AI agent over MCP
openmhs mcp is an MCP server on stdin and stdout. It is a client of a
running gateway and holds one agent token, never the operator's. For Claude Code:
claude mcp add openmhs -e OPENMHS_TOKEN=<token> -- openmhs mcp
Any other MCP host: a stdio server, command openmhs, argument
mcp, environment OPENMHS_TOKEN.
{
"mcpServers": {
"openmhs": {
"command": "openmhs",
"args": ["mcp"],
"env": { "OPENMHS_TOKEN": "<token>" }
}
}
}
The agent then sees tools such as list_devices,
describe_device, read_state, check_command,
submit_command, get_command,
reconcile_command and the plan tools. Refusals come back as structured
results with a suggested next step. Details:
docs/mcp.md.
Python SDK
Standard library only, Python 3.10+. Not on PyPI yet; install it from the checkout.
pip install ./python
from openmhs import Client, Quantity
gw = Client.from_home(token="<agent token>") # finds the running gateway
r = gw.read("sim.thermo-01", "temperature.ambient")
if r.usable: # fresh enough to act on
print(r.value)
receipt = gw.write("sim.psu-01", "power.output.voltage.setpoint", Quantity(2, "V"),
idempotency_key="step-1")
if receipt.confirmed: # verified by reading the device back
print("done")
elif receipt.denied: # a refusal is a receipt, not an exception
for reason in receipt.reasons:
print(reason.code, reason.details)
Leases and plans are available too. See python/README.md.
Check what happened
As the operator (no OPENMHS_TOKEN):
openmhs commands list # every command, its state and why
openmhs audit tail # the latest audit records
openmhs audit verify # audit log intact: N records, hash chain verified
When something says no, the error code says why, and troubleshooting says what to do.
Watch an agent replan
A scripted agent, with no model and no API key, connects over MCP, hits a slew limit, reads the gateway's advice, and ramps instead:
cargo build -p openmhs-cli
python examples/agent-loop/mock_agent.py
An optional variant drives the same loop with Claude; see examples/agent-loop.
Run conformance
openmhs conformance --report-dir reports # simulators, about 15 seconds, one JSON report each
openmhs conformance --profile adapters/openmhs-scpi/profiles/bench-psu.json
openmhs conformance --adapter-manifest adapters/python/psu-sim/manifest.json
To list an adapter in the registry and the compatibility matrix, commit its report and an entry; see registry/README.md.
Write an adapter
openmhs adapter new bench-lamp --openmhs-path .
This creates a working adapter crate with a fake device and conformance tests. Follow the adapter guide and the walkthrough. Adapters in Python use the Python adapter SDK and run out of process; the OctoPrint adapter is a complete example.
Run the tests
The full list CI runs is on the guidelines page. The main ones:
cargo test --workspace # spec fixtures, policy, engine, gateway, CLI, MCP
python tools/check_schemas.py # schemas in Python (pip install jsonschema)
python -m unittest discover -s python/tests # Python SDK, examples, parity (cargo build first)
Reference documents
These live in the repository and are the authoritative versions. An mdBook documentation site is built from them but not yet published.
| Document | Covers |
|---|---|
| Concepts | The ideas in one page |
| Tutorial and first device | Step-by-step introductions |
| CLI reference | Every command |
| Configuration | The gateway home, settings |
| Simulators | The virtual lab and its faults |
| Specification | Index of the v0.1 draft and its JSON Schemas |
| Descriptors, units | Devices, capabilities, constraints; the UCUM unit table |
| Lifecycle, errors | Command states, idempotency, recovery; stable error codes |
| Authorization | Principals, grants, approvals, defaults |
| Local API | Transport, authentication, roles, methods |
| Conformance | Levels L0 to L3, fixtures, faults, requirements |
| Topology, plans | Capability graph, leases, multi-device plans |
| Device profiles | Modbus TCP, MQTT, SCPI, HTTP |
| Compatibility matrix | Generated evidence per adapter |
| Limitations | What it does not do, and what is untested |
| Transport security | TLS for MQTT and HTTP; when plaintext is refused |
| Architecture decisions | Why each design was chosen |
| Changelog | What changed; no release yet |