@el4cteo/rbx-studio-mcp 0.1.0 → 0.1.1
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/README.md +46 -4
- package/dist/bridge/api.js +25 -0
- package/dist/bridge/api.js.map +1 -0
- package/dist/bridge/remote.js +106 -0
- package/dist/bridge/remote.js.map +1 -0
- package/dist/bridge/rpc.js +24 -4
- package/dist/bridge/rpc.js.map +1 -1
- package/dist/bridge/server.js +84 -4
- package/dist/bridge/server.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/errors.js +48 -3
- package/dist/lib/errors.js.map +1 -1
- package/dist/tools/input.js +13 -1
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/playtest.js +3 -3
- package/dist/tools/playtest.js.map +1 -1
- package/dist/tools/screenshot.js +46 -11
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/session.js +10 -8
- package/dist/tools/session.js.map +1 -1
- package/package.json +62 -62
- package/plugin/src/Config.luau +59 -59
- package/plugin/src/handlers/Capture.luau +366 -187
- package/plugin/src/handlers/Debug.luau +2 -1
- package/plugin/src/handlers/Input.luau +166 -0
- package/scripts/prism.mjs +310 -0
package/dist/tools/playtest.js
CHANGED
|
@@ -10,7 +10,7 @@ import { defineTool } from "../lib/tool.js";
|
|
|
10
10
|
* and its context is exactly what the caller needs.
|
|
11
11
|
*/
|
|
12
12
|
async function findPlaytestSession(bridge) {
|
|
13
|
-
const sessions = bridge.
|
|
13
|
+
const sessions = (await bridge.sessions()).list;
|
|
14
14
|
const cached = sessions.find((session) => session.context?.includes("playtest"));
|
|
15
15
|
if (cached)
|
|
16
16
|
return cached.studioId;
|
|
@@ -18,7 +18,7 @@ async function findPlaytestSession(bridge) {
|
|
|
18
18
|
const probed = await Promise.all(unknown.map(async (session) => {
|
|
19
19
|
try {
|
|
20
20
|
const status = await bridge.call("studio.status", {}, { studioId: session.studioId, timeoutMs: 3_000 });
|
|
21
|
-
bridge.notePlaceName(session.studioId, status.placeName, status.context);
|
|
21
|
+
await bridge.notePlaceName(session.studioId, status.placeName, status.context);
|
|
22
22
|
return status.context?.includes("playtest") ? session.studioId : null;
|
|
23
23
|
}
|
|
24
24
|
catch {
|
|
@@ -105,7 +105,7 @@ export function registerPlaytestTools(context) {
|
|
|
105
105
|
timeoutMs: 10_000,
|
|
106
106
|
})
|
|
107
107
|
.catch(() => null);
|
|
108
|
-
const survivor = bridge.
|
|
108
|
+
const survivor = (await bridge.sessions()).list.find((session) => session.studioId !== playtest);
|
|
109
109
|
if (survivor) {
|
|
110
110
|
//[[
|
|
111
111
|
// Waited for, not sampled. The teardown is deliberately deferred so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playtest.js","sourceRoot":"","sources":["../../src/tools/playtest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,IAAI,EAAmB,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAoB,MAAM,gBAAgB,CAAC;AAkB9D;;;;;;;GAOG;AACH,KAAK,UAAU,mBAAmB,CAAC,MAA6B;IAC9D,MAAM,QAAQ,GAAG,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"playtest.js","sourceRoot":"","sources":["../../src/tools/playtest.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/screenshot.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { zstdDecompressSync } from "node:zlib";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { ToolError } from "../lib/errors.js";
|
|
3
4
|
import { image } from "../lib/format.js";
|
|
4
5
|
import { encodePng } from "../lib/png.js";
|
|
5
6
|
import { defineTool } from "../lib/tool.js";
|
|
@@ -19,6 +20,37 @@ function toPngBase64(response) {
|
|
|
19
20
|
const rgb = zstdDecompressSync(Buffer.from(response.data, "base64"));
|
|
20
21
|
return encodePng(rgb, response.width, response.height).toString("base64");
|
|
21
22
|
}
|
|
23
|
+
const isPlaytest = (session) => (session.context ?? "").startsWith("playtest");
|
|
24
|
+
/** Which session this call will land on, when that can be told from here. */
|
|
25
|
+
function targetOf(sessions, activeId, wanted) {
|
|
26
|
+
if (wanted !== undefined)
|
|
27
|
+
return sessions.find((session) => session.studioId === wanted);
|
|
28
|
+
if (activeId !== null)
|
|
29
|
+
return sessions.find((session) => session.studioId === activeId);
|
|
30
|
+
return sessions.length === 1 ? sessions[0] : undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A playtest screenshot, which takes two sessions.
|
|
34
|
+
*
|
|
35
|
+
* The client is the only thing that can take the picture, and it cannot read
|
|
36
|
+
* it back: `CaptureService` returns a temporary texture id and
|
|
37
|
+
* `CreateEditableImageAsync` refuses those at script identity. The id names a
|
|
38
|
+
* texture in the Studio *process*, though, so the editor session's plugin —
|
|
39
|
+
* same process, plugin identity — opens it without complaint. This is the only
|
|
40
|
+
* place that can see both sessions at once, so the pairing lives here.
|
|
41
|
+
*/
|
|
42
|
+
async function playtestShot(bridge, playtest, sessions, width) {
|
|
43
|
+
const editor = sessions.find((session) => session.placeId === playtest.placeId && !isPlaytest(session));
|
|
44
|
+
if (editor === undefined) {
|
|
45
|
+
throw new ToolError("NO_EDITOR_SESSION", "The playtest is connected but its editor session is not, and reading the " +
|
|
46
|
+
"captured image needs the editor's plugin identity.", "The editor window must stay connected while the playtest runs. Check " +
|
|
47
|
+
"`list_studios` — there should be two entries for this place.");
|
|
48
|
+
}
|
|
49
|
+
const { contentId } = await bridge.call("capture.playtestId", {}, { studioId: playtest.studioId, timeoutMs: 40_000 });
|
|
50
|
+
// Straight on, with no await in between: the texture is temporary and there
|
|
51
|
+
// is no promise about how long Studio keeps one nobody has opened yet.
|
|
52
|
+
return bridge.call("capture.decode", { contentId, width, context: "playtest client" }, { studioId: editor.studioId, timeoutMs: 60_000 });
|
|
53
|
+
}
|
|
22
54
|
export function registerScreenshotTools(context) {
|
|
23
55
|
const { bridge } = context;
|
|
24
56
|
defineTool(context, {
|
|
@@ -36,13 +68,12 @@ export function registerScreenshotTools(context) {
|
|
|
36
68
|
"their camera angle, not a framing of your choosing. Frame the subject " +
|
|
37
69
|
"with `viewport op=\"focus\"` first — that is what makes this tool " +
|
|
38
70
|
"worth calling.\n\n" +
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"through `character`, `console` and `find` instead.",
|
|
71
|
+
"Works during a playtest too — address it at the playtest's studioId and " +
|
|
72
|
+
"you get the player's own view, which is the only way to check what a GUI " +
|
|
73
|
+
"actually looks like in front of the game. That one is taken on the client " +
|
|
74
|
+
"and read back through the editor session, so it is a little slower and " +
|
|
75
|
+
"needs the editor window still connected; the caption says `playtest " +
|
|
76
|
+
"client` when it came from there.",
|
|
46
77
|
inputSchema: {
|
|
47
78
|
width: z
|
|
48
79
|
.number()
|
|
@@ -57,10 +88,14 @@ export function registerScreenshotTools(context) {
|
|
|
57
88
|
},
|
|
58
89
|
readOnly: true,
|
|
59
90
|
}, async (args) => {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
91
|
+
const { list, activeId } = await bridge.sessions();
|
|
92
|
+
const target = targetOf(list, activeId, args.studioId);
|
|
93
|
+
const response = target !== undefined && isPlaytest(target)
|
|
94
|
+
? await playtestShot(bridge, target, list, args.width)
|
|
95
|
+
: await bridge.call("capture.screenshot", { width: args.width },
|
|
96
|
+
// Capturing, reading the pixels back and compressing them all happen
|
|
97
|
+
// before the reply, and none is instant on a big viewport.
|
|
98
|
+
{ studioId: args.studioId, timeoutMs: 60_000 });
|
|
64
99
|
const png = toPngBase64(response);
|
|
65
100
|
// The device is named in the caption rather than left to be inferred: an
|
|
66
101
|
// emulated phone makes every screenshot tall and narrow until it is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/session.js
CHANGED
|
@@ -33,12 +33,13 @@ export function registerSessionTools(context) {
|
|
|
33
33
|
readOnly: true,
|
|
34
34
|
}, async ({ studioId }) => {
|
|
35
35
|
const status = await bridge.call("studio.status", {}, { studioId });
|
|
36
|
-
const
|
|
36
|
+
const view = await bridge.sessions();
|
|
37
|
+
const targetId = studioId ?? view.activeId;
|
|
37
38
|
if (targetId)
|
|
38
|
-
bridge.notePlaceName(targetId, status.placeName, status.context);
|
|
39
|
+
await bridge.notePlaceName(targetId, status.placeName, status.context);
|
|
39
40
|
// A stale plugin answers with older handlers and no other symptom, so the
|
|
40
41
|
// warning rides along with the one call agents are told to make first.
|
|
41
|
-
const session =
|
|
42
|
+
const session = view.list.find((entry) => entry.studioId === targetId);
|
|
42
43
|
const warning = pluginStalenessWarning(session?.buildId);
|
|
43
44
|
return json(status, warning ? `WARNING: ${warning}` : undefined);
|
|
44
45
|
});
|
|
@@ -67,7 +68,8 @@ export function registerSessionTools(context) {
|
|
|
67
68
|
inputSchema: {},
|
|
68
69
|
readOnly: true,
|
|
69
70
|
}, async () => {
|
|
70
|
-
const
|
|
71
|
+
const view = await bridge.sessions();
|
|
72
|
+
const sessions = view.list;
|
|
71
73
|
if (sessions.length === 0) {
|
|
72
74
|
return text("No Studio instances are connected.\n" +
|
|
73
75
|
"Open Roblox Studio and enable the Studio MCP plugin (Plugins tab -> " +
|
|
@@ -76,7 +78,7 @@ export function registerSessionTools(context) {
|
|
|
76
78
|
// A defaulted target is not reported as active: tools refuse to use it
|
|
77
79
|
// while several are connected, so showing it as active would explain
|
|
78
80
|
// neither the AMBIGUOUS_STUDIO that follows nor how to clear it.
|
|
79
|
-
const active =
|
|
81
|
+
const active = view.activeIsChosen || sessions.length === 1 ? view.activeId : null;
|
|
80
82
|
// The identity captured at connect only knows the data model's name. Each
|
|
81
83
|
// Studio is asked live for the name the user would actually recognise,
|
|
82
84
|
// plus what it has open — which is usually how someone identifies a window
|
|
@@ -85,7 +87,7 @@ export function registerSessionTools(context) {
|
|
|
85
87
|
const detail = await Promise.all(sessions.map(async (session) => {
|
|
86
88
|
try {
|
|
87
89
|
const status = await bridge.call("studio.status", {}, { studioId: session.studioId, timeoutMs: 3_000 });
|
|
88
|
-
bridge.notePlaceName(session.studioId, status.placeName, status.context);
|
|
90
|
+
await bridge.notePlaceName(session.studioId, status.placeName, status.context);
|
|
89
91
|
return {
|
|
90
92
|
placeName: status.placeName,
|
|
91
93
|
context: status.context,
|
|
@@ -133,8 +135,8 @@ export function registerSessionTools(context) {
|
|
|
133
135
|
destructive: false,
|
|
134
136
|
idempotent: true,
|
|
135
137
|
}, async ({ studioId }) => {
|
|
136
|
-
bridge.setActive(studioId);
|
|
137
|
-
const session = bridge.
|
|
138
|
+
await bridge.setActive(studioId);
|
|
139
|
+
const session = (await bridge.sessions()).list.find((s) => s.studioId === studioId);
|
|
138
140
|
// Confirmed with the session's context because two entries can share a
|
|
139
141
|
// place name, and picking the playtest one means work that disappears
|
|
140
142
|
// when the user presses stop.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/tools/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAmB,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAoB,MAAM,gBAAgB,CAAC;AA+B9D,MAAM,UAAU,oBAAoB,CAAC,OAAoB;IACvD,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAE3B,UAAU,CACR,OAAO,EACP;QACE,IAAI,EAAE,eAAe;QACrB,KAAK,EAAE,eAAe;QACtB,WAAW,EACT,uEAAuE;YACvE,wEAAwE;YACxE,oEAAoE;YACpE,2EAA2E;YAC3E,uEAAuE;YACvE,yEAAyE;YACzE,0EAA0E;YAC1E,2EAA2E;YAC3E,4EAA4E;YAC5E,oEAAoE;YACpE,wEAAwE;YACxE,2EAA2E;YAC3E,yEAAyE;YACzE,UAAU;YACV,2EAA2E;YAC3E,mDAAmD;QACrD,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC;iBACR,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CACP,gEAAgE;gBAC9D,yCAAyC,CAC5C;SACJ;QACD,QAAQ,EAAE,IAAI;KACf,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAuB,EAAE;QAC1C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAe,eAAe,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;QAElF,MAAM,QAAQ,GAAG,QAAQ,IAAI,
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/tools/session.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@el4cteo/rbx-studio-mcp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Free, open-source MCP server for Roblox Studio. Push-based SSE transport, editor-safe script edits, token-lean tools.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"bin": {
|
|
8
|
-
"rbx-studio-mcp": "dist/index.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"dist",
|
|
12
|
-
"plugin",
|
|
13
|
-
"scripts",
|
|
14
|
-
"README.md",
|
|
15
|
-
"LICENSE"
|
|
16
|
-
],
|
|
17
|
-
"engines": {
|
|
18
|
-
"node": ">=22.15"
|
|
19
|
-
},
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "tsc",
|
|
22
|
-
"watch": "tsc --watch",
|
|
23
|
-
"typecheck": "tsc --noEmit",
|
|
24
|
-
"start": "node dist/index.js",
|
|
25
|
-
"build:plugin": "node scripts/check-plugin.mjs && node scripts/build-plugin.mjs",
|
|
26
|
-
"sourcemap": "node scripts/sourcemap.mjs",
|
|
27
|
-
"test": "node scripts/test-plugin.mjs",
|
|
28
|
-
"build:all": "npm run build && npm run build:plugin",
|
|
29
|
-
"install:plugin": "node scripts/install-plugin.mjs",
|
|
30
|
-
"check:plugin": "node scripts/check-plugin.mjs",
|
|
31
|
-
"prepack": "npm run build"
|
|
32
|
-
},
|
|
33
|
-
"keywords": [
|
|
34
|
-
"mcp",
|
|
35
|
-
"model-context-protocol",
|
|
36
|
-
"roblox",
|
|
37
|
-
"roblox-studio",
|
|
38
|
-
"luau",
|
|
39
|
-
"claude",
|
|
40
|
-
"ai"
|
|
41
|
-
],
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
44
|
-
"zod": "^4.4.3"
|
|
45
|
-
},
|
|
46
|
-
"devDependencies": {
|
|
47
|
-
"@types/node": "^26.2.0",
|
|
48
|
-
"typescript": "^7.0.2"
|
|
49
|
-
},
|
|
50
|
-
"author": "EL4CTEO",
|
|
51
|
-
"repository": {
|
|
52
|
-
"type": "git",
|
|
53
|
-
"url": "git+https://github.com/EL4CTEO/
|
|
54
|
-
},
|
|
55
|
-
"homepage": "https://github.com/EL4CTEO/
|
|
56
|
-
"bugs": {
|
|
57
|
-
"url": "https://github.com/EL4CTEO/
|
|
58
|
-
},
|
|
59
|
-
"publishConfig": {
|
|
60
|
-
"access": "public"
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@el4cteo/rbx-studio-mcp",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Free, open-source MCP server for Roblox Studio. Push-based SSE transport, editor-safe script edits, token-lean tools.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"rbx-studio-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"plugin",
|
|
13
|
+
"scripts",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.15"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"watch": "tsc --watch",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"start": "node dist/index.js",
|
|
25
|
+
"build:plugin": "node scripts/check-plugin.mjs && node scripts/build-plugin.mjs",
|
|
26
|
+
"sourcemap": "node scripts/sourcemap.mjs",
|
|
27
|
+
"test": "node scripts/test-plugin.mjs",
|
|
28
|
+
"build:all": "npm run build && npm run build:plugin",
|
|
29
|
+
"install:plugin": "node scripts/install-plugin.mjs",
|
|
30
|
+
"check:plugin": "node scripts/check-plugin.mjs",
|
|
31
|
+
"prepack": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"mcp",
|
|
35
|
+
"model-context-protocol",
|
|
36
|
+
"roblox",
|
|
37
|
+
"roblox-studio",
|
|
38
|
+
"luau",
|
|
39
|
+
"claude",
|
|
40
|
+
"ai"
|
|
41
|
+
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
44
|
+
"zod": "^4.4.3"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^26.2.0",
|
|
48
|
+
"typescript": "^7.0.2"
|
|
49
|
+
},
|
|
50
|
+
"author": "EL4CTEO",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/EL4CTEO/rbx-studio-mcp.git"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/EL4CTEO/rbx-studio-mcp#readme",
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/EL4CTEO/rbx-studio-mcp/issues"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
}
|
|
62
|
+
}
|
package/plugin/src/Config.luau
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--[[
|
|
3
|
-
Connection settings for the Studio MCP plugin.
|
|
4
|
-
|
|
5
|
-
The port must match the one the Node server was started with (`--port`, or
|
|
6
|
-
the ROBLOX_STUDIO_MCP_PORT environment variable). It is stored as a plugin
|
|
7
|
-
setting so a user running on a non-default port only has to set it once.
|
|
8
|
-
]]
|
|
9
|
-
|
|
10
|
-
local Config = {}
|
|
11
|
-
|
|
12
|
-
Config.PLUGIN_VERSION = "0.1.
|
|
13
|
-
|
|
14
|
-
-- Fingerprint of plugin/src, stamped in by scripts/build-plugin.mjs. The server
|
|
15
|
-
-- computes the same hash from its own copy of the sources and compares, so a
|
|
16
|
-
-- plugin left running from an older build is reported rather than silently
|
|
17
|
-
-- answering with stale handlers. Stays "dev" when the tree is loaded unbuilt.
|
|
18
|
-
Config.BUILD_ID = "dev"
|
|
19
|
-
Config.PROTOCOL_VERSION = 1
|
|
20
|
-
Config.DEFAULT_PORT = 44755
|
|
21
|
-
|
|
22
|
-
-- Header the bridge requires on every request. A web page cannot set it
|
|
23
|
-
-- cross-origin without a preflight the bridge never answers, which is what
|
|
24
|
-
-- keeps a malicious site from driving Studio through the loopback port.
|
|
25
|
-
Config.CLIENT_HEADER = "x-roblox-studio-mcp"
|
|
26
|
-
|
|
27
|
-
-- How long to wait before retrying after the stream drops. Studio closes SSE
|
|
28
|
-
-- connections at the 30 minute mark, so reconnects are routine, not exceptional.
|
|
29
|
-
Config.RECONNECT_DELAY = 1
|
|
30
|
-
Config.MAX_RECONNECT_DELAY = 30
|
|
31
|
-
|
|
32
|
-
-- How long a healthy long-poll session runs before standing aside so streaming
|
|
33
|
-
-- can be tried again. Long enough that a Studio which genuinely cannot stream
|
|
34
|
-
-- is not reconnecting constantly, short enough that a session downgraded by one
|
|
35
|
-
-- unlucky attempt does not spend the rest of its life slower than it should be.
|
|
36
|
-
Config.RESTREAM_INTERVAL = 60
|
|
37
|
-
|
|
38
|
-
local port: number? = nil
|
|
39
|
-
|
|
40
|
-
function Config.setPort(value: number)
|
|
41
|
-
port = value
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
function Config.getPort(): number
|
|
45
|
-
return port or Config.DEFAULT_PORT
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
function Config.baseUrl(): string
|
|
49
|
-
return string.format("http://127.0.0.1:%d", Config.getPort())
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
function Config.headers(): { [string]: string }
|
|
53
|
-
return {
|
|
54
|
-
[Config.CLIENT_HEADER] = tostring(Config.PROTOCOL_VERSION),
|
|
55
|
-
["Content-Type"] = "application/json",
|
|
56
|
-
}
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
return Config
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
Connection settings for the Studio MCP plugin.
|
|
4
|
+
|
|
5
|
+
The port must match the one the Node server was started with (`--port`, or
|
|
6
|
+
the ROBLOX_STUDIO_MCP_PORT environment variable). It is stored as a plugin
|
|
7
|
+
setting so a user running on a non-default port only has to set it once.
|
|
8
|
+
]]
|
|
9
|
+
|
|
10
|
+
local Config = {}
|
|
11
|
+
|
|
12
|
+
Config.PLUGIN_VERSION = "0.1.1"
|
|
13
|
+
|
|
14
|
+
-- Fingerprint of plugin/src, stamped in by scripts/build-plugin.mjs. The server
|
|
15
|
+
-- computes the same hash from its own copy of the sources and compares, so a
|
|
16
|
+
-- plugin left running from an older build is reported rather than silently
|
|
17
|
+
-- answering with stale handlers. Stays "dev" when the tree is loaded unbuilt.
|
|
18
|
+
Config.BUILD_ID = "dev"
|
|
19
|
+
Config.PROTOCOL_VERSION = 1
|
|
20
|
+
Config.DEFAULT_PORT = 44755
|
|
21
|
+
|
|
22
|
+
-- Header the bridge requires on every request. A web page cannot set it
|
|
23
|
+
-- cross-origin without a preflight the bridge never answers, which is what
|
|
24
|
+
-- keeps a malicious site from driving Studio through the loopback port.
|
|
25
|
+
Config.CLIENT_HEADER = "x-roblox-studio-mcp"
|
|
26
|
+
|
|
27
|
+
-- How long to wait before retrying after the stream drops. Studio closes SSE
|
|
28
|
+
-- connections at the 30 minute mark, so reconnects are routine, not exceptional.
|
|
29
|
+
Config.RECONNECT_DELAY = 1
|
|
30
|
+
Config.MAX_RECONNECT_DELAY = 30
|
|
31
|
+
|
|
32
|
+
-- How long a healthy long-poll session runs before standing aside so streaming
|
|
33
|
+
-- can be tried again. Long enough that a Studio which genuinely cannot stream
|
|
34
|
+
-- is not reconnecting constantly, short enough that a session downgraded by one
|
|
35
|
+
-- unlucky attempt does not spend the rest of its life slower than it should be.
|
|
36
|
+
Config.RESTREAM_INTERVAL = 60
|
|
37
|
+
|
|
38
|
+
local port: number? = nil
|
|
39
|
+
|
|
40
|
+
function Config.setPort(value: number)
|
|
41
|
+
port = value
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
function Config.getPort(): number
|
|
45
|
+
return port or Config.DEFAULT_PORT
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
function Config.baseUrl(): string
|
|
49
|
+
return string.format("http://127.0.0.1:%d", Config.getPort())
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
function Config.headers(): { [string]: string }
|
|
53
|
+
return {
|
|
54
|
+
[Config.CLIENT_HEADER] = tostring(Config.PROTOCOL_VERSION),
|
|
55
|
+
["Content-Type"] = "application/json",
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
return Config
|