@chrrxs/robloxstudio-mcp 2.16.4 → 2.17.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
@@ -891,13 +891,23 @@ var init_http_server = __esm({
891
891
  capture_device_matrix: (tools, body) => tools.captureDeviceMatrix(body.entries, body.target, body.format, body.quality, body.settleSeconds, body.restoreAfter, body.instance_id),
892
892
  start_playtest: (tools, body) => tools.startPlaytest(body.mode, body.numPlayers, body.instance_id),
893
893
  stop_playtest: (tools, body) => tools.stopPlaytest(body.instance_id),
894
- get_playtest_output: (tools, body) => tools.getPlaytestOutput(body.target, body.instance_id),
895
894
  multiplayer_test_start: (tools, body) => tools.multiplayerTestStart(body.numPlayers, body.testArgs, body.timeout, body.instance_id),
896
895
  multiplayer_test_state: (tools, body) => tools.multiplayerTestState(body.instance_id),
897
896
  multiplayer_test_add_players: (tools, body) => tools.multiplayerTestAddPlayers(body.numPlayers, body.timeout, body.instance_id),
898
897
  multiplayer_test_leave_client: (tools, body) => tools.multiplayerTestLeaveClient(body.target, body.timeout, body.instance_id),
899
898
  multiplayer_test_end: (tools, body) => tools.multiplayerTestEnd(body.value, body.timeout, body.instance_id),
900
899
  get_runtime_logs: (tools, body) => tools.getRuntimeLogs(body.target, body.since, body.tail, body.filter, body.instance_id),
900
+ capture_script_profiler: (tools, body) => tools.captureScriptProfiler(body.target, {
901
+ duration_ms: body.duration_ms,
902
+ frequency: body.frequency,
903
+ max_functions: body.max_functions,
904
+ min_total_us: body.min_total_us,
905
+ filter: body.filter,
906
+ include_native: body.include_native,
907
+ include_plugin: body.include_plugin,
908
+ output_path: body.output_path
909
+ }, body.instance_id),
910
+ breakpoints: (tools, body) => tools.breakpoints(body.action, body, body.target, body.instance_id),
901
911
  get_connected_instances: (tools) => tools.getConnectedInstances(),
902
912
  export_build: (tools, body) => tools.exportBuild(body.instancePath, body.outputId, body.style, body.instance_id),
903
913
  create_build: (tools, body) => tools.createBuild(body.id, body.style, body.palette, body.parts, body.bounds),
@@ -918,12 +928,10 @@ var init_http_server = __esm({
918
928
  clone_object: (tools, body) => tools.cloneObject(body.instancePath, body.targetParentPath, body.instance_id),
919
929
  get_descendants: (tools, body) => tools.getDescendants(body.instancePath, body.maxDepth, body.classFilter, body.instance_id),
920
930
  compare_instances: (tools, body) => tools.compareInstances(body.instancePathA, body.instancePathB, body.instance_id),
921
- get_output_log: (tools, body) => tools.getOutputLog(body.maxEntries, body.messageType, body.instance_id),
922
931
  bulk_set_attributes: (tools, body) => tools.bulkSetAttributes(body.instancePath, body.attributes, body.instance_id),
923
932
  capture_screenshot: (tools, body) => tools.captureScreenshot(body.instance_id, body.format, body.quality),
924
933
  simulate_mouse_input: (tools, body) => tools.simulateMouseInput(body.action, body.x, body.y, body.button, body.scrollDirection, body.target, body.instance_id),
925
934
  simulate_keyboard_input: (tools, body) => tools.simulateKeyboardInput(body.keyCode, body.action, body.duration, body.text, body.target, body.instance_id),
926
- character_navigation: (tools, body) => tools.characterNavigation(body.position, body.instancePath, body.waitForCompletion, body.timeout, body.target, body.instance_id),
927
935
  get_memory_breakdown: (tools, body) => tools.getMemoryBreakdown(body.target, body.tags, body.instance_id),
928
936
  get_scene_analysis: (tools, body) => tools.getSceneAnalysis(body.mode, body.target, body.topN, body.raw, body.instance_id),
929
937
  export_rbxm: (tools, body) => tools.exportRbxm(body.instance_paths, body.output_path, body.target, body.instance_id),
@@ -4458,6 +4466,76 @@ ${code}`
4458
4466
  content: [{ type: "text", text: JSON.stringify(body) }]
4459
4467
  };
4460
4468
  }
4469
+ async captureScriptProfiler(target, request = {}, instance_id) {
4470
+ const targetRole = target ?? "server";
4471
+ const data = { ...request };
4472
+ const outputPath = data.output_path;
4473
+ delete data.output_path;
4474
+ if (outputPath !== void 0 && typeof outputPath !== "string") {
4475
+ throw new Error("output_path must be a string when provided");
4476
+ }
4477
+ if (outputPath) {
4478
+ data.__mcp_include_raw_json = true;
4479
+ }
4480
+ const resolved = this.bridge.resolveTarget({ instance_id, target: targetRole });
4481
+ if (!resolved.ok)
4482
+ throw new RoutingFailure(resolved.error);
4483
+ if (resolved.mode !== "single") {
4484
+ throw new RoutingFailure({
4485
+ code: "target_role_not_present_on_instance",
4486
+ message: 'capture_script_profiler profiles one runtime peer at a time. Pick target="server" or a specific "client-N".',
4487
+ data: {
4488
+ instances: this.bridge.getPublicInstances(),
4489
+ count: this.bridge.getInstances().length
4490
+ }
4491
+ });
4492
+ }
4493
+ data.__mcp_instance_id = resolved.targetInstanceId;
4494
+ data.__mcp_target_role = resolved.targetRole;
4495
+ const response = await this.client.request("/api/capture-script-profiler", data, resolved.targetInstanceId, resolved.targetRole);
4496
+ const body = response !== null && typeof response === "object" && !Array.isArray(response) ? { ...response, target: resolved.targetRole } : response;
4497
+ if (body !== null && typeof body === "object" && !Array.isArray(body)) {
4498
+ const mutable = body;
4499
+ const rawJson = mutable.raw_json;
4500
+ if (typeof rawJson === "string") {
4501
+ if (typeof outputPath === "string" && outputPath !== "") {
4502
+ const resolvedOutputPath = path.resolve(outputPath);
4503
+ fs.mkdirSync(path.dirname(resolvedOutputPath), { recursive: true });
4504
+ fs.writeFileSync(resolvedOutputPath, rawJson, "utf8");
4505
+ mutable.output_path = resolvedOutputPath;
4506
+ }
4507
+ delete mutable.raw_json;
4508
+ }
4509
+ }
4510
+ return { content: [{ type: "text", text: JSON.stringify(body) }] };
4511
+ }
4512
+ async breakpoints(action, request = {}, target, instance_id) {
4513
+ if (!action || typeof action !== "string") {
4514
+ throw new Error("breakpoints requires action=set|remove|clear|list");
4515
+ }
4516
+ const targetRole = target ?? "edit";
4517
+ const data = { ...request, action };
4518
+ delete data.target;
4519
+ delete data.instance_id;
4520
+ const resolved = this.bridge.resolveTarget({ instance_id, target: targetRole });
4521
+ if (!resolved.ok)
4522
+ throw new RoutingFailure(resolved.error);
4523
+ if (resolved.mode !== "single") {
4524
+ throw new RoutingFailure({
4525
+ code: "target_role_not_present_on_instance",
4526
+ message: "This tool does not support target=all. Pick a specific role or omit target.",
4527
+ data: {
4528
+ instances: this.bridge.getPublicInstances(),
4529
+ count: this.bridge.getInstances().length
4530
+ }
4531
+ });
4532
+ }
4533
+ data.__mcp_instance_id = resolved.targetInstanceId;
4534
+ data.__mcp_target_role = resolved.targetRole;
4535
+ const response = await this.client.request("/api/breakpoints", data, resolved.targetInstanceId, resolved.targetRole);
4536
+ const body = response !== null && typeof response === "object" && !Array.isArray(response) ? { ...response, target: resolved.targetRole } : response;
4537
+ return { content: [{ type: "text", text: JSON.stringify(body) }] };
4538
+ }
4461
4539
  async startPlaytest(mode, numPlayers, instance_id) {
4462
4540
  if (mode !== "play" && mode !== "run") {
4463
4541
  throw new Error('mode must be "play" or "run"');
@@ -4569,17 +4647,6 @@ ${code}`
4569
4647
  content: [{ type: "text", text: JSON.stringify(body) }]
4570
4648
  };
4571
4649
  }
4572
- async getPlaytestOutput(target, instance_id) {
4573
- const response = await this._callSingle("/api/get-playtest-output", {}, target || "edit", instance_id);
4574
- return {
4575
- content: [
4576
- {
4577
- type: "text",
4578
- text: JSON.stringify(response)
4579
- }
4580
- ]
4581
- };
4582
- }
4583
4650
  async _buildMultiplayerState(instanceId) {
4584
4651
  const peers = this.bridge.getPublicInstances().filter((i) => i.instanceId === instanceId).sort((a, b) => a.role.localeCompare(b.role));
4585
4652
  const body = {
@@ -5530,23 +5597,6 @@ ${code}`
5530
5597
  }]
5531
5598
  };
5532
5599
  }
5533
- async characterNavigation(position, instancePath, waitForCompletion, timeout, target, instance_id) {
5534
- if (!position && !instancePath) {
5535
- throw new Error("Either position or instancePath is required for character_navigation");
5536
- }
5537
- const response = await this._callSingle("/api/character-navigation", {
5538
- position,
5539
- instancePath,
5540
- waitForCompletion,
5541
- timeout
5542
- }, target || "edit", instance_id);
5543
- return {
5544
- content: [{
5545
- type: "text",
5546
- text: JSON.stringify(response)
5547
- }]
5548
- };
5549
- }
5550
5600
  async cloneObject(instancePath, targetParentPath, instance_id) {
5551
5601
  if (!instancePath || !targetParentPath) {
5552
5602
  throw new Error("instancePath and targetParentPath are required for clone_object");
@@ -5568,10 +5618,6 @@ ${code}`
5568
5618
  const response = await this._callSingle("/api/compare-instances", { instancePathA, instancePathB }, void 0, instance_id);
5569
5619
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
5570
5620
  }
5571
- async getOutputLog(maxEntries, messageType, instance_id) {
5572
- const response = await this._callSingle("/api/get-output-log", { maxEntries, messageType }, void 0, instance_id);
5573
- return { content: [{ type: "text", text: JSON.stringify(response) }] };
5574
- }
5575
5621
  async bulkSetAttributes(instancePath, attributes, instance_id) {
5576
5622
  if (!instancePath || !attributes) {
5577
5623
  throw new Error("instancePath and attributes are required for bulk_set_attributes");
@@ -6145,7 +6191,7 @@ var init_definitions = __esm({
6145
6191
  properties: {
6146
6192
  path: {
6147
6193
  type: "string",
6148
- description: "Root path (default: game root)"
6194
+ description: 'Canonical DataModel path (default: game root), such as game.Workspace or game.ServerScriptService[".dir"]'
6149
6195
  },
6150
6196
  instance_id: {
6151
6197
  type: "string",
@@ -6249,7 +6295,7 @@ var init_definitions = __esm({
6249
6295
  properties: {
6250
6296
  instancePath: {
6251
6297
  type: "string",
6252
- description: "Instance path (dot notation)"
6298
+ description: 'Canonical DataModel path, such as game.Workspace.Part or game.ServerScriptService[".dir"].Main'
6253
6299
  },
6254
6300
  excludeSource: {
6255
6301
  type: "boolean",
@@ -6272,7 +6318,7 @@ var init_definitions = __esm({
6272
6318
  properties: {
6273
6319
  instancePath: {
6274
6320
  type: "string",
6275
- description: "Instance path (dot notation)"
6321
+ description: 'Canonical DataModel path, such as game.Workspace.Part or game.ServerScriptService[".dir"].Main'
6276
6322
  },
6277
6323
  instance_id: {
6278
6324
  type: "string",
@@ -6334,7 +6380,7 @@ var init_definitions = __esm({
6334
6380
  properties: {
6335
6381
  path: {
6336
6382
  type: "string",
6337
- description: "Root path (default: workspace root)"
6383
+ description: "Canonical DataModel path (default: workspace root)"
6338
6384
  },
6339
6385
  maxDepth: {
6340
6386
  type: "number",
@@ -6361,7 +6407,7 @@ var init_definitions = __esm({
6361
6407
  properties: {
6362
6408
  instancePath: {
6363
6409
  type: "string",
6364
- description: "Instance path (dot notation)"
6410
+ description: "Canonical DataModel path"
6365
6411
  },
6366
6412
  propertyName: {
6367
6413
  type: "string",
@@ -6388,7 +6434,7 @@ var init_definitions = __esm({
6388
6434
  paths: {
6389
6435
  type: "array",
6390
6436
  items: { type: "string" },
6391
- description: "Instance paths"
6437
+ description: "Canonical DataModel paths"
6392
6438
  },
6393
6439
  propertyName: {
6394
6440
  type: "string",
@@ -6415,7 +6461,7 @@ var init_definitions = __esm({
6415
6461
  paths: {
6416
6462
  type: "array",
6417
6463
  items: { type: "string" },
6418
- description: "Instance paths"
6464
+ description: "Canonical DataModel paths"
6419
6465
  },
6420
6466
  propertyName: {
6421
6467
  type: "string",
@@ -6438,7 +6484,7 @@ var init_definitions = __esm({
6438
6484
  properties: {
6439
6485
  instancePath: {
6440
6486
  type: "string",
6441
- description: "Instance path"
6487
+ description: "Canonical DataModel path"
6442
6488
  },
6443
6489
  properties: {
6444
6490
  type: "object",
@@ -6466,7 +6512,7 @@ var init_definitions = __esm({
6466
6512
  },
6467
6513
  parent: {
6468
6514
  type: "string",
6469
- description: "Parent instance path"
6515
+ description: "Canonical parent DataModel path"
6470
6516
  },
6471
6517
  name: {
6472
6518
  type: "string",
@@ -6502,7 +6548,7 @@ var init_definitions = __esm({
6502
6548
  },
6503
6549
  parent: {
6504
6550
  type: "string",
6505
- description: "Parent instance path"
6551
+ description: "Canonical parent DataModel path"
6506
6552
  },
6507
6553
  name: {
6508
6554
  type: "string",
@@ -6534,7 +6580,7 @@ var init_definitions = __esm({
6534
6580
  properties: {
6535
6581
  instancePath: {
6536
6582
  type: "string",
6537
- description: "Instance path (dot notation)"
6583
+ description: "Canonical DataModel path"
6538
6584
  },
6539
6585
  instance_id: {
6540
6586
  type: "string",
@@ -6554,7 +6600,7 @@ var init_definitions = __esm({
6554
6600
  properties: {
6555
6601
  instancePath: {
6556
6602
  type: "string",
6557
- description: "Instance path (dot notation)"
6603
+ description: "Canonical DataModel path"
6558
6604
  },
6559
6605
  count: {
6560
6606
  type: "number",
@@ -6615,7 +6661,7 @@ var init_definitions = __esm({
6615
6661
  properties: {
6616
6662
  instancePath: {
6617
6663
  type: "string",
6618
- description: "Instance path (dot notation)"
6664
+ description: "Canonical DataModel path"
6619
6665
  },
6620
6666
  count: {
6621
6667
  type: "number",
@@ -6678,7 +6724,7 @@ var init_definitions = __esm({
6678
6724
  properties: {
6679
6725
  instancePath: {
6680
6726
  type: "string",
6681
- description: "Script instance path"
6727
+ description: "Canonical path to a LuaSourceContainer"
6682
6728
  },
6683
6729
  startLine: {
6684
6730
  type: "number",
@@ -6705,7 +6751,7 @@ var init_definitions = __esm({
6705
6751
  properties: {
6706
6752
  instancePath: {
6707
6753
  type: "string",
6708
- description: "Script instance path"
6754
+ description: "Canonical path to a LuaSourceContainer"
6709
6755
  },
6710
6756
  source: {
6711
6757
  type: "string",
@@ -6728,7 +6774,7 @@ var init_definitions = __esm({
6728
6774
  properties: {
6729
6775
  instancePath: {
6730
6776
  type: "string",
6731
- description: "Script instance path"
6777
+ description: "Canonical path to a LuaSourceContainer"
6732
6778
  },
6733
6779
  old_string: {
6734
6780
  type: "string",
@@ -6759,7 +6805,7 @@ var init_definitions = __esm({
6759
6805
  properties: {
6760
6806
  instancePath: {
6761
6807
  type: "string",
6762
- description: "Script instance path"
6808
+ description: "Canonical path to a LuaSourceContainer"
6763
6809
  },
6764
6810
  afterLine: {
6765
6811
  type: "number",
@@ -6786,7 +6832,7 @@ var init_definitions = __esm({
6786
6832
  properties: {
6787
6833
  instancePath: {
6788
6834
  type: "string",
6789
- description: "Script instance path"
6835
+ description: "Canonical path to a LuaSourceContainer"
6790
6836
  },
6791
6837
  startLine: {
6792
6838
  type: "number",
@@ -6814,7 +6860,7 @@ var init_definitions = __esm({
6814
6860
  properties: {
6815
6861
  instancePath: {
6816
6862
  type: "string",
6817
- description: "Instance path (dot notation)"
6863
+ description: "Canonical DataModel path"
6818
6864
  },
6819
6865
  attributeName: {
6820
6866
  type: "string",
@@ -6844,7 +6890,7 @@ var init_definitions = __esm({
6844
6890
  properties: {
6845
6891
  instancePath: {
6846
6892
  type: "string",
6847
- description: "Instance path (dot notation)"
6893
+ description: "Canonical DataModel path"
6848
6894
  },
6849
6895
  instance_id: {
6850
6896
  type: "string",
@@ -6863,7 +6909,7 @@ var init_definitions = __esm({
6863
6909
  properties: {
6864
6910
  instancePath: {
6865
6911
  type: "string",
6866
- description: "Instance path (dot notation)"
6912
+ description: "Canonical DataModel path"
6867
6913
  },
6868
6914
  attributeName: {
6869
6915
  type: "string",
@@ -6887,7 +6933,7 @@ var init_definitions = __esm({
6887
6933
  properties: {
6888
6934
  instancePath: {
6889
6935
  type: "string",
6890
- description: "Instance path (dot notation)"
6936
+ description: "Canonical DataModel path"
6891
6937
  },
6892
6938
  instance_id: {
6893
6939
  type: "string",
@@ -6906,7 +6952,7 @@ var init_definitions = __esm({
6906
6952
  properties: {
6907
6953
  instancePath: {
6908
6954
  type: "string",
6909
- description: "Instance path (dot notation)"
6955
+ description: "Canonical DataModel path"
6910
6956
  },
6911
6957
  tagName: {
6912
6958
  type: "string",
@@ -6929,7 +6975,7 @@ var init_definitions = __esm({
6929
6975
  properties: {
6930
6976
  instancePath: {
6931
6977
  type: "string",
6932
- description: "Instance path (dot notation)"
6978
+ description: "Canonical DataModel path"
6933
6979
  },
6934
6980
  tagName: {
6935
6981
  type: "string",
@@ -7100,7 +7146,7 @@ var init_definitions = __esm({
7100
7146
  {
7101
7147
  name: "start_playtest",
7102
7148
  category: "write",
7103
- description: "Start a simple single-player Studio playtest in play or run mode, waiting until a runtime peer registers with MCP. Captures print/warn/error via LogService. Poll with get_playtest_output, end with stop_playtest. For multi-client testing use multiplayer_test_start instead.",
7149
+ description: "Start a simple single-player Studio playtest in play or run mode, waiting until a runtime peer registers with MCP. Read print/warn/error output with get_runtime_logs, then end with stop_playtest. For multi-client testing use multiplayer_test_start instead.",
7104
7150
  inputSchema: {
7105
7151
  type: "object",
7106
7152
  properties: {
@@ -7124,7 +7170,7 @@ var init_definitions = __esm({
7124
7170
  {
7125
7171
  name: "stop_playtest",
7126
7172
  category: "write",
7127
- description: "Stop playtest and return all captured output.",
7173
+ description: "Stop playtest and wait for runtime peers to disconnect.",
7128
7174
  inputSchema: {
7129
7175
  type: "object",
7130
7176
  properties: {
@@ -7135,24 +7181,6 @@ var init_definitions = __esm({
7135
7181
  }
7136
7182
  }
7137
7183
  },
7138
- {
7139
- name: "get_playtest_output",
7140
- category: "read",
7141
- description: "Poll output buffer without stopping. Returns isRunning and captured messages.",
7142
- inputSchema: {
7143
- type: "object",
7144
- properties: {
7145
- target: {
7146
- type: "string",
7147
- description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
7148
- },
7149
- instance_id: {
7150
- type: "string",
7151
- description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
7152
- }
7153
- }
7154
- }
7155
- },
7156
7184
  {
7157
7185
  name: "set_network_profile",
7158
7186
  category: "write",
@@ -7536,7 +7564,7 @@ var init_definitions = __esm({
7536
7564
  {
7537
7565
  name: "get_runtime_logs",
7538
7566
  category: "read",
7539
- description: "Read the in-memory log buffers captured by Studio plugin peers. Each buffer captures ~64 KB of recent LogService.MessageOut entries; oldest entries drop when over budget. Entries include capturedBy for the plugin buffer that observed the log. In ordinary Studio play/run sessions, LogService reflects logs across edit/server/client, so script-origin peer is not reliable and entries omit peer. In StudioTestService multiplayer sessions only, peer attribution is reliable and entries also include peer. target=all (default) merges buffers and dedups same-message-and-level entries captured within 2s across different buffers.",
7567
+ description: "Read the in-memory log buffers captured by Studio plugin peers. Each buffer captures ~64 KB of recent LogService output; runtime peers seed from LogService:GetLogHistory() at plugin load so early startup logs emitted before the plugin finishes loading can still be returned, then continue capturing LogService.MessageOut entries. Oldest entries drop when over budget. Entries include capturedBy for the plugin buffer that observed the log. In ordinary Studio play/run sessions, LogService reflects logs across edit/server/client, so script-origin peer is not reliable and entries omit peer. In StudioTestService multiplayer sessions only, peer attribution is reliable and entries also include peer. target=all (default) merges buffers and dedups same-message-and-level entries captured within 2s across different buffers.",
7540
7568
  inputSchema: {
7541
7569
  type: "object",
7542
7570
  properties: {
@@ -7563,6 +7591,120 @@ var init_definitions = __esm({
7563
7591
  }
7564
7592
  }
7565
7593
  },
7594
+ {
7595
+ name: "capture_script_profiler",
7596
+ category: "read",
7597
+ description: `Capture one short ScriptProfilerService sample on a running server or client peer and return a compact CPU summary. Use this for Luau/script optimization, not render, physics, networking, or engine microprofiler lanes. Minimal flow: start or reproduce the workload, call capture_script_profiler with target="server" or a specific "client-N", inspect top_functions, patch the suspected hot path, then capture again with the same target/workload/duration_ms/frequency/filter/min_total_us to compare. top_functions is sorted by descending total_us after native/plugin/min/filter exclusions; each row includes rank plus function_index, the 1-based index into the raw Roblox Functions array. Function and node TotalDuration values follow Roblox's exported Script Profiler JSON format and are reported in microseconds as total_us. total_us is cumulative profiler TotalDuration during the capture; nested labels/functions can overlap, so do not sum rows as total CPU time. source is the runtime script path reported by Roblox and may need mapping back to editable source with search tools. If function names are too broad, add debug.profilebegin("Area:SpecificStep") / debug.profileend() around suspected code and pass filter="Area:" or another label prefix; matching custom labels appear in debug_labels and top_functions with their script source and no line number. The result echoes effective options in applied and omitted.filtered_out counts rows removed by filter. Keep captures short while actively triggering the behavior; duration_ms defaults to 1000 and is clamped to 100-15000. Pass output_path when you need the raw Roblox Script Profiler JSON for offline comparison or deeper analysis. This tool owns the start/stop/request profiler lifecycle for one capture and does not expose long-lived profiler sessions.`,
7598
+ inputSchema: {
7599
+ type: "object",
7600
+ properties: {
7601
+ target: {
7602
+ type: "string",
7603
+ pattern: "^(server|client-[0-9]+)$",
7604
+ description: 'Runtime peer to profile: "server" (default) or "client-N". Use get_connected_instances to discover available runtime roles. target="edit" is invalid because ScriptProfiler captures running code.'
7605
+ },
7606
+ duration_ms: {
7607
+ type: "number",
7608
+ default: 1e3,
7609
+ minimum: 100,
7610
+ maximum: 15e3,
7611
+ description: "Sample duration in milliseconds. Defaults to 1000; clamped to 100-15000 so the Studio bridge does not hang on long captures."
7612
+ },
7613
+ frequency: {
7614
+ type: "number",
7615
+ default: 1e3,
7616
+ minimum: 1,
7617
+ maximum: 1e4,
7618
+ description: "ScriptProfiler sampling frequency in samples per second (Hz). Defaults to 1000."
7619
+ },
7620
+ max_functions: {
7621
+ type: "number",
7622
+ default: 20,
7623
+ minimum: 1,
7624
+ maximum: 100,
7625
+ description: "Maximum number of top_functions and debug_labels to return. Defaults to 20; clamped to 1-100."
7626
+ },
7627
+ min_total_us: {
7628
+ type: "number",
7629
+ default: 0,
7630
+ minimum: 0,
7631
+ description: "Omit functions below this TotalDuration in microseconds after capture. Defaults to 0."
7632
+ },
7633
+ filter: {
7634
+ type: "string",
7635
+ description: "Optional case-insensitive substring matched against function name and source before top_functions are returned. Useful for focusing on one module or debug.profilebegin label prefix."
7636
+ },
7637
+ include_native: {
7638
+ type: "boolean",
7639
+ description: "Include native Roblox frames in top_functions. Defaults to false to keep optimization output focused on game Luau and debug labels."
7640
+ },
7641
+ include_plugin: {
7642
+ type: "boolean",
7643
+ description: "Include plugin frames in top_functions. Defaults to false because the MCP capture implementation can otherwise add noise."
7644
+ },
7645
+ output_path: {
7646
+ type: "string",
7647
+ description: "Optional local path where the MCP server writes the raw Script Profiler JSON. The tool result then includes output_path instead of inlining the raw JSON."
7648
+ },
7649
+ instance_id: {
7650
+ type: "string",
7651
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
7652
+ }
7653
+ }
7654
+ }
7655
+ },
7656
+ {
7657
+ name: "breakpoints",
7658
+ category: "write",
7659
+ description: 'Manage Studio debugger breakpoints through ScriptDebuggerService. Use this when the user asks to debug with Studio breakpoints. Prefer log breakpoints for agent debugging: pass log_message and let continue_execution default to true, reproduce the issue, then read get_runtime_logs filtered by "Breakpoint". Minimal flow: set a log breakpoint, run or trigger the behavior, call get_runtime_logs with filter="Breakpoint", then call action="clear" to remove MCP-managed breakpoints. Generated breakpoint logs are prefixed with "Breakpoint" plus script_path:line; Studio breakpoint errors also start with "Breakpoint", so this filter captures both successful breakpoint logs and breakpoint-related failures. Set breakpoints on target="edit" before starting a playtest when possible; for an already-running playtest target the runtime DataModel directly, such as "server" or "client-1". Do not set continue_execution=false unless the target DataModel already has a ScriptDebuggerService.OnStopped handler that returns Enum.DebuggerResumeType.Resume for breakpoint/non-exception stops; otherwise the playtest can get stuck and MCP can lose the server/client peers. Minimal OnStopped reference: local sds=game:GetService("ScriptDebuggerService"); sds.OnStopped=function(info) if info.Reason ~= Enum.ScriptStoppedReason.Exception then return Enum.DebuggerResumeType.Resume end print("EXCEPTION:", info.ExceptionText); return Enum.DebuggerResumeType.Resume end. MCP-managed breakpoints persist minimal script_path/line recovery data per place and target so action="list" and action="clear" can find tool-created edit/server/client breakpoints after MCP/plugin reloads. action="clear" removes only breakpoints created through this MCP tool by default; pass clear_all=true only when you intentionally want to clear every Studio breakpoint in the targeted DataModel, including user-created breakpoints. This tool only manages breakpoint lifecycle; it does not pause, resume, step, inspect variables, or install OnStopped callbacks. Requires Studio Debugger Luau API beta enabled.',
7660
+ inputSchema: {
7661
+ type: "object",
7662
+ properties: {
7663
+ action: {
7664
+ type: "string",
7665
+ enum: ["set", "remove", "clear", "list"],
7666
+ description: "Breakpoint action to run. set/remove require script_path and line. clear removes MCP-managed breakpoints by default. list returns breakpoints created through this MCP tool in the targeted DataModel."
7667
+ },
7668
+ clear_all: {
7669
+ type: "boolean",
7670
+ description: 'Only applies to action="clear". Omit or set false to remove only MCP-managed breakpoints tracked by this tool. Set true to call ScriptDebuggerService:ClearBreakpoints() and clear every Studio breakpoint in the targeted DataModel, including user-created breakpoints.'
7671
+ },
7672
+ script_path: {
7673
+ type: "string",
7674
+ description: 'Canonical path to a LuaSourceContainer, for example game.ServerScriptService.Main or game.ServerScriptService[".dir"].ReproScript. Required for set/remove.'
7675
+ },
7676
+ line: {
7677
+ type: "number",
7678
+ description: "1-based line number for set/remove."
7679
+ },
7680
+ enabled: {
7681
+ type: "boolean",
7682
+ description: "Whether the breakpoint is enabled when set. Defaults to true."
7683
+ },
7684
+ condition: {
7685
+ type: "string",
7686
+ description: "Optional Luau condition expression for set."
7687
+ },
7688
+ log_message: {
7689
+ type: "string",
7690
+ description: `Optional Studio breakpoint log expression list for set, such as "'health', health". Literal text must be quoted as a Luau string. The tool prefixes this with "Breakpoint" and script_path:line. After reproducing, read get_runtime_logs with filter="Breakpoint" so breakpoint logs and Studio breakpoint errors are both visible.`
7691
+ },
7692
+ continue_execution: {
7693
+ type: "boolean",
7694
+ description: "Whether the breakpoint should log and continue without pausing. Defaults to true when log_message is provided; otherwise false. Only set false when you have first installed a ScriptDebuggerService.OnStopped handler on the same target that resumes breakpoint/non-exception stops with Enum.DebuggerResumeType.Resume; without that handler the playtest can get stuck and MCP can lose server/client peers."
7695
+ },
7696
+ target: {
7697
+ type: "string",
7698
+ description: 'Peer to target: "edit" (default), "server", or "client-N". Set edit breakpoints before playtests; target server/client-N for running play DataModels.'
7699
+ },
7700
+ instance_id: {
7701
+ type: "string",
7702
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
7703
+ }
7704
+ },
7705
+ required: ["action"]
7706
+ }
7707
+ },
7566
7708
  // === Multi-Instance ===
7567
7709
  {
7568
7710
  name: "get_connected_instances",
@@ -7612,7 +7754,7 @@ var init_definitions = __esm({
7612
7754
  properties: {
7613
7755
  instancePath: {
7614
7756
  type: "string",
7615
- description: "Path to the Model or Folder to export (dot notation)"
7757
+ description: "Canonical path to the Model or Folder to export"
7616
7758
  },
7617
7759
  outputId: {
7618
7760
  type: "string",
@@ -7769,7 +7911,7 @@ part(0,2,0,2,1,1,"b")`,
7769
7911
  },
7770
7912
  targetPath: {
7771
7913
  type: "string",
7772
- description: "Parent instance path where the model will be created"
7914
+ description: "Canonical parent DataModel path where the model will be created"
7773
7915
  },
7774
7916
  position: {
7775
7917
  type: "array",
@@ -7900,7 +8042,7 @@ part(0,2,0,2,1,1,"b")`,
7900
8042
  },
7901
8043
  targetPath: {
7902
8044
  type: "string",
7903
- description: "Parent instance path for the scene (default: game.Workspace)"
8045
+ description: "Canonical parent DataModel path for the scene (default: game.Workspace)"
7904
8046
  },
7905
8047
  instance_id: {
7906
8048
  type: "string",
@@ -7992,7 +8134,7 @@ part(0,2,0,2,1,1,"b")`,
7992
8134
  },
7993
8135
  parentPath: {
7994
8136
  type: "string",
7995
- description: "Parent instance path (default: game.Workspace)"
8137
+ description: "Canonical parent DataModel path (default: game.Workspace)"
7996
8138
  },
7997
8139
  position: {
7998
8140
  type: "object",
@@ -8170,42 +8312,6 @@ part(0,2,0,2,1,1,"b")`,
8170
8312
  }
8171
8313
  }
8172
8314
  },
8173
- // === Character Navigation ===
8174
- {
8175
- name: "character_navigation",
8176
- category: "write",
8177
- description: 'Move the player character to a target position or instance during playtest. Uses PathfindingService for automatic navigation around obstacles, falling back to direct movement. Requires an active playtest in "play" mode. Does NOT simulate player input - moves the character directly.',
8178
- inputSchema: {
8179
- type: "object",
8180
- properties: {
8181
- position: {
8182
- type: "array",
8183
- items: { type: "number" },
8184
- description: "Target world position [x, y, z]. Either this or instancePath is required."
8185
- },
8186
- instancePath: {
8187
- type: "string",
8188
- description: "Instance to navigate to (dot notation). The character walks to its Position. Either this or position is required."
8189
- },
8190
- waitForCompletion: {
8191
- type: "boolean",
8192
- description: "Wait for the character to arrive before returning (default: true)"
8193
- },
8194
- timeout: {
8195
- type: "number",
8196
- description: "Max seconds to wait for navigation to complete (default: 25)"
8197
- },
8198
- target: {
8199
- type: "string",
8200
- description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
8201
- },
8202
- instance_id: {
8203
- type: "string",
8204
- description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
8205
- }
8206
- }
8207
- }
8208
- },
8209
8315
  // === Instance Operations ===
8210
8316
  {
8211
8317
  name: "clone_object",
@@ -8216,11 +8322,11 @@ part(0,2,0,2,1,1,"b")`,
8216
8322
  properties: {
8217
8323
  instancePath: {
8218
8324
  type: "string",
8219
- description: "Path of the instance to clone"
8325
+ description: "Canonical path of the instance to clone"
8220
8326
  },
8221
8327
  targetParentPath: {
8222
8328
  type: "string",
8223
- description: "Path of the parent to place the clone under"
8329
+ description: "Canonical path of the parent to place the clone under"
8224
8330
  },
8225
8331
  instance_id: {
8226
8332
  type: "string",
@@ -8240,7 +8346,7 @@ part(0,2,0,2,1,1,"b")`,
8240
8346
  properties: {
8241
8347
  instancePath: {
8242
8348
  type: "string",
8243
- description: "Root instance path"
8349
+ description: "Canonical root DataModel path"
8244
8350
  },
8245
8351
  maxDepth: {
8246
8352
  type: "number",
@@ -8267,11 +8373,11 @@ part(0,2,0,2,1,1,"b")`,
8267
8373
  properties: {
8268
8374
  instancePathA: {
8269
8375
  type: "string",
8270
- description: "First instance path"
8376
+ description: "First canonical DataModel path"
8271
8377
  },
8272
8378
  instancePathB: {
8273
8379
  type: "string",
8274
- description: "Second instance path"
8380
+ description: "Second canonical DataModel path"
8275
8381
  },
8276
8382
  instance_id: {
8277
8383
  type: "string",
@@ -8281,29 +8387,6 @@ part(0,2,0,2,1,1,"b")`,
8281
8387
  required: ["instancePathA", "instancePathB"]
8282
8388
  }
8283
8389
  },
8284
- // === Output & Diagnostics ===
8285
- {
8286
- name: "get_output_log",
8287
- category: "read",
8288
- description: "Get the Studio output log history. Works in both edit and play mode.",
8289
- inputSchema: {
8290
- type: "object",
8291
- properties: {
8292
- maxEntries: {
8293
- type: "number",
8294
- description: "Maximum number of log entries to return (default: 100)"
8295
- },
8296
- messageType: {
8297
- type: "string",
8298
- description: 'Filter by message type (e.g. "Enum.MessageType.MessageOutput", "Enum.MessageType.MessageWarning", "Enum.MessageType.MessageError")'
8299
- },
8300
- instance_id: {
8301
- type: "string",
8302
- description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
8303
- }
8304
- }
8305
- }
8306
- },
8307
8390
  // === Bulk Attributes ===
8308
8391
  {
8309
8392
  name: "bulk_set_attributes",
@@ -8314,7 +8397,7 @@ part(0,2,0,2,1,1,"b")`,
8314
8397
  properties: {
8315
8398
  instancePath: {
8316
8399
  type: "string",
8317
- description: "Instance path"
8400
+ description: "Canonical DataModel path"
8318
8401
  },
8319
8402
  attributes: {
8320
8403
  type: "object",
@@ -8396,7 +8479,7 @@ part(0,2,0,2,1,1,"b")`,
8396
8479
  instance_paths: {
8397
8480
  type: "array",
8398
8481
  items: { type: "string" },
8399
- description: 'DataModel paths to serialize (e.g. ["Workspace.TestRig", "ServerStorage.Templates.NPC"])'
8482
+ description: 'Canonical DataModel paths to serialize (e.g. ["game.Workspace.TestRig", "game.ServerStorage.Templates.NPC"])'
8400
8483
  },
8401
8484
  output_path: {
8402
8485
  type: "string",
@@ -8438,7 +8521,7 @@ part(0,2,0,2,1,1,"b")`,
8438
8521
  },
8439
8522
  parent_path: {
8440
8523
  type: "string",
8441
- description: 'DataModel path of the Instance to parent imported instances under (e.g. "ServerStorage.Imported")'
8524
+ description: 'Canonical DataModel path of the Instance to parent imported instances under (e.g. "game.ServerStorage.Imported")'
8442
8525
  },
8443
8526
  target: {
8444
8527
  type: "string",