@akiojin/unity-mcp-server 2.40.2 → 2.40.4
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 +21 -0
- package/bin/unity-mcp-server +1 -1
- package/package.json +4 -3
- package/src/core/codeIndex.js +143 -20
- package/src/core/indexWatcher.js +11 -0
- package/src/core/server.js +9 -34
- package/src/core/sqliteFallback.js +75 -0
- package/src/handlers/analysis/AnalyzeSceneContentsToolHandler.js +27 -24
- package/src/handlers/analysis/FindByComponentToolHandler.js +4 -1
- package/src/handlers/analysis/GetAnimatorStateToolHandler.js +5 -5
- package/src/handlers/analysis/GetComponentValuesToolHandler.js +4 -1
- package/src/handlers/analysis/GetGameObjectDetailsToolHandler.js +27 -24
- package/src/handlers/analysis/GetInputActionsStateToolHandler.js +5 -5
- package/src/handlers/analysis/GetObjectReferencesToolHandler.js +4 -1
- package/src/handlers/asset/AssetDatabaseManageToolHandler.js +24 -6
- package/src/handlers/asset/AssetDependencyAnalyzeToolHandler.js +21 -11
- package/src/handlers/asset/AssetImportSettingsManageToolHandler.js +7 -7
- package/src/handlers/asset/AssetMaterialCreateToolHandler.js +78 -81
- package/src/handlers/asset/AssetMaterialModifyToolHandler.js +57 -61
- package/src/handlers/asset/AssetPrefabCreateToolHandler.js +61 -64
- package/src/handlers/asset/AssetPrefabExitModeToolHandler.js +9 -13
- package/src/handlers/asset/AssetPrefabInstantiateToolHandler.js +110 -116
- package/src/handlers/asset/AssetPrefabModifyToolHandler.js +58 -58
- package/src/handlers/asset/AssetPrefabOpenToolHandler.js +7 -5
- package/src/handlers/asset/AssetPrefabSaveToolHandler.js +13 -6
- package/src/handlers/compilation/CompilationGetStateToolHandler.js +4 -3
- package/src/handlers/component/ComponentAddToolHandler.js +2 -2
- package/src/handlers/component/ComponentGetTypesToolHandler.js +17 -21
- package/src/handlers/component/ComponentListToolHandler.js +5 -3
- package/src/handlers/component/ComponentModifyToolHandler.js +3 -3
- package/src/handlers/component/ComponentRemoveToolHandler.js +2 -2
- package/src/handlers/console/ConsoleClearToolHandler.js +36 -46
- package/src/handlers/editor/EditorLayersManageToolHandler.js +7 -6
- package/src/handlers/editor/EditorTagsManageToolHandler.js +20 -11
- package/src/handlers/editor/EditorToolsManageToolHandler.js +2 -2
- package/src/handlers/editor/EditorWindowsManageToolHandler.js +6 -5
- package/src/handlers/gameobject/GameObjectCreateToolHandler.js +62 -66
- package/src/handlers/gameobject/GameObjectDeleteToolHandler.js +9 -9
- package/src/handlers/gameobject/GameObjectFindToolHandler.js +13 -11
- package/src/handlers/gameobject/GameObjectGetHierarchyToolHandler.js +22 -16
- package/src/handlers/input/InputActionAddToolHandler.js +2 -2
- package/src/handlers/input/InputActionMapCreateToolHandler.js +2 -2
- package/src/handlers/input/InputActionMapRemoveToolHandler.js +2 -2
- package/src/handlers/input/InputActionRemoveToolHandler.js +2 -2
- package/src/handlers/input/InputBindingAddToolHandler.js +2 -2
- package/src/handlers/input/InputBindingCompositeCreateToolHandler.js +2 -2
- package/src/handlers/input/InputBindingRemoveAllToolHandler.js +2 -2
- package/src/handlers/input/InputBindingRemoveToolHandler.js +2 -2
- package/src/handlers/input/InputControlSchemesManageToolHandler.js +2 -2
- package/src/handlers/package/PackageManagerToolHandler.js +41 -44
- package/src/handlers/package/RegistryConfigToolHandler.js +28 -7
- package/src/handlers/playmode/PlaymodeGetStateToolHandler.js +12 -16
- package/src/handlers/playmode/PlaymodePauseToolHandler.js +8 -12
- package/src/handlers/playmode/PlaymodeWaitForStateToolHandler.js +6 -3
- package/src/handlers/scene/GetSceneInfoToolHandler.js +11 -11
- package/src/handlers/scene/SceneCreateToolHandler.js +28 -31
- package/src/handlers/scene/SceneListToolHandler.js +21 -24
- package/src/handlers/scene/SceneLoadToolHandler.js +27 -29
- package/src/handlers/scene/SceneSaveToolHandler.js +19 -22
- package/src/handlers/screenshot/ScreenshotCaptureToolHandler.js +88 -66
- package/src/handlers/script/CodeIndexBuildToolHandler.js +7 -0
- package/src/handlers/script/CodeIndexStatusToolHandler.js +4 -3
- package/src/handlers/script/CodeIndexUpdateToolHandler.js +24 -14
- package/src/handlers/script/ScriptCreateClassToolHandler.js +44 -9
- package/src/handlers/script/ScriptPackagesListToolHandler.js +91 -91
- package/src/handlers/script/ScriptRefactorRenameToolHandler.js +80 -71
- package/src/handlers/script/ScriptRemoveSymbolToolHandler.js +21 -7
- package/src/handlers/script/ScriptSearchToolHandler.js +299 -266
- package/src/handlers/script/ScriptSymbolsGetToolHandler.js +88 -79
- package/src/handlers/settings/SettingsGetToolHandler.js +28 -13
- package/src/handlers/settings/SettingsUpdateToolHandler.js +20 -6
- package/src/handlers/ui/UIClickElementToolHandler.js +87 -96
- package/src/handlers/ui/UIFindElementsToolHandler.js +45 -55
- package/src/handlers/ui/UIGetElementStateToolHandler.js +35 -43
- package/src/handlers/ui/UISetElementValueToolHandler.js +42 -49
- package/src/handlers/ui/UISimulateInputToolHandler.js +134 -136
- package/src/handlers/video/VideoCaptureForToolHandler.js +24 -7
- package/src/lsp/LspRpcClient.js +24 -12
- package/src/tools/analysis/analyzeSceneContents.js +85 -85
- package/src/tools/analysis/findByComponent.js +73 -73
- package/src/tools/analysis/getAnimatorState.js +287 -287
- package/src/tools/analysis/getComponentValues.js +161 -161
- package/src/tools/analysis/getGameObjectDetails.js +138 -138
- package/src/tools/analysis/getInputActionsState.js +291 -291
- package/src/tools/analysis/getObjectReferences.js +72 -72
- package/src/tools/input/inputActionsEditor.js +522 -474
- package/src/tools/scene/createScene.js +98 -97
- package/src/tools/scene/getSceneInfo.js +82 -81
- package/src/tools/scene/listScenes.js +70 -69
- package/src/tools/scene/loadScene.js +108 -106
- package/src/tools/scene/saveScene.js +78 -77
- package/src/tools/system/ping.js +9 -12
- package/src/utils/validators.js +2 -2
package/README.md
CHANGED
|
@@ -332,6 +332,27 @@ npm uninstall -g @akiojin/unity-mcp-server
|
|
|
332
332
|
npm install -g @akiojin/unity-mcp-server
|
|
333
333
|
```
|
|
334
334
|
|
|
335
|
+
### MCP Client Shows "Capabilities: none"
|
|
336
|
+
|
|
337
|
+
If your MCP client (Claude Code, Cursor, etc.) shows "Capabilities: none" despite successful connection:
|
|
338
|
+
|
|
339
|
+
**Symptom**: Server connects successfully, but no tools are visible to the client.
|
|
340
|
+
|
|
341
|
+
**Root Cause**: Empty capability objects (`resources: {}`, `prompts: {}`) in MCP SDK v0.6.1 cause capability validation to fail silently.
|
|
342
|
+
|
|
343
|
+
**Solution**: Update to latest version with the fix:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
# Update to latest version (2.41.0+)
|
|
347
|
+
npm update -g @akiojin/unity-mcp-server
|
|
348
|
+
|
|
349
|
+
# Or reinstall
|
|
350
|
+
npm uninstall -g @akiojin/unity-mcp-server
|
|
351
|
+
npm install -g @akiojin/unity-mcp-server
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**Verification**: After restart, your MCP client should display 107 available tools.
|
|
355
|
+
|
|
335
356
|
## Repository
|
|
336
357
|
|
|
337
358
|
Full source code and documentation: <https://github.com/akiojin/unity-mcp-server>
|
package/bin/unity-mcp-server
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akiojin/unity-mcp-server",
|
|
3
|
-
"version": "2.40.
|
|
3
|
+
"version": "2.40.4",
|
|
4
4
|
"description": "MCP server and Unity Editor bridge — enables AI assistants to control Unity for AI-assisted workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/core/server.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"test:verbose": "VERBOSE_TEST=true node --test tests/**/*.test.js",
|
|
28
28
|
"prepare": "cd .. && husky || true",
|
|
29
29
|
"prepublishOnly": "npm run test:ci",
|
|
30
|
-
"postinstall": "chmod +x bin/unity-mcp-server || true",
|
|
30
|
+
"postinstall": "node scripts/ensure-better-sqlite3.mjs && chmod +x bin/unity-mcp-server || true",
|
|
31
31
|
"test:ci:unity": "timeout 60 node --test tests/unit/core/codeIndex.test.js tests/unit/core/codeIndexDb.test.js tests/unit/core/config.test.js tests/unit/core/indexWatcher.test.js tests/unit/core/projectInfo.test.js tests/unit/core/server.test.js || exit 0",
|
|
32
32
|
"test:unity": "node tests/run-unity-integration.mjs",
|
|
33
33
|
"test:nounity": "npm run test:integration",
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@modelcontextprotocol/sdk": "^0.6.1",
|
|
52
52
|
"better-sqlite3": "^9.4.3",
|
|
53
|
-
"find-up": "^6.3.0"
|
|
53
|
+
"find-up": "^6.3.0",
|
|
54
|
+
"sql.js": "^1.13.0"
|
|
54
55
|
},
|
|
55
56
|
"engines": {
|
|
56
57
|
"node": ">=18 <23"
|
package/src/core/codeIndex.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { ProjectInfoProvider } from './projectInfo.js';
|
|
4
|
+
import { logger } from './config.js';
|
|
5
|
+
import { createSqliteFallback } from './sqliteFallback.js';
|
|
6
|
+
|
|
7
|
+
// Shared driver availability state across CodeIndex instances
|
|
8
|
+
const driverStatus = {
|
|
9
|
+
available: null,
|
|
10
|
+
error: null,
|
|
11
|
+
logged: false
|
|
12
|
+
};
|
|
4
13
|
|
|
5
14
|
export class CodeIndex {
|
|
6
15
|
constructor(unityConnection) {
|
|
@@ -9,21 +18,42 @@ export class CodeIndex {
|
|
|
9
18
|
this.db = null;
|
|
10
19
|
this.dbPath = null;
|
|
11
20
|
this.disabled = false; // set true if better-sqlite3 is unavailable
|
|
21
|
+
this.disableReason = null;
|
|
12
22
|
this._Database = null;
|
|
23
|
+
this._openFallback = null;
|
|
24
|
+
this._persistFallback = null;
|
|
13
25
|
}
|
|
14
26
|
|
|
15
27
|
async _ensureDriver() {
|
|
16
|
-
if (this.disabled)
|
|
28
|
+
if (driverStatus.available === false || this.disabled) {
|
|
29
|
+
this.disabled = true;
|
|
30
|
+
this.disableReason = this.disableReason || driverStatus.error;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
17
33
|
if (this._Database) return true;
|
|
18
34
|
try {
|
|
19
35
|
// Dynamic import to avoid hard failure when native binding is missing
|
|
20
36
|
const mod = await import('better-sqlite3');
|
|
21
37
|
this._Database = mod.default || mod;
|
|
38
|
+
driverStatus.available = true;
|
|
39
|
+
driverStatus.error = null;
|
|
22
40
|
return true;
|
|
23
41
|
} catch (e) {
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
// Try wasm fallback (sql.js) before giving up
|
|
43
|
+
try {
|
|
44
|
+
this._openFallback = createSqliteFallback;
|
|
45
|
+
driverStatus.available = true;
|
|
46
|
+
driverStatus.error = null;
|
|
47
|
+
logger?.info?.('[index] falling back to sql.js (WASM) for code index');
|
|
48
|
+
return true;
|
|
49
|
+
} catch (fallbackError) {
|
|
50
|
+
this.disabled = true;
|
|
51
|
+
this.disableReason = `better-sqlite3 unavailable: ${e?.message || e}. Fallback failed: ${fallbackError?.message || fallbackError}`;
|
|
52
|
+
driverStatus.available = false;
|
|
53
|
+
driverStatus.error = this.disableReason;
|
|
54
|
+
this._logDisable(this.disableReason);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
27
57
|
}
|
|
28
58
|
}
|
|
29
59
|
|
|
@@ -36,11 +66,35 @@ export class CodeIndex {
|
|
|
36
66
|
fs.mkdirSync(dir, { recursive: true });
|
|
37
67
|
const dbPath = path.join(dir, 'code-index.db');
|
|
38
68
|
this.dbPath = dbPath;
|
|
39
|
-
|
|
69
|
+
try {
|
|
70
|
+
if (this._Database) {
|
|
71
|
+
this.db = new this._Database(dbPath);
|
|
72
|
+
} else if (this._openFallback) {
|
|
73
|
+
this.db = await this._openFallback(dbPath);
|
|
74
|
+
this._persistFallback = this.db.persist;
|
|
75
|
+
} else {
|
|
76
|
+
throw new Error('No database driver available');
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
this.disabled = true;
|
|
80
|
+
this.disableReason = e?.message || 'Failed to open code index database';
|
|
81
|
+
driverStatus.available = false;
|
|
82
|
+
driverStatus.error = this.disableReason;
|
|
83
|
+
this._logDisable(this.disableReason);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
40
86
|
this._initSchema();
|
|
41
87
|
return this.db;
|
|
42
88
|
}
|
|
43
89
|
|
|
90
|
+
_logDisable(reason) {
|
|
91
|
+
if (driverStatus.logged) return;
|
|
92
|
+
driverStatus.logged = true;
|
|
93
|
+
try {
|
|
94
|
+
logger?.warn?.(`[index] code index disabled: ${reason}`);
|
|
95
|
+
} catch {}
|
|
96
|
+
}
|
|
97
|
+
|
|
44
98
|
_initSchema() {
|
|
45
99
|
if (!this.db) return;
|
|
46
100
|
const db = this.db;
|
|
@@ -68,6 +122,7 @@ export class CodeIndex {
|
|
|
68
122
|
CREATE INDEX IF NOT EXISTS idx_symbols_kind ON symbols(kind);
|
|
69
123
|
CREATE INDEX IF NOT EXISTS idx_symbols_path ON symbols(path);
|
|
70
124
|
`);
|
|
125
|
+
if (this._persistFallback) this._persistFallback();
|
|
71
126
|
}
|
|
72
127
|
|
|
73
128
|
async isReady() {
|
|
@@ -80,16 +135,30 @@ export class CodeIndex {
|
|
|
80
135
|
async clearAndLoad(symbols) {
|
|
81
136
|
const db = await this.open();
|
|
82
137
|
if (!db) throw new Error('CodeIndex is unavailable (better-sqlite3 not installed)');
|
|
83
|
-
const insert = db.prepare(
|
|
84
|
-
|
|
138
|
+
const insert = db.prepare(
|
|
139
|
+
'INSERT INTO symbols(path,name,kind,container,namespace,line,column) VALUES (?,?,?,?,?,?,?)'
|
|
140
|
+
);
|
|
141
|
+
const tx = db.transaction(rows => {
|
|
85
142
|
db.exec('DELETE FROM symbols');
|
|
86
143
|
db.exec('DELETE FROM files');
|
|
87
144
|
for (const r of rows) {
|
|
88
|
-
insert.run(
|
|
145
|
+
insert.run(
|
|
146
|
+
r.path,
|
|
147
|
+
r.name,
|
|
148
|
+
r.kind,
|
|
149
|
+
r.container || null,
|
|
150
|
+
r.ns || r.namespace || null,
|
|
151
|
+
r.line || null,
|
|
152
|
+
r.column || null
|
|
153
|
+
);
|
|
89
154
|
}
|
|
90
|
-
db.prepare('REPLACE INTO meta(key,value) VALUES (?,?)').run(
|
|
155
|
+
db.prepare('REPLACE INTO meta(key,value) VALUES (?,?)').run(
|
|
156
|
+
'lastIndexedAt',
|
|
157
|
+
new Date().toISOString()
|
|
158
|
+
);
|
|
91
159
|
});
|
|
92
160
|
tx(symbols || []);
|
|
161
|
+
await this._flushFallback();
|
|
93
162
|
return { total: symbols?.length || 0 };
|
|
94
163
|
}
|
|
95
164
|
|
|
@@ -106,17 +175,23 @@ export class CodeIndex {
|
|
|
106
175
|
async upsertFile(pathStr, sig) {
|
|
107
176
|
const db = await this.open();
|
|
108
177
|
if (!db) return;
|
|
109
|
-
db.prepare('REPLACE INTO files(path,sig,updatedAt) VALUES (?,?,?)').run(
|
|
178
|
+
db.prepare('REPLACE INTO files(path,sig,updatedAt) VALUES (?,?,?)').run(
|
|
179
|
+
pathStr,
|
|
180
|
+
sig || '',
|
|
181
|
+
new Date().toISOString()
|
|
182
|
+
);
|
|
183
|
+
await this._flushFallback();
|
|
110
184
|
}
|
|
111
185
|
|
|
112
186
|
async removeFile(pathStr) {
|
|
113
187
|
const db = await this.open();
|
|
114
188
|
if (!db) return;
|
|
115
|
-
const tx = db.transaction(
|
|
189
|
+
const tx = db.transaction(p => {
|
|
116
190
|
db.prepare('DELETE FROM symbols WHERE path = ?').run(p);
|
|
117
191
|
db.prepare('DELETE FROM files WHERE path = ?').run(p);
|
|
118
192
|
});
|
|
119
193
|
tx(pathStr);
|
|
194
|
+
await this._flushFallback();
|
|
120
195
|
}
|
|
121
196
|
|
|
122
197
|
async replaceSymbolsForPath(pathStr, rows) {
|
|
@@ -124,11 +199,26 @@ export class CodeIndex {
|
|
|
124
199
|
if (!db) return;
|
|
125
200
|
const tx = db.transaction((p, list) => {
|
|
126
201
|
db.prepare('DELETE FROM symbols WHERE path = ?').run(p);
|
|
127
|
-
const insert = db.prepare(
|
|
128
|
-
|
|
129
|
-
|
|
202
|
+
const insert = db.prepare(
|
|
203
|
+
'INSERT INTO symbols(path,name,kind,container,namespace,line,column) VALUES (?,?,?,?,?,?,?)'
|
|
204
|
+
);
|
|
205
|
+
for (const r of list)
|
|
206
|
+
insert.run(
|
|
207
|
+
p,
|
|
208
|
+
r.name,
|
|
209
|
+
r.kind,
|
|
210
|
+
r.container || null,
|
|
211
|
+
r.ns || r.namespace || null,
|
|
212
|
+
r.line || null,
|
|
213
|
+
r.column || null
|
|
214
|
+
);
|
|
215
|
+
db.prepare('REPLACE INTO meta(key,value) VALUES (?,?)').run(
|
|
216
|
+
'lastIndexedAt',
|
|
217
|
+
new Date().toISOString()
|
|
218
|
+
);
|
|
130
219
|
});
|
|
131
220
|
tx(pathStr, rows || []);
|
|
221
|
+
await this._flushFallback();
|
|
132
222
|
}
|
|
133
223
|
|
|
134
224
|
async querySymbols({ name, kind, scope = 'all', exact = false }) {
|
|
@@ -137,27 +227,60 @@ export class CodeIndex {
|
|
|
137
227
|
let sql = 'SELECT path,name,kind,container,namespace,line,column FROM symbols WHERE 1=1';
|
|
138
228
|
const params = {};
|
|
139
229
|
if (name) {
|
|
140
|
-
if (exact) {
|
|
141
|
-
|
|
230
|
+
if (exact) {
|
|
231
|
+
sql += ' AND name = @name';
|
|
232
|
+
params.name = name;
|
|
233
|
+
} else {
|
|
234
|
+
sql += ' AND name LIKE @name';
|
|
235
|
+
params.name = `%${name}%`;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (kind) {
|
|
239
|
+
sql += ' AND kind = @kind';
|
|
240
|
+
params.kind = kind;
|
|
142
241
|
}
|
|
143
|
-
if (kind) { sql += ' AND kind = @kind'; params.kind = kind; }
|
|
144
242
|
const rows = db.prepare(sql).all(params);
|
|
145
243
|
// Apply path-based scope filter in JS (simpler than CASE in SQL)
|
|
146
244
|
const filtered = rows.filter(r => {
|
|
147
245
|
const p = String(r.path || '').replace(/\\\\/g, '/');
|
|
148
246
|
if (scope === 'assets') return p.startsWith('Assets/');
|
|
149
|
-
if (scope === 'packages')
|
|
247
|
+
if (scope === 'packages')
|
|
248
|
+
return p.startsWith('Packages/') || p.includes('Library/PackageCache/');
|
|
150
249
|
if (scope === 'embedded') return p.startsWith('Packages/');
|
|
151
250
|
return true;
|
|
152
251
|
});
|
|
153
|
-
return filtered.map(r => ({
|
|
252
|
+
return filtered.map(r => ({
|
|
253
|
+
path: r.path,
|
|
254
|
+
name: r.name,
|
|
255
|
+
kind: r.kind,
|
|
256
|
+
container: r.container,
|
|
257
|
+
ns: r.namespace,
|
|
258
|
+
line: r.line,
|
|
259
|
+
column: r.column
|
|
260
|
+
}));
|
|
154
261
|
}
|
|
155
262
|
|
|
156
263
|
async getStats() {
|
|
157
264
|
const db = await this.open();
|
|
158
265
|
if (!db) return { total: 0, lastIndexedAt: null };
|
|
159
266
|
const total = db.prepare('SELECT COUNT(*) AS c FROM symbols').get().c || 0;
|
|
160
|
-
const last =
|
|
267
|
+
const last =
|
|
268
|
+
db.prepare("SELECT value AS v FROM meta WHERE key = 'lastIndexedAt'").get()?.v || null;
|
|
161
269
|
return { total, lastIndexedAt: last };
|
|
162
270
|
}
|
|
271
|
+
|
|
272
|
+
async _flushFallback() {
|
|
273
|
+
if (typeof this._persistFallback === 'function') {
|
|
274
|
+
try {
|
|
275
|
+
await this._persistFallback();
|
|
276
|
+
} catch {}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Test-only helper to reset cached driver status between runs
|
|
282
|
+
export function __resetCodeIndexDriverStatusForTest() {
|
|
283
|
+
driverStatus.available = null;
|
|
284
|
+
driverStatus.error = null;
|
|
285
|
+
driverStatus.logged = false;
|
|
163
286
|
}
|
package/src/core/indexWatcher.js
CHANGED
|
@@ -34,6 +34,17 @@ export class IndexWatcher {
|
|
|
34
34
|
if (this.running) return;
|
|
35
35
|
this.running = true;
|
|
36
36
|
try {
|
|
37
|
+
// Skip watcher entirely when the native SQLite binding is unavailable
|
|
38
|
+
const { CodeIndex } = await import('./codeIndex.js');
|
|
39
|
+
const probe = new CodeIndex(this.unityConnection);
|
|
40
|
+
const driverOk = await probe._ensureDriver();
|
|
41
|
+
if (!driverOk || probe.disabled) {
|
|
42
|
+
const reason = probe.disableReason || 'SQLite native binding not available';
|
|
43
|
+
logger.warn(`[index] watcher: code index disabled (${reason}); stopping watcher`);
|
|
44
|
+
this.stop();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
// Check if code index DB file exists (before opening DB)
|
|
38
49
|
const { ProjectInfoProvider } = await import('./projectInfo.js');
|
|
39
50
|
const projectInfo = new ProjectInfoProvider(this.unityConnection);
|
package/src/core/server.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
-
import {
|
|
4
|
-
ListToolsRequestSchema,
|
|
5
|
-
CallToolRequestSchema,
|
|
6
|
-
ListResourcesRequestSchema,
|
|
7
|
-
ListPromptsRequestSchema
|
|
8
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
9
4
|
// Note: filename is lowercase on disk; use exact casing for POSIX filesystems
|
|
10
5
|
import { UnityConnection } from './unityConnection.js';
|
|
11
6
|
import { createHandlers } from '../handlers/index.js';
|
|
@@ -29,9 +24,7 @@ const server = new Server(
|
|
|
29
24
|
capabilities: {
|
|
30
25
|
// Explicitly advertise tool support; some MCP clients expect a non-empty object
|
|
31
26
|
// Setting listChanged enables future push updates if we emit notifications
|
|
32
|
-
tools: { listChanged: true }
|
|
33
|
-
resources: {},
|
|
34
|
-
prompts: {}
|
|
27
|
+
tools: { listChanged: true }
|
|
35
28
|
}
|
|
36
29
|
}
|
|
37
30
|
);
|
|
@@ -63,20 +56,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
63
56
|
return { tools };
|
|
64
57
|
});
|
|
65
58
|
|
|
66
|
-
// Handle resources listing
|
|
67
|
-
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
68
|
-
logger.debug('[MCP] Received resources/list request');
|
|
69
|
-
// Unity MCP server doesn't provide resources
|
|
70
|
-
return { resources: [] };
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Handle prompts listing
|
|
74
|
-
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
75
|
-
logger.debug('[MCP] Received prompts/list request');
|
|
76
|
-
// Unity MCP server doesn't provide prompts
|
|
77
|
-
return { prompts: [] };
|
|
78
|
-
});
|
|
79
|
-
|
|
80
59
|
// Handle tool execution
|
|
81
60
|
server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
82
61
|
const { name, arguments: args } = request.params;
|
|
@@ -240,6 +219,12 @@ export async function startServer() {
|
|
|
240
219
|
const ready = await index.isReady();
|
|
241
220
|
|
|
242
221
|
if (!ready) {
|
|
222
|
+
if (index.disabled) {
|
|
223
|
+
logger.warn(
|
|
224
|
+
`[startup] Code index disabled: ${index.disableReason || 'SQLite native binding missing'}. Skipping auto-build.`
|
|
225
|
+
);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
243
228
|
logger.info('[startup] Code index DB not ready. Starting auto-build...');
|
|
244
229
|
const { CodeIndexBuildToolHandler } = await import(
|
|
245
230
|
'../handlers/script/CodeIndexBuildToolHandler.js'
|
|
@@ -298,9 +283,7 @@ export async function createServer(customConfig = config) {
|
|
|
298
283
|
},
|
|
299
284
|
{
|
|
300
285
|
capabilities: {
|
|
301
|
-
tools: { listChanged: true }
|
|
302
|
-
resources: {},
|
|
303
|
-
prompts: {}
|
|
286
|
+
tools: { listChanged: true }
|
|
304
287
|
}
|
|
305
288
|
}
|
|
306
289
|
);
|
|
@@ -311,14 +294,6 @@ export async function createServer(customConfig = config) {
|
|
|
311
294
|
return { tools };
|
|
312
295
|
});
|
|
313
296
|
|
|
314
|
-
testServer.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
315
|
-
return { resources: [] };
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
testServer.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
319
|
-
return { prompts: [] };
|
|
320
|
-
});
|
|
321
|
-
|
|
322
297
|
testServer.setRequestHandler(CallToolRequestSchema, async request => {
|
|
323
298
|
const { name, arguments: args } = request.params;
|
|
324
299
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import initSqlJs from 'sql.js';
|
|
4
|
+
|
|
5
|
+
// Create a lightweight better-sqlite3 compatible surface using sql.js (WASM)
|
|
6
|
+
export async function createSqliteFallback(dbPath) {
|
|
7
|
+
const wasmPath = path.resolve('node_modules/sql.js/dist/sql-wasm.wasm');
|
|
8
|
+
const SQL = await initSqlJs({ locateFile: () => wasmPath });
|
|
9
|
+
|
|
10
|
+
const loadDb = () => {
|
|
11
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
12
|
+
if (fs.existsSync(dbPath)) {
|
|
13
|
+
const data = fs.readFileSync(dbPath);
|
|
14
|
+
return new SQL.Database(new Uint8Array(data));
|
|
15
|
+
}
|
|
16
|
+
return new SQL.Database();
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const db = loadDb();
|
|
20
|
+
|
|
21
|
+
const persist = () => {
|
|
22
|
+
const data = db.export();
|
|
23
|
+
fs.writeFileSync(dbPath, Buffer.from(data));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Wrap sql.js Statement to look like better-sqlite3's
|
|
27
|
+
const wrapStatement = stmt => ({
|
|
28
|
+
run(...params) {
|
|
29
|
+
stmt.bind(params);
|
|
30
|
+
// sql.js run via stepping through the statement
|
|
31
|
+
while (stmt.step()) {
|
|
32
|
+
/* consume rows for statements that return data */
|
|
33
|
+
}
|
|
34
|
+
stmt.reset();
|
|
35
|
+
persist();
|
|
36
|
+
return this;
|
|
37
|
+
},
|
|
38
|
+
get(...params) {
|
|
39
|
+
stmt.bind(params);
|
|
40
|
+
const has = stmt.step();
|
|
41
|
+
const row = has ? stmt.getAsObject() : undefined;
|
|
42
|
+
stmt.reset();
|
|
43
|
+
return row;
|
|
44
|
+
},
|
|
45
|
+
all(...params) {
|
|
46
|
+
stmt.bind(params);
|
|
47
|
+
const rows = [];
|
|
48
|
+
while (stmt.step()) rows.push(stmt.getAsObject());
|
|
49
|
+
stmt.reset();
|
|
50
|
+
return rows;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const prepare = sql => wrapStatement(db.prepare(sql));
|
|
55
|
+
|
|
56
|
+
// Mimic better-sqlite3 transaction(fn)
|
|
57
|
+
const transaction =
|
|
58
|
+
fn =>
|
|
59
|
+
(...args) => {
|
|
60
|
+
const result = fn(...args);
|
|
61
|
+
persist();
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Minimal surface used by CodeIndex
|
|
66
|
+
return {
|
|
67
|
+
exec: sql => {
|
|
68
|
+
db.exec(sql);
|
|
69
|
+
persist();
|
|
70
|
+
},
|
|
71
|
+
prepare,
|
|
72
|
+
transaction,
|
|
73
|
+
persist
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -1,35 +1,38 @@
|
|
|
1
1
|
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
analyzeSceneContentsToolDefinition,
|
|
4
|
+
analyzeSceneContentsHandler
|
|
5
|
+
} from '../../tools/analysis/analyzeSceneContents.js';
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Handler for analyze_scene_contents tool
|
|
6
9
|
*/
|
|
7
10
|
export class AnalyzeSceneContentsToolHandler extends BaseToolHandler {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
constructor(unityConnection) {
|
|
12
|
+
super(
|
|
13
|
+
analyzeSceneContentsToolDefinition.name,
|
|
14
|
+
analyzeSceneContentsToolDefinition.description,
|
|
15
|
+
analyzeSceneContentsToolDefinition.inputSchema
|
|
16
|
+
);
|
|
17
|
+
this.unityConnection = unityConnection;
|
|
18
|
+
this.handler = analyzeSceneContentsHandler;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async execute(args) {
|
|
22
|
+
// Check connection
|
|
23
|
+
if (!this.unityConnection.isConnected()) {
|
|
24
|
+
throw new Error('Unity connection not available');
|
|
16
25
|
}
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (!this.unityConnection.isConnected()) {
|
|
21
|
-
throw new Error('Unity connection not available');
|
|
22
|
-
}
|
|
27
|
+
// Use the handler function
|
|
28
|
+
const result = await this.handler(this.unityConnection, args);
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// If the handler returns an error response, throw it
|
|
28
|
-
if (result.isError) {
|
|
29
|
-
throw new Error(result.content[0].text);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Return the content
|
|
33
|
-
return result;
|
|
30
|
+
// If the handler returns an error response, throw it
|
|
31
|
+
if (result.isError) {
|
|
32
|
+
throw new Error(result.content[0].text);
|
|
34
33
|
}
|
|
34
|
+
|
|
35
|
+
// Return the content
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
35
38
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
findByComponentToolDefinition,
|
|
4
|
+
findByComponentHandler
|
|
5
|
+
} from '../../tools/analysis/findByComponent.js';
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Handler for the analysis_component_find tool
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import {
|
|
3
|
+
getAnimatorStateToolDefinition,
|
|
4
|
+
getAnimatorRuntimeInfoToolDefinition,
|
|
5
|
+
getAnimatorStateHandler,
|
|
6
|
+
getAnimatorRuntimeInfoHandler
|
|
7
7
|
} from '../../tools/analysis/getAnimatorState.js';
|
|
8
8
|
|
|
9
9
|
export class GetAnimatorStateToolHandler extends BaseToolHandler {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getComponentValuesToolDefinition,
|
|
4
|
+
getComponentValuesHandler
|
|
5
|
+
} from '../../tools/analysis/getComponentValues.js';
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Handler for the analysis_component_values_get tool
|
|
@@ -1,35 +1,38 @@
|
|
|
1
1
|
import { BaseToolHandler } from '../base/BaseToolHandler.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getGameObjectDetailsToolDefinition,
|
|
4
|
+
getGameObjectDetailsHandler
|
|
5
|
+
} from '../../tools/analysis/getGameObjectDetails.js';
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Handler for get_gameobject_details tool
|
|
6
9
|
*/
|
|
7
10
|
export class GetGameObjectDetailsToolHandler extends BaseToolHandler {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
constructor(unityConnection) {
|
|
12
|
+
super(
|
|
13
|
+
getGameObjectDetailsToolDefinition.name,
|
|
14
|
+
getGameObjectDetailsToolDefinition.description,
|
|
15
|
+
getGameObjectDetailsToolDefinition.inputSchema
|
|
16
|
+
);
|
|
17
|
+
this.unityConnection = unityConnection;
|
|
18
|
+
this.handler = getGameObjectDetailsHandler;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async execute(args) {
|
|
22
|
+
// Check connection
|
|
23
|
+
if (!this.unityConnection.isConnected()) {
|
|
24
|
+
throw new Error('Unity connection not available');
|
|
16
25
|
}
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (!this.unityConnection.isConnected()) {
|
|
21
|
-
throw new Error('Unity connection not available');
|
|
22
|
-
}
|
|
27
|
+
// Use the handler function
|
|
28
|
+
const result = await this.handler(this.unityConnection, args);
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// If the handler returns an error response, throw it
|
|
28
|
-
if (result.isError) {
|
|
29
|
-
throw new Error(result.content[0].text);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Return the content
|
|
33
|
-
return result;
|
|
30
|
+
// If the handler returns an error response, throw it
|
|
31
|
+
if (result.isError) {
|
|
32
|
+
throw new Error(result.content[0].text);
|
|
34
33
|
}
|
|
34
|
+
|
|
35
|
+
// Return the content
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
35
38
|
}
|