keyshot-mcp 0.7.0 → 0.9.0
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.
- package/README.md +154 -43
- package/dist/cli/status.js +2 -2
- package/dist/config.js +5 -1
- package/dist/diagnostics.js +191 -0
- package/dist/index.js +26 -8
- package/dist/output-collisions.js +43 -0
- package/dist/product-render.js +92 -0
- package/dist/quality.js +24 -0
- package/dist/result.js +77 -0
- package/dist/runner.js +24 -4
- package/dist/schemas.js +94 -0
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/scripts/keyshot-smoke.mjs +65 -11
- package/scripts/keyshot_bridge.py +208 -26
- package/server.json +3 -3
- package/assets/star-history-dark.svg +0 -25
- package/assets/star-history-light.svg +0 -25
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ the local computer.
|
|
|
28
28
|
- Apply materials directly or through a local material preset library.
|
|
29
29
|
- Select environments, change brightness, and rotate the active environment.
|
|
30
30
|
- Save edited scenes to a controlled output directory.
|
|
31
|
+
- Prepare and render a product from a model or existing scene in one tool call.
|
|
31
32
|
|
|
32
33
|
### Requirements and compatibility
|
|
33
34
|
|
|
@@ -40,7 +41,7 @@ the local computer.
|
|
|
40
41
|
|
|
41
42
|
### Install
|
|
42
43
|
|
|
43
|
-
The current release is `0.
|
|
44
|
+
The current release is `0.9.0`.
|
|
44
45
|
|
|
45
46
|
#### Option 1: run with npx
|
|
46
47
|
|
|
@@ -51,7 +52,7 @@ This requires no global npm installation:
|
|
|
51
52
|
"mcpServers": {
|
|
52
53
|
"keyshot": {
|
|
53
54
|
"command": "npx",
|
|
54
|
-
"args": ["-y", "keyshot-mcp@0.
|
|
55
|
+
"args": ["-y", "keyshot-mcp@0.9.0"],
|
|
55
56
|
"env": {
|
|
56
57
|
"KEYSHOT_HEADLESS_EXE": "C:/Program Files/KeyShot Studio/bin/keyshot_headless.exe"
|
|
57
58
|
}
|
|
@@ -63,7 +64,7 @@ This requires no global npm installation:
|
|
|
63
64
|
#### Option 2: install globally
|
|
64
65
|
|
|
65
66
|
```bash
|
|
66
|
-
npm install -g keyshot-mcp@0.
|
|
67
|
+
npm install -g keyshot-mcp@0.9.0
|
|
67
68
|
```
|
|
68
69
|
|
|
69
70
|
```json
|
|
@@ -112,14 +113,14 @@ The following prompt can be pasted into a coding agent that is allowed to edit
|
|
|
112
113
|
the MCP client configuration:
|
|
113
114
|
|
|
114
115
|
```text
|
|
115
|
-
Install KeyShot MCP 0.
|
|
116
|
+
Install KeyShot MCP 0.9.0 and configure it in my MCP client.
|
|
116
117
|
|
|
117
118
|
1. Find the local KeyShot headless executable.
|
|
118
119
|
2. Add an MCP server named "keyshot" that runs:
|
|
119
|
-
npx -y keyshot-mcp@0.
|
|
120
|
+
npx -y keyshot-mcp@0.9.0
|
|
120
121
|
3. Set KEYSHOT_HEADLESS_EXE to the executable path.
|
|
121
122
|
4. Keep KEYSHOT_ALLOW_EXTERNAL_OUTPUTS disabled.
|
|
122
|
-
5. Restart or reload the MCP client, then
|
|
123
|
+
5. Restart or reload the MCP client, call keyshot_status, then use keyshot_product_render for a one-click product render.
|
|
123
124
|
6. Do not upload or publish any KeyShot scenes, models, renders, or license data.
|
|
124
125
|
```
|
|
125
126
|
|
|
@@ -128,7 +129,7 @@ Install KeyShot MCP 0.7.0 and configure it in my MCP client.
|
|
|
128
129
|
| Variable | Default | Purpose |
|
|
129
130
|
| --- | --- | --- |
|
|
130
131
|
| `KEYSHOT_HEADLESS_EXE` | `keyshot_headless.exe` on Windows | Absolute executable path or a command available on `PATH`. |
|
|
131
|
-
| `KEYSHOT_OUTPUT_DIR` |
|
|
132
|
+
| `KEYSHOT_OUTPUT_DIR` | `~/Documents/KeyShot MCP Outputs` | Root directory for rendered images and saved scenes. |
|
|
132
133
|
| `KEYSHOT_ALLOW_EXTERNAL_OUTPUTS` | `false` | Allows output paths outside `KEYSHOT_OUTPUT_DIR` when explicitly set to `true`. |
|
|
133
134
|
| `KEYSHOT_TIMEOUT_MS` | `600000` | Timeout for one KeyShot headless process. |
|
|
134
135
|
| `KEYSHOT_LICENSE_ARGS` | empty | Optional additional KeyShot launch arguments. |
|
|
@@ -139,11 +140,17 @@ Relative output paths are resolved inside `KEYSHOT_OUTPUT_DIR`. Parent traversal
|
|
|
139
140
|
adjacent-prefix paths, and symbolic-link escapes are rejected by default. Input
|
|
140
141
|
scene, model, material, and environment paths may be located elsewhere.
|
|
141
142
|
|
|
143
|
+
Run `keyshot_status` after installation. It reports the MCP and KeyShot
|
|
144
|
+
versions, resolved executable, output write access, preset validity, headless
|
|
145
|
+
startup result, and actionable suggestions. Additional KeyShot launch arguments
|
|
146
|
+
are never included in the diagnostic output.
|
|
147
|
+
|
|
142
148
|
### Tools
|
|
143
149
|
|
|
144
150
|
| Tool | Purpose |
|
|
145
151
|
| --- | --- |
|
|
146
|
-
| `keyshot_status` |
|
|
152
|
+
| `keyshot_status` | Diagnose local configuration, output access, presets, and KeyShot headless startup. |
|
|
153
|
+
| `keyshot_product_render` | Prepare, save, and render a model or existing scene in one headless process. |
|
|
147
154
|
| `keyshot_inspect_scene` | Inspect objects, cameras, materials, model sets, and references. |
|
|
148
155
|
| `keyshot_list_cameras` | List available camera names. |
|
|
149
156
|
| `keyshot_render` | Render one image. |
|
|
@@ -160,9 +167,61 @@ scene, model, material, and environment paths may be located elsewhere.
|
|
|
160
167
|
| `keyshot_set_environment` | Select or adjust an environment, brightness, or rotation. |
|
|
161
168
|
| `keyshot_save_scene` | Save a scene to a new output path. |
|
|
162
169
|
|
|
163
|
-
The server
|
|
170
|
+
The server exposes 17 tools, a `keyshot_product_render` MCP prompt, and a
|
|
164
171
|
`keyshot://workflow` resource.
|
|
165
172
|
|
|
173
|
+
### One-click product render
|
|
174
|
+
|
|
175
|
+
In an MCP client, a designer can use a natural-language request:
|
|
176
|
+
|
|
177
|
+
```text
|
|
178
|
+
Turn C:\models\speaker.obj into a product render. Center it, place it on the
|
|
179
|
+
ground, use the Isometric camera preset, save the KeyShot scene, and render a
|
|
180
|
+
1600 x 1200 PNG with 128 samples.
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The `keyshot_product_render` tool accepts either `modelPath` or `scenePath`. New
|
|
184
|
+
models default to centered and grounded geometry with a `Product Hero`
|
|
185
|
+
isometric camera. Existing scenes keep their current camera, materials, and
|
|
186
|
+
environment unless explicit changes are requested.
|
|
187
|
+
|
|
188
|
+
One-click product rendering defaults to the `standard` quality preset. All
|
|
189
|
+
rendering tools accept:
|
|
190
|
+
|
|
191
|
+
| Preset | Resolution | Samples |
|
|
192
|
+
| --- | ---: | ---: |
|
|
193
|
+
| `preview` | 960 × 540 | 16 |
|
|
194
|
+
| `standard` | 1920 × 1080 | 64 |
|
|
195
|
+
| `final` | 3840 × 2160 | 256 |
|
|
196
|
+
|
|
197
|
+
Explicit `width`, `height`, or `samples` values override the corresponding
|
|
198
|
+
preset value. `maxTimeSeconds` replaces preset samples. Explicit `samples` and
|
|
199
|
+
`maxTimeSeconds` cannot be used together.
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"modelPath": "C:/models/speaker.obj",
|
|
204
|
+
"outputScenePath": "speaker-product.bip",
|
|
205
|
+
"outputPath": "speaker-product.png",
|
|
206
|
+
"materialAssignments": [
|
|
207
|
+
{ "objectName": "Body", "presetName": "Brushed Steel" }
|
|
208
|
+
],
|
|
209
|
+
"cameraPresetName": "Isometric",
|
|
210
|
+
"focalLength": 55,
|
|
211
|
+
"brightness": 1.2,
|
|
212
|
+
"rotation": 45,
|
|
213
|
+
"qualityPreset": "standard"
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Use `renderMode: "allCameras"` with `outputDir` to render every named camera.
|
|
218
|
+
Generated names are derived from the source filename when output paths are
|
|
219
|
+
omitted. If an automatically named result exists, the scene and image receive
|
|
220
|
+
the same `-2`, `-3`, and later suffix. Explicit output paths remain protected
|
|
221
|
+
and return an error when occupied; set `overwrite: true` only when replacement
|
|
222
|
+
is intentional. Material assignments always require an
|
|
223
|
+
explicit object and never overwrite the entire model implicitly.
|
|
224
|
+
|
|
166
225
|
### Product composition examples
|
|
167
226
|
|
|
168
227
|
Import a model and prepare its initial composition:
|
|
@@ -222,7 +281,8 @@ them.
|
|
|
222
281
|
The repository includes a smoke test built from generated cube geometry in
|
|
223
282
|
`examples/demo`. It verifies startup, import composition, scene inspection,
|
|
224
283
|
camera presets, focal length, field of view, camera distance, environment
|
|
225
|
-
rotation, scene saving, camera discovery,
|
|
284
|
+
rotation, scene saving, camera discovery, one-click model rendering, one-click
|
|
285
|
+
existing-scene rendering, and real PNG output:
|
|
226
286
|
|
|
227
287
|
```bash
|
|
228
288
|
npm run smoke:keyshot
|
|
@@ -254,18 +314,6 @@ Linux.
|
|
|
254
314
|
- Add depth-of-field and additional lens controls when stable headless APIs are
|
|
255
315
|
available.
|
|
256
316
|
|
|
257
|
-
### Star history
|
|
258
|
-
|
|
259
|
-
<picture>
|
|
260
|
-
<source media="(prefers-color-scheme: dark)" srcset="assets/star-history-dark.svg">
|
|
261
|
-
<source media="(prefers-color-scheme: light)" srcset="assets/star-history-light.svg">
|
|
262
|
-
<img alt="GitHub star history for truman-t3/keyshot-mcp" src="assets/star-history-light.svg">
|
|
263
|
-
</picture>
|
|
264
|
-
|
|
265
|
-
The chart uses the actual `starred_at` date for each GitHub star and displays a
|
|
266
|
-
cumulative step line. A weekly GitHub Action updates it only when detailed
|
|
267
|
-
stargazer dates are available.
|
|
268
|
-
|
|
269
317
|
### License and security
|
|
270
318
|
|
|
271
319
|
Released under the [MIT License](LICENSE). See [SECURITY.md](SECURITY.md) for
|
|
@@ -273,6 +321,18 @@ security guidance and [CONTRIBUTING.md](CONTRIBUTING.md) for development notes.
|
|
|
273
321
|
Do not commit KeyShot licenses, private scenes, customer assets, or unpublished
|
|
274
322
|
renders.
|
|
275
323
|
|
|
324
|
+
### Trademark and project status
|
|
325
|
+
|
|
326
|
+
KeyShot is a trademark of KeyShot ApS and/or KeyShot Inc. This is an independent,
|
|
327
|
+
open-source community project and is not affiliated with, endorsed by, or
|
|
328
|
+
sponsored by KeyShot.
|
|
329
|
+
|
|
330
|
+
This project provides only an MCP integration. It does not include KeyShot
|
|
331
|
+
Studio, KeyShot assets, or a KeyShot license. Users must install and license
|
|
332
|
+
KeyShot Studio separately and comply with the applicable KeyShot terms. Do not
|
|
333
|
+
use this project to bypass licensing, share credentials, or redistribute KeyShot
|
|
334
|
+
software or proprietary resources.
|
|
335
|
+
|
|
276
336
|
---
|
|
277
337
|
|
|
278
338
|
## 中文
|
|
@@ -291,6 +351,7 @@ headless 脚本让兼容 MCP 的 AI Agent 检查、编辑和渲染 KeyShot 场
|
|
|
291
351
|
- 直接应用材质,或使用本地材质预设库。
|
|
292
352
|
- 选择环境、调整亮度并旋转当前环境。
|
|
293
353
|
- 将修改后的场景保存到受控输出目录。
|
|
354
|
+
- 通过一次工具调用完成模型或现有场景的产品构图与渲染。
|
|
294
355
|
|
|
295
356
|
### 运行要求与兼容性
|
|
296
357
|
|
|
@@ -302,7 +363,7 @@ headless 脚本让兼容 MCP 的 AI Agent 检查、编辑和渲染 KeyShot 场
|
|
|
302
363
|
|
|
303
364
|
### 安装
|
|
304
365
|
|
|
305
|
-
当前版本为 `0.
|
|
366
|
+
当前版本为 `0.9.0`。
|
|
306
367
|
|
|
307
368
|
#### 方式一:使用 npx
|
|
308
369
|
|
|
@@ -313,7 +374,7 @@ headless 脚本让兼容 MCP 的 AI Agent 检查、编辑和渲染 KeyShot 场
|
|
|
313
374
|
"mcpServers": {
|
|
314
375
|
"keyshot": {
|
|
315
376
|
"command": "npx",
|
|
316
|
-
"args": ["-y", "keyshot-mcp@0.
|
|
377
|
+
"args": ["-y", "keyshot-mcp@0.9.0"],
|
|
317
378
|
"env": {
|
|
318
379
|
"KEYSHOT_HEADLESS_EXE": "C:/Program Files/KeyShot Studio/bin/keyshot_headless.exe"
|
|
319
380
|
}
|
|
@@ -325,7 +386,7 @@ headless 脚本让兼容 MCP 的 AI Agent 检查、编辑和渲染 KeyShot 场
|
|
|
325
386
|
#### 方式二:全局安装
|
|
326
387
|
|
|
327
388
|
```bash
|
|
328
|
-
npm install -g keyshot-mcp@0.
|
|
389
|
+
npm install -g keyshot-mcp@0.9.0
|
|
329
390
|
```
|
|
330
391
|
|
|
331
392
|
```json
|
|
@@ -373,14 +434,14 @@ pnpm build
|
|
|
373
434
|
下面的提示词适用于有权限修改 MCP 客户端配置的编程 Agent:
|
|
374
435
|
|
|
375
436
|
```text
|
|
376
|
-
请帮我安装 KeyShot MCP 0.
|
|
437
|
+
请帮我安装 KeyShot MCP 0.9.0,并添加到我的 MCP 客户端。
|
|
377
438
|
|
|
378
439
|
1. 查找本机 KeyShot headless 可执行文件。
|
|
379
440
|
2. 添加名为 keyshot 的 MCP server,运行:
|
|
380
|
-
npx -y keyshot-mcp@0.
|
|
441
|
+
npx -y keyshot-mcp@0.9.0
|
|
381
442
|
3. 将 KEYSHOT_HEADLESS_EXE 设置为可执行文件路径。
|
|
382
443
|
4. 保持 KEYSHOT_ALLOW_EXTERNAL_OUTPUTS 关闭。
|
|
383
|
-
5. 重启或重新加载 MCP
|
|
444
|
+
5. 重启或重新加载 MCP 客户端,调用 keyshot_status,然后使用 keyshot_product_render 一键完成产品出图。
|
|
384
445
|
6. 不要上传或发布任何 KeyShot 场景、模型、渲染图或许可证数据。
|
|
385
446
|
```
|
|
386
447
|
|
|
@@ -389,7 +450,7 @@ pnpm build
|
|
|
389
450
|
| 环境变量 | 默认值 | 用途 |
|
|
390
451
|
| --- | --- | --- |
|
|
391
452
|
| `KEYSHOT_HEADLESS_EXE` | Windows 上为 `keyshot_headless.exe` | KeyShot headless 绝对路径,或系统 `PATH` 中的命令。 |
|
|
392
|
-
| `KEYSHOT_OUTPUT_DIR` |
|
|
453
|
+
| `KEYSHOT_OUTPUT_DIR` | `~/Documents/KeyShot MCP Outputs` | 渲染图和已保存场景的根目录。 |
|
|
393
454
|
| `KEYSHOT_ALLOW_EXTERNAL_OUTPUTS` | `false` | 明确设为 `true` 时允许写入输出根目录之外。 |
|
|
394
455
|
| `KEYSHOT_TIMEOUT_MS` | `600000` | 单个 KeyShot headless 进程的超时时间。 |
|
|
395
456
|
| `KEYSHOT_LICENSE_ARGS` | 空 | 可选的 KeyShot 启动参数。 |
|
|
@@ -399,11 +460,16 @@ pnpm build
|
|
|
399
460
|
相对输出路径会自动放入 `KEYSHOT_OUTPUT_DIR`。默认拒绝 `..`、相邻同名前缀目录和
|
|
400
461
|
软链接逃逸。输入场景、模型、材质和环境文件可位于其他目录。
|
|
401
462
|
|
|
463
|
+
安装后调用 `keyshot_status`,可检查 MCP 与 KeyShot 版本、实际可执行文件路径、
|
|
464
|
+
输出目录写入权限、预设文件和 headless 启动状态,并获得可直接执行的修复建议。
|
|
465
|
+
诊断结果不会回显额外的 KeyShot 启动参数。
|
|
466
|
+
|
|
402
467
|
### MCP 工具
|
|
403
468
|
|
|
404
469
|
| 工具 | 用途 |
|
|
405
470
|
| --- | --- |
|
|
406
|
-
| `keyshot_status` |
|
|
471
|
+
| `keyshot_status` | 诊断本地配置、输出权限、预设文件和 KeyShot headless 启动状态。 |
|
|
472
|
+
| `keyshot_product_render` | 在一个 headless 进程中完成模型或场景准备、保存和渲染。 |
|
|
407
473
|
| `keyshot_inspect_scene` | 检查对象、相机、材质、模型集和外部引用。 |
|
|
408
474
|
| `keyshot_list_cameras` | 列出场景中的相机名称。 |
|
|
409
475
|
| `keyshot_render` | 渲染一张图片。 |
|
|
@@ -420,7 +486,54 @@ pnpm build
|
|
|
420
486
|
| `keyshot_set_environment` | 选择环境并调整亮度或旋转角度。 |
|
|
421
487
|
| `keyshot_save_scene` | 将场景保存到新的输出路径。 |
|
|
422
488
|
|
|
423
|
-
|
|
489
|
+
服务共提供 17 个工具,并提供 `keyshot_product_render` MCP 提示词和
|
|
490
|
+
`keyshot://workflow` 资源。
|
|
491
|
+
|
|
492
|
+
### 一键产品出图
|
|
493
|
+
|
|
494
|
+
设计师可以直接在支持 MCP 的 Agent 中描述需求:
|
|
495
|
+
|
|
496
|
+
```text
|
|
497
|
+
把 C:\models\speaker.obj 做成产品渲染图。自动居中、贴地,使用 Isometric
|
|
498
|
+
相机预设,保存 KeyShot 场景,并用 128 采样渲染一张 1600 x 1200 PNG。
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
`keyshot_product_render` 可以接收 `modelPath` 或 `scenePath`。新模型默认自动居中、
|
|
502
|
+
贴地并创建名为 `Product Hero` 的等轴测相机;已有场景默认保留当前相机、材质和环境,
|
|
503
|
+
只有明确提供参数时才修改。
|
|
504
|
+
|
|
505
|
+
一键产品出图未指定质量时默认使用 `standard`。全部渲染工具都支持:
|
|
506
|
+
|
|
507
|
+
| 预设 | 分辨率 | 采样 |
|
|
508
|
+
| --- | ---: | ---: |
|
|
509
|
+
| `preview` | 960 × 540 | 16 |
|
|
510
|
+
| `standard` | 1920 × 1080 | 64 |
|
|
511
|
+
| `final` | 3840 × 2160 | 256 |
|
|
512
|
+
|
|
513
|
+
显式填写的 `width`、`height` 或 `samples` 会分别覆盖预设值。
|
|
514
|
+
`maxTimeSeconds` 会替代预设采样;不能同时显式提供 `samples` 和
|
|
515
|
+
`maxTimeSeconds`。
|
|
516
|
+
|
|
517
|
+
```json
|
|
518
|
+
{
|
|
519
|
+
"modelPath": "C:/models/speaker.obj",
|
|
520
|
+
"outputScenePath": "speaker-product.bip",
|
|
521
|
+
"outputPath": "speaker-product.png",
|
|
522
|
+
"materialAssignments": [
|
|
523
|
+
{ "objectName": "Body", "presetName": "Brushed Steel" }
|
|
524
|
+
],
|
|
525
|
+
"cameraPresetName": "Isometric",
|
|
526
|
+
"focalLength": 55,
|
|
527
|
+
"brightness": 1.2,
|
|
528
|
+
"rotation": 45,
|
|
529
|
+
"qualityPreset": "standard"
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
将 `renderMode` 设为 `allCameras` 并提供 `outputDir`,即可渲染场景中的全部命名相机。
|
|
534
|
+
省略输出路径时会根据源文件名自动生成。自动名称已存在时,场景和图片会统一增加
|
|
535
|
+
`-2`、`-3` 等编号;用户明确填写的路径仍会严格防止覆盖。只有明确设置
|
|
536
|
+
`overwrite: true` 才会替换。材质指定必须包含明确的对象,不会隐式覆盖整个模型。
|
|
424
537
|
|
|
425
538
|
### 产品构图示例
|
|
426
539
|
|
|
@@ -474,7 +587,7 @@ pnpm build
|
|
|
474
587
|
|
|
475
588
|
仓库提供基于 `examples/demo` 生成立方体几何体的 smoke test,用于验证 KeyShot
|
|
476
589
|
启动、导入构图、场景检查、相机预设、焦距、视野角、相机距离、环境旋转、场景保存、
|
|
477
|
-
|
|
590
|
+
相机发现、模型一键出图、现有场景一键渲染全部相机和真实 PNG 输出:
|
|
478
591
|
|
|
479
592
|
```bash
|
|
480
593
|
npm run smoke:keyshot
|
|
@@ -503,19 +616,17 @@ CI 在 Windows 和 Ubuntu 上使用 Node.js 20、24 运行。Linux CI 验证 MCP
|
|
|
503
616
|
- 验证 macOS 安装和 headless 行为。
|
|
504
617
|
- 在 headless API 稳定支持后增加景深和更多镜头控制。
|
|
505
618
|
|
|
506
|
-
### 星标趋势
|
|
507
|
-
|
|
508
|
-
<picture>
|
|
509
|
-
<source media="(prefers-color-scheme: dark)" srcset="assets/star-history-dark.svg">
|
|
510
|
-
<source media="(prefers-color-scheme: light)" srcset="assets/star-history-light.svg">
|
|
511
|
-
<img alt="truman-t3/keyshot-mcp 的 GitHub 星标趋势" src="assets/star-history-light.svg">
|
|
512
|
-
</picture>
|
|
513
|
-
|
|
514
|
-
趋势图使用每颗 GitHub Star 的真实 `starred_at` 日期绘制累计阶梯线。GitHub Actions
|
|
515
|
-
每周检查一次,并且只在能够取得逐星日期时更新。
|
|
516
|
-
|
|
517
619
|
### 许可证与安全
|
|
518
620
|
|
|
519
621
|
项目采用 [MIT License](LICENSE)。安全说明见 [SECURITY.md](SECURITY.md),开发说明
|
|
520
622
|
见 [CONTRIBUTING.md](CONTRIBUTING.md)。请勿提交 KeyShot 许可证、私有场景、客户素材
|
|
521
623
|
或未公开渲染图。
|
|
624
|
+
|
|
625
|
+
### 商标与项目性质
|
|
626
|
+
|
|
627
|
+
KeyShot 是 KeyShot ApS 和/或 KeyShot Inc. 的商标。本项目是独立的开源社区项目,
|
|
628
|
+
与 KeyShot 官方无隶属、认可、赞助或其他合作关系。
|
|
629
|
+
|
|
630
|
+
本项目仅提供 MCP 集成功能,不包含 KeyShot Studio、KeyShot 官方素材或 KeyShot
|
|
631
|
+
许可证。用户必须自行安装并合法授权 KeyShot Studio,同时遵守适用的 KeyShot 条款。
|
|
632
|
+
不得使用本项目绕过许可证、共享账号凭据,或重新分发 KeyShot 软件及其专有资源。
|
package/dist/cli/status.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getConfig } from "../config.js";
|
|
2
|
-
import {
|
|
3
|
-
const result = await
|
|
2
|
+
import { runKeyShotDiagnostics } from "../diagnostics.js";
|
|
3
|
+
const result = await runKeyShotDiagnostics(getConfig());
|
|
4
4
|
console.log(JSON.stringify(result, null, 2));
|
|
5
5
|
process.exit(result.ok ? 0 : 1);
|
package/dist/config.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
const DEFAULT_KEYSHOT_EXE = process.platform === "win32" ? "keyshot_headless.exe" : "keyshot_headless";
|
|
4
5
|
const DEFAULT_TIMEOUT_MS = 600_000;
|
|
5
6
|
export function getConfig() {
|
|
6
7
|
const projectRoot = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
7
|
-
const keyshotOutputDir = path.resolve(process.env.KEYSHOT_OUTPUT_DIR ??
|
|
8
|
+
const keyshotOutputDir = path.resolve(process.env.KEYSHOT_OUTPUT_DIR ?? defaultOutputDir());
|
|
8
9
|
return {
|
|
9
10
|
projectRoot,
|
|
10
11
|
keyshotHeadlessExe: process.env.KEYSHOT_HEADLESS_EXE ?? DEFAULT_KEYSHOT_EXE,
|
|
@@ -18,6 +19,9 @@ export function getConfig() {
|
|
|
18
19
|
cameraPresetsPath: path.resolve(process.env.KEYSHOT_CAMERA_PRESETS ?? path.join(projectRoot, "presets", "cameras.json")),
|
|
19
20
|
};
|
|
20
21
|
}
|
|
22
|
+
export function defaultOutputDir() {
|
|
23
|
+
return path.join(os.homedir(), "Documents", "KeyShot MCP Outputs");
|
|
24
|
+
}
|
|
21
25
|
function parseBoolean(value) {
|
|
22
26
|
return ["1", "true", "yes", "on"].includes((value ?? "").trim().toLowerCase());
|
|
23
27
|
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { loadCameraPresets } from "./camera-presets.js";
|
|
5
|
+
import { loadMaterialPresets } from "./presets.js";
|
|
6
|
+
import { runKeyShotSerialized } from "./runner.js";
|
|
7
|
+
import { VERSION } from "./version.js";
|
|
8
|
+
export async function runKeyShotDiagnostics(config, runStatus = runKeyShotSerialized) {
|
|
9
|
+
const checks = [];
|
|
10
|
+
const resolvedExecutable = await resolveExecutable(config.keyshotHeadlessExe);
|
|
11
|
+
checks.push({
|
|
12
|
+
id: "keyshot-executable",
|
|
13
|
+
label: "KeyShot headless executable",
|
|
14
|
+
ok: resolvedExecutable !== null,
|
|
15
|
+
severity: "error",
|
|
16
|
+
message: resolvedExecutable
|
|
17
|
+
? `Resolved KeyShot headless executable: ${resolvedExecutable}`
|
|
18
|
+
: `KeyShot headless executable was not found: ${config.keyshotHeadlessExe}`,
|
|
19
|
+
suggestion: resolvedExecutable
|
|
20
|
+
? undefined
|
|
21
|
+
: "Set KEYSHOT_HEADLESS_EXE to the full path of keyshot_headless.exe and restart the MCP client.",
|
|
22
|
+
});
|
|
23
|
+
checks.push(await fileCheck("bridge-script", "KeyShot bridge script", config.bridgeScriptPath, "Reinstall keyshot-mcp so scripts/keyshot_bridge.py is included."));
|
|
24
|
+
checks.push(await outputWriteCheck(config.keyshotOutputDir));
|
|
25
|
+
checks.push(await presetCheck("camera-presets", "Camera presets", () => loadCameraPresets(config), config.cameraPresetsPath));
|
|
26
|
+
checks.push(await presetCheck("material-presets", "Material presets", () => loadMaterialPresets(config), config.materialPresetsPath));
|
|
27
|
+
let keyshotResult;
|
|
28
|
+
if (resolvedExecutable && checks.find((check) => check.id === "bridge-script")?.ok) {
|
|
29
|
+
keyshotResult = await runStatus(config, { operation: "status" });
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
keyshotResult = {
|
|
33
|
+
ok: false,
|
|
34
|
+
data: null,
|
|
35
|
+
outputFiles: [],
|
|
36
|
+
warnings: [],
|
|
37
|
+
keyshotStdoutTail: "",
|
|
38
|
+
error: "KeyShot startup was skipped because a required local file was not available.",
|
|
39
|
+
errorCode: "DIAGNOSTIC_PREREQUISITE_FAILED",
|
|
40
|
+
suggestions: [],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
checks.push({
|
|
44
|
+
id: "keyshot-startup",
|
|
45
|
+
label: "KeyShot headless startup",
|
|
46
|
+
ok: keyshotResult.ok,
|
|
47
|
+
severity: "error",
|
|
48
|
+
message: keyshotResult.ok
|
|
49
|
+
? "KeyShot headless scripting started successfully."
|
|
50
|
+
: keyshotResult.error ?? "KeyShot headless scripting did not start.",
|
|
51
|
+
suggestion: keyshotResult.ok
|
|
52
|
+
? undefined
|
|
53
|
+
: keyshotResult.suggestions?.[0] ?? "Open KeyShot normally, confirm the license is active, and retry.",
|
|
54
|
+
});
|
|
55
|
+
const ready = checks.every((check) => check.severity !== "error" || check.ok);
|
|
56
|
+
const suggestions = unique([
|
|
57
|
+
...checks.flatMap((check) => !check.ok && check.suggestion ? [check.suggestion] : []),
|
|
58
|
+
...(keyshotResult.suggestions ?? []),
|
|
59
|
+
]);
|
|
60
|
+
const bridgeData = keyshotResult.data && typeof keyshotResult.data === "object"
|
|
61
|
+
? keyshotResult.data
|
|
62
|
+
: {};
|
|
63
|
+
const availableFunctions = Array.isArray(bridgeData.availableFunctions)
|
|
64
|
+
? bridgeData.availableFunctions.filter((name) => typeof name === "string")
|
|
65
|
+
: [];
|
|
66
|
+
const { availableFunctions: _omittedFunctions, ...bridgeSummary } = bridgeData;
|
|
67
|
+
return {
|
|
68
|
+
...keyshotResult,
|
|
69
|
+
ok: ready,
|
|
70
|
+
data: {
|
|
71
|
+
...bridgeSummary,
|
|
72
|
+
availableFunctionCount: availableFunctions.length,
|
|
73
|
+
capabilities: capabilitySummary(availableFunctions),
|
|
74
|
+
serverVersion: VERSION,
|
|
75
|
+
ready,
|
|
76
|
+
config: {
|
|
77
|
+
keyshotHeadlessExe: config.keyshotHeadlessExe,
|
|
78
|
+
resolvedExecutable,
|
|
79
|
+
outputDir: config.keyshotOutputDir,
|
|
80
|
+
allowExternalOutputs: config.keyshotAllowExternalOutputs,
|
|
81
|
+
timeoutMs: config.keyshotTimeoutMs,
|
|
82
|
+
licenseArgsConfigured: config.keyshotLicenseArgs.length > 0,
|
|
83
|
+
bridgeScriptPath: config.bridgeScriptPath,
|
|
84
|
+
cameraPresetsPath: config.cameraPresetsPath,
|
|
85
|
+
materialPresetsPath: config.materialPresetsPath,
|
|
86
|
+
},
|
|
87
|
+
checks,
|
|
88
|
+
suggestions,
|
|
89
|
+
},
|
|
90
|
+
error: ready ? null : keyshotResult.error ?? "KeyShot MCP is not ready. Review the failed diagnostic checks.",
|
|
91
|
+
errorCode: ready ? null : keyshotResult.errorCode ?? "DIAGNOSTIC_FAILED",
|
|
92
|
+
suggestions,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function capabilitySummary(availableFunctions) {
|
|
96
|
+
const available = new Set(availableFunctions);
|
|
97
|
+
return {
|
|
98
|
+
render: available.has("renderImage"),
|
|
99
|
+
inspectObjects: available.has("getObjects"),
|
|
100
|
+
cameras: available.has("getCameras") && available.has("newCamera"),
|
|
101
|
+
standardViews: available.has("setStandardView"),
|
|
102
|
+
importModel: available.has("importFile"),
|
|
103
|
+
materials: available.has("setObjectMaterial"),
|
|
104
|
+
renderOptions: available.has("getRenderOptions"),
|
|
105
|
+
environments: available.has("getActiveEnvironment"),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export async function resolveExecutable(command) {
|
|
109
|
+
if (path.isAbsolute(command) || command.includes("/") || command.includes("\\")) {
|
|
110
|
+
const candidate = path.resolve(command);
|
|
111
|
+
return await isFile(candidate) ? candidate : null;
|
|
112
|
+
}
|
|
113
|
+
const pathEntries = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
|
|
114
|
+
const extensions = process.platform === "win32" && path.extname(command) === ""
|
|
115
|
+
? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
|
|
116
|
+
: [""];
|
|
117
|
+
for (const entry of pathEntries) {
|
|
118
|
+
for (const extension of extensions) {
|
|
119
|
+
const candidate = path.join(entry.replace(/^"|"$/g, ""), `${command}${extension}`);
|
|
120
|
+
if (await isFile(candidate))
|
|
121
|
+
return path.resolve(candidate);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
async function fileCheck(id, label, filePath, suggestion) {
|
|
127
|
+
const ok = await isFile(filePath);
|
|
128
|
+
return { id, label, ok, severity: "error", message: ok ? `Found: ${filePath}` : `Missing: ${filePath}`, suggestion: ok ? undefined : suggestion };
|
|
129
|
+
}
|
|
130
|
+
async function outputWriteCheck(outputDir) {
|
|
131
|
+
const probe = path.join(outputDir, `.keyshot-mcp-write-test-${randomUUID()}`);
|
|
132
|
+
try {
|
|
133
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
134
|
+
await fs.writeFile(probe, "ok", "utf8");
|
|
135
|
+
await fs.rm(probe, { force: true });
|
|
136
|
+
return {
|
|
137
|
+
id: "output-directory",
|
|
138
|
+
label: "Output directory",
|
|
139
|
+
ok: true,
|
|
140
|
+
severity: "error",
|
|
141
|
+
message: `Output directory is writable: ${outputDir}`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
await fs.rm(probe, { force: true }).catch(() => undefined);
|
|
146
|
+
return {
|
|
147
|
+
id: "output-directory",
|
|
148
|
+
label: "Output directory",
|
|
149
|
+
ok: false,
|
|
150
|
+
severity: "error",
|
|
151
|
+
message: `Output directory is not writable: ${outputDir} (${errorMessage(error)})`,
|
|
152
|
+
suggestion: "Set KEYSHOT_OUTPUT_DIR to a local folder where the current user can create files.",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function presetCheck(id, label, loader, source) {
|
|
157
|
+
try {
|
|
158
|
+
const presets = await loader();
|
|
159
|
+
return {
|
|
160
|
+
id,
|
|
161
|
+
label,
|
|
162
|
+
ok: true,
|
|
163
|
+
severity: "warning",
|
|
164
|
+
message: `${label} loaded successfully (${presets.length}) from ${source}`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
return {
|
|
169
|
+
id,
|
|
170
|
+
label,
|
|
171
|
+
ok: false,
|
|
172
|
+
severity: "warning",
|
|
173
|
+
message: errorMessage(error),
|
|
174
|
+
suggestion: `Fix the JSON syntax in ${source}, or remove the custom environment variable to use built-in presets.`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function isFile(value) {
|
|
179
|
+
try {
|
|
180
|
+
return (await fs.stat(value)).isFile();
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function unique(values) {
|
|
187
|
+
return [...new Set(values)];
|
|
188
|
+
}
|
|
189
|
+
function errorMessage(error) {
|
|
190
|
+
return error instanceof Error ? error.message : String(error);
|
|
191
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,11 @@ import { runKeyShotSerialized } from "./runner.js";
|
|
|
7
7
|
import { runRenderQueue } from "./queue.js";
|
|
8
8
|
import { loadMaterialPresets, findMaterialPreset } from "./presets.js";
|
|
9
9
|
import { loadCameraPresets, findCameraPreset } from "./camera-presets.js";
|
|
10
|
+
import { prepareProductRenderRequest } from "./product-render.js";
|
|
10
11
|
import { VERSION } from "./version.js";
|
|
11
|
-
import {
|
|
12
|
+
import { applyRenderQuality } from "./quality.js";
|
|
13
|
+
import { runKeyShotDiagnostics } from "./diagnostics.js";
|
|
14
|
+
import { applyMaterialSchema, applyMaterialInputSchema, applyMaterialPresetInputSchema, applyMaterialPresetSchema, applyCameraPresetSchema, batchRenderSchema, batchRenderInputSchema, importModelSchema, listCamerasSchema, listMaterialPresetsSchema, listCameraPresetsSchema, productRenderInputSchema, productRenderSchema, renderQueueSchema, renderQueueInputSchema, renderAllCamerasSchema, renderAllCamerasInputSchema, renderSchema, renderInputSchema, saveSceneSchema, scenePathSchema, setCameraInputSchema, setCameraSchema, setEnvironmentSchema, } from "./schemas.js";
|
|
12
15
|
const config = getConfig();
|
|
13
16
|
const server = new McpServer({
|
|
14
17
|
name: "keyshot-mcp",
|
|
@@ -39,6 +42,7 @@ server.registerPrompt("keyshot_product_render", {
|
|
|
39
42
|
title: "Render a KeyShot product scene",
|
|
40
43
|
description: "Create a practical prompt for rendering or batch-rendering a KeyShot product scene.",
|
|
41
44
|
argsSchema: {
|
|
45
|
+
modelPath: scenePathSchema.shape.scenePath.optional(),
|
|
42
46
|
scenePath: scenePathSchema.shape.scenePath.optional(),
|
|
43
47
|
goal: scenePathSchema.shape.scenePath.optional(),
|
|
44
48
|
},
|
|
@@ -50,32 +54,46 @@ server.registerPrompt("keyshot_product_render", {
|
|
|
50
54
|
type: "text",
|
|
51
55
|
text: [
|
|
52
56
|
"Use KeyShot MCP to prepare a product render.",
|
|
53
|
-
args.
|
|
57
|
+
args.modelPath
|
|
58
|
+
? `Model path: ${args.modelPath}`
|
|
59
|
+
: args.scenePath
|
|
60
|
+
? `Scene path: ${args.scenePath}`
|
|
61
|
+
: "Ask me for either a model path or a KeyShot scene path first.",
|
|
54
62
|
args.goal ? `Goal: ${args.goal}` : "Inspect the scene, choose a suitable camera, then render a PNG preview.",
|
|
55
|
-
"
|
|
63
|
+
"Call the keyshot_product_render tool for the complete workflow. Use the lower-level tools only when individual steps need manual control.",
|
|
56
64
|
].join("\n"),
|
|
57
65
|
},
|
|
58
66
|
},
|
|
59
67
|
],
|
|
60
68
|
}));
|
|
61
|
-
server.tool("keyshot_status", "
|
|
69
|
+
server.tool("keyshot_status", "Diagnose the local KeyShot MCP installation and verify headless startup.", {}, async () => toolResponse(await runKeyShotDiagnostics(config)));
|
|
70
|
+
server.tool("keyshot_product_render", "Prepare and render a product from either a model file or an existing KeyShot scene in one headless process.", productRenderInputSchema.shape, async (args) => {
|
|
71
|
+
try {
|
|
72
|
+
const parsed = productRenderSchema.parse(args);
|
|
73
|
+
const request = await prepareProductRenderRequest(config, parsed);
|
|
74
|
+
return toolResponse(await runKeyShotSerialized(config, request));
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return toolResponse(localFailure(errorMessage(error)));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
62
80
|
server.tool("keyshot_inspect_scene", "Open a KeyShot scene and return available objects, cameras, materials and scene metadata.", scenePathSchema.shape, async (args) => toolResponse(await runKeyShotSerialized(config, { operation: "inspect_scene", ...args })));
|
|
63
81
|
server.tool("keyshot_list_cameras", "Open a KeyShot scene and return the list of available camera names (useful before batch rendering).", listCamerasSchema.shape, async (args) => toolResponse(await runKeyShotSerialized(config, { operation: "list_cameras", ...args })));
|
|
64
82
|
server.tool("keyshot_render", "Render a KeyShot scene to an image file.", renderSchema.shape, async (args) => {
|
|
65
83
|
const parsed = renderInputSchema.parse(args);
|
|
66
|
-
return toolResponse(await runKeyShotSerialized(config, { operation: "render", ...parsed }));
|
|
84
|
+
return toolResponse(await runKeyShotSerialized(config, { operation: "render", ...applyRenderQuality(parsed) }));
|
|
67
85
|
});
|
|
68
86
|
server.tool("keyshot_render_queue", "Render several jobs sequentially. Stops at the first failure unless continueOnError is set.", renderQueueSchema.shape, async (args) => {
|
|
69
87
|
const parsed = renderQueueInputSchema.parse(args);
|
|
70
|
-
return toolResponse(await runRenderQueue(config, parsed.jobs, { continueOnError: parsed.continueOnError ?? false }));
|
|
88
|
+
return toolResponse(await runRenderQueue(config, parsed.jobs.map((job) => applyRenderQuality(job)), { continueOnError: parsed.continueOnError ?? false }));
|
|
71
89
|
});
|
|
72
90
|
server.tool("keyshot_batch_render", "Render multiple named cameras from one KeyShot scene into an output directory.", batchRenderSchema.shape, async (args) => {
|
|
73
91
|
const parsed = batchRenderInputSchema.parse(args);
|
|
74
|
-
return toolResponse(await runKeyShotSerialized(config, { operation: "batch_render", ...parsed }));
|
|
92
|
+
return toolResponse(await runKeyShotSerialized(config, { operation: "batch_render", ...applyRenderQuality(parsed) }));
|
|
75
93
|
});
|
|
76
94
|
server.tool("keyshot_render_all_cameras", "Discover every camera in one KeyShot scene and render each view into an output directory.", renderAllCamerasSchema.shape, async (args) => {
|
|
77
95
|
const parsed = renderAllCamerasInputSchema.parse(args);
|
|
78
|
-
return toolResponse(await runKeyShotSerialized(config, { operation: "render_all_cameras", ...parsed }));
|
|
96
|
+
return toolResponse(await runKeyShotSerialized(config, { operation: "render_all_cameras", ...applyRenderQuality(parsed) }));
|
|
79
97
|
});
|
|
80
98
|
server.tool("keyshot_import_model", "Import a model into an optional base scene, optionally center and ground it, adjust the camera or environment, and save the resulting scene.", importModelSchema.shape, async (args) => toolResponse(await runKeyShotSerialized(config, {
|
|
81
99
|
operation: "import_model",
|