@chrrxs/robloxstudio-mcp 2.16.3 → 2.17.0
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 +232 -65
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +1100 -168
- package/studio-plugin/MCPPlugin.rbxmx +1100 -168
- package/studio-plugin/src/modules/ClientBroker.ts +10 -0
- package/studio-plugin/src/modules/Communication.ts +4 -2
- package/studio-plugin/src/modules/RuntimeLogBuffer.ts +36 -10
- package/studio-plugin/src/modules/handlers/BreakpointHandlers.ts +460 -0
- package/studio-plugin/src/modules/handlers/QueryHandlers.ts +0 -33
- package/studio-plugin/src/modules/handlers/ScriptProfilerHandlers.ts +386 -0
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +14 -44
- package/studio-plugin/src/server/index.server.ts +2 -0
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,7 +928,6 @@ 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),
|
|
@@ -3225,6 +3234,16 @@ var init_tools = __esm({
|
|
|
3225
3234
|
const instanceIds = new Set(this.bridge.getEquivalentInstanceIds(instanceId));
|
|
3226
3235
|
return this.bridge.getInstances().filter((i) => instanceIds.has(i.instanceId) && (i.role === "server" || /^client-\d+$/.test(i.role))).map((i) => ({ instanceId: i.instanceId, role: i.role }));
|
|
3227
3236
|
}
|
|
3237
|
+
_compactSimulationResetResult(result) {
|
|
3238
|
+
const compact = {};
|
|
3239
|
+
if ("network" in result)
|
|
3240
|
+
compact.network = true;
|
|
3241
|
+
if ("deviceSimulator" in result)
|
|
3242
|
+
compact.deviceSimulator = true;
|
|
3243
|
+
if (result.errors !== void 0)
|
|
3244
|
+
compact.errors = result.errors;
|
|
3245
|
+
return compact;
|
|
3246
|
+
}
|
|
3228
3247
|
_resolveDeviceSimulatorSingleTarget(target, instance_id, toolName) {
|
|
3229
3248
|
const selectedTarget = target ?? "edit";
|
|
3230
3249
|
if (selectedTarget === "server" || selectedTarget === "all" || selectedTarget === "all-clients" || selectedTarget === "edit-proxy") {
|
|
@@ -4137,10 +4156,12 @@ ${code}`
|
|
|
4137
4156
|
}
|
|
4138
4157
|
return { role, result };
|
|
4139
4158
|
}));
|
|
4159
|
+
const rawRoles = {};
|
|
4140
4160
|
const roles = {};
|
|
4141
4161
|
const failures = [];
|
|
4142
4162
|
for (const entry of roleEntries) {
|
|
4143
|
-
|
|
4163
|
+
rawRoles[entry.role] = entry.result;
|
|
4164
|
+
roles[entry.role] = this._compactSimulationResetResult(entry.result);
|
|
4144
4165
|
const errors = entry.result.errors;
|
|
4145
4166
|
if (errors) {
|
|
4146
4167
|
for (const [kind, message] of Object.entries(errors)) {
|
|
@@ -4149,15 +4170,15 @@ ${code}`
|
|
|
4149
4170
|
}
|
|
4150
4171
|
}
|
|
4151
4172
|
const body = {
|
|
4173
|
+
success: true,
|
|
4152
4174
|
target: resolved.selectedTarget,
|
|
4153
4175
|
network: resetNetwork,
|
|
4154
4176
|
deviceSimulator: resetDeviceSimulator,
|
|
4155
4177
|
roles,
|
|
4156
|
-
warnings: resolved.warnings
|
|
4157
|
-
persistenceNotes: SIMULATION_PERSISTENCE_NOTES
|
|
4178
|
+
warnings: resolved.warnings
|
|
4158
4179
|
};
|
|
4159
4180
|
if (failures.length > 0) {
|
|
4160
|
-
throw new Error(`reset_simulation_state failed for ${failures.join("; ")}. Partial result: ${JSON.stringify(body)}`);
|
|
4181
|
+
throw new Error(`reset_simulation_state failed for ${failures.join("; ")}. Partial result: ${JSON.stringify({ ...body, roles: rawRoles })}`);
|
|
4161
4182
|
}
|
|
4162
4183
|
return {
|
|
4163
4184
|
content: [{
|
|
@@ -4446,6 +4467,76 @@ ${code}`
|
|
|
4446
4467
|
content: [{ type: "text", text: JSON.stringify(body) }]
|
|
4447
4468
|
};
|
|
4448
4469
|
}
|
|
4470
|
+
async captureScriptProfiler(target, request = {}, instance_id) {
|
|
4471
|
+
const targetRole = target ?? "server";
|
|
4472
|
+
const data = { ...request };
|
|
4473
|
+
const outputPath = data.output_path;
|
|
4474
|
+
delete data.output_path;
|
|
4475
|
+
if (outputPath !== void 0 && typeof outputPath !== "string") {
|
|
4476
|
+
throw new Error("output_path must be a string when provided");
|
|
4477
|
+
}
|
|
4478
|
+
if (outputPath) {
|
|
4479
|
+
data.__mcp_include_raw_json = true;
|
|
4480
|
+
}
|
|
4481
|
+
const resolved = this.bridge.resolveTarget({ instance_id, target: targetRole });
|
|
4482
|
+
if (!resolved.ok)
|
|
4483
|
+
throw new RoutingFailure(resolved.error);
|
|
4484
|
+
if (resolved.mode !== "single") {
|
|
4485
|
+
throw new RoutingFailure({
|
|
4486
|
+
code: "target_role_not_present_on_instance",
|
|
4487
|
+
message: 'capture_script_profiler profiles one runtime peer at a time. Pick target="server" or a specific "client-N".',
|
|
4488
|
+
data: {
|
|
4489
|
+
instances: this.bridge.getPublicInstances(),
|
|
4490
|
+
count: this.bridge.getInstances().length
|
|
4491
|
+
}
|
|
4492
|
+
});
|
|
4493
|
+
}
|
|
4494
|
+
data.__mcp_instance_id = resolved.targetInstanceId;
|
|
4495
|
+
data.__mcp_target_role = resolved.targetRole;
|
|
4496
|
+
const response = await this.client.request("/api/capture-script-profiler", data, resolved.targetInstanceId, resolved.targetRole);
|
|
4497
|
+
const body = response !== null && typeof response === "object" && !Array.isArray(response) ? { ...response, target: resolved.targetRole } : response;
|
|
4498
|
+
if (body !== null && typeof body === "object" && !Array.isArray(body)) {
|
|
4499
|
+
const mutable = body;
|
|
4500
|
+
const rawJson = mutable.raw_json;
|
|
4501
|
+
if (typeof rawJson === "string") {
|
|
4502
|
+
if (typeof outputPath === "string" && outputPath !== "") {
|
|
4503
|
+
const resolvedOutputPath = path.resolve(outputPath);
|
|
4504
|
+
fs.mkdirSync(path.dirname(resolvedOutputPath), { recursive: true });
|
|
4505
|
+
fs.writeFileSync(resolvedOutputPath, rawJson, "utf8");
|
|
4506
|
+
mutable.output_path = resolvedOutputPath;
|
|
4507
|
+
}
|
|
4508
|
+
delete mutable.raw_json;
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
return { content: [{ type: "text", text: JSON.stringify(body) }] };
|
|
4512
|
+
}
|
|
4513
|
+
async breakpoints(action, request = {}, target, instance_id) {
|
|
4514
|
+
if (!action || typeof action !== "string") {
|
|
4515
|
+
throw new Error("breakpoints requires action=set|remove|clear|list");
|
|
4516
|
+
}
|
|
4517
|
+
const targetRole = target ?? "edit";
|
|
4518
|
+
const data = { ...request, action };
|
|
4519
|
+
delete data.target;
|
|
4520
|
+
delete data.instance_id;
|
|
4521
|
+
const resolved = this.bridge.resolveTarget({ instance_id, target: targetRole });
|
|
4522
|
+
if (!resolved.ok)
|
|
4523
|
+
throw new RoutingFailure(resolved.error);
|
|
4524
|
+
if (resolved.mode !== "single") {
|
|
4525
|
+
throw new RoutingFailure({
|
|
4526
|
+
code: "target_role_not_present_on_instance",
|
|
4527
|
+
message: "This tool does not support target=all. Pick a specific role or omit target.",
|
|
4528
|
+
data: {
|
|
4529
|
+
instances: this.bridge.getPublicInstances(),
|
|
4530
|
+
count: this.bridge.getInstances().length
|
|
4531
|
+
}
|
|
4532
|
+
});
|
|
4533
|
+
}
|
|
4534
|
+
data.__mcp_instance_id = resolved.targetInstanceId;
|
|
4535
|
+
data.__mcp_target_role = resolved.targetRole;
|
|
4536
|
+
const response = await this.client.request("/api/breakpoints", data, resolved.targetInstanceId, resolved.targetRole);
|
|
4537
|
+
const body = response !== null && typeof response === "object" && !Array.isArray(response) ? { ...response, target: resolved.targetRole } : response;
|
|
4538
|
+
return { content: [{ type: "text", text: JSON.stringify(body) }] };
|
|
4539
|
+
}
|
|
4449
4540
|
async startPlaytest(mode, numPlayers, instance_id) {
|
|
4450
4541
|
if (mode !== "play" && mode !== "run") {
|
|
4451
4542
|
throw new Error('mode must be "play" or "run"');
|
|
@@ -4468,6 +4559,24 @@ ${code}`
|
|
|
4468
4559
|
}
|
|
4469
4560
|
});
|
|
4470
4561
|
}
|
|
4562
|
+
const existingRuntime = this._runtimeTargetsForEquivalentInstances(resolved.targetInstanceId);
|
|
4563
|
+
if (existingRuntime.length > 0) {
|
|
4564
|
+
const roles = this._rolesForEquivalentInstances(resolved.targetInstanceId);
|
|
4565
|
+
return {
|
|
4566
|
+
content: [{
|
|
4567
|
+
type: "text",
|
|
4568
|
+
text: JSON.stringify({
|
|
4569
|
+
success: false,
|
|
4570
|
+
error: "Playtest already running.",
|
|
4571
|
+
message: "A playtest is already running for this Studio place. Stop the current playtest before starting another.",
|
|
4572
|
+
runtimeReady: true,
|
|
4573
|
+
timedOut: false,
|
|
4574
|
+
roles,
|
|
4575
|
+
runtimeRoles: existingRuntime.map((target) => target.role)
|
|
4576
|
+
})
|
|
4577
|
+
}]
|
|
4578
|
+
};
|
|
4579
|
+
}
|
|
4471
4580
|
const response = await this.client.request("/api/start-playtest", data, resolved.targetInstanceId, resolved.targetRole);
|
|
4472
4581
|
let wait;
|
|
4473
4582
|
if (response?.success === true) {
|
|
@@ -4539,17 +4648,6 @@ ${code}`
|
|
|
4539
4648
|
content: [{ type: "text", text: JSON.stringify(body) }]
|
|
4540
4649
|
};
|
|
4541
4650
|
}
|
|
4542
|
-
async getPlaytestOutput(target, instance_id) {
|
|
4543
|
-
const response = await this._callSingle("/api/get-playtest-output", {}, target || "edit", instance_id);
|
|
4544
|
-
return {
|
|
4545
|
-
content: [
|
|
4546
|
-
{
|
|
4547
|
-
type: "text",
|
|
4548
|
-
text: JSON.stringify(response)
|
|
4549
|
-
}
|
|
4550
|
-
]
|
|
4551
|
-
};
|
|
4552
|
-
}
|
|
4553
4651
|
async _buildMultiplayerState(instanceId) {
|
|
4554
4652
|
const peers = this.bridge.getPublicInstances().filter((i) => i.instanceId === instanceId).sort((a, b) => a.role.localeCompare(b.role));
|
|
4555
4653
|
const body = {
|
|
@@ -5538,10 +5636,6 @@ ${code}`
|
|
|
5538
5636
|
const response = await this._callSingle("/api/compare-instances", { instancePathA, instancePathB }, void 0, instance_id);
|
|
5539
5637
|
return { content: [{ type: "text", text: JSON.stringify(response) }] };
|
|
5540
5638
|
}
|
|
5541
|
-
async getOutputLog(maxEntries, messageType, instance_id) {
|
|
5542
|
-
const response = await this._callSingle("/api/get-output-log", { maxEntries, messageType }, void 0, instance_id);
|
|
5543
|
-
return { content: [{ type: "text", text: JSON.stringify(response) }] };
|
|
5544
|
-
}
|
|
5545
5639
|
async bulkSetAttributes(instancePath, attributes, instance_id) {
|
|
5546
5640
|
if (!instancePath || !attributes) {
|
|
5547
5641
|
throw new Error("instancePath and attributes are required for bulk_set_attributes");
|
|
@@ -7070,7 +7164,7 @@ var init_definitions = __esm({
|
|
|
7070
7164
|
{
|
|
7071
7165
|
name: "start_playtest",
|
|
7072
7166
|
category: "write",
|
|
7073
|
-
description: "Start a simple single-player Studio playtest in play or run mode, waiting until a runtime peer registers with MCP.
|
|
7167
|
+
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.",
|
|
7074
7168
|
inputSchema: {
|
|
7075
7169
|
type: "object",
|
|
7076
7170
|
properties: {
|
|
@@ -7094,7 +7188,7 @@ var init_definitions = __esm({
|
|
|
7094
7188
|
{
|
|
7095
7189
|
name: "stop_playtest",
|
|
7096
7190
|
category: "write",
|
|
7097
|
-
description: "Stop playtest and
|
|
7191
|
+
description: "Stop playtest and wait for runtime peers to disconnect.",
|
|
7098
7192
|
inputSchema: {
|
|
7099
7193
|
type: "object",
|
|
7100
7194
|
properties: {
|
|
@@ -7105,24 +7199,6 @@ var init_definitions = __esm({
|
|
|
7105
7199
|
}
|
|
7106
7200
|
}
|
|
7107
7201
|
},
|
|
7108
|
-
{
|
|
7109
|
-
name: "get_playtest_output",
|
|
7110
|
-
category: "read",
|
|
7111
|
-
description: "Poll output buffer without stopping. Returns isRunning and captured messages.",
|
|
7112
|
-
inputSchema: {
|
|
7113
|
-
type: "object",
|
|
7114
|
-
properties: {
|
|
7115
|
-
target: {
|
|
7116
|
-
type: "string",
|
|
7117
|
-
description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
|
|
7118
|
-
},
|
|
7119
|
-
instance_id: {
|
|
7120
|
-
type: "string",
|
|
7121
|
-
description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
|
|
7122
|
-
}
|
|
7123
|
-
}
|
|
7124
|
-
}
|
|
7125
|
-
},
|
|
7126
7202
|
{
|
|
7127
7203
|
name: "set_network_profile",
|
|
7128
7204
|
category: "write",
|
|
@@ -7506,7 +7582,7 @@ var init_definitions = __esm({
|
|
|
7506
7582
|
{
|
|
7507
7583
|
name: "get_runtime_logs",
|
|
7508
7584
|
category: "read",
|
|
7509
|
-
description: "Read the in-memory log buffers captured by Studio plugin peers. Each buffer captures ~64 KB of recent LogService.MessageOut entries
|
|
7585
|
+
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.",
|
|
7510
7586
|
inputSchema: {
|
|
7511
7587
|
type: "object",
|
|
7512
7588
|
properties: {
|
|
@@ -7533,6 +7609,120 @@ var init_definitions = __esm({
|
|
|
7533
7609
|
}
|
|
7534
7610
|
}
|
|
7535
7611
|
},
|
|
7612
|
+
{
|
|
7613
|
+
name: "capture_script_profiler",
|
|
7614
|
+
category: "read",
|
|
7615
|
+
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.`,
|
|
7616
|
+
inputSchema: {
|
|
7617
|
+
type: "object",
|
|
7618
|
+
properties: {
|
|
7619
|
+
target: {
|
|
7620
|
+
type: "string",
|
|
7621
|
+
pattern: "^(server|client-[0-9]+)$",
|
|
7622
|
+
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.'
|
|
7623
|
+
},
|
|
7624
|
+
duration_ms: {
|
|
7625
|
+
type: "number",
|
|
7626
|
+
default: 1e3,
|
|
7627
|
+
minimum: 100,
|
|
7628
|
+
maximum: 15e3,
|
|
7629
|
+
description: "Sample duration in milliseconds. Defaults to 1000; clamped to 100-15000 so the Studio bridge does not hang on long captures."
|
|
7630
|
+
},
|
|
7631
|
+
frequency: {
|
|
7632
|
+
type: "number",
|
|
7633
|
+
default: 1e3,
|
|
7634
|
+
minimum: 1,
|
|
7635
|
+
maximum: 1e4,
|
|
7636
|
+
description: "ScriptProfiler sampling frequency in samples per second (Hz). Defaults to 1000."
|
|
7637
|
+
},
|
|
7638
|
+
max_functions: {
|
|
7639
|
+
type: "number",
|
|
7640
|
+
default: 20,
|
|
7641
|
+
minimum: 1,
|
|
7642
|
+
maximum: 100,
|
|
7643
|
+
description: "Maximum number of top_functions and debug_labels to return. Defaults to 20; clamped to 1-100."
|
|
7644
|
+
},
|
|
7645
|
+
min_total_us: {
|
|
7646
|
+
type: "number",
|
|
7647
|
+
default: 0,
|
|
7648
|
+
minimum: 0,
|
|
7649
|
+
description: "Omit functions below this TotalDuration in microseconds after capture. Defaults to 0."
|
|
7650
|
+
},
|
|
7651
|
+
filter: {
|
|
7652
|
+
type: "string",
|
|
7653
|
+
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."
|
|
7654
|
+
},
|
|
7655
|
+
include_native: {
|
|
7656
|
+
type: "boolean",
|
|
7657
|
+
description: "Include native Roblox frames in top_functions. Defaults to false to keep optimization output focused on game Luau and debug labels."
|
|
7658
|
+
},
|
|
7659
|
+
include_plugin: {
|
|
7660
|
+
type: "boolean",
|
|
7661
|
+
description: "Include plugin frames in top_functions. Defaults to false because the MCP capture implementation can otherwise add noise."
|
|
7662
|
+
},
|
|
7663
|
+
output_path: {
|
|
7664
|
+
type: "string",
|
|
7665
|
+
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."
|
|
7666
|
+
},
|
|
7667
|
+
instance_id: {
|
|
7668
|
+
type: "string",
|
|
7669
|
+
description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
|
|
7670
|
+
}
|
|
7671
|
+
}
|
|
7672
|
+
}
|
|
7673
|
+
},
|
|
7674
|
+
{
|
|
7675
|
+
name: "breakpoints",
|
|
7676
|
+
category: "write",
|
|
7677
|
+
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.',
|
|
7678
|
+
inputSchema: {
|
|
7679
|
+
type: "object",
|
|
7680
|
+
properties: {
|
|
7681
|
+
action: {
|
|
7682
|
+
type: "string",
|
|
7683
|
+
enum: ["set", "remove", "clear", "list"],
|
|
7684
|
+
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."
|
|
7685
|
+
},
|
|
7686
|
+
clear_all: {
|
|
7687
|
+
type: "boolean",
|
|
7688
|
+
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.'
|
|
7689
|
+
},
|
|
7690
|
+
script_path: {
|
|
7691
|
+
type: "string",
|
|
7692
|
+
description: "Path to a LuaSourceContainer, for example game.ServerScriptService.Main. Required for set/remove."
|
|
7693
|
+
},
|
|
7694
|
+
line: {
|
|
7695
|
+
type: "number",
|
|
7696
|
+
description: "1-based line number for set/remove."
|
|
7697
|
+
},
|
|
7698
|
+
enabled: {
|
|
7699
|
+
type: "boolean",
|
|
7700
|
+
description: "Whether the breakpoint is enabled when set. Defaults to true."
|
|
7701
|
+
},
|
|
7702
|
+
condition: {
|
|
7703
|
+
type: "string",
|
|
7704
|
+
description: "Optional Luau condition expression for set."
|
|
7705
|
+
},
|
|
7706
|
+
log_message: {
|
|
7707
|
+
type: "string",
|
|
7708
|
+
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.`
|
|
7709
|
+
},
|
|
7710
|
+
continue_execution: {
|
|
7711
|
+
type: "boolean",
|
|
7712
|
+
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."
|
|
7713
|
+
},
|
|
7714
|
+
target: {
|
|
7715
|
+
type: "string",
|
|
7716
|
+
description: 'Peer to target: "edit" (default), "server", or "client-N". Set edit breakpoints before playtests; target server/client-N for running play DataModels.'
|
|
7717
|
+
},
|
|
7718
|
+
instance_id: {
|
|
7719
|
+
type: "string",
|
|
7720
|
+
description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
|
|
7721
|
+
}
|
|
7722
|
+
},
|
|
7723
|
+
required: ["action"]
|
|
7724
|
+
}
|
|
7725
|
+
},
|
|
7536
7726
|
// === Multi-Instance ===
|
|
7537
7727
|
{
|
|
7538
7728
|
name: "get_connected_instances",
|
|
@@ -8251,29 +8441,6 @@ part(0,2,0,2,1,1,"b")`,
|
|
|
8251
8441
|
required: ["instancePathA", "instancePathB"]
|
|
8252
8442
|
}
|
|
8253
8443
|
},
|
|
8254
|
-
// === Output & Diagnostics ===
|
|
8255
|
-
{
|
|
8256
|
-
name: "get_output_log",
|
|
8257
|
-
category: "read",
|
|
8258
|
-
description: "Get the Studio output log history. Works in both edit and play mode.",
|
|
8259
|
-
inputSchema: {
|
|
8260
|
-
type: "object",
|
|
8261
|
-
properties: {
|
|
8262
|
-
maxEntries: {
|
|
8263
|
-
type: "number",
|
|
8264
|
-
description: "Maximum number of log entries to return (default: 100)"
|
|
8265
|
-
},
|
|
8266
|
-
messageType: {
|
|
8267
|
-
type: "string",
|
|
8268
|
-
description: 'Filter by message type (e.g. "Enum.MessageType.MessageOutput", "Enum.MessageType.MessageWarning", "Enum.MessageType.MessageError")'
|
|
8269
|
-
},
|
|
8270
|
-
instance_id: {
|
|
8271
|
-
type: "string",
|
|
8272
|
-
description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
|
|
8273
|
-
}
|
|
8274
|
-
}
|
|
8275
|
-
}
|
|
8276
|
-
},
|
|
8277
8444
|
// === Bulk Attributes ===
|
|
8278
8445
|
{
|
|
8279
8446
|
name: "bulk_set_attributes",
|