feat(genx): GenX connector — capacity analysis, diurnal plots, SLURM submission (integrates #51) - #59
Merged
Merged
Conversation
…submission (#51) Merges lauraahwa's #51, which added a GenX connector as 12 new files, and brings it up to the standard the rest of the distribution holds to. The author's commits are preserved as a parent of the merge. The PR merged cleanly but was inert: it imported mcp 1.x, appeared in no packaging or registry list, and could not start without a .env file. Made it able to start: - server.py imported mcp.server.fastmcp, removed in mcp 2.0. This repo pins mcp>=2,<3, so the server raised ModuleNotFoundError before registering a single tool. Now imports MCPServer, like every other server after #57. - slurm.py resolved GENX_DIR at import and raised RuntimeError when unset, so `import GenX.server` failed on any machine without a .env -- taking down the plotting tools, which need no cluster at all. Everything resolves at call time now. - Dropped the .env / python-dotenv mechanism for the repo's own ~/.powermcp/config.toml, read through powermcp.config.get_path with GENX_DIR still honoured as an override. That removes a parallel config mechanism, a dependency the base install does not carry, and the reason the entrypoint smoke test could not have run this server. Made it ship: - Registered in powermcp/registry.py with a genx.repo_root config key, added to the wheel force-include and the sdist include list, and given a `genx` extra. Without these the directory was in neither the wheel nor the sdist, and `powermcp run genx` did not exist. Verified GenX/ is present in both built artifacts. - Added to tests/test_sdk_imports.py SERVER_DIRS, which is what would have caught the mcp 1.x import in the first place. Security and correctness: - case_name was interpolated unescaped into the bash script piped to sbatch, both in an #SBATCH directive and inside echo "Case: ...". A name closing the quote and opening a command substitution executed on the cluster under the user's account. Job names are now restricted to [A-Za-z0-9._-] and every interpolated value is shlex.quote'd. - No path argument went through powermcp.sandbox, the policy every other connector follows. csv_path and the plot outputs are checked directly; case_dir and scenario_path are checked after resolution, since either may be relative to the GenX directory and only the resolver knows the path that will actually be opened. - print() in diurnal_generation and plot_capacity wrote into the stdio JSON-RPC stream. Routed to logging. - summarize_capacity returned a pandas DataFrame from a `-> dict` tool. - load_capacity_csv printed on a missing required column and then walked into the KeyError two lines later. - Three of six tools had no exception handling, so ordinary input errors escaped as raw MCP protocol errors while the others returned {"success": False}. One shape now. - compute_capacity_cost silently skipped an unknown CapRes region and reported a successful $0 capacity price, and divided by a zero peak demand. - sbatch gained a timeout: a wedged SLURM controller froze the whole server. Added tests/test_genx_server.py (25 tests): script generation and injection refusal, configuration resolution, capacity CSV analysis, the shared error shape, and containment. The registry entry also puts GenX into the existing entrypoint and SDK-import sweeps. Not addressed, filed for follow-up: plot_capacity.classify_resource and diurnal_generation.classify are two independent resource classifiers that disagree on petroleum and distributed generation. Real, but unifying them changes plot output and belongs with the author. Full suite: 408 passed, 17 skipped. Co-Authored-By: Laura Hwa <lauraahwa@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merged
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.
Integrates #51 by @lauraahwa, bringing the new GenX connector up to the standard the rest of the distribution holds to. The author's commits are preserved as a parent of the merge.
#51 merged cleanly but was inert: it imported the mcp 1.x API, appeared in no packaging or registry list, and could not start without a
.envfile.Made it able to start
server.pyimportedmcp.server.fastmcp, removed in mcp 2.0. This repo pinsmcp>=2,<3, so the server raisedModuleNotFoundErrorbefore registering a single tool.slurm.pyresolvedGENX_DIRat import and raisedRuntimeErrorwhen unset, soimport GenX.serverfailed on any machine without a.env— taking down the plotting tools, which need no cluster at all. Everything resolves at call time now..env/python-dotenvmechanism for the repo's own~/.powermcp/config.toml, read throughpowermcp.config.get_path, withGENX_DIRstill honoured as an override. That removes a parallel config mechanism and a dependency the base install does not carry.Made it ship
powermcp/registry.pywith agenx.repo_rootconfig key, added to the wheel force-include and the sdist include list, and given agenxextra. Without these the directory was in neither the wheel nor the sdist, andpowermcp run genxdid not exist.tests/test_sdk_imports.pySERVER_DIRS— the sweep that would have caught the mcp 1.x import in the first place.Security and correctness
case_namewas interpolated unescaped into the bash script piped tosbatch, in both an#SBATCHdirective and insideecho "Case: ...". A name closing the quote and opening a command substitution executed on the cluster under the user's own account. Job names are now restricted to[A-Za-z0-9._-]and every interpolated value isshlex.quoted.powermcp.sandbox, the policy every other connector follows.csv_pathand the plot outputs are checked directly;case_dirandscenario_pathare checked after resolution, since either may be relative to the GenX directory and only the resolver knows the path that actually gets opened.print()indiurnal_generationandplot_capacitywrote into the stdio JSON-RPC stream. Routed tologging.summarize_capacityreturned a pandas DataFrame from a-> dicttool.load_capacity_csvprinted on a missing required column and then walked into theKeyErrortwo lines later.{"success": False}. One shape now.compute_capacity_costsilently skipped an unknown CapRes region and reported a successful $0 capacity price, and divided by a zero peak demand.sbatchgained a timeout — a wedged SLURM controller froze the whole server.Verification
tests/test_genx_server.py, 25 new tests: script generation and injection refusal, configuration resolution, capacity CSV analysis, the shared error shape, and containment.python -m build→GenX/confirmed present in both the wheel (powermcp/_servers/GenX/) and the sdist;twine checkpasses.powermcp listshowsgenx;powermcp doctorreportsokwithset: genx.repo_root.Not addressed
plot_capacity.classify_resourceanddiurnal_generation.classifyare two independent resource classifiers that disagree on petroleum and distributed generation. Real, but unifying them changes plot output and belongs with the author — filed as a follow-up.Closes #51.
🤖 Generated with Claude Code