keyshot-mcp 0.12.0 → 0.13.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/CHANGELOG.md CHANGED
@@ -4,6 +4,53 @@ All notable changes to KeyShot MCP are documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.13.0] - 2026-09-30
8
+
9
+ ### Added / 新增
10
+
11
+ - Support MCP protocol `2026-07-28` over stdio with the official TypeScript SDK v2.
12
+ Existing SDK v1 clients can continue to connect using `2025-11-25`.
13
+ 使用官方 TypeScript SDK v2,通过 stdio 支持 MCP `2026-07-28`;旧 SDK v1
14
+ 客户端仍可使用 `2025-11-25` 连接。
15
+ - Add child-process protocol tests for both client generations, covering all
16
+ 19 tools, preset results, prompts, resources and invalid tool arguments.
17
+ 新增真实子进程协议测试,覆盖新旧客户端、19 个工具、预设结果、提示词、资源和无效输入。
18
+
19
+ ### Changed / 变更
20
+
21
+ - Upgrade schema validation to Zod 4 and generate parameter documentation through
22
+ its public JSON Schema API. Tool names, input parameters and output safety rules
23
+ remain compatible. Test runs build the stdio entry before exercising it.
24
+ 升级至 Zod 4,使用公开 JSON Schema 接口生成参数文档;工具名称、输入参数和输出安全
25
+ 规则保持兼容。测试会先构建,再检查实际 stdio 入口。
26
+ - Add bilingual upgrade guidance and release notes. MCP Events subscriptions are
27
+ not implemented in this release; protocol support alone does not enable them.
28
+ 增加双语升级说明和发布说明。本版未实现 MCP Events 订阅,协议升级不会自动开启事件推送。
29
+
30
+ ### Fixed / 修复
31
+
32
+ - Resolve native KeyShot object IDs through the scene tree so scene inspection
33
+ returns actual object names and assigned materials instead of numeric IDs.
34
+ 修复场景检查把对象 ID 当成名称、无法读取已分配材质的问题。
35
+ - Apply materials using KeyShot's material-name and object-ID parameters. Reject
36
+ ambiguous object names; use the `objectPath` returned by scene inspection to
37
+ select a specific object. Inspect again after renaming or reorganizing objects.
38
+ 修正材质赋值参数;同名对象需使用场景检查返回的 `objectPath` 精确选择。
39
+ 重命名或调整对象层级后,应重新检查场景以获取路径。
40
+
41
+ ## [0.12.1] - 2026-09-02
42
+
43
+ ### Security
44
+
45
+ - Updated the MCP SDK dependency baseline to pull patched Hono, Hono Node
46
+ server, `fast-uri`, and `ip-address` runtime dependencies.
47
+ - Refreshed the development lockfile to use patched PostCSS and Nano ID releases.
48
+
49
+ ### Changed
50
+
51
+ - Updated the Node.js and Python setup actions used by CI and release workflows.
52
+ - CI and release validation now reject high-severity dependency vulnerabilities.
53
+
7
54
  ## [0.12.0] - 2026-09-01
8
55
 
9
56
  ### Added
@@ -55,7 +102,8 @@ All notable changes to KeyShot MCP are documented in this file.
55
102
 
56
103
  - Documented that scene metadata and preview images may be sent by an MCP client to its configured model provider.
57
104
 
58
- [Unreleased]: https://github.com/truman-t3/keyshot-mcp/compare/v0.12.0...HEAD
105
+ [Unreleased]: https://github.com/truman-t3/keyshot-mcp/compare/v0.12.1...HEAD
106
+ [0.12.1]: https://github.com/truman-t3/keyshot-mcp/compare/v0.12.0...v0.12.1
59
107
  [0.12.0]: https://github.com/truman-t3/keyshot-mcp/compare/v0.11.0...v0.12.0
60
108
  [0.11.0]: https://github.com/truman-t3/keyshot-mcp/compare/v0.10.0...v0.11.0
61
109
  [0.10.0]: https://github.com/truman-t3/keyshot-mcp/compare/v0.9.1...v0.10.0
package/README.md CHANGED
@@ -22,6 +22,7 @@
22
22
  <img src="https://img.shields.io/badge/KeyShot-14.1_tested-00AEEF" alt="KeyShot 14.1 tested">
23
23
  <img src="https://img.shields.io/badge/Node.js-20%2B-339933" alt="Node.js 20 or newer">
24
24
  <img src="https://img.shields.io/badge/TypeScript-5.9-3178C6" alt="TypeScript 5.9">
25
+ <a href="https://m8ven.ai/mcp/truman-t3-keyshot-mcp-v7af97"><img src="https://m8ven.ai/badge/mcp/truman-t3-keyshot-mcp-v7af97?variant=verified" alt="M8ven Verified"></a>
25
26
  </p>
26
27
 
27
28
  <p align="center">
@@ -71,9 +72,9 @@ The easiest setup is to send this prompt to an agent that can edit your MCP
71
72
  configuration:
72
73
 
73
74
  ```text
74
- Install KeyShot MCP 0.12.0 and configure it in my MCP client.
75
+ Install KeyShot MCP 0.13.0 and configure it in my MCP client.
75
76
 
76
- 1. Use: npx -y keyshot-mcp@0.12.0
77
+ 1. Use: npx -y keyshot-mcp@0.13.0
77
78
  2. Find my local keyshot_headless.exe and set KEYSHOT_HEADLESS_EXE to its full path.
78
79
  3. Keep outputs in the default KeyShot MCP Outputs folder unless I choose another safe folder.
79
80
  4. Keep KEYSHOT_ALLOW_EXTERNAL_OUTPUTS disabled.
@@ -133,7 +134,17 @@ folder, show me a preview, and do not overwrite the original scene.
133
134
 
134
135
  ### Install
135
136
 
136
- The current release is `0.12.0`.
137
+ The current release is `0.13.0`.
138
+
139
+ Version 0.13.0 supports MCP protocol `2026-07-28` over stdio using the official
140
+ TypeScript SDK v2. Existing clients using the `2025-11-25` handshake can still
141
+ connect with the same configuration. All 19 tool names and input parameters are
142
+ preserved. See [upgrade notes](docs/UPGRADING.md) for compatibility and verification.
143
+
144
+ ChatGPT MCP Events subscriptions are not included in this release. They require
145
+ event methods, persistent subscription storage, authenticated connectivity and
146
+ signed HTTPS callbacks in addition to protocol support. This server continues to
147
+ run locally and process saved KeyShot scenes.
137
148
 
138
149
  #### Run with npx
139
150
 
@@ -144,7 +155,7 @@ No global npm installation is required:
144
155
  "mcpServers": {
145
156
  "keyshot": {
146
157
  "command": "npx",
147
- "args": ["-y", "keyshot-mcp@0.12.0"],
158
+ "args": ["-y", "keyshot-mcp@0.13.0"],
148
159
  "env": {
149
160
  "KEYSHOT_HEADLESS_EXE": "C:/Program Files/KeyShot Studio/bin/keyshot_headless.exe"
150
161
  }
@@ -156,7 +167,7 @@ No global npm installation is required:
156
167
  #### Install globally
157
168
 
158
169
  ```bash
159
- npm install -g keyshot-mcp@0.12.0
170
+ npm install -g keyshot-mcp@0.13.0
160
171
  ```
161
172
 
162
173
  ```json
@@ -409,9 +420,9 @@ KeyShot MCP 在本机处理 KeyShot 文件;但 MCP 客户端可能会把工具
409
420
  最简单的安装方式,是把下面这段话发给能够修改 MCP 配置的 Agent:
410
421
 
411
422
  ```text
412
- 请安装 KeyShot MCP 0.12.0,并配置到我的 MCP 客户端。
423
+ 请安装 KeyShot MCP 0.13.0,并配置到我的 MCP 客户端。
413
424
 
414
- 1. 使用:npx -y keyshot-mcp@0.12.0
425
+ 1. 使用:npx -y keyshot-mcp@0.13.0
415
426
  2. 查找本机 keyshot_headless.exe,并把完整路径设置为 KEYSHOT_HEADLESS_EXE。
416
427
  3. 默认把结果保存在“文档/KeyShot MCP Outputs”,除非我明确选择其他安全目录。
417
428
  4. 保持 KEYSHOT_ALLOW_EXTERNAL_OUTPUTS 关闭。
@@ -464,7 +475,14 @@ KeyShot MCP 在本机处理 KeyShot 文件;但 MCP 客户端可能会把工具
464
475
 
465
476
  ### 安装
466
477
 
467
- 当前正式版本为 `0.12.0`。
478
+ 当前正式版本为 `0.13.0`。
479
+
480
+ 0.13.0 使用官方 TypeScript SDK v2,通过 stdio 支持 MCP `2026-07-28` 协议。
481
+ 使用 `2025-11-25` 握手的现有客户端仍可沿用原配置连接,19 个工具的名称和输入参数
482
+ 保持兼容。兼容范围和验证方法见[升级说明](docs/UPGRADING.md)。
483
+
484
+ 本版尚未提供 ChatGPT MCP Events 订阅。该能力除新协议外,还需要事件接口、持久化
485
+ 订阅、经过认证的连接和签名 HTTPS 回调。服务继续在本机处理已保存的 KeyShot 场景。
468
486
 
469
487
  #### 使用 npx 免安装运行
470
488
 
@@ -473,7 +491,7 @@ KeyShot MCP 在本机处理 KeyShot 文件;但 MCP 客户端可能会把工具
473
491
  "mcpServers": {
474
492
  "keyshot": {
475
493
  "command": "npx",
476
- "args": ["-y", "keyshot-mcp@0.12.0"],
494
+ "args": ["-y", "keyshot-mcp@0.13.0"],
477
495
  "env": {
478
496
  "KEYSHOT_HEADLESS_EXE": "C:/Program Files/KeyShot Studio/bin/keyshot_headless.exe"
479
497
  }
@@ -485,7 +503,7 @@ KeyShot MCP 在本机处理 KeyShot 文件;但 MCP 客户端可能会把工具
485
503
  #### 全局安装
486
504
 
487
505
  ```bash
488
- npm install -g keyshot-mcp@0.12.0
506
+ npm install -g keyshot-mcp@0.13.0
489
507
  ```
490
508
 
491
509
  ```json
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
3
3
  import { createKeyShotServer } from "./server.js";
4
- const server = createKeyShotServer();
5
- await server.connect(new StdioServerTransport());
4
+ await serveStdio(() => createKeyShotServer());
package/dist/server.js CHANGED
@@ -1,4 +1,5 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1
+ import { McpServer } from "@modelcontextprotocol/server";
2
+ import { z } from "zod";
2
3
  import { getConfig } from "./config.js";
3
4
  import { toolResponse, localFailure } from "./result.js";
4
5
  import { runKeyShotSerialized } from "./runner.js";
@@ -11,7 +12,7 @@ import { applyRenderQuality } from "./quality.js";
11
12
  import { runKeyShotDiagnostics } from "./diagnostics.js";
12
13
  import { renderPreview } from "./preview.js";
13
14
  import { syncSavedScene } from "./save-sync.js";
14
- import { applyCameraPresetTool, applyMaterialPresetTool, applyMaterialTool, batchRenderTool, importModelTool, inspectSceneTool, listCameraPresetsTool, listCamerasTool, listMaterialPresetsTool, previewRenderTool, productRenderTool, registrationOptions, renderAllCamerasTool, renderQueueTool, renderTool, saveSceneTool, setCameraTool, setEnvironmentTool, statusTool, syncSavedSceneTool, } from "./tools/catalog.js";
15
+ import { applyCameraPresetTool, applyMaterialPresetTool, applyMaterialTool, batchRenderTool, importModelTool, inspectSceneTool, listCameraPresetsTool, listCamerasTool, listMaterialPresetsTool, previewRenderTool, productRenderTool, renderAllCamerasTool, renderQueueTool, renderTool, saveSceneTool, setCameraTool, setEnvironmentTool, statusTool, syncSavedSceneTool, } from "./tools/catalog.js";
15
16
  import { applyMaterialSchema, applyMaterialPresetSchema, batchRenderInputSchema, productRenderSchema, renderQueueInputSchema, renderAllCamerasInputSchema, renderInputSchema, scenePathSchema, setCameraSchema, } from "./schemas.js";
16
17
  function errorMessage(error) {
17
18
  return error instanceof Error ? error.message : String(error);
@@ -45,11 +46,11 @@ export function createKeyShotServer(config = getConfig()) {
45
46
  server.registerPrompt("keyshot_product_render", {
46
47
  title: "Render a KeyShot product scene",
47
48
  description: "Create a practical prompt for rendering or batch-rendering a KeyShot product scene.",
48
- argsSchema: {
49
+ argsSchema: z.object({
49
50
  modelPath: scenePathSchema.shape.scenePath.optional(),
50
51
  scenePath: scenePathSchema.shape.scenePath.optional(),
51
52
  goal: scenePathSchema.shape.scenePath.optional(),
52
- },
53
+ }),
53
54
  }, async (args) => ({
54
55
  messages: [
55
56
  {
@@ -66,15 +67,30 @@ export function createKeyShotServer(config = getConfig()) {
66
67
  args.goal
67
68
  ? `Goal: ${args.goal}`
68
69
  : "Inspect the scene and choose a suitable camera.",
69
- "Recommended workflow: run keyshot_status, inspect the scene, call keyshot_preview_render, describe visible composition/material/lighting issues, ask for confirmation, then call keyshot_product_render for the approved standard or final output.",
70
+ "Run keyshot_status first. For a model source, use keyshot_import_model to create a scene copy before inspection or preview. For an existing scene, inspect the saved scene directly.",
71
+ "Call keyshot_preview_render and verify that image content was returned before describing visible composition, materials, or lighting. If the image is unavailable, report that limitation rather than inventing visual observations.",
72
+ "For changes you propose, explain the visible issue, target object or camera, intended parameters, and expected effect; obtain approval before editing. Reuse explicit approval already provided by the user without asking again.",
73
+ "Apply approved edits to a scene copy, then preview the returned scene path again. Stop when the approved goal is met; do not repeatedly adjust the design without a user request. Use the approved scene for the standard or final render.",
70
74
  "Use lower-level tools only when individual steps need manual control.",
71
75
  ].join("\n"),
72
76
  },
73
77
  },
74
78
  ],
75
79
  }));
76
- server.registerTool("keyshot_status", registrationOptions(statusTool), async () => toolResponse(await runKeyShotDiagnostics(config)));
77
- server.registerTool("keyshot_product_render", registrationOptions(productRenderTool), async (args) => {
80
+ server.registerTool("keyshot_status", {
81
+ title: statusTool.title,
82
+ description: statusTool.description,
83
+ inputSchema: statusTool.inputSchema,
84
+ outputSchema: statusTool.outputSchema,
85
+ annotations: statusTool.annotations,
86
+ }, async () => toolResponse(await runKeyShotDiagnostics(config)));
87
+ server.registerTool("keyshot_product_render", {
88
+ title: productRenderTool.title,
89
+ description: productRenderTool.description,
90
+ inputSchema: productRenderTool.inputSchema,
91
+ outputSchema: productRenderTool.outputSchema,
92
+ annotations: productRenderTool.annotations,
93
+ }, async (args) => {
78
94
  try {
79
95
  const parsed = productRenderSchema.parse(args);
80
96
  const request = await prepareProductRenderRequest(config, parsed);
@@ -84,53 +100,119 @@ export function createKeyShotServer(config = getConfig()) {
84
100
  return toolResponse(localFailure(errorMessage(error)));
85
101
  }
86
102
  });
87
- server.registerTool("keyshot_inspect_scene", registrationOptions(inspectSceneTool), async (args) => toolResponse(await runKeyShotSerialized(config, {
103
+ server.registerTool("keyshot_inspect_scene", {
104
+ title: inspectSceneTool.title,
105
+ description: inspectSceneTool.description,
106
+ inputSchema: inspectSceneTool.inputSchema,
107
+ outputSchema: inspectSceneTool.outputSchema,
108
+ annotations: inspectSceneTool.annotations,
109
+ }, async (args) => toolResponse(await runKeyShotSerialized(config, {
88
110
  operation: "inspect_scene",
89
111
  ...args,
90
112
  })));
91
- server.registerTool("keyshot_list_cameras", registrationOptions(listCamerasTool), async (args) => toolResponse(await runKeyShotSerialized(config, {
113
+ server.registerTool("keyshot_list_cameras", {
114
+ title: listCamerasTool.title,
115
+ description: listCamerasTool.description,
116
+ inputSchema: listCamerasTool.inputSchema,
117
+ outputSchema: listCamerasTool.outputSchema,
118
+ annotations: listCamerasTool.annotations,
119
+ }, async (args) => toolResponse(await runKeyShotSerialized(config, {
92
120
  operation: "list_cameras",
93
121
  ...args,
94
122
  })));
95
- server.registerTool("keyshot_preview_render", registrationOptions(previewRenderTool), async (args) => renderPreview(config, args));
96
- server.registerTool("keyshot_sync_saved_scene", registrationOptions(syncSavedSceneTool), async (args) => syncSavedScene(config, args));
97
- server.registerTool("keyshot_render", registrationOptions(renderTool), async (args) => {
123
+ server.registerTool("keyshot_preview_render", {
124
+ title: previewRenderTool.title,
125
+ description: previewRenderTool.description,
126
+ inputSchema: previewRenderTool.inputSchema,
127
+ outputSchema: previewRenderTool.outputSchema,
128
+ annotations: previewRenderTool.annotations,
129
+ }, async (args) => renderPreview(config, args));
130
+ server.registerTool("keyshot_sync_saved_scene", {
131
+ title: syncSavedSceneTool.title,
132
+ description: syncSavedSceneTool.description,
133
+ inputSchema: syncSavedSceneTool.inputSchema,
134
+ outputSchema: syncSavedSceneTool.outputSchema,
135
+ annotations: syncSavedSceneTool.annotations,
136
+ }, async (args) => syncSavedScene(config, args));
137
+ server.registerTool("keyshot_render", {
138
+ title: renderTool.title,
139
+ description: renderTool.description,
140
+ inputSchema: renderTool.inputSchema,
141
+ outputSchema: renderTool.outputSchema,
142
+ annotations: renderTool.annotations,
143
+ }, async (args) => {
98
144
  const parsed = renderInputSchema.parse(args);
99
145
  return toolResponse(await runKeyShotSerialized(config, {
100
146
  operation: "render",
101
147
  ...applyRenderQuality(parsed),
102
148
  }));
103
149
  });
104
- server.registerTool("keyshot_render_queue", registrationOptions(renderQueueTool), async (args) => {
150
+ server.registerTool("keyshot_render_queue", {
151
+ title: renderQueueTool.title,
152
+ description: renderQueueTool.description,
153
+ inputSchema: renderQueueTool.inputSchema,
154
+ outputSchema: renderQueueTool.outputSchema,
155
+ annotations: renderQueueTool.annotations,
156
+ }, async (args) => {
105
157
  const parsed = renderQueueInputSchema.parse(args);
106
158
  return toolResponse(await runRenderQueue(config, parsed.jobs.map((job) => applyRenderQuality(job)), { continueOnError: parsed.continueOnError ?? false }));
107
159
  });
108
- server.registerTool("keyshot_batch_render", registrationOptions(batchRenderTool), async (args) => {
160
+ server.registerTool("keyshot_batch_render", {
161
+ title: batchRenderTool.title,
162
+ description: batchRenderTool.description,
163
+ inputSchema: batchRenderTool.inputSchema,
164
+ outputSchema: batchRenderTool.outputSchema,
165
+ annotations: batchRenderTool.annotations,
166
+ }, async (args) => {
109
167
  const parsed = batchRenderInputSchema.parse(args);
110
168
  return toolResponse(await runKeyShotSerialized(config, {
111
169
  operation: "batch_render",
112
170
  ...applyRenderQuality(parsed),
113
171
  }));
114
172
  });
115
- server.registerTool("keyshot_render_all_cameras", registrationOptions(renderAllCamerasTool), async (args) => {
173
+ server.registerTool("keyshot_render_all_cameras", {
174
+ title: renderAllCamerasTool.title,
175
+ description: renderAllCamerasTool.description,
176
+ inputSchema: renderAllCamerasTool.inputSchema,
177
+ outputSchema: renderAllCamerasTool.outputSchema,
178
+ annotations: renderAllCamerasTool.annotations,
179
+ }, async (args) => {
116
180
  const parsed = renderAllCamerasInputSchema.parse(args);
117
181
  return toolResponse(await runKeyShotSerialized(config, {
118
182
  operation: "render_all_cameras",
119
183
  ...applyRenderQuality(parsed),
120
184
  }));
121
185
  });
122
- server.registerTool("keyshot_import_model", registrationOptions(importModelTool), async (args) => toolResponse(await runKeyShotSerialized(config, {
186
+ server.registerTool("keyshot_import_model", {
187
+ title: importModelTool.title,
188
+ description: importModelTool.description,
189
+ inputSchema: importModelTool.inputSchema,
190
+ outputSchema: importModelTool.outputSchema,
191
+ annotations: importModelTool.annotations,
192
+ }, async (args) => toolResponse(await runKeyShotSerialized(config, {
123
193
  operation: "import_model",
124
194
  ...args,
125
195
  })));
126
- server.registerTool("keyshot_apply_material", registrationOptions(applyMaterialTool), async (args) => {
196
+ server.registerTool("keyshot_apply_material", {
197
+ title: applyMaterialTool.title,
198
+ description: applyMaterialTool.description,
199
+ inputSchema: applyMaterialTool.inputSchema,
200
+ outputSchema: applyMaterialTool.outputSchema,
201
+ annotations: applyMaterialTool.annotations,
202
+ }, async (args) => {
127
203
  const parsed = applyMaterialSchema.parse(args);
128
204
  return toolResponse(await runKeyShotSerialized(config, {
129
205
  operation: "apply_material",
130
206
  ...parsed,
131
207
  }));
132
208
  });
133
- server.registerTool("keyshot_list_material_presets", registrationOptions(listMaterialPresetsTool), async () => {
209
+ server.registerTool("keyshot_list_material_presets", {
210
+ title: listMaterialPresetsTool.title,
211
+ description: listMaterialPresetsTool.description,
212
+ inputSchema: listMaterialPresetsTool.inputSchema,
213
+ outputSchema: listMaterialPresetsTool.outputSchema,
214
+ annotations: listMaterialPresetsTool.annotations,
215
+ }, async () => {
134
216
  try {
135
217
  const presets = await loadMaterialPresets(config);
136
218
  return toolResponse({
@@ -154,7 +236,13 @@ export function createKeyShotServer(config = getConfig()) {
154
236
  return toolResponse(localFailure(errorMessage(error)));
155
237
  }
156
238
  });
157
- server.registerTool("keyshot_apply_material_preset", registrationOptions(applyMaterialPresetTool), async (args) => {
239
+ server.registerTool("keyshot_apply_material_preset", {
240
+ title: applyMaterialPresetTool.title,
241
+ description: applyMaterialPresetTool.description,
242
+ inputSchema: applyMaterialPresetTool.inputSchema,
243
+ outputSchema: applyMaterialPresetTool.outputSchema,
244
+ annotations: applyMaterialPresetTool.annotations,
245
+ }, async (args) => {
158
246
  const parsed = applyMaterialPresetSchema.parse(args);
159
247
  let presets;
160
248
  try {
@@ -178,14 +266,26 @@ export function createKeyShotServer(config = getConfig()) {
178
266
  outputScenePath: parsed.outputScenePath,
179
267
  }));
180
268
  });
181
- server.registerTool("keyshot_set_camera", registrationOptions(setCameraTool), async (args) => {
269
+ server.registerTool("keyshot_set_camera", {
270
+ title: setCameraTool.title,
271
+ description: setCameraTool.description,
272
+ inputSchema: setCameraTool.inputSchema,
273
+ outputSchema: setCameraTool.outputSchema,
274
+ annotations: setCameraTool.annotations,
275
+ }, async (args) => {
182
276
  const parsed = setCameraSchema.parse(args);
183
277
  return toolResponse(await runKeyShotSerialized(config, {
184
278
  operation: "set_camera",
185
279
  ...parsed,
186
280
  }));
187
281
  });
188
- server.registerTool("keyshot_list_camera_presets", registrationOptions(listCameraPresetsTool), async () => {
282
+ server.registerTool("keyshot_list_camera_presets", {
283
+ title: listCameraPresetsTool.title,
284
+ description: listCameraPresetsTool.description,
285
+ inputSchema: listCameraPresetsTool.inputSchema,
286
+ outputSchema: listCameraPresetsTool.outputSchema,
287
+ annotations: listCameraPresetsTool.annotations,
288
+ }, async () => {
189
289
  try {
190
290
  const presets = await loadCameraPresets(config);
191
291
  return toolResponse({
@@ -205,7 +305,13 @@ export function createKeyShotServer(config = getConfig()) {
205
305
  return toolResponse(localFailure(errorMessage(error)));
206
306
  }
207
307
  });
208
- server.registerTool("keyshot_apply_camera_preset", registrationOptions(applyCameraPresetTool), async (args) => {
308
+ server.registerTool("keyshot_apply_camera_preset", {
309
+ title: applyCameraPresetTool.title,
310
+ description: applyCameraPresetTool.description,
311
+ inputSchema: applyCameraPresetTool.inputSchema,
312
+ outputSchema: applyCameraPresetTool.outputSchema,
313
+ annotations: applyCameraPresetTool.annotations,
314
+ }, async (args) => {
209
315
  let presets;
210
316
  try {
211
317
  presets = await loadCameraPresets(config);
@@ -245,11 +351,23 @@ export function createKeyShotServer(config = getConfig()) {
245
351
  }
246
352
  return toolResponse(result);
247
353
  });
248
- server.registerTool("keyshot_set_environment", registrationOptions(setEnvironmentTool), async (args) => toolResponse(await runKeyShotSerialized(config, {
354
+ server.registerTool("keyshot_set_environment", {
355
+ title: setEnvironmentTool.title,
356
+ description: setEnvironmentTool.description,
357
+ inputSchema: setEnvironmentTool.inputSchema,
358
+ outputSchema: setEnvironmentTool.outputSchema,
359
+ annotations: setEnvironmentTool.annotations,
360
+ }, async (args) => toolResponse(await runKeyShotSerialized(config, {
249
361
  operation: "set_environment",
250
362
  ...args,
251
363
  })));
252
- server.registerTool("keyshot_save_scene", registrationOptions(saveSceneTool), async (args) => toolResponse(await runKeyShotSerialized(config, {
364
+ server.registerTool("keyshot_save_scene", {
365
+ title: saveSceneTool.title,
366
+ description: saveSceneTool.description,
367
+ inputSchema: saveSceneTool.inputSchema,
368
+ outputSchema: saveSceneTool.outputSchema,
369
+ annotations: saveSceneTool.annotations,
370
+ }, async (args) => toolResponse(await runKeyShotSerialized(config, {
253
371
  operation: "save_scene",
254
372
  ...args,
255
373
  })));
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.12.0";
1
+ export const VERSION = "0.13.0";
@@ -0,0 +1,92 @@
1
+ # Model workflow evaluation / 模型工作流评估
2
+
3
+ This protocol evaluates an MCP client and model together. Passing unit tests does
4
+ not establish model quality or desktop-control support. Results are pending until
5
+ the tasks are run in both clients with recorded evidence.
6
+
7
+ 本清单评估模型与 MCP 客户端的实际协作。单元测试通过不能证明模型表现或桌面控制能力。
8
+ 两组任务均完成并留下记录后,才能得出对比结论。
9
+
10
+ ## Setup / 准备
11
+
12
+ - Use the same MCP commit, KeyShot version, client version, and generated cube at
13
+ `examples/demo/keyshot-mcp-cube.obj`. Record exact model IDs and reasoning settings.
14
+ - Start each run in a fresh conversation with the same Skill and prompt. Use a
15
+ separate safe output directory per run and the same preview settings.
16
+ - Run each task three times per model. Record failed runs as well as successes.
17
+ - Use only generated assets. Review any logs before sharing them; omit credentials
18
+ and personal paths. Preview images are processed by the configured model provider.
19
+
20
+ - 固定 MCP 提交、KeyShot 和客户端版本,使用仓库立方体;记录完整模型名称和推理设置。
21
+ - 每轮使用新对话、相同 Skill 和提示词、独立安全输出目录与相同预览参数。
22
+ - 每个模型每项任务运行三次,失败也计入结果。
23
+ - 仅使用生成资产;分享记录前移除凭据和个人路径,预览会交给配置的模型服务处理。
24
+
25
+ ## Tasks / 任务
26
+
27
+ Run the same language version for both models. Substitute absolute fixture paths.
28
+ 两种模型使用同一语言版本,将路径占位符替换为测试资产的绝对路径。
29
+
30
+ | ID | Prompt / 提示词 | Pass condition / 通过条件 |
31
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
32
+ | T1 | Check whether KeyShot MCP is ready. / 检查 KeyShot MCP 是否准备就绪。 | Reports actual diagnostic results; explains failures without claiming success. / 如实反馈诊断,失败时说明原因。 |
33
+ | T2 | Import `<cube.obj>` to a copy and show a preview; do not produce a final render yet. / 导入立方体并展示预览,暂不正式出图。 | Imports before preview, returns a visible image, preserves source. / 先导入再预览,保留源文件。 |
34
+ | T3 | Inspect this preview and propose one camera adjustment; wait before applying it. / 检查预览,提出一项相机调整,先别执行。 | Describes visible evidence and concrete parameters; makes no edit. / 依据画面提出具体参数,不提前修改。 |
35
+ | T4 | Apply that adjustment to a copy and show the new preview. / 把刚才的调整应用到副本并重新预览。 | Reuses approval, edits the identified camera, previews the returned copy. / 不重复索要确认,预览实际修改的副本。 |
36
+ | T5 | List available material presets and propose one for this object; wait for approval. / 列出可用材质预设,为该对象推荐一个,等待确认。 | Uses real preset names and scene object identifiers, never invents a material. / 使用真实预设和对象标识。 |
37
+ | T6 | Render the approved scene at standard quality. / 按标准质量渲染已确认的场景。 | Uses the approved scene, reports actual output and warnings, stops when complete. / 使用已确认场景,如实报告结果,完成后停止。 |
38
+
39
+ For T4, use the same approved camera adjustment in both model runs. For T6, use
40
+ the same approved scene. This separates execution reliability from design taste.
41
+
42
+ T4 两组使用同一项已批准的相机参数;T6 使用同一份已批准场景,以便比较执行可靠性。
43
+
44
+ ## Record / 记录模板
45
+
46
+ Copy this block for each task and repetition. Pending is not a pass.
47
+ 每项任务每轮填写一次,未执行不能标记通过。
48
+
49
+ ```text
50
+ Date / 日期:
51
+ MCP commit and version / 提交与版本:
52
+ Model ID and reasoning / 模型与推理设置:
53
+ Client and KeyShot versions / 客户端与 KeyShot 版本:
54
+ Task and repetition / 任务与轮次:
55
+ Result / 结果: pending | pass | fail | blocked
56
+ Elapsed seconds / 总耗时:
57
+ Tool calls and retries / 工具调用与重试次数:
58
+ Unnecessary confirmation requests / 重复确认次数:
59
+ Actual image received / 是否收到图片:
60
+ Source preserved / 源文件是否保留:
61
+ Evidence and error / 证据与错误:
62
+ Visual judgment / 视觉判断: user review required
63
+ ```
64
+
65
+ Compare success rate, median elapsed time, retries, unnecessary questions, and
66
+ unsupported visual claims. Keep subjective design preference separate. Report
67
+ cost only when client usage data is available; do not estimate it from elapsed time.
68
+
69
+ 比较成功率、耗时中位数、重试、重复确认和无依据的画面描述;设计偏好另行记录。
70
+ 费用仅引用客户端实际用量,不按耗时推算。
71
+
72
+ ## Local baseline / 本地基线
73
+
74
+ On 2026-09-14, KeyShot 14.1.0.154 returned `ready: true` on the development
75
+ Windows machine. The generated cube was imported to a scene copy and rendered
76
+ at 960 x 540 with 16 samples. The preview response included PNG image content
77
+ and reported 179159 bytes. This was a direct tool smoke check, not a comparison
78
+ of GPT-5.6 and GPT-6. Both model evaluations and desktop interaction remain pending.
79
+
80
+ 2026-09-14,开发机 KeyShot 14.1.0.154 状态检查通过,仓库立方体成功导入副本,
81
+ 生成 960 x 540、16 samples 的预览,响应包含 PNG 图片内容,大小为 179159 字节。
82
+ 这是直接工具验证;GPT-5.6、GPT-6 对照评估和桌面交互仍待执行。
83
+
84
+ ## Desktop experiment / 桌面实验
85
+
86
+ Optional and separate: with an explicitly available desktop tool, test opening a
87
+ generated output folder and scene copy. Record the desktop provider and actual
88
+ outcome. This does not demonstrate persistent Live Bridge support or support in
89
+ other clients. The stable MCP workflow remains saved-scene and headless based.
90
+
91
+ 可选独立实验:客户端确实提供桌面操作工具时,测试打开生成的输出目录和场景副本。
92
+ 记录工具来源与实际结果,不据此宣称 Live Bridge 或其他客户端已经兼容。
@@ -0,0 +1,67 @@
1
+ # Upgrading KeyShot MCP / KeyShot MCP 升级说明
2
+
3
+ ## 0.13.0: MCP protocol update
4
+
5
+ KeyShot MCP 0.13.0 serves MCP `2026-07-28` over stdio through the official
6
+ TypeScript SDK v2 `serveStdio` entry. The same entry accepts legacy
7
+ `2025-11-25` clients. Configuration remains `npx -y keyshot-mcp@0.13.0` with the
8
+ existing KeyShot and output environment variables.
9
+
10
+ 1. Update the pinned package version in your MCP client configuration.
11
+ 2. Restart the MCP server or client so it loads the new package.
12
+ 3. Run `keyshot_status`, inspect a saved scene, and request a preview.
13
+
14
+ All 19 tools, the product-render prompt and workflow resource remain available.
15
+ Results retain JSON text, structured content and preview image content. Local
16
+ output restrictions and saved-scene workflows remain in effect. Node.js 20 or
17
+ later is required.
18
+
19
+ The automated suite exercises the built stdio server with an SDK v2 client pinned
20
+ to `2026-07-28` and a real SDK v1 client using its legacy handshake. This validates
21
+ the protocol paths, not every third-party client's installation or UI behavior.
22
+
23
+ ### MCP Events
24
+
25
+ This release does not advertise an `events` capability or implement ChatGPT event
26
+ subscriptions. [OpenAI's MCP Events integration](https://developers.openai.com/plugins/build/mcp-events)
27
+ also requires authenticated access, event discovery/subscription methods,
28
+ persistent subscriptions and signed HTTPS webhook delivery. These are a separate
29
+ integration from a local stdio protocol upgrade. No background monitoring,
30
+ remote endpoint or GUI bridge is enabled by upgrading.
31
+
32
+ ### Object selection
33
+
34
+ Scene inspection now resolves native KeyShot object IDs into names, materials and
35
+ unique `objectPath` values. Use the returned path when multiple objects share a
36
+ name. Inspect again after renaming or reorganizing objects. Material changes
37
+ continue to save an output scene rather than modifying the source file in place.
38
+
39
+ ## 0.13.0:MCP 协议升级
40
+
41
+ KeyShot MCP 0.13.0 使用官方 TypeScript SDK v2 的 `serveStdio` 入口,通过 stdio
42
+ 支持 MCP `2026-07-28`。同一入口仍接受使用 `2025-11-25` 握手的旧客户端。
43
+ 安装配置为 `npx -y keyshot-mcp@0.13.0`,沿用原有 KeyShot 和输出目录环境变量。
44
+
45
+ 1. 将 MCP 客户端配置中的包版本更新到 0.13.0。
46
+ 2. 重启 MCP 服务或客户端,以加载新版本。
47
+ 3. 运行 `keyshot_status`,检查已保存场景,再请求预览。
48
+
49
+ 19 个工具、产品渲染 Prompt 和工作流 Resource 均保留。结果继续提供 JSON 文本、
50
+ 结构化数据和预览图片。本地输出安全限制及已保存场景工作流保持兼容,要求 Node.js 20
51
+ 或更高版本。
52
+
53
+ 自动测试使用锁定 `2026-07-28` 的 SDK v2 客户端,以及真正的 SDK v1 客户端连接构建后
54
+ 的 stdio 服务。测试验证协议路径,不代表所有第三方客户端的安装和界面行为均已实测。
55
+
56
+ ### MCP Events 事件订阅
57
+
58
+ 本版不声明 `events` 能力,也未实现 ChatGPT 事件订阅。
59
+ [OpenAI 官方接入要求](https://developers.openai.com/plugins/build/mcp-events)还包括经过认证的
60
+ 连接、事件发现与订阅接口、持久化订阅和签名 HTTPS webhook 投递。这是后续需要单独
61
+ 实现的集成。升级本地协议不会自动开启后台监控、远程接口或 GUI 实时桥接。
62
+
63
+ ### 对象选择
64
+
65
+ 场景检查现在能将 KeyShot 原生对象 ID 解析为名称、材质和唯一的 `objectPath`。
66
+ 遇到同名对象时,应使用检查结果中的路径;重命名或调整对象层级后,应重新读取场景。
67
+ 材质修改继续保存到输出场景,避免直接改写原文件。
package/lhm.plugin.json CHANGED
@@ -2386,5 +2386,5 @@
2386
2386
  }
2387
2387
  }
2388
2388
  ],
2389
- "version": "0.12.0"
2389
+ "version": "0.13.0"
2390
2390
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keyshot-mcp",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "mcpName": "io.github.truman-t3/keyshot-mcp",
5
5
  "description": "Local MCP server for KeyShot product visualization, scene editing, camera control, and rendering.",
6
6
  "type": "module",
@@ -20,7 +20,7 @@
20
20
  "prepare": "pnpm run build",
21
21
  "start": "node ./dist/index.js",
22
22
  "check": "tsc -p tsconfig.json --noEmit",
23
- "test": "vitest run",
23
+ "test": "pnpm run build && vitest run",
24
24
  "docs:tools": "pnpm run build && node ./scripts/generate-tools-docs.mjs",
25
25
  "docs:tools:check": "pnpm run build && node ./scripts/generate-tools-docs.mjs --check",
26
26
  "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\" \"scripts/*.mjs\" \"*.json\" \"*.md\" \"docs/**/*.md\" \"skills/**/*.md\"",
@@ -72,13 +72,16 @@
72
72
  },
73
73
  "packageManager": "pnpm@10.17.1",
74
74
  "dependencies": {
75
- "@modelcontextprotocol/sdk": "^1.18.1",
76
- "zod": "^3.25.76"
75
+ "@modelcontextprotocol/core": "2.2.0",
76
+ "@modelcontextprotocol/server": "2.2.0",
77
+ "zod": "^4.5.4"
77
78
  },
78
79
  "devDependencies": {
80
+ "@modelcontextprotocol/client": "2.2.0",
81
+ "@modelcontextprotocol/sdk": "1.30.0",
79
82
  "@types/node": "^22.17.2",
80
83
  "prettier": "3.6.2",
81
84
  "typescript": "^5.9.2",
82
- "vitest": "^3.2.4"
85
+ "vitest": "^4.1.11"
83
86
  }
84
87
  }
@@ -85,7 +85,7 @@ def status():
85
85
 
86
86
 
87
87
  def inspect_scene():
88
- objects = [describe_object(obj) for obj in safe_list_call("getObjects")]
88
+ objects = [dict(describe_object(obj), path=path) for obj, path in scene_objects()]
89
89
  cameras = [serialize_value(camera) for camera in safe_list_call("getCameras")]
90
90
  model_sets = [serialize_value(model_set) for model_set in safe_list_call("getModelSets")]
91
91
  environments = [serialize_value(environment) for environment in safe_list_call("getEnvironments")]
@@ -584,11 +584,12 @@ def apply_material_current(payload, warnings):
584
584
  raise RuntimeError("Object not found. Provide a valid objectName or objectPath.")
585
585
 
586
586
  material = resolve_material(payload.get("materialName"), payload.get("materialPath"), warnings)
587
- call_variants(
587
+ target_id = first_success(lambda: target.getID(), default=None)
588
+ call_non_false_variants(
588
589
  "set object material",
589
- lambda: lux.setObjectMaterial(target, material),
590
- lambda: lux.setObjectMaterial(target, payload.get("materialName")),
591
- lambda: lux.setObjectMaterial(target, payload.get("materialPath")),
590
+ lambda: lux.setObjectMaterial(mat=material, obj=target_id if target_id is not None else target),
591
+ lambda: (lux.setObjectMaterial(material, target_id) if target_id is not None
592
+ else lux.setObjectMaterial(target, material)),
592
593
  )
593
594
 
594
595
  return {
@@ -873,11 +874,49 @@ def safe_list_call(name):
873
874
  return [value]
874
875
 
875
876
 
877
+ def scene_objects():
878
+ raw = safe_list_call("getObjects")
879
+ if not raw:
880
+ return []
881
+ # Older wrappers expose node objects; native KeyShot returns integer IDs.
882
+ if all(not isinstance(obj, int) for obj in raw):
883
+ return [(obj, describe_object(obj)["path"]) for obj in raw]
884
+ root = first_non_none(lambda: lux.getSceneTree())
885
+ if root is None:
886
+ raise RuntimeError("Scene inspection is unsupported: getSceneTree is required to resolve object IDs.")
887
+ wanted = set(raw)
888
+ found = {}
889
+ stack = [(root, "")]
890
+ visited = set()
891
+ while stack:
892
+ node, path = stack.pop()
893
+ node_id = node.getID()
894
+ if node_id in visited:
895
+ continue
896
+ visited.add(node_id)
897
+ if node_id in wanted:
898
+ found[node_id] = (node, path)
899
+ children = first_success(lambda: node.getChildren(), default=[]) or []
900
+ entries = []
901
+ occurrences = {}
902
+ for child in children:
903
+ name = str(child.getName()).replace("%", "%25").replace("/", "%2F")
904
+ name = name.replace("[", "%5B").replace("]", "%5D")
905
+ occurrences[name] = occurrences.get(name, 0) + 1
906
+ # Native IDs can change across headless launches; paths use sibling order.
907
+ entries.append((child, "%s/%s[%s]" % (path, name, occurrences[name])))
908
+ stack.extend(reversed(entries))
909
+ missing = wanted.difference(found)
910
+ if missing:
911
+ raise RuntimeError("Scene inspection could not resolve object IDs: %s" % sorted(missing))
912
+ return [found[node_id] for node_id in raw]
913
+
914
+
876
915
  def describe_object(obj):
877
916
  return {
878
917
  "name": first_success(lambda: obj.getName(), lambda: obj.name(), lambda: obj.name, default=repr(obj)),
879
918
  "path": first_success(lambda: obj.getPath(), lambda: obj.path(), lambda: obj.path, default=None),
880
- "type": first_success(lambda: obj.getType(), lambda: obj.type(), lambda: obj.type, default=type(obj).__name__),
919
+ "type": first_success(lambda: obj.getKind(), lambda: obj.getType(), lambda: obj.type(), lambda: obj.type, default=type(obj).__name__),
881
920
  "material": serialize_value(
882
921
  first_success(lambda: obj.getMaterial(), lambda: obj.material(), lambda: obj.material, default=None)
883
922
  ),
@@ -886,13 +925,13 @@ def describe_object(obj):
886
925
 
887
926
 
888
927
  def find_object(name, path):
889
- for obj in safe_list_call("getObjects"):
890
- description = describe_object(obj)
891
- if name and description.get("name") == name:
892
- return obj
893
- if path and description.get("path") == path:
894
- return obj
895
- return None
928
+ matches = []
929
+ for obj, object_path in scene_objects():
930
+ if (path and object_path == path) or (not path and name and describe_object(obj)["name"] == name):
931
+ matches.append(obj)
932
+ if len(matches) > 1:
933
+ raise RuntimeError("Ambiguous object name. Inspect the scene and provide a unique objectPath.")
934
+ return matches[0] if matches else None
896
935
 
897
936
 
898
937
  def resolve_material(material_name, material_path, warnings):
package/server.json CHANGED
@@ -9,12 +9,12 @@
9
9
  "url": "https://github.com/truman-t3/keyshot-mcp",
10
10
  "source": "github"
11
11
  },
12
- "version": "0.12.0",
12
+ "version": "0.13.0",
13
13
  "packages": [
14
14
  {
15
15
  "registryType": "npm",
16
16
  "identifier": "keyshot-mcp",
17
- "version": "0.12.0",
17
+ "version": "0.13.0",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },
@@ -2,7 +2,7 @@
2
2
  name: keyshot-mcp
3
3
  author: truman-t3
4
4
  description: Install, diagnose, and safely use KeyShot MCP for local product visualization, scene editing, camera control, material assignment, and rendering.
5
- version: 0.12.0
5
+ version: 0.13.0
6
6
  ---
7
7
 
8
8
  # KeyShot MCP
@@ -35,7 +35,7 @@ Respond in the user's language and explain errors in plain, non-technical terms.
35
35
  1. Confirm Node.js 20 or newer is available.
36
36
  2. Confirm KeyShot is installed and locally licensed.
37
37
  3. Locate `keyshot_headless.exe`. Prefer its absolute path.
38
- 4. Configure the MCP client to run `npx -y keyshot-mcp@0.12.0` and set
38
+ 4. Configure the MCP client to run `npx -y keyshot-mcp@0.13.0` and set
39
39
  `KEYSHOT_HEADLESS_EXE`.
40
40
  5. Leave `KEYSHOT_OUTPUT_DIR` unset to use
41
41
  `<home>/Documents/KeyShot MCP Outputs`, or set a user-approved directory.
@@ -50,7 +50,7 @@ Example configuration:
50
50
  "mcpServers": {
51
51
  "keyshot": {
52
52
  "command": "npx",
53
- "args": ["-y", "keyshot-mcp@0.12.0"],
53
+ "args": ["-y", "keyshot-mcp@0.13.0"],
54
54
  "env": {
55
55
  "KEYSHOT_HEADLESS_EXE": "C:/Program Files/KeyShot Studio/bin/keyshot_headless.exe"
56
56
  }
@@ -104,11 +104,15 @@ Every tool returns `ok`, operation-specific `data`, `outputFiles`, `warnings`,
104
104
  ## Recommended feedback loop
105
105
 
106
106
  1. Run `keyshot_status`.
107
- 2. Inspect the scene and identify the intended camera or object.
107
+ 2. For a model input, import it to a scene copy first. Inspect the saved scene and identify the intended camera or object.
108
108
  3. Call `keyshot_preview_render` and describe only what is visible in the returned image.
109
- 4. Ask the user to confirm material, camera, and environment changes.
109
+ 4. For proposed changes, state the visible issue, target, exact parameters, and expected effect. Obtain approval for new design decisions; reuse explicit approval already given.
110
110
  5. Apply approved edits to a scene copy.
111
- 6. Render at `standard` or `final` quality.
111
+ 6. Preview the returned scene copy again and compare it with the approved goal. Do not infer visual success from a file path alone. If image content is unavailable, explain that limitation.
112
+ 7. Render the approved scene at `standard` or `final` quality. Stop rather than making unsolicited iterative adjustments.
113
+
114
+ Model comparison tasks and a reusable evaluation record are in
115
+ [`docs/MODEL-EVALUATION.md`](../../docs/MODEL-EVALUATION.md).
112
116
 
113
117
  ## Boundaries
114
118