@akiojin/unity-mcp-server 2.43.1 → 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
|
|
|
@@ -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
|
-
}
|