@akiojin/unity-mcp-server 2.43.3 → 2.44.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/server.js +253 -190
- package/src/lsp/CSharpLspUtils.js +9 -5
- package/src/lsp/LspProcessManager.js +5 -5
- package/src/lsp/LspRpcClient.js +4 -2
package/package.json
CHANGED
package/src/core/server.js
CHANGED
|
@@ -1,183 +1,75 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unity MCP Server - Main Server Module
|
|
4
|
+
*
|
|
5
|
+
* This module implements a deferred initialization pattern to ensure
|
|
6
|
+
* npx compatibility. The MCP transport connection is established FIRST,
|
|
7
|
+
* before loading handlers and other heavy dependencies, to avoid
|
|
8
|
+
* Claude Code's 30-second timeout.
|
|
9
|
+
*
|
|
10
|
+
* Initialization order:
|
|
11
|
+
* 1. MCP SDK imports (minimal, fast)
|
|
12
|
+
* 2. Transport connection (must complete before timeout)
|
|
13
|
+
* 3. Handler loading (deferred, after connection)
|
|
14
|
+
* 4. Unity connection (deferred, non-blocking)
|
|
15
|
+
*/
|
|
2
16
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
17
|
import {
|
|
4
18
|
ListToolsRequestSchema,
|
|
5
19
|
CallToolRequestSchema,
|
|
6
20
|
SetLevelRequestSchema
|
|
7
21
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
-
// Note: filename is lowercase on disk; use exact casing for POSIX filesystems
|
|
9
|
-
import { UnityConnection } from './unityConnection.js';
|
|
10
|
-
import { createHandlers } from '../handlers/index.js';
|
|
11
|
-
import { config, logger } from './config.js';
|
|
12
|
-
import { IndexWatcher } from './indexWatcher.js';
|
|
13
22
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
23
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
logger.info(`Log level changed to: ${level}`);
|
|
46
|
-
return {};
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Handle tool listing
|
|
50
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
51
|
-
const tools = Array.from(handlers.values())
|
|
52
|
-
.map((handler, index) => {
|
|
53
|
-
try {
|
|
54
|
-
const definition = handler.getDefinition();
|
|
55
|
-
// Validate inputSchema
|
|
56
|
-
if (definition.inputSchema && definition.inputSchema.type !== 'object') {
|
|
57
|
-
logger.error(
|
|
58
|
-
`[MCP] Tool ${handler.name} (index ${index}) has invalid inputSchema type: ${definition.inputSchema.type}`
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
return definition;
|
|
62
|
-
} catch (error) {
|
|
63
|
-
logger.error(`[MCP] Failed to get definition for handler ${handler.name}:`, error);
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
.filter(tool => tool !== null);
|
|
68
|
-
|
|
69
|
-
logger.info(`[MCP] Returning ${tools.length} tool definitions`);
|
|
70
|
-
return { tools };
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Handle tool execution
|
|
74
|
-
server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
75
|
-
const { name, arguments: args } = request.params;
|
|
76
|
-
const requestTime = Date.now();
|
|
77
|
-
|
|
78
|
-
logger.info(
|
|
79
|
-
`[MCP] Received tool call request: ${name} at ${new Date(requestTime).toISOString()}`,
|
|
80
|
-
{ args }
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const handler = handlers.get(name);
|
|
84
|
-
if (!handler) {
|
|
85
|
-
logger.error(`[MCP] Tool not found: ${name}`);
|
|
86
|
-
throw new Error(`Tool not found: ${name}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
logger.info(`[MCP] Starting handler execution for: ${name} at ${new Date().toISOString()}`);
|
|
91
|
-
const startTime = Date.now();
|
|
92
|
-
|
|
93
|
-
// Handler returns response in our format
|
|
94
|
-
const result = await handler.handle(args);
|
|
95
|
-
|
|
96
|
-
const duration = Date.now() - startTime;
|
|
97
|
-
const totalDuration = Date.now() - requestTime;
|
|
98
|
-
logger.info(`[MCP] Handler completed at ${new Date().toISOString()}: ${name}`, {
|
|
99
|
-
handlerDuration: `${duration}ms`,
|
|
100
|
-
totalDuration: `${totalDuration}ms`,
|
|
101
|
-
status: result.status
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Convert to MCP format
|
|
105
|
-
if (result.status === 'error') {
|
|
106
|
-
logger.error(`[MCP] Handler returned error: ${name}`, {
|
|
107
|
-
error: result.error,
|
|
108
|
-
code: result.code
|
|
109
|
-
});
|
|
110
|
-
return {
|
|
111
|
-
content: [
|
|
112
|
-
{
|
|
113
|
-
type: 'text',
|
|
114
|
-
text: `Error: ${result.error}\nCode: ${result.code || 'UNKNOWN_ERROR'}${result.details ? '\nDetails: ' + JSON.stringify(result.details, null, 2) : ''}`
|
|
115
|
-
}
|
|
116
|
-
]
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Success response
|
|
121
|
-
logger.info(`[MCP] Returning success response for: ${name} at ${new Date().toISOString()}`);
|
|
122
|
-
|
|
123
|
-
// Handle undefined or null results from handlers
|
|
124
|
-
let responseText;
|
|
125
|
-
if (result.result === undefined || result.result === null) {
|
|
126
|
-
responseText = JSON.stringify(
|
|
127
|
-
{
|
|
128
|
-
status: 'success',
|
|
129
|
-
message: 'Operation completed successfully but no details were returned',
|
|
130
|
-
tool: name
|
|
131
|
-
},
|
|
132
|
-
null,
|
|
133
|
-
2
|
|
134
|
-
);
|
|
135
|
-
} else {
|
|
136
|
-
responseText = JSON.stringify(result.result, null, 2);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
content: [
|
|
141
|
-
{
|
|
142
|
-
type: 'text',
|
|
143
|
-
text: responseText
|
|
144
|
-
}
|
|
145
|
-
]
|
|
146
|
-
};
|
|
147
|
-
} catch (error) {
|
|
148
|
-
const errorTime = Date.now();
|
|
149
|
-
logger.error(`[MCP] Handler threw exception at ${new Date(errorTime).toISOString()}: ${name}`, {
|
|
150
|
-
error: error.message,
|
|
151
|
-
stack: error.stack,
|
|
152
|
-
duration: `${errorTime - requestTime}ms`
|
|
153
|
-
});
|
|
154
|
-
return {
|
|
155
|
-
content: [
|
|
156
|
-
{
|
|
157
|
-
type: 'text',
|
|
158
|
-
text: `Error: ${error.message}`
|
|
159
|
-
}
|
|
160
|
-
]
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// Handle connection events
|
|
166
|
-
unityConnection.on('connected', () => {
|
|
167
|
-
logger.info('Unity connection established');
|
|
168
|
-
});
|
|
24
|
+
// Deferred state - will be initialized after transport connection
|
|
25
|
+
let unityConnection = null;
|
|
26
|
+
let handlers = null;
|
|
27
|
+
let config = null;
|
|
28
|
+
let logger = null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Lazily load handlers and dependencies
|
|
32
|
+
* Called after MCP transport is connected
|
|
33
|
+
*/
|
|
34
|
+
async function ensureInitialized() {
|
|
35
|
+
if (handlers !== null) return;
|
|
36
|
+
|
|
37
|
+
// Load config first (needed for logging)
|
|
38
|
+
const configModule = await import('./config.js');
|
|
39
|
+
config = configModule.config;
|
|
40
|
+
logger = configModule.logger;
|
|
41
|
+
|
|
42
|
+
// Load UnityConnection
|
|
43
|
+
const { UnityConnection } = await import('./unityConnection.js');
|
|
44
|
+
unityConnection = new UnityConnection();
|
|
45
|
+
|
|
46
|
+
// Load and create handlers
|
|
47
|
+
const { createHandlers } = await import('../handlers/index.js');
|
|
48
|
+
handlers = createHandlers(unityConnection);
|
|
49
|
+
|
|
50
|
+
// Set up Unity connection event handlers
|
|
51
|
+
unityConnection.on('connected', () => {
|
|
52
|
+
logger.info('Unity connection established');
|
|
53
|
+
});
|
|
169
54
|
|
|
170
|
-
unityConnection.on('disconnected', () => {
|
|
171
|
-
|
|
172
|
-
});
|
|
55
|
+
unityConnection.on('disconnected', () => {
|
|
56
|
+
logger.info('Unity connection lost');
|
|
57
|
+
});
|
|
173
58
|
|
|
174
|
-
unityConnection.on('error', error => {
|
|
175
|
-
|
|
176
|
-
});
|
|
59
|
+
unityConnection.on('error', error => {
|
|
60
|
+
logger.error('Unity connection error:', error.message);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
177
63
|
|
|
178
64
|
// Initialize server
|
|
179
65
|
export async function startServer(options = {}) {
|
|
180
66
|
try {
|
|
67
|
+
// Step 1: Load minimal config for server metadata
|
|
68
|
+
// We need server name/version before creating the Server instance
|
|
69
|
+
const { config: serverConfig, logger: serverLogger } = await import('./config.js');
|
|
70
|
+
config = serverConfig;
|
|
71
|
+
logger = serverLogger;
|
|
72
|
+
|
|
181
73
|
const runtimeConfig = {
|
|
182
74
|
...config,
|
|
183
75
|
http: { ...config.http, ...(options.http || {}) },
|
|
@@ -185,7 +77,24 @@ export async function startServer(options = {}) {
|
|
|
185
77
|
stdioEnabled: options.stdioEnabled !== undefined ? options.stdioEnabled : true
|
|
186
78
|
};
|
|
187
79
|
|
|
188
|
-
// Create
|
|
80
|
+
// Step 2: Create MCP server with minimal configuration
|
|
81
|
+
const server = new Server(
|
|
82
|
+
{
|
|
83
|
+
name: config.server.name,
|
|
84
|
+
version: config.server.version
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
capabilities: {
|
|
88
|
+
// Explicitly advertise tool support; some MCP clients expect a non-empty object
|
|
89
|
+
// Setting listChanged enables future push updates if we emit notifications
|
|
90
|
+
tools: { listChanged: true },
|
|
91
|
+
// Enable MCP logging capability for sendLoggingMessage
|
|
92
|
+
logging: {}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Step 3: Connect transport FIRST (critical for npx timeout avoidance)
|
|
189
98
|
let transport;
|
|
190
99
|
if (runtimeConfig.stdioEnabled !== false) {
|
|
191
100
|
console.error(`[unity-mcp-server] MCP transport connecting...`);
|
|
@@ -197,9 +106,148 @@ export async function startServer(options = {}) {
|
|
|
197
106
|
logger.setServer(server);
|
|
198
107
|
}
|
|
199
108
|
|
|
109
|
+
// Step 4: Register request handlers (they will lazily load dependencies)
|
|
110
|
+
// Handle logging/setLevel request (REQ-6)
|
|
111
|
+
server.setRequestHandler(SetLevelRequestSchema, async request => {
|
|
112
|
+
await ensureInitialized();
|
|
113
|
+
const { level } = request.params;
|
|
114
|
+
logger.setLevel(level);
|
|
115
|
+
logger.info(`Log level changed to: ${level}`);
|
|
116
|
+
return {};
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Handle tool listing
|
|
120
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
121
|
+
await ensureInitialized();
|
|
122
|
+
|
|
123
|
+
const tools = Array.from(handlers.values())
|
|
124
|
+
.map((handler, index) => {
|
|
125
|
+
try {
|
|
126
|
+
const definition = handler.getDefinition();
|
|
127
|
+
// Validate inputSchema
|
|
128
|
+
if (definition.inputSchema && definition.inputSchema.type !== 'object') {
|
|
129
|
+
logger.error(
|
|
130
|
+
`[MCP] Tool ${handler.name} (index ${index}) has invalid inputSchema type: ${definition.inputSchema.type}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return definition;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
logger.error(`[MCP] Failed to get definition for handler ${handler.name}:`, error);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
.filter(tool => tool !== null);
|
|
140
|
+
|
|
141
|
+
logger.info(`[MCP] Returning ${tools.length} tool definitions`);
|
|
142
|
+
return { tools };
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Handle tool execution
|
|
146
|
+
server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
147
|
+
await ensureInitialized();
|
|
148
|
+
|
|
149
|
+
const { name, arguments: args } = request.params;
|
|
150
|
+
const requestTime = Date.now();
|
|
151
|
+
|
|
152
|
+
logger.info(
|
|
153
|
+
`[MCP] Received tool call request: ${name} at ${new Date(requestTime).toISOString()}`,
|
|
154
|
+
{ args }
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const handler = handlers.get(name);
|
|
158
|
+
if (!handler) {
|
|
159
|
+
logger.error(`[MCP] Tool not found: ${name}`);
|
|
160
|
+
throw new Error(`Tool not found: ${name}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
logger.info(`[MCP] Starting handler execution for: ${name} at ${new Date().toISOString()}`);
|
|
165
|
+
const startTime = Date.now();
|
|
166
|
+
|
|
167
|
+
// Handler returns response in our format
|
|
168
|
+
const result = await handler.handle(args);
|
|
169
|
+
|
|
170
|
+
const duration = Date.now() - startTime;
|
|
171
|
+
const totalDuration = Date.now() - requestTime;
|
|
172
|
+
logger.info(`[MCP] Handler completed at ${new Date().toISOString()}: ${name}`, {
|
|
173
|
+
handlerDuration: `${duration}ms`,
|
|
174
|
+
totalDuration: `${totalDuration}ms`,
|
|
175
|
+
status: result.status
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Convert to MCP format
|
|
179
|
+
if (result.status === 'error') {
|
|
180
|
+
logger.error(`[MCP] Handler returned error: ${name}`, {
|
|
181
|
+
error: result.error,
|
|
182
|
+
code: result.code
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
content: [
|
|
186
|
+
{
|
|
187
|
+
type: 'text',
|
|
188
|
+
text: `Error: ${result.error}\nCode: ${result.code || 'UNKNOWN_ERROR'}${result.details ? '\nDetails: ' + JSON.stringify(result.details, null, 2) : ''}`
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Success response
|
|
195
|
+
logger.info(`[MCP] Returning success response for: ${name} at ${new Date().toISOString()}`);
|
|
196
|
+
|
|
197
|
+
// Handle undefined or null results from handlers
|
|
198
|
+
let responseText;
|
|
199
|
+
if (result.result === undefined || result.result === null) {
|
|
200
|
+
responseText = JSON.stringify(
|
|
201
|
+
{
|
|
202
|
+
status: 'success',
|
|
203
|
+
message: 'Operation completed successfully but no details were returned',
|
|
204
|
+
tool: name
|
|
205
|
+
},
|
|
206
|
+
null,
|
|
207
|
+
2
|
|
208
|
+
);
|
|
209
|
+
} else {
|
|
210
|
+
responseText = JSON.stringify(result.result, null, 2);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
content: [
|
|
215
|
+
{
|
|
216
|
+
type: 'text',
|
|
217
|
+
text: responseText
|
|
218
|
+
}
|
|
219
|
+
]
|
|
220
|
+
};
|
|
221
|
+
} catch (error) {
|
|
222
|
+
const errorTime = Date.now();
|
|
223
|
+
logger.error(
|
|
224
|
+
`[MCP] Handler threw exception at ${new Date(errorTime).toISOString()}: ${name}`,
|
|
225
|
+
{
|
|
226
|
+
error: error.message,
|
|
227
|
+
stack: error.stack,
|
|
228
|
+
duration: `${errorTime - requestTime}ms`
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
return {
|
|
232
|
+
content: [
|
|
233
|
+
{
|
|
234
|
+
type: 'text',
|
|
235
|
+
text: `Error: ${error.message}`
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
200
242
|
// Now safe to log after connection established
|
|
201
243
|
logger.info('MCP server started successfully');
|
|
202
244
|
|
|
245
|
+
// Step 5: Background initialization (non-blocking)
|
|
246
|
+
// Start loading handlers in background so first request is faster
|
|
247
|
+
ensureInitialized().catch(err => {
|
|
248
|
+
console.error(`[unity-mcp-server] Background initialization failed: ${err.message}`);
|
|
249
|
+
});
|
|
250
|
+
|
|
203
251
|
// Optional HTTP transport
|
|
204
252
|
let httpServerInstance;
|
|
205
253
|
if (runtimeConfig.http?.enabled) {
|
|
@@ -219,16 +267,19 @@ export async function startServer(options = {}) {
|
|
|
219
267
|
}
|
|
220
268
|
}
|
|
221
269
|
|
|
222
|
-
// Attempt to connect to Unity
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
270
|
+
// Attempt to connect to Unity (deferred, non-blocking)
|
|
271
|
+
(async () => {
|
|
272
|
+
await ensureInitialized();
|
|
273
|
+
console.error(`[unity-mcp-server] Unity connection starting...`);
|
|
274
|
+
try {
|
|
275
|
+
await unityConnection.connect();
|
|
276
|
+
console.error(`[unity-mcp-server] Unity connection established`);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error(`[unity-mcp-server] Unity connection failed: ${error.message}`);
|
|
279
|
+
logger.error('Initial Unity connection failed:', error.message);
|
|
280
|
+
logger.info('Unity connection will retry automatically');
|
|
281
|
+
}
|
|
282
|
+
})();
|
|
232
283
|
|
|
233
284
|
// Best-effort: prepare and start persistent C# LSP process (non-blocking)
|
|
234
285
|
(async () => {
|
|
@@ -250,18 +301,23 @@ export async function startServer(options = {}) {
|
|
|
250
301
|
})();
|
|
251
302
|
|
|
252
303
|
// Start periodic index watcher (incremental)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
304
|
+
(async () => {
|
|
305
|
+
await ensureInitialized();
|
|
306
|
+
const { IndexWatcher } = await import('./indexWatcher.js');
|
|
307
|
+
const watcher = new IndexWatcher(unityConnection);
|
|
308
|
+
watcher.start();
|
|
309
|
+
const stopWatch = () => {
|
|
310
|
+
try {
|
|
311
|
+
watcher.stop();
|
|
312
|
+
} catch {}
|
|
313
|
+
};
|
|
314
|
+
process.on('SIGINT', stopWatch);
|
|
315
|
+
process.on('SIGTERM', stopWatch);
|
|
316
|
+
})();
|
|
262
317
|
|
|
263
318
|
// Auto-initialize code index if DB doesn't exist
|
|
264
319
|
(async () => {
|
|
320
|
+
await ensureInitialized();
|
|
265
321
|
try {
|
|
266
322
|
const { CodeIndex } = await import('./codeIndex.js');
|
|
267
323
|
const index = new CodeIndex(unityConnection);
|
|
@@ -299,7 +355,7 @@ export async function startServer(options = {}) {
|
|
|
299
355
|
// Handle shutdown
|
|
300
356
|
process.on('SIGINT', async () => {
|
|
301
357
|
logger.info('Shutting down...');
|
|
302
|
-
unityConnection.disconnect();
|
|
358
|
+
if (unityConnection) unityConnection.disconnect();
|
|
303
359
|
if (transport) await server.close();
|
|
304
360
|
if (httpServerInstance) await httpServerInstance.close();
|
|
305
361
|
process.exit(0);
|
|
@@ -307,7 +363,7 @@ export async function startServer(options = {}) {
|
|
|
307
363
|
|
|
308
364
|
process.on('SIGTERM', async () => {
|
|
309
365
|
logger.info('Shutting down...');
|
|
310
|
-
unityConnection.disconnect();
|
|
366
|
+
if (unityConnection) unityConnection.disconnect();
|
|
311
367
|
if (transport) await server.close();
|
|
312
368
|
if (httpServerInstance) await httpServerInstance.close();
|
|
313
369
|
process.exit(0);
|
|
@@ -323,14 +379,21 @@ export async function startServer(options = {}) {
|
|
|
323
379
|
export const main = startServer;
|
|
324
380
|
|
|
325
381
|
// Export for testing
|
|
326
|
-
export async function createServer(customConfig
|
|
382
|
+
export async function createServer(customConfig) {
|
|
383
|
+
// For testing, we need to load dependencies synchronously
|
|
384
|
+
const { config: defaultConfig } = await import('./config.js');
|
|
385
|
+
const actualConfig = customConfig || defaultConfig;
|
|
386
|
+
|
|
387
|
+
const { UnityConnection } = await import('./unityConnection.js');
|
|
388
|
+
const { createHandlers } = await import('../handlers/index.js');
|
|
389
|
+
|
|
327
390
|
const testUnityConnection = new UnityConnection();
|
|
328
391
|
const testHandlers = createHandlers(testUnityConnection);
|
|
329
392
|
|
|
330
393
|
const testServer = new Server(
|
|
331
394
|
{
|
|
332
|
-
name:
|
|
333
|
-
version:
|
|
395
|
+
name: actualConfig.server.name,
|
|
396
|
+
version: actualConfig.server.version
|
|
334
397
|
},
|
|
335
398
|
{
|
|
336
399
|
capabilities: {
|
|
@@ -90,9 +90,9 @@ export class CSharpLspUtils {
|
|
|
90
90
|
fs.copyFileSync(legacyVersion, primaryVersion);
|
|
91
91
|
} catch {}
|
|
92
92
|
}
|
|
93
|
-
logger.info(`[
|
|
93
|
+
logger.info(`[unity-mcp-server:lsp] migrated legacy binary to ${path.dirname(primary)}`);
|
|
94
94
|
} catch (e) {
|
|
95
|
-
logger.warning(`[
|
|
95
|
+
logger.warning(`[unity-mcp-server:lsp] legacy migration failed: ${e.message}`);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -135,7 +135,7 @@ export class CSharpLspUtils {
|
|
|
135
135
|
// バージョン取得失敗時もバイナリが存在すれば使用
|
|
136
136
|
if (!desired) {
|
|
137
137
|
if (fs.existsSync(p)) {
|
|
138
|
-
logger.warning('[
|
|
138
|
+
logger.warning('[unity-mcp-server:lsp] version not found, using existing binary');
|
|
139
139
|
return p;
|
|
140
140
|
}
|
|
141
141
|
throw new Error('mcp-server version not found; cannot resolve LSP tag');
|
|
@@ -152,7 +152,9 @@ export class CSharpLspUtils {
|
|
|
152
152
|
return p;
|
|
153
153
|
} catch (e) {
|
|
154
154
|
if (fs.existsSync(p)) {
|
|
155
|
-
logger.warning(
|
|
155
|
+
logger.warning(
|
|
156
|
+
`[unity-mcp-server:lsp] download failed, using existing binary: ${e.message}`
|
|
157
|
+
);
|
|
156
158
|
return p;
|
|
157
159
|
}
|
|
158
160
|
throw e;
|
|
@@ -212,7 +214,9 @@ export class CSharpLspUtils {
|
|
|
212
214
|
try {
|
|
213
215
|
if (process.platform !== 'win32') fs.chmodSync(dest, 0o755);
|
|
214
216
|
} catch {}
|
|
215
|
-
logger.info(
|
|
217
|
+
logger.info(
|
|
218
|
+
`[unity-mcp-server:lsp] downloaded: ${path.basename(dest)} @ ${path.dirname(dest)}`
|
|
219
|
+
);
|
|
216
220
|
// manifestから実際のバージョンを取得(信頼性の高いソースとして使用)
|
|
217
221
|
const actualVersion = manifest.version || targetVersion;
|
|
218
222
|
return actualVersion;
|
|
@@ -21,22 +21,22 @@ export class LspProcessManager {
|
|
|
21
21
|
const rid = this.utils.detectRid();
|
|
22
22
|
const bin = await this.utils.ensureLocal(rid);
|
|
23
23
|
const proc = spawn(bin, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
24
|
-
proc.on('error', e => logger.error(`[
|
|
24
|
+
proc.on('error', e => logger.error(`[unity-mcp-server:lsp] process error: ${e.message}`));
|
|
25
25
|
proc.on('close', (code, sig) => {
|
|
26
|
-
logger.warning(`[
|
|
26
|
+
logger.warning(`[unity-mcp-server:lsp] exited code=${code} signal=${sig || ''}`);
|
|
27
27
|
if (this.state.proc === proc) {
|
|
28
28
|
this.state.proc = null;
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
proc.stderr.on('data', d => {
|
|
32
32
|
const s = String(d || '').trim();
|
|
33
|
-
if (s) logger.debug(`[
|
|
33
|
+
if (s) logger.debug(`[unity-mcp-server:lsp] ${s}`);
|
|
34
34
|
});
|
|
35
35
|
this.state.proc = proc;
|
|
36
|
-
logger.info(`[
|
|
36
|
+
logger.info(`[unity-mcp-server:lsp] started (pid=${proc.pid})`);
|
|
37
37
|
return proc;
|
|
38
38
|
} catch (e) {
|
|
39
|
-
logger.error(`[
|
|
39
|
+
logger.error(`[unity-mcp-server:lsp] failed to start: ${e.message}`);
|
|
40
40
|
throw e;
|
|
41
41
|
}
|
|
42
42
|
})();
|
package/src/lsp/LspRpcClient.js
CHANGED
|
@@ -76,7 +76,7 @@ export class LspRpcClient {
|
|
|
76
76
|
try {
|
|
77
77
|
this.proc.stdin.write(payload, 'utf8');
|
|
78
78
|
} catch (e) {
|
|
79
|
-
logger.error(`[
|
|
79
|
+
logger.error(`[unity-mcp-server:lsp] writeMessage failed: ${e.message}`);
|
|
80
80
|
// Mark process as unavailable to prevent further writes
|
|
81
81
|
this.proc = null;
|
|
82
82
|
this.initialized = false;
|
|
@@ -161,7 +161,9 @@ export class LspRpcClient {
|
|
|
161
161
|
this.proc = null;
|
|
162
162
|
this.initialized = false;
|
|
163
163
|
this.buf = Buffer.alloc(0);
|
|
164
|
-
logger.warning(
|
|
164
|
+
logger.warning(
|
|
165
|
+
`[unity-mcp-server:lsp] recoverable error on ${method}: ${msg}. Retrying once...`
|
|
166
|
+
);
|
|
165
167
|
return await this.#requestWithRetry(method, params, attempt + 1);
|
|
166
168
|
}
|
|
167
169
|
// Standardize error message
|