@akiojin/unity-mcp-server 2.25.0 → 2.26.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.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/src/core/codeIndexDb.js +26 -10
  3. package/src/core/config.js +242 -242
  4. package/src/core/projectInfo.js +19 -10
  5. package/src/core/server.js +88 -65
  6. package/src/core/transports/HybridStdioServerTransport.js +179 -0
  7. package/src/core/unityConnection.js +52 -45
  8. package/src/handlers/addressables/AddressablesAnalyzeToolHandler.js +59 -49
  9. package/src/handlers/addressables/AddressablesBuildToolHandler.js +63 -62
  10. package/src/handlers/addressables/AddressablesManageToolHandler.js +84 -78
  11. package/src/handlers/base/BaseToolHandler.js +5 -5
  12. package/src/handlers/component/ComponentFieldSetToolHandler.js +419 -419
  13. package/src/handlers/console/ConsoleReadToolHandler.js +56 -66
  14. package/src/handlers/editor/EditorSelectionManageToolHandler.js +10 -9
  15. package/src/handlers/gameobject/GameObjectModifyToolHandler.js +22 -11
  16. package/src/handlers/index.js +437 -437
  17. package/src/handlers/menu/MenuItemExecuteToolHandler.js +75 -37
  18. package/src/handlers/screenshot/ScreenshotAnalyzeToolHandler.js +12 -10
  19. package/src/handlers/script/ScriptEditStructuredToolHandler.js +162 -154
  20. package/src/handlers/script/ScriptReadToolHandler.js +80 -85
  21. package/src/handlers/script/ScriptRefsFindToolHandler.js +123 -123
  22. package/src/handlers/script/ScriptSymbolFindToolHandler.js +125 -112
  23. package/src/handlers/system/SystemGetCommandStatsToolHandler.js +1 -1
  24. package/src/handlers/system/SystemRefreshAssetsToolHandler.js +10 -14
  25. package/src/handlers/video/VideoCaptureStartToolHandler.js +15 -5
  26. package/src/handlers/video/VideoCaptureStatusToolHandler.js +5 -9
  27. package/src/handlers/video/VideoCaptureStopToolHandler.js +8 -9
  28. package/src/lsp/LspProcessManager.js +26 -9
  29. package/src/tools/video/recordFor.js +13 -7
  30. package/src/tools/video/recordPlayMode.js +7 -6
  31. package/src/utils/csharpParse.js +14 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akiojin/unity-mcp-server",
3
- "version": "2.25.0",
3
+ "version": "2.26.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",
@@ -1,8 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import Database from 'better-sqlite3';
4
- import { logger } from './config.js';
5
-
6
4
  let dbCache = new Map();
7
5
 
8
6
  function getDbPath(projectRoot) {
@@ -47,16 +45,29 @@ export function openDb(projectRoot) {
47
45
  }
48
46
 
49
47
  export function upsertFile(db, filePath, mtimeMs) {
50
- const stmt = db.prepare('INSERT INTO files(path, mtime) VALUES(?, ?) ON CONFLICT(path) DO UPDATE SET mtime=excluded.mtime');
48
+ const stmt = db.prepare(
49
+ 'INSERT INTO files(path, mtime) VALUES(?, ?) ON CONFLICT(path) DO UPDATE SET mtime=excluded.mtime'
50
+ );
51
51
  stmt.run(filePath, Math.floor(mtimeMs));
52
52
  }
53
53
 
54
54
  export function replaceSymbols(db, filePath, symbols) {
55
55
  const del = db.prepare('DELETE FROM symbols WHERE path = ?');
56
56
  del.run(filePath);
57
- const ins = db.prepare('INSERT INTO symbols(path, name, kind, container, ns, line, column) VALUES(?,?,?,?,?,?,?)');
58
- const tr = db.transaction((rows) => {
59
- for (const s of rows) ins.run(filePath, s.name || '', s.kind || '', s.container || null, s.ns || null, s.line || 0, s.column || 0);
57
+ const ins = db.prepare(
58
+ 'INSERT INTO symbols(path, name, kind, container, ns, line, column) VALUES(?,?,?,?,?,?,?)'
59
+ );
60
+ const tr = db.transaction(rows => {
61
+ for (const s of rows)
62
+ ins.run(
63
+ filePath,
64
+ s.name || '',
65
+ s.kind || '',
66
+ s.container || null,
67
+ s.ns || null,
68
+ s.line || 0,
69
+ s.column || 0
70
+ );
60
71
  });
61
72
  tr(symbols || []);
62
73
  }
@@ -65,7 +76,7 @@ export function replaceReferences(db, filePath, refs) {
65
76
  const del = db.prepare('DELETE FROM refs WHERE path = ?');
66
77
  del.run(filePath);
67
78
  const ins = db.prepare('INSERT INTO refs(path, name, line, snippet) VALUES(?,?,?,?)');
68
- const tr = db.transaction((rows) => {
79
+ const tr = db.transaction(rows => {
69
80
  for (const r of rows) ins.run(filePath, r.name || '', r.line || 0, r.snippet || null);
70
81
  });
71
82
  tr(refs || []);
@@ -73,9 +84,15 @@ export function replaceReferences(db, filePath, refs) {
73
84
 
74
85
  export function querySymbolsByName(db, name, kind = null) {
75
86
  if (kind) {
76
- return db.prepare('SELECT path,name,kind,container,ns,line,column FROM symbols WHERE name = ? AND kind = ? LIMIT 500').all(name, kind);
87
+ return db
88
+ .prepare(
89
+ 'SELECT path,name,kind,container,ns,line,column FROM symbols WHERE name = ? AND kind = ? LIMIT 500'
90
+ )
91
+ .all(name, kind);
77
92
  }
78
- return db.prepare('SELECT path,name,kind,container,ns,line,column FROM symbols WHERE name = ? LIMIT 500').all(name);
93
+ return db
94
+ .prepare('SELECT path,name,kind,container,ns,line,column FROM symbols WHERE name = ? LIMIT 500')
95
+ .all(name);
79
96
  }
80
97
 
81
98
  export function queryRefsByName(db, name) {
@@ -93,4 +110,3 @@ export function isFresh(projectRoot, filePath, db) {
93
110
  return false;
94
111
  }
95
112
  }
96
-
@@ -1,242 +1,242 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { findUpSync } from 'find-up';
4
-
5
- /**
6
- * Shallow merge utility (simple objects only)
7
- */
8
- function merge(a, b) {
9
- const out = { ...a };
10
- for (const [k, v] of Object.entries(b || {})) {
11
- if (v && typeof v === 'object' && !Array.isArray(v) && a[k] && typeof a[k] === 'object') {
12
- out[k] = { ...a[k], ...v };
13
- } else {
14
- out[k] = v;
15
- }
16
- }
17
- return out;
18
- }
19
-
20
- /**
21
- * Base configuration for Unity Editor MCP Server
22
- */
23
- const envUnityHost =
24
- process.env.UNITY_UNITY_HOST ||
25
- process.env.UNITY_BIND_HOST ||
26
- process.env.UNITY_HOST ||
27
- null;
28
-
29
- const envMcpHost =
30
- process.env.UNITY_MCP_HOST ||
31
- process.env.UNITY_CLIENT_HOST ||
32
- process.env.UNITY_HOST ||
33
- null;
34
-
35
- const envBindHost = process.env.UNITY_BIND_HOST || null;
36
-
37
- const baseConfig = {
38
- // Unity connection settings
39
- unity: {
40
- unityHost: envUnityHost,
41
- mcpHost: envMcpHost,
42
- bindHost: envBindHost,
43
- port: parseInt(process.env.UNITY_PORT || '', 10) || 6400,
44
- reconnectDelay: 1000,
45
- maxReconnectDelay: 30000,
46
- reconnectBackoffMultiplier: 2,
47
- commandTimeout: 30000,
48
- },
49
-
50
- // Server settings
51
- server: {
52
- name: 'unity-mcp-server',
53
- version: '0.1.0',
54
- description: 'MCP server for Unity Editor integration',
55
- },
56
-
57
- // Logging settings
58
- logging: {
59
- level: process.env.LOG_LEVEL || 'info',
60
- prefix: '[Unity Editor MCP]',
61
- },
62
-
63
- // Write queue removed: all edits go through structured Roslyn tools.
64
-
65
- // Search-related defaults and engine selection
66
- search: {
67
- // detail alias: 'compact' maps to returnMode 'snippets'
68
- defaultDetail: (process.env.SEARCH_DEFAULT_DETAIL || 'compact').toLowerCase(), // compact|metadata|snippets|full
69
- engine: (process.env.SEARCH_ENGINE || 'naive').toLowerCase(), // naive|treesitter (future)
70
- },
71
-
72
- // LSP client defaults
73
- lsp: {
74
- requestTimeoutMs: Number(process.env.LSP_REQUEST_TIMEOUT_MS || 60000),
75
- },
76
-
77
- // Indexing (code index) settings
78
- indexing: {
79
- // Enable periodic incremental index updates (polling watcher)
80
- watch: (process.env.INDEX_WATCH || 'false').toLowerCase() === 'true',
81
- // Polling interval (ms)
82
- intervalMs: Number(process.env.INDEX_WATCH_INTERVAL_MS || 15000),
83
- // Build options
84
- concurrency: Number(process.env.INDEX_CONCURRENCY || 8),
85
- retry: Number(process.env.INDEX_RETRY || 2),
86
- reportEvery: Number(process.env.INDEX_REPORT_EVERY || 500),
87
- },
88
- };
89
-
90
- /**
91
- * External config resolution (no legacy compatibility):
92
- * Priority:
93
- * 1) UNITY_MCP_CONFIG (explicit file path)
94
- * 2) ./.unity/config.json (project-local)
95
- * 3) ~/.unity/config.json (user-global)
96
- * If none found, create ./.unity/config.json with defaults.
97
- */
98
- function ensureDefaultProjectConfig(baseDir) {
99
- const dir = path.resolve(baseDir, '.unity');
100
- const file = path.join(dir, 'config.json');
101
-
102
- try {
103
- if (!fs.existsSync(dir)) {
104
- fs.mkdirSync(dir, { recursive: true });
105
- }
106
-
107
- if (!fs.existsSync(file)) {
108
- const inferredRoot = fs.existsSync(path.join(baseDir, 'Assets')) ? baseDir : '';
109
- const defaultConfig = {
110
- unity: {
111
- unityHost: 'localhost',
112
- mcpHost: 'localhost',
113
- port: 6400,
114
- },
115
- project: {
116
- root: inferredRoot ? inferredRoot.replace(/\\/g, '/') : '',
117
- },
118
- };
119
- fs.writeFileSync(file, `${JSON.stringify(defaultConfig, null, 2)}\n`, 'utf8');
120
- }
121
- return file;
122
- } catch (error) {
123
- return null;
124
- }
125
- }
126
-
127
- function loadExternalConfig() {
128
- const explicitPath = process.env.UNITY_MCP_CONFIG;
129
-
130
- const projectPath = findUpSync((directory) => {
131
- const candidate = path.resolve(directory, '.unity', 'config.json');
132
- return fs.existsSync(candidate) ? candidate : undefined;
133
- }, { cwd: process.cwd() });
134
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
135
- const userPath = homeDir ? path.resolve(homeDir, '.unity', 'config.json') : null;
136
-
137
- const candidates = [explicitPath, projectPath, userPath].filter(Boolean);
138
- for (const p of candidates) {
139
- try {
140
- if (p && fs.existsSync(p)) {
141
- const raw = fs.readFileSync(p, 'utf8');
142
- const json = JSON.parse(raw);
143
- const out = json && typeof json === 'object' ? json : {};
144
- out.__configPath = p;
145
- return out;
146
- }
147
- } catch (e) {
148
- return { __configLoadError: `${p}: ${e.message}` };
149
- }
150
- }
151
- const fallbackPath = ensureDefaultProjectConfig(process.cwd());
152
- if (fallbackPath && fs.existsSync(fallbackPath)) {
153
- try {
154
- const raw = fs.readFileSync(fallbackPath, 'utf8');
155
- const json = JSON.parse(raw);
156
- const out = json && typeof json === 'object' ? json : {};
157
- out.__configPath = fallbackPath;
158
- out.__configGenerated = true;
159
- return out;
160
- } catch (e) {
161
- return { __configLoadError: `${fallbackPath}: ${e.message}` };
162
- }
163
- }
164
- return {};
165
- }
166
-
167
- const external = loadExternalConfig();
168
- export const config = merge(baseConfig, external);
169
-
170
- const normalizeUnityConfig = () => {
171
- const unityConfig = config.unity || (config.unity = {});
172
-
173
- // Legacy aliases coming from config files or env vars
174
- const legacyHost = unityConfig.host;
175
- const legacyClientHost = unityConfig.clientHost;
176
- const legacyBindHost = unityConfig.bindHost;
177
-
178
- if (!unityConfig.unityHost) {
179
- unityConfig.unityHost = legacyBindHost || legacyHost || envUnityHost || 'localhost';
180
- }
181
-
182
- if (!unityConfig.mcpHost) {
183
- unityConfig.mcpHost = legacyClientHost || envMcpHost || legacyHost || unityConfig.unityHost;
184
- }
185
-
186
- // Keep bindHost for backwards compatibility with legacy code paths
187
- if (!unityConfig.bindHost) {
188
- unityConfig.bindHost = legacyBindHost || envBindHost || unityConfig.unityHost;
189
- }
190
-
191
- // Maintain legacy properties so older handlers keep working
192
- unityConfig.host = unityConfig.unityHost;
193
- unityConfig.clientHost = unityConfig.mcpHost;
194
- };
195
-
196
- normalizeUnityConfig();
197
-
198
- // Workspace root detection: directory that contains .unity/config.json used
199
- const initialCwd = process.cwd();
200
- let workspaceRoot = initialCwd;
201
- try {
202
- if (config.__configPath) {
203
- const cfgDir = path.dirname(config.__configPath); // <workspace>/.unity
204
- workspaceRoot = path.dirname(cfgDir); // <workspace>
205
- }
206
- } catch {}
207
- export const WORKSPACE_ROOT = workspaceRoot;
208
-
209
- /**
210
- * Logger utility
211
- * IMPORTANT: In MCP servers, all stdout output must be JSON-RPC protocol messages.
212
- * Logging must go to stderr to avoid breaking the protocol.
213
- */
214
- export const logger = {
215
- info: (message, ...args) => {
216
- if (['info', 'debug'].includes(config.logging.level)) {
217
- console.error(`${config.logging.prefix} ${message}`, ...args);
218
- }
219
- },
220
-
221
- warn: (message, ...args) => {
222
- if (['info', 'debug', 'warn'].includes(config.logging.level)) {
223
- console.error(`${config.logging.prefix} WARN: ${message}`, ...args);
224
- }
225
- },
226
-
227
- error: (message, ...args) => {
228
- console.error(`${config.logging.prefix} ERROR: ${message}`, ...args);
229
- },
230
-
231
- debug: (message, ...args) => {
232
- if (config.logging.level === 'debug') {
233
- console.error(`${config.logging.prefix} DEBUG: ${message}`, ...args);
234
- }
235
- }
236
- };
237
-
238
- // Late log if external config failed to load
239
- if (config.__configLoadError) {
240
- console.error(`${baseConfig.logging.prefix} WARN: Failed to load external config: ${config.__configLoadError}`);
241
- delete config.__configLoadError;
242
- }
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { findUpSync } from 'find-up';
4
+
5
+ /**
6
+ * Shallow merge utility (simple objects only)
7
+ */
8
+ function merge(a, b) {
9
+ const out = { ...a };
10
+ for (const [k, v] of Object.entries(b || {})) {
11
+ if (v && typeof v === 'object' && !Array.isArray(v) && a[k] && typeof a[k] === 'object') {
12
+ out[k] = { ...a[k], ...v };
13
+ } else {
14
+ out[k] = v;
15
+ }
16
+ }
17
+ return out;
18
+ }
19
+
20
+ /**
21
+ * Base configuration for Unity Editor MCP Server
22
+ */
23
+ const envUnityHost =
24
+ process.env.UNITY_UNITY_HOST ||
25
+ process.env.UNITY_BIND_HOST ||
26
+ process.env.UNITY_HOST ||
27
+ null;
28
+
29
+ const envMcpHost =
30
+ process.env.UNITY_MCP_HOST ||
31
+ process.env.UNITY_CLIENT_HOST ||
32
+ process.env.UNITY_HOST ||
33
+ null;
34
+
35
+ const envBindHost = process.env.UNITY_BIND_HOST || null;
36
+
37
+ const baseConfig = {
38
+ // Unity connection settings
39
+ unity: {
40
+ unityHost: envUnityHost,
41
+ mcpHost: envMcpHost,
42
+ bindHost: envBindHost,
43
+ port: parseInt(process.env.UNITY_PORT || '', 10) || 6400,
44
+ reconnectDelay: 1000,
45
+ maxReconnectDelay: 30000,
46
+ reconnectBackoffMultiplier: 2,
47
+ commandTimeout: 30000,
48
+ },
49
+
50
+ // Server settings
51
+ server: {
52
+ name: 'unity-mcp-server',
53
+ version: '0.1.0',
54
+ description: 'MCP server for Unity Editor integration',
55
+ },
56
+
57
+ // Logging settings
58
+ logging: {
59
+ level: process.env.LOG_LEVEL || 'info',
60
+ prefix: '[Unity Editor MCP]',
61
+ },
62
+
63
+ // Write queue removed: all edits go through structured Roslyn tools.
64
+
65
+ // Search-related defaults and engine selection
66
+ search: {
67
+ // detail alias: 'compact' maps to returnMode 'snippets'
68
+ defaultDetail: (process.env.SEARCH_DEFAULT_DETAIL || 'compact').toLowerCase(), // compact|metadata|snippets|full
69
+ engine: (process.env.SEARCH_ENGINE || 'naive').toLowerCase(), // naive|treesitter (future)
70
+ },
71
+
72
+ // LSP client defaults
73
+ lsp: {
74
+ requestTimeoutMs: Number(process.env.LSP_REQUEST_TIMEOUT_MS || 60000),
75
+ },
76
+
77
+ // Indexing (code index) settings
78
+ indexing: {
79
+ // Enable periodic incremental index updates (polling watcher)
80
+ watch: (process.env.INDEX_WATCH || 'false').toLowerCase() === 'true',
81
+ // Polling interval (ms)
82
+ intervalMs: Number(process.env.INDEX_WATCH_INTERVAL_MS || 15000),
83
+ // Build options
84
+ concurrency: Number(process.env.INDEX_CONCURRENCY || 8),
85
+ retry: Number(process.env.INDEX_RETRY || 2),
86
+ reportEvery: Number(process.env.INDEX_REPORT_EVERY || 500),
87
+ },
88
+ };
89
+
90
+ /**
91
+ * External config resolution (no legacy compatibility):
92
+ * Priority:
93
+ * 1) UNITY_MCP_CONFIG (explicit file path)
94
+ * 2) ./.unity/config.json (project-local)
95
+ * 3) ~/.unity/config.json (user-global)
96
+ * If none found, create ./.unity/config.json with defaults.
97
+ */
98
+ function ensureDefaultProjectConfig(baseDir) {
99
+ const dir = path.resolve(baseDir, '.unity');
100
+ const file = path.join(dir, 'config.json');
101
+
102
+ try {
103
+ if (!fs.existsSync(dir)) {
104
+ fs.mkdirSync(dir, { recursive: true });
105
+ }
106
+
107
+ if (!fs.existsSync(file)) {
108
+ const inferredRoot = fs.existsSync(path.join(baseDir, 'Assets')) ? baseDir : '';
109
+ const defaultConfig = {
110
+ unity: {
111
+ unityHost: 'localhost',
112
+ mcpHost: 'localhost',
113
+ port: 6400,
114
+ },
115
+ project: {
116
+ root: inferredRoot ? inferredRoot.replace(/\\/g, '/') : '',
117
+ },
118
+ };
119
+ fs.writeFileSync(file, `${JSON.stringify(defaultConfig, null, 2)}\n`, 'utf8');
120
+ }
121
+ return file;
122
+ } catch (error) {
123
+ return null;
124
+ }
125
+ }
126
+
127
+ function loadExternalConfig() {
128
+ const explicitPath = process.env.UNITY_MCP_CONFIG;
129
+
130
+ const projectPath = findUpSync((directory) => {
131
+ const candidate = path.resolve(directory, '.unity', 'config.json');
132
+ return fs.existsSync(candidate) ? candidate : undefined;
133
+ }, { cwd: process.cwd() });
134
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '';
135
+ const userPath = homeDir ? path.resolve(homeDir, '.unity', 'config.json') : null;
136
+
137
+ const candidates = [explicitPath, projectPath, userPath].filter(Boolean);
138
+ for (const p of candidates) {
139
+ try {
140
+ if (p && fs.existsSync(p)) {
141
+ const raw = fs.readFileSync(p, 'utf8');
142
+ const json = JSON.parse(raw);
143
+ const out = json && typeof json === 'object' ? json : {};
144
+ out.__configPath = p;
145
+ return out;
146
+ }
147
+ } catch (e) {
148
+ return { __configLoadError: `${p}: ${e.message}` };
149
+ }
150
+ }
151
+ const fallbackPath = ensureDefaultProjectConfig(process.cwd());
152
+ if (fallbackPath && fs.existsSync(fallbackPath)) {
153
+ try {
154
+ const raw = fs.readFileSync(fallbackPath, 'utf8');
155
+ const json = JSON.parse(raw);
156
+ const out = json && typeof json === 'object' ? json : {};
157
+ out.__configPath = fallbackPath;
158
+ out.__configGenerated = true;
159
+ return out;
160
+ } catch (e) {
161
+ return { __configLoadError: `${fallbackPath}: ${e.message}` };
162
+ }
163
+ }
164
+ return {};
165
+ }
166
+
167
+ const external = loadExternalConfig();
168
+ export const config = merge(baseConfig, external);
169
+
170
+ const normalizeUnityConfig = () => {
171
+ const unityConfig = config.unity || (config.unity = {});
172
+
173
+ // Legacy aliases coming from config files or env vars
174
+ const legacyHost = unityConfig.host;
175
+ const legacyClientHost = unityConfig.clientHost;
176
+ const legacyBindHost = unityConfig.bindHost;
177
+
178
+ if (!unityConfig.unityHost) {
179
+ unityConfig.unityHost = legacyBindHost || legacyHost || envUnityHost || 'localhost';
180
+ }
181
+
182
+ if (!unityConfig.mcpHost) {
183
+ unityConfig.mcpHost = legacyClientHost || envMcpHost || legacyHost || unityConfig.unityHost;
184
+ }
185
+
186
+ // Keep bindHost for backwards compatibility with legacy code paths
187
+ if (!unityConfig.bindHost) {
188
+ unityConfig.bindHost = legacyBindHost || envBindHost || unityConfig.unityHost;
189
+ }
190
+
191
+ // Maintain legacy properties so older handlers keep working
192
+ unityConfig.host = unityConfig.unityHost;
193
+ unityConfig.clientHost = unityConfig.mcpHost;
194
+ };
195
+
196
+ normalizeUnityConfig();
197
+
198
+ // Workspace root detection: directory that contains .unity/config.json used
199
+ const initialCwd = process.cwd();
200
+ let workspaceRoot = initialCwd;
201
+ try {
202
+ if (config.__configPath) {
203
+ const cfgDir = path.dirname(config.__configPath); // <workspace>/.unity
204
+ workspaceRoot = path.dirname(cfgDir); // <workspace>
205
+ }
206
+ } catch {}
207
+ export const WORKSPACE_ROOT = workspaceRoot;
208
+
209
+ /**
210
+ * Logger utility
211
+ * IMPORTANT: In MCP servers, all stdout output must be JSON-RPC protocol messages.
212
+ * Logging must go to stderr to avoid breaking the protocol.
213
+ */
214
+ export const logger = {
215
+ info: (message, ...args) => {
216
+ if (['info', 'debug'].includes(config.logging.level)) {
217
+ console.error(`${config.logging.prefix} ${message}`, ...args);
218
+ }
219
+ },
220
+
221
+ warn: (message, ...args) => {
222
+ if (['info', 'debug', 'warn'].includes(config.logging.level)) {
223
+ console.error(`${config.logging.prefix} WARN: ${message}`, ...args);
224
+ }
225
+ },
226
+
227
+ error: (message, ...args) => {
228
+ console.error(`${config.logging.prefix} ERROR: ${message}`, ...args);
229
+ },
230
+
231
+ debug: (message, ...args) => {
232
+ if (config.logging.level === 'debug') {
233
+ console.error(`${config.logging.prefix} DEBUG: ${message}`, ...args);
234
+ }
235
+ }
236
+ };
237
+
238
+ // Late log if external config failed to load
239
+ if (config.__configLoadError) {
240
+ console.error(`${baseConfig.logging.prefix} WARN: Failed to load external config: ${config.__configLoadError}`);
241
+ delete config.__configLoadError;
242
+ }
@@ -1,10 +1,9 @@
1
- import fs from 'fs';
2
1
  import path from 'path';
3
2
  import { logger, config, WORKSPACE_ROOT } from './config.js';
4
3
 
5
- const normalize = (p) => p.replace(/\\/g, '/');
4
+ const normalize = p => p.replace(/\\/g, '/');
6
5
 
7
- const resolveDefaultCodeIndexRoot = (projectRoot) => {
6
+ const resolveDefaultCodeIndexRoot = projectRoot => {
8
7
  const base = WORKSPACE_ROOT || projectRoot || process.cwd();
9
8
  return normalize(path.join(base, '.unity', 'cache', 'code-index'));
10
9
  };
@@ -24,19 +23,23 @@ export class ProjectInfoProvider {
24
23
  if (typeof cfgRootRaw === 'string' && cfgRootRaw.trim().length > 0) {
25
24
  const cfgRoot = cfgRootRaw.trim();
26
25
  // 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));
26
+ const projectRoot = normalize(
27
+ path.isAbsolute(cfgRoot) ? cfgRoot : path.resolve(WORKSPACE_ROOT, cfgRoot)
28
+ );
29
+ const codeIndexRoot = normalize(
30
+ config?.project?.codeIndexRoot || resolveDefaultCodeIndexRoot(projectRoot)
31
+ );
29
32
  this.cached = {
30
33
  projectRoot,
31
34
  assetsPath: normalize(path.join(projectRoot, 'Assets')),
32
35
  packagesPath: normalize(path.join(projectRoot, 'Packages')),
33
- codeIndexRoot,
36
+ codeIndexRoot
34
37
  };
35
38
  return this.cached;
36
39
  }
37
40
  // Try Unity if connected (rate-limit attempts)
38
41
  const now = Date.now();
39
- if (this.unityConnection && this.unityConnection.isConnected() && (now - this.lastTried > 1000)) {
42
+ if (this.unityConnection && this.unityConnection.isConnected() && now - this.lastTried > 1000) {
40
43
  this.lastTried = now;
41
44
  try {
42
45
  const info = await this.unityConnection.sendCommand('get_editor_info', {});
@@ -45,7 +48,9 @@ export class ProjectInfoProvider {
45
48
  projectRoot: info.projectRoot,
46
49
  assetsPath: info.assetsPath,
47
50
  packagesPath: normalize(info.packagesPath || path.join(info.projectRoot, 'Packages')),
48
- codeIndexRoot: normalize(info.codeIndexRoot || resolveDefaultCodeIndexRoot(info.projectRoot)),
51
+ codeIndexRoot: normalize(
52
+ info.codeIndexRoot || resolveDefaultCodeIndexRoot(info.projectRoot)
53
+ )
49
54
  };
50
55
  return this.cached;
51
56
  }
@@ -54,9 +59,13 @@ export class ProjectInfoProvider {
54
59
  }
55
60
  }
56
61
  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.');
62
+ throw new Error(
63
+ 'project.root is configured but empty. Set a valid path in .unity/config.json or UNITY_MCP_CONFIG.'
64
+ );
58
65
  }
59
- throw new Error('Unable to resolve Unity project root. Configure project.root in .unity/config.json or provide UNITY_MCP_CONFIG.');
66
+ throw new Error(
67
+ 'Unable to resolve Unity project root. Configure project.root in .unity/config.json or provide UNITY_MCP_CONFIG.'
68
+ );
60
69
  }
61
70
 
62
71
  inferFromCwd() {