@chrrxs/robloxstudio-mcp-inspector 2.23.0 → 2.23.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 CHANGED
@@ -3856,8 +3856,8 @@ var init_http_server = __esm({
3856
3856
  solo_playtest: (tools, body) => tools.soloPlaytest(body.action, body.mode, body.timeout, body.instance_id),
3857
3857
  start_playtest: (tools, body) => tools.startPlaytest(body.mode, body.numPlayers, body.instance_id),
3858
3858
  stop_playtest: (tools, body) => tools.stopPlaytest(body.instance_id),
3859
- multiplayer_playtest: (tools, body) => tools.multiplayerPlaytest(body.action, body.numPlayers, body.target, body.testArgs, body.value, body.timeout, body.instance_id, body.force),
3860
- multiplayer_test_start: (tools, body) => tools.multiplayerTestStart(body.numPlayers, body.testArgs, body.timeout, body.instance_id, body.force),
3859
+ multiplayer_playtest: (tools, body) => tools.multiplayerPlaytest(body.action, body.numPlayers, body.target, body.testArgs, body.value, body.timeout, body.instance_id),
3860
+ multiplayer_test_start: (tools, body) => tools.multiplayerTestStart(body.numPlayers, body.testArgs, body.timeout, body.instance_id),
3861
3861
  multiplayer_test_state: (tools, body) => tools.multiplayerTestState(body.instance_id),
3862
3862
  multiplayer_test_add_players: (tools, body) => tools.multiplayerTestAddPlayers(body.numPlayers, body.timeout, body.instance_id),
3863
3863
  multiplayer_test_leave_client: (tools, body) => tools.multiplayerTestLeaveClient(body.target, body.timeout, body.instance_id),
@@ -7883,16 +7883,6 @@ var init_png_encoder = __esm({
7883
7883
  import * as fs3 from "fs";
7884
7884
  import * as os3 from "os";
7885
7885
  import * as path5 from "path";
7886
- function multiplayerStopDisabledBody() {
7887
- return {
7888
- success: false,
7889
- error: "multiplayer_stop_disabled",
7890
- message: MULTIPLAYER_STOP_DISABLED_MESSAGE,
7891
- reason: "StudioTestService:EndTest does not reliably end StudioTestService multiplayer sessions from MCP right now.",
7892
- manualCleanupRequired: true,
7893
- recoveryHint: "Close the Roblox Studio multiplayer test windows manually."
7894
- };
7895
- }
7896
7886
  function normalizeCreatorStoreSearch(assetType, query) {
7897
7887
  if (!CREATOR_STORE_SEARCH_TYPES.has(assetType)) {
7898
7888
  throw new Error(`search_assets assetType must be one of: ${Array.from(CREATOR_STORE_SEARCH_TYPES).join(", ")}`);
@@ -8548,7 +8538,7 @@ end
8548
8538
  error("Unsupported device simulator operation: " .. tostring(opts.operation), 0)
8549
8539
  `.trim();
8550
8540
  }
8551
- var MAX_INLINE_IMAGE_BYTES, DEFAULT_ASSET_AUDIO_PREVIEWS, MAX_ASSET_AUDIO_PREVIEWS, MAX_INLINE_AUDIO_PREVIEW_BYTES, MAX_INLINE_AUDIO_PREVIEW_TOTAL_BYTES, DEFAULT_ASSET_PREVIEW_DEPTH, MAX_ASSET_PREVIEW_HIERARCHY_NODES, MAX_SEARCH_ASSET_DESCRIPTION_LENGTH, ROBLOX_CREATOR_USER_ID, MAX_DEVICE_MATRIX_ENTRIES, MAX_NETWORK_PACKET_LOSS_PERCENT, STUDIO_ASSISTANT_SOURCE_IMAGE_LABEL, CREATOR_STORE_SEARCH_TYPES, CREATOR_STORE_SORT_CATEGORIES, MULTIPLAYER_FORCE_REQUIRED_MESSAGE, MULTIPLAYER_STOP_DISABLED_MESSAGE, NETWORK_PROFILE_KEYS, NETWORK_PROFILES, ZERO_NETWORK_PROFILE, SIMULATION_PERSISTENCE_NOTES, RobloxStudioTools;
8541
+ var MAX_INLINE_IMAGE_BYTES, DEFAULT_ASSET_AUDIO_PREVIEWS, MAX_ASSET_AUDIO_PREVIEWS, MAX_INLINE_AUDIO_PREVIEW_BYTES, MAX_INLINE_AUDIO_PREVIEW_TOTAL_BYTES, DEFAULT_ASSET_PREVIEW_DEPTH, MAX_ASSET_PREVIEW_HIERARCHY_NODES, MAX_SEARCH_ASSET_DESCRIPTION_LENGTH, ROBLOX_CREATOR_USER_ID, MAX_DEVICE_MATRIX_ENTRIES, MAX_NETWORK_PACKET_LOSS_PERCENT, STUDIO_ASSISTANT_SOURCE_IMAGE_LABEL, CREATOR_STORE_SEARCH_TYPES, CREATOR_STORE_SORT_CATEGORIES, NETWORK_PROFILE_KEYS, NETWORK_PROFILES, ZERO_NETWORK_PROFILE, SIMULATION_PERSISTENCE_NOTES, RobloxStudioTools;
8552
8542
  var init_tools = __esm({
8553
8543
  "../core/dist/tools/index.js"() {
8554
8544
  "use strict";
@@ -8596,8 +8586,6 @@ var init_tools = __esm({
8596
8586
  "UpdatedTime",
8597
8587
  "Ratings"
8598
8588
  ]);
8599
- MULTIPLAYER_FORCE_REQUIRED_MESSAGE = "StudioTestService multiplayer stop is currently disabled because StudioTestService:EndTest is broken for this flow. Pass force=true only if you understand you must manually close the multiplayer test windows afterward.";
8600
- MULTIPLAYER_STOP_DISABLED_MESSAGE = "Multiplayer playtest stop/end is disabled because StudioTestService:EndTest is currently broken for this flow. Manually close the Studio multiplayer test windows instead.";
8601
8589
  NETWORK_PROFILE_KEYS = [
8602
8590
  "InboundNetworkMinDelayMs",
8603
8591
  "OutboundNetworkMinDelayMs",
@@ -10834,7 +10822,7 @@ ${code}`
10834
10822
  }
10835
10823
  return { ok: false, roles: this._rolesForInstance(instanceId), timedOut: true, phase: lastPhase };
10836
10824
  }
10837
- async multiplayerPlaytest(action, numPlayers, target, testArgs, value, timeout, instance_id, force) {
10825
+ async multiplayerPlaytest(action, numPlayers, target, testArgs, value, timeout, instance_id) {
10838
10826
  if (action !== "start" && action !== "status" && action !== "add_players" && action !== "leave_client" && action !== "end") {
10839
10827
  throw new Error("multiplayer_playtest requires action=start|status|add_players|leave_client|end");
10840
10828
  }
@@ -10843,7 +10831,6 @@ ${code}`
10843
10831
  return {
10844
10832
  phase: state.phase,
10845
10833
  roles: Array.isArray(state.peers) ? state.peers.map((peer) => peer.role).filter((role) => typeof role === "string") : [],
10846
- clientRoles: Array.isArray(state.clientRoles) ? state.clientRoles : [],
10847
10834
  playerCount: typeof state.playerCount === "number" ? state.playerCount : void 0,
10848
10835
  error: typeof state.error === "string" ? state.error : void 0
10849
10836
  };
@@ -10856,102 +10843,87 @@ ${code}`
10856
10843
  });
10857
10844
  }
10858
10845
  if (action === "start") {
10859
- if (force !== true) {
10860
- return this._textResult({
10861
- success: false,
10862
- action,
10863
- error: "multiplayer_force_required",
10864
- message: MULTIPLAYER_FORCE_REQUIRED_MESSAGE,
10865
- requiresForce: true,
10866
- manualCleanupRequired: true
10867
- });
10868
- }
10869
- const body = this._parseTextResult(await this.multiplayerTestStart(numPlayers, testArgs, timeout, instance_id, force));
10870
- const state = body.state && typeof body.state === "object" ? body.state : {};
10871
- const launched = body.success === true && body.ready === true;
10846
+ const body2 = this._parseTextResult(await this.multiplayerTestStart(numPlayers, testArgs, timeout, instance_id));
10847
+ const state = body2.state && typeof body2.state === "object" ? body2.state : {};
10848
+ const launched = body2.success === true && body2.ready === true;
10872
10849
  return this._textResult(launched ? {
10873
10850
  success: true,
10874
10851
  action,
10875
- message: "Multiplayer playtest started. Stop/end is disabled; close the multiplayer test windows manually when finished.",
10876
- ready: true,
10877
- manualCleanupRequired: true,
10878
- roles: Array.isArray(body.roles) ? body.roles : void 0,
10879
- clientRoles: Array.isArray(state.clientRoles) ? state.clientRoles : void 0,
10852
+ message: "Multiplayer playtest started.",
10853
+ roles: Array.isArray(body2.roles) ? body2.roles : void 0,
10880
10854
  playerCount: typeof state.playerCount === "number" ? state.playerCount : void 0
10881
10855
  } : {
10882
10856
  success: false,
10883
10857
  action,
10884
- error: body.error ?? body.wait?.error ?? "multiplayer_start_not_detected",
10885
- message: body.success === true ? "Multiplayer playtest start was requested, but MCP did not detect the required server/client peers before timeout. You may need to close the test windows manually." : body.message ?? "Multiplayer playtest did not start.",
10886
- manualCleanupRequired: body.startRequested === true || body.launched === true ? true : void 0,
10887
- roles: Array.isArray(body.roles) ? body.roles : void 0
10858
+ error: body2.error ?? body2.wait?.error ?? "multiplayer_start_not_detected",
10859
+ message: body2.success === true ? "Multiplayer playtest start was requested, but MCP did not detect the required server/client peers before timeout." : body2.message ?? "Multiplayer playtest did not start.",
10860
+ roles: Array.isArray(body2.roles) ? body2.roles : void 0
10888
10861
  });
10889
10862
  }
10890
10863
  if (action === "add_players") {
10891
- const body = this._parseTextResult(await this.multiplayerTestAddPlayers(numPlayers, timeout, instance_id));
10892
- const state = body.state && typeof body.state === "object" ? body.state : {};
10893
- const success = body.success === true && body.ready === true;
10864
+ const body2 = this._parseTextResult(await this.multiplayerTestAddPlayers(numPlayers, timeout, instance_id));
10865
+ const state = body2.state && typeof body2.state === "object" ? body2.state : {};
10866
+ const success = body2.success === true && body2.ready === true;
10894
10867
  return this._textResult(success ? {
10895
10868
  success: true,
10896
10869
  action,
10897
10870
  message: "Players added.",
10898
- roles: Array.isArray(body.roles) ? body.roles : void 0,
10899
- clientRoles: Array.isArray(state.clientRoles) ? state.clientRoles : void 0,
10871
+ roles: Array.isArray(body2.roles) ? body2.roles : void 0,
10900
10872
  playerCount: typeof state.playerCount === "number" ? state.playerCount : void 0
10901
10873
  } : {
10902
10874
  success: false,
10903
10875
  action,
10904
- error: body.error ?? "add_players_failed",
10905
- message: body.success === true ? "Players did not finish joining before timeout." : body.message ?? "Players were not added.",
10906
- roles: Array.isArray(body.roles) ? body.roles : void 0
10876
+ error: body2.error ?? "add_players_failed",
10877
+ message: body2.success === true ? "Players did not finish joining before timeout." : body2.message ?? "Players were not added.",
10878
+ roles: Array.isArray(body2.roles) ? body2.roles : void 0
10907
10879
  });
10908
10880
  }
10909
10881
  if (action === "leave_client") {
10910
- const body = this._parseTextResult(await this.multiplayerTestLeaveClient(target ?? "client-1", timeout, instance_id));
10911
- return this._textResult(body.success === true && body.left === true ? {
10882
+ const body2 = this._parseTextResult(await this.multiplayerTestLeaveClient(target ?? "client-1", timeout, instance_id));
10883
+ return this._textResult(body2.success === true && body2.left === true ? {
10912
10884
  success: true,
10913
10885
  action,
10914
10886
  message: "Client left.",
10915
- roles: Array.isArray(body.roles) ? body.roles : void 0
10887
+ roles: Array.isArray(body2.roles) ? body2.roles : void 0
10916
10888
  } : {
10917
10889
  success: false,
10918
10890
  action,
10919
- error: body.error ?? "leave_client_failed",
10920
- message: body.message ?? "Client did not leave.",
10921
- roles: Array.isArray(body.roles) ? body.roles : void 0
10891
+ error: body2.error ?? "leave_client_failed",
10892
+ message: body2.message ?? "Client did not leave.",
10893
+ roles: Array.isArray(body2.roles) ? body2.roles : void 0
10922
10894
  });
10923
10895
  }
10924
- return this._textResult({
10896
+ const body = this._parseTextResult(await this.multiplayerTestEnd(value, timeout, instance_id));
10897
+ return this._textResult(body.success === true && body.ended === true ? {
10898
+ success: true,
10899
+ action,
10900
+ message: body.alreadyEnded === true ? "Multiplayer playtest already ended." : body.teardownConfirmed === false ? 'Multiplayer playtest end requested; teardown still in progress. Use multiplayer_playtest action="status" to confirm.' : "Multiplayer playtest ended.",
10901
+ teardownConfirmed: body.teardownConfirmed === true
10902
+ } : {
10903
+ success: false,
10925
10904
  action,
10926
- ...multiplayerStopDisabledBody()
10905
+ error: body.error ?? "end_failed",
10906
+ message: body.message ?? "Multiplayer playtest did not end.",
10907
+ roles: Array.isArray(body.roles) ? body.roles : void 0,
10908
+ editDone: body.editDone === false ? false : void 0
10927
10909
  });
10928
10910
  }
10929
- async multiplayerTestStart(numPlayers, testArgs, timeout, instance_id, force) {
10911
+ async multiplayerTestStart(numPlayers, testArgs, timeout, instance_id) {
10930
10912
  if (!Number.isInteger(numPlayers) || numPlayers < 1 || numPlayers > 8) {
10931
10913
  throw new Error("numPlayers must be an integer from 1 to 8");
10932
10914
  }
10933
10915
  const editTarget = this._resolveSingleTarget("edit", instance_id);
10934
- if (force !== true) {
10935
- return this._textResult({
10936
- success: false,
10937
- error: "multiplayer_force_required",
10938
- message: MULTIPLAYER_FORCE_REQUIRED_MESSAGE,
10939
- requiresForce: true,
10940
- manualCleanupRequired: true
10941
- });
10942
- }
10943
10916
  const existingRuntime = this._runtimeTargetsForEquivalentInstances(editTarget.instanceId);
10944
10917
  if (existingRuntime.length > 0) {
10945
10918
  const roles = this._rolesForEquivalentInstances(editTarget.instanceId);
10946
10919
  return this._textResult({
10947
10920
  success: false,
10948
10921
  error: "Multiplayer playtest already running.",
10949
- message: "A Studio runtime is already connected for this place. Close the existing playtest windows manually before starting another multiplayer playtest.",
10922
+ message: "A Studio runtime is already connected for this place. End the existing playtest before starting another multiplayer playtest.",
10950
10923
  ready: true,
10951
10924
  timedOut: false,
10952
10925
  roles,
10953
- runtimeRoles: existingRuntime.map((target) => target.role),
10954
- manualCleanupRequired: true
10926
+ runtimeRoles: existingRuntime.map((target) => target.role)
10955
10927
  });
10956
10928
  }
10957
10929
  const startedAt = Date.now();
@@ -10977,8 +10949,7 @@ ${code}`
10977
10949
  roles: wait.roles,
10978
10950
  state,
10979
10951
  error: success ? void 0 : wait.error ?? "multiplayer_start_not_detected",
10980
- message: success ? "Multiplayer Studio test started and runtime peers detected." : "Multiplayer Studio test start was requested, but MCP did not detect the required server/client peers before timeout. Close the multiplayer test windows manually if Studio launched them.",
10981
- manualCleanupRequired: success || response.success === true ? true : void 0,
10952
+ message: success ? "Multiplayer Studio test started and runtime peers detected." : "Multiplayer Studio test start was requested, but MCP did not detect the required server/client peers before timeout.",
10982
10953
  startedAt
10983
10954
  })
10984
10955
  }]
@@ -11040,10 +11011,39 @@ ${code}`
11040
11011
  };
11041
11012
  }
11042
11013
  async multiplayerTestEnd(value, timeout, instance_id) {
11043
- void value;
11044
- void timeout;
11045
- void instance_id;
11046
- return this._textResult(multiplayerStopDisabledBody());
11014
+ let serverTarget;
11015
+ try {
11016
+ serverTarget = this._resolveSingleTarget("server", instance_id);
11017
+ } catch (err2) {
11018
+ const instanceId = this._resolveInstanceIdOnly(instance_id);
11019
+ const hasRuntime = this._rolesForInstance(instanceId).some((role) => role === "server" || /^client-\d+$/.test(role));
11020
+ if (!hasRuntime) {
11021
+ return this._textResult({
11022
+ success: true,
11023
+ ended: true,
11024
+ alreadyEnded: true,
11025
+ teardownConfirmed: true,
11026
+ message: "No active multiplayer test to end (already ended)."
11027
+ });
11028
+ }
11029
+ throw err2;
11030
+ }
11031
+ const response = await this.client.request("/api/multiplayer-test-end", { value: value ?? "ended_by_mcp" }, serverTarget.instanceId, serverTarget.role);
11032
+ if (response?.error) {
11033
+ return this._textResult(response);
11034
+ }
11035
+ const editDone = await this._waitForMultiplayerEditDone(serverTarget.instanceId, timeout ?? 30);
11036
+ const wait = await this._waitForRuntimeRoles(serverTarget.instanceId, { noRuntime: true }, timeout ?? 30);
11037
+ const state = await this._buildMultiplayerState(serverTarget.instanceId);
11038
+ return this._textResult({
11039
+ ...response,
11040
+ ended: response.success === true,
11041
+ teardownConfirmed: wait.ok,
11042
+ editDone,
11043
+ timedOut: wait.timedOut,
11044
+ roles: wait.roles,
11045
+ state
11046
+ });
11047
11047
  }
11048
11048
  async getConnectedInstances() {
11049
11049
  const instances = this.bridge.getPublicInstances();
@@ -14107,7 +14107,7 @@ var init_definitions = __esm({
14107
14107
  {
14108
14108
  name: "multiplayer_playtest",
14109
14109
  category: "write",
14110
- description: 'Start or inspect a StudioTestService multiplayer playtest. Use action="start" with numPlayers and force=true only when you accept that MCP cannot stop it and you must manually close the multiplayer test windows afterward. action="status" inspects state, action="add_players" adds players, and action="leave_client" removes one client. action="end" is disabled for now and returns the StudioTestService:EndTest broken-API reason. Returns brief lifecycle status only; read script output with get_runtime_logs.',
14110
+ description: 'Start, inspect, add players to, remove a client from, or end a StudioTestService multiplayer playtest. Use action="start" with numPlayers, action="status", action="add_players" with numPlayers, action="leave_client" with target="client-N", or action="end". Returns brief lifecycle status only; read script output with get_runtime_logs.',
14111
14111
  inputSchema: {
14112
14112
  type: "object",
14113
14113
  properties: {
@@ -14128,15 +14128,11 @@ var init_definitions = __esm({
14128
14128
  description: 'For action="start": JSON-compatible table passed to StudioTestService:GetTestArgs() on server and clients.'
14129
14129
  },
14130
14130
  value: {
14131
- description: 'Ignored while action="end" is disabled.'
14132
- },
14133
- force: {
14134
- type: "boolean",
14135
- description: 'Required for action="start". Pass true only if you understand StudioTestService:EndTest is broken in this flow and you will manually close the multiplayer test windows.'
14131
+ description: 'For action="end": JSON-compatible value returned to the edit-side ExecuteMultiplayerTestAsync call.'
14136
14132
  },
14137
14133
  timeout: {
14138
14134
  type: "number",
14139
- description: "Max seconds to wait for start peer detection or action completion. Defaults to 30."
14135
+ description: "Max seconds to wait for action completion. Defaults to 30."
14140
14136
  },
14141
14137
  instance_id: {
14142
14138
  type: "string",
@@ -15479,7 +15475,7 @@ part(0,2,0,2,1,1,"b")`,
15479
15475
  {
15480
15476
  name: "multiplayer_test_start",
15481
15477
  category: "write",
15482
- description: 'Deprecated. Use multiplayer_playtest with action="start" instead. Starts a StudioTestService multiplayer test only when force=true acknowledges that MCP cannot stop it and the test windows must be closed manually.',
15478
+ description: 'Deprecated. Use multiplayer_playtest with action="start" instead. Starts a StudioTestService multiplayer test.',
15483
15479
  inputSchema: {
15484
15480
  type: "object",
15485
15481
  properties: {
@@ -15490,10 +15486,6 @@ part(0,2,0,2,1,1,"b")`,
15490
15486
  testArgs: {
15491
15487
  description: "JSON-compatible table passed to StudioTestService:GetTestArgs() on server and clients."
15492
15488
  },
15493
- force: {
15494
- type: "boolean",
15495
- description: "Required. Pass true only if you understand StudioTestService:EndTest is broken in this flow and you will manually close the multiplayer test windows."
15496
- },
15497
15489
  timeout: {
15498
15490
  type: "number",
15499
15491
  description: "Max seconds to wait for server + clients to register (default 30)."
@@ -15503,7 +15495,7 @@ part(0,2,0,2,1,1,"b")`,
15503
15495
  description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
15504
15496
  }
15505
15497
  },
15506
- required: ["numPlayers", "force"]
15498
+ required: ["numPlayers"]
15507
15499
  }
15508
15500
  },
15509
15501
  {
@@ -15568,16 +15560,16 @@ part(0,2,0,2,1,1,"b")`,
15568
15560
  {
15569
15561
  name: "multiplayer_test_end",
15570
15562
  category: "write",
15571
- description: "Deprecated. Multiplayer StudioTestService stop/end is disabled for now because StudioTestService:EndTest is broken in this flow. This tool returns a disabled error and does not call EndTest.",
15563
+ description: 'Deprecated. Use multiplayer_playtest with action="end" instead. Ends a running StudioTestService multiplayer test.',
15572
15564
  inputSchema: {
15573
15565
  type: "object",
15574
15566
  properties: {
15575
15567
  value: {
15576
- description: "Ignored while multiplayer stop/end is disabled."
15568
+ description: "JSON-compatible value returned to the edit-side ExecuteMultiplayerTestAsync call."
15577
15569
  },
15578
15570
  timeout: {
15579
15571
  type: "number",
15580
- description: "Ignored while multiplayer stop/end is disabled."
15572
+ description: "Max seconds to wait for runtime peers to disconnect (default 30)."
15581
15573
  },
15582
15574
  instance_id: {
15583
15575
  type: "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrrxs/robloxstudio-mcp-inspector",
3
- "version": "2.23.0",
3
+ "version": "2.23.1",
4
4
  "description": "Read-only MCP server for inspecting and debugging Roblox Studio from AI coding tools",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -42,19 +42,19 @@ Complete your AI assistant integration with this easy-to-install Studio plugin.
42
42
 
43
43
  ## Setup & Configuration
44
44
 
45
- ### 1. Enable HTTP Requests (Required)
46
- **Game Settings** > **Security** > **"Allow HTTP Requests"**
45
+ ### Optional: Allow third-party Creator Store assets
47
46
 
48
- To preview or insert public Creator Store assets that you do not own, also
49
- enable **"Allow Loading Third Party Assets"** on the same Security page.
47
+ To preview or insert public Creator Store assets that you do not own, enable
48
+ **"Allow Loading Third Party Assets"** under
49
+ **Game Settings** > **Security**.
50
50
  Roblox disables this setting by default.
51
51
 
52
- ### 2. Activate the Plugin
52
+ ### 1. Activate the Plugin
53
53
  **Plugins toolbar** > Click **"MCP Server"** button
54
54
  - **Green status** = Connected and ready
55
55
  - **Red status** = Disconnected (normal until MCP server runs)
56
56
 
57
- ### 3. Install MCP Server
57
+ ### 2. Install MCP Server
58
58
  Choose your AI assistant:
59
59
 
60
60
  **For Claude Code:**
@@ -116,10 +116,6 @@ If you encounter issues, you may need to run it through `cmd`. Update your confi
116
116
  - Restart Roblox Studio completely
117
117
  - Check Output window for error messages
118
118
 
119
- ### "HTTP 403 Forbidden" Errors
120
- - Enable "Allow HTTP Requests" in Game Settings > Security
121
- - Verify MCP server is running (status should show connected)
122
-
123
119
  ### Plugin Shows "Disconnected"
124
120
  - **Normal behavior** when MCP server isn't running
125
121
  - Click "MCP Server" button to activate
@@ -132,10 +128,18 @@ If you encounter issues, you may need to run it through `cmd`. Update your confi
132
128
 
133
129
  ## Security & Privacy
134
130
 
135
- - **Local-only**: All communication stays on your machine
136
- - **No external servers**: Plugin only talks to localhost
137
- - **Read-only access**: Plugin extracts data but never modifies your place
138
- - **No data collection**: Your projects remain private
131
+ - **Local bridge by default**: The plugin connects to `http://localhost:58741`
132
+ unless you explicitly configure another server URL.
133
+ - **Explicit tool access**: The full plugin can modify a place when a write tool
134
+ is called. Install the Inspector edition when you need read-only access.
135
+ - **Provider boundary**: Tool results pass through your configured MCP client.
136
+ Your AI provider's data-handling policy applies to content sent by that
137
+ client.
138
+
139
+ See the repository's
140
+ [configuration guide](../docs/configuration.md) for HTTP bridge authentication
141
+ and network settings. Report suspected vulnerabilities through the
142
+ [security policy](../SECURITY.md), not a public issue.
139
143
 
140
144
  ## Advanced Usage
141
145
 
@@ -1550,9 +1550,9 @@ local function computeBridgeStamp()
1550
1550
  for i = 1, #combined do
1551
1551
  h = (h * 33 + (string.byte(combined, i))) % 2147483647
1552
1552
  end
1553
- -- "2.23.0" is replaced with the package version at package time
1553
+ -- "2.23.1" is replaced with the package version at package time
1554
1554
  -- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
1555
- return `{tostring(h)}-2.23.0`
1555
+ return `{tostring(h)}-2.23.1`
1556
1556
  end
1557
1557
  local BRIDGE_STAMP = computeBridgeStamp()
1558
1558
  local function setSource(scriptInst, source)
@@ -10036,13 +10036,25 @@ local function multiplayerTestLeaveClient(_requestData)
10036
10036
  localPlayer = localPlayer,
10037
10037
  }
10038
10038
  end
10039
- local function multiplayerTestEnd(_requestData)
10039
+ local function multiplayerTestEnd(requestData)
10040
+ if not RunService:IsRunning() or not RunService:IsServer() then
10041
+ return {
10042
+ error = "multiplayer_test_end must be called on the running server peer. Route with target=server.",
10043
+ }
10044
+ end
10045
+ local value = if requestData.value ~= nil then requestData.value else "ended_by_mcp"
10046
+ local ok, result = pcall(function()
10047
+ return StudioTestService:EndTest(value)
10048
+ end)
10049
+ if not ok then
10050
+ return {
10051
+ error = tostring(result),
10052
+ }
10053
+ end
10040
10054
  return {
10041
- success = false,
10042
- error = "multiplayer_stop_disabled",
10043
- message = "Multiplayer playtest stop/end is disabled because StudioTestService:EndTest is currently broken for this flow. Manually close the Studio multiplayer test windows instead.",
10044
- reason = "StudioTestService:EndTest does not reliably end StudioTestService multiplayer sessions from MCP right now.",
10045
- manualCleanupRequired = true,
10055
+ success = true,
10056
+ message = "Multiplayer Studio test end requested.",
10057
+ value = value,
10046
10058
  }
10047
10059
  end
10048
10060
  return {
@@ -11080,7 +11092,7 @@ return {
11080
11092
  <Properties>
11081
11093
  <string name="Name">State</string>
11082
11094
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
11083
- local CURRENT_VERSION = "2.23.0"
11095
+ local CURRENT_VERSION = "2.23.1"
11084
11096
  local PLUGIN_VARIANT = "inspector"
11085
11097
  local BASE_PORT = 58741
11086
11098
  local function createConnection(port)
@@ -1550,9 +1550,9 @@ local function computeBridgeStamp()
1550
1550
  for i = 1, #combined do
1551
1551
  h = (h * 33 + (string.byte(combined, i))) % 2147483647
1552
1552
  end
1553
- -- "2.23.0" is replaced with the package version at package time
1553
+ -- "2.23.1" is replaced with the package version at package time
1554
1554
  -- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
1555
- return `{tostring(h)}-2.23.0`
1555
+ return `{tostring(h)}-2.23.1`
1556
1556
  end
1557
1557
  local BRIDGE_STAMP = computeBridgeStamp()
1558
1558
  local function setSource(scriptInst, source)
@@ -10036,13 +10036,25 @@ local function multiplayerTestLeaveClient(_requestData)
10036
10036
  localPlayer = localPlayer,
10037
10037
  }
10038
10038
  end
10039
- local function multiplayerTestEnd(_requestData)
10039
+ local function multiplayerTestEnd(requestData)
10040
+ if not RunService:IsRunning() or not RunService:IsServer() then
10041
+ return {
10042
+ error = "multiplayer_test_end must be called on the running server peer. Route with target=server.",
10043
+ }
10044
+ end
10045
+ local value = if requestData.value ~= nil then requestData.value else "ended_by_mcp"
10046
+ local ok, result = pcall(function()
10047
+ return StudioTestService:EndTest(value)
10048
+ end)
10049
+ if not ok then
10050
+ return {
10051
+ error = tostring(result),
10052
+ }
10053
+ end
10040
10054
  return {
10041
- success = false,
10042
- error = "multiplayer_stop_disabled",
10043
- message = "Multiplayer playtest stop/end is disabled because StudioTestService:EndTest is currently broken for this flow. Manually close the Studio multiplayer test windows instead.",
10044
- reason = "StudioTestService:EndTest does not reliably end StudioTestService multiplayer sessions from MCP right now.",
10045
- manualCleanupRequired = true,
10055
+ success = true,
10056
+ message = "Multiplayer Studio test end requested.",
10057
+ value = value,
10046
10058
  }
10047
10059
  end
10048
10060
  return {
@@ -11080,7 +11092,7 @@ return {
11080
11092
  <Properties>
11081
11093
  <string name="Name">State</string>
11082
11094
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
11083
- local CURRENT_VERSION = "2.23.0"
11095
+ local CURRENT_VERSION = "2.23.1"
11084
11096
  local PLUGIN_VARIANT = "main"
11085
11097
  local BASE_PORT = 58741
11086
11098
  local function createConnection(port)
@@ -322,13 +322,20 @@ function multiplayerTestLeaveClient(_requestData: Record<string, unknown>) {
322
322
  };
323
323
  }
324
324
 
325
- function multiplayerTestEnd(_requestData: Record<string, unknown>) {
325
+ function multiplayerTestEnd(requestData: Record<string, unknown>) {
326
+ if (!RunService.IsRunning() || !RunService.IsServer()) {
327
+ return { error: "multiplayer_test_end must be called on the running server peer. Route with target=server." };
328
+ }
329
+
330
+ const value = requestData.value !== undefined ? requestData.value : "ended_by_mcp";
331
+ const [ok, result] = pcall(() => StudioTestService.EndTest(value));
332
+ if (!ok) {
333
+ return { error: tostring(result) };
334
+ }
326
335
  return {
327
- success: false,
328
- error: "multiplayer_stop_disabled",
329
- message: "Multiplayer playtest stop/end is disabled because StudioTestService:EndTest is currently broken for this flow. Manually close the Studio multiplayer test windows instead.",
330
- reason: "StudioTestService:EndTest does not reliably end StudioTestService multiplayer sessions from MCP right now.",
331
- manualCleanupRequired: true,
336
+ success: true,
337
+ message: "Multiplayer Studio test end requested.",
338
+ value,
332
339
  };
333
340
  }
334
341