@akiojin/unity-mcp-server 2.26.0 → 2.26.1

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.
@@ -70,7 +70,7 @@ export class GameObjectModifyToolHandler extends BaseToolHandler {
70
70
  required: ['path']
71
71
  }
72
72
  );
73
-
73
+
74
74
  this.unityConnection = unityConnection;
75
75
  }
76
76
 
@@ -81,23 +81,34 @@ export class GameObjectModifyToolHandler extends BaseToolHandler {
81
81
  */
82
82
  validate(params) {
83
83
  super.validate(params);
84
-
84
+
85
85
  // Path is required
86
86
  validateGameObjectPath(params.path);
87
-
87
+
88
88
  // At least one modification must be specified
89
- const modifiableProps = ['name', 'position', 'rotation', 'scale', 'active', 'parentPath', 'tag', 'layer'];
90
- const hasModification = modifiableProps.some(prop => params.hasOwnProperty(prop));
91
-
89
+ const modifiableProps = [
90
+ 'name',
91
+ 'position',
92
+ 'rotation',
93
+ 'scale',
94
+ 'active',
95
+ 'parentPath',
96
+ 'tag',
97
+ 'layer'
98
+ ];
99
+ const hasModification = modifiableProps.some(prop =>
100
+ Object.prototype.hasOwnProperty.call(params, prop)
101
+ );
102
+
92
103
  if (!hasModification) {
93
104
  throw new Error('At least one property to modify must be specified');
94
105
  }
95
-
106
+
96
107
  // Validate vector3 properties
97
108
  if (params.position) validateVector3(params.position, 'position');
98
109
  if (params.rotation) validateVector3(params.rotation, 'rotation');
99
110
  if (params.scale) validateVector3(params.scale, 'scale');
100
-
111
+
101
112
  // Validate layer
102
113
  if (params.layer !== undefined) {
103
114
  validateLayer(Number(params.layer));
@@ -114,15 +125,15 @@ export class GameObjectModifyToolHandler extends BaseToolHandler {
114
125
  if (!this.unityConnection.isConnected()) {
115
126
  await this.unityConnection.connect();
116
127
  }
117
-
128
+
118
129
  // Send modify_gameobject command
119
130
  const result = await this.unityConnection.sendCommand('modify_gameobject', params);
120
-
131
+
121
132
  // Check for errors from Unity
122
133
  if (result.error) {
123
134
  throw new Error(result.error);
124
135
  }
125
-
136
+
126
137
  return result;
127
138
  }
128
139
  }
@@ -18,7 +18,8 @@ export class MenuItemExecuteToolHandler extends BaseToolHandler {
18
18
  action: {
19
19
  type: 'string',
20
20
  enum: ['execute', 'get_available_menus'],
21
- description: 'Action to perform: execute menu item or get available menus (default: execute)'
21
+ description:
22
+ 'Action to perform: execute menu item or get available menus (default: execute)'
22
23
  },
23
24
  alias: {
24
25
  type: 'string',
@@ -30,47 +31,48 @@ export class MenuItemExecuteToolHandler extends BaseToolHandler {
30
31
  },
31
32
  safetyCheck: {
32
33
  type: 'boolean',
33
- description: 'Enable safety checks to prevent execution of dangerous menu items (default: true)'
34
+ description:
35
+ 'Enable safety checks to prevent execution of dangerous menu items (default: true)'
34
36
  }
35
37
  },
36
38
  required: ['menuPath']
37
39
  }
38
40
  );
39
-
41
+
40
42
  this.unityConnection = unityConnection;
41
-
43
+
42
44
  // Define blacklisted menu items for safety
43
45
  // Includes dialog-opening menus that cause MCP hanging
44
46
  this.blacklistedMenus = new Set([
45
47
  // Application control
46
48
  'File/Quit',
47
-
49
+
48
50
  // Dialog-opening file operations (cause MCP hanging)
49
51
  'File/Open Scene',
50
- 'File/New Scene',
52
+ 'File/New Scene',
51
53
  'File/Save Scene As...',
52
54
  'File/Build Settings...',
53
55
  'File/Build And Run',
54
-
56
+
55
57
  // Dialog-opening asset operations (cause MCP hanging)
56
58
  'Assets/Import New Asset...',
57
59
  'Assets/Import Package/Custom Package...',
58
60
  'Assets/Export Package...',
59
61
  'Assets/Delete',
60
-
62
+
61
63
  // Dialog-opening preferences and settings (cause MCP hanging)
62
64
  'Edit/Preferences...',
63
65
  'Edit/Project Settings...',
64
-
66
+
65
67
  // Dialog-opening window operations (may cause issues)
66
68
  'Window/Package Manager',
67
69
  'Window/Asset Store',
68
-
70
+
69
71
  // Scene view operations that may require focus (potential hanging)
70
72
  'GameObject/Align With View',
71
73
  'GameObject/Align View to Selected'
72
74
  ]);
73
-
75
+
74
76
  // Common menu aliases
75
77
  this.menuAliases = new Map([
76
78
  ['refresh', 'Assets/Refresh'],
@@ -104,12 +106,18 @@ export class MenuItemExecuteToolHandler extends BaseToolHandler {
104
106
 
105
107
  // Safety check for blacklisted items with security normalization (BEFORE format validation)
106
108
  if (safetyCheck && this.isMenuPathBlacklisted(menuPath)) {
107
- throw new Error(`Menu item is blacklisted for safety: ${menuPath}. Use safetyCheck: false to override.`);
109
+ throw new Error(
110
+ `Menu item is blacklisted for safety: ${menuPath}. Use safetyCheck: false to override.`
111
+ );
108
112
  }
109
113
 
110
114
  // Validate menu path format (should contain at least one slash) - after normalization for security
111
115
  const normalizedForValidation = this.normalizeMenuPath(menuPath);
112
- if (!normalizedForValidation.includes('/') || normalizedForValidation.startsWith('/') || normalizedForValidation.endsWith('/')) {
116
+ if (
117
+ !normalizedForValidation.includes('/') ||
118
+ normalizedForValidation.startsWith('/') ||
119
+ normalizedForValidation.endsWith('/')
120
+ ) {
113
121
  throw new Error('menuPath must be in format "Category/MenuItem" (e.g., "Assets/Refresh")');
114
122
  }
115
123
 
@@ -125,13 +133,7 @@ export class MenuItemExecuteToolHandler extends BaseToolHandler {
125
133
  * @returns {Promise<Object>} The result of the menu operation
126
134
  */
127
135
  async execute(params) {
128
- const {
129
- menuPath,
130
- action = 'execute',
131
- alias,
132
- parameters,
133
- safetyCheck = true
134
- } = params;
136
+ const { menuPath, action = 'execute', alias, parameters, safetyCheck = true } = params;
135
137
 
136
138
  // Ensure connection to Unity
137
139
  if (!this.unityConnection.isConnected()) {
@@ -242,7 +244,7 @@ export class MenuItemExecuteToolHandler extends BaseToolHandler {
242
244
  isMenuPathBlacklisted(menuPath) {
243
245
  // Normalize the input path to prevent bypass attacks
244
246
  const normalizedPath = this.normalizeMenuPath(menuPath);
245
-
247
+
246
248
  // Check against normalized blacklist entries
247
249
  for (const blacklistedItem of this.blacklistedMenus) {
248
250
  const normalizedBlacklistItem = this.normalizeMenuPath(blacklistedItem);
@@ -250,7 +252,7 @@ export class MenuItemExecuteToolHandler extends BaseToolHandler {
250
252
  return true;
251
253
  }
252
254
  }
253
-
255
+
254
256
  return false;
255
257
  }
256
258
 
@@ -265,40 +267,76 @@ export class MenuItemExecuteToolHandler extends BaseToolHandler {
265
267
  }
266
268
 
267
269
  // Step 1: Remove zero-width and invisible Unicode characters
268
- let normalized = menuPath.replace(/[\u200B-\u200D\uFEFF\u00AD\u034F\u061C\u180E\u2060-\u2069]/g, '');
269
-
270
+ // eslint-disable-next-line no-misleading-character-class
271
+ let normalized = menuPath.replace(
272
+ /[\u200B-\u200D\uFEFF\u00AD\u034F\u061C\u180E\u2060-\u2069]/gu,
273
+ ''
274
+ );
275
+
270
276
  // Step 2: Normalize Unicode to canonical form (handles homograph attacks)
271
277
  normalized = normalized.normalize('NFC');
272
-
278
+
273
279
  // Step 3: Convert to lowercase for case-insensitive comparison
274
280
  normalized = normalized.toLowerCase();
275
-
281
+
276
282
  // Step 4: Trim whitespace and remove all internal whitespace (security bypass prevention)
277
283
  normalized = normalized.trim().replace(/\s+/g, '');
278
-
284
+
279
285
  // Step 5: Normalize path separators (convert backslashes to forward slashes)
280
286
  normalized = normalized.replace(/\\/g, '/');
281
-
287
+
282
288
  // Step 6: Remove duplicate path separators
283
289
  normalized = normalized.replace(/\/+/g, '/');
284
-
290
+
285
291
  // Step 7: Handle common homograph substitutions for ASCII characters
286
292
  const homographMap = {
287
293
  // Cyrillic lookalikes
288
- 'а': 'a', 'е': 'e', 'о': 'o', 'р': 'p', 'с': 'c', 'х': 'x', 'у': 'y',
289
- 'і': 'i', 'ј': 'j', 'ѕ': 's', 'һ': 'h', 'ց': 'q', 'ԁ': 'd', 'ɡ': 'g',
294
+ а: 'a',
295
+ е: 'e',
296
+ о: 'o',
297
+ р: 'p',
298
+ с: 'c',
299
+ х: 'x',
300
+ у: 'y',
301
+ і: 'i',
302
+ ј: 'j',
303
+ ѕ: 's',
304
+ һ: 'h',
305
+ ց: 'q',
306
+ ԁ: 'd',
307
+ ɡ: 'g',
290
308
  // Greek lookalikes
291
- 'α': 'a', 'β': 'b', 'γ': 'g', 'δ': 'd', 'ε': 'e', 'ζ': 'z', 'η': 'h',
292
- 'θ': 'o', 'ι': 'i', 'κ': 'k', 'λ': 'l', 'μ': 'm', 'ν': 'n', 'ξ': 'x',
293
- 'ο': 'o', 'π': 'p', 'ρ': 'p', 'σ': 's', 'τ': 't', 'υ': 'u', 'φ': 'f',
294
- 'χ': 'x', 'ψ': 'y', 'ω': 'w'
309
+ α: 'a',
310
+ β: 'b',
311
+ γ: 'g',
312
+ δ: 'd',
313
+ ε: 'e',
314
+ ζ: 'z',
315
+ η: 'h',
316
+ θ: 'o',
317
+ ι: 'i',
318
+ κ: 'k',
319
+ λ: 'l',
320
+ μ: 'm',
321
+ ν: 'n',
322
+ ξ: 'x',
323
+ ο: 'o',
324
+ π: 'p',
325
+ ρ: 'p',
326
+ σ: 's',
327
+ τ: 't',
328
+ υ: 'u',
329
+ φ: 'f',
330
+ χ: 'x',
331
+ ψ: 'y',
332
+ ω: 'w'
295
333
  };
296
-
334
+
297
335
  // Replace homographs
298
336
  for (const [homograph, ascii] of Object.entries(homographMap)) {
299
337
  normalized = normalized.replace(new RegExp(homograph, 'g'), ascii);
300
338
  }
301
-
339
+
302
340
  return normalized;
303
341
  }
304
342
  }
@@ -1,5 +1,4 @@
1
1
  import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
- import fs from 'fs/promises';
3
2
  import path from 'path';
4
3
 
5
4
  /**
@@ -24,18 +23,20 @@ export class ScreenshotAnalyzeToolHandler extends BaseToolHandler {
24
23
  analysisType: {
25
24
  type: 'string',
26
25
  enum: ['basic', 'ui', 'content', 'full'],
27
-
28
- description: 'Type of analysis: basic (colors, dimensions), ui (UI element detection), content (scene content), full (all)'
26
+
27
+ description:
28
+ 'Type of analysis: basic (colors, dimensions), ui (UI element detection), content (scene content), full (all)'
29
29
  },
30
30
  prompt: {
31
31
  type: 'string',
32
- description: 'Optional prompt for AI-based analysis (e.g., "Find all buttons in the UI")'
32
+ description:
33
+ 'Optional prompt for AI-based analysis (e.g., "Find all buttons in the UI")'
33
34
  }
34
35
  },
35
36
  required: []
36
37
  }
37
38
  );
38
-
39
+
39
40
  this.unityConnection = unityConnection;
40
41
  }
41
42
 
@@ -62,7 +63,7 @@ export class ScreenshotAnalyzeToolHandler extends BaseToolHandler {
62
63
  if (!imagePath.startsWith('Assets/')) {
63
64
  throw new Error('imagePath must be within the Assets folder');
64
65
  }
65
-
66
+
66
67
  const ext = path.extname(imagePath).toLowerCase();
67
68
  if (!['.png', '.jpg', '.jpeg'].includes(ext)) {
68
69
  throw new Error('imagePath must be a PNG or JPEG file');
@@ -159,24 +160,25 @@ export class ScreenshotAnalyzeToolHandler extends BaseToolHandler {
159
160
  result.analysis = {
160
161
  note: 'Basic analysis of base64 images requires image processing library integration',
161
162
  fileSize: fileSize,
162
- estimatedFormat: fileSize > 100000 ? 'Likely PNG or high-quality JPEG' : 'Likely compressed JPEG'
163
+ estimatedFormat:
164
+ fileSize > 100000 ? 'Likely PNG or high-quality JPEG' : 'Likely compressed JPEG'
163
165
  };
164
166
  break;
165
-
167
+
166
168
  case 'ui':
167
169
  result.uiAnalysis = {
168
170
  note: 'UI element detection requires computer vision integration',
169
171
  placeholder: 'This would detect buttons, text fields, panels, etc.'
170
172
  };
171
173
  break;
172
-
174
+
173
175
  case 'content':
174
176
  result.contentAnalysis = {
175
177
  note: 'Content analysis requires scene understanding models',
176
178
  placeholder: 'This would identify GameObjects, lighting, materials, etc.'
177
179
  };
178
180
  break;
179
-
181
+
180
182
  case 'full':
181
183
  result.fullAnalysis = {
182
184
  basic: { note: 'Requires image processing library' },