Skip to content

feat(genx): GenX connector — capacity analysis, diurnal plots, SLURM submission (integrates #51) - #59

Merged
qian-harvard merged 7 commits into
mainfrom
integration/genx-51
Aug 22, 2026
Merged

feat(genx): GenX connector — capacity analysis, diurnal plots, SLURM submission (integrates #51)#59
qian-harvard merged 7 commits into
mainfrom
integration/genx-51

Conversation

@qian-harvard

Copy link
Copy Markdown
Contributor

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.

Stacked on #58 (ANDES) — merge that first; the base will retarget to main automatically.

#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 .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.
  • 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 and a dependency the base install does not carry.

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.
  • Added to tests/test_sdk_imports.py SERVER_DIRS — the sweep that would have caught the mcp 1.x import in the first place.

Security and correctness

  • Shell injection. case_name was interpolated unescaped into the bash script piped to sbatch, in both 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 own account. Job names are now restricted to [A-Za-z0-9._-] and every interpolated value is shlex.quoted.
  • No path containment. No 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 actually gets opened.
  • stdout pollution. 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.

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.
  • Full suite: 408 passed, 17 skipped.
  • python -m buildGenX/ confirmed present in both the wheel (powermcp/_servers/GenX/) and the sdist; twine check passes.
  • powermcp list shows genx; powermcp doctor reports ok with set: genx.repo_root.

Not addressed

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 — filed as a follow-up.

Closes #51.

🤖 Generated with Claude Code

lauraahwa and others added 7 commits July 13, 2026 09:46
…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>
@qian-harvard qian-harvard mentioned this pull request Aug 22, 2026
@qian-harvard
qian-harvard changed the base branch from integration/andes-52 to main August 22, 2026 20:54
@qian-harvard
qian-harvard merged commit 13ff9b8 into main Aug 22, 2026
2 checks passed
@qian-harvard
qian-harvard deleted the integration/genx-51 branch August 22, 2026 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants