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
6 changes: 1 addition & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,9 @@ jobs:

- uses: dtolnay/rust-toolchain@stable

- name: Install companion PowerIO draft
run: |
python -m pip install --upgrade pip
python -m pip install "powerio[mcp,matrix] @ git+https://github.com/eigenergy/powerio.git@feat/mcp-dist-display-tools"

- name: Install package
run: |
python -m pip install --upgrade pip
python -m pip install -e . pytest

- name: Run tests
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ These tools wrap commercial or locally-installed software, so PowerMCP stores th

PowerMCP ships a compiler server backed by [powerio](https://github.com/eigenergy/powerio) as a **core dependency** (no extra needed). It parses transmission and distribution formats into canonical JSON transports, converts between target artifacts 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 save runtime artifacts only when a backend needs a file.
Its JSON transport is the exchange format between PowerMCP servers: parse a case once, pass the returned `json` string between tool calls, and save runtime artifacts only when a backend needs a file. Existing `json` transport workflows remain supported.

```
parse(path="case9.raw") # powerio server -> {"json": ..., "summary": ...}
Expand All @@ -139,6 +139,17 @@ matrix(kind="ptdf", json=...) # powerio server builds matri
save(to_format="psse", out_path="case9.raw", json=...) # stage a file for path only servers
```

PowerIO 0.4.0 also supports the `.pio.json` package transport, which carries the model plus package metadata and structured diagnostics:

```
parsed = parse(path="case9.raw", transport="package")
pkg = parsed["package_json"]
summary(package_json=pkg)
matrix(kind="ptdf", package_json=pkg)
save(to_format="psse", out_path="case9.raw", package_json=pkg)
diagnostics(package_json=pkg) # package diagnostics summary and structured findings
```

`summary` returns the canonical nested shape used by PowerIO and PowerMCP: counts live under `elements` (`elements.buses`, `elements.branches`, `elements.generators`) and topology metadata lives under `topology` (`topology.connected_components`, `topology.reference_buses`).

`save` covers the servers without a bridge: write the converted case to disk and point their load tools at the file. For OpenDSS, save a distribution transport as DSS, then compile that DSS file:
Expand Down
5 changes: 4 additions & 1 deletion powerio/powerio_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
this server keeps no copy of the conversion/summary/matrix tools. PowerIO is the
cross-server compiler layer for transmission and distribution cases:

- ``parse``: source format to canonical JSON transport.
- ``parse``: source format to canonical JSON or ``.pio.json`` package transport.
- ``convert`` / ``save``: canonical transport or source format to target artifact.
- ``summary``: canonical network summary.
- ``normalize``: normalized transmission transport.
- ``matrix``: sparse transmission matrix outputs.
- ``diagnostics``: structured diagnostics for ``.pio.json`` packages.
- ``display``: display artifacts such as PowerWorld ``.pwd`` geometry.

PowerMCP deliberately re-exports only those canonical tools. GridFM and PyPSA
Expand All @@ -30,6 +31,7 @@
parse = _server.parse
normalize = _server.normalize
matrix = _server.matrix
diagnostics = _server.diagnostics
display = _server.display

__all__ = [
Expand All @@ -40,6 +42,7 @@
"parse",
"normalize",
"matrix",
"diagnostics",
"display",
]

Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ build-backend = "hatchling.build"

[project]
name = "powermcp"
version = "0.2.0"
version = "0.2.1"
description = "MCP servers for power-system software (PowerWorld, OpenDSS, PSS/E, pandapower, PyPSA, and more)"
readme = "README.md"
license = { file = "LICENSE" }
Expand Down Expand Up @@ -40,10 +40,10 @@ dependencies = [
# 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.
# >=0.3.3: the MCP tool surface is the seven canonical verbs:
# convert, save, summary, parse, normalize, matrix, display.
# >=0.4.0: the MCP tool surface is:
# convert, save, summary, parse, normalize, matrix, diagnostics, display.
# powerio_mcp.py re-exports exactly that surface with no local overlay.
"powerio[mcp,matrix]>=0.3.3",
"powerio[mcp,matrix]>=0.4.0",
# CLI / installer toolkit:
"typer>=0.12",
"questionary>=2.0",
Expand Down
41 changes: 40 additions & 1 deletion tests/test_powerio_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

import pytest

pytest.importorskip("powerio", minversion="0.3.3")
pytest.importorskip("powerio", minversion="0.4.0")

import powerio # noqa: E402

Expand Down Expand Up @@ -91,19 +91,26 @@ def test_tool_surface_is_canonical():
"parse",
"normalize",
"matrix",
"diagnostics",
"display",
}
for name in ("parse", "summary", "normalize", "matrix", "display"):
props = tools[name].inputSchema["properties"]
assert "from_format" in props
assert "format" not in props
parse_props = tools["parse"].inputSchema["properties"]
assert "transport" in parse_props
convert_props = tools["convert"].inputSchema["properties"]
assert "to_format" in convert_props and "from_format" in convert_props
assert "package_json" in convert_props
assert "to" not in convert_props and "format" not in convert_props
for name in ("summary", "normalize", "matrix"):
assert "package_json" in tools[name].inputSchema["properties"]
save_schema = tools["save"].inputSchema
assert save_schema["required"] == ["out_path"]
save_props = save_schema["properties"]
assert "to_format" in save_props and "from_format" in save_props
assert "package_json" in save_props
assert "to" not in save_props and "format" not in save_props


Expand Down Expand Up @@ -225,6 +232,38 @@ def test_save_accepts_json_transport(tmp_path):
assert powerio.parse_file(out).n_buses == 9


def test_package_transport_flows_through_core_tools(tmp_path):
parsed = powerio_mcp.parse(path=str(CASE9), transport="package")
assert parsed["schema"] == "powerio.parse"
assert parsed["transport"] == "package"
assert parsed["json_format"] == "package"
assert parsed["domain"] == "transmission"
assert parsed["model"] == "balanced"
assert "package_json" in parsed

package = json.loads(parsed["package_json"])
assert package["model_kind"] == "balanced"
assert package["model"]["kind"] == "balanced"

package_json = parsed["package_json"]
assert powerio_mcp.summary(package_json=package_json)["elements"]["buses"] == 9

matrix = powerio_mcp.matrix("bprime", package_json=package_json)
assert matrix["kind"] == "bprime"
assert matrix["shape"] == [9, 9]

out = tmp_path / "case9.m"
powerio_mcp.save(out_path=str(out), package_json=package_json)
assert powerio.parse_file(out).n_buses == 9

diag = powerio_mcp.diagnostics(package_json)
assert diag["schema"] == "powerio.diagnostics"
assert diag["model_kind"] == "balanced"
assert diag["summary"]["status"] in {"ok", "info", "warning", "error", "fatal"}
assert isinstance(diag["summary"]["text"], str)
assert isinstance(diag["diagnostics"], list)


def test_save_exactly_one_input(tmp_path):
out = tmp_path / "x.m"
with pytest.raises(ValueError):
Expand Down
Loading