paraview-mcp-python 0.1.3__tar.gz → 0.1.4__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.3 → paraview_mcp_python-0.1.4}/PKG-INFO +41 -53
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/README.md +40 -52
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/docs/architecture.md +22 -21
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/docs/python-execute-design.md +6 -4
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/pyproject.toml +2 -1
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/start_paraview_bridge.py +19 -1
- paraview_mcp_python-0.1.4/src/paraview_mcp_server/launcher.py +151 -0
- paraview_mcp_python-0.1.4/tests/test_launcher.py +63 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/.gitignore +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/LICENSE +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/bridge/__init__.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/bridge/command_handler.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/bridge/execution.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/bridge/gui_bridge.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/bridge/models.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/bridge/server.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/library/color_by.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/library/create_contour.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/library/create_slice.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/library/open_dataset.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/library/reset_camera.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/library/save_screenshot.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/paraview_bridge_request.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/start_paraview_gui_bridge.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/src/paraview_mcp_server/__init__.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/src/paraview_mcp_server/headless.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/src/paraview_mcp_server/server.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/tests/__init__.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/tests/test_bridge_server.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/tests/test_command_handler.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/tests/test_gui_bridge.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/tests/test_protocol.py +0 -0
- {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/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.4
|
|
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
|
|
@@ -50,18 +50,21 @@ paraview-mcp-server
|
|
|
50
50
|
|
|
51
51
|
## What Parts Are There?
|
|
52
52
|
|
|
53
|
-
There are
|
|
53
|
+
There are four moving pieces in the GUI workflow:
|
|
54
54
|
|
|
55
55
|
| Part | Runs where | Purpose |
|
|
56
56
|
|---|---|---|
|
|
57
57
|
| **MCP client** | Codex CLI, Claude Desktop, or another MCP host | Starts the MCP server and calls tools. |
|
|
58
58
|
| **MCP server** | Normal Python environment | Speaks MCP over stdio and forwards tool calls to ParaView over TCP. |
|
|
59
|
-
| **ParaView
|
|
59
|
+
| **ParaView bridge** | `pvpython` process | Receives TCP JSON commands and executes `paraview.simple` operations. |
|
|
60
|
+
| **ParaView runtime** | `pvserver` plus a ParaView GUI client | Owns the shared ParaView session that the GUI and bridge both use. |
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
ParaView is not Blender: Python plugins and VTK timer callbacks are pipeline
|
|
63
|
+
extension mechanisms, not a safe general-purpose remote-control hook for a live
|
|
64
|
+
GUI process. The supported GUI workflow here uses ParaView's client/server
|
|
65
|
+
model. `paraview-mcp-launch` starts a local `pvserver`, connects the ParaView
|
|
66
|
+
GUI as the first client, then connects a `pvpython` bridge client to the same
|
|
67
|
+
session.
|
|
65
68
|
|
|
66
69
|
```
|
|
67
70
|
┌──────────────────────────────┐ stdio ┌────────────────────────┐
|
|
@@ -72,24 +75,24 @@ Python Shell using `scripts/start_paraview_gui_bridge.py`.
|
|
|
72
75
|
│ 127.0.0.1:9876
|
|
73
76
|
┌──────────▼─────────────┐
|
|
74
77
|
│ ParaView Bridge │
|
|
75
|
-
│
|
|
78
|
+
│ pvpython client │
|
|
76
79
|
└──────────┬─────────────┘
|
|
77
|
-
│
|
|
80
|
+
│ ParaView client/server
|
|
78
81
|
┌──────────▼─────────────┐
|
|
79
|
-
│
|
|
80
|
-
│ ParaView
|
|
82
|
+
│ pvserver + GUI client │
|
|
83
|
+
│ shared ParaView state │
|
|
81
84
|
└────────────────────────┘
|
|
82
85
|
```
|
|
83
86
|
|
|
84
87
|
Why a ParaView-side bridge? ParaView's useful automation API is
|
|
85
88
|
`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
|
|
87
|
-
unless
|
|
89
|
+
MCP server itself is only a protocol adapter; it cannot modify a ParaView
|
|
90
|
+
session unless a ParaView-side bridge is running.
|
|
88
91
|
|
|
89
92
|
For live GUI modification, use:
|
|
90
93
|
|
|
91
94
|
```text
|
|
92
|
-
Codex/Claude -> MCP server -> bridge
|
|
95
|
+
Codex/Claude -> MCP server -> pvpython bridge -> pvserver <- ParaView GUI
|
|
93
96
|
```
|
|
94
97
|
|
|
95
98
|
For headless automation, use:
|
|
@@ -118,6 +121,7 @@ This installs:
|
|
|
118
121
|
|
|
119
122
|
```bash
|
|
120
123
|
paraview-mcp-server
|
|
124
|
+
paraview-mcp-launch
|
|
121
125
|
```
|
|
122
126
|
|
|
123
127
|
### Option B: Clone this repository for the ParaView bridge
|
|
@@ -137,54 +141,37 @@ This creates:
|
|
|
137
141
|
|
|
138
142
|
```bash
|
|
139
143
|
.venv/bin/paraview-mcp-server
|
|
144
|
+
.venv/bin/paraview-mcp-launch
|
|
140
145
|
```
|
|
141
146
|
|
|
142
147
|
---
|
|
143
148
|
|
|
144
149
|
## Start Everything
|
|
145
150
|
|
|
146
|
-
Start the
|
|
151
|
+
Start the ParaView side with one command:
|
|
147
152
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
```bash
|
|
154
|
+
cd /path/to/paraview-mcp-server
|
|
155
|
+
paraview-mcp-launch
|
|
156
|
+
```
|
|
151
157
|
|
|
152
|
-
|
|
153
|
-
2. Click **Run Script**.
|
|
154
|
-
3. Select:
|
|
158
|
+
For a local editable checkout:
|
|
155
159
|
|
|
156
|
-
```
|
|
157
|
-
/
|
|
160
|
+
```bash
|
|
161
|
+
.venv/bin/paraview-mcp-launch
|
|
158
162
|
```
|
|
159
163
|
|
|
160
164
|
Expected output:
|
|
161
165
|
|
|
162
166
|
```text
|
|
163
|
-
ParaView MCP
|
|
167
|
+
ParaView MCP bridge ready on 127.0.0.1:9876
|
|
168
|
+
Launching ParaView GUI connected to cs://127.0.0.1:11111
|
|
164
169
|
```
|
|
165
170
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
embedded GUI Python environment is fully ready for pipeline edits. Use **Tools
|
|
169
|
-
-> Python Shell -> Run Script** for the live GUI bridge.
|
|
170
|
-
|
|
171
|
-
If `paraview-mcp-python` is installed into ParaView's Python environment, you
|
|
172
|
-
can also start the live GUI bridge directly from the Python Shell:
|
|
171
|
+
Keep that terminal running. Closing it stops the GUI, bridge, and local
|
|
172
|
+
`pvserver` session.
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
from bridge.gui_bridge import start_gui_bridge, stop_gui_bridge
|
|
176
|
-
start_gui_bridge()
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
Leave ParaView open. MCP commands now modify this live GUI session.
|
|
180
|
-
|
|
181
|
-
To stop the bridge from the ParaView Python Shell:
|
|
182
|
-
|
|
183
|
-
```python
|
|
184
|
-
stop_gui_bridge()
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### 2. Optional: Start a Headless `pvpython` Bridge
|
|
174
|
+
### Optional: Start a Headless `pvpython` Bridge
|
|
188
175
|
|
|
189
176
|
Use this only when you do not need to modify an already-open ParaView GUI:
|
|
190
177
|
|
|
@@ -202,7 +189,7 @@ ParaView bridge ready on 127.0.0.1:9876
|
|
|
202
189
|
Keep that terminal running. This controls the `pvpython` session, not a GUI
|
|
203
190
|
window opened separately.
|
|
204
191
|
|
|
205
|
-
###
|
|
192
|
+
### Verify the Bridge Directly
|
|
206
193
|
|
|
207
194
|
Before involving an MCP client, send one raw bridge command:
|
|
208
195
|
|
|
@@ -224,7 +211,7 @@ Expected response shape:
|
|
|
224
211
|
|
|
225
212
|
If this fails, fix the bridge before configuring Codex or Claude.
|
|
226
213
|
|
|
227
|
-
###
|
|
214
|
+
### Register the MCP Server with Codex CLI
|
|
228
215
|
|
|
229
216
|
If you installed from PyPI:
|
|
230
217
|
|
|
@@ -243,7 +230,7 @@ codex mcp list
|
|
|
243
230
|
Codex starts the MCP server automatically when needed. The ParaView bridge
|
|
244
231
|
must already be running separately.
|
|
245
232
|
|
|
246
|
-
###
|
|
233
|
+
### Register with Claude Desktop
|
|
247
234
|
|
|
248
235
|
**Claude Desktop** — add to `claude_desktop_config.json`:
|
|
249
236
|
|
|
@@ -265,7 +252,7 @@ which paraview-mcp-server
|
|
|
265
252
|
|
|
266
253
|
Restart Claude Desktop after editing the config.
|
|
267
254
|
|
|
268
|
-
###
|
|
255
|
+
### Verify Through Your MCP Client
|
|
269
256
|
|
|
270
257
|
With the bridge still running, ask your MCP client:
|
|
271
258
|
|
|
@@ -274,8 +261,8 @@ List all sources in the current ParaView session.
|
|
|
274
261
|
```
|
|
275
262
|
|
|
276
263
|
The client should call `paraview_scene_list_sources` and return the current
|
|
277
|
-
ParaView pipeline sources from the
|
|
278
|
-
`
|
|
264
|
+
ParaView pipeline sources from the server-backed GUI session started by
|
|
265
|
+
`paraview-mcp-launch`.
|
|
279
266
|
|
|
280
267
|
---
|
|
281
268
|
|
|
@@ -485,16 +472,17 @@ paraview-mcp-server/
|
|
|
485
472
|
│ └── paraview_mcp_server/
|
|
486
473
|
│ ├── __init__.py # Re-exports main()
|
|
487
474
|
│ ├── server.py # FastMCP stdio server (31 tools)
|
|
475
|
+
│ ├── launcher.py # Starts pvserver, GUI, and bridge together
|
|
488
476
|
│ └── headless.py # Headless pvpython executor + job manager
|
|
489
477
|
├── bridge/
|
|
490
478
|
│ ├── __init__.py
|
|
491
479
|
│ ├── server.py # TCP socket bridge server
|
|
492
|
-
│ ├── gui_bridge.py #
|
|
480
|
+
│ ├── gui_bridge.py # Experimental in-GUI bridge helpers
|
|
493
481
|
│ ├── command_handler.py # Command registry + paraview.simple handlers (27 commands)
|
|
494
482
|
│ └── execution.py # trusted local python.execute helper
|
|
495
483
|
├── scripts/
|
|
496
|
-
│ ├── start_paraview_gui_bridge.py
|
|
497
484
|
│ ├── start_paraview_bridge.py
|
|
485
|
+
│ ├── start_paraview_gui_bridge.py
|
|
498
486
|
│ ├── paraview_bridge_request.py
|
|
499
487
|
│ └── library/ # Reusable pvpython snippets
|
|
500
488
|
│ ├── open_dataset.py
|
|
@@ -17,18 +17,21 @@ paraview-mcp-server
|
|
|
17
17
|
|
|
18
18
|
## What Parts Are There?
|
|
19
19
|
|
|
20
|
-
There are
|
|
20
|
+
There are four moving pieces in the GUI workflow:
|
|
21
21
|
|
|
22
22
|
| Part | Runs where | Purpose |
|
|
23
23
|
|---|---|---|
|
|
24
24
|
| **MCP client** | Codex CLI, Claude Desktop, or another MCP host | Starts the MCP server and calls tools. |
|
|
25
25
|
| **MCP server** | Normal Python environment | Speaks MCP over stdio and forwards tool calls to ParaView over TCP. |
|
|
26
|
-
| **ParaView
|
|
26
|
+
| **ParaView bridge** | `pvpython` process | Receives TCP JSON commands and executes `paraview.simple` operations. |
|
|
27
|
+
| **ParaView runtime** | `pvserver` plus a ParaView GUI client | Owns the shared ParaView session that the GUI and bridge both use. |
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
ParaView is not Blender: Python plugins and VTK timer callbacks are pipeline
|
|
30
|
+
extension mechanisms, not a safe general-purpose remote-control hook for a live
|
|
31
|
+
GUI process. The supported GUI workflow here uses ParaView's client/server
|
|
32
|
+
model. `paraview-mcp-launch` starts a local `pvserver`, connects the ParaView
|
|
33
|
+
GUI as the first client, then connects a `pvpython` bridge client to the same
|
|
34
|
+
session.
|
|
32
35
|
|
|
33
36
|
```
|
|
34
37
|
┌──────────────────────────────┐ stdio ┌────────────────────────┐
|
|
@@ -39,24 +42,24 @@ Python Shell using `scripts/start_paraview_gui_bridge.py`.
|
|
|
39
42
|
│ 127.0.0.1:9876
|
|
40
43
|
┌──────────▼─────────────┐
|
|
41
44
|
│ ParaView Bridge │
|
|
42
|
-
│
|
|
45
|
+
│ pvpython client │
|
|
43
46
|
└──────────┬─────────────┘
|
|
44
|
-
│
|
|
47
|
+
│ ParaView client/server
|
|
45
48
|
┌──────────▼─────────────┐
|
|
46
|
-
│
|
|
47
|
-
│ ParaView
|
|
49
|
+
│ pvserver + GUI client │
|
|
50
|
+
│ shared ParaView state │
|
|
48
51
|
└────────────────────────┘
|
|
49
52
|
```
|
|
50
53
|
|
|
51
54
|
Why a ParaView-side bridge? ParaView's useful automation API is
|
|
52
55
|
`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
|
|
54
|
-
unless
|
|
56
|
+
MCP server itself is only a protocol adapter; it cannot modify a ParaView
|
|
57
|
+
session unless a ParaView-side bridge is running.
|
|
55
58
|
|
|
56
59
|
For live GUI modification, use:
|
|
57
60
|
|
|
58
61
|
```text
|
|
59
|
-
Codex/Claude -> MCP server -> bridge
|
|
62
|
+
Codex/Claude -> MCP server -> pvpython bridge -> pvserver <- ParaView GUI
|
|
60
63
|
```
|
|
61
64
|
|
|
62
65
|
For headless automation, use:
|
|
@@ -85,6 +88,7 @@ This installs:
|
|
|
85
88
|
|
|
86
89
|
```bash
|
|
87
90
|
paraview-mcp-server
|
|
91
|
+
paraview-mcp-launch
|
|
88
92
|
```
|
|
89
93
|
|
|
90
94
|
### Option B: Clone this repository for the ParaView bridge
|
|
@@ -104,54 +108,37 @@ This creates:
|
|
|
104
108
|
|
|
105
109
|
```bash
|
|
106
110
|
.venv/bin/paraview-mcp-server
|
|
111
|
+
.venv/bin/paraview-mcp-launch
|
|
107
112
|
```
|
|
108
113
|
|
|
109
114
|
---
|
|
110
115
|
|
|
111
116
|
## Start Everything
|
|
112
117
|
|
|
113
|
-
Start the
|
|
118
|
+
Start the ParaView side with one command:
|
|
114
119
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
```bash
|
|
121
|
+
cd /path/to/paraview-mcp-server
|
|
122
|
+
paraview-mcp-launch
|
|
123
|
+
```
|
|
118
124
|
|
|
119
|
-
|
|
120
|
-
2. Click **Run Script**.
|
|
121
|
-
3. Select:
|
|
125
|
+
For a local editable checkout:
|
|
122
126
|
|
|
123
|
-
```
|
|
124
|
-
/
|
|
127
|
+
```bash
|
|
128
|
+
.venv/bin/paraview-mcp-launch
|
|
125
129
|
```
|
|
126
130
|
|
|
127
131
|
Expected output:
|
|
128
132
|
|
|
129
133
|
```text
|
|
130
|
-
ParaView MCP
|
|
134
|
+
ParaView MCP bridge ready on 127.0.0.1:9876
|
|
135
|
+
Launching ParaView GUI connected to cs://127.0.0.1:11111
|
|
131
136
|
```
|
|
132
137
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
embedded GUI Python environment is fully ready for pipeline edits. Use **Tools
|
|
136
|
-
-> Python Shell -> Run Script** for the live GUI bridge.
|
|
137
|
-
|
|
138
|
-
If `paraview-mcp-python` is installed into ParaView's Python environment, you
|
|
139
|
-
can also start the live GUI bridge directly from the Python Shell:
|
|
138
|
+
Keep that terminal running. Closing it stops the GUI, bridge, and local
|
|
139
|
+
`pvserver` session.
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
from bridge.gui_bridge import start_gui_bridge, stop_gui_bridge
|
|
143
|
-
start_gui_bridge()
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Leave ParaView open. MCP commands now modify this live GUI session.
|
|
147
|
-
|
|
148
|
-
To stop the bridge from the ParaView Python Shell:
|
|
149
|
-
|
|
150
|
-
```python
|
|
151
|
-
stop_gui_bridge()
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### 2. Optional: Start a Headless `pvpython` Bridge
|
|
141
|
+
### Optional: Start a Headless `pvpython` Bridge
|
|
155
142
|
|
|
156
143
|
Use this only when you do not need to modify an already-open ParaView GUI:
|
|
157
144
|
|
|
@@ -169,7 +156,7 @@ ParaView bridge ready on 127.0.0.1:9876
|
|
|
169
156
|
Keep that terminal running. This controls the `pvpython` session, not a GUI
|
|
170
157
|
window opened separately.
|
|
171
158
|
|
|
172
|
-
###
|
|
159
|
+
### Verify the Bridge Directly
|
|
173
160
|
|
|
174
161
|
Before involving an MCP client, send one raw bridge command:
|
|
175
162
|
|
|
@@ -191,7 +178,7 @@ Expected response shape:
|
|
|
191
178
|
|
|
192
179
|
If this fails, fix the bridge before configuring Codex or Claude.
|
|
193
180
|
|
|
194
|
-
###
|
|
181
|
+
### Register the MCP Server with Codex CLI
|
|
195
182
|
|
|
196
183
|
If you installed from PyPI:
|
|
197
184
|
|
|
@@ -210,7 +197,7 @@ codex mcp list
|
|
|
210
197
|
Codex starts the MCP server automatically when needed. The ParaView bridge
|
|
211
198
|
must already be running separately.
|
|
212
199
|
|
|
213
|
-
###
|
|
200
|
+
### Register with Claude Desktop
|
|
214
201
|
|
|
215
202
|
**Claude Desktop** — add to `claude_desktop_config.json`:
|
|
216
203
|
|
|
@@ -232,7 +219,7 @@ which paraview-mcp-server
|
|
|
232
219
|
|
|
233
220
|
Restart Claude Desktop after editing the config.
|
|
234
221
|
|
|
235
|
-
###
|
|
222
|
+
### Verify Through Your MCP Client
|
|
236
223
|
|
|
237
224
|
With the bridge still running, ask your MCP client:
|
|
238
225
|
|
|
@@ -241,8 +228,8 @@ List all sources in the current ParaView session.
|
|
|
241
228
|
```
|
|
242
229
|
|
|
243
230
|
The client should call `paraview_scene_list_sources` and return the current
|
|
244
|
-
ParaView pipeline sources from the
|
|
245
|
-
`
|
|
231
|
+
ParaView pipeline sources from the server-backed GUI session started by
|
|
232
|
+
`paraview-mcp-launch`.
|
|
246
233
|
|
|
247
234
|
---
|
|
248
235
|
|
|
@@ -452,16 +439,17 @@ paraview-mcp-server/
|
|
|
452
439
|
│ └── paraview_mcp_server/
|
|
453
440
|
│ ├── __init__.py # Re-exports main()
|
|
454
441
|
│ ├── server.py # FastMCP stdio server (31 tools)
|
|
442
|
+
│ ├── launcher.py # Starts pvserver, GUI, and bridge together
|
|
455
443
|
│ └── headless.py # Headless pvpython executor + job manager
|
|
456
444
|
├── bridge/
|
|
457
445
|
│ ├── __init__.py
|
|
458
446
|
│ ├── server.py # TCP socket bridge server
|
|
459
|
-
│ ├── gui_bridge.py #
|
|
447
|
+
│ ├── gui_bridge.py # Experimental in-GUI bridge helpers
|
|
460
448
|
│ ├── command_handler.py # Command registry + paraview.simple handlers (27 commands)
|
|
461
449
|
│ └── execution.py # trusted local python.execute helper
|
|
462
450
|
├── scripts/
|
|
463
|
-
│ ├── start_paraview_gui_bridge.py
|
|
464
451
|
│ ├── start_paraview_bridge.py
|
|
452
|
+
│ ├── start_paraview_gui_bridge.py
|
|
465
453
|
│ ├── paraview_bridge_request.py
|
|
466
454
|
│ └── library/ # Reusable pvpython snippets
|
|
467
455
|
│ ├── open_dataset.py
|
|
@@ -19,16 +19,16 @@
|
|
|
19
19
|
│ JSON / TCP localhost:9876
|
|
20
20
|
│ (newline-delimited JSON)
|
|
21
21
|
┌──────────▼─────────────┐
|
|
22
|
-
│ ParaView
|
|
23
|
-
│ bridge/ │
|
|
22
|
+
│ ParaView bridge │ Runs in pvpython
|
|
23
|
+
│ bridge/ │ connected to pvserver
|
|
24
24
|
│ · ParaViewBridgeServer│
|
|
25
25
|
│ · CommandHandler │ 27 registered commands
|
|
26
26
|
│ · execute_code() │
|
|
27
27
|
└──────────┬─────────────┘
|
|
28
|
-
│
|
|
28
|
+
│ ParaView client/server
|
|
29
29
|
┌──────────▼─────────────┐
|
|
30
|
-
│
|
|
31
|
-
│
|
|
30
|
+
│ pvserver │ Shared ParaView state
|
|
31
|
+
│ ParaView GUI client │
|
|
32
32
|
└────────────────────────┘
|
|
33
33
|
```
|
|
34
34
|
|
|
@@ -220,31 +220,32 @@ MCP client
|
|
|
220
220
|
|
|
221
221
|
## Lifecycle
|
|
222
222
|
|
|
223
|
-
1. User starts the
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
223
|
+
1. User starts the ParaView side with `paraview-mcp-launch`.
|
|
224
|
+
2. The launcher starts `pvserver --multi-clients`.
|
|
225
|
+
3. The launcher connects the ParaView GUI as the first client.
|
|
226
|
+
4. The launcher starts `pvpython scripts/start_paraview_bridge.py --server-host ...`,
|
|
227
|
+
which connects to the same `pvserver` and binds the MCP TCP bridge on
|
|
228
|
+
`127.0.0.1:9876`.
|
|
229
|
+
5. User starts an MCP client (Claude Desktop, Codex CLI, etc.)
|
|
230
|
+
6. MCP client spawns `paraview-mcp-server` over stdio.
|
|
231
|
+
7. MCP server connects to bridge on startup (or lazy-connects on first tool call).
|
|
232
|
+
8. User issues a natural language request → client calls an MCP tool → server
|
|
230
233
|
forwards as JSON → bridge dispatches → returns result.
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
9. User exits ParaView or presses Ctrl+C in the launcher terminal to stop the
|
|
235
|
+
GUI, bridge, and local `pvserver`.
|
|
233
236
|
|
|
234
237
|
---
|
|
235
238
|
|
|
236
239
|
## Configuration
|
|
237
240
|
|
|
238
|
-
###
|
|
241
|
+
### Server-backed GUI launcher
|
|
239
242
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
```text
|
|
243
|
-
scripts/start_paraview_gui_bridge.py
|
|
243
|
+
```bash
|
|
244
|
+
paraview-mcp-launch
|
|
244
245
|
```
|
|
245
246
|
|
|
246
|
-
|
|
247
|
-
|
|
247
|
+
This starts a local `pvserver`, connects the ParaView GUI, then connects a
|
|
248
|
+
`pvpython` bridge client to the same server-backed ParaView session.
|
|
248
249
|
|
|
249
250
|
### Headless Bridge
|
|
250
251
|
|
|
@@ -8,9 +8,10 @@ escape hatch for workflows that require more than the fixed tool set.
|
|
|
8
8
|
Two transports are supported:
|
|
9
9
|
|
|
10
10
|
1. **Bridge** (default) - code runs inside the active bridge process via exec().
|
|
11
|
-
For
|
|
12
|
-
`
|
|
13
|
-
the `pvpython scripts/start_paraview_bridge.py`
|
|
11
|
+
For GUI control started by `paraview-mcp-launch`, that process is a
|
|
12
|
+
`pvpython` client connected to the same `pvserver` as the ParaView GUI. For
|
|
13
|
+
headless control, it is the standalone `pvpython scripts/start_paraview_bridge.py`
|
|
14
|
+
process.
|
|
14
15
|
2. **Headless** - code runs in a separate pvpython subprocess via HeadlessPvpythonExecutor.
|
|
15
16
|
|
|
16
17
|
---
|
|
@@ -71,7 +72,8 @@ Headless transport adds:
|
|
|
71
72
|
### Trusted local execution
|
|
72
73
|
|
|
73
74
|
Bridge scripts run inside the local ParaView Python process and may import
|
|
74
|
-
normal Python modules. In
|
|
75
|
+
normal Python modules. In GUI mode, that is a `pvpython` bridge client attached
|
|
76
|
+
to the same `pvserver` as the ParaView GUI.
|
|
75
77
|
This is intentional: `paraview_python_exec` is the escape hatch for full
|
|
76
78
|
ParaView automation when the fixed MCP tool set is too small.
|
|
77
79
|
|
|
@@ -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.4"
|
|
8
8
|
description = "MCP server for controlling ParaView via AI assistants"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -62,6 +62,7 @@ include = [
|
|
|
62
62
|
|
|
63
63
|
[project.scripts]
|
|
64
64
|
paraview-mcp-server = "paraview_mcp_server:main"
|
|
65
|
+
paraview-mcp-launch = "paraview_mcp_server.launcher:main"
|
|
65
66
|
|
|
66
67
|
[tool.pytest.ini_options]
|
|
67
68
|
asyncio_mode = "strict"
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
Run this with pvpython:
|
|
5
5
|
|
|
6
6
|
pvpython scripts/start_paraview_bridge.py [--host 127.0.0.1] [--port 9876]
|
|
7
|
+
pvpython scripts/start_paraview_bridge.py --server-host 127.0.0.1 --server-port 11111
|
|
7
8
|
|
|
8
9
|
The bridge will listen for JSON commands from the paraview-mcp-server process.
|
|
9
10
|
"""
|
|
@@ -28,6 +29,8 @@ def parse_args() -> argparse.Namespace:
|
|
|
28
29
|
parser = argparse.ArgumentParser(description="Start the ParaView TCP bridge server.")
|
|
29
30
|
parser.add_argument("--host", default="127.0.0.1", help="Host to bind to")
|
|
30
31
|
parser.add_argument("--port", type=int, default=9876, help="Port to listen on")
|
|
32
|
+
parser.add_argument("--server-host", help="Optional pvserver host to connect to before starting the bridge")
|
|
33
|
+
parser.add_argument("--server-port", type=int, default=11111, help="pvserver port used with --server-host")
|
|
31
34
|
return parser.parse_args()
|
|
32
35
|
|
|
33
36
|
|
|
@@ -43,13 +46,28 @@ def main() -> None:
|
|
|
43
46
|
|
|
44
47
|
from bridge.server import ParaViewBridgeServer
|
|
45
48
|
|
|
49
|
+
process_server_events = None
|
|
50
|
+
if args.server_host:
|
|
51
|
+
from paraview.simple import Connect
|
|
52
|
+
|
|
53
|
+
logger.info("Connecting bridge runtime to pvserver at %s:%s.", args.server_host, args.server_port)
|
|
54
|
+
Connect(args.server_host, args.server_port)
|
|
55
|
+
try:
|
|
56
|
+
from paraview.collaboration import processServerEvents
|
|
57
|
+
|
|
58
|
+
process_server_events = processServerEvents
|
|
59
|
+
except ImportError:
|
|
60
|
+
process_server_events = None
|
|
61
|
+
|
|
46
62
|
server = ParaViewBridgeServer(host=args.host, port=args.port)
|
|
47
63
|
server.start()
|
|
48
64
|
|
|
49
65
|
logger.info("ParaView bridge ready on %s:%s — press Ctrl+C to stop.", args.host, args.port)
|
|
50
66
|
try:
|
|
51
67
|
while True:
|
|
52
|
-
|
|
68
|
+
if process_server_events is not None:
|
|
69
|
+
process_server_events()
|
|
70
|
+
time.sleep(0.1)
|
|
53
71
|
except KeyboardInterrupt:
|
|
54
72
|
logger.info("Shutting down ParaView bridge.")
|
|
55
73
|
server.stop()
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Launch a server-backed ParaView GUI session with the MCP bridge attached."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import socket
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _repo_root() -> Path:
|
|
16
|
+
package_root = Path(__file__).resolve().parents[2]
|
|
17
|
+
if (package_root / "scripts" / "start_paraview_bridge.py").is_file():
|
|
18
|
+
return package_root
|
|
19
|
+
return Path.cwd()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _wait_for_port(host: str, port: int, *, timeout: float, name: str) -> None:
|
|
23
|
+
deadline = time.monotonic() + timeout
|
|
24
|
+
last_error: OSError | None = None
|
|
25
|
+
while time.monotonic() < deadline:
|
|
26
|
+
try:
|
|
27
|
+
with socket.create_connection((host, port), timeout=0.5):
|
|
28
|
+
return
|
|
29
|
+
except OSError as exc:
|
|
30
|
+
last_error = exc
|
|
31
|
+
time.sleep(0.2)
|
|
32
|
+
raise RuntimeError(f"Timed out waiting for {name} on {host}:{port}: {last_error}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _wait_for_listen_port(port: int, *, timeout: float, name: str) -> None:
|
|
36
|
+
deadline = time.monotonic() + timeout
|
|
37
|
+
needle = f":{port:04X}"
|
|
38
|
+
while time.monotonic() < deadline:
|
|
39
|
+
try:
|
|
40
|
+
lines = Path("/proc/net/tcp").read_text(encoding="utf-8").splitlines()
|
|
41
|
+
except OSError:
|
|
42
|
+
lines = []
|
|
43
|
+
for line in lines[1:]:
|
|
44
|
+
columns = line.split()
|
|
45
|
+
if len(columns) >= 4 and columns[1].endswith(needle) and columns[3] == "0A":
|
|
46
|
+
return
|
|
47
|
+
time.sleep(0.2)
|
|
48
|
+
raise RuntimeError(f"Timed out waiting for {name} to listen on port {port}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _terminate(proc: subprocess.Popen[bytes] | None) -> None:
|
|
52
|
+
if proc is None or proc.poll() is not None:
|
|
53
|
+
return
|
|
54
|
+
proc.terminate()
|
|
55
|
+
try:
|
|
56
|
+
proc.wait(timeout=5)
|
|
57
|
+
except subprocess.TimeoutExpired:
|
|
58
|
+
proc.kill()
|
|
59
|
+
proc.wait(timeout=5)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
63
|
+
parser = argparse.ArgumentParser(
|
|
64
|
+
description=(
|
|
65
|
+
"Start pvserver, attach the ParaView MCP bridge, and launch a ParaView GUI "
|
|
66
|
+
"connected to the same server-backed session."
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
parser.add_argument("--paraview", default=os.environ.get("PARAVIEW_BIN", "paraview"), help="ParaView executable")
|
|
70
|
+
parser.add_argument("--pvserver", default=os.environ.get("PVSERVER_BIN", "pvserver"), help="pvserver executable")
|
|
71
|
+
parser.add_argument("--pvpython", default=os.environ.get("PVPYTHON_BIN", "pvpython"), help="pvpython executable")
|
|
72
|
+
parser.add_argument("--server-host", default="127.0.0.1", help="Host used by local clients to reach pvserver")
|
|
73
|
+
parser.add_argument("--server-port", type=int, default=11111, help="pvserver port")
|
|
74
|
+
parser.add_argument("--bridge-host", default="127.0.0.1", help="MCP bridge bind host")
|
|
75
|
+
parser.add_argument("--bridge-port", type=int, default=9876, help="MCP bridge bind port")
|
|
76
|
+
parser.add_argument(
|
|
77
|
+
"paraview_args",
|
|
78
|
+
nargs=argparse.REMAINDER,
|
|
79
|
+
help="Extra arguments passed to ParaView. Prefix them with --, for example: -- --data file.vtu",
|
|
80
|
+
)
|
|
81
|
+
return parser.parse_args(argv)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main(argv: list[str] | None = None) -> int:
|
|
85
|
+
args = parse_args(argv)
|
|
86
|
+
repo_root = _repo_root()
|
|
87
|
+
bridge_script = repo_root / "scripts" / "start_paraview_bridge.py"
|
|
88
|
+
if not bridge_script.is_file():
|
|
89
|
+
raise SystemExit(f"Could not find bridge script: {bridge_script}")
|
|
90
|
+
|
|
91
|
+
paraview = shutil.which(args.paraview) or args.paraview
|
|
92
|
+
pvserver = shutil.which(args.pvserver) or args.pvserver
|
|
93
|
+
pvpython = shutil.which(args.pvpython) or args.pvpython
|
|
94
|
+
extra_args = list(args.paraview_args)
|
|
95
|
+
if extra_args[:1] == ["--"]:
|
|
96
|
+
extra_args = extra_args[1:]
|
|
97
|
+
|
|
98
|
+
server_proc: subprocess.Popen[bytes] | None = None
|
|
99
|
+
bridge_proc: subprocess.Popen[bytes] | None = None
|
|
100
|
+
gui_proc: subprocess.Popen[bytes] | None = None
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
server_proc = subprocess.Popen(
|
|
104
|
+
[
|
|
105
|
+
pvserver,
|
|
106
|
+
"--multi-clients",
|
|
107
|
+
f"--server-port={args.server_port}",
|
|
108
|
+
"--bind-address=127.0.0.1",
|
|
109
|
+
]
|
|
110
|
+
)
|
|
111
|
+
_wait_for_listen_port(args.server_port, timeout=20, name="pvserver")
|
|
112
|
+
|
|
113
|
+
print(f"Launching ParaView GUI connected to cs://{args.server_host}:{args.server_port}", flush=True)
|
|
114
|
+
gui_proc = subprocess.Popen(
|
|
115
|
+
[
|
|
116
|
+
paraview,
|
|
117
|
+
"--server-url",
|
|
118
|
+
f"cs://{args.server_host}:{args.server_port}",
|
|
119
|
+
*extra_args,
|
|
120
|
+
]
|
|
121
|
+
)
|
|
122
|
+
time.sleep(3)
|
|
123
|
+
|
|
124
|
+
bridge_proc = subprocess.Popen(
|
|
125
|
+
[
|
|
126
|
+
pvpython,
|
|
127
|
+
str(bridge_script),
|
|
128
|
+
"--host",
|
|
129
|
+
args.bridge_host,
|
|
130
|
+
"--port",
|
|
131
|
+
str(args.bridge_port),
|
|
132
|
+
"--server-host",
|
|
133
|
+
args.server_host,
|
|
134
|
+
"--server-port",
|
|
135
|
+
str(args.server_port),
|
|
136
|
+
],
|
|
137
|
+
cwd=str(repo_root),
|
|
138
|
+
)
|
|
139
|
+
_wait_for_port(args.bridge_host, args.bridge_port, timeout=20, name="ParaView MCP bridge")
|
|
140
|
+
print(f"ParaView MCP bridge ready on {args.bridge_host}:{args.bridge_port}", flush=True)
|
|
141
|
+
return gui_proc.wait()
|
|
142
|
+
except KeyboardInterrupt:
|
|
143
|
+
return 130
|
|
144
|
+
finally:
|
|
145
|
+
_terminate(gui_proc)
|
|
146
|
+
_terminate(bridge_proc)
|
|
147
|
+
_terminate(server_proc)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
if __name__ == "__main__":
|
|
151
|
+
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Tests for the ParaView MCP launcher."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from unittest.mock import MagicMock, patch
|
|
7
|
+
|
|
8
|
+
from paraview_mcp_server import launcher
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_launcher_starts_gui_before_bridge():
|
|
12
|
+
calls = []
|
|
13
|
+
|
|
14
|
+
def fake_popen(cmd, **kwargs):
|
|
15
|
+
proc = MagicMock()
|
|
16
|
+
proc.poll.return_value = None
|
|
17
|
+
proc.wait.return_value = 0
|
|
18
|
+
proc.cmd = cmd
|
|
19
|
+
calls.append((cmd, kwargs, proc))
|
|
20
|
+
return proc
|
|
21
|
+
|
|
22
|
+
with (
|
|
23
|
+
patch("paraview_mcp_server.launcher._repo_root", return_value=Path.cwd()),
|
|
24
|
+
patch("paraview_mcp_server.launcher._wait_for_listen_port"),
|
|
25
|
+
patch("paraview_mcp_server.launcher._wait_for_port"),
|
|
26
|
+
patch("paraview_mcp_server.launcher.time.sleep"),
|
|
27
|
+
patch("paraview_mcp_server.launcher.subprocess.Popen", side_effect=fake_popen),
|
|
28
|
+
patch("paraview_mcp_server.launcher.shutil.which", side_effect=lambda value: value),
|
|
29
|
+
):
|
|
30
|
+
result = launcher.main([])
|
|
31
|
+
|
|
32
|
+
assert result == 0
|
|
33
|
+
assert calls[0][0][0] == "pvserver"
|
|
34
|
+
assert "--multi-clients" in calls[0][0]
|
|
35
|
+
assert calls[1][0][0] == "paraview"
|
|
36
|
+
assert "--server-url" in calls[1][0]
|
|
37
|
+
assert calls[2][0][0] == "pvpython"
|
|
38
|
+
assert "--server-host" in calls[2][0]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_launcher_strips_separator_from_paraview_args():
|
|
42
|
+
calls = []
|
|
43
|
+
|
|
44
|
+
def fake_popen(cmd, **kwargs):
|
|
45
|
+
proc = MagicMock()
|
|
46
|
+
proc.poll.return_value = None
|
|
47
|
+
proc.wait.return_value = 0
|
|
48
|
+
calls.append(cmd)
|
|
49
|
+
return proc
|
|
50
|
+
|
|
51
|
+
with (
|
|
52
|
+
patch("paraview_mcp_server.launcher._repo_root", return_value=Path.cwd()),
|
|
53
|
+
patch("paraview_mcp_server.launcher._wait_for_listen_port"),
|
|
54
|
+
patch("paraview_mcp_server.launcher._wait_for_port"),
|
|
55
|
+
patch("paraview_mcp_server.launcher.time.sleep"),
|
|
56
|
+
patch("paraview_mcp_server.launcher.subprocess.Popen", side_effect=fake_popen),
|
|
57
|
+
patch("paraview_mcp_server.launcher.shutil.which", side_effect=lambda value: value),
|
|
58
|
+
):
|
|
59
|
+
launcher.main(["--", "--data", "disk.vtu"])
|
|
60
|
+
|
|
61
|
+
assert "--data" in calls[1]
|
|
62
|
+
assert "disk.vtu" in calls[1]
|
|
63
|
+
assert "--" not in calls[1]
|
|
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
|
{paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/start_paraview_gui_bridge.py
RENAMED
|
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
|