@akiojin/unity-mcp-server 2.39.2 → 2.40.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/package.json
CHANGED
package/src/core/config.js
CHANGED
|
@@ -97,7 +97,7 @@ const baseConfig = {
|
|
|
97
97
|
// Indexing (code index) settings
|
|
98
98
|
indexing: {
|
|
99
99
|
// Enable periodic incremental index updates (polling watcher)
|
|
100
|
-
watch:
|
|
100
|
+
watch: true,
|
|
101
101
|
// Polling interval (ms)
|
|
102
102
|
intervalMs: Number(process.env.INDEX_WATCH_INTERVAL_MS || 15000),
|
|
103
103
|
// Build options
|
package/src/core/indexWatcher.js
CHANGED
|
@@ -34,10 +34,44 @@ export class IndexWatcher {
|
|
|
34
34
|
if (this.running) return;
|
|
35
35
|
this.running = true;
|
|
36
36
|
try {
|
|
37
|
+
// Check if code index DB file exists (before opening DB)
|
|
38
|
+
const { ProjectInfoProvider } = await import('./projectInfo.js');
|
|
39
|
+
const projectInfo = new ProjectInfoProvider(this.unityConnection);
|
|
40
|
+
const info = await projectInfo.get();
|
|
41
|
+
const fs = await import('fs');
|
|
42
|
+
const path = await import('path');
|
|
43
|
+
const dbPath = path.default.join(info.codeIndexRoot, 'code-index.db');
|
|
44
|
+
const dbExists = fs.default.existsSync(dbPath);
|
|
45
|
+
|
|
46
|
+
if (!dbExists) {
|
|
47
|
+
logger.warn('[index] watcher: code index DB file not found, triggering full rebuild');
|
|
48
|
+
// Force full rebuild when DB file is missing
|
|
49
|
+
const jobId = `watcher-rebuild-${Date.now()}`;
|
|
50
|
+
this.currentWatcherJobId = jobId;
|
|
51
|
+
|
|
52
|
+
const { CodeIndexBuildToolHandler } = await import(
|
|
53
|
+
'../handlers/script/CodeIndexBuildToolHandler.js'
|
|
54
|
+
);
|
|
55
|
+
const handler = new CodeIndexBuildToolHandler(this.unityConnection);
|
|
56
|
+
|
|
57
|
+
this.jobManager.create(jobId, async job => {
|
|
58
|
+
const params = {
|
|
59
|
+
concurrency: config.indexing.concurrency || 8,
|
|
60
|
+
retry: config.indexing.retry || 2,
|
|
61
|
+
reportPercentage: 10
|
|
62
|
+
};
|
|
63
|
+
return await handler._executeBuild(params, job);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
logger.info(`[index] watcher: started DB rebuild job ${jobId}`);
|
|
67
|
+
this._monitorJob(jobId);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
37
71
|
// Check if manual build is already running (jobs starting with 'build-')
|
|
38
72
|
const allJobs = this.jobManager.getAllJobs();
|
|
39
|
-
const manualBuildRunning = allJobs.some(
|
|
40
|
-
job.id.startsWith('build-') && job.status === 'running'
|
|
73
|
+
const manualBuildRunning = allJobs.some(
|
|
74
|
+
job => job.id.startsWith('build-') && job.status === 'running'
|
|
41
75
|
);
|
|
42
76
|
|
|
43
77
|
if (manualBuildRunning) {
|
|
@@ -60,15 +94,17 @@ export class IndexWatcher {
|
|
|
60
94
|
const jobId = `watcher-${Date.now()}`;
|
|
61
95
|
this.currentWatcherJobId = jobId;
|
|
62
96
|
|
|
63
|
-
const { CodeIndexBuildToolHandler } = await import(
|
|
97
|
+
const { CodeIndexBuildToolHandler } = await import(
|
|
98
|
+
'../handlers/script/CodeIndexBuildToolHandler.js'
|
|
99
|
+
);
|
|
64
100
|
const handler = new CodeIndexBuildToolHandler(this.unityConnection);
|
|
65
101
|
|
|
66
102
|
// Create the build job through JobManager
|
|
67
|
-
this.jobManager.create(jobId, async
|
|
103
|
+
this.jobManager.create(jobId, async job => {
|
|
68
104
|
const params = {
|
|
69
105
|
concurrency: config.indexing.concurrency || 8,
|
|
70
106
|
retry: config.indexing.retry || 2,
|
|
71
|
-
reportEvery: config.indexing.reportEvery || 500
|
|
107
|
+
reportEvery: config.indexing.reportEvery || 500
|
|
72
108
|
};
|
|
73
109
|
return await handler._executeBuild(params, job);
|
|
74
110
|
});
|
|
@@ -78,7 +114,6 @@ export class IndexWatcher {
|
|
|
78
114
|
// Monitor job completion in background
|
|
79
115
|
// (Job result will be logged when it completes/fails)
|
|
80
116
|
this._monitorJob(jobId);
|
|
81
|
-
|
|
82
117
|
} catch (e) {
|
|
83
118
|
logger.warn(`[index] watcher exception: ${e.message}`);
|
|
84
119
|
} finally {
|
|
@@ -101,7 +136,9 @@ export class IndexWatcher {
|
|
|
101
136
|
}
|
|
102
137
|
|
|
103
138
|
if (job.status === 'completed') {
|
|
104
|
-
logger.info(
|
|
139
|
+
logger.info(
|
|
140
|
+
`[index] watcher: auto-build completed - updated=${job.result?.updatedFiles || 0} removed=${job.result?.removedFiles || 0} total=${job.result?.totalIndexedSymbols || 0}`
|
|
141
|
+
);
|
|
105
142
|
clearInterval(checkInterval);
|
|
106
143
|
} else if (job.status === 'failed') {
|
|
107
144
|
logger.warn(`[index] watcher: auto-build failed - ${job.error}`);
|
package/src/core/server.js
CHANGED
|
@@ -232,6 +232,36 @@ export async function startServer() {
|
|
|
232
232
|
process.on('SIGINT', stopWatch);
|
|
233
233
|
process.on('SIGTERM', stopWatch);
|
|
234
234
|
|
|
235
|
+
// Auto-initialize code index if DB doesn't exist
|
|
236
|
+
(async () => {
|
|
237
|
+
try {
|
|
238
|
+
const { CodeIndex } = await import('./codeIndex.js');
|
|
239
|
+
const index = new CodeIndex(unityConnection);
|
|
240
|
+
const ready = await index.isReady();
|
|
241
|
+
|
|
242
|
+
if (!ready) {
|
|
243
|
+
logger.info('[startup] Code index DB not ready. Starting auto-build...');
|
|
244
|
+
const { CodeIndexBuildToolHandler } = await import(
|
|
245
|
+
'../handlers/script/CodeIndexBuildToolHandler.js'
|
|
246
|
+
);
|
|
247
|
+
const builder = new CodeIndexBuildToolHandler(unityConnection);
|
|
248
|
+
const result = await builder.execute({});
|
|
249
|
+
|
|
250
|
+
if (result.success) {
|
|
251
|
+
logger.info(
|
|
252
|
+
`[startup] Code index auto-build started: jobId=${result.jobId}. Use code_index_status to check progress.`
|
|
253
|
+
);
|
|
254
|
+
} else {
|
|
255
|
+
logger.warn(`[startup] Code index auto-build failed: ${result.message}`);
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
logger.info('[startup] Code index DB already exists. Skipping auto-build.');
|
|
259
|
+
}
|
|
260
|
+
} catch (e) {
|
|
261
|
+
logger.warn(`[startup] Code index auto-init failed: ${e.message}`);
|
|
262
|
+
}
|
|
263
|
+
})();
|
|
264
|
+
|
|
235
265
|
// Handle shutdown
|
|
236
266
|
process.on('SIGINT', async () => {
|
|
237
267
|
logger.info('Shutting down...');
|
|
@@ -18,12 +18,14 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
18
18
|
throttleMs: {
|
|
19
19
|
type: 'number',
|
|
20
20
|
minimum: 0,
|
|
21
|
-
description:
|
|
21
|
+
description:
|
|
22
|
+
'Optional delay in milliseconds after processing each file (testing/debugging).'
|
|
22
23
|
},
|
|
23
24
|
delayStartMs: {
|
|
24
25
|
type: 'number',
|
|
25
26
|
minimum: 0,
|
|
26
|
-
description:
|
|
27
|
+
description:
|
|
28
|
+
'Optional delay before processing begins (useful to keep job in running state briefly).'
|
|
27
29
|
}
|
|
28
30
|
},
|
|
29
31
|
required: []
|
|
@@ -56,7 +58,7 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
56
58
|
this.currentJobId = jobId;
|
|
57
59
|
|
|
58
60
|
// Create background job
|
|
59
|
-
this.jobManager.create(jobId, async
|
|
61
|
+
this.jobManager.create(jobId, async job => {
|
|
60
62
|
return await this._executeBuild(params, job);
|
|
61
63
|
});
|
|
62
64
|
|
|
@@ -81,7 +83,7 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
81
83
|
const roots = [
|
|
82
84
|
path.resolve(info.projectRoot, 'Assets'),
|
|
83
85
|
path.resolve(info.projectRoot, 'Packages'),
|
|
84
|
-
path.resolve(info.projectRoot, 'Library/PackageCache')
|
|
86
|
+
path.resolve(info.projectRoot, 'Library/PackageCache')
|
|
85
87
|
];
|
|
86
88
|
const files = [];
|
|
87
89
|
const seen = new Set();
|
|
@@ -94,14 +96,21 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
94
96
|
logger.info(`[index][${job.id}] LSP initialized for project: ${info.projectRoot}`);
|
|
95
97
|
} catch (lspError) {
|
|
96
98
|
logger.error(`[index][${job.id}] LSP initialization failed: ${lspError.message}`);
|
|
97
|
-
throw new Error(
|
|
99
|
+
throw new Error(
|
|
100
|
+
`LSP initialization failed: ${lspError.message}. Ensure C# LSP is properly configured and OmniSharp is available.`
|
|
101
|
+
);
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
104
|
const lsp = this.lsp;
|
|
101
105
|
|
|
102
106
|
// Incremental detection based on size-mtime signature
|
|
103
|
-
const makeSig =
|
|
104
|
-
try {
|
|
107
|
+
const makeSig = abs => {
|
|
108
|
+
try {
|
|
109
|
+
const st = fs.statSync(abs);
|
|
110
|
+
return `${st.size}-${Math.floor(st.mtimeMs)}`;
|
|
111
|
+
} catch {
|
|
112
|
+
return '0-0';
|
|
113
|
+
}
|
|
105
114
|
};
|
|
106
115
|
const wanted = new Map(files.map(abs => [this.toRel(abs, info.projectRoot), makeSig(abs)]));
|
|
107
116
|
const current = await this.index.getFiles();
|
|
@@ -118,7 +127,15 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
118
127
|
const kind = this.kindFromLsp(s.kind);
|
|
119
128
|
const name = s.name || '';
|
|
120
129
|
const start = s.range?.start || s.selectionRange?.start || {};
|
|
121
|
-
rows.push({
|
|
130
|
+
rows.push({
|
|
131
|
+
path: rel,
|
|
132
|
+
name,
|
|
133
|
+
kind,
|
|
134
|
+
container: container || null,
|
|
135
|
+
ns: null,
|
|
136
|
+
line: (start.line ?? 0) + 1,
|
|
137
|
+
column: (start.character ?? 0) + 1
|
|
138
|
+
});
|
|
122
139
|
if (Array.isArray(s.children)) for (const c of s.children) visit(c, name || container);
|
|
123
140
|
};
|
|
124
141
|
if (Array.isArray(symbols)) for (const s of symbols) visit(s, null);
|
|
@@ -131,19 +148,27 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
131
148
|
// Update changed files
|
|
132
149
|
const absList = changed.map(rel => path.resolve(info.projectRoot, rel));
|
|
133
150
|
const concurrency = Math.max(1, Math.min(64, Number(params?.concurrency ?? 8)));
|
|
134
|
-
const
|
|
151
|
+
const reportPercentage = Math.max(1, Math.min(100, Number(params?.reportPercentage ?? 10)));
|
|
135
152
|
const startAt = Date.now();
|
|
136
|
-
let i = 0;
|
|
153
|
+
let i = 0;
|
|
154
|
+
let updated = 0;
|
|
155
|
+
let processed = 0;
|
|
156
|
+
let lastReportedPercentage = 0;
|
|
137
157
|
|
|
138
158
|
// Initialize progress
|
|
139
159
|
job.progress.total = absList.length;
|
|
140
160
|
job.progress.processed = 0;
|
|
141
161
|
job.progress.rate = 0;
|
|
142
162
|
|
|
143
|
-
logger.info(
|
|
163
|
+
logger.info(
|
|
164
|
+
`[index][${job.id}] Build started: ${absList.length} files to process, ${removed.length} to remove (status: ${job.status})`
|
|
165
|
+
);
|
|
144
166
|
|
|
145
167
|
// LSP request with small retry/backoff
|
|
146
|
-
const requestWithRetry = async (
|
|
168
|
+
const requestWithRetry = async (
|
|
169
|
+
uri,
|
|
170
|
+
maxRetries = Math.max(0, Math.min(5, Number(params?.retry ?? 2)))
|
|
171
|
+
) => {
|
|
147
172
|
let lastErr = null;
|
|
148
173
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
149
174
|
try {
|
|
@@ -180,17 +205,26 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
180
205
|
// Log occasionally to avoid spam
|
|
181
206
|
logger.warn(`[index][${job.id}] Skipped file due to error: ${rel} - ${err.message}`);
|
|
182
207
|
}
|
|
183
|
-
}
|
|
184
|
-
finally {
|
|
208
|
+
} finally {
|
|
185
209
|
processed += 1;
|
|
186
210
|
|
|
187
211
|
// Update job progress
|
|
188
212
|
const elapsed = Math.max(1, Date.now() - startAt);
|
|
189
213
|
job.progress.processed = processed;
|
|
190
|
-
job.progress.rate = parseFloat((processed * 1000 / elapsed).toFixed(1));
|
|
214
|
+
job.progress.rate = parseFloat(((processed * 1000) / elapsed).toFixed(1));
|
|
191
215
|
|
|
192
|
-
|
|
193
|
-
|
|
216
|
+
// Calculate current percentage
|
|
217
|
+
const currentPercentage = Math.floor((processed / absList.length) * 100);
|
|
218
|
+
|
|
219
|
+
// Log when percentage increases by reportPercentage (default: 10%)
|
|
220
|
+
if (
|
|
221
|
+
currentPercentage >= lastReportedPercentage + reportPercentage ||
|
|
222
|
+
processed === absList.length
|
|
223
|
+
) {
|
|
224
|
+
logger.info(
|
|
225
|
+
`[index][${job.id}] progress ${currentPercentage}% (${processed}/${absList.length}) removed:${removed.length} rate:${job.progress.rate} f/s`
|
|
226
|
+
);
|
|
227
|
+
lastReportedPercentage = currentPercentage;
|
|
194
228
|
}
|
|
195
229
|
|
|
196
230
|
if (throttleMs > 0) {
|
|
@@ -216,7 +250,9 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
216
250
|
lastIndexedAt: stats.lastIndexedAt
|
|
217
251
|
};
|
|
218
252
|
|
|
219
|
-
logger.info(
|
|
253
|
+
logger.info(
|
|
254
|
+
`[index][${job.id}] Build completed successfully: updated=${result.updatedFiles}, removed=${result.removedFiles}, total=${result.totalIndexedSymbols} (status: completed)`
|
|
255
|
+
);
|
|
220
256
|
|
|
221
257
|
return result;
|
|
222
258
|
} catch (e) {
|
|
@@ -242,7 +278,10 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
242
278
|
if (!fs.existsSync(root)) return;
|
|
243
279
|
const st = fs.statSync(root);
|
|
244
280
|
if (st.isFile()) {
|
|
245
|
-
if (root.endsWith('.cs') && !seen.has(root)) {
|
|
281
|
+
if (root.endsWith('.cs') && !seen.has(root)) {
|
|
282
|
+
files.push(root);
|
|
283
|
+
seen.add(root);
|
|
284
|
+
}
|
|
246
285
|
return;
|
|
247
286
|
}
|
|
248
287
|
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
@@ -261,13 +300,22 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
|
|
|
261
300
|
|
|
262
301
|
kindFromLsp(k) {
|
|
263
302
|
switch (k) {
|
|
264
|
-
case 5:
|
|
265
|
-
|
|
266
|
-
case
|
|
267
|
-
|
|
268
|
-
case
|
|
269
|
-
|
|
270
|
-
case
|
|
271
|
-
|
|
303
|
+
case 5:
|
|
304
|
+
return 'class';
|
|
305
|
+
case 23:
|
|
306
|
+
return 'struct';
|
|
307
|
+
case 11:
|
|
308
|
+
return 'interface';
|
|
309
|
+
case 10:
|
|
310
|
+
return 'enum';
|
|
311
|
+
case 6:
|
|
312
|
+
return 'method';
|
|
313
|
+
case 7:
|
|
314
|
+
return 'property';
|
|
315
|
+
case 8:
|
|
316
|
+
return 'field';
|
|
317
|
+
case 3:
|
|
318
|
+
return 'namespace';
|
|
319
|
+
}
|
|
272
320
|
}
|
|
273
321
|
}
|