A Modbus MCP server implementation in Go with support for reading/writing holding registers, coils, input registers, and discrete inputs. Includes typed data handling, tag-based semantic addressing, and configurable retry/circuit-breaker policies.
This Modbus MCP server demonstrates solid definition quality with 12 well-named tools, mostly complete schemas, and functional descriptions. All tools follow verb_noun naming conventions (read-*, write-*, get-*, list-*), which is excellent. Schemas are present and properly typed with parameter descriptions. However, several tools have moderately short descriptions (20-60 chars) that lack context about WHEN to use them or WHAT they return. Parameter descriptions are present but sometimes generic ('Number of registers or coils to read' appears verbatim in 4 tools). Output schemas are not explicitly documented, the server returns CallToolResult objects but doesn't specify what fields agents should expect in the response. Error handling exists but lacks recovery guidance. No tool annotations (readOnlyHint/destructiveHint/idempotentHint) are visible, despite clear risk classifications in the input data. The read-holding-registers-typed and write-holding-registers-typed tools have complex interdependent parameters (quantity/data_type/byte_order/word_order) that lack explicit documentation of dependencies.
Get Modbus client retry and connection lifecycle status
List configured semantic Modbus tags
Read Modbus coils (digital inputs/outputs)
Read Modbus discrete inputs
Read Modbus holding registers
Read holding registers and decode typed value
Read Modbus input registers
Output schemas not documented. CallToolResult objects are returned but the structure and fields agents should extract are not specified. This forces LLMs to infer output format, risking parsing errors and suboptimal response handling.
Tool descriptions are too short (45 - 60 characters) and lack context about WHEN to use each tool and WHAT it returns. The description 'Read Modbus holding registers' does not explain whether this is for raw register access, what data format is returned, or how it differs from read-holding-registers-typed.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 60 | <=2025-11-25 | v2 |
Read a configured semantic Modbus tag
Write values to Modbus coils (digital outputs)
Write values to Modbus holding registers
Write typed value to Modbus holding registers
Write a configured semantic Modbus tag
Complex parameter interdependencies not documented. In read-holding-registers-typed and write-holding-registers-typed, quantity is 'Optional (derived from data_type when omitted)', but the description does not clearly state which data_type values allow quantity omission or what the derivation logic is. LLMs will struggle to provide correct inputs.
Generic parameter descriptions used verbatim across multiple tools. 'Number of registers or coils to read' appears identically in read-holding-registers, read-coils, read-input-registers, and read-discrete-inputs, but the semantic meaning differs for each (holding registers vs coils vs input registers vs discrete inputs). Descriptions should clarify the distinction.
No tool annotations visible (readOnlyHint, destructiveHint, idempotentHint). The input data lists clear risk classifications (READ_ONLY vs WRITE), but these are not expressed as tool annotations in the MCP schema, preventing agents from making safe/unsafe inferences without parsing tool descriptions.
Error handling lacks recovery guidance. The tool_helpers.go errorResult() function returns a CallToolResult with IsError=true and a plain error message, but does not provide actionable next steps. E.g., 'Connection timeout' should suggest 'Retrying with backoff' or 'Check modbus-ip and modbus-port configuration'.
No enumeration constraints documented for string parameters. data_type in read-holding-registers-typed and write-holding-registers-typed accepts 'uint16,int16,uint32,int32,float32,string' but the description lists these as a comma-separated example rather than defining an explicit enum. Agents may hallucinate invalid values like 'float64' or 'int64'.
byte_order and word_order parameters lack documentation of valid enum values. The descriptions say 'Optional byte order: big or little' and 'Optional word order: msw or lsw' but do not formalize these as enum constraints, leaving LLMs unsure whether 'big-endian', 'BE', or other variants are valid.