paraview-mcp-python 0.1.0__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/PKG-INFO +6 -6
  2. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/README.md +5 -5
  3. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/bridge/execution.py +2 -78
  4. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/docs/architecture.md +2 -2
  5. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/docs/python-execute-design.md +6 -6
  6. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/pyproject.toml +1 -1
  7. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/tests/test_command_handler.py +7 -6
  8. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/.gitignore +0 -0
  9. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/LICENSE +0 -0
  10. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/bridge/__init__.py +0 -0
  11. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/bridge/command_handler.py +0 -0
  12. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/bridge/models.py +0 -0
  13. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/bridge/server.py +0 -0
  14. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/scripts/library/color_by.py +0 -0
  15. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/scripts/library/create_contour.py +0 -0
  16. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/scripts/library/create_slice.py +0 -0
  17. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/scripts/library/open_dataset.py +0 -0
  18. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/scripts/library/reset_camera.py +0 -0
  19. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/scripts/library/save_screenshot.py +0 -0
  20. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/scripts/paraview_bridge_request.py +0 -0
  21. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/scripts/start_paraview_bridge.py +0 -0
  22. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/src/paraview_mcp_server/__init__.py +0 -0
  23. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/src/paraview_mcp_server/headless.py +0 -0
  24. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/src/paraview_mcp_server/server.py +0 -0
  25. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/tests/__init__.py +0 -0
  26. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/tests/test_bridge_server.py +0 -0
  27. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/tests/test_protocol.py +0 -0
  28. {paraview_mcp_python-0.1.0 → paraview_mcp_python-0.1.1}/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.0
3
+ Version: 0.1.1
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
@@ -259,10 +259,10 @@ Async jobs run in a separate headless `pvpython` process via `HeadlessPvpythonEx
259
259
 
260
260
  ---
261
261
 
262
- ## Safety model
262
+ ## Python execution trust model
263
263
 
264
- - **Blocked modules** — scripts cannot import `subprocess`, `shutil`, `socket`, `ctypes`,
265
- `multiprocessing`, `webbrowser`, or several network-facing stdlib modules.
264
+ - **Trusted local execution** — `paraview_python_exec` can run arbitrary Python available to `pvpython`,
265
+ including imports and full `paraview.simple` workflows.
266
266
  - **Output bounding** — stdout/stderr capped at **50 KB**.
267
267
  - **Cooperative timeout** — default 30 seconds per script execution.
268
268
  - **Script path validation** — optionally restrict execution to approved root directories.
@@ -312,7 +312,7 @@ paraview-mcp-server/
312
312
  │ ├── __init__.py
313
313
  │ ├── server.py # TCP socket bridge server
314
314
  │ ├── command_handler.py # Command registry + paraview.simple handlers (27 commands)
315
- │ └── execution.py # python.execute helper with safety controls
315
+ │ └── execution.py # trusted local python.execute helper
316
316
  ├── scripts/
317
317
  │ ├── start_paraview_bridge.py
318
318
  │ ├── paraview_bridge_request.py
@@ -329,7 +329,7 @@ paraview-mcp-server/
329
329
  └── tests/
330
330
  ├── test_server.py # 31 tools, connection, headless, async jobs
331
331
  ├── test_protocol.py # Wire encoding, fake bridge integration
332
- └── test_command_handler.py # All 27 handlers + safety controls
332
+ └── test_command_handler.py # All 27 handlers + execution controls
333
333
  ```
334
334
 
335
335
  ---
@@ -226,10 +226,10 @@ Async jobs run in a separate headless `pvpython` process via `HeadlessPvpythonEx
226
226
 
227
227
  ---
228
228
 
229
- ## Safety model
229
+ ## Python execution trust model
230
230
 
231
- - **Blocked modules** — scripts cannot import `subprocess`, `shutil`, `socket`, `ctypes`,
232
- `multiprocessing`, `webbrowser`, or several network-facing stdlib modules.
231
+ - **Trusted local execution** — `paraview_python_exec` can run arbitrary Python available to `pvpython`,
232
+ including imports and full `paraview.simple` workflows.
233
233
  - **Output bounding** — stdout/stderr capped at **50 KB**.
234
234
  - **Cooperative timeout** — default 30 seconds per script execution.
235
235
  - **Script path validation** — optionally restrict execution to approved root directories.
@@ -279,7 +279,7 @@ paraview-mcp-server/
279
279
  │ ├── __init__.py
280
280
  │ ├── server.py # TCP socket bridge server
281
281
  │ ├── command_handler.py # Command registry + paraview.simple handlers (27 commands)
282
- │ └── execution.py # python.execute helper with safety controls
282
+ │ └── execution.py # trusted local python.execute helper
283
283
  ├── scripts/
284
284
  │ ├── start_paraview_bridge.py
285
285
  │ ├── paraview_bridge_request.py
@@ -296,7 +296,7 @@ paraview-mcp-server/
296
296
  └── tests/
297
297
  ├── test_server.py # 31 tools, connection, headless, async jobs
298
298
  ├── test_protocol.py # Wire encoding, fake bridge integration
299
- └── test_command_handler.py # All 27 handlers + safety controls
299
+ └── test_command_handler.py # All 27 handlers + execution controls
300
300
  ```
301
301
 
302
302
  ---
@@ -1,9 +1,7 @@
1
1
  """Python script execution helper for the ParaView bridge.
2
2
 
3
- Safety controls
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()
@@ -68,7 +68,7 @@ Alternative headless transport (no bridge required):
68
68
  |---|---|
69
69
  | `server.py` | Threaded TCP socket server, newline-delimited JSON framing |
70
70
  | `command_handler.py` | Command registry mapping 27 command names to `paraview.simple` calls |
71
- | `execution.py` | `execute_code()` — exec-based Python execution with safety controls (blocked modules, timeout, output cap, script path validation) |
71
+ | `execution.py` | `execute_code()` — trusted local Python execution with timeout, output cap, and optional script path validation |
72
72
  | `__init__.py` | Package marker |
73
73
 
74
74
  ---
@@ -283,7 +283,7 @@ paraview-mcp-server/
283
283
  │ ├── __init__.py
284
284
  │ ├── server.py # TCP bridge server
285
285
  │ ├── command_handler.py # 27-command registry
286
- │ └── execution.py # python.execute with safety controls
286
+ │ └── execution.py # trusted local python.execute helper
287
287
  ├── scripts/
288
288
  │ ├── start_paraview_bridge.py
289
289
  │ ├── paraview_bridge_request.py
@@ -63,13 +63,13 @@ Headless transport adds:
63
63
 
64
64
  ---
65
65
 
66
- ## Safety model
66
+ ## Python Execution Trust Model
67
67
 
68
- ### Blocked modules
68
+ ### Trusted local execution
69
69
 
70
- The following modules are blocked during script execution:
71
- subprocess, shutil, socket, ctypes, multiprocessing, webbrowser,
72
- http.server, xmlrpc.server, ftplib, smtplib, telnetlib.
70
+ Bridge scripts run inside the local `pvpython` process and may import normal
71
+ Python modules. This is intentional: `paraview_python_exec` is the escape hatch
72
+ for full ParaView automation when the fixed MCP tool set is too small.
73
73
 
74
74
  ### Output bounding
75
75
 
@@ -134,5 +134,5 @@ Use paraview_python_exec_async for long-running scripts:
134
134
 
135
135
  - Cancellation token (bridge transport) for cooperative cancellation
136
136
  - Script library registry for referencing scripts by name
137
- - Sandboxed execution beyond the blocked-module list
137
+ - Optional sandboxed execution mode for deployments that need stricter isolation
138
138
  - 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.0"
7
+ version = "0.1.1"
8
8
  description = "MCP server for controlling ParaView via AI assistants"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -532,15 +532,16 @@ class TestPythonExecuteHandler:
532
532
  assert "timeout" in result["error"].lower()
533
533
 
534
534
 
535
- class TestExecutionSafety:
536
- """Test the safety controls in bridge/execution.py."""
535
+ class TestExecutionControls:
536
+ """Test the execution controls in bridge/execution.py."""
537
537
 
538
- def test_blocked_module_import(self):
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
- assert result["error"] is not None
543
- assert "blocked" in result["error"].lower() or "subprocess" in result["error"]
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