dsh-unity-mcp 0.0.5 → 0.0.6
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 +5 -3
- package/index.mjs +2 -3
- package/lib/install-codergamester.mjs +1 -1
- package/lib/lazy-tools.mjs +1 -1
- package/lib/open-project.mjs +1 -6
- package/lib/project-lifecycle.mjs +0 -6
- package/lib/project.mjs +64 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## 能做什么
|
|
8
8
|
|
|
9
|
-
**不依赖已连接的 Unity MCP:** 在 Windows
|
|
9
|
+
**不依赖已连接的 Unity MCP:** 在 Windows 上查看已打开的工程及 DSH 配置的连接目标,按工程版本匹配并打开编辑器、等待工程就绪、准备 CoderGamester 安装、保存已加载场景和修改资源、先保存再安全关闭编辑器,以及保存 DSH 的工程连接配置。保存、关闭和就绪检查会向工程加入一个小型 Editor 桥接,仍需编辑器正常编译并运行。
|
|
10
10
|
|
|
11
11
|
**依赖引擎端 CoderGamester MCP:** 查询和编辑场景、GameObject、组件、材质与资源,查看控制台,以及调用上游提供的编辑器和测试功能。具体能力随上游版本变化;有工具清单不代表编辑器已经连通。
|
|
12
12
|
|
|
@@ -28,9 +28,11 @@ dsh-unity-mcp
|
|
|
28
28
|
|
|
29
29
|
可以直接说:“打开 `C:/Projects/MyGame`,准备 CoderGamester,再连接这个工程。”
|
|
30
30
|
|
|
31
|
-
Agent
|
|
31
|
+
Agent 加载工具后,先用 `unity_project` 的 `list` 查看打开的工程和 DSH 配置的连接目标,再对明确的工程路径执行 `status`、`open`、`save` 或 `close`。`list` 只读,不会写入桥接或启动工程;工程路径无法读取的编辑器进程也会列出,并标为未知。`status` 会检查就绪但不启动编辑器,`open` 则在需要时按版本匹配并启动。
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
安装 CoderGamester 和保存 DSH 连接分别由独立工具处理,避免把打开工程、修改包和切换所有会话的连接混为一次操作。工程处于关闭状态时,也可以先离线准备安装,再打开编辑器。包依赖固定在已核验的上游 commit,已有不同的 CoderGamester 依赖时不会擅自替换;其他包会保留。
|
|
34
|
+
|
|
35
|
+
**打开工程需要时间。** 工具会等待编辑器完成当前导入、编译并回应新鲜握手:返回 `editor_ready` / `editorReady: true` 才代表这次编辑器就绪检查通过。`list` 中的 `running_not_verified` 只表示发现了进程。`status` 返回 `readiness_unconfirmed` 或 `open` 返回 `editor_loading` 时,应继续检查或等待,不要重复启动或假设工程已经加载完成。运行或启动中的工程,安装工具也会在修改依赖前执行就绪检查;等待失败不会改写依赖。关闭工程的离线安装只代表文件准备完成,不代表工程已打开。
|
|
34
36
|
|
|
35
37
|
编辑器就绪仍不等于 MCP 连通。Package Manager 还需下载和编译引擎包,服务需要成功启动;Node 服务器缺少构建文件时,还需在其 `Server~` 目录执行 `npm install` 和 `npm run build`。安装工具不会代为下载或构建 Node 服务器,也不自动改变 DSH 连接。
|
|
36
38
|
|
package/index.mjs
CHANGED
|
@@ -2,8 +2,7 @@ import z from '@deepseek-ai/schemastery';
|
|
|
2
2
|
import * as mcp from '@deepseek-ai/dsh-mcp-client';
|
|
3
3
|
import { connectionConfig } from './lib/connection.mjs';
|
|
4
4
|
import { installLazyTools } from './lib/lazy-tools.mjs';
|
|
5
|
-
import {
|
|
6
|
-
import { lifecycleTools } from './lib/project-lifecycle.mjs';
|
|
5
|
+
import { projectTool } from './lib/project.mjs';
|
|
7
6
|
import { installationTool } from './lib/install-codergamester.mjs';
|
|
8
7
|
import { configurationTool } from './lib/configure-project.mjs';
|
|
9
8
|
export const name = 'dsh-unity-mcp';
|
|
@@ -19,7 +18,7 @@ export const Config = z.object({
|
|
|
19
18
|
});
|
|
20
19
|
export async function apply(ctx, config) {
|
|
21
20
|
// Install the loader even before configuration, so discovery explains setup.
|
|
22
|
-
installLazyTools(ctx, config.lazyTools, [
|
|
21
|
+
installLazyTools(ctx, config.lazyTools, [projectTool(ctx), installationTool(), configurationTool(ctx)]);
|
|
23
22
|
if (!config.projectPath && !config.serverPath) {
|
|
24
23
|
ctx.logger.info('Unity MCP: configure projectPath or serverPath to connect CoderGamester/mcp-unity.');
|
|
25
24
|
return;
|
|
@@ -17,7 +17,7 @@ export async function installCoderGamester(input, deps = {}) {
|
|
|
17
17
|
const lock = await stat(path.join(root, 'Temp/UnityLockfile')).catch(e => { if (e.code !== 'ENOENT') throw e; });
|
|
18
18
|
if (editor || launchPending(root) || lock) {
|
|
19
19
|
try { await (deps.waitReady ?? waitForEditor)(root, Math.max(1, deadline - Date.now()), deps); }
|
|
20
|
-
catch (error) { throw Error('Editor not ready; package dependencies were not changed. Wait with
|
|
20
|
+
catch (error) { throw Error('Editor not ready; package dependencies were not changed. Wait with unity_project action=status or open before retrying installation. ' + error.message); }
|
|
21
21
|
}
|
|
22
22
|
const manifest = path.join(root, 'Packages/manifest.json');
|
|
23
23
|
const original = await readFile(manifest, 'utf8');
|
package/lib/lazy-tools.mjs
CHANGED
|
@@ -32,7 +32,7 @@ export function installLazyTools(ctx, lazy = true, setupTools = []) {
|
|
|
32
32
|
states.set(agent, state);
|
|
33
33
|
if (lazy) state.disposers.push(agent.ctx.tools.register({
|
|
34
34
|
name: 'unity_load_tools',
|
|
35
|
-
description: 'Load Unity/Tuanjie tools for this agent
|
|
35
|
+
description: 'Load Unity/Tuanjie tools for this agent. Start with unity_project action=list to discover open projects and the configured DSH target; then inspect/open/save/close a chosen project. Local management and package installation work without MCP; scene/object/asset tools require CoderGamester in the selected engine. Loading changes no project or connection.',
|
|
36
36
|
parameters: empty,
|
|
37
37
|
output: { schema: { type: 'object', properties: { tools: { type: 'array', items: { type: 'string' } } }, required: ['tools'], additionalProperties: false }, render: (_, value) => [{ type: 'text', text: JSON.stringify(value) }] },
|
|
38
38
|
execute: async () => {
|
package/lib/open-project.mjs
CHANGED
|
@@ -21,7 +21,7 @@ async function finishOpen(result, input, deps) {
|
|
|
21
21
|
await (deps.waitReady ?? waitForEditor)(result.projectPath, input.timeoutMs ?? 120000, deps);
|
|
22
22
|
return { ...result, status: 'editor_ready', editorReady: true, message: 'Editor responded after import/compilation settled. MCP connectivity is not verified.' };
|
|
23
23
|
} catch (error) {
|
|
24
|
-
return { ...result, status: 'editor_loading', editorReady: false, message: 'Not ready: ' + error.message + ' Do not assume loading finished. Retry
|
|
24
|
+
return { ...result, status: 'editor_loading', editorReady: false, message: 'Not ready: ' + error.message + ' Do not assume loading finished. Retry unity_project action=status or open to wait again; do not relaunch or configure MCP yet.' };
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
async function powershell(script) {
|
|
@@ -97,8 +97,3 @@ async function launchEditor(editor, project) {
|
|
|
97
97
|
child.once('spawn', () => { child.unref(); resolve(child.pid); });
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
|
-
export function openProjectTool() {
|
|
101
|
-
return { name: 'unity_open_project', description: 'Open a Unity/Tuanjie project in its exact matching Windows editor and wait for an Editor readiness handshake, including when already running. May import assets and run project scripts; installs/updates the DSH Editor bridge. editor_loading means not ready: retry this tool to wait again, without relaunching. Does not configure or verify MCP.',
|
|
102
|
-
parameters: { type: 'object', properties: { projectPath: { type: 'string', description: 'Absolute existing project directory.' }, editorPath: { type: 'string', description: 'Optional absolute matching Unity.exe/Tuanjie.exe; omit to search standard Hub install locations.' }, timeoutMs: { type: 'integer', minimum: 1000, maximum: 180000, description: 'Editor readiness wait budget; default 120000 ms. Timeout returns editor_loading; retry to wait longer.' } }, required: ['projectPath'], additionalProperties: false },
|
|
103
|
-
output: { schema: { type: 'object', properties: { status: { type: 'string' }, projectPath: { type: 'string' }, editorPath: { type: 'string' }, version: { type: 'string' }, pid: { type: 'integer' }, editorReady: { type: 'boolean' }, message: { type: 'string' } }, required: ['status', 'projectPath', 'editorPath', 'version', 'pid', 'editorReady', 'message'], additionalProperties: false }, render: (_, value) => [{ type: 'text', text: JSON.stringify(value) }] }, execute: input => openProject(input) };
|
|
104
|
-
}
|
|
@@ -75,9 +75,3 @@ export async function requestProject(input, action, deps = {}) {
|
|
|
75
75
|
throw Error('Editor response timed out. Operation may have begun; inspect the editor before retrying. No force kill was attempted.');
|
|
76
76
|
} finally { if (request) await unlink(request).catch(() => {}); pending.delete(root); }
|
|
77
77
|
}
|
|
78
|
-
export function lifecycleTools() {
|
|
79
|
-
return ['save', 'close'].map(action => ({ name: `unity_${action}_project`,
|
|
80
|
-
description: action === 'save' ? 'Save all named loaded scenes and dirty assets in a running Windows Unity/Tuanjie project. Installs a small Editor bridge in Assets/Editor/DshUnityMcp (may trigger compilation); no existing MCP connection required. Rejects Play mode, Prefab Mode and unnamed scenes.' : 'Save scenes/assets then request normal exit for the specified Windows Unity/Tuanjie project and verify process exit. Installs an Editor bridge if absent. Save failure prevents exit; never force-kills or discards changes. Timeout is not proof of exit.',
|
|
81
|
-
parameters: { type: 'object', properties: { projectPath: { type: 'string', description: 'Absolute running project directory.' }, timeoutMs: { type: 'integer', minimum: 1000, maximum: 180000, description: 'Wait budget including bridge compilation and exit; default 60000 ms.' } }, required: ['projectPath'], additionalProperties: false },
|
|
82
|
-
output: { schema: { type: 'object', properties: { status: { type: 'string' }, projectPath: { type: 'string' }, pid: { type: 'integer' } }, required: ['status', 'projectPath', 'pid'], additionalProperties: false }, render: (_, value) => [{ type: 'text', text: JSON.stringify(value) }] }, execute: input => requestProject(input, action) }));
|
|
83
|
-
}
|
package/lib/project.mjs
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readFile, realpath } from 'node:fs/promises';
|
|
3
|
+
import { windowsProcesses, projectFromCommandLine, openProject } from './open-project.mjs';
|
|
4
|
+
import { requestProject, projectRoot } from './project-lifecycle.mjs';
|
|
5
|
+
|
|
6
|
+
export function connectionTarget(ctx) {
|
|
7
|
+
const rows = ctx.configEditor.configuration().filter(row => row.entry.options.name === 'dsh-unity-mcp');
|
|
8
|
+
if (rows.length !== 1) return { status: 'ambiguous', projectPath: '', serverPath: '' };
|
|
9
|
+
const config = { ...rows[0].inherited, ...rows[0].override };
|
|
10
|
+
return { status: config.projectPath || config.serverPath ? 'configured_not_verified' : 'not_configured', projectPath: config.projectPath || '', serverPath: config.serverPath || '' };
|
|
11
|
+
}
|
|
12
|
+
export async function listProjects(target = {}, deps = {}) {
|
|
13
|
+
if ((deps.platform ?? process.platform) !== 'win32') throw Error('Project discovery currently supports Windows only.');
|
|
14
|
+
const processes = await (deps.processes ?? windowsProcesses)();
|
|
15
|
+
const canonical = async value => value ? realpath(value).catch(() => path.resolve(value)) : '';
|
|
16
|
+
const selected = (await canonical(target.projectPath)).toLowerCase();
|
|
17
|
+
const projects = [];
|
|
18
|
+
for (const row of Array.isArray(processes) ? processes : [processes]) {
|
|
19
|
+
if (!row) continue;
|
|
20
|
+
const raw = projectFromCommandLine(row.CommandLine);
|
|
21
|
+
const projectPath = await canonical(raw);
|
|
22
|
+
let version = '';
|
|
23
|
+
if (projectPath) version = /^m_EditorVersion:\s*(\S+)/m.exec(await readFile(path.join(projectPath, 'ProjectSettings/ProjectVersion.txt'), 'utf8').catch(() => ''))?.[1] || '';
|
|
24
|
+
projects.push({ pid: row.ProcessId, projectPath, editorPath: row.ExecutablePath || '', version, state: projectPath ? 'running_not_verified' : 'project_unknown', configuredTarget: Boolean(selected && selected === projectPath.toLowerCase()) });
|
|
25
|
+
}
|
|
26
|
+
projects.sort((a, b) => a.pid - b.pid);
|
|
27
|
+
return { projects: projects.slice(0, 100), total: projects.length, truncated: projects.length > 100, connection: target, message: 'Process inventory only; readiness and MCP connectivity are not verified. Unknown project paths may reflect inaccessible process arguments. Use status for a selected project. Connection settings affect all profile sessions.' };
|
|
28
|
+
}
|
|
29
|
+
export async function manageProject(input, target = {}, deps = {}) {
|
|
30
|
+
if (input.action === 'list') return listProjects(target, deps);
|
|
31
|
+
if (!input.projectPath) throw Error('projectPath is required for this action. Use action=list to discover open projects.');
|
|
32
|
+
if (input.action === 'open') return (deps.open ?? openProject)(input, deps);
|
|
33
|
+
if (input.action === 'save' || input.action === 'close') return (deps.request ?? requestProject)(input, input.action, deps);
|
|
34
|
+
if (input.action === 'status') {
|
|
35
|
+
const root = await projectRoot(input.projectPath);
|
|
36
|
+
const inventory = await listProjects(target, deps);
|
|
37
|
+
if (inventory.truncated) throw Error('Editor inventory exceeds 100 processes; status cannot safely select a project.');
|
|
38
|
+
const matches = inventory.projects.filter(p => p.projectPath.toLowerCase() === root.toLowerCase());
|
|
39
|
+
if (!matches.length) return { status: 'not_running', projectPath: root, editorReady: false, connection: target, message: 'No matching editor process found; MCP connectivity is not verified.' };
|
|
40
|
+
if (matches.length !== 1) throw Error('Multiple editor processes match this project. Resolve duplicates before acting.');
|
|
41
|
+
try {
|
|
42
|
+
await (deps.request ?? requestProject)({ ...input, timeoutMs: input.timeoutMs ?? 10000 }, 'ready', deps);
|
|
43
|
+
return { ...matches[0], state: 'editor_ready', status: 'editor_ready', editorReady: true, connection: target, message: 'Editor readiness confirmed; MCP connectivity is not verified.' };
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return { ...matches[0], status: 'readiness_unconfirmed', editorReady: false, connection: target, message: error.message };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
throw Error('Unknown project action.');
|
|
49
|
+
}
|
|
50
|
+
export function projectTool(ctx) {
|
|
51
|
+
return { name: 'unity_project', description: 'Discover and manage Windows Unity/Tuanjie projects without MCP. list is read-only and shows all editor processes plus the configured DSH target; it does not prove readiness. status waits for readiness without launching; open launches if needed and waits. status/open/save/close may install an Editor bridge and trigger compilation. close saves first, verifies normal exit, never force-kills or discards changes. Save/close reject Play Mode, Prefab Mode and unnamed scenes. Does not switch or verify MCP connections.',
|
|
52
|
+
parameters: { type: 'object', properties: {
|
|
53
|
+
action: { type: 'string', enum: ['list', 'status', 'open', 'save', 'close'], description: 'Start with list to choose an explicit project. status checks readiness; open also starts a closed project; save saves all named loaded scenes/assets; close saves then exits.' },
|
|
54
|
+
projectPath: { type: 'string', description: 'Absolute project directory; required except for list. Never inferred from the current connection.' },
|
|
55
|
+
editorPath: { type: 'string', description: 'Only for open: optional absolute exact-version Unity.exe/Tuanjie.exe.' },
|
|
56
|
+
timeoutMs: { type: 'integer', minimum: 1000, maximum: 180000, description: 'Wait budget: status default 10000 ms, open 120000 ms, save/close 60000 ms. Retry status/open after an unconfirmed readiness result; do not assume it is ready.' }
|
|
57
|
+
}, required: ['action'], additionalProperties: false },
|
|
58
|
+
output: { schema: { type: 'object' }, render: (_, value) => [{ type: 'text', text: JSON.stringify(value) }] },
|
|
59
|
+
execute: input => {
|
|
60
|
+
if (input.action === 'list' && (input.projectPath || input.editorPath)) throw Error('list accepts no projectPath or editorPath.');
|
|
61
|
+
if (input.editorPath && input.action !== 'open') throw Error('editorPath is only used by open.');
|
|
62
|
+
return manageProject(input, connectionTarget(ctx));
|
|
63
|
+
} };
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-unity-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Unity and Tuanjie editor tools for DSH, powered by CoderGamester/mcp-unity with per-agent lazy loading.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
|
-
"check": "node --check index.mjs && node --check lib/connection.mjs && node --check lib/lazy-tools.mjs && node --check lib/configure-project.mjs && node --check lib/open-project.mjs && node --check lib/project-lifecycle.mjs && node --check lib/install-codergamester.mjs",
|
|
42
|
+
"check": "node --check index.mjs && node --check lib/project.mjs && node --check lib/connection.mjs && node --check lib/lazy-tools.mjs && node --check lib/configure-project.mjs && node --check lib/open-project.mjs && node --check lib/project-lifecycle.mjs && node --check lib/install-codergamester.mjs",
|
|
43
43
|
"test": "node --test test/*.test.mjs",
|
|
44
44
|
"build": "npm run check",
|
|
45
45
|
"pack:check": "npm pack --dry-run --json"
|