rbxstudio-mcp 1.9.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -0
  3. package/dist/__tests__/bridge-service.test.d.ts +2 -0
  4. package/dist/__tests__/bridge-service.test.d.ts.map +1 -0
  5. package/dist/__tests__/bridge-service.test.js +109 -0
  6. package/dist/__tests__/bridge-service.test.js.map +1 -0
  7. package/dist/__tests__/http-server.test.d.ts +2 -0
  8. package/dist/__tests__/http-server.test.d.ts.map +1 -0
  9. package/dist/__tests__/http-server.test.js +193 -0
  10. package/dist/__tests__/http-server.test.js.map +1 -0
  11. package/dist/__tests__/integration.test.d.ts +2 -0
  12. package/dist/__tests__/integration.test.d.ts.map +1 -0
  13. package/dist/__tests__/integration.test.js +182 -0
  14. package/dist/__tests__/integration.test.js.map +1 -0
  15. package/dist/__tests__/smoke.test.d.ts +2 -0
  16. package/dist/__tests__/smoke.test.d.ts.map +1 -0
  17. package/dist/__tests__/smoke.test.js +63 -0
  18. package/dist/__tests__/smoke.test.js.map +1 -0
  19. package/dist/bridge-service.d.ts +17 -0
  20. package/dist/bridge-service.d.ts.map +1 -0
  21. package/dist/bridge-service.js +77 -0
  22. package/dist/bridge-service.js.map +1 -0
  23. package/dist/http-server.d.ts +4 -0
  24. package/dist/http-server.d.ts.map +1 -0
  25. package/dist/http-server.js +290 -0
  26. package/dist/http-server.js.map +1 -0
  27. package/dist/index.d.ts +18 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +1102 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/tools/index.d.ts +273 -0
  32. package/dist/tools/index.d.ts.map +1 -0
  33. package/dist/tools/index.js +628 -0
  34. package/dist/tools/index.js.map +1 -0
  35. package/dist/tools/studio-client.d.ts +7 -0
  36. package/dist/tools/studio-client.d.ts.map +1 -0
  37. package/dist/tools/studio-client.js +19 -0
  38. package/dist/tools/studio-client.js.map +1 -0
  39. package/package.json +69 -0
  40. package/studio-plugin/INSTALLATION.md +150 -0
  41. package/studio-plugin/MCPPlugin.rbxmx +3253 -0
  42. package/studio-plugin/plugin.json +10 -0
  43. package/studio-plugin/plugin.luau +3584 -0
package/dist/index.js ADDED
@@ -0,0 +1,1102 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Roblox Studio MCP Server
4
+ *
5
+ * This server provides Model Context Protocol (MCP) tools for interacting with Roblox Studio.
6
+ * It allows AI assistants to access Studio data, scripts, and objects through a bridge plugin.
7
+ *
8
+ * Usage:
9
+ * npx robloxstudio-mcp
10
+ *
11
+ * Or add to your MCP configuration:
12
+ * "robloxstudio": {
13
+ * "command": "npx",
14
+ * "args": ["-y", "robloxstudio-mcp"]
15
+ * }
16
+ */
17
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
18
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
19
+ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
20
+ import { createHttpServer } from './http-server.js';
21
+ import { RobloxStudioTools } from './tools/index.js';
22
+ import { BridgeService } from './bridge-service.js';
23
+ class RobloxStudioMCPServer {
24
+ server;
25
+ tools;
26
+ bridge;
27
+ constructor() {
28
+ this.server = new Server({
29
+ name: 'rbxstudio-mcp',
30
+ version: '1.9.0',
31
+ }, {
32
+ capabilities: {
33
+ tools: {},
34
+ },
35
+ });
36
+ this.bridge = new BridgeService();
37
+ this.tools = new RobloxStudioTools(this.bridge);
38
+ this.setupToolHandlers();
39
+ }
40
+ setupToolHandlers() {
41
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
42
+ return {
43
+ tools: [
44
+ // Instance Hierarchy Tools (NOT local filesystem - these operate on Roblox Studio instances)
45
+ {
46
+ name: 'get_file_tree',
47
+ description: 'Get the Roblox instance hierarchy tree from Roblox Studio. Returns game instances (Parts, Scripts, Models, Folders, etc.) as a tree structure. NOTE: This operates on Roblox Studio instances, NOT local filesystem files.',
48
+ inputSchema: {
49
+ type: 'object',
50
+ properties: {
51
+ path: {
52
+ type: 'string',
53
+ description: 'Roblox instance path to start from using dot notation (e.g., "game.Workspace", "game.ServerScriptService"). Defaults to game root if empty.',
54
+ default: ''
55
+ }
56
+ }
57
+ }
58
+ },
59
+ {
60
+ name: 'search_files',
61
+ description: 'Search for Roblox instances by name, class type, or script content. NOTE: This searches Roblox Studio instances, NOT local filesystem files.',
62
+ inputSchema: {
63
+ type: 'object',
64
+ properties: {
65
+ query: {
66
+ type: 'string',
67
+ description: 'Search query - instance name, class type (e.g., "Script", "Part"), or Lua code pattern'
68
+ },
69
+ searchType: {
70
+ type: 'string',
71
+ enum: ['name', 'type', 'content'],
72
+ description: 'Type of search: "name" for instance names, "type" for class names, "content" for script source code',
73
+ default: 'name'
74
+ }
75
+ },
76
+ required: ['query']
77
+ }
78
+ },
79
+ // Studio Context Tools
80
+ {
81
+ name: 'get_place_info',
82
+ description: 'Get place ID, name, and game settings',
83
+ inputSchema: {
84
+ type: 'object',
85
+ properties: {}
86
+ }
87
+ },
88
+ {
89
+ name: 'get_services',
90
+ description: 'Get available Roblox services and their children',
91
+ inputSchema: {
92
+ type: 'object',
93
+ properties: {
94
+ serviceName: {
95
+ type: 'string',
96
+ description: 'Optional specific service name to query'
97
+ }
98
+ }
99
+ }
100
+ },
101
+ {
102
+ name: 'search_objects',
103
+ description: 'Find instances by name, class, or properties',
104
+ inputSchema: {
105
+ type: 'object',
106
+ properties: {
107
+ query: {
108
+ type: 'string',
109
+ description: 'Search query'
110
+ },
111
+ searchType: {
112
+ type: 'string',
113
+ enum: ['name', 'class', 'property'],
114
+ description: 'Type of search to perform',
115
+ default: 'name'
116
+ },
117
+ propertyName: {
118
+ type: 'string',
119
+ description: 'Property name when searchType is "property"'
120
+ }
121
+ },
122
+ required: ['query']
123
+ }
124
+ },
125
+ // Property & Instance Tools
126
+ {
127
+ name: 'get_instance_properties',
128
+ description: 'Get all properties of a specific Roblox instance in Studio',
129
+ inputSchema: {
130
+ type: 'object',
131
+ properties: {
132
+ instancePath: {
133
+ type: 'string',
134
+ description: 'Roblox instance path using dot notation (e.g., "game.Workspace.Part", "game.ServerScriptService.MainScript", "game.ReplicatedStorage.ModuleScript")'
135
+ }
136
+ },
137
+ required: ['instancePath']
138
+ }
139
+ },
140
+ {
141
+ name: 'get_instance_children',
142
+ description: 'Get child instances and their class types from a Roblox parent instance',
143
+ inputSchema: {
144
+ type: 'object',
145
+ properties: {
146
+ instancePath: {
147
+ type: 'string',
148
+ description: 'Roblox instance path using dot notation (e.g., "game.Workspace", "game.ServerScriptService")'
149
+ }
150
+ },
151
+ required: ['instancePath']
152
+ }
153
+ },
154
+ {
155
+ name: 'search_by_property',
156
+ description: 'Find objects with specific property values',
157
+ inputSchema: {
158
+ type: 'object',
159
+ properties: {
160
+ propertyName: {
161
+ type: 'string',
162
+ description: 'Name of the property to search'
163
+ },
164
+ propertyValue: {
165
+ type: 'string',
166
+ description: 'Value to search for'
167
+ }
168
+ },
169
+ required: ['propertyName', 'propertyValue']
170
+ }
171
+ },
172
+ {
173
+ name: 'get_class_info',
174
+ description: 'Get available properties/methods for Roblox classes',
175
+ inputSchema: {
176
+ type: 'object',
177
+ properties: {
178
+ className: {
179
+ type: 'string',
180
+ description: 'Roblox class name'
181
+ }
182
+ },
183
+ required: ['className']
184
+ }
185
+ },
186
+ // Project Tools
187
+ {
188
+ name: 'get_project_structure',
189
+ description: 'Get complete game hierarchy. IMPORTANT: Use maxDepth parameter (default: 3) to explore deeper levels of the hierarchy. Set higher values like 5-10 for comprehensive exploration',
190
+ inputSchema: {
191
+ type: 'object',
192
+ properties: {
193
+ path: {
194
+ type: 'string',
195
+ description: 'Optional path to start from (defaults to workspace root)',
196
+ default: ''
197
+ },
198
+ maxDepth: {
199
+ type: 'number',
200
+ description: 'Maximum depth to traverse (default: 3). RECOMMENDED: Use 5-10 for thorough exploration. Higher values provide more complete structure',
201
+ default: 3
202
+ },
203
+ scriptsOnly: {
204
+ type: 'boolean',
205
+ description: 'Show only scripts and script containers',
206
+ default: false
207
+ }
208
+ }
209
+ }
210
+ },
211
+ // Property Modification Tools
212
+ {
213
+ name: 'set_property',
214
+ description: 'Set a property on any Roblox instance',
215
+ inputSchema: {
216
+ type: 'object',
217
+ properties: {
218
+ instancePath: {
219
+ type: 'string',
220
+ description: 'Path to the instance (e.g., "game.Workspace.Part")'
221
+ },
222
+ propertyName: {
223
+ type: 'string',
224
+ description: 'Name of the property to set'
225
+ },
226
+ propertyValue: {
227
+ description: 'Value to set the property to (any type)'
228
+ }
229
+ },
230
+ required: ['instancePath', 'propertyName', 'propertyValue']
231
+ }
232
+ },
233
+ {
234
+ name: 'mass_set_property',
235
+ description: 'Set the same property on multiple instances at once',
236
+ inputSchema: {
237
+ type: 'object',
238
+ properties: {
239
+ paths: {
240
+ type: 'array',
241
+ items: { type: 'string' },
242
+ description: 'Array of instance paths to modify'
243
+ },
244
+ propertyName: {
245
+ type: 'string',
246
+ description: 'Name of the property to set'
247
+ },
248
+ propertyValue: {
249
+ description: 'Value to set the property to (any type)'
250
+ }
251
+ },
252
+ required: ['paths', 'propertyName', 'propertyValue']
253
+ }
254
+ },
255
+ {
256
+ name: 'mass_get_property',
257
+ description: 'Get the same property from multiple instances at once',
258
+ inputSchema: {
259
+ type: 'object',
260
+ properties: {
261
+ paths: {
262
+ type: 'array',
263
+ items: { type: 'string' },
264
+ description: 'Array of instance paths to read from'
265
+ },
266
+ propertyName: {
267
+ type: 'string',
268
+ description: 'Name of the property to get'
269
+ }
270
+ },
271
+ required: ['paths', 'propertyName']
272
+ }
273
+ },
274
+ // Object Creation/Deletion Tools
275
+ {
276
+ name: 'create_object',
277
+ description: 'Create a new Roblox object instance (basic, without properties)',
278
+ inputSchema: {
279
+ type: 'object',
280
+ properties: {
281
+ className: {
282
+ type: 'string',
283
+ description: 'Roblox class name (e.g., "Part", "Script", "Folder")'
284
+ },
285
+ parent: {
286
+ type: 'string',
287
+ description: 'Path to the parent instance (e.g., "game.Workspace")'
288
+ },
289
+ name: {
290
+ type: 'string',
291
+ description: 'Optional name for the new object'
292
+ }
293
+ },
294
+ required: ['className', 'parent']
295
+ }
296
+ },
297
+ {
298
+ name: 'create_object_with_properties',
299
+ description: 'Create a new Roblox object instance with initial properties',
300
+ inputSchema: {
301
+ type: 'object',
302
+ properties: {
303
+ className: {
304
+ type: 'string',
305
+ description: 'Roblox class name (e.g., "Part", "Script", "Folder")'
306
+ },
307
+ parent: {
308
+ type: 'string',
309
+ description: 'Path to the parent instance (e.g., "game.Workspace")'
310
+ },
311
+ name: {
312
+ type: 'string',
313
+ description: 'Optional name for the new object'
314
+ },
315
+ properties: {
316
+ type: 'object',
317
+ description: 'Properties to set on creation'
318
+ }
319
+ },
320
+ required: ['className', 'parent']
321
+ }
322
+ },
323
+ {
324
+ name: 'mass_create_objects',
325
+ description: 'Create multiple objects at once (basic, without properties)',
326
+ inputSchema: {
327
+ type: 'object',
328
+ properties: {
329
+ objects: {
330
+ type: 'array',
331
+ items: {
332
+ type: 'object',
333
+ properties: {
334
+ className: {
335
+ type: 'string',
336
+ description: 'Roblox class name'
337
+ },
338
+ parent: {
339
+ type: 'string',
340
+ description: 'Path to the parent instance'
341
+ },
342
+ name: {
343
+ type: 'string',
344
+ description: 'Optional name for the object'
345
+ }
346
+ },
347
+ required: ['className', 'parent']
348
+ },
349
+ description: 'Array of objects to create'
350
+ }
351
+ },
352
+ required: ['objects']
353
+ }
354
+ },
355
+ {
356
+ name: 'mass_create_objects_with_properties',
357
+ description: 'Create multiple objects at once with initial properties',
358
+ inputSchema: {
359
+ type: 'object',
360
+ properties: {
361
+ objects: {
362
+ type: 'array',
363
+ items: {
364
+ type: 'object',
365
+ properties: {
366
+ className: {
367
+ type: 'string',
368
+ description: 'Roblox class name'
369
+ },
370
+ parent: {
371
+ type: 'string',
372
+ description: 'Path to the parent instance'
373
+ },
374
+ name: {
375
+ type: 'string',
376
+ description: 'Optional name for the object'
377
+ },
378
+ properties: {
379
+ type: 'object',
380
+ description: 'Properties to set on creation'
381
+ }
382
+ },
383
+ required: ['className', 'parent']
384
+ },
385
+ description: 'Array of objects to create with properties'
386
+ }
387
+ },
388
+ required: ['objects']
389
+ }
390
+ },
391
+ {
392
+ name: 'delete_object',
393
+ description: 'Delete a Roblox object instance',
394
+ inputSchema: {
395
+ type: 'object',
396
+ properties: {
397
+ instancePath: {
398
+ type: 'string',
399
+ description: 'Path to the instance to delete'
400
+ }
401
+ },
402
+ required: ['instancePath']
403
+ }
404
+ },
405
+ // Smart Duplication Tools
406
+ {
407
+ name: 'smart_duplicate',
408
+ description: 'Smart duplication with automatic naming, positioning, and property variations',
409
+ inputSchema: {
410
+ type: 'object',
411
+ properties: {
412
+ instancePath: {
413
+ type: 'string',
414
+ description: 'Path to the instance to duplicate'
415
+ },
416
+ count: {
417
+ type: 'number',
418
+ description: 'Number of duplicates to create'
419
+ },
420
+ options: {
421
+ type: 'object',
422
+ properties: {
423
+ namePattern: {
424
+ type: 'string',
425
+ description: 'Name pattern with {n} placeholder (e.g., "Button{n}")'
426
+ },
427
+ positionOffset: {
428
+ type: 'array',
429
+ items: { type: 'number' },
430
+ minItems: 3,
431
+ maxItems: 3,
432
+ description: 'X, Y, Z offset per duplicate'
433
+ },
434
+ rotationOffset: {
435
+ type: 'array',
436
+ items: { type: 'number' },
437
+ minItems: 3,
438
+ maxItems: 3,
439
+ description: 'X, Y, Z rotation offset per duplicate'
440
+ },
441
+ scaleOffset: {
442
+ type: 'array',
443
+ items: { type: 'number' },
444
+ minItems: 3,
445
+ maxItems: 3,
446
+ description: 'X, Y, Z scale multiplier per duplicate'
447
+ },
448
+ propertyVariations: {
449
+ type: 'object',
450
+ description: 'Property name to array of values'
451
+ },
452
+ targetParents: {
453
+ type: 'array',
454
+ items: { type: 'string' },
455
+ description: 'Different parent for each duplicate'
456
+ }
457
+ }
458
+ }
459
+ },
460
+ required: ['instancePath', 'count']
461
+ }
462
+ },
463
+ {
464
+ name: 'mass_duplicate',
465
+ description: 'Perform multiple smart duplications at once',
466
+ inputSchema: {
467
+ type: 'object',
468
+ properties: {
469
+ duplications: {
470
+ type: 'array',
471
+ items: {
472
+ type: 'object',
473
+ properties: {
474
+ instancePath: {
475
+ type: 'string',
476
+ description: 'Path to the instance to duplicate'
477
+ },
478
+ count: {
479
+ type: 'number',
480
+ description: 'Number of duplicates to create'
481
+ },
482
+ options: {
483
+ type: 'object',
484
+ properties: {
485
+ namePattern: {
486
+ type: 'string',
487
+ description: 'Name pattern with {n} placeholder'
488
+ },
489
+ positionOffset: {
490
+ type: 'array',
491
+ items: { type: 'number' },
492
+ minItems: 3,
493
+ maxItems: 3,
494
+ description: 'X, Y, Z offset per duplicate'
495
+ },
496
+ rotationOffset: {
497
+ type: 'array',
498
+ items: { type: 'number' },
499
+ minItems: 3,
500
+ maxItems: 3,
501
+ description: 'X, Y, Z rotation offset per duplicate'
502
+ },
503
+ scaleOffset: {
504
+ type: 'array',
505
+ items: { type: 'number' },
506
+ minItems: 3,
507
+ maxItems: 3,
508
+ description: 'X, Y, Z scale multiplier per duplicate'
509
+ },
510
+ propertyVariations: {
511
+ type: 'object',
512
+ description: 'Property name to array of values'
513
+ },
514
+ targetParents: {
515
+ type: 'array',
516
+ items: { type: 'string' },
517
+ description: 'Different parent for each duplicate'
518
+ }
519
+ }
520
+ }
521
+ },
522
+ required: ['instancePath', 'count']
523
+ },
524
+ description: 'Array of duplication operations'
525
+ }
526
+ },
527
+ required: ['duplications']
528
+ }
529
+ },
530
+ // Calculated Property Tools
531
+ {
532
+ name: 'set_calculated_property',
533
+ description: 'Set properties using mathematical formulas and variables',
534
+ inputSchema: {
535
+ type: 'object',
536
+ properties: {
537
+ paths: {
538
+ type: 'array',
539
+ items: { type: 'string' },
540
+ description: 'Array of instance paths to modify'
541
+ },
542
+ propertyName: {
543
+ type: 'string',
544
+ description: 'Name of the property to set'
545
+ },
546
+ formula: {
547
+ type: 'string',
548
+ description: 'Mathematical formula (e.g., "Position.magnitude * 2", "index * 50")'
549
+ },
550
+ variables: {
551
+ type: 'object',
552
+ description: 'Additional variables for the formula'
553
+ }
554
+ },
555
+ required: ['paths', 'propertyName', 'formula']
556
+ }
557
+ },
558
+ // Relative Property Tools
559
+ {
560
+ name: 'set_relative_property',
561
+ description: 'Modify properties relative to their current values',
562
+ inputSchema: {
563
+ type: 'object',
564
+ properties: {
565
+ paths: {
566
+ type: 'array',
567
+ items: { type: 'string' },
568
+ description: 'Array of instance paths to modify'
569
+ },
570
+ propertyName: {
571
+ type: 'string',
572
+ description: 'Name of the property to modify'
573
+ },
574
+ operation: {
575
+ type: 'string',
576
+ enum: ['add', 'multiply', 'divide', 'subtract', 'power'],
577
+ description: 'Mathematical operation to perform'
578
+ },
579
+ value: {
580
+ description: 'Value to use in the operation'
581
+ },
582
+ component: {
583
+ type: 'string',
584
+ enum: ['X', 'Y', 'Z'],
585
+ description: 'Specific component for Vector3/UDim2 properties'
586
+ }
587
+ },
588
+ required: ['paths', 'propertyName', 'operation', 'value']
589
+ }
590
+ },
591
+ // Script Management Tools (for Roblox Studio scripts - NOT local files)
592
+ {
593
+ name: 'get_script_source',
594
+ description: 'Get the source code of a Roblox script (LocalScript, Script, or ModuleScript). Returns both "source" (raw code) and "numberedSource" (with line numbers prefixed like "1: code"). Use numberedSource to accurately identify line numbers for editing. For large scripts (>1500 lines), use startLine/endLine to read specific sections.',
595
+ inputSchema: {
596
+ type: 'object',
597
+ properties: {
598
+ instancePath: {
599
+ type: 'string',
600
+ description: 'Roblox instance path to the script using dot notation (e.g., "game.ServerScriptService.MainScript", "game.StarterPlayer.StarterPlayerScripts.LocalScript")'
601
+ },
602
+ startLine: {
603
+ type: 'number',
604
+ description: 'Optional: Start line number (1-indexed). Use for reading specific sections of large scripts.'
605
+ },
606
+ endLine: {
607
+ type: 'number',
608
+ description: 'Optional: End line number (inclusive). Use for reading specific sections of large scripts.'
609
+ }
610
+ },
611
+ required: ['instancePath']
612
+ }
613
+ },
614
+ {
615
+ name: 'set_script_source',
616
+ description: 'Replace the entire source code of a Roblox script. Uses ScriptEditorService:UpdateSourceAsync (works with open editors). For partial edits, prefer edit_script_lines, insert_script_lines, or delete_script_lines.',
617
+ inputSchema: {
618
+ type: 'object',
619
+ properties: {
620
+ instancePath: {
621
+ type: 'string',
622
+ description: 'Roblox instance path to the script (e.g., "game.ServerScriptService.MainScript")'
623
+ },
624
+ source: {
625
+ type: 'string',
626
+ description: 'New source code for the script'
627
+ }
628
+ },
629
+ required: ['instancePath', 'source']
630
+ }
631
+ },
632
+ // Partial Script Editing Tools - use "numberedSource" from get_script_source to identify correct line numbers
633
+ {
634
+ name: 'edit_script_lines',
635
+ description: 'Replace specific lines in a Roblox script without rewriting the entire source. IMPORTANT: Use the "numberedSource" field from get_script_source to identify the correct line numbers. Lines are 1-indexed and ranges are inclusive.',
636
+ inputSchema: {
637
+ type: 'object',
638
+ properties: {
639
+ instancePath: {
640
+ type: 'string',
641
+ description: 'Roblox instance path to the script (e.g., "game.ServerScriptService.MainScript")'
642
+ },
643
+ startLine: {
644
+ type: 'number',
645
+ description: 'First line to replace (1-indexed). Get this from the "numberedSource" field.'
646
+ },
647
+ endLine: {
648
+ type: 'number',
649
+ description: 'Last line to replace (inclusive). Get this from the "numberedSource" field.'
650
+ },
651
+ newContent: {
652
+ type: 'string',
653
+ description: 'New content to replace the specified lines (can be multiple lines separated by newlines)'
654
+ }
655
+ },
656
+ required: ['instancePath', 'startLine', 'endLine', 'newContent']
657
+ }
658
+ },
659
+ {
660
+ name: 'insert_script_lines',
661
+ description: 'Insert new lines into a Roblox script at a specific position. IMPORTANT: Use the "numberedSource" field from get_script_source to identify the correct line numbers.',
662
+ inputSchema: {
663
+ type: 'object',
664
+ properties: {
665
+ instancePath: {
666
+ type: 'string',
667
+ description: 'Roblox instance path to the script (e.g., "game.ServerScriptService.MainScript")'
668
+ },
669
+ afterLine: {
670
+ type: 'number',
671
+ description: 'Insert after this line number (0 = insert at very beginning, 1 = after first line). Get line numbers from "numberedSource".',
672
+ default: 0
673
+ },
674
+ newContent: {
675
+ type: 'string',
676
+ description: 'Content to insert (can be multiple lines separated by newlines)'
677
+ }
678
+ },
679
+ required: ['instancePath', 'newContent']
680
+ }
681
+ },
682
+ {
683
+ name: 'delete_script_lines',
684
+ description: 'Delete specific lines from a Roblox script. IMPORTANT: Use the "numberedSource" field from get_script_source to identify the correct line numbers.',
685
+ inputSchema: {
686
+ type: 'object',
687
+ properties: {
688
+ instancePath: {
689
+ type: 'string',
690
+ description: 'Roblox instance path to the script (e.g., "game.ServerScriptService.MainScript")'
691
+ },
692
+ startLine: {
693
+ type: 'number',
694
+ description: 'First line to delete (1-indexed). Get this from the "numberedSource" field.'
695
+ },
696
+ endLine: {
697
+ type: 'number',
698
+ description: 'Last line to delete (inclusive). Get this from the "numberedSource" field.'
699
+ }
700
+ },
701
+ required: ['instancePath', 'startLine', 'endLine']
702
+ }
703
+ },
704
+ // Attribute Tools (for Roblox instance attributes)
705
+ {
706
+ name: 'get_attribute',
707
+ description: 'Get a single attribute value from a Roblox instance',
708
+ inputSchema: {
709
+ type: 'object',
710
+ properties: {
711
+ instancePath: {
712
+ type: 'string',
713
+ description: 'Roblox instance path using dot notation (e.g., "game.Workspace.Part", "game.ServerStorage.DataStore")'
714
+ },
715
+ attributeName: {
716
+ type: 'string',
717
+ description: 'Name of the attribute to get'
718
+ }
719
+ },
720
+ required: ['instancePath', 'attributeName']
721
+ }
722
+ },
723
+ {
724
+ name: 'set_attribute',
725
+ description: 'Set an attribute value on a Roblox instance. Supports string, number, boolean, Vector3, Color3, UDim2, and BrickColor.',
726
+ inputSchema: {
727
+ type: 'object',
728
+ properties: {
729
+ instancePath: {
730
+ type: 'string',
731
+ description: 'Roblox instance path using dot notation (e.g., "game.Workspace.Part")'
732
+ },
733
+ attributeName: {
734
+ type: 'string',
735
+ description: 'Name of the attribute to set'
736
+ },
737
+ attributeValue: {
738
+ description: 'Value to set. For Vector3: {X, Y, Z}, Color3: {R, G, B}, UDim2: {X: {Scale, Offset}, Y: {Scale, Offset}}'
739
+ },
740
+ valueType: {
741
+ type: 'string',
742
+ description: 'Optional type hint: "Vector3", "Color3", "UDim2", "BrickColor"'
743
+ }
744
+ },
745
+ required: ['instancePath', 'attributeName', 'attributeValue']
746
+ }
747
+ },
748
+ {
749
+ name: 'get_attributes',
750
+ description: 'Get all attributes on a Roblox instance',
751
+ inputSchema: {
752
+ type: 'object',
753
+ properties: {
754
+ instancePath: {
755
+ type: 'string',
756
+ description: 'Roblox instance path using dot notation (e.g., "game.Workspace.Part")'
757
+ }
758
+ },
759
+ required: ['instancePath']
760
+ }
761
+ },
762
+ {
763
+ name: 'delete_attribute',
764
+ description: 'Delete an attribute from a Roblox instance',
765
+ inputSchema: {
766
+ type: 'object',
767
+ properties: {
768
+ instancePath: {
769
+ type: 'string',
770
+ description: 'Roblox instance path using dot notation (e.g., "game.Workspace.Part")'
771
+ },
772
+ attributeName: {
773
+ type: 'string',
774
+ description: 'Name of the attribute to delete'
775
+ }
776
+ },
777
+ required: ['instancePath', 'attributeName']
778
+ }
779
+ },
780
+ // Tag Tools (CollectionService) - for Roblox instance tags
781
+ {
782
+ name: 'get_tags',
783
+ description: 'Get all CollectionService tags on a Roblox instance',
784
+ inputSchema: {
785
+ type: 'object',
786
+ properties: {
787
+ instancePath: {
788
+ type: 'string',
789
+ description: 'Roblox instance path using dot notation (e.g., "game.Workspace.Part")'
790
+ }
791
+ },
792
+ required: ['instancePath']
793
+ }
794
+ },
795
+ {
796
+ name: 'add_tag',
797
+ description: 'Add a CollectionService tag to a Roblox instance',
798
+ inputSchema: {
799
+ type: 'object',
800
+ properties: {
801
+ instancePath: {
802
+ type: 'string',
803
+ description: 'Roblox instance path using dot notation (e.g., "game.Workspace.Part")'
804
+ },
805
+ tagName: {
806
+ type: 'string',
807
+ description: 'Name of the tag to add'
808
+ }
809
+ },
810
+ required: ['instancePath', 'tagName']
811
+ }
812
+ },
813
+ {
814
+ name: 'remove_tag',
815
+ description: 'Remove a CollectionService tag from a Roblox instance',
816
+ inputSchema: {
817
+ type: 'object',
818
+ properties: {
819
+ instancePath: {
820
+ type: 'string',
821
+ description: 'Roblox instance path using dot notation (e.g., "game.Workspace.Part")'
822
+ },
823
+ tagName: {
824
+ type: 'string',
825
+ description: 'Name of the tag to remove'
826
+ }
827
+ },
828
+ required: ['instancePath', 'tagName']
829
+ }
830
+ },
831
+ {
832
+ name: 'get_tagged',
833
+ description: 'Get all instances with a specific tag',
834
+ inputSchema: {
835
+ type: 'object',
836
+ properties: {
837
+ tagName: {
838
+ type: 'string',
839
+ description: 'Name of the tag to search for'
840
+ }
841
+ },
842
+ required: ['tagName']
843
+ }
844
+ },
845
+ {
846
+ name: 'get_selection',
847
+ description: 'Get all currently selected objects',
848
+ inputSchema: {
849
+ type: 'object',
850
+ properties: {}
851
+ }
852
+ },
853
+ // ============================================
854
+ // OUTPUT CAPTURE TOOL
855
+ // ============================================
856
+ {
857
+ name: 'get_output',
858
+ description: 'Read the Output window content from Roblox Studio. Captures print(), warn(), and error() messages. Use after play_solo to debug scripts.',
859
+ inputSchema: {
860
+ type: 'object',
861
+ properties: {
862
+ limit: {
863
+ type: 'number',
864
+ description: 'Maximum number of messages to return (default: 100)',
865
+ default: 100
866
+ },
867
+ since: {
868
+ type: 'number',
869
+ description: 'Only return messages after this Unix timestamp'
870
+ },
871
+ messageTypes: {
872
+ type: 'array',
873
+ items: { type: 'string' },
874
+ description: 'Filter by message type: MessageOutput, MessageInfo, MessageWarning, MessageError'
875
+ },
876
+ clear: {
877
+ type: 'boolean',
878
+ description: 'Clear the output buffer after reading (default: false)',
879
+ default: false
880
+ }
881
+ }
882
+ }
883
+ },
884
+ // ============================================
885
+ // INSTANCE MANIPULATION TOOLS
886
+ // ============================================
887
+ {
888
+ name: 'clone_instance',
889
+ description: 'Clone (copy) a Roblox instance to a new parent location. Creates a deep copy including all children and properties.',
890
+ inputSchema: {
891
+ type: 'object',
892
+ properties: {
893
+ sourcePath: {
894
+ type: 'string',
895
+ description: 'Path to the instance to clone (e.g., "game.Workspace.walkietalkie")'
896
+ },
897
+ targetParent: {
898
+ type: 'string',
899
+ description: 'Path to the new parent (e.g., "game.ReplicatedStorage")'
900
+ },
901
+ newName: {
902
+ type: 'string',
903
+ description: 'Optional new name for the cloned instance'
904
+ }
905
+ },
906
+ required: ['sourcePath', 'targetParent']
907
+ }
908
+ },
909
+ {
910
+ name: 'move_instance',
911
+ description: 'Move a Roblox instance to a new parent location. Changes the Parent property.',
912
+ inputSchema: {
913
+ type: 'object',
914
+ properties: {
915
+ instancePath: {
916
+ type: 'string',
917
+ description: 'Path to the instance to move (e.g., "game.Workspace.Tool")'
918
+ },
919
+ newParent: {
920
+ type: 'string',
921
+ description: 'Path to the new parent (e.g., "game.StarterPack")'
922
+ }
923
+ },
924
+ required: ['instancePath', 'newParent']
925
+ }
926
+ },
927
+ // ============================================
928
+ // SCRIPT VALIDATION TOOL
929
+ // ============================================
930
+ {
931
+ name: 'validate_script',
932
+ description: 'Validate Lua/Luau script syntax without running it. Returns syntax errors and warnings for deprecated patterns (wait, spawn, delay). Can validate either an existing script or raw source code.',
933
+ inputSchema: {
934
+ type: 'object',
935
+ properties: {
936
+ instancePath: {
937
+ type: 'string',
938
+ description: 'Path to the script to validate (e.g., "game.ServerScriptService.MainScript")'
939
+ },
940
+ source: {
941
+ type: 'string',
942
+ description: 'Raw Lua source code to validate (alternative to instancePath)'
943
+ }
944
+ }
945
+ }
946
+ }
947
+ ]
948
+ };
949
+ });
950
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
951
+ const { name, arguments: args } = request.params;
952
+ try {
953
+ switch (name) {
954
+ // File System Tools
955
+ case 'get_file_tree':
956
+ return await this.tools.getFileTree(args?.path || '');
957
+ case 'search_files':
958
+ return await this.tools.searchFiles(args?.query, args?.searchType || 'name');
959
+ // Studio Context Tools
960
+ case 'get_place_info':
961
+ return await this.tools.getPlaceInfo();
962
+ case 'get_services':
963
+ return await this.tools.getServices(args?.serviceName);
964
+ case 'search_objects':
965
+ return await this.tools.searchObjects(args?.query, args?.searchType || 'name', args?.propertyName);
966
+ // Property & Instance Tools
967
+ case 'get_instance_properties':
968
+ return await this.tools.getInstanceProperties(args?.instancePath);
969
+ case 'get_instance_children':
970
+ return await this.tools.getInstanceChildren(args?.instancePath);
971
+ case 'search_by_property':
972
+ return await this.tools.searchByProperty(args?.propertyName, args?.propertyValue);
973
+ case 'get_class_info':
974
+ return await this.tools.getClassInfo(args?.className);
975
+ // Project Tools
976
+ case 'get_project_structure':
977
+ return await this.tools.getProjectStructure(args?.path, args?.maxDepth, args?.scriptsOnly);
978
+ // Property Modification Tools
979
+ case 'set_property':
980
+ return await this.tools.setProperty(args?.instancePath, args?.propertyName, args?.propertyValue);
981
+ // Mass Property Tools
982
+ case 'mass_set_property':
983
+ return await this.tools.massSetProperty(args?.paths, args?.propertyName, args?.propertyValue);
984
+ case 'mass_get_property':
985
+ return await this.tools.massGetProperty(args?.paths, args?.propertyName);
986
+ // Object Creation/Deletion Tools
987
+ case 'create_object':
988
+ return await this.tools.createObject(args?.className, args?.parent, args?.name);
989
+ case 'create_object_with_properties':
990
+ return await this.tools.createObjectWithProperties(args?.className, args?.parent, args?.name, args?.properties);
991
+ case 'mass_create_objects':
992
+ return await this.tools.massCreateObjects(args?.objects);
993
+ case 'mass_create_objects_with_properties':
994
+ return await this.tools.massCreateObjectsWithProperties(args?.objects);
995
+ case 'delete_object':
996
+ return await this.tools.deleteObject(args?.instancePath);
997
+ // Smart Duplication Tools
998
+ case 'smart_duplicate':
999
+ return await this.tools.smartDuplicate(args?.instancePath, args?.count, args?.options);
1000
+ case 'mass_duplicate':
1001
+ return await this.tools.massDuplicate(args?.duplications);
1002
+ // Calculated Property Tools
1003
+ case 'set_calculated_property':
1004
+ return await this.tools.setCalculatedProperty(args?.paths, args?.propertyName, args?.formula, args?.variables);
1005
+ // Relative Property Tools
1006
+ case 'set_relative_property':
1007
+ return await this.tools.setRelativeProperty(args?.paths, args?.propertyName, args?.operation, args?.value, args?.component);
1008
+ // Script Management Tools
1009
+ case 'get_script_source':
1010
+ return await this.tools.getScriptSource(args?.instancePath, args?.startLine, args?.endLine);
1011
+ case 'set_script_source':
1012
+ return await this.tools.setScriptSource(args?.instancePath, args?.source);
1013
+ // Partial Script Editing Tools
1014
+ case 'edit_script_lines':
1015
+ return await this.tools.editScriptLines(args?.instancePath, args?.startLine, args?.endLine, args?.newContent);
1016
+ case 'insert_script_lines':
1017
+ return await this.tools.insertScriptLines(args?.instancePath, args?.afterLine, args?.newContent);
1018
+ case 'delete_script_lines':
1019
+ return await this.tools.deleteScriptLines(args?.instancePath, args?.startLine, args?.endLine);
1020
+ // Attribute Tools
1021
+ case 'get_attribute':
1022
+ return await this.tools.getAttribute(args?.instancePath, args?.attributeName);
1023
+ case 'set_attribute':
1024
+ return await this.tools.setAttribute(args?.instancePath, args?.attributeName, args?.attributeValue, args?.valueType);
1025
+ case 'get_attributes':
1026
+ return await this.tools.getAttributes(args?.instancePath);
1027
+ case 'delete_attribute':
1028
+ return await this.tools.deleteAttribute(args?.instancePath, args?.attributeName);
1029
+ // Tag Tools (CollectionService)
1030
+ case 'get_tags':
1031
+ return await this.tools.getTags(args?.instancePath);
1032
+ case 'add_tag':
1033
+ return await this.tools.addTag(args?.instancePath, args?.tagName);
1034
+ case 'remove_tag':
1035
+ return await this.tools.removeTag(args?.instancePath, args?.tagName);
1036
+ case 'get_tagged':
1037
+ return await this.tools.getTagged(args?.tagName);
1038
+ // Selection Tools
1039
+ case 'get_selection':
1040
+ return await this.tools.getSelection();
1041
+ // Output Capture Tool
1042
+ case 'get_output':
1043
+ return await this.tools.getOutput(args?.limit, args?.since, args?.messageTypes, args?.clear);
1044
+ // Instance Manipulation Tools
1045
+ case 'clone_instance':
1046
+ return await this.tools.cloneInstance(args?.sourcePath, args?.targetParent, args?.newName);
1047
+ case 'move_instance':
1048
+ return await this.tools.moveInstance(args?.instancePath, args?.newParent);
1049
+ // Script Validation Tool
1050
+ case 'validate_script':
1051
+ return await this.tools.validateScript(args?.instancePath, args?.source);
1052
+ default:
1053
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
1054
+ }
1055
+ }
1056
+ catch (error) {
1057
+ throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`);
1058
+ }
1059
+ });
1060
+ }
1061
+ async run() {
1062
+ const port = process.env.ROBLOX_STUDIO_PORT ? parseInt(process.env.ROBLOX_STUDIO_PORT) : 3002;
1063
+ const host = process.env.ROBLOX_STUDIO_HOST || '0.0.0.0';
1064
+ const httpServer = createHttpServer(this.tools, this.bridge);
1065
+ await new Promise((resolve) => {
1066
+ httpServer.listen(port, host, () => {
1067
+ console.error(`HTTP server listening on ${host}:${port} for Studio plugin`);
1068
+ resolve();
1069
+ });
1070
+ });
1071
+ const transport = new StdioServerTransport();
1072
+ await this.server.connect(transport);
1073
+ console.error('Roblox Studio MCP server running on stdio');
1074
+ httpServer.setMCPServerActive(true);
1075
+ console.error('MCP server marked as active');
1076
+ console.error('Waiting for Studio plugin to connect...');
1077
+ setInterval(() => {
1078
+ const pluginConnected = httpServer.isPluginConnected();
1079
+ const mcpActive = httpServer.isMCPServerActive();
1080
+ if (pluginConnected && mcpActive) {
1081
+ }
1082
+ else if (pluginConnected && !mcpActive) {
1083
+ console.error('Studio plugin connected, but MCP server inactive');
1084
+ }
1085
+ else if (!pluginConnected && mcpActive) {
1086
+ console.error('MCP server active, waiting for Studio plugin...');
1087
+ }
1088
+ else {
1089
+ console.error('Waiting for connections...');
1090
+ }
1091
+ }, 5000);
1092
+ setInterval(() => {
1093
+ this.bridge.cleanupOldRequests();
1094
+ }, 5000);
1095
+ }
1096
+ }
1097
+ const server = new RobloxStudioMCPServer();
1098
+ server.run().catch((error) => {
1099
+ console.error('Server failed to start:', error);
1100
+ process.exit(1);
1101
+ });
1102
+ //# sourceMappingURL=index.js.map