@akiojin/unity-mcp-server 2.25.0 → 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/package.json +1 -1
- package/src/core/config.js +242 -242
- package/src/core/projectInfo.js +65 -65
- package/src/handlers/addressables/AddressablesAnalyzeToolHandler.js +59 -49
- package/src/handlers/addressables/AddressablesBuildToolHandler.js +63 -62
- package/src/handlers/addressables/AddressablesManageToolHandler.js +84 -78
- package/src/handlers/component/ComponentFieldSetToolHandler.js +419 -419
- package/src/handlers/console/ConsoleReadToolHandler.js +295 -295
- package/src/handlers/index.js +437 -437
package/package.json
CHANGED
package/src/core/config.js
CHANGED
|
@@ -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
|
+
}
|
package/src/core/projectInfo.js
CHANGED
|
@@ -1,65 +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 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
|
-
}
|
|
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
|
+
}
|