pyrestoolbox 3.7.0__tar.gz → 3.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/PKG-INFO +1 -1
- pyrestoolbox-3.7.1/mcp/DEPLOYMENT.md +53 -0
- pyrestoolbox-3.7.1/mcp/Dockerfile +23 -0
- pyrestoolbox-3.7.1/mcp/README.md +87 -0
- pyrestoolbox-3.7.1/mcp/pyproject.toml +26 -0
- pyrestoolbox-3.7.1/mcp/pyrestoolbox_mcp/__init__.py +3 -0
- pyrestoolbox-3.7.1/mcp/pyrestoolbox_mcp/serialize.py +50 -0
- pyrestoolbox-3.7.1/mcp/pyrestoolbox_mcp/server.py +295 -0
- pyrestoolbox-3.7.1/mcp/tests/test_server.py +183 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyproject.toml +1 -1
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/__init__.py +23 -2
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/brine/_lib_vle_engine.py +98 -22
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/brine/brine.py +11 -12
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/brine.rst +3 -3
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/changelist.rst +4 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/gas.rst +9 -3
- pyrestoolbox-3.7.1/pyrestoolbox/docs/index.rst +62 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/layer.rst +2 -2
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/nodal.rst +9 -6
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/oil.rst +7 -8
- pyrestoolbox-3.7.1/pyrestoolbox/docs/plyasunov.rst +230 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/simtools.rst +7 -3
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/gas/gas.py +18 -28
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/layer/layer.py +1 -1
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/nodal/nodal.py +2 -2
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/plyasunov/plyasunov_model.py +9 -2
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/zfactor/mod.rs +2 -33
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/.github/workflows/build-wheels.yml +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/.github/workflows/tests.yml +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/.gitignore +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/Cargo.lock +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/Cargo.toml +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/LICENSE +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/MANIFEST.in +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/README.rst +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/ResToolbox/privacy_policy.md +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/benchmark_rust_vs_python.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/build_pure_python.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/_accelerator.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/brine/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/classes/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/classes/classes.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/constants/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/constants/constants.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/dca/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/dca/dca.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/dca.rst +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/examples.ipynb +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/img/bot.png +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/img/bot_img.png +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/img/dry_gas.png +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/img/influence.png +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/img/properties_df.png +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/img/sgof.png +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/img/swof.png +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/library.rst +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/matbal.rst +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/nodal_examples.ipynb +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/nodal_hydrate_demo.ipynb +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/recommend.rst +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/docs/sensitivity.rst +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/gas/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/gas/_hydrate.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/layer/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/library/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/library/component_library.xlsx +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/library/library.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/matbal/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/matbal/matbal.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/nodal/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/oil/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/oil/_compressibility.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/oil/_constants.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/oil/_correlations.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/oil/_density.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/oil/_harmonize.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/oil/_pvt_class.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/oil/_rate.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/oil/_separator.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/oil/_tables.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/oil/_utils.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/plyasunov/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/plyasunov/iapws_if97.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/plyasunov/water_properties.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/recommend/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/recommend/recommend.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/sensitivity/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/sensitivity/sensitivity.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/shared_fns/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/simtools/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/simtools/_aquifer.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/simtools/_decks.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/simtools/_pvt_tables.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/simtools/_relperm.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/simtools/_vfp.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/simtools/simtools.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/validate/__init__.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/pyrestoolbox/validate/validate.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/setup.cfg +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/setup.py +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/bessel.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/critical_properties/mod.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/dca/hyperbolic.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/dca/mod.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/dca/ransac.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/gas_viscosity/mod.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/gwr.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/lib.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/matbal/mod.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/matbal/objective.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/oil/density.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/oil/mod.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/pseudopressure.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/spycher_pruess/mod.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/spycher_pruess/solubility.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vle/alpha.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vle/bip.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vle/components.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vle/eos.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vle/flash.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vle/fugacity.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vle/k_init.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vle/mod.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vle/rachford_rice.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vlp/constants.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vlp/friction.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vlp/holdup_bb.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vlp/holdup_gray.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vlp/holdup_wg.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vlp/ift.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vlp/march.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vlp/mod.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vlp/pvt_helpers.rs +0 -0
- {pyrestoolbox-3.7.0 → pyrestoolbox-3.7.1}/src/vlp/static_column.rs +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# pyrestoolbox-mcp: deployment note for the Mawson platform team
|
|
2
|
+
|
|
3
|
+
## What this is
|
|
4
|
+
|
|
5
|
+
An MCP (Model Context Protocol) server exposing pyResToolbox, a petroleum
|
|
6
|
+
engineering calculation library (gas/oil/brine PVT, nodal analysis, decline
|
|
7
|
+
curve analysis, material balance). Source: https://github.com/mwburgoyne/pyResToolbox
|
|
8
|
+
(server code in `mcp/`). Contact: Mark Burgoyne.
|
|
9
|
+
|
|
10
|
+
It gives an LLM seven tools: three discovery/dispatch tools that reach the
|
|
11
|
+
library's full public API (about 125 functions), a correlation-method
|
|
12
|
+
recommender, and three one-shot calculation wrappers. Module documentation
|
|
13
|
+
with worked examples is served as MCP resources. Wrong inputs return errors
|
|
14
|
+
that list the valid options, so the model self-corrects without help.
|
|
15
|
+
|
|
16
|
+
## Security properties
|
|
17
|
+
|
|
18
|
+
- Pure computation. The server makes no outbound network calls, reads no
|
|
19
|
+
external data sources, and writes nothing to disk. Requests contain only
|
|
20
|
+
numeric/string calculation inputs; responses are JSON numbers and text.
|
|
21
|
+
- No credentials, no state, no user data. Nothing is retained between
|
|
22
|
+
requests.
|
|
23
|
+
- Licence: GPL-3.0. Internal use is not distribution, so no obligations are
|
|
24
|
+
triggered, and the source is public in any case.
|
|
25
|
+
|
|
26
|
+
## How to deploy
|
|
27
|
+
|
|
28
|
+
1. Build the container from the repo's `mcp/` directory (Dockerfile
|
|
29
|
+
included; pyresToolbox version is pinned via a build arg). No base-image
|
|
30
|
+
requirements beyond python:3.11-slim.
|
|
31
|
+
2. Run it anywhere inside the Santos network. It listens on port 8000 and
|
|
32
|
+
serves MCP streamable HTTP at `/mcp`. It is stateless: replicas can sit
|
|
33
|
+
behind a load balancer without session affinity.
|
|
34
|
+
3. Put your standard API gateway in front for authentication. The server
|
|
35
|
+
itself does not authenticate; it expects to live behind the gateway.
|
|
36
|
+
4. Register the endpoint URL with Mawson as a remote MCP server.
|
|
37
|
+
|
|
38
|
+
If Mawson does not support remote MCP servers and needs an OpenAPI tool
|
|
39
|
+
spec instead, tell us; the tool layer is plain typed Python and a REST
|
|
40
|
+
wrapper is straightforward.
|
|
41
|
+
|
|
42
|
+
## Sizing
|
|
43
|
+
|
|
44
|
+
Single-digit milliseconds to low seconds per calculation, CPU-bound, modest
|
|
45
|
+
memory (numpy/scipy stack, roughly 500 MB resident). One small container is
|
|
46
|
+
enough to start; scale horizontally if usage grows.
|
|
47
|
+
|
|
48
|
+
## Updates
|
|
49
|
+
|
|
50
|
+
Version bumps are deliberate: rebuild the image with a new
|
|
51
|
+
`PYRESTOOLBOX_VERSION` build arg after the library publishes a release.
|
|
52
|
+
The calculation library is regression-tested (900+ tests, frozen numerical
|
|
53
|
+
baselines, documentation examples verified in CI).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# pyrestoolbox-mcp: remote MCP server (streamable HTTP)
|
|
2
|
+
# Build from the mcp/ directory: docker build -t pyrestoolbox-mcp .
|
|
3
|
+
# Run: docker run -p 8000:8000 pyrestoolbox-mcp
|
|
4
|
+
# MCP endpoint: http://<host>:8000/mcp
|
|
5
|
+
|
|
6
|
+
FROM python:3.11-slim
|
|
7
|
+
|
|
8
|
+
# Pin the library version for reproducible corporate deployments; bump
|
|
9
|
+
# deliberately and rebuild.
|
|
10
|
+
ARG PYRESTOOLBOX_VERSION=3.7.0
|
|
11
|
+
|
|
12
|
+
WORKDIR /app
|
|
13
|
+
COPY . /app/mcp
|
|
14
|
+
|
|
15
|
+
RUN pip install --no-cache-dir "pyrestoolbox==${PYRESTOOLBOX_VERSION}" /app/mcp
|
|
16
|
+
|
|
17
|
+
# Non-root runtime user
|
|
18
|
+
RUN useradd --create-home --shell /usr/sbin/nologin mcp
|
|
19
|
+
USER mcp
|
|
20
|
+
|
|
21
|
+
EXPOSE 8000
|
|
22
|
+
|
|
23
|
+
CMD ["pyrestoolbox-mcp", "--transport", "streamable-http", "--host", "0.0.0.0", "--port", "8000"]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# pyrestoolbox-mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server exposing the
|
|
4
|
+
[pyrestoolbox](https://github.com/mwburgoyne/pyResToolbox) reservoir
|
|
5
|
+
engineering library to agents without a Python execution environment
|
|
6
|
+
(Claude Desktop, claude.ai connectors, other MCP hosts).
|
|
7
|
+
|
|
8
|
+
If your agent CAN run Python, prefer `pip install pyrestoolbox` directly -
|
|
9
|
+
the library ships its full documentation inside the wheel
|
|
10
|
+
(`pyrestoolbox.docs_dir()`), and native code execution is more flexible than
|
|
11
|
+
any tool surface.
|
|
12
|
+
|
|
13
|
+
## Design
|
|
14
|
+
|
|
15
|
+
A small meta-tool surface instead of one MCP tool per library function:
|
|
16
|
+
|
|
17
|
+
| Tool | Purpose |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `list_functions(module?)` | Module map, or public functions of one module |
|
|
20
|
+
| `describe(name)` | Signature + docstring for `module.function` |
|
|
21
|
+
| `call(name, arguments)` | Invoke any public function with keyword args |
|
|
22
|
+
| `recommend_methods(...)` | Correlation-method recommendations with rationale |
|
|
23
|
+
| `co2_brine_props(...)` | One-shot CO2-saturated brine properties |
|
|
24
|
+
| `sw_brine_props(...)` | One-shot multicomponent gas-saturated brine properties |
|
|
25
|
+
| `fit_and_forecast(...)` | Fit a decline model and forecast in one call |
|
|
26
|
+
|
|
27
|
+
Every module's RST documentation (shipped in the pyrestoolbox wheel) is
|
|
28
|
+
exposed as an MCP resource: `docs://gas`, `docs://oil`, `docs://index`, ...
|
|
29
|
+
|
|
30
|
+
Correlation methods are passed as strings (`"zmethod": "DAK"`); an invalid
|
|
31
|
+
method string returns an error listing the valid options, so agents
|
|
32
|
+
self-correct in one round trip. Results are JSON: numpy arrays become lists,
|
|
33
|
+
DataFrames become `{columns, records}`, non-finite floats become null.
|
|
34
|
+
|
|
35
|
+
## Install and configure
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
pip install pyrestoolbox-mcp # once published; or: pip install ./mcp
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Claude Code:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
claude mcp add pyrestoolbox -- pyrestoolbox-mcp
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Claude Desktop (`claude_desktop_config.json`):
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"pyrestoolbox": {
|
|
53
|
+
"command": "pyrestoolbox-mcp"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Remote deployment (enterprise LLM platforms)
|
|
60
|
+
|
|
61
|
+
The server also runs as a remote MCP server over streamable HTTP:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
pyrestoolbox-mcp --transport streamable-http --host 0.0.0.0 --port 8000
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
It is stateless (no session affinity needed behind a load balancer) and
|
|
68
|
+
serves the MCP endpoint at `/mcp`. A `Dockerfile` is included; put an API
|
|
69
|
+
gateway in front for authentication. See `DEPLOYMENT.md` for the platform
|
|
70
|
+
team one-pager.
|
|
71
|
+
|
|
72
|
+
## Tests
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
PYTHONPATH=<repo>:<repo>/mcp python3 -m pytest mcp/tests/ -q
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Notes
|
|
79
|
+
|
|
80
|
+
- Units default to oilfield (psia, deg F, ft, mD, cP); pass `metric: true`
|
|
81
|
+
where the function supports it. The plyasunov module is SI (K, MPa).
|
|
82
|
+
- The oil module is scalar-only; gas and brine functions accept lists.
|
|
83
|
+
- Stateful classes (`GasPVT`, `CO2_Brine_Mixture`, `SoreideWhitson`,
|
|
84
|
+
`DeclineResult`) are not exposed through `call` - the one-shot wrapper
|
|
85
|
+
tools cover the common workflows.
|
|
86
|
+
- File-writing simtools functions keep their `export=False` defaults, so
|
|
87
|
+
calls return table text rather than touching disk.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyrestoolbox-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP server exposing the pyrestoolbox reservoir engineering library"
|
|
9
|
+
license = {text = "GPL-3.0-or-later"}
|
|
10
|
+
authors = [{name = "Mark W. Burgoyne", email = "mark.w.burgoyne@gmail.com"}]
|
|
11
|
+
keywords = ["mcp", "petroleum", "reservoir", "pyrestoolbox"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"mcp>=1.2",
|
|
15
|
+
"pyrestoolbox>=3.7",
|
|
16
|
+
]
|
|
17
|
+
readme = "README.md"
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/mwburgoyne/pyResToolbox"
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
pyrestoolbox-mcp = "pyrestoolbox_mcp.server:main"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
include = ["pyrestoolbox_mcp*"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Convert pyrestoolbox return values to JSON-serializable structures."""
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import math
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def to_jsonable(obj):
|
|
9
|
+
"""Recursively convert obj to JSON-serializable Python primitives.
|
|
10
|
+
|
|
11
|
+
numpy scalars/arrays, pandas DataFrames/Series, dataclasses, Enums and
|
|
12
|
+
nested containers are handled. Non-finite floats become None (JSON has
|
|
13
|
+
no NaN/inf).
|
|
14
|
+
"""
|
|
15
|
+
if obj is None or isinstance(obj, (bool, str)):
|
|
16
|
+
return obj
|
|
17
|
+
if isinstance(obj, int):
|
|
18
|
+
return int(obj)
|
|
19
|
+
if isinstance(obj, float): # np.float64 subclasses float - cast to plain float
|
|
20
|
+
return float(obj) if math.isfinite(obj) else None
|
|
21
|
+
if isinstance(obj, Enum):
|
|
22
|
+
return obj.name
|
|
23
|
+
if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
|
|
24
|
+
return {f.name: to_jsonable(getattr(obj, f.name)) for f in dataclasses.fields(obj)}
|
|
25
|
+
# numpy
|
|
26
|
+
try:
|
|
27
|
+
import numpy as np
|
|
28
|
+
if isinstance(obj, np.generic):
|
|
29
|
+
return to_jsonable(obj.item())
|
|
30
|
+
if isinstance(obj, np.ndarray):
|
|
31
|
+
return [to_jsonable(v) for v in obj.tolist()]
|
|
32
|
+
except ImportError:
|
|
33
|
+
pass
|
|
34
|
+
# pandas
|
|
35
|
+
try:
|
|
36
|
+
import pandas as pd
|
|
37
|
+
if isinstance(obj, pd.DataFrame):
|
|
38
|
+
return {
|
|
39
|
+
"columns": [str(c) for c in obj.columns],
|
|
40
|
+
"records": to_jsonable(obj.to_dict(orient="records")),
|
|
41
|
+
}
|
|
42
|
+
if isinstance(obj, pd.Series):
|
|
43
|
+
return to_jsonable(obj.tolist())
|
|
44
|
+
except ImportError:
|
|
45
|
+
pass
|
|
46
|
+
if isinstance(obj, dict):
|
|
47
|
+
return {str(k): to_jsonable(v) for k, v in obj.items()}
|
|
48
|
+
if isinstance(obj, (list, tuple, set)):
|
|
49
|
+
return [to_jsonable(v) for v in obj]
|
|
50
|
+
return str(obj)
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""MCP server exposing the pyrestoolbox reservoir engineering library.
|
|
2
|
+
|
|
3
|
+
Meta-tool design: rather than one MCP tool per library function (125+), a
|
|
4
|
+
small set of tools lets an agent discover and call the whole public API:
|
|
5
|
+
|
|
6
|
+
list_functions - module map, or the public functions of one module
|
|
7
|
+
describe - signature + docstring for module.function
|
|
8
|
+
call - invoke any public function with keyword arguments
|
|
9
|
+
recommend_methods - structured correlation-method recommendations
|
|
10
|
+
co2_brine_props / sw_brine_props / fit_and_forecast - one-shot wrappers
|
|
11
|
+
around the stateful classes
|
|
12
|
+
|
|
13
|
+
The full RST documentation shipped inside the pyrestoolbox wheel is exposed
|
|
14
|
+
as MCP resources (docs://gas, docs://oil, ...).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import importlib
|
|
18
|
+
import inspect
|
|
19
|
+
|
|
20
|
+
from mcp.server.fastmcp import FastMCP
|
|
21
|
+
|
|
22
|
+
from pyrestoolbox_mcp.serialize import to_jsonable
|
|
23
|
+
|
|
24
|
+
# Modules whose public functions are callable through this server.
|
|
25
|
+
ALLOWED_MODULES = {
|
|
26
|
+
"gas": "Gas PVT (Z-factor, viscosity, Bg, Cg, density, pseudopressure), flow rates, pseudoskins, hydrates",
|
|
27
|
+
"oil": "Oil PVT (Pb, Rs, Bo, density, viscosity, compressibility), flow rates, harmonization (scalar inputs only)",
|
|
28
|
+
"brine": "Brine properties: CH4-saturated, CO2-saturated, multicomponent gas-saturated",
|
|
29
|
+
"plyasunov": "Infinite-dilution partial molar volumes of dissolved gases in water (SI units: K, MPa, cm3/mol)",
|
|
30
|
+
"nodal": "VLP/IPR nodal analysis: fbhp, fthp, outflow_curve, ipr_curve, operating_point",
|
|
31
|
+
"dca": "Decline curve analysis: Arps, Duong, modified hyperbolic, two-segment hyperbolic, fitting, EUR",
|
|
32
|
+
"matbal": "Material balance: P/Z gas and Havlena-Odeh oil",
|
|
33
|
+
"simtools": "Simulation helpers: rel-perm tables, aquifer influence, VFP/PVT tables, deck checking",
|
|
34
|
+
"layer": "Lorenz heterogeneity and permeability layering",
|
|
35
|
+
"library": "Component critical property database",
|
|
36
|
+
"recommend": "Correlation method recommendation engine",
|
|
37
|
+
"sensitivity": "Parameter sweeps and tornado analysis",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
mcp = FastMCP(
|
|
41
|
+
"pyrestoolbox",
|
|
42
|
+
instructions=(
|
|
43
|
+
"Petroleum/reservoir engineering calculations from the pyrestoolbox "
|
|
44
|
+
"library. Start with list_functions() for the module map, "
|
|
45
|
+
"describe('module.function') before calling anything, and "
|
|
46
|
+
"recommend_methods(...) if unsure which correlation method suits a "
|
|
47
|
+
"fluid. All inputs default to oilfield units (psia, deg F, ft, mD, "
|
|
48
|
+
"cP); pass metric=True for Eclipse METRIC units where supported. "
|
|
49
|
+
"Full module documentation is available as docs:// resources."
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _get_module(module: str):
|
|
55
|
+
if module not in ALLOWED_MODULES:
|
|
56
|
+
raise ValueError(f"Unknown module: {module!r}. Available: {sorted(ALLOWED_MODULES)}")
|
|
57
|
+
return importlib.import_module(f"pyrestoolbox.{module}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _public_names(mod):
|
|
61
|
+
names = getattr(mod, "__all__", None)
|
|
62
|
+
if names is None:
|
|
63
|
+
names = [n for n in dir(mod) if not n.startswith("_")]
|
|
64
|
+
return [n for n in names if not n.startswith("_")]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _resolve(name: str):
|
|
68
|
+
"""Resolve 'module.function' to (module, attribute)."""
|
|
69
|
+
if "." not in name:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"Use 'module.function' form, e.g. 'gas.gas_z'. Got: {name!r}. "
|
|
72
|
+
f"Modules: {sorted(ALLOWED_MODULES)}"
|
|
73
|
+
)
|
|
74
|
+
module, _, attr = name.partition(".")
|
|
75
|
+
mod = _get_module(module)
|
|
76
|
+
if attr.startswith("_") or attr not in _public_names(mod):
|
|
77
|
+
raise ValueError(f"{attr!r} is not a public name in pyrestoolbox.{module}")
|
|
78
|
+
return mod, getattr(mod, attr)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@mcp.tool()
|
|
82
|
+
def list_functions(module: str = "") -> dict:
|
|
83
|
+
"""List pyrestoolbox modules, or the public functions of one module.
|
|
84
|
+
|
|
85
|
+
With no argument, returns {module: description}. With a module name
|
|
86
|
+
(e.g. 'gas'), returns {function: one-line summary} for every public
|
|
87
|
+
function and class in that module.
|
|
88
|
+
"""
|
|
89
|
+
if not module:
|
|
90
|
+
return dict(ALLOWED_MODULES)
|
|
91
|
+
mod = _get_module(module)
|
|
92
|
+
out = {}
|
|
93
|
+
for name in _public_names(mod):
|
|
94
|
+
obj = getattr(mod, name, None)
|
|
95
|
+
if callable(obj):
|
|
96
|
+
doc = inspect.getdoc(obj) or ""
|
|
97
|
+
out[name] = doc.split("\n", 1)[0].strip()
|
|
98
|
+
return out
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@mcp.tool()
|
|
102
|
+
def describe(name: str) -> str:
|
|
103
|
+
"""Signature and full docstring for a public function or class.
|
|
104
|
+
|
|
105
|
+
name is 'module.function', e.g. 'gas.gas_z' or 'dca.fit_decline'.
|
|
106
|
+
Also see the docs://<module> resources for worked examples.
|
|
107
|
+
"""
|
|
108
|
+
_, obj = _resolve(name)
|
|
109
|
+
target = obj.__init__ if inspect.isclass(obj) else obj
|
|
110
|
+
try:
|
|
111
|
+
raw = inspect.signature(target)
|
|
112
|
+
# Strip type annotations - docstrings carry the units/types, and
|
|
113
|
+
# numpy typing unions are unreadable noise in a tool result.
|
|
114
|
+
params = [p.replace(annotation=inspect.Parameter.empty)
|
|
115
|
+
for p in raw.parameters.values() if p.name != "self"]
|
|
116
|
+
sig = str(raw.replace(parameters=params,
|
|
117
|
+
return_annotation=inspect.Signature.empty))
|
|
118
|
+
except (TypeError, ValueError):
|
|
119
|
+
sig = "(...)"
|
|
120
|
+
kind = "class" if inspect.isclass(obj) else "function"
|
|
121
|
+
doc = inspect.getdoc(obj) or "(no docstring)"
|
|
122
|
+
return f"{kind} {name}{sig}\n\n{doc}"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@mcp.tool()
|
|
126
|
+
def call(name: str, arguments: dict = {}) -> dict:
|
|
127
|
+
"""Call any public pyrestoolbox function with keyword arguments.
|
|
128
|
+
|
|
129
|
+
name is 'module.function', e.g. 'gas.gas_z'. arguments is a JSON object
|
|
130
|
+
of keyword arguments, e.g. {"p": 2000, "sg": 0.75, "degf": 200}.
|
|
131
|
+
Correlation methods are strings (e.g. "zmethod": "DAK"); an invalid
|
|
132
|
+
method string returns an error listing the valid options. Gas and brine
|
|
133
|
+
functions accept lists for pressure-like inputs; oil functions are
|
|
134
|
+
scalar-only. Classes cannot be called here - use the one-shot wrapper
|
|
135
|
+
tools (co2_brine_props, sw_brine_props, fit_and_forecast) instead.
|
|
136
|
+
"""
|
|
137
|
+
_, obj = _resolve(name)
|
|
138
|
+
if inspect.isclass(obj):
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"{name} is a class. Use describe({name!r}) to inspect it, or a "
|
|
141
|
+
"one-shot wrapper tool (co2_brine_props, sw_brine_props, "
|
|
142
|
+
"fit_and_forecast) if one covers it."
|
|
143
|
+
)
|
|
144
|
+
if not callable(obj):
|
|
145
|
+
raise ValueError(f"{name} is not callable (it is {type(obj).__name__})")
|
|
146
|
+
result = obj(**arguments)
|
|
147
|
+
return {"result": to_jsonable(result)}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@mcp.tool()
|
|
151
|
+
def recommend_methods(sg: float = 0.65, co2: float = 0, h2s: float = 0,
|
|
152
|
+
n2: float = 0, h2: float = 0, api: float | None = None,
|
|
153
|
+
deviation: float = 0, well_type: str = "gas") -> dict:
|
|
154
|
+
"""Recommend correlation methods for a fluid/well, with rationale.
|
|
155
|
+
|
|
156
|
+
Composition fractions are molar. api: stock tank oil gravity (include to
|
|
157
|
+
get oil PVT method recommendations). deviation: max wellbore deviation
|
|
158
|
+
from vertical (degrees). well_type: 'gas' or 'oil'. Returns one
|
|
159
|
+
recommendation per category (zmethod, cmethod, vlp_method, ...), each
|
|
160
|
+
with the recommended method, rationale, alternatives and whether the
|
|
161
|
+
choice is mandatory.
|
|
162
|
+
"""
|
|
163
|
+
from pyrestoolbox import recommend
|
|
164
|
+
recs = recommend.recommend_methods(sg=sg, co2=co2, h2s=h2s, n2=n2, h2=h2,
|
|
165
|
+
api=api, deviation=deviation,
|
|
166
|
+
well_type=well_type)
|
|
167
|
+
return to_jsonable(recs)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@mcp.tool()
|
|
171
|
+
def co2_brine_props(pres: float, temp: float, ppm: float = 0,
|
|
172
|
+
cw_sat: bool = False, metric: bool = False) -> dict:
|
|
173
|
+
"""CO2-saturated brine properties (Spycher-Pruess mutual solubility).
|
|
174
|
+
|
|
175
|
+
pres: pressure (psia, or barsa if metric=True). temp: temperature
|
|
176
|
+
(deg F, or deg C if metric=True). ppm: NaCl-equivalent salinity (weight
|
|
177
|
+
salt per million weight of brine). cw_sat=True also computes the
|
|
178
|
+
saturated pseudo-compressibility. Returns aqueous/vapor compositions,
|
|
179
|
+
gas density (g/cm3), brine density/viscosity/Bw triplets
|
|
180
|
+
[CO2-saturated, pure brine, freshwater], Rs and compressibilities.
|
|
181
|
+
"""
|
|
182
|
+
from pyrestoolbox import brine
|
|
183
|
+
mix = brine.CO2_Brine_Mixture(pres=pres, temp=temp, ppm=ppm,
|
|
184
|
+
cw_sat=cw_sat, metric=metric)
|
|
185
|
+
keys = ("x", "y", "xSalt", "rhoGas", "bDen", "bVis", "bVisblty", "bw",
|
|
186
|
+
"Rs", "Cf_usat", "Cf_sat")
|
|
187
|
+
return {k: to_jsonable(getattr(mix, k, None)) for k in keys}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@mcp.tool()
|
|
191
|
+
def sw_brine_props(pres: float, temp: float, ppm: float = 0,
|
|
192
|
+
y_CO2: float = 0, y_H2S: float = 0, y_N2: float = 0,
|
|
193
|
+
y_H2: float = 0, sg: float = 0.65, cw_sat: bool = False,
|
|
194
|
+
metric: bool = False) -> dict:
|
|
195
|
+
"""Multicomponent gas-saturated brine properties (Soreide-Whitson VLE).
|
|
196
|
+
|
|
197
|
+
pres: pressure (psia, or barsa if metric=True). temp: temperature
|
|
198
|
+
(deg F, or deg C if metric=True). ppm: NaCl-equivalent salinity.
|
|
199
|
+
y_*: molar fractions of CO2/H2S/N2/H2 in the equilibrium gas; the
|
|
200
|
+
remainder is hydrocarbon gas of specific gravity sg. Returns aqueous
|
|
201
|
+
composition, brine density/viscosity triplets, Rs, water content and
|
|
202
|
+
compressibilities.
|
|
203
|
+
"""
|
|
204
|
+
from pyrestoolbox import brine
|
|
205
|
+
sw = brine.SoreideWhitson(pres=pres, temp=temp, ppm=ppm, y_CO2=y_CO2,
|
|
206
|
+
y_H2S=y_H2S, y_N2=y_N2, y_H2=y_H2, sg=sg,
|
|
207
|
+
cw_sat=cw_sat, metric=metric)
|
|
208
|
+
keys = ("x", "bDen", "bVis", "bw", "Rs", "Rs_total", "water_content",
|
|
209
|
+
"Cf_usat", "Cf_sat")
|
|
210
|
+
return {k: to_jsonable(getattr(sw, k, None)) for k in keys}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@mcp.tool()
|
|
214
|
+
def fit_and_forecast(t: list, q: list, t_end: float, method: str = "best",
|
|
215
|
+
dt: float = 1.0, q_min: float = 0.0) -> dict:
|
|
216
|
+
"""Fit a decline model to rate-time data and forecast to t_end.
|
|
217
|
+
|
|
218
|
+
t: time array. q: rate array (same length; any consistent units - the
|
|
219
|
+
dca module is unit-agnostic). method: 'best' (excludes 'mh'/'hyp2'),
|
|
220
|
+
'exponential', 'harmonic', 'hyperbolic', 'duong', 'mh' or 'hyp2'.
|
|
221
|
+
Forecast runs from the fitted model at dt spacing until t_end or until
|
|
222
|
+
rate falls to q_min. Returns fitted parameters (qi, di, b, ...),
|
|
223
|
+
r_squared, EUR and the forecast t/q/cumulative arrays.
|
|
224
|
+
"""
|
|
225
|
+
from pyrestoolbox import dca
|
|
226
|
+
fit = dca.fit_decline(t=t, q=q, method=method)
|
|
227
|
+
fc = dca.forecast(fit, t_end=t_end, dt=dt, q_min=q_min)
|
|
228
|
+
return {
|
|
229
|
+
"fit": {k: to_jsonable(getattr(fit, k)) for k in
|
|
230
|
+
("method", "qi", "di", "b", "a", "m", "dterm", "b2", "telf",
|
|
231
|
+
"r_squared")},
|
|
232
|
+
"forecast": {"t": to_jsonable(fc.t), "q": to_jsonable(fc.q),
|
|
233
|
+
"cum": to_jsonable(fc.Qcum), "eur": to_jsonable(fc.eur)},
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _register_doc_resources():
|
|
238
|
+
"""Expose every RST file shipped in the pyrestoolbox wheel as docs://<name>."""
|
|
239
|
+
import os
|
|
240
|
+
import pyrestoolbox
|
|
241
|
+
docs = pyrestoolbox.docs_dir()
|
|
242
|
+
for fname in sorted(os.listdir(docs)):
|
|
243
|
+
if not fname.endswith(".rst"):
|
|
244
|
+
continue
|
|
245
|
+
stem = fname[:-4]
|
|
246
|
+
path = os.path.join(docs, fname)
|
|
247
|
+
|
|
248
|
+
def _make_reader(p):
|
|
249
|
+
def _read() -> str:
|
|
250
|
+
with open(p, encoding="utf-8") as f:
|
|
251
|
+
return f.read()
|
|
252
|
+
return _read
|
|
253
|
+
|
|
254
|
+
mcp.resource(
|
|
255
|
+
f"docs://{stem}",
|
|
256
|
+
name=f"pyrestoolbox {stem} documentation",
|
|
257
|
+
description=f"RST documentation with worked examples: {stem}",
|
|
258
|
+
mime_type="text/x-rst",
|
|
259
|
+
)(_make_reader(path))
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
_register_doc_resources()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def build_parser():
|
|
266
|
+
import argparse
|
|
267
|
+
parser = argparse.ArgumentParser(
|
|
268
|
+
prog="pyrestoolbox-mcp",
|
|
269
|
+
description="MCP server exposing the pyrestoolbox library",
|
|
270
|
+
)
|
|
271
|
+
parser.add_argument("--transport", choices=["stdio", "streamable-http"],
|
|
272
|
+
default="stdio",
|
|
273
|
+
help="stdio for local hosts (default); streamable-http "
|
|
274
|
+
"for remote deployment (serves on /mcp)")
|
|
275
|
+
parser.add_argument("--host", default="127.0.0.1",
|
|
276
|
+
help="bind address for streamable-http (default "
|
|
277
|
+
"127.0.0.1; use 0.0.0.0 in a container)")
|
|
278
|
+
parser.add_argument("--port", type=int, default=8000,
|
|
279
|
+
help="port for streamable-http (default 8000)")
|
|
280
|
+
return parser
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def main():
|
|
284
|
+
args = build_parser().parse_args()
|
|
285
|
+
if args.transport == "streamable-http":
|
|
286
|
+
mcp.settings.host = args.host
|
|
287
|
+
mcp.settings.port = args.port
|
|
288
|
+
# Independent request handling; no server-side session affinity needed
|
|
289
|
+
# behind a load balancer.
|
|
290
|
+
mcp.settings.stateless_http = True
|
|
291
|
+
mcp.run(transport=args.transport)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
if __name__ == "__main__":
|
|
295
|
+
main()
|