unreal-engine-mcp-server 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) 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 +1 -0
  6. package/.github/workflows/release-drafter.yml +1 -1
  7. package/.github/workflows/release.yml +3 -3
  8. package/CHANGELOG.md +71 -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/config.d.ts +0 -1
  14. package/dist/config.js +0 -1
  15. package/dist/constants.d.ts +4 -0
  16. package/dist/constants.js +4 -0
  17. package/dist/graphql/loaders.d.ts +64 -0
  18. package/dist/graphql/loaders.js +117 -0
  19. package/dist/graphql/resolvers.d.ts +3 -3
  20. package/dist/graphql/resolvers.js +33 -30
  21. package/dist/graphql/server.js +3 -1
  22. package/dist/graphql/types.d.ts +2 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +13 -2
  25. package/dist/server-setup.d.ts +0 -1
  26. package/dist/server-setup.js +0 -40
  27. package/dist/tools/actors.d.ts +40 -24
  28. package/dist/tools/actors.js +8 -2
  29. package/dist/tools/assets.d.ts +19 -71
  30. package/dist/tools/assets.js +28 -22
  31. package/dist/tools/base-tool.d.ts +4 -4
  32. package/dist/tools/base-tool.js +1 -1
  33. package/dist/tools/blueprint.d.ts +33 -61
  34. package/dist/tools/consolidated-tool-handlers.js +96 -110
  35. package/dist/tools/dynamic-handler-registry.d.ts +11 -9
  36. package/dist/tools/dynamic-handler-registry.js +17 -95
  37. package/dist/tools/editor.d.ts +19 -193
  38. package/dist/tools/editor.js +8 -0
  39. package/dist/tools/environment.d.ts +8 -14
  40. package/dist/tools/foliage.d.ts +18 -143
  41. package/dist/tools/foliage.js +4 -2
  42. package/dist/tools/handlers/actor-handlers.js +0 -5
  43. package/dist/tools/handlers/asset-handlers.js +454 -454
  44. package/dist/tools/landscape.d.ts +16 -116
  45. package/dist/tools/landscape.js +7 -3
  46. package/dist/tools/level.d.ts +22 -103
  47. package/dist/tools/level.js +24 -16
  48. package/dist/tools/lighting.js +5 -1
  49. package/dist/tools/materials.js +5 -1
  50. package/dist/tools/niagara.js +37 -2
  51. package/dist/tools/performance.d.ts +0 -1
  52. package/dist/tools/performance.js +0 -1
  53. package/dist/tools/physics.js +5 -1
  54. package/dist/tools/sequence.d.ts +24 -24
  55. package/dist/tools/sequence.js +13 -0
  56. package/dist/tools/ui.d.ts +0 -2
  57. package/dist/types/automation-responses.d.ts +115 -0
  58. package/dist/types/automation-responses.js +2 -0
  59. package/dist/types/responses.d.ts +249 -0
  60. package/dist/types/responses.js +2 -0
  61. package/dist/types/tool-interfaces.d.ts +135 -135
  62. package/dist/utils/command-validator.js +3 -2
  63. package/dist/utils/path-security.d.ts +2 -0
  64. package/dist/utils/path-security.js +24 -0
  65. package/dist/utils/response-factory.d.ts +4 -4
  66. package/dist/utils/response-factory.js +15 -21
  67. package/docs/Migration-Guide-v0.5.0.md +1 -9
  68. package/docs/testing-guide.md +2 -2
  69. package/package.json +12 -6
  70. package/scripts/run-all-tests.mjs +25 -20
  71. package/server.json +3 -2
  72. package/src/config.ts +1 -1
  73. package/src/constants.ts +7 -0
  74. package/src/graphql/loaders.ts +244 -0
  75. package/src/graphql/resolvers.ts +47 -49
  76. package/src/graphql/server.ts +3 -1
  77. package/src/graphql/types.ts +3 -0
  78. package/src/index.ts +15 -2
  79. package/src/resources/assets.ts +5 -4
  80. package/src/server-setup.ts +3 -37
  81. package/src/tools/actors.ts +36 -28
  82. package/src/tools/animation.ts +1 -0
  83. package/src/tools/assets.ts +74 -63
  84. package/src/tools/base-tool.ts +3 -3
  85. package/src/tools/blueprint.ts +59 -59
  86. package/src/tools/consolidated-tool-handlers.ts +129 -150
  87. package/src/tools/dynamic-handler-registry.ts +22 -140
  88. package/src/tools/editor.ts +39 -26
  89. package/src/tools/environment.ts +21 -27
  90. package/src/tools/foliage.ts +28 -25
  91. package/src/tools/handlers/actor-handlers.ts +2 -8
  92. package/src/tools/handlers/asset-handlers.ts +484 -484
  93. package/src/tools/handlers/sequence-handlers.ts +1 -1
  94. package/src/tools/landscape.ts +34 -28
  95. package/src/tools/level.ts +96 -76
  96. package/src/tools/lighting.ts +6 -1
  97. package/src/tools/materials.ts +8 -2
  98. package/src/tools/niagara.ts +44 -2
  99. package/src/tools/performance.ts +1 -2
  100. package/src/tools/physics.ts +7 -1
  101. package/src/tools/sequence.ts +41 -25
  102. package/src/tools/ui.ts +0 -2
  103. package/src/types/automation-responses.ts +119 -0
  104. package/src/types/responses.ts +355 -0
  105. package/src/types/tool-interfaces.ts +135 -135
  106. package/src/utils/command-validator.ts +3 -2
  107. package/src/utils/normalize.test.ts +162 -0
  108. package/src/utils/path-security.ts +43 -0
  109. package/src/utils/response-factory.ts +29 -24
  110. package/src/utils/safe-json.test.ts +90 -0
  111. package/src/utils/validation.test.ts +184 -0
  112. package/tests/test-animation.mjs +358 -33
  113. package/tests/test-asset-graph.mjs +311 -0
  114. package/tests/test-audio.mjs +314 -116
  115. package/tests/test-behavior-tree.mjs +327 -144
  116. package/tests/test-blueprint-graph.mjs +343 -12
  117. package/tests/test-control-editor.mjs +85 -53
  118. package/tests/test-graphql.mjs +58 -8
  119. package/tests/test-input.mjs +349 -0
  120. package/tests/test-inspect.mjs +291 -61
  121. package/tests/test-landscape.mjs +304 -48
  122. package/tests/test-lighting.mjs +428 -0
  123. package/tests/test-manage-level.mjs +70 -51
  124. package/tests/test-performance.mjs +539 -0
  125. package/tests/test-sequence.mjs +82 -46
  126. package/tests/test-system.mjs +72 -33
  127. package/tests/test-wasm.mjs +98 -8
  128. package/vitest.config.ts +35 -0
  129. package/.github/release-drafter.yml +0 -148
  130. package/dist/prompts/index.d.ts +0 -21
  131. package/dist/prompts/index.js +0 -217
  132. package/dist/tools/blueprint/helpers.d.ts +0 -29
  133. package/dist/tools/blueprint/helpers.js +0 -182
  134. package/src/prompts/index.ts +0 -249
  135. package/src/tools/blueprint/helpers.ts +0 -189
  136. package/tests/test-blueprint-events.mjs +0 -35
  137. package/tests/test-extra-tools.mjs +0 -38
  138. package/tests/test-render.mjs +0 -33
  139. 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