@akiojin/unity-mcp-server 2.14.14

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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +206 -0
  3. package/bin/unity-mcp-server +2 -0
  4. package/package.json +73 -0
  5. package/src/core/codeIndex.js +163 -0
  6. package/src/core/codeIndexDb.js +96 -0
  7. package/src/core/config.js +165 -0
  8. package/src/core/indexWatcher.js +52 -0
  9. package/src/core/projectInfo.js +111 -0
  10. package/src/core/server.js +294 -0
  11. package/src/core/unityConnection.js +426 -0
  12. package/src/handlers/analysis/AnalyzeSceneContentsToolHandler.js +35 -0
  13. package/src/handlers/analysis/FindByComponentToolHandler.js +20 -0
  14. package/src/handlers/analysis/GetAnimatorStateToolHandler.js +37 -0
  15. package/src/handlers/analysis/GetComponentValuesToolHandler.js +20 -0
  16. package/src/handlers/analysis/GetGameObjectDetailsToolHandler.js +35 -0
  17. package/src/handlers/analysis/GetInputActionsStateToolHandler.js +37 -0
  18. package/src/handlers/analysis/GetObjectReferencesToolHandler.js +20 -0
  19. package/src/handlers/asset/AssetDatabaseToolHandler.js +221 -0
  20. package/src/handlers/asset/AssetDependencyToolHandler.js +201 -0
  21. package/src/handlers/asset/AssetImportSettingsToolHandler.js +170 -0
  22. package/src/handlers/asset/CreateMaterialToolHandler.js +96 -0
  23. package/src/handlers/asset/CreatePrefabToolHandler.js +78 -0
  24. package/src/handlers/asset/ExitPrefabModeToolHandler.js +83 -0
  25. package/src/handlers/asset/InstantiatePrefabToolHandler.js +133 -0
  26. package/src/handlers/asset/ModifyMaterialToolHandler.js +76 -0
  27. package/src/handlers/asset/ModifyPrefabToolHandler.js +72 -0
  28. package/src/handlers/asset/OpenPrefabToolHandler.js +121 -0
  29. package/src/handlers/asset/SavePrefabToolHandler.js +106 -0
  30. package/src/handlers/base/BaseToolHandler.js +133 -0
  31. package/src/handlers/compilation/GetCompilationStateToolHandler.js +90 -0
  32. package/src/handlers/component/AddComponentToolHandler.js +126 -0
  33. package/src/handlers/component/GetComponentTypesToolHandler.js +100 -0
  34. package/src/handlers/component/ListComponentsToolHandler.js +85 -0
  35. package/src/handlers/component/ModifyComponentToolHandler.js +143 -0
  36. package/src/handlers/component/RemoveComponentToolHandler.js +108 -0
  37. package/src/handlers/console/ClearConsoleToolHandler.js +160 -0
  38. package/src/handlers/console/ReadConsoleToolHandler.js +276 -0
  39. package/src/handlers/editor/LayerManagementToolHandler.js +160 -0
  40. package/src/handlers/editor/SelectionToolHandler.js +141 -0
  41. package/src/handlers/editor/TagManagementToolHandler.js +129 -0
  42. package/src/handlers/editor/ToolManagementToolHandler.js +135 -0
  43. package/src/handlers/editor/WindowManagementToolHandler.js +125 -0
  44. package/src/handlers/gameobject/CreateGameObjectToolHandler.js +131 -0
  45. package/src/handlers/gameobject/DeleteGameObjectToolHandler.js +101 -0
  46. package/src/handlers/gameobject/FindGameObjectToolHandler.js +119 -0
  47. package/src/handlers/gameobject/GetHierarchyToolHandler.js +132 -0
  48. package/src/handlers/gameobject/ModifyGameObjectToolHandler.js +128 -0
  49. package/src/handlers/index.js +389 -0
  50. package/src/handlers/input/AddInputActionToolHandler.js +20 -0
  51. package/src/handlers/input/AddInputBindingToolHandler.js +20 -0
  52. package/src/handlers/input/CreateActionMapToolHandler.js +20 -0
  53. package/src/handlers/input/CreateCompositeBindingToolHandler.js +20 -0
  54. package/src/handlers/input/GamepadSimulationHandler.js +116 -0
  55. package/src/handlers/input/InputSystemHandler.js +80 -0
  56. package/src/handlers/input/KeyboardSimulationHandler.js +79 -0
  57. package/src/handlers/input/ManageControlSchemesToolHandler.js +20 -0
  58. package/src/handlers/input/MouseSimulationHandler.js +107 -0
  59. package/src/handlers/input/RemoveActionMapToolHandler.js +20 -0
  60. package/src/handlers/input/RemoveAllBindingsToolHandler.js +20 -0
  61. package/src/handlers/input/RemoveInputActionToolHandler.js +20 -0
  62. package/src/handlers/input/RemoveInputBindingToolHandler.js +20 -0
  63. package/src/handlers/input/TouchSimulationHandler.js +142 -0
  64. package/src/handlers/menu/ExecuteMenuItemToolHandler.js +304 -0
  65. package/src/handlers/package/PackageManagerToolHandler.js +248 -0
  66. package/src/handlers/package/RegistryConfigToolHandler.js +198 -0
  67. package/src/handlers/playmode/GetEditorStateToolHandler.js +81 -0
  68. package/src/handlers/playmode/PauseToolHandler.js +44 -0
  69. package/src/handlers/playmode/PlayToolHandler.js +91 -0
  70. package/src/handlers/playmode/StopToolHandler.js +77 -0
  71. package/src/handlers/playmode/WaitForEditorStateToolHandler.js +45 -0
  72. package/src/handlers/scene/CreateSceneToolHandler.js +91 -0
  73. package/src/handlers/scene/GetSceneInfoToolHandler.js +20 -0
  74. package/src/handlers/scene/ListScenesToolHandler.js +58 -0
  75. package/src/handlers/scene/LoadSceneToolHandler.js +92 -0
  76. package/src/handlers/scene/SaveSceneToolHandler.js +76 -0
  77. package/src/handlers/screenshot/AnalyzeScreenshotToolHandler.js +238 -0
  78. package/src/handlers/screenshot/CaptureScreenshotToolHandler.js +692 -0
  79. package/src/handlers/script/BuildCodeIndexToolHandler.js +163 -0
  80. package/src/handlers/script/ScriptCreateClassFileToolHandler.js +60 -0
  81. package/src/handlers/script/ScriptEditStructuredToolHandler.js +173 -0
  82. package/src/handlers/script/ScriptIndexStatusToolHandler.js +61 -0
  83. package/src/handlers/script/ScriptPackagesListToolHandler.js +103 -0
  84. package/src/handlers/script/ScriptReadToolHandler.js +106 -0
  85. package/src/handlers/script/ScriptRefactorRenameToolHandler.js +83 -0
  86. package/src/handlers/script/ScriptRefsFindToolHandler.js +144 -0
  87. package/src/handlers/script/ScriptRemoveSymbolToolHandler.js +79 -0
  88. package/src/handlers/script/ScriptSearchToolHandler.js +320 -0
  89. package/src/handlers/script/ScriptSymbolFindToolHandler.js +117 -0
  90. package/src/handlers/script/ScriptSymbolsGetToolHandler.js +96 -0
  91. package/src/handlers/settings/GetProjectSettingsToolHandler.js +161 -0
  92. package/src/handlers/settings/UpdateProjectSettingsToolHandler.js +272 -0
  93. package/src/handlers/system/GetCommandStatsToolHandler.js +25 -0
  94. package/src/handlers/system/PingToolHandler.js +53 -0
  95. package/src/handlers/system/RefreshAssetsToolHandler.js +45 -0
  96. package/src/handlers/ui/ClickUIElementToolHandler.js +110 -0
  97. package/src/handlers/ui/FindUIElementsToolHandler.js +63 -0
  98. package/src/handlers/ui/GetUIElementStateToolHandler.js +50 -0
  99. package/src/handlers/ui/SetUIElementValueToolHandler.js +49 -0
  100. package/src/handlers/ui/SimulateUIInputToolHandler.js +156 -0
  101. package/src/handlers/video/CaptureVideoForToolHandler.js +96 -0
  102. package/src/handlers/video/CaptureVideoStartToolHandler.js +38 -0
  103. package/src/handlers/video/CaptureVideoStatusToolHandler.js +30 -0
  104. package/src/handlers/video/CaptureVideoStopToolHandler.js +32 -0
  105. package/src/lsp/CSharpLspUtils.js +134 -0
  106. package/src/lsp/LspProcessManager.js +60 -0
  107. package/src/lsp/LspRpcClient.js +133 -0
  108. package/src/tools/analysis/analyzeSceneContents.js +100 -0
  109. package/src/tools/analysis/findByComponent.js +87 -0
  110. package/src/tools/analysis/getAnimatorState.js +326 -0
  111. package/src/tools/analysis/getComponentValues.js +182 -0
  112. package/src/tools/analysis/getGameObjectDetails.js +159 -0
  113. package/src/tools/analysis/getInputActionsState.js +329 -0
  114. package/src/tools/analysis/getObjectReferences.js +86 -0
  115. package/src/tools/input/inputActionsEditor.js +556 -0
  116. package/src/tools/scene/createScene.js +112 -0
  117. package/src/tools/scene/getSceneInfo.js +95 -0
  118. package/src/tools/scene/listScenes.js +82 -0
  119. package/src/tools/scene/loadScene.js +122 -0
  120. package/src/tools/scene/saveScene.js +91 -0
  121. package/src/tools/system/ping.js +72 -0
  122. package/src/tools/video/recordFor.js +31 -0
  123. package/src/tools/video/recordPlayMode.js +61 -0
  124. package/src/utils/csharpParse.js +88 -0
  125. package/src/utils/validators.js +90 -0
@@ -0,0 +1,304 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+
3
+ /**
4
+ * Handler for executing Unity Editor menu items
5
+ */
6
+ export class ExecuteMenuItemToolHandler extends BaseToolHandler {
7
+ constructor(unityConnection) {
8
+ super(
9
+ 'execute_menu_item',
10
+ 'Execute a Unity menu item or list available menus (with safety checks).',
11
+ {
12
+ type: 'object',
13
+ properties: {
14
+ menuPath: {
15
+ type: 'string',
16
+ description: 'Unity menu path (e.g., "Assets/Refresh", "Window/General/Console")'
17
+ },
18
+ action: {
19
+ type: 'string',
20
+ enum: ['execute', 'get_available_menus'],
21
+ description: 'Action to perform: execute menu item or get available menus (default: execute)'
22
+ },
23
+ alias: {
24
+ type: 'string',
25
+ description: 'Menu alias for common operations (e.g., "refresh", "console")'
26
+ },
27
+ parameters: {
28
+ type: 'object',
29
+ description: 'Additional parameters for menu execution (if supported)'
30
+ },
31
+ safetyCheck: {
32
+ type: 'boolean',
33
+ description: 'Enable safety checks to prevent execution of dangerous menu items (default: true)'
34
+ }
35
+ },
36
+ required: ['menuPath']
37
+ }
38
+ );
39
+
40
+ this.unityConnection = unityConnection;
41
+
42
+ // Define blacklisted menu items for safety
43
+ // Includes dialog-opening menus that cause MCP hanging
44
+ this.blacklistedMenus = new Set([
45
+ // Application control
46
+ 'File/Quit',
47
+
48
+ // Dialog-opening file operations (cause MCP hanging)
49
+ 'File/Open Scene',
50
+ 'File/New Scene',
51
+ 'File/Save Scene As...',
52
+ 'File/Build Settings...',
53
+ 'File/Build And Run',
54
+
55
+ // Dialog-opening asset operations (cause MCP hanging)
56
+ 'Assets/Import New Asset...',
57
+ 'Assets/Import Package/Custom Package...',
58
+ 'Assets/Export Package...',
59
+ 'Assets/Delete',
60
+
61
+ // Dialog-opening preferences and settings (cause MCP hanging)
62
+ 'Edit/Preferences...',
63
+ 'Edit/Project Settings...',
64
+
65
+ // Dialog-opening window operations (may cause issues)
66
+ 'Window/Package Manager',
67
+ 'Window/Asset Store',
68
+
69
+ // Scene view operations that may require focus (potential hanging)
70
+ 'GameObject/Align With View',
71
+ 'GameObject/Align View to Selected'
72
+ ]);
73
+
74
+ // Common menu aliases
75
+ this.menuAliases = new Map([
76
+ ['refresh', 'Assets/Refresh'],
77
+ ['console', 'Window/General/Console'],
78
+ ['inspector', 'Window/General/Inspector'],
79
+ ['hierarchy', 'Window/General/Hierarchy'],
80
+ ['project', 'Window/General/Project'],
81
+ ['scene', 'Window/General/Scene'],
82
+ ['game', 'Window/General/Game'],
83
+ ['animation', 'Window/Animation/Animation'],
84
+ ['animator', 'Window/Animation/Animator']
85
+ ]);
86
+ }
87
+
88
+ /**
89
+ * Validates the input parameters
90
+ * @param {Object} params - The input parameters
91
+ * @throws {Error} If validation fails
92
+ */
93
+ validate(params) {
94
+ const { menuPath, action, safetyCheck = true } = params;
95
+
96
+ // menuPath is required
97
+ if (!menuPath) {
98
+ throw new Error('menuPath is required');
99
+ }
100
+
101
+ if (typeof menuPath !== 'string' || menuPath.trim() === '') {
102
+ throw new Error('menuPath cannot be empty');
103
+ }
104
+
105
+ // Safety check for blacklisted items with security normalization (BEFORE format validation)
106
+ if (safetyCheck && this.isMenuPathBlacklisted(menuPath)) {
107
+ throw new Error(`Menu item is blacklisted for safety: ${menuPath}. Use safetyCheck: false to override.`);
108
+ }
109
+
110
+ // Validate menu path format (should contain at least one slash) - after normalization for security
111
+ const normalizedForValidation = this.normalizeMenuPath(menuPath);
112
+ if (!normalizedForValidation.includes('/') || normalizedForValidation.startsWith('/') || normalizedForValidation.endsWith('/')) {
113
+ throw new Error('menuPath must be in format "Category/MenuItem" (e.g., "Assets/Refresh")');
114
+ }
115
+
116
+ // Validate action if provided
117
+ if (action && !['execute', 'get_available_menus'].includes(action)) {
118
+ throw new Error('action must be one of: execute, get_available_menus');
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Executes the menu item operation
124
+ * @param {Object} params - The input parameters
125
+ * @returns {Promise<Object>} The result of the menu operation
126
+ */
127
+ async execute(params) {
128
+ const {
129
+ menuPath,
130
+ action = 'execute',
131
+ alias,
132
+ parameters,
133
+ safetyCheck = true
134
+ } = params;
135
+
136
+ // Ensure connection to Unity
137
+ if (!this.unityConnection.isConnected()) {
138
+ await this.unityConnection.connect();
139
+ }
140
+
141
+ // Resolve alias if provided
142
+ let resolvedMenuPath = menuPath;
143
+ if (alias && this.menuAliases.has(alias)) {
144
+ resolvedMenuPath = this.menuAliases.get(alias);
145
+ }
146
+
147
+ // Prepare command parameters
148
+ const commandParams = {
149
+ action,
150
+ menuPath: resolvedMenuPath,
151
+ safetyCheck
152
+ };
153
+
154
+ // Add optional parameters
155
+ if (alias) {
156
+ commandParams.alias = alias;
157
+ }
158
+ if (parameters) {
159
+ commandParams.parameters = parameters;
160
+ }
161
+
162
+ // Send command to Unity
163
+ const response = await this.unityConnection.sendCommand('execute_menu_item', commandParams);
164
+
165
+ // Handle Unity response
166
+ if (response.success === false) {
167
+ throw new Error(response.error || 'Failed to execute menu operation');
168
+ }
169
+
170
+ // Build result object based on action
171
+ if (action === 'get_available_menus') {
172
+ return {
173
+ availableMenus: response.availableMenus || [],
174
+ totalMenus: response.totalMenus,
175
+ filteredCount: response.filteredCount,
176
+ message: response.message || 'Available menus retrieved successfully'
177
+ };
178
+ } else {
179
+ // Execute action
180
+ const result = {
181
+ menuPath: response.menuPath || resolvedMenuPath,
182
+ message: response.message || 'Menu item executed successfully'
183
+ };
184
+
185
+ // Include optional metadata if available
186
+ if (response.executed !== undefined) {
187
+ result.executed = response.executed;
188
+ }
189
+ if (response.executionTime !== undefined) {
190
+ result.executionTime = response.executionTime;
191
+ }
192
+ if (response.menuExists !== undefined) {
193
+ result.menuExists = response.menuExists;
194
+ }
195
+ if (response.alias) {
196
+ result.alias = response.alias;
197
+ }
198
+
199
+ return result;
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Gets a list of common menu aliases
205
+ * @returns {Object} Map of aliases to menu paths
206
+ */
207
+ getMenuAliases() {
208
+ return Object.fromEntries(this.menuAliases);
209
+ }
210
+
211
+ /**
212
+ * Gets a list of blacklisted menu items
213
+ * @returns {Array} List of blacklisted menu paths
214
+ */
215
+ getBlacklistedMenus() {
216
+ return Array.from(this.blacklistedMenus);
217
+ }
218
+
219
+ /**
220
+ * Adds a custom menu alias
221
+ * @param {string} alias - The alias name
222
+ * @param {string} menuPath - The Unity menu path
223
+ */
224
+ addMenuAlias(alias, menuPath) {
225
+ this.menuAliases.set(alias, menuPath);
226
+ }
227
+
228
+ /**
229
+ * Adds a menu item to the blacklist
230
+ * @param {string} menuPath - The Unity menu path to blacklist
231
+ */
232
+ addToBlacklist(menuPath) {
233
+ this.blacklistedMenus.add(menuPath);
234
+ }
235
+
236
+ /**
237
+ * Securely checks if a menu path is blacklisted using normalized comparison
238
+ * Prevents bypass attacks using case changes, whitespace, Unicode, etc.
239
+ * @param {string} menuPath - The menu path to check
240
+ * @returns {boolean} True if the path is blacklisted
241
+ */
242
+ isMenuPathBlacklisted(menuPath) {
243
+ // Normalize the input path to prevent bypass attacks
244
+ const normalizedPath = this.normalizeMenuPath(menuPath);
245
+
246
+ // Check against normalized blacklist entries
247
+ for (const blacklistedItem of this.blacklistedMenus) {
248
+ const normalizedBlacklistItem = this.normalizeMenuPath(blacklistedItem);
249
+ if (normalizedPath === normalizedBlacklistItem) {
250
+ return true;
251
+ }
252
+ }
253
+
254
+ return false;
255
+ }
256
+
257
+ /**
258
+ * Normalizes a menu path to prevent security bypass attacks
259
+ * @param {string} menuPath - The raw menu path
260
+ * @returns {string} The normalized path
261
+ */
262
+ normalizeMenuPath(menuPath) {
263
+ if (!menuPath || typeof menuPath !== 'string') {
264
+ return '';
265
+ }
266
+
267
+ // 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
+ // Step 2: Normalize Unicode to canonical form (handles homograph attacks)
271
+ normalized = normalized.normalize('NFC');
272
+
273
+ // Step 3: Convert to lowercase for case-insensitive comparison
274
+ normalized = normalized.toLowerCase();
275
+
276
+ // Step 4: Trim whitespace and remove all internal whitespace (security bypass prevention)
277
+ normalized = normalized.trim().replace(/\s+/g, '');
278
+
279
+ // Step 5: Normalize path separators (convert backslashes to forward slashes)
280
+ normalized = normalized.replace(/\\/g, '/');
281
+
282
+ // Step 6: Remove duplicate path separators
283
+ normalized = normalized.replace(/\/+/g, '/');
284
+
285
+ // Step 7: Handle common homograph substitutions for ASCII characters
286
+ const homographMap = {
287
+ // Cyrillic lookalikes
288
+ 'а': 'a', 'е': 'e', 'о': 'o', 'р': 'p', 'с': 'c', 'х': 'x', 'у': 'y',
289
+ 'і': 'i', 'ј': 'j', 'ѕ': 's', 'һ': 'h', 'ց': 'q', 'ԁ': 'd', 'ɡ': 'g',
290
+ // 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'
295
+ };
296
+
297
+ // Replace homographs
298
+ for (const [homograph, ascii] of Object.entries(homographMap)) {
299
+ normalized = normalized.replace(new RegExp(homograph, 'g'), ascii);
300
+ }
301
+
302
+ return normalized;
303
+ }
304
+ }
@@ -0,0 +1,248 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+
3
+ /**
4
+ * Package Manager Tool Handler for Unity MCP
5
+ * Handles Unity Package Manager operations including search, install, and management
6
+ */
7
+
8
+ export default class PackageManagerToolHandler extends BaseToolHandler {
9
+ constructor(unityConnection) {
10
+ super(
11
+ 'package_manager',
12
+ 'Manage Unity packages - search, install, remove, and list packages',
13
+ {
14
+ type: 'object',
15
+ properties: {
16
+ action: {
17
+ type: 'string',
18
+ enum: ['search', 'list', 'install', 'remove', 'info', 'recommend'],
19
+ description: 'The package operation to perform'
20
+ },
21
+ keyword: {
22
+ type: 'string',
23
+ description: 'Search keyword (for search action)'
24
+ },
25
+ packageId: {
26
+ type: 'string',
27
+ description: 'Package ID to install (e.g., com.unity.textmeshpro)'
28
+ },
29
+ packageName: {
30
+ type: 'string',
31
+ description: 'Package name to remove or get info'
32
+ },
33
+ version: {
34
+ type: 'string',
35
+ description: 'Specific version to install (optional)'
36
+ },
37
+ category: {
38
+ type: 'string',
39
+ enum: ['essential', 'rendering', 'tools', 'networking', 'mobile'],
40
+ description: 'Category for recommendations'
41
+ },
42
+ includeBuiltIn: {
43
+ type: 'boolean',
44
+ description: 'Include built-in packages in list (default: false)'
45
+ },
46
+ limit: {
47
+ type: 'number',
48
+ description: 'Maximum number of search results (default: 20)'
49
+ }
50
+ },
51
+ required: ['action']
52
+ }
53
+ );
54
+ this.unityConnection = unityConnection;
55
+ }
56
+
57
+ validate(params) {
58
+ const { action, keyword, packageId, packageName } = params || {};
59
+
60
+ if (!action) {
61
+ throw new Error('action is required');
62
+ }
63
+
64
+ const valid = ['search', 'list', 'install', 'remove', 'info', 'recommend'];
65
+ if (!valid.includes(action)) {
66
+ throw new Error(`Invalid action: ${action}. Must be one of: ${valid.join(', ')}`);
67
+ }
68
+
69
+ switch (action) {
70
+ case 'search':
71
+ if (!keyword) throw new Error('Search keyword is required for search action');
72
+ break;
73
+ case 'install':
74
+ if (!packageId) throw new Error('Package ID is required for install action');
75
+ break;
76
+ case 'remove':
77
+ case 'info':
78
+ if (!packageName) throw new Error('Package name is required for this action');
79
+ break;
80
+ }
81
+ }
82
+
83
+ async execute(params) {
84
+ const { action, ...parameters } = params;
85
+
86
+ // Ensure connected
87
+ if (!this.unityConnection.isConnected()) {
88
+ await this.unityConnection.connect();
89
+ }
90
+
91
+ const result = await this.unityConnection.sendCommand('package_manager', {
92
+ action,
93
+ ...parameters
94
+ });
95
+
96
+ return this.formatResponse(action, result);
97
+ }
98
+
99
+ formatResponse(action, result) {
100
+ if (result && result.error) {
101
+ return {
102
+ success: false,
103
+ error: result.error
104
+ };
105
+ }
106
+
107
+ switch (action) {
108
+ case 'search':
109
+ return {
110
+ success: result.success,
111
+ action: 'search',
112
+ keyword: result.keyword,
113
+ totalCount: result.totalCount || 0,
114
+ packages: (result.packages || []).map(pkg => ({
115
+ packageId: pkg.packageId,
116
+ name: pkg.name,
117
+ displayName: pkg.displayName,
118
+ description: this.truncateDescription(pkg.description),
119
+ version: pkg.version,
120
+ category: pkg.category
121
+ })),
122
+ message: result.message || `Found ${result.totalCount || 0} packages`
123
+ };
124
+
125
+ case 'list':
126
+ return {
127
+ success: result.success,
128
+ action: 'list',
129
+ totalCount: result.totalCount || 0,
130
+ packages: (result.packages || []).map(pkg => ({
131
+ packageId: pkg.packageId,
132
+ name: pkg.name,
133
+ displayName: pkg.displayName,
134
+ version: pkg.version,
135
+ source: pkg.source,
136
+ isDirectDependency: pkg.isDirectDependency
137
+ })),
138
+ message: result.message || `Listed ${result.totalCount || 0} installed packages`
139
+ };
140
+
141
+ case 'install':
142
+ return {
143
+ success: result.success,
144
+ action: 'install',
145
+ packageId: result.packageId,
146
+ name: result.name,
147
+ displayName: result.displayName,
148
+ version: result.version,
149
+ message: result.message || `Successfully installed ${result.displayName || result.name}`
150
+ };
151
+
152
+ case 'remove':
153
+ return {
154
+ success: result.success,
155
+ action: 'remove',
156
+ packageName: result.packageName,
157
+ message: result.message || `Successfully removed ${result.packageName}`
158
+ };
159
+
160
+ case 'info':
161
+ if (result.package) {
162
+ return {
163
+ success: result.success,
164
+ action: 'info',
165
+ package: {
166
+ ...result.package,
167
+ description: this.truncateDescription(result.package.description)
168
+ },
169
+ message: `Package information for ${result.package.displayName || result.package.name}`
170
+ };
171
+ }
172
+ return result;
173
+
174
+ case 'recommend':
175
+ return {
176
+ success: result.success,
177
+ action: 'recommend',
178
+ category: result.category,
179
+ categories: result.categories,
180
+ packages: result.packages,
181
+ allPackages: result.allPackages,
182
+ message: result.message || 'Package recommendations retrieved'
183
+ }; }
184
+ }
185
+
186
+ truncateDescription(description) {
187
+ if (!description) return '';
188
+ const maxLength = 150;
189
+ if (description.length <= maxLength) return description;
190
+ return description.substring(0, maxLength) + '...';
191
+ }
192
+
193
+ static getExamples() {
194
+ return [
195
+ {
196
+ description: 'Search for packages',
197
+ input: {
198
+ action: 'search',
199
+ keyword: 'timeline',
200
+ limit: 10
201
+ }
202
+ },
203
+ {
204
+ description: 'Install a package',
205
+ input: {
206
+ action: 'install',
207
+ packageId: 'com.unity.textmeshpro'
208
+ }
209
+ },
210
+ {
211
+ description: 'Install a specific version',
212
+ input: {
213
+ action: 'install',
214
+ packageId: 'com.unity.timeline',
215
+ version: '1.7.1'
216
+ }
217
+ },
218
+ {
219
+ description: 'List installed packages',
220
+ input: {
221
+ action: 'list',
222
+ includeBuiltIn: false
223
+ }
224
+ },
225
+ {
226
+ description: 'Remove a package',
227
+ input: {
228
+ action: 'remove',
229
+ packageName: 'com.unity.timeline'
230
+ }
231
+ },
232
+ {
233
+ description: 'Get package info',
234
+ input: {
235
+ action: 'info',
236
+ packageName: 'com.unity.textmeshpro'
237
+ }
238
+ },
239
+ {
240
+ description: 'Get recommended packages',
241
+ input: {
242
+ action: 'recommend',
243
+ category: 'essential'
244
+ }
245
+ }
246
+ ];
247
+ }
248
+ }