@akiojin/unity-mcp-server 2.42.1 → 2.42.2
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/bin/unity-mcp-server +89 -5
- package/package.json +2 -2
- package/src/cli/commands/listInstances.js +10 -0
- package/src/cli/commands/setActive.js +6 -0
- package/src/core/config.js +313 -267
- package/src/core/httpServer.js +147 -0
- package/src/core/instanceRegistry.js +73 -0
- package/src/core/server.js +41 -7
- package/src/core/unityConnection.js +9 -2
package/bin/unity-mcp-server
CHANGED
|
@@ -1,8 +1,92 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { startServer } from '../src/core/server.js';
|
|
3
|
+
import { listInstances } from '../src/cli/commands/listInstances.js';
|
|
4
|
+
import { setActive } from '../src/cli/commands/setActive.js';
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const command = args[0] && !args[0].startsWith('--') ? args[0] : null;
|
|
8
|
+
const rest = command ? args.slice(1) : args;
|
|
9
|
+
let httpEnabled = false;
|
|
10
|
+
let httpPort;
|
|
11
|
+
let stdioEnabled = true;
|
|
12
|
+
let telemetryEnabled;
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < rest.length; i++) {
|
|
15
|
+
const arg = rest[i];
|
|
16
|
+
switch (arg) {
|
|
17
|
+
case '--http':
|
|
18
|
+
httpEnabled = true;
|
|
19
|
+
if (args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
20
|
+
httpPort = parseInt(args[i + 1], 10);
|
|
21
|
+
i++;
|
|
22
|
+
}
|
|
23
|
+
break;
|
|
24
|
+
case '--no-http':
|
|
25
|
+
httpEnabled = false;
|
|
26
|
+
break;
|
|
27
|
+
case '--stdio':
|
|
28
|
+
stdioEnabled = true;
|
|
29
|
+
break;
|
|
30
|
+
case '--no-stdio':
|
|
31
|
+
stdioEnabled = false;
|
|
32
|
+
break;
|
|
33
|
+
case '--telemetry':
|
|
34
|
+
telemetryEnabled = true;
|
|
35
|
+
break;
|
|
36
|
+
case '--no-telemetry':
|
|
37
|
+
telemetryEnabled = false;
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function main() {
|
|
45
|
+
if (command === 'list-instances') {
|
|
46
|
+
const portsArg = rest.find(a => a.startsWith('--ports='));
|
|
47
|
+
const ports = portsArg ? portsArg.replace('--ports=', '').split(',').map(p => Number(p)) : [];
|
|
48
|
+
const hostArg = rest.find(a => a.startsWith('--host='));
|
|
49
|
+
const host = hostArg ? hostArg.replace('--host=', '') : 'localhost';
|
|
50
|
+
const json = rest.includes('--json');
|
|
51
|
+
const list = await listInstances({ ports, host });
|
|
52
|
+
if (json) {
|
|
53
|
+
console.log(JSON.stringify(list, null, 2));
|
|
54
|
+
} else {
|
|
55
|
+
for (const e of list) {
|
|
56
|
+
console.log(`${e.active ? '*' : ' '} ${e.id} ${e.status}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (command === 'set-active') {
|
|
63
|
+
const id = rest[0];
|
|
64
|
+
if (!id) {
|
|
65
|
+
console.error('Usage: unity-mcp-server set-active <host:port>');
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const result = await setActive({ id });
|
|
70
|
+
console.log(JSON.stringify(result, null, 2));
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.error(e.message);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
startServer({
|
|
79
|
+
http: {
|
|
80
|
+
enabled: httpEnabled,
|
|
81
|
+
port: httpPort
|
|
82
|
+
},
|
|
83
|
+
telemetry: telemetryEnabled === undefined ? undefined : { enabled: telemetryEnabled },
|
|
84
|
+
stdioEnabled
|
|
85
|
+
}).catch(error => {
|
|
86
|
+
console.error('Fatal error:', error);
|
|
87
|
+
console.error('Stack trace:', error?.stack);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akiojin/unity-mcp-server",
|
|
3
|
-
"version": "2.42.
|
|
3
|
+
"version": "2.42.2",
|
|
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",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"author": "Akio Jinsenji <akio-jinsenji@cloud-creative-studios.com>",
|
|
51
51
|
"license": "MIT",
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@modelcontextprotocol/sdk": "^1.24.
|
|
53
|
+
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
54
54
|
"better-sqlite3": "^9.4.3",
|
|
55
55
|
"find-up": "^6.3.0"
|
|
56
56
|
},
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { InstanceRegistry } from '../../core/instanceRegistry.js';
|
|
2
|
+
|
|
3
|
+
export async function listInstances({ ports, host = 'localhost', registry }) {
|
|
4
|
+
const reg = registry || new InstanceRegistry();
|
|
5
|
+
if (Array.isArray(ports)) {
|
|
6
|
+
ports.forEach(p => reg.add({ host, port: Number(p) }));
|
|
7
|
+
}
|
|
8
|
+
await reg.refreshStatus();
|
|
9
|
+
return reg.list();
|
|
10
|
+
}
|
package/src/core/config.js
CHANGED
|
@@ -1,267 +1,313 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import * as findUpPkg from 'find-up';
|
|
4
|
+
function findUpSyncCompat(matcher, options = {}) {
|
|
5
|
+
if (typeof matcher === 'function') {
|
|
6
|
+
let dir = options.cwd || process.cwd();
|
|
7
|
+
const { root } = path.parse(dir);
|
|
8
|
+
// walk up until root
|
|
9
|
+
while (true) {
|
|
10
|
+
const found = matcher(dir);
|
|
11
|
+
if (found) return found;
|
|
12
|
+
if (dir === root) return undefined;
|
|
13
|
+
dir = path.dirname(dir);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (typeof findUpPkg.sync === 'function') return findUpPkg.sync(matcher, options);
|
|
17
|
+
if (typeof findUpPkg === 'function') return findUpPkg(matcher, options);
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Shallow merge utility (simple objects only)
|
|
23
|
+
*/
|
|
24
|
+
function merge(a, b) {
|
|
25
|
+
const out = { ...a };
|
|
26
|
+
for (const [k, v] of Object.entries(b || {})) {
|
|
27
|
+
if (v && typeof v === 'object' && !Array.isArray(v) && a[k] && typeof a[k] === 'object') {
|
|
28
|
+
out[k] = { ...a[k], ...v };
|
|
29
|
+
} else {
|
|
30
|
+
out[k] = v;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolvePackageVersion() {
|
|
37
|
+
const candidates = [];
|
|
38
|
+
|
|
39
|
+
// Resolve relative to this module (always inside mcp-server/src/core)
|
|
40
|
+
try {
|
|
41
|
+
const moduleDir = path.dirname(new URL(import.meta.url).pathname);
|
|
42
|
+
candidates.push(path.resolve(moduleDir, '../../package.json'));
|
|
43
|
+
} catch {}
|
|
44
|
+
|
|
45
|
+
// When executed from workspace root (monorepo) or inside mcp-server package
|
|
46
|
+
try {
|
|
47
|
+
const here = findUpSyncCompat('package.json', { cwd: process.cwd() });
|
|
48
|
+
if (here) candidates.push(here);
|
|
49
|
+
} catch {}
|
|
50
|
+
|
|
51
|
+
for (const candidate of candidates) {
|
|
52
|
+
try {
|
|
53
|
+
if (!candidate || !fs.existsSync(candidate)) continue;
|
|
54
|
+
const pkg = JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
55
|
+
if (pkg?.version) return pkg.version;
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return '0.1.0';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Base configuration for Unity MCP Server Server
|
|
64
|
+
*/
|
|
65
|
+
const envUnityHost = process.env.UNITY_BIND_HOST || process.env.UNITY_HOST || null;
|
|
66
|
+
|
|
67
|
+
const envMcpHost =
|
|
68
|
+
process.env.UNITY_MCP_HOST || process.env.UNITY_CLIENT_HOST || process.env.UNITY_HOST || null;
|
|
69
|
+
|
|
70
|
+
const envBindHost = process.env.UNITY_BIND_HOST || null;
|
|
71
|
+
|
|
72
|
+
const baseConfig = {
|
|
73
|
+
// Unity connection settings
|
|
74
|
+
unity: {
|
|
75
|
+
unityHost: envUnityHost,
|
|
76
|
+
mcpHost: envMcpHost,
|
|
77
|
+
bindHost: envBindHost,
|
|
78
|
+
port: parseInt(process.env.UNITY_PORT || '', 10) || 6400,
|
|
79
|
+
reconnectDelay: 1000,
|
|
80
|
+
maxReconnectDelay: 30000,
|
|
81
|
+
reconnectBackoffMultiplier: 2,
|
|
82
|
+
commandTimeout: 30000
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// Server settings
|
|
86
|
+
server: {
|
|
87
|
+
name: 'unity-mcp-server',
|
|
88
|
+
version: resolvePackageVersion(),
|
|
89
|
+
description: 'MCP server for Unity Editor integration'
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// Logging settings
|
|
93
|
+
logging: {
|
|
94
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
95
|
+
prefix: '[unity-mcp-server]'
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// HTTP transport (off by default)
|
|
99
|
+
http: {
|
|
100
|
+
enabled: (process.env.UNITY_MCP_HTTP_ENABLED || 'false').toLowerCase() === 'true',
|
|
101
|
+
host: process.env.UNITY_MCP_HTTP_HOST || '0.0.0.0',
|
|
102
|
+
port: parseInt(process.env.UNITY_MCP_HTTP_PORT || '', 10) || 6401,
|
|
103
|
+
healthPath: '/healthz',
|
|
104
|
+
allowedHosts: (process.env.UNITY_MCP_HTTP_ALLOWED_HOSTS || 'localhost,127.0.0.1')
|
|
105
|
+
.split(',')
|
|
106
|
+
.map(h => h.trim())
|
|
107
|
+
.filter(h => h.length > 0)
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
telemetry: {
|
|
111
|
+
enabled: (process.env.UNITY_MCP_TELEMETRY || 'off').toLowerCase() === 'on',
|
|
112
|
+
destinations: [],
|
|
113
|
+
fields: []
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
// Write queue removed: all edits go through structured Roslyn tools.
|
|
117
|
+
|
|
118
|
+
// Search-related defaults and engine selection
|
|
119
|
+
search: {
|
|
120
|
+
// detail alias: 'compact' maps to returnMode 'snippets'
|
|
121
|
+
defaultDetail: (process.env.SEARCH_DEFAULT_DETAIL || 'compact').toLowerCase(), // compact|metadata|snippets|full
|
|
122
|
+
engine: (process.env.SEARCH_ENGINE || 'naive').toLowerCase() // naive|treesitter (future)
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// LSP client defaults
|
|
126
|
+
lsp: {
|
|
127
|
+
requestTimeoutMs: Number(process.env.LSP_REQUEST_TIMEOUT_MS || 60000)
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// Indexing (code index) settings
|
|
131
|
+
indexing: {
|
|
132
|
+
// Enable periodic incremental index updates (polling watcher)
|
|
133
|
+
watch: true,
|
|
134
|
+
// Polling interval (ms)
|
|
135
|
+
intervalMs: Number(process.env.INDEX_WATCH_INTERVAL_MS || 15000),
|
|
136
|
+
// Build options
|
|
137
|
+
concurrency: Number(process.env.INDEX_CONCURRENCY || 8),
|
|
138
|
+
retry: Number(process.env.INDEX_RETRY || 2),
|
|
139
|
+
reportEvery: Number(process.env.INDEX_REPORT_EVERY || 500)
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* External config resolution (no legacy compatibility):
|
|
145
|
+
* Priority:
|
|
146
|
+
* 1) UNITY_MCP_CONFIG (explicit file path)
|
|
147
|
+
* 2) ./.unity/config.json (project-local)
|
|
148
|
+
* 3) ~/.unity/config.json (user-global)
|
|
149
|
+
* If none found, create ./.unity/config.json with defaults.
|
|
150
|
+
*/
|
|
151
|
+
function ensureDefaultProjectConfig(baseDir) {
|
|
152
|
+
const dir = path.resolve(baseDir, '.unity');
|
|
153
|
+
const file = path.join(dir, 'config.json');
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
if (!fs.existsSync(dir)) {
|
|
157
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!fs.existsSync(file)) {
|
|
161
|
+
const inferredRoot = fs.existsSync(path.join(baseDir, 'Assets')) ? baseDir : '';
|
|
162
|
+
const defaultConfig = {
|
|
163
|
+
unity: {
|
|
164
|
+
unityHost: 'localhost',
|
|
165
|
+
mcpHost: 'localhost',
|
|
166
|
+
port: 6400
|
|
167
|
+
},
|
|
168
|
+
project: {
|
|
169
|
+
root: inferredRoot ? inferredRoot.replace(/\\/g, '/') : ''
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
fs.writeFileSync(file, `${JSON.stringify(defaultConfig, null, 2)}\n`, 'utf8');
|
|
173
|
+
}
|
|
174
|
+
return file;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function loadExternalConfig() {
|
|
181
|
+
if (typeof findUpSyncCompat !== 'function') {
|
|
182
|
+
return {};
|
|
183
|
+
}
|
|
184
|
+
const explicitPath = process.env.UNITY_MCP_CONFIG;
|
|
185
|
+
|
|
186
|
+
const projectPath = findUpSyncCompat(
|
|
187
|
+
directory => {
|
|
188
|
+
const candidate = path.resolve(directory, '.unity', 'config.json');
|
|
189
|
+
return fs.existsSync(candidate) ? candidate : undefined;
|
|
190
|
+
},
|
|
191
|
+
{ cwd: process.cwd() }
|
|
192
|
+
);
|
|
193
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
194
|
+
const userPath = homeDir ? path.resolve(homeDir, '.unity', 'config.json') : null;
|
|
195
|
+
|
|
196
|
+
const candidates = [explicitPath, projectPath, userPath].filter(Boolean);
|
|
197
|
+
for (const p of candidates) {
|
|
198
|
+
try {
|
|
199
|
+
if (p && fs.existsSync(p)) {
|
|
200
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
201
|
+
const json = JSON.parse(raw);
|
|
202
|
+
const out = json && typeof json === 'object' ? json : {};
|
|
203
|
+
out.__configPath = p;
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
} catch (e) {
|
|
207
|
+
return { __configLoadError: `${p}: ${e.message}` };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const fallbackPath = ensureDefaultProjectConfig(process.cwd());
|
|
211
|
+
if (fallbackPath && fs.existsSync(fallbackPath)) {
|
|
212
|
+
try {
|
|
213
|
+
const raw = fs.readFileSync(fallbackPath, 'utf8');
|
|
214
|
+
const json = JSON.parse(raw);
|
|
215
|
+
const out = json && typeof json === 'object' ? json : {};
|
|
216
|
+
out.__configPath = fallbackPath;
|
|
217
|
+
out.__configGenerated = true;
|
|
218
|
+
return out;
|
|
219
|
+
} catch (e) {
|
|
220
|
+
return { __configLoadError: `${fallbackPath}: ${e.message}` };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return {};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const external = loadExternalConfig();
|
|
227
|
+
export const config = merge(baseConfig, external);
|
|
228
|
+
|
|
229
|
+
const normalizeUnityConfig = () => {
|
|
230
|
+
const unityConfig = config.unity || (config.unity = {});
|
|
231
|
+
|
|
232
|
+
// Legacy aliases coming from config files or env vars
|
|
233
|
+
const legacyHost = unityConfig.host;
|
|
234
|
+
const legacyClientHost = unityConfig.clientHost;
|
|
235
|
+
const legacyBindHost = unityConfig.bindHost;
|
|
236
|
+
|
|
237
|
+
if (!unityConfig.unityHost) {
|
|
238
|
+
unityConfig.unityHost = legacyBindHost || legacyHost || envUnityHost || 'localhost';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!unityConfig.mcpHost) {
|
|
242
|
+
unityConfig.mcpHost = legacyClientHost || envMcpHost || legacyHost || unityConfig.unityHost;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Keep bindHost for backwards compatibility with legacy code paths
|
|
246
|
+
if (!unityConfig.bindHost) {
|
|
247
|
+
unityConfig.bindHost = legacyBindHost || envBindHost || unityConfig.unityHost;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Maintain legacy properties so older handlers keep working
|
|
251
|
+
unityConfig.host = unityConfig.unityHost;
|
|
252
|
+
unityConfig.clientHost = unityConfig.mcpHost;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
normalizeUnityConfig();
|
|
256
|
+
|
|
257
|
+
// Workspace root detection: directory that contains .unity/config.json used
|
|
258
|
+
const initialCwd = process.cwd();
|
|
259
|
+
let workspaceRoot = initialCwd;
|
|
260
|
+
try {
|
|
261
|
+
if (config.__configPath) {
|
|
262
|
+
const cfgDir = path.dirname(config.__configPath); // <workspace>/.unity
|
|
263
|
+
workspaceRoot = path.dirname(cfgDir); // <workspace>
|
|
264
|
+
}
|
|
265
|
+
} catch {}
|
|
266
|
+
export const WORKSPACE_ROOT = workspaceRoot;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Logger utility
|
|
270
|
+
* IMPORTANT: In MCP servers, all stdout output must be JSON-RPC protocol messages.
|
|
271
|
+
* Logging must go to stderr to avoid breaking the protocol.
|
|
272
|
+
*/
|
|
273
|
+
export const logger = {
|
|
274
|
+
info: (message, ...args) => {
|
|
275
|
+
if (['info', 'debug'].includes(config.logging.level)) {
|
|
276
|
+
console.error(`${config.logging.prefix} ${message}`, ...args);
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
warn: (message, ...args) => {
|
|
281
|
+
if (['info', 'debug', 'warn'].includes(config.logging.level)) {
|
|
282
|
+
console.error(`${config.logging.prefix} WARN: ${message}`, ...args);
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
error: (message, ...args) => {
|
|
287
|
+
console.error(`${config.logging.prefix} ERROR: ${message}`, ...args);
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
debug: (message, ...args) => {
|
|
291
|
+
if (config.logging.level === 'debug') {
|
|
292
|
+
console.error(`${config.logging.prefix} DEBUG: ${message}`, ...args);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Late log if external config failed to load
|
|
298
|
+
if (config.__configLoadError) {
|
|
299
|
+
console.error(
|
|
300
|
+
`${baseConfig.logging.prefix} WARN: Failed to load external config: ${config.__configLoadError}`
|
|
301
|
+
);
|
|
302
|
+
delete config.__configLoadError;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Startup debug log: output config info to stderr for troubleshooting
|
|
306
|
+
// This helps diagnose connection issues (especially in WSL2/Docker environments)
|
|
307
|
+
console.error(`[unity-mcp-server] Startup config:`);
|
|
308
|
+
console.error(`[unity-mcp-server] Config file: ${config.__configPath || '(defaults)'}`);
|
|
309
|
+
console.error(
|
|
310
|
+
`[unity-mcp-server] Unity host: ${config.unity.mcpHost || config.unity.unityHost || 'localhost'}`
|
|
311
|
+
);
|
|
312
|
+
console.error(`[unity-mcp-server] Unity port: ${config.unity.port}`);
|
|
313
|
+
console.error(`[unity-mcp-server] Workspace root: ${WORKSPACE_ROOT}`);
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import { logger } from './config.js';
|
|
3
|
+
|
|
4
|
+
function buildHealthResponse({ startedAt, mode, port, telemetryEnabled }) {
|
|
5
|
+
return {
|
|
6
|
+
status: 'ok',
|
|
7
|
+
mode,
|
|
8
|
+
port,
|
|
9
|
+
telemetryEnabled,
|
|
10
|
+
uptimeMs: Date.now() - startedAt
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function suggestPorts(port) {
|
|
15
|
+
if (!port || typeof port !== 'number') return [];
|
|
16
|
+
return [port + 1, port + 2, port + 11];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createHttpServer({
|
|
20
|
+
handlers,
|
|
21
|
+
host = '0.0.0.0',
|
|
22
|
+
port = 6401,
|
|
23
|
+
telemetryEnabled = false,
|
|
24
|
+
healthPath = '/healthz',
|
|
25
|
+
allowedHosts = ['localhost', '127.0.0.1']
|
|
26
|
+
} = {}) {
|
|
27
|
+
const startedAt = Date.now();
|
|
28
|
+
let server;
|
|
29
|
+
|
|
30
|
+
const listener = async (req, res) => {
|
|
31
|
+
try {
|
|
32
|
+
const hostHeader = req.headers.host?.split(':')[0];
|
|
33
|
+
if (allowedHosts && allowedHosts.length && hostHeader && !allowedHosts.includes(hostHeader)) {
|
|
34
|
+
res.writeHead(403, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
35
|
+
res.end(JSON.stringify({ error: 'forbidden host' }));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
40
|
+
|
|
41
|
+
if (req.method === 'GET' && url.pathname === healthPath) {
|
|
42
|
+
const body = buildHealthResponse({
|
|
43
|
+
startedAt,
|
|
44
|
+
mode: 'http',
|
|
45
|
+
port: server?.address()?.port,
|
|
46
|
+
telemetryEnabled
|
|
47
|
+
});
|
|
48
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
49
|
+
res.end(JSON.stringify(body));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (req.method === 'POST' && url.pathname === '/rpc') {
|
|
54
|
+
const chunks = [];
|
|
55
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
56
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
57
|
+
let payload;
|
|
58
|
+
try {
|
|
59
|
+
payload = JSON.parse(raw || '{}');
|
|
60
|
+
} catch (e) {
|
|
61
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
62
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32700, message: 'Invalid JSON' } }));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { method, params, id } = payload || {};
|
|
67
|
+
if (method === 'tools/list' || method === 'listTools') {
|
|
68
|
+
const tools = Array.from(handlers.values()).map(h => h.getDefinition());
|
|
69
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
70
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', id, result: { tools } }));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (method === 'tools/call' || method === 'callTool') {
|
|
75
|
+
const name = params?.name;
|
|
76
|
+
const args = params?.arguments || {};
|
|
77
|
+
const handler = handlers.get(name);
|
|
78
|
+
if (!handler) {
|
|
79
|
+
res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
80
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', id, error: { code: -32004, message: `Tool not found: ${name}` } }));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const result = await handler.handle(args);
|
|
85
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
86
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', id, result }));
|
|
87
|
+
} catch (e) {
|
|
88
|
+
logger.error(`[http] tool error ${name}: ${e.message}`);
|
|
89
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
90
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', id, error: { code: -32000, message: e.message } }));
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
96
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' } }));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
101
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
102
|
+
} catch (e) {
|
|
103
|
+
logger.error(`[http] unexpected error: ${e.message}`);
|
|
104
|
+
try {
|
|
105
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
106
|
+
res.end(JSON.stringify({ error: 'internal error' }));
|
|
107
|
+
} catch {}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
server = http.createServer(listener);
|
|
112
|
+
|
|
113
|
+
const start = () =>
|
|
114
|
+
new Promise((resolve, reject) => {
|
|
115
|
+
const onError = err => {
|
|
116
|
+
server.off('error', onError);
|
|
117
|
+
if (err.code === 'EADDRINUSE') {
|
|
118
|
+
const suggestions = suggestPorts(port);
|
|
119
|
+
const msg = `Port ${port} is already in use. Try: ${suggestions.join(', ')}`;
|
|
120
|
+
logger.error(msg);
|
|
121
|
+
}
|
|
122
|
+
reject(err);
|
|
123
|
+
};
|
|
124
|
+
server.once('error', onError);
|
|
125
|
+
server.listen(port, host, () => {
|
|
126
|
+
server.off('error', onError);
|
|
127
|
+
const address = server.address();
|
|
128
|
+
logger.info(`HTTP listening on http://${host}:${address.port}, telemetry: ${telemetryEnabled ? 'on' : 'off'}`);
|
|
129
|
+
resolve(address.port);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const close = () =>
|
|
134
|
+
new Promise((resolve, reject) => {
|
|
135
|
+
server.close(err => {
|
|
136
|
+
if (err) reject(err);
|
|
137
|
+
else resolve();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
start,
|
|
143
|
+
close,
|
|
144
|
+
getPort: () => server.address()?.port,
|
|
145
|
+
health: () => buildHealthResponse({ startedAt, mode: 'http', port: server.address()?.port, telemetryEnabled })
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import net from 'node:net';
|
|
2
|
+
|
|
3
|
+
async function ping(host, port, timeoutMs = 1000) {
|
|
4
|
+
return new Promise(resolve => {
|
|
5
|
+
const socket = net.connect({ host, port });
|
|
6
|
+
const timer = setTimeout(() => {
|
|
7
|
+
socket.destroy();
|
|
8
|
+
resolve(false);
|
|
9
|
+
}, timeoutMs);
|
|
10
|
+
|
|
11
|
+
socket.on('connect', () => {
|
|
12
|
+
clearTimeout(timer);
|
|
13
|
+
socket.end();
|
|
14
|
+
resolve(true);
|
|
15
|
+
});
|
|
16
|
+
socket.on('error', () => {
|
|
17
|
+
clearTimeout(timer);
|
|
18
|
+
resolve(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class InstanceRegistry {
|
|
24
|
+
constructor(entries = []) {
|
|
25
|
+
this.entries = [];
|
|
26
|
+
this.activeId = null;
|
|
27
|
+
entries.forEach(e => this.add(e));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
add(entry) {
|
|
31
|
+
const id = entry.id || `${entry.host}:${entry.port}`;
|
|
32
|
+
if (this.entries.find(e => e.id === id)) return;
|
|
33
|
+
this.entries.push({
|
|
34
|
+
id,
|
|
35
|
+
host: entry.host,
|
|
36
|
+
port: entry.port,
|
|
37
|
+
status: entry.status || 'unknown',
|
|
38
|
+
lastCheckedAt: entry.lastCheckedAt,
|
|
39
|
+
active: false
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
remove(id) {
|
|
44
|
+
this.entries = this.entries.filter(e => e.id !== id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
list() {
|
|
48
|
+
return this.entries.map(e => ({ ...e, active: e.id === this.activeId }));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async refreshStatus(timeoutMs = 1000) {
|
|
52
|
+
for (const entry of this.entries) {
|
|
53
|
+
const ok = await ping(entry.host, entry.port, timeoutMs);
|
|
54
|
+
entry.status = ok ? 'up' : 'down';
|
|
55
|
+
entry.lastCheckedAt = new Date().toISOString();
|
|
56
|
+
}
|
|
57
|
+
return this.list();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async setActive(id, { timeoutMs = 1000 } = {}) {
|
|
61
|
+
const entry = this.entries.find(e => e.id === id);
|
|
62
|
+
if (!entry) throw new Error(`Instance not found: ${id}`);
|
|
63
|
+
|
|
64
|
+
const ok = await ping(entry.host, entry.port, timeoutMs);
|
|
65
|
+
if (!ok) throw new Error(`Instance unreachable: ${id}`);
|
|
66
|
+
|
|
67
|
+
const previousId = this.activeId;
|
|
68
|
+
this.activeId = id;
|
|
69
|
+
entry.status = 'up';
|
|
70
|
+
entry.lastCheckedAt = new Date().toISOString();
|
|
71
|
+
return { activeId: id, previousId };
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/core/server.js
CHANGED
|
@@ -162,21 +162,53 @@ unityConnection.on('error', error => {
|
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
// Initialize server
|
|
165
|
-
export async function startServer() {
|
|
165
|
+
export async function startServer(options = {}) {
|
|
166
166
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
const runtimeConfig = {
|
|
168
|
+
...config,
|
|
169
|
+
http: { ...config.http, ...(options.http || {}) },
|
|
170
|
+
telemetry: { ...config.telemetry, ...(options.telemetry || {}) },
|
|
171
|
+
stdioEnabled: options.stdioEnabled !== undefined ? options.stdioEnabled : true
|
|
172
|
+
};
|
|
169
173
|
|
|
170
|
-
//
|
|
171
|
-
|
|
174
|
+
// Create transport - no logging before connection
|
|
175
|
+
let transport;
|
|
176
|
+
if (runtimeConfig.stdioEnabled !== false) {
|
|
177
|
+
console.error(`[unity-mcp-server] MCP transport connecting...`);
|
|
178
|
+
transport = new HybridStdioServerTransport();
|
|
179
|
+
await server.connect(transport);
|
|
180
|
+
console.error(`[unity-mcp-server] MCP transport connected`);
|
|
181
|
+
}
|
|
172
182
|
|
|
173
183
|
// Now safe to log after connection established
|
|
174
184
|
logger.info('MCP server started successfully');
|
|
175
185
|
|
|
186
|
+
// Optional HTTP transport
|
|
187
|
+
let httpServerInstance;
|
|
188
|
+
if (runtimeConfig.http?.enabled) {
|
|
189
|
+
const { createHttpServer } = await import('./httpServer.js');
|
|
190
|
+
httpServerInstance = createHttpServer({
|
|
191
|
+
handlers,
|
|
192
|
+
host: runtimeConfig.http.host,
|
|
193
|
+
port: runtimeConfig.http.port,
|
|
194
|
+
telemetryEnabled: runtimeConfig.telemetry.enabled,
|
|
195
|
+
healthPath: runtimeConfig.http.healthPath
|
|
196
|
+
});
|
|
197
|
+
try {
|
|
198
|
+
await httpServerInstance.start();
|
|
199
|
+
} catch (err) {
|
|
200
|
+
logger.error(`HTTP server failed to start: ${err.message}`);
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
176
205
|
// Attempt to connect to Unity
|
|
206
|
+
console.error(`[unity-mcp-server] Unity connection starting...`);
|
|
177
207
|
try {
|
|
178
208
|
await unityConnection.connect();
|
|
209
|
+
console.error(`[unity-mcp-server] Unity connection established`);
|
|
179
210
|
} catch (error) {
|
|
211
|
+
console.error(`[unity-mcp-server] Unity connection failed: ${error.message}`);
|
|
180
212
|
logger.error('Initial Unity connection failed:', error.message);
|
|
181
213
|
logger.info('Unity connection will retry automatically');
|
|
182
214
|
}
|
|
@@ -251,14 +283,16 @@ export async function startServer() {
|
|
|
251
283
|
process.on('SIGINT', async () => {
|
|
252
284
|
logger.info('Shutting down...');
|
|
253
285
|
unityConnection.disconnect();
|
|
254
|
-
await server.close();
|
|
286
|
+
if (transport) await server.close();
|
|
287
|
+
if (httpServerInstance) await httpServerInstance.close();
|
|
255
288
|
process.exit(0);
|
|
256
289
|
});
|
|
257
290
|
|
|
258
291
|
process.on('SIGTERM', async () => {
|
|
259
292
|
logger.info('Shutting down...');
|
|
260
293
|
unityConnection.disconnect();
|
|
261
|
-
await server.close();
|
|
294
|
+
if (transport) await server.close();
|
|
295
|
+
if (httpServerInstance) await httpServerInstance.close();
|
|
262
296
|
process.exit(0);
|
|
263
297
|
});
|
|
264
298
|
} catch (error) {
|
|
@@ -48,6 +48,9 @@ export class UnityConnection extends EventEmitter {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const targetHost = config.unity.mcpHost || config.unity.unityHost;
|
|
51
|
+
console.error(
|
|
52
|
+
`[unity-mcp-server] Unity TCP connecting to ${targetHost}:${config.unity.port}...`
|
|
53
|
+
);
|
|
51
54
|
logger.info(`Connecting to Unity at ${targetHost}:${config.unity.port}...`);
|
|
52
55
|
|
|
53
56
|
this.socket = new net.Socket();
|
|
@@ -74,6 +77,7 @@ export class UnityConnection extends EventEmitter {
|
|
|
74
77
|
|
|
75
78
|
// Set up event handlers
|
|
76
79
|
this.socket.on('connect', () => {
|
|
80
|
+
console.error(`[unity-mcp-server] Unity TCP connected successfully`);
|
|
77
81
|
logger.info('Connected to Unity Editor');
|
|
78
82
|
this.connected = true;
|
|
79
83
|
this.reconnectAttempts = 0;
|
|
@@ -93,6 +97,7 @@ export class UnityConnection extends EventEmitter {
|
|
|
93
97
|
});
|
|
94
98
|
|
|
95
99
|
this.socket.on('error', error => {
|
|
100
|
+
console.error(`[unity-mcp-server] Unity TCP error: ${error.message}`);
|
|
96
101
|
logger.error('Socket error:', error.message);
|
|
97
102
|
if (this.listenerCount('error') > 0) {
|
|
98
103
|
this.emit('error', error);
|
|
@@ -121,9 +126,11 @@ export class UnityConnection extends EventEmitter {
|
|
|
121
126
|
|
|
122
127
|
// Check if we're already handling disconnection
|
|
123
128
|
if (this.isDisconnecting || !this.socket) {
|
|
129
|
+
console.error(`[unity-mcp-server] Unity TCP close event (already disconnecting)`);
|
|
124
130
|
return;
|
|
125
131
|
}
|
|
126
132
|
|
|
133
|
+
console.error(`[unity-mcp-server] Unity TCP disconnected`);
|
|
127
134
|
logger.info('Disconnected from Unity Editor');
|
|
128
135
|
this.connected = false;
|
|
129
136
|
this.socket = null;
|
|
@@ -240,7 +247,7 @@ export class UnityConnection extends EventEmitter {
|
|
|
240
247
|
// Check if this is an unframed Unity debug log
|
|
241
248
|
if (data.length > 0 && !this.messageBuffer.length) {
|
|
242
249
|
const dataStr = data.toString('utf8');
|
|
243
|
-
if (dataStr.startsWith('[
|
|
250
|
+
if (dataStr.startsWith('[unity-mcp-server]') || dataStr.startsWith('[Unity]')) {
|
|
244
251
|
logger.debug(`[Unity] Received unframed debug log: ${dataStr.trim()}`);
|
|
245
252
|
// Don't process unframed logs as messages
|
|
246
253
|
return;
|
|
@@ -315,7 +322,7 @@ export class UnityConnection extends EventEmitter {
|
|
|
315
322
|
|
|
316
323
|
// Check if this looks like a Unity log message
|
|
317
324
|
const messageStr = messageData.toString();
|
|
318
|
-
if (messageStr.includes('[
|
|
325
|
+
if (messageStr.includes('[unity-mcp-server]')) {
|
|
319
326
|
logger.debug('[Unity] Received Unity log message instead of JSON response');
|
|
320
327
|
// Don't treat this as a critical error
|
|
321
328
|
}
|