Specification · section 3
Scale: one device among thousands
MCP's first year taught a specific lesson. Loading every tool definition up front cost about 55,000 tokens for five ordinary servers, and selection accuracy fell as the list grew. Anthropic's fixes, described in "Advanced tool use", were three: a tool search tool with deferred loading (85% fewer tokens, accuracy up from 49% to 74% on Opus 4), programmatic tool calling so intermediate results never enter the model's context (37% fewer tokens on research tasks), and usage examples inside tool definitions (72% to 90% on complex parameters). MHP builds all three in from the start, because a lab has more devices than a workspace has tools, and every device descriptor is longer than a tool schema.
3.1 Descriptor detail tiers
device/describe takes a detail parameter. A host never has to load the whole descriptor to decide whether it wants the device.
| Tier | Cost | Contents |
|---|---|---|
card |
~40 tokens | Level 1: the DEVICE.md frontmatter (id, class, make, model, location, tags, description), live state, and the names of every signal, setting and action |
summary |
under 1k tokens | Level 2: card + the DEVICE.md operating instructions + every signal/setting/action with type, unit, limits, approval level and interlocks + the list of bundled resources |
full |
unbounded | Level 3: the whole descriptor, including notes, params and examples |
select fetches full specs for named items only: {"select": {"actions": ["run_protocol"], "settings": ["target_temperature"]}}. The normal path is card → summary → select the two things you will use. A host SHOULD default to summary; full is for authoring and debugging.
3.2 The directory
A directory is an MHP server whose role is finding devices rather than being one. It indexes cards, not descriptors, and keeps no open connection to the devices it lists.
| Method | Params | Returns |
|---|---|---|
directory/search |
query (free text), class, tags, location, state, limit, live (default true) |
ranked cards with live state, each with a target the client can connect to |
directory/get |
id |
one card + target, with live state (a ping, or a cached answer younger than 2 s) |
directory/stats |
counts by class and state |
Ranking is BM25 over id, class, make, model, location, tags, notes and the names of signals, settings and actions, so a query like "heat a 96-well plate to 95 °C for PCR in bay 12" finds thermocyclers in bay 12 without the agent knowing any ids. Filters are exact. state lets an agent ask for an idle instrument.
A directory is built by asking each device for its card once (device/describe {detail: "card"}), or from a manifest file the lab maintains. State is never served from the index. The index holds only what is static about a device (identity, location, tags, capability names). At query time the directory pings the top candidates in parallel with a short timeout (the reference uses 500 ms and pings four times limit candidates when a state filter is given), fills in each card's state from the answer, and only then applies the filter. directory/get uses the same live state, reusing an answer younger than the cache window (2 s in the reference). A device that does not answer is reported as unreachable and never matches state: "idle". A client MAY pass live: false to skip pinging when it only wants identity, and MUST still treat the device's own describe or ping as authoritative before acting.
Small labs need no directory: a client with an explicit name-to-target map scans its own devices' cards.
3.3 Constant tool surface
An MCP host connected to an MHP lab sees eight operating tools regardless of device count: mhp_find, mhp_describe, mhp_read, mhp_write, mhp_invoke, mhp_job, mhp_estop, mhp_run, plus three for the lab itself: mhp_lab (§9.1), mhp_data (§3.5) and mhp_method (§5.8) — eleven in total. There are never per-device tools. The device descriptor, loaded on demand, is the documentation. Bridges MUST NOT enumerate devices into the tool list or the resource list at startup; resources list only the devices the session has opened.
3.4 Programmatic runs
mhp_run executes an orchestration script against the lab and returns only what it prints, capped. A script that takes five hundred temperature readings and reports a mean puts one line in the model's context, not five hundred numbers. The script sees the same Lab client as §10.3, a run_dir folder to save files into, and every call it makes passes the same safety gates as a direct tool call. Hosts SHOULD sandbox script execution; the reference implementation runs scripts in-process so simulated device state is shared, and says so.
Three modes matter for real work:
- Recipes.
mhp_lab op="recipes"searches tested cross-instrument procedures (bundled ones such aspcr-with-plate-transfer,timed-hold,setpoint-sweep,periodic-readings, plus any in~/.openmhp/recipes/). Each is an ordinary script with a header (# recipe,# needs,# asks,# summary) and aPARAMSdict;mhp_run recipe=<name> params={...}merges the caller's overrides intoPARAMSas data, never as generated code, before the body runs. An agent adapts a recipe rather than composing a protocol from scratch. - Plan mode.
mhp_run plan=truerehearses a script against a plan lab: reads are real, every write and invoke is a dry run through the gates (including the driver's whole-request parameter check), any other mutating request is recorded but not executed, jobs complete instantly,timeis virtual, and a plan stops after 300 steps. The result is the ordered list of steps, the ones a person must confirm, the long-running ones, the first step the driver would refuse, and the steps whose interlocks are open right now. A plan follows one path using current readings: a loop that waits on a live reading is recorded as onewait_untilstep or truncates the plan, and the verdict says which. Hosts SHOULD show the plan to the person before running anything that heats or moves. - Background runs.
mhp_run background=truestarts the script and returns a run id; the script continues after the tool call, for hours if needed.mhp_data(§3.5) reads its output and files later.
3.5 Runs, data and the run log
Every mhp_run is a run with a folder under ~/.openmhp/runs/<id>/ holding script.py, stdout.txt, meta.json and whatever the script saved to run_dir (readings as CSV, images, logs). mhp_data lists runs, lists a run's files, and reads a text file from one, capped; binary files are reported by size and path. This is the protocol's answer to bulk data in this draft: results live in files next to the run that produced them, and an agent pulls in only the file it needs.
A run's meta.json is written when it starts, and a run whose process ended before it finished is reported as interrupted. Script output goes only to the run's own file; nothing redirects the process's standard output, which the MCP transport owns.
The run log is append-only JSONL, one file per day under ~/.openmhp/log/. The bridge records every call made through its clients, from direct tools and from run scripts (tagged with the run id): reads, writes, invokes, refusals, confirmations, e-stops and leases, plus scans, adds, plans, run boundaries and events from in-process devices. Sequence numbers continue across restarts; a cursor older than memory is served from disk, and the answer says complete: false when history is missing. Events from devices served over HTTP are not yet consumed (§16). mhp_lab op="events" since=<seq> returns what happened after a point, so a returning agent can catch up; the same events are pushed to the harness as MCP logging messages when it declared the capability. A run is reproducible from its script and the log, and a lab-notebook entry can be generated from them.
3.6 Examples in the descriptor
Actions MAY carry examples, an array of realistic parameter objects. Bridges MUST forward them (the reference bridge exposes them through mhp_describe, and its own tools ship input_examples). A schema says what is valid; an example says what is normal.
3.7 Measured
The reference examples/scale_demo.py builds 2,000 simulated devices across 8 classes and 40 bays.
| Tokens in agent context | |
|---|---|
| Every descriptor loaded up front | 1,224,382 |
mhp_find (5 cards) + summary of the chosen device (with its operating instructions) + full spec of the 2 items used |
1,270 |
That is 0.10% of the naive cost, with search taking well under a millisecond. The device is then operated through exactly the same primitives as in a two-device lab.