MCP Server Reference
The epanetparser MCP (Model Context Protocol) server exposes epanetparser functionality as standardized tools for agentic AI systems. It communicates via stdio transport using JSON-RPC 2.0.
Installation
pip install epanetparser[agentic]
This installs the mcp package and registers the epanetparser-mcp console
script.
Running the Server
epanetparser-mcp
The server runs on stdio (standard input/output), which is the transport mechanism used by MCP clients like Claude Desktop, Cursor, and VS Code.
Client Configuration
Claude Desktop (claude_desktop_config.json)
{
"mcpServers": {
"epanetparser": {
"command": "epanetparser-mcp"
}
}
}
On macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
On Windows: %APPDATA%\Claude\claude_desktop_config.json
On Linux: ~/.config/Claude/claude_desktop_config.json
Cursor (.cursor/mcp.json)
{
"mcpServers": {
"epanetparser": {
"command": "epanetparser-mcp"
}
}
}
VS Code (settings.json)
{
"mcp": {
"servers": {
"epanetparser": {
"command": "epanetparser-mcp"
}
}
}
}
Claude Code (.mcp.json / claude mcp add)
Project scope (``.mcp.json`` at repo root):
{
"mcpServers": {
"epanetparser": {
"command": "epanetparser-mcp",
"args": []
}
}
}
CLI equivalent:
claude mcp add --transport stdio epanetparser -- epanetparser-mcp
Note: default scope is local; add --scope project to write .mcp.json, or --scope user for all projects. A stdio entry has no type field (or "type": "stdio"). Project-scoped .mcp.json servers require interactive approval via claude (or claude mcp reset-project-choices to reset), and claude mcp list shows connection status.
OpenCode (opencode.json)
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"epanetparser": {
"type": "local",
"command": ["epanetparser-mcp"],
"enabled": true
}
}
}
Note: For a local server the required fields are type: "local" and command (an array of command + args); optional enabled, environment, cwd, timeout. Tools are exposed to the model; reference them by server name in a prompt.
After configuration, restart your MCP client to connect to the server.
Available Tools
The server exposes 8 tools:
1. validate_network
Validate an EPANET network model with optional custom rulesets.
Parameters
Parameter |
Type |
Required |
Description |
|---|---|---|---|
|
string |
No* |
Path to .inp or .json file |
|
string |
No* |
Raw JSON string of WNTR network model |
|
array[string] |
No |
Custom ruleset keys (e.g., |
|
string |
No |
|
*Provide exactly one of file_path or json_content.
Returns: ValidationReport as JSON string or TOON string. The two formats
describe the same report: the same validity, counts, issue codes and
validation_layer values, including the E_LOAD_FAILED issue returned
when the model cannot be loaded (tagged "static").
Example Request
{
"file_path": "network.json",
"rulesets": ["milp"],
"output_format": "toon"
}
Example Response (JSON)
{
"is_valid": false,
"counts": {"ERROR": 2, "WARNING": 1, "INFO": 0},
"issues": [
{
"code": "E_MILP_TIMESTEP",
"message": "Simulation timestep 7200 seconds not equal to MILOPS timestep 3600 seconds",
"severity": "ERROR",
"rule_id": "rule_hydraulic_timestep",
"ruleset_key": "milp",
"component_type": "network",
"component_name": null,
"attribute": "hydraulic_timestep",
"component_data": {},
"context": {"ruleset": "milp"},
"category": "Option",
"fix_suggestion": "Set hydraulic timestep to 3600 seconds in [OPTIONS] TIME section."
}
]
}
Example Response (TOON)
is_valid: false
counts: {ERROR: 2, WARNING: 1, INFO: 0}
issues[3]{code,message,severity,rule_id,ruleset_key,component_type,component_name,attribute,component_data,context,category,fix_suggestion}:
E_MILP_TIMESTEP,"Simulation timestep 7200 seconds not equal to MILOPS timestep 3600 seconds",ERROR,rule_hydraulic_timestep,milp,network,,hydraulic_timestep,{}, {ruleset: milp},Option,Set hydraulic timestep to 3600 seconds in [OPTIONS] TIME section.
2. parse_network
Parse a network and return summary statistics without validation.
Parameters
Parameter |
Type |
Required |
Description |
|---|---|---|---|
|
string |
No* |
Path to .inp or .json file |
|
string |
No* |
Raw JSON string of WNTR network model |
*Provide exactly one of file_path or json_content.
Returns: Network summary with name, component counts, and metadata.
Example Request
{
"file_path": "network.json"
}
Example Response
{
"success": true,
"name": "Net1",
"comment": "Example network",
"version": "2.2",
"component_counts": {
"nodes": 11,
"links": 13,
"curves": 1,
"patterns": 2,
"controls": 0
},
"verbose_counts": {
"Nodes": 11,
"Links": 13,
"Curves": 1,
"Patterns": 2,
"Controls": 0
}
}
3. convert_format
Convert between EPANET INP and WNTR JSON formats.
Parameters
Parameter |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Path to input file (.inp or .json) |
|
string |
No |
Output path (default: same name with swapped extension) |
|
number |
No |
EPANET version for INP output (default: 2.2) |
Returns: Conversion result with output file path.
Example Request (INP to JSON)
{
"input_path": "Net1.inp",
"output_path": "Net1.json"
}
Example Request (JSON to INP)
{
"input_path": "Net1.json",
"output_path": "Net1_2_0.inp",
"epanet_version": 2.0
}
Example Response
{
"success": true,
"input_file": "Net1.inp",
"output_file": "Net1.json",
"direction": "inp_to_json"
}
4. list_rulesets
List all available validation rulesets (core and custom).
Parameters: None
Returns: Object with core_rulesets and custom_rulesets arrays.
Example Response
{
"core_rulesets": [
{
"key": "epanet_core",
"name": "EPANET core rules",
"version": "1.0.0",
"type": "core",
"layer": "static",
"description": "Simulator-agnostic checks that a model is a well-formed EPANET model.",
"component_rules": 44,
"network_rules": 12,
"simulation_rules": 0
},
{
"key": "hydraulic",
"name": "Hydraulic Simulation Validation",
"version": "1.0.0",
"type": "core",
"layer": "dynamic",
"description": "Validation rules for hydraulic simulation results.",
"component_rules": 0,
"network_rules": 0,
"simulation_rules": 7
}
],
"custom_rulesets": [
{
"key": "milp",
"name": "Mixed Integer Linear Programming ruleset",
"version": "0.2.0",
"type": "custom",
"layer": "static",
"description": "Constraints imposed by an example MILP optimal pump scheduling tool.",
"component_rules": 17,
"network_rules": 0,
"simulation_rules": 1
}
]
}
Each entry carries layer ("static" or "dynamic") alongside
type ("core" or "custom").
5. get_ruleset_details
Get detailed information about a specific ruleset, including all its rules.
Parameters
Parameter |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Ruleset key (e.g., |
|
string |
No |
Filter by component type (e.g., |
Returns: Ruleset details including all component and network rules.
Example Request
{
"ruleset_key": "epanet_core",
"component_type": "WNTREPANETNode"
}
Example Response
{
"key": "epanet_core",
"name": "EPANET core rules",
"version": "1.0.0",
"type": "core",
"layer": "static",
"description": "Simulator-agnostic checks that a model is a well-formed EPANET model.",
"component_rules": [
{
"rule_id": "rule_node_has_name",
"code": "E_NODE_NAME_MISSING",
"description": "A node must have a name.",
"severity": "ERROR",
"attribute": "name",
"component_type": "WNTREPANETNode",
"layer": "static"
},
{
"rule_id": "warn_node_has_type",
"code": "W_NODE_TYPE_MISSING",
"description": "A node should declare which kind of node it is.",
"severity": "WARNING",
"attribute": "node_type",
"component_type": "WNTREPANETNode",
"layer": "static"
}
],
"network_rules": [
{
"rule_id": "rule_network_has_name",
"code": "E_NETWORK_NAME_MISSING",
"description": "Network missing a name",
"severity": "ERROR",
"attribute": "name",
"layer": "static"
}
],
"simulation_rules": []
}
The details carry the ruleset layer and each rule’s layer, plus the
simulation_rules list, so a dynamic rule set’s details are not empty.
6. check_inp
Check an EPANET INP file for structural well-formedness. The structural layer
reports parser problems without building or validating a model. Malformed INP
files never raise: they come back as well_formed: false with each parser
problem mapped to a ValidationIssue tagged validation_layer:
"structural".
Parameters
Parameter |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Path to the .inp file |
Returns: well_formed flag, the given file_path, and the parser
issues (empty when well formed).
Example Response (well formed)
{
"well_formed": true,
"file_path": "Net1.inp",
"issues": []
}
Example Response (malformed)
{
"well_formed": false,
"file_path": "broken.inp",
"issues": [
{
"code": "E_PARSE_FAILED",
"message": "Invalid numeric value 'notanumber' for elevation",
"severity": "ERROR",
"rule_id": "parse",
"ruleset_key": "parser",
"component_type": "network",
"component_name": null,
"attribute": null,
"component_data": {},
"context": {
"error_code": "202",
"line_number": 7,
"section": "[JUNCTIONS]",
"suggestions": ["Check that the value is a valid number"],
"exception_type": "InpSyntaxError"
},
"category": "",
"fix_suggestion": "Check that the value is a valid number",
"validation_layer": "structural",
"simulation_metadata": {}
}
]
}
7. validate_static
Validate an EPANET network model at the static layer: the rule-based checks
that need no simulation. The behaviour mirrors validate_network, and every
reported issue is tagged validation_layer: "static".
Parameters
Parameter |
Type |
Required |
Description |
|---|---|---|---|
|
string |
No* |
Path to .inp or .json file |
|
string |
No* |
Raw JSON string of WNTR network model |
|
array[string] |
No |
Custom ruleset keys (e.g., |
|
string |
No |
|
*Provide exactly one of file_path or json_content.
Returns: ValidationReport as JSON string or TOON string, with every issue
at the static layer. As with validate_network, the two formats describe
the same report.
Example Response
{
"is_valid": false,
"counts": {"ERROR": 2, "WARNING": 1, "INFO": 0},
"issues": [
{
"code": "E_MILP_TIMESTEP",
"message": "Simulation timestep 7200 seconds not equal to MILOPS timestep 3600 seconds",
"severity": "ERROR",
"rule_id": "rule_hydraulic_timestep",
"ruleset_key": "milp",
"validation_layer": "static"
}
]
}
8. validate_simulation
Validate an EPANET network model at the simulation layer. Runs the static
rules first, then simulates the model with WNTR and applies the requested
simulation rulesets to the results. Simulation findings are tagged
validation_layer: "simulation" and reported alongside the static ones.
This tool requires the optional simulation extra. When WNTR is not
installed it returns an isError result with an actionable install message
instead of a traceback.
Parameters
Parameter |
Type |
Required |
Description |
|---|---|---|---|
|
string |
No* |
Path to .inp or .json file |
|
string |
No* |
Raw JSON string of WNTR network model |
|
array[string] |
No |
Simulation ruleset keys (default: |
|
array[string] |
No |
Custom ruleset keys for the static layer (e.g., |
|
string |
No |
|
*Provide exactly one of file_path or json_content.
Returns: The combined static + simulation ValidationReport. Simulation
issues carry validation_layer: "simulation". JSON and TOON describe the
same report, as with the other two validation tools.
Example Response
{
"is_valid": false,
"counts": {"ERROR": 2, "WARNING": 1, "INFO": 0},
"issues": [
{
"code": "E_MINIMUM_PRESSURE",
"message": "Minimum pressure -12.7 psi below required 20.0 psi",
"severity": "ERROR",
"rule_id": "rule_minimum_pressure",
"ruleset_key": "hydraulic",
"component_type": "simulation",
"attribute": "pressure",
"validation_layer": "simulation",
"simulation_metadata": {
"required_outputs": ["node.pressure"]
}
}
]
}
Example Error (WNTR not installed)
Simulation validation requires the optional 'simulation' extra. Install
with: pip install epanetparser[simulation]
Error Handling
All tools return structured error responses when something goes wrong:
{
"error": "File not found: network.json",
"success": false
}
Or for validation errors:
{
"is_valid": false,
"counts": {"ERROR": 1, "WARNING": 0, "INFO": 0},
"issues": [
{
"code": "E_LOAD_FAILED",
"message": "Error loading network: ...",
"severity": "ERROR",
"rule_id": "load",
"ruleset_key": "parser",
"component_type": "network",
"component_name": null,
"attribute": null,
"component_data": {},
"context": {}
}
]
}
The MCP protocol uses isError: true in the CallToolResult for tool-level
errors (e.g., unknown tool name).
Logging
The MCP server suppresses all logging to stdout/stderr to avoid polluting the JSON-RPC protocol stream. Logs are directed to the system logger at CRITICAL level only.
To debug the server, run it directly in a terminal:
epanetparser-mcp
Type requests manually (JSON-RPC format) or use an MCP inspector tool.
Architecture
The MCP server is implemented in epanetparser/core/mcp_server.py using the
official Python mcp SDK. It uses the stdio transport (stdio_server)
which communicates over standard input/output with line-delimited JSON-RPC 2.0
messages.
The server registers 8 tools at startup and handles call_tool requests by
dispatching to the appropriate implementation function. Each implementation
function:
Validates input parameters
Calls the corresponding epanetparser library function
Returns a
CallToolResultwithTextContentcontaining JSON or TOON
The server is stateless — each tool call is independent and does not share state with other calls.
See Also
Agentic AI Integration — Agentic AI integration overview
TOON Format Reference — TOON output format details
Basic Usage — CLI equivalent commands
MCP Specification: https://modelcontextprotocol.io