@chrrxs/robloxstudio-mcp-inspector 3.0.3 → 3.0.4
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 +203 -80
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +585 -321
package/dist/index.js
CHANGED
|
@@ -1971,6 +1971,8 @@ var DUPLICATE_TAKEOVER_MS = 3e3;
|
|
|
1971
1971
|
var INSTANCE_ALIAS_TTL_MS = 5 * 60 * 1e3;
|
|
1972
1972
|
var ACCEPTED_REQUEST_TOMBSTONE_TTL_MS = 6e4;
|
|
1973
1973
|
var MAX_ACCEPTED_REQUEST_TOMBSTONES = 4096;
|
|
1974
|
+
var CANCELLATION_TOMBSTONE_TTL_MS = 6e4;
|
|
1975
|
+
var MAX_CANCELLATION_TOMBSTONES = 4096;
|
|
1974
1976
|
function publishedInstanceId(placeId) {
|
|
1975
1977
|
if (placeId === void 0 || !Number.isFinite(placeId) || placeId <= 0)
|
|
1976
1978
|
return void 0;
|
|
@@ -1979,6 +1981,7 @@ function publishedInstanceId(placeId) {
|
|
|
1979
1981
|
var BridgeService = class {
|
|
1980
1982
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
1981
1983
|
acceptedRequestIds = /* @__PURE__ */ new Map();
|
|
1984
|
+
pendingCancellations = /* @__PURE__ */ new Map();
|
|
1982
1985
|
// Keyed by pluginSessionId (the per-plugin GUID).
|
|
1983
1986
|
instances = /* @__PURE__ */ new Map();
|
|
1984
1987
|
instanceAliases = /* @__PURE__ */ new Map();
|
|
@@ -2037,6 +2040,21 @@ var BridgeService = class {
|
|
|
2037
2040
|
}
|
|
2038
2041
|
}
|
|
2039
2042
|
}
|
|
2043
|
+
notifyRequestCancelled(request, reason) {
|
|
2044
|
+
const physicalSessionId = request.lastDeliveryPhysicalSessionId;
|
|
2045
|
+
if (!physicalSessionId || this.pendingCancellations.has(request.id))
|
|
2046
|
+
return;
|
|
2047
|
+
const now = Date.now();
|
|
2048
|
+
this.prunePendingCancellations(now);
|
|
2049
|
+
this.pendingCancellations.set(request.id, {
|
|
2050
|
+
requestId: request.id,
|
|
2051
|
+
reason,
|
|
2052
|
+
physicalSessionId,
|
|
2053
|
+
createdAt: now
|
|
2054
|
+
});
|
|
2055
|
+
this.prunePendingCancellations(now);
|
|
2056
|
+
this.notifyRequestAvailable(physicalSessionId);
|
|
2057
|
+
}
|
|
2040
2058
|
physicalSessionsForTarget(targetInstanceId, targetRole) {
|
|
2041
2059
|
const physicalSessionIds = /* @__PURE__ */ new Set();
|
|
2042
2060
|
for (const instance of this.instances.values()) {
|
|
@@ -2196,11 +2214,10 @@ var BridgeService = class {
|
|
|
2196
2214
|
}
|
|
2197
2215
|
for (const child of logicalChildren)
|
|
2198
2216
|
this.unregisterInstance(child.pluginSessionId);
|
|
2199
|
-
for (const
|
|
2217
|
+
for (const req of Array.from(this.pendingRequests.values())) {
|
|
2200
2218
|
const stillHasHandler = Array.from(this.instances.values()).some((i) => i.instanceId === req.targetInstanceId && i.role === req.targetRole);
|
|
2201
2219
|
if (!stillHasHandler) {
|
|
2202
|
-
|
|
2203
|
-
this.pendingRequests.delete(id);
|
|
2220
|
+
this.removePendingRequest(req);
|
|
2204
2221
|
req.reject(new Error(`Target (${req.targetInstanceId}, ${req.targetRole}) disconnected`));
|
|
2205
2222
|
}
|
|
2206
2223
|
}
|
|
@@ -2390,16 +2407,21 @@ var BridgeService = class {
|
|
|
2390
2407
|
const onlyInstanceId = distinctInstanceIds.values().next().value;
|
|
2391
2408
|
return this.resolveTarget({ instance_id: onlyInstanceId, target });
|
|
2392
2409
|
}
|
|
2393
|
-
async sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs = this.requestTimeout) {
|
|
2410
|
+
async sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs = this.requestTimeout, signal) {
|
|
2394
2411
|
const requestId = randomUUID();
|
|
2395
2412
|
const effectiveTimeoutMs = Math.max(1, timeoutMs);
|
|
2413
|
+
if (signal?.aborted)
|
|
2414
|
+
throw new Error("Request aborted");
|
|
2396
2415
|
return new Promise((resolve5, reject) => {
|
|
2397
|
-
const
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2416
|
+
const cancelPending = (reason, error) => {
|
|
2417
|
+
const pending = this.pendingRequests.get(requestId);
|
|
2418
|
+
if (!pending || !this.removePendingRequest(pending))
|
|
2419
|
+
return;
|
|
2420
|
+
this.notifyRequestCancelled(pending, reason);
|
|
2421
|
+
pending.reject(error);
|
|
2422
|
+
};
|
|
2423
|
+
const timeoutId = setTimeout(() => cancelPending("timeout", new Error("Request timeout")), effectiveTimeoutMs);
|
|
2424
|
+
const abortListener = () => cancelPending("aborted", new Error("Request aborted"));
|
|
2403
2425
|
const request = {
|
|
2404
2426
|
id: requestId,
|
|
2405
2427
|
endpoint,
|
|
@@ -2410,14 +2432,31 @@ var BridgeService = class {
|
|
|
2410
2432
|
resolve: resolve5,
|
|
2411
2433
|
reject,
|
|
2412
2434
|
timeoutId,
|
|
2413
|
-
timeoutMs: effectiveTimeoutMs
|
|
2435
|
+
timeoutMs: effectiveTimeoutMs,
|
|
2436
|
+
abortSignal: signal,
|
|
2437
|
+
abortListener
|
|
2414
2438
|
};
|
|
2415
2439
|
this.pendingRequests.set(requestId, request);
|
|
2416
|
-
|
|
2417
|
-
|
|
2440
|
+
signal?.addEventListener("abort", abortListener, { once: true });
|
|
2441
|
+
if (signal?.aborted)
|
|
2442
|
+
abortListener();
|
|
2443
|
+
if (this.pendingRequests.has(requestId)) {
|
|
2444
|
+
for (const physicalSessionId of this.physicalSessionsForTarget(targetInstanceId, targetRole)) {
|
|
2445
|
+
this.notifyRequestAvailable(physicalSessionId);
|
|
2446
|
+
}
|
|
2418
2447
|
}
|
|
2419
2448
|
});
|
|
2420
2449
|
}
|
|
2450
|
+
removePendingRequest(request) {
|
|
2451
|
+
if (this.pendingRequests.get(request.id) !== request)
|
|
2452
|
+
return false;
|
|
2453
|
+
clearTimeout(request.timeoutId);
|
|
2454
|
+
if (request.abortSignal && request.abortListener) {
|
|
2455
|
+
request.abortSignal.removeEventListener("abort", request.abortListener);
|
|
2456
|
+
}
|
|
2457
|
+
this.pendingRequests.delete(request.id);
|
|
2458
|
+
return true;
|
|
2459
|
+
}
|
|
2421
2460
|
claimNextRequestForPhysical(physicalSessionId, claimOwner) {
|
|
2422
2461
|
let oldestRequest;
|
|
2423
2462
|
let logicalSessionId = "";
|
|
@@ -2441,14 +2480,27 @@ var BridgeService = class {
|
|
|
2441
2480
|
if (!oldestRequest)
|
|
2442
2481
|
return null;
|
|
2443
2482
|
oldestRequest.claimOwner = claimOwner;
|
|
2483
|
+
oldestRequest.lastDeliveryPhysicalSessionId = physicalSessionId;
|
|
2444
2484
|
return {
|
|
2445
2485
|
requestId: oldestRequest.id,
|
|
2446
2486
|
logicalSessionId,
|
|
2447
2487
|
target: oldestRequest.targetRole,
|
|
2448
2488
|
endpoint: oldestRequest.endpoint,
|
|
2449
|
-
data: oldestRequest.data
|
|
2489
|
+
data: oldestRequest.data,
|
|
2490
|
+
remainingMs: Math.max(1, oldestRequest.timeoutMs - (Date.now() - oldestRequest.timestamp))
|
|
2450
2491
|
};
|
|
2451
2492
|
}
|
|
2493
|
+
claimNextCancellationForPhysical(physicalSessionId, claimOwner) {
|
|
2494
|
+
this.prunePendingCancellations(Date.now());
|
|
2495
|
+
for (const cancellation of this.pendingCancellations.values()) {
|
|
2496
|
+
if (cancellation.physicalSessionId !== physicalSessionId || cancellation.claimOwner !== void 0) {
|
|
2497
|
+
continue;
|
|
2498
|
+
}
|
|
2499
|
+
cancellation.claimOwner = claimOwner;
|
|
2500
|
+
return { requestId: cancellation.requestId, reason: cancellation.reason };
|
|
2501
|
+
}
|
|
2502
|
+
return null;
|
|
2503
|
+
}
|
|
2452
2504
|
releaseDeliveryClaims(claimOwner) {
|
|
2453
2505
|
const physicalSessionIds = /* @__PURE__ */ new Set();
|
|
2454
2506
|
for (const request of this.pendingRequests.values()) {
|
|
@@ -2459,6 +2511,12 @@ var BridgeService = class {
|
|
|
2459
2511
|
physicalSessionIds.add(physicalSessionId);
|
|
2460
2512
|
}
|
|
2461
2513
|
}
|
|
2514
|
+
for (const cancellation of this.pendingCancellations.values()) {
|
|
2515
|
+
if (cancellation.claimOwner !== claimOwner)
|
|
2516
|
+
continue;
|
|
2517
|
+
cancellation.claimOwner = void 0;
|
|
2518
|
+
physicalSessionIds.add(cancellation.physicalSessionId);
|
|
2519
|
+
}
|
|
2462
2520
|
for (const physicalSessionId of physicalSessionIds) {
|
|
2463
2521
|
this.notifyRequestAvailable(physicalSessionId);
|
|
2464
2522
|
}
|
|
@@ -2476,8 +2534,7 @@ var BridgeService = class {
|
|
|
2476
2534
|
if (!request) {
|
|
2477
2535
|
return this.acceptedRequestIds.has(requestId) ? "already_settled" : "unknown";
|
|
2478
2536
|
}
|
|
2479
|
-
|
|
2480
|
-
this.pendingRequests.delete(requestId);
|
|
2537
|
+
this.removePendingRequest(request);
|
|
2481
2538
|
this.acceptedRequestIds.set(requestId, now);
|
|
2482
2539
|
this.pruneAcceptedRequestIds(now);
|
|
2483
2540
|
settle(request);
|
|
@@ -2496,22 +2553,34 @@ var BridgeService = class {
|
|
|
2496
2553
|
this.acceptedRequestIds.delete(oldestRequestId);
|
|
2497
2554
|
}
|
|
2498
2555
|
}
|
|
2556
|
+
prunePendingCancellations(now) {
|
|
2557
|
+
for (const [requestId, cancellation] of this.pendingCancellations) {
|
|
2558
|
+
if (now - cancellation.createdAt < CANCELLATION_TOMBSTONE_TTL_MS)
|
|
2559
|
+
break;
|
|
2560
|
+
this.pendingCancellations.delete(requestId);
|
|
2561
|
+
}
|
|
2562
|
+
while (this.pendingCancellations.size > MAX_CANCELLATION_TOMBSTONES) {
|
|
2563
|
+
const oldestRequestId = this.pendingCancellations.keys().next().value;
|
|
2564
|
+
if (oldestRequestId === void 0)
|
|
2565
|
+
break;
|
|
2566
|
+
this.pendingCancellations.delete(oldestRequestId);
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2499
2569
|
cleanupOldRequests() {
|
|
2500
2570
|
const now = Date.now();
|
|
2501
|
-
for (const
|
|
2502
|
-
if (now - request.timestamp > request.timeoutMs) {
|
|
2503
|
-
|
|
2504
|
-
this.pendingRequests.delete(id);
|
|
2571
|
+
for (const request of this.pendingRequests.values()) {
|
|
2572
|
+
if (now - request.timestamp > request.timeoutMs && this.removePendingRequest(request)) {
|
|
2573
|
+
this.notifyRequestCancelled(request, "timeout");
|
|
2505
2574
|
request.reject(new Error("Request timeout"));
|
|
2506
2575
|
}
|
|
2507
2576
|
}
|
|
2508
2577
|
}
|
|
2509
2578
|
clearAllPendingRequests() {
|
|
2510
|
-
for (const
|
|
2511
|
-
|
|
2579
|
+
for (const request of Array.from(this.pendingRequests.values())) {
|
|
2580
|
+
this.removePendingRequest(request);
|
|
2512
2581
|
request.reject(new Error("Connection closed"));
|
|
2513
2582
|
}
|
|
2514
|
-
this.
|
|
2583
|
+
this.pendingCancellations.clear();
|
|
2515
2584
|
}
|
|
2516
2585
|
};
|
|
2517
2586
|
|
|
@@ -5536,9 +5605,9 @@ function createToolServer(options) {
|
|
|
5536
5605
|
inputSchema: fromJsonSchema(publicDefinition.inputSchema),
|
|
5537
5606
|
...publicDefinition.outputSchema ? { outputSchema: fromJsonSchema(publicDefinition.outputSchema) } : {},
|
|
5538
5607
|
annotations: publicDefinition.annotations
|
|
5539
|
-
}, async (args) => {
|
|
5608
|
+
}, async (args, context) => {
|
|
5540
5609
|
try {
|
|
5541
|
-
const raw = await options.invoke(options.getTools(), definition.name, args);
|
|
5610
|
+
const raw = await options.invoke(options.getTools(), definition.name, args, { signal: context.mcpReq.signal });
|
|
5542
5611
|
return normalizeToolResult(raw, options.era);
|
|
5543
5612
|
} catch (error) {
|
|
5544
5613
|
return normalizeToolResult({
|
|
@@ -5723,6 +5792,13 @@ var SseStudioTransport = class {
|
|
|
5723
5792
|
return;
|
|
5724
5793
|
}
|
|
5725
5794
|
}
|
|
5795
|
+
while (!stream.closed && !stream.blocked) {
|
|
5796
|
+
const cancellation = this.queue.claimNextCancellationForPhysical(stream.physicalSessionId, stream.claimOwner);
|
|
5797
|
+
if (!cancellation)
|
|
5798
|
+
break;
|
|
5799
|
+
if (!this.write(stream, { kind: "cancel", ...cancellation }))
|
|
5800
|
+
return;
|
|
5801
|
+
}
|
|
5726
5802
|
while (!stream.closed && !stream.blocked) {
|
|
5727
5803
|
const request = this.queue.claimNextRequestForPhysical(stream.physicalSessionId, stream.claimOwner);
|
|
5728
5804
|
if (!request)
|
|
@@ -5733,7 +5809,8 @@ var SseStudioTransport = class {
|
|
|
5733
5809
|
logicalSessionId: request.logicalSessionId,
|
|
5734
5810
|
target: request.target,
|
|
5735
5811
|
endpoint: request.endpoint,
|
|
5736
|
-
data: request.data === void 0 ? null : request.data
|
|
5812
|
+
data: request.data === void 0 ? null : request.data,
|
|
5813
|
+
remainingMs: request.remainingMs
|
|
5737
5814
|
};
|
|
5738
5815
|
if (!this.write(stream, event))
|
|
5739
5816
|
return;
|
|
@@ -5834,7 +5911,7 @@ var TOOL_HANDLERS = {
|
|
|
5834
5911
|
get_instance_properties: (tools, body) => tools.getInstanceProperties(body.instancePath, body.excludeSource, body.instance_id),
|
|
5835
5912
|
get_project_structure: (tools, body) => tools.getProjectStructure(body.path, body.maxDepth, body.scriptsOnly, body.instance_id),
|
|
5836
5913
|
set_properties: (tools, body) => tools.setProperties(body.instancePath, body.properties, body.instance_id),
|
|
5837
|
-
grep_scripts: (tools, body) => tools.grepScripts(body.pattern, {
|
|
5914
|
+
grep_scripts: (tools, body, context) => tools.grepScripts(body.pattern, {
|
|
5838
5915
|
caseSensitive: body.caseSensitive,
|
|
5839
5916
|
usePattern: body.usePattern,
|
|
5840
5917
|
contextLines: body.contextLines,
|
|
@@ -5843,7 +5920,7 @@ var TOOL_HANDLERS = {
|
|
|
5843
5920
|
filesOnly: body.filesOnly,
|
|
5844
5921
|
path: body.path,
|
|
5845
5922
|
classFilter: body.classFilter
|
|
5846
|
-
}, body.instance_id),
|
|
5923
|
+
}, body.instance_id, context?.signal),
|
|
5847
5924
|
get_script_source: (tools, body) => {
|
|
5848
5925
|
const { startLine, endLine } = optionalLineRange(body, "get_script_source");
|
|
5849
5926
|
return tools.getScriptSource(body.instancePath, startLine, endLine, body.instance_id);
|
|
@@ -6273,30 +6350,45 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
6273
6350
|
res.json({ success: true, disposition });
|
|
6274
6351
|
});
|
|
6275
6352
|
app.post("/proxy", async (req, res) => {
|
|
6276
|
-
const { endpoint, data, targetInstanceId, targetRole, proxyInstanceId } = req.body;
|
|
6353
|
+
const { endpoint, data, targetInstanceId, targetRole, proxyInstanceId, timeoutMs } = req.body;
|
|
6277
6354
|
if (!endpoint || !targetInstanceId || !targetRole) {
|
|
6278
6355
|
res.status(400).json({ error: "endpoint, targetInstanceId, and targetRole are required" });
|
|
6279
6356
|
return;
|
|
6280
6357
|
}
|
|
6358
|
+
if (timeoutMs !== void 0 && (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 3e5)) {
|
|
6359
|
+
res.status(400).json({ error: "timeoutMs must be an integer between 1 and 300000" });
|
|
6360
|
+
return;
|
|
6361
|
+
}
|
|
6281
6362
|
if (proxyInstanceId) {
|
|
6282
6363
|
proxyInstances.add(proxyInstanceId);
|
|
6283
6364
|
}
|
|
6365
|
+
const controller = new AbortController();
|
|
6366
|
+
const abort = () => controller.abort();
|
|
6367
|
+
req.once("aborted", abort);
|
|
6368
|
+
res.once("close", abort);
|
|
6284
6369
|
try {
|
|
6285
|
-
const response = await bridge.sendRequest(endpoint, data, targetInstanceId, targetRole);
|
|
6370
|
+
const response = await bridge.sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs, controller.signal);
|
|
6286
6371
|
res.json({ response });
|
|
6287
|
-
} catch (
|
|
6288
|
-
res.
|
|
6372
|
+
} catch (error) {
|
|
6373
|
+
if (!res.headersSent && !res.destroyed) {
|
|
6374
|
+
res.status(500).json({
|
|
6375
|
+
error: error instanceof Error ? error.message : "Proxy request failed"
|
|
6376
|
+
});
|
|
6377
|
+
}
|
|
6378
|
+
} finally {
|
|
6379
|
+
req.removeListener("aborted", abort);
|
|
6380
|
+
res.removeListener("close", abort);
|
|
6289
6381
|
}
|
|
6290
6382
|
});
|
|
6291
6383
|
const mcpHandler = serverConfig ? createToolHttpHandler({
|
|
6292
6384
|
config: serverConfig,
|
|
6293
6385
|
getTools: () => tools,
|
|
6294
6386
|
allowedTools,
|
|
6295
|
-
invoke: async (currentTools, name, args) => {
|
|
6387
|
+
invoke: async (currentTools, name, args, context) => {
|
|
6296
6388
|
const handler = TOOL_HANDLERS[name];
|
|
6297
6389
|
if (!handler)
|
|
6298
6390
|
throw new Error(`Unknown tool: ${name}`);
|
|
6299
|
-
return handler(currentTools, args);
|
|
6391
|
+
return handler(currentTools, args, context);
|
|
6300
6392
|
}
|
|
6301
6393
|
}) : void 0;
|
|
6302
6394
|
const nodeMcpHandler = mcpHandler ? toNodeHandler(mcpHandler) : void 0;
|
|
@@ -6341,25 +6433,21 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
6341
6433
|
};
|
|
6342
6434
|
return app;
|
|
6343
6435
|
}
|
|
6344
|
-
function listenWithRetry(app, host, startPort, maxAttempts = 5) {
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
console.error(`Port ${port} in use, trying next...`);
|
|
6355
|
-
continue;
|
|
6356
|
-
}
|
|
6357
|
-
reject(err2);
|
|
6358
|
-
return;
|
|
6436
|
+
async function listenWithRetry(app, host, startPort, maxAttempts = 5) {
|
|
6437
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
6438
|
+
const port = startPort + i;
|
|
6439
|
+
try {
|
|
6440
|
+
const server = await bindPort(app, host, port);
|
|
6441
|
+
return { server, port };
|
|
6442
|
+
} catch (error) {
|
|
6443
|
+
if (error !== null && typeof error === "object" && "code" in error && error.code === "EADDRINUSE") {
|
|
6444
|
+
console.error(`Port ${port} in use, trying next...`);
|
|
6445
|
+
continue;
|
|
6359
6446
|
}
|
|
6447
|
+
throw error;
|
|
6360
6448
|
}
|
|
6361
|
-
|
|
6362
|
-
});
|
|
6449
|
+
}
|
|
6450
|
+
throw new Error(`All ports ${startPort}-${startPort + maxAttempts - 1} are in use. Stop some MCP server instances and retry.`);
|
|
6363
6451
|
}
|
|
6364
6452
|
function bindPort(app, host, port) {
|
|
6365
6453
|
return new Promise((resolve5, reject) => {
|
|
@@ -6382,9 +6470,9 @@ var StudioHttpClient = class {
|
|
|
6382
6470
|
constructor(bridge) {
|
|
6383
6471
|
this.bridge = bridge;
|
|
6384
6472
|
}
|
|
6385
|
-
async request(endpoint, data, targetInstanceId, targetRole, timeoutMs) {
|
|
6473
|
+
async request(endpoint, data, targetInstanceId, targetRole, timeoutMs, signal) {
|
|
6386
6474
|
try {
|
|
6387
|
-
const response = await this.bridge.sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs);
|
|
6475
|
+
const response = await this.bridge.sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs, signal);
|
|
6388
6476
|
return response;
|
|
6389
6477
|
} catch (error) {
|
|
6390
6478
|
if (error instanceof Error && error.message === "Request timeout") {
|
|
@@ -9199,6 +9287,8 @@ var MAX_SEARCH_ASSET_DESCRIPTION_LENGTH = 240;
|
|
|
9199
9287
|
var ROBLOX_CREATOR_USER_ID = 1;
|
|
9200
9288
|
var MAX_DEVICE_MATRIX_ENTRIES = 6;
|
|
9201
9289
|
var MAX_NETWORK_PACKET_LOSS_PERCENT = 0.5;
|
|
9290
|
+
var GREP_SCRIPTS_TIMEOUT_MS = 12e4;
|
|
9291
|
+
var MAX_GREP_PATTERN_UTF8_BYTES = 4096;
|
|
9202
9292
|
var STUDIO_ASSISTANT_SOURCE_IMAGE_LABEL = "Studio Assistant Source Image";
|
|
9203
9293
|
var CREATOR_STORE_SEARCH_TYPES = /* @__PURE__ */ new Set([
|
|
9204
9294
|
"Audio",
|
|
@@ -10025,7 +10115,7 @@ var RobloxStudioTools = class {
|
|
|
10025
10115
|
// ambiguous, missing, or asks for fanout on a non-fanout-capable tool —
|
|
10026
10116
|
// the MCP transport layer surfaces it as a structured error result so
|
|
10027
10117
|
// the LLM can recover via the embedded data.instances list.
|
|
10028
|
-
async _callSingle(endpoint, data, target, instance_id, timeoutMs) {
|
|
10118
|
+
async _callSingle(endpoint, data, target, instance_id, timeoutMs, signal) {
|
|
10029
10119
|
const r = this.bridge.resolveTarget({ instance_id, target });
|
|
10030
10120
|
if (!r.ok)
|
|
10031
10121
|
throw new RoutingFailure(r.error);
|
|
@@ -10039,7 +10129,10 @@ var RobloxStudioTools = class {
|
|
|
10039
10129
|
}
|
|
10040
10130
|
});
|
|
10041
10131
|
}
|
|
10042
|
-
|
|
10132
|
+
if (signal === void 0) {
|
|
10133
|
+
return this.client.request(endpoint, data, r.targetInstanceId, r.targetRole, timeoutMs);
|
|
10134
|
+
}
|
|
10135
|
+
return this.client.request(endpoint, data, r.targetInstanceId, r.targetRole, timeoutMs, signal);
|
|
10043
10136
|
}
|
|
10044
10137
|
// Resolves which connected place a tool should target and whether a playtest
|
|
10045
10138
|
// CLIENT peer is present on it. Used by capture/input to auto-route to the
|
|
@@ -10539,14 +10632,17 @@ var RobloxStudioTools = class {
|
|
|
10539
10632
|
]
|
|
10540
10633
|
};
|
|
10541
10634
|
}
|
|
10542
|
-
async grepScripts(pattern, options, instance_id) {
|
|
10635
|
+
async grepScripts(pattern, options, instance_id, signal) {
|
|
10543
10636
|
if (!pattern) {
|
|
10544
10637
|
throw new Error("Pattern is required for grep_scripts");
|
|
10545
10638
|
}
|
|
10639
|
+
if (Buffer.byteLength(pattern, "utf8") > MAX_GREP_PATTERN_UTF8_BYTES) {
|
|
10640
|
+
throw new Error(`Pattern must not exceed ${MAX_GREP_PATTERN_UTF8_BYTES} UTF-8 bytes`);
|
|
10641
|
+
}
|
|
10546
10642
|
const response = await this._callSingle("/api/grep-scripts", {
|
|
10547
10643
|
pattern,
|
|
10548
10644
|
...options ?? {}
|
|
10549
|
-
}, void 0, instance_id);
|
|
10645
|
+
}, void 0, instance_id, GREP_SCRIPTS_TIMEOUT_MS, signal);
|
|
10550
10646
|
return {
|
|
10551
10647
|
content: [
|
|
10552
10648
|
{
|
|
@@ -12988,6 +13084,7 @@ var RobloxStudioTools = class {
|
|
|
12988
13084
|
|
|
12989
13085
|
// ../core/dist/proxy-bridge-service.js
|
|
12990
13086
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
13087
|
+
var PROXY_RESPONSE_GRACE_MS = 5e3;
|
|
12991
13088
|
var ProxyBridgeService = class _ProxyBridgeService extends BridgeService {
|
|
12992
13089
|
primaryBaseUrl;
|
|
12993
13090
|
authToken;
|
|
@@ -13063,9 +13160,20 @@ var ProxyBridgeService = class _ProxyBridgeService extends BridgeService {
|
|
|
13063
13160
|
this.refreshTimer = void 0;
|
|
13064
13161
|
}
|
|
13065
13162
|
}
|
|
13066
|
-
async sendRequest(endpoint, data, targetInstanceId, targetRole) {
|
|
13163
|
+
async sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs = this.proxyRequestTimeout, signal) {
|
|
13164
|
+
if (signal?.aborted)
|
|
13165
|
+
throw new Error("Request aborted");
|
|
13067
13166
|
const controller = new AbortController();
|
|
13068
|
-
const
|
|
13167
|
+
const effectiveTimeoutMs = Math.max(1, timeoutMs);
|
|
13168
|
+
let timedOut = false;
|
|
13169
|
+
const abortFromCaller = () => controller.abort();
|
|
13170
|
+
signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
13171
|
+
if (signal?.aborted)
|
|
13172
|
+
controller.abort();
|
|
13173
|
+
const timeoutId = setTimeout(() => {
|
|
13174
|
+
timedOut = true;
|
|
13175
|
+
controller.abort();
|
|
13176
|
+
}, effectiveTimeoutMs + PROXY_RESPONSE_GRACE_MS);
|
|
13069
13177
|
try {
|
|
13070
13178
|
const response = await fetch(`${this.primaryBaseUrl}/proxy`, {
|
|
13071
13179
|
method: "POST",
|
|
@@ -13075,26 +13183,34 @@ var ProxyBridgeService = class _ProxyBridgeService extends BridgeService {
|
|
|
13075
13183
|
data,
|
|
13076
13184
|
targetInstanceId,
|
|
13077
13185
|
targetRole,
|
|
13078
|
-
proxyInstanceId: this.proxyInstanceId
|
|
13186
|
+
proxyInstanceId: this.proxyInstanceId,
|
|
13187
|
+
timeoutMs: effectiveTimeoutMs
|
|
13079
13188
|
}),
|
|
13080
13189
|
signal: controller.signal
|
|
13081
13190
|
});
|
|
13082
|
-
clearTimeout(timeoutId);
|
|
13083
13191
|
if (!response.ok) {
|
|
13084
13192
|
const body = await response.text();
|
|
13085
13193
|
throw new Error(`Proxy request failed (${response.status}): ${body}`);
|
|
13086
13194
|
}
|
|
13087
13195
|
const result = await response.json();
|
|
13088
|
-
if (result.
|
|
13196
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
13197
|
+
throw new Error("Proxy returned an invalid response");
|
|
13198
|
+
}
|
|
13199
|
+
if ("error" in result && typeof result.error === "string" && result.error.length > 0) {
|
|
13089
13200
|
throw new Error(result.error);
|
|
13090
13201
|
}
|
|
13091
|
-
return result.response;
|
|
13092
|
-
} catch (
|
|
13093
|
-
|
|
13094
|
-
if (
|
|
13202
|
+
return "response" in result ? result.response : void 0;
|
|
13203
|
+
} catch (error) {
|
|
13204
|
+
const isAbortError = error instanceof Error ? error.name === "AbortError" : !!error && typeof error === "object" && "name" in error && error.name === "AbortError";
|
|
13205
|
+
if (isAbortError) {
|
|
13206
|
+
if (!timedOut && signal?.aborted)
|
|
13207
|
+
throw new Error("Request aborted");
|
|
13095
13208
|
throw new Error("Proxy request timeout");
|
|
13096
13209
|
}
|
|
13097
|
-
throw
|
|
13210
|
+
throw error;
|
|
13211
|
+
} finally {
|
|
13212
|
+
clearTimeout(timeoutId);
|
|
13213
|
+
signal?.removeEventListener("abort", abortFromCaller);
|
|
13098
13214
|
}
|
|
13099
13215
|
}
|
|
13100
13216
|
cleanupOldRequests() {
|
|
@@ -13190,11 +13306,11 @@ var RobloxStudioMCPServer = class {
|
|
|
13190
13306
|
getTools: () => this.tools,
|
|
13191
13307
|
allowedTools: this.allowedToolNames,
|
|
13192
13308
|
era: context.era,
|
|
13193
|
-
invoke: async (tools, name, args) => {
|
|
13309
|
+
invoke: async (tools, name, args, invocation) => {
|
|
13194
13310
|
const handler = TOOL_HANDLERS[name];
|
|
13195
13311
|
if (!handler)
|
|
13196
13312
|
throw new Error(`Unknown tool: ${name}`);
|
|
13197
|
-
return handler(tools, args);
|
|
13313
|
+
return handler(tools, args, invocation);
|
|
13198
13314
|
}
|
|
13199
13315
|
}), { onerror: (error) => console.error("[mcp:stdio]", error) });
|
|
13200
13316
|
console.error(`${this.config.name} v${this.config.version} running on stdio`);
|
|
@@ -13641,37 +13757,44 @@ var TOOL_DEFINITIONS = [
|
|
|
13641
13757
|
{
|
|
13642
13758
|
name: "grep_scripts",
|
|
13643
13759
|
category: "read",
|
|
13644
|
-
description: "Use to
|
|
13760
|
+
description: "Use to search script sources.",
|
|
13645
13761
|
inputSchema: {
|
|
13646
13762
|
type: "object",
|
|
13647
13763
|
properties: {
|
|
13648
13764
|
pattern: {
|
|
13649
13765
|
type: "string",
|
|
13650
|
-
|
|
13766
|
+
maxLength: 4096,
|
|
13767
|
+
description: "Literal or Lua pattern; max 4096 UTF-8 bytes."
|
|
13651
13768
|
},
|
|
13652
13769
|
caseSensitive: {
|
|
13653
13770
|
type: "boolean",
|
|
13654
|
-
description: "
|
|
13771
|
+
description: "Lua patterns are always case-sensitive."
|
|
13655
13772
|
},
|
|
13656
13773
|
usePattern: {
|
|
13657
13774
|
type: "boolean",
|
|
13658
|
-
description: "
|
|
13775
|
+
description: "Enable Lua pattern with top-level | (not PCRE)."
|
|
13659
13776
|
},
|
|
13660
13777
|
contextLines: {
|
|
13661
|
-
type: "
|
|
13662
|
-
|
|
13778
|
+
type: "integer",
|
|
13779
|
+
minimum: 0,
|
|
13780
|
+
maximum: 100,
|
|
13781
|
+
description: "Surrounding lines (0-100; default 0)."
|
|
13663
13782
|
},
|
|
13664
13783
|
maxResults: {
|
|
13665
|
-
type: "
|
|
13666
|
-
|
|
13784
|
+
type: "integer",
|
|
13785
|
+
minimum: 1,
|
|
13786
|
+
maximum: 1e4,
|
|
13787
|
+
description: "Default 100."
|
|
13667
13788
|
},
|
|
13668
13789
|
maxResultsPerScript: {
|
|
13669
|
-
type: "
|
|
13670
|
-
|
|
13790
|
+
type: "integer",
|
|
13791
|
+
minimum: 0,
|
|
13792
|
+
maximum: 1e4,
|
|
13793
|
+
description: "Per-script cap; 0 is unlimited."
|
|
13671
13794
|
},
|
|
13672
13795
|
filesOnly: {
|
|
13673
13796
|
type: "boolean",
|
|
13674
|
-
description: "
|
|
13797
|
+
description: "Paths only; default false."
|
|
13675
13798
|
},
|
|
13676
13799
|
path: {
|
|
13677
13800
|
type: "string",
|
package/package.json
CHANGED