@mseep/anklebreaker-unity-mcp 2.30.0

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.
@@ -0,0 +1,627 @@
1
+ // AnkleBreaker Unity MCP - UMA (Unity Multipurpose Avatar) tool definitions
2
+ // These tools are only functional when UMA is installed in the Unity project.
3
+ // The C# side is wrapped in #if UMA_INSTALLED - calls will return an error if UMA is absent.
4
+ import * as bridge from "../uma-bridge.js";
5
+
6
+ export const umaTools = [
7
+ {
8
+ name: "unity_uma_inspect_fbx",
9
+ description:
10
+ "Inspect an FBX file to list all SkinnedMeshRenderers with vertex counts, weighted bones (keepList), and bone counts. " +
11
+ "Essential first step before creating UMA slots — shows which SMRs are available and their bone data.",
12
+ inputSchema: {
13
+ type: "object",
14
+ properties: {
15
+ fbxPath: {
16
+ type: "string",
17
+ description: "Asset path to the FBX file (e.g. 'Assets/Models/Armor.fbx')",
18
+ },
19
+ },
20
+ required: ["fbxPath"],
21
+ },
22
+ handler: async (params) =>
23
+ JSON.stringify(await bridge.umaInspectFbx(params), null, 2),
24
+ },
25
+ {
26
+ name: "unity_uma_create_slot",
27
+ description:
28
+ "Create a UMA SlotDataAsset from an FBX SkinnedMeshRenderer. Automatically extracts weighted bones for keepList, " +
29
+ "fixes SlotName, initializes tags/Races arrays, and cleans up subfolder/parasite artifacts. " +
30
+ "Use unity_uma_inspect_fbx first to see available SMRs.\n\nEXAMPLE CALL:\n{\n \"fbxPath\": \"Assets/Models/Armor/SK_Hu_M_IronChest.fbx\",\n \"smrName\": \"Chest\",\n \"slotName\": \"PT_Iron_Chest\",\n \"outputFolder\": \"Assets/UMA/Slots/Armor\",\n \"umaMaterialPath\": \"Assets/UMA/Content/UMA_Core/MaterialSamples/UMA_ClothesBase.asset\"\n}",
31
+ inputSchema: {
32
+ type: "object",
33
+ properties: {
34
+ fbxPath: {
35
+ type: "string",
36
+ description: "Asset path to the FBX (e.g. 'Assets/Models/Armor.fbx')",
37
+ },
38
+ smrName: {
39
+ type: "string",
40
+ description: "Name of the SkinnedMeshRenderer inside the FBX (from inspect_fbx results)",
41
+ },
42
+ slotName: {
43
+ type: "string",
44
+ description: "Name for the created SlotDataAsset (e.g. 'PT_Iron_Chest')",
45
+ },
46
+ outputFolder: {
47
+ type: "string",
48
+ description: "Folder to save the slot asset (e.g. 'Assets/UMA/Slots/Armor')",
49
+ },
50
+ umaMaterialPath: {
51
+ type: "string",
52
+ description: "FULL asset path (NOT just the name) to the UMAMaterial. Must start with 'Assets/' and end with '.asset'. Example: 'Assets/UMA/Content/UMA_Core/MaterialSamples/UMA_ClothesBase.asset'",
53
+ },
54
+ keepAllBones: {
55
+ type: "boolean",
56
+ description: "If true, keep all bones instead of only weighted ones (default: false — auto-extracts keepList)",
57
+ },
58
+ },
59
+ required: ["fbxPath", "smrName", "slotName", "outputFolder", "umaMaterialPath"],
60
+ },
61
+ handler: async (params) =>
62
+ JSON.stringify(await bridge.umaCreateSlot(params), null, 2),
63
+ },
64
+ {
65
+ name: "unity_uma_create_overlay",
66
+ description:
67
+ "Create a UMA OverlayDataAsset with the correct number of texture channels based on the UMA Material. " +
68
+ "Assigns textures to channels (diffuse, normal, etc.).\n\nEXAMPLE CALL:\n{\n \"overlayName\": \"PT_Iron_Chest_Overlay\",\n \"outputFolder\": \"Assets/UMA/Overlays/Armor\",\n \"umaMaterialPath\": \"Assets/UMA/Content/UMA_Core/MaterialSamples/UMA_ClothesBase.asset\",\n \"textures\": [\"Assets/Textures/Chest_D.png\", \"Assets/Textures/Chest_N.png\", \"Assets/Textures/Chest_MR.png\"]\n}\n\nCRITICAL: textures MUST be a flat JSON array of strings. Do NOT use dict format like {\"0\":\"path\"}. Parameter is outputFolder, NOT savePath.",
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {
72
+ overlayName: {
73
+ type: "string",
74
+ description: "Name for the overlay asset (e.g. 'PT_Iron_Chest_Overlay')",
75
+ },
76
+ outputFolder: {
77
+ type: "string",
78
+ description: "Folder to save the overlay (e.g. 'Assets/UMA/Overlays/Armor')",
79
+ },
80
+ umaMaterialPath: {
81
+ type: "string",
82
+ description: "FULL asset path (NOT just the name) to the UMAMaterial. Must start with 'Assets/' and end with '.asset'. Example: 'Assets/UMA/Content/UMA_Core/MaterialSamples/UMA_ClothesBase.asset'. Must match the slot's material.",
83
+ },
84
+ textures: {
85
+ type: "array",
86
+ description: "Ordered array of texture asset paths, one per UMAMaterial channel index. Index 0 = diffuse/albedo, index 1 = normal map, index 2 = metallic/roughness. Use null or empty string for unused channels. Example: [\"Assets/Textures/Chest_D.png\", \"Assets/Textures/Chest_N.png\", null]. Do NOT use channel0/channel1/channel2 object format — must be a flat array of strings.",
87
+ items: { type: "string" },
88
+ },
89
+ },
90
+ required: ["overlayName", "outputFolder", "umaMaterialPath", "textures"],
91
+ },
92
+ handler: async (params) =>
93
+ JSON.stringify(await bridge.umaCreateOverlay(params), null, 2),
94
+ },
95
+ {
96
+ name: "unity_uma_create_wardrobe_recipe",
97
+ description:
98
+ "Create a UMA WardrobeRecipe that binds slots and overlays together as an equippable item. " +
99
+ "Handles the complex recipeString v3 JSON format, fColors, Hides, DisplayValue, and compatibleRaces. " +
100
+ "Slots and overlays must already exist.\n\nEXAMPLE CALL:\n{\n \"recipeName\": \"PT_Iron_Chest_Recipe\",\n \"outputFolder\": \"Assets/UMA/Recipes/Armor\",\n \"wardrobeSlot\": \"Chest\",\n \"compatibleRaces\": [\"HumanMaleDCS\"],\n \"slots\": [\n {\n \"slotName\": \"PT_Iron_Chest\",\n \"overlays\": [{\"overlayName\": \"PT_Iron_Chest_Overlay\"}]\n }\n ]\n}\n\nFor multi-sub-mesh items, add one slot entry per sub-mesh, each with its own overlay.",
101
+ inputSchema: {
102
+ type: "object",
103
+ properties: {
104
+ recipeName: {
105
+ type: "string",
106
+ description: "Name for the wardrobe recipe (e.g. 'PT_Iron_Chest_Recipe')",
107
+ },
108
+ outputFolder: {
109
+ type: "string",
110
+ description: "Folder to save the recipe (e.g. 'Assets/UMA/Recipes/Armor')",
111
+ },
112
+ wardrobeSlot: {
113
+ type: "string",
114
+ description: "Wardrobe slot name (e.g. 'Chest', 'Legs', 'Feet')",
115
+ },
116
+ compatibleRaces: {
117
+ type: "array",
118
+ description: "Array of race names this recipe is compatible with (e.g. ['HumanMaleDCS', 'HumanFemaleDCS'])",
119
+ items: { type: "string" },
120
+ },
121
+ slots: {
122
+ type: "array",
123
+ description:
124
+ "Array of slot objects. Each slot has a slotName (the SlotDataAsset name) and an overlays array. " +
125
+ "Each overlay has overlayName and optional channelCount (default 3). " +
126
+ "For backward compatibility, a single overlayName at slot level is also accepted but overlays array is preferred.",
127
+ items: {
128
+ type: "object",
129
+ properties: {
130
+ slotName: {
131
+ type: "string",
132
+ description: "Name of the SlotDataAsset (e.g. 'Boots_Peasant_Armor')",
133
+ },
134
+ overlays: {
135
+ type: "array",
136
+ description: "Array of overlays stacked on this slot. Order matters: first overlay is the base layer.",
137
+ items: {
138
+ type: "object",
139
+ properties: {
140
+ overlayName: {
141
+ type: "string",
142
+ description: "Name of the OverlayDataAsset (e.g. 'Boots_Peasant_Armor_Overlay')",
143
+ },
144
+ channelCount: {
145
+ type: "number",
146
+ description: "Number of texture channels for this overlay (default: 3 = diffuse+normal+mask)",
147
+ },
148
+ },
149
+ required: ["overlayName"],
150
+ },
151
+ },
152
+ overlayName: {
153
+ type: "string",
154
+ description: "DEPRECATED: use overlays array instead. Single overlay name for backward compat.",
155
+ },
156
+ channelCount: {
157
+ type: "number",
158
+ description: "DEPRECATED: use overlays[].channelCount instead. Channel count when using single overlayName.",
159
+ },
160
+ },
161
+ required: ["slotName"],
162
+ },
163
+ },
164
+ hides: {
165
+ type: "array",
166
+ description: "Optional array of slot names to hide when this recipe is equipped (e.g. ['MaleUnderwear'])",
167
+ items: { type: "string" },
168
+ },
169
+ displayValue: {
170
+ type: "string",
171
+ description: "Optional display name shown in character creator UI",
172
+ },
173
+ },
174
+ required: ["recipeName", "outputFolder", "wardrobeSlot", "compatibleRaces", "slots"],
175
+ },
176
+ handler: async (params) =>
177
+ JSON.stringify(await bridge.umaCreateWardrobeRecipe(params), null, 2),
178
+ },
179
+ {
180
+ name: "unity_uma_register_assets",
181
+ description:
182
+ "Register UMA assets (Slot, Overlay, or WardrobeRecipe) in the UMA Global Library so they are available at runtime. " +
183
+ "Must be called after creating assets.",
184
+ inputSchema: {
185
+ type: "object",
186
+ properties: {
187
+ assetPaths: {
188
+ type: "array",
189
+ description: "Array of asset paths to register. Optional if folderPath is provided.",
190
+ items: { type: "string" },
191
+ },
192
+ folderPath: {
193
+ type: "string",
194
+ description: "Folder to scan recursively for all UMA assets (Slots, Overlays, Recipes). Alternative to listing each path manually.",
195
+ },
196
+ },
197
+ },
198
+ handler: async (params) =>
199
+ JSON.stringify(await bridge.umaRegisterAssets(params), null, 2),
200
+ },
201
+ {
202
+ name: "unity_uma_list_global_library",
203
+ description:
204
+ "List assets registered in the UMA Global Library. Can filter by type (Slot, Overlay, Race, WardrobeRecipe) and/or name pattern.",
205
+ inputSchema: {
206
+ type: "object",
207
+ properties: {
208
+ type: {
209
+ type: "string",
210
+ description: "Filter by asset type. Accepts short names: 'Slot', 'Overlay', 'Race', 'WardrobeRecipe', full C# names: 'SlotDataAsset', 'OverlayDataAsset', 'RaceData', 'UMAWardrobeRecipe', or 'All' (default). Case-insensitive.",
211
+ },
212
+ nameFilter: {
213
+ type: "string",
214
+ description: "Optional substring filter on asset names (case-insensitive)",
215
+ },
216
+ },
217
+ },
218
+ handler: async (params) =>
219
+ JSON.stringify(await bridge.umaListGlobalLibrary(params), null, 2),
220
+ },
221
+ {
222
+ name: "unity_uma_list_wardrobe_slots",
223
+ description:
224
+ "List all wardrobe slot names available for a given UMA race (e.g. 'Chest', 'Legs', 'Feet', 'Helmet').",
225
+ inputSchema: {
226
+ type: "object",
227
+ properties: {
228
+ raceName: {
229
+ type: "string",
230
+ description: "UMA race name (e.g. 'HumanMaleDCS')",
231
+ },
232
+ },
233
+ required: ["raceName"],
234
+ },
235
+ handler: async (params) =>
236
+ JSON.stringify(await bridge.umaListWardrobeSlots(params), null, 2),
237
+ },
238
+ {
239
+ name: "unity_uma_list_uma_materials",
240
+ description:
241
+ "List all UMAMaterial assets in the project with their channel counts. Useful for choosing the right material when creating slots and overlays.",
242
+ inputSchema: {
243
+ type: "object",
244
+ properties: {},
245
+ },
246
+ handler: async (params) =>
247
+ JSON.stringify(await bridge.umaListUMAMaterials(params), null, 2),
248
+ },
249
+ {
250
+ name: "unity_uma_get_project_config",
251
+ description:
252
+ "Get project-specific UMA configuration by querying existing assets. Returns detected races, UMA materials, wardrobe slots, and recipe patterns. Call this before creating slots/overlays/recipes to auto-detect correct parameters.",
253
+ inputSchema: {
254
+ type: "object",
255
+ properties: {},
256
+ },
257
+ handler: async (params) =>
258
+ JSON.stringify(await bridge.umaGetProjectConfig(params), null, 2),
259
+ },
260
+ {
261
+ name: "unity_uma_verify_recipe",
262
+ description:
263
+ "Validate a UMA WardrobeRecipe by checking that all referenced slots and overlays exist in the Global Library, " +
264
+ "that materialName is set on slots, and that fColors has enough entries for the colorIdx values used. " +
265
+ "Returns a list of issues (critical/warning) and a valid/invalid verdict. Essential post-creation check.",
266
+ inputSchema: {
267
+ type: "object",
268
+ properties: {
269
+ recipePath: {
270
+ type: "string",
271
+ description: "Asset path to the WardrobeRecipe to verify (e.g. 'Assets/UMA/Recipes/MyRecipe.asset')",
272
+ },
273
+ },
274
+ required: ["recipePath"],
275
+ },
276
+ handler: async (params) =>
277
+ JSON.stringify(await bridge.umaVerifyRecipe(params), null, 2),
278
+ },
279
+ {
280
+ name: "unity_uma_rebuild_global_library",
281
+ description:
282
+ "Rebuild or repair the UMA Global Library (UMAAssetIndexer). " +
283
+ "Use this when assets are missing from the library, after bulk creation/deletion, " +
284
+ "or when the library is in a corrupted state.\n\n" +
285
+ "MODES:\n" +
286
+ "- 'rebuild' (default): Full rebuild from project. Clears the library, rescans all UMA assets, and re-registers them. " +
287
+ "Equivalent to the 'Rebuild From Project' button in the UMA Global Library inspector.\n" +
288
+ "- 'rebuild_with_text': Same as rebuild but also includes UMATextRecipe assets.\n" +
289
+ "- 'repair': Light repair. Rebuilds type tables and removes broken/invalid entries without a full rescan. Faster but less thorough.\n\n" +
290
+ "Returns asset counts before and after the operation with a breakdown by type (slots, overlays, recipes, races).",
291
+ inputSchema: {
292
+ type: "object",
293
+ properties: {
294
+ mode: {
295
+ type: "string",
296
+ description: "Rebuild mode: 'rebuild' (full rescan, default), 'rebuild_with_text' (full + text recipes), or 'repair' (light cleanup).",
297
+ enum: ["rebuild", "rebuild_with_text", "repair"],
298
+ },
299
+ },
300
+ },
301
+ handler: async (params) =>
302
+ JSON.stringify(await bridge.umaRebuildGlobalLibrary(params), null, 2),
303
+ },
304
+ {
305
+ name: "unity_uma_create_wardrobe_from_fbx",
306
+ description:
307
+ "Create all UMA assets from a single FBX file in one atomic call. " +
308
+ "Inspects the FBX for sub-meshes, creates SlotDataAssets (via UMA's CreateSlotData), " +
309
+ "creates OverlayDataAssets with texture deduplication, assembles WardrobeRecipes (recipeString v3), " +
310
+ "and registers everything in the Global Library. Supports multiple texture variants in a single call. " +
311
+ "This replaces the manual 5-step workflow (inspect + create-slot + create-overlay + create-recipe + register).\n\n" +
312
+ "TEXTURE DETECTION: Textures are always read automatically from the Unity materials assigned to each FBX sub-mesh. " +
313
+ "No manual texture paths needed. Each variant reads from the FBX sharedMaterials (diffuse, normal, metallic/roughness).\n\n" +
314
+ "EXAMPLE (single variant):\n" +
315
+ "{ fbxPath: 'Assets/Models/Armor/SK_Hu_M_IronChest.fbx', outputFolder: 'Assets/UMA/Generated/IronChest', " +
316
+ "wardrobeSlot: 'Chest', umaMaterialPath: 'Assets/UMA/.../UMA_ClothesBase.asset', variants: [{ suffix: 'Iron' }] }\n\n" +
317
+ "EXAMPLE (two variants from same FBX):\n" +
318
+ "{ ..., variants: [{ suffix: 'Peasant' }, { suffix: 'Noble' }] }\n\n" +
319
+ "CRITICAL RULES:\n" +
320
+ "- variants[].suffix is REQUIRED (not name or variantName)\n" +
321
+ "- Textures are always auto-read from FBX materials - no texture paths needed\n" +
322
+ "- umaMaterialPath MUST be a full path starting with Assets/ and ending with .asset\n" +
323
+ "- For custom texture control, use the individual tools (create_slot, create_overlay, create_wardrobe_recipe) instead",
324
+ inputSchema: {
325
+ type: "object",
326
+ properties: {
327
+ fbxPath: {
328
+ type: "string",
329
+ description: "Asset path to the FBX file (e.g. 'Assets/Models/Armor/SK_Hu_M_IronChest.fbx')",
330
+ },
331
+ outputFolder: {
332
+ type: "string",
333
+ description: "Root folder for generated assets (e.g. 'Assets/UMA/GeneratedAssets/IronChest'). Sub-folders Slots/, Overlays/, Recipes/ are created automatically.",
334
+ },
335
+ wardrobeSlot: {
336
+ type: "string",
337
+ description: "Wardrobe slot name (e.g. 'Chest', 'Legs', 'Feet', 'Head')",
338
+ },
339
+ umaMaterialPath: {
340
+ type: "string",
341
+ description: "FULL asset path (NOT just the name) to the UMAMaterial to assign to all slots. Must start with 'Assets/' and end with '.asset'. Example: 'Assets/UMA/Content/UMA_Core/MaterialSamples/UMA_ClothesBase.asset'",
342
+ },
343
+ variants: {
344
+ type: "array",
345
+ description:
346
+ "Array of variant definitions. Each variant produces one WardrobeRecipe. " +
347
+ "Textures are auto-read from FBX materials for each sub-mesh. " +
348
+ "If two sub-meshes share identical textures they share the same overlay (deduplication).",
349
+ items: {
350
+ type: "object",
351
+ properties: {
352
+ suffix: {
353
+ type: "string",
354
+ description: "Suffix used in asset naming for this variant (e.g. 'Iron', 'Gold', 'Peasant'). Appended to slot/overlay/recipe names.",
355
+ },
356
+ },
357
+ required: ["suffix"],
358
+ },
359
+ },
360
+ recipeName: {
361
+ type: "string",
362
+ description: "Optional base name for recipes. Defaults to FBX filename with prefix stripped (e.g. SK_Hu_M_ removed). Each variant appends _{suffix}_Recipe.",
363
+ },
364
+ race: {
365
+ type: "string",
366
+ description: "Race name for compatible races (default: auto-detect from FBX prefix, e.g. SK_Hu_M_ = HumanMaleDCS)",
367
+ },
368
+ registerInGlobalLibrary: {
369
+ type: "boolean",
370
+ description: "Whether to register all created assets in the UMA Global Library (default: true)",
371
+ },
372
+ verifyAfterCreation: {
373
+ type: "boolean",
374
+ description: "Whether to run VerifyRecipe on each created recipe after assembly (default: true)",
375
+ },
376
+ },
377
+ required: ["fbxPath", "outputFolder", "wardrobeSlot", "umaMaterialPath", "variants"],
378
+ },
379
+ handler: async (params) =>
380
+ JSON.stringify(await bridge.umaCreateWardrobeFromFbx(params), null, 2),
381
+ },
382
+ {
383
+ name: "unity_uma_wardrobe_equip",
384
+ description:
385
+ "Equip or unequip a UMA wardrobe recipe on a DynamicCharacterAvatar in the scene. " +
386
+ "Automatically detects Play/Edit mode and uses the correct API path:\n" +
387
+ "- Play Mode: Uses runtime DCA API (SetSlot/ClearSlot + BuildCharacter)\n" +
388
+ "- Edit Mode: Modifies serialized preloadWardrobeRecipes list + triggers visual rebuild via GenerateSingleUMA\n\n" +
389
+ "To UNEQUIP a slot, pass recipeName as null or omit it.\n\n" +
390
+ "EXAMPLE (equip):\n" +
391
+ '{ "gameObjectPath": "PlayerPrefabv2_TestWardrobe", "wardrobeSlot": "Helmet", "recipeName": "Helm_Peasant_Bl_Recipe" }\n\n' +
392
+ "EXAMPLE (unequip):\n" +
393
+ '{ "gameObjectPath": "PlayerPrefabv2_TestWardrobe", "wardrobeSlot": "Helmet", "recipeName": null }\n\n' +
394
+ "WARDROBE SLOTS: Waist, Feet, Shoulders, Chest, Helmet, Legs, Hands, Hair, Complexion, Eyebrows, Beard, Eyes, Face, Ears, Underwear",
395
+ inputSchema: {
396
+ type: "object",
397
+ properties: {
398
+ gameObjectPath: {
399
+ type: "string",
400
+ description:
401
+ "Hierarchy path to the GameObject (or its parent) containing DynamicCharacterAvatar (e.g. 'PlayerPrefabv2_TestWardrobe')",
402
+ },
403
+ wardrobeSlot: {
404
+ type: "string",
405
+ description:
406
+ "UMA wardrobe slot name: 'Waist', 'Feet', 'Shoulders', 'Chest', 'Helmet', 'Legs', 'Hands', etc.",
407
+ },
408
+ recipeName: {
409
+ type: ["string", "null"],
410
+ description:
411
+ "Recipe asset name (e.g. 'Helm_Peasant_Bl_Recipe'). null or omitted = unequip (clear the slot)",
412
+ },
413
+ },
414
+ required: ["gameObjectPath", "wardrobeSlot"],
415
+ },
416
+ handler: async (params) =>
417
+ JSON.stringify(await bridge.umaWardrobeEquip(params), null, 2),
418
+ },
419
+ {
420
+ name: "unity_uma_edit_race",
421
+ description:
422
+ "Edit properties of an existing UMA RaceData asset. Supports renaming (with automatic cascade " +
423
+ "to all WardrobeRecipes, base recipe, and DCA in scene), modifying wardrobe slots, physics " +
424
+ "properties, tags, and cross-compatibility settings.\n\n" +
425
+ "RENAME CASCADE: When newRaceName is provided, automatically updates:\n" +
426
+ "- The base recipe (compatibleRaces + recipeString JSON)\n" +
427
+ "- All UMATextRecipe/WardrobeRecipe whose compatibleRaces or recipeString references the old name\n" +
428
+ "- All DynamicCharacterAvatars in the current scene (activeRace.name)\n" +
429
+ "- The .asset filename on disk\n" +
430
+ "- The UMA Global Library\n\n" +
431
+ "EXAMPLE (rename):\n" +
432
+ '{ "raceName": "HumanRace", "newRaceName": "HumanMaleRace" }\n\n' +
433
+ "EXAMPLE (add wardrobe slot):\n" +
434
+ '{ "raceName": "HumanMaleRace", "addWardrobeSlots": ["TorsoAdditional"] }\n\n' +
435
+ "EXAMPLE (full edit):\n" +
436
+ '{ "raceName": "HumanRace", "newRaceName": "HumanMaleRace", ' +
437
+ '"raceHeight": 1.8, "tags": ["playable", "male"] }',
438
+ inputSchema: {
439
+ type: "object",
440
+ properties: {
441
+ raceName: {
442
+ type: "string",
443
+ description:
444
+ "Current race name to edit (looked up in Global Library). Provide this OR racePath.",
445
+ },
446
+ racePath: {
447
+ type: "string",
448
+ description:
449
+ "Direct asset path to the RaceData (e.g. 'Assets/.../HumanRace.asset'). Alternative to raceName.",
450
+ },
451
+ newRaceName: {
452
+ type: "string",
453
+ description:
454
+ "New name for the race. Triggers cascade update on all recipes, DCA, and asset file.",
455
+ },
456
+ wardrobeSlots: {
457
+ type: "array",
458
+ description:
459
+ "Complete replacement of wardrobe slot list. Mutually exclusive with addWardrobeSlots/removeWardrobeSlots.",
460
+ items: { type: "string" },
461
+ },
462
+ addWardrobeSlots: {
463
+ type: "array",
464
+ description: "Slots to add to the existing list (no duplicates).",
465
+ items: { type: "string" },
466
+ },
467
+ removeWardrobeSlots: {
468
+ type: "array",
469
+ description: "Slots to remove from the existing list.",
470
+ items: { type: "string" },
471
+ },
472
+ umaTarget: {
473
+ type: "string",
474
+ description: "'Humanoid' or 'Generic'.",
475
+ enum: ["Humanoid", "Generic"],
476
+ },
477
+ fixupRotations: {
478
+ type: "boolean",
479
+ description: "Set to true for Blender FBX slots.",
480
+ },
481
+ tags: {
482
+ type: "array",
483
+ description: "Replace all tags on the race.",
484
+ items: { type: "string" },
485
+ },
486
+ backwardsCompatibleWith: {
487
+ type: "array",
488
+ description: "Race names this race is cross-compatible with.",
489
+ items: { type: "string" },
490
+ },
491
+ raceHeight: { type: "number", description: "Race default height." },
492
+ raceRadius: { type: "number", description: "Race default radius." },
493
+ raceMass: { type: "number", description: "Race default mass." },
494
+ baseRaceRecipePath: {
495
+ type: "string",
496
+ description:
497
+ "Asset path to a new base race recipe (UMATextRecipe) to assign. Only if changing the base recipe reference.",
498
+ },
499
+ updateRecipes: {
500
+ type: "boolean",
501
+ description:
502
+ "When renaming, update compatibleRaces + recipeString in all recipes (default: true).",
503
+ },
504
+ updateDCA: {
505
+ type: "boolean",
506
+ description:
507
+ "When renaming, update DCA in current scene (default: true).",
508
+ },
509
+ renameAssetFile: {
510
+ type: "boolean",
511
+ description:
512
+ "When renaming, also rename the .asset file on disk (default: true).",
513
+ },
514
+ rebuildLibrary: {
515
+ type: "boolean",
516
+ description:
517
+ "Rebuild UMA Global Library after modifications (default: true).",
518
+ },
519
+ },
520
+ },
521
+ handler: async (params) =>
522
+ JSON.stringify(await bridge.umaEditRace(params), null, 2),
523
+ },
524
+ {
525
+ name: "unity_uma_create_race",
526
+ description:
527
+ "Create a new UMA RaceData asset. Two modes:\n\n" +
528
+ "DUPLICATE MODE (recommended): Provide sourceRaceName to copy all settings from an existing race. " +
529
+ "The base recipe is automatically duplicated with the new race name in recipeString and compatibleRaces. " +
530
+ "Any optional parameter overrides the duplicated value.\n\n" +
531
+ "SCRATCH MODE: Omit sourceRaceName to create from scratch with sensible defaults " +
532
+ "(Humanoid, standard wardrobe slots, default physics). No base recipe is created — assign one via edit_race.\n\n" +
533
+ "EXAMPLE (duplicate):\n" +
534
+ '{ "raceName": "HumanFemaleRace", "sourceRaceName": "HumanMaleRace" }\n\n' +
535
+ "EXAMPLE (duplicate with overrides):\n" +
536
+ '{ "raceName": "HumanFemaleRace", "sourceRaceName": "HumanMaleRace", ' +
537
+ '"raceHeight": 1.85, "tags": ["playable", "female"] }\n\n' +
538
+ "EXAMPLE (from scratch):\n" +
539
+ '{ "raceName": "DwarfRace", "outputFolder": "Assets/UMA/Races", ' +
540
+ '"wardrobeSlots": ["Chest", "Legs", "Feet", "Helmet"], "raceHeight": 1.2 }\n\n' +
541
+ "FBX MODE: Provide fbxPath + umaMaterialPath to create a full race from an FBX body mesh. " +
542
+ "Inspects the FBX, creates body SlotDataAssets (one per SMR), OverlayDataAssets with auto-detected " +
543
+ "textures, and assembles a base race recipe (UMATextRecipe). Optionally combine with sourceRaceName " +
544
+ "to copy wardrobeSlots/physics from an existing race.\n\n" +
545
+ "EXAMPLE (FBX mode):\n" +
546
+ '{ "raceName": "HumanFemaleRace", "fbxPath": "Assets/.../SK_Hu_F_FullBody.fbx", ' +
547
+ '"umaMaterialPath": "Assets/.../UMA_ClothesBase.asset", "outputFolder": "Assets/UMA/Races/Female" }\n\n' +
548
+ "EXAMPLE (FBX + copy settings from existing race):\n" +
549
+ '{ "raceName": "HumanFemaleRace", "sourceRaceName": "HumanMaleRace", ' +
550
+ '"fbxPath": "Assets/.../SK_Hu_F_FullBody.fbx", "umaMaterialPath": "Assets/.../UMA_ClothesBase.asset" }',
551
+ inputSchema: {
552
+ type: "object",
553
+ properties: {
554
+ raceName: {
555
+ type: "string",
556
+ description: "Name for the new race (REQUIRED). Must be unique.",
557
+ },
558
+ sourceRaceName: {
559
+ type: "string",
560
+ description:
561
+ "Name of an existing race to duplicate from (Global Library lookup). If provided, enters duplicate mode.",
562
+ },
563
+ sourceRacePath: {
564
+ type: "string",
565
+ description:
566
+ "Direct asset path to the source RaceData. Alternative to sourceRaceName.",
567
+ },
568
+ outputFolder: {
569
+ type: "string",
570
+ description:
571
+ "Folder to save the new RaceData (and base recipe). Defaults to same folder as source race in duplicate mode.",
572
+ },
573
+ wardrobeSlots: {
574
+ type: "array",
575
+ description: "Wardrobe slot list. In duplicate mode, overrides the source's list.",
576
+ items: { type: "string" },
577
+ },
578
+ umaTarget: {
579
+ type: "string",
580
+ description: "'Humanoid' (default) or 'Generic'.",
581
+ enum: ["Humanoid", "Generic"],
582
+ },
583
+ fixupRotations: {
584
+ type: "boolean",
585
+ description: "Set to true for Blender FBX slots (default: copies source or true).",
586
+ },
587
+ tags: {
588
+ type: "array",
589
+ description: "Tags for the new race.",
590
+ items: { type: "string" },
591
+ },
592
+ fbxPath: {
593
+ type: "string",
594
+ description:
595
+ "Asset path to a body FBX file (e.g. 'Assets/.../SK_Hu_F_FullBody.fbx'). " +
596
+ "Enables FBX mode: creates body slots, overlays, and base race recipe from the FBX.",
597
+ },
598
+ umaMaterialPath: {
599
+ type: "string",
600
+ description:
601
+ "FULL asset path to the UMAMaterial for body slots (required in FBX mode). " +
602
+ "Must start with 'Assets/' and end with '.asset'.",
603
+ },
604
+ backwardsCompatibleWith: {
605
+ type: "array",
606
+ description: "Cross-compatible race names.",
607
+ items: { type: "string" },
608
+ },
609
+ raceHeight: { type: "number", description: "Default height (default: 2.0)." },
610
+ raceRadius: { type: "number", description: "Default radius (default: 0.25)." },
611
+ raceMass: { type: "number", description: "Default mass (default: 50)." },
612
+ duplicateBaseRecipe: {
613
+ type: "boolean",
614
+ description:
615
+ "In duplicate mode, also duplicate the base recipe with updated race name (default: true).",
616
+ },
617
+ registerInLibrary: {
618
+ type: "boolean",
619
+ description: "Register the new race in the UMA Global Library (default: true).",
620
+ },
621
+ },
622
+ required: ["raceName"],
623
+ },
624
+ handler: async (params) =>
625
+ JSON.stringify(await bridge.umaCreateRace(params), null, 2),
626
+ },
627
+ ];