@chrrxs/robloxstudio-mcp-inspector 2.11.1 → 2.11.3
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/dist/index.js +77 -21
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +266 -147
- package/studio-plugin/MCPPlugin.rbxmx +266 -147
- package/studio-plugin/src/modules/ClientBroker.ts +8 -50
- package/studio-plugin/src/modules/StopPlayMonitor.ts +87 -0
- package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +77 -54
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +63 -23
- package/studio-plugin/src/server/index.server.ts +10 -0
package/dist/index.js
CHANGED
|
@@ -1217,7 +1217,28 @@ ${s}
|
|
|
1217
1217
|
}
|
|
1218
1218
|
function buildModuleScriptInvokeWrapper(opts) {
|
|
1219
1219
|
const wrapped = `return ((function()
|
|
1220
|
+
local __mcp_output = {}
|
|
1221
|
+
local __mcp_real_print = print
|
|
1222
|
+
local __mcp_real_warn = warn
|
|
1223
|
+
local print = function(...)
|
|
1224
|
+
__mcp_real_print(...)
|
|
1225
|
+
local args = {...}
|
|
1226
|
+
local parts = table.create(#args)
|
|
1227
|
+
for i, a in ipairs(args) do parts[i] = tostring(a) end
|
|
1228
|
+
table.insert(__mcp_output, table.concat(parts, "\\t"))
|
|
1229
|
+
end
|
|
1230
|
+
local warn = function(...)
|
|
1231
|
+
__mcp_real_warn(...)
|
|
1232
|
+
local args = {...}
|
|
1233
|
+
local parts = table.create(#args)
|
|
1234
|
+
for i, a in ipairs(args) do parts[i] = tostring(a) end
|
|
1235
|
+
table.insert(__mcp_output, "[warn] " .. table.concat(parts, "\\t"))
|
|
1236
|
+
end
|
|
1237
|
+
local function __mcp_run()
|
|
1220
1238
|
${opts.userCode}
|
|
1239
|
+
end
|
|
1240
|
+
local ok, errOrValue = xpcall(__mcp_run, debug.traceback)
|
|
1241
|
+
return { ok = ok, value = errOrValue, output = __mcp_output }
|
|
1221
1242
|
end)())`;
|
|
1222
1243
|
return `
|
|
1223
1244
|
local HttpService = game:GetService("HttpService")
|
|
@@ -1234,15 +1255,29 @@ m.Name = "__MCPEvalPayload"
|
|
|
1234
1255
|
local okSet, setErr = pcall(function() m.Source = USER_CODE end)
|
|
1235
1256
|
if not okSet then
|
|
1236
1257
|
m:Destroy()
|
|
1237
|
-
return HttpService:JSONEncode({ bridge = "ok", ok = false,
|
|
1258
|
+
return HttpService:JSONEncode({ bridge = "ok", ok = false, error = "ModuleScript Source set failed: " .. tostring(setErr) })
|
|
1238
1259
|
end
|
|
1239
1260
|
m.Parent = workspace
|
|
1240
|
-
local
|
|
1261
|
+
local bridgeOk, inner = bf:Invoke(m)
|
|
1241
1262
|
m:Destroy()
|
|
1263
|
+
if not bridgeOk then
|
|
1264
|
+
return HttpService:JSONEncode({ bridge = "ok", ok = false, error = tostring(inner) })
|
|
1265
|
+
end
|
|
1266
|
+
-- inner is the {ok, value, output} table from our IIFE. Defensive: if it's
|
|
1267
|
+
-- somehow not a table (caller bypassed the wrapper), fall back to old shape.
|
|
1268
|
+
if typeof(inner) ~= "table" then
|
|
1269
|
+
return HttpService:JSONEncode({
|
|
1270
|
+
bridge = "ok",
|
|
1271
|
+
ok = true,
|
|
1272
|
+
result = if inner == nil then nil else tostring(inner),
|
|
1273
|
+
})
|
|
1274
|
+
end
|
|
1242
1275
|
return HttpService:JSONEncode({
|
|
1243
1276
|
bridge = "ok",
|
|
1244
|
-
ok = ok,
|
|
1245
|
-
result = if
|
|
1277
|
+
ok = inner.ok == true,
|
|
1278
|
+
result = if inner.ok and inner.value ~= nil then tostring(inner.value) else nil,
|
|
1279
|
+
error = if not inner.ok then tostring(inner.value) else nil,
|
|
1280
|
+
output = inner.output or {},
|
|
1246
1281
|
})
|
|
1247
1282
|
`;
|
|
1248
1283
|
}
|
|
@@ -1928,23 +1963,9 @@ ${code}`
|
|
|
1928
1963
|
};
|
|
1929
1964
|
}
|
|
1930
1965
|
async stopPlaytest() {
|
|
1931
|
-
|
|
1932
|
-
if (!hasProxy) {
|
|
1933
|
-
const deadline = Date.now() + 1500;
|
|
1934
|
-
while (!hasProxy && Date.now() < deadline) {
|
|
1935
|
-
await new Promise((r) => setTimeout(r, 150));
|
|
1936
|
-
hasProxy = this.bridge.getInstances().some((i) => i.role === "edit-proxy");
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
const target = hasProxy ? "edit-proxy" : "edit";
|
|
1940
|
-
const response = await this.client.request("/api/stop-playtest", {}, target);
|
|
1966
|
+
const response = await this.client.request("/api/stop-playtest", {}, "edit");
|
|
1941
1967
|
return {
|
|
1942
|
-
content: [
|
|
1943
|
-
{
|
|
1944
|
-
type: "text",
|
|
1945
|
-
text: JSON.stringify(response)
|
|
1946
|
-
}
|
|
1947
|
-
]
|
|
1968
|
+
content: [{ type: "text", text: JSON.stringify(response) }]
|
|
1948
1969
|
};
|
|
1949
1970
|
}
|
|
1950
1971
|
async getPlaytestOutput(target) {
|
|
@@ -3087,14 +3108,42 @@ var init_proxy_bridge_service = __esm({
|
|
|
3087
3108
|
"../core/dist/proxy-bridge-service.js"() {
|
|
3088
3109
|
"use strict";
|
|
3089
3110
|
init_bridge_service();
|
|
3090
|
-
ProxyBridgeService = class extends BridgeService {
|
|
3111
|
+
ProxyBridgeService = class _ProxyBridgeService extends BridgeService {
|
|
3091
3112
|
primaryBaseUrl;
|
|
3092
3113
|
proxyInstanceId;
|
|
3093
3114
|
proxyRequestTimeout = 3e4;
|
|
3115
|
+
cachedInstances = [];
|
|
3116
|
+
refreshTimer;
|
|
3117
|
+
static REFRESH_INTERVAL_MS = 1e3;
|
|
3094
3118
|
constructor(primaryBaseUrl) {
|
|
3095
3119
|
super();
|
|
3096
3120
|
this.primaryBaseUrl = primaryBaseUrl;
|
|
3097
3121
|
this.proxyInstanceId = uuidv42();
|
|
3122
|
+
this.refreshInstances();
|
|
3123
|
+
this.refreshTimer = setInterval(() => this.refreshInstances(), _ProxyBridgeService.REFRESH_INTERVAL_MS);
|
|
3124
|
+
}
|
|
3125
|
+
async refreshInstances() {
|
|
3126
|
+
try {
|
|
3127
|
+
const res = await fetch(`${this.primaryBaseUrl}/instances`);
|
|
3128
|
+
if (!res.ok)
|
|
3129
|
+
return;
|
|
3130
|
+
const body = await res.json();
|
|
3131
|
+
if (Array.isArray(body.instances)) {
|
|
3132
|
+
this.cachedInstances = body.instances;
|
|
3133
|
+
}
|
|
3134
|
+
} catch {
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
getInstances() {
|
|
3138
|
+
return this.cachedInstances;
|
|
3139
|
+
}
|
|
3140
|
+
/** Called when this proxy is being discarded (e.g. promotion to primary
|
|
3141
|
+
replaced it). Stops the background refresh so it doesn't leak. */
|
|
3142
|
+
stop() {
|
|
3143
|
+
if (this.refreshTimer) {
|
|
3144
|
+
clearInterval(this.refreshTimer);
|
|
3145
|
+
this.refreshTimer = void 0;
|
|
3146
|
+
}
|
|
3098
3147
|
}
|
|
3099
3148
|
async sendRequest(endpoint, data, target = "edit") {
|
|
3100
3149
|
const controller = new AbortController();
|
|
@@ -3222,8 +3271,12 @@ var init_server = __esm({
|
|
|
3222
3271
|
const candidateApp = createHttpServer(candidateTools, candidateBridge, this.allowedToolNames, this.config);
|
|
3223
3272
|
try {
|
|
3224
3273
|
const result = await listenWithRetry(candidateApp, host, basePort, 1);
|
|
3274
|
+
const oldBridge = this.bridge;
|
|
3225
3275
|
this.bridge = candidateBridge;
|
|
3226
3276
|
this.tools = candidateTools;
|
|
3277
|
+
if (oldBridge instanceof ProxyBridgeService) {
|
|
3278
|
+
oldBridge.stop();
|
|
3279
|
+
}
|
|
3227
3280
|
httpHandle = result.server;
|
|
3228
3281
|
boundPort = result.port;
|
|
3229
3282
|
primaryApp = candidateApp;
|
|
@@ -3286,6 +3339,9 @@ var init_server = __esm({
|
|
|
3286
3339
|
clearInterval(cleanupInterval);
|
|
3287
3340
|
if (promotionInterval)
|
|
3288
3341
|
clearInterval(promotionInterval);
|
|
3342
|
+
if (this.bridge instanceof ProxyBridgeService) {
|
|
3343
|
+
this.bridge.stop();
|
|
3344
|
+
}
|
|
3289
3345
|
await this.server.close().catch(() => {
|
|
3290
3346
|
});
|
|
3291
3347
|
if (httpHandle)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrrxs/robloxstudio-mcp-inspector",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.3",
|
|
4
4
|
"description": "Read-only MCP Server for Roblox Studio (fork of boshyxd/robloxstudio-mcp-inspector with per-peer execute_luau fixes baked in)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|