@chrrxs/robloxstudio-mcp-inspector 3.0.0 → 3.0.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 +217 -51
- package/package.json +2 -2
- package/studio-plugin/MCPInspectorPlugin.rbxmx +332 -40
- package/studio-plugin/.Carbon.rbxm.lock +0 -0
- package/studio-plugin/Carbon.rbxm +0 -0
- package/studio-plugin/INSTALLATION.md +0 -170
- package/studio-plugin/MCPPlugin.rbxmx +0 -169759
- package/studio-plugin/default.project.json +0 -19
- package/studio-plugin/dev.project.json +0 -23
- package/studio-plugin/include/LibMP.lua +0 -156378
- package/studio-plugin/inspector-icon.png +0 -0
- package/studio-plugin/package-lock.json +0 -706
- package/studio-plugin/package.json +0 -19
- package/studio-plugin/plugin.json +0 -10
- package/studio-plugin/src/modules/AssetSanitizationPolicy.ts +0 -127
- package/studio-plugin/src/modules/ClientBroker.ts +0 -450
- package/studio-plugin/src/modules/Communication.ts +0 -601
- package/studio-plugin/src/modules/EvalBridges.ts +0 -255
- package/studio-plugin/src/modules/HttpDiagnostics.ts +0 -50
- package/studio-plugin/src/modules/LuauExec.ts +0 -403
- package/studio-plugin/src/modules/Recording.ts +0 -28
- package/studio-plugin/src/modules/RenderMonitor.ts +0 -60
- package/studio-plugin/src/modules/RuntimeLogBuffer.ts +0 -210
- package/studio-plugin/src/modules/ServerUrlSettings.ts +0 -117
- package/studio-plugin/src/modules/State.ts +0 -39
- package/studio-plugin/src/modules/StopPlayMonitor.ts +0 -267
- package/studio-plugin/src/modules/UI.ts +0 -597
- package/studio-plugin/src/modules/Utils.ts +0 -527
- package/studio-plugin/src/modules/handlers/AssetHandlers.ts +0 -391
- package/studio-plugin/src/modules/handlers/BreakpointHandlers.ts +0 -460
- package/studio-plugin/src/modules/handlers/CaptureHandlers.ts +0 -170
- package/studio-plugin/src/modules/handlers/EvalRuntimeHandlers.ts +0 -149
- package/studio-plugin/src/modules/handlers/GenerateModelHandlers.ts +0 -168
- package/studio-plugin/src/modules/handlers/InputHandlers.ts +0 -163
- package/studio-plugin/src/modules/handlers/LogHandlers.ts +0 -14
- package/studio-plugin/src/modules/handlers/MemoryHandlers.ts +0 -44
- package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +0 -96
- package/studio-plugin/src/modules/handlers/MicroProfilerHandlers.ts +0 -1263
- package/studio-plugin/src/modules/handlers/PropertyHandlers.ts +0 -62
- package/studio-plugin/src/modules/handlers/QueryHandlers.ts +0 -716
- package/studio-plugin/src/modules/handlers/SceneAnalysisHandlers.ts +0 -216
- package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +0 -531
- package/studio-plugin/src/modules/handlers/ScriptProfilerHandlers.ts +0 -386
- package/studio-plugin/src/modules/handlers/SerializationHandlers.ts +0 -172
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +0 -350
- package/studio-plugin/src/server/index.server.ts +0 -135
- package/studio-plugin/src/types/index.d.ts +0 -57
- package/studio-plugin/tsconfig.json +0 -20
package/dist/index.js
CHANGED
|
@@ -774,7 +774,6 @@ Tool descriptions explain selection. Input schemas explain arguments. This guide
|
|
|
774
774
|
- Use canonical DataModel paths returned by the tools. Paths usually start with game.
|
|
775
775
|
- Call get_connected_instances when more than one place may be connected. Pass the chosen row's id as instance_id on later calls.
|
|
776
776
|
- Use get_place_info for the active place identity and settings.
|
|
777
|
-
- Use get_selection only when the user's current Studio selection should set the scope.
|
|
778
777
|
|
|
779
778
|
## Discovery and edit work
|
|
780
779
|
|
|
@@ -783,6 +782,13 @@ Tool descriptions explain selection. Input schemas explain arguments. This guide
|
|
|
783
782
|
- Use execute_luau for custom traversal, bulk edits, and work that would otherwise need many tool calls. It runs through the Studio plugin context.
|
|
784
783
|
- Use set_properties when several known properties on one instance can be updated in one request.
|
|
785
784
|
|
|
785
|
+
## Selection and viewport
|
|
786
|
+
|
|
787
|
+
- Use selection with action=get when the user's Studio selection should define the scope.
|
|
788
|
+
- Use action=set with instance paths to replace, add to, or remove from the selection. An empty paths array in set mode clears it.
|
|
789
|
+
- Use action=view with a BasePart or Model path to frame it. The current viewing direction is preserved unless from or angleY overrides it. padding below 1 crops closer and above 1 pulls back.
|
|
790
|
+
- For visual proof, change the instance, frame it with selection, then call capture_screenshot.
|
|
791
|
+
|
|
786
792
|
## Script changes
|
|
787
793
|
|
|
788
794
|
- Read the relevant source with get_script_source before changing it.
|
|
@@ -1479,6 +1485,14 @@ function parseStudioProcessEnvironmentPatch(value) {
|
|
|
1479
1485
|
}
|
|
1480
1486
|
return { set, remove };
|
|
1481
1487
|
}
|
|
1488
|
+
function parseStudioWorkingDirectory(value) {
|
|
1489
|
+
if (value === void 0)
|
|
1490
|
+
return void 0;
|
|
1491
|
+
if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
|
|
1492
|
+
throw new Error("studio_working_directory must be a non-empty string without null characters.");
|
|
1493
|
+
}
|
|
1494
|
+
return value;
|
|
1495
|
+
}
|
|
1482
1496
|
function patchedProcessEnvironment(patch) {
|
|
1483
1497
|
const environment = { ...process.env };
|
|
1484
1498
|
for (const name of patch.remove ?? [])
|
|
@@ -1514,7 +1528,7 @@ function quoteWindowsCommandLineArg(value) {
|
|
|
1514
1528
|
}
|
|
1515
1529
|
return `${quoted}${"\\".repeat(backslashes * 2)}"`;
|
|
1516
1530
|
}
|
|
1517
|
-
function buildWindowsStudioStartScriptFromConvertedExe(windowsExe, args, processEnvironment) {
|
|
1531
|
+
function buildWindowsStudioStartScriptFromConvertedExe(windowsExe, args, processEnvironment, windowsWorkingDirectory) {
|
|
1518
1532
|
const environmentPatch = parseStudioProcessEnvironmentPatch(processEnvironment);
|
|
1519
1533
|
const commandLine = [windowsExe, ...args].map(quoteWindowsCommandLineArg).join(" ");
|
|
1520
1534
|
return [
|
|
@@ -1714,7 +1728,7 @@ public sealed class McpSuspendedStudio : IDisposable
|
|
|
1714
1728
|
throw new InvalidOperationException("Unexpected Studio process wait result: " + wait);
|
|
1715
1729
|
}
|
|
1716
1730
|
|
|
1717
|
-
public static McpSuspendedStudio Start(string application, string commandLine)
|
|
1731
|
+
public static McpSuspendedStudio Start(string application, string commandLine, string currentDirectory)
|
|
1718
1732
|
{
|
|
1719
1733
|
IntPtr jobHandle = CreateJobObjectW(IntPtr.Zero, null);
|
|
1720
1734
|
if (jobHandle == IntPtr.Zero)
|
|
@@ -1726,9 +1740,9 @@ public sealed class McpSuspendedStudio : IDisposable
|
|
|
1726
1740
|
startup.cb = Marshal.SizeOf(startup);
|
|
1727
1741
|
ProcessInformation created;
|
|
1728
1742
|
uint flags = CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB;
|
|
1729
|
-
bool started = CreateProcessW(application, new StringBuilder(commandLine), IntPtr.Zero, IntPtr.Zero, false, flags, IntPtr.Zero,
|
|
1743
|
+
bool started = CreateProcessW(application, new StringBuilder(commandLine), IntPtr.Zero, IntPtr.Zero, false, flags, IntPtr.Zero, currentDirectory, ref startup, out created);
|
|
1730
1744
|
if (!started && Marshal.GetLastWin32Error() == 5)
|
|
1731
|
-
started = CreateProcessW(application, new StringBuilder(commandLine), IntPtr.Zero, IntPtr.Zero, false, CREATE_SUSPENDED, IntPtr.Zero,
|
|
1745
|
+
started = CreateProcessW(application, new StringBuilder(commandLine), IntPtr.Zero, IntPtr.Zero, false, CREATE_SUSPENDED, IntPtr.Zero, currentDirectory, ref startup, out created);
|
|
1732
1746
|
if (!started)
|
|
1733
1747
|
throw new Win32Exception(Marshal.GetLastWin32Error(), "CreateProcessW failed");
|
|
1734
1748
|
try
|
|
@@ -1805,7 +1819,7 @@ public sealed class McpSuspendedStudio : IDisposable
|
|
|
1805
1819
|
}
|
|
1806
1820
|
}
|
|
1807
1821
|
'@`,
|
|
1808
|
-
`$launch = [McpSuspendedStudio]::Start(${powershellStringLiteral(windowsExe)}, ${powershellStringLiteral(commandLine)})`,
|
|
1822
|
+
`$launch = [McpSuspendedStudio]::Start(${powershellStringLiteral(windowsExe)}, ${powershellStringLiteral(commandLine)}, ${windowsWorkingDirectory === void 0 ? "$null" : powershellStringLiteral(windowsWorkingDirectory)})`,
|
|
1809
1823
|
"try {",
|
|
1810
1824
|
"[Console]::Out.WriteLine((ConvertTo-Json @{ pid = $launch.ProcessId; started = [string]$launch.StartedAtFileTime } -Compress)); [Console]::Out.Flush()",
|
|
1811
1825
|
"$accepted = $false",
|
|
@@ -1847,9 +1861,10 @@ function buildWindowsStudioStopScript(processId, startedAt) {
|
|
|
1847
1861
|
async function stopWindowsStudio(processId, startedAt) {
|
|
1848
1862
|
await powershellAsync(buildWindowsStudioStopScript(processId, startedAt));
|
|
1849
1863
|
}
|
|
1850
|
-
async function spawnWindowsStudio(exe, args, processEnvironment) {
|
|
1864
|
+
async function spawnWindowsStudio(exe, args, processEnvironment, studioWorkingDirectory) {
|
|
1851
1865
|
const windowsExe = await toStudioLaunchArgAsync(exe);
|
|
1852
|
-
const
|
|
1866
|
+
const windowsWorkingDirectory = studioWorkingDirectory ? await toStudioLaunchArgAsync(studioWorkingDirectory) : void 0;
|
|
1867
|
+
const script = buildWindowsStudioStartScriptFromConvertedExe(windowsExe, args, processEnvironment, windowsWorkingDirectory);
|
|
1853
1868
|
const launcher = spawn("powershell.exe", ["-NoProfile", "-Command", script], {
|
|
1854
1869
|
cwd: isWsl() && existsSync2("/mnt/c/Windows") ? "/mnt/c/Windows" : process.cwd(),
|
|
1855
1870
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2548,6 +2563,7 @@ var init_studio_instance_manager = __esm({
|
|
|
2548
2563
|
const initialSnapshot = await this.getProcessSnapshot(true);
|
|
2549
2564
|
await this.sweepRegistry(initialSnapshot);
|
|
2550
2565
|
const processEnvironment = parseStudioProcessEnvironmentPatch(options.processEnvironment);
|
|
2566
|
+
const studioWorkingDirectory = parseStudioWorkingDirectory(options.studioWorkingDirectory);
|
|
2551
2567
|
if (options.studioExecutable !== void 0 && (typeof options.studioExecutable !== "string" || options.studioExecutable.length === 0 || options.studioExecutable.includes("\0"))) {
|
|
2552
2568
|
throw new Error("studio_executable must be a non-empty string without null characters.");
|
|
2553
2569
|
}
|
|
@@ -2561,7 +2577,7 @@ var init_studio_instance_manager = __esm({
|
|
|
2561
2577
|
const exe = preparedOptions.studioExecutable ?? (this.processAdapter.resolveStudioExe ? await this.processAdapter.resolveStudioExe() : await resolveStudioExeAsync());
|
|
2562
2578
|
const args = await Promise.all(buildStudioLaunchArgs(preparedOptions).map(toStudioLaunchArgAsync));
|
|
2563
2579
|
const spawnOptions = {
|
|
2564
|
-
cwd: isWsl() && existsSync2("/mnt/c/Windows") ? "/mnt/c/Windows" : process.cwd(),
|
|
2580
|
+
cwd: studioWorkingDirectory ?? (isWsl() && existsSync2("/mnt/c/Windows") ? "/mnt/c/Windows" : process.cwd()),
|
|
2565
2581
|
detached: true,
|
|
2566
2582
|
stdio: "ignore",
|
|
2567
2583
|
...processEnvironment ? { env: patchedProcessEnvironment(processEnvironment) } : {}
|
|
@@ -2571,7 +2587,7 @@ var init_studio_instance_manager = __esm({
|
|
|
2571
2587
|
if (this.processAdapter.spawnStudio) {
|
|
2572
2588
|
proc = await this.processAdapter.spawnStudio(exe, args, spawnOptions);
|
|
2573
2589
|
} else if (lifecycleCapabilities.processIdentity.launcher === "windows-retained" || lifecycleCapabilities.processIdentity.launcher === "wsl-windows-retained") {
|
|
2574
|
-
proc = await this.windowsStudioLauncher(exe, args, processEnvironment);
|
|
2590
|
+
proc = await this.windowsStudioLauncher(exe, args, processEnvironment, studioWorkingDirectory);
|
|
2575
2591
|
} else {
|
|
2576
2592
|
const child = spawn(exe, args, spawnOptions);
|
|
2577
2593
|
proc = {
|
|
@@ -2634,6 +2650,7 @@ var init_studio_instance_manager = __esm({
|
|
|
2634
2650
|
universeId: preparedOptions.universeId,
|
|
2635
2651
|
placeVersion: preparedOptions.placeVersion,
|
|
2636
2652
|
localPlaceFile: preparedOptions.localPlaceFile,
|
|
2653
|
+
studioWorkingDirectory,
|
|
2637
2654
|
launchedAt,
|
|
2638
2655
|
connectionDeadlineAt: options.requireProcessIdentity ? void 0 : launchedAt + (options.connectionTimeoutMs ?? 12e4),
|
|
2639
2656
|
state: "launching",
|
|
@@ -3215,6 +3232,7 @@ var init_studio_instance_manager = __esm({
|
|
|
3215
3232
|
placeVersion: record.placeVersion,
|
|
3216
3233
|
localPlaceFile: record.localPlaceFile,
|
|
3217
3234
|
deleteLocalPlaceFileOnClose: record.deleteLocalPlaceFileOnClose,
|
|
3235
|
+
studioWorkingDirectory: record.studioWorkingDirectory,
|
|
3218
3236
|
launchedAt: record.launchedAt,
|
|
3219
3237
|
attachedAt: record.connectedAt,
|
|
3220
3238
|
connectionDeadlineAt: record.connectionDeadlineAt,
|
|
@@ -3250,6 +3268,7 @@ var init_studio_instance_manager = __esm({
|
|
|
3250
3268
|
universeId: record.universeId,
|
|
3251
3269
|
placeVersion: record.placeVersion,
|
|
3252
3270
|
localPlaceFile: record.localPlaceFile,
|
|
3271
|
+
studioWorkingDirectory: record.studioWorkingDirectory,
|
|
3253
3272
|
launchedAt: record.launchedAt,
|
|
3254
3273
|
connectionDeadlineAt: record.connectionDeadlineAt ?? (state === "launching" && record.processAuthorizationState === void 0 ? record.launchedAt + 12e4 : void 0),
|
|
3255
3274
|
state,
|
|
@@ -3420,6 +3439,9 @@ function serverInstructions(definitions) {
|
|
|
3420
3439
|
if (has("capture_screenshot", "simulate_mouse_input")) {
|
|
3421
3440
|
instructions.push("Capture a screenshot before sending mouse input so its pixel coordinates match the viewport.");
|
|
3422
3441
|
}
|
|
3442
|
+
if (has("selection", "capture_screenshot")) {
|
|
3443
|
+
instructions.push("Use selection action=view before capture_screenshot when the viewport must frame a specific instance.");
|
|
3444
|
+
}
|
|
3423
3445
|
if (has("get_roblox_docs")) {
|
|
3424
3446
|
instructions.push("Use get_roblox_docs instead of guessing unfamiliar engine or Luau APIs.");
|
|
3425
3447
|
}
|
|
@@ -3489,7 +3511,8 @@ var init_mcp_runtime = __esm({
|
|
|
3489
3511
|
SIDE_EFFECTING_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
3490
3512
|
"capture_micro_profiler",
|
|
3491
3513
|
"capture_script_profiler",
|
|
3492
|
-
"export_rbxm"
|
|
3514
|
+
"export_rbxm",
|
|
3515
|
+
"selection"
|
|
3493
3516
|
]);
|
|
3494
3517
|
NON_DESTRUCTIVE_SIDE_EFFECT_TOOLS = /* @__PURE__ */ new Set([
|
|
3495
3518
|
"capture_device_matrix",
|
|
@@ -3497,7 +3520,8 @@ var init_mcp_runtime = __esm({
|
|
|
3497
3520
|
"import_rbxm",
|
|
3498
3521
|
"insert_asset",
|
|
3499
3522
|
"insert_script_lines",
|
|
3500
|
-
"upload_asset"
|
|
3523
|
+
"upload_asset",
|
|
3524
|
+
"selection"
|
|
3501
3525
|
]);
|
|
3502
3526
|
IDEMPOTENT_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
3503
3527
|
"capture_device_matrix",
|
|
@@ -3506,7 +3530,8 @@ var init_mcp_runtime = __esm({
|
|
|
3506
3530
|
"set_device_simulator",
|
|
3507
3531
|
"set_network_profile",
|
|
3508
3532
|
"set_properties",
|
|
3509
|
-
"set_script_source"
|
|
3533
|
+
"set_script_source",
|
|
3534
|
+
"selection"
|
|
3510
3535
|
]);
|
|
3511
3536
|
OPEN_WORLD_TOOLS = /* @__PURE__ */ new Set([
|
|
3512
3537
|
"eval_client_runtime",
|
|
@@ -4053,7 +4078,7 @@ var init_http_server = __esm({
|
|
|
4053
4078
|
return tools.deleteScriptLines(body.instancePath, startLine, endLine, body.instance_id);
|
|
4054
4079
|
},
|
|
4055
4080
|
get_attributes: (tools, body) => tools.getAttributes(body.instancePath, body.instance_id),
|
|
4056
|
-
|
|
4081
|
+
selection: (tools, body) => tools.selection(body.action, body, body.instance_id),
|
|
4057
4082
|
execute_luau: (tools, body) => tools.executeLuau(body.code, body.target, body.instance_id),
|
|
4058
4083
|
eval_server_runtime: (tools, body) => tools.evalServerRuntime(body.code, body.instance_id),
|
|
4059
4084
|
eval_client_runtime: (tools, body) => tools.evalClientRuntime(body.code, body.target, body.instance_id),
|
|
@@ -8231,8 +8256,70 @@ var init_tools = __esm({
|
|
|
8231
8256
|
]
|
|
8232
8257
|
};
|
|
8233
8258
|
}
|
|
8259
|
+
async selection(action, opts = {}, instance_id) {
|
|
8260
|
+
if (action !== "get" && action !== "set" && action !== "view") {
|
|
8261
|
+
throw new Error("selection requires action=get|set|view");
|
|
8262
|
+
}
|
|
8263
|
+
if (action === "get") {
|
|
8264
|
+
return this.getSelection(instance_id);
|
|
8265
|
+
}
|
|
8266
|
+
if (action === "set") {
|
|
8267
|
+
if (!Array.isArray(opts.paths)) {
|
|
8268
|
+
throw new Error("selection action=set requires a paths array; empty clears");
|
|
8269
|
+
}
|
|
8270
|
+
return this.setSelection(opts.paths, opts.mode, instance_id);
|
|
8271
|
+
}
|
|
8272
|
+
if (!opts.path) {
|
|
8273
|
+
throw new Error("selection action=view requires path");
|
|
8274
|
+
}
|
|
8275
|
+
return this.focusViewport(opts.path, opts.from, opts.padding, opts.angleY, instance_id);
|
|
8276
|
+
}
|
|
8234
8277
|
async getSelection(instance_id) {
|
|
8235
|
-
const response = await this._callSingle("/api/get-selection", {},
|
|
8278
|
+
const response = await this._callSingle("/api/get-selection", {}, "edit", instance_id);
|
|
8279
|
+
return {
|
|
8280
|
+
content: [
|
|
8281
|
+
{
|
|
8282
|
+
type: "text",
|
|
8283
|
+
text: JSON.stringify(response)
|
|
8284
|
+
}
|
|
8285
|
+
]
|
|
8286
|
+
};
|
|
8287
|
+
}
|
|
8288
|
+
async setSelection(paths, mode, instance_id) {
|
|
8289
|
+
if (!Array.isArray(paths) || paths.some((path6) => typeof path6 !== "string" || path6.length === 0)) {
|
|
8290
|
+
throw new Error("selection paths must contain only non-empty instance paths");
|
|
8291
|
+
}
|
|
8292
|
+
const selectionMode = mode ?? "set";
|
|
8293
|
+
if (!["set", "add", "remove"].includes(selectionMode)) {
|
|
8294
|
+
throw new Error(`selection mode must be "set", "add" or "remove" (got: ${selectionMode})`);
|
|
8295
|
+
}
|
|
8296
|
+
const response = await this._callSingle("/api/set-selection", { paths, mode: selectionMode }, "edit", instance_id);
|
|
8297
|
+
return {
|
|
8298
|
+
content: [
|
|
8299
|
+
{
|
|
8300
|
+
type: "text",
|
|
8301
|
+
text: JSON.stringify(response)
|
|
8302
|
+
}
|
|
8303
|
+
]
|
|
8304
|
+
};
|
|
8305
|
+
}
|
|
8306
|
+
async focusViewport(instancePath, from, padding, angleY, instance_id) {
|
|
8307
|
+
if (!instancePath) {
|
|
8308
|
+
throw new Error("selection action=view requires path");
|
|
8309
|
+
}
|
|
8310
|
+
if (padding !== void 0 && (padding <= 0 || padding > 10)) {
|
|
8311
|
+
throw new Error("selection padding must be greater than 0 and at most 10");
|
|
8312
|
+
}
|
|
8313
|
+
if (angleY !== void 0 && (angleY < -89 || angleY > 89)) {
|
|
8314
|
+
throw new Error("selection angleY must be between -89 and 89");
|
|
8315
|
+
}
|
|
8316
|
+
const { instanceId, clientRole } = this._resolveRuntime(instance_id);
|
|
8317
|
+
const response = await this._callSingle("/api/focus-viewport", {
|
|
8318
|
+
path: instancePath,
|
|
8319
|
+
from,
|
|
8320
|
+
padding,
|
|
8321
|
+
angleY
|
|
8322
|
+
}, clientRole ?? "edit", instanceId);
|
|
8236
8323
|
return {
|
|
8237
8324
|
content: [
|
|
8238
8325
|
{
|
|
@@ -8981,6 +9068,7 @@ var init_tools = __esm({
|
|
|
8981
9068
|
consecutive_confirmed_misses: record.consecutiveConfirmedMisses ?? 0,
|
|
8982
9069
|
source: record.source,
|
|
8983
9070
|
local_place_file: record.localPlaceFile,
|
|
9071
|
+
studio_working_directory: record.studioWorkingDirectory,
|
|
8984
9072
|
place_id: record.placeId,
|
|
8985
9073
|
place_version: record.placeVersion,
|
|
8986
9074
|
launched_at: new Date(record.launchedAt).toISOString(),
|
|
@@ -9177,6 +9265,7 @@ var init_tools = __esm({
|
|
|
9177
9265
|
studioExecutable = request.studio_executable;
|
|
9178
9266
|
}
|
|
9179
9267
|
const processEnvironment = parseStudioProcessEnvironmentPatch(request.process_environment);
|
|
9268
|
+
const studioWorkingDirectory = parseStudioWorkingDirectory(request.studio_working_directory);
|
|
9180
9269
|
if (launchSource === "published_place" && placeId !== void 0 && await this._isLatestPublishedPlaceOpen(placeId)) {
|
|
9181
9270
|
return this._textResult({
|
|
9182
9271
|
error: "Place is already open.",
|
|
@@ -9200,6 +9289,7 @@ var init_tools = __esm({
|
|
|
9200
9289
|
connectionTimeoutMs: timeoutMs,
|
|
9201
9290
|
studioExecutable,
|
|
9202
9291
|
processEnvironment,
|
|
9292
|
+
studioWorkingDirectory,
|
|
9203
9293
|
...requireProcessIdentity ? { requireProcessIdentity: true } : {}
|
|
9204
9294
|
});
|
|
9205
9295
|
if (!waitForConnection) {
|
|
@@ -10744,7 +10834,11 @@ var init_server = __esm({
|
|
|
10744
10834
|
boundPort = result.port;
|
|
10745
10835
|
console.error(`HTTP server listening on ${host}:${boundPort} for Studio plugin (primary mode)`);
|
|
10746
10836
|
console.error(`Streamable HTTP MCP endpoint: http://localhost:${boundPort}/mcp`);
|
|
10747
|
-
} catch {
|
|
10837
|
+
} catch (error) {
|
|
10838
|
+
if (process.env.ROBLOX_STUDIO_REQUIRE_PRIMARY === "1") {
|
|
10839
|
+
console.error(`Port ${basePort} is unavailable and ROBLOX_STUDIO_REQUIRE_PRIMARY=1; refusing proxy mode`);
|
|
10840
|
+
throw error;
|
|
10841
|
+
}
|
|
10748
10842
|
bridgeMode = "proxy";
|
|
10749
10843
|
primaryApp = void 0;
|
|
10750
10844
|
const proxyBridge = new ProxyBridgeService(`http://localhost:${basePort}`, auth.token);
|
|
@@ -11136,24 +11230,62 @@ var init_definitions = __esm({
|
|
|
11136
11230
|
},
|
|
11137
11231
|
// === Selection ===
|
|
11138
11232
|
{
|
|
11139
|
-
name: "
|
|
11233
|
+
name: "selection",
|
|
11140
11234
|
category: "read",
|
|
11141
|
-
description: "Use
|
|
11235
|
+
description: "Use to get, set, or frame selection.",
|
|
11142
11236
|
inputSchema: {
|
|
11143
11237
|
type: "object",
|
|
11144
11238
|
properties: {
|
|
11239
|
+
action: {
|
|
11240
|
+
type: "string",
|
|
11241
|
+
enum: ["get", "set", "view"],
|
|
11242
|
+
description: "View frames the target."
|
|
11243
|
+
},
|
|
11244
|
+
paths: {
|
|
11245
|
+
type: "array",
|
|
11246
|
+
items: { type: "string", minLength: 1 },
|
|
11247
|
+
description: "Set needs paths; empty clears in set mode."
|
|
11248
|
+
},
|
|
11249
|
+
mode: {
|
|
11250
|
+
type: "string",
|
|
11251
|
+
enum: ["set", "add", "remove"],
|
|
11252
|
+
default: "set",
|
|
11253
|
+
description: "How set applies paths."
|
|
11254
|
+
},
|
|
11255
|
+
path: {
|
|
11256
|
+
type: "string",
|
|
11257
|
+
minLength: 1,
|
|
11258
|
+
description: "View needs a BasePart or Model path."
|
|
11259
|
+
},
|
|
11260
|
+
from: {
|
|
11261
|
+
type: "number",
|
|
11262
|
+
description: "View azimuth: 0 +X, 90 +Z."
|
|
11263
|
+
},
|
|
11264
|
+
padding: {
|
|
11265
|
+
type: "number",
|
|
11266
|
+
exclusiveMinimum: 0,
|
|
11267
|
+
maximum: 10,
|
|
11268
|
+
default: 1,
|
|
11269
|
+
description: "View distance scale."
|
|
11270
|
+
},
|
|
11271
|
+
angleY: {
|
|
11272
|
+
type: "number",
|
|
11273
|
+
minimum: -89,
|
|
11274
|
+
maximum: 89,
|
|
11275
|
+
description: "View elevation in degrees."
|
|
11276
|
+
},
|
|
11145
11277
|
instance_id: {
|
|
11146
11278
|
type: "string",
|
|
11147
|
-
description: "Connected place ID
|
|
11279
|
+
description: "Connected place ID if multiple are open."
|
|
11148
11280
|
}
|
|
11149
|
-
}
|
|
11281
|
+
},
|
|
11282
|
+
required: ["action"]
|
|
11150
11283
|
}
|
|
11151
11284
|
},
|
|
11152
|
-
// === Luau Execution ===
|
|
11153
11285
|
{
|
|
11154
11286
|
name: "execute_luau",
|
|
11155
11287
|
category: "write",
|
|
11156
|
-
description: "Use for custom
|
|
11288
|
+
description: "Use for custom Studio traversal, edits, or peer execution.",
|
|
11157
11289
|
inputSchema: {
|
|
11158
11290
|
type: "object",
|
|
11159
11291
|
properties: {
|
|
@@ -11176,7 +11308,7 @@ var init_definitions = __esm({
|
|
|
11176
11308
|
{
|
|
11177
11309
|
name: "eval_server_runtime",
|
|
11178
11310
|
category: "write",
|
|
11179
|
-
description: "Use
|
|
11311
|
+
description: "Use to run Luau in the live server VM with its require cache.",
|
|
11180
11312
|
inputSchema: {
|
|
11181
11313
|
type: "object",
|
|
11182
11314
|
properties: {
|
|
@@ -11195,7 +11327,7 @@ var init_definitions = __esm({
|
|
|
11195
11327
|
{
|
|
11196
11328
|
name: "eval_client_runtime",
|
|
11197
11329
|
category: "write",
|
|
11198
|
-
description: "Use
|
|
11330
|
+
description: "Use to run Luau in a live client VM with its require cache.",
|
|
11199
11331
|
inputSchema: {
|
|
11200
11332
|
type: "object",
|
|
11201
11333
|
properties: {
|
|
@@ -11272,7 +11404,7 @@ var init_definitions = __esm({
|
|
|
11272
11404
|
{
|
|
11273
11405
|
name: "manage_instance",
|
|
11274
11406
|
category: "write",
|
|
11275
|
-
description: "Use to
|
|
11407
|
+
description: "Use to manage Studio processes or list place revisions.",
|
|
11276
11408
|
inputSchema: {
|
|
11277
11409
|
type: "object",
|
|
11278
11410
|
properties: {
|
|
@@ -11314,6 +11446,10 @@ var init_definitions = __esm({
|
|
|
11314
11446
|
type: "string",
|
|
11315
11447
|
description: "Exact Studio executable for launch; otherwise auto-discovered."
|
|
11316
11448
|
},
|
|
11449
|
+
studio_working_directory: {
|
|
11450
|
+
type: "string",
|
|
11451
|
+
description: "Studio process working directory; isolates relative plugin folders per launch."
|
|
11452
|
+
},
|
|
11317
11453
|
process_environment: {
|
|
11318
11454
|
type: "object",
|
|
11319
11455
|
description: "Launch-only environment changes; never stored.",
|
|
@@ -11392,7 +11528,7 @@ var init_definitions = __esm({
|
|
|
11392
11528
|
{
|
|
11393
11529
|
name: "set_network_profile",
|
|
11394
11530
|
category: "write",
|
|
11395
|
-
description: "Use to
|
|
11531
|
+
description: "Use to simulate client latency, jitter, or packet loss.",
|
|
11396
11532
|
inputSchema: {
|
|
11397
11533
|
type: "object",
|
|
11398
11534
|
properties: {
|
|
@@ -11455,7 +11591,7 @@ var init_definitions = __esm({
|
|
|
11455
11591
|
{
|
|
11456
11592
|
name: "get_simulation_state",
|
|
11457
11593
|
category: "read",
|
|
11458
|
-
description: "Use to inspect network and device
|
|
11594
|
+
description: "Use to inspect current network and device simulation.",
|
|
11459
11595
|
inputSchema: {
|
|
11460
11596
|
type: "object",
|
|
11461
11597
|
properties: {
|
|
@@ -11478,7 +11614,7 @@ var init_definitions = __esm({
|
|
|
11478
11614
|
{
|
|
11479
11615
|
name: "reset_simulation_state",
|
|
11480
11616
|
category: "write",
|
|
11481
|
-
description: "Use to clear network and device
|
|
11617
|
+
description: "Use to clear network and device simulation state.",
|
|
11482
11618
|
inputSchema: {
|
|
11483
11619
|
type: "object",
|
|
11484
11620
|
properties: {
|
|
@@ -11504,7 +11640,7 @@ var init_definitions = __esm({
|
|
|
11504
11640
|
{
|
|
11505
11641
|
name: "get_device_simulator_state",
|
|
11506
11642
|
category: "read",
|
|
11507
|
-
description: "Use to inspect
|
|
11643
|
+
description: "Use to inspect device simulation or list device presets.",
|
|
11508
11644
|
inputSchema: {
|
|
11509
11645
|
type: "object",
|
|
11510
11646
|
properties: {
|
|
@@ -11530,7 +11666,7 @@ var init_definitions = __esm({
|
|
|
11530
11666
|
{
|
|
11531
11667
|
name: "set_device_simulator",
|
|
11532
11668
|
category: "write",
|
|
11533
|
-
description: "Use to
|
|
11669
|
+
description: "Use to manage device simulation in edit or a playtest client.",
|
|
11534
11670
|
inputSchema: {
|
|
11535
11671
|
type: "object",
|
|
11536
11672
|
properties: {
|
|
@@ -11667,7 +11803,7 @@ var init_definitions = __esm({
|
|
|
11667
11803
|
{
|
|
11668
11804
|
name: "multiplayer_playtest",
|
|
11669
11805
|
category: "write",
|
|
11670
|
-
description: "Use to run or inspect a
|
|
11806
|
+
description: "Use to run or inspect a multi-client Studio playtest.",
|
|
11671
11807
|
inputSchema: {
|
|
11672
11808
|
type: "object",
|
|
11673
11809
|
properties: {
|
|
@@ -11797,7 +11933,7 @@ var init_definitions = __esm({
|
|
|
11797
11933
|
{
|
|
11798
11934
|
name: "capture_micro_profiler",
|
|
11799
11935
|
category: "read",
|
|
11800
|
-
description: "Use to
|
|
11936
|
+
description: "Use to profile engine and game frame time on a live peer.",
|
|
11801
11937
|
inputSchema: {
|
|
11802
11938
|
type: "object",
|
|
11803
11939
|
properties: {
|
|
@@ -11924,7 +12060,7 @@ var init_definitions = __esm({
|
|
|
11924
12060
|
{
|
|
11925
12061
|
name: "breakpoints",
|
|
11926
12062
|
category: "write",
|
|
11927
|
-
description: "Use
|
|
12063
|
+
description: "Use to trace script execution with breakpoints or logpoints.",
|
|
11928
12064
|
inputSchema: {
|
|
11929
12065
|
type: "object",
|
|
11930
12066
|
properties: {
|
|
@@ -12023,7 +12159,7 @@ var init_definitions = __esm({
|
|
|
12023
12159
|
{
|
|
12024
12160
|
name: "get_asset_details",
|
|
12025
12161
|
category: "read",
|
|
12026
|
-
description: "Use to inspect
|
|
12162
|
+
description: "Use to inspect a shortlisted Creator Store asset.",
|
|
12027
12163
|
inputSchema: {
|
|
12028
12164
|
type: "object",
|
|
12029
12165
|
properties: {
|
|
@@ -12090,7 +12226,7 @@ var init_definitions = __esm({
|
|
|
12090
12226
|
{
|
|
12091
12227
|
name: "generate_model",
|
|
12092
12228
|
category: "write",
|
|
12093
|
-
description: "Use to
|
|
12229
|
+
description: "Use to stage a Roblox model from text or an image.",
|
|
12094
12230
|
inputSchema: {
|
|
12095
12231
|
type: "object",
|
|
12096
12232
|
properties: {
|
|
@@ -12242,7 +12378,7 @@ var init_definitions = __esm({
|
|
|
12242
12378
|
{
|
|
12243
12379
|
name: "capture_screenshot",
|
|
12244
12380
|
category: "read",
|
|
12245
|
-
description: "Use
|
|
12381
|
+
description: "Use to capture the Studio viewport or map input coordinates.",
|
|
12246
12382
|
inputSchema: {
|
|
12247
12383
|
type: "object",
|
|
12248
12384
|
properties: {
|
|
@@ -12339,7 +12475,7 @@ var init_definitions = __esm({
|
|
|
12339
12475
|
{
|
|
12340
12476
|
name: "get_memory_breakdown",
|
|
12341
12477
|
category: "read",
|
|
12342
|
-
description: "Use to compare
|
|
12478
|
+
description: "Use to compare memory categories across Studio peers.",
|
|
12343
12479
|
inputSchema: {
|
|
12344
12480
|
type: "object",
|
|
12345
12481
|
properties: {
|
|
@@ -12362,7 +12498,7 @@ var init_definitions = __esm({
|
|
|
12362
12498
|
{
|
|
12363
12499
|
name: "get_scene_analysis",
|
|
12364
12500
|
category: "read",
|
|
12365
|
-
description: "Use to attribute scene cost
|
|
12501
|
+
description: "Use to attribute scene cost across instances and content.",
|
|
12366
12502
|
inputSchema: {
|
|
12367
12503
|
type: "object",
|
|
12368
12504
|
properties: {
|
|
@@ -12464,7 +12600,7 @@ var init_definitions = __esm({
|
|
|
12464
12600
|
{
|
|
12465
12601
|
name: "find_and_replace_in_scripts",
|
|
12466
12602
|
category: "write",
|
|
12467
|
-
description: "Use to preview or apply
|
|
12603
|
+
description: "Use to preview or apply one replacement across scripts.",
|
|
12468
12604
|
inputSchema: {
|
|
12469
12605
|
type: "object",
|
|
12470
12606
|
properties: {
|
|
@@ -12513,7 +12649,7 @@ var init_definitions = __esm({
|
|
|
12513
12649
|
{
|
|
12514
12650
|
name: "get_roblox_skills",
|
|
12515
12651
|
category: "read",
|
|
12516
|
-
description: "Use to
|
|
12652
|
+
description: "Use to read installed Roblox Studio Assistant skills.",
|
|
12517
12653
|
inputSchema: {
|
|
12518
12654
|
type: "object",
|
|
12519
12655
|
properties: {
|
|
@@ -12534,7 +12670,7 @@ var init_definitions = __esm({
|
|
|
12534
12670
|
{
|
|
12535
12671
|
name: "get_roblox_docs",
|
|
12536
12672
|
category: "read",
|
|
12537
|
-
description: "Use to
|
|
12673
|
+
description: "Use to read official Roblox engine or Luau references.",
|
|
12538
12674
|
inputSchema: {
|
|
12539
12675
|
type: "object",
|
|
12540
12676
|
properties: {
|
|
@@ -12565,6 +12701,29 @@ import { existsSync as existsSync7, unlinkSync } from "fs";
|
|
|
12565
12701
|
import { execSync } from "child_process";
|
|
12566
12702
|
import { join as join5 } from "path";
|
|
12567
12703
|
import { homedir as homedir4 } from "os";
|
|
12704
|
+
function configurePluginAssetForPort(source, rawPort = process.env.ROBLOX_STUDIO_PORT) {
|
|
12705
|
+
if (rawPort === void 0 || rawPort === "")
|
|
12706
|
+
return source;
|
|
12707
|
+
if (!/^\d+$/.test(rawPort)) {
|
|
12708
|
+
throw new Error(`ROBLOX_STUDIO_PORT must be an integer from 1 to 65535; received ${JSON.stringify(rawPort)}`);
|
|
12709
|
+
}
|
|
12710
|
+
const port = Number(rawPort);
|
|
12711
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65535) {
|
|
12712
|
+
throw new Error(`ROBLOX_STUDIO_PORT must be an integer from 1 to 65535; received ${JSON.stringify(rawPort)}`);
|
|
12713
|
+
}
|
|
12714
|
+
if (port === DEFAULT_PLUGIN_PORT)
|
|
12715
|
+
return source;
|
|
12716
|
+
const defaultPort = String(DEFAULT_PLUGIN_PORT);
|
|
12717
|
+
const configuredPort = String(port);
|
|
12718
|
+
const defaultUrl = `http://localhost:${defaultPort}`;
|
|
12719
|
+
const defaultBasePort = `BASE_PORT = ${defaultPort}`;
|
|
12720
|
+
const original = source.toString("utf8");
|
|
12721
|
+
if (!original.includes(defaultUrl) || !original.includes(defaultBasePort)) {
|
|
12722
|
+
throw new Error(`Bundled Studio plugin does not contain the expected default port ${defaultPort}`);
|
|
12723
|
+
}
|
|
12724
|
+
const configured = original.replaceAll(defaultUrl, `http://localhost:${configuredPort}`).replaceAll(defaultBasePort, `BASE_PORT = ${configuredPort}`).replace(SERVER_URL_SETTING_KEY_PATTERN, (key) => key.endsWith("_") ? `${key}PORT_${configuredPort}_` : `${key}_PORT_${configuredPort}`);
|
|
12725
|
+
return Buffer.from(configured, "utf8");
|
|
12726
|
+
}
|
|
12568
12727
|
function isWSL() {
|
|
12569
12728
|
return getStudioPlatformCapabilities().isWsl;
|
|
12570
12729
|
}
|
|
@@ -12618,10 +12777,13 @@ Only one MCP plugin variant should be present. If both variants are in the Studi
|
|
|
12618
12777
|
Delete ${otherAssetName} manually or use the default CLI installer behavior to replace it.
|
|
12619
12778
|
`);
|
|
12620
12779
|
}
|
|
12780
|
+
var DEFAULT_PLUGIN_PORT, SERVER_URL_SETTING_KEY_PATTERN;
|
|
12621
12781
|
var init_install_plugin_helpers = __esm({
|
|
12622
12782
|
"../core/dist/install-plugin-helpers.js"() {
|
|
12623
12783
|
"use strict";
|
|
12624
12784
|
init_studio_platform();
|
|
12785
|
+
DEFAULT_PLUGIN_PORT = 58741;
|
|
12786
|
+
SERVER_URL_SETTING_KEY_PATTERN = /MCP_LAST_SUCCESSFUL_SERVER_URL_GLOBAL_V1|MCP_LAST_SUCCESSFUL_SERVER_URL_|MCP_SERVER_URL_/g;
|
|
12625
12787
|
}
|
|
12626
12788
|
});
|
|
12627
12789
|
|
|
@@ -12648,7 +12810,7 @@ __export(install_plugin_exports, {
|
|
|
12648
12810
|
installBundledPlugin: () => installBundledPlugin,
|
|
12649
12811
|
installPlugin: () => installPlugin
|
|
12650
12812
|
});
|
|
12651
|
-
import {
|
|
12813
|
+
import { createWriteStream, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
12652
12814
|
import { dirname as dirname5, join as join6 } from "path";
|
|
12653
12815
|
import { fileURLToPath } from "url";
|
|
12654
12816
|
import { get } from "https";
|
|
@@ -12750,11 +12912,10 @@ function assertBundledPluginVersion(source) {
|
|
|
12750
12912
|
);
|
|
12751
12913
|
}
|
|
12752
12914
|
}
|
|
12753
|
-
function filesMatch(
|
|
12754
|
-
if (!existsSync8(
|
|
12755
|
-
const
|
|
12756
|
-
|
|
12757
|
-
return aBytes.length === bBytes.length && aBytes.equals(bBytes);
|
|
12915
|
+
function filesMatch(expected, actualPath) {
|
|
12916
|
+
if (!existsSync8(actualPath)) return false;
|
|
12917
|
+
const actual = readFileSync7(actualPath);
|
|
12918
|
+
return expected.length === actual.length && expected.equals(actual);
|
|
12758
12919
|
}
|
|
12759
12920
|
async function installBundledPlugin(options = {}) {
|
|
12760
12921
|
const log = options.log ?? console.log;
|
|
@@ -12767,8 +12928,9 @@ async function installBundledPlugin(options = {}) {
|
|
|
12767
12928
|
assertBundledPluginVersion(source);
|
|
12768
12929
|
const pluginsFolder = prepareInstall({ replaceVariant, log, warn });
|
|
12769
12930
|
const dest = join6(pluginsFolder, ASSET_NAME);
|
|
12770
|
-
|
|
12771
|
-
|
|
12931
|
+
const configured = configurePluginAssetForPort(readFileSync7(source));
|
|
12932
|
+
if (filesMatch(configured, dest)) return;
|
|
12933
|
+
writeFileSync3(dest, configured);
|
|
12772
12934
|
log(`Installed ${ASSET_NAME} to ${dest}`);
|
|
12773
12935
|
}
|
|
12774
12936
|
async function installPlugin(options = {}) {
|
|
@@ -12780,11 +12942,12 @@ async function installPlugin(options = {}) {
|
|
|
12780
12942
|
if (bundled) {
|
|
12781
12943
|
assertBundledPluginVersion(bundled);
|
|
12782
12944
|
const dest2 = join6(pluginsFolder, ASSET_NAME);
|
|
12783
|
-
|
|
12945
|
+
const configured2 = configurePluginAssetForPort(readFileSync7(bundled));
|
|
12946
|
+
if (filesMatch(configured2, dest2)) {
|
|
12784
12947
|
log(`${ASSET_NAME} already installed.`);
|
|
12785
12948
|
return;
|
|
12786
12949
|
}
|
|
12787
|
-
|
|
12950
|
+
writeFileSync3(dest2, configured2);
|
|
12788
12951
|
log(`Installed bundled ${ASSET_NAME} to ${dest2}`);
|
|
12789
12952
|
return;
|
|
12790
12953
|
}
|
|
@@ -12797,6 +12960,9 @@ async function installPlugin(options = {}) {
|
|
|
12797
12960
|
const dest = join6(pluginsFolder, ASSET_NAME);
|
|
12798
12961
|
log(`Downloading ${ASSET_NAME} from ${release.tag_name}...`);
|
|
12799
12962
|
await download(asset.browser_download_url, dest);
|
|
12963
|
+
const downloaded = readFileSync7(dest);
|
|
12964
|
+
const configured = configurePluginAssetForPort(downloaded);
|
|
12965
|
+
if (configured !== downloaded) writeFileSync3(dest, configured);
|
|
12800
12966
|
log(`Installed to ${dest}`);
|
|
12801
12967
|
}
|
|
12802
12968
|
var REPO, ASSET_NAME, OTHER_VARIANT, TIMEOUT_MS, MAX_REDIRECTS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrrxs/robloxstudio-mcp-inspector",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "DataModel read-only MCP server for inspecting and debugging Roblox Studio from AI coding tools",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"dist/**/*",
|
|
15
|
-
"studio-plugin
|
|
15
|
+
"studio-plugin/MCPInspectorPlugin.rbxmx"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsup",
|