conduit-mcp 2.0.4 → 2.1.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.
|
@@ -698,6 +698,10 @@ function truncateToTokenBudget(text, budget) {
|
|
|
698
698
|
|
|
699
699
|
// src/utils/formatting.ts
|
|
700
700
|
function formatTree(data, depth = 0, indent = "") {
|
|
701
|
+
if (data.className === "_collapsed") {
|
|
702
|
+
return `${indent} *${data.name}*
|
|
703
|
+
`;
|
|
704
|
+
}
|
|
701
705
|
let line = `${indent}- **${data.name}** \`${data.className}\``;
|
|
702
706
|
if (data.properties && Object.keys(data.properties).length > 0) {
|
|
703
707
|
const props = Object.entries(data.properties).map(([k, v]) => `${k}=${formatValue(v)}`).join(", ");
|
|
@@ -844,10 +848,13 @@ function register(server, bridge) {
|
|
|
844
848
|
"get_info",
|
|
845
849
|
{
|
|
846
850
|
title: "Get Instance Info",
|
|
847
|
-
description: "Get detailed information about a single instance: class, parent, children count, properties, attributes, tags, and optionally a typed property list \u2014 all in one call.",
|
|
851
|
+
description: "Get detailed information about a single instance: class, parent, children count, properties, attributes, tags, and optionally a typed property list \u2014 all in one call.\n\nUse this to inspect runtime UI layout (Size, Position, Transparency, Visible, Text, etc.), part properties, lighting, sounds, and more. Reads ~60 common properties automatically. Use `propertyNames` to request only specific properties for a leaner response.\n\nFor UI debugging: check BackgroundTransparency, Visible, Size, Position, AnchorPoint, ZIndex, LayoutOrder on GuiObjects.",
|
|
848
852
|
inputSchema: z.object({
|
|
849
853
|
path: z.string().describe("Path to the instance, e.g. 'game.Workspace.Part'"),
|
|
850
|
-
includeProperties: z.boolean().default(true).describe("Include property values"),
|
|
854
|
+
includeProperties: z.boolean().default(true).describe("Include property values (reads ~60 common properties)"),
|
|
855
|
+
propertyNames: z.array(z.string()).optional().describe(
|
|
856
|
+
"Request only these specific properties by name, e.g. ['Size', 'Position', 'Transparency']. When set, only these properties are returned (much leaner than the full property dump)."
|
|
857
|
+
),
|
|
851
858
|
includeAttributes: z.boolean().default(true).describe("Include custom attributes"),
|
|
852
859
|
includeTags: z.boolean().default(true).describe("Include CollectionService tags"),
|
|
853
860
|
includePropertyList: z.boolean().default(false).describe("Include a typed list of all discoverable properties with types and values"),
|
|
@@ -865,6 +872,7 @@ function register(server, bridge) {
|
|
|
865
872
|
const result = await bridge.send("get_info", {
|
|
866
873
|
path: params.path,
|
|
867
874
|
includeProperties: params.includeProperties,
|
|
875
|
+
propertyNames: params.propertyNames,
|
|
868
876
|
includeAttributes: params.includeAttributes,
|
|
869
877
|
includeTags: params.includeTags,
|
|
870
878
|
includePropertyList: params.includePropertyList,
|
|
@@ -1187,13 +1195,26 @@ function registerReadScript(server, bridge) {
|
|
|
1187
1195
|
"read_script",
|
|
1188
1196
|
{
|
|
1189
1197
|
title: "Read Script Source",
|
|
1190
|
-
description: "Read the Lua source code of a script instance. Optionally restrict to a line range.",
|
|
1198
|
+
description: "Read the Lua source code of a script instance. Optionally restrict to a line range. Use outline=true to get just function signatures and top-level declarations with line numbers \u2014 much cheaper than reading the full source for large scripts.\n\nSupports batch mode: pass `paths` (array) instead of `path` to read multiple scripts in a single call. Each entry can independently use outline or lineRange.",
|
|
1191
1199
|
inputSchema: z3.object({
|
|
1192
|
-
path: z3.string().describe("Path to
|
|
1200
|
+
path: z3.string().optional().describe("Path to a single script instance"),
|
|
1201
|
+
paths: z3.array(
|
|
1202
|
+
z3.object({
|
|
1203
|
+
path: z3.string().describe("Script path"),
|
|
1204
|
+
outline: z3.boolean().default(false).describe("Return outline instead of source"),
|
|
1205
|
+
lineRange: z3.object({
|
|
1206
|
+
start: z3.number().int().min(1).describe("Start line (1-based)"),
|
|
1207
|
+
end: z3.number().int().min(1).describe("End line (1-based, inclusive)")
|
|
1208
|
+
}).optional().describe("Optional line range")
|
|
1209
|
+
})
|
|
1210
|
+
).optional().describe("Batch mode: read multiple scripts in one call. Each entry can independently use outline or lineRange."),
|
|
1211
|
+
outline: z3.boolean().default(false).describe(
|
|
1212
|
+
"If true, return only a structural outline (function signatures, top-level locals, types, return) with line numbers instead of full source. Ideal for navigating large scripts."
|
|
1213
|
+
),
|
|
1193
1214
|
lineRange: z3.object({
|
|
1194
1215
|
start: z3.number().int().min(1).describe("Start line (1-based)"),
|
|
1195
1216
|
end: z3.number().int().min(1).describe("End line (1-based, inclusive)")
|
|
1196
|
-
}).optional().describe("Optional line range to read"),
|
|
1217
|
+
}).optional().describe("Optional line range to read (ignored when outline=true)"),
|
|
1197
1218
|
maxTokens: z3.number().optional().describe("Maximum token budget for the response")
|
|
1198
1219
|
}),
|
|
1199
1220
|
annotations: {
|
|
@@ -1204,6 +1225,69 @@ function registerReadScript(server, bridge) {
|
|
|
1204
1225
|
}
|
|
1205
1226
|
},
|
|
1206
1227
|
async (params) => {
|
|
1228
|
+
if (params.paths && params.paths.length > 0) {
|
|
1229
|
+
const result2 = await bridge.send("batch_read_scripts", {
|
|
1230
|
+
scripts: params.paths
|
|
1231
|
+
});
|
|
1232
|
+
const sections = [];
|
|
1233
|
+
for (const r of result2.results) {
|
|
1234
|
+
if (r.outline) {
|
|
1235
|
+
const lines = [`### Outline: \`${r.path}\` (${r.totalLines} lines)`, ""];
|
|
1236
|
+
for (const entry of r.outline) {
|
|
1237
|
+
const pad = " ".repeat(entry.indent);
|
|
1238
|
+
lines.push(`${entry.line}: ${pad}${entry.text}`);
|
|
1239
|
+
}
|
|
1240
|
+
if (r.outline.length === 0) {
|
|
1241
|
+
lines.push("*No functions, type definitions, or top-level declarations found.*");
|
|
1242
|
+
}
|
|
1243
|
+
sections.push(lines.join("\n"));
|
|
1244
|
+
} else {
|
|
1245
|
+
sections.push(formatScript(r.source ?? "", r.path));
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
if (result2.errors && result2.errors.length > 0) {
|
|
1249
|
+
const errLines = ["**Errors:**"];
|
|
1250
|
+
for (const e of result2.errors) {
|
|
1251
|
+
errLines.push(`- \`${e.path}\`: ${e.error}`);
|
|
1252
|
+
}
|
|
1253
|
+
sections.push(errLines.join("\n"));
|
|
1254
|
+
}
|
|
1255
|
+
const text2 = sections.join("\n\n---\n\n");
|
|
1256
|
+
return {
|
|
1257
|
+
content: [
|
|
1258
|
+
{ type: "text", text: applyTokenBudget(text2, params.maxTokens) }
|
|
1259
|
+
]
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
if (!params.paths && !params.path) {
|
|
1263
|
+
return {
|
|
1264
|
+
content: [{ type: "text", text: "Provide either `path` (single script) or `paths` (batch read)." }],
|
|
1265
|
+
isError: true
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
const path = params.path;
|
|
1269
|
+
if (params.outline) {
|
|
1270
|
+
const result2 = await bridge.send("outline_script", {
|
|
1271
|
+
path
|
|
1272
|
+
});
|
|
1273
|
+
const lines = [
|
|
1274
|
+
`### Outline: \`${result2.path}\` (${result2.totalLines} lines)`,
|
|
1275
|
+
""
|
|
1276
|
+
];
|
|
1277
|
+
for (const entry of result2.outline) {
|
|
1278
|
+
const pad = " ".repeat(entry.indent);
|
|
1279
|
+
lines.push(`${entry.line}: ${pad}${entry.text}`);
|
|
1280
|
+
}
|
|
1281
|
+
if (result2.outline.length === 0) {
|
|
1282
|
+
lines.push("*No functions, type definitions, or top-level declarations found.*");
|
|
1283
|
+
}
|
|
1284
|
+
const text2 = lines.join("\n");
|
|
1285
|
+
return {
|
|
1286
|
+
content: [
|
|
1287
|
+
{ type: "text", text: applyTokenBudget(text2, params.maxTokens) }
|
|
1288
|
+
]
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1207
1291
|
if (params.lineRange && params.lineRange.end < params.lineRange.start) {
|
|
1208
1292
|
return {
|
|
1209
1293
|
content: [{ type: "text", text: "lineRange.end must be >= lineRange.start." }],
|
|
@@ -1211,10 +1295,10 @@ function registerReadScript(server, bridge) {
|
|
|
1211
1295
|
};
|
|
1212
1296
|
}
|
|
1213
1297
|
const result = await bridge.send("read_script", {
|
|
1214
|
-
path
|
|
1298
|
+
path,
|
|
1215
1299
|
lineRange: params.lineRange
|
|
1216
1300
|
});
|
|
1217
|
-
const text = formatScript(result.source,
|
|
1301
|
+
const text = formatScript(result.source, path);
|
|
1218
1302
|
return {
|
|
1219
1303
|
content: [
|
|
1220
1304
|
{ type: "text", text: applyTokenBudget(text, params.maxTokens) }
|
|
@@ -1228,10 +1312,10 @@ function registerWriteTools(server, bridge) {
|
|
|
1228
1312
|
"edit_script",
|
|
1229
1313
|
{
|
|
1230
1314
|
title: "Edit Script Source",
|
|
1231
|
-
description: "Edit the Lua source code of a script. Supports
|
|
1315
|
+
description: "Edit the Lua source code of a script. Supports five modes:\n- 'full': Replace entire source.\n- 'range': Replace specific line/column ranges.\n- 'find_replace': Text find-and-replace on one script.\n- 'multi_replace': Same find-and-replace across multiple scripts.\n- 'batch': Different find-and-replace edits across different scripts in one atomic operation. Use this when you need different changes in different scripts.\n\nTip: Wrap multiple edits in a transaction (begin/commit) to group them into a single Ctrl+Z undo point.",
|
|
1232
1316
|
inputSchema: z3.object({
|
|
1233
|
-
path: z3.string().describe("Path to the script instance"),
|
|
1234
|
-
mode: z3.enum(["full", "range", "find_replace", "multi_replace"]).describe("Edit mode
|
|
1317
|
+
path: z3.string().optional().describe("Path to the script instance (not needed for 'multi_replace' or 'batch' modes)"),
|
|
1318
|
+
mode: z3.enum(["full", "range", "find_replace", "multi_replace", "batch"]).describe("Edit mode"),
|
|
1235
1319
|
source: z3.string().optional().describe("Complete new source (for 'full' mode)"),
|
|
1236
1320
|
edits: z3.array(
|
|
1237
1321
|
z3.object({
|
|
@@ -1245,7 +1329,18 @@ function registerWriteTools(server, bridge) {
|
|
|
1245
1329
|
find: z3.string().optional().describe("Text or pattern to find (for 'find_replace' mode)"),
|
|
1246
1330
|
replace: z3.string().optional().describe("Replacement text (for 'find_replace' mode)"),
|
|
1247
1331
|
regex: z3.boolean().optional().describe("Treat 'find' as a Lua pattern (for 'find_replace' and 'multi_replace' modes)"),
|
|
1248
|
-
scripts: z3.array(z3.string()).optional().describe("Array of script paths to apply find/replace across (for 'multi_replace' mode)")
|
|
1332
|
+
scripts: z3.array(z3.string()).optional().describe("Array of script paths to apply find/replace across (for 'multi_replace' mode)"),
|
|
1333
|
+
batch: z3.array(
|
|
1334
|
+
z3.object({
|
|
1335
|
+
path: z3.string().describe("Script path"),
|
|
1336
|
+
source: z3.string().optional().describe("Complete new source (for full replacement). Mutually exclusive with find/replace."),
|
|
1337
|
+
find: z3.string().optional().describe("Text or pattern to find"),
|
|
1338
|
+
replace: z3.string().default("").describe("Replacement text (used with find)"),
|
|
1339
|
+
regex: z3.boolean().default(false).describe("Treat find as a Lua pattern (used with find)")
|
|
1340
|
+
})
|
|
1341
|
+
).optional().describe(
|
|
1342
|
+
"Array of per-script edit operations (for 'batch' mode). Each entry can either use `source` for full replacement or `find`/`replace` for targeted edits."
|
|
1343
|
+
)
|
|
1249
1344
|
}),
|
|
1250
1345
|
annotations: {
|
|
1251
1346
|
readOnlyHint: false,
|
|
@@ -1307,6 +1402,51 @@ function registerWriteTools(server, bridge) {
|
|
|
1307
1402
|
}
|
|
1308
1403
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1309
1404
|
}
|
|
1405
|
+
if (params.mode === "batch") {
|
|
1406
|
+
if (!params.batch || params.batch.length === 0) {
|
|
1407
|
+
return {
|
|
1408
|
+
content: [{ type: "text", text: "batch mode requires a non-empty `batch` array." }],
|
|
1409
|
+
isError: true
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
for (const entry of params.batch) {
|
|
1413
|
+
if (entry.source !== void 0 && entry.find !== void 0) {
|
|
1414
|
+
return {
|
|
1415
|
+
content: [{ type: "text", text: `Batch entry for \`${entry.path}\`: provide either \`source\` or \`find\`, not both.` }],
|
|
1416
|
+
isError: true
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
if (entry.source === void 0 && entry.find === void 0) {
|
|
1420
|
+
return {
|
|
1421
|
+
content: [{ type: "text", text: `Batch entry for \`${entry.path}\`: must provide either \`source\` (full replacement) or \`find\` (find/replace).` }],
|
|
1422
|
+
isError: true
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
const result2 = await bridge.send("batch_edit_scripts", {
|
|
1427
|
+
edits: params.batch
|
|
1428
|
+
});
|
|
1429
|
+
const lines = [
|
|
1430
|
+
`**Batch edit** \u2014 ${result2.scriptsModified} script(s) modified`,
|
|
1431
|
+
""
|
|
1432
|
+
];
|
|
1433
|
+
for (const r of result2.results) {
|
|
1434
|
+
if (r.success) {
|
|
1435
|
+
if (r.mode === "full") {
|
|
1436
|
+
lines.push(`- \`${r.path}\`: full source replaced`);
|
|
1437
|
+
} else {
|
|
1438
|
+
lines.push(`- \`${r.path}\`: ${r.replacements ?? 0} replacement(s)`);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
if (result2.errors && result2.errors.length > 0) {
|
|
1443
|
+
lines.push("", "**Errors:**");
|
|
1444
|
+
for (const e of result2.errors) {
|
|
1445
|
+
lines.push(`- \`${e.path}\`: ${e.error}`);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1449
|
+
}
|
|
1310
1450
|
const result = await bridge.send("edit_script", {
|
|
1311
1451
|
path: params.path,
|
|
1312
1452
|
mode: params.mode,
|
|
@@ -1379,9 +1519,9 @@ function register4(server, bridge) {
|
|
|
1379
1519
|
"playtest",
|
|
1380
1520
|
{
|
|
1381
1521
|
title: "Playtest Control & Virtual Input",
|
|
1382
|
-
description: "Control Roblox Studio playtesting and simulate user input.\n\nActions:\n- `start`: Begin a playtest session. Defaults to Play mode (F5, full client with player character). Set mode='run' for Run mode (F8, server-only, no player).\n- `stop`: End the current playtest.\n- `execute`: Run Lua code in the running game context.\n- `get_output`: Get console/log output from Studio (works in edit mode and during playtest).\n- `inspect`: Evaluate a Luau expression and return the typed result (requires active playtest).\n- `navigate`: Walk the player character to a position using PathfindingService (requires client playtest).\n- `mouse_click`: Simulate a mouse click at screen coordinates.\n- `mouse_move`: Move the virtual mouse to screen coordinates.\n- `key_press`: Press and release a key.\n- `key_down`: Hold a key down.\n- `key_up`: Release a held key.",
|
|
1522
|
+
description: "Control Roblox Studio playtesting and simulate user input.\n\nActions:\n- `start`: Begin a playtest session. Defaults to Play mode (F5, full client with player character). Set mode='run' for Run mode (F8, server-only, no player).\n- `stop`: End the current playtest.\n- `execute`: Run Lua code in the running game context.\n- `get_output`: Get console/log output from Studio (works in edit mode and during playtest).\n- `inspect`: Evaluate a Luau expression and return the typed result (requires active playtest).\n- `navigate`: Walk the player character to a position using PathfindingService (requires client playtest).\n- `mouse_click`: Simulate a mouse click at screen coordinates.\n- `mouse_move`: Move the virtual mouse to screen coordinates.\n- `key_press`: Press and release a key.\n- `key_down`: Hold a key down.\n- `key_up`: Release a held key.\n- `screenshot`: Capture the viewport during playtest. Useful for seeing the game state visually.",
|
|
1383
1523
|
inputSchema: z4.object({
|
|
1384
|
-
action: z4.enum(["start", "stop", "execute", "get_output", "inspect", "navigate", "mouse_click", "mouse_move", "key_press", "key_down", "key_up"]).describe("Playtest action"),
|
|
1524
|
+
action: z4.enum(["start", "stop", "execute", "get_output", "inspect", "navigate", "mouse_click", "mouse_move", "key_press", "key_down", "key_up", "screenshot"]).describe("Playtest action"),
|
|
1385
1525
|
mode: z4.enum(["play", "run"]).default("play").describe("Playtest mode: 'play' (F5, full client with player) or 'run' (F8, server-only). Default: play"),
|
|
1386
1526
|
code: z4.string().optional().describe("Lua code to execute (for 'execute' action)"),
|
|
1387
1527
|
// get_output params
|
|
@@ -1465,6 +1605,25 @@ ${logText}
|
|
|
1465
1605
|
${result2.message}` : ""}`;
|
|
1466
1606
|
return { content: [{ type: "text", text: text2 }] };
|
|
1467
1607
|
}
|
|
1608
|
+
if (params.action === "screenshot") {
|
|
1609
|
+
const result2 = await bridge.send("screenshot", {});
|
|
1610
|
+
if (result2.imageBase64 && result2.mimeType) {
|
|
1611
|
+
return {
|
|
1612
|
+
content: [
|
|
1613
|
+
{
|
|
1614
|
+
type: "image",
|
|
1615
|
+
data: result2.imageBase64,
|
|
1616
|
+
mimeType: result2.mimeType
|
|
1617
|
+
}
|
|
1618
|
+
]
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
return {
|
|
1622
|
+
content: [
|
|
1623
|
+
{ type: "text", text: `Playtest screenshot: ${result2.message ?? result2.status}` }
|
|
1624
|
+
]
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1468
1627
|
if (params.action === "mouse_click" || params.action === "mouse_move" || params.action === "key_press" || params.action === "key_down" || params.action === "key_up") {
|
|
1469
1628
|
if ((params.action === "mouse_click" || params.action === "mouse_move") && (params.x === void 0 || params.y === void 0)) {
|
|
1470
1629
|
return {
|
|
@@ -1955,7 +2114,7 @@ function register7(server, bridge) {
|
|
|
1955
2114
|
"screenshot",
|
|
1956
2115
|
{
|
|
1957
2116
|
title: "Take Screenshot",
|
|
1958
|
-
description: "Capture a screenshot of the current Roblox Studio viewport.
|
|
2117
|
+
description: "Capture a screenshot of the current Roblox Studio viewport. Works in both edit mode and during playtest.\n\nThe screenshot is saved to the user's Roblox screenshots folder. Base64 image data is returned when the EditableImage API is available (Roblox platform limitation).\n\nTip: During playtest, use this to capture the game viewport. Combine with `playtest start` + a short delay via `playtest execute` (e.g. `task.wait(2)`) to capture after the game loads.",
|
|
1959
2118
|
inputSchema: z7.object({}),
|
|
1960
2119
|
annotations: {
|
|
1961
2120
|
readOnlyHint: true,
|
|
@@ -2014,10 +2173,11 @@ function register7(server, bridge) {
|
|
|
2014
2173
|
"transaction",
|
|
2015
2174
|
{
|
|
2016
2175
|
title: "Transaction Control",
|
|
2017
|
-
description: "Group multiple
|
|
2176
|
+
description: "Group multiple tool calls into a single Ctrl+Z undo point \u2014 essential when making several related edits that the user should be able to revert together.\n\n**When to use:** Before starting a multi-edit session (e.g. refactoring across scripts, creating multiple instances, UI changes). Call `begin`, make all your changes, then `commit`. The user can undo everything in one Ctrl+Z.\n\nActions:\n- `begin`: Start a transaction. All subsequent writes share one undo recording.\n- `commit`: Finish and commit all changes as one undo point.\n- `rollback`: Cancel and revert all changes since begin.\n\nTransactions auto-rollback after the timeout (default 120s) if not committed.",
|
|
2018
2177
|
inputSchema: z7.object({
|
|
2019
2178
|
action: z7.enum(["begin", "commit", "rollback"]).describe("Transaction action"),
|
|
2020
|
-
name: z7.string().optional().describe("Transaction name for the undo history (for 'begin' action)")
|
|
2179
|
+
name: z7.string().optional().describe("Transaction name for the undo history (for 'begin' action)"),
|
|
2180
|
+
timeout: z7.number().int().min(10).max(300).optional().describe("Auto-rollback timeout in seconds (default 120, min 10, max 300). Increase for large multi-edit sessions.")
|
|
2021
2181
|
}),
|
|
2022
2182
|
annotations: {
|
|
2023
2183
|
readOnlyHint: false,
|
|
@@ -2031,7 +2191,8 @@ function register7(server, bridge) {
|
|
|
2031
2191
|
if (params.action === "begin") {
|
|
2032
2192
|
try {
|
|
2033
2193
|
const result = await bridge.send("begin_transaction", {
|
|
2034
|
-
name: params.name
|
|
2194
|
+
name: params.name,
|
|
2195
|
+
timeout: params.timeout
|
|
2035
2196
|
});
|
|
2036
2197
|
transactionState.set(studioId, true);
|
|
2037
2198
|
return {
|
|
@@ -2428,4 +2589,4 @@ async function startServer(port = 3200, options = {}) {
|
|
|
2428
2589
|
export {
|
|
2429
2590
|
startServer
|
|
2430
2591
|
};
|
|
2431
|
-
//# sourceMappingURL=chunk-
|
|
2592
|
+
//# sourceMappingURL=chunk-3FU4T3TX.js.map
|