@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.
- package/LICENSE +21 -0
- package/README.md +206 -0
- package/bin/unity-mcp-server +2 -0
- package/package.json +73 -0
- package/src/core/codeIndex.js +163 -0
- package/src/core/codeIndexDb.js +96 -0
- package/src/core/config.js +165 -0
- package/src/core/indexWatcher.js +52 -0
- package/src/core/projectInfo.js +111 -0
- package/src/core/server.js +294 -0
- package/src/core/unityConnection.js +426 -0
- package/src/handlers/analysis/AnalyzeSceneContentsToolHandler.js +35 -0
- package/src/handlers/analysis/FindByComponentToolHandler.js +20 -0
- package/src/handlers/analysis/GetAnimatorStateToolHandler.js +37 -0
- package/src/handlers/analysis/GetComponentValuesToolHandler.js +20 -0
- package/src/handlers/analysis/GetGameObjectDetailsToolHandler.js +35 -0
- package/src/handlers/analysis/GetInputActionsStateToolHandler.js +37 -0
- package/src/handlers/analysis/GetObjectReferencesToolHandler.js +20 -0
- package/src/handlers/asset/AssetDatabaseToolHandler.js +221 -0
- package/src/handlers/asset/AssetDependencyToolHandler.js +201 -0
- package/src/handlers/asset/AssetImportSettingsToolHandler.js +170 -0
- package/src/handlers/asset/CreateMaterialToolHandler.js +96 -0
- package/src/handlers/asset/CreatePrefabToolHandler.js +78 -0
- package/src/handlers/asset/ExitPrefabModeToolHandler.js +83 -0
- package/src/handlers/asset/InstantiatePrefabToolHandler.js +133 -0
- package/src/handlers/asset/ModifyMaterialToolHandler.js +76 -0
- package/src/handlers/asset/ModifyPrefabToolHandler.js +72 -0
- package/src/handlers/asset/OpenPrefabToolHandler.js +121 -0
- package/src/handlers/asset/SavePrefabToolHandler.js +106 -0
- package/src/handlers/base/BaseToolHandler.js +133 -0
- package/src/handlers/compilation/GetCompilationStateToolHandler.js +90 -0
- package/src/handlers/component/AddComponentToolHandler.js +126 -0
- package/src/handlers/component/GetComponentTypesToolHandler.js +100 -0
- package/src/handlers/component/ListComponentsToolHandler.js +85 -0
- package/src/handlers/component/ModifyComponentToolHandler.js +143 -0
- package/src/handlers/component/RemoveComponentToolHandler.js +108 -0
- package/src/handlers/console/ClearConsoleToolHandler.js +160 -0
- package/src/handlers/console/ReadConsoleToolHandler.js +276 -0
- package/src/handlers/editor/LayerManagementToolHandler.js +160 -0
- package/src/handlers/editor/SelectionToolHandler.js +141 -0
- package/src/handlers/editor/TagManagementToolHandler.js +129 -0
- package/src/handlers/editor/ToolManagementToolHandler.js +135 -0
- package/src/handlers/editor/WindowManagementToolHandler.js +125 -0
- package/src/handlers/gameobject/CreateGameObjectToolHandler.js +131 -0
- package/src/handlers/gameobject/DeleteGameObjectToolHandler.js +101 -0
- package/src/handlers/gameobject/FindGameObjectToolHandler.js +119 -0
- package/src/handlers/gameobject/GetHierarchyToolHandler.js +132 -0
- package/src/handlers/gameobject/ModifyGameObjectToolHandler.js +128 -0
- package/src/handlers/index.js +389 -0
- package/src/handlers/input/AddInputActionToolHandler.js +20 -0
- package/src/handlers/input/AddInputBindingToolHandler.js +20 -0
- package/src/handlers/input/CreateActionMapToolHandler.js +20 -0
- package/src/handlers/input/CreateCompositeBindingToolHandler.js +20 -0
- package/src/handlers/input/GamepadSimulationHandler.js +116 -0
- package/src/handlers/input/InputSystemHandler.js +80 -0
- package/src/handlers/input/KeyboardSimulationHandler.js +79 -0
- package/src/handlers/input/ManageControlSchemesToolHandler.js +20 -0
- package/src/handlers/input/MouseSimulationHandler.js +107 -0
- package/src/handlers/input/RemoveActionMapToolHandler.js +20 -0
- package/src/handlers/input/RemoveAllBindingsToolHandler.js +20 -0
- package/src/handlers/input/RemoveInputActionToolHandler.js +20 -0
- package/src/handlers/input/RemoveInputBindingToolHandler.js +20 -0
- package/src/handlers/input/TouchSimulationHandler.js +142 -0
- package/src/handlers/menu/ExecuteMenuItemToolHandler.js +304 -0
- package/src/handlers/package/PackageManagerToolHandler.js +248 -0
- package/src/handlers/package/RegistryConfigToolHandler.js +198 -0
- package/src/handlers/playmode/GetEditorStateToolHandler.js +81 -0
- package/src/handlers/playmode/PauseToolHandler.js +44 -0
- package/src/handlers/playmode/PlayToolHandler.js +91 -0
- package/src/handlers/playmode/StopToolHandler.js +77 -0
- package/src/handlers/playmode/WaitForEditorStateToolHandler.js +45 -0
- package/src/handlers/scene/CreateSceneToolHandler.js +91 -0
- package/src/handlers/scene/GetSceneInfoToolHandler.js +20 -0
- package/src/handlers/scene/ListScenesToolHandler.js +58 -0
- package/src/handlers/scene/LoadSceneToolHandler.js +92 -0
- package/src/handlers/scene/SaveSceneToolHandler.js +76 -0
- package/src/handlers/screenshot/AnalyzeScreenshotToolHandler.js +238 -0
- package/src/handlers/screenshot/CaptureScreenshotToolHandler.js +692 -0
- package/src/handlers/script/BuildCodeIndexToolHandler.js +163 -0
- package/src/handlers/script/ScriptCreateClassFileToolHandler.js +60 -0
- package/src/handlers/script/ScriptEditStructuredToolHandler.js +173 -0
- package/src/handlers/script/ScriptIndexStatusToolHandler.js +61 -0
- package/src/handlers/script/ScriptPackagesListToolHandler.js +103 -0
- package/src/handlers/script/ScriptReadToolHandler.js +106 -0
- package/src/handlers/script/ScriptRefactorRenameToolHandler.js +83 -0
- package/src/handlers/script/ScriptRefsFindToolHandler.js +144 -0
- package/src/handlers/script/ScriptRemoveSymbolToolHandler.js +79 -0
- package/src/handlers/script/ScriptSearchToolHandler.js +320 -0
- package/src/handlers/script/ScriptSymbolFindToolHandler.js +117 -0
- package/src/handlers/script/ScriptSymbolsGetToolHandler.js +96 -0
- package/src/handlers/settings/GetProjectSettingsToolHandler.js +161 -0
- package/src/handlers/settings/UpdateProjectSettingsToolHandler.js +272 -0
- package/src/handlers/system/GetCommandStatsToolHandler.js +25 -0
- package/src/handlers/system/PingToolHandler.js +53 -0
- package/src/handlers/system/RefreshAssetsToolHandler.js +45 -0
- package/src/handlers/ui/ClickUIElementToolHandler.js +110 -0
- package/src/handlers/ui/FindUIElementsToolHandler.js +63 -0
- package/src/handlers/ui/GetUIElementStateToolHandler.js +50 -0
- package/src/handlers/ui/SetUIElementValueToolHandler.js +49 -0
- package/src/handlers/ui/SimulateUIInputToolHandler.js +156 -0
- package/src/handlers/video/CaptureVideoForToolHandler.js +96 -0
- package/src/handlers/video/CaptureVideoStartToolHandler.js +38 -0
- package/src/handlers/video/CaptureVideoStatusToolHandler.js +30 -0
- package/src/handlers/video/CaptureVideoStopToolHandler.js +32 -0
- package/src/lsp/CSharpLspUtils.js +134 -0
- package/src/lsp/LspProcessManager.js +60 -0
- package/src/lsp/LspRpcClient.js +133 -0
- package/src/tools/analysis/analyzeSceneContents.js +100 -0
- package/src/tools/analysis/findByComponent.js +87 -0
- package/src/tools/analysis/getAnimatorState.js +326 -0
- package/src/tools/analysis/getComponentValues.js +182 -0
- package/src/tools/analysis/getGameObjectDetails.js +159 -0
- package/src/tools/analysis/getInputActionsState.js +329 -0
- package/src/tools/analysis/getObjectReferences.js +86 -0
- package/src/tools/input/inputActionsEditor.js +556 -0
- package/src/tools/scene/createScene.js +112 -0
- package/src/tools/scene/getSceneInfo.js +95 -0
- package/src/tools/scene/listScenes.js +82 -0
- package/src/tools/scene/loadScene.js +122 -0
- package/src/tools/scene/saveScene.js +91 -0
- package/src/tools/system/ping.js +72 -0
- package/src/tools/video/recordFor.js +31 -0
- package/src/tools/video/recordPlayMode.js +61 -0
- package/src/utils/csharpParse.js +88 -0
- 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
|
+
}
|