unreal-engine-mcp-server 0.5.0 → 0.5.2

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 (188) hide show
  1. package/.env.example +1 -1
  2. package/.github/release-drafter-config.yml +51 -0
  3. package/.github/workflows/greetings.yml +5 -1
  4. package/.github/workflows/labeler.yml +2 -1
  5. package/.github/workflows/publish-mcp.yml +2 -4
  6. package/.github/workflows/release-drafter.yml +3 -2
  7. package/.github/workflows/release.yml +3 -3
  8. package/CHANGELOG.md +109 -0
  9. package/CONTRIBUTING.md +1 -1
  10. package/GEMINI.md +115 -0
  11. package/Public/Plugin_setup_guide.mp4 +0 -0
  12. package/README.md +166 -200
  13. package/dist/automation/bridge.d.ts +1 -2
  14. package/dist/automation/bridge.js +24 -23
  15. package/dist/automation/connection-manager.d.ts +1 -0
  16. package/dist/automation/connection-manager.js +10 -0
  17. package/dist/automation/message-handler.js +5 -4
  18. package/dist/automation/request-tracker.d.ts +4 -0
  19. package/dist/automation/request-tracker.js +11 -3
  20. package/dist/config.d.ts +0 -1
  21. package/dist/config.js +0 -1
  22. package/dist/constants.d.ts +4 -0
  23. package/dist/constants.js +4 -0
  24. package/dist/graphql/loaders.d.ts +64 -0
  25. package/dist/graphql/loaders.js +117 -0
  26. package/dist/graphql/resolvers.d.ts +3 -3
  27. package/dist/graphql/resolvers.js +33 -30
  28. package/dist/graphql/server.js +3 -1
  29. package/dist/graphql/types.d.ts +2 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.js +13 -2
  32. package/dist/server-setup.d.ts +0 -1
  33. package/dist/server-setup.js +0 -40
  34. package/dist/tools/actors.d.ts +58 -24
  35. package/dist/tools/actors.js +22 -6
  36. package/dist/tools/assets.d.ts +19 -71
  37. package/dist/tools/assets.js +28 -22
  38. package/dist/tools/base-tool.d.ts +4 -4
  39. package/dist/tools/base-tool.js +1 -1
  40. package/dist/tools/blueprint.d.ts +45 -61
  41. package/dist/tools/blueprint.js +43 -14
  42. package/dist/tools/consolidated-tool-definitions.js +2 -1
  43. package/dist/tools/consolidated-tool-handlers.js +96 -110
  44. package/dist/tools/dynamic-handler-registry.d.ts +11 -9
  45. package/dist/tools/dynamic-handler-registry.js +17 -95
  46. package/dist/tools/editor.d.ts +19 -193
  47. package/dist/tools/editor.js +11 -2
  48. package/dist/tools/environment.d.ts +8 -14
  49. package/dist/tools/foliage.d.ts +18 -143
  50. package/dist/tools/foliage.js +4 -2
  51. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  52. package/dist/tools/handlers/actor-handlers.js +14 -13
  53. package/dist/tools/handlers/asset-handlers.js +454 -454
  54. package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
  55. package/dist/tools/handlers/sequence-handlers.js +24 -13
  56. package/dist/tools/introspection.d.ts +1 -1
  57. package/dist/tools/introspection.js +1 -1
  58. package/dist/tools/landscape.d.ts +16 -116
  59. package/dist/tools/landscape.js +7 -3
  60. package/dist/tools/level.d.ts +22 -103
  61. package/dist/tools/level.js +26 -18
  62. package/dist/tools/lighting.d.ts +54 -7
  63. package/dist/tools/lighting.js +9 -5
  64. package/dist/tools/materials.d.ts +1 -1
  65. package/dist/tools/materials.js +5 -1
  66. package/dist/tools/niagara.js +37 -2
  67. package/dist/tools/performance.d.ts +0 -1
  68. package/dist/tools/performance.js +0 -1
  69. package/dist/tools/physics.js +5 -1
  70. package/dist/tools/sequence.d.ts +24 -24
  71. package/dist/tools/sequence.js +13 -0
  72. package/dist/tools/ui.d.ts +0 -2
  73. package/dist/types/automation-responses.d.ts +115 -0
  74. package/dist/types/automation-responses.js +2 -0
  75. package/dist/types/responses.d.ts +249 -0
  76. package/dist/types/responses.js +2 -0
  77. package/dist/types/tool-interfaces.d.ts +135 -135
  78. package/dist/types/tool-types.d.ts +2 -0
  79. package/dist/unreal-bridge.js +4 -4
  80. package/dist/utils/command-validator.js +7 -5
  81. package/dist/utils/error-handler.d.ts +24 -2
  82. package/dist/utils/error-handler.js +58 -23
  83. package/dist/utils/normalize.d.ts +7 -4
  84. package/dist/utils/normalize.js +12 -10
  85. package/dist/utils/path-security.d.ts +2 -0
  86. package/dist/utils/path-security.js +24 -0
  87. package/dist/utils/response-factory.d.ts +4 -4
  88. package/dist/utils/response-factory.js +15 -21
  89. package/dist/utils/response-validator.js +88 -73
  90. package/dist/utils/unreal-command-queue.d.ts +2 -0
  91. package/dist/utils/unreal-command-queue.js +8 -1
  92. package/docs/Migration-Guide-v0.5.0.md +1 -9
  93. package/docs/handler-mapping.md +4 -2
  94. package/docs/testing-guide.md +2 -2
  95. package/package.json +12 -6
  96. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
  97. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
  98. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
  99. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
  100. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
  101. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
  102. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
  103. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
  104. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
  105. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
  106. package/scripts/run-all-tests.mjs +25 -20
  107. package/server.json +3 -2
  108. package/src/automation/bridge.ts +27 -25
  109. package/src/automation/connection-manager.ts +18 -0
  110. package/src/automation/message-handler.ts +33 -8
  111. package/src/automation/request-tracker.ts +39 -7
  112. package/src/config.ts +1 -1
  113. package/src/constants.ts +7 -0
  114. package/src/graphql/loaders.ts +244 -0
  115. package/src/graphql/resolvers.ts +47 -49
  116. package/src/graphql/server.ts +3 -1
  117. package/src/graphql/types.ts +3 -0
  118. package/src/index.ts +15 -2
  119. package/src/resources/assets.ts +5 -4
  120. package/src/server/tool-registry.ts +3 -3
  121. package/src/server-setup.ts +3 -37
  122. package/src/tools/actors.ts +77 -44
  123. package/src/tools/animation.ts +1 -0
  124. package/src/tools/assets.ts +76 -65
  125. package/src/tools/base-tool.ts +3 -3
  126. package/src/tools/blueprint.ts +170 -104
  127. package/src/tools/consolidated-tool-definitions.ts +2 -1
  128. package/src/tools/consolidated-tool-handlers.ts +129 -150
  129. package/src/tools/dynamic-handler-registry.ts +22 -140
  130. package/src/tools/editor.ts +43 -29
  131. package/src/tools/environment.ts +21 -27
  132. package/src/tools/foliage.ts +28 -25
  133. package/src/tools/handlers/actor-handlers.ts +16 -17
  134. package/src/tools/handlers/asset-handlers.ts +484 -484
  135. package/src/tools/handlers/sequence-handlers.ts +85 -62
  136. package/src/tools/introspection.ts +7 -7
  137. package/src/tools/landscape.ts +34 -28
  138. package/src/tools/level.ts +100 -80
  139. package/src/tools/lighting.ts +25 -20
  140. package/src/tools/materials.ts +9 -3
  141. package/src/tools/niagara.ts +44 -2
  142. package/src/tools/performance.ts +1 -2
  143. package/src/tools/physics.ts +7 -1
  144. package/src/tools/sequence.ts +42 -26
  145. package/src/tools/ui.ts +1 -3
  146. package/src/types/automation-responses.ts +119 -0
  147. package/src/types/responses.ts +355 -0
  148. package/src/types/tool-interfaces.ts +135 -135
  149. package/src/types/tool-types.ts +4 -0
  150. package/src/unreal-bridge.ts +71 -26
  151. package/src/utils/command-validator.ts +47 -5
  152. package/src/utils/error-handler.ts +128 -45
  153. package/src/utils/normalize.test.ts +162 -0
  154. package/src/utils/normalize.ts +38 -16
  155. package/src/utils/path-security.ts +43 -0
  156. package/src/utils/response-factory.ts +29 -24
  157. package/src/utils/response-validator.ts +103 -87
  158. package/src/utils/safe-json.test.ts +90 -0
  159. package/src/utils/unreal-command-queue.ts +13 -1
  160. package/src/utils/validation.test.ts +184 -0
  161. package/tests/test-animation.mjs +358 -33
  162. package/tests/test-asset-graph.mjs +311 -0
  163. package/tests/test-audio.mjs +314 -116
  164. package/tests/test-behavior-tree.mjs +327 -144
  165. package/tests/test-blueprint-graph.mjs +343 -12
  166. package/tests/test-control-editor.mjs +85 -53
  167. package/tests/test-graphql.mjs +58 -8
  168. package/tests/test-input.mjs +349 -0
  169. package/tests/test-inspect.mjs +291 -61
  170. package/tests/test-landscape.mjs +304 -48
  171. package/tests/test-lighting.mjs +428 -0
  172. package/tests/test-manage-level.mjs +70 -51
  173. package/tests/test-performance.mjs +539 -0
  174. package/tests/test-sequence.mjs +82 -46
  175. package/tests/test-system.mjs +72 -33
  176. package/tests/test-wasm.mjs +98 -8
  177. package/vitest.config.ts +35 -0
  178. package/.github/release-drafter.yml +0 -148
  179. package/dist/prompts/index.d.ts +0 -21
  180. package/dist/prompts/index.js +0 -217
  181. package/dist/tools/blueprint/helpers.d.ts +0 -29
  182. package/dist/tools/blueprint/helpers.js +0 -182
  183. package/src/prompts/index.ts +0 -249
  184. package/src/tools/blueprint/helpers.ts +0 -189
  185. package/tests/test-blueprint-events.mjs +0 -35
  186. package/tests/test-extra-tools.mjs +0 -38
  187. package/tests/test-render.mjs +0 -33
  188. package/tests/test-search-assets.mjs +0 -66
@@ -2,531 +2,531 @@ import { cleanObject } from '../../utils/safe-json.js';
2
2
  import { ITools } from '../../types/tool-interfaces.js';
3
3
  import { executeAutomationRequest } from './common-handlers.js';
4
4
  import { normalizeArgs } from './argument-helper.js';
5
+ import { ResponseFactory } from '../../utils/response-factory.js';
5
6
 
6
7
  export async function handleAssetTools(action: string, args: any, tools: ITools) {
7
- switch (action) {
8
- case 'list': {
9
- // Route through C++ HandleListAssets for proper asset enumeration
10
- const params = normalizeArgs(args, [
11
- { key: 'path', aliases: ['directory', 'assetPath'], default: '/Game' },
12
- { key: 'limit', default: 50 },
13
- { key: 'recursive', default: false },
14
- { key: 'depth', default: undefined }
15
- ]);
16
-
17
- const recursive = params.recursive === true || (params.depth !== undefined && params.depth > 0);
18
-
19
- const res = await executeAutomationRequest(tools, 'list', {
20
- path: params.path,
21
- recursive,
22
- depth: params.depth
23
- });
24
-
25
- // const result = cleanObject(res); // Unused
26
- const response = res as any;
27
- const assets = (Array.isArray(response.assets) ? response.assets :
28
- (Array.isArray(response.result) ? response.result : (response.result?.assets || [])));
29
-
30
- // New: Handle folders
31
- const folders = Array.isArray(response.folders) ? response.folders : (response.result?.folders || []);
32
-
33
- const totalCount = assets.length;
34
- const limitedAssets = assets.slice(0, params.limit);
35
- const remaining = Math.max(0, totalCount - params.limit);
36
-
37
- let message = `Found ${totalCount} assets`;
38
- if (folders.length > 0) {
39
- message += ` and ${folders.length} folders`;
40
- }
41
- message += `: ${limitedAssets.map((a: any) => a.path || a.package || a.name).join(', ')}`;
42
-
43
- if (folders.length > 0 && limitedAssets.length < params.limit) {
44
- const remainingLimit = params.limit - limitedAssets.length;
45
- if (remainingLimit > 0) {
46
- const limitedFolders = folders.slice(0, remainingLimit);
47
- if (limitedAssets.length > 0) message += ', ';
48
- message += `Folders: [${limitedFolders.join(', ')}]`;
49
- if (folders.length > remainingLimit) message += '...';
8
+ try {
9
+ switch (action) {
10
+ case 'list': {
11
+ // Route through C++ HandleListAssets for proper asset enumeration
12
+ const params = normalizeArgs(args, [
13
+ { key: 'path', aliases: ['directory', 'assetPath'], default: '/Game' },
14
+ { key: 'limit', default: 50 },
15
+ { key: 'recursive', default: false },
16
+ { key: 'depth', default: undefined }
17
+ ]);
18
+
19
+ const recursive = params.recursive === true || (params.depth !== undefined && params.depth > 0);
20
+
21
+ const res = await executeAutomationRequest(tools, 'list', {
22
+ path: params.path,
23
+ recursive,
24
+ depth: params.depth
25
+ });
26
+
27
+
28
+ const response = res as any;
29
+ const assets = (Array.isArray(response.assets) ? response.assets :
30
+ (Array.isArray(response.result) ? response.result : (response.result?.assets || [])));
31
+
32
+ // New: Handle folders
33
+ const folders = Array.isArray(response.folders) ? response.folders : (response.result?.folders || []);
34
+
35
+ const totalCount = assets.length;
36
+ const limitedAssets = assets.slice(0, params.limit);
37
+ const remaining = Math.max(0, totalCount - params.limit);
38
+
39
+ let message = `Found ${totalCount} assets`;
40
+ if (folders.length > 0) {
41
+ message += ` and ${folders.length} folders`;
50
42
  }
43
+ message += `: ${limitedAssets.map((a: any) => a.path || a.package || a.name).join(', ')}`;
44
+
45
+ if (folders.length > 0 && limitedAssets.length < params.limit) {
46
+ const remainingLimit = params.limit - limitedAssets.length;
47
+ if (remainingLimit > 0) {
48
+ const limitedFolders = folders.slice(0, remainingLimit);
49
+ if (limitedAssets.length > 0) message += ', ';
50
+ message += `Folders: [${limitedFolders.join(', ')}]`;
51
+ if (folders.length > remainingLimit) message += '...';
52
+ }
53
+ }
54
+
55
+ if (remaining > 0) {
56
+ message += `... and ${remaining} others`;
57
+ }
58
+
59
+ return ResponseFactory.success({
60
+ assets: limitedAssets,
61
+ folders: folders,
62
+ totalCount: totalCount,
63
+ count: limitedAssets.length
64
+ }, message);
51
65
  }
66
+ case 'create_folder': {
67
+ const params = normalizeArgs(args, [
68
+ { key: 'path', aliases: ['directoryPath'], required: true }
69
+ ]);
70
+ const res = await tools.assetTools.createFolder(params.path);
71
+ return ResponseFactory.success(res, 'Folder created successfully');
72
+ }
73
+ case 'import': {
74
+ const params = normalizeArgs(args, [
75
+ { key: 'sourcePath', required: true },
76
+ { key: 'destinationPath', required: true },
77
+ { key: 'overwrite', default: false },
78
+ { key: 'save', default: true }
79
+ ]);
52
80
 
53
- if (remaining > 0) {
54
- message += `... and ${remaining} others`;
81
+ const res = await tools.assetTools.importAsset({
82
+ sourcePath: params.sourcePath,
83
+ destinationPath: params.destinationPath,
84
+ overwrite: params.overwrite,
85
+ save: params.save
86
+ });
87
+ return ResponseFactory.success(res, 'Asset imported successfully');
55
88
  }
89
+ case 'duplicate': {
90
+ const params = normalizeArgs(args, [
91
+ { key: 'sourcePath', aliases: ['assetPath'], required: true },
92
+ { key: 'destinationPath' },
93
+ { key: 'newName' }
94
+ ]);
95
+
96
+ let destinationPath = params.destinationPath;
97
+ if (params.newName) {
98
+ if (!destinationPath) {
99
+ const lastSlash = params.sourcePath.lastIndexOf('/');
100
+ const parentDir = lastSlash > 0 ? params.sourcePath.substring(0, lastSlash) : '/Game';
101
+ destinationPath = `${parentDir}/${params.newName}`;
102
+ } else if (!destinationPath.endsWith(params.newName)) {
103
+ if (destinationPath.endsWith('/')) {
104
+ destinationPath = `${destinationPath}${params.newName}`;
105
+ }
106
+ }
107
+ }
56
108
 
57
- return {
58
- message: message,
59
- assets: limitedAssets,
60
- folders: folders,
61
- totalCount: totalCount,
62
- count: limitedAssets.length
63
- };
64
- }
65
- case 'create_folder': {
66
- const params = normalizeArgs(args, [
67
- { key: 'path', aliases: ['directoryPath'], required: true }
68
- ]);
69
- const res = await tools.assetTools.createFolder(params.path);
70
- return cleanObject(res);
71
- }
72
- case 'import': {
73
- const params = normalizeArgs(args, [
74
- { key: 'sourcePath', required: true },
75
- { key: 'destinationPath', required: true },
76
- { key: 'overwrite', default: false },
77
- { key: 'save', default: true }
78
- ]);
79
-
80
- const res = await tools.assetTools.importAsset({
81
- sourcePath: params.sourcePath,
82
- destinationPath: params.destinationPath,
83
- overwrite: params.overwrite,
84
- save: params.save
85
- });
86
- return cleanObject(res);
87
- }
88
- case 'duplicate': {
89
- const params = normalizeArgs(args, [
90
- { key: 'sourcePath', aliases: ['assetPath'], required: true },
91
- { key: 'destinationPath' },
92
- { key: 'newName' }
93
- ]);
94
-
95
- let destinationPath = params.destinationPath;
96
- if (params.newName) {
97
109
  if (!destinationPath) {
110
+ throw new Error('destinationPath or newName is required for duplicate action');
111
+ }
112
+
113
+ const res = await tools.assetTools.duplicateAsset({
114
+ sourcePath: params.sourcePath,
115
+ destinationPath
116
+ });
117
+ return ResponseFactory.success(res, 'Asset duplicated successfully');
118
+ }
119
+ case 'rename': {
120
+ const params = normalizeArgs(args, [
121
+ { key: 'sourcePath', aliases: ['assetPath'], required: true },
122
+ { key: 'destinationPath' },
123
+ { key: 'newName' }
124
+ ]);
125
+
126
+ let destinationPath = params.destinationPath;
127
+ if (!destinationPath && params.newName) {
98
128
  const lastSlash = params.sourcePath.lastIndexOf('/');
99
129
  const parentDir = lastSlash > 0 ? params.sourcePath.substring(0, lastSlash) : '/Game';
100
130
  destinationPath = `${parentDir}/${params.newName}`;
101
- } else if (!destinationPath.endsWith(params.newName)) {
102
- if (destinationPath.endsWith('/')) {
103
- destinationPath = `${destinationPath}${params.newName}`;
131
+ }
132
+
133
+ if (!destinationPath) throw new Error('Missing destinationPath or newName');
134
+
135
+ const res: any = await tools.assetTools.renameAsset({
136
+ sourcePath: params.sourcePath,
137
+ destinationPath
138
+ });
139
+
140
+ if (res && res.success === false) {
141
+ const msg = (res.message || '').toLowerCase();
142
+ if (msg.includes('already exists') || msg.includes('exists')) {
143
+ return cleanObject({
144
+ success: false,
145
+ error: 'ASSET_ALREADY_EXISTS',
146
+ message: res.message || 'Asset already exists at destination',
147
+ sourcePath: params.sourcePath,
148
+ destinationPath
149
+ });
104
150
  }
105
151
  }
152
+ return cleanObject(res);
106
153
  }
154
+ case 'move': {
155
+ const params = normalizeArgs(args, [
156
+ { key: 'sourcePath', aliases: ['assetPath'], required: true },
157
+ { key: 'destinationPath' }
158
+ ]);
107
159
 
108
- if (!destinationPath) {
109
- throw new Error('destinationPath or newName is required for duplicate action');
110
- }
160
+ let destinationPath = params.destinationPath;
161
+ const assetName = params.sourcePath.split('/').pop();
162
+ if (assetName && destinationPath && !destinationPath.endsWith(assetName)) {
163
+ destinationPath = `${destinationPath.replace(/\/$/, '')}/${assetName}`;
164
+ }
111
165
 
112
- const res = await tools.assetTools.duplicateAsset({
113
- sourcePath: params.sourcePath,
114
- destinationPath
115
- });
116
- return cleanObject(res);
117
- }
118
- case 'rename': {
119
- const params = normalizeArgs(args, [
120
- { key: 'sourcePath', aliases: ['assetPath'], required: true },
121
- { key: 'destinationPath' },
122
- { key: 'newName' }
123
- ]);
124
-
125
- let destinationPath = params.destinationPath;
126
- if (!destinationPath && params.newName) {
127
- const lastSlash = params.sourcePath.lastIndexOf('/');
128
- const parentDir = lastSlash > 0 ? params.sourcePath.substring(0, lastSlash) : '/Game';
129
- destinationPath = `${parentDir}/${params.newName}`;
166
+ const res = await tools.assetTools.moveAsset({
167
+ sourcePath: params.sourcePath,
168
+ destinationPath
169
+ });
170
+ return ResponseFactory.success(res, 'Asset moved successfully');
130
171
  }
172
+ case 'delete_assets':
173
+ case 'delete_asset':
174
+ case 'delete': {
175
+ let paths: string[] = [];
176
+ if (Array.isArray(args.paths)) {
177
+ paths = args.paths;
178
+ } else if (Array.isArray(args.assetPaths)) {
179
+ paths = args.assetPaths;
180
+ } else {
181
+ const single = args.assetPath || args.path;
182
+ if (typeof single === 'string' && single.trim()) {
183
+ paths = [single.trim()];
184
+ }
185
+ }
131
186
 
132
- if (!destinationPath) throw new Error('Missing destinationPath or newName');
187
+ if (paths.length === 0) {
188
+ throw new Error('No paths provided for delete action');
189
+ }
133
190
 
134
- const res: any = await tools.assetTools.renameAsset({
135
- sourcePath: params.sourcePath,
136
- destinationPath
137
- });
191
+ const res = await tools.assetTools.deleteAssets({ paths });
192
+ return ResponseFactory.success(res, 'Assets deleted successfully');
193
+ }
194
+ case 'generate_lods': {
195
+ const params = normalizeArgs(args, [
196
+ { key: 'assetPath', required: true },
197
+ { key: 'lodCount', required: true }
198
+ ]);
199
+ const res = await tools.assetTools.generateLODs({
200
+ assetPath: params.assetPath,
201
+ lodCount: params.lodCount
202
+ });
203
+ return ResponseFactory.success(res, 'LODs generated successfully');
204
+ }
205
+ case 'create_thumbnail': {
206
+ const params = normalizeArgs(args, [
207
+ { key: 'assetPath', required: true },
208
+ { key: 'width' },
209
+ { key: 'height' }
210
+ ]);
211
+ const res = await tools.assetTools.createThumbnail({
212
+ assetPath: params.assetPath,
213
+ width: params.width,
214
+ height: params.height
215
+ });
216
+ return ResponseFactory.success(res, 'Thumbnail created successfully');
217
+ }
218
+ case 'set_tags': {
219
+ try {
220
+ const params = normalizeArgs(args, [
221
+ { key: 'assetPath', required: true },
222
+ { key: 'tags', required: true }
223
+ ]);
224
+ const res = await tools.assetTools.setTags({ assetPath: params.assetPath, tags: params.tags });
225
+ return ResponseFactory.success(res, 'Tags set successfully');
226
+ } catch (err: any) {
227
+ const message = String(err?.message || err || '').toLowerCase();
228
+ if (
229
+ message.includes('not_implemented') ||
230
+ message.includes('not implemented') ||
231
+ message.includes('unknown action') ||
232
+ message.includes('unknown subaction')
233
+ ) {
234
+ return ResponseFactory.error('NOT_IMPLEMENTED', 'Asset tag writes are not implemented by the automation plugin.');
235
+ }
236
+ throw err;
237
+ }
238
+ }
239
+ case 'get_metadata': {
240
+ const params = normalizeArgs(args, [
241
+ { key: 'assetPath', required: true }
242
+ ]);
243
+ const res: any = await tools.assetTools.getMetadata({ assetPath: params.assetPath });
244
+ const tags = res.tags || {};
245
+ const metadata = res.metadata || {};
246
+ const merged = { ...tags, ...metadata };
247
+ const tagCount = Object.keys(merged).length;
248
+
249
+ const cleanRes = cleanObject(res);
250
+ cleanRes.message = `Metadata retrieved (${tagCount} items)`;
251
+ cleanRes.tags = tags;
252
+ if (Object.keys(metadata).length > 0) {
253
+ cleanRes.metadata = metadata;
254
+ }
138
255
 
139
- if (res && res.success === false) {
140
- const msg = (res.message || '').toLowerCase();
141
- if (msg.includes('already exists') || msg.includes('exists')) {
256
+ return ResponseFactory.success(cleanRes, cleanRes.message);
257
+ }
258
+ case 'set_metadata': {
259
+ const res = await executeAutomationRequest(tools, 'set_metadata', args);
260
+ return ResponseFactory.success(res, 'Metadata set successfully');
261
+ }
262
+ case 'validate':
263
+ case 'validate_asset': {
264
+ const params = normalizeArgs(args, [
265
+ { key: 'assetPath', required: true }
266
+ ]);
267
+ const res = await tools.assetTools.validate({ assetPath: params.assetPath });
268
+ return ResponseFactory.success(res, 'Asset validation complete');
269
+ }
270
+ case 'generate_report': {
271
+ const params = normalizeArgs(args, [
272
+ { key: 'directory' },
273
+ { key: 'reportType' },
274
+ { key: 'outputPath' }
275
+ ]);
276
+ const res = await tools.assetTools.generateReport({
277
+ directory: params.directory,
278
+ reportType: params.reportType,
279
+ outputPath: params.outputPath
280
+ });
281
+ return ResponseFactory.success(res, 'Report generated successfully');
282
+ }
283
+ case 'create_material_instance': {
284
+ const res: any = await executeAutomationRequest(
285
+ tools,
286
+ 'create_material_instance',
287
+ args,
288
+ 'Automation bridge not available for create_material_instance'
289
+ );
290
+
291
+ const result = res?.result ?? res ?? {};
292
+ const errorCode = typeof result.error === 'string' ? result.error.toUpperCase() : '';
293
+ const message = typeof result.message === 'string' ? result.message : '';
294
+
295
+ if (errorCode === 'PARENT_NOT_FOUND' || message.toLowerCase().includes('parent material not found')) {
296
+ // Keep specific error structure for this business logic case
142
297
  return cleanObject({
143
298
  success: false,
144
- error: 'ASSET_ALREADY_EXISTS',
145
- message: res.message || 'Asset already exists at destination',
146
- sourcePath: params.sourcePath,
147
- destinationPath
299
+ error: 'PARENT_NOT_FOUND',
300
+ message: message || 'Parent material not found',
301
+ path: result.path,
302
+ parentMaterial: args.parentMaterial
148
303
  });
149
304
  }
305
+
306
+ return ResponseFactory.success(res, 'Material instance created successfully');
150
307
  }
151
- return cleanObject(res);
152
- }
153
- case 'move': {
154
- const params = normalizeArgs(args, [
155
- { key: 'sourcePath', aliases: ['assetPath'], required: true },
156
- { key: 'destinationPath' }
157
- ]);
158
-
159
- let destinationPath = params.destinationPath;
160
- const assetName = params.sourcePath.split('/').pop();
161
- if (assetName && destinationPath && !destinationPath.endsWith(assetName)) {
162
- destinationPath = `${destinationPath.replace(/\/$/, '')}/${assetName}`;
308
+ case 'search_assets': {
309
+ const params = normalizeArgs(args, [
310
+ { key: 'classNames' },
311
+ { key: 'packagePaths' },
312
+ { key: 'recursivePaths' },
313
+ { key: 'recursiveClasses' },
314
+ { key: 'limit' }
315
+ ]);
316
+ const res = await tools.assetTools.searchAssets({
317
+ classNames: params.classNames,
318
+ packagePaths: params.packagePaths,
319
+ recursivePaths: params.recursivePaths,
320
+ recursiveClasses: params.recursiveClasses,
321
+ limit: params.limit
322
+ });
323
+ return ResponseFactory.success(res, 'Assets found');
163
324
  }
164
-
165
- const res = await tools.assetTools.moveAsset({
166
- sourcePath: params.sourcePath,
167
- destinationPath
168
- });
169
- return cleanObject(res);
170
- }
171
- case 'delete_assets':
172
- case 'delete_asset':
173
- case 'delete': {
174
- let paths: string[] = [];
175
- if (Array.isArray(args.paths)) {
176
- paths = args.paths;
177
- } else if (Array.isArray(args.assetPaths)) {
178
- paths = args.assetPaths;
179
- } else {
180
- const single = args.assetPath || args.path;
181
- if (typeof single === 'string' && single.trim()) {
182
- paths = [single.trim()];
183
- }
325
+ case 'find_by_tag': {
326
+ const params = normalizeArgs(args, [
327
+ { key: 'tag', required: true },
328
+ { key: 'value' }
329
+ ]);
330
+ const res = await tools.assetTools.findByTag({ tag: params.tag, value: params.value });
331
+ return ResponseFactory.success(res, 'Assets found by tag');
184
332
  }
185
-
186
- if (paths.length === 0) {
187
- throw new Error('No paths provided for delete action');
333
+ case 'get_dependencies': {
334
+ const params = normalizeArgs(args, [
335
+ { key: 'assetPath', required: true },
336
+ { key: 'recursive' }
337
+ ]);
338
+ const res = await tools.assetTools.getDependencies({ assetPath: params.assetPath, recursive: params.recursive });
339
+ return ResponseFactory.success(res, 'Dependencies retrieved');
188
340
  }
189
-
190
- const res = await tools.assetTools.deleteAssets({ paths });
191
- return cleanObject(res);
192
- }
193
- case 'generate_lods': {
194
- const params = normalizeArgs(args, [
195
- { key: 'assetPath', required: true },
196
- { key: 'lodCount', required: true }
197
- ]);
198
- return cleanObject(await tools.assetTools.generateLODs({
199
- assetPath: params.assetPath,
200
- lodCount: params.lodCount
201
- }));
202
- }
203
- case 'create_thumbnail': {
204
- const params = normalizeArgs(args, [
205
- { key: 'assetPath', required: true },
206
- { key: 'width' },
207
- { key: 'height' }
208
- ]);
209
- const res = await tools.assetTools.createThumbnail({
210
- assetPath: params.assetPath,
211
- width: params.width,
212
- height: params.height
213
- });
214
- return cleanObject(res);
215
- }
216
- case 'set_tags': {
217
- try {
341
+ case 'get_source_control_state': {
342
+ const params = normalizeArgs(args, [
343
+ { key: 'assetPath', required: true }
344
+ ]);
345
+ const res = await tools.assetTools.getSourceControlState({ assetPath: params.assetPath });
346
+ return ResponseFactory.success(res, 'Source control state retrieved');
347
+ }
348
+ case 'analyze_graph': {
218
349
  const params = normalizeArgs(args, [
219
350
  { key: 'assetPath', required: true },
220
- { key: 'tags', required: true }
351
+ { key: 'maxDepth' }
221
352
  ]);
222
- const res = await tools.assetTools.setTags({ assetPath: params.assetPath, tags: params.tags });
223
- return cleanObject(res);
224
- } catch (err: any) {
225
- const message = String(err?.message || err || '').toLowerCase();
226
- if (
227
- message.includes('not_implemented') ||
228
- message.includes('not implemented') ||
229
- message.includes('unknown action') ||
230
- message.includes('unknown subaction')
231
- ) {
232
- return cleanObject({
233
- success: false,
234
- error: 'NOT_IMPLEMENTED',
235
- message: 'Asset tag writes are not implemented by the automation plugin.',
236
- action: 'set_tags',
237
- assetPath: args.assetPath,
238
- tags: args.tags
239
- });
240
- }
241
- throw err;
353
+ const res = await executeAutomationRequest(tools, 'get_asset_graph', {
354
+ assetPath: params.assetPath,
355
+ maxDepth: params.maxDepth
356
+ });
357
+ return ResponseFactory.success(res, 'Graph analysis complete');
242
358
  }
243
- }
244
- case 'get_metadata': {
245
- const params = normalizeArgs(args, [
246
- { key: 'assetPath', required: true }
247
- ]);
248
- const res: any = await tools.assetTools.getMetadata({ assetPath: params.assetPath });
249
- const tags = res.tags || {};
250
- const metadata = res.metadata || {};
251
- const merged = { ...tags, ...metadata };
252
- const tagCount = Object.keys(merged).length;
253
-
254
- const cleanRes = cleanObject(res);
255
- cleanRes.message = `Metadata retrieved (${tagCount} items)`;
256
- cleanRes.tags = tags;
257
- if (Object.keys(metadata).length > 0) {
258
- cleanRes.metadata = metadata;
359
+ case 'create_render_target': {
360
+ const params = normalizeArgs(args, [
361
+ { key: 'name', required: true },
362
+ { key: 'packagePath', aliases: ['path'], default: '/Game' },
363
+ { key: 'width' },
364
+ { key: 'height' },
365
+ { key: 'format' }
366
+ ]);
367
+ const res = await executeAutomationRequest(tools, 'manage_render', {
368
+ subAction: 'create_render_target',
369
+ name: params.name,
370
+ packagePath: params.packagePath,
371
+ width: params.width,
372
+ height: params.height,
373
+ format: params.format,
374
+ save: true
375
+ });
376
+ return ResponseFactory.success(res, 'Render target created successfully');
259
377
  }
260
-
261
- return cleanRes;
262
- }
263
- case 'set_metadata': {
264
- const res = await executeAutomationRequest(tools, 'set_metadata', args);
265
- return cleanObject(res);
266
- }
267
- case 'validate':
268
- case 'validate_asset': {
269
- const params = normalizeArgs(args, [
270
- { key: 'assetPath', required: true }
271
- ]);
272
- const res = await tools.assetTools.validate({ assetPath: params.assetPath });
273
- return cleanObject(res);
274
- }
275
- case 'generate_report': {
276
- const params = normalizeArgs(args, [
277
- { key: 'directory' },
278
- { key: 'reportType' },
279
- { key: 'outputPath' }
280
- ]);
281
- const res = await tools.assetTools.generateReport({
282
- directory: params.directory,
283
- reportType: params.reportType,
284
- outputPath: params.outputPath
285
- });
286
- return cleanObject(res);
287
- }
288
- case 'create_material_instance': {
289
- const res: any = await executeAutomationRequest(
290
- tools,
291
- 'create_material_instance',
292
- args,
293
- 'Automation bridge not available for create_material_instance'
294
- );
295
-
296
- const result = res?.result ?? res ?? {};
297
- const errorCode = typeof result.error === 'string' ? result.error.toUpperCase() : '';
298
- const message = typeof result.message === 'string' ? result.message : '';
299
-
300
- if (errorCode === 'PARENT_NOT_FOUND' || message.toLowerCase().includes('parent material not found')) {
301
- return cleanObject({
302
- success: false,
303
- error: 'PARENT_NOT_FOUND',
304
- message: message || 'Parent material not found',
305
- path: result.path,
306
- parentMaterial: args.parentMaterial
378
+ case 'nanite_rebuild_mesh': {
379
+ const params = normalizeArgs(args, [
380
+ { key: 'assetPath', aliases: ['meshPath'], required: true }
381
+ ]);
382
+ const res = await executeAutomationRequest(tools, 'manage_render', {
383
+ subAction: 'nanite_rebuild_mesh',
384
+ assetPath: params.assetPath
307
385
  });
386
+ return ResponseFactory.success(res, 'Nanite mesh rebuilt successfully');
308
387
  }
388
+ case 'fixup_redirectors': {
389
+ const directoryRaw = typeof args.directory === 'string' && args.directory.trim().length > 0
390
+ ? args.directory.trim()
391
+ : (typeof args.directoryPath === 'string' && args.directoryPath.trim().length > 0
392
+ ? args.directoryPath.trim()
393
+ : '');
394
+
395
+ const payload: any = {};
396
+ if (directoryRaw) {
397
+ payload.directoryPath = directoryRaw;
398
+ }
399
+ if (typeof args.checkoutFiles === 'boolean') {
400
+ payload.checkoutFiles = args.checkoutFiles;
401
+ }
309
402
 
310
- return cleanObject(res);
311
- }
312
- case 'search_assets': {
313
- const params = normalizeArgs(args, [
314
- { key: 'classNames' },
315
- { key: 'packagePaths' },
316
- { key: 'recursivePaths' },
317
- { key: 'recursiveClasses' },
318
- { key: 'limit' }
319
- ]);
320
- const res = await tools.assetTools.searchAssets({
321
- classNames: params.classNames,
322
- packagePaths: params.packagePaths,
323
- recursivePaths: params.recursivePaths,
324
- recursiveClasses: params.recursiveClasses,
325
- limit: params.limit
326
- });
327
- return cleanObject(res);
328
- }
329
- case 'find_by_tag': {
330
- const params = normalizeArgs(args, [
331
- { key: 'tag', required: true },
332
- { key: 'value' }
333
- ]);
334
- return tools.assetTools.findByTag({ tag: params.tag, value: params.value });
335
- }
336
- case 'get_dependencies': {
337
- const params = normalizeArgs(args, [
338
- { key: 'assetPath', required: true },
339
- { key: 'recursive' }
340
- ]);
341
- const res = await tools.assetTools.getDependencies({ assetPath: params.assetPath, recursive: params.recursive });
342
- return cleanObject(res);
343
- }
344
- case 'get_source_control_state': {
345
- const params = normalizeArgs(args, [
346
- { key: 'assetPath', required: true }
347
- ]);
348
- const res = await tools.assetTools.getSourceControlState({ assetPath: params.assetPath });
349
- return cleanObject(res);
350
- }
351
- case 'analyze_graph': {
352
- const params = normalizeArgs(args, [
353
- { key: 'assetPath', required: true },
354
- { key: 'maxDepth' }
355
- ]);
356
- const res = await executeAutomationRequest(tools, 'get_asset_graph', {
357
- assetPath: params.assetPath,
358
- maxDepth: params.maxDepth
359
- });
360
- return cleanObject(res);
361
- }
362
- case 'create_render_target': {
363
- const params = normalizeArgs(args, [
364
- { key: 'name', required: true },
365
- { key: 'packagePath', aliases: ['path'], default: '/Game' },
366
- { key: 'width' },
367
- { key: 'height' },
368
- { key: 'format' }
369
- ]);
370
- const res = await executeAutomationRequest(tools, 'manage_render', {
371
- subAction: 'create_render_target',
372
- name: params.name,
373
- packagePath: params.packagePath,
374
- width: params.width,
375
- height: params.height,
376
- format: params.format,
377
- save: true
378
- });
379
- return cleanObject(res);
380
- }
381
- case 'nanite_rebuild_mesh': {
382
- const params = normalizeArgs(args, [
383
- { key: 'assetPath', aliases: ['meshPath'], required: true }
384
- ]);
385
- const res = await executeAutomationRequest(tools, 'manage_render', {
386
- subAction: 'nanite_rebuild_mesh',
387
- assetPath: params.assetPath
388
- });
389
- return cleanObject(res);
390
- }
391
- case 'fixup_redirectors': {
392
- const directoryRaw = typeof args.directory === 'string' && args.directory.trim().length > 0
393
- ? args.directory.trim()
394
- : (typeof args.directoryPath === 'string' && args.directoryPath.trim().length > 0
395
- ? args.directoryPath.trim()
396
- : '');
397
-
398
- const payload: any = {};
399
- if (directoryRaw) {
400
- payload.directoryPath = directoryRaw;
403
+ const res = await executeAutomationRequest(tools, 'fixup_redirectors', payload);
404
+ return ResponseFactory.success(res, 'Redirectors fixed up successfully');
401
405
  }
402
- if (typeof args.checkoutFiles === 'boolean') {
403
- payload.checkoutFiles = args.checkoutFiles;
406
+ case 'add_material_parameter': {
407
+ const params = normalizeArgs(args, [
408
+ { key: 'assetPath', required: true },
409
+ { key: 'parameterName', aliases: ['name'], required: true },
410
+ { key: 'parameterType', aliases: ['type'] },
411
+ { key: 'value', aliases: ['defaultValue'] }
412
+ ]);
413
+ const res = await executeAutomationRequest(tools, 'add_material_parameter', {
414
+ assetPath: params.assetPath,
415
+ name: params.parameterName,
416
+ type: params.parameterType,
417
+ value: params.value
418
+ });
419
+ return ResponseFactory.success(res, 'Material parameter added successfully');
420
+ }
421
+ case 'list_instances': {
422
+ const params = normalizeArgs(args, [
423
+ { key: 'assetPath', required: true }
424
+ ]);
425
+ const res = await executeAutomationRequest(tools, 'list_instances', {
426
+ assetPath: params.assetPath
427
+ });
428
+ return ResponseFactory.success(res, 'Instances listed successfully');
429
+ }
430
+ case 'reset_instance_parameters': {
431
+ const params = normalizeArgs(args, [
432
+ { key: 'assetPath', required: true }
433
+ ]);
434
+ const res = await executeAutomationRequest(tools, 'reset_instance_parameters', {
435
+ assetPath: params.assetPath
436
+ });
437
+ return ResponseFactory.success(res, 'Instance parameters reset successfully');
404
438
  }
439
+ case 'exists': {
440
+ const params = normalizeArgs(args, [
441
+ { key: 'assetPath', required: true }
442
+ ]);
443
+ const res = await executeAutomationRequest(tools, 'exists', {
444
+ assetPath: params.assetPath
445
+ });
446
+ return ResponseFactory.success(res, 'Asset existence check complete');
447
+ }
448
+ case 'get_material_stats': {
449
+ const params = normalizeArgs(args, [
450
+ { key: 'assetPath', required: true }
451
+ ]);
452
+ const res = await executeAutomationRequest(tools, 'get_material_stats', {
453
+ assetPath: params.assetPath
454
+ });
455
+ return ResponseFactory.success(res, 'Material stats retrieved');
456
+ }
457
+ case 'rebuild_material': {
458
+ const params = normalizeArgs(args, [
459
+ { key: 'assetPath', required: true }
460
+ ]);
461
+ const res = await executeAutomationRequest(tools, 'rebuild_material', {
462
+ assetPath: params.assetPath
463
+ });
464
+ return ResponseFactory.success(res, 'Material rebuilt successfully');
465
+ }
466
+ case 'add_material_node': {
467
+ const materialNodeAliases: Record<string, string> = {
468
+ 'Multiply': 'MaterialExpressionMultiply',
469
+ 'Add': 'MaterialExpressionAdd',
470
+ 'Subtract': 'MaterialExpressionSubtract',
471
+ 'Divide': 'MaterialExpressionDivide',
472
+ 'Power': 'MaterialExpressionPower',
473
+ 'Clamp': 'MaterialExpressionClamp',
474
+ 'Constant': 'MaterialExpressionConstant',
475
+ 'Constant2Vector': 'MaterialExpressionConstant2Vector',
476
+ 'Constant3Vector': 'MaterialExpressionConstant3Vector',
477
+ 'Constant4Vector': 'MaterialExpressionConstant4Vector',
478
+ 'TextureSample': 'MaterialExpressionTextureSample',
479
+ 'TextureCoordinate': 'MaterialExpressionTextureCoordinate',
480
+ 'Panner': 'MaterialExpressionPanner',
481
+ 'Rotator': 'MaterialExpressionRotator',
482
+ 'Lerp': 'MaterialExpressionLinearInterpolate',
483
+ 'LinearInterpolate': 'MaterialExpressionLinearInterpolate',
484
+ 'Sine': 'MaterialExpressionSine',
485
+ 'Cosine': 'MaterialExpressionCosine',
486
+ 'Append': 'MaterialExpressionAppendVector',
487
+ 'AppendVector': 'MaterialExpressionAppendVector',
488
+ 'ComponentMask': 'MaterialExpressionComponentMask',
489
+ 'Fresnel': 'MaterialExpressionFresnel',
490
+ 'Time': 'MaterialExpressionTime',
491
+ 'ScalarParameter': 'MaterialExpressionScalarParameter',
492
+ 'VectorParameter': 'MaterialExpressionVectorParameter',
493
+ 'StaticSwitchParameter': 'MaterialExpressionStaticSwitchParameter'
494
+ };
405
495
 
406
- const res = await executeAutomationRequest(tools, 'fixup_redirectors', payload);
407
- return cleanObject(res);
408
- }
409
- case 'add_material_parameter': {
410
- const params = normalizeArgs(args, [
411
- { key: 'assetPath', required: true },
412
- { key: 'parameterName', aliases: ['name'], required: true },
413
- { key: 'parameterType', aliases: ['type'] },
414
- { key: 'value', aliases: ['defaultValue'] }
415
- ]);
416
- const res = await executeAutomationRequest(tools, 'add_material_parameter', {
417
- assetPath: params.assetPath,
418
- name: params.parameterName,
419
- type: params.parameterType,
420
- value: params.value
421
- });
422
- return cleanObject(res);
423
- }
424
- case 'list_instances': {
425
- const params = normalizeArgs(args, [
426
- { key: 'assetPath', required: true }
427
- ]);
428
- const res = await executeAutomationRequest(tools, 'list_instances', {
429
- assetPath: params.assetPath
430
- });
431
- return cleanObject(res);
432
- }
433
- case 'reset_instance_parameters': {
434
- const params = normalizeArgs(args, [
435
- { key: 'assetPath', required: true }
436
- ]);
437
- const res = await executeAutomationRequest(tools, 'reset_instance_parameters', {
438
- assetPath: params.assetPath
439
- });
440
- return cleanObject(res);
441
- }
442
- case 'exists': {
443
- const params = normalizeArgs(args, [
444
- { key: 'assetPath', required: true }
445
- ]);
446
- const res = await executeAutomationRequest(tools, 'exists', {
447
- assetPath: params.assetPath
448
- });
449
- return cleanObject(res);
450
- }
451
- case 'get_material_stats': {
452
- const params = normalizeArgs(args, [
453
- { key: 'assetPath', required: true }
454
- ]);
455
- const res = await executeAutomationRequest(tools, 'get_material_stats', {
456
- assetPath: params.assetPath
457
- });
458
- return cleanObject(res);
459
- }
460
- case 'rebuild_material': {
461
- const params = normalizeArgs(args, [
462
- { key: 'assetPath', required: true }
463
- ]);
464
- const res = await executeAutomationRequest(tools, 'rebuild_material', {
465
- assetPath: params.assetPath
466
- });
467
- return cleanObject(res);
468
- }
469
- case 'add_material_node': {
470
- const materialNodeAliases: Record<string, string> = {
471
- 'Multiply': 'MaterialExpressionMultiply',
472
- 'Add': 'MaterialExpressionAdd',
473
- 'Subtract': 'MaterialExpressionSubtract',
474
- 'Divide': 'MaterialExpressionDivide',
475
- 'Power': 'MaterialExpressionPower',
476
- 'Clamp': 'MaterialExpressionClamp',
477
- 'Constant': 'MaterialExpressionConstant',
478
- 'Constant2Vector': 'MaterialExpressionConstant2Vector',
479
- 'Constant3Vector': 'MaterialExpressionConstant3Vector',
480
- 'Constant4Vector': 'MaterialExpressionConstant4Vector',
481
- 'TextureSample': 'MaterialExpressionTextureSample',
482
- 'TextureCoordinate': 'MaterialExpressionTextureCoordinate',
483
- 'Panner': 'MaterialExpressionPanner',
484
- 'Rotator': 'MaterialExpressionRotator',
485
- 'Lerp': 'MaterialExpressionLinearInterpolate',
486
- 'LinearInterpolate': 'MaterialExpressionLinearInterpolate',
487
- 'Sine': 'MaterialExpressionSine',
488
- 'Cosine': 'MaterialExpressionCosine',
489
- 'Append': 'MaterialExpressionAppendVector',
490
- 'AppendVector': 'MaterialExpressionAppendVector',
491
- 'ComponentMask': 'MaterialExpressionComponentMask',
492
- 'Fresnel': 'MaterialExpressionFresnel',
493
- 'Time': 'MaterialExpressionTime',
494
- 'ScalarParameter': 'MaterialExpressionScalarParameter',
495
- 'VectorParameter': 'MaterialExpressionVectorParameter',
496
- 'StaticSwitchParameter': 'MaterialExpressionStaticSwitchParameter'
497
- };
498
-
499
- const params = normalizeArgs(args, [
500
- { key: 'assetPath', required: true },
501
- { key: 'nodeType', aliases: ['type'], required: true, map: materialNodeAliases },
502
- { key: 'posX' },
503
- { key: 'posY' }
504
- ]);
505
-
506
- const res = await executeAutomationRequest(tools, 'add_material_node', {
507
- assetPath: params.assetPath,
508
- nodeType: params.nodeType,
509
- posX: params.posX,
510
- posY: params.posY
511
- });
512
- return cleanObject(res);
513
- }
514
- default:
515
- const res: any = await executeAutomationRequest(tools, action || 'manage_asset', args);
516
- const result = res?.result ?? res ?? {};
517
- const errorCode = typeof result.error === 'string' ? result.error.toUpperCase() : '';
518
- const message = typeof result.message === 'string' ? result.message : '';
519
-
520
- if (errorCode === 'INVALID_SUBACTION' || message.toLowerCase().includes('unknown subaction')) {
521
- return cleanObject({
522
- success: false,
523
- error: 'INVALID_SUBACTION',
524
- message: 'Asset action not recognized by the automation plugin.',
525
- action: action || 'manage_asset',
526
- assetPath: args.assetPath ?? args.path
496
+ const params = normalizeArgs(args, [
497
+ { key: 'assetPath', required: true },
498
+ { key: 'nodeType', aliases: ['type'], required: true, map: materialNodeAliases },
499
+ { key: 'posX' },
500
+ { key: 'posY' }
501
+ ]);
502
+
503
+ const res = await executeAutomationRequest(tools, 'add_material_node', {
504
+ assetPath: params.assetPath,
505
+ nodeType: params.nodeType,
506
+ posX: params.posX,
507
+ posY: params.posY
527
508
  });
509
+ return ResponseFactory.success(res, 'Material node added successfully');
528
510
  }
511
+ default:
512
+ const res: any = await executeAutomationRequest(tools, action || 'manage_asset', args);
513
+ const result = res?.result ?? res ?? {};
514
+ const errorCode = typeof result.error === 'string' ? result.error.toUpperCase() : '';
515
+ const message = typeof result.message === 'string' ? result.message : '';
516
+
517
+ if (errorCode === 'INVALID_SUBACTION' || message.toLowerCase().includes('unknown subaction')) {
518
+ return cleanObject({
519
+ success: false,
520
+ error: 'INVALID_SUBACTION',
521
+ message: 'Asset action not recognized by the automation plugin.',
522
+ action: action || 'manage_asset',
523
+ assetPath: args.assetPath ?? args.path
524
+ });
525
+ }
529
526
 
530
- return cleanObject(res);
527
+ return ResponseFactory.success(res, 'Asset action executed successfully');
528
+ }
529
+ } catch (error) {
530
+ return ResponseFactory.error(error);
531
531
  }
532
532
  }