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, programmatic tool calling so intermediate results never enter the model's context, and usage examples inside tool definitions. 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.
| MCP mechanism | Reported effect | MHP equivalent |
|---|---|---|
| Tool search, deferred loading | 85% fewer tokens; Opus 4 accuracy 49% → 74% | Descriptor detail tiers + directory/search (§3.1, §3.2) |
| Programmatic tool calling | 37% fewer tokens on research tasks | mhp_run scripts against the Lab client (§3.4) |
| Tool use examples | 72% → 90% on complex parameters | examples on actions; input_examples on every bridge tool (§3.5) |
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, 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 and 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, then summary, then 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, state pinged now |
directory/stats | counts by class and state |
→ {"method":"directory/search","params":{"query":"heat a 96-well plate to 95 C for PCR in bay 12",
"class":"thermocycler","state":"idle","limit":3}}
← {"result":{"results":[
{"id":"thermocycler-0411","class":"thermocycler","make":"Bio-Rad","model":"C1000","location":"bay 12",
"tags":["pcr","heating"],"notes":"96-well block.","state":"idle",
"signals":["block_temperature","lid_temperature","lid_closed","cycle"],
"settings":["target_temperature","lid_heater"],"actions":["run_protocol","open_lid"],
"target":"http://bay12-bench3:18921","score":10.08}, ...]}}
Ranking is BM25 over id, class, make, model, location, tags, notes and the names of signals, settings and actions, so the query above 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, 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, fills in each card's state from the answer, and only then applies the filter. directory/get always pings. A device that does not answer is reported as unreachable and never matches state: "idle".
| Reference directory | Value |
|---|---|
| Ping timeout per device | 500 ms |
| Candidates pinged when a state filter is given | 4 × limit |
| A ping is reused for | 2 s |
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 two for the lab itself, mhp_lab (§9.1) and mhp_data (§3.6). There are never per-device tools. 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, 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.
# mhp_run: 500 readings in, one line out
t = lab["thermocycler-0411"]
with t:
j = t.wait(t.invoke("run_protocol", steps=[{"temp": 95, "hold_s": 30}], cycles=5))
rs = [t.read("block_temperature") for _ in range(500)]
print("cycles", j["result"]["cycles_completed"], "mean", sum(rs) / len(rs))
Three modes matter for real work. Recipes: mhp_lab op="recipes" searches tested procedures, each an ordinary script with a header and a PARAMS dict; mhp_run recipe=<name> params={...} merges the caller's overrides before the body runs. Plan mode: mhp_run plan=true rehearses a script against a plan lab where reads are real, every write and invoke is a dry run through all six gates, and nothing moves; the result lists the steps, the ones a human must confirm, and the first the driver would refuse. Background runs: background=true returns a run id and the script continues for as long as it needs.
3.5 Examples in the descriptor
Actions MAY carry examples, an array of realistic parameter objects. Bridges MUST forward them, and the reference bridge's own tools ship input_examples. A schema says what is valid; an example says what is normal.
3.6 Runs, data and the run log
Every mhp_run is a run with a folder under ~/.openmhp/runs/<id>/ holding the script, its output, and whatever it saved to run_dir. mhp_data lists runs and reads their files, capped. The run log is append-only JSONL, one file per day: every write, invoke, refusal, confirmation, e-stop, plan and run boundary, and device events, each with a sequence number. mhp_lab op="events" since=<seq> lets a returning agent catch up, and the same events reach the harness as MCP logging messages when it declared the capability.
3.7 Measured
The reference scale demo builds 2,000 simulated devices across 8 classes and 40 bays, then runs the query above.
| Approach | Tokens in agent context |
|---|---|
| Every descriptor loaded up front | 937,845 |
mhp_find (5 cards) + summary of the chosen device + full spec of the 2 items used | 1,087 |
That is 0.11% 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.