@el4cteo/rbx-studio-mcp 0.1.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 +203 -0
- package/dist/bridge/rpc.js +243 -0
- package/dist/bridge/rpc.js.map +1 -0
- package/dist/bridge/server.js +281 -0
- package/dist/bridge/server.js.map +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/apidump.js +269 -0
- package/dist/lib/apidump.js.map +1 -0
- package/dist/lib/errors.js +38 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/format.js +191 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/pluginbuild.js +83 -0
- package/dist/lib/pluginbuild.js.map +1 -0
- package/dist/lib/png.js +84 -0
- package/dist/lib/png.js.map +1 -0
- package/dist/lib/protocol.js +22 -0
- package/dist/lib/protocol.js.map +1 -0
- package/dist/lib/tool.js +27 -0
- package/dist/lib/tool.js.map +1 -0
- package/dist/resources.js +70 -0
- package/dist/resources.js.map +1 -0
- package/dist/tools/api.js +78 -0
- package/dist/tools/api.js.map +1 -0
- package/dist/tools/character.js +94 -0
- package/dist/tools/character.js.map +1 -0
- package/dist/tools/debug.js +211 -0
- package/dist/tools/debug.js.map +1 -0
- package/dist/tools/device.js +74 -0
- package/dist/tools/device.js.map +1 -0
- package/dist/tools/discover.js +217 -0
- package/dist/tools/discover.js.map +1 -0
- package/dist/tools/exec.js +191 -0
- package/dist/tools/exec.js.map +1 -0
- package/dist/tools/input.js +96 -0
- package/dist/tools/input.js.map +1 -0
- package/dist/tools/instances.js +261 -0
- package/dist/tools/instances.js.map +1 -0
- package/dist/tools/perf.js +367 -0
- package/dist/tools/perf.js.map +1 -0
- package/dist/tools/playtest.js +153 -0
- package/dist/tools/playtest.js.map +1 -0
- package/dist/tools/screenshot.js +75 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/scripts.js +316 -0
- package/dist/tools/scripts.js.map +1 -0
- package/dist/tools/session.js +152 -0
- package/dist/tools/session.js.map +1 -0
- package/dist/tools/world.js +281 -0
- package/dist/tools/world.js.map +1 -0
- package/package.json +62 -0
- package/plugin/default.project.json +6 -0
- package/plugin/src/Config.luau +59 -0
- package/plugin/src/Console.luau +657 -0
- package/plugin/src/Context.luau +35 -0
- package/plugin/src/Dispatch.luau +90 -0
- package/plugin/src/Editor.luau +142 -0
- package/plugin/src/Emulation.luau +151 -0
- package/plugin/src/LogBuffer.luau +277 -0
- package/plugin/src/Net.luau +102 -0
- package/plugin/src/Paths.luau +255 -0
- package/plugin/src/Phrase.luau +465 -0
- package/plugin/src/Png.luau +238 -0
- package/plugin/src/Scope.luau +78 -0
- package/plugin/src/ScriptEdit.luau +100 -0
- package/plugin/src/Serialize.luau +287 -0
- package/plugin/src/TextEdit.luau +296 -0
- package/plugin/src/Transport.luau +328 -0
- package/plugin/src/Undo.luau +72 -0
- package/plugin/src/Visuals.luau +710 -0
- package/plugin/src/handlers/Api.luau +242 -0
- package/plugin/src/handlers/Assets.luau +145 -0
- package/plugin/src/handlers/Capture.luau +187 -0
- package/plugin/src/handlers/Character.luau +361 -0
- package/plugin/src/handlers/Debug.luau +391 -0
- package/plugin/src/handlers/Device.luau +119 -0
- package/plugin/src/handlers/Discover.luau +289 -0
- package/plugin/src/handlers/Exec.luau +270 -0
- package/plugin/src/handlers/Geometry.luau +261 -0
- package/plugin/src/handlers/Input.luau +287 -0
- package/plugin/src/handlers/Instances.luau +389 -0
- package/plugin/src/handlers/Perf.luau +645 -0
- package/plugin/src/handlers/Playtest.luau +205 -0
- package/plugin/src/handlers/Scripts.luau +387 -0
- package/plugin/src/handlers/Session.luau +168 -0
- package/plugin/src/handlers/Viewport.luau +302 -0
- package/plugin/src/handlers/World.luau +176 -0
- package/plugin/src/init.server.luau +317 -0
- package/scripts/build-plugin.mjs +157 -0
- package/scripts/check-plugin.mjs +97 -0
- package/scripts/install-plugin.mjs +39 -0
- package/scripts/latency.mjs +201 -0
- package/scripts/locate-luau.mjs +51 -0
- package/scripts/sourcemap.mjs +58 -0
- package/scripts/test-plugin.mjs +82 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { CLIENT_HEADER, PROTOCOL_VERSION } from "../lib/protocol.js";
|
|
3
|
+
import { Bridge } from "./rpc.js";
|
|
4
|
+
/** Default loopback port. Deliberately not 58741 — that is drgost1's server. */
|
|
5
|
+
export const DEFAULT_PORT = 44755;
|
|
6
|
+
/** SSE comment cadence. Keeps intermediaries and Studio from reaping an idle stream. */
|
|
7
|
+
const HEARTBEAT_MS = 15_000;
|
|
8
|
+
/** Largest body we accept from the plugin (script sources and screenshots are big). */
|
|
9
|
+
const MAX_BODY_BYTES = 32 * 1024 * 1024;
|
|
10
|
+
/**
|
|
11
|
+
* Starts the loopback HTTP endpoint the Studio plugin talks to.
|
|
12
|
+
*
|
|
13
|
+
* Routes:
|
|
14
|
+
* POST /connect handshake; body is a StudioIdentity, answers with server info
|
|
15
|
+
* POST /events SSE stream, server -> plugin commands (preferred path)
|
|
16
|
+
* GET /poll long-poll fallback for Studio builds without web streams
|
|
17
|
+
* POST /result plugin -> server command results
|
|
18
|
+
* POST /bye clean disconnect on plugin unload
|
|
19
|
+
* GET /latency times N round trips to a connected plugin; measurement only
|
|
20
|
+
*/
|
|
21
|
+
export function startBridgeServer(options = {}) {
|
|
22
|
+
const bridge = options.bridge ?? new Bridge();
|
|
23
|
+
const port = options.port ?? DEFAULT_PORT;
|
|
24
|
+
const server = createServer((req, res) => {
|
|
25
|
+
void handle(bridge, req, res);
|
|
26
|
+
});
|
|
27
|
+
// Long-poll requests park for POLL_HOLD_MS; Node's 2-minute default would be
|
|
28
|
+
// fine, but SSE streams must never be reaped by the server itself.
|
|
29
|
+
server.headersTimeout = 0;
|
|
30
|
+
server.requestTimeout = 0;
|
|
31
|
+
server.keepAliveTimeout = 0;
|
|
32
|
+
const reaper = setInterval(() => bridge.reapStale(), 30_000);
|
|
33
|
+
reaper.unref();
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
server.once("error", (cause) => {
|
|
36
|
+
// Overwhelmingly the cause is a second copy of this server (a stale
|
|
37
|
+
// process, or the same server configured in two MCP clients). Both would
|
|
38
|
+
// fight over the plugin's connection, so say so plainly instead of
|
|
39
|
+
// surfacing a bare EADDRINUSE.
|
|
40
|
+
if (cause.code === "EADDRINUSE") {
|
|
41
|
+
reject(new Error(`Port ${port} is already in use — another roblox-studio-mcp is probably ` +
|
|
42
|
+
`running. Stop it, or start this one with --port <other> and set the ` +
|
|
43
|
+
`matching port in the Studio plugin widget.`));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
reject(cause);
|
|
47
|
+
});
|
|
48
|
+
// Loopback only: nothing on the network should be able to drive Studio.
|
|
49
|
+
server.listen(port, "127.0.0.1", () => {
|
|
50
|
+
server.removeListener("error", reject);
|
|
51
|
+
resolve({
|
|
52
|
+
bridge,
|
|
53
|
+
port,
|
|
54
|
+
close: () => closeServer(server, reaper),
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function closeServer(server, reaper) {
|
|
60
|
+
clearInterval(reaper);
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
server.closeAllConnections();
|
|
63
|
+
server.close(() => resolve());
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function handle(bridge, req, res) {
|
|
67
|
+
// A browser cannot set CLIENT_HEADER cross-origin without a preflight we never
|
|
68
|
+
// answer, and same-origin means it is already local. Together with the Origin
|
|
69
|
+
// rejection below this is what blocks DNS-rebinding attacks on the bridge.
|
|
70
|
+
if (req.headers.origin !== undefined)
|
|
71
|
+
return send(res, 403, { error: "origin not allowed" });
|
|
72
|
+
if (req.headers[CLIENT_HEADER] === undefined) {
|
|
73
|
+
return send(res, 403, { error: `missing ${CLIENT_HEADER} header` });
|
|
74
|
+
}
|
|
75
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
76
|
+
try {
|
|
77
|
+
switch (`${req.method} ${url.pathname}`) {
|
|
78
|
+
case "POST /connect":
|
|
79
|
+
return await handleConnect(bridge, req, res);
|
|
80
|
+
case "POST /events":
|
|
81
|
+
return await handleEvents(bridge, req, res);
|
|
82
|
+
case "GET /poll":
|
|
83
|
+
return await handlePoll(bridge, url, res);
|
|
84
|
+
case "POST /result":
|
|
85
|
+
return await handleResult(bridge, url, req, res);
|
|
86
|
+
case "GET /latency":
|
|
87
|
+
return await handleLatency(bridge, url, res);
|
|
88
|
+
case "POST /transport":
|
|
89
|
+
return await handleTransport(bridge, url, res);
|
|
90
|
+
case "POST /bye":
|
|
91
|
+
bridge.detach(url.searchParams.get("studioId") ?? "");
|
|
92
|
+
return send(res, 200, { ok: true });
|
|
93
|
+
default:
|
|
94
|
+
return send(res, 404, { error: "unknown route" });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (cause) {
|
|
98
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
99
|
+
if (!res.headersSent)
|
|
100
|
+
send(res, 400, { error: message });
|
|
101
|
+
else
|
|
102
|
+
res.end();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Times sequential round trips to a connected Studio and returns the spread.
|
|
107
|
+
*
|
|
108
|
+
* This exists so the project's central performance claim -- that pushing over
|
|
109
|
+
* SSE beats the long-polling every other Studio MCP server uses -- can be
|
|
110
|
+
* checked by whoever doubts it, without them having to take the number on
|
|
111
|
+
* trust. It lives on the bridge rather than only in a script because the bridge
|
|
112
|
+
* owns the port: a standalone measurement tool has to bind 44755 itself, which
|
|
113
|
+
* means shutting down the editor's own connection first, and a measurement that
|
|
114
|
+
* costs you your session is one nobody runs twice.
|
|
115
|
+
*
|
|
116
|
+
* `studio.ping` is the payload because it is the smallest command the plugin
|
|
117
|
+
* answers, so what is left is the transport. Sequential rather than concurrent
|
|
118
|
+
* on purpose: an agent waits for each answer before choosing what to ask next,
|
|
119
|
+
* so serial round trips are the figure that matters, and running them in
|
|
120
|
+
* parallel would measure throughput instead.
|
|
121
|
+
*/
|
|
122
|
+
async function handleLatency(bridge, url, res) {
|
|
123
|
+
const count = Math.min(Math.max(Number(url.searchParams.get("count") ?? 50), 1), 500);
|
|
124
|
+
const requested = url.searchParams.get("studioId") ?? undefined;
|
|
125
|
+
const studios = bridge.list();
|
|
126
|
+
const target = requested
|
|
127
|
+
? studios.find((entry) => entry.studioId === requested)
|
|
128
|
+
: studios[0];
|
|
129
|
+
if (target === undefined) {
|
|
130
|
+
// Reporting zero for a run that never happened would be worse than failing.
|
|
131
|
+
return send(res, 409, {
|
|
132
|
+
error: studios.length === 0
|
|
133
|
+
? "No Studio is connected. Open Studio with the plugin installed."
|
|
134
|
+
: `No connected Studio has id ${requested}.`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// One warm-up that is not recorded: the first call after an idle period pays
|
|
138
|
+
// for stream wake-up, which is real but is not what the tenth call costs.
|
|
139
|
+
await bridge.call("studio.ping", {}, { studioId: target.studioId });
|
|
140
|
+
const samples = [];
|
|
141
|
+
for (let index = 0; index < count; index += 1) {
|
|
142
|
+
const started = process.hrtime.bigint();
|
|
143
|
+
await bridge.call("studio.ping", {}, { studioId: target.studioId });
|
|
144
|
+
samples.push(Number(process.hrtime.bigint() - started) / 1e6);
|
|
145
|
+
}
|
|
146
|
+
const sorted = [...samples].sort((a, b) => a - b);
|
|
147
|
+
const at = (fraction) => sorted[Math.max(0, Math.ceil(fraction * sorted.length) - 1)] ?? 0;
|
|
148
|
+
const round = (value) => Number(value.toFixed(2));
|
|
149
|
+
send(res, 200, {
|
|
150
|
+
transport: target.transport,
|
|
151
|
+
place: target.placeName,
|
|
152
|
+
samples: samples.length,
|
|
153
|
+
meanMs: round(samples.reduce((sum, value) => sum + value, 0) / samples.length),
|
|
154
|
+
medianMs: round(at(0.5)),
|
|
155
|
+
p95Ms: round(at(0.95)),
|
|
156
|
+
minMs: round(sorted[0] ?? 0),
|
|
157
|
+
maxMs: round(sorted[sorted.length - 1] ?? 0),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Moves a connected plugin between the push and long-poll transports.
|
|
162
|
+
*
|
|
163
|
+
* Paired with /latency so one command can measure both sides of the comparison
|
|
164
|
+
* the README rests on, rather than only the side this project prefers.
|
|
165
|
+
*/
|
|
166
|
+
async function handleTransport(bridge, url, res) {
|
|
167
|
+
const mode = url.searchParams.get("mode");
|
|
168
|
+
if (mode !== "sse" && mode !== "poll") {
|
|
169
|
+
return send(res, 400, { error: 'mode must be "sse" or "poll"' });
|
|
170
|
+
}
|
|
171
|
+
const requested = url.searchParams.get("studioId") ?? undefined;
|
|
172
|
+
const target = requested
|
|
173
|
+
? bridge.list().find((entry) => entry.studioId === requested)
|
|
174
|
+
: bridge.list()[0];
|
|
175
|
+
if (target === undefined) {
|
|
176
|
+
return send(res, 409, { error: "No Studio is connected." });
|
|
177
|
+
}
|
|
178
|
+
const result = await bridge.call("studio.transport", { mode }, { studioId: target.studioId });
|
|
179
|
+
send(res, 200, result);
|
|
180
|
+
}
|
|
181
|
+
async function handleConnect(bridge, req, res) {
|
|
182
|
+
const identity = parseIdentity(await readBody(req));
|
|
183
|
+
bridge.attach(identity, null);
|
|
184
|
+
send(res, 200, {
|
|
185
|
+
ok: true,
|
|
186
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
187
|
+
pollHoldMs: 25_000,
|
|
188
|
+
heartbeatMs: HEARTBEAT_MS,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Opens the push channel. The plugin reaches this through
|
|
193
|
+
* `HttpService:CreateWebStreamClient`, which issues a normal POST and then
|
|
194
|
+
* keeps the response body open, so identity rides in the request body.
|
|
195
|
+
*/
|
|
196
|
+
async function handleEvents(bridge, req, res) {
|
|
197
|
+
const identity = parseIdentity(await readBody(req), "sse");
|
|
198
|
+
res.writeHead(200, {
|
|
199
|
+
"Content-Type": "text/event-stream",
|
|
200
|
+
"Cache-Control": "no-cache, no-transform",
|
|
201
|
+
Connection: "keep-alive",
|
|
202
|
+
"X-Accel-Buffering": "no",
|
|
203
|
+
});
|
|
204
|
+
// Flush headers immediately so the plugin's Closed signal is meaningful.
|
|
205
|
+
res.write(`: connected ${PROTOCOL_VERSION}\n\n`);
|
|
206
|
+
const studioId = bridge.attach(identity, res);
|
|
207
|
+
const heartbeat = setInterval(() => {
|
|
208
|
+
if (res.writableEnded)
|
|
209
|
+
return;
|
|
210
|
+
res.write(": ping\n\n");
|
|
211
|
+
bridge.touch(studioId);
|
|
212
|
+
}, HEARTBEAT_MS);
|
|
213
|
+
const teardown = () => {
|
|
214
|
+
clearInterval(heartbeat);
|
|
215
|
+
bridge.detach(studioId);
|
|
216
|
+
};
|
|
217
|
+
req.on("close", teardown);
|
|
218
|
+
req.on("error", teardown);
|
|
219
|
+
}
|
|
220
|
+
async function handlePoll(bridge, url, res) {
|
|
221
|
+
const studioId = url.searchParams.get("studioId");
|
|
222
|
+
if (!studioId || !bridge.has(studioId)) {
|
|
223
|
+
// The plugin restarted the server, or we restarted under it. Either way it
|
|
224
|
+
// must hand us its identity again before we can route commands to it.
|
|
225
|
+
return send(res, 409, { error: "unknown studioId", reconnect: true });
|
|
226
|
+
}
|
|
227
|
+
const command = await bridge.waitForCommand(studioId);
|
|
228
|
+
send(res, 200, command ? { command } : { idle: true });
|
|
229
|
+
}
|
|
230
|
+
async function handleResult(bridge, url, req, res) {
|
|
231
|
+
const studioId = url.searchParams.get("studioId") ?? "";
|
|
232
|
+
const body = await readBody(req);
|
|
233
|
+
const parsed = JSON.parse(body);
|
|
234
|
+
if (typeof parsed?.id !== "string")
|
|
235
|
+
throw new Error("result is missing an id");
|
|
236
|
+
bridge.settle(studioId, parsed);
|
|
237
|
+
send(res, 200, { ok: true });
|
|
238
|
+
}
|
|
239
|
+
function parseIdentity(body, transport = "poll") {
|
|
240
|
+
const raw = JSON.parse(body);
|
|
241
|
+
if (typeof raw.studioId !== "string" || raw.studioId.length === 0) {
|
|
242
|
+
throw new Error("handshake is missing studioId");
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
studioId: raw.studioId,
|
|
246
|
+
placeName: raw.placeName ?? "Unnamed place",
|
|
247
|
+
placeId: raw.placeId ?? 0,
|
|
248
|
+
pluginVersion: raw.pluginVersion ?? "unknown",
|
|
249
|
+
buildId: raw.buildId ?? "unknown",
|
|
250
|
+
transport: raw.transport ?? transport,
|
|
251
|
+
// Optional, because a plugin older than this field still connects fine —
|
|
252
|
+
// it simply lists without a context, as every session did before.
|
|
253
|
+
context: raw.context,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function readBody(req) {
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
const chunks = [];
|
|
259
|
+
let size = 0;
|
|
260
|
+
req.on("data", (chunk) => {
|
|
261
|
+
size += chunk.length;
|
|
262
|
+
if (size > MAX_BODY_BYTES) {
|
|
263
|
+
reject(new Error("request body too large"));
|
|
264
|
+
req.destroy();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
chunks.push(chunk);
|
|
268
|
+
});
|
|
269
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
270
|
+
req.on("error", reject);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
function send(res, status, body) {
|
|
274
|
+
const payload = JSON.stringify(body);
|
|
275
|
+
res.writeHead(status, {
|
|
276
|
+
"Content-Type": "application/json",
|
|
277
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
278
|
+
});
|
|
279
|
+
res.end(payload);
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/bridge/server.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import { DEFAULT_PORT, startBridgeServer } from "./bridge/server.js";
|
|
8
|
+
import { registerDiscoverTools } from "./tools/discover.js";
|
|
9
|
+
import { registerInstanceTools } from "./tools/instances.js";
|
|
10
|
+
import { registerDebugTools } from "./tools/debug.js";
|
|
11
|
+
import { registerExecTools } from "./tools/exec.js";
|
|
12
|
+
import { registerPerfTools } from "./tools/perf.js";
|
|
13
|
+
import { registerPlaytestTools } from "./tools/playtest.js";
|
|
14
|
+
import { registerScreenshotTools } from "./tools/screenshot.js";
|
|
15
|
+
import { registerWorldTools } from "./tools/world.js";
|
|
16
|
+
import { registerCharacterTools } from "./tools/character.js";
|
|
17
|
+
import { registerScriptTools } from "./tools/scripts.js";
|
|
18
|
+
import { registerSessionTools } from "./tools/session.js";
|
|
19
|
+
import { registerInputTools } from "./tools/input.js";
|
|
20
|
+
import { registerDeviceTools } from "./tools/device.js";
|
|
21
|
+
import { registerApiTools } from "./tools/api.js";
|
|
22
|
+
import { registerResources } from "./resources.js";
|
|
23
|
+
const VERSION = "0.1.0";
|
|
24
|
+
function parsePort(argv) {
|
|
25
|
+
const flag = argv.indexOf("--port");
|
|
26
|
+
const raw = flag !== -1 ? argv[flag + 1] : process.env["ROBLOX_STUDIO_MCP_PORT"];
|
|
27
|
+
const port = raw ? Number.parseInt(raw, 10) : DEFAULT_PORT;
|
|
28
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
29
|
+
throw new Error(`Invalid port: ${raw}`);
|
|
30
|
+
}
|
|
31
|
+
return port;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Builds the Studio plugin and copies it into the local plugins folder.
|
|
35
|
+
*
|
|
36
|
+
* Without this the npm package is only half of what someone needs: the server
|
|
37
|
+
* installs with `npx` and the plugin is only reachable by cloning the repo,
|
|
38
|
+
* which is a strange thing to ask of someone who just installed a package.
|
|
39
|
+
*
|
|
40
|
+
* The work is delegated to `scripts/install-plugin.mjs` rather than reimplemented
|
|
41
|
+
* here, because Studio's plugins directory differs per platform and two copies of
|
|
42
|
+
* that knowledge is one too many. That script is shipped in the package.
|
|
43
|
+
*/
|
|
44
|
+
function installPlugin() {
|
|
45
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
46
|
+
execFileSync(process.execPath, [join(root, "scripts", "install-plugin.mjs")], {
|
|
47
|
+
stdio: "inherit",
|
|
48
|
+
});
|
|
49
|
+
process.stderr.write("Studio plugin installed. Open Studio and look for the Studio MCP button.\n");
|
|
50
|
+
}
|
|
51
|
+
async function main() {
|
|
52
|
+
if (process.argv.includes("--install-plugin")) {
|
|
53
|
+
installPlugin();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const bridgeServer = await startBridgeServer({ port: parsePort(process.argv) });
|
|
57
|
+
const server = new McpServer({ name: "roblox-studio-mcp", version: VERSION }, {
|
|
58
|
+
instructions: "Drives a live Roblox Studio session through a companion plugin.\n\n" +
|
|
59
|
+
"Start with `studio_status` to confirm Studio is connected. Instances are " +
|
|
60
|
+
"addressed by dot-notation path from the DataModel root, e.g. " +
|
|
61
|
+
'"Workspace.Map.Spawn" or "ServerScriptService.Systems.Combat". Paths are ' +
|
|
62
|
+
"case-sensitive.\n\n" +
|
|
63
|
+
"The user may have several Studio windows open on different places. When " +
|
|
64
|
+
"more than one is connected, no place is targeted by default and tools " +
|
|
65
|
+
"refuse with AMBIGUOUS_STUDIO: call `list_studios`, ask the user which " +
|
|
66
|
+
"place they mean, then `set_active_studio`. Do the same whenever they " +
|
|
67
|
+
"mention their other place — never assume a switch.\n\n" +
|
|
68
|
+
"Prefer the batch tools: `create`, `modify`, `delete`, `move` and " +
|
|
69
|
+
"`script_edit` all take arrays and apply as a single undo step, so one " +
|
|
70
|
+
"call beats a loop of calls both in latency and in how cleanly the user " +
|
|
71
|
+
"can revert your work. Reach for `execute_luau` only when no dedicated " +
|
|
72
|
+
"tool fits — it is the escape hatch, not the default.",
|
|
73
|
+
});
|
|
74
|
+
const context = { server, bridge: bridgeServer.bridge };
|
|
75
|
+
registerSessionTools(context);
|
|
76
|
+
registerDiscoverTools(context);
|
|
77
|
+
registerScriptTools(context);
|
|
78
|
+
registerInstanceTools(context);
|
|
79
|
+
registerPerfTools(context);
|
|
80
|
+
registerPlaytestTools(context);
|
|
81
|
+
registerExecTools(context);
|
|
82
|
+
registerDebugTools(context);
|
|
83
|
+
registerScreenshotTools(context);
|
|
84
|
+
registerInputTools(context);
|
|
85
|
+
registerDeviceTools(context);
|
|
86
|
+
registerApiTools(context);
|
|
87
|
+
registerWorldTools(context);
|
|
88
|
+
registerCharacterTools(context);
|
|
89
|
+
registerResources(context);
|
|
90
|
+
const shutdown = async () => {
|
|
91
|
+
await bridgeServer.close();
|
|
92
|
+
await server.close();
|
|
93
|
+
process.exit(0);
|
|
94
|
+
};
|
|
95
|
+
process.on("SIGINT", () => void shutdown());
|
|
96
|
+
process.on("SIGTERM", () => void shutdown());
|
|
97
|
+
// stdout belongs to the MCP transport from here on; nothing else may write to it.
|
|
98
|
+
await server.connect(new StdioServerTransport());
|
|
99
|
+
}
|
|
100
|
+
main().catch((cause) => {
|
|
101
|
+
process.stderr.write(`roblox-studio-mcp failed to start: ${cause instanceof Error ? cause.message : String(cause)}\n`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|