OpenMHP
Reference implementation of the Open Model Hardware Protocol (MHP): an open protocol through which an AI agent finds a physical device among thousands, learns how to operate it safely, reads from it, writes to it, and runs long actions on it.
MHP is to instruments and machines what MCP is to software tools, with two lessons from MCP's first year built in from the start: the agent's context stays flat as the lab grows, and safety is enforced on the device side of the wire.
| Primitive | Verb | Example |
|---|---|---|
| Directory | directory/search |
"something idle that can heat a 96-well plate to 95 °C in bay 12" → 5 cards |
| Describe | device/describe {detail, select} |
card (~40 tokens) → summary (~200) → full spec of the two items you'll use |
| Signals | signals/read, signals/subscribe |
block temperature, arm position, lid state |
| Settings | settings/write |
target temperature = 95 °C, refused above 105 |
| Actions | actions/invoke → job |
run PCR protocol, pick plate; device does the work |
| Safety | safety/limits, safety/estop, safety/reset |
limits, interlocks, approval levels, e-stop, watchdog |
Python ≥ 3.10; PyYAML is the only dependency.
Docs: openmhp.com · Quickstart · Add an instrument · Adapters · Specification. The site's source is kushalsinha/openMHP-website; SPEC.md here is the specification's source of truth.
For scientists: one command
npx openmhp setup
That installs the runtime into ~/.openmhp, puts the three Agent Skills where your harness
looks for them, and registers the MCP server with Claude Code and Codex (other harnesses get a
one-line config to paste). Then talk to your agent:
- "find the instruments on my network": the agent scans for devices that speak MHP and lists them
- "add the thermocycler": it joins your lab and is searchable from then on
- "onboard my hotplate": the agent interviews you, writes the device package, validates it, adds it
- "run a 30-cycle PCR at 95/58/72 and hold at 4 °C": find, read the owner's instructions, dry-run, run, verify
Try it without hardware: npx openmhp demo adds two simulated instruments. Until the npm and PyPI packages are published, point the launcher at this checkout:
OPENMHP_SOURCE=/path/to/openmhp npx ./npm setup.
For developers: try it in 60 seconds
pip install -e ".[discovery]"
# 1. code file: orchestrate a robot arm and a thermocycler
python examples/pcr_run.py
# 2. scale: 2,000 devices, find the right one for 1,087 tokens instead of 937,845
python examples/scale_demo.py
# 3. CLI: run devices and a directory over HTTP
mhp serve local:openmhp.devices.sim_thermocycler:SimThermocycler --http 18921 &
mhp serve local:openmhp.devices.sim_arm:SimArm --http 18922 &
mhp serve-directory thermo=http://localhost:18921 arm=http://localhost:18922 --http 18900 &
mhp http://localhost:18900 find pick plates # directory search
curl localhost:18921/mhp.json # discovery document
mhp http://localhost:18921 describe card # card | summary | full
mhp http://localhost:18921 write target_temperature 200 # refused: LimitViolation
mhp http://localhost:18921 invoke run_protocol '{"steps":[{"temp":95,"hold_s":5}],"cycles":3}' --wait
mhp http://localhost:18921 estop
# 4. MCP: expose the whole lab to any agent harness through eight tools, whatever its size
mhp-mcp --directory http://localhost:18900 # stdio
mhp-mcp --directory http://localhost:18900 --http 18800 # MCP Streamable HTTP at /mcp
# 5. adapters + live directory, verified against fakes (no hardware)
python tests/test_adapters.py
Already running SiLA 2, PyLabRobot, MADSci, OPC UA or ROS 2?
Each device is a few lines with the matching adapter; every gate, tier and job comes for free:
from openmhp.adapters.sila2 import sila_device # also: pylabrobot.plr_device,
DEVICE = sila_device("10.0.0.12", 50052, # madsci.madsci_node, opcua.opcua_device,
device={"id": "arm-01", "class": "robot_arm", "location": "bay 3", "tags": ["plates"], # ros2.ros2_device
"notes": "PF400 plate mover. Light curtain trips safe_zone_clear."},
signals={"position": "RobotController.Position", "safe_zone_clear": ("SafetyController.SafeZoneClear", "boolean")},
settings={"speed": ("RobotController.SetSpeed.Speed", {"min": 1, "max": 100})},
actions={"move_to": ("RobotController.MoveTo", {"observable": True, "interlocks": ["safe_zone_clear"]})},
estop="RobotController.EmergencyStop")
Anything else with a Python callable goes through BoundDriver(Signal, Setting, Action) directly.
Agent Skills
openmhp/skills/ holds three Agent Skills any skills-capable harness can load (mhp skills install copies them into place):
| Skill | Use it to |
|---|---|
openmhp-onboard-device |
interview a device owner and write a validated descriptor + driver |
openmhp-adapt-fleet |
bring a SiLA 2 / PyLabRobot / MADSci / OPC UA / ROS 2 fleet under MHP, build the directory, publish mhp-mcp |
openmhp-operate |
run experiments safely through the eight tools: find → describe → check → dry-run → act → verify |
npx openmhp setup or mhp skills install installs them into ~/.claude/skills, ~/.codex/skills and any other harness skills folder that exists.
Claude Desktop / Claude Code config for the bridge:
{"mcpServers": {"lab": {"command": "mhp-mcp", "args": ["--directory", "http://directory:18900"]}}}
The bridge exposes mhp_find, mhp_describe, mhp_read, mhp_write,
mhp_invoke, mhp_job, mhp_estop, mhp_run (run a script against the
lab, get back only what it prints) and mhp_lab (scan the network, add and
onboard devices). Every tool carries input_examples. There are never
per-device tools.
A device is a package, like a skill
devices/thermocycler-01/
├── DEVICE.md Level 1: YAML frontmatter = the card (~40 tokens in search results)
│ Level 2: Markdown body = operating instructions (loaded when chosen)
├── descriptor.yaml Level 3: limits, interlocks, params, examples (loaded per item)
├── driver.py Level 3: code
├── references/ Level 3: SOPs, manual excerpts
└── scripts/ Level 3: tested mhp_run scripts
mhp serve pkg:openmhp/devices/thermocycler-01 --http 18921
mhp http://localhost:18921 describe card # level 1
mhp http://localhost:18921 describe summary # level 2, with instructions
mhp http://localhost:18921 resources scripts/pcr.py # level 3
Writing a driver
A driver is a package plus three hooks. Everything else (limit checks, interlocks, approval gating, leases, jobs, e-stop, notifications, detail tiers) is inherited.
from openmhp.driver import Driver
class MyHotplate(Driver):
descriptor = {
"device": {"id": "hotplate-01", "class": "hotplate", "make": "IKA", "model": "C-MAG",
"location": "fume hood 2", "tags": ["heating", "stirring"],
"notes": "Fume hood 2. Stir bar rattles above 800 rpm with 50 mL flasks."},
"physical": {"mass_kg": 3.2, "notes": "Plate surface reaches 500 °C; keep solvents capped."},
"signals": [{"name": "plate_temperature", "type": "number", "unit": "degC"}],
"settings": [{"name": "target_temperature", "type": "number", "unit": "degC",
"limits": {"min": 20, "max": 300}, "approval": "auto"},
{"name": "stir_rpm", "type": "number", "limits": {"min": 0, "max": 1500}}],
"actions": [{"name": "shutdown", "duration": "short", "approval": "confirm"}],
"safety": {"estop": True, "watchdog_s": 5},
}
def setup(self): self.dev = serial.Serial("/dev/ttyUSB0")
def on_read(self, name): return float(self.dev.query("IN_PV_1"))
def on_write(self, name, value): self.dev.write(f"OUT_SP_1 {value}")
def on_invoke(self, job): self.dev.write("STOP")
def on_estop(self): self.dev.write("STOP")
mhp serve mypkg.hotplate:MyHotplate --http 18921
Layout
openmhp/driver.py Driver base class: primitives, safety gates, jobs, leases, detail tiers, resources
openmhp/package.py device packages: DEVICE.md frontmatter + body, descriptor.yaml, resources
openmhp/directory.py Directory: card index + BM25 search + live state pings; serves directory/*
openmhp/fleet.py the lab's device list (~/.openmhp/fleet.json); in-process hosting of packages
openmhp/discovery.py mDNS advertise/browse (optional zeroconf) and HTTP probe of /mhp.json
openmhp/validate.py device package validator (`mhp validate`)
openmhp/skills_install.py copies bundled skills into harness skill dirs (`mhp skills install`)
npm/ the `npx openmhp` launcher (Node, no deps): runtime bootstrap, setup, scan/add
openmhp/adapters/ BoundDriver bindings; sila2, pylabrobot, madsci, opcua, ros2 adapters
openmhp/skills/ Agent Skills: onboard-device, adapt-fleet, operate (bundled)
tests/test_adapters.py adapters and live directory against injected fakes
openmhp/transport.py stdio and HTTP(+SSE) transports; /mhp.json discovery
openmhp/client.py Device / Lab client SDK (local, stdio, http); Lab is lazy and directory-aware
openmhp/cli.py `mhp` command, incl. serve and serve-directory
openmhp/mcp_bridge.py `mhp-mcp`: eight tools, constant in device count; mhp_run
openmhp/devices/ simulated thermocycler and robot arm, each as a device package
examples/pcr_run.py cross-device orchestration script
examples/scale_demo.py 2,000 devices; context cost old way vs MHP way
SPEC.md the specification, Markdown
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openmhp-0.3.0.tar.gz.
File metadata
- Download URL: openmhp-0.3.0.tar.gz
- Upload date:
- Size: 69.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
486a41aad1fcc6739f9c9e58b08e1e2b4f9ef9e88375409d59b2fbbe985bed6f
|
|
| MD5 |
2108841ed74c3f1a7c0ef670b67f5bce
|
|
| BLAKE2b-256 |
3d663b8e798cd82e03d495ee49b78dcc28b979e96bac218688c06450b53e53d6
|
File details
Details for the file openmhp-0.3.0-py3-none-any.whl.
File metadata
- Download URL: openmhp-0.3.0-py3-none-any.whl
- Upload date:
- Size: 78.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
755b08c5bb1bb924e840096491b6d5632be450332da864627d96dc1999d895de
|
|
| MD5 |
6b90f23aeb04f4e4fd8235dd38277482
|
|
| BLAKE2b-256 |
92e63fee7bdc8de06d501c4cf328c5ff28fcf05a450fabdd82de91402bb0d6c9
|