@akiojin/unity-mcp-server 2.14.14

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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +206 -0
  3. package/bin/unity-mcp-server +2 -0
  4. package/package.json +73 -0
  5. package/src/core/codeIndex.js +163 -0
  6. package/src/core/codeIndexDb.js +96 -0
  7. package/src/core/config.js +165 -0
  8. package/src/core/indexWatcher.js +52 -0
  9. package/src/core/projectInfo.js +111 -0
  10. package/src/core/server.js +294 -0
  11. package/src/core/unityConnection.js +426 -0
  12. package/src/handlers/analysis/AnalyzeSceneContentsToolHandler.js +35 -0
  13. package/src/handlers/analysis/FindByComponentToolHandler.js +20 -0
  14. package/src/handlers/analysis/GetAnimatorStateToolHandler.js +37 -0
  15. package/src/handlers/analysis/GetComponentValuesToolHandler.js +20 -0
  16. package/src/handlers/analysis/GetGameObjectDetailsToolHandler.js +35 -0
  17. package/src/handlers/analysis/GetInputActionsStateToolHandler.js +37 -0
  18. package/src/handlers/analysis/GetObjectReferencesToolHandler.js +20 -0
  19. package/src/handlers/asset/AssetDatabaseToolHandler.js +221 -0
  20. package/src/handlers/asset/AssetDependencyToolHandler.js +201 -0
  21. package/src/handlers/asset/AssetImportSettingsToolHandler.js +170 -0
  22. package/src/handlers/asset/CreateMaterialToolHandler.js +96 -0
  23. package/src/handlers/asset/CreatePrefabToolHandler.js +78 -0
  24. package/src/handlers/asset/ExitPrefabModeToolHandler.js +83 -0
  25. package/src/handlers/asset/InstantiatePrefabToolHandler.js +133 -0
  26. package/src/handlers/asset/ModifyMaterialToolHandler.js +76 -0
  27. package/src/handlers/asset/ModifyPrefabToolHandler.js +72 -0
  28. package/src/handlers/asset/OpenPrefabToolHandler.js +121 -0
  29. package/src/handlers/asset/SavePrefabToolHandler.js +106 -0
  30. package/src/handlers/base/BaseToolHandler.js +133 -0
  31. package/src/handlers/compilation/GetCompilationStateToolHandler.js +90 -0
  32. package/src/handlers/component/AddComponentToolHandler.js +126 -0
  33. package/src/handlers/component/GetComponentTypesToolHandler.js +100 -0
  34. package/src/handlers/component/ListComponentsToolHandler.js +85 -0
  35. package/src/handlers/component/ModifyComponentToolHandler.js +143 -0
  36. package/src/handlers/component/RemoveComponentToolHandler.js +108 -0
  37. package/src/handlers/console/ClearConsoleToolHandler.js +160 -0
  38. package/src/handlers/console/ReadConsoleToolHandler.js +276 -0
  39. package/src/handlers/editor/LayerManagementToolHandler.js +160 -0
  40. package/src/handlers/editor/SelectionToolHandler.js +141 -0
  41. package/src/handlers/editor/TagManagementToolHandler.js +129 -0
  42. package/src/handlers/editor/ToolManagementToolHandler.js +135 -0
  43. package/src/handlers/editor/WindowManagementToolHandler.js +125 -0
  44. package/src/handlers/gameobject/CreateGameObjectToolHandler.js +131 -0
  45. package/src/handlers/gameobject/DeleteGameObjectToolHandler.js +101 -0
  46. package/src/handlers/gameobject/FindGameObjectToolHandler.js +119 -0
  47. package/src/handlers/gameobject/GetHierarchyToolHandler.js +132 -0
  48. package/src/handlers/gameobject/ModifyGameObjectToolHandler.js +128 -0
  49. package/src/handlers/index.js +389 -0
  50. package/src/handlers/input/AddInputActionToolHandler.js +20 -0
  51. package/src/handlers/input/AddInputBindingToolHandler.js +20 -0
  52. package/src/handlers/input/CreateActionMapToolHandler.js +20 -0
  53. package/src/handlers/input/CreateCompositeBindingToolHandler.js +20 -0
  54. package/src/handlers/input/GamepadSimulationHandler.js +116 -0
  55. package/src/handlers/input/InputSystemHandler.js +80 -0
  56. package/src/handlers/input/KeyboardSimulationHandler.js +79 -0
  57. package/src/handlers/input/ManageControlSchemesToolHandler.js +20 -0
  58. package/src/handlers/input/MouseSimulationHandler.js +107 -0
  59. package/src/handlers/input/RemoveActionMapToolHandler.js +20 -0
  60. package/src/handlers/input/RemoveAllBindingsToolHandler.js +20 -0
  61. package/src/handlers/input/RemoveInputActionToolHandler.js +20 -0
  62. package/src/handlers/input/RemoveInputBindingToolHandler.js +20 -0
  63. package/src/handlers/input/TouchSimulationHandler.js +142 -0
  64. package/src/handlers/menu/ExecuteMenuItemToolHandler.js +304 -0
  65. package/src/handlers/package/PackageManagerToolHandler.js +248 -0
  66. package/src/handlers/package/RegistryConfigToolHandler.js +198 -0
  67. package/src/handlers/playmode/GetEditorStateToolHandler.js +81 -0
  68. package/src/handlers/playmode/PauseToolHandler.js +44 -0
  69. package/src/handlers/playmode/PlayToolHandler.js +91 -0
  70. package/src/handlers/playmode/StopToolHandler.js +77 -0
  71. package/src/handlers/playmode/WaitForEditorStateToolHandler.js +45 -0
  72. package/src/handlers/scene/CreateSceneToolHandler.js +91 -0
  73. package/src/handlers/scene/GetSceneInfoToolHandler.js +20 -0
  74. package/src/handlers/scene/ListScenesToolHandler.js +58 -0
  75. package/src/handlers/scene/LoadSceneToolHandler.js +92 -0
  76. package/src/handlers/scene/SaveSceneToolHandler.js +76 -0
  77. package/src/handlers/screenshot/AnalyzeScreenshotToolHandler.js +238 -0
  78. package/src/handlers/screenshot/CaptureScreenshotToolHandler.js +692 -0
  79. package/src/handlers/script/BuildCodeIndexToolHandler.js +163 -0
  80. package/src/handlers/script/ScriptCreateClassFileToolHandler.js +60 -0
  81. package/src/handlers/script/ScriptEditStructuredToolHandler.js +173 -0
  82. package/src/handlers/script/ScriptIndexStatusToolHandler.js +61 -0
  83. package/src/handlers/script/ScriptPackagesListToolHandler.js +103 -0
  84. package/src/handlers/script/ScriptReadToolHandler.js +106 -0
  85. package/src/handlers/script/ScriptRefactorRenameToolHandler.js +83 -0
  86. package/src/handlers/script/ScriptRefsFindToolHandler.js +144 -0
  87. package/src/handlers/script/ScriptRemoveSymbolToolHandler.js +79 -0
  88. package/src/handlers/script/ScriptSearchToolHandler.js +320 -0
  89. package/src/handlers/script/ScriptSymbolFindToolHandler.js +117 -0
  90. package/src/handlers/script/ScriptSymbolsGetToolHandler.js +96 -0
  91. package/src/handlers/settings/GetProjectSettingsToolHandler.js +161 -0
  92. package/src/handlers/settings/UpdateProjectSettingsToolHandler.js +272 -0
  93. package/src/handlers/system/GetCommandStatsToolHandler.js +25 -0
  94. package/src/handlers/system/PingToolHandler.js +53 -0
  95. package/src/handlers/system/RefreshAssetsToolHandler.js +45 -0
  96. package/src/handlers/ui/ClickUIElementToolHandler.js +110 -0
  97. package/src/handlers/ui/FindUIElementsToolHandler.js +63 -0
  98. package/src/handlers/ui/GetUIElementStateToolHandler.js +50 -0
  99. package/src/handlers/ui/SetUIElementValueToolHandler.js +49 -0
  100. package/src/handlers/ui/SimulateUIInputToolHandler.js +156 -0
  101. package/src/handlers/video/CaptureVideoForToolHandler.js +96 -0
  102. package/src/handlers/video/CaptureVideoStartToolHandler.js +38 -0
  103. package/src/handlers/video/CaptureVideoStatusToolHandler.js +30 -0
  104. package/src/handlers/video/CaptureVideoStopToolHandler.js +32 -0
  105. package/src/lsp/CSharpLspUtils.js +134 -0
  106. package/src/lsp/LspProcessManager.js +60 -0
  107. package/src/lsp/LspRpcClient.js +133 -0
  108. package/src/tools/analysis/analyzeSceneContents.js +100 -0
  109. package/src/tools/analysis/findByComponent.js +87 -0
  110. package/src/tools/analysis/getAnimatorState.js +326 -0
  111. package/src/tools/analysis/getComponentValues.js +182 -0
  112. package/src/tools/analysis/getGameObjectDetails.js +159 -0
  113. package/src/tools/analysis/getInputActionsState.js +329 -0
  114. package/src/tools/analysis/getObjectReferences.js +86 -0
  115. package/src/tools/input/inputActionsEditor.js +556 -0
  116. package/src/tools/scene/createScene.js +112 -0
  117. package/src/tools/scene/getSceneInfo.js +95 -0
  118. package/src/tools/scene/listScenes.js +82 -0
  119. package/src/tools/scene/loadScene.js +122 -0
  120. package/src/tools/scene/saveScene.js +91 -0
  121. package/src/tools/system/ping.js +72 -0
  122. package/src/tools/video/recordFor.js +31 -0
  123. package/src/tools/video/recordPlayMode.js +61 -0
  124. package/src/utils/csharpParse.js +88 -0
  125. package/src/utils/validators.js +90 -0
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Handler for querying video capture status in Unity Editor (via MCP)
3
+ */
4
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
5
+
6
+ export class CaptureVideoStatusToolHandler extends BaseToolHandler {
7
+ constructor(unityConnection) {
8
+ super(
9
+ 'capture_video_status',
10
+ 'Get current video recording status.',
11
+ {
12
+ type: 'object',
13
+ properties: {}
14
+ }
15
+ );
16
+ this.unityConnection = unityConnection;
17
+ }
18
+
19
+ /** @override */
20
+ async execute(params, context) {
21
+ const response = await this.unityConnection.sendCommand('capture_video_status', params || {});
22
+ if (response.error) {
23
+ return { error: response.error, code: response.code || 'UNITY_ERROR' };
24
+ }
25
+ return {
26
+ ...response,
27
+ message: response.message || 'Video recording status retrieved'
28
+ };
29
+ }
30
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Handler for stopping video capture in Unity Editor (via MCP)
3
+ */
4
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
5
+
6
+ export class CaptureVideoStopToolHandler extends BaseToolHandler {
7
+ constructor(unityConnection) {
8
+ super(
9
+ 'capture_video_stop',
10
+ 'Stop current video recording and finalize the file.',
11
+ {
12
+ type: 'object',
13
+ properties: {
14
+ recordingId: { type: 'string', description: 'Optional. Stop a specific recording session. Defaults to the latest.' }
15
+ }
16
+ }
17
+ );
18
+ this.unityConnection = unityConnection;
19
+ }
20
+
21
+ /** @override */
22
+ async execute(params, context) {
23
+ const response = await this.unityConnection.sendCommand('capture_video_stop', params || {});
24
+ if (response.error) {
25
+ return { error: response.error, code: response.code || 'UNITY_ERROR' };
26
+ }
27
+ return {
28
+ ...response,
29
+ message: response.message || 'Video recording stopped'
30
+ };
31
+ }
32
+ }
@@ -0,0 +1,134 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { logger } from '../core/config.js';
4
+ import { WORKSPACE_ROOT } from '../core/config.js';
5
+
6
+ export class CSharpLspUtils {
7
+ constructor() {}
8
+
9
+ detectRid() {
10
+ if (process.platform === 'win32') return process.arch === 'arm64' ? 'win-arm64' : 'win-x64';
11
+ if (process.platform === 'darwin') return process.arch === 'arm64' ? 'osx-arm64' : 'osx-x64';
12
+ return process.arch === 'arm64' ? 'linux-arm64' : 'linux-x64';
13
+ }
14
+
15
+ getDesiredVersion() {
16
+ try {
17
+ const pkg = JSON.parse(fs.readFileSync(path.resolve('mcp-server/package.json'), 'utf8'));
18
+ return pkg.version;
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ getLocalPath(rid) {
25
+ const root = WORKSPACE_ROOT || process.cwd();
26
+ const exe = process.platform === 'win32' ? 'server.exe' : 'server';
27
+ return path.resolve(root, '.unity', 'tools', 'csharp-lsp', rid, exe);
28
+ }
29
+
30
+ getVersionMarkerPath(rid) {
31
+ const bin = this.getLocalPath(rid);
32
+ return path.resolve(path.dirname(bin), 'VERSION');
33
+ }
34
+
35
+ readLocalVersion(rid) {
36
+ try {
37
+ const m = this.getVersionMarkerPath(rid);
38
+ if (fs.existsSync(m)) return fs.readFileSync(m, 'utf8').trim();
39
+ } catch {}
40
+ return null;
41
+ }
42
+
43
+ writeLocalVersion(rid, version) {
44
+ try {
45
+ const m = this.getVersionMarkerPath(rid);
46
+ fs.writeFileSync(m, String(version || '').trim() + '\n', 'utf8');
47
+ } catch {}
48
+ }
49
+
50
+ async ensureLocal(rid) {
51
+ const p = this.getLocalPath(rid);
52
+ const desired = this.getDesiredVersion();
53
+ if (!desired) throw new Error('mcp-server version not found; cannot resolve LSP tag');
54
+ const current = this.readLocalVersion(rid);
55
+ if (fs.existsSync(p) && current === desired) return p;
56
+ await this.autoDownload(rid, desired);
57
+ if (!fs.existsSync(p)) throw new Error('csharp-lsp binary not found after download');
58
+ this.writeLocalVersion(rid, desired);
59
+ return p;
60
+ }
61
+
62
+ async autoDownload(rid, version) {
63
+ const repo = process.env.GITHUB_REPOSITORY || 'akiojin/unity-mcp-server';
64
+ const tag = `v${version}`;
65
+ const manifestUrl = `https://github.com/${repo}/releases/download/${tag}/csharp-lsp-manifest.json`;
66
+ const manifest = await this.fetchJson(manifestUrl);
67
+ const entry = manifest?.assets?.[rid];
68
+ if (!entry?.url || !entry?.sha256) throw new Error(`manifest missing entry for ${rid}`);
69
+
70
+ const dest = this.getLocalPath(rid);
71
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
72
+ const tmp = dest + '.download';
73
+ await this.downloadTo(entry.url, tmp);
74
+ const actual = await this.sha256File(tmp);
75
+ if (String(actual).toLowerCase() !== String(entry.sha256).toLowerCase()) {
76
+ try { fs.unlinkSync(tmp); } catch {}
77
+ throw new Error('checksum mismatch for csharp-lsp asset');
78
+ }
79
+ // atomic replace
80
+ try { fs.renameSync(tmp, dest); } catch (e) {
81
+ // Windows may need removal before rename
82
+ try { fs.unlinkSync(dest); } catch {}
83
+ fs.renameSync(tmp, dest);
84
+ }
85
+ try { if (process.platform !== 'win32') fs.chmodSync(dest, 0o755); } catch {}
86
+ logger.info(`[csharp-lsp] downloaded: ${path.basename(dest)} @ ${path.dirname(dest)}`);
87
+ }
88
+
89
+ async fetchJson(url) {
90
+ const res = await fetch(url, { headers: { 'User-Agent': 'unity-mcp-server' } });
91
+ if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
92
+ return await res.json();
93
+ }
94
+
95
+ async downloadTo(url, dest) {
96
+ const res = await fetch(url, { headers: { 'User-Agent': 'unity-mcp-server' } });
97
+ if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
98
+ const file = fs.createWriteStream(dest);
99
+ const body = res.body;
100
+ if (body && typeof body.pipe === 'function') {
101
+ await new Promise((resolve, reject) => {
102
+ body.pipe(file);
103
+ body.on('error', reject);
104
+ file.on('finish', resolve);
105
+ file.on('error', reject);
106
+ });
107
+ return;
108
+ }
109
+ try {
110
+ const { Readable } = await import('node:stream');
111
+ const nodeStream = Readable.fromWeb(body);
112
+ await new Promise((resolve, reject) => {
113
+ nodeStream.pipe(file);
114
+ nodeStream.on('error', reject);
115
+ file.on('finish', resolve);
116
+ file.on('error', reject);
117
+ });
118
+ } catch (e) {
119
+ const ab = await res.arrayBuffer();
120
+ await fs.promises.writeFile(dest, Buffer.from(ab));
121
+ }
122
+ }
123
+
124
+ async sha256File(file) {
125
+ const { createHash } = await import('crypto');
126
+ return new Promise((resolve, reject) => {
127
+ const hash = createHash('sha256');
128
+ const stream = fs.createReadStream(file);
129
+ stream.on('data', d => hash.update(d));
130
+ stream.on('error', reject);
131
+ stream.on('end', () => resolve(hash.digest('hex')));
132
+ });
133
+ }
134
+ }
@@ -0,0 +1,60 @@
1
+ import { spawn } from 'child_process';
2
+ import { logger } from '../core/config.js';
3
+ import { CSharpLspUtils } from './CSharpLspUtils.js';
4
+
5
+ export class LspProcessManager {
6
+ constructor() {
7
+ this.proc = null;
8
+ this.starting = null;
9
+ this.utils = new CSharpLspUtils();
10
+ }
11
+
12
+ async ensureStarted() {
13
+ if (this.proc && !this.proc.killed) return this.proc;
14
+ if (this.starting) return this.starting;
15
+ this.starting = (async () => {
16
+ const rid = this.utils.detectRid();
17
+ const bin = await this.utils.ensureLocal(rid);
18
+ const proc = spawn(bin, { stdio: ['pipe', 'pipe', 'pipe'] });
19
+ proc.on('error', (e) => logger.error(`[csharp-lsp] process error: ${e.message}`));
20
+ proc.on('close', (code, sig) => {
21
+ logger.warn(`[csharp-lsp] exited code=${code} signal=${sig || ''}`);
22
+ this.proc = null;
23
+ });
24
+ proc.stderr.on('data', d => {
25
+ const s = String(d || '').trim();
26
+ if (s) logger.debug(`[csharp-lsp] ${s}`);
27
+ });
28
+ this.proc = proc;
29
+ logger.info(`[csharp-lsp] started (pid=${proc.pid})`);
30
+ return proc;
31
+ })();
32
+ try { return await this.starting; } finally { this.starting = null; }
33
+ }
34
+
35
+ async stop(graceMs = 3000) {
36
+ if (!this.proc || this.proc.killed) return;
37
+ const p = this.proc;
38
+ this.proc = null;
39
+ try {
40
+ // Send LSP shutdown/exit if possible
41
+ const shutdown = (obj) => {
42
+ try {
43
+ const json = JSON.stringify(obj);
44
+ const payload = `Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`;
45
+ p.stdin.write(payload, 'utf8');
46
+ } catch {}
47
+ };
48
+ shutdown({ jsonrpc: '2.0', id: 1, method: 'shutdown', params: {} });
49
+ shutdown({ jsonrpc: '2.0', method: 'exit' });
50
+ p.stdin.end();
51
+ } catch {}
52
+ await new Promise((resolve) => {
53
+ const to = setTimeout(() => {
54
+ try { p.kill('SIGTERM'); } catch {}
55
+ setTimeout(() => { try { p.kill('SIGKILL'); } catch {} ; resolve(); }, 1000);
56
+ }, Math.max(0, graceMs));
57
+ p.on('close', () => { clearTimeout(to); resolve(); });
58
+ });
59
+ }
60
+ }
@@ -0,0 +1,133 @@
1
+ import { LspProcessManager } from './LspProcessManager.js';
2
+ import { config, logger } from '../core/config.js';
3
+
4
+ export class LspRpcClient {
5
+ constructor(projectRoot = null) {
6
+ this.mgr = new LspProcessManager();
7
+ this.proc = null;
8
+ this.seq = 1;
9
+ this.pending = new Map();
10
+ this.buf = Buffer.alloc(0);
11
+ this.initialized = false;
12
+ this.projectRoot = projectRoot;
13
+ this.boundOnData = null;
14
+ }
15
+
16
+ async ensure() {
17
+ if (this.proc && !this.proc.killed) return this.proc;
18
+ this.proc = await this.mgr.ensureStarted();
19
+ // Attach data handler once per process
20
+ if (this.boundOnData) {
21
+ try { this.proc.stdout.off('data', this.boundOnData); } catch {}
22
+ }
23
+ this.boundOnData = (chunk) => this.onData(chunk);
24
+ this.proc.stdout.on('data', this.boundOnData);
25
+ // On process close: reject all pending and reset state
26
+ this.proc.on('close', () => {
27
+ for (const [id, p] of Array.from(this.pending.entries())) {
28
+ try { p.reject(new Error('LSP process exited')); } catch {}
29
+ this.pending.delete(id);
30
+ }
31
+ this.initialized = false;
32
+ this.proc = null;
33
+ });
34
+ if (!this.initialized) await this.initialize();
35
+ return this.proc;
36
+ }
37
+
38
+ onData(chunk) {
39
+ this.buf = Buffer.concat([this.buf, Buffer.from(chunk)]);
40
+ while (true) {
41
+ const headerEnd = this.buf.indexOf('\r\n\r\n');
42
+ if (headerEnd < 0) break;
43
+ const header = this.buf.slice(0, headerEnd).toString('utf8');
44
+ const m = header.match(/Content-Length:\s*(\d+)/i);
45
+ const len = m ? parseInt(m[1], 10) : 0;
46
+ const total = headerEnd + 4 + len;
47
+ if (this.buf.length < total) break;
48
+ const jsonBuf = this.buf.slice(headerEnd + 4, total);
49
+ this.buf = this.buf.slice(total);
50
+ try {
51
+ const msg = JSON.parse(jsonBuf.toString('utf8'));
52
+ if (msg.id && this.pending.has(msg.id)) {
53
+ this.pending.get(msg.id).resolve(msg);
54
+ this.pending.delete(msg.id);
55
+ }
56
+ } catch { /* ignore */ }
57
+ }
58
+ }
59
+
60
+ writeMessage(obj) {
61
+ const json = JSON.stringify(obj);
62
+ const payload = `Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`;
63
+ this.proc.stdin.write(payload, 'utf8');
64
+ }
65
+
66
+ async initialize() {
67
+ await this.ensure();
68
+ const id = this.seq++;
69
+ const req = {
70
+ jsonrpc: '2.0',
71
+ id,
72
+ method: 'initialize',
73
+ params: {
74
+ processId: process.pid,
75
+ rootUri: this.projectRoot ? ('file://' + String(this.projectRoot).replace(/\\/g, '/')) : null,
76
+ capabilities: {},
77
+ workspaceFolders: null,
78
+ }
79
+ };
80
+ const timeoutMs = Math.max(5000, Math.min(60000, config.lsp?.requestTimeoutMs || 60000));
81
+ const p = new Promise((resolve, reject) => {
82
+ this.pending.set(id, { resolve, reject });
83
+ setTimeout(() => {
84
+ if (this.pending.has(id)) {
85
+ this.pending.delete(id);
86
+ reject(new Error(`initialize timed out after ${timeoutMs} ms`));
87
+ }
88
+ }, timeoutMs);
89
+ });
90
+ this.writeMessage(req);
91
+ const resp = await p; // ignore result contents for stub
92
+ // send initialized notification
93
+ this.writeMessage({ jsonrpc: '2.0', method: 'initialized', params: {} });
94
+ this.initialized = true;
95
+ return resp;
96
+ }
97
+
98
+ async request(method, params) {
99
+ return await this.#requestWithRetry(method, params, 1);
100
+ }
101
+
102
+ async #requestWithRetry(method, params, attempt) {
103
+ await this.ensure();
104
+ const id = this.seq++;
105
+ const timeoutMs = Math.max(1000, Math.min(300000, config.lsp?.requestTimeoutMs || 60000));
106
+ const p = new Promise((resolve, reject) => {
107
+ this.pending.set(id, { resolve, reject });
108
+ setTimeout(() => {
109
+ if (this.pending.has(id)) {
110
+ this.pending.delete(id);
111
+ reject(new Error(`${method} timed out after ${timeoutMs} ms`));
112
+ }
113
+ }, timeoutMs);
114
+ });
115
+ try {
116
+ this.writeMessage({ jsonrpc: '2.0', id, method, params });
117
+ return await p;
118
+ } catch (e) {
119
+ const msg = String(e && e.message || e);
120
+ const recoverable = /timed out|LSP process exited/i.test(msg);
121
+ if (recoverable && attempt === 1) {
122
+ // Auto-reinit and retry once
123
+ try { await this.mgr.stop(0); } catch {}
124
+ this.proc = null; this.initialized = false; this.buf = Buffer.alloc(0);
125
+ logger.warn(`[csharp-lsp] recoverable error on ${method}: ${msg}. Retrying once...`);
126
+ return await this.#requestWithRetry(method, params, attempt + 1);
127
+ }
128
+ // Standardize error message
129
+ const hint = recoverable ? 'The server was restarted. Try again if the issue persists.' : 'Check request parameters or increase lsp.requestTimeoutMs.';
130
+ throw new Error(`[${method}] failed: ${msg}. ${hint}`);
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Tool definition for analyze_scene_contents
3
+ */
4
+ export const analyzeSceneContentsToolDefinition = {
5
+ name: 'analyze_scene_contents',
6
+ description: 'Analyze current scene: object counts, types, prefabs, and memory stats.',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ includeInactive: {
11
+ type: 'boolean',
12
+ description: 'Include inactive objects in analysis. Default: true'
13
+ },
14
+ groupByType: {
15
+ type: 'boolean',
16
+ description: 'Group results by component types. Default: true'
17
+ },
18
+ includePrefabInfo: {
19
+ type: 'boolean',
20
+ description: 'Include prefab connection info. Default: true'
21
+ },
22
+ includeMemoryInfo: {
23
+ type: 'boolean',
24
+ description: 'Include memory usage estimates. Default: false'
25
+ }
26
+ },
27
+ required: []
28
+ }
29
+ };
30
+
31
+ /**
32
+ * Handler for analyze_scene_contents tool
33
+ */
34
+ export async function analyzeSceneContentsHandler(unityConnection, args) {
35
+ try {
36
+ // Check connection
37
+ if (!unityConnection.isConnected()) {
38
+ return {
39
+ content: [
40
+ {
41
+ type: 'text',
42
+ text: 'Failed to analyze scene: Unity connection not available'
43
+ }
44
+ ],
45
+ isError: true
46
+ };
47
+ }
48
+
49
+ // Send command to Unity with provided parameters
50
+ const result = await unityConnection.sendCommand('analyze_scene_contents', args);
51
+
52
+ // The unityConnection.sendCommand already extracts the result field
53
+ // from the response, so we access properties directly on result
54
+ if (!result || typeof result === 'string') {
55
+ return {
56
+ content: [
57
+ {
58
+ type: 'text',
59
+ text: `Failed to analyze scene: Invalid response format`
60
+ }
61
+ ],
62
+ isError: true
63
+ };
64
+ }
65
+
66
+ // Check if result has error property (error response from Unity)
67
+ if (result.error) {
68
+ return {
69
+ content: [
70
+ {
71
+ type: 'text',
72
+ text: `Failed to analyze scene: ${result.error}`
73
+ }
74
+ ],
75
+ isError: true
76
+ };
77
+ }
78
+
79
+ // Success response - result is already the unwrapped data
80
+ return {
81
+ content: [
82
+ {
83
+ type: 'text',
84
+ text: result.summary || 'Scene analysis complete'
85
+ }
86
+ ],
87
+ isError: false
88
+ };
89
+ } catch (error) {
90
+ return {
91
+ content: [
92
+ {
93
+ type: 'text',
94
+ text: `Failed to analyze scene: ${error.message}`
95
+ }
96
+ ],
97
+ isError: true
98
+ };
99
+ }
100
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Tool definition for find_by_component
3
+ */
4
+ export const findByComponentToolDefinition = {
5
+ name: 'find_by_component',
6
+ description: 'Find GameObjects that have a specific component type (scene/prefabs/all).',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ componentType: {
11
+ type: 'string',
12
+ description: 'Component type to search for (e.g., "Light", "Collider", "AudioSource")'
13
+ },
14
+ includeInactive: {
15
+ type: 'boolean',
16
+ description: 'Include inactive GameObjects. Default: true'
17
+ },
18
+ searchScope: {
19
+ type: 'string',
20
+ enum: ['scene', 'prefabs', 'all'],
21
+ description: 'Where to search: current scene, prefabs, or all. Default: "scene"'
22
+ },
23
+ matchExactType: {
24
+ type: 'boolean',
25
+ description: 'Match exact type only (not derived types). Default: true'
26
+ }
27
+ },
28
+ required: ['componentType']
29
+ }
30
+ };
31
+
32
+ /**
33
+ * Handler for find_by_component tool
34
+ */
35
+ export async function findByComponentHandler(unityConnection, args) {
36
+ try {
37
+ // Check connection
38
+ if (!unityConnection.isConnected()) {
39
+ return {
40
+ content: [
41
+ {
42
+ type: 'text',
43
+ text: 'Failed to find GameObjects: Unity connection not available'
44
+ }
45
+ ],
46
+ isError: true
47
+ };
48
+ }
49
+
50
+ // Send command to Unity
51
+ const result = await unityConnection.sendCommand('find_by_component', args);
52
+
53
+ // Handle Unity response
54
+ if (result.status === 'error') {
55
+ return {
56
+ content: [
57
+ {
58
+ type: 'text',
59
+ text: `Failed to find GameObjects: ${result.error}`
60
+ }
61
+ ],
62
+ isError: true
63
+ };
64
+ }
65
+
66
+ // Success response
67
+ return {
68
+ content: [
69
+ {
70
+ type: 'text',
71
+ text: result.result.summary || `Found ${result.result.totalFound} GameObjects`
72
+ }
73
+ ],
74
+ isError: false
75
+ };
76
+ } catch (error) {
77
+ return {
78
+ content: [
79
+ {
80
+ type: 'text',
81
+ text: `Failed to find GameObjects: ${error.message}`
82
+ }
83
+ ],
84
+ isError: true
85
+ };
86
+ }
87
+ }