keyshot-mcp 0.13.1 → 0.13.5
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/CHANGELOG.md +43 -1
- package/CONTRIBUTING.md +15 -0
- package/README.md +38 -24
- package/SECURITY.md +66 -8
- package/dist/config.js +1 -1
- package/dist/diagnostics.js +25 -11
- package/dist/output-paths.js +73 -0
- package/dist/preview.js +55 -11
- package/dist/queue.js +5 -1
- package/dist/result.js +20 -0
- package/dist/runner.js +25 -20
- package/dist/save-sync.js +6 -0
- package/dist/schemas.js +14 -1
- package/dist/server.js +3 -0
- package/dist/version.js +1 -1
- package/docs/RELEASE-READINESS.md +85 -4
- package/docs/TOOLS.md +61 -53
- package/docs/UPGRADING.md +23 -0
- package/lhm.plugin.json +241 -491
- package/package.json +5 -2
- package/scripts/keyshot-output-smoke.mjs +144 -0
- package/scripts/keyshot_bridge.py +65 -10
- package/server.json +2 -2
- package/skills/keyshot-mcp/SKILL.md +12 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,47 @@ All notable changes to KeyShot MCP are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.13.5] - 2026-10-20
|
|
8
|
+
|
|
9
|
+
### Installation and Diagnostics / 安装与诊断
|
|
10
|
+
|
|
11
|
+
- Isolate each bridge request in a private temporary subdirectory, clean it after execution, and return structured failures for local filesystem errors. Preserve completed diagnostic checks when startup throws; check temporary storage before launching KeyShot.
|
|
12
|
+
- 每次桥接请求使用独立临时子目录并在执行后清理;本地文件系统出错时返回结构化结果。启动抛异常时保留已完成的诊断,并在启动 KeyShot 前检查临时目录。
|
|
13
|
+
|
|
14
|
+
- Store bridge request/result files in the system temporary directory instead of the installed package, avoiding Windows path-length failures in nested installations.
|
|
15
|
+
- 将桥接请求与结果文件移到系统临时目录,避免 Windows 嵌套安装路径过长导致 KeyShot 无法写回结果。
|
|
16
|
+
|
|
17
|
+
### Publication Privacy / 发布隐私
|
|
18
|
+
|
|
19
|
+
- Add a redacted public-content check to CI and publication, including credential
|
|
20
|
+
patterns, private paths, file names and PNG metadata. Reject unreviewed binary
|
|
21
|
+
assets; extend ignore rules for local environment, credential, scene and license files.
|
|
22
|
+
- 为 CI 与发布流程增加脱敏的公开内容检查,覆盖疑似凭据、私人路径、文件名及 PNG 元数据;
|
|
23
|
+
拦截未经复核的二进制素材,补全本地环境、凭据、场景和许可证文件忽略规则。
|
|
24
|
+
- Bound preview file reads before allocation and while reading, reject special-file
|
|
25
|
+
output destinations, and check case variants and malformed PNG metadata containers.
|
|
26
|
+
- 预览读取在分配内存前和读取过程中均限制大小,拒绝特殊文件输出目标,并补查文件名
|
|
27
|
+
大小写变体及损坏的 PNG 元数据容器。
|
|
28
|
+
|
|
29
|
+
### Output Safety / 输出安全
|
|
30
|
+
|
|
31
|
+
- Apply default no-overwrite protection to single renders, queued jobs, imports,
|
|
32
|
+
scene edits and saves. Add optional `overwrite` to low-level writing tools without
|
|
33
|
+
renaming tools or removing inputs. Declared input files and linked aliases remain
|
|
34
|
+
protected even with overwrite enabled; scene and image outputs must be distinct.
|
|
35
|
+
- 将默认不覆盖保护统一到单张渲染、队列、导入、场景编辑与保存。底层写入工具增加可选
|
|
36
|
+
`overwrite`,不改工具名称或删除参数;即使允许覆盖,也不能替换本次声明的输入文件及其
|
|
37
|
+
链接别名,场景与图片不能写入同一文件。
|
|
38
|
+
- Recheck every generated camera output inside the bridge, protect every queue input,
|
|
39
|
+
disambiguate selected-camera filenames and avoid deleting rejected preview outputs.
|
|
40
|
+
- bridge 在逐相机写入前重新检查路径,保护整个队列的输入场景,为清理后同名的指定相机
|
|
41
|
+
生成不同文件名,并避免误删被拒绝的预览输出。
|
|
42
|
+
|
|
43
|
+
### Documentation / 文档
|
|
44
|
+
|
|
45
|
+
- Clarify the roles of the MCP client's Agent, client configuration and model provider in privacy notices; distinguish client-managed transmission from the local KeyShot MCP service.
|
|
46
|
+
- 明确隐私提示中 MCP 客户端 Agent、客户端配置与模型服务的职责,区分客户端处理的数据传输与本机 KeyShot MCP 服务。
|
|
47
|
+
|
|
7
48
|
## [0.13.1] - 2026-10-15
|
|
8
49
|
|
|
9
50
|
### Release Safety / 发布安全
|
|
@@ -143,7 +184,8 @@ All notable changes to KeyShot MCP are documented in this file.
|
|
|
143
184
|
|
|
144
185
|
- Documented that scene metadata and preview images may be sent by an MCP client to its configured model provider.
|
|
145
186
|
|
|
146
|
-
[Unreleased]: https://github.com/truman-t3/keyshot-mcp/compare/v0.13.
|
|
187
|
+
[Unreleased]: https://github.com/truman-t3/keyshot-mcp/compare/v0.13.5...HEAD
|
|
188
|
+
[0.13.5]: https://github.com/truman-t3/keyshot-mcp/compare/v0.13.1...v0.13.5
|
|
147
189
|
[0.13.1]: https://github.com/truman-t3/keyshot-mcp/compare/v0.13.0...v0.13.1
|
|
148
190
|
[0.13.0]: https://github.com/truman-t3/keyshot-mcp/compare/v0.12.1...v0.13.0
|
|
149
191
|
[0.12.1]: https://github.com/truman-t3/keyshot-mcp/compare/v0.12.0...v0.12.1
|
package/CONTRIBUTING.md
CHANGED
|
@@ -22,6 +22,7 @@ Requirements: Node.js 20 or newer, pnpm 10, and Python 3.11 or newer.
|
|
|
22
22
|
```bash
|
|
23
23
|
pnpm install --frozen-lockfile
|
|
24
24
|
pnpm run check
|
|
25
|
+
pnpm run check:public-safety
|
|
25
26
|
pnpm test
|
|
26
27
|
python -m unittest discover -s tests -p "test_*.py"
|
|
27
28
|
pnpm run format:check
|
|
@@ -38,6 +39,10 @@ reinspection, invalid-name rejection and an unsupported Wavefront MTL fixture.
|
|
|
38
39
|
Optionally pass `--material-file "<native KeyShot MTL>"` to test a trusted local
|
|
39
40
|
single-material file. The native file is read only, never copied into the repository.
|
|
40
41
|
Tests use generated geometry and a new output subfolder, not user scenes.
|
|
42
|
+
Run `pnpm run smoke:keyshot:outputs` to check existing-file rejection, source hashes,
|
|
43
|
+
explicit replacement of a distinct copy and PNG output in a new test subfolder.
|
|
44
|
+
Use a fresh `KEYSHOT_OUTPUT_DIR` when repeating the full smoke test, which uses fixed names.
|
|
45
|
+
`pnpm docs:tools` also synchronizes the platform manifest's input schemas.
|
|
41
46
|
CI on Linux validates the MCP and bridge logic; it does not verify KeyShot itself.
|
|
42
47
|
|
|
43
48
|
### Pull requests
|
|
@@ -54,6 +59,9 @@ Never include proprietary KeyShot scenes, license data, customer assets, credent
|
|
|
54
59
|
private paths, or unpublished renders in Issues, Discussions, commits, or pull requests.
|
|
55
60
|
Report vulnerabilities through GitHub's private security reporting form.
|
|
56
61
|
|
|
62
|
+
Use a GitHub no-reply commit email. Check filenames and image metadata as well as
|
|
63
|
+
visible content; new public images require review in `scripts/check-public-safety.mjs`.
|
|
64
|
+
|
|
57
65
|
## 中文
|
|
58
66
|
|
|
59
67
|
欢迎设计师、开发者、技术美术和测试人员参与。比较有价值的贡献包括:
|
|
@@ -74,6 +82,7 @@ Report vulnerabilities through GitHub's private security reporting form.
|
|
|
74
82
|
```bash
|
|
75
83
|
pnpm install --frozen-lockfile
|
|
76
84
|
pnpm run check
|
|
85
|
+
pnpm run check:public-safety
|
|
77
86
|
pnpm test
|
|
78
87
|
python -m unittest discover -s tests -p "test_*.py"
|
|
79
88
|
pnpm run format:check
|
|
@@ -89,6 +98,9 @@ KeyShot。
|
|
|
89
98
|
运行 `pnpm run smoke:keyshot:materials` 可验证材质预设、保存后复查、无效名称及不支持的
|
|
90
99
|
Wavefront MTL 拒绝;可选传入 `--material-file "<KeyShot 原生 MTL>"` 检查可信本地单材质文件。
|
|
91
100
|
原生文件只读,不复制进仓库;测试仅使用生成模型和新输出子目录,不覆盖用户场景。
|
|
101
|
+
运行 `pnpm run smoke:keyshot:outputs` 可验证重名拒绝、源文件哈希、明确覆盖另一份副本及
|
|
102
|
+
PNG 输出,文件保存在新测试子目录。完整 smoke 使用固定文件名,重复运行时请设置新的
|
|
103
|
+
`KEYSHOT_OUTPUT_DIR`。`pnpm docs:tools` 同时同步平台清单中的输入 Schema。
|
|
92
104
|
|
|
93
105
|
### Pull Request
|
|
94
106
|
|
|
@@ -100,3 +112,6 @@ Wavefront MTL 拒绝;可选传入 `--material-file "<KeyShot 原生 MTL>"` 检
|
|
|
100
112
|
|
|
101
113
|
不要在 Issue、Discussion、提交或 PR 中包含专有 KeyShot 场景、许可证数据、客户资产、
|
|
102
114
|
账号凭据、私人路径或未公开渲染图。安全漏洞请通过 GitHub 私密安全报告提交。
|
|
115
|
+
|
|
116
|
+
提交使用 GitHub no-reply 邮箱;除可见内容外,也检查文件名和图片元数据。
|
|
117
|
+
新增公开图片需要在 `scripts/check-public-safety.mjs` 中完成复核登记。
|
package/README.md
CHANGED
|
@@ -44,9 +44,14 @@
|
|
|
44
44
|
> computer as KeyShot. It does not read unsaved edits or control the open KeyShot window.
|
|
45
45
|
> **处理已保存的场景,不是实时 GUI 控制。** 服务在本机后台运行 KeyShot,不能读取窗口中尚未保存的修改。
|
|
46
46
|
>
|
|
47
|
-
>
|
|
48
|
-
>
|
|
49
|
-
>
|
|
47
|
+
> **Client Agent and privacy.** The Agent in your MCP client receives scene information
|
|
48
|
+
> and previews returned by tools. Depending on the client's implementation and settings,
|
|
49
|
+
> these results may be sent to its configured model provider. KeyShot MCP itself does
|
|
50
|
+
> not upload them. Review [privacy and security](SECURITY.md) before confidential work.
|
|
51
|
+
>
|
|
52
|
+
> **客户端 Agent 与隐私。** MCP 客户端中的 Agent 会接收工具返回的场景信息和预览;
|
|
53
|
+
> 这些内容可能根据客户端的实现与配置发送给其所选模型服务。KeyShot MCP 本身不主动上传这些内容。
|
|
54
|
+
> 保密项目请先阅读[隐私与安全说明](SECURITY.md)。
|
|
50
55
|
|
|
51
56
|
## From a Request to a Render / 一句话到出图
|
|
52
57
|
|
|
@@ -111,11 +116,12 @@ licensed KeyShot edition with headless scripting. **Windows 11 + KeyShot Studio
|
|
|
111
116
|
2025 / KeyShot 14.1 is tested.** Other systems and KeyShot versions are not verified.
|
|
112
117
|
KeyShot software, licenses and library assets are not included.
|
|
113
118
|
|
|
114
|
-
<!-- release-version: 0.13.
|
|
119
|
+
<!-- release-version: 0.13.5 -->
|
|
115
120
|
|
|
116
|
-
**Version 0.13.
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
**Version 0.13.5.** This maintenance batch
|
|
122
|
+
strengthens output protection, bounded previews, publication privacy, Windows
|
|
123
|
+
installation paths and startup diagnostics. Installation examples below target
|
|
124
|
+
this version. See the [release validation record and 1.0 checklist](docs/RELEASE-READINESS.md).
|
|
119
125
|
See [Changelog](CHANGELOG.md) and [upgrade notes](docs/UPGRADING.md).
|
|
120
126
|
|
|
121
127
|
#### Ask Your Agent to Install
|
|
@@ -123,7 +129,7 @@ See [Changelog](CHANGELOG.md) and [upgrade notes](docs/UPGRADING.md).
|
|
|
123
129
|
Send this to an Agent that can edit your client's MCP configuration:
|
|
124
130
|
|
|
125
131
|
```text
|
|
126
|
-
Install KeyShot MCP 0.13.
|
|
132
|
+
Install KeyShot MCP 0.13.5 in my MCP client using npx -y keyshot-mcp@0.13.5.
|
|
127
133
|
Find the full path of my local keyshot_headless.exe and set KEYSHOT_HEADLESS_EXE.
|
|
128
134
|
Keep the default Documents/KeyShot MCP Outputs folder and external outputs disabled.
|
|
129
135
|
Use my client's configuration format, restart or reload it, and run keyshot_status.
|
|
@@ -142,7 +148,7 @@ and replace the executable path with the actual path on your computer.
|
|
|
142
148
|
"mcpServers": {
|
|
143
149
|
"keyshot": {
|
|
144
150
|
"command": "npx",
|
|
145
|
-
"args": ["-y", "keyshot-mcp@0.13.
|
|
151
|
+
"args": ["-y", "keyshot-mcp@0.13.5"],
|
|
146
152
|
"env": {
|
|
147
153
|
"KEYSHOT_HEADLESS_EXE": "C:/Program Files/KeyShot Studio/bin/keyshot_headless.exe"
|
|
148
154
|
}
|
|
@@ -157,7 +163,7 @@ the executable, output access, bridge, presets and a minimal KeyShot startup.
|
|
|
157
163
|
<details>
|
|
158
164
|
<summary>Global npm installation or source build</summary>
|
|
159
165
|
|
|
160
|
-
**Global:** run `npm install -g keyshot-mcp@0.13.
|
|
166
|
+
**Global:** run `npm install -g keyshot-mcp@0.13.5`, then use `keyshot-mcp` as the
|
|
161
167
|
client command with no `npx` arguments. Keep the same environment settings.
|
|
162
168
|
|
|
163
169
|
**Source:**
|
|
@@ -306,15 +312,19 @@ See [material assignment and error guidance](docs/MATERIALS.md).
|
|
|
306
312
|
|
|
307
313
|
Inputs can come from any trusted local path. Output traversal and symlink/junction
|
|
308
314
|
escapes are rejected by default. Automatically named product outputs use `-2`,
|
|
309
|
-
`-3`, etc. on collision; explicit paths are not silently renamed.
|
|
310
|
-
|
|
315
|
+
`-3`, etc. on collision; explicit paths are not silently renamed. Writing tools reject
|
|
316
|
+
existing files by default. Set `overwrite: true` only to replace a distinct output
|
|
317
|
+
intentionally; it never permits replacing this request's input scene, model, material
|
|
318
|
+
or environment, including linked aliases. Preview and saved-scene sync never overwrite.
|
|
319
|
+
Keep scene and image destinations separate. Prefer scene copies.
|
|
311
320
|
KeyShot headless tasks run sequentially within each server instance to reduce
|
|
312
321
|
license and file conflicts; this does not coordinate separate MCP server processes.
|
|
313
322
|
|
|
314
|
-
Processing is local
|
|
315
|
-
and preview images to its selected model provider
|
|
316
|
-
|
|
317
|
-
|
|
323
|
+
Processing is local. When an Agent in your MCP client uses these tools, **the client
|
|
324
|
+
may send tool results, scene metadata and preview images to its selected model provider**,
|
|
325
|
+
depending on the client's implementation and settings. KeyShot MCP itself does not
|
|
326
|
+
upload these results. Review privacy settings before confidential work. No license
|
|
327
|
+
bypass or arbitrary Python execution is provided. See [Security](SECURITY.md).
|
|
318
328
|
|
|
319
329
|
### Questions and Support
|
|
320
330
|
|
|
@@ -374,8 +384,9 @@ Community conduct is covered by [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
|
374
384
|
KeyShot**。已实测 **Windows 11 + KeyShot Studio 2025 / KeyShot 14.1**;其他系统和
|
|
375
385
|
KeyShot 版本尚未实机验证。项目不包含 KeyShot、许可证或官方材质库。
|
|
376
386
|
|
|
377
|
-
**本版为 `0.13.
|
|
378
|
-
|
|
387
|
+
**本版为 `0.13.5`:** 集中加固输出保护、预览大小限制、发布隐私、
|
|
388
|
+
Windows 安装路径和启动诊断。下方安装示例使用此版本。
|
|
389
|
+
验证记录与正式版准备见[验收清单](docs/RELEASE-READINESS.md),
|
|
379
390
|
版本变更见[更新记录](CHANGELOG.md)和[升级说明](docs/UPGRADING.md)。
|
|
380
391
|
|
|
381
392
|
#### 把安装交给 Agent
|
|
@@ -383,7 +394,7 @@ KeyShot 版本尚未实机验证。项目不包含 KeyShot、许可证或官方
|
|
|
383
394
|
将下面这段话发给能够修改客户端 MCP 配置的 Agent:
|
|
384
395
|
|
|
385
396
|
```text
|
|
386
|
-
请用 npx -y keyshot-mcp@0.13.
|
|
397
|
+
请用 npx -y keyshot-mcp@0.13.5 将 KeyShot MCP 安装到我的 MCP 客户端。
|
|
387
398
|
找到本机 keyshot_headless.exe 的完整路径,设置 KEYSHOT_HEADLESS_EXE。
|
|
388
399
|
保留默认“文档/KeyShot MCP Outputs”输出目录,关闭外部输出。
|
|
389
400
|
按这个客户端实际支持的格式配置,重启或重新加载后运行 keyshot_status。
|
|
@@ -402,7 +413,7 @@ KeyShot 版本尚未实机验证。项目不包含 KeyShot、许可证或官方
|
|
|
402
413
|
"mcpServers": {
|
|
403
414
|
"keyshot": {
|
|
404
415
|
"command": "npx",
|
|
405
|
-
"args": ["-y", "keyshot-mcp@0.13.
|
|
416
|
+
"args": ["-y", "keyshot-mcp@0.13.5"],
|
|
406
417
|
"env": {
|
|
407
418
|
"KEYSHOT_HEADLESS_EXE": "C:/Program Files/KeyShot Studio/bin/keyshot_headless.exe"
|
|
408
419
|
}
|
|
@@ -417,7 +428,7 @@ KeyShot 版本尚未实机验证。项目不包含 KeyShot、许可证或官方
|
|
|
417
428
|
<details>
|
|
418
429
|
<summary>全局 npm 安装或源码运行</summary>
|
|
419
430
|
|
|
420
|
-
**全局安装:** 运行 `npm install -g keyshot-mcp@0.13.
|
|
431
|
+
**全局安装:** 运行 `npm install -g keyshot-mcp@0.13.5`,客户端命令改成 `keyshot-mcp`,
|
|
421
432
|
不再填写 `npx` 参数;环境变量保持一致。
|
|
422
433
|
|
|
423
434
|
**源码运行:**
|
|
@@ -549,12 +560,15 @@ pnpm run build
|
|
|
549
560
|
|
|
550
561
|
输入可以来自任意可信本地路径。默认拒绝越界路径及软链接/目录联接逃逸。
|
|
551
562
|
产品工具自动命名的输出重名时使用 `-2`、`-3` 等编号,显式路径不被静默改名。
|
|
552
|
-
|
|
563
|
+
写入工具默认拒绝覆盖已有文件。只有确实要替换另一个输出文件时才设置
|
|
564
|
+
`overwrite: true`;即使开启,也不能覆盖本次调用的输入场景、模型、材质或环境文件,
|
|
565
|
+
包括指向它们的链接。预览与保存同步始终不覆盖。场景和图片必须使用不同路径,平时使用场景副本。
|
|
553
566
|
同一个服务实例内的 KeyShot 后台任务按顺序执行,以减少许可证和文件冲突;
|
|
554
567
|
多个独立 MCP 服务进程之间不共享这一排队机制。
|
|
555
568
|
|
|
556
|
-
|
|
557
|
-
|
|
569
|
+
处理在本机完成。**MCP 客户端中的 Agent 使用工具时,返回的工具结果、场景元数据和
|
|
570
|
+
预览图可能根据客户端的实现与配置发送给所选模型服务。** KeyShot MCP 本身不主动上传
|
|
571
|
+
这些内容。保密项目请先核对隐私设置。项目不提供许可证绕过或任意 Python 执行。
|
|
558
572
|
更多说明见 [SECURITY.md](SECURITY.md)。
|
|
559
573
|
|
|
560
574
|
### 常见问题与支持
|
package/SECURITY.md
CHANGED
|
@@ -7,11 +7,12 @@ model, material, and environment files and can write rendered images or scene co
|
|
|
7
7
|
|
|
8
8
|
### Model-provider data flow
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
client
|
|
14
|
-
|
|
10
|
+
The Agent in an MCP client receives tool results, including scene metadata and
|
|
11
|
+
images returned by `keyshot_preview_render`. Depending on the client's implementation
|
|
12
|
+
and settings, those results may be sent to the user's selected model service.
|
|
13
|
+
The MCP client controls this data transmission; it is not an upload initiated by
|
|
14
|
+
KeyShot MCP. Review the client and model provider's privacy settings before using
|
|
15
|
+
private or customer work.
|
|
15
16
|
|
|
16
17
|
KeyShot MCP itself does not upload files, run a remote service, or collect telemetry.
|
|
17
18
|
It never requires license keys in prompts and does not expose arbitrary Python
|
|
@@ -28,6 +29,37 @@ execution.
|
|
|
28
29
|
explicitly required and trusted.
|
|
29
30
|
- Review a preview before approving edits or final renders, and preserve source scenes.
|
|
30
31
|
|
|
32
|
+
### Output protection
|
|
33
|
+
|
|
34
|
+
Writing tools reject existing files unless `overwrite: true` is explicitly supported
|
|
35
|
+
and requested. Input files declared in the call (including material assignments) and
|
|
36
|
+
all scene inputs in a render queue remain protected even when overwrite is enabled.
|
|
37
|
+
Path comparisons resolve links and detect hard-linked input aliases. Image and scene
|
|
38
|
+
outputs must not share the same file. Preview and saved-scene sync never overwrite.
|
|
39
|
+
|
|
40
|
+
Paths are checked before headless startup and again in the bridge immediately before
|
|
41
|
+
each save or render, including camera-generated names. These checks are not a filesystem
|
|
42
|
+
sandbox or a cross-process lock: do not run unrelated writers against the same output
|
|
43
|
+
files or modify their links while a task runs. Use a separate output folder per process.
|
|
44
|
+
|
|
45
|
+
Existing low-level workflows that previously replaced a file implicitly now need a new
|
|
46
|
+
destination or explicit `overwrite: true`; overwriting a declared input is not supported.
|
|
47
|
+
|
|
48
|
+
### Publication checks
|
|
49
|
+
|
|
50
|
+
Maintainers run `pnpm run check:public-safety` before a pull request or release.
|
|
51
|
+
CI rejects credential-like values, personal home paths, unreviewed image filenames,
|
|
52
|
+
scene/license files and suspicious PNG metadata in the tracked publication source.
|
|
53
|
+
Public example paths must be generic. New images require explicit review before being
|
|
54
|
+
added to the reviewed-image list; retain third-party credits and licenses.
|
|
55
|
+
|
|
56
|
+
The check is a rule-based guard, not proof that an asset is non-confidential. It does
|
|
57
|
+
not remove old commits, cached packages, uploaded release artifacts or public issue
|
|
58
|
+
attachments. Review Git history separately, use a GitHub no-reply commit email and
|
|
59
|
+
sanitize diagnostics before sharing: local file paths, scene names and KeyShot output
|
|
60
|
+
can identify a project even without a credential. If a real credential is exposed,
|
|
61
|
+
revoke it first; deleting a file from the latest commit is not sufficient.
|
|
62
|
+
|
|
31
63
|
### Reporting issues
|
|
32
64
|
|
|
33
65
|
Report vulnerabilities through the repository's
|
|
@@ -44,9 +76,10 @@ KeyShot MCP 在本机运行 KeyShot headless。它可以读取本地场景、模
|
|
|
44
76
|
|
|
45
77
|
### 模型服务的数据流
|
|
46
78
|
|
|
47
|
-
MCP
|
|
48
|
-
|
|
49
|
-
|
|
79
|
+
MCP 客户端中的 Agent 会接收工具结果,包括场景检查返回的元数据和
|
|
80
|
+
`keyshot_preview_render` 返回的预览图片。这些内容可能根据客户端的实现与配置发送给
|
|
81
|
+
用户选择的模型服务。数据传输由 MCP 客户端处理,不是 KeyShot MCP 主动上传。
|
|
82
|
+
处理私有项目或客户项目之前,请先确认 MCP 客户端和模型服务商的隐私设置。
|
|
50
83
|
|
|
51
84
|
KeyShot MCP 本身不会上传文件、运行远程服务或收集遥测数据。它不会要求用户在提示词
|
|
52
85
|
中提供许可证密钥,也不开放任意 Python 执行。
|
|
@@ -60,6 +93,31 @@ KeyShot MCP 本身不会上传文件、运行远程服务或收集遥测数据
|
|
|
60
93
|
- 除非明确需要并信任外部位置,否则保持 `KEYSHOT_ALLOW_EXTERNAL_OUTPUTS` 关闭。
|
|
61
94
|
- 批准修改或最终渲染前先检查预览,并保留原始场景。
|
|
62
95
|
|
|
96
|
+
### 输出保护
|
|
97
|
+
|
|
98
|
+
写入工具默认拒绝覆盖已有文件,仅在工具支持且明确设置 `overwrite: true` 时允许替换。
|
|
99
|
+
本次调用声明的输入文件(包括材质赋值中的文件)和渲染队列中所有任务的输入场景,
|
|
100
|
+
即使开启覆盖也仍受保护。路径比较会解析链接,并识别输入文件的硬链接别名。
|
|
101
|
+
场景与图片不能写入同一文件;预览与保存同步始终不覆盖。
|
|
102
|
+
|
|
103
|
+
服务在启动后台进程前检查路径,bridge 在每次保存、渲染前再次检查,包括按相机名生成的路径。
|
|
104
|
+
这些检查不是文件系统沙箱或跨进程锁:任务运行期间不要让其他程序写同一输出文件,
|
|
105
|
+
也不要修改相关链接。多个进程应使用不同输出文件夹。
|
|
106
|
+
|
|
107
|
+
以前依赖底层工具隐式替换文件的流程,需要改用新路径或显式 `overwrite: true`;
|
|
108
|
+
覆盖本次声明的输入文件不受支持。
|
|
109
|
+
|
|
110
|
+
### 发布前检查
|
|
111
|
+
|
|
112
|
+
维护者在 PR 或发布前运行 `pnpm run check:public-safety`。CI 检查疑似凭据、个人用户目录、
|
|
113
|
+
未经复核的图片文件名、场景或许可证文件及 PNG 元数据。公开示例只用通用占位路径。
|
|
114
|
+
新图片经明确复核后才能加入已审核图片清单;保留第三方作者与许可说明。
|
|
115
|
+
|
|
116
|
+
规则扫描不能证明素材一定不保密,也不会删除旧提交、缓存发布包、Release 附件或公开
|
|
117
|
+
Issue 附件。Git 历史需要单独审查;提交使用 GitHub no-reply 邮箱。分享诊断前先脱敏,
|
|
118
|
+
本机路径、场景名和 KeyShot 输出即使不含密钥,也可能识别项目。真实凭据一旦公开,
|
|
119
|
+
应先撤销,仅从最新提交删除文件是不够的。
|
|
120
|
+
|
|
63
121
|
### 报告安全问题
|
|
64
122
|
|
|
65
123
|
请先通过仓库的
|
package/dist/config.js
CHANGED
|
@@ -13,7 +13,7 @@ export function getConfig() {
|
|
|
13
13
|
keyshotAllowExternalOutputs: parseBoolean(process.env.KEYSHOT_ALLOW_EXTERNAL_OUTPUTS),
|
|
14
14
|
keyshotLicenseArgs: splitWindowsArgs(process.env.KEYSHOT_LICENSE_ARGS ?? ""),
|
|
15
15
|
keyshotTimeoutMs: parsePositiveInt(process.env.KEYSHOT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS),
|
|
16
|
-
tmpDir: path.join(
|
|
16
|
+
tmpDir: path.join(os.tmpdir(), "keyshot-mcp"),
|
|
17
17
|
bridgeScriptPath: path.join(projectRoot, "scripts", "keyshot_bridge.py"),
|
|
18
18
|
materialPresetsPath: path.resolve(process.env.KEYSHOT_MATERIAL_PRESETS ??
|
|
19
19
|
path.join(projectRoot, "presets", "materials.json")),
|
package/dist/diagnostics.js
CHANGED
|
@@ -5,6 +5,7 @@ import { loadCameraPresets } from "./camera-presets.js";
|
|
|
5
5
|
import { loadMaterialPresets } from "./presets.js";
|
|
6
6
|
import { runKeyShotSerialized } from "./runner.js";
|
|
7
7
|
import { VERSION } from "./version.js";
|
|
8
|
+
import { localFailure } from "./result.js";
|
|
8
9
|
export async function runKeyShotDiagnostics(config, runStatus = runKeyShotSerialized) {
|
|
9
10
|
const checks = [];
|
|
10
11
|
const resolvedExecutable = await resolveExecutable(config.keyshotHeadlessExe);
|
|
@@ -22,12 +23,24 @@ export async function runKeyShotDiagnostics(config, runStatus = runKeyShotSerial
|
|
|
22
23
|
});
|
|
23
24
|
checks.push(await fileCheck("bridge-script", "KeyShot bridge script", config.bridgeScriptPath, "Reinstall keyshot-mcp so scripts/keyshot_bridge.py is included."));
|
|
24
25
|
checks.push(await outputWriteCheck(config.keyshotOutputDir));
|
|
26
|
+
checks.push(await outputWriteCheck(config.tmpDir, "temporary-directory", "Temporary directory", "Check that your system temporary directory is writable, then restart the MCP client. 请检查系统临时目录写入权限,然后重启 MCP 客户端。"));
|
|
25
27
|
checks.push(await presetCheck("camera-presets", "Camera presets", () => loadCameraPresets(config), config.cameraPresetsPath));
|
|
26
28
|
checks.push(await presetCheck("material-presets", "Material presets", () => loadMaterialPresets(config), config.materialPresetsPath));
|
|
27
29
|
let keyshotResult;
|
|
28
30
|
if (resolvedExecutable &&
|
|
29
|
-
checks.
|
|
30
|
-
|
|
31
|
+
checks.every((check) => check.severity !== "error" || check.ok)) {
|
|
32
|
+
try {
|
|
33
|
+
keyshotResult = await runStatus(config, { operation: "status" });
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
keyshotResult = localFailure(errorMessage(error), {
|
|
37
|
+
errorCode: "KEYSHOT_STARTUP_FAILED",
|
|
38
|
+
suggestions: [
|
|
39
|
+
"Check local folder permissions and KeyShot startup, then retry keyshot_status.",
|
|
40
|
+
"请检查本地目录权限及 KeyShot 能否启动,然后重试 keyshot_status。",
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
31
44
|
}
|
|
32
45
|
else {
|
|
33
46
|
keyshotResult = {
|
|
@@ -36,7 +49,7 @@ export async function runKeyShotDiagnostics(config, runStatus = runKeyShotSerial
|
|
|
36
49
|
outputFiles: [],
|
|
37
50
|
warnings: [],
|
|
38
51
|
keyshotStdoutTail: "",
|
|
39
|
-
error: "KeyShot startup was skipped because a required local
|
|
52
|
+
error: "KeyShot startup was skipped because a required local check failed.",
|
|
40
53
|
errorCode: "DIAGNOSTIC_PREREQUISITE_FAILED",
|
|
41
54
|
suggestions: [],
|
|
42
55
|
};
|
|
@@ -88,6 +101,7 @@ export async function runKeyShotDiagnostics(config, runStatus = runKeyShotSerial
|
|
|
88
101
|
outputDir: config.keyshotOutputDir,
|
|
89
102
|
allowExternalOutputs: config.keyshotAllowExternalOutputs,
|
|
90
103
|
timeoutMs: config.keyshotTimeoutMs,
|
|
104
|
+
temporaryDir: config.tmpDir,
|
|
91
105
|
licenseArgsConfigured: config.keyshotLicenseArgs.length > 0,
|
|
92
106
|
bridgeScriptPath: config.bridgeScriptPath,
|
|
93
107
|
cameraPresetsPath: config.cameraPresetsPath,
|
|
@@ -150,29 +164,29 @@ async function fileCheck(id, label, filePath, suggestion) {
|
|
|
150
164
|
suggestion: ok ? undefined : suggestion,
|
|
151
165
|
};
|
|
152
166
|
}
|
|
153
|
-
async function outputWriteCheck(outputDir) {
|
|
167
|
+
async function outputWriteCheck(outputDir, id = "output-directory", label = "Output directory", suggestion = "Set KEYSHOT_OUTPUT_DIR to a local folder where the current user can create files.") {
|
|
154
168
|
const probe = path.join(outputDir, `.keyshot-mcp-write-test-${randomUUID()}`);
|
|
155
169
|
try {
|
|
156
170
|
await fs.mkdir(outputDir, { recursive: true });
|
|
157
171
|
await fs.writeFile(probe, "ok", "utf8");
|
|
158
172
|
await fs.rm(probe, { force: true });
|
|
159
173
|
return {
|
|
160
|
-
id
|
|
161
|
-
label
|
|
174
|
+
id,
|
|
175
|
+
label,
|
|
162
176
|
ok: true,
|
|
163
177
|
severity: "error",
|
|
164
|
-
message:
|
|
178
|
+
message: `${label} is writable: ${outputDir}`,
|
|
165
179
|
};
|
|
166
180
|
}
|
|
167
181
|
catch (error) {
|
|
168
182
|
await fs.rm(probe, { force: true }).catch(() => undefined);
|
|
169
183
|
return {
|
|
170
|
-
id
|
|
171
|
-
label
|
|
184
|
+
id,
|
|
185
|
+
label,
|
|
172
186
|
ok: false,
|
|
173
187
|
severity: "error",
|
|
174
|
-
message:
|
|
175
|
-
suggestion
|
|
188
|
+
message: `${label} is not writable: ${outputDir} (${errorMessage(error)})`,
|
|
189
|
+
suggestion,
|
|
176
190
|
};
|
|
177
191
|
}
|
|
178
192
|
}
|
package/dist/output-paths.js
CHANGED
|
@@ -1,6 +1,79 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
const OUTPUT_FIELDS = ["outputPath", "outputDir", "outputScenePath"];
|
|
4
|
+
const INPUT_FIELDS = new Set([
|
|
5
|
+
"scenePath",
|
|
6
|
+
"sourcePath",
|
|
7
|
+
"modelPath",
|
|
8
|
+
"baseScenePath",
|
|
9
|
+
"materialPath",
|
|
10
|
+
"environmentPath",
|
|
11
|
+
]);
|
|
12
|
+
export function inputFilePaths(value) {
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return value.flatMap(inputFilePaths);
|
|
15
|
+
if (!value || typeof value !== "object")
|
|
16
|
+
return [];
|
|
17
|
+
return Object.entries(value).flatMap(([key, entry]) => {
|
|
18
|
+
if (INPUT_FIELDS.has(key) && typeof entry === "string")
|
|
19
|
+
return [entry];
|
|
20
|
+
if (key === "_protectedInputPaths" && Array.isArray(entry))
|
|
21
|
+
return entry.filter((item) => typeof item === "string");
|
|
22
|
+
return inputFilePaths(entry);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export async function assertOutputFilesSafe(request) {
|
|
26
|
+
const inputs = inputFilePaths(request);
|
|
27
|
+
const outputs = [request.outputScenePath, request.outputPath].filter((value) => typeof value === "string");
|
|
28
|
+
for (let index = 0; index < outputs.length; index += 1) {
|
|
29
|
+
const output = outputs[index];
|
|
30
|
+
for (const input of inputs) {
|
|
31
|
+
if (await sameFile(output, input))
|
|
32
|
+
throw new Error(`Output must not replace an input file: ${output}`);
|
|
33
|
+
}
|
|
34
|
+
for (const previous of outputs.slice(0, index)) {
|
|
35
|
+
if (await sameFile(output, previous))
|
|
36
|
+
throw new Error(`Output paths must refer to different files: ${output}`);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const stat = await fs.lstat(output);
|
|
40
|
+
let target = stat;
|
|
41
|
+
if (stat.isSymbolicLink()) {
|
|
42
|
+
try {
|
|
43
|
+
target = await fs.stat(output);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw new Error(`Output file path must be a regular file: ${output}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (target.isDirectory())
|
|
50
|
+
throw new Error(`Output file path is a directory: ${output}`);
|
|
51
|
+
if (!target.isFile())
|
|
52
|
+
throw new Error(`Output file path must be a regular file: ${output}`);
|
|
53
|
+
if (request.overwrite !== true)
|
|
54
|
+
throw new Error(`Output already exists and overwrite is false: ${output}`);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error.code !== "ENOENT")
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function sameFile(first, second) {
|
|
63
|
+
if (normalizeCase(await realpathWithMissingTail(first)) ===
|
|
64
|
+
normalizeCase(await realpathWithMissingTail(second)))
|
|
65
|
+
return true;
|
|
66
|
+
try {
|
|
67
|
+
const a = await fs.stat(first, { bigint: true });
|
|
68
|
+
const b = await fs.stat(second, { bigint: true });
|
|
69
|
+
return a.ino !== 0n && a.dev === b.dev && a.ino === b.ino;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error.code === "ENOENT")
|
|
73
|
+
return false;
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
4
77
|
export async function normalizeOutputPaths(config, request) {
|
|
5
78
|
await fs.mkdir(config.keyshotOutputDir, { recursive: true });
|
|
6
79
|
const normalized = { ...request };
|