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.
- package/LICENSE +21 -0
- package/README.md +319 -0
- package/dist/__tests__/bridge-service.test.d.ts +2 -0
- package/dist/__tests__/bridge-service.test.d.ts.map +1 -0
- package/dist/__tests__/bridge-service.test.js +109 -0
- package/dist/__tests__/bridge-service.test.js.map +1 -0
- package/dist/__tests__/http-server.test.d.ts +2 -0
- package/dist/__tests__/http-server.test.d.ts.map +1 -0
- package/dist/__tests__/http-server.test.js +193 -0
- package/dist/__tests__/http-server.test.js.map +1 -0
- package/dist/__tests__/integration.test.d.ts +2 -0
- package/dist/__tests__/integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration.test.js +182 -0
- package/dist/__tests__/integration.test.js.map +1 -0
- package/dist/__tests__/smoke.test.d.ts +2 -0
- package/dist/__tests__/smoke.test.d.ts.map +1 -0
- package/dist/__tests__/smoke.test.js +63 -0
- package/dist/__tests__/smoke.test.js.map +1 -0
- package/dist/bridge-service.d.ts +17 -0
- package/dist/bridge-service.d.ts.map +1 -0
- package/dist/bridge-service.js +77 -0
- package/dist/bridge-service.js.map +1 -0
- package/dist/http-server.d.ts +4 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +290 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1102 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/index.d.ts +273 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +628 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/studio-client.d.ts +7 -0
- package/dist/tools/studio-client.d.ts.map +1 -0
- package/dist/tools/studio-client.js +19 -0
- package/dist/tools/studio-client.js.map +1 -0
- package/package.json +69 -0
- package/studio-plugin/INSTALLATION.md +150 -0
- package/studio-plugin/MCPPlugin.rbxmx +3253 -0
- package/studio-plugin/plugin.json +10 -0
- 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 @@
|
|
|
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 @@
|
|
|
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
|