@akiojin/unity-mcp-server 2.43.2 → 2.43.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/core/server.js
CHANGED
|
@@ -10,7 +10,7 @@ import { UnityConnection } from './unityConnection.js';
|
|
|
10
10
|
import { createHandlers } from '../handlers/index.js';
|
|
11
11
|
import { config, logger } from './config.js';
|
|
12
12
|
import { IndexWatcher } from './indexWatcher.js';
|
|
13
|
-
import {
|
|
13
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
14
|
|
|
15
15
|
// Create Unity connection
|
|
16
16
|
const unityConnection = new UnityConnection();
|
|
@@ -189,7 +189,7 @@ export async function startServer(options = {}) {
|
|
|
189
189
|
let transport;
|
|
190
190
|
if (runtimeConfig.stdioEnabled !== false) {
|
|
191
191
|
console.error(`[unity-mcp-server] MCP transport connecting...`);
|
|
192
|
-
transport = new
|
|
192
|
+
transport = new StdioServerTransport();
|
|
193
193
|
await server.connect(transport);
|
|
194
194
|
console.error(`[unity-mcp-server] MCP transport connected`);
|
|
195
195
|
|
|
@@ -261,41 +261,40 @@ export async function startServer(options = {}) {
|
|
|
261
261
|
process.on('SIGTERM', stopWatch);
|
|
262
262
|
|
|
263
263
|
// Auto-initialize code index if DB doesn't exist
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
// })();
|
|
264
|
+
(async () => {
|
|
265
|
+
try {
|
|
266
|
+
const { CodeIndex } = await import('./codeIndex.js');
|
|
267
|
+
const index = new CodeIndex(unityConnection);
|
|
268
|
+
const ready = await index.isReady();
|
|
269
|
+
|
|
270
|
+
if (!ready) {
|
|
271
|
+
if (index.disabled) {
|
|
272
|
+
logger.warning(
|
|
273
|
+
`[startup] Code index disabled: ${index.disableReason || 'SQLite native binding missing'}. Skipping auto-build.`
|
|
274
|
+
);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
logger.info('[startup] Code index DB not ready. Starting auto-build...');
|
|
278
|
+
const { CodeIndexBuildToolHandler } = await import(
|
|
279
|
+
'../handlers/script/CodeIndexBuildToolHandler.js'
|
|
280
|
+
);
|
|
281
|
+
const builder = new CodeIndexBuildToolHandler(unityConnection);
|
|
282
|
+
const result = await builder.execute({});
|
|
283
|
+
|
|
284
|
+
if (result.success) {
|
|
285
|
+
logger.info(
|
|
286
|
+
`[startup] Code index auto-build started: jobId=${result.jobId}. Use code_index_status to check progress.`
|
|
287
|
+
);
|
|
288
|
+
} else {
|
|
289
|
+
logger.warning(`[startup] Code index auto-build failed: ${result.message}`);
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
logger.info('[startup] Code index DB already exists. Skipping auto-build.');
|
|
293
|
+
}
|
|
294
|
+
} catch (e) {
|
|
295
|
+
logger.warning(`[startup] Code index auto-init failed: ${e.message}`);
|
|
296
|
+
}
|
|
297
|
+
})();
|
|
299
298
|
|
|
300
299
|
// Handle shutdown
|
|
301
300
|
process.on('SIGINT', async () => {
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import process from 'node:process';
|
|
2
|
-
import { JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
-
|
|
4
|
-
const HEADER_END = '\r\n\r\n';
|
|
5
|
-
const HEADER_RE = /Content-Length:\s*(\d+)/i;
|
|
6
|
-
const DEFAULT_BUFFER = Buffer.alloc(0);
|
|
7
|
-
|
|
8
|
-
function encodeContentLength(message) {
|
|
9
|
-
const json = JSON.stringify(message);
|
|
10
|
-
const header = `Content-Length: ${Buffer.byteLength(json, 'utf8')}${HEADER_END}`;
|
|
11
|
-
return header + json;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function parseJson(text) {
|
|
15
|
-
return JSONRPCMessageSchema.parse(JSON.parse(text));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class HybridStdioServerTransport {
|
|
19
|
-
constructor(stdin = process.stdin, stdout = process.stdout) {
|
|
20
|
-
this._stdin = stdin;
|
|
21
|
-
this._stdout = stdout;
|
|
22
|
-
this._buffer = DEFAULT_BUFFER;
|
|
23
|
-
this._started = false;
|
|
24
|
-
this._mode = null; // 'content-length' | 'ndjson'
|
|
25
|
-
|
|
26
|
-
this._onData = chunk => {
|
|
27
|
-
this._buffer = this._buffer.length
|
|
28
|
-
? Buffer.concat([this._buffer, chunk])
|
|
29
|
-
: Buffer.from(chunk);
|
|
30
|
-
this._processBuffer();
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
this._onError = error => {
|
|
34
|
-
this.onerror?.(error);
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
get framingMode() {
|
|
39
|
-
return this._mode;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async start() {
|
|
43
|
-
if (this._started) {
|
|
44
|
-
throw new Error('HybridStdioServerTransport already started');
|
|
45
|
-
}
|
|
46
|
-
this._started = true;
|
|
47
|
-
this._stdin.on('data', this._onData);
|
|
48
|
-
this._stdin.on('error', this._onError);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async close() {
|
|
52
|
-
if (!this._started) return;
|
|
53
|
-
this._stdin.off('data', this._onData);
|
|
54
|
-
this._stdin.off('error', this._onError);
|
|
55
|
-
this._buffer = DEFAULT_BUFFER;
|
|
56
|
-
this._started = false;
|
|
57
|
-
this.onclose?.();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
send(message) {
|
|
61
|
-
return new Promise(resolve => {
|
|
62
|
-
// Always use Content-Length framing for output (MCP protocol standard)
|
|
63
|
-
// Input remains hybrid (Content-Length or NDJSON) for client compatibility
|
|
64
|
-
const payload = encodeContentLength(message);
|
|
65
|
-
if (this._stdout.write(payload)) {
|
|
66
|
-
resolve();
|
|
67
|
-
} else {
|
|
68
|
-
this._stdout.once('drain', resolve);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
_processBuffer() {
|
|
74
|
-
while (true) {
|
|
75
|
-
const message = this._readMessage();
|
|
76
|
-
if (message === null) {
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
this.onmessage?.(message);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
_readMessage() {
|
|
84
|
-
if (!this._buffer || this._buffer.length === 0) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (this._mode === 'content-length') {
|
|
89
|
-
return this._readContentLengthMessage();
|
|
90
|
-
}
|
|
91
|
-
if (this._mode === 'ndjson') {
|
|
92
|
-
return this._readNdjsonMessage();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const prefix = this._peekPrefix();
|
|
96
|
-
if (!prefix.length) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if ('content-length:'.startsWith(prefix.toLowerCase())) {
|
|
101
|
-
return null; // Wait for full header keyword before deciding
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (prefix.toLowerCase().startsWith('content-length:')) {
|
|
105
|
-
this._mode = 'content-length';
|
|
106
|
-
return this._readContentLengthMessage();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const newlineIndex = this._buffer.indexOf(0x0a); // '\n'
|
|
110
|
-
if (newlineIndex === -1) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
this._mode = 'ndjson';
|
|
115
|
-
return this._readNdjsonMessage();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
_peekPrefix() {
|
|
119
|
-
const length = Math.min(this._buffer.length, 32);
|
|
120
|
-
return this._buffer.toString('utf8', 0, length).trimStart();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
_readContentLengthMessage() {
|
|
124
|
-
const { headerEndIndex, separatorLength } = (() => {
|
|
125
|
-
const crlfIndex = this._buffer.indexOf('\r\n\r\n');
|
|
126
|
-
const lfIndex = this._buffer.indexOf('\n\n');
|
|
127
|
-
|
|
128
|
-
if (crlfIndex === -1 && lfIndex === -1) return { headerEndIndex: -1, separatorLength: 0 };
|
|
129
|
-
if (crlfIndex === -1) return { headerEndIndex: lfIndex, separatorLength: 2 };
|
|
130
|
-
if (lfIndex === -1) return { headerEndIndex: crlfIndex, separatorLength: 4 };
|
|
131
|
-
|
|
132
|
-
return crlfIndex < lfIndex
|
|
133
|
-
? { headerEndIndex: crlfIndex, separatorLength: 4 }
|
|
134
|
-
: { headerEndIndex: lfIndex, separatorLength: 2 };
|
|
135
|
-
})();
|
|
136
|
-
|
|
137
|
-
if (headerEndIndex === -1) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const header = this._buffer.toString('utf8', 0, headerEndIndex);
|
|
142
|
-
const match = header.match(HEADER_RE);
|
|
143
|
-
if (!match) {
|
|
144
|
-
this._buffer = this._buffer.subarray(headerEndIndex + separatorLength);
|
|
145
|
-
this.onerror?.(new Error('Invalid Content-Length header'));
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const length = Number(match[1]);
|
|
150
|
-
const totalMessageLength = headerEndIndex + separatorLength + length;
|
|
151
|
-
if (this._buffer.length < totalMessageLength) {
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const json = this._buffer.toString(
|
|
156
|
-
'utf8',
|
|
157
|
-
headerEndIndex + separatorLength,
|
|
158
|
-
totalMessageLength
|
|
159
|
-
);
|
|
160
|
-
this._buffer = this._buffer.subarray(totalMessageLength);
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
return parseJson(json);
|
|
164
|
-
} catch (error) {
|
|
165
|
-
this.onerror?.(error);
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
_readNdjsonMessage() {
|
|
171
|
-
while (true) {
|
|
172
|
-
const newlineIndex = this._buffer.indexOf(0x0a);
|
|
173
|
-
if (newlineIndex === -1) {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
let line = this._buffer.toString('utf8', 0, newlineIndex);
|
|
178
|
-
this._buffer = this._buffer.subarray(newlineIndex + 1);
|
|
179
|
-
line = line.trim();
|
|
180
|
-
if (!line) {
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
return parseJson(line);
|
|
186
|
-
} catch (error) {
|
|
187
|
-
this.onerror?.(error);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|