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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,319 @@
1
+ # Roblox Studio MCP Server
2
+
3
+ MCP server for AI-powered Roblox Studio integration. 22 specialized tools for exploring projects, analyzing scripts, and performing bulk operations.
4
+
5
+ https://devforum.roblox.com/t/v180-roblox-studio-mcp-speed-up-your-workflow-by-letting-ai-read-paths-and-properties/3707071
6
+
7
+ <a href="https://glama.ai/mcp/servers/@boshyxd/robloxstudio-mcp">
8
+ <img width="380" height="200" src="https://glama.ai/mcp/servers/@boshyxd/robloxstudio-mcp/badge" alt="Roblox Studio Server MCP server" />
9
+ </a>
10
+
11
+ ## Quick Start
12
+
13
+ **For Claude Code users:**
14
+ ```bash
15
+ claude mcp add robloxstudio -- npx -y robloxstudio-mcp
16
+ ```
17
+
18
+ **For other MCP clients (Claude Desktop, etc.):**
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "robloxstudio-mcp": {
23
+ "command": "npx",
24
+ "args": ["-y", "robloxstudio-mcp"],
25
+ "description": "Advanced Roblox Studio integration for AI assistants"
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ **Install NPM Package**
32
+ ```bash
33
+ npm i robloxstudio-mcp
34
+ ```
35
+
36
+ <details>
37
+ <summary>Note for native Windows users</summary>
38
+ If you encounter issues, you may need to run it through `cmd`. Update your configuration like this:
39
+
40
+ ```json
41
+ {
42
+ "mcpServers": {
43
+ "robloxstudio-mcp": {
44
+ "command": "cmd",
45
+ "args": ["/c", "npx", "-y", "robloxstudio-mcp@latest"]
46
+ }
47
+ }
48
+ }
49
+ ```
50
+ </details>
51
+
52
+ ## Studio Plugin Setup (Required)
53
+
54
+ The MCP server requires a companion Roblox Studio plugin:
55
+
56
+ - Download [MCPPlugin.rbxmx](https://github.com/boshyxd/robloxstudio-mcp/releases/latest/download/MCPPlugin.rbxmx)
57
+ - Save to your `%LOCALAPPDATA%/Roblox/Plugins` folder
58
+
59
+ **After installation:**
60
+ - Enable "Allow HTTP Requests" in Game Settings > Security
61
+ - Click the "MCP Server" button in the Plugins toolbar
62
+ - Status should show "Connected" when working
63
+
64
+ ## Architecture Overview
65
+
66
+ Dual-component system bridging Roblox Studio with AI assistants:
67
+
68
+ ```mermaid
69
+ %%{init: {'theme':'dark', 'themeVariables': {'primaryColor':'#2d3748', 'primaryTextColor':'#ffffff', 'primaryBorderColor':'#4a5568', 'lineColor':'#718096', 'sectionBkgColor':'#1a202c', 'altSectionBkgColor':'#2d3748', 'gridColor':'#4a5568', 'secondaryColor':'#2b6cb0', 'tertiaryColor':'#319795'}}}%%
70
+ graph TB
71
+ subgraph AI_ENV ["AI Environment"]
72
+ AI["AI Assistant<br/>Claude Code/Desktop"]
73
+ MCP["MCP Server<br/>Node.js + TypeScript"]
74
+ end
75
+
76
+ subgraph COMM_LAYER ["Communication Layer"]
77
+ HTTP["HTTP Bridge<br/>localhost:3002"]
78
+ QUEUE["Request Queue<br/>UUID tracking"]
79
+ end
80
+
81
+ subgraph STUDIO_ENV ["Roblox Studio Environment"]
82
+ PLUGIN["Studio Plugin<br/>Luau Script"]
83
+ STUDIO["Roblox Studio<br/>APIs & Data"]
84
+ end
85
+
86
+ subgraph TOOLS ["22 AI Tools"]
87
+ FILE["File System<br/>Trees, Search"]
88
+ CONTEXT["Studio Context<br/>Services, Objects"]
89
+ PROPS["Properties<br/>Get, Set, Mass Ops"]
90
+ CREATE["Object Creation<br/>Single, Mass, Properties"]
91
+ PROJECT["Project Analysis<br/>Smart Structure"]
92
+ OUTPUT["Output Capture<br/>Logs, Errors"]
93
+ INSTMANIP["Instance Ops<br/>Clone, Move"]
94
+ VALIDATE["Script Validation<br/>Syntax Check"]
95
+ end
96
+
97
+ AI -->|stdio| MCP
98
+ MCP -->|HTTP POST| HTTP
99
+ HTTP -->|Queue Request| QUEUE
100
+ PLUGIN -->|Poll every 500ms| HTTP
101
+ HTTP -->|Pending Work| PLUGIN
102
+ PLUGIN -->|Execute APIs| STUDIO
103
+ STUDIO -->|Return Data| PLUGIN
104
+ PLUGIN -->|HTTP Response| HTTP
105
+ HTTP -->|Resolve Promise| MCP
106
+ MCP -->|Tool Result| AI
107
+
108
+ MCP -.->|Exposes| FILE
109
+ MCP -.->|Exposes| CONTEXT
110
+ MCP -.->|Exposes| PROPS
111
+ MCP -.->|Exposes| CREATE
112
+ MCP -.->|Exposes| PROJECT
113
+ MCP -.->|Exposes| OUTPUT
114
+ MCP -.->|Exposes| INSTMANIP
115
+ MCP -.->|Exposes| VALIDATE
116
+
117
+ classDef aiStyle fill:#1e40af,stroke:#3b82f6,stroke-width:2px,color:#ffffff
118
+ classDef mcpStyle fill:#7c3aed,stroke:#8b5cf6,stroke-width:2px,color:#ffffff
119
+ classDef httpStyle fill:#ea580c,stroke:#f97316,stroke-width:2px,color:#ffffff
120
+ classDef pluginStyle fill:#059669,stroke:#10b981,stroke-width:2px,color:#ffffff
121
+ classDef studioStyle fill:#dc2626,stroke:#ef4444,stroke-width:2px,color:#ffffff
122
+ classDef toolStyle fill:#0891b2,stroke:#06b6d4,stroke-width:2px,color:#ffffff
123
+
124
+ class AI aiStyle
125
+ class MCP mcpStyle
126
+ class HTTP,QUEUE httpStyle
127
+ class PLUGIN pluginStyle
128
+ class STUDIO studioStyle
129
+ class FILE,CONTEXT,PROPS,CREATE,PROJECT,OUTPUT,INSTMANIP,VALIDATE toolStyle
130
+ ```
131
+
132
+ ### Key Components:
133
+ - MCP Server (Node.js/TypeScript) - Exposes 22 tools via stdio
134
+ - HTTP Bridge - Request/response queue on localhost:3002
135
+ - Studio Plugin (Luau) - Polls server and executes API calls
136
+ - Smart Caching - Efficient data transfer
137
+
138
+ ## 22 AI Tools
139
+
140
+ ### File System Tools
141
+ - `get_file_tree` - Complete project hierarchy with scripts, models, folders
142
+ - `search_files` - Find files by name, type, or content patterns
143
+
144
+ ### Studio Context Tools
145
+ - `get_place_info` - Place ID, name, game settings, workspace info
146
+ - `get_services` - All Roblox services and their child counts
147
+ - `search_objects` - Find instances by name, class, or properties
148
+
149
+ ### Instance & Property Tools
150
+ - `get_instance_properties` - Complete property dump for any object
151
+ - `get_instance_children` - Child objects with metadata
152
+ - `search_by_property` - Find objects with specific property values
153
+ - `get_class_info` - Available properties/methods for Roblox classes
154
+
155
+ ### Property Modification Tools
156
+ - `set_property` - Set a property on any Roblox instance
157
+ - `mass_set_property` - Set the same property on multiple instances
158
+ - `mass_get_property` - Get the same property from multiple instances
159
+
160
+ ### Object Creation Tools
161
+ - `create_object` - Create a new Roblox object instance
162
+ - `create_object_with_properties` - Create objects with initial properties
163
+ - `mass_create_objects` - Create multiple objects at once
164
+ - `mass_create_objects_with_properties` - Create multiple objects with properties
165
+ - `delete_object` - Delete a Roblox object instance
166
+
167
+ ### Project Analysis Tools
168
+ - `get_project_structure` - Smart hierarchy with depth control (recommended: 5-10)
169
+
170
+ ### Output Capture Tool (NEW in v1.9.0)
171
+ - `get_output` - Read Output window content (print, warn, error messages)
172
+
173
+ ### Instance Manipulation Tools (NEW in v1.9.0)
174
+ - `clone_instance` - Clone/copy an instance to a new parent (deep copy)
175
+ - `move_instance` - Move an instance to a new parent location
176
+
177
+ ### Script Validation Tool (NEW in v1.9.0)
178
+ - `validate_script` - Validate Lua/Luau syntax without running, includes deprecation warnings
179
+
180
+ > Note: Previous tools removed: `get_file_content`, `get_file_properties`, `get_selection`, `get_dependencies`, `validate_references`. Use Rojo/Argon workflows instead.
181
+
182
+ ## AI-Optimized Features
183
+
184
+ ### Mass Operations (v1.3.0)
185
+ - Bulk property editing
186
+ - Mass object creation
187
+ - Batch property reading
188
+ - Atomic undo/redo operations
189
+
190
+ ```typescript
191
+ // Example: Set multiple parts to red
192
+ mass_set_property(["game.Workspace.Part1", "game.Workspace.Part2"], "BrickColor", "Really red")
193
+ ```
194
+
195
+ ### Smart Project Structure
196
+ - Service overview with child counts
197
+ - Path-based exploration: `get_project_structure("game.ServerStorage", maxDepth=5)`
198
+ - Script-only filtering for code analysis
199
+ - Intelligent grouping for large folders
200
+ - Recommended maxDepth=5-10
201
+
202
+ ### Rich Metadata
203
+ - Script status tracking
204
+ - GUI intelligence
205
+ - Performance optimized
206
+
207
+ ## Development & Testing
208
+
209
+ ### Commands
210
+ ```bash
211
+ npm run dev # Development server with hot reload
212
+ npm run build # Production build
213
+ npm start # Run built server
214
+ npm run lint # ESLint code quality
215
+ npm run typecheck # TypeScript validation
216
+ ```
217
+
218
+ ### Plugin Development
219
+ - Live reload
220
+ - Robust error handling
221
+ - Debug logging
222
+ - Visual status indicators
223
+
224
+ ## Communication Protocol
225
+
226
+ ```mermaid
227
+ %%{init: {'theme':'dark', 'themeVariables': {'primaryColor':'#2d3748', 'primaryTextColor':'#ffffff', 'primaryBorderColor':'#4a5568', 'lineColor':'#10b981', 'sectionBkgColor':'#1a202c', 'altSectionBkgColor':'#2d3748', 'gridColor':'#4a5568', 'secondaryColor':'#3b82f6', 'tertiaryColor':'#8b5cf6', 'background':'#1a202c', 'mainBkg':'#2d3748', 'secondBkg':'#374151', 'tertiaryColor':'#6366f1'}}}%%
228
+ sequenceDiagram
229
+ participant AI as AI Assistant
230
+ participant MCP as MCP Server
231
+ participant HTTP as HTTP Bridge
232
+ participant PLUGIN as Studio Plugin
233
+ participant STUDIO as Roblox Studio
234
+
235
+ Note over AI,STUDIO: Tool Request Flow
236
+
237
+ AI->>+MCP: Call tool (e.g., get_file_tree)
238
+ MCP->>+HTTP: Queue request with UUID
239
+ HTTP->>HTTP: Store in pending requests map
240
+ HTTP-->>-MCP: Request queued
241
+
242
+ Note over PLUGIN: Polling every 500ms
243
+ PLUGIN->>+HTTP: GET /poll
244
+ HTTP->>-PLUGIN: Return pending request + UUID
245
+
246
+ PLUGIN->>+STUDIO: Execute Studio APIs
247
+ Note over STUDIO: game.ServerStorage<br/>Selection:Get()<br/>Instance properties
248
+ STUDIO->>-PLUGIN: Return Studio data
249
+
250
+ PLUGIN->>+HTTP: POST /response with UUID + data
251
+ HTTP->>-MCP: Resolve promise with data
252
+ MCP->>-AI: Return tool result
253
+
254
+ Note over AI,STUDIO: Error Handling
255
+
256
+ alt Request Timeout (30s)
257
+ HTTP->>MCP: Reject promise with timeout
258
+ MCP->>AI: Return error message
259
+ end
260
+
261
+ alt Plugin Disconnected
262
+ PLUGIN->>HTTP: Connection lost
263
+ HTTP->>HTTP: Exponential backoff retry
264
+ Note over PLUGIN: Status: "Waiting for server..."
265
+ end
266
+ ```
267
+
268
+ **Features:**
269
+ - 30-second timeouts with exponential backoff
270
+ - Automatic retries
271
+ - Response limiting
272
+ - Request deduplication
273
+
274
+ ## Example Usage
275
+
276
+ ```javascript
277
+ // Get service overview
278
+ get_project_structure()
279
+
280
+ // Explore weapons folder
281
+ get_project_structure("game.ServerStorage.Weapons", maxDepth=2)
282
+
283
+ // Find all Sound objects
284
+ search_by_property("ClassName", "Sound")
285
+
286
+ // Get UI component details
287
+ get_instance_properties("game.StarterGui.MainMenu.SettingsFrame")
288
+
289
+ // === NEW in v1.9.0 ===
290
+
291
+ // Clone an object to a different location
292
+ clone_instance("game.Workspace.walkietalkie", "game.ReplicatedStorage")
293
+
294
+ // Move a tool to StarterPack
295
+ move_instance("game.Workspace.Sword", "game.StarterPack")
296
+
297
+ // Validate a script before running
298
+ validate_script("game.ServerScriptService.MainScript")
299
+
300
+ // Monitor game output after testing
301
+ get_output({ limit: 50, messageTypes: ["MessageError", "MessageWarning"] })
302
+ ```
303
+
304
+ ## Configuration
305
+
306
+ **Environment Variables:**
307
+ - `MCP_SERVER_PORT` - MCP server port (default: stdio)
308
+ - `HTTP_SERVER_PORT` - HTTP bridge port (default: 3002)
309
+ - `PLUGIN_POLL_INTERVAL` - Plugin poll frequency (default: 500ms)
310
+ - `REQUEST_TIMEOUT` - Request timeout (default: 30000ms)
311
+
312
+ **Studio Settings:**
313
+ - **Allow HTTP Requests** (Game Settings > Security)
314
+ - **HttpService.HttpEnabled = true**
315
+ - **Plugin activated** via toolbar button
316
+
317
+ ## License
318
+
319
+ MIT License - Feel free to use in commercial and personal projects!
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bridge-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-service.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/bridge-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,109 @@
1
+ import { BridgeService } from '../bridge-service';
2
+ describe('BridgeService', () => {
3
+ let bridgeService;
4
+ beforeEach(() => {
5
+ bridgeService = new BridgeService();
6
+ jest.useFakeTimers();
7
+ });
8
+ afterEach(() => {
9
+ jest.useRealTimers();
10
+ });
11
+ describe('Request Management', () => {
12
+ test('should create and store a pending request', async () => {
13
+ const endpoint = '/api/test';
14
+ const data = { test: 'data' };
15
+ const requestPromise = bridgeService.sendRequest(endpoint, data);
16
+ // Check that request is pending
17
+ const pendingRequest = bridgeService.getPendingRequest();
18
+ expect(pendingRequest).toBeTruthy();
19
+ expect(pendingRequest?.request.endpoint).toBe(endpoint);
20
+ expect(pendingRequest?.request.data).toEqual(data);
21
+ });
22
+ test('should resolve request when response is received', async () => {
23
+ const endpoint = '/api/test';
24
+ const data = { test: 'data' };
25
+ const response = { result: 'success' };
26
+ const requestPromise = bridgeService.sendRequest(endpoint, data);
27
+ const pendingRequest = bridgeService.getPendingRequest();
28
+ // Resolve the request
29
+ bridgeService.resolveRequest(pendingRequest.requestId, response);
30
+ const result = await requestPromise;
31
+ expect(result).toEqual(response);
32
+ });
33
+ test('should reject request on error', async () => {
34
+ const endpoint = '/api/test';
35
+ const data = { test: 'data' };
36
+ const error = 'Test error';
37
+ const requestPromise = bridgeService.sendRequest(endpoint, data);
38
+ const pendingRequest = bridgeService.getPendingRequest();
39
+ // Reject the request
40
+ bridgeService.rejectRequest(pendingRequest.requestId, error);
41
+ await expect(requestPromise).rejects.toEqual(error);
42
+ });
43
+ test('should timeout request after 30 seconds', async () => {
44
+ const endpoint = '/api/test';
45
+ const data = { test: 'data' };
46
+ const requestPromise = bridgeService.sendRequest(endpoint, data);
47
+ // Fast-forward time by 31 seconds
48
+ jest.advanceTimersByTime(31000);
49
+ await expect(requestPromise).rejects.toThrow('Request timeout');
50
+ });
51
+ });
52
+ describe('Cleanup Operations', () => {
53
+ test('should clean up old requests', async () => {
54
+ // Create multiple requests
55
+ const promises = [
56
+ bridgeService.sendRequest('/api/test1', {}),
57
+ bridgeService.sendRequest('/api/test2', {}),
58
+ bridgeService.sendRequest('/api/test3', {})
59
+ ];
60
+ // Fast-forward time by 31 seconds
61
+ jest.advanceTimersByTime(31000);
62
+ // Clean up old requests
63
+ bridgeService.cleanupOldRequests();
64
+ // All requests should be rejected
65
+ for (const promise of promises) {
66
+ await expect(promise).rejects.toThrow('Request timeout');
67
+ }
68
+ // No pending requests should remain
69
+ expect(bridgeService.getPendingRequest()).toBeNull();
70
+ });
71
+ test('should clear all pending requests on disconnect', async () => {
72
+ // Create multiple requests
73
+ const promises = [
74
+ bridgeService.sendRequest('/api/test1', {}),
75
+ bridgeService.sendRequest('/api/test2', {}),
76
+ bridgeService.sendRequest('/api/test3', {})
77
+ ];
78
+ // Clear all requests
79
+ bridgeService.clearAllPendingRequests();
80
+ // All requests should be rejected with connection closed error
81
+ for (const promise of promises) {
82
+ await expect(promise).rejects.toThrow('Connection closed');
83
+ }
84
+ // No pending requests should remain
85
+ expect(bridgeService.getPendingRequest()).toBeNull();
86
+ });
87
+ });
88
+ describe('Request Priority', () => {
89
+ test('should return oldest request first', async () => {
90
+ // Create requests with small delays
91
+ bridgeService.sendRequest('/api/test1', { order: 1 });
92
+ // Small delay to ensure different timestamps
93
+ await new Promise(resolve => setTimeout(resolve, 10));
94
+ bridgeService.sendRequest('/api/test2', { order: 2 });
95
+ await new Promise(resolve => setTimeout(resolve, 10));
96
+ bridgeService.sendRequest('/api/test3', { order: 3 });
97
+ // Should get the first (oldest) request
98
+ const firstRequest = bridgeService.getPendingRequest();
99
+ expect(firstRequest?.request.data.order).toBe(1);
100
+ // Should get the second request next
101
+ const secondRequest = bridgeService.getPendingRequest();
102
+ expect(secondRequest?.request.data.order).toBe(2);
103
+ // Should get the third request last
104
+ const thirdRequest = bridgeService.getPendingRequest();
105
+ expect(thirdRequest?.request.data.order).toBe(3);
106
+ });
107
+ });
108
+ });
109
+ //# sourceMappingURL=bridge-service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-service.test.js","sourceRoot":"","sources":["../../src/__tests__/bridge-service.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=http-server.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/http-server.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,193 @@
1
+ import request from 'supertest';
2
+ import { createHttpServer } from '../http-server';
3
+ import { RobloxStudioTools } from '../tools/index';
4
+ import { BridgeService } from '../bridge-service';
5
+ describe('HTTP Server', () => {
6
+ let app;
7
+ let bridge;
8
+ let tools;
9
+ beforeEach(() => {
10
+ bridge = new BridgeService();
11
+ tools = new RobloxStudioTools(bridge);
12
+ app = createHttpServer(tools, bridge);
13
+ });
14
+ describe('Health Check', () => {
15
+ test('should return health status', async () => {
16
+ const response = await request(app)
17
+ .get('/health')
18
+ .expect(200);
19
+ expect(response.body).toMatchObject({
20
+ status: 'ok',
21
+ service: 'robloxstudio-mcp',
22
+ pluginConnected: false,
23
+ mcpServerActive: false
24
+ });
25
+ });
26
+ });
27
+ describe('Plugin Connection Management', () => {
28
+ test('should handle plugin ready notification', async () => {
29
+ const response = await request(app)
30
+ .post('/ready')
31
+ .expect(200);
32
+ expect(response.body).toEqual({ success: true });
33
+ expect(app.isPluginConnected()).toBe(true);
34
+ });
35
+ test('should handle plugin disconnect', async () => {
36
+ // First connect
37
+ await request(app).post('/ready').expect(200);
38
+ expect(app.isPluginConnected()).toBe(true);
39
+ // Then disconnect
40
+ const response = await request(app)
41
+ .post('/disconnect')
42
+ .expect(200);
43
+ expect(response.body).toEqual({ success: true });
44
+ expect(app.isPluginConnected()).toBe(false);
45
+ });
46
+ test('should clear pending requests on disconnect', async () => {
47
+ // Add some pending requests
48
+ bridge.sendRequest('/api/test1', {});
49
+ bridge.sendRequest('/api/test2', {});
50
+ expect(bridge.getPendingRequest()).toBeTruthy();
51
+ // Disconnect
52
+ await request(app).post('/disconnect').expect(200);
53
+ // All requests should be cleared
54
+ expect(bridge.getPendingRequest()).toBeNull();
55
+ });
56
+ test('should timeout plugin connection after inactivity', async () => {
57
+ // Connect plugin
58
+ await request(app).post('/ready').expect(200);
59
+ expect(app.isPluginConnected()).toBe(true);
60
+ // Simulate time passing (11 seconds of inactivity)
61
+ const originalDateNow = Date.now;
62
+ Date.now = jest.fn(() => originalDateNow() + 11000);
63
+ // Plugin should be considered disconnected
64
+ expect(app.isPluginConnected()).toBe(false);
65
+ // Restore Date.now
66
+ Date.now = originalDateNow;
67
+ });
68
+ });
69
+ describe('Polling Endpoint', () => {
70
+ test('should return 503 when MCP server is not active', async () => {
71
+ const response = await request(app)
72
+ .get('/poll')
73
+ .expect(503);
74
+ expect(response.body).toMatchObject({
75
+ error: 'MCP server not connected',
76
+ pluginConnected: true,
77
+ mcpConnected: false,
78
+ request: null
79
+ });
80
+ });
81
+ test('should return pending request when MCP is active', async () => {
82
+ // Activate MCP server
83
+ app.setMCPServerActive(true);
84
+ // Add a pending request
85
+ bridge.sendRequest('/api/test', { data: 'test' });
86
+ const response = await request(app)
87
+ .get('/poll')
88
+ .expect(200);
89
+ expect(response.body).toMatchObject({
90
+ request: {
91
+ endpoint: '/api/test',
92
+ data: { data: 'test' }
93
+ },
94
+ mcpConnected: true,
95
+ pluginConnected: true
96
+ });
97
+ expect(response.body.requestId).toBeTruthy();
98
+ });
99
+ test('should return null request when no pending requests', async () => {
100
+ // Activate MCP server
101
+ app.setMCPServerActive(true);
102
+ const response = await request(app)
103
+ .get('/poll')
104
+ .expect(200);
105
+ expect(response.body).toMatchObject({
106
+ request: null,
107
+ mcpConnected: true,
108
+ pluginConnected: true
109
+ });
110
+ });
111
+ test('should mark plugin as connected when polling', async () => {
112
+ expect(app.isPluginConnected()).toBe(false);
113
+ await request(app).get('/poll').expect(503);
114
+ expect(app.isPluginConnected()).toBe(true);
115
+ });
116
+ });
117
+ describe('Response Handling', () => {
118
+ test('should handle successful response', async () => {
119
+ const requestId = 'test-request-id';
120
+ const responseData = { result: 'success' };
121
+ // Create a pending request
122
+ const requestPromise = bridge.sendRequest('/api/test', {});
123
+ const pendingRequest = bridge.getPendingRequest();
124
+ // Send response
125
+ const response = await request(app)
126
+ .post('/response')
127
+ .send({
128
+ requestId: pendingRequest.requestId,
129
+ response: responseData
130
+ })
131
+ .expect(200);
132
+ expect(response.body).toEqual({ success: true });
133
+ // Check that the request was resolved
134
+ const result = await requestPromise;
135
+ expect(result).toEqual(responseData);
136
+ });
137
+ test('should handle error response', async () => {
138
+ const error = 'Test error message';
139
+ // Create a pending request
140
+ const requestPromise = bridge.sendRequest('/api/test', {});
141
+ const pendingRequest = bridge.getPendingRequest();
142
+ // Send error response
143
+ const response = await request(app)
144
+ .post('/response')
145
+ .send({
146
+ requestId: pendingRequest.requestId,
147
+ error: error
148
+ })
149
+ .expect(200);
150
+ expect(response.body).toEqual({ success: true });
151
+ // Check that the request was rejected
152
+ await expect(requestPromise).rejects.toEqual(error);
153
+ });
154
+ });
155
+ describe('MCP Server State Management', () => {
156
+ test('should track MCP server activity', async () => {
157
+ app.setMCPServerActive(true);
158
+ expect(app.isMCPServerActive()).toBe(true);
159
+ // Simulate activity
160
+ app.trackMCPActivity();
161
+ // Should still be active
162
+ expect(app.isMCPServerActive()).toBe(true);
163
+ });
164
+ test('should timeout MCP server after inactivity', async () => {
165
+ app.setMCPServerActive(true);
166
+ expect(app.isMCPServerActive()).toBe(true);
167
+ // Simulate time passing (16 seconds of inactivity)
168
+ const originalDateNow = Date.now;
169
+ Date.now = jest.fn(() => originalDateNow() + 16000);
170
+ // MCP server should be considered inactive
171
+ expect(app.isMCPServerActive()).toBe(false);
172
+ // Restore Date.now
173
+ Date.now = originalDateNow;
174
+ });
175
+ });
176
+ describe('Status Endpoint', () => {
177
+ test('should return current status', async () => {
178
+ // Set up some state
179
+ await request(app).post('/ready').expect(200);
180
+ app.setMCPServerActive(true);
181
+ const response = await request(app)
182
+ .get('/status')
183
+ .expect(200);
184
+ expect(response.body).toMatchObject({
185
+ pluginConnected: true,
186
+ mcpServerActive: true
187
+ });
188
+ expect(response.body.lastMCPActivity).toBeGreaterThan(0);
189
+ expect(response.body.uptime).toBeGreaterThan(0);
190
+ });
191
+ });
192
+ });
193
+ //# sourceMappingURL=http-server.test.js.map