@akiojin/unity-mcp-server 2.42.4 → 2.43.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.
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Unity MCP Server - Single Entry Point
4
+ *
5
+ * This file uses ONLY dynamic imports to ensure:
6
+ * 1. Early stderr output before any module loading
7
+ * 2. Global exception handlers are registered first
8
+ * 3. Module load failures are caught and reported
9
+ *
10
+ * ESM static imports are hoisted, so we use dynamic import() to
11
+ * ensure our error handlers are set up before loading any modules.
12
+ */
13
+
14
+ // Synchronous stderr write - this MUST appear before any module loading
15
+ process.stderr.write('[unity-mcp-server] Starting...\n');
16
+
17
+ // Global exception handlers - catch any unhandled errors
18
+ process.on('uncaughtException', err => {
19
+ process.stderr.write(`[unity-mcp-server] FATAL: Uncaught exception: ${err.message}\n`);
20
+ process.stderr.write(`${err.stack}\n`);
21
+ process.exit(1);
22
+ });
23
+
24
+ process.on('unhandledRejection', (reason, _promise) => {
25
+ const msg = reason instanceof Error ? reason.message : String(reason);
26
+ const stack = reason instanceof Error ? reason.stack : '';
27
+ process.stderr.write(`[unity-mcp-server] FATAL: Unhandled rejection: ${msg}\n`);
28
+ if (stack) process.stderr.write(`${stack}\n`);
29
+ process.exit(1);
30
+ });
31
+
32
+ // Parse command line arguments (no static imports required)
33
+ const args = process.argv.slice(2);
34
+ const command = args[0] && !args[0].startsWith('--') ? args[0] : null;
35
+ const rest = command ? args.slice(1) : args;
36
+
37
+ let httpEnabled = false;
38
+ let httpPort;
39
+ let stdioEnabled = true;
40
+ let telemetryEnabled;
41
+
42
+ for (let i = 0; i < rest.length; i++) {
43
+ const arg = rest[i];
44
+ switch (arg) {
45
+ case '--http':
46
+ httpEnabled = true;
47
+ if (rest[i + 1] && !rest[i + 1].startsWith('--')) {
48
+ httpPort = parseInt(rest[i + 1], 10);
49
+ i++;
50
+ }
51
+ break;
52
+ case '--no-http':
53
+ httpEnabled = false;
54
+ break;
55
+ case '--stdio':
56
+ stdioEnabled = true;
57
+ break;
58
+ case '--no-stdio':
59
+ stdioEnabled = false;
60
+ break;
61
+ case '--telemetry':
62
+ telemetryEnabled = true;
63
+ break;
64
+ case '--no-telemetry':
65
+ telemetryEnabled = false;
66
+ break;
67
+ default:
68
+ break;
69
+ }
70
+ }
71
+
72
+ // Main entry point - all imports are dynamic
73
+ async function main() {
74
+ try {
75
+ if (command === 'list-instances') {
76
+ const { listInstances } = await import('../src/cli/commands/listInstances.js');
77
+ const portsArg = rest.find(a => a.startsWith('--ports='));
78
+ const ports = portsArg
79
+ ? portsArg
80
+ .replace('--ports=', '')
81
+ .split(',')
82
+ .map(p => Number(p))
83
+ : [];
84
+ const hostArg = rest.find(a => a.startsWith('--host='));
85
+ const host = hostArg ? hostArg.replace('--host=', '') : 'localhost';
86
+ const json = rest.includes('--json');
87
+ const list = await listInstances({ ports, host });
88
+ if (json) {
89
+ console.log(JSON.stringify(list, null, 2));
90
+ } else {
91
+ for (const e of list) {
92
+ console.log(`${e.active ? '*' : ' '} ${e.id} ${e.status}`);
93
+ }
94
+ }
95
+ return;
96
+ }
97
+
98
+ if (command === 'set-active') {
99
+ const { setActive } = await import('../src/cli/commands/setActive.js');
100
+ const id = rest[0];
101
+ if (!id) {
102
+ console.error('Usage: unity-mcp-server set-active <host:port>');
103
+ process.exit(1);
104
+ }
105
+ try {
106
+ const result = await setActive({ id });
107
+ console.log(JSON.stringify(result, null, 2));
108
+ } catch (e) {
109
+ console.error(e.message);
110
+ process.exit(1);
111
+ }
112
+ return;
113
+ }
114
+
115
+ // Start MCP server (dynamic import)
116
+ const { startServer } = await import('../src/core/server.js');
117
+ await startServer({
118
+ http: {
119
+ enabled: httpEnabled,
120
+ port: httpPort
121
+ },
122
+ telemetry: telemetryEnabled === undefined ? undefined : { enabled: telemetryEnabled },
123
+ stdioEnabled
124
+ });
125
+ } catch (err) {
126
+ process.stderr.write(`[unity-mcp-server] Startup failed: ${err.message}\n`);
127
+ process.stderr.write(`${err.stack}\n`);
128
+ process.exit(1);
129
+ }
130
+ }
131
+
132
+ main();
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@akiojin/unity-mcp-server",
3
- "version": "2.42.4",
3
+ "version": "2.43.0",
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",
7
7
  "bin": {
8
- "unity-mcp-server": "./bin/bootstrap.js"
8
+ "unity-mcp-server": "./bin/unity-mcp-server.js"
9
9
  },
10
10
  "scripts": {
11
11
  "start": "node src/core/server.js",
@@ -29,7 +29,7 @@
29
29
  "prebuild:better-sqlite3": "node scripts/prebuild-better-sqlite3.mjs",
30
30
  "prebuilt:manifest": "node scripts/generate-prebuilt-manifest.mjs",
31
31
  "prepublishOnly": "npm run test:ci && npm run prebuilt:manifest",
32
- "postinstall": "node scripts/ensure-better-sqlite3.mjs && chmod +x bin/bootstrap.js bin/unity-mcp-server || true",
32
+ "postinstall": "node scripts/ensure-better-sqlite3.mjs && chmod +x bin/unity-mcp-server.js || true",
33
33
  "test:ci:unity": "timeout 60 node --test tests/unit/core/codeIndex.test.js tests/unit/core/config.test.js tests/unit/core/indexWatcher.test.js tests/unit/core/projectInfo.test.js tests/unit/core/server.test.js || exit 0",
34
34
  "test:unity": "node tests/run-unity-integration.mjs",
35
35
  "test:nounity": "npm run test:integration",
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import * as findUpPkg from 'find-up';
4
+ import { MCPLogger } from './mcpLogger.js';
4
5
 
5
6
  // Diagnostic log: confirm module loading reached this point
6
7
  process.stderr.write('[unity-mcp-server] Config module loading...\n');
@@ -273,30 +274,11 @@ export const WORKSPACE_ROOT = workspaceRoot;
273
274
  * Logger utility
274
275
  * IMPORTANT: In MCP servers, all stdout output must be JSON-RPC protocol messages.
275
276
  * Logging must go to stderr to avoid breaking the protocol.
277
+ *
278
+ * MCP SDK-compliant logger with RFC 5424 log levels.
279
+ * Supports dual output: stderr (developer) + MCP notification (client)
276
280
  */
277
- export const logger = {
278
- info: (message, ...args) => {
279
- if (['info', 'debug'].includes(config.logging.level)) {
280
- console.error(`${config.logging.prefix} ${message}`, ...args);
281
- }
282
- },
283
-
284
- warn: (message, ...args) => {
285
- if (['info', 'debug', 'warn'].includes(config.logging.level)) {
286
- console.error(`${config.logging.prefix} WARN: ${message}`, ...args);
287
- }
288
- },
289
-
290
- error: (message, ...args) => {
291
- console.error(`${config.logging.prefix} ERROR: ${message}`, ...args);
292
- },
293
-
294
- debug: (message, ...args) => {
295
- if (config.logging.level === 'debug') {
296
- console.error(`${config.logging.prefix} DEBUG: ${message}`, ...args);
297
- }
298
- }
299
- };
281
+ export const logger = new MCPLogger(config);
300
282
 
301
283
  // Late log if external config failed to load
302
284
  if (config.__configLoadError) {
@@ -68,7 +68,7 @@ export class IndexBuildWorkerPool {
68
68
  try {
69
69
  cb(message.data);
70
70
  } catch (e) {
71
- logger.warn(`[worker-pool] Progress callback error: ${e.message}`);
71
+ logger.warning(`[worker-pool] Progress callback error: ${e.message}`);
72
72
  }
73
73
  });
74
74
 
@@ -102,7 +102,7 @@ export class IndexBuildWorkerPool {
102
102
 
103
103
  this.worker.on('exit', code => {
104
104
  if (code !== 0 && this.worker) {
105
- logger.warn(`[worker-pool] Worker exited with code ${code}`);
105
+ logger.warning(`[worker-pool] Worker exited with code ${code}`);
106
106
  this._cleanup();
107
107
  reject(new Error(`Worker exited with code ${code}`));
108
108
  }
@@ -54,7 +54,7 @@ export class IndexWatcher {
54
54
  const driverOk = await probe._ensureDriver();
55
55
  if (!driverOk || probe.disabled) {
56
56
  const reason = probe.disableReason || 'SQLite native binding not available';
57
- logger.warn(`[index] watcher: code index disabled (${reason}); stopping watcher`);
57
+ logger.warning(`[index] watcher: code index disabled (${reason}); stopping watcher`);
58
58
  this.stop();
59
59
  return;
60
60
  }
@@ -69,7 +69,7 @@ export class IndexWatcher {
69
69
  const dbExists = fs.default.existsSync(dbPath);
70
70
 
71
71
  if (!dbExists) {
72
- logger.warn('[index] watcher: code index DB file not found, triggering full rebuild');
72
+ logger.warning('[index] watcher: code index DB file not found, triggering full rebuild');
73
73
  // Force full rebuild when DB file is missing
74
74
  const jobId = `watcher-rebuild-${Date.now()}`;
75
75
  this.currentWatcherJobId = jobId;
@@ -117,7 +117,7 @@ export class IndexWatcher {
117
117
  // (Job result will be logged when it completes/fails)
118
118
  this._monitorJob(jobId);
119
119
  } catch (e) {
120
- logger.warn(`[index] watcher exception: ${e.message}`);
120
+ logger.warning(`[index] watcher exception: ${e.message}`);
121
121
  } finally {
122
122
  this.running = false;
123
123
  }
@@ -174,7 +174,7 @@ export class IndexWatcher {
174
174
  );
175
175
  clearInterval(checkInterval);
176
176
  } else if (job.status === 'failed') {
177
- logger.warn(`[index] watcher: auto-build failed - ${job.error}`);
177
+ logger.warning(`[index] watcher: auto-build failed - ${job.error}`);
178
178
  clearInterval(checkInterval);
179
179
  }
180
180
  }, 1000);
@@ -0,0 +1,182 @@
1
+ /**
2
+ * MCP SDK-compliant Logger
3
+ *
4
+ * RFC 5424 compliant logging with MCP notification support.
5
+ * Provides 8 log levels: emergency, alert, critical, error, warning, notice, info, debug
6
+ *
7
+ * Features:
8
+ * - Dual output: stderr (developer) + MCP notification (client)
9
+ * - Dynamic log level control via setLevel()
10
+ * - Fire-and-forget MCP notifications (non-blocking)
11
+ * - Error resilience: continues logging even if MCP notification fails
12
+ */
13
+
14
+ /**
15
+ * RFC 5424 log levels (syslog severity)
16
+ * Lower numbers = higher severity
17
+ */
18
+ export const LOG_LEVELS = {
19
+ emergency: 0, // System is unusable
20
+ alert: 1, // Action must be taken immediately
21
+ critical: 2, // Critical conditions
22
+ error: 3, // Error conditions
23
+ warning: 4, // Warning conditions
24
+ notice: 5, // Normal but significant condition
25
+ info: 6, // Informational messages
26
+ debug: 7 // Debug-level messages
27
+ };
28
+
29
+ /**
30
+ * MCP-compliant Logger class
31
+ */
32
+ export class MCPLogger {
33
+ /**
34
+ * @param {Object} config - Configuration object
35
+ * @param {Object} [config.logging] - Logging configuration
36
+ * @param {string} [config.logging.prefix='[unity-mcp-server]'] - Log prefix
37
+ * @param {string} [config.logging.level='info'] - Minimum log level
38
+ */
39
+ constructor(config) {
40
+ this.prefix = config?.logging?.prefix || '[unity-mcp-server]';
41
+ this.minLevel = LOG_LEVELS[config?.logging?.level || 'info'];
42
+ this.server = null;
43
+ this.transportConnected = false;
44
+ }
45
+
46
+ /**
47
+ * Set MCP Server instance for sending notifications
48
+ * Should be called after transport is connected
49
+ * @param {Object} server - MCP Server instance with sendLoggingMessage method
50
+ */
51
+ setServer(server) {
52
+ this.server = server;
53
+ this.transportConnected = true;
54
+ }
55
+
56
+ /**
57
+ * Dynamically change log level
58
+ * @param {string} level - New log level (RFC 5424 level name)
59
+ */
60
+ setLevel(level) {
61
+ if (LOG_LEVELS[level] !== undefined) {
62
+ this.minLevel = LOG_LEVELS[level];
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Internal log method
68
+ * @param {string} level - Log level
69
+ * @param {string} message - Log message
70
+ * @param {...any} args - Additional arguments
71
+ */
72
+ _log(level, message, ...args) {
73
+ const levelNum = LOG_LEVELS[level];
74
+
75
+ // Filter by minimum level
76
+ if (levelNum > this.minLevel) return;
77
+
78
+ // 1. Always output to stderr (developer-facing)
79
+ const formatted = this._formatMessage(level, message);
80
+ console.error(formatted, ...args);
81
+
82
+ // 2. Send MCP notification (client-facing, only when connected)
83
+ if (this.transportConnected && this.server) {
84
+ this._sendMcpLog(level, message, args);
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Send log message via MCP notification
90
+ * @param {string} level - Log level
91
+ * @param {string} message - Log message
92
+ * @param {any[]} args - Additional arguments
93
+ */
94
+ _sendMcpLog(level, message, args) {
95
+ try {
96
+ // Concatenate additional arguments into a single string
97
+ const fullMessage =
98
+ args.length > 0
99
+ ? `${message} ${args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ')}`
100
+ : message;
101
+
102
+ this.server.sendLoggingMessage({
103
+ level,
104
+ logger: 'unity-mcp-server',
105
+ data: fullMessage // String only (no structured data)
106
+ });
107
+ } catch (e) {
108
+ // Log failure but continue (error resilience)
109
+ console.error(`${this.prefix} [MCP notification failed] ${e.message}`);
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Format message for stderr output
115
+ * @param {string} level - Log level
116
+ * @param {string} message - Log message
117
+ * @returns {string} Formatted message
118
+ */
119
+ _formatMessage(level, message) {
120
+ // info level doesn't include level label (matches existing behavior)
121
+ const label = level === 'info' ? '' : ` ${level.toUpperCase()}:`;
122
+ return `${this.prefix}${label} ${message}`;
123
+ }
124
+
125
+ // Public log methods (RFC 5424 levels)
126
+
127
+ /**
128
+ * Emergency: System is unusable
129
+ */
130
+ emergency(message, ...args) {
131
+ this._log('emergency', message, ...args);
132
+ }
133
+
134
+ /**
135
+ * Alert: Action must be taken immediately
136
+ */
137
+ alert(message, ...args) {
138
+ this._log('alert', message, ...args);
139
+ }
140
+
141
+ /**
142
+ * Critical: Critical conditions
143
+ */
144
+ critical(message, ...args) {
145
+ this._log('critical', message, ...args);
146
+ }
147
+
148
+ /**
149
+ * Error: Error conditions
150
+ */
151
+ error(message, ...args) {
152
+ this._log('error', message, ...args);
153
+ }
154
+
155
+ /**
156
+ * Warning: Warning conditions
157
+ */
158
+ warning(message, ...args) {
159
+ this._log('warning', message, ...args);
160
+ }
161
+
162
+ /**
163
+ * Notice: Normal but significant condition
164
+ */
165
+ notice(message, ...args) {
166
+ this._log('notice', message, ...args);
167
+ }
168
+
169
+ /**
170
+ * Info: Informational messages
171
+ */
172
+ info(message, ...args) {
173
+ this._log('info', message, ...args);
174
+ }
175
+
176
+ /**
177
+ * Debug: Debug-level messages
178
+ */
179
+ debug(message, ...args) {
180
+ this._log('debug', message, ...args);
181
+ }
182
+ }
@@ -70,7 +70,7 @@ export class ProjectInfoProvider {
70
70
  return this.cached;
71
71
  }
72
72
  } catch (e) {
73
- logger.warn(`get_editor_info failed: ${e.message}`);
73
+ logger.warning(`get_editor_info failed: ${e.message}`);
74
74
  }
75
75
  }
76
76
  if (typeof cfgRootRaw === 'string') {
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
3
+ import {
4
+ ListToolsRequestSchema,
5
+ CallToolRequestSchema,
6
+ SetLevelRequestSchema
7
+ } from '@modelcontextprotocol/sdk/types.js';
4
8
  // Note: filename is lowercase on disk; use exact casing for POSIX filesystems
5
9
  import { UnityConnection } from './unityConnection.js';
6
10
  import { createHandlers } from '../handlers/index.js';
@@ -24,7 +28,9 @@ const server = new Server(
24
28
  capabilities: {
25
29
  // Explicitly advertise tool support; some MCP clients expect a non-empty object
26
30
  // Setting listChanged enables future push updates if we emit notifications
27
- tools: { listChanged: true }
31
+ tools: { listChanged: true },
32
+ // Enable MCP logging capability for sendLoggingMessage
33
+ logging: {}
28
34
  }
29
35
  }
30
36
  );
@@ -32,6 +38,14 @@ const server = new Server(
32
38
  // Register MCP protocol handlers
33
39
  // Note: Do not log here as it breaks MCP protocol initialization
34
40
 
41
+ // Handle logging/setLevel request (REQ-6)
42
+ server.setRequestHandler(SetLevelRequestSchema, async request => {
43
+ const { level } = request.params;
44
+ logger.setLevel(level);
45
+ logger.info(`Log level changed to: ${level}`);
46
+ return {};
47
+ });
48
+
35
49
  // Handle tool listing
36
50
  server.setRequestHandler(ListToolsRequestSchema, async () => {
37
51
  const tools = Array.from(handlers.values())
@@ -178,6 +192,9 @@ export async function startServer(options = {}) {
178
192
  transport = new HybridStdioServerTransport();
179
193
  await server.connect(transport);
180
194
  console.error(`[unity-mcp-server] MCP transport connected`);
195
+
196
+ // Enable MCP logging notifications (REQ-4)
197
+ logger.setServer(server);
181
198
  }
182
199
 
183
200
  // Now safe to log after connection established
@@ -228,7 +245,7 @@ export async function startServer(options = {}) {
228
245
  process.on('SIGINT', shutdown);
229
246
  process.on('SIGTERM', shutdown);
230
247
  } catch (e) {
231
- logger.warn(`[startup] csharp-lsp start failed: ${e.message}`);
248
+ logger.warning(`[startup] csharp-lsp start failed: ${e.message}`);
232
249
  }
233
250
  })();
234
251
 
@@ -252,7 +269,7 @@ export async function startServer(options = {}) {
252
269
 
253
270
  if (!ready) {
254
271
  if (index.disabled) {
255
- logger.warn(
272
+ logger.warning(
256
273
  `[startup] Code index disabled: ${index.disableReason || 'SQLite native binding missing'}. Skipping auto-build.`
257
274
  );
258
275
  return;
@@ -269,13 +286,13 @@ export async function startServer(options = {}) {
269
286
  `[startup] Code index auto-build started: jobId=${result.jobId}. Use code_index_status to check progress.`
270
287
  );
271
288
  } else {
272
- logger.warn(`[startup] Code index auto-build failed: ${result.message}`);
289
+ logger.warning(`[startup] Code index auto-build failed: ${result.message}`);
273
290
  }
274
291
  } else {
275
292
  logger.info('[startup] Code index DB already exists. Skipping auto-build.');
276
293
  }
277
294
  } catch (e) {
278
- logger.warn(`[startup] Code index auto-init failed: ${e.message}`);
295
+ logger.warning(`[startup] Code index auto-init failed: ${e.message}`);
279
296
  }
280
297
  })();
281
298
 
@@ -300,7 +300,7 @@ export class UnityConnection extends EventEmitter {
300
300
  }
301
301
 
302
302
  if (recoveryIndex > 0) {
303
- logger.warn(`[Unity] Discarding ${recoveryIndex} bytes of invalid data`);
303
+ logger.warning(`[Unity] Discarding ${recoveryIndex} bytes of invalid data`);
304
304
  this.messageBuffer = this.messageBuffer.slice(recoveryIndex);
305
305
  continue;
306
306
  } else {
@@ -323,7 +323,7 @@ export class UnityConnection extends EventEmitter {
323
323
 
324
324
  // Skip non-JSON messages (like debug logs)
325
325
  if (!message.trim().startsWith('{')) {
326
- logger.warn(`[Unity] Skipping non-JSON message: ${message.substring(0, 50)}...`);
326
+ logger.warning(`[Unity] Skipping non-JSON message: ${message.substring(0, 50)}...`);
327
327
  continue;
328
328
  }
329
329
 
@@ -359,7 +359,7 @@ export class UnityConnection extends EventEmitter {
359
359
  logger.info(`[Unity] enqueue sendCommand: ${type}`, { connected: this.connected });
360
360
 
361
361
  if (!this.connected) {
362
- logger.warn('[Unity] Not connected; waiting for reconnection before sending command');
362
+ logger.warning('[Unity] Not connected; waiting for reconnection before sending command');
363
363
  await this.ensureConnected({
364
364
  timeoutMs: config.unity.commandTimeout
365
365
  });
@@ -463,7 +463,7 @@ export class UnityConnection extends EventEmitter {
463
463
  result = JSON.parse(result);
464
464
  logger.info(`[Unity] Parsed string result as JSON:`, result);
465
465
  } catch (parseError) {
466
- logger.warn(`[Unity] Failed to parse result as JSON: ${parseError.message}`);
466
+ logger.warning(`[Unity] Failed to parse result as JSON: ${parseError.message}`);
467
467
  }
468
468
  }
469
469
  if (response.version) result._version = response.version;
@@ -476,7 +476,7 @@ export class UnityConnection extends EventEmitter {
476
476
  err.code = response.code;
477
477
  pending.reject(err);
478
478
  } else {
479
- logger.warn(`[Unity] Command ${targetId} has unknown response format`);
479
+ logger.warning(`[Unity] Command ${targetId} has unknown response format`);
480
480
  pending.resolve(response);
481
481
  }
482
482
  return;
@@ -210,7 +210,9 @@ export class CodeIndexBuildToolHandler extends BaseToolHandler {
210
210
  // This allows build to continue even if some files fail
211
211
  if (processed % 50 === 0) {
212
212
  // Log occasionally to avoid spam
213
- logger.warn(`[index][${job.id}] Skipped file due to error: ${rel} - ${err.message}`);
213
+ logger.warning(
214
+ `[index][${job.id}] Skipped file due to error: ${rel} - ${err.message}`
215
+ );
214
216
  }
215
217
  } finally {
216
218
  processed += 1;
@@ -92,7 +92,7 @@ export class CSharpLspUtils {
92
92
  }
93
93
  logger.info(`[csharp-lsp] migrated legacy binary to ${path.dirname(primary)}`);
94
94
  } catch (e) {
95
- logger.warn(`[csharp-lsp] legacy migration failed: ${e.message}`);
95
+ logger.warning(`[csharp-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.warn('[csharp-lsp] version not found, using existing binary');
138
+ logger.warning('[csharp-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,7 @@ export class CSharpLspUtils {
152
152
  return p;
153
153
  } catch (e) {
154
154
  if (fs.existsSync(p)) {
155
- logger.warn(`[csharp-lsp] download failed, using existing binary: ${e.message}`);
155
+ logger.warning(`[csharp-lsp] download failed, using existing binary: ${e.message}`);
156
156
  return p;
157
157
  }
158
158
  throw e;
@@ -23,7 +23,7 @@ export class LspProcessManager {
23
23
  const proc = spawn(bin, { stdio: ['pipe', 'pipe', 'pipe'] });
24
24
  proc.on('error', e => logger.error(`[csharp-lsp] process error: ${e.message}`));
25
25
  proc.on('close', (code, sig) => {
26
- logger.warn(`[csharp-lsp] exited code=${code} signal=${sig || ''}`);
26
+ logger.warning(`[csharp-lsp] exited code=${code} signal=${sig || ''}`);
27
27
  if (this.state.proc === proc) {
28
28
  this.state.proc = null;
29
29
  }
@@ -161,7 +161,7 @@ export class LspRpcClient {
161
161
  this.proc = null;
162
162
  this.initialized = false;
163
163
  this.buf = Buffer.alloc(0);
164
- logger.warn(`[csharp-lsp] recoverable error on ${method}: ${msg}. Retrying once...`);
164
+ logger.warning(`[csharp-lsp] recoverable error on ${method}: ${msg}. Retrying once...`);
165
165
  return await this.#requestWithRetry(method, params, attempt + 1);
166
166
  }
167
167
  // Standardize error message
@@ -42,7 +42,7 @@ export class LspRpcClientSingleton {
42
42
  try {
43
43
  await instance.mgr.stop();
44
44
  } catch (e) {
45
- logger.warn(`[LspRpcClientSingleton] error stopping: ${e.message}`);
45
+ logger.warning(`[LspRpcClientSingleton] error stopping: ${e.message}`);
46
46
  }
47
47
  instance = null;
48
48
  currentProjectRoot = null;
@@ -59,7 +59,7 @@ export class LspRpcClientSingleton {
59
59
  if (!instance) return;
60
60
  // Check if process is still alive before attempting heartbeat
61
61
  if (!instance.proc || instance.proc.killed) {
62
- logger.warn('[LspRpcClientSingleton] process dead, resetting...');
62
+ logger.warning('[LspRpcClientSingleton] process dead, resetting...');
63
63
  instance = null;
64
64
  currentProjectRoot = null;
65
65
  LspRpcClientSingleton.#stopHeartbeat();
@@ -69,7 +69,7 @@ export class LspRpcClientSingleton {
69
69
  // Use workspace/symbol with empty query as a lightweight ping
70
70
  await instance.request('workspace/symbol', { query: '' });
71
71
  } catch (e) {
72
- logger.warn(`[LspRpcClientSingleton] heartbeat failed: ${e.message}, resetting...`);
72
+ logger.warning(`[LspRpcClientSingleton] heartbeat failed: ${e.message}, resetting...`);
73
73
  // Process is dead, reset instance for next request
74
74
  instance = null;
75
75
  currentProjectRoot = null;
@@ -30,7 +30,9 @@ export async function persistTestResults(result) {
30
30
  await fs.writeFile(filePath, `${JSON.stringify(result, null, 2)}\n`, 'utf8');
31
31
  return filePath;
32
32
  } catch (error) {
33
- logger.warn(`[TestResultsCache] Failed to write test results to ${filePath}: ${error.message}`);
33
+ logger.warning(
34
+ `[TestResultsCache] Failed to write test results to ${filePath}: ${error.message}`
35
+ );
34
36
  return null;
35
37
  }
36
38
  }
@@ -46,7 +48,7 @@ export async function loadCachedTestResults(targetPath) {
46
48
  return JSON.parse(data);
47
49
  } catch (error) {
48
50
  if (error.code !== 'ENOENT') {
49
- logger.warn(
51
+ logger.warning(
50
52
  `[TestResultsCache] Failed to read test results from ${filePath}: ${error.message}`
51
53
  );
52
54
  }
@@ -63,7 +65,7 @@ export async function resetTestResultsCache() {
63
65
  try {
64
66
  await fs.rm(filePath, { force: true });
65
67
  } catch (error) {
66
- logger.warn(
68
+ logger.warning(
67
69
  `[TestResultsCache] Failed to reset test results cache ${filePath}: ${error.message}`
68
70
  );
69
71
  }
@@ -27,7 +27,7 @@ await (async () => {
27
27
  logger.info(`[testRunState] Loaded persisted state from ${runStatePath}`);
28
28
  } catch (error) {
29
29
  if (error.code !== 'ENOENT') {
30
- logger.warn(`[testRunState] Failed to load state from ${runStatePath}: ${error.message}`);
30
+ logger.warning(`[testRunState] Failed to load state from ${runStatePath}: ${error.message}`);
31
31
  }
32
32
  }
33
33
  })();
@@ -85,6 +85,6 @@ async function persist() {
85
85
  await fs.mkdir(dir, { recursive: true });
86
86
  await fs.writeFile(runStatePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
87
87
  } catch (error) {
88
- logger.warn(`[testRunState] Failed to persist state to ${runStatePath}: ${error.message}`);
88
+ logger.warning(`[testRunState] Failed to persist state to ${runStatePath}: ${error.message}`);
89
89
  }
90
90
  }
package/bin/bootstrap.js DELETED
@@ -1,38 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Bootstrap wrapper for unity-mcp-server
4
- *
5
- * This wrapper ensures:
6
- * 1. Early stderr output before any module loading
7
- * 2. Global exception handlers are registered first
8
- * 3. Module load failures are caught and reported
9
- *
10
- * ESM static imports are hoisted, so we use dynamic import() to
11
- * ensure our error handlers are set up before loading the main module.
12
- */
13
-
14
- // Synchronous stderr write - this MUST appear before any module loading
15
- process.stderr.write('[unity-mcp-server] Bootstrap starting...\n');
16
-
17
- // Global exception handlers - catch any unhandled errors
18
- process.on('uncaughtException', err => {
19
- process.stderr.write(`[unity-mcp-server] FATAL: Uncaught exception: ${err.message}\n`);
20
- process.stderr.write(`${err.stack}\n`);
21
- process.exit(1);
22
- });
23
-
24
- process.on('unhandledRejection', (reason, _promise) => {
25
- const msg = reason instanceof Error ? reason.message : String(reason);
26
- const stack = reason instanceof Error ? reason.stack : '';
27
- process.stderr.write(`[unity-mcp-server] FATAL: Unhandled rejection: ${msg}\n`);
28
- if (stack) process.stderr.write(`${stack}\n`);
29
- process.exit(1);
30
- });
31
-
32
- // Dynamic import to load the main module AFTER error handlers are set up
33
- // This allows us to catch module load failures
34
- import('./unity-mcp-server').catch(err => {
35
- process.stderr.write(`[unity-mcp-server] FATAL: Module load failed: ${err.message}\n`);
36
- process.stderr.write(`${err.stack}\n`);
37
- process.exit(1);
38
- });
@@ -1,92 +0,0 @@
1
- #!/usr/bin/env node
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';
5
-
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();