Skip to content

feat: browser WASM execution (phases 1+2 of 3)#57

Open
samuelduchesne wants to merge 5 commits into
mainfrom
feat/energyplus-wasm-execution
Open

feat: browser WASM execution (phases 1+2 of 3)#57
samuelduchesne wants to merge 5 commits into
mainfrom
feat/energyplus-wasm-execution

Conversation

@samuelduchesne
Copy link
Copy Markdown
Collaborator

Summary

Completes phases 1 + 2 of the browser-side EnergyPlus execution work. Clients that can render MCP App iframes (e.g. Claude Desktop) can now run a design-day or small-annual EnergyPlus simulation entirely in the browser via the bundled WASM build, with the server only ingesting the output artifacts.

  • Phase 1 (already on this branch): upload_simulation_result accepts pre-computed EnergyPlus artifacts and makes them visible through every idfkit://simulation/* resource.
  • Phase 2 (this changeset):
    • run_simulation_in_browser handoff tool: serializes the pre-flighted IDF + EPW into _meta.browser_run for the companion UI resource.
    • ui://idfkit/simulator.html (new simulator_viewer.py): self-contained iframe that loads the EnergyPlus WASM build, runs the simulation, and posts outputs back through upload_simulation_result.
    • fetch_energyplus_asset (paginated via offset/chunk_size): proxies WASM glue / binary / IDD / datasets through the MCP Apps SDK tool channel, sidestepping the cross-origin/CSP/mixed-content restrictions that block direct HTTP fetch from sandboxed MCP App iframes. Drives byte-level progress in the iframe.
    • get_results_summary tool: wraps the existing idfkit://simulation/results resource so agents whose clients don't autonomously read resources can still pull QA diagnostics.
    • upload_simulation_result no longer requires eplusout.sql — failed browser runs upload eplusout.err so the server can still surface the fatal.
    • WASM bundle packaging: make sync-wasm-assets copies envelop/public/energyplus/ into src/idfkit_mcp/assets/energyplus/, hatchling force-include ships it inside the wheel, the Dockerfile pulls it from the monorepo build context. Env var IDFKIT_MCP_ENERGYPLUS_DIR lets deployments mirror assets elsewhere.

Architecture notes

  • The iframe does no network fetch(). Every asset and every round-trip goes through the MCP Apps SDK's callServerTool, which means there is no CORS, CSP, or mixed-content path to negotiate with — tunnels, localhost, and remote connectors all behave the same.
  • Gate 3 (end-to-end browser run in Claude Desktop) is verified on feat/energyplus-wasm-execution via npx mcp-remote http://127.0.0.1:8000/mcp/.

Test plan

  • make check green (ruff, pyright strict on src/, deptry).
  • make test green — 243 tests passing, including:
    • tests/test_run_simulation_in_browser.py: handoff shape, pre-flight on a copy, EPW encoding, version resolution, simulation-lock rejection, unreadable-weather handling, fetch_energyplus_asset allowlist + chunking + offset-past-EOF, asset route 503/404 + CORS.
    • tests/test_upload_simulation_result.py: updated — missing SQL is now accepted with a fatal-only err payload; empty files is rejected.
  • make sync-wasm-assets && make serve-http; curl /assets/energyplus/* returns the expected WASM / IDD / datasets with Access-Control-Allow-Origin: *; traversal returns 404; missing-sync returns 503.
  • Claude Desktop → run_simulation_in_browser → iframe fetches ~30 MB WASM with visible progress → callMain runs → upload_simulation_result populates idfkit://simulation/results on the same session.

🤖 Generated with Claude Code

samuelduchesne and others added 2 commits April 23, 2026 13:35
…ecution)

Accept pre-computed EnergyPlus artifacts (eplusout.sql and friends) via a
new MCP tool and materialize them into state.simulation_result via the
existing SimulationResult.from_directory factory. Every
idfkit://simulation/* resource and every analysis tool (query_timeseries,
query_simulation_table, analyze_peak_loads, view_simulation_report) now
reads the uploaded run transparently — no contract changes downstream.

This is the foundation for running EnergyPlus in the browser via WASM:
phase 2 will wrap the idfkit-app WASM runtime behind an embeddable JS API,
phase 3 will register ui://idfkit/simulator.html and wire the iframe's
tools/call back to upload_simulation_result.

Covered by a pytest subprocess harness that spins up the server in
streamable-HTTP mode on a free port and exercises the full round-trip
(upload → read resource) to validate mcp-session-id affinity.

Adds `make serve-http` for manual smoke testing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…xecution)

Ship the iframe that executes EnergyPlus in the browser and posts outputs
back through upload_simulation_result, closing the loop that phase 1 set
up server-side.

- New tool run_simulation_in_browser serializes the pre-flighted IDF + EPW
  and hands them to the companion UI resource ui://idfkit/simulator.html.
- New tool fetch_energyplus_asset pages the WASM glue / binary / IDD /
  datasets back to the iframe via the MCP Apps SDK tool channel,
  sidestepping cross-origin fetch restrictions in sandboxed MCP App
  iframes. Supports offset/chunk_size for byte-level progress.
- New tool get_results_summary surfaces idfkit://simulation/results as a
  callable tool for agents whose clients do not autonomously read
  resources.
- EnergyPlus WASM bundle ships inside the wheel via hatchling
  force-include (build artifact populated by make sync-wasm-assets or the
  Dockerfile assets stage).
- upload_simulation_result no longer requires eplusout.sql so failed runs
  can still upload diagnostic artifacts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Docs preview for this PR is available at:
https://mcp.idfkit.com/pr-preview/pr-57/

github-actions Bot added a commit that referenced this pull request Apr 23, 2026
Drops the hardcoded `energyplus.js-26.1.wasm` entry in the asset
allowlist. The next envelop rebuild against a newer EnergyPlus release
would have emitted a differently-named .wasm and produced an opaque
"not in the allowlist" error with no path forward.

- `fetch_energyplus_asset` now builds its allowlist by scanning the
  installed assets directory through a narrow set of globs
  (`energyplus.js`, `energyplus*.wasm`, `Energy+.idd`, `datasets/*.idf`).
  Attack surface unchanged; future version bumps work without edits.
- `run_simulation_in_browser` passes `wasm_candidates` through the handoff
  so the iframe can't drift from what's actually on disk. The iframe keeps
  a static fallback list for older handoff shapes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
github-actions Bot added a commit that referenced this pull request Apr 24, 2026
run_simulation and upload_simulation_result include a sample of severe
and warning messages capped at 10 to keep tool responses small. UIs had
to compare array length against the total count to know whether the
sample was complete, leading to displays like "10 Warning" next to
"258 warnings" with no explanation.

Add severe_messages_truncated / warning_messages_truncated to
SimulationErrorDetail and severe_messages_truncated to
GetResultsSummaryResult so consumers can render "10 of 258 shown"
without inferring the cap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
github-actions Bot added a commit that referenced this pull request Apr 24, 2026
github-actions Bot added a commit that referenced this pull request Apr 24, 2026
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.

1 participant