ovito-mcp 2026.1.dev2__tar.gz → 2026.1.dev3__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.
@@ -0,0 +1,195 @@
1
+ Metadata-Version: 2.4
2
+ Name: ovito-mcp
3
+ Version: 2026.1.dev3
4
+ Summary: MCP server for interactive work in OVITO Pro
5
+ Project-URL: Homepage, https://www.ovito.org
6
+ Project-URL: Documentation, https://docs.ovito.org/reference/data_inspector/coding_agent.html
7
+ Project-URL: Repository, https://gitlab.com/ovito-org/ovito-mcp-server
8
+ Project-URL: Bug Tracker, https://gitlab.com/ovito-org/ovito-mcp-server/-/issues
9
+ Author-email: OVITO GmbH <support@ovito.org>
10
+ License: Proprietary
11
+ License-File: LICENSE
12
+ Keywords: mcp,molecular-dynamics,ovito,visualization
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: Other/Proprietary License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Scientific/Engineering :: Visualization
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: jupyter-client>=8.0
25
+ Requires-Dist: jupyter-core>=5.0
26
+ Requires-Dist: mcp>=1.2.0
27
+ Requires-Dist: numpy>=1.26
28
+ Requires-Dist: sentence-transformers>=5.6.0
29
+ Provides-Extra: test
30
+ Requires-Dist: pytest; extra == 'test'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # OVITO Pro MCP Server
34
+
35
+ Connects an AI coding agent to a running **OVITO Pro** session, so you can work with your
36
+ simulation data by describing what you want in plain language. The agent operates inside
37
+ the session you already have open — it can load files, build and adjust pipelines, analyze
38
+ results, and look things up in the OVITO documentation, while you watch each step happen
39
+ live in the GUI and keep working in the same window yourself.
40
+
41
+ Works with Claude Code, GitHub Copilot CLI, OpenAI Codex, OpenCode, Google Antigravity, and
42
+ other coding agents that support MCP.
43
+
44
+ ## Requirements
45
+
46
+ - **OVITO Pro 3.16 or later**, with a valid license
47
+ - **Python 3.10 or later**
48
+ - A supported coding agent, installed and set up separately (see
49
+ [Agent configuration](#agent-configuration))
50
+
51
+ ## Installation
52
+
53
+ Install the server:
54
+
55
+ ```sh
56
+ pip install ovito-mcp
57
+ ```
58
+
59
+ Verify the installation and check the version with:
60
+
61
+ ```sh
62
+ ovito-mcp --version
63
+ ```
64
+
65
+ The documentation-search tools provided by the OVITO MCP server use two small sentence-transformers models that are downloaded
66
+ once from the Hugging Face Hub (~180 MB total) and cached locally. The server downloads them
67
+ automatically in the background the first time it starts, and never touches the network for
68
+ them again. If you would rather do this one-time download up front — recommended on a slow or
69
+ restricted network, so it can't slow down the first search — run:
70
+
71
+ ```sh
72
+ ovito-mcp --download-models
73
+ ```
74
+
75
+ Then register it with your coding agent, using the **absolute path** to the `ovito-mcp` console
76
+ script. For Claude Code:
77
+
78
+ ```powershell
79
+ # Windows (PowerShell)
80
+ claude mcp add ovito-mcp --scope user -- (Get-Command ovito-mcp).Source
81
+ ```
82
+
83
+ ```sh
84
+ # macOS / Linux
85
+ claude mcp add ovito-mcp --scope user -- "$(which ovito-mcp)"
86
+ ```
87
+
88
+ See [Agent configuration](#agent-configuration) below for the other agents.
89
+
90
+ ## Using it
91
+
92
+ In OVITO Pro, open the
93
+ [**AI Agent**](https://docs.ovito.org/reference/data_inspector/coding_agent.html) tab in
94
+ the data inspector panel at the bottom of the main window, pick your coding agent, and
95
+ press **Start**.
96
+
97
+ OVITO launches the agent and connects it to the session in front of you. Anything the agent
98
+ does appears live in the GUI, and you can keep interacting with the same scene while it
99
+ works.
100
+
101
+ No further setup is needed — the agent finds everything it needs from the running OVITO Pro
102
+ session.
103
+
104
+ See the [AI Agent manual page](https://docs.ovito.org/reference/data_inspector/coding_agent.html)
105
+ for the OVITO-side documentation, including security considerations.
106
+
107
+ ## Agent configuration
108
+
109
+ Register `ovito-mcp` by its **absolute path** in the configurations below. Find that path with:
110
+
111
+ ```powershell
112
+ # Windows (PowerShell)
113
+ (Get-Command ovito-mcp).Source
114
+ ```
115
+
116
+ ```sh
117
+ # macOS / Linux
118
+ which ovito-mcp
119
+ ```
120
+
121
+ and substitute it for `/path/to/ovito-mcp` in the snippets below.
122
+
123
+ ### Claude Code / Claude Desktop
124
+
125
+ Add this to your `~/.claude.json`:
126
+
127
+ ```json
128
+ "mcpServers": {
129
+ "ovito-mcp": {
130
+ "type": "stdio",
131
+ "command": "/path/to/ovito-mcp"
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### OpenCode
137
+
138
+ Add this to your `~/.opencode.json`:
139
+
140
+ ```json
141
+ "mcp": {
142
+ "ovito-mcp": {
143
+ "type": "local",
144
+ "command": ["/path/to/ovito-mcp"],
145
+ "enabled": true
146
+ }
147
+ }
148
+ ```
149
+
150
+ ### Google Antigravity
151
+
152
+ Add this to your `~/.antigravity/config.json`:
153
+
154
+ ```json
155
+ "mcpServers": {
156
+ "ovito-mcp": {
157
+ "command": "/path/to/ovito-mcp"
158
+ }
159
+ }
160
+ ```
161
+
162
+ ### OpenAI Codex
163
+
164
+ Add this to your `$HOME/.codex/config.toml`:
165
+
166
+ ```toml
167
+ [mcp_servers.ovito-mcp]
168
+ command = "/path/to/ovito-mcp"
169
+ ```
170
+
171
+ ### GitHub Copilot
172
+
173
+ Add this to your `~/.copilot/mcp-config.json`:
174
+
175
+ ```json
176
+ "mcpServers": {
177
+ "ovito-mcp": {
178
+ "command": "/path/to/ovito-mcp"
179
+ }
180
+ }
181
+ ```
182
+
183
+ ## Advanced configuration
184
+
185
+ Neither setting below is needed for normal use. Both are environment variables, intended
186
+ for development and unusual setups.
187
+
188
+ | Env var | Purpose |
189
+ | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
190
+ | `OVITO_EXE` | Path to the OVITO Pro executable (`ovito.exe` on Windows, `ovito` on Linux, `Ovito.app/Contents/MacOS/ovito` on macOS). Only needed to let the agent start OVITO Pro itself, instead of connecting to a session you already have running. |
191
+ | `OVITO_RAG_DB` | Path to an alternative documentation database. Overrides the one that ships with OVITO Pro, for developing against a custom build. |
192
+
193
+ ## Support
194
+
195
+ Questions and bug reports: <support@ovito.org>
@@ -0,0 +1,163 @@
1
+ # OVITO Pro MCP Server
2
+
3
+ Connects an AI coding agent to a running **OVITO Pro** session, so you can work with your
4
+ simulation data by describing what you want in plain language. The agent operates inside
5
+ the session you already have open — it can load files, build and adjust pipelines, analyze
6
+ results, and look things up in the OVITO documentation, while you watch each step happen
7
+ live in the GUI and keep working in the same window yourself.
8
+
9
+ Works with Claude Code, GitHub Copilot CLI, OpenAI Codex, OpenCode, Google Antigravity, and
10
+ other coding agents that support MCP.
11
+
12
+ ## Requirements
13
+
14
+ - **OVITO Pro 3.16 or later**, with a valid license
15
+ - **Python 3.10 or later**
16
+ - A supported coding agent, installed and set up separately (see
17
+ [Agent configuration](#agent-configuration))
18
+
19
+ ## Installation
20
+
21
+ Install the server:
22
+
23
+ ```sh
24
+ pip install ovito-mcp
25
+ ```
26
+
27
+ Verify the installation and check the version with:
28
+
29
+ ```sh
30
+ ovito-mcp --version
31
+ ```
32
+
33
+ The documentation-search tools provided by the OVITO MCP server use two small sentence-transformers models that are downloaded
34
+ once from the Hugging Face Hub (~180 MB total) and cached locally. The server downloads them
35
+ automatically in the background the first time it starts, and never touches the network for
36
+ them again. If you would rather do this one-time download up front — recommended on a slow or
37
+ restricted network, so it can't slow down the first search — run:
38
+
39
+ ```sh
40
+ ovito-mcp --download-models
41
+ ```
42
+
43
+ Then register it with your coding agent, using the **absolute path** to the `ovito-mcp` console
44
+ script. For Claude Code:
45
+
46
+ ```powershell
47
+ # Windows (PowerShell)
48
+ claude mcp add ovito-mcp --scope user -- (Get-Command ovito-mcp).Source
49
+ ```
50
+
51
+ ```sh
52
+ # macOS / Linux
53
+ claude mcp add ovito-mcp --scope user -- "$(which ovito-mcp)"
54
+ ```
55
+
56
+ See [Agent configuration](#agent-configuration) below for the other agents.
57
+
58
+ ## Using it
59
+
60
+ In OVITO Pro, open the
61
+ [**AI Agent**](https://docs.ovito.org/reference/data_inspector/coding_agent.html) tab in
62
+ the data inspector panel at the bottom of the main window, pick your coding agent, and
63
+ press **Start**.
64
+
65
+ OVITO launches the agent and connects it to the session in front of you. Anything the agent
66
+ does appears live in the GUI, and you can keep interacting with the same scene while it
67
+ works.
68
+
69
+ No further setup is needed — the agent finds everything it needs from the running OVITO Pro
70
+ session.
71
+
72
+ See the [AI Agent manual page](https://docs.ovito.org/reference/data_inspector/coding_agent.html)
73
+ for the OVITO-side documentation, including security considerations.
74
+
75
+ ## Agent configuration
76
+
77
+ Register `ovito-mcp` by its **absolute path** in the configurations below. Find that path with:
78
+
79
+ ```powershell
80
+ # Windows (PowerShell)
81
+ (Get-Command ovito-mcp).Source
82
+ ```
83
+
84
+ ```sh
85
+ # macOS / Linux
86
+ which ovito-mcp
87
+ ```
88
+
89
+ and substitute it for `/path/to/ovito-mcp` in the snippets below.
90
+
91
+ ### Claude Code / Claude Desktop
92
+
93
+ Add this to your `~/.claude.json`:
94
+
95
+ ```json
96
+ "mcpServers": {
97
+ "ovito-mcp": {
98
+ "type": "stdio",
99
+ "command": "/path/to/ovito-mcp"
100
+ }
101
+ }
102
+ ```
103
+
104
+ ### OpenCode
105
+
106
+ Add this to your `~/.opencode.json`:
107
+
108
+ ```json
109
+ "mcp": {
110
+ "ovito-mcp": {
111
+ "type": "local",
112
+ "command": ["/path/to/ovito-mcp"],
113
+ "enabled": true
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### Google Antigravity
119
+
120
+ Add this to your `~/.antigravity/config.json`:
121
+
122
+ ```json
123
+ "mcpServers": {
124
+ "ovito-mcp": {
125
+ "command": "/path/to/ovito-mcp"
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### OpenAI Codex
131
+
132
+ Add this to your `$HOME/.codex/config.toml`:
133
+
134
+ ```toml
135
+ [mcp_servers.ovito-mcp]
136
+ command = "/path/to/ovito-mcp"
137
+ ```
138
+
139
+ ### GitHub Copilot
140
+
141
+ Add this to your `~/.copilot/mcp-config.json`:
142
+
143
+ ```json
144
+ "mcpServers": {
145
+ "ovito-mcp": {
146
+ "command": "/path/to/ovito-mcp"
147
+ }
148
+ }
149
+ ```
150
+
151
+ ## Advanced configuration
152
+
153
+ Neither setting below is needed for normal use. Both are environment variables, intended
154
+ for development and unusual setups.
155
+
156
+ | Env var | Purpose |
157
+ | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
158
+ | `OVITO_EXE` | Path to the OVITO Pro executable (`ovito.exe` on Windows, `ovito` on Linux, `Ovito.app/Contents/MacOS/ovito` on macOS). Only needed to let the agent start OVITO Pro itself, instead of connecting to a session you already have running. |
159
+ | `OVITO_RAG_DB` | Path to an alternative documentation database. Overrides the one that ships with OVITO Pro, for developing against a custom build. |
160
+
161
+ ## Support
162
+
163
+ Questions and bug reports: <support@ovito.org>
@@ -60,15 +60,19 @@ PEP 440 pre-release — a dev release (`2026.1.dev1`), alpha (`2026.1a1`), beta
60
60
  (`2026.1b1`), or release candidate (`2026.1rc1`). The procedure is identical to above;
61
61
  the tag pattern in `.gitlab-ci.yml` already matches these.
62
62
 
63
- Pre-releases are **invisible to ordinary installs**: `pip install ovito-mcp` ignores
64
- them. Installing one is explicit:
63
+ Once at least one stable version exists, pre-releases are skipped by an ordinary
64
+ `pip install ovito-mcp`, and reaching one is explicit:
65
65
 
66
66
  ```sh
67
67
  pip install --pre ovito-mcp # newest, including pre-releases
68
- pip install ovito-mcp==2026.1.dev1 # or pin it exactly
68
+ pip install ovito-mcp==2026.1.dev2 # or pin it exactly
69
69
  ```
70
70
 
71
- So `2026.1.dev1` can be published while `2026.1` stays unreleased and unaffected.
71
+ **Caveat while no stable release exists.** pip falls back to pre-releases when a project
72
+ has *no* stable version to choose from, so until `2026.1` is published, a plain
73
+ `pip install ovito-mcp` resolves to the newest pre-release. A pre-release is therefore not
74
+ a private one before the first stable release — it is publicly installable by anyone who
75
+ knows the name.
72
76
 
73
77
  **Write versions in canonical PEP 440 form.** `2026.1-rc1` and `2026.1.rc1` are accepted
74
78
  by the build but normalize to `2026.1rc1` in the artifact and on PyPI, which would make
@@ -0,0 +1,47 @@
1
+ """Entry point for ``python -m ovito_mcp`` and the ``ovito-mcp`` console script."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import sys
7
+
8
+ from . import mcp, shutdown_kernel
9
+ from .rag import db as ragdb
10
+
11
+
12
+ def main() -> None:
13
+ # `ovito-mcp --version` prints the installed package version and exits. Handy
14
+ # for bug reports and for confirming that a registered (possibly absolute) path
15
+ # resolves to the expected install.
16
+ if "--version" in sys.argv[1:]:
17
+ from importlib.metadata import version
18
+ print(version("ovito-mcp"))
19
+ return
20
+
21
+ # `ovito-mcp --download-models` pre-downloads the retrieval models and exits,
22
+ # so the one-time Hub download can be done deterministically (e.g. right after
23
+ # `pip install ovito-mcp`) instead of during the first search tool call.
24
+ if "--download-models" in sys.argv[1:]:
25
+ logging.basicConfig(level=logging.INFO, format="%(message)s")
26
+ print("Downloading OVITO MCP retrieval models (one-time)...", file=sys.stderr)
27
+ try:
28
+ ragdb.ensure_models()
29
+ except ragdb.ModelsUnavailableError as exc:
30
+ print(f"Failed: {exc}", file=sys.stderr)
31
+ raise SystemExit(1)
32
+ print("Done. Models are cached locally.", file=sys.stderr)
33
+ return
34
+
35
+ # Warm the retrieval models in the background at startup so the first
36
+ # search_ovito_* call doesn't pay the load (and possible one-time download)
37
+ # cost on the request path, where it would risk the MCP client's tool timeout.
38
+ ragdb.warm_models_in_background()
39
+
40
+ try:
41
+ mcp.run()
42
+ finally:
43
+ shutdown_kernel()
44
+
45
+
46
+ if __name__ == "__main__":
47
+ main()
@@ -64,13 +64,30 @@ on each call — see `ovito_mcp.rag.DocsIndex` for that caching.
64
64
 
65
65
  from __future__ import annotations
66
66
 
67
- import functools
67
+ import logging
68
+ import os
68
69
  import sqlite3
70
+ import threading
69
71
  from pathlib import Path
70
72
  from typing import Any
71
73
 
72
74
  import numpy as np
73
75
 
76
+ logger = logging.getLogger(__name__)
77
+
78
+ # Shorten the per-request Hugging Face Hub metadata timeout. This only affects the
79
+ # code path that actually downloads models (the warmup thread and
80
+ # ``ovito-mcp --download-models``, via allow_download=True); it trims each metadata
81
+ # request from the default 10s to 5s. It is a best-effort nicety, NOT the mechanism
82
+ # that keeps searches responsive: the Hub client still retries and does not bound
83
+ # the repo-listing call or DNS resolution, so a fully unreachable Hub can still take
84
+ # a while to give up. User-facing searches don't depend on any of that -- they load
85
+ # offline-only and never touch the network (see get_model/get_cross_encoder), and
86
+ # the load runs outside the model lock so a slow/stuck warmup download can't block
87
+ # them either. Set as a default only, before any HF import, so a deployer can still
88
+ # override it.
89
+ os.environ.setdefault("HF_HUB_ETAG_TIMEOUT", "5")
90
+
74
91
  EMBED_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
75
92
  EMBED_DIM = 384
76
93
 
@@ -153,12 +170,93 @@ def connect(db_path: str | Path) -> sqlite3.Connection:
153
170
  # ── Embedding ──────────────────────────────────────────────────────────────────
154
171
 
155
172
 
156
- @functools.lru_cache(maxsize=1)
157
- def get_model():
158
- """Load (and cache) the sentence-transformers embedding model."""
173
+ class ModelsUnavailableError(RuntimeError):
174
+ """Raised when a retrieval model is neither cached locally nor downloadable.
175
+
176
+ A RuntimeError subclass so _call_rag() surfaces its (already user-facing)
177
+ message verbatim to the agent, rather than wrapping it as an opaque failure.
178
+ """
179
+
180
+
181
+ _MODELS_UNAVAILABLE_MSG = (
182
+ "OVITO documentation search is unavailable because its search models are not "
183
+ "installed. OVITO ships the documentation database itself, but the two small "
184
+ "sentence-transformers models used to search it (~180 MB total) are downloaded "
185
+ "once from the Hugging Face Hub and cached locally -- and this machine has no "
186
+ "cached copy and cannot reach the Hub. To enable documentation search, run "
187
+ "'ovito-mcp --download-models' once on a machine with internet access (under the "
188
+ "same user account), or copy an existing Hugging Face cache directory "
189
+ "(~/.cache/huggingface) into place. Everything else -- running code, screenshots, "
190
+ "and pipeline inspection -- works without these models."
191
+ )
192
+
193
+
194
+ # Each model is loaded at most once per process and then reused. The loaded
195
+ # instance is *published* under a lock (double-checked), but the load itself runs
196
+ # OUTSIDE the lock -- see get_model()/get_cross_encoder() for why: a download on
197
+ # the warmup path can stall (or, on a broken network, hang) for a long time, and
198
+ # holding the lock across it would block a concurrent offline-only search behind
199
+ # it, defeating the fast-fail guarantee. Because only ensure_models() ever passes
200
+ # allow_download=True (and the CLI download runs in its own process), there is at
201
+ # most one downloader, so loading outside the lock cannot start two parallel
202
+ # downloads; at worst two threads redundantly load the *cached* files and the
203
+ # first published instance wins.
204
+ _model = None
205
+ _model_lock = threading.Lock()
206
+ _cross_encoder = None
207
+ _cross_encoder_lock = threading.Lock()
208
+
209
+
210
+ def _load_st_model(cls, name: str, *, allow_download: bool):
211
+ """Load a sentence-transformers model, preferring the local HF cache.
212
+
213
+ Always tries the local cache first (``local_files_only=True``): that path is
214
+ instant and never issues a network request, so a warm process makes no HTTP
215
+ HEAD calls to huggingface.co on load (those add seconds of latency even when
216
+ the files are cached, and fail outright on a slow/offline/rate-limited
217
+ connection -- the original cause of ``search_ovito_*`` timing out).
218
+
219
+ ``allow_download`` controls the not-cached case:
220
+
221
+ * False (the default for user-facing searches via get_model/get_cross_encoder)
222
+ -- raise ModelsUnavailableError immediately. The search path therefore never
223
+ touches the network and never hangs, whatever the connection state.
224
+ * True (only the warmup thread and ``ovito-mcp --download-models``) -- attempt
225
+ a one-time download from the Hub, bounded by HF_HUB_ETAG_TIMEOUT so an
226
+ unreachable Hub fails in a few seconds rather than hanging. Still raises
227
+ ModelsUnavailableError if the download can't complete.
228
+ """
229
+ try:
230
+ return cls(name, local_files_only=True)
231
+ except Exception as cache_miss:
232
+ if not allow_download:
233
+ raise ModelsUnavailableError(_MODELS_UNAVAILABLE_MSG) from cache_miss
234
+ # allow_download and not cached: attempt a one-time (bounded) Hub download.
235
+ try:
236
+ return cls(name)
237
+ except Exception as exc:
238
+ raise ModelsUnavailableError(_MODELS_UNAVAILABLE_MSG) from exc
239
+
240
+
241
+ def get_model(*, allow_download: bool = False):
242
+ """Load (and cache) the sentence-transformers embedding model.
243
+
244
+ Offline-only by default: raises ModelsUnavailableError if the model isn't
245
+ already cached. Only ensure_models() passes allow_download=True to permit the
246
+ one-time download. The load runs outside _model_lock (the lock only guards
247
+ publication) so an offline-only caller is never blocked behind a slow or
248
+ hanging download on the warmup path.
249
+ """
250
+ global _model
251
+ if _model is not None: # atomic reference read under the GIL
252
+ return _model
159
253
  from sentence_transformers import SentenceTransformer
160
254
 
161
- return SentenceTransformer(EMBED_MODEL)
255
+ loaded = _load_st_model(SentenceTransformer, EMBED_MODEL, allow_download=allow_download)
256
+ with _model_lock:
257
+ if _model is None:
258
+ _model = loaded
259
+ return _model
162
260
 
163
261
 
164
262
  def embed(texts: list[str]) -> np.ndarray:
@@ -174,12 +272,84 @@ def embed_one(text: str) -> np.ndarray:
174
272
  return embed([text])[0]
175
273
 
176
274
 
177
- @functools.lru_cache(maxsize=1)
178
- def get_cross_encoder():
179
- """Load (and cache) the cross-encoder used to re-rank candidate passages."""
275
+ def get_cross_encoder(*, allow_download: bool = False):
276
+ """Load (and cache) the cross-encoder used to re-rank candidate passages.
277
+
278
+ Offline-only by default: raises ModelsUnavailableError if the model isn't
279
+ already cached. Only ensure_models() passes allow_download=True to permit the
280
+ one-time download. The load runs outside _cross_encoder_lock (the lock only
281
+ guards publication) so an offline-only caller is never blocked behind a slow
282
+ or hanging download on the warmup path.
283
+ """
284
+ global _cross_encoder
285
+ if _cross_encoder is not None: # atomic reference read under the GIL
286
+ return _cross_encoder
180
287
  from sentence_transformers import CrossEncoder
181
288
 
182
- return CrossEncoder(RERANK_MODEL)
289
+ loaded = _load_st_model(CrossEncoder, RERANK_MODEL, allow_download=allow_download)
290
+ with _cross_encoder_lock:
291
+ if _cross_encoder is None:
292
+ _cross_encoder = loaded
293
+ return _cross_encoder
294
+
295
+
296
+ def ensure_models() -> None:
297
+ """Download (once) and load both retrieval models into this process.
298
+
299
+ Idempotent: after the first successful call both models are cached in memory
300
+ (and on disk, in the HF cache) and later calls are no-ops. This is the single
301
+ place a model download from huggingface.co is expected to happen; running it
302
+ up front -- at server startup via warm_models_in_background(), or explicitly
303
+ via ``ovito-mcp --download-models`` -- keeps that one-time cost out of the
304
+ first search_ovito_* tool call, where it would otherwise surface as a timeout.
305
+ Loading is deferred to the getters, so this also warms the in-memory singletons.
306
+
307
+ This is the only place allowed to download the models (allow_download=True);
308
+ user-facing searches load offline-only and never hit the network. Raises
309
+ ModelsUnavailableError if a model is neither cached nor downloadable.
310
+
311
+ NB: this must NOT wrap the loads in contextlib.redirect_stdout(). Unlike the
312
+ on-demand _call_rag() path -- which runs after mcp.run() has already captured
313
+ the real stdout buffer for the JSON-RPC transport -- ensure_models() is called
314
+ from warm_models_in_background() *before/while* mcp.run() sets that transport
315
+ up. A process-global sys.stdout swap here races that capture and can bind the
316
+ protocol stream to stderr, breaking the MCP handshake entirely. The model
317
+ libraries send their progress/log output to stderr on their own, so no redirect
318
+ is needed for correctness on this path.
319
+ """
320
+ get_model(allow_download=True)
321
+ get_cross_encoder(allow_download=True)
322
+
323
+
324
+ def warm_models_in_background() -> threading.Thread:
325
+ """Kick off ensure_models() in a daemon thread and return it immediately.
326
+
327
+ The retrieval models take several seconds to import+load (and, on a machine
328
+ that has never run the server before, a one-time download). Doing that lazily
329
+ inside the first search_ovito_* call makes that call slow enough to hit the
330
+ MCP client's tool timeout. Warming at startup instead moves the cost off the
331
+ request path: by the time a search arrives the models are usually ready, and
332
+ if one races the warmup it simply blocks on the same lock rather than starting
333
+ a second load. Failures here are non-fatal -- they are logged and the getters
334
+ retry on demand -- so a transient network problem at startup never prevents
335
+ the server from running (or from serving non-search tools).
336
+ """
337
+
338
+ def _warm() -> None:
339
+ try:
340
+ ensure_models()
341
+ logger.info("OVITO MCP: retrieval models ready.")
342
+ except ModelsUnavailableError as exc:
343
+ # Expected on an offline machine with no cached models: documentation
344
+ # search will report the same actionable message when called. All other
345
+ # tools keep working, so this is a warning, not a fatal error.
346
+ logger.warning("OVITO MCP: %s", exc)
347
+ except Exception as exc: # noqa: BLE001 - warmup must never crash the server
348
+ logger.warning("OVITO MCP: model warmup failed unexpectedly (%s).", exc)
349
+
350
+ thread = threading.Thread(target=_warm, name="ovito-mcp-model-warmup", daemon=True)
351
+ thread.start()
352
+ return thread
183
353
 
184
354
 
185
355
  def _vec_to_blob(vec: np.ndarray) -> bytes:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ovito-mcp"
7
- version = "2026.1.dev2"
7
+ version = "2026.1.dev3"
8
8
  description = "MCP server for interactive work in OVITO Pro"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -17,7 +17,7 @@ dependencies = [
17
17
  ]
18
18
  license = { text = "Proprietary" }
19
19
  authors = [{ name = "OVITO GmbH", email = "support@ovito.org" }]
20
- keywords = ["ovito", "mcp", "molecular-dynamics", "visualization", "jupyter"]
20
+ keywords = ["ovito", "mcp", "molecular-dynamics", "visualization"]
21
21
  classifiers = [
22
22
  "Development Status :: 4 - Beta",
23
23
  "Intended Audience :: Science/Research",
@@ -1,240 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: ovito-mcp
3
- Version: 2026.1.dev2
4
- Summary: MCP server for interactive work in OVITO Pro
5
- Project-URL: Homepage, https://www.ovito.org
6
- Project-URL: Documentation, https://docs.ovito.org/reference/data_inspector/coding_agent.html
7
- Project-URL: Repository, https://gitlab.com/ovito-org/ovito-mcp-server
8
- Project-URL: Bug Tracker, https://gitlab.com/ovito-org/ovito-mcp-server/-/issues
9
- Author-email: OVITO GmbH <support@ovito.org>
10
- License: Proprietary
11
- License-File: LICENSE
12
- Keywords: jupyter,mcp,molecular-dynamics,ovito,visualization
13
- Classifier: Development Status :: 4 - Beta
14
- Classifier: Intended Audience :: Science/Research
15
- Classifier: License :: Other/Proprietary License
16
- Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.10
18
- Classifier: Programming Language :: Python :: 3.11
19
- Classifier: Programming Language :: Python :: 3.12
20
- Classifier: Programming Language :: Python :: 3.13
21
- Classifier: Programming Language :: Python :: 3.14
22
- Classifier: Topic :: Scientific/Engineering :: Visualization
23
- Requires-Python: >=3.10
24
- Requires-Dist: jupyter-client>=8.0
25
- Requires-Dist: jupyter-core>=5.0
26
- Requires-Dist: mcp>=1.2.0
27
- Requires-Dist: numpy>=1.26
28
- Requires-Dist: sentence-transformers>=5.6.0
29
- Provides-Extra: test
30
- Requires-Dist: pytest; extra == 'test'
31
- Description-Content-Type: text/markdown
32
-
33
- # OVITO MCP Server
34
-
35
- An MCP server that connects a coding agent to a live **OVITO Pro** session, so an LLM can
36
- do interactive OVITO work — execute Python in a persistent kernel, inspect the scene,
37
- drive the GUI window, and look up documentation. Runs as a stdio MCP server in Claude
38
- Code, GitHub Copilot CLI, Codex, OpenCode, Antigravity, or any other MCP host.
39
-
40
- ## Quick start
41
-
42
- ```sh
43
- pip install ovito-mcp
44
- ```
45
-
46
- Register it with your coding agent — for Claude Code:
47
-
48
- ```sh
49
- claude mcp add ovito-mcp --scope user -- ovito-mcp
50
- ```
51
-
52
- See [Agent configuration](#agent-configuration) below for the equivalent config in other
53
- agents.
54
-
55
- Then, in OVITO Pro, open the **AI Agent** tab in the data inspector panel at the bottom
56
- of the main window, pick your coding agent, and press **Start**. OVITO launches the agent
57
- and hands it the connection details of its own embedded Python kernel, so the agent
58
- attaches to the session you are looking at — anything it does shows up live in the GUI,
59
- and you can keep interacting with the same scene yourself.
60
-
61
- That's the whole setup. **No environment variables are required**, and you do not need to
62
- tell the server where OVITO is installed: it is already talking to a running OVITO Pro.
63
-
64
- See the [AI Agent manual page](https://docs.ovito.org/reference/data_inspector/coding_agent.html)
65
- for the OVITO-side documentation, including security considerations.
66
-
67
- ## How it works
68
-
69
- - `ovito_mcp/__init__.py` uses `jupyter_client` to attach to (or, as a fallback, start) an
70
- OVITO kernel, and exposes tools to `execute` code and manage the kernel lifecycle. State
71
- persists across calls — variables, imports, and the OVITO pipeline carry over.
72
- - OVITO Pro's *AI Agent* panel starts an embedded kernel and passes its connection-file
73
- path to the agent in the startup prompt; the agent calls `connect_kernel()` with it.
74
- The same kernel can also be attached from JupyterLab to watch along.
75
- - `start_kernel()` is the fallback for standalone use with no OVITO Pro session running.
76
- It launches a separate OVITO process and is the only part of the server that needs
77
- `OVITO_EXE` — see [Advanced configuration](#advanced-configuration).
78
- - GUI tools (`create_window`, `close_window`, `screenshot_window`) drive OVITO Pro's Qt
79
- main window inside the kernel, so a human can watch and interact with the same scene.
80
- - Scene-inspection tools (`list_pipelines`, `list_modifiers`, `inspect_pipeline_data`,
81
- `get_timeline_info`, `set_current_frame`) let the LLM read back pipeline/data state
82
- without hand-writing introspection code.
83
- - The documentation tools (`search_ovito_docs` and friends) perform semantic search over a
84
- local SQLite index of the OVITO manual and Python API docs, so the LLM can look up
85
- correct usage instead of guessing at APIs. The index ships inside OVITO Pro and is found
86
- automatically — nothing to configure.
87
-
88
- ```
89
- connect_kernel(<connection_file>) # the normal path: attach to OVITO Pro's own kernel
90
- \-> start_kernel() # fallback: launch a separate OVITO process (needs OVITO_EXE)
91
- \-> execute("import ovito; print(ovito.version_string)")
92
- ```
93
-
94
- ## Advanced configuration
95
-
96
- Neither variable below is needed for the standard flow described in
97
- [Quick start](#quick-start). Set them only for the cases described.
98
-
99
- | Env var | Purpose |
100
- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
101
- | `OVITO_EXE` | Path to the OVITO Pro executable (`ovito.exe` on Windows, `ovito` on Linux, `Ovito.app/Contents/MacOS/ovito` on macOS). Required *only* by `start_kernel()`, i.e. when you want the agent to launch OVITO itself instead of connecting to a running session. |
102
- | `OVITO_RAG_DB` | Path to an alternative documentation database (`.sqlite3`). Overrides the copy that ships with OVITO Pro — useful when developing against a hand-built database. |
103
-
104
- Both are read at import time but never validated there, so the server always starts
105
- without configuration. `start_kernel()` raises `ValueError` if `OVITO_EXE` is unset, and
106
- the documentation tools report a message telling you to connect a kernel first if the
107
- database cannot be located.
108
-
109
- By default the documentation database is discovered by asking the connected kernel for
110
- `ovito.__file__` and looking for `rag.sqlite3` next to the resolved `ovito` package. That
111
- works without `OVITO_EXE`, which is why the standard flow needs no setup at all.
112
-
113
- ## Tools
114
-
115
- **Kernel lifecycle**
116
-
117
- | Tool | Purpose |
118
- | --------------------------------- | ------------------------------------------------------------------------------------------------------------- |
119
- | `connect_kernel(connection_file)` | Attach to an already-running kernel via its connection file; state is shared with whoever else is driving it. |
120
- | `start_kernel()` | Launch a new OVITO kernel and make it active (tears down any current session first). Requires `OVITO_EXE`. |
121
- | `get_kernel_id()` | UUID of the active kernel. |
122
- | `kernel_status()` | Active kernel id, ownership (started here vs. externally connected), liveness. |
123
- | `interrupt_kernel()` | Interrupt a running execution (kernels started by this server only). |
124
- | `restart_kernel()` | Restart and clear all state (kernels started by this server only). |
125
- | `shutdown_kernel()` | Shut down an owned kernel, or detach from an externally-connected one (left running). |
126
-
127
- **Execution**
128
-
129
- | Tool | Purpose |
130
- | --------------------------- | ------------------------------------------------------------------------------------------ |
131
- | `execute(code, timeout=60)` | Run Python in the active kernel; returns status, stdout/stderr, result, and any traceback. |
132
-
133
- **GUI window**
134
-
135
- | Tool | Purpose |
136
- | ------------------------------ | ----------------------------------------------------------------------------------------------- |
137
- | `create_window(contents=None)` | Open (or reuse) OVITO's graphical main window in the kernel so a human can watch/interact live. |
138
- | `close_window()` | Close a window this server itself created — never one it merely reused. |
139
- | `screenshot_window()` | Return a PNG screenshot of the open OVITO main window. |
140
-
141
- **Scene inspection**
142
-
143
- | Tool | Purpose |
144
- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
145
- | `list_pipelines()` | List pipelines in the scene with their keys and names. |
146
- | `list_modifiers(pipeline_key)` | List modifiers on a given pipeline. |
147
- | `inspect_pipeline_data(pipeline_key)` | Markdown overview of a pipeline's computed output: attributes, data objects, property containers, simulation cell. |
148
- | `list_pipeline_status()` | Status (including warning/error triangles) of every pipeline, modifier, and visual element in the scene. |
149
- | `get_timeline_info()` | Current frame and frame range. |
150
- | `set_current_frame(frame)` | Set the current animation frame (keeps the GUI in sync; use instead of setting `ovito.scene.anim.current_frame` directly). |
151
-
152
- **Documentation**
153
-
154
- | Tool | Purpose |
155
- | ------------------------------------------------------ | ------------------------------------------------------------------------- |
156
- | `search_ovito_docs(query, top_k=5)` | Semantic search across the OVITO manual and Python API docs. |
157
- | `search_ovito_manual(query, top_k=5)` | Semantic search over manual pages only. |
158
- | `search_ovito_api_symbols(query, top_k=5)` | Semantic search over Python API symbols only. |
159
- | `get_ovito_manual_page(full_page_id)` | Fetch a manual page's full text by id (from a manual search result). |
160
- | `get_ovito_docstring(doc_string_id=None, symbol=None)` | Fetch a Python symbol's full docstring by id or exact qualified name. |
161
-
162
- These are backed by `ovito_mcp.rag` — a single inspectable SQLite file (`rag.sqlite3`)
163
- searched with a two-stage strategy: bi-encoder retrieval over stored embeddings, then
164
- cross-encoder re-ranking of the candidates. No vector-store service is required. Manual
165
- hits are resolved to their complete source page rather than the matching chunk, so results
166
- are not truncated excerpts.
167
-
168
- If no database can be located, these tools return a configuration hint instead of crashing
169
- the server; every other tool is unaffected.
170
-
171
- ## Agent configuration
172
-
173
- All configurations below assume `ovito-mcp` is on your `PATH` (it is installed as a
174
- console script by `pip install ovito-mcp`). If you installed into an environment that
175
- isn't active when the agent runs, use the absolute path to the script instead —
176
- `/path/to/env/bin/ovito-mcp`. No `env` block is needed.
177
-
178
- ### Claude Code / Claude Desktop
179
-
180
- ```sh
181
- claude mcp add ovito-mcp --scope user -- ovito-mcp
182
- ```
183
-
184
- Or add this to your `~/.claude.json`:
185
-
186
- ```json
187
- "mcpServers": {
188
- "ovito-mcp": {
189
- "type": "stdio",
190
- "command": "ovito-mcp"
191
- }
192
- }
193
- ```
194
-
195
- ### OpenCode
196
-
197
- Add this to your `~/.opencode.json`:
198
-
199
- ```json
200
- "mcp": {
201
- "ovito-mcp": {
202
- "type": "local",
203
- "command": ["ovito-mcp"],
204
- "enabled": true
205
- }
206
- }
207
- ```
208
-
209
- ### Google Antigravity
210
-
211
- Add this to your `~/.antigravity/config.json`:
212
-
213
- ```json
214
- "mcpServers": {
215
- "ovito-mcp": {
216
- "command": "ovito-mcp"
217
- }
218
- }
219
- ```
220
-
221
- ### OpenAI Codex
222
-
223
- Add this to your `$HOME/.codex/config.toml`:
224
-
225
- ```toml
226
- [mcp_servers.ovito-mcp]
227
- command = "ovito-mcp"
228
- ```
229
-
230
- ### GitHub Copilot
231
-
232
- Add this to your `~/.copilot/mcp-config.json`:
233
-
234
- ```json
235
- "mcpServers": {
236
- "ovito-mcp": {
237
- "command": "ovito-mcp"
238
- }
239
- }
240
- ```
@@ -1,208 +0,0 @@
1
- # OVITO MCP Server
2
-
3
- An MCP server that connects a coding agent to a live **OVITO Pro** session, so an LLM can
4
- do interactive OVITO work — execute Python in a persistent kernel, inspect the scene,
5
- drive the GUI window, and look up documentation. Runs as a stdio MCP server in Claude
6
- Code, GitHub Copilot CLI, Codex, OpenCode, Antigravity, or any other MCP host.
7
-
8
- ## Quick start
9
-
10
- ```sh
11
- pip install ovito-mcp
12
- ```
13
-
14
- Register it with your coding agent — for Claude Code:
15
-
16
- ```sh
17
- claude mcp add ovito-mcp --scope user -- ovito-mcp
18
- ```
19
-
20
- See [Agent configuration](#agent-configuration) below for the equivalent config in other
21
- agents.
22
-
23
- Then, in OVITO Pro, open the **AI Agent** tab in the data inspector panel at the bottom
24
- of the main window, pick your coding agent, and press **Start**. OVITO launches the agent
25
- and hands it the connection details of its own embedded Python kernel, so the agent
26
- attaches to the session you are looking at — anything it does shows up live in the GUI,
27
- and you can keep interacting with the same scene yourself.
28
-
29
- That's the whole setup. **No environment variables are required**, and you do not need to
30
- tell the server where OVITO is installed: it is already talking to a running OVITO Pro.
31
-
32
- See the [AI Agent manual page](https://docs.ovito.org/reference/data_inspector/coding_agent.html)
33
- for the OVITO-side documentation, including security considerations.
34
-
35
- ## How it works
36
-
37
- - `ovito_mcp/__init__.py` uses `jupyter_client` to attach to (or, as a fallback, start) an
38
- OVITO kernel, and exposes tools to `execute` code and manage the kernel lifecycle. State
39
- persists across calls — variables, imports, and the OVITO pipeline carry over.
40
- - OVITO Pro's *AI Agent* panel starts an embedded kernel and passes its connection-file
41
- path to the agent in the startup prompt; the agent calls `connect_kernel()` with it.
42
- The same kernel can also be attached from JupyterLab to watch along.
43
- - `start_kernel()` is the fallback for standalone use with no OVITO Pro session running.
44
- It launches a separate OVITO process and is the only part of the server that needs
45
- `OVITO_EXE` — see [Advanced configuration](#advanced-configuration).
46
- - GUI tools (`create_window`, `close_window`, `screenshot_window`) drive OVITO Pro's Qt
47
- main window inside the kernel, so a human can watch and interact with the same scene.
48
- - Scene-inspection tools (`list_pipelines`, `list_modifiers`, `inspect_pipeline_data`,
49
- `get_timeline_info`, `set_current_frame`) let the LLM read back pipeline/data state
50
- without hand-writing introspection code.
51
- - The documentation tools (`search_ovito_docs` and friends) perform semantic search over a
52
- local SQLite index of the OVITO manual and Python API docs, so the LLM can look up
53
- correct usage instead of guessing at APIs. The index ships inside OVITO Pro and is found
54
- automatically — nothing to configure.
55
-
56
- ```
57
- connect_kernel(<connection_file>) # the normal path: attach to OVITO Pro's own kernel
58
- \-> start_kernel() # fallback: launch a separate OVITO process (needs OVITO_EXE)
59
- \-> execute("import ovito; print(ovito.version_string)")
60
- ```
61
-
62
- ## Advanced configuration
63
-
64
- Neither variable below is needed for the standard flow described in
65
- [Quick start](#quick-start). Set them only for the cases described.
66
-
67
- | Env var | Purpose |
68
- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
69
- | `OVITO_EXE` | Path to the OVITO Pro executable (`ovito.exe` on Windows, `ovito` on Linux, `Ovito.app/Contents/MacOS/ovito` on macOS). Required *only* by `start_kernel()`, i.e. when you want the agent to launch OVITO itself instead of connecting to a running session. |
70
- | `OVITO_RAG_DB` | Path to an alternative documentation database (`.sqlite3`). Overrides the copy that ships with OVITO Pro — useful when developing against a hand-built database. |
71
-
72
- Both are read at import time but never validated there, so the server always starts
73
- without configuration. `start_kernel()` raises `ValueError` if `OVITO_EXE` is unset, and
74
- the documentation tools report a message telling you to connect a kernel first if the
75
- database cannot be located.
76
-
77
- By default the documentation database is discovered by asking the connected kernel for
78
- `ovito.__file__` and looking for `rag.sqlite3` next to the resolved `ovito` package. That
79
- works without `OVITO_EXE`, which is why the standard flow needs no setup at all.
80
-
81
- ## Tools
82
-
83
- **Kernel lifecycle**
84
-
85
- | Tool | Purpose |
86
- | --------------------------------- | ------------------------------------------------------------------------------------------------------------- |
87
- | `connect_kernel(connection_file)` | Attach to an already-running kernel via its connection file; state is shared with whoever else is driving it. |
88
- | `start_kernel()` | Launch a new OVITO kernel and make it active (tears down any current session first). Requires `OVITO_EXE`. |
89
- | `get_kernel_id()` | UUID of the active kernel. |
90
- | `kernel_status()` | Active kernel id, ownership (started here vs. externally connected), liveness. |
91
- | `interrupt_kernel()` | Interrupt a running execution (kernels started by this server only). |
92
- | `restart_kernel()` | Restart and clear all state (kernels started by this server only). |
93
- | `shutdown_kernel()` | Shut down an owned kernel, or detach from an externally-connected one (left running). |
94
-
95
- **Execution**
96
-
97
- | Tool | Purpose |
98
- | --------------------------- | ------------------------------------------------------------------------------------------ |
99
- | `execute(code, timeout=60)` | Run Python in the active kernel; returns status, stdout/stderr, result, and any traceback. |
100
-
101
- **GUI window**
102
-
103
- | Tool | Purpose |
104
- | ------------------------------ | ----------------------------------------------------------------------------------------------- |
105
- | `create_window(contents=None)` | Open (or reuse) OVITO's graphical main window in the kernel so a human can watch/interact live. |
106
- | `close_window()` | Close a window this server itself created — never one it merely reused. |
107
- | `screenshot_window()` | Return a PNG screenshot of the open OVITO main window. |
108
-
109
- **Scene inspection**
110
-
111
- | Tool | Purpose |
112
- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
113
- | `list_pipelines()` | List pipelines in the scene with their keys and names. |
114
- | `list_modifiers(pipeline_key)` | List modifiers on a given pipeline. |
115
- | `inspect_pipeline_data(pipeline_key)` | Markdown overview of a pipeline's computed output: attributes, data objects, property containers, simulation cell. |
116
- | `list_pipeline_status()` | Status (including warning/error triangles) of every pipeline, modifier, and visual element in the scene. |
117
- | `get_timeline_info()` | Current frame and frame range. |
118
- | `set_current_frame(frame)` | Set the current animation frame (keeps the GUI in sync; use instead of setting `ovito.scene.anim.current_frame` directly). |
119
-
120
- **Documentation**
121
-
122
- | Tool | Purpose |
123
- | ------------------------------------------------------ | ------------------------------------------------------------------------- |
124
- | `search_ovito_docs(query, top_k=5)` | Semantic search across the OVITO manual and Python API docs. |
125
- | `search_ovito_manual(query, top_k=5)` | Semantic search over manual pages only. |
126
- | `search_ovito_api_symbols(query, top_k=5)` | Semantic search over Python API symbols only. |
127
- | `get_ovito_manual_page(full_page_id)` | Fetch a manual page's full text by id (from a manual search result). |
128
- | `get_ovito_docstring(doc_string_id=None, symbol=None)` | Fetch a Python symbol's full docstring by id or exact qualified name. |
129
-
130
- These are backed by `ovito_mcp.rag` — a single inspectable SQLite file (`rag.sqlite3`)
131
- searched with a two-stage strategy: bi-encoder retrieval over stored embeddings, then
132
- cross-encoder re-ranking of the candidates. No vector-store service is required. Manual
133
- hits are resolved to their complete source page rather than the matching chunk, so results
134
- are not truncated excerpts.
135
-
136
- If no database can be located, these tools return a configuration hint instead of crashing
137
- the server; every other tool is unaffected.
138
-
139
- ## Agent configuration
140
-
141
- All configurations below assume `ovito-mcp` is on your `PATH` (it is installed as a
142
- console script by `pip install ovito-mcp`). If you installed into an environment that
143
- isn't active when the agent runs, use the absolute path to the script instead —
144
- `/path/to/env/bin/ovito-mcp`. No `env` block is needed.
145
-
146
- ### Claude Code / Claude Desktop
147
-
148
- ```sh
149
- claude mcp add ovito-mcp --scope user -- ovito-mcp
150
- ```
151
-
152
- Or add this to your `~/.claude.json`:
153
-
154
- ```json
155
- "mcpServers": {
156
- "ovito-mcp": {
157
- "type": "stdio",
158
- "command": "ovito-mcp"
159
- }
160
- }
161
- ```
162
-
163
- ### OpenCode
164
-
165
- Add this to your `~/.opencode.json`:
166
-
167
- ```json
168
- "mcp": {
169
- "ovito-mcp": {
170
- "type": "local",
171
- "command": ["ovito-mcp"],
172
- "enabled": true
173
- }
174
- }
175
- ```
176
-
177
- ### Google Antigravity
178
-
179
- Add this to your `~/.antigravity/config.json`:
180
-
181
- ```json
182
- "mcpServers": {
183
- "ovito-mcp": {
184
- "command": "ovito-mcp"
185
- }
186
- }
187
- ```
188
-
189
- ### OpenAI Codex
190
-
191
- Add this to your `$HOME/.codex/config.toml`:
192
-
193
- ```toml
194
- [mcp_servers.ovito-mcp]
195
- command = "ovito-mcp"
196
- ```
197
-
198
- ### GitHub Copilot
199
-
200
- Add this to your `~/.copilot/mcp-config.json`:
201
-
202
- ```json
203
- "mcpServers": {
204
- "ovito-mcp": {
205
- "command": "ovito-mcp"
206
- }
207
- }
208
- ```
@@ -1,16 +0,0 @@
1
- """Entry point for ``python -m ovito_mcp``."""
2
-
3
- from __future__ import annotations
4
-
5
- from . import mcp, shutdown_kernel
6
-
7
-
8
- def main() -> None:
9
- try:
10
- mcp.run()
11
- finally:
12
- shutdown_kernel()
13
-
14
-
15
- if __name__ == "__main__":
16
- main()
File without changes