@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.
Files changed (48) hide show
  1. package/dist/index.js +217 -51
  2. package/package.json +2 -2
  3. package/studio-plugin/MCPInspectorPlugin.rbxmx +332 -40
  4. package/studio-plugin/.Carbon.rbxm.lock +0 -0
  5. package/studio-plugin/Carbon.rbxm +0 -0
  6. package/studio-plugin/INSTALLATION.md +0 -170
  7. package/studio-plugin/MCPPlugin.rbxmx +0 -169759
  8. package/studio-plugin/default.project.json +0 -19
  9. package/studio-plugin/dev.project.json +0 -23
  10. package/studio-plugin/include/LibMP.lua +0 -156378
  11. package/studio-plugin/inspector-icon.png +0 -0
  12. package/studio-plugin/package-lock.json +0 -706
  13. package/studio-plugin/package.json +0 -19
  14. package/studio-plugin/plugin.json +0 -10
  15. package/studio-plugin/src/modules/AssetSanitizationPolicy.ts +0 -127
  16. package/studio-plugin/src/modules/ClientBroker.ts +0 -450
  17. package/studio-plugin/src/modules/Communication.ts +0 -601
  18. package/studio-plugin/src/modules/EvalBridges.ts +0 -255
  19. package/studio-plugin/src/modules/HttpDiagnostics.ts +0 -50
  20. package/studio-plugin/src/modules/LuauExec.ts +0 -403
  21. package/studio-plugin/src/modules/Recording.ts +0 -28
  22. package/studio-plugin/src/modules/RenderMonitor.ts +0 -60
  23. package/studio-plugin/src/modules/RuntimeLogBuffer.ts +0 -210
  24. package/studio-plugin/src/modules/ServerUrlSettings.ts +0 -117
  25. package/studio-plugin/src/modules/State.ts +0 -39
  26. package/studio-plugin/src/modules/StopPlayMonitor.ts +0 -267
  27. package/studio-plugin/src/modules/UI.ts +0 -597
  28. package/studio-plugin/src/modules/Utils.ts +0 -527
  29. package/studio-plugin/src/modules/handlers/AssetHandlers.ts +0 -391
  30. package/studio-plugin/src/modules/handlers/BreakpointHandlers.ts +0 -460
  31. package/studio-plugin/src/modules/handlers/CaptureHandlers.ts +0 -170
  32. package/studio-plugin/src/modules/handlers/EvalRuntimeHandlers.ts +0 -149
  33. package/studio-plugin/src/modules/handlers/GenerateModelHandlers.ts +0 -168
  34. package/studio-plugin/src/modules/handlers/InputHandlers.ts +0 -163
  35. package/studio-plugin/src/modules/handlers/LogHandlers.ts +0 -14
  36. package/studio-plugin/src/modules/handlers/MemoryHandlers.ts +0 -44
  37. package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +0 -96
  38. package/studio-plugin/src/modules/handlers/MicroProfilerHandlers.ts +0 -1263
  39. package/studio-plugin/src/modules/handlers/PropertyHandlers.ts +0 -62
  40. package/studio-plugin/src/modules/handlers/QueryHandlers.ts +0 -716
  41. package/studio-plugin/src/modules/handlers/SceneAnalysisHandlers.ts +0 -216
  42. package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +0 -531
  43. package/studio-plugin/src/modules/handlers/ScriptProfilerHandlers.ts +0 -386
  44. package/studio-plugin/src/modules/handlers/SerializationHandlers.ts +0 -172
  45. package/studio-plugin/src/modules/handlers/TestHandlers.ts +0 -350
  46. package/studio-plugin/src/server/index.server.ts +0 -135
  47. package/studio-plugin/src/types/index.d.ts +0 -57
  48. 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, null, ref startup, out created);
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, null, ref startup, out created);
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 script = buildWindowsStudioStartScriptFromConvertedExe(windowsExe, args, processEnvironment);
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
- get_selection: (tools, body) => tools.getSelection(body.instance_id),
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", {}, void 0, instance_id);
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: "get_selection",
11233
+ name: "selection",
11140
11234
  category: "read",
11141
- description: "Use when the current Studio selection should define the objects to work on.",
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; required with multiple places."
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 edit work or plugin-context access to a live server or client.",
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 when Luau must run in the live server Script VM and share the game's require cache.",
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 when Luau must run in a live client LocalScript VM and share the game's require cache.",
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 launch, inspect, authorize, or close Studio processes and list place revisions.",
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 apply simulated latency, jitter, or packet loss to playtest clients.",
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 simulator state before or after simulation tests.",
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 simulator settings between simulation tests.",
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 active device simulation or list built-in device presets.",
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 start, change, or stop device simulation in edit mode or on playtest clients.",
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 StudioTestService playtest with multiple clients.",
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 attribute frame time across engine and game work on a running server or client.",
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 when Studio breakpoints or logpoints are needed to trace script execution.",
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 full catalog metadata for one shortlisted Creator Store asset.",
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 generate and stage a Roblox model from text or an image reference.",
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 when you need the current Studio viewport as an image or input coordinate map.",
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 Roblox memory categories across edit, server, or client peers.",
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 to instances, scripts, triangles, animation, or audio.",
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 the same text replacement across multiple scripts.",
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 list or read Roblox-authored Studio Assistant skills on demand.",
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 look up official Roblox engine or Luau reference documentation.",
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 { copyFileSync as copyFileSync2, createWriteStream, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
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(a, b) {
12754
- if (!existsSync8(b)) return false;
12755
- const aBytes = readFileSync7(a);
12756
- const bBytes = readFileSync7(b);
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
- if (filesMatch(source, dest)) return;
12771
- copyFileSync2(source, dest);
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
- if (filesMatch(bundled, dest2)) {
12945
+ const configured2 = configurePluginAssetForPort(readFileSync7(bundled));
12946
+ if (filesMatch(configured2, dest2)) {
12784
12947
  log(`${ASSET_NAME} already installed.`);
12785
12948
  return;
12786
12949
  }
12787
- copyFileSync2(bundled, dest2);
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.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",