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

file_path

string

No*

Path to .inp or .json file

json_content

string

No*

Raw JSON string of WNTR network model

rulesets

array[string]

No

Custom ruleset keys (e.g., ["milp"])

output_format

string

No

"json" or "toon" (default: "json")

*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

file_path

string

No*

Path to .inp or .json file

json_content

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

input_path

string

Yes

Path to input file (.inp or .json)

output_path

string

No

Output path (default: same name with swapped extension)

epanet_version

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

ruleset_key

string

Yes

Ruleset key (e.g., "epanet_core", "milp")

component_type

string

No

Filter by component type (e.g., "WNTREPANETNode")

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

file_path

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

file_path

string

No*

Path to .inp or .json file

json_content

string

No*

Raw JSON string of WNTR network model

rulesets

array[string]

No

Custom ruleset keys (e.g., ["milp"])

output_format

string

No

"json" or "toon" (default: "json")

*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

file_path

string

No*

Path to .inp or .json file

json_content

string

No*

Raw JSON string of WNTR network model

simulation_rulesets

array[string]

No

Simulation ruleset keys (default: ["hydraulic"])

rulesets

array[string]

No

Custom ruleset keys for the static layer (e.g., ["milp"])

output_format

string

No

"json" or "toon" (default: "json")

*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:

  1. Validates input parameters

  2. Calls the corresponding epanetparser library function

  3. Returns a CallToolResult with TextContent containing JSON or TOON

The server is stateless — each tool call is independent and does not share state with other calls.

See Also