Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions ANDES/andes_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,8 @@ def load_network_from_json(
Accepts the ``json`` string returned by the powerio server's parse_case or
case_to_json tools. Converts the network to MATPOWER format, writes it to
out_path (use a .m extension), and returns the path along with component
counts. Pass out_path to run_power_flow to run the simulation. Requires the
powerio extra (pip install 'powermcp[powerio]').
counts. Pass out_path to run_power_flow to run the simulation. powerio is a
core dependency, so this is always available.

Args:
network_json: The JSON transport string from powerio
Expand Down Expand Up @@ -399,8 +399,8 @@ def load_network_from_any(

Reads MATPOWER .m, PSS/E .raw (v33), PowerWorld .aux, PowerModels JSON, or
egret JSON via powerio and writes a MATPOWER file to out_path (use a .m
extension). Pass out_path to run_power_flow to run the simulation. Requires
the powerio extra (pip install 'powermcp[powerio]').
extension). Pass out_path to run_power_flow to run the simulation. powerio
is a core dependency, so this is always available.

Args:
file_path: Path to the source case file
Expand Down
8 changes: 4 additions & 4 deletions Egret/egret_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ def load_model_from_any(file_path: str, source_format: Optional[str] = None) ->
egret JSON via powerio, converts it to egret JSON, validates it as an
egret ModelData, and stages it to a temp file. Pass the returned
`case_file` path to solve_ac_opf, solve_dc_opf, or
solve_unit_commitment_problem. Requires the powerio extra
(pip install 'powermcp[powerio]').
solve_unit_commitment_problem. powerio is a core dependency, so this is
always available.

Args:
file_path: Path to the case file
Expand Down Expand Up @@ -254,8 +254,8 @@ def load_model_from_json(network_json: str) -> Dict[str, Any]:
case_to_json tools, so a case parsed once there feeds egret without
re-reading the file. Converts it to egret JSON, validates it as an egret
ModelData, and stages it to a temp file. Pass the returned `case_file`
path to the solver tools. Requires the powerio extra
(pip install 'powermcp[powerio]').
path to the solver tools. powerio is a core dependency, so this is always
available.

Args:
network_json: The JSON transport string from powerio
Expand Down
8 changes: 4 additions & 4 deletions PyPSA/pypsa_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,8 +727,8 @@ def import_case_from_any(
.nc extension); pass that path as network_name to the other tools. PyPSA's
ppc import drops generator cost data; anything else it cannot represent is
listed in the returned warnings. Branches with rating 0 are imported with
s_nom 0 unless overwrite_zero_s_nom supplies a value. Requires the powerio
extra (pip install 'powermcp[powerio]').
s_nom 0 unless overwrite_zero_s_nom supplies a value. powerio is a core
dependency, so this is always available.

Args:
file_path: Path to the case file
Expand Down Expand Up @@ -776,8 +776,8 @@ def import_case_from_json(
a file around or re-parsing it. Expects source-valued tables (MW, degrees)
as parse_case emits them, not the per-unit normalize_case form. Writes the
network to output_path (use a .nc extension); pass that path as
network_name to the other tools. Requires the powerio extra
(pip install 'powermcp[powerio]').
network_name to the other tools. powerio is a core dependency, so this is
always available.

Args:
network_json: The JSON transport string from powerio
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ PowerMCP installs as a single Python package with an interactive CLI. Python 3.1
pip install powermcp
```

The base install includes the two open-source engines that need no extra setup — **pandapower** and **PyPSA**. Every other tool is opt-in via an extra:
The base install includes the open-source engines that need no extra setup — **pandapower**, **PyPSA**, and **PowerIO** (the cross-server case-conversion substrate). Every other tool is opt-in via an extra:

```bash
pip install powermcp[psse] # add PSS/E support
pip install powermcp[andes,opendss] # add several tools at once
pip install powermcp[opensource] # all open-source tools (ANDES, Egret, OpenDSS, surge, HOPE, LTSpice, PowerIO)
pip install powermcp[opensource] # all open-source tools (ANDES, Egret, OpenDSS, surge, HOPE, LTSpice)
pip install powermcp[all] # everything (closed-source tools still need the local software)
```

Expand All @@ -87,7 +87,7 @@ pip install powermcp[all] # everything (closed-source tools still
powermcp install
```

The wizard lets you pick tools (pandapower + PyPSA pre-selected), captures the local install path for any closed-source/EXE-based tools you choose (PSS/E, PSLF, PowerFactory, PSCAD, LTSpice), installs the right extras, and writes the MCP client configuration for **Claude Desktop**, **Claude Code**, and the **Codex CLI**. Use `--dry-run` to preview the changes, or `--yes` for a non-interactive core install.
The wizard lets you pick tools (pandapower + PyPSA + PowerIO pre-selected), captures the local install path for any closed-source/EXE-based tools you choose (PSS/E, PSLF, PowerFactory, PSCAD, LTSpice), installs the right extras, and writes the MCP client configuration for **Claude Desktop**, **Claude Code**, and the **Codex CLI**. Use `--dry-run` to preview the changes, or `--yes` for a non-interactive core install.

In the interactive picker, move with ↑/↓ and **press SPACE to toggle each tool** before ENTER (ENTER alone keeps only the preselected tools). Prefer not to use the checkbox? Choose tools directly:

Expand Down Expand Up @@ -126,7 +126,7 @@ These tools wrap commercial or locally-installed software, so PowerMCP stores th

### Case conversion between servers (PowerIO)

The `powerio` extra adds a conversion server backed by [powerio](https://github.com/eigenergy/powerio). It parses MATPOWER `.m`, PSS/E `.raw`, PowerWorld `.aux`, PowerModels JSON, and egret JSON into one format neutral network, converts between those formats with fidelity warnings, and builds the sparse matrices solvers need (B', B'', Y_bus, PTDF, LODF, Laplacian, LACPF).
PowerMCP ships a conversion server backed by [powerio](https://github.com/eigenergy/powerio) as a **core dependency** (no extra needed). It parses MATPOWER `.m`, PSS/E `.raw`, PowerWorld `.aux`, PowerModels JSON, and egret JSON into one format neutral network, converts between those formats with fidelity warnings, and builds the sparse matrices solvers need (B', B'', Y_bus, PTDF, LODF, Laplacian, LACPF).

Its JSON transport is the exchange format between PowerMCP servers: parse a case once, pass the returned `json` string between tool calls, and load it anywhere.

Expand Down
8 changes: 4 additions & 4 deletions pandapower/panda_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ def load_network_from_any(file_path: str, source_format: Optional[str] = None) -
Reads MATPOWER .m, PSS/E .raw (v33), PowerWorld .aux, PowerModels JSON, or
egret JSON via powerio and converts it to a pandapower network, replacing
the currently loaded one. Use this for case formats load_network does not
accept. Requires the powerio extra (pip install 'powermcp[powerio]').
accept. powerio is a core dependency, so this is always available.

Args:
file_path: Path to the case file
Expand Down Expand Up @@ -422,8 +422,8 @@ def load_network_from_json(network_json: str) -> Dict[str, Any]:
case_to_json tools, so a case parsed once there loads here without passing
a file around or re-parsing it. Expects source-valued tables (MW, degrees)
as parse_case emits them, not the per-unit normalize_case form. Replaces
the currently loaded network. Requires the powerio extra
(pip install 'powermcp[powerio]').
the currently loaded network. powerio is a core dependency, so this is
always available.

Args:
network_json: The JSON transport string from powerio
Expand Down Expand Up @@ -452,7 +452,7 @@ def export_network_to_format(to_format: str) -> Dict[str, Any]:
Converts the loaded network to MATPOWER tables and serializes them with
powerio. to_format is a powerio format name: matpower (m),
powermodels-json (pm), egret-json (egret), psse (raw), powerworld (aux).
Requires the powerio extra (pip install 'powermcp[powerio]').
powerio is a core dependency, so this is always available.

Args:
to_format: Target format name
Expand Down
10 changes: 7 additions & 3 deletions powermcp/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ def resolve_module_root(self) -> Path:
external_solvers=("Julia",),
),
Tool(
"powerio", "PowerIO", "open-source", windows_only=False, extra="powerio",
"powerio", "PowerIO", "open-source", windows_only=False, extra=None,
server_dir="powerio", run_kind="script", entry_rel="powerio_mcp.py",
probe="powerio",
notes="Format-neutral case conversion and matrix builder; the JSON transport is the cross-server exchange format.",
notes="Format-neutral case conversion and matrix builder; the JSON transport is the cross-server exchange format. Core dependency: it is the cross-server exchange substrate the pandapower/Egret/PyPSA/ANDES bridges build on.",
),
# ---- CLOSED-SOURCE / VENDOR ----
Tool(
Expand Down Expand Up @@ -205,7 +205,11 @@ def resolve_module_root(self) -> Path:
}

# Tools installed by a bare `pip install powermcp` and pre-checked in the wizard.
CORE: tuple[str, ...] = ("pandapower", "pypsa")
# powerio is core because it is the cross-server exchange substrate (the
# pandapower/Egret/PyPSA/ANDES bridges all build on its JSON transport) and is
# cheap: abi3 wheels, zero required runtime deps, extras resolving to numpy
# (core) + scipy (transitive via pandapower).
CORE: tuple[str, ...] = ("pandapower", "pypsa", "powerio")


def get_tool(name: str) -> "Tool":
Expand Down
10 changes: 8 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ dependencies = [
"numpy>=1.24",
"pandas>=2.0",
"mcp>=1.0",
# powerio is the cross-server exchange substrate (the pandapower/Egret/PyPSA/
# ANDES bridges build on its JSON transport), and is cheap to require: abi3
# wheels for five platforms, zero required runtime deps, [mcp,matrix] adding
# only scipy (already transitive via pandapower) on top of mcp+numpy.
"powerio[mcp,matrix]>=0.1.1",
# CLI / installer toolkit:
"typer>=0.12",
"questionary>=2.0",
Expand All @@ -53,7 +58,8 @@ opendss = ["py_dss_toolkit"]
ltspice = ["PyLTSpice", "matplotlib"]
surge = ["surge-py>=0.1.5; python_version >= '3.12' and python_version < '3.15'"]
hope = ["PyYAML>=6.0"]
powerio = ["powerio[mcp,matrix]>=0.1.1"]
# powerio moved to core `dependencies`; the extra is gone (a bare
# `pip install powermcp` now always provides the conversion server).
# --- closed-source / vendor tool engines ---
powerworld = ["esa", "numba"] # esa auto-discovers a running Simulator via COM.
# numba is required: esa 1.3.5's no-numba code path
Expand All @@ -65,7 +71,7 @@ psse = [] # vendor `psspy` not on PyPI —
pslf = ["pandas"] # vendor `PSLF_PYTHON` not on PyPI — path captured in config.toml
powerfactory = ["fastmcp>=2.0", "numpy>=1.26", "matplotlib>=3.8", "pandas>=1.5"] # vendor `powerfactory` not on PyPI
# --- convenience groups ---
opensource = ["powermcp[andes]", "powermcp[egret]", "powermcp[opendss]", "powermcp[ltspice]", "powermcp[surge]", "powermcp[hope]", "powermcp[powerio]"]
opensource = ["powermcp[andes]", "powermcp[egret]", "powermcp[opendss]", "powermcp[ltspice]", "powermcp[surge]", "powermcp[hope]"]
windows = ["powermcp[pscad-windows]", "powermcp[powerworld]", "powermcp[powerfactory]", "powermcp[psse]", "powermcp[pslf]"]
all = ["powermcp[opensource]", "powermcp[powerworld]", "powermcp[powerfactory]", "powermcp[pscad-windows]", "powermcp[psse]", "powermcp[pslf]"]

Expand Down
5 changes: 3 additions & 2 deletions tests/test_powerio_server.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Tests for the powerio conversion server, the PyPSA bridge, and the
registry/runner wiring.

The whole module skips when powerio is not installed (it is an opt-in extra).
powerio is a core dependency, so it is normally present; the importorskip below
stays as insurance for stripped-down environments.
The FastMCP-decorated tools stay ordinary callables, so we exercise them
in-process without a transport. The launch test lives here rather than in
test_runner.py so it skips with the rest of the module.
Expand Down Expand Up @@ -236,7 +237,7 @@ def test_pypsa_import_missing_file(tmp_path):
def test_registry_entry():
t = TOOLS["powerio"]
assert t.kind == "open-source"
assert t.extra == "powerio"
assert t.extra is None # promoted to a core dependency (issue #30)
assert t.run_kind == "script"
assert t.windows_only is False
assert t.probe == "powerio"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


def test_core_tools_present_and_have_no_extra():
assert set(CORE) == {"pandapower", "pypsa"}
assert set(CORE) == {"pandapower", "pypsa", "powerio"}
for name in CORE:
assert TOOLS[name].extra is None

Expand Down
Loading