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 @sinkush/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 @sinkush/openmhp demo adds two simulated instruments.

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 @sinkush/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 @sinkush/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.1.tar.gz (69.5 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.1-py3-none-any.whl (78.8 kB view details)

Uploaded Python 3

File details

Details for the file openmhp-0.3.1.tar.gz.

File metadata

  • Download URL: openmhp-0.3.1.tar.gz
  • Upload date:
  • Size: 69.5 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.1.tar.gz
Algorithm Hash digest
SHA256 cc1d5ed47c805b6125da20b69e51536f4a73ecbe398e8f261b3cef028ab688ab
MD5 f1a5219ba23e2a6e075172d253a2c483
BLAKE2b-256 881f6571423e636a1b8cb7600600fa171313c2253e41015508e34d39e76b1e69

See more details on using hashes here.

File details

Details for the file openmhp-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: openmhp-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 78.8 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 55719bd7779f4ad347e33323d2ebc690d642675a1019970c169981aacf45cda8
MD5 98c8eb4ae7a36f201ec7c91de0f068ae
BLAKE2b-256 66a06307d2df70196081630b12c04bfc2deb1f105d80438afa0c181a63329b44

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.0

2 files

0.4.0

2 files

This release

0.3.1 This release

2 files

0.3.0

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