MCP server for controlling Expert Sleepers FH-2 MIDI-to-CV Eurorack module
Server has 12 tools with basic structure but significant quality gaps. Tool names follow verb_noun convention well (list_, connect_, disconnect_, read_, take_, display_, panel_, navigate_, set_, zero_). Descriptions exist for all tools and parameters but many are terse and miss guidance on prerequisites or error recovery. All tools have input schemas with type definitions, but documentation of output structures is absent. No parameter validation rules (min/max for integers, enum constraints for action strings). Error handling is minimal, no recovery guidance, no categorization of errors as retryable vs fatal. Security: no secrets exposed as params (good), but no audit logging or permission gates visible. Composition is reasonable, tools are single-responsibility, but some opportunities for batching (e.g., navigate_menu does multiple panel_events in one call, which is good). The lifespan context pattern with auto-connect is sound. However, the server provides no end-user guidance on which tools to call first or in what sequence.
Connect to FH-2 MIDI ports. Auto-detects "FH-2" if no port specified.
Disconnect from FH-2 MIDI ports.
Show a text message on the FH-2 OLED display (CMD 0x02).
List all available MIDI input and output ports.
Send a sequence of panel events with delays, then return display text. Useful for menu navigation sequences like: turn encoder 3 times, press.
Send a virtual encoder/button event to navigate the FH-2 menu (CMD 0x48).
Read the FH-2 4-line OLED display text (CMD 0x41 → 0x42). Returns the current display contents as 4 lines of text.
Output schemas are not documented. Tools like read_display, take_screenshot, navigate_menu, connect_fh2 return structured strings, but the exact JSON response structure and field names are not declared. LLMs cannot reliably plan downstream operations without knowing what fields to extract.
panel_press action parameter lacks enum constraint. Description says 'One of: encoder_press, encoder_cw, encoder_ccw, left_press, left_long_press' but these are not formalized as an enum in the schema. LLMs may hallucinate other action values (e.g. 'encoder_double_press'). See protocol.py PANEL_EVENTS for valid values, expose those as an enum.
Missing parameter validation rules. set_cv_output, set_cv_voltage, set_all_cv accept output numbers 1-8 and CC values 0-127, but no min/max constraints visible in schema. set_cv_voltage accepts range_min and range_max with no bounds, unbounded floats invite absurd values like ±1e10. Add minValue/maxValue constraints and describe ranges in parameter descriptions.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | D | 52 | 2026-07-28+ | v2 |
| 2026-03-09 | F | 40 | - | v1 |
Set all 8 CV outputs at once via CC 0-7.
Set a CV output (1-8) via MIDI CC 0-7.
Set a CV output by target voltage, mapped from the output's voltage range.
Capture the FH-2 128x32 OLED display as ASCII art (CMD 0x01 → 0x33). Returns a text representation of the screen pixels.
Reset all 8 CV outputs to 0 (CC 0-7 = 0).
Error handling provides no recovery guidance. When _require_connection(ctx) fails with 'Not connected to FH-2', the error does not suggest calling connect_fh2 first. All tools that depend on connection should mention this prerequisite in their description and suggest recovery steps.
Descriptions are terse and lack context. Tools like panel_press (59 chars), disconnect_fh2 (32 chars), zero_all_cv (57 chars) fall well below the 100+ char baseline for production tools. They do not explain WHY or WHEN to use the tool, what the prerequisites are, or what happens on success/failure.
No tool annotation hints (readOnlyHint, destructiveHint, idempotentHint). Tools like set_cv_output, set_cv_voltage, zero_all_cv are marked WRITE risk but not decorated with destructiveHint. read_display and take_screenshot are READ_ONLY but lack readOnlyHint. These annotations help agents plan safely and avoid unnecessary retries of side-effecting operations.
connect_fh2 has a confusing overloaded 'port' parameter behavior. If output_port is a substring like 'FH-2' it is treated as a name; if it is '0' it is an index. The description mentions this but does not formalize it, LLMs may pass ambiguous values (e.g. 'FH2' vs 'FH-2'). Consider separate parameters (output_port_name, output_port_index) or an enum/union.
navigate_menu returns a summary string, not the structured display result. The description says 'return display text' but for LLM chaining, it should return structured JSON with line array and mode. Currently the agent must re-call read_display to get the actual display state, wasting a round-trip.