opm-mcp
# opm-mcp
[](https://github.com/ojaogezi/opm-mcp/actions/workflows/ci.yml)
[](LICENSE)
[](https://www.python.org/downloads/)
[](https://modelcontextprotocol.io/)
A Model Context Protocol (MCP) server that gives Claude direct access to the
[Open Porous Media](https://opm-project.org/) reservoir-simulation stack:
- **OPM Flow** — black-oil and compositional reservoir simulator
(ECLIPSE-compatible), from [OPM/opm-simulators](https://github.com/OPM/opm-simulators).
- **ResInsight** — 3D viewer and post-processor.
- **OPM utility binaries** — `opmpack`, `opmhash`, `summary`, `convertECL`, `compareECL`.
- **OPM upscaling** (optional `opm-upscaling` package).
> `flow` and its Python bindings (`python3-opm-simulators`) are built from the
> [OPM/opm-simulators](https://github.com/OPM/opm-simulators) repository — see it
> for the simulator source, supported keywords, build instructions, and issue
> tracker.
On **Windows** the server runs natively (so Claude Code / Claude Desktop can
spawn it) and delegates every Linux-only binary to an Ubuntu distro under WSL2.
On **native Linux** the same package runs the OPM binaries directly — no WSL
involved. Result files (UNSMRY / UNRST / EGRID / INIT / RFT) are parsed
in-process via [`resdata`](https://github.com/equinor/resdata) either way.
## Architecture
```
+-------------------+ +----------------------+
| Windows | | WSL2 Ubuntu-24.04 |
| | | |
| Claude <-----> | wsl.exe | flow |
| opm-mcp (Py) | -----------> | ResInsight |
| resdata | | python3-opm-common |
| (UNSMRY etc.) | | convertECL/etc. |
+-------------------+ +----------------------+
^ |
| reads result files via UNC / |
+--- /mnt/c/... path translation -----+
```
## Prerequisites
On Windows:
- Python 3.11+ (tested with 3.13).
- WSL2 enabled, with an Ubuntu-24.04 distro available (`wsl -l -v`).
In the WSL distro, install OPM:
```bash
sudo apt-get update
sudo apt-get install flow resinsight python3-opm-common python3-opm-simulators
# Optional, for the upscaling tools:
sudo apt-get install opm-upscaling
# Optional, for fully headless ResInsight rendering without WSLg:
sudo apt-get install xvfb mesa-utils libgl1-mesa-dri
```
The `flow` simulator comes from
[OPM/opm-simulators](https://github.com/OPM/opm-simulators). The `apt` packages
above track upstream releases; to run a development build or a specific version,
follow the [build instructions](https://github.com/OPM/opm-simulators#building-opm)
there and point the server at it with `OPM_MCP_FLOW_BIN` (see
[Configuration](#configuration-env-vars)).
## Install
```powershell
git clone https://github.com/ojaogezi/opm-mcp.git
cd opm-mcp
py -m pip install -e .
```
This installs the `opm-mcp` console script and pulls in `mcp[cli]`, `resdata`,
`pydantic` and `numpy`.
For development:
```powershell
py -m pip install -e ".[dev]"
pytest
```
## Register with Claude Code
```powershell
claude mcp add opm --scope user -- py -m opm_mcp.server
```
Or, if you prefer a JSON config (Claude Desktop / cursor / etc.), add this to
your `mcpServers` block:
```json
{
"mcpServers": {
"opm": {
"command": "py",
"args": ["-m", "opm_mcp.server"],
"env": {
"OPM_MCP_WSL_DISTRO": "Ubuntu-24.04"
}
}
}
}
```
## Configuration (env vars)
| Variable | Default | Purpose |
| ------------------------- | -------------------- | ---------------------------------------------- |
| `OPM_MCP_WSL_DISTRO` | `Ubuntu-24.04` | Name of the WSL distro to run OPM in. |
| `OPM_MCP_FLOW_BIN` | `flow` | Binary name / absolute WSL path for Flow. |
| `OPM_MCP_RESINSIGHT_BIN` | `ResInsight` | Binary name / absolute WSL path for ResInsight.|
| `OPM_MCP_WORK_DIR` | `~/.opm-mcp` (Win) | Where job logs and artefacts go. |
| `OPM_MCP_FLOW_THREADS` | `0` (auto) | `--threads-per-process` default for runs. |
| `OPM_MCP_JOB_HISTORY` | `50` | How many recent jobs to remember in-memory. |
| `OPM_MCP_LOG_TAIL` | `200` | Default line count for `flow_log_tail`. |
| `OPM_MCP_MAX_PARALLEL` | `2` | Max concurrent Flow runs; extra submissions queue. |
## Tool reference
### Environment & paths
| Tool | Purpose |
| ----------------- | ------------------------------------------------------------------ |
| `opm_environment` | Probe backend mode (WSL / native Linux), `flow`, `ResInsight`, OPM Python bindings. |
| `list_cases` | Scan a directory tree for decks/results — the session opener. |
| `to_wsl_path` | Translate `C:\foo\bar` to `/mnt/c/foo/bar`. |
| `to_windows_path` | Reverse translation. |
### Deck inspection (uses `python3-opm-common` in WSL)
| Tool | Purpose |
| ---------------- | --------------------------------------------------------------- |
| `validate_deck` | Parse a DATA deck, report keyword counts. |
| `summarize_deck` | Title, phases, units, grid dims, well/group names, schedule length. |
| `list_wells` | Per-well info at schedule step 0. |
| `deck_text` | Numbered text slice of the deck or any include file (host-native, no WSL). |
| `deck_grep` | Regex search across the deck **and its INCLUDE tree** (resolves `PATHS` `$ALIAS`es). |
### OPM Flow (background jobs)
| Tool | Purpose |
| ---------------- | -------------------------------------------------------------------- |
| `run_flow` | Start a Flow job; returns a `job_id` immediately. Queues (`state="queued"`) past `OPM_MCP_MAX_PARALLEL`; `mpi_processes=N` runs `mpirun -np N`. |
| `flow_status` | Current state, queue position, last progress line, provenance (flow version + deck SHA-256). |
| `flow_log_tail` | Tail of combined stdout/stderr; `grep=` regex-filters the whole log (e.g. `"will be shut\|Error:"`). |
| `flow_cancel` | Cancel a running or queued job (works across server restarts). |
| `flow_jobs` | List recent jobs (most recent first). |
### Result readers (native Windows via `resdata`)
| Tool | Purpose |
| ------------------------ | ------------------------------------------------------ |
| `list_summary_vectors` | All summary keys (FOPR, WBHP:*, GOPR:*, ...). |
| `read_summary` | Time series for one or more keys, with per-vector units + elapsed days, downsampled. |
| `field_summary` | Cumulative production/injection, FPR, final rates (with units). |
| `well_summary` | Per-well WOPR/WGPR/WWPR/WBHP/WOPT/etc. min/max/last. |
| `grid_info` | EGRID dims, active count, INIT property stats. |
| `restart_info` | UNRST report step list + keywords. |
| `compare_summaries` | Diff field totals between two cases. |
### Native plotting (matplotlib — no ResInsight/OpenGL)
Plots return MCP **image content**, so they render directly in the chat.
| Tool | Purpose |
| ------------------- | ------------------------------------------------------------------ |
| `plot_summary` | Summary vectors to PNG; **wildcards** (`WWCT:*`), auto twin-axis by unit, date or days x-axis. |
| `plot_property_map` | I–J areal map of a layer property — dynamic (PRESSURE/SWAT/SGAS/SOIL from UNRST) or static (PORO/PERMX/NTG from INIT). |
### Reservoir-engineering analytics
| Tool | Purpose |
| -------------------------- | ------------------------------------------------------------------- |
| `material_balance_check` | OOIP, recovery factor, **exact** oil mass-balance drift, surface VRR. |
| `flow_convergence_report` | Parse INFOSTEP/INFOITER: wall time by stage, wasted (cut) steps, worst timesteps, residuals. |
| `restart_property` | Per-layer stats (PV-weighted, also on grids with inactive cells) for PRESSURE/SWAT/SGAS/SOIL at a report step, plus histogram. |
| `compare_to_observed` | History-match QC: RMSE/bias/MAE/NRMSE of sim vs an observations CSV, with overlay plot. |
| `rft_data` | RFT pressure-vs-depth surveys per well/date (needs `WRFTPLT` in the deck). |
> For PVT, SCAL/relative-permeability fitting and deck-physics validation, this
> server defers to the **[pyrestoolbox MCP](https://github.com/whitsonAS)** if
> you have it connected, rather than duplicating those calculators.
### Deck editing (sensitivities / history matching)
| Tool | Purpose |
| ------------ | ---------------------------------------------------------------------------- |
| `clone_deck` | Write a NEW deck with safe overrides (`append_before_end`, `replace_regex`, `set_title`, `insert_after_keyword`), reporting per-op substitution counts and re-validating. Enables clone → run → compare loops. |
### ResInsight automation (CLI command files)
| Tool | Purpose |
| --------------------- | ---------------------------------------------------------------- |
| `resinsight_snapshot` | Open a case, render the default 3D view, save PNG. |
| `resinsight_well_log` | Export an LAS well log for one well. |
| `resinsight_open` | Launch interactive ResInsight (uses WSLg on Windows 11). |
If ResInsight fails with `MESA: error: ZINK ...`, pass
`force_software_gl: true` or install `xvfb` and `libgl1-mesa-dri` in WSL.
### OPM utility binaries
| Tool | Purpose |
| -------------------- | ------------------------------------------------------------- |
| `convert_ecl` | Toggle formatted/unformatted ECL output files. |
| `compare_ecl` | Diff two result files within tolerances. |
| `make_restart_deck` | History→prediction deck via `rst_deck` (auto-adds `UNIFIN` for unified restarts). |
| `pack_deck` | Flatten a deck + INCLUDEs into one file (`opmpack`). |
| `hash_deck` | Keyword-level deterministic hash (`opmhash`). |
| `summary_tool` | Text summary table via the OPM `summary` CLI. |
| `upscaling_status` | Detect whether `opm-upscaling` is installed in WSL. |
| `run_upscaling` | Generic passthrough to `upscale_perm`, `upscale_relperm`, ... |
## Bigger test cases
This repo only ships the tiny `examples/SPE1_MINI.DATA`. For real benchmark
decks (Norne, SPE9, SPE10, Equinor gas-lift demos, etc.) use the upstream
[OPM/opm-tests](https://github.com/OPM/opm-tests) repository — they're
distributed under the Open Database License (ODbL 1.0) and shouldn't be
re-vendored here. Clone alongside:
```powershell
git clone https://github.com/OPM/opm-tests.git C:\models\opm-tests
```
Then point the MCP tools at any deck under that tree, e.g.
`C:\models\opm-tests\norne\NORNE_ATW2013.DATA`.
## Smoke test
```powershell
# 1. Validate a small example deck
py -c "from opm_mcp.tools import deck; import json; print(json.dumps(deck.summarize_deck(r'C:\Users\ogezi\RCI\opm-mcp\examples\SPE1_MINI.DATA'), indent=2))"
# 2. Run Flow on it (completes in a couple of seconds)
py -c "from opm_mcp.tools import flow; import time, json; j = flow.run_flow(r'C:\Users\ogezi\RCI\opm-mcp\examples\SPE1_MINI.DATA'); print(j['job_id']);
import time
time.sleep(5)
print(json.dumps(flow.flow_status(j['job_id']), indent=2))"
# 3. Read summary vectors
py -c "from opm_mcp.tools import results as r; import json; print(json.dumps(r.field_summary(r'C:\Users\ogezi\.opm-mcp\jobs\<JOB_ID>\out\SPE1_MINI'), indent=2))"
```
## Project layout
```
opm-mcp/
pyproject.toml
README.md
examples/
SPE1_MINI.DATA
src/opm_mcp/
__init__.py
config.py # env-var driven settings
wsl.py # WSL bridge + path translation (wslpath-aware)
jobs.py # background flow-job tracker (restart-safe)
server.py # FastMCP entry point (`opm-mcp` script)
tools/
deck.py # validate / summarise / wells
flow.py # run, status, log_tail, cancel, jobs
results.py # resdata-backed summary/grid/restart readers (units + cache)
plots.py # native matplotlib plot_summary
analysis.py # material balance, convergence report, restart property
edit.py # clone_deck with safe overrides
resinsight.py # snapshot, well log, interactive open
utils.py # opmpack, opmhash, summary, compareECL, upscaling
helpers/
deck_inspect.py # runs inside WSL — uses python3-opm-common
```
## Notes
- **Path handling.** Every path argument can be Windows-style (`C:\\foo`) or
WSL-style (`/mnt/c/foo`, `/home/...`); the server normalises both directions.
Result-file tools accept either the bare case name (`SPE1_MINI`) or any of
its known extensions (`.DATA`, `.SMSPEC`, `.EGRID`, `.UNRST`).
- **Background simulations.** `run_flow` returns immediately and writes its
Linux PID + exit code under `OPM_MCP_WORK_DIR/jobs/<id>/`. Jobs are
**rehydrated on server restart**, so you can still poll (and cancel, via
`wsl kill`) a run that was started before Claude Desktop restarted. Runs also
emit INFOSTEP/INFOITER by default for `flow_convergence_report`.
- **Optional upscaling.** The `opm-upscaling` Debian package is *not* a
dependency of Flow; `upscaling_status` tells Claude whether it's available
and prints the install command if not.
## License
[MIT](LICENSE) — see [LICENSE](LICENSE) for the full text.
## Acknowledgements
- [Open Porous Media](https://opm-project.org/) — the underlying reservoir-simulation stack.
- [OPM/opm-simulators](https://github.com/OPM/opm-simulators) — OPM Flow, the
reservoir simulator (and its Python bindings) this server drives.
- [OPM/opm-common](https://github.com/OPM/opm-common) — deck parsing, ECL I/O,
and the utility binaries (`opmpack`, `convertECL`, `summary`, …).
- [Equinor `resdata`](https://github.com/equinor/resdata) — open-source ECL result-file reader.
- [Model Context Protocol](https://modelcontextprotocol.io/) and the
[official Python SDK](https://github.com/modelcontextprotocol/python-sdk).
TDQS
Scored across 40 tools
Most tools have a clear resource-action separation: deck tools, flow job tools, summary tools, and ResInsight tools are easy to tell apart. The main ambiguity is summary_tool vs read_summary/list_summary_vectors, and the cluster of result-reading/plotting tools could occasionally be confused.
There are strong consistent clusters like flow_*, list_*, plot_*, compare_*, and resinsight_*, but the overall naming is mixed: validate_deck/summarize_deck/deck_text/deck_grep place deck inconsistently, and summary_tool/flow_convergence_report break the verb_noun pattern. The style is readable but not fully predictable.
At 40 tools, the surface is well beyond the 25+ threshold for a heavy MCP server, even though the domain spans deck prep, simulation control, results analysis, plotting, restart, and upscaling. Many tools are individually useful, but the overall count creates a steep learning curve and likely too broad an API surface.
The set covers the core OPM workflow well: environment checks, path translation, deck validation/editing, simulation launch and monitoring, summary/grid/RFT access, history-match comparison, restart deck generation, and upscaling. Minor gaps include no direct case deletion/rename, limited schedule editing beyond clone_deck overrides, and no explicit cleanup/management of generated files or jobs.