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