@adiholick/nvstudio-mcp 2.2.3 → 2.4.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.
Files changed (42) hide show
  1. package/README.md +57 -47
  2. package/agent-plugin/mcp_config.json +8 -0
  3. package/agent-plugin/plugin.json +6 -0
  4. package/agent-plugin/rules/AGENTS.md +118 -0
  5. package/agent-plugin/skills/nvstudio-animation-vfx/SKILL.md +120 -0
  6. package/agent-plugin/skills/nvstudio-assets-terrain-guide/SKILL.md +27 -0
  7. package/agent-plugin/skills/nvstudio-gui-standards/SKILL.md +419 -0
  8. package/agent-plugin/skills/nvstudio-mcp-guide/SKILL.md +83 -0
  9. package/agent-plugin/skills/nvstudio-rig-engine/SKILL.md +170 -0
  10. package/agent-plugin/skills/nvstudio-roblox-sync-guide/SKILL.md +16 -0
  11. package/dist/build-plugin.js +55 -0
  12. package/dist/index.js +841 -3
  13. package/dist/luau-validator.js +99 -34
  14. package/dist/roblox-bridge.js +152 -4
  15. package/dist/roblox-docs.js +264 -0
  16. package/dist/vfx-database.js +507 -0
  17. package/package.json +6 -4
  18. package/public/index.html +1590 -180
  19. package/studio-plugin/Main.server.lua +1 -1
  20. package/studio-plugin/animate_character.lua +36 -0
  21. package/studio-plugin/apply_vfx.lua +59 -0
  22. package/studio-plugin/batch_create_instances.lua +128 -0
  23. package/studio-plugin/batch_set_properties.lua +69 -0
  24. package/studio-plugin/edit_rig.lua +35 -0
  25. package/studio-plugin/insert_asset.lua +85 -12
  26. package/studio-plugin/nv_anim_dispatcher.lua +239 -0
  27. package/studio-plugin/nv_auto_rigger.lua +392 -0
  28. package/studio-plugin/nv_curves.lua +260 -0
  29. package/studio-plugin/nv_joint_math.lua +138 -0
  30. package/studio-plugin/nv_kinematics.lua +248 -0
  31. package/studio-plugin/nv_motion_generator.lua +279 -0
  32. package/studio-plugin/nv_rig_dispatcher.lua +319 -0
  33. package/studio-plugin/nv_vfx_serializer.lua +363 -0
  34. package/studio-plugin/nvstudio_mcp.rbxmx +3824 -60
  35. package/studio-plugin/preview_asset.lua +99 -0
  36. package/studio-plugin/scan_security.lua +81 -0
  37. package/studio-plugin/scan_vfx.lua +85 -0
  38. package/studio-plugin/scrub_timeline.lua +122 -0
  39. package/studio-plugin/security_scanner.lua +346 -0
  40. package/studio-plugin/set_properties.lua +83 -0
  41. package/studio-plugin/solve_limb_ik.lua +63 -0
  42. package/studio-plugin/ui_manager.lua +1 -1
@@ -0,0 +1,507 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.VfxDatabase = void 0;
7
+ exports.normalizeVfxCategory = normalizeVfxCategory;
8
+ exports.getVfxDatabase = getVfxDatabase;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const uuid_1 = require("uuid");
12
+ const DATA_DIR = path_1.default.join(process.cwd(), 'data');
13
+ const DB_FILE = path_1.default.join(DATA_DIR, 'vfx-database.json');
14
+ // ── Default Seed Presets ───────────────────────────────────────────────────────
15
+ const DEFAULT_PRESETS = [
16
+ {
17
+ id: 'preset-cyan-slash',
18
+ name: 'Cyan Horizon Slash',
19
+ category: 'Slash',
20
+ tags: ['sword', 'cyan', 'combat', 'slash', 'beam'],
21
+ description: 'High-speed sharp cybernetic slash effect with dual-layer brightness beams.',
22
+ author: 'NVStudio',
23
+ createdAt: new Date().toISOString(),
24
+ nodeCount: 5,
25
+ stats: { emitters: 0, beams: 2, trails: 0, lights: 0 },
26
+ data: {
27
+ version: 1,
28
+ rootName: 'CyanHorizonSlash',
29
+ rootClass: 'Folder',
30
+ nodeCount: 5,
31
+ stats: { emitters: 0, beams: 2, trails: 0, lights: 0 },
32
+ nodes: [
33
+ { class: 'Folder', name: 'CyanHorizonSlash', properties: { Name: 'CyanHorizonSlash' } },
34
+ {
35
+ parent: 1,
36
+ class: 'Attachment',
37
+ name: 'Att0',
38
+ properties: {
39
+ Name: 'Att0',
40
+ Visible: false,
41
+ CFrame: { t: 'CFrame', v: [2, 0.5, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1] },
42
+ },
43
+ },
44
+ {
45
+ parent: 1,
46
+ class: 'Attachment',
47
+ name: 'Att1',
48
+ properties: {
49
+ Name: 'Att1',
50
+ Visible: false,
51
+ CFrame: { t: 'CFrame', v: [-2, -0.5, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1] },
52
+ },
53
+ },
54
+ {
55
+ parent: 1,
56
+ class: 'Beam',
57
+ name: 'SlashCore',
58
+ properties: {
59
+ Name: 'SlashCore',
60
+ Enabled: true,
61
+ Attachment0: { t: 'Ref', v: 2 },
62
+ Attachment1: { t: 'Ref', v: 3 },
63
+ Brightness: 3000,
64
+ LightEmission: 1,
65
+ LightInfluence: 0,
66
+ Width0: 1.2,
67
+ Width1: 0.1,
68
+ Texture: 'rbxassetid://85772363699785',
69
+ TextureSpeed: 5,
70
+ TextureLength: 1,
71
+ Color: {
72
+ t: 'ColorSequence',
73
+ v: [
74
+ [0, { t: 'Color3', v: [0, 0.8, 1] }],
75
+ [1, { t: 'Color3', v: [0, 1, 0.8] }],
76
+ ],
77
+ },
78
+ Transparency: {
79
+ t: 'NumberSequence',
80
+ v: [
81
+ [0, 0, 0],
82
+ [1, 1, 0],
83
+ ],
84
+ },
85
+ },
86
+ },
87
+ {
88
+ parent: 1,
89
+ class: 'Beam',
90
+ name: 'SlashGlow',
91
+ properties: {
92
+ Name: 'SlashGlow',
93
+ Enabled: true,
94
+ Attachment0: { t: 'Ref', v: 2 },
95
+ Attachment1: { t: 'Ref', v: 3 },
96
+ Brightness: 1500,
97
+ LightEmission: 0.8,
98
+ Width0: 2.5,
99
+ Width1: 0.5,
100
+ TextureSpeed: 3,
101
+ Color: {
102
+ t: 'ColorSequence',
103
+ v: [
104
+ [0, { t: 'Color3', v: [0.1, 0.5, 1] }],
105
+ [1, { t: 'Color3', v: [0.2, 0.8, 1] }],
106
+ ],
107
+ },
108
+ Transparency: {
109
+ t: 'NumberSequence',
110
+ v: [
111
+ [0, 0.2, 0],
112
+ [1, 1, 0],
113
+ ],
114
+ },
115
+ },
116
+ },
117
+ ],
118
+ },
119
+ },
120
+ {
121
+ id: 'preset-solar-burst',
122
+ name: 'Solar Flare Burst',
123
+ category: 'Explosion',
124
+ tags: ['fire', 'orange', 'explosion', 'particles', 'burst'],
125
+ description: 'Radial fiery shockwave burst with expanding sparks and heat glow.',
126
+ author: 'NVStudio',
127
+ createdAt: new Date().toISOString(),
128
+ nodeCount: 3,
129
+ stats: { emitters: 2, beams: 0, trails: 0, lights: 1 },
130
+ data: {
131
+ version: 1,
132
+ rootName: 'SolarFlareBurst',
133
+ rootClass: 'Folder',
134
+ nodeCount: 3,
135
+ stats: { emitters: 2, beams: 0, trails: 0, lights: 1 },
136
+ nodes: [
137
+ { class: 'Folder', name: 'SolarFlareBurst', properties: { Name: 'SolarFlareBurst' } },
138
+ {
139
+ parent: 1,
140
+ class: 'ParticleEmitter',
141
+ name: 'Sparks',
142
+ properties: {
143
+ Name: 'Sparks',
144
+ Enabled: true,
145
+ Rate: 200,
146
+ Speed: { t: 'NumberRange', v: [25, 45] },
147
+ SpreadAngle: { t: 'Vector2', v: [180, 180] },
148
+ Lifetime: { t: 'NumberRange', v: [0.4, 0.8] },
149
+ LightEmission: 1,
150
+ LightInfluence: 0,
151
+ Brightness: 10,
152
+ Size: {
153
+ t: 'NumberSequence',
154
+ v: [
155
+ [0, 1.2, 0],
156
+ [0.7, 0.4, 0],
157
+ [1, 0, 0],
158
+ ],
159
+ },
160
+ Color: {
161
+ t: 'ColorSequence',
162
+ v: [
163
+ [0, { t: 'Color3', v: [1, 0.9, 0.5] }],
164
+ [0.5, { t: 'Color3', v: [1, 0.4, 0] }],
165
+ [1, { t: 'Color3', v: [0.8, 0.1, 0] }],
166
+ ],
167
+ },
168
+ Transparency: {
169
+ t: 'NumberSequence',
170
+ v: [
171
+ [0, 0, 0],
172
+ [1, 1, 0],
173
+ ],
174
+ },
175
+ Texture: 'rbxassetid://244221448',
176
+ },
177
+ },
178
+ {
179
+ parent: 1,
180
+ class: 'PointLight',
181
+ name: 'ExplosionLight',
182
+ properties: {
183
+ Name: 'ExplosionLight',
184
+ Enabled: true,
185
+ Brightness: 8,
186
+ Range: 24,
187
+ Color: { t: 'Color3', v: [1, 0.5, 0.1] },
188
+ Shadows: true,
189
+ },
190
+ },
191
+ ],
192
+ },
193
+ },
194
+ {
195
+ id: 'preset-dragon-aura',
196
+ name: 'Azure Dragon Aura',
197
+ category: 'Aura',
198
+ tags: ['aura', 'magic', 'energy', 'cyan', 'blue'],
199
+ description: 'Swirling ethereal flame aura for boss phases, charging states, and powerups.',
200
+ author: 'NVStudio',
201
+ createdAt: new Date().toISOString(),
202
+ nodeCount: 2,
203
+ stats: { emitters: 1, beams: 0, trails: 0, lights: 1 },
204
+ data: {
205
+ version: 1,
206
+ rootName: 'AzureDragonAura',
207
+ rootClass: 'Folder',
208
+ nodeCount: 2,
209
+ stats: { emitters: 1, beams: 0, trails: 0, lights: 1 },
210
+ nodes: [
211
+ { class: 'Folder', name: 'AzureDragonAura', properties: { Name: 'AzureDragonAura' } },
212
+ {
213
+ parent: 1,
214
+ class: 'ParticleEmitter',
215
+ name: 'FlameAura',
216
+ properties: {
217
+ Name: 'FlameAura',
218
+ Enabled: true,
219
+ Rate: 35,
220
+ Speed: { t: 'NumberRange', v: [2, 5] },
221
+ EmissionDirection: { t: 'EnumItem', e: 'NormalId', v: 'Top' },
222
+ Lifetime: { t: 'NumberRange', v: [0.8, 1.4] },
223
+ LightEmission: 0.9,
224
+ LightInfluence: 0,
225
+ Brightness: 5,
226
+ Drag: 2,
227
+ Acceleration: { t: 'Vector3', v: [0, 8, 0] },
228
+ Size: {
229
+ t: 'NumberSequence',
230
+ v: [
231
+ [0, 0.5, 0],
232
+ [0.5, 2.5, 0],
233
+ [1, 0.2, 0],
234
+ ],
235
+ },
236
+ Color: {
237
+ t: 'ColorSequence',
238
+ v: [
239
+ [0, { t: 'Color3', v: [0, 0.7, 1] }],
240
+ [0.6, { t: 'Color3', v: [0.2, 0.2, 0.9] }],
241
+ [1, { t: 'Color3', v: [0.05, 0, 0.4] }],
242
+ ],
243
+ },
244
+ Transparency: {
245
+ t: 'NumberSequence',
246
+ v: [
247
+ [0, 0.8, 0],
248
+ [0.3, 0.2, 0],
249
+ [1, 1, 0],
250
+ ],
251
+ },
252
+ Texture: 'rbxassetid://258128463',
253
+ },
254
+ },
255
+ ],
256
+ },
257
+ },
258
+ {
259
+ id: 'preset-arcane-rune',
260
+ name: 'Arcane Summoning Circle',
261
+ category: 'Magic',
262
+ tags: ['magic', 'spell', 'circle', 'rune', 'purple'],
263
+ description: 'Luminous spinning arcane circle with floating magical motes.',
264
+ author: 'NVStudio',
265
+ createdAt: new Date().toISOString(),
266
+ nodeCount: 3,
267
+ stats: { emitters: 1, beams: 0, trails: 0, lights: 1 },
268
+ data: {
269
+ version: 1,
270
+ rootName: 'ArcaneSummoningCircle',
271
+ rootClass: 'Folder',
272
+ nodeCount: 3,
273
+ stats: { emitters: 1, beams: 0, trails: 0, lights: 1 },
274
+ nodes: [
275
+ { class: 'Folder', name: 'ArcaneSummoningCircle', properties: { Name: 'ArcaneSummoningCircle' } },
276
+ {
277
+ parent: 1,
278
+ class: 'ParticleEmitter',
279
+ name: 'MagicSparks',
280
+ properties: {
281
+ Name: 'MagicSparks',
282
+ Enabled: true,
283
+ Rate: 40,
284
+ Speed: { t: 'NumberRange', v: [1, 4] },
285
+ Lifetime: { t: 'NumberRange', v: [1, 2] },
286
+ LightEmission: 1,
287
+ Size: {
288
+ t: 'NumberSequence',
289
+ v: [
290
+ [0, 0.2, 0],
291
+ [0.5, 0.6, 0],
292
+ [1, 0, 0],
293
+ ],
294
+ },
295
+ Color: {
296
+ t: 'ColorSequence',
297
+ v: [
298
+ [0, { t: 'Color3', v: [0.8, 0.2, 1] }],
299
+ [1, { t: 'Color3', v: [0.4, 0.8, 1] }],
300
+ ],
301
+ },
302
+ Transparency: {
303
+ t: 'NumberSequence',
304
+ v: [
305
+ [0, 0, 0],
306
+ [1, 1, 0],
307
+ ],
308
+ },
309
+ Texture: 'rbxassetid://244221448',
310
+ },
311
+ },
312
+ {
313
+ parent: 1,
314
+ class: 'PointLight',
315
+ name: 'RuneGlow',
316
+ properties: {
317
+ Name: 'RuneGlow',
318
+ Enabled: true,
319
+ Brightness: 4,
320
+ Range: 16,
321
+ Color: { t: 'Color3', v: [0.7, 0.2, 1] },
322
+ },
323
+ },
324
+ ],
325
+ },
326
+ },
327
+ {
328
+ id: 'preset-emerald-beam',
329
+ name: 'Emerald Laser Beam',
330
+ category: 'Beam',
331
+ tags: ['beam', 'laser', 'emerald', 'green', 'energy'],
332
+ description: 'High-density focused energy beam with glowing outer edge.',
333
+ author: 'NVStudio',
334
+ createdAt: new Date().toISOString(),
335
+ nodeCount: 4,
336
+ stats: { emitters: 0, beams: 1, trails: 0, lights: 1 },
337
+ data: {
338
+ version: 1,
339
+ rootName: 'EmeraldLaserBeam',
340
+ rootClass: 'Folder',
341
+ nodeCount: 4,
342
+ stats: { emitters: 0, beams: 1, trails: 0, lights: 1 },
343
+ nodes: [
344
+ { class: 'Folder', name: 'EmeraldLaserBeam', properties: { Name: 'EmeraldLaserBeam' } },
345
+ {
346
+ parent: 1,
347
+ class: 'Attachment',
348
+ name: 'Origin',
349
+ properties: { Name: 'Origin', CFrame: { t: 'CFrame', v: [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1] } },
350
+ },
351
+ {
352
+ parent: 1,
353
+ class: 'Attachment',
354
+ name: 'Target',
355
+ properties: { Name: 'Target', CFrame: { t: 'CFrame', v: [0, 0, -30, 1, 0, 0, 0, 1, 0, 0, 0, 1] } },
356
+ },
357
+ {
358
+ parent: 1,
359
+ class: 'Beam',
360
+ name: 'LaserCore',
361
+ properties: {
362
+ Name: 'LaserCore',
363
+ Enabled: true,
364
+ Attachment0: { t: 'Ref', v: 2 },
365
+ Attachment1: { t: 'Ref', v: 3 },
366
+ Brightness: 2500,
367
+ LightEmission: 1,
368
+ Width0: 1.5,
369
+ Width1: 1.5,
370
+ TextureSpeed: 8,
371
+ Color: {
372
+ t: 'ColorSequence',
373
+ v: [
374
+ [0, { t: 'Color3', v: [0.2, 1, 0.4] }],
375
+ [1, { t: 'Color3', v: [0, 0.8, 0.2] }],
376
+ ],
377
+ },
378
+ Transparency: {
379
+ t: 'NumberSequence',
380
+ v: [
381
+ [0, 0.1, 0],
382
+ [1, 0.1, 0],
383
+ ],
384
+ },
385
+ },
386
+ },
387
+ ],
388
+ },
389
+ },
390
+ ];
391
+ function normalizeVfxCategory(rawCategory) {
392
+ if (!rawCategory || typeof rawCategory !== 'string')
393
+ return 'Ambient';
394
+ const trimmed = rawCategory.trim();
395
+ if (!trimmed)
396
+ return 'Ambient';
397
+ return trimmed.charAt(0).toUpperCase() + trimmed.slice(1).toLowerCase();
398
+ }
399
+ class VfxDatabase {
400
+ items = new Map();
401
+ constructor() {
402
+ this.ensureDirectory();
403
+ this.loadDatabase();
404
+ }
405
+ ensureDirectory() {
406
+ if (!fs_1.default.existsSync(DATA_DIR)) {
407
+ fs_1.default.mkdirSync(DATA_DIR, { recursive: true });
408
+ }
409
+ }
410
+ loadDatabase() {
411
+ if (fs_1.default.existsSync(DB_FILE)) {
412
+ try {
413
+ const raw = fs_1.default.readFileSync(DB_FILE, 'utf-8');
414
+ const list = JSON.parse(raw);
415
+ for (const item of list) {
416
+ item.category = normalizeVfxCategory(item.category);
417
+ this.items.set(item.id, item);
418
+ }
419
+ }
420
+ catch (err) {
421
+ console.error('[VfxDatabase] Gagal membaca vfx-database.json, menggunakan preset default:', err);
422
+ this.seedPresets();
423
+ }
424
+ }
425
+ else {
426
+ this.seedPresets();
427
+ }
428
+ }
429
+ seedPresets() {
430
+ for (const preset of DEFAULT_PRESETS) {
431
+ this.items.set(preset.id, preset);
432
+ }
433
+ this.saveToFile();
434
+ }
435
+ saveToFile() {
436
+ try {
437
+ this.ensureDirectory();
438
+ const list = Array.from(this.items.values());
439
+ fs_1.default.writeFileSync(DB_FILE, JSON.stringify(list, null, 2), 'utf-8');
440
+ }
441
+ catch (err) {
442
+ console.error('[VfxDatabase] Gagal menyimpan ke vfx-database.json:', err);
443
+ }
444
+ }
445
+ saveVfx(item) {
446
+ const id = item.id || `vfx-${(0, uuid_1.v4)().slice(0, 8)}`;
447
+ const createdAt = item.createdAt || new Date().toISOString();
448
+ const category = normalizeVfxCategory(item.category);
449
+ const vfxItem = {
450
+ ...item,
451
+ id,
452
+ category,
453
+ createdAt,
454
+ tags: Array.isArray(item.tags) ? item.tags : [category.toLowerCase()],
455
+ nodeCount: item.nodeCount || (item.data?.nodes ? item.data.nodes.length : 1),
456
+ stats: item.stats || { emitters: 0, beams: 0, trails: 0, lights: 0 },
457
+ };
458
+ this.items.set(id, vfxItem);
459
+ this.saveToFile();
460
+ return vfxItem;
461
+ }
462
+ getVfx(id) {
463
+ return this.items.get(id);
464
+ }
465
+ listVfx(filter) {
466
+ let result = Array.from(this.items.values());
467
+ if (filter?.category && filter.category !== 'All' && filter.category !== 'Semua') {
468
+ const catLower = filter.category.toLowerCase();
469
+ result = result.filter(item => item.category.toLowerCase() === catLower);
470
+ }
471
+ if (filter?.query && filter.query.trim()) {
472
+ const q = filter.query.toLowerCase().trim();
473
+ result = result.filter(item => item.name.toLowerCase().includes(q) ||
474
+ item.category.toLowerCase().includes(q) ||
475
+ (item.description && item.description.toLowerCase().includes(q)) ||
476
+ (item.tags && item.tags.some(t => t.toLowerCase().includes(q))));
477
+ }
478
+ return result.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
479
+ }
480
+ deleteVfx(id) {
481
+ const deleted = this.items.delete(id);
482
+ if (deleted) {
483
+ this.saveToFile();
484
+ }
485
+ return deleted;
486
+ }
487
+ getCategories() {
488
+ const counts = new Map();
489
+ for (const item of this.items.values()) {
490
+ const cat = normalizeVfxCategory(item.category);
491
+ counts.set(cat, (counts.get(cat) || 0) + 1);
492
+ }
493
+ const categories = [];
494
+ for (const [name, count] of counts.entries()) {
495
+ categories.push({ name, count });
496
+ }
497
+ return categories.sort((a, b) => b.count - a.count);
498
+ }
499
+ }
500
+ exports.VfxDatabase = VfxDatabase;
501
+ let instance = null;
502
+ function getVfxDatabase() {
503
+ if (!instance) {
504
+ instance = new VfxDatabase();
505
+ }
506
+ return instance;
507
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@adiholick/nvstudio-mcp",
3
- "version": "2.2.3",
4
- "description": "Local Bridge Server MCP for Roblox Studio",
3
+ "version": "2.4.0",
4
+ "description": "Local Bridge Server MCP for Roblox Studio with Animation, VFX, and Rigging Engines",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "nvstudio-mcp": "dist/index.js"
@@ -9,7 +9,8 @@
9
9
  "files": [
10
10
  "dist",
11
11
  "public",
12
- "studio-plugin"
12
+ "studio-plugin",
13
+ "agent-plugin"
13
14
  ],
14
15
  "scripts": {
15
16
  "build": "tsc && npm run build:plugin",
@@ -29,7 +30,8 @@
29
30
  "express": "^5.2.1",
30
31
  "socket.io": "^4.8.3",
31
32
  "uuid": "^14.0.2",
32
- "ws": "^8.22.0"
33
+ "ws": "^8.22.0",
34
+ "yaml": "^2.9.1"
33
35
  },
34
36
  "devDependencies": {
35
37
  "@types/express": "^5.0.6",