@chrrxs/robloxstudio-mcp 3.1.0 → 3.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/dist/index.js +6 -3
- package/package.json +1 -1
- package/studio-plugin/MCPPlugin.rbxmx +152 -35
package/dist/index.js
CHANGED
|
@@ -6116,7 +6116,7 @@ var TOOL_HANDLERS = {
|
|
|
6116
6116
|
manage_instance: (tools, body) => tools.manageInstance(body),
|
|
6117
6117
|
solo_playtest: (tools, body) => tools.soloPlaytest(body.action, body.mode, body.timeout, body.instance_id),
|
|
6118
6118
|
multiplayer_playtest: (tools, body) => tools.multiplayerPlaytest(body.action, body.numPlayers, body.target, body.testArgs, body.value, body.timeout, body.instance_id),
|
|
6119
|
-
get_runtime_logs: (tools, body) => tools.getRuntimeLogs(body.instance_id, body.multiplayer_group_id, body.cursor, body.cursor_by_instance, body.tail, body.filter),
|
|
6119
|
+
get_runtime_logs: (tools, body, context) => tools.getRuntimeLogs(body.instance_id, body.multiplayer_group_id, body.cursor, body.cursor_by_instance, body.tail, body.filter, context?.signal),
|
|
6120
6120
|
capture_script_profiler: (tools, body) => tools.captureScriptProfiler(body.target, {
|
|
6121
6121
|
duration_ms: body.duration_ms,
|
|
6122
6122
|
frequency: body.frequency,
|
|
@@ -9506,6 +9506,7 @@ var MAX_DEVICE_MATRIX_ENTRIES = 6;
|
|
|
9506
9506
|
var MAX_NETWORK_PACKET_LOSS_PERCENT = 0.5;
|
|
9507
9507
|
var GREP_SCRIPTS_TIMEOUT_MS = 12e4;
|
|
9508
9508
|
var MAX_GREP_PATTERN_UTF8_BYTES = 4096;
|
|
9509
|
+
var RUNTIME_LOG_PEER_TIMEOUT_MS = 5e3;
|
|
9509
9510
|
var STUDIO_ASSISTANT_SOURCE_IMAGE_LABEL = "Studio Assistant Source Image";
|
|
9510
9511
|
var CREATOR_STORE_SEARCH_TYPES = /* @__PURE__ */ new Set([
|
|
9511
9512
|
"Audio",
|
|
@@ -11367,7 +11368,7 @@ var RobloxStudioTools = class {
|
|
|
11367
11368
|
]
|
|
11368
11369
|
};
|
|
11369
11370
|
}
|
|
11370
|
-
async getRuntimeLogs(instance_id, multiplayer_group_id, cursor, cursor_by_instance, tail, filter) {
|
|
11371
|
+
async getRuntimeLogs(instance_id, multiplayer_group_id, cursor, cursor_by_instance, tail, filter, signal) {
|
|
11371
11372
|
if (instance_id !== void 0 && multiplayer_group_id !== void 0) {
|
|
11372
11373
|
throw new Error("get_runtime_logs accepts only one of instance_id or multiplayer_group_id.");
|
|
11373
11374
|
}
|
|
@@ -11511,7 +11512,7 @@ var RobloxStudioTools = class {
|
|
|
11511
11512
|
if (filter !== void 0)
|
|
11512
11513
|
data.filter = filter;
|
|
11513
11514
|
try {
|
|
11514
|
-
const responseValue = await this.client.request("/api/get-runtime-logs", data, peer.peerId);
|
|
11515
|
+
const responseValue = await this.client.request("/api/get-runtime-logs", data, peer.peerId, RUNTIME_LOG_PEER_TIMEOUT_MS, signal);
|
|
11515
11516
|
if (typeof responseValue !== "object" || responseValue === null || Array.isArray(responseValue)) {
|
|
11516
11517
|
return {
|
|
11517
11518
|
peerId: peer.peerId,
|
|
@@ -11538,6 +11539,8 @@ var RobloxStudioTools = class {
|
|
|
11538
11539
|
nextSince: response.nextSince
|
|
11539
11540
|
};
|
|
11540
11541
|
} catch (error) {
|
|
11542
|
+
if (signal?.aborted)
|
|
11543
|
+
throw error;
|
|
11541
11544
|
return { peerId: peer.peerId, role: peer.role, error: errorMessage(error) };
|
|
11542
11545
|
}
|
|
11543
11546
|
}));
|
package/package.json
CHANGED
|
@@ -1472,9 +1472,9 @@ local function computeBridgeStamp()
|
|
|
1472
1472
|
for i = 1, #combined do
|
|
1473
1473
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1474
1474
|
end
|
|
1475
|
-
-- "3.1.
|
|
1475
|
+
-- "3.1.1" is replaced with the package version at package time
|
|
1476
1476
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1477
|
-
return `{tostring(h)}-3.1.
|
|
1477
|
+
return `{tostring(h)}-3.1.1`
|
|
1478
1478
|
end
|
|
1479
1479
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1480
1480
|
local function setSource(scriptInst, source)
|
|
@@ -9736,7 +9736,7 @@ return {
|
|
|
9736
9736
|
<Properties>
|
|
9737
9737
|
<string name="Name">State</string>
|
|
9738
9738
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
9739
|
-
local CURRENT_VERSION = "3.1.
|
|
9739
|
+
local CURRENT_VERSION = "3.1.1"
|
|
9740
9740
|
local PLUGIN_VARIANT = "main"
|
|
9741
9741
|
local BASE_PORT = 58741
|
|
9742
9742
|
local function createConnection(port)
|
|
@@ -10013,6 +10013,8 @@ local INITIAL_RESPONSE_RETRY_DELAY_SECONDS = 0.5
|
|
|
10013
10013
|
local MAX_RESPONSE_RETRY_DELAY_SECONDS = 5
|
|
10014
10014
|
local MAX_TERMINAL_RESPONSES = 256
|
|
10015
10015
|
local STREAM_SILENCE_TIMEOUT_SECONDS = 20
|
|
10016
|
+
-- The bridge accepts request bodies up to 50 MiB; leave room for its envelope.
|
|
10017
|
+
local MAX_STREAM_FRAME_BYTES = 64 * 1024 * 1024
|
|
10016
10018
|
local options
|
|
10017
10019
|
local active = false
|
|
10018
10020
|
local shutdownSuspended = false
|
|
@@ -10026,15 +10028,13 @@ local pendingResponses = {}
|
|
|
10026
10028
|
local terminalResponseIds = {}
|
|
10027
10029
|
local terminalResponseOrder = {}
|
|
10028
10030
|
local readyFailureLogKeys = {}
|
|
10029
|
-
local
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
payload = (string.gsub((string.gsub(string.sub(normalized, 6), "^%s+", "")), "%s+$", ""))
|
|
10037
|
-
end
|
|
10031
|
+
local streamLineParts = {}
|
|
10032
|
+
local streamDataLines = {}
|
|
10033
|
+
local streamFrameBytes = 0
|
|
10034
|
+
local streamLineHasContent = false
|
|
10035
|
+
local streamDiscardingFrame = false
|
|
10036
|
+
local streamSkipLf = false
|
|
10037
|
+
local function decodeMessage(payload)
|
|
10038
10038
|
local decodeOk, decoded = pcall(function()
|
|
10039
10039
|
return HttpService:JSONDecode(payload)
|
|
10040
10040
|
end)
|
|
@@ -10135,9 +10135,123 @@ local function decodeMessage(message)
|
|
|
10135
10135
|
end
|
|
10136
10136
|
return nil
|
|
10137
10137
|
end
|
|
10138
|
+
local function resetStreamFrame()
|
|
10139
|
+
streamLineParts = {}
|
|
10140
|
+
streamDataLines = {}
|
|
10141
|
+
streamFrameBytes = 0
|
|
10142
|
+
streamLineHasContent = false
|
|
10143
|
+
streamDiscardingFrame = false
|
|
10144
|
+
end
|
|
10145
|
+
local function finishStreamLine(events)
|
|
10146
|
+
if not streamLineHasContent then
|
|
10147
|
+
if not streamDiscardingFrame and #streamDataLines > 0 then
|
|
10148
|
+
local event = decodeMessage(table.concat(streamDataLines, "\n"))
|
|
10149
|
+
if event ~= nil then
|
|
10150
|
+
table.insert(events, event)
|
|
10151
|
+
end
|
|
10152
|
+
end
|
|
10153
|
+
resetStreamFrame()
|
|
10154
|
+
return nil
|
|
10155
|
+
end
|
|
10156
|
+
if not streamDiscardingFrame then
|
|
10157
|
+
local line = table.concat(streamLineParts, "")
|
|
10158
|
+
if string.sub(line, 1, 5) == "data:" then
|
|
10159
|
+
local start = if string.sub(line, 6, 6) == " " then 7 else 6
|
|
10160
|
+
local _streamDataLines = streamDataLines
|
|
10161
|
+
local _arg0 = string.sub(line, start)
|
|
10162
|
+
table.insert(_streamDataLines, _arg0)
|
|
10163
|
+
elseif line == "data" then
|
|
10164
|
+
table.insert(streamDataLines, "")
|
|
10165
|
+
end
|
|
10166
|
+
end
|
|
10167
|
+
streamLineParts = {}
|
|
10168
|
+
streamLineHasContent = false
|
|
10169
|
+
end
|
|
10170
|
+
-- MessageReceived may coalesce frames (notably server/client fanout) or split
|
|
10171
|
+
-- them across callbacks. Callback boundaries are not SSE event boundaries.
|
|
10172
|
+
local function decodeMessages(message)
|
|
10173
|
+
local events = {}
|
|
10174
|
+
local messageBytes = #message
|
|
10175
|
+
if messageBytes == 0 then
|
|
10176
|
+
return events
|
|
10177
|
+
end
|
|
10178
|
+
-- Some Studio versions deliver the decoded SSE data or a complete data line
|
|
10179
|
+
-- without delimiters. Only recognize those forms with no unfinished frame;
|
|
10180
|
+
-- once framing starts, callback boundaries never terminate partial data.
|
|
10181
|
+
if streamFrameBytes == 0 and not streamDiscardingFrame and messageBytes <= MAX_STREAM_FRAME_BYTES then
|
|
10182
|
+
local first = string.sub(message, 1, 1)
|
|
10183
|
+
if first == "{" or first == " " or first == "\t" or first == "\r" or first == "\n" then
|
|
10184
|
+
local event = decodeMessage(message)
|
|
10185
|
+
if event ~= nil then
|
|
10186
|
+
streamSkipLf = false
|
|
10187
|
+
table.insert(events, event)
|
|
10188
|
+
return events
|
|
10189
|
+
end
|
|
10190
|
+
if first == "{" then
|
|
10191
|
+
return events
|
|
10192
|
+
end
|
|
10193
|
+
end
|
|
10194
|
+
if string.sub(message, 1, 5) == "data:" and (string.find(message, "[\r\n]")) == nil then
|
|
10195
|
+
local event = decodeMessage(string.sub(message, 6))
|
|
10196
|
+
if event ~= nil then
|
|
10197
|
+
streamSkipLf = false
|
|
10198
|
+
table.insert(events, event)
|
|
10199
|
+
return events
|
|
10200
|
+
end
|
|
10201
|
+
end
|
|
10202
|
+
end
|
|
10203
|
+
local offset = 1
|
|
10204
|
+
while offset <= messageBytes do
|
|
10205
|
+
if streamSkipLf then
|
|
10206
|
+
streamSkipLf = false
|
|
10207
|
+
local _message = message
|
|
10208
|
+
local _offset = offset
|
|
10209
|
+
local _offset_1 = offset
|
|
10210
|
+
if string.sub(_message, _offset, _offset_1) == "\n" then
|
|
10211
|
+
offset += 1
|
|
10212
|
+
continue
|
|
10213
|
+
end
|
|
10214
|
+
end
|
|
10215
|
+
local _message = message
|
|
10216
|
+
local _offset = offset
|
|
10217
|
+
local newline = string.find(_message, "[\r\n]", _offset)
|
|
10218
|
+
local lineEnd = if newline == nil then messageBytes + 1 else newline
|
|
10219
|
+
local partBytes = lineEnd - offset
|
|
10220
|
+
if partBytes > 0 then
|
|
10221
|
+
streamLineHasContent = true
|
|
10222
|
+
end
|
|
10223
|
+
if not streamDiscardingFrame then
|
|
10224
|
+
streamFrameBytes += partBytes + (if newline == nil then 0 else 1)
|
|
10225
|
+
if streamFrameBytes > MAX_STREAM_FRAME_BYTES then
|
|
10226
|
+
-- Keep scanning delimiters but retain no oversized frame bytes.
|
|
10227
|
+
streamDiscardingFrame = true
|
|
10228
|
+
streamLineParts = {}
|
|
10229
|
+
streamDataLines = {}
|
|
10230
|
+
elseif partBytes > 0 then
|
|
10231
|
+
-- Join only on a line boundary, not every callback (large scripts
|
|
10232
|
+
-- can arrive in many fragments).
|
|
10233
|
+
local _streamLineParts = streamLineParts
|
|
10234
|
+
local _message_1 = message
|
|
10235
|
+
local _offset_1 = offset
|
|
10236
|
+
local _arg1 = lineEnd - 1
|
|
10237
|
+
local _arg0 = string.sub(_message_1, _offset_1, _arg1)
|
|
10238
|
+
table.insert(_streamLineParts, _arg0)
|
|
10239
|
+
end
|
|
10240
|
+
end
|
|
10241
|
+
if newline == nil then
|
|
10242
|
+
break
|
|
10243
|
+
end
|
|
10244
|
+
finishStreamLine(events)
|
|
10245
|
+
streamSkipLf = string.sub(message, newline, newline) == "\r"
|
|
10246
|
+
offset = newline + 1
|
|
10247
|
+
end
|
|
10248
|
+
return events
|
|
10249
|
+
end
|
|
10138
10250
|
local function closeCurrentStream()
|
|
10139
10251
|
local current = streamClient
|
|
10140
10252
|
streamClient = nil
|
|
10253
|
+
resetStreamFrame()
|
|
10254
|
+
streamSkipLf = false
|
|
10141
10255
|
for _, connection in streamConnections do
|
|
10142
10256
|
connection:Disconnect()
|
|
10143
10257
|
end
|
|
@@ -10616,30 +10730,33 @@ function connect(expectedGeneration)
|
|
|
10616
10730
|
if not active or generation ~= expectedGeneration or streamClient ~= createdClient then
|
|
10617
10731
|
return nil
|
|
10618
10732
|
end
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
|
|
10733
|
+
for _, event in decodeMessages(message) do
|
|
10734
|
+
-- Status callbacks can refresh/stop the stream. The remainder of
|
|
10735
|
+
-- that callback belongs to the old connection, not its successor.
|
|
10736
|
+
if not active or generation ~= expectedGeneration or streamClient ~= createdClient then
|
|
10737
|
+
return nil
|
|
10738
|
+
end
|
|
10739
|
+
lastValidEventAt = tick()
|
|
10740
|
+
if event.kind == "heartbeat" then
|
|
10741
|
+
invokeCallback("event stream heartbeat", function()
|
|
10742
|
+
return currentOptions.onHeartbeat(event.timestamp)
|
|
10743
|
+
end)
|
|
10744
|
+
continue
|
|
10745
|
+
end
|
|
10746
|
+
if event.kind == "cancel" then
|
|
10747
|
+
cancelRequest(event)
|
|
10748
|
+
continue
|
|
10749
|
+
end
|
|
10750
|
+
if event.kind == "request" then
|
|
10751
|
+
dispatchRequest(event)
|
|
10752
|
+
continue
|
|
10753
|
+
end
|
|
10754
|
+
invokeCallback("event stream status", function()
|
|
10755
|
+
return currentOptions.onStatus(event)
|
|
10627
10756
|
end)
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
cancelRequest(event)
|
|
10632
|
-
return nil
|
|
10633
|
-
end
|
|
10634
|
-
if event.kind == "request" then
|
|
10635
|
-
dispatchRequest(event)
|
|
10636
|
-
return nil
|
|
10637
|
-
end
|
|
10638
|
-
invokeCallback("event stream status", function()
|
|
10639
|
-
return currentOptions.onStatus(event)
|
|
10640
|
-
end)
|
|
10641
|
-
if not event.knownPeer then
|
|
10642
|
-
refresh()
|
|
10757
|
+
if not event.knownPeer then
|
|
10758
|
+
refresh()
|
|
10759
|
+
end
|
|
10643
10760
|
end
|
|
10644
10761
|
end), createdClient.Error:Connect(function(statusCode, message)
|
|
10645
10762
|
if not active or generation ~= expectedGeneration or streamClient ~= createdClient then
|