@akiojin/unity-mcp-server 2.16.1 → 2.26.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/LICENSE +0 -0
- package/README.md +14 -4
- package/bin/unity-mcp-server +7 -1
- package/package.json +27 -5
- package/src/core/codeIndex.js +0 -0
- package/src/core/codeIndexDb.js +0 -0
- package/src/core/config.js +242 -200
- package/src/core/indexWatcher.js +83 -13
- package/src/core/jobManager.js +178 -0
- package/src/core/projectInfo.js +65 -118
- package/src/core/server.js +15 -2
- package/src/core/unityConnection.js +0 -0
- package/src/handlers/addressables/AddressablesAnalyzeToolHandler.js +180 -0
- package/src/handlers/addressables/AddressablesBuildToolHandler.js +146 -0
- package/src/handlers/addressables/AddressablesManageToolHandler.js +272 -0
- package/src/handlers/analysis/AnalyzeSceneContentsToolHandler.js +0 -0
- package/src/handlers/analysis/FindByComponentToolHandler.js +0 -0
- package/src/handlers/analysis/GetAnimatorStateToolHandler.js +0 -0
- package/src/handlers/analysis/GetComponentValuesToolHandler.js +0 -0
- package/src/handlers/analysis/GetGameObjectDetailsToolHandler.js +0 -0
- package/src/handlers/analysis/GetInputActionsStateToolHandler.js +0 -0
- package/src/handlers/analysis/GetObjectReferencesToolHandler.js +0 -0
- package/src/handlers/asset/AssetDatabaseManageToolHandler.js +0 -0
- package/src/handlers/asset/AssetDependencyAnalyzeToolHandler.js +0 -0
- package/src/handlers/asset/AssetImportSettingsManageToolHandler.js +0 -0
- package/src/handlers/asset/AssetMaterialCreateToolHandler.js +0 -0
- package/src/handlers/asset/AssetMaterialModifyToolHandler.js +0 -0
- package/src/handlers/asset/AssetPrefabCreateToolHandler.js +0 -0
- package/src/handlers/asset/AssetPrefabExitModeToolHandler.js +0 -0
- package/src/handlers/asset/AssetPrefabInstantiateToolHandler.js +0 -0
- package/src/handlers/asset/AssetPrefabModifyToolHandler.js +0 -0
- package/src/handlers/asset/AssetPrefabOpenToolHandler.js +0 -0
- package/src/handlers/asset/AssetPrefabSaveToolHandler.js +0 -0
- package/src/handlers/base/BaseToolHandler.js +0 -0
- package/src/handlers/compilation/CompilationGetStateToolHandler.js +0 -0
- package/src/handlers/component/ComponentAddToolHandler.js +0 -0
- package/src/handlers/component/ComponentFieldSetToolHandler.js +419 -0
- package/src/handlers/component/ComponentGetTypesToolHandler.js +0 -0
- package/src/handlers/component/ComponentListToolHandler.js +0 -0
- package/src/handlers/component/ComponentModifyToolHandler.js +0 -0
- package/src/handlers/component/ComponentRemoveToolHandler.js +0 -0
- package/src/handlers/console/ConsoleClearToolHandler.js +0 -0
- package/src/handlers/console/ConsoleReadToolHandler.js +295 -276
- package/src/handlers/editor/EditorLayersManageToolHandler.js +0 -0
- package/src/handlers/editor/EditorSelectionManageToolHandler.js +0 -0
- package/src/handlers/editor/EditorTagsManageToolHandler.js +0 -0
- package/src/handlers/editor/EditorToolsManageToolHandler.js +0 -0
- package/src/handlers/editor/EditorWindowsManageToolHandler.js +0 -0
- package/src/handlers/gameobject/GameObjectCreateToolHandler.js +0 -0
- package/src/handlers/gameobject/GameObjectDeleteToolHandler.js +0 -0
- package/src/handlers/gameobject/GameObjectFindToolHandler.js +0 -0
- package/src/handlers/gameobject/GameObjectGetHierarchyToolHandler.js +0 -0
- package/src/handlers/gameobject/GameObjectModifyToolHandler.js +0 -0
- package/src/handlers/index.js +437 -406
- package/src/handlers/input/InputActionAddToolHandler.js +0 -0
- package/src/handlers/input/InputActionMapCreateToolHandler.js +0 -0
- package/src/handlers/input/InputActionMapRemoveToolHandler.js +0 -0
- package/src/handlers/input/InputActionRemoveToolHandler.js +0 -0
- package/src/handlers/input/InputBindingAddToolHandler.js +0 -0
- package/src/handlers/input/InputBindingCompositeCreateToolHandler.js +0 -0
- package/src/handlers/input/InputBindingRemoveAllToolHandler.js +0 -0
- package/src/handlers/input/InputBindingRemoveToolHandler.js +0 -0
- package/src/handlers/input/InputControlSchemesManageToolHandler.js +0 -0
- package/src/handlers/input/InputGamepadSimulateToolHandler.js +0 -0
- package/src/handlers/input/InputKeyboardSimulateToolHandler.js +0 -0
- package/src/handlers/input/InputMouseSimulateToolHandler.js +0 -0
- package/src/handlers/input/InputSystemControlToolHandler.js +0 -0
- package/src/handlers/input/InputTouchSimulateToolHandler.js +0 -0
- package/src/handlers/menu/MenuItemExecuteToolHandler.js +0 -0
- package/src/handlers/package/PackageManagerToolHandler.js +0 -0
- package/src/handlers/package/RegistryConfigToolHandler.js +0 -0
- package/src/handlers/playmode/PlaymodeGetStateToolHandler.js +1 -1
- package/src/handlers/playmode/PlaymodePauseToolHandler.js +1 -1
- package/src/handlers/playmode/PlaymodePlayToolHandler.js +0 -0
- package/src/handlers/playmode/PlaymodeStopToolHandler.js +0 -0
- package/src/handlers/playmode/PlaymodeWaitForStateToolHandler.js +0 -0
- package/src/handlers/scene/GetSceneInfoToolHandler.js +0 -0
- package/src/handlers/scene/SceneCreateToolHandler.js +0 -0
- package/src/handlers/scene/SceneListToolHandler.js +0 -0
- package/src/handlers/scene/SceneLoadToolHandler.js +0 -0
- package/src/handlers/scene/SceneSaveToolHandler.js +0 -0
- package/src/handlers/screenshot/ScreenshotAnalyzeToolHandler.js +0 -0
- package/src/handlers/screenshot/ScreenshotCaptureToolHandler.js +0 -0
- package/src/handlers/script/CodeIndexBuildToolHandler.js +125 -15
- package/src/handlers/script/CodeIndexStatusToolHandler.js +129 -0
- package/src/handlers/script/ScriptCreateClassToolHandler.js +0 -0
- package/src/handlers/script/ScriptEditSnippetToolHandler.js +1 -1
- package/src/handlers/script/ScriptEditStructuredToolHandler.js +1 -1
- package/src/handlers/script/ScriptPackagesListToolHandler.js +0 -0
- package/src/handlers/script/ScriptReadToolHandler.js +0 -0
- package/src/handlers/script/ScriptRefactorRenameToolHandler.js +0 -0
- package/src/handlers/script/ScriptRefsFindToolHandler.js +0 -0
- package/src/handlers/script/ScriptRemoveSymbolToolHandler.js +0 -0
- package/src/handlers/script/ScriptSearchToolHandler.js +0 -0
- package/src/handlers/script/ScriptSymbolFindToolHandler.js +0 -0
- package/src/handlers/script/ScriptSymbolsGetToolHandler.js +0 -0
- package/src/handlers/settings/SettingsGetToolHandler.js +0 -0
- package/src/handlers/settings/SettingsUpdateToolHandler.js +0 -0
- package/src/handlers/system/SystemGetCommandStatsToolHandler.js +0 -0
- package/src/handlers/system/SystemPingToolHandler.js +0 -0
- package/src/handlers/system/SystemRefreshAssetsToolHandler.js +0 -0
- package/src/handlers/test/TestRunToolHandler.js +0 -0
- package/src/handlers/ui/UIClickElementToolHandler.js +0 -0
- package/src/handlers/ui/UIFindElementsToolHandler.js +0 -0
- package/src/handlers/ui/UIGetElementStateToolHandler.js +0 -0
- package/src/handlers/ui/UISetElementValueToolHandler.js +0 -0
- package/src/handlers/ui/UISimulateInputToolHandler.js +0 -0
- package/src/handlers/video/VideoCaptureForToolHandler.js +0 -0
- package/src/handlers/video/VideoCaptureStartToolHandler.js +0 -0
- package/src/handlers/video/VideoCaptureStatusToolHandler.js +0 -0
- package/src/handlers/video/VideoCaptureStopToolHandler.js +0 -0
- package/src/lsp/CSharpLspUtils.js +27 -4
- package/src/lsp/LspProcessManager.js +0 -0
- package/src/lsp/LspRpcClient.js +0 -0
- package/src/tools/analysis/analyzeSceneContents.js +0 -0
- package/src/tools/analysis/findByComponent.js +0 -0
- package/src/tools/analysis/getAnimatorState.js +0 -0
- package/src/tools/analysis/getComponentValues.js +0 -0
- package/src/tools/analysis/getGameObjectDetails.js +0 -0
- package/src/tools/analysis/getInputActionsState.js +0 -0
- package/src/tools/analysis/getObjectReferences.js +0 -0
- package/src/tools/input/inputActionsEditor.js +0 -0
- package/src/tools/scene/createScene.js +0 -0
- package/src/tools/scene/getSceneInfo.js +0 -0
- package/src/tools/scene/listScenes.js +0 -0
- package/src/tools/scene/loadScene.js +0 -0
- package/src/tools/scene/saveScene.js +0 -0
- package/src/tools/system/ping.js +0 -0
- package/src/tools/video/recordFor.js +0 -0
- package/src/tools/video/recordPlayMode.js +0 -0
- package/src/utils/csharpParse.js +0 -0
- package/src/utils/validators.js +0 -0
- package/src/handlers/script/ScriptIndexStatusToolHandler.js +0 -61
package/src/core/indexWatcher.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { logger, config } from './config.js';
|
|
2
|
+
import { JobManager } from './jobManager.js';
|
|
2
3
|
|
|
3
4
|
export class IndexWatcher {
|
|
4
5
|
constructor(unityConnection) {
|
|
5
6
|
this.unityConnection = unityConnection;
|
|
6
7
|
this.timer = null;
|
|
7
8
|
this.running = false;
|
|
9
|
+
this.jobManager = JobManager.getInstance();
|
|
10
|
+
this.currentWatcherJobId = null;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
start() {
|
|
@@ -13,6 +16,9 @@ export class IndexWatcher {
|
|
|
13
16
|
const interval = Math.max(2000, Number(config.indexing.intervalMs || 15000));
|
|
14
17
|
logger.info(`[index] watcher enabled (interval=${interval}ms)`);
|
|
15
18
|
this.timer = setInterval(() => this.tick(), interval);
|
|
19
|
+
if (typeof this.timer.unref === 'function') {
|
|
20
|
+
this.timer.unref();
|
|
21
|
+
}
|
|
16
22
|
// Initial kick
|
|
17
23
|
this.tick();
|
|
18
24
|
}
|
|
@@ -28,24 +34,88 @@ export class IndexWatcher {
|
|
|
28
34
|
if (this.running) return;
|
|
29
35
|
this.running = true;
|
|
30
36
|
try {
|
|
37
|
+
// Check if manual build is already running (jobs starting with 'build-')
|
|
38
|
+
const allJobs = this.jobManager.getAllJobs();
|
|
39
|
+
const manualBuildRunning = allJobs.some(job =>
|
|
40
|
+
job.id.startsWith('build-') && job.status === 'running'
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if (manualBuildRunning) {
|
|
44
|
+
logger.info('[index] watcher: skipping auto-build (manual build in progress)');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check if our watcher job is still running
|
|
49
|
+
if (this.currentWatcherJobId) {
|
|
50
|
+
const watcherJob = this.jobManager.get(this.currentWatcherJobId);
|
|
51
|
+
if (watcherJob && watcherJob.status === 'running') {
|
|
52
|
+
logger.info('[index] watcher: previous auto-build still running, skipping');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Previous watcher job completed or cleaned up
|
|
56
|
+
this.currentWatcherJobId = null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Start new watcher build job
|
|
60
|
+
const jobId = `watcher-${Date.now()}`;
|
|
61
|
+
this.currentWatcherJobId = jobId;
|
|
62
|
+
|
|
31
63
|
const { CodeIndexBuildToolHandler } = await import('../handlers/script/CodeIndexBuildToolHandler.js');
|
|
32
64
|
const handler = new CodeIndexBuildToolHandler(this.unityConnection);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
}
|
|
65
|
+
|
|
66
|
+
// Create the build job through JobManager
|
|
67
|
+
this.jobManager.create(jobId, async (job) => {
|
|
68
|
+
const params = {
|
|
69
|
+
concurrency: config.indexing.concurrency || 8,
|
|
70
|
+
retry: config.indexing.retry || 2,
|
|
71
|
+
reportEvery: config.indexing.reportEvery || 500,
|
|
72
|
+
};
|
|
73
|
+
return await handler._executeBuild(params, job);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
logger.info(`[index] watcher: started auto-build job ${jobId}`);
|
|
77
|
+
|
|
78
|
+
// Monitor job completion in background
|
|
79
|
+
// (Job result will be logged when it completes/fails)
|
|
80
|
+
this._monitorJob(jobId);
|
|
81
|
+
|
|
45
82
|
} catch (e) {
|
|
46
|
-
logger.warn(`[index]
|
|
83
|
+
logger.warn(`[index] watcher exception: ${e.message}`);
|
|
47
84
|
} finally {
|
|
48
85
|
this.running = false;
|
|
49
86
|
}
|
|
50
87
|
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Monitor job completion for logging
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
async _monitorJob(jobId) {
|
|
94
|
+
// Check job status periodically
|
|
95
|
+
const checkInterval = setInterval(() => {
|
|
96
|
+
const job = this.jobManager.get(jobId);
|
|
97
|
+
if (!job) {
|
|
98
|
+
// Job cleaned up
|
|
99
|
+
clearInterval(checkInterval);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (job.status === 'completed') {
|
|
104
|
+
logger.info(`[index] watcher: auto-build completed - updated=${job.result?.updatedFiles || 0} removed=${job.result?.removedFiles || 0} total=${job.result?.totalIndexedSymbols || 0}`);
|
|
105
|
+
clearInterval(checkInterval);
|
|
106
|
+
} else if (job.status === 'failed') {
|
|
107
|
+
logger.warn(`[index] watcher: auto-build failed - ${job.error}`);
|
|
108
|
+
clearInterval(checkInterval);
|
|
109
|
+
}
|
|
110
|
+
}, 1000);
|
|
111
|
+
if (typeof checkInterval.unref === 'function') {
|
|
112
|
+
checkInterval.unref();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Cleanup interval after 10 minutes (longer than job cleanup)
|
|
116
|
+
const cleanupTimeout = setTimeout(() => clearInterval(checkInterval), 600000);
|
|
117
|
+
if (typeof cleanupTimeout.unref === 'function') {
|
|
118
|
+
cleanupTimeout.unref();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
51
121
|
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JobManager - Manages background job execution and tracking
|
|
3
|
+
*
|
|
4
|
+
* SPEC-yt3ikddd: Background job execution for code index build
|
|
5
|
+
* - Memory-based job tracking (no persistence)
|
|
6
|
+
* - Single concurrent job execution
|
|
7
|
+
* - Auto-cleanup after 5 minutes (configurable)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* BuildJob structure
|
|
12
|
+
* @typedef {Object} BuildJob
|
|
13
|
+
* @property {string} id - Unique job identifier
|
|
14
|
+
* @property {'running'|'completed'|'failed'} status - Current job status
|
|
15
|
+
* @property {Object} progress - Progress information
|
|
16
|
+
* @property {number} progress.processed - Number of processed items
|
|
17
|
+
* @property {number} progress.total - Total items to process
|
|
18
|
+
* @property {number} progress.rate - Processing rate (items/sec)
|
|
19
|
+
* @property {Object|null} result - Result object (only for completed jobs)
|
|
20
|
+
* @property {number} result.updatedFiles - Number of updated files
|
|
21
|
+
* @property {number} result.removedFiles - Number of removed files
|
|
22
|
+
* @property {number} result.totalIndexedSymbols - Total indexed symbols
|
|
23
|
+
* @property {string} result.lastIndexedAt - ISO8601 timestamp of last index
|
|
24
|
+
* @property {string|null} error - Error message (only for failed jobs)
|
|
25
|
+
* @property {string} startedAt - ISO8601 timestamp when job started
|
|
26
|
+
* @property {string|null} completedAt - ISO8601 timestamp when job completed
|
|
27
|
+
* @property {string|null} failedAt - ISO8601 timestamp when job failed
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export class JobManager {
|
|
31
|
+
constructor() {
|
|
32
|
+
/**
|
|
33
|
+
* Map of jobId -> BuildJob
|
|
34
|
+
* @type {Map<string, BuildJob>}
|
|
35
|
+
*/
|
|
36
|
+
this.jobs = new Map();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Map of jobId -> cleanup timeout handle
|
|
40
|
+
* @type {Map<string, NodeJS.Timeout>}
|
|
41
|
+
*/
|
|
42
|
+
this.cleanupTimers = new Map();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the singleton instance
|
|
47
|
+
* @returns {JobManager}
|
|
48
|
+
*/
|
|
49
|
+
static getInstance() {
|
|
50
|
+
if (!JobManager._instance) {
|
|
51
|
+
JobManager._instance = new JobManager();
|
|
52
|
+
}
|
|
53
|
+
return JobManager._instance;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a new job and execute it in background
|
|
58
|
+
*
|
|
59
|
+
* @param {string} jobId - Unique job identifier
|
|
60
|
+
* @param {Function} jobFn - Async function to execute: async (job) => result
|
|
61
|
+
* @returns {string} jobId - Returns immediately
|
|
62
|
+
*/
|
|
63
|
+
create(jobId, jobFn) {
|
|
64
|
+
// Create initial job object
|
|
65
|
+
const job = {
|
|
66
|
+
id: jobId,
|
|
67
|
+
status: 'running',
|
|
68
|
+
progress: {
|
|
69
|
+
processed: 0,
|
|
70
|
+
total: 0,
|
|
71
|
+
rate: 0
|
|
72
|
+
},
|
|
73
|
+
result: null,
|
|
74
|
+
error: null,
|
|
75
|
+
startedAt: new Date().toISOString(),
|
|
76
|
+
completedAt: null,
|
|
77
|
+
failedAt: null
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Store job
|
|
81
|
+
this.jobs.set(jobId, job);
|
|
82
|
+
|
|
83
|
+
// Execute in background (don't await)
|
|
84
|
+
this._executeJob(jobId, jobFn, job);
|
|
85
|
+
|
|
86
|
+
// Return jobId immediately
|
|
87
|
+
return jobId;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get job by ID
|
|
92
|
+
*
|
|
93
|
+
* @param {string} jobId - Job identifier
|
|
94
|
+
* @returns {BuildJob|undefined} Job object or undefined if not found
|
|
95
|
+
*/
|
|
96
|
+
get(jobId) {
|
|
97
|
+
return this.jobs.get(jobId);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Schedule job cleanup after retention period
|
|
102
|
+
*
|
|
103
|
+
* @param {string} jobId - Job identifier
|
|
104
|
+
* @param {number} retentionMs - Retention period in milliseconds (default: 5 minutes)
|
|
105
|
+
*/
|
|
106
|
+
cleanup(jobId, retentionMs = 300000) {
|
|
107
|
+
// Clear any existing cleanup timer
|
|
108
|
+
if (this.cleanupTimers.has(jobId)) {
|
|
109
|
+
clearTimeout(this.cleanupTimers.get(jobId));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Schedule new cleanup
|
|
113
|
+
const timer = setTimeout(() => {
|
|
114
|
+
this.jobs.delete(jobId);
|
|
115
|
+
this.cleanupTimers.delete(jobId);
|
|
116
|
+
}, retentionMs);
|
|
117
|
+
if (typeof timer.unref === 'function') {
|
|
118
|
+
timer.unref();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.cleanupTimers.set(jobId, timer);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Execute job function in background
|
|
126
|
+
*
|
|
127
|
+
* @private
|
|
128
|
+
* @param {string} jobId - Job identifier
|
|
129
|
+
* @param {Function} jobFn - Async job function
|
|
130
|
+
* @param {BuildJob} job - Job object (passed by reference for progress updates)
|
|
131
|
+
*/
|
|
132
|
+
async _executeJob(jobId, jobFn, job) {
|
|
133
|
+
try {
|
|
134
|
+
// Execute job function
|
|
135
|
+
// jobFn can update job.progress directly
|
|
136
|
+
const result = await jobFn(job);
|
|
137
|
+
|
|
138
|
+
// Update job on success
|
|
139
|
+
job.status = 'completed';
|
|
140
|
+
job.result = result;
|
|
141
|
+
job.completedAt = new Date().toISOString();
|
|
142
|
+
|
|
143
|
+
// Schedule cleanup after 5 minutes
|
|
144
|
+
this.cleanup(jobId, 300000);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
// Update job on failure
|
|
147
|
+
job.status = 'failed';
|
|
148
|
+
job.error = error.message || String(error);
|
|
149
|
+
job.failedAt = new Date().toISOString();
|
|
150
|
+
|
|
151
|
+
// Schedule cleanup after 5 minutes
|
|
152
|
+
this.cleanup(jobId, 300000);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get all jobs (for debugging/testing)
|
|
158
|
+
*
|
|
159
|
+
* @returns {BuildJob[]} Array of all jobs
|
|
160
|
+
*/
|
|
161
|
+
getAllJobs() {
|
|
162
|
+
return Array.from(this.jobs.values());
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Clear all jobs (for testing)
|
|
167
|
+
*/
|
|
168
|
+
clearAll() {
|
|
169
|
+
// Clear all cleanup timers
|
|
170
|
+
for (const timer of this.cleanupTimers.values()) {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
}
|
|
173
|
+
this.cleanupTimers.clear();
|
|
174
|
+
|
|
175
|
+
// Clear all jobs
|
|
176
|
+
this.jobs.clear();
|
|
177
|
+
}
|
|
178
|
+
}
|
package/src/core/projectInfo.js
CHANGED
|
@@ -1,118 +1,65 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { logger, config, WORKSPACE_ROOT } from './config.js';
|
|
4
|
-
|
|
5
|
-
const normalize = (p) => p.replace(/\\/g, '/');
|
|
6
|
-
|
|
7
|
-
const resolveDefaultCodeIndexRoot = (projectRoot) => {
|
|
8
|
-
const base = WORKSPACE_ROOT || projectRoot || process.cwd();
|
|
9
|
-
return normalize(path.join(base, '.unity', 'cache', 'code-index'));
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
// Lazy project info resolver. Prefers Unity via get_editor_info, otherwise infers by walking up for Assets/Packages.
|
|
13
|
-
export class ProjectInfoProvider {
|
|
14
|
-
constructor(unityConnection) {
|
|
15
|
-
this.unityConnection = unityConnection;
|
|
16
|
-
this.cached = null;
|
|
17
|
-
this.lastTried = 0;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async get() {
|
|
21
|
-
if (this.cached) return this.cached;
|
|
22
|
-
// Config-driven project root (no env fallback)
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const dockerDefaultPath = '/unity-mcp-server/UnityMCPServer';
|
|
67
|
-
const dockerAssets = path.join(dockerDefaultPath, 'Assets');
|
|
68
|
-
if (fs.existsSync(dockerAssets)) {
|
|
69
|
-
const projectRoot = normalize(dockerDefaultPath);
|
|
70
|
-
logger.info(`Found Unity project at Docker default path: ${projectRoot}`);
|
|
71
|
-
return {
|
|
72
|
-
projectRoot,
|
|
73
|
-
assetsPath: normalize(dockerAssets),
|
|
74
|
-
packagesPath: normalize(path.join(dockerDefaultPath, 'Packages')),
|
|
75
|
-
codeIndexRoot: resolveDefaultCodeIndexRoot(projectRoot),
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let dir = process.cwd();
|
|
80
|
-
// Walk up max 5 levels (look for Assets directly under a directory)
|
|
81
|
-
for (let i = 0; i < 5; i++) {
|
|
82
|
-
const assets = path.join(dir, 'Assets');
|
|
83
|
-
if (fs.existsSync(assets)) {
|
|
84
|
-
const projectRoot = normalize(dir);
|
|
85
|
-
return {
|
|
86
|
-
projectRoot,
|
|
87
|
-
assetsPath: normalize(assets),
|
|
88
|
-
packagesPath: normalize(path.join(dir, 'Packages')),
|
|
89
|
-
codeIndexRoot: resolveDefaultCodeIndexRoot(projectRoot),
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
dir = path.dirname(dir);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Fallback: search shallow subdirectories for Unity projects (depth 2)
|
|
96
|
-
const rootsToCheck = [process.cwd(), path.dirname(process.cwd())];
|
|
97
|
-
for (const root of rootsToCheck) {
|
|
98
|
-
try {
|
|
99
|
-
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
100
|
-
for (const e of entries) {
|
|
101
|
-
if (!e.isDirectory()) continue;
|
|
102
|
-
const candidate = path.join(root, e.name);
|
|
103
|
-
const assets = path.join(candidate, 'Assets');
|
|
104
|
-
if (fs.existsSync(assets)) {
|
|
105
|
-
const projectRoot = normalize(candidate);
|
|
106
|
-
return {
|
|
107
|
-
projectRoot,
|
|
108
|
-
assetsPath: normalize(assets),
|
|
109
|
-
packagesPath: normalize(path.join(candidate, 'Packages')),
|
|
110
|
-
codeIndexRoot: resolveDefaultCodeIndexRoot(projectRoot),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
} catch {}
|
|
115
|
-
}
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { logger, config, WORKSPACE_ROOT } from './config.js';
|
|
4
|
+
|
|
5
|
+
const normalize = (p) => p.replace(/\\/g, '/');
|
|
6
|
+
|
|
7
|
+
const resolveDefaultCodeIndexRoot = (projectRoot) => {
|
|
8
|
+
const base = WORKSPACE_ROOT || projectRoot || process.cwd();
|
|
9
|
+
return normalize(path.join(base, '.unity', 'cache', 'code-index'));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Lazy project info resolver. Prefers Unity via get_editor_info, otherwise infers by walking up for Assets/Packages.
|
|
13
|
+
export class ProjectInfoProvider {
|
|
14
|
+
constructor(unityConnection) {
|
|
15
|
+
this.unityConnection = unityConnection;
|
|
16
|
+
this.cached = null;
|
|
17
|
+
this.lastTried = 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async get() {
|
|
21
|
+
if (this.cached) return this.cached;
|
|
22
|
+
// Config-driven project root (no env fallback)
|
|
23
|
+
const cfgRootRaw = config?.project?.root;
|
|
24
|
+
if (typeof cfgRootRaw === 'string' && cfgRootRaw.trim().length > 0) {
|
|
25
|
+
const cfgRoot = cfgRootRaw.trim();
|
|
26
|
+
// Resolve relative paths against WORKSPACE_ROOT
|
|
27
|
+
const projectRoot = normalize(path.isAbsolute(cfgRoot) ? cfgRoot : path.resolve(WORKSPACE_ROOT, cfgRoot));
|
|
28
|
+
const codeIndexRoot = normalize(config?.project?.codeIndexRoot || resolveDefaultCodeIndexRoot(projectRoot));
|
|
29
|
+
this.cached = {
|
|
30
|
+
projectRoot,
|
|
31
|
+
assetsPath: normalize(path.join(projectRoot, 'Assets')),
|
|
32
|
+
packagesPath: normalize(path.join(projectRoot, 'Packages')),
|
|
33
|
+
codeIndexRoot,
|
|
34
|
+
};
|
|
35
|
+
return this.cached;
|
|
36
|
+
}
|
|
37
|
+
// Try Unity if connected (rate-limit attempts)
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
if (this.unityConnection && this.unityConnection.isConnected() && (now - this.lastTried > 1000)) {
|
|
40
|
+
this.lastTried = now;
|
|
41
|
+
try {
|
|
42
|
+
const info = await this.unityConnection.sendCommand('get_editor_info', {});
|
|
43
|
+
if (info && info.projectRoot && info.assetsPath) {
|
|
44
|
+
this.cached = {
|
|
45
|
+
projectRoot: info.projectRoot,
|
|
46
|
+
assetsPath: info.assetsPath,
|
|
47
|
+
packagesPath: normalize(info.packagesPath || path.join(info.projectRoot, 'Packages')),
|
|
48
|
+
codeIndexRoot: normalize(info.codeIndexRoot || resolveDefaultCodeIndexRoot(info.projectRoot)),
|
|
49
|
+
};
|
|
50
|
+
return this.cached;
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
logger.warn(`get_editor_info failed: ${e.message}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (typeof cfgRootRaw === 'string') {
|
|
57
|
+
throw new Error('project.root is configured but empty. Set a valid path in .unity/config.json or UNITY_MCP_CONFIG.');
|
|
58
|
+
}
|
|
59
|
+
throw new Error('Unable to resolve Unity project root. Configure project.root in .unity/config.json or provide UNITY_MCP_CONFIG.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
inferFromCwd() {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/core/server.js
CHANGED
|
@@ -169,7 +169,7 @@ unityConnection.on('error', (error) => {
|
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
// Initialize server
|
|
172
|
-
async function
|
|
172
|
+
export async function startServer() {
|
|
173
173
|
try {
|
|
174
174
|
// Create transport - no logging before connection
|
|
175
175
|
const transport = new StdioServerTransport();
|
|
@@ -232,6 +232,9 @@ async function main() {
|
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
// Maintain backwards compatibility for older callers that expect main()
|
|
236
|
+
const main = startServer;
|
|
237
|
+
|
|
235
238
|
// Export for testing
|
|
236
239
|
export async function createServer(customConfig = config) {
|
|
237
240
|
const testUnityConnection = new UnityConnection();
|
|
@@ -287,7 +290,17 @@ export async function createServer(customConfig = config) {
|
|
|
287
290
|
}
|
|
288
291
|
|
|
289
292
|
// Start the server
|
|
290
|
-
|
|
293
|
+
const isDirectExecution = (() => {
|
|
294
|
+
if (process.env.NODE_ENV === 'test') return false;
|
|
295
|
+
if (!process.argv[1]) return false;
|
|
296
|
+
try {
|
|
297
|
+
return import.meta.url === new URL('file://' + process.argv[1]).href;
|
|
298
|
+
} catch {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
})();
|
|
302
|
+
|
|
303
|
+
if (isDirectExecution) startServer().catch((error) => {
|
|
291
304
|
console.error('Fatal error:', error);
|
|
292
305
|
console.error('Stack trace:', error.stack);
|
|
293
306
|
process.exit(1);
|
|
File without changes
|