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.
- ovito_mcp-2026.1.dev3/PKG-INFO +195 -0
- ovito_mcp-2026.1.dev3/README.md +163 -0
- {ovito_mcp-2026.1.dev2 → ovito_mcp-2026.1.dev3}/RELEASING.md +8 -4
- ovito_mcp-2026.1.dev3/ovito_mcp/__main__.py +47 -0
- {ovito_mcp-2026.1.dev2 → ovito_mcp-2026.1.dev3}/ovito_mcp/rag/db.py +179 -9
- {ovito_mcp-2026.1.dev2 → ovito_mcp-2026.1.dev3}/pyproject.toml +2 -2
- ovito_mcp-2026.1.dev2/PKG-INFO +0 -240
- ovito_mcp-2026.1.dev2/README.md +0 -208
- ovito_mcp-2026.1.dev2/ovito_mcp/__main__.py +0 -16
- {ovito_mcp-2026.1.dev2 → ovito_mcp-2026.1.dev3}/.gitignore +0 -0
- {ovito_mcp-2026.1.dev2 → ovito_mcp-2026.1.dev3}/.gitlab-ci.yml +0 -0
- {ovito_mcp-2026.1.dev2 → ovito_mcp-2026.1.dev3}/LICENSE +0 -0
- {ovito_mcp-2026.1.dev2 → ovito_mcp-2026.1.dev3}/ovito_mcp/__init__.py +0 -0
- {ovito_mcp-2026.1.dev2 → ovito_mcp-2026.1.dev3}/ovito_mcp/rag/__init__.py +0 -0
- {ovito_mcp-2026.1.dev2 → ovito_mcp-2026.1.dev3}/tests/test_docs_index.py +0 -0
- {ovito_mcp-2026.1.dev2 → ovito_mcp-2026.1.dev3}/tests/test_docs_index_discovery.py +0 -0
- {ovito_mcp-2026.1.dev2 → ovito_mcp-2026.1.dev3}/tests/test_rag_db.py +0 -0
|
@@ -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
|
-
|
|
64
|
-
|
|
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.
|
|
68
|
+
pip install ovito-mcp==2026.1.dev2 # or pin it exactly
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
|
|
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
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
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.
|
|
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"
|
|
20
|
+
keywords = ["ovito", "mcp", "molecular-dynamics", "visualization"]
|
|
21
21
|
classifiers = [
|
|
22
22
|
"Development Status :: 4 - Beta",
|
|
23
23
|
"Intended Audience :: Science/Research",
|
ovito_mcp-2026.1.dev2/PKG-INFO
DELETED
|
@@ -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
|
-
```
|
ovito_mcp-2026.1.dev2/README.md
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|