@akiojin/unity-mcp-server 5.2.1 → 5.3.1
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 +1 -0
- package/package.json +28 -40
- package/src/core/codeIndex.js +54 -7
- package/src/core/config.js +15 -1
- package/src/core/httpServer.js +30 -6
- package/src/core/indexBuildWorkerPool.js +57 -3
- package/src/core/indexWatcher.js +10 -4
- package/src/core/projectInfo.js +34 -12
- package/src/core/server.js +58 -27
- package/src/core/toolManifest.json +145 -629
- package/src/handlers/addressables/AddressablesAnalyzeToolHandler.js +14 -6
- package/src/handlers/addressables/AddressablesBuildToolHandler.js +6 -3
- package/src/handlers/addressables/AddressablesManageToolHandler.js +6 -3
- package/src/handlers/input/InputSystemControlToolHandler.js +1 -1
- package/src/handlers/input/InputTouchToolHandler.js +7 -3
- package/src/handlers/package/PackageManagerToolHandler.js +6 -3
- package/src/handlers/script/CodeIndexStatusToolHandler.js +37 -1
- package/src/handlers/script/CodeIndexUpdateToolHandler.js +1 -1
- package/src/handlers/script/ScriptEditSnippetToolHandler.js +1 -2
- package/src/handlers/script/ScriptEditStructuredToolHandler.js +6 -1
- package/src/handlers/script/ScriptRefactorRenameToolHandler.js +3 -1
- package/src/handlers/script/ScriptRefsFindToolHandler.js +22 -4
- package/src/handlers/script/ScriptRemoveSymbolToolHandler.js +6 -1
- package/src/handlers/script/ScriptSymbolsGetToolHandler.js +1 -1
- package/src/lsp/CSharpLspUtils.js +11 -3
- package/src/lsp/LspProcessManager.js +3 -2
- package/src/lsp/LspRpcClient.js +115 -23
- package/src/lsp/LspRpcClientSingleton.js +79 -2
package/README.md
CHANGED
|
@@ -63,6 +63,7 @@ Use the `search_tools` meta-tool to find the right tool quickly.
|
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
More details:
|
|
66
|
+
|
|
66
67
|
- Tools & Code Index workflow: [`docs/tools.md`](https://github.com/akiojin/unity-mcp-server/blob/main/docs/tools.md)
|
|
67
68
|
- Configuration reference: [`docs/configuration.md`](https://github.com/akiojin/unity-mcp-server/blob/main/docs/configuration.md)
|
|
68
69
|
|
package/package.json
CHANGED
|
@@ -1,38 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akiojin/unity-mcp-server",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.1",
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
8
|
"unity-mcp-server": "./bin/unity-mcp-server.js"
|
|
9
9
|
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"start": "node src/core/server.js",
|
|
12
|
-
"dev": "node --watch src/core/server.js",
|
|
13
|
-
"build:index": "node src/tools/buildCodeIndex.js",
|
|
14
|
-
"test": "node --test tests/unit/**/*.test.js tests/integration/*.test.js",
|
|
15
|
-
"test:unit": "NODE_ENV=test node --test tests/unit/**/*.test.js",
|
|
16
|
-
"test:integration": "NODE_ENV=test node scripts/run-non-unity-tests.mjs",
|
|
17
|
-
"test:e2e": "NODE_ENV=test node --test tests/e2e/*.test.js",
|
|
18
|
-
"test:coverage": "c8 --reporter=lcov --reporter=text --reporter=html node --test tests/unit/**/*.test.js tests/integration/*.test.js",
|
|
19
|
-
"test:coverage:full": "c8 --reporter=lcov --reporter=text --reporter=html node --test tests/**/*.test.js",
|
|
20
|
-
"test:watch": "node --watch --test tests/unit/**/*.test.js",
|
|
21
|
-
"test:watch:all": "node --watch --test tests/**/*.test.js",
|
|
22
|
-
"test:performance": "node --test tests/performance/*.test.js",
|
|
23
|
-
"test:ci": "CI=true NODE_ENV=test node --test tests/unit/core/codeIndex.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 tests/unit/core/startupPerformance.test.js tests/unit/handlers/script/CodeIndexStatusToolHandler.test.js",
|
|
24
|
-
"test:ci:coverage": "c8 --reporter=lcov --reporter=text node --test tests/unit/core/codeIndex.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 tests/unit/handlers/script/CodeIndexStatusToolHandler.test.js",
|
|
25
|
-
"test:ci:all": "c8 --reporter=lcov node --test tests/unit/**/*.test.js",
|
|
26
|
-
"simulate:code-index": "node scripts/simulate-code-index-status.mjs",
|
|
27
|
-
"test:verbose": "VERBOSE_TEST=true node --test tests/**/*.test.js",
|
|
28
|
-
"prepare": "cd .. && husky || true",
|
|
29
|
-
"prepublishOnly": "npm run test:ci",
|
|
30
|
-
"postinstall": "node scripts/ensure-better-sqlite3.mjs",
|
|
31
|
-
"test:ci:unity": "timeout 60 node --test tests/unit/core/codeIndex.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 tests/unit/core/startupPerformance.test.js || exit 0",
|
|
32
|
-
"test:unity": "node tests/run-unity-integration.mjs",
|
|
33
|
-
"test:nounity": "npm run test:integration",
|
|
34
|
-
"test:ci:integration": "CI=true NODE_ENV=test node --test tests/integration/code-index-background.test.js"
|
|
35
|
-
},
|
|
36
10
|
"keywords": [
|
|
37
11
|
"mcp",
|
|
38
12
|
"unity",
|
|
@@ -48,7 +22,7 @@
|
|
|
48
22
|
"author": "Akio Jinsenji <akio-jinsenji@cloud-creative-studios.com>",
|
|
49
23
|
"license": "MIT",
|
|
50
24
|
"dependencies": {
|
|
51
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
52
26
|
"find-up": "^6.3.0",
|
|
53
27
|
"@akiojin/fast-sql": "^0.1.0",
|
|
54
28
|
"lru-cache": "^11.0.2"
|
|
@@ -78,17 +52,31 @@
|
|
|
78
52
|
"access": "public"
|
|
79
53
|
},
|
|
80
54
|
"devDependencies": {
|
|
81
|
-
"@commitlint/cli": "^18.6.1",
|
|
82
|
-
"@commitlint/config-conventional": "^18.6.3",
|
|
83
55
|
"c8": "^10.1.3",
|
|
84
|
-
"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
56
|
+
"nodemon": "^3.1.7"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"start": "node src/core/server.js",
|
|
60
|
+
"dev": "node --watch src/core/server.js",
|
|
61
|
+
"build:index": "node src/tools/buildCodeIndex.js",
|
|
62
|
+
"test": "node --test tests/unit/**/*.test.js tests/integration/*.test.js",
|
|
63
|
+
"test:unit": "NODE_ENV=test node --test tests/unit/**/*.test.js",
|
|
64
|
+
"test:integration": "NODE_ENV=test node scripts/run-non-unity-tests.mjs",
|
|
65
|
+
"test:e2e": "NODE_ENV=test node --test tests/e2e/*.test.js",
|
|
66
|
+
"test:coverage": "c8 --reporter=lcov --reporter=text --reporter=html node --test tests/unit/**/*.test.js tests/integration/*.test.js",
|
|
67
|
+
"test:coverage:full": "c8 --reporter=lcov --reporter=text --reporter=html node --test tests/**/*.test.js",
|
|
68
|
+
"test:watch": "node --watch --test tests/unit/**/*.test.js",
|
|
69
|
+
"test:watch:all": "node --watch --test tests/**/*.test.js",
|
|
70
|
+
"test:performance": "node --test tests/performance/*.test.js",
|
|
71
|
+
"test:ci": "CI=true NODE_ENV=test node --test tests/unit/core/codeIndex.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 tests/unit/core/startupPerformance.test.js tests/unit/handlers/script/CodeIndexStatusToolHandler.test.js tests/unit/handlers/input/InputTouchToolHandler.test.js tests/unit/handlers/addressables/AddressablesAnalyzeToolHandler.test.js tests/unit/handlers/addressables/AddressablesBuildToolHandler.test.js tests/unit/handlers/addressables/AddressablesManageToolHandler.test.js",
|
|
72
|
+
"test:ci:coverage": "c8 --reporter=lcov --reporter=text node --test tests/unit/core/codeIndex.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 tests/unit/handlers/script/CodeIndexStatusToolHandler.test.js",
|
|
73
|
+
"test:ci:all": "c8 --reporter=lcov node --test tests/unit/**/*.test.js",
|
|
74
|
+
"simulate:code-index": "node scripts/simulate-code-index-status.mjs",
|
|
75
|
+
"test:verbose": "VERBOSE_TEST=true node --test tests/**/*.test.js",
|
|
76
|
+
"postinstall": "node scripts/ensure-better-sqlite3.mjs",
|
|
77
|
+
"test:ci:unity": "timeout 60 node --test tests/unit/core/codeIndex.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 tests/unit/core/startupPerformance.test.js || exit 0",
|
|
78
|
+
"test:unity": "node tests/run-unity-integration.mjs",
|
|
79
|
+
"test:nounity": "npm run test:integration",
|
|
80
|
+
"test:ci:integration": "CI=true NODE_ENV=test node --test tests/integration/code-index-background.test.js"
|
|
93
81
|
}
|
|
94
|
-
}
|
|
82
|
+
}
|
package/src/core/codeIndex.js
CHANGED
|
@@ -32,6 +32,7 @@ const driverStatus = {
|
|
|
32
32
|
const sharedConnections = {
|
|
33
33
|
db: null,
|
|
34
34
|
dbPath: null,
|
|
35
|
+
dbStat: null,
|
|
35
36
|
schemaInitialized: false,
|
|
36
37
|
SQL: null // sql.js factory
|
|
37
38
|
};
|
|
@@ -73,8 +74,25 @@ export class CodeIndex {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
invalidateSharedConnection(reason) {
|
|
78
|
+
if (reason) logger.info(`[index] ${reason}`);
|
|
79
|
+
try {
|
|
80
|
+
if (sharedConnections.db) {
|
|
81
|
+
sharedConnections.db.close();
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// Ignore close errors
|
|
85
|
+
}
|
|
86
|
+
sharedConnections.db = null;
|
|
87
|
+
sharedConnections.dbPath = null;
|
|
88
|
+
sharedConnections.dbStat = null;
|
|
89
|
+
sharedConnections.schemaInitialized = false;
|
|
90
|
+
queryCache.clear();
|
|
91
|
+
statsCache.clear();
|
|
92
|
+
this.db = null;
|
|
93
|
+
}
|
|
94
|
+
|
|
76
95
|
async open() {
|
|
77
|
-
if (this.db) return this.db;
|
|
78
96
|
const ok = await this._ensureDriver();
|
|
79
97
|
if (!ok) return null;
|
|
80
98
|
const info = await this.projectInfo.get();
|
|
@@ -83,15 +101,32 @@ export class CodeIndex {
|
|
|
83
101
|
const dbPath = path.join(dir, 'code-index.db');
|
|
84
102
|
this.dbPath = dbPath;
|
|
85
103
|
|
|
104
|
+
const fileStat = (() => {
|
|
105
|
+
try {
|
|
106
|
+
if (!fs.existsSync(dbPath)) return null;
|
|
107
|
+
const stat = fs.statSync(dbPath);
|
|
108
|
+
return { mtimeMs: stat.mtimeMs, size: stat.size };
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
})();
|
|
113
|
+
|
|
114
|
+
if (this.db && (sharedConnections.db !== this.db || sharedConnections.dbPath !== dbPath)) {
|
|
115
|
+
this.db = null;
|
|
116
|
+
}
|
|
117
|
+
|
|
86
118
|
// Use shared connection for all CodeIndex instances
|
|
87
119
|
if (sharedConnections.db && sharedConnections.dbPath === dbPath) {
|
|
88
120
|
// Verify the DB file still exists before returning cached connection
|
|
89
|
-
if (!
|
|
121
|
+
if (!fileStat) {
|
|
90
122
|
// File was deleted or never created, invalidate cache
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
sharedConnections.
|
|
94
|
-
sharedConnections.
|
|
123
|
+
this.invalidateSharedConnection('DB file missing, invalidating cached connection');
|
|
124
|
+
} else if (
|
|
125
|
+
sharedConnections.dbStat &&
|
|
126
|
+
(fileStat.mtimeMs !== sharedConnections.dbStat.mtimeMs ||
|
|
127
|
+
fileStat.size !== sharedConnections.dbStat.size)
|
|
128
|
+
) {
|
|
129
|
+
this.invalidateSharedConnection('DB file changed on disk, reloading connection');
|
|
95
130
|
} else {
|
|
96
131
|
this.db = sharedConnections.db;
|
|
97
132
|
return this.db;
|
|
@@ -108,6 +143,7 @@ export class CodeIndex {
|
|
|
108
143
|
}
|
|
109
144
|
sharedConnections.db = this.db;
|
|
110
145
|
sharedConnections.dbPath = dbPath;
|
|
146
|
+
sharedConnections.dbStat = fileStat;
|
|
111
147
|
} catch (e) {
|
|
112
148
|
this.disabled = true;
|
|
113
149
|
const errMsg = e && typeof e === 'object' && 'message' in e ? e.message : String(e);
|
|
@@ -131,6 +167,14 @@ export class CodeIndex {
|
|
|
131
167
|
const data = this.db.exportDb();
|
|
132
168
|
const buffer = Buffer.from(data);
|
|
133
169
|
fs.writeFileSync(this.dbPath, buffer);
|
|
170
|
+
if (sharedConnections.dbPath === this.dbPath) {
|
|
171
|
+
try {
|
|
172
|
+
const stat = fs.statSync(this.dbPath);
|
|
173
|
+
sharedConnections.dbStat = { mtimeMs: stat.mtimeMs, size: stat.size };
|
|
174
|
+
} catch {
|
|
175
|
+
sharedConnections.dbStat = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
134
178
|
} catch (e) {
|
|
135
179
|
const errMsg = e && typeof e === 'object' && 'message' in e ? e.message : String(e);
|
|
136
180
|
logger.warn(`[index] Failed to save database: ${errMsg}`);
|
|
@@ -417,14 +461,16 @@ export class CodeIndex {
|
|
|
417
461
|
* Close the database connection
|
|
418
462
|
*/
|
|
419
463
|
close() {
|
|
464
|
+
const wasShared = sharedConnections.db === this.db;
|
|
420
465
|
if (this.db) {
|
|
421
466
|
this._saveToFile();
|
|
422
467
|
this.db.close();
|
|
423
468
|
this.db = null;
|
|
424
469
|
}
|
|
425
|
-
if (
|
|
470
|
+
if (wasShared) {
|
|
426
471
|
sharedConnections.db = null;
|
|
427
472
|
sharedConnections.dbPath = null;
|
|
473
|
+
sharedConnections.dbStat = null;
|
|
428
474
|
}
|
|
429
475
|
}
|
|
430
476
|
}
|
|
@@ -449,4 +495,5 @@ export function __resetCodeIndexDriverStatusForTest() {
|
|
|
449
495
|
sharedConnections.dbPath = null;
|
|
450
496
|
sharedConnections.schemaInitialized = false;
|
|
451
497
|
sharedConnections.SQL = null;
|
|
498
|
+
sharedConnections.dbStat = null;
|
|
452
499
|
}
|
package/src/core/config.js
CHANGED
|
@@ -153,7 +153,8 @@ const baseConfig = {
|
|
|
153
153
|
|
|
154
154
|
// LSP client defaults
|
|
155
155
|
lsp: {
|
|
156
|
-
requestTimeoutMs: 120000
|
|
156
|
+
requestTimeoutMs: 120000,
|
|
157
|
+
slowRequestWarnMs: 2000
|
|
157
158
|
},
|
|
158
159
|
|
|
159
160
|
// Indexing (code index) settings
|
|
@@ -184,6 +185,7 @@ function loadEnvConfig() {
|
|
|
184
185
|
|
|
185
186
|
const telemetryEnabled = parseBoolEnv(process.env.UNITY_MCP_TELEMETRY_ENABLED);
|
|
186
187
|
const lspRequestTimeoutMs = parseIntEnv(process.env.UNITY_MCP_LSP_REQUEST_TIMEOUT_MS);
|
|
188
|
+
const lspSlowRequestWarnMs = parseIntEnv(process.env.UNITY_MCP_LSP_SLOW_REQUEST_WARN_MS);
|
|
187
189
|
|
|
188
190
|
const out = {};
|
|
189
191
|
|
|
@@ -221,6 +223,9 @@ function loadEnvConfig() {
|
|
|
221
223
|
if (lspRequestTimeoutMs !== undefined) {
|
|
222
224
|
out.lsp = { requestTimeoutMs: lspRequestTimeoutMs };
|
|
223
225
|
}
|
|
226
|
+
if (lspSlowRequestWarnMs !== undefined) {
|
|
227
|
+
out.lsp = { ...(out.lsp || {}), slowRequestWarnMs: lspSlowRequestWarnMs };
|
|
228
|
+
}
|
|
224
229
|
|
|
225
230
|
return out;
|
|
226
231
|
}
|
|
@@ -301,6 +306,15 @@ function validateAndNormalizeConfig(cfg) {
|
|
|
301
306
|
cfg.lsp.requestTimeoutMs = 60000;
|
|
302
307
|
}
|
|
303
308
|
}
|
|
309
|
+
if (cfg.lsp?.slowRequestWarnMs !== undefined) {
|
|
310
|
+
const t = Number(cfg.lsp.slowRequestWarnMs);
|
|
311
|
+
if (!Number.isFinite(t) || t < 0) {
|
|
312
|
+
logger.warning(
|
|
313
|
+
`[unity-mcp-server] WARN: Invalid UNITY_MCP_LSP_SLOW_REQUEST_WARN_MS (${cfg.lsp.slowRequestWarnMs}); using default 2000`
|
|
314
|
+
);
|
|
315
|
+
cfg.lsp.slowRequestWarnMs = 2000;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
304
318
|
}
|
|
305
319
|
|
|
306
320
|
export const config = merge(baseConfig, loadEnvConfig());
|
package/src/core/httpServer.js
CHANGED
|
@@ -59,7 +59,9 @@ export function createHttpServer({
|
|
|
59
59
|
payload = JSON.parse(raw || '{}');
|
|
60
60
|
} catch (e) {
|
|
61
61
|
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
62
|
-
res.end(
|
|
62
|
+
res.end(
|
|
63
|
+
JSON.stringify({ jsonrpc: '2.0', error: { code: -32700, message: 'Invalid JSON' } })
|
|
64
|
+
);
|
|
63
65
|
return;
|
|
64
66
|
}
|
|
65
67
|
|
|
@@ -77,7 +79,13 @@ export function createHttpServer({
|
|
|
77
79
|
const handler = handlers.get(name);
|
|
78
80
|
if (!handler) {
|
|
79
81
|
res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
80
|
-
res.end(
|
|
82
|
+
res.end(
|
|
83
|
+
JSON.stringify({
|
|
84
|
+
jsonrpc: '2.0',
|
|
85
|
+
id,
|
|
86
|
+
error: { code: -32004, message: `Tool not found: ${name}` }
|
|
87
|
+
})
|
|
88
|
+
);
|
|
81
89
|
return;
|
|
82
90
|
}
|
|
83
91
|
try {
|
|
@@ -87,13 +95,21 @@ export function createHttpServer({
|
|
|
87
95
|
} catch (e) {
|
|
88
96
|
logger.error(`[http] tool error ${name}: ${e.message}`);
|
|
89
97
|
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
90
|
-
res.end(
|
|
98
|
+
res.end(
|
|
99
|
+
JSON.stringify({ jsonrpc: '2.0', id, error: { code: -32000, message: e.message } })
|
|
100
|
+
);
|
|
91
101
|
}
|
|
92
102
|
return;
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
96
|
-
res.end(
|
|
106
|
+
res.end(
|
|
107
|
+
JSON.stringify({
|
|
108
|
+
jsonrpc: '2.0',
|
|
109
|
+
id,
|
|
110
|
+
error: { code: -32601, message: 'Method not found' }
|
|
111
|
+
})
|
|
112
|
+
);
|
|
97
113
|
return;
|
|
98
114
|
}
|
|
99
115
|
|
|
@@ -125,7 +141,9 @@ export function createHttpServer({
|
|
|
125
141
|
server.listen(port, host, () => {
|
|
126
142
|
server.off('error', onError);
|
|
127
143
|
const address = server.address();
|
|
128
|
-
logger.info(
|
|
144
|
+
logger.info(
|
|
145
|
+
`HTTP listening on http://${host}:${address.port}, telemetry: ${telemetryEnabled ? 'on' : 'off'}`
|
|
146
|
+
);
|
|
129
147
|
resolve(address.port);
|
|
130
148
|
});
|
|
131
149
|
});
|
|
@@ -142,6 +160,12 @@ export function createHttpServer({
|
|
|
142
160
|
start,
|
|
143
161
|
close,
|
|
144
162
|
getPort: () => server.address()?.port,
|
|
145
|
-
health: () =>
|
|
163
|
+
health: () =>
|
|
164
|
+
buildHealthResponse({
|
|
165
|
+
startedAt,
|
|
166
|
+
mode: 'http',
|
|
167
|
+
port: server.address()?.port,
|
|
168
|
+
telemetryEnabled
|
|
169
|
+
})
|
|
146
170
|
};
|
|
147
171
|
}
|
|
@@ -1,11 +1,58 @@
|
|
|
1
1
|
import { Worker } from 'worker_threads';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import crypto from 'crypto';
|
|
3
6
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { logger } from './config.js';
|
|
7
|
+
import { logger, WORKSPACE_ROOT } from './config.js';
|
|
5
8
|
import { JobManager } from './jobManager.js';
|
|
6
9
|
|
|
7
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
11
|
const __dirname = path.dirname(__filename);
|
|
12
|
+
const DEFAULT_DB_RELATIVE_PATH = path.join('.unity', 'cache', 'code-index', 'code-index.db');
|
|
13
|
+
const DEFAULT_TMP_DB_DIR = path.join(os.tmpdir(), 'unity-mcp-code-index');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve database path for Worker Thread builds.
|
|
17
|
+
* Falls back to workspace-root based cache when dbPath is not provided.
|
|
18
|
+
* @param {Object} [options] - Build options
|
|
19
|
+
* @param {string} [options.dbPath] - Explicit database path
|
|
20
|
+
* @param {string} [options.projectRoot] - Unity project root path
|
|
21
|
+
* @returns {string} Resolved database path
|
|
22
|
+
*/
|
|
23
|
+
export function resolveDbPath(options = {}) {
|
|
24
|
+
if (typeof options.dbPath === 'string' && options.dbPath.trim().length > 0) {
|
|
25
|
+
return options.dbPath;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const workspaceRoot =
|
|
29
|
+
typeof WORKSPACE_ROOT === 'string' && WORKSPACE_ROOT.trim().length > 0 ? WORKSPACE_ROOT : '';
|
|
30
|
+
if (workspaceRoot) {
|
|
31
|
+
return path.resolve(workspaceRoot, DEFAULT_DB_RELATIVE_PATH);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const projectRoot =
|
|
35
|
+
typeof options.projectRoot === 'string' && options.projectRoot.trim().length > 0
|
|
36
|
+
? options.projectRoot
|
|
37
|
+
: '';
|
|
38
|
+
const projectExists = projectRoot && fs.existsSync(projectRoot);
|
|
39
|
+
|
|
40
|
+
if (projectExists) {
|
|
41
|
+
return path.join(projectRoot, DEFAULT_DB_RELATIVE_PATH);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (projectRoot) {
|
|
45
|
+
// Fallback for mock/non-existent roots used in integration tests.
|
|
46
|
+
const hash = crypto
|
|
47
|
+
.createHash('sha1')
|
|
48
|
+
.update(projectRoot || 'unknown-project-root', 'utf8')
|
|
49
|
+
.digest('hex')
|
|
50
|
+
.slice(0, 12);
|
|
51
|
+
return path.join(DEFAULT_TMP_DB_DIR, hash, 'code-index.db');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return path.resolve(process.cwd(), DEFAULT_DB_RELATIVE_PATH);
|
|
55
|
+
}
|
|
9
56
|
|
|
10
57
|
/**
|
|
11
58
|
* Worker Thread pool for non-blocking code index builds.
|
|
@@ -52,12 +99,19 @@ export class IndexBuildWorkerPool {
|
|
|
52
99
|
|
|
53
100
|
return new Promise((resolve, reject) => {
|
|
54
101
|
const workerPath = path.join(__dirname, 'workers', 'indexBuildWorker.js');
|
|
102
|
+
const buildOptions = options ?? {};
|
|
103
|
+
const resolvedDbPath = resolveDbPath(buildOptions);
|
|
104
|
+
const concurrency =
|
|
105
|
+
Number.isFinite(buildOptions.concurrency) && buildOptions.concurrency > 0
|
|
106
|
+
? buildOptions.concurrency
|
|
107
|
+
: 1;
|
|
55
108
|
|
|
56
109
|
try {
|
|
57
110
|
this.worker = new Worker(workerPath, {
|
|
58
111
|
workerData: {
|
|
59
|
-
...
|
|
60
|
-
|
|
112
|
+
...buildOptions,
|
|
113
|
+
dbPath: resolvedDbPath,
|
|
114
|
+
concurrency
|
|
61
115
|
}
|
|
62
116
|
});
|
|
63
117
|
|
package/src/core/indexWatcher.js
CHANGED
|
@@ -6,6 +6,7 @@ export class IndexWatcher {
|
|
|
6
6
|
constructor(unityConnection) {
|
|
7
7
|
this.unityConnection = unityConnection;
|
|
8
8
|
this.timer = null;
|
|
9
|
+
this.startTimeout = null;
|
|
9
10
|
this.running = false;
|
|
10
11
|
this.jobManager = JobManager.getInstance();
|
|
11
12
|
this.currentWatcherJobId = null;
|
|
@@ -16,7 +17,7 @@ export class IndexWatcher {
|
|
|
16
17
|
|
|
17
18
|
start() {
|
|
18
19
|
if (!config.indexing?.watch) return;
|
|
19
|
-
if (this.timer) return;
|
|
20
|
+
if (this.timer || this.startTimeout) return;
|
|
20
21
|
const interval = Math.max(2000, Number(config.indexing.intervalMs || 15000));
|
|
21
22
|
// Initial delay: wait longer to allow MCP server to fully initialize
|
|
22
23
|
// and first tool calls to complete before starting background indexing
|
|
@@ -24,7 +25,8 @@ export class IndexWatcher {
|
|
|
24
25
|
logger.info(`[index] watcher enabled (interval=${interval}ms, initialDelay=${initialDelay}ms)`);
|
|
25
26
|
|
|
26
27
|
// Delay initial tick significantly to avoid blocking MCP server initialization
|
|
27
|
-
|
|
28
|
+
this.startTimeout = setTimeout(() => {
|
|
29
|
+
this.startTimeout = null;
|
|
28
30
|
this.tick();
|
|
29
31
|
// Start periodic timer only after first tick
|
|
30
32
|
this.timer = setInterval(() => this.tick(), interval);
|
|
@@ -32,12 +34,16 @@ export class IndexWatcher {
|
|
|
32
34
|
this.timer.unref();
|
|
33
35
|
}
|
|
34
36
|
}, initialDelay);
|
|
35
|
-
if (typeof
|
|
36
|
-
|
|
37
|
+
if (typeof this.startTimeout?.unref === 'function') {
|
|
38
|
+
this.startTimeout.unref();
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
stop() {
|
|
43
|
+
if (this.startTimeout) {
|
|
44
|
+
clearTimeout(this.startTimeout);
|
|
45
|
+
this.startTimeout = null;
|
|
46
|
+
}
|
|
41
47
|
if (this.timer) {
|
|
42
48
|
clearInterval(this.timer);
|
|
43
49
|
this.timer = null;
|
package/src/core/projectInfo.js
CHANGED
|
@@ -104,18 +104,40 @@ export class ProjectInfoProvider {
|
|
|
104
104
|
try {
|
|
105
105
|
const info = await this.unityConnection.sendCommand('get_editor_info', {});
|
|
106
106
|
if (info && info.projectRoot && info.assetsPath) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
107
|
+
const projectRoot = normalize(info.projectRoot);
|
|
108
|
+
const assetsPath = normalize(info.assetsPath || path.join(info.projectRoot, 'Assets'));
|
|
109
|
+
const packagesPath = normalize(
|
|
110
|
+
info.packagesPath || path.join(info.projectRoot, 'Packages')
|
|
111
|
+
);
|
|
112
|
+
let localReady = false;
|
|
113
|
+
try {
|
|
114
|
+
localReady =
|
|
115
|
+
fs.existsSync(assetsPath) &&
|
|
116
|
+
fs.existsSync(packagesPath) &&
|
|
117
|
+
fs.statSync(assetsPath).isDirectory() &&
|
|
118
|
+
fs.statSync(packagesPath).isDirectory();
|
|
119
|
+
} catch {
|
|
120
|
+
localReady = false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (localReady) {
|
|
124
|
+
this.cached = {
|
|
125
|
+
projectRoot,
|
|
126
|
+
assetsPath,
|
|
127
|
+
packagesPath,
|
|
128
|
+
packageCachePath: normalize(
|
|
129
|
+
info.packageCachePath || path.join(info.projectRoot, 'Library/PackageCache')
|
|
130
|
+
),
|
|
131
|
+
codeIndexRoot: normalize(
|
|
132
|
+
info.codeIndexRoot || resolveDefaultCodeIndexRoot(info.projectRoot)
|
|
133
|
+
)
|
|
134
|
+
};
|
|
135
|
+
return this.cached;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
logger.warning(
|
|
139
|
+
'get_editor_info returned paths not found locally; falling back to local inference'
|
|
140
|
+
);
|
|
119
141
|
}
|
|
120
142
|
} catch (e) {
|
|
121
143
|
logger.warning(`get_editor_info failed: ${e.message}`);
|