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.
Files changed (33) hide show
  1. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/PKG-INFO +41 -53
  2. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/README.md +40 -52
  3. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/docs/architecture.md +22 -21
  4. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/docs/python-execute-design.md +6 -4
  5. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/pyproject.toml +2 -1
  6. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/start_paraview_bridge.py +19 -1
  7. paraview_mcp_python-0.1.4/src/paraview_mcp_server/launcher.py +151 -0
  8. paraview_mcp_python-0.1.4/tests/test_launcher.py +63 -0
  9. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/.gitignore +0 -0
  10. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/LICENSE +0 -0
  11. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/bridge/__init__.py +0 -0
  12. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/bridge/command_handler.py +0 -0
  13. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/bridge/execution.py +0 -0
  14. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/bridge/gui_bridge.py +0 -0
  15. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/bridge/models.py +0 -0
  16. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/bridge/server.py +0 -0
  17. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/library/color_by.py +0 -0
  18. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/library/create_contour.py +0 -0
  19. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/library/create_slice.py +0 -0
  20. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/library/open_dataset.py +0 -0
  21. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/library/reset_camera.py +0 -0
  22. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/library/save_screenshot.py +0 -0
  23. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/paraview_bridge_request.py +0 -0
  24. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/scripts/start_paraview_gui_bridge.py +0 -0
  25. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/src/paraview_mcp_server/__init__.py +0 -0
  26. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/src/paraview_mcp_server/headless.py +0 -0
  27. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/src/paraview_mcp_server/server.py +0 -0
  28. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/tests/__init__.py +0 -0
  29. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/tests/test_bridge_server.py +0 -0
  30. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/tests/test_command_handler.py +0 -0
  31. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/tests/test_gui_bridge.py +0 -0
  32. {paraview_mcp_python-0.1.3 → paraview_mcp_python-0.1.4}/tests/test_protocol.py +0 -0
  33. {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
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 three moving pieces:
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 GUI bridge** | Already-open ParaView GUI process | Receives TCP JSON commands and executes `paraview.simple` operations in that live GUI session. |
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
- 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`.
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
- live GUI process
78
+ pvpython client
76
79
  └──────────┬─────────────┘
77
-
80
+ ParaView client/server
78
81
  ┌──────────▼─────────────┐
79
- paraview.simple
80
- │ ParaView runtime
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 GUI
87
- unless the bridge is running inside that GUI process.
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 inside open ParaView GUI -> live GUI session
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 pieces in this order.
151
+ Start the ParaView side with one command:
147
152
 
148
- ### 1. Start the ParaView GUI Bridge
149
-
150
- Open ParaView, then run the GUI bridge script from the Python Shell:
153
+ ```bash
154
+ cd /path/to/paraview-mcp-server
155
+ paraview-mcp-launch
156
+ ```
151
157
 
152
- 1. In ParaView, open **Tools -> Python Shell**.
153
- 2. Click **Run Script**.
154
- 3. Select:
158
+ For a local editable checkout:
155
159
 
156
- ```text
157
- /absolute/path/to/paraview-mcp-server/scripts/start_paraview_gui_bridge.py
160
+ ```bash
161
+ .venv/bin/paraview-mcp-launch
158
162
  ```
159
163
 
160
164
  Expected output:
161
165
 
162
166
  ```text
163
- ParaView MCP GUI bridge started on 127.0.0.1:9876
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
- Do not start the live GUI bridge with `paraview --script
167
- scripts/start_paraview_gui_bridge.py`. ParaView runs startup scripts before the
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
- ```python
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
- ### 3. Verify the Bridge Directly
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
- ### 4. Register the MCP Server with Codex CLI
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
- ### 5. Register with Claude Desktop
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
- ### 6. Verify Through Your MCP Client
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 live GUI session if you started
278
- `start_paraview_gui_bridge.py`.
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 # Non-blocking live GUI bridge lifecycle
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 three moving pieces:
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 GUI bridge** | Already-open ParaView GUI process | Receives TCP JSON commands and executes `paraview.simple` operations in that live GUI session. |
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
- 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`.
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
- live GUI process
45
+ pvpython client
43
46
  └──────────┬─────────────┘
44
-
47
+ ParaView client/server
45
48
  ┌──────────▼─────────────┐
46
- paraview.simple
47
- │ ParaView runtime
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 GUI
54
- unless the bridge is running inside that GUI process.
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 inside open ParaView GUI -> live GUI session
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 pieces in this order.
118
+ Start the ParaView side with one command:
114
119
 
115
- ### 1. Start the ParaView GUI Bridge
116
-
117
- Open ParaView, then run the GUI bridge script from the Python Shell:
120
+ ```bash
121
+ cd /path/to/paraview-mcp-server
122
+ paraview-mcp-launch
123
+ ```
118
124
 
119
- 1. In ParaView, open **Tools -> Python Shell**.
120
- 2. Click **Run Script**.
121
- 3. Select:
125
+ For a local editable checkout:
122
126
 
123
- ```text
124
- /absolute/path/to/paraview-mcp-server/scripts/start_paraview_gui_bridge.py
127
+ ```bash
128
+ .venv/bin/paraview-mcp-launch
125
129
  ```
126
130
 
127
131
  Expected output:
128
132
 
129
133
  ```text
130
- ParaView MCP GUI bridge started on 127.0.0.1:9876
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
- Do not start the live GUI bridge with `paraview --script
134
- scripts/start_paraview_gui_bridge.py`. ParaView runs startup scripts before the
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
- ```python
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
- ### 3. Verify the Bridge Directly
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
- ### 4. Register the MCP Server with Codex CLI
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
- ### 5. Register with Claude Desktop
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
- ### 6. Verify Through Your MCP Client
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 live GUI session if you started
245
- `start_paraview_gui_bridge.py`.
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 # Non-blocking live GUI bridge lifecycle
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 GUI bridge │ Runs inside the open ParaView GUI
23
- │ bridge/ │ via scripts/start_paraview_gui_bridge.py
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
- paraview.simple Live ParaView GUI session
31
- servermanager
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 live GUI bridge from ParaView: **Tools Python Shell → Run Script**,
224
- selecting `scripts/start_paraview_gui_bridge.py`.
225
- 2. Bridge server binds to `127.0.0.1:9876` and listens for TCP connections.
226
- 3. User starts an MCP client (Claude Desktop, Codex CLI, etc.)
227
- 4. MCP client spawns `paraview-mcp-server` over stdio.
228
- 5. MCP server connects to bridge on startup (or lazy-connects on first tool call).
229
- 6. User issues a natural language request → client calls an MCP tool → server
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
- 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.
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
- ### Live GUI Bridge
241
+ ### Server-backed GUI launcher
239
242
 
240
- Run this from ParaView's Python Shell with **Run Script**:
241
-
242
- ```text
243
- scripts/start_paraview_gui_bridge.py
243
+ ```bash
244
+ paraview-mcp-launch
244
245
  ```
245
246
 
246
- The script starts a background TCP server in the open ParaView GUI process and
247
- returns immediately.
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 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
+ 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 live GUI mode, that is the open ParaView GUI process.
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.3"
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
- time.sleep(1)
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]