@akiojin/unity-mcp-server 3.0.0 → 3.1.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/package.json +1 -1
- package/src/core/projectInfo.js +21 -0
- package/src/core/workers/indexBuildWorker.js +169 -26
- package/src/handlers/scene/SceneCreateToolHandler.js +5 -6
- package/src/handlers/scene/SceneLoadToolHandler.js +5 -6
- package/src/handlers/scene/SceneSaveToolHandler.js +5 -6
- package/src/handlers/script/CodeIndexBuildToolHandler.js +9 -2
- package/src/handlers/script/ScriptSearchToolHandler.js +4 -3
- package/src/handlers/ui/UIFindElementsToolHandler.js +14 -2
- package/src/tools/scene/getSceneInfo.js +4 -4
package/package.json
CHANGED
package/src/core/projectInfo.js
CHANGED
|
@@ -46,6 +46,22 @@ export class ProjectInfoProvider {
|
|
|
46
46
|
|
|
47
47
|
async get() {
|
|
48
48
|
if (this.cached) return this.cached;
|
|
49
|
+
// Env-driven project root override (primarily for tests)
|
|
50
|
+
const envRootRaw = process.env.UNITY_PROJECT_ROOT;
|
|
51
|
+
if (typeof envRootRaw === 'string' && envRootRaw.trim().length > 0) {
|
|
52
|
+
const envRoot = envRootRaw.trim();
|
|
53
|
+
const projectRoot = normalize(path.resolve(envRoot));
|
|
54
|
+
const codeIndexRoot = normalize(resolveDefaultCodeIndexRoot(projectRoot));
|
|
55
|
+
this.cached = {
|
|
56
|
+
projectRoot,
|
|
57
|
+
assetsPath: normalize(path.join(projectRoot, 'Assets')),
|
|
58
|
+
packagesPath: normalize(path.join(projectRoot, 'Packages')),
|
|
59
|
+
packageCachePath: normalize(path.join(projectRoot, 'Library/PackageCache')),
|
|
60
|
+
codeIndexRoot
|
|
61
|
+
};
|
|
62
|
+
return this.cached;
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
// Config-driven project root (no env fallback)
|
|
50
66
|
const cfgRootRaw = config?.project?.root;
|
|
51
67
|
if (typeof cfgRootRaw === 'string' && cfgRootRaw.trim().length > 0) {
|
|
@@ -61,6 +77,7 @@ export class ProjectInfoProvider {
|
|
|
61
77
|
projectRoot,
|
|
62
78
|
assetsPath: normalize(path.join(projectRoot, 'Assets')),
|
|
63
79
|
packagesPath: normalize(path.join(projectRoot, 'Packages')),
|
|
80
|
+
packageCachePath: normalize(path.join(projectRoot, 'Library/PackageCache')),
|
|
64
81
|
codeIndexRoot
|
|
65
82
|
};
|
|
66
83
|
return this.cached;
|
|
@@ -76,6 +93,9 @@ export class ProjectInfoProvider {
|
|
|
76
93
|
projectRoot: info.projectRoot,
|
|
77
94
|
assetsPath: info.assetsPath,
|
|
78
95
|
packagesPath: normalize(info.packagesPath || path.join(info.projectRoot, 'Packages')),
|
|
96
|
+
packageCachePath: normalize(
|
|
97
|
+
info.packageCachePath || path.join(info.projectRoot, 'Library/PackageCache')
|
|
98
|
+
),
|
|
79
99
|
codeIndexRoot: normalize(
|
|
80
100
|
info.codeIndexRoot || resolveDefaultCodeIndexRoot(info.projectRoot)
|
|
81
101
|
)
|
|
@@ -96,6 +116,7 @@ export class ProjectInfoProvider {
|
|
|
96
116
|
projectRoot,
|
|
97
117
|
assetsPath: normalize(path.join(projectRoot, 'Assets')),
|
|
98
118
|
packagesPath: normalize(path.join(projectRoot, 'Packages')),
|
|
119
|
+
packageCachePath: normalize(path.join(projectRoot, 'Library/PackageCache')),
|
|
99
120
|
codeIndexRoot
|
|
100
121
|
};
|
|
101
122
|
return this.cached;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { parentPort, workerData } from 'worker_threads';
|
|
18
|
-
import { spawn } from 'child_process';
|
|
18
|
+
import { spawn, execFile } from 'child_process';
|
|
19
19
|
import fs from 'fs';
|
|
20
20
|
import path from 'path';
|
|
21
21
|
import os from 'os';
|
|
@@ -107,6 +107,111 @@ function walkCs(root, files, seen) {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Fast file enumeration using OS native commands (find on Unix, PowerShell on Windows)
|
|
112
|
+
* Falls back to walkCs on failure
|
|
113
|
+
* @param {string[]} roots - Array of root directories to scan
|
|
114
|
+
* @returns {Promise<string[]>} Array of absolute C# file paths
|
|
115
|
+
*/
|
|
116
|
+
async function fastWalkCs(roots) {
|
|
117
|
+
const isWindows = process.platform === 'win32';
|
|
118
|
+
const allFiles = [];
|
|
119
|
+
const seen = new Set();
|
|
120
|
+
|
|
121
|
+
for (const root of roots) {
|
|
122
|
+
if (!fs.existsSync(root)) {
|
|
123
|
+
log('info', `[worker] Skipping non-existent root: ${root}`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
log('info', `[worker] Scanning ${path.basename(root)}...`);
|
|
128
|
+
const startTime = Date.now();
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const files = await new Promise((resolve, reject) => {
|
|
132
|
+
if (isWindows) {
|
|
133
|
+
// Windows: Use PowerShell (secure - no shell interpretation)
|
|
134
|
+
execFile(
|
|
135
|
+
'powershell',
|
|
136
|
+
[
|
|
137
|
+
'-NoProfile',
|
|
138
|
+
'-Command',
|
|
139
|
+
`Get-ChildItem -Path '${root.replace(/'/g, "''")}' -Recurse -Include *.cs -File -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName`
|
|
140
|
+
],
|
|
141
|
+
{ maxBuffer: 50 * 1024 * 1024 }, // 50MB buffer for large projects
|
|
142
|
+
(error, stdout) => {
|
|
143
|
+
if (error) {
|
|
144
|
+
reject(error);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const files = stdout
|
|
148
|
+
.split('\r\n')
|
|
149
|
+
.map(f => f.trim())
|
|
150
|
+
.filter(f => f && f.endsWith('.cs'));
|
|
151
|
+
resolve(files);
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
} else {
|
|
155
|
+
// Unix: Use find command (secure - no shell interpretation)
|
|
156
|
+
// Note: We intentionally don't exclude hidden directories (*/.*) because:
|
|
157
|
+
// - The search root may be inside .worktrees which contains a dot
|
|
158
|
+
// - -name '*.cs' already filters to only C# files (excluding .meta etc.)
|
|
159
|
+
execFile(
|
|
160
|
+
'find',
|
|
161
|
+
[
|
|
162
|
+
root,
|
|
163
|
+
'-name',
|
|
164
|
+
'*.cs',
|
|
165
|
+
'-type',
|
|
166
|
+
'f',
|
|
167
|
+
'-not',
|
|
168
|
+
'-path',
|
|
169
|
+
'*/obj/*',
|
|
170
|
+
'-not',
|
|
171
|
+
'-path',
|
|
172
|
+
'*/bin/*'
|
|
173
|
+
],
|
|
174
|
+
{ maxBuffer: 50 * 1024 * 1024 }, // 50MB buffer for large projects
|
|
175
|
+
(error, stdout) => {
|
|
176
|
+
if (error) {
|
|
177
|
+
reject(error);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const files = stdout
|
|
181
|
+
.split('\n')
|
|
182
|
+
.map(f => f.trim())
|
|
183
|
+
.filter(f => f && f.endsWith('.cs'));
|
|
184
|
+
resolve(files);
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const elapsed = Date.now() - startTime;
|
|
191
|
+
log('info', `[worker] Found ${files.length} files in ${path.basename(root)} (${elapsed}ms)`);
|
|
192
|
+
|
|
193
|
+
// Add unique files
|
|
194
|
+
for (const f of files) {
|
|
195
|
+
if (!seen.has(f)) {
|
|
196
|
+
seen.add(f);
|
|
197
|
+
allFiles.push(f);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch (e) {
|
|
201
|
+
// Fallback to walkCs on error
|
|
202
|
+
log(
|
|
203
|
+
'warn',
|
|
204
|
+
`[worker] Fast scan failed for ${path.basename(root)}: ${e.message}. Using fallback.`
|
|
205
|
+
);
|
|
206
|
+
const fallbackFiles = [];
|
|
207
|
+
walkCs(root, fallbackFiles, seen);
|
|
208
|
+
allFiles.push(...fallbackFiles);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return allFiles;
|
|
213
|
+
}
|
|
214
|
+
|
|
110
215
|
/**
|
|
111
216
|
* Convert absolute path to relative path
|
|
112
217
|
*/
|
|
@@ -406,7 +511,9 @@ async function runBuild() {
|
|
|
406
511
|
concurrency: _concurrency = 1, // Reserved for future parallel processing
|
|
407
512
|
throttleMs = 0,
|
|
408
513
|
retry = 2,
|
|
409
|
-
reportPercentage = 10
|
|
514
|
+
reportPercentage = 10,
|
|
515
|
+
excludePackageCache = false,
|
|
516
|
+
forceRebuild = false // Skip change detection and rebuild all files
|
|
410
517
|
} = workerData;
|
|
411
518
|
void _concurrency; // Explicitly mark as intentionally unused
|
|
412
519
|
|
|
@@ -473,36 +580,72 @@ async function runBuild() {
|
|
|
473
580
|
runSQL(db, 'CREATE INDEX IF NOT EXISTS idx_symbols_path ON symbols(path)');
|
|
474
581
|
|
|
475
582
|
// Scan for C# files
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
path.resolve(projectRoot, 'Library/PackageCache')
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
583
|
+
// Build roots list based on excludePackageCache option
|
|
584
|
+
const roots = [path.resolve(projectRoot, 'Assets'), path.resolve(projectRoot, 'Packages')];
|
|
585
|
+
if (!excludePackageCache) {
|
|
586
|
+
roots.push(path.resolve(projectRoot, 'Library/PackageCache'));
|
|
587
|
+
}
|
|
588
|
+
log(
|
|
589
|
+
'info',
|
|
590
|
+
`[worker] Scanning roots: ${roots.map(r => path.basename(r)).join(', ')}${excludePackageCache ? ' (PackageCache excluded)' : ''}`
|
|
591
|
+
);
|
|
484
592
|
|
|
593
|
+
// Use fast find command when available, fallback to walkCs
|
|
594
|
+
const files = await fastWalkCs(roots);
|
|
485
595
|
log('info', `[worker] Found ${files.length} C# files to process`);
|
|
486
596
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
597
|
+
let changed = [];
|
|
598
|
+
let removed = [];
|
|
599
|
+
const wanted = new Map();
|
|
600
|
+
|
|
601
|
+
if (forceRebuild) {
|
|
602
|
+
// Skip change detection - process all files
|
|
603
|
+
log('info', `[worker] forceRebuild=true, skipping change detection`);
|
|
604
|
+
// Clear all existing data
|
|
605
|
+
runSQL(db, 'DELETE FROM symbols');
|
|
606
|
+
runSQL(db, 'DELETE FROM files');
|
|
607
|
+
// Mark all files as changed
|
|
608
|
+
for (const abs of files) {
|
|
609
|
+
const rel = toRel(abs, projectRoot);
|
|
610
|
+
wanted.set(rel, '0-0'); // Dummy signature for forceRebuild
|
|
611
|
+
changed.push(rel);
|
|
612
|
+
}
|
|
613
|
+
log('info', `[worker] All ${changed.length} files marked for processing`);
|
|
614
|
+
} else {
|
|
615
|
+
// Normal change detection
|
|
616
|
+
// Get current indexed files
|
|
617
|
+
const currentResult = querySQL(db, 'SELECT path, sig FROM files');
|
|
618
|
+
const current = new Map();
|
|
619
|
+
if (currentResult.length > 0) {
|
|
620
|
+
for (const row of currentResult[0].values) {
|
|
621
|
+
current.set(row[0], row[1]);
|
|
622
|
+
}
|
|
493
623
|
}
|
|
494
|
-
}
|
|
495
624
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
625
|
+
// Determine changes (this calls makeSig for each file)
|
|
626
|
+
log('info', `[worker] Computing file signatures (${files.length} files)...`);
|
|
627
|
+
const sigStartTime = Date.now();
|
|
628
|
+
let sigProcessed = 0;
|
|
629
|
+
for (const abs of files) {
|
|
630
|
+
const rel = toRel(abs, projectRoot);
|
|
631
|
+
const sig = makeSig(abs);
|
|
632
|
+
wanted.set(rel, sig);
|
|
633
|
+
sigProcessed++;
|
|
634
|
+
// Report progress every 10000 files
|
|
635
|
+
if (sigProcessed % 10000 === 0) {
|
|
636
|
+
const elapsed = ((Date.now() - sigStartTime) / 1000).toFixed(1);
|
|
637
|
+
log('info', `[worker] Signature progress: ${sigProcessed}/${files.length} (${elapsed}s)`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
const sigTime = ((Date.now() - sigStartTime) / 1000).toFixed(1);
|
|
641
|
+
log('info', `[worker] Signatures computed in ${sigTime}s`);
|
|
500
642
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
643
|
+
for (const [rel, sig] of wanted) {
|
|
644
|
+
if (current.get(rel) !== sig) changed.push(rel);
|
|
645
|
+
}
|
|
646
|
+
for (const [rel] of current) {
|
|
647
|
+
if (!wanted.has(rel)) removed.push(rel);
|
|
648
|
+
}
|
|
506
649
|
}
|
|
507
650
|
|
|
508
651
|
log('info', `[worker] Changes: ${changed.length} to update, ${removed.length} to remove`);
|
|
@@ -64,17 +64,16 @@ export class SceneCreateToolHandler extends BaseToolHandler {
|
|
|
64
64
|
// Send command to Unity
|
|
65
65
|
const result = await this.unityConnection.sendCommand('create_scene', params);
|
|
66
66
|
|
|
67
|
-
//
|
|
68
|
-
if (result
|
|
67
|
+
// Unity returns errors as { error: "..." } payloads (still wrapped in a success response)
|
|
68
|
+
if (result && result.error) {
|
|
69
69
|
const error = new Error(result.error);
|
|
70
70
|
error.code = 'UNITY_ERROR';
|
|
71
71
|
throw error;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
// Handle undefined or null results from Unity
|
|
75
|
-
if (result
|
|
74
|
+
// Handle undefined or null results from Unity (best-effort)
|
|
75
|
+
if (result === undefined || result === null) {
|
|
76
76
|
return {
|
|
77
|
-
status: 'success',
|
|
78
77
|
sceneName: params.sceneName,
|
|
79
78
|
path: params.path || 'Assets/Scenes/',
|
|
80
79
|
loadScene: params.loadScene !== false,
|
|
@@ -83,6 +82,6 @@ export class SceneCreateToolHandler extends BaseToolHandler {
|
|
|
83
82
|
};
|
|
84
83
|
}
|
|
85
84
|
|
|
86
|
-
return result
|
|
85
|
+
return result;
|
|
87
86
|
}
|
|
88
87
|
}
|
|
@@ -67,17 +67,16 @@ export class SceneLoadToolHandler extends BaseToolHandler {
|
|
|
67
67
|
// Send command to Unity
|
|
68
68
|
const result = await this.unityConnection.sendCommand('load_scene', params);
|
|
69
69
|
|
|
70
|
-
//
|
|
71
|
-
if (result
|
|
70
|
+
// Unity returns errors as { error: "..." } payloads (still wrapped in a success response)
|
|
71
|
+
if (result && result.error) {
|
|
72
72
|
const error = new Error(result.error);
|
|
73
73
|
error.code = 'UNITY_ERROR';
|
|
74
74
|
throw error;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
// Handle undefined or null results from Unity
|
|
78
|
-
if (result
|
|
77
|
+
// Handle undefined or null results from Unity (best-effort)
|
|
78
|
+
if (result === undefined || result === null) {
|
|
79
79
|
return {
|
|
80
|
-
status: 'success',
|
|
81
80
|
sceneName: params.sceneName || 'Unknown',
|
|
82
81
|
scenePath: params.scenePath || 'Unknown',
|
|
83
82
|
loadMode: params.loadMode || 'Single',
|
|
@@ -85,6 +84,6 @@ export class SceneLoadToolHandler extends BaseToolHandler {
|
|
|
85
84
|
};
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
return result
|
|
87
|
+
return result;
|
|
89
88
|
}
|
|
90
89
|
}
|
|
@@ -51,23 +51,22 @@ export class SceneSaveToolHandler extends BaseToolHandler {
|
|
|
51
51
|
// Send command to Unity
|
|
52
52
|
const result = await this.unityConnection.sendCommand('save_scene', params);
|
|
53
53
|
|
|
54
|
-
//
|
|
55
|
-
if (result
|
|
54
|
+
// Unity returns errors as { error: "..." } payloads (still wrapped in a success response)
|
|
55
|
+
if (result && result.error) {
|
|
56
56
|
const error = new Error(result.error);
|
|
57
57
|
error.code = 'UNITY_ERROR';
|
|
58
58
|
throw error;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
// Handle undefined or null results from Unity
|
|
62
|
-
if (result
|
|
61
|
+
// Handle undefined or null results from Unity (best-effort)
|
|
62
|
+
if (result === undefined || result === null) {
|
|
63
63
|
return {
|
|
64
|
-
status: 'success',
|
|
65
64
|
scenePath: params.scenePath || 'Current scene path',
|
|
66
65
|
saveAs: params.saveAs === true,
|
|
67
66
|
message: 'Scene save completed but Unity returned no details'
|
|
68
67
|
};
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
return result
|
|
70
|
+
return result;
|
|
72
71
|
}
|
|
73
72
|
}
|
|
@@ -23,6 +23,11 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
23
23
|
minimum: 0,
|
|
24
24
|
maximum: 5,
|
|
25
25
|
description: 'Number of retries for LSP requests (default: 2).'
|
|
26
|
+
},
|
|
27
|
+
excludePackageCache: {
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
description:
|
|
30
|
+
'Exclude Library/PackageCache from indexing (default: false). Set to true for faster builds when package symbols are not needed.'
|
|
26
31
|
}
|
|
27
32
|
},
|
|
28
33
|
required: []
|
|
@@ -54,7 +59,8 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
54
59
|
return {
|
|
55
60
|
success: false,
|
|
56
61
|
error: 'build_already_running',
|
|
57
|
-
message:
|
|
62
|
+
message:
|
|
63
|
+
'Code index build is already running (Worker Thread). Use code_index_status to check progress.',
|
|
58
64
|
jobId: this.currentJobId
|
|
59
65
|
};
|
|
60
66
|
}
|
|
@@ -86,7 +92,8 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
86
92
|
concurrency: 1, // Worker Thread uses sequential processing for stability
|
|
87
93
|
throttleMs: Math.max(0, Number(params?.throttleMs ?? 0)),
|
|
88
94
|
retry: Math.max(0, Math.min(5, Number(params?.retry ?? 2))),
|
|
89
|
-
reportPercentage: 10
|
|
95
|
+
reportPercentage: 10,
|
|
96
|
+
excludePackageCache: Boolean(params?.excludePackageCache)
|
|
90
97
|
});
|
|
91
98
|
|
|
92
99
|
// Clear current job tracking on success
|
|
@@ -8,7 +8,7 @@ export class ScriptSearchToolHandler extends BaseToolHandler {
|
|
|
8
8
|
constructor(unityConnection) {
|
|
9
9
|
super(
|
|
10
10
|
'script_search',
|
|
11
|
-
'[OFFLINE] No Unity connection required. Search C# by substring/regex/glob with pagination and snippet context. PRIORITY: Use to locate symbols/files; avoid full contents. Use returnMode="snippets" (or "metadata") with small snippetContext (1–2). Narrow aggressively via include globs under Assets
|
|
11
|
+
'[OFFLINE] No Unity connection required. Search C# by substring/regex/glob with pagination and snippet context. PRIORITY: Use to locate symbols/files; avoid full contents. Use returnMode="snippets" (or "metadata") with small snippetContext (1–2). Narrow aggressively via include globs under Assets/**, Packages/**, or Library/PackageCache/** and semantic filters (namespace/container/identifier). Do NOT prefix repository folders.',
|
|
12
12
|
{
|
|
13
13
|
type: 'object',
|
|
14
14
|
properties: {
|
|
@@ -29,9 +29,9 @@ export class ScriptSearchToolHandler extends BaseToolHandler {
|
|
|
29
29
|
},
|
|
30
30
|
scope: {
|
|
31
31
|
type: 'string',
|
|
32
|
-
enum: ['assets', 'packages', 'embedded', 'all'],
|
|
32
|
+
enum: ['assets', 'packages', 'embedded', 'library', 'all'],
|
|
33
33
|
description:
|
|
34
|
-
'Search scope: assets (Assets
|
|
34
|
+
'Search scope: assets (Assets/), packages (Packages/), embedded, library (Library/PackageCache/), or all. Default: all.'
|
|
35
35
|
},
|
|
36
36
|
include: {
|
|
37
37
|
type: 'string',
|
|
@@ -151,6 +151,7 @@ export class ScriptSearchToolHandler extends BaseToolHandler {
|
|
|
151
151
|
if (scope === 'assets' || scope === 'all') roots.push(info.assetsPath);
|
|
152
152
|
if (scope === 'packages' || scope === 'embedded' || scope === 'all')
|
|
153
153
|
roots.push(info.packagesPath);
|
|
154
|
+
if (scope === 'library' || scope === 'all') roots.push(info.packageCachePath);
|
|
154
155
|
|
|
155
156
|
const includeRx = globToRegExp(include);
|
|
156
157
|
const excludeRx = exclude ? globToRegExp(exclude) : null;
|
|
@@ -24,6 +24,10 @@ export class UIFindElementsToolHandler extends BaseToolHandler {
|
|
|
24
24
|
canvasFilter: {
|
|
25
25
|
type: 'string',
|
|
26
26
|
description: 'Filter by parent Canvas name.'
|
|
27
|
+
},
|
|
28
|
+
uiSystem: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Filter by UI system: ugui|uitk|imgui|all (default: all).'
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
required: []
|
|
@@ -32,7 +36,14 @@ export class UIFindElementsToolHandler extends BaseToolHandler {
|
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
async execute(params = {}) {
|
|
35
|
-
const {
|
|
39
|
+
const {
|
|
40
|
+
elementType,
|
|
41
|
+
tagFilter,
|
|
42
|
+
namePattern,
|
|
43
|
+
includeInactive = false,
|
|
44
|
+
canvasFilter,
|
|
45
|
+
uiSystem
|
|
46
|
+
} = params;
|
|
36
47
|
|
|
37
48
|
// Ensure connected
|
|
38
49
|
if (!this.unityConnection.isConnected()) {
|
|
@@ -44,7 +55,8 @@ export class UIFindElementsToolHandler extends BaseToolHandler {
|
|
|
44
55
|
tagFilter,
|
|
45
56
|
namePattern,
|
|
46
57
|
includeInactive,
|
|
47
|
-
canvasFilter
|
|
58
|
+
canvasFilter,
|
|
59
|
+
uiSystem
|
|
48
60
|
});
|
|
49
61
|
|
|
50
62
|
// Return the result for the BaseToolHandler to format
|
|
@@ -57,10 +57,10 @@ export async function getSceneInfoHandler(unityConnection, args) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// Send command to Unity
|
|
60
|
-
const result = await unityConnection.sendCommand('
|
|
60
|
+
const result = await unityConnection.sendCommand('get_scene_info', args);
|
|
61
61
|
|
|
62
|
-
//
|
|
63
|
-
if (result
|
|
62
|
+
// Unity returns errors as { error: "..." } payloads (still wrapped in a success response)
|
|
63
|
+
if (result && result.error) {
|
|
64
64
|
return {
|
|
65
65
|
content: [
|
|
66
66
|
{
|
|
@@ -77,7 +77,7 @@ export async function getSceneInfoHandler(unityConnection, args) {
|
|
|
77
77
|
content: [
|
|
78
78
|
{
|
|
79
79
|
type: 'text',
|
|
80
|
-
text: result
|
|
80
|
+
text: result?.summary || `Scene information retrieved`
|
|
81
81
|
}
|
|
82
82
|
],
|
|
83
83
|
isError: false
|