Conversation
`resolve_solver_case` resolves one file or one serialized PowerIO IR document into a typed module, selects a collection entry by `time_index` or `scenario_id`, and hands a writer the balanced network an operating point states. `OperatingPoint.network` supplies that network directly, so no MATPOWER round trip narrows the state to a representable subset. `pio-ir` and `pio` are the format tokens PowerIO accepts; a document carrying neither is refused on PowerIO's own terms. Diagnostics follow PowerIO's severity vocabulary: `error` fails validation, `remark` and `note` stay out of the user-facing warning list. `check_diagnostics`, `operating_point_module` and `select_entry` are public because a second server module calls them. `value_type_name` isolates the one structural-type read PowerIO publishes no accessor for. `powermcp.sandbox` re-exports `staged_file_write` alongside the rest of the path policy and states powerio's containment default. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pandapower, PyPSA, ANDES, Egret and surge accept `powerio_ir` as the primary argument, keep `network_json` as its alias, and splice the shared response tail: `value_type`, `selection`, `diagnostics`, `warnings`, plus the emission fidelity and the typed-edit report for the adapters that convert into their own model. PowerIO 0.11.2 hands a directory writer a path that does not exist yet, so the PyPSA CSV export and the surge table export create the staging directory before writing into it. ANDES and Egret install their one emitted case file through `staged_file_write`, so a write that fails part way leaves whatever stands at the destination untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Fourteen declarations now read `powerio[mcp,matrix]>=0.11.2,<0.12`. The floor is 0.11.2 because 0.11.1 added `OperatingPoint.network` and matrix `row_ids`, and 0.11.2 sets the path-containment default this repository documents. `GenX/requirements.txt` gains a pin: `GenX/server.py` imports `powermcp.sandbox`, which imports powerio. Version 0.4.0. The sdist excludes local virtual environments and uv build caches, and `.gitignore` covers `.venv-*/`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PowerIO 0.11.2 confines MCP paths to the directory the process started in when no root variable names one. A session-scoped autouse fixture names the repository checkout, pytest's temporary base and the system temporary directory, so a test writing under `tmp_path` is admitted. A test that sets its own roots still overrides it, because `allowed_roots()` reads the environment on every call. `_containment_status` distinguishes an explicit root list from the implicit default, reports the default as a setting to make rather than a policy already chosen, and reports the refusal PowerIO raises for a root variable naming no directory. The doctor's user-visible strings carry no decorative characters. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The powerio and solver-boundary tests require powerio 0.11.2, so the matrix tools name their axes unconditionally and a retired Package document is asserted for what it is: not PowerIO IR. `tests/test_transport.py` states why it exists. The SDK parses a string argument whose text is JSON before validation, so only a real stdio session proves a `powerio_ir` argument reaches the tool intact. `tests/test_sdk_imports.py` covers PLEXOSDB, the one server directory it missed. `tests/test_publish_workflow.py` asserts the sdist exclude list keeps local virtual environments and build caches out of a release. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The PowerIO section describes the server shipped in 0.11.2, names the arguments every adapter takes, and states the containment default: with no root variable set, paths stay beneath the directory the server process started in. Per-server READMEs describe what the import tools accept, which is serialized PowerIO IR or one selected entry of a collection. The retired `Package`, `model-json`, `package_json` and package `study_commit` formats keep their migration note: re-parse the original case and pass its `powerio_ir`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
stdout carries JSON-RPC once a server runs, so the ANDES startup notes go to its logger, which writes to stderr, and the two PowerWorld error reports go to stderr explicitly. `GenX/server.py` keeps the repository root on `sys.path` for the process lifetime and says why: GenX carries no `__init__.py`, so its namespace `__path__` is recomputed from `sys.path` on every attribute lookup and the lazy `GenX.tool_logic` imports would fail without it. PLEXOSDB's notes on its shared import name say what the module is: a re-export of the `plexosdb-mcp` server object under that same name. The install picker's instruction line and the `powermcp` package docstring carry no decorative characters. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
powerio 0.11.2 ships a cp39-abi3 manylinux wheel serving Python 3.9 through 3.14, and the only other Rust dependency, surge-py, lives in an opt-in extra CI never installs. Without the toolchain step a future wheel gap fails loudly instead of building from an sdist. The suite spawns roughly forty subprocesses per job with 60 second timeouts, so stacked pushes on a busy runner turn a timeout into a failure. The concurrency group cancels superseded pull request runs and leaves pushes to main alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
powermcp/tellegen.py exchanges PowerIO IR with the tellegen binary over stdin and stdout: the powerio server parses, Tellegen solves, and the solution comes back as a PowerIO module every other tool reads. The tools are capabilities, contract, solve, solve_module, plan and the Study family. Applying a Study proposal binds a recommendation to the Study and stays a human action, so study_run refuses it. The binary resolves from POWERMCP_TELLEGEN_BINARY, then the tellegen.binary config key, then PATH. A multiconductor value and a module PowerIO marks with an error are both refused before the process starts. The tellegen extra is empty because the CLI is a Rust binary rather than a pip package. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The binary is not a Python package, so the import probe says nothing about it. The doctor runs the configured executable's capabilities subcommand and reports green only when it exits zero and prints JSON. Output decodes as UTF-8 with replacement so a non-UTF-8 console cannot raise UnicodeDecodeError. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
tests/data/fake_tellegen.py stands in for the compiled binary and speaks the same JSON protocol, so CI needs no Rust toolchain. One test runs against a real build and skips unless TELLEGEN_BIN names one. The checked-arguments table in tests/test_sandbox.py lists the three helpers every tellegen tool reaches the filesystem through, and test_windows_only_flags names tellegen among the servers that run everywhere. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
powermcp/TELLEGEN.md covers configuration, the Study bundle format, revision binding and cancellation. The README section states how to build the CLI, point PowerMCP at it, and read the response tail, and the PowerIO section names Tellegen as a consumer of the same IR. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
powermcp/errors.py names the result shape every bundled tool reports:
{"status": "success", ...} on success and {"status": "error", "message": ...}
on failure. run_tool applies it to a tool body, passing the text of a refused
path or a rejected argument straight through and logging anything else with
its traceback before reporting it by type.
The GenX server runs each tool through run_tool and returns tool_success /
tool_error, as do the tool_logic helpers whose dicts the tools hand back
unchanged. A caller reads status once and has no second failure path to
handle from this connector.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbZRpXL3GhRRgXrccNDWka
The PowerFactory tools serialize their result to a JSON string, so the shape travels in the string rather than in a dict. Each one now carries "status" of "success" or "error" plus a message, through tool_success and tool_error and through _reported, which maps a DIgSILENTAgent (ok, message) pair onto it. run_pipeline's step report is handed back as a tool result by run_simulation and run_custom_case, so it carries the same two keys alongside its per-step records; a stopped pipeline names the step that stopped it. read_results_csv still returns raw CSV text on success and now reports a missing or unreadable file through the error shape. tests/test_powerfactory_results.py covers both branches against a substituted agent, since PowerFactory itself is Windows-only vendor software. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbZRpXL3GhRRgXrccNDWka
Both tools run through run_tool, so an r2x failure reaches the caller as
{"status": "error", "message": "<type>: <text>"} rather than as an MCP protocol
error, and the traceback is logged to stderr instead of the JSON-RPC channel.
The refused-path branch and the success branch use the same two keys.
Path containment stays inside each tool function, where tests/test_sandbox.py
reads it from the source.
tests/test_plexosdb_results.py substitutes the r2x packages with fakes that
raise, since r2x needs a real PLEXOS XML study and is not installed in CI.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbZRpXL3GhRRgXrccNDWka
core/engine.py built the py_dss_interface DSS object at import, which loads the OpenDSS engine library. core/server.py imported that module, so a machine without a loadable engine could not start the server at all, not even to list its tools. get_dss() builds the instance and points dss_tools at it on the first call, and compile_opendss_file is the one entry that reaches it: every other tool requires a compiled circuit first. The OpenDSS tools now report through the shared shape, so a "status" of "success" carries the tabular data under "payload" and a failure carries a message, matching the other bundled servers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbZRpXL3GhRRgXrccNDWka
Three module-scope sys.exit calls took the process down on a missing mcp, matplotlib or spicelib, so an install without the optional extras could not start the server or list its tools. The MCP SDK is a plain import, since the runner reports a missing SDK before it launches the file. matplotlib is imported inside the plot tool, after selecting the Agg backend, and spicelib inside the two tools that read .raw files; each names the package to install when it is absent. Only those two tools need either one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbZRpXL3GhRRgXrccNDWka
The root README gains a "Tool results" section stating the shape every tool in this repository reports and pointing at powermcp/errors.py, and the package layout lists that module. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbZRpXL3GhRRgXrccNDWka
powerio 0.11.3 publishes PioModule.type_name, the canonical structural name of a module's value, and the top-level diagnostic_record and diagnostic_records converters. The property replaces the read of the private _inner._type_name, and the two converters replace PowerMCP's own copies, which produced the same keys in the same order. The dependency floor moves to 0.11.3 in every place that declares it. Every test module that reaches the typed powerio APIs now requires that floor, so an older powerio skips those modules instead of failing inside them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbZRpXL3GhRRgXrccNDWka
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbZRpXL3GhRRgXrccNDWka
samtalki
marked this pull request as ready for review
September 17, 2026 22:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
powerio[...]>=0.11.3,<0.12everywhere (15 places), and give every test module that needs the 0.11.3 names the same version floor, so an older powerio skips instead of failing.PioModule.type_nameand build diagnostic records withpowerio.diagnostic_records, both public in powerio 0.11.3. The private attribute read and the two copies inpowermcp/solver_case.pyare gone;powermcp/tellegen.pyandpandapower/panda_mcp.pyuse the powerio names.Stacked on #70, which stacks on #67 and #64. Draft until powerio 0.11.3 is on PyPI: until then the pin cannot resolve and CI cannot install the package.
Testing
python -m pytest tests -qon CPython 3.12.12 from the branch root, against a wheel built from the powerio 0.11.3 release stack:🤖 Generated with Claude Code
https://claude.ai/code/session_01TbZRpXL3GhRRgXrccNDWka