Skip to main content

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

openmhp-0.3.0.tar.gz (69.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

openmhp-0.3.0-py3-none-any.whl (78.9 kB view details)

Uploaded Python 3

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

Hashes for openmhp-0.3.0.tar.gz
Algorithm Hash digest
SHA256 486a41aad1fcc6739f9c9e58b08e1e2b4f9ef9e88375409d59b2fbbe985bed6f
MD5 2108841ed74c3f1a7c0ef670b67f5bce
BLAKE2b-256 3d663b8e798cd82e03d495ee49b78dcc28b979e96bac218688c06450b53e53d6

See more details on using hashes here.

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

Hashes for openmhp-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 755b08c5bb1bb924e840096491b6d5632be450332da864627d96dc1999d895de
MD5 6b90f23aeb04f4e4fd8235dd38277482
BLAKE2b-256 92e63fee7bdc8de06d501c4cf328c5ff28fcf05a450fabdd82de91402bb0d6c9

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.0

2 files

0.4.0

2 files

0.3.1

2 files

This release

0.3.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page