paraview-mcp-python 0.1.0__tar.gz → 0.1.2__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.
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/PKG-INFO +211 -36
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/README.md +210 -35
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/bridge/execution.py +2 -78
- paraview_mcp_python-0.1.2/bridge/gui_bridge.py +55 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/bridge/models.py +2 -2
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/bridge/server.py +13 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/docs/architecture.md +29 -9
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/docs/python-execute-design.md +11 -7
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/pyproject.toml +2 -2
- paraview_mcp_python-0.1.2/scripts/start_paraview_gui_bridge.py +42 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/tests/test_command_handler.py +7 -6
- paraview_mcp_python-0.1.2/tests/test_gui_bridge.py +55 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/.gitignore +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/LICENSE +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/bridge/__init__.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/bridge/command_handler.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/scripts/library/color_by.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/scripts/library/create_contour.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/scripts/library/create_slice.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/scripts/library/open_dataset.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/scripts/library/reset_camera.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/scripts/library/save_screenshot.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/scripts/paraview_bridge_request.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/scripts/start_paraview_bridge.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/src/paraview_mcp_server/__init__.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/src/paraview_mcp_server/headless.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/src/paraview_mcp_server/server.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/tests/__init__.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/tests/test_bridge_server.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/tests/test_protocol.py +0 -0
- {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.2}/tests/test_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: paraview-mcp-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: MCP server for controlling ParaView via AI assistants
|
|
5
5
|
Project-URL: Homepage, https://github.com/djeada/paraview-mcp-server
|
|
6
6
|
Project-URL: Repository, https://github.com/djeada/paraview-mcp-server
|
|
@@ -31,50 +31,99 @@ Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
|
31
31
|
Requires-Dist: ruff>=0.11; extra == 'dev'
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
33
33
|
|
|
34
|
-
#
|
|
34
|
+
# ParaView MCP Server
|
|
35
35
|
|
|
36
36
|
**Control ParaView with AI assistants through the Model Context Protocol.**
|
|
37
37
|
|
|
38
|
-
`paraview-mcp-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
`paraview-mcp-python` provides an MCP server plus a ParaView-side bridge so
|
|
39
|
+
AI assistants such as Codex CLI and Claude Desktop can inspect a ParaView
|
|
40
|
+
session, open datasets, apply filters, color data, run ParaView Python, and
|
|
41
|
+
export screenshots.
|
|
42
|
+
|
|
43
|
+
The command installed for MCP clients is still:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
paraview-mcp-server
|
|
47
|
+
```
|
|
41
48
|
|
|
42
49
|
---
|
|
43
50
|
|
|
44
|
-
##
|
|
51
|
+
## What Parts Are There?
|
|
52
|
+
|
|
53
|
+
There are three moving pieces:
|
|
54
|
+
|
|
55
|
+
| Part | Runs where | Purpose |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| **MCP client** | Codex CLI, Claude Desktop, or another MCP host | Starts the MCP server and calls tools. |
|
|
58
|
+
| **MCP server** | Normal Python environment | Speaks MCP over stdio and forwards tool calls to ParaView over TCP. |
|
|
59
|
+
| **ParaView GUI bridge** | Already-open ParaView GUI process | Receives TCP JSON commands and executes `paraview.simple` operations in that live GUI session. |
|
|
60
|
+
|
|
61
|
+
The ParaView bridge is the ParaView-side component. It plays the same role as
|
|
62
|
+
the Blender add-on in `blender-mcp-server`: it must run inside the application
|
|
63
|
+
you want to control. For live GUI control, start the bridge from ParaView's
|
|
64
|
+
Python Shell using `scripts/start_paraview_gui_bridge.py`.
|
|
45
65
|
|
|
46
66
|
```
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
┌──────────────────────────────┐ stdio ┌────────────────────────┐
|
|
68
|
+
│ MCP Client │ ◄──────────────► │ MCP Server │
|
|
69
|
+
│ Codex / Claude / other host │ │ paraview-mcp-server │
|
|
70
|
+
└──────────────────────────────┘ └──────────┬─────────────┘
|
|
71
|
+
│ JSON/TCP
|
|
72
|
+
│ 127.0.0.1:9876
|
|
73
|
+
┌──────────▼─────────────┐
|
|
74
|
+
│ ParaView Bridge │
|
|
75
|
+
│ live GUI process │
|
|
76
|
+
└──────────┬─────────────┘
|
|
77
|
+
│
|
|
78
|
+
┌──────────▼─────────────┐
|
|
79
|
+
│ paraview.simple │
|
|
80
|
+
│ ParaView runtime │
|
|
81
|
+
└────────────────────────┘
|
|
54
82
|
```
|
|
55
83
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
Why a ParaView-side bridge? ParaView's useful automation API is
|
|
85
|
+
`paraview.simple`, and it must execute inside a ParaView Python runtime. The
|
|
86
|
+
MCP server itself is only a protocol adapter; it cannot modify a ParaView GUI
|
|
87
|
+
unless the bridge is running inside that GUI process.
|
|
88
|
+
|
|
89
|
+
For live GUI modification, use:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
Codex/Claude -> MCP server -> bridge inside open ParaView GUI -> live GUI session
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
For headless automation, use:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
Codex/Claude -> MCP server -> bridge inside pvpython -> headless ParaView runtime
|
|
99
|
+
```
|
|
63
100
|
|
|
64
101
|
See [`docs/architecture.md`](docs/architecture.md) for a full diagram, protocol reference,
|
|
65
102
|
and tool namespace table.
|
|
66
103
|
|
|
67
104
|
---
|
|
68
105
|
|
|
69
|
-
##
|
|
106
|
+
## Install
|
|
70
107
|
|
|
71
|
-
###
|
|
108
|
+
### Option A: Install the MCP server from PyPI
|
|
109
|
+
|
|
110
|
+
Use this when you only need the MCP server executable in your normal Python
|
|
111
|
+
environment:
|
|
72
112
|
|
|
73
113
|
```bash
|
|
74
114
|
pip install paraview-mcp-python
|
|
75
115
|
```
|
|
76
116
|
|
|
77
|
-
|
|
117
|
+
This installs:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
paraview-mcp-server
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Option B: Clone this repository for the ParaView bridge
|
|
124
|
+
|
|
125
|
+
The bridge code must be available to ParaView's Python runtime. For live GUI
|
|
126
|
+
control and local development, clone the repository:
|
|
78
127
|
|
|
79
128
|
```bash
|
|
80
129
|
git clone https://github.com/djeada/paraview-mcp-server.git
|
|
@@ -84,18 +133,112 @@ source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
|
84
133
|
pip install -e .
|
|
85
134
|
```
|
|
86
135
|
|
|
87
|
-
|
|
136
|
+
This creates:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
.venv/bin/paraview-mcp-server
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Start Everything
|
|
145
|
+
|
|
146
|
+
Start the pieces in this order.
|
|
147
|
+
|
|
148
|
+
### 1. Start the ParaView GUI Bridge
|
|
149
|
+
|
|
150
|
+
Open ParaView, then run the GUI bridge script from the Python Shell:
|
|
151
|
+
|
|
152
|
+
1. In ParaView, open **Tools -> Python Shell**.
|
|
153
|
+
2. Click **Run Script**.
|
|
154
|
+
3. Select:
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
/absolute/path/to/paraview-mcp-server/scripts/start_paraview_gui_bridge.py
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Expected output:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
ParaView MCP GUI bridge started on 127.0.0.1:9876
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If `paraview-mcp-python` is installed into ParaView's Python environment, you
|
|
167
|
+
can also start the live GUI bridge directly from the Python Shell:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from bridge.gui_bridge import start_gui_bridge, stop_gui_bridge
|
|
171
|
+
start_gui_bridge()
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Leave ParaView open. MCP commands now modify this live GUI session.
|
|
175
|
+
|
|
176
|
+
To stop the bridge from the ParaView Python Shell:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
stop_gui_bridge()
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 2. Optional: Start a Headless `pvpython` Bridge
|
|
88
183
|
|
|
89
|
-
|
|
184
|
+
Use this only when you do not need to modify an already-open ParaView GUI:
|
|
90
185
|
|
|
91
186
|
```bash
|
|
187
|
+
cd /path/to/paraview-mcp-server
|
|
92
188
|
pvpython scripts/start_paraview_bridge.py
|
|
93
|
-
# → ParaView bridge ready on 127.0.0.1:9876
|
|
94
189
|
```
|
|
95
190
|
|
|
96
|
-
|
|
191
|
+
Expected output:
|
|
192
|
+
|
|
193
|
+
```text
|
|
194
|
+
ParaView bridge ready on 127.0.0.1:9876
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Keep that terminal running. This controls the `pvpython` session, not a GUI
|
|
198
|
+
window opened separately.
|
|
199
|
+
|
|
200
|
+
### 3. Verify the Bridge Directly
|
|
201
|
+
|
|
202
|
+
Before involving an MCP client, send one raw bridge command:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
python scripts/paraview_bridge_request.py scene.get_info
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Expected response shape:
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"success": true,
|
|
213
|
+
"result": {
|
|
214
|
+
"source_count": 0,
|
|
215
|
+
"active_view_type": "RenderView"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
If this fails, fix the bridge before configuring Codex or Claude.
|
|
221
|
+
|
|
222
|
+
### 4. Register the MCP Server with Codex CLI
|
|
223
|
+
|
|
224
|
+
If you installed from PyPI:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
codex mcp add paraview -- paraview-mcp-server
|
|
228
|
+
codex mcp list
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
If you are using the local repository:
|
|
97
232
|
|
|
98
|
-
|
|
233
|
+
```bash
|
|
234
|
+
codex mcp add paraview -- /absolute/path/to/paraview-mcp-server/.venv/bin/paraview-mcp-server
|
|
235
|
+
codex mcp list
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Codex starts the MCP server automatically when needed. The ParaView bridge
|
|
239
|
+
must already be running separately.
|
|
240
|
+
|
|
241
|
+
### 5. Register with Claude Desktop
|
|
99
242
|
|
|
100
243
|
**Claude Desktop** — add to `claude_desktop_config.json`:
|
|
101
244
|
|
|
@@ -109,12 +252,42 @@ The bridge listens for JSON commands from the MCP server.
|
|
|
109
252
|
}
|
|
110
253
|
```
|
|
111
254
|
|
|
112
|
-
|
|
255
|
+
For a PyPI install, use the absolute path returned by:
|
|
113
256
|
|
|
114
257
|
```bash
|
|
115
|
-
|
|
258
|
+
which paraview-mcp-server
|
|
116
259
|
```
|
|
117
260
|
|
|
261
|
+
Restart Claude Desktop after editing the config.
|
|
262
|
+
|
|
263
|
+
### 6. Verify Through Your MCP Client
|
|
264
|
+
|
|
265
|
+
With the bridge still running, ask your MCP client:
|
|
266
|
+
|
|
267
|
+
```text
|
|
268
|
+
List all sources in the current ParaView session.
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The client should call `paraview_scene_list_sources` and return the current
|
|
272
|
+
ParaView pipeline sources from the live GUI session if you started
|
|
273
|
+
`start_paraview_gui_bridge.py`.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## What Can It Control?
|
|
278
|
+
|
|
279
|
+
There are two levels of control:
|
|
280
|
+
|
|
281
|
+
1. **Fixed MCP tools** for common workflows: scene inspection, loading data,
|
|
282
|
+
filters, display/coloring, camera, screenshots, data export, and animation
|
|
283
|
+
export.
|
|
284
|
+
2. **Python execution** through `paraview_python_exec`, which can run trusted
|
|
285
|
+
local Python inside the ParaView bridge session. Use this for anything not
|
|
286
|
+
covered by a fixed tool, including arbitrary `paraview.simple` scripts.
|
|
287
|
+
|
|
288
|
+
So the fixed tool list is intentionally finite, but the Python execution tool
|
|
289
|
+
is the general escape hatch for the broader ParaView API.
|
|
290
|
+
|
|
118
291
|
---
|
|
119
292
|
|
|
120
293
|
## Example prompts
|
|
@@ -259,14 +432,14 @@ Async jobs run in a separate headless `pvpython` process via `HeadlessPvpythonEx
|
|
|
259
432
|
|
|
260
433
|
---
|
|
261
434
|
|
|
262
|
-
##
|
|
435
|
+
## Python execution trust model
|
|
263
436
|
|
|
264
|
-
- **
|
|
265
|
-
|
|
437
|
+
- **Trusted local execution** — `paraview_python_exec` can run arbitrary Python available to the active
|
|
438
|
+
ParaView Python process, including imports and full `paraview.simple` workflows.
|
|
266
439
|
- **Output bounding** — stdout/stderr capped at **50 KB**.
|
|
267
440
|
- **Cooperative timeout** — default 30 seconds per script execution.
|
|
268
441
|
- **Script path validation** — optionally restrict execution to approved root directories.
|
|
269
|
-
- The bridge runs inside
|
|
442
|
+
- The bridge runs inside ParaView's Python runtime with the same trust level as that local session.
|
|
270
443
|
- This is a local desktop automation tool — not a public API sandbox.
|
|
271
444
|
|
|
272
445
|
---
|
|
@@ -311,9 +484,11 @@ paraview-mcp-server/
|
|
|
311
484
|
├── bridge/
|
|
312
485
|
│ ├── __init__.py
|
|
313
486
|
│ ├── server.py # TCP socket bridge server
|
|
487
|
+
│ ├── gui_bridge.py # Non-blocking live GUI bridge lifecycle
|
|
314
488
|
│ ├── command_handler.py # Command registry + paraview.simple handlers (27 commands)
|
|
315
|
-
│ └── execution.py # python.execute helper
|
|
489
|
+
│ └── execution.py # trusted local python.execute helper
|
|
316
490
|
├── scripts/
|
|
491
|
+
│ ├── start_paraview_gui_bridge.py
|
|
317
492
|
│ ├── start_paraview_bridge.py
|
|
318
493
|
│ ├── paraview_bridge_request.py
|
|
319
494
|
│ └── library/ # Reusable pvpython snippets
|
|
@@ -329,7 +504,7 @@ paraview-mcp-server/
|
|
|
329
504
|
└── tests/
|
|
330
505
|
├── test_server.py # 31 tools, connection, headless, async jobs
|
|
331
506
|
├── test_protocol.py # Wire encoding, fake bridge integration
|
|
332
|
-
└── test_command_handler.py # All 27 handlers +
|
|
507
|
+
└── test_command_handler.py # All 27 handlers + execution controls
|
|
333
508
|
```
|
|
334
509
|
|
|
335
510
|
---
|
|
@@ -1,47 +1,96 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ParaView MCP Server
|
|
2
2
|
|
|
3
3
|
**Control ParaView with AI assistants through the Model Context Protocol.**
|
|
4
4
|
|
|
5
|
-
`paraview-mcp-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
`paraview-mcp-python` provides an MCP server plus a ParaView-side bridge so
|
|
6
|
+
AI assistants such as Codex CLI and Claude Desktop can inspect a ParaView
|
|
7
|
+
session, open datasets, apply filters, color data, run ParaView Python, and
|
|
8
|
+
export screenshots.
|
|
9
|
+
|
|
10
|
+
The command installed for MCP clients is still:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
paraview-mcp-server
|
|
14
|
+
```
|
|
8
15
|
|
|
9
16
|
---
|
|
10
17
|
|
|
11
|
-
##
|
|
18
|
+
## What Parts Are There?
|
|
19
|
+
|
|
20
|
+
There are three moving pieces:
|
|
21
|
+
|
|
22
|
+
| Part | Runs where | Purpose |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| **MCP client** | Codex CLI, Claude Desktop, or another MCP host | Starts the MCP server and calls tools. |
|
|
25
|
+
| **MCP server** | Normal Python environment | Speaks MCP over stdio and forwards tool calls to ParaView over TCP. |
|
|
26
|
+
| **ParaView GUI bridge** | Already-open ParaView GUI process | Receives TCP JSON commands and executes `paraview.simple` operations in that live GUI session. |
|
|
27
|
+
|
|
28
|
+
The ParaView bridge is the ParaView-side component. It plays the same role as
|
|
29
|
+
the Blender add-on in `blender-mcp-server`: it must run inside the application
|
|
30
|
+
you want to control. For live GUI control, start the bridge from ParaView's
|
|
31
|
+
Python Shell using `scripts/start_paraview_gui_bridge.py`.
|
|
12
32
|
|
|
13
33
|
```
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
34
|
+
┌──────────────────────────────┐ stdio ┌────────────────────────┐
|
|
35
|
+
│ MCP Client │ ◄──────────────► │ MCP Server │
|
|
36
|
+
│ Codex / Claude / other host │ │ paraview-mcp-server │
|
|
37
|
+
└──────────────────────────────┘ └──────────┬─────────────┘
|
|
38
|
+
│ JSON/TCP
|
|
39
|
+
│ 127.0.0.1:9876
|
|
40
|
+
┌──────────▼─────────────┐
|
|
41
|
+
│ ParaView Bridge │
|
|
42
|
+
│ live GUI process │
|
|
43
|
+
└──────────┬─────────────┘
|
|
44
|
+
│
|
|
45
|
+
┌──────────▼─────────────┐
|
|
46
|
+
│ paraview.simple │
|
|
47
|
+
│ ParaView runtime │
|
|
48
|
+
└────────────────────────┘
|
|
21
49
|
```
|
|
22
50
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
51
|
+
Why a ParaView-side bridge? ParaView's useful automation API is
|
|
52
|
+
`paraview.simple`, and it must execute inside a ParaView Python runtime. The
|
|
53
|
+
MCP server itself is only a protocol adapter; it cannot modify a ParaView GUI
|
|
54
|
+
unless the bridge is running inside that GUI process.
|
|
55
|
+
|
|
56
|
+
For live GUI modification, use:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
Codex/Claude -> MCP server -> bridge inside open ParaView GUI -> live GUI session
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For headless automation, use:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
Codex/Claude -> MCP server -> bridge inside pvpython -> headless ParaView runtime
|
|
66
|
+
```
|
|
30
67
|
|
|
31
68
|
See [`docs/architecture.md`](docs/architecture.md) for a full diagram, protocol reference,
|
|
32
69
|
and tool namespace table.
|
|
33
70
|
|
|
34
71
|
---
|
|
35
72
|
|
|
36
|
-
##
|
|
73
|
+
## Install
|
|
37
74
|
|
|
38
|
-
###
|
|
75
|
+
### Option A: Install the MCP server from PyPI
|
|
76
|
+
|
|
77
|
+
Use this when you only need the MCP server executable in your normal Python
|
|
78
|
+
environment:
|
|
39
79
|
|
|
40
80
|
```bash
|
|
41
81
|
pip install paraview-mcp-python
|
|
42
82
|
```
|
|
43
83
|
|
|
44
|
-
|
|
84
|
+
This installs:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
paraview-mcp-server
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Option B: Clone this repository for the ParaView bridge
|
|
91
|
+
|
|
92
|
+
The bridge code must be available to ParaView's Python runtime. For live GUI
|
|
93
|
+
control and local development, clone the repository:
|
|
45
94
|
|
|
46
95
|
```bash
|
|
47
96
|
git clone https://github.com/djeada/paraview-mcp-server.git
|
|
@@ -51,18 +100,112 @@ source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
|
51
100
|
pip install -e .
|
|
52
101
|
```
|
|
53
102
|
|
|
54
|
-
|
|
103
|
+
This creates:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
.venv/bin/paraview-mcp-server
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Start Everything
|
|
112
|
+
|
|
113
|
+
Start the pieces in this order.
|
|
114
|
+
|
|
115
|
+
### 1. Start the ParaView GUI Bridge
|
|
116
|
+
|
|
117
|
+
Open ParaView, then run the GUI bridge script from the Python Shell:
|
|
118
|
+
|
|
119
|
+
1. In ParaView, open **Tools -> Python Shell**.
|
|
120
|
+
2. Click **Run Script**.
|
|
121
|
+
3. Select:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
/absolute/path/to/paraview-mcp-server/scripts/start_paraview_gui_bridge.py
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Expected output:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
ParaView MCP GUI bridge started on 127.0.0.1:9876
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
If `paraview-mcp-python` is installed into ParaView's Python environment, you
|
|
134
|
+
can also start the live GUI bridge directly from the Python Shell:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from bridge.gui_bridge import start_gui_bridge, stop_gui_bridge
|
|
138
|
+
start_gui_bridge()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Leave ParaView open. MCP commands now modify this live GUI session.
|
|
142
|
+
|
|
143
|
+
To stop the bridge from the ParaView Python Shell:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
stop_gui_bridge()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 2. Optional: Start a Headless `pvpython` Bridge
|
|
55
150
|
|
|
56
|
-
|
|
151
|
+
Use this only when you do not need to modify an already-open ParaView GUI:
|
|
57
152
|
|
|
58
153
|
```bash
|
|
154
|
+
cd /path/to/paraview-mcp-server
|
|
59
155
|
pvpython scripts/start_paraview_bridge.py
|
|
60
|
-
# → ParaView bridge ready on 127.0.0.1:9876
|
|
61
156
|
```
|
|
62
157
|
|
|
63
|
-
|
|
158
|
+
Expected output:
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
ParaView bridge ready on 127.0.0.1:9876
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Keep that terminal running. This controls the `pvpython` session, not a GUI
|
|
165
|
+
window opened separately.
|
|
166
|
+
|
|
167
|
+
### 3. Verify the Bridge Directly
|
|
168
|
+
|
|
169
|
+
Before involving an MCP client, send one raw bridge command:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
python scripts/paraview_bridge_request.py scene.get_info
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Expected response shape:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"success": true,
|
|
180
|
+
"result": {
|
|
181
|
+
"source_count": 0,
|
|
182
|
+
"active_view_type": "RenderView"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
If this fails, fix the bridge before configuring Codex or Claude.
|
|
188
|
+
|
|
189
|
+
### 4. Register the MCP Server with Codex CLI
|
|
190
|
+
|
|
191
|
+
If you installed from PyPI:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
codex mcp add paraview -- paraview-mcp-server
|
|
195
|
+
codex mcp list
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
If you are using the local repository:
|
|
64
199
|
|
|
65
|
-
|
|
200
|
+
```bash
|
|
201
|
+
codex mcp add paraview -- /absolute/path/to/paraview-mcp-server/.venv/bin/paraview-mcp-server
|
|
202
|
+
codex mcp list
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Codex starts the MCP server automatically when needed. The ParaView bridge
|
|
206
|
+
must already be running separately.
|
|
207
|
+
|
|
208
|
+
### 5. Register with Claude Desktop
|
|
66
209
|
|
|
67
210
|
**Claude Desktop** — add to `claude_desktop_config.json`:
|
|
68
211
|
|
|
@@ -76,12 +219,42 @@ The bridge listens for JSON commands from the MCP server.
|
|
|
76
219
|
}
|
|
77
220
|
```
|
|
78
221
|
|
|
79
|
-
|
|
222
|
+
For a PyPI install, use the absolute path returned by:
|
|
80
223
|
|
|
81
224
|
```bash
|
|
82
|
-
|
|
225
|
+
which paraview-mcp-server
|
|
83
226
|
```
|
|
84
227
|
|
|
228
|
+
Restart Claude Desktop after editing the config.
|
|
229
|
+
|
|
230
|
+
### 6. Verify Through Your MCP Client
|
|
231
|
+
|
|
232
|
+
With the bridge still running, ask your MCP client:
|
|
233
|
+
|
|
234
|
+
```text
|
|
235
|
+
List all sources in the current ParaView session.
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The client should call `paraview_scene_list_sources` and return the current
|
|
239
|
+
ParaView pipeline sources from the live GUI session if you started
|
|
240
|
+
`start_paraview_gui_bridge.py`.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## What Can It Control?
|
|
245
|
+
|
|
246
|
+
There are two levels of control:
|
|
247
|
+
|
|
248
|
+
1. **Fixed MCP tools** for common workflows: scene inspection, loading data,
|
|
249
|
+
filters, display/coloring, camera, screenshots, data export, and animation
|
|
250
|
+
export.
|
|
251
|
+
2. **Python execution** through `paraview_python_exec`, which can run trusted
|
|
252
|
+
local Python inside the ParaView bridge session. Use this for anything not
|
|
253
|
+
covered by a fixed tool, including arbitrary `paraview.simple` scripts.
|
|
254
|
+
|
|
255
|
+
So the fixed tool list is intentionally finite, but the Python execution tool
|
|
256
|
+
is the general escape hatch for the broader ParaView API.
|
|
257
|
+
|
|
85
258
|
---
|
|
86
259
|
|
|
87
260
|
## Example prompts
|
|
@@ -226,14 +399,14 @@ Async jobs run in a separate headless `pvpython` process via `HeadlessPvpythonEx
|
|
|
226
399
|
|
|
227
400
|
---
|
|
228
401
|
|
|
229
|
-
##
|
|
402
|
+
## Python execution trust model
|
|
230
403
|
|
|
231
|
-
- **
|
|
232
|
-
|
|
404
|
+
- **Trusted local execution** — `paraview_python_exec` can run arbitrary Python available to the active
|
|
405
|
+
ParaView Python process, including imports and full `paraview.simple` workflows.
|
|
233
406
|
- **Output bounding** — stdout/stderr capped at **50 KB**.
|
|
234
407
|
- **Cooperative timeout** — default 30 seconds per script execution.
|
|
235
408
|
- **Script path validation** — optionally restrict execution to approved root directories.
|
|
236
|
-
- The bridge runs inside
|
|
409
|
+
- The bridge runs inside ParaView's Python runtime with the same trust level as that local session.
|
|
237
410
|
- This is a local desktop automation tool — not a public API sandbox.
|
|
238
411
|
|
|
239
412
|
---
|
|
@@ -278,9 +451,11 @@ paraview-mcp-server/
|
|
|
278
451
|
├── bridge/
|
|
279
452
|
│ ├── __init__.py
|
|
280
453
|
│ ├── server.py # TCP socket bridge server
|
|
454
|
+
│ ├── gui_bridge.py # Non-blocking live GUI bridge lifecycle
|
|
281
455
|
│ ├── command_handler.py # Command registry + paraview.simple handlers (27 commands)
|
|
282
|
-
│ └── execution.py # python.execute helper
|
|
456
|
+
│ └── execution.py # trusted local python.execute helper
|
|
283
457
|
├── scripts/
|
|
458
|
+
│ ├── start_paraview_gui_bridge.py
|
|
284
459
|
│ ├── start_paraview_bridge.py
|
|
285
460
|
│ ├── paraview_bridge_request.py
|
|
286
461
|
│ └── library/ # Reusable pvpython snippets
|
|
@@ -296,7 +471,7 @@ paraview-mcp-server/
|
|
|
296
471
|
└── tests/
|
|
297
472
|
├── test_server.py # 31 tools, connection, headless, async jobs
|
|
298
473
|
├── test_protocol.py # Wire encoding, fake bridge integration
|
|
299
|
-
└── test_command_handler.py # All 27 handlers +
|
|
474
|
+
└── test_command_handler.py # All 27 handlers + execution controls
|
|
300
475
|
```
|
|
301
476
|
|
|
302
477
|
---
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"""Python script execution helper for the ParaView bridge.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
- **Blocked-module import hook** prevents scripts from importing dangerous
|
|
6
|
-
standard-library modules (``subprocess``, ``shutil``, ``socket``, …).
|
|
3
|
+
Execution controls
|
|
4
|
+
------------------
|
|
7
5
|
- **Output bounding** caps stdout/stderr at 50 KB.
|
|
8
6
|
- **Timeout** (cooperative) — the caller can supply ``timeout_seconds``.
|
|
9
7
|
- **Script-path execution** reads the script from disk, validating that it
|
|
@@ -12,12 +10,9 @@ Safety controls
|
|
|
12
10
|
|
|
13
11
|
from __future__ import annotations
|
|
14
12
|
|
|
15
|
-
import importlib.abc
|
|
16
|
-
import importlib.machinery
|
|
17
13
|
import io
|
|
18
14
|
import json
|
|
19
15
|
import os
|
|
20
|
-
import sys
|
|
21
16
|
import threading
|
|
22
17
|
import time
|
|
23
18
|
import traceback
|
|
@@ -27,20 +22,6 @@ from typing import Any
|
|
|
27
22
|
|
|
28
23
|
MAX_OUTPUT_SIZE = 50_000
|
|
29
24
|
|
|
30
|
-
BLOCKED_MODULES: set[str] = {
|
|
31
|
-
"subprocess",
|
|
32
|
-
"shutil",
|
|
33
|
-
"socket",
|
|
34
|
-
"ctypes",
|
|
35
|
-
"multiprocessing",
|
|
36
|
-
"webbrowser",
|
|
37
|
-
"http.server",
|
|
38
|
-
"xmlrpc.server",
|
|
39
|
-
"ftplib",
|
|
40
|
-
"smtplib",
|
|
41
|
-
"telnetlib",
|
|
42
|
-
}
|
|
43
|
-
|
|
44
25
|
# Optionally set via bridge config; empty list → no restriction.
|
|
45
26
|
APPROVED_SCRIPT_ROOTS: list[str] = []
|
|
46
27
|
|
|
@@ -50,60 +31,6 @@ ALLOW_INLINE_CODE: bool = True
|
|
|
50
31
|
DEFAULT_TIMEOUT_SECONDS: float = 30.0
|
|
51
32
|
|
|
52
33
|
|
|
53
|
-
# ---------------------------------------------------------------------------
|
|
54
|
-
# Import hook that blocks dangerous modules during script execution
|
|
55
|
-
# ---------------------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class _BlockedImportFinder(importlib.abc.MetaPathFinder):
|
|
59
|
-
"""Raises ``ImportError`` for modules in the *blocked* set."""
|
|
60
|
-
|
|
61
|
-
_active = False
|
|
62
|
-
_blocked: set[str] = set()
|
|
63
|
-
|
|
64
|
-
def find_module(self, fullname: str, path=None):
|
|
65
|
-
"""Return *self* if the module should be blocked, ``None`` otherwise (legacy hook)."""
|
|
66
|
-
if self._active and fullname in self._blocked:
|
|
67
|
-
return self
|
|
68
|
-
return None
|
|
69
|
-
|
|
70
|
-
def find_spec(self, fullname: str, path=None, target=None):
|
|
71
|
-
"""Raise ``ImportError`` for blocked modules (modern import hook)."""
|
|
72
|
-
if self._active and fullname in self._blocked:
|
|
73
|
-
raise ImportError(f"Module {fullname!r} is blocked during ParaView MCP script execution")
|
|
74
|
-
return None
|
|
75
|
-
|
|
76
|
-
def load_module(self, fullname: str):
|
|
77
|
-
raise ImportError(f"Module {fullname!r} is blocked during ParaView MCP script execution")
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
_BLOCKER = _BlockedImportFinder()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def _install_import_blocker() -> dict[str, Any]:
|
|
84
|
-
"""Activate the blocker and hide already-imported blocked modules.
|
|
85
|
-
|
|
86
|
-
Returns a dict of hidden modules that must be restored later.
|
|
87
|
-
"""
|
|
88
|
-
_BLOCKER._blocked = BLOCKED_MODULES
|
|
89
|
-
_BLOCKER._active = True
|
|
90
|
-
if _BLOCKER not in sys.meta_path:
|
|
91
|
-
sys.meta_path.insert(0, _BLOCKER)
|
|
92
|
-
# Temporarily remove blocked modules from sys.modules so `import X`
|
|
93
|
-
# falls through to the meta-path hooks instead of hitting the cache.
|
|
94
|
-
hidden: dict[str, Any] = {}
|
|
95
|
-
for mod_name in BLOCKED_MODULES:
|
|
96
|
-
if mod_name in sys.modules:
|
|
97
|
-
hidden[mod_name] = sys.modules.pop(mod_name)
|
|
98
|
-
return hidden
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def _remove_import_blocker(hidden: dict[str, Any]) -> None:
|
|
102
|
-
_BLOCKER._active = False
|
|
103
|
-
# Restore previously-imported modules.
|
|
104
|
-
sys.modules.update(hidden)
|
|
105
|
-
|
|
106
|
-
|
|
107
34
|
# ---------------------------------------------------------------------------
|
|
108
35
|
# Helpers
|
|
109
36
|
# ---------------------------------------------------------------------------
|
|
@@ -199,14 +126,11 @@ def execute_code(
|
|
|
199
126
|
assert isinstance(code, str)
|
|
200
127
|
|
|
201
128
|
def _run() -> None:
|
|
202
|
-
hidden = _install_import_blocker()
|
|
203
129
|
try:
|
|
204
130
|
with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf):
|
|
205
131
|
exec(compile(code, "<paraview-mcp-script>", "exec"), namespace) # noqa: S102
|
|
206
132
|
except Exception as exc:
|
|
207
133
|
result_holder["error"] = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
|
|
208
|
-
finally:
|
|
209
|
-
_remove_import_blocker(hidden)
|
|
210
134
|
|
|
211
135
|
thread = threading.Thread(target=_run, daemon=True)
|
|
212
136
|
thread.start()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Helpers for starting the bridge from an already-open ParaView GUI session."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from bridge.server import HOST, PORT, ParaViewBridgeServer
|
|
8
|
+
|
|
9
|
+
_SERVER: ParaViewBridgeServer | None = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def start_gui_bridge(host: str = HOST, port: int = PORT) -> dict[str, Any]:
|
|
13
|
+
"""Start the bridge inside the current ParaView Python process.
|
|
14
|
+
|
|
15
|
+
This function is intentionally non-blocking so it can be called from the
|
|
16
|
+
ParaView GUI Python shell without freezing the application.
|
|
17
|
+
"""
|
|
18
|
+
global _SERVER
|
|
19
|
+
if _SERVER is not None and _SERVER.is_running:
|
|
20
|
+
return {
|
|
21
|
+
"host": _SERVER.host,
|
|
22
|
+
"port": _SERVER.port,
|
|
23
|
+
"running": True,
|
|
24
|
+
"already_running": True,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
server = ParaViewBridgeServer(host=host, port=port)
|
|
28
|
+
server.start()
|
|
29
|
+
_SERVER = server
|
|
30
|
+
return {
|
|
31
|
+
"host": server.host,
|
|
32
|
+
"port": server.port,
|
|
33
|
+
"running": True,
|
|
34
|
+
"already_running": False,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def stop_gui_bridge() -> dict[str, Any]:
|
|
39
|
+
"""Stop the bridge started by :func:`start_gui_bridge`."""
|
|
40
|
+
global _SERVER
|
|
41
|
+
if _SERVER is None:
|
|
42
|
+
return {"running": False, "stopped": False}
|
|
43
|
+
|
|
44
|
+
host = _SERVER.host
|
|
45
|
+
port = _SERVER.port
|
|
46
|
+
_SERVER.stop()
|
|
47
|
+
_SERVER = None
|
|
48
|
+
return {"host": host, "port": port, "running": False, "stopped": True}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def gui_bridge_status() -> dict[str, Any]:
|
|
52
|
+
"""Return the current GUI bridge status."""
|
|
53
|
+
if _SERVER is None or not _SERVER.is_running:
|
|
54
|
+
return {"running": False}
|
|
55
|
+
return {"host": _SERVER.host, "port": _SERVER.port, "running": True}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Dependency-free bridge command parameter validation.
|
|
2
2
|
|
|
3
|
-
The bridge runs inside ParaView's
|
|
4
|
-
the MCP server's Python dependencies installed.
|
|
3
|
+
The bridge runs inside ParaView's Python runtime, which commonly does not have
|
|
4
|
+
the MCP server's Python dependencies installed. These classes intentionally
|
|
5
5
|
provide the small ``model_validate(...).model_dump(...)`` surface used by the
|
|
6
6
|
command handler without importing third-party packages.
|
|
7
7
|
"""
|
|
@@ -33,6 +33,18 @@ class ParaViewBridgeServer:
|
|
|
33
33
|
self._handler = CommandHandler()
|
|
34
34
|
self._handler_lock = threading.Lock()
|
|
35
35
|
|
|
36
|
+
@property
|
|
37
|
+
def host(self) -> str:
|
|
38
|
+
return self._host
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def port(self) -> int:
|
|
42
|
+
return self._port
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def is_running(self) -> bool:
|
|
46
|
+
return self._running
|
|
47
|
+
|
|
36
48
|
def start(self):
|
|
37
49
|
if self._running:
|
|
38
50
|
return
|
|
@@ -40,6 +52,7 @@ class ParaViewBridgeServer:
|
|
|
40
52
|
self._server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
41
53
|
self._server_socket.settimeout(1.0)
|
|
42
54
|
self._server_socket.bind((self._host, self._port))
|
|
55
|
+
self._host, self._port = self._server_socket.getsockname()[:2]
|
|
43
56
|
self._server_socket.listen(5)
|
|
44
57
|
self._running = True
|
|
45
58
|
self._thread = threading.Thread(target=self._accept_loop, daemon=True)
|
|
@@ -19,20 +19,26 @@
|
|
|
19
19
|
│ JSON / TCP localhost:9876
|
|
20
20
|
│ (newline-delimited JSON)
|
|
21
21
|
┌──────────▼─────────────┐
|
|
22
|
-
│ ParaView bridge
|
|
23
|
-
│ bridge/ │
|
|
22
|
+
│ ParaView GUI bridge │ Runs inside the open ParaView GUI
|
|
23
|
+
│ bridge/ │ via scripts/start_paraview_gui_bridge.py
|
|
24
24
|
│ · ParaViewBridgeServer│
|
|
25
25
|
│ · CommandHandler │ 27 registered commands
|
|
26
26
|
│ · execute_code() │
|
|
27
27
|
└──────────┬─────────────┘
|
|
28
28
|
│
|
|
29
29
|
┌──────────▼─────────────┐
|
|
30
|
-
│ paraview.simple │ ParaView
|
|
30
|
+
│ paraview.simple │ Live ParaView GUI session
|
|
31
31
|
│ servermanager │
|
|
32
32
|
└────────────────────────┘
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Alternative headless
|
|
35
|
+
Alternative headless bridge:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
MCP Client → paraview-mcp-server → pvpython scripts/start_paraview_bridge.py
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Alternative headless script transport (no long-running bridge required):
|
|
36
42
|
|
|
37
43
|
```
|
|
38
44
|
┌────────────────────────┐
|
|
@@ -67,8 +73,9 @@ Alternative headless transport (no bridge required):
|
|
|
67
73
|
| Module | Responsibility |
|
|
68
74
|
|---|---|
|
|
69
75
|
| `server.py` | Threaded TCP socket server, newline-delimited JSON framing |
|
|
76
|
+
| `gui_bridge.py` | Non-blocking helpers for starting/stopping the bridge inside ParaView GUI |
|
|
70
77
|
| `command_handler.py` | Command registry mapping 27 command names to `paraview.simple` calls |
|
|
71
|
-
| `execution.py` | `execute_code()` —
|
|
78
|
+
| `execution.py` | `execute_code()` — trusted local Python execution with timeout, output cap, and optional script path validation |
|
|
72
79
|
| `__init__.py` | Package marker |
|
|
73
80
|
|
|
74
81
|
---
|
|
@@ -213,20 +220,33 @@ MCP client
|
|
|
213
220
|
|
|
214
221
|
## Lifecycle
|
|
215
222
|
|
|
216
|
-
1. User starts the bridge:
|
|
223
|
+
1. User starts the live GUI bridge from ParaView: **Tools → Python Shell → Run Script**,
|
|
224
|
+
selecting `scripts/start_paraview_gui_bridge.py`.
|
|
217
225
|
2. Bridge server binds to `127.0.0.1:9876` and listens for TCP connections.
|
|
218
226
|
3. User starts an MCP client (Claude Desktop, Codex CLI, etc.)
|
|
219
227
|
4. MCP client spawns `paraview-mcp-server` over stdio.
|
|
220
228
|
5. MCP server connects to bridge on startup (or lazy-connects on first tool call).
|
|
221
229
|
6. User issues a natural language request → client calls an MCP tool → server
|
|
222
230
|
forwards as JSON → bridge dispatches → returns result.
|
|
223
|
-
7. User stops the bridge with
|
|
231
|
+
7. User stops the GUI bridge with `stop_gui_bridge()` in ParaView's Python Shell.
|
|
232
|
+
Server reconnects on next call if the bridge restarts.
|
|
224
233
|
|
|
225
234
|
---
|
|
226
235
|
|
|
227
236
|
## Configuration
|
|
228
237
|
|
|
229
|
-
### Bridge
|
|
238
|
+
### Live GUI Bridge
|
|
239
|
+
|
|
240
|
+
Run this from ParaView's Python Shell with **Run Script**:
|
|
241
|
+
|
|
242
|
+
```text
|
|
243
|
+
scripts/start_paraview_gui_bridge.py
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The script starts a background TCP server in the open ParaView GUI process and
|
|
247
|
+
returns immediately.
|
|
248
|
+
|
|
249
|
+
### Headless Bridge
|
|
230
250
|
|
|
231
251
|
```bash
|
|
232
252
|
pvpython scripts/start_paraview_bridge.py --host 127.0.0.1 --port 9876
|
|
@@ -283,7 +303,7 @@ paraview-mcp-server/
|
|
|
283
303
|
│ ├── __init__.py
|
|
284
304
|
│ ├── server.py # TCP bridge server
|
|
285
305
|
│ ├── command_handler.py # 27-command registry
|
|
286
|
-
│ └── execution.py # python.execute
|
|
306
|
+
│ └── execution.py # trusted local python.execute helper
|
|
287
307
|
├── scripts/
|
|
288
308
|
│ ├── start_paraview_bridge.py
|
|
289
309
|
│ ├── paraview_bridge_request.py
|
|
@@ -7,7 +7,10 @@ escape hatch for workflows that require more than the fixed tool set.
|
|
|
7
7
|
|
|
8
8
|
Two transports are supported:
|
|
9
9
|
|
|
10
|
-
1. **Bridge** (default) - code runs inside the
|
|
10
|
+
1. **Bridge** (default) - code runs inside the active bridge process via exec().
|
|
11
|
+
For live GUI control, that process is the open ParaView GUI where
|
|
12
|
+
`scripts/start_paraview_gui_bridge.py` was run. For headless control, it is
|
|
13
|
+
the `pvpython scripts/start_paraview_bridge.py` process.
|
|
11
14
|
2. **Headless** - code runs in a separate pvpython subprocess via HeadlessPvpythonExecutor.
|
|
12
15
|
|
|
13
16
|
---
|
|
@@ -63,13 +66,14 @@ Headless transport adds:
|
|
|
63
66
|
|
|
64
67
|
---
|
|
65
68
|
|
|
66
|
-
##
|
|
69
|
+
## Python Execution Trust Model
|
|
67
70
|
|
|
68
|
-
###
|
|
71
|
+
### Trusted local execution
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
Bridge scripts run inside the local ParaView Python process and may import
|
|
74
|
+
normal Python modules. In live GUI mode, that is the open ParaView GUI process.
|
|
75
|
+
This is intentional: `paraview_python_exec` is the escape hatch for full
|
|
76
|
+
ParaView automation when the fixed MCP tool set is too small.
|
|
73
77
|
|
|
74
78
|
### Output bounding
|
|
75
79
|
|
|
@@ -134,5 +138,5 @@ Use paraview_python_exec_async for long-running scripts:
|
|
|
134
138
|
|
|
135
139
|
- Cancellation token (bridge transport) for cooperative cancellation
|
|
136
140
|
- Script library registry for referencing scripts by name
|
|
137
|
-
-
|
|
141
|
+
- Optional sandboxed execution mode for deployments that need stricter isolation
|
|
138
142
|
- Resource limits for memory usage per script
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "paraview-mcp-python"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "MCP server for controlling ParaView via AI assistants"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -46,7 +46,7 @@ dev = [
|
|
|
46
46
|
]
|
|
47
47
|
|
|
48
48
|
[tool.hatch.build.targets.wheel]
|
|
49
|
-
packages = ["src/paraview_mcp_server"]
|
|
49
|
+
packages = ["src/paraview_mcp_server", "bridge"]
|
|
50
50
|
|
|
51
51
|
[tool.hatch.build.targets.sdist]
|
|
52
52
|
include = [
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Start the MCP bridge inside an already-open ParaView GUI session.
|
|
2
|
+
|
|
3
|
+
Run from ParaView:
|
|
4
|
+
|
|
5
|
+
Tools -> Python Shell -> Run Script
|
|
6
|
+
|
|
7
|
+
Select this file. The script starts the TCP bridge in a background thread and
|
|
8
|
+
returns immediately, so the ParaView GUI remains usable. MCP commands will then
|
|
9
|
+
modify this open ParaView session.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
|
|
19
|
+
|
|
20
|
+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
21
|
+
if REPO_ROOT not in sys.path:
|
|
22
|
+
sys.path.insert(0, REPO_ROOT)
|
|
23
|
+
|
|
24
|
+
from bridge.gui_bridge import gui_bridge_status, start_gui_bridge, stop_gui_bridge # noqa: E402
|
|
25
|
+
|
|
26
|
+
globals()["stop_gui_bridge"] = stop_gui_bridge
|
|
27
|
+
globals()["gui_bridge_status"] = gui_bridge_status
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main() -> None:
|
|
31
|
+
host = os.environ.get("PARAVIEW_MCP_HOST", "127.0.0.1")
|
|
32
|
+
port = int(os.environ.get("PARAVIEW_MCP_PORT", "9876"))
|
|
33
|
+
status = start_gui_bridge(host=host, port=port)
|
|
34
|
+
state = "already running" if status["already_running"] else "started"
|
|
35
|
+
print(f"ParaView MCP GUI bridge {state} on {status['host']}:{status['port']}")
|
|
36
|
+
print("Verify from a terminal with:")
|
|
37
|
+
print(" python scripts/paraview_bridge_request.py scene.get_info")
|
|
38
|
+
print("Stop from the ParaView Python Shell with:")
|
|
39
|
+
print(" stop_gui_bridge()")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
main()
|
|
@@ -532,15 +532,16 @@ class TestPythonExecuteHandler:
|
|
|
532
532
|
assert "timeout" in result["error"].lower()
|
|
533
533
|
|
|
534
534
|
|
|
535
|
-
class
|
|
536
|
-
"""Test the
|
|
535
|
+
class TestExecutionControls:
|
|
536
|
+
"""Test the execution controls in bridge/execution.py."""
|
|
537
537
|
|
|
538
|
-
def
|
|
538
|
+
def test_standard_library_imports_are_allowed(self):
|
|
539
539
|
from bridge.execution import execute_code
|
|
540
540
|
|
|
541
|
-
result = execute_code(code="import subprocess")
|
|
542
|
-
|
|
543
|
-
assert
|
|
541
|
+
result = execute_code(code="import subprocess\n__result__ = subprocess.__name__")
|
|
542
|
+
|
|
543
|
+
assert result["error"] is None
|
|
544
|
+
assert result["result"] == "subprocess"
|
|
544
545
|
|
|
545
546
|
def test_output_capping(self):
|
|
546
547
|
from bridge.execution import _cap_output
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Tests for the ParaView GUI bridge lifecycle helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest.mock import MagicMock, patch
|
|
6
|
+
|
|
7
|
+
from bridge import gui_bridge
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def teardown_function(_function):
|
|
11
|
+
gui_bridge.stop_gui_bridge()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_start_gui_bridge_is_non_blocking_and_reports_status():
|
|
15
|
+
with patch("bridge.command_handler.CommandHandler", return_value=MagicMock()):
|
|
16
|
+
status = gui_bridge.start_gui_bridge(port=0)
|
|
17
|
+
|
|
18
|
+
assert status["running"] is True
|
|
19
|
+
assert status["already_running"] is False
|
|
20
|
+
assert status["host"] == "127.0.0.1"
|
|
21
|
+
assert status["port"] > 0
|
|
22
|
+
assert gui_bridge.gui_bridge_status() == {
|
|
23
|
+
"host": status["host"],
|
|
24
|
+
"port": status["port"],
|
|
25
|
+
"running": True,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_start_gui_bridge_is_idempotent():
|
|
30
|
+
with patch("bridge.command_handler.CommandHandler", return_value=MagicMock()):
|
|
31
|
+
first = gui_bridge.start_gui_bridge(port=0)
|
|
32
|
+
second = gui_bridge.start_gui_bridge(port=0)
|
|
33
|
+
|
|
34
|
+
assert first["running"] is True
|
|
35
|
+
assert second == {
|
|
36
|
+
"host": first["host"],
|
|
37
|
+
"port": first["port"],
|
|
38
|
+
"running": True,
|
|
39
|
+
"already_running": True,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_stop_gui_bridge_stops_running_server():
|
|
44
|
+
with patch("bridge.command_handler.CommandHandler", return_value=MagicMock()):
|
|
45
|
+
started = gui_bridge.start_gui_bridge(port=0)
|
|
46
|
+
|
|
47
|
+
stopped = gui_bridge.stop_gui_bridge()
|
|
48
|
+
|
|
49
|
+
assert stopped == {
|
|
50
|
+
"host": started["host"],
|
|
51
|
+
"port": started["port"],
|
|
52
|
+
"running": False,
|
|
53
|
+
"stopped": True,
|
|
54
|
+
}
|
|
55
|
+
assert gui_bridge.gui_bridge_status() == {"running": False}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|