@chrrxs/robloxstudio-mcp 2.11.4 → 2.13.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 CHANGED
@@ -9,6 +9,299 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // ../core/dist/bridge-service.js
13
+ import { v4 as uuidv4 } from "uuid";
14
+ function toPublic(inst) {
15
+ return {
16
+ instanceId: inst.instanceId,
17
+ role: inst.role,
18
+ placeId: inst.placeId,
19
+ placeName: inst.placeName,
20
+ dataModelName: inst.dataModelName,
21
+ isRunning: inst.isRunning,
22
+ lastActivity: inst.lastActivity,
23
+ connectedAt: inst.connectedAt
24
+ };
25
+ }
26
+ var RoutingFailure, STALE_INSTANCE_MS, BridgeService;
27
+ var init_bridge_service = __esm({
28
+ "../core/dist/bridge-service.js"() {
29
+ "use strict";
30
+ RoutingFailure = class extends Error {
31
+ routingError;
32
+ constructor(routingError) {
33
+ super(routingError.message);
34
+ this.name = "RoutingFailure";
35
+ this.routingError = routingError;
36
+ }
37
+ };
38
+ STALE_INSTANCE_MS = 3e4;
39
+ BridgeService = class {
40
+ pendingRequests = /* @__PURE__ */ new Map();
41
+ // Keyed by pluginSessionId (the per-plugin GUID).
42
+ instances = /* @__PURE__ */ new Map();
43
+ requestTimeout = 3e4;
44
+ registerInstance(input) {
45
+ const { pluginSessionId, instanceId, role } = input;
46
+ let assignedRole = role;
47
+ if (role === "client") {
48
+ const used = /* @__PURE__ */ new Set();
49
+ for (const inst of this.instances.values()) {
50
+ const match = inst.role.match(/^client-(\d+)$/);
51
+ if (match)
52
+ used.add(Number(match[1]));
53
+ }
54
+ let idx = 1;
55
+ while (used.has(idx))
56
+ idx++;
57
+ assignedRole = `client-${idx}`;
58
+ }
59
+ const existing = Array.from(this.instances.values()).find((i) => i.instanceId === instanceId && i.role === assignedRole && i.pluginSessionId !== pluginSessionId);
60
+ if (existing) {
61
+ return {
62
+ ok: false,
63
+ error: {
64
+ code: "duplicate_instance_role",
65
+ message: `Another plugin is already registered as (${instanceId}, ${assignedRole}).`,
66
+ existing: toPublic(existing)
67
+ }
68
+ };
69
+ }
70
+ this.instances.set(pluginSessionId, {
71
+ pluginSessionId,
72
+ instanceId,
73
+ role: assignedRole,
74
+ placeId: input.placeId ?? 0,
75
+ placeName: input.placeName ?? "",
76
+ dataModelName: input.dataModelName ?? "",
77
+ isRunning: input.isRunning ?? false,
78
+ lastActivity: Date.now(),
79
+ connectedAt: Date.now()
80
+ });
81
+ return { ok: true, assignedRole, instanceId };
82
+ }
83
+ unregisterInstance(pluginSessionId) {
84
+ const removed = this.instances.get(pluginSessionId);
85
+ this.instances.delete(pluginSessionId);
86
+ if (!removed)
87
+ return;
88
+ for (const [id, req] of this.pendingRequests.entries()) {
89
+ const stillHasHandler = Array.from(this.instances.values()).some((i) => i.instanceId === req.targetInstanceId && i.role === req.targetRole);
90
+ if (!stillHasHandler) {
91
+ clearTimeout(req.timeoutId);
92
+ this.pendingRequests.delete(id);
93
+ req.reject(new Error(`Target (${req.targetInstanceId}, ${req.targetRole}) disconnected`));
94
+ }
95
+ }
96
+ }
97
+ getInstances() {
98
+ return Array.from(this.instances.values());
99
+ }
100
+ getPublicInstances() {
101
+ return this.getInstances().map(toPublic);
102
+ }
103
+ getInstanceBySessionId(pluginSessionId) {
104
+ return this.instances.get(pluginSessionId);
105
+ }
106
+ getPendingRequestCount() {
107
+ return this.pendingRequests.size;
108
+ }
109
+ updateInstanceActivity(pluginSessionId) {
110
+ const inst = this.instances.get(pluginSessionId);
111
+ if (inst) {
112
+ inst.lastActivity = Date.now();
113
+ }
114
+ }
115
+ updateInstanceMetadata(pluginSessionId, metadata) {
116
+ const inst = this.instances.get(pluginSessionId);
117
+ if (!inst)
118
+ return;
119
+ if (metadata.placeId !== void 0)
120
+ inst.placeId = metadata.placeId;
121
+ if (metadata.placeName !== void 0)
122
+ inst.placeName = metadata.placeName;
123
+ if (metadata.dataModelName !== void 0)
124
+ inst.dataModelName = metadata.dataModelName;
125
+ if (metadata.isRunning !== void 0)
126
+ inst.isRunning = metadata.isRunning;
127
+ }
128
+ cleanupStaleInstances() {
129
+ const now = Date.now();
130
+ for (const [id, inst] of this.instances.entries()) {
131
+ if (now - inst.lastActivity > STALE_INSTANCE_MS) {
132
+ this.unregisterInstance(id);
133
+ }
134
+ }
135
+ }
136
+ // Resolves (instance_id, target-role) MCP arguments to a concrete
137
+ // routing decision: either a single (instanceId, role) tuple or a fanout
138
+ // list. Returns an error result with the full instance list embedded so
139
+ // the caller (tool layer) can surface it without a second round-trip.
140
+ resolveTarget(input) {
141
+ const instances = this.getInstances();
142
+ const publicList = instances.map(toPublic);
143
+ const errorData = { instances: publicList, count: publicList.length };
144
+ const { instance_id, target } = input;
145
+ const isFanout = target === "all";
146
+ const role = target && target !== "all" ? target : void 0;
147
+ if (instance_id !== void 0) {
148
+ const matchingInstances = instances.filter((i) => i.instanceId === instance_id);
149
+ if (matchingInstances.length === 0) {
150
+ return {
151
+ ok: false,
152
+ error: {
153
+ code: "unrecognized_instance_id",
154
+ message: `instance_id "${instance_id}" is not connected. Pass one from data.instances.`,
155
+ data: errorData
156
+ }
157
+ };
158
+ }
159
+ if (isFanout) {
160
+ return {
161
+ ok: true,
162
+ mode: "fanout",
163
+ targets: matchingInstances.map((i) => ({
164
+ targetInstanceId: i.instanceId,
165
+ targetRole: i.role
166
+ }))
167
+ };
168
+ }
169
+ if (role) {
170
+ const exact = matchingInstances.find((i) => i.role === role);
171
+ if (!exact) {
172
+ return {
173
+ ok: false,
174
+ error: {
175
+ code: "target_role_not_present_on_instance",
176
+ message: `instance "${instance_id}" has no role "${role}". Available roles: ${matchingInstances.map((i) => i.role).join(", ")}.`,
177
+ data: errorData
178
+ }
179
+ };
180
+ }
181
+ return { ok: true, mode: "single", targetInstanceId: instance_id, targetRole: role };
182
+ }
183
+ if (matchingInstances.length === 1) {
184
+ return {
185
+ ok: true,
186
+ mode: "single",
187
+ targetInstanceId: instance_id,
188
+ targetRole: matchingInstances[0].role
189
+ };
190
+ }
191
+ const edit = matchingInstances.find((i) => i.role === "edit");
192
+ if (edit) {
193
+ return { ok: true, mode: "single", targetInstanceId: instance_id, targetRole: "edit" };
194
+ }
195
+ return {
196
+ ok: false,
197
+ error: {
198
+ code: "target_role_required",
199
+ message: `instance "${instance_id}" has multiple roles connected: ${matchingInstances.map((i) => i.role).join(", ")}. Pass target=<role>.`,
200
+ data: errorData
201
+ }
202
+ };
203
+ }
204
+ const distinctInstanceIds = new Set(instances.map((i) => i.instanceId));
205
+ if (distinctInstanceIds.size === 0) {
206
+ return {
207
+ ok: false,
208
+ error: {
209
+ code: "unrecognized_instance_id",
210
+ message: "No Studio plugin is connected.",
211
+ data: errorData
212
+ }
213
+ };
214
+ }
215
+ if (distinctInstanceIds.size > 1) {
216
+ const errorCode = role ? "ambiguous_target" : "multiple_instances_connected";
217
+ const msg = role ? `target=${role} is ambiguous: multiple places have this role. Pass instance_id.` : "Multiple Studio places are connected. Pass instance_id to disambiguate.";
218
+ return { ok: false, error: { code: errorCode, message: msg, data: errorData } };
219
+ }
220
+ const onlyInstanceId = instances[0].instanceId;
221
+ return this.resolveTarget({ instance_id: onlyInstanceId, target });
222
+ }
223
+ async sendRequest(endpoint, data, targetInstanceId, targetRole) {
224
+ const requestId = uuidv4();
225
+ return new Promise((resolve2, reject) => {
226
+ const timeoutId = setTimeout(() => {
227
+ if (this.pendingRequests.has(requestId)) {
228
+ this.pendingRequests.delete(requestId);
229
+ reject(new Error("Request timeout"));
230
+ }
231
+ }, this.requestTimeout);
232
+ const request = {
233
+ id: requestId,
234
+ endpoint,
235
+ data,
236
+ targetInstanceId,
237
+ targetRole,
238
+ timestamp: Date.now(),
239
+ resolve: resolve2,
240
+ reject,
241
+ timeoutId
242
+ };
243
+ this.pendingRequests.set(requestId, request);
244
+ });
245
+ }
246
+ getPendingRequest(callerInstanceId, callerRole) {
247
+ let oldestRequest = null;
248
+ for (const request of this.pendingRequests.values()) {
249
+ if (request.targetInstanceId !== callerInstanceId)
250
+ continue;
251
+ if (request.targetRole !== callerRole)
252
+ continue;
253
+ if (!oldestRequest || request.timestamp < oldestRequest.timestamp) {
254
+ oldestRequest = request;
255
+ }
256
+ }
257
+ if (oldestRequest) {
258
+ return {
259
+ requestId: oldestRequest.id,
260
+ request: {
261
+ endpoint: oldestRequest.endpoint,
262
+ data: oldestRequest.data
263
+ }
264
+ };
265
+ }
266
+ return null;
267
+ }
268
+ resolveRequest(requestId, response) {
269
+ const request = this.pendingRequests.get(requestId);
270
+ if (request) {
271
+ clearTimeout(request.timeoutId);
272
+ this.pendingRequests.delete(requestId);
273
+ request.resolve(response);
274
+ }
275
+ }
276
+ rejectRequest(requestId, error) {
277
+ const request = this.pendingRequests.get(requestId);
278
+ if (request) {
279
+ clearTimeout(request.timeoutId);
280
+ this.pendingRequests.delete(requestId);
281
+ request.reject(error);
282
+ }
283
+ }
284
+ cleanupOldRequests() {
285
+ const now = Date.now();
286
+ for (const [id, request] of this.pendingRequests.entries()) {
287
+ if (now - request.timestamp > this.requestTimeout) {
288
+ clearTimeout(request.timeoutId);
289
+ this.pendingRequests.delete(id);
290
+ request.reject(new Error("Request timeout"));
291
+ }
292
+ }
293
+ }
294
+ clearAllPendingRequests() {
295
+ for (const [, request] of this.pendingRequests.entries()) {
296
+ clearTimeout(request.timeoutId);
297
+ request.reject(new Error("Connection closed"));
298
+ }
299
+ this.pendingRequests.clear();
300
+ }
301
+ };
302
+ }
303
+ });
304
+
12
305
  // ../core/dist/http-server.js
13
306
  import express from "express";
14
307
  import cors from "cors";
@@ -56,12 +349,7 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig) {
56
349
  version: serverConfig?.version,
57
350
  pluginConnected: instances.length > 0,
58
351
  instanceCount: instances.length,
59
- instances: instances.map((i) => ({
60
- instanceId: i.instanceId,
61
- role: i.role,
62
- lastActivity: i.lastActivity,
63
- connectedAt: i.connectedAt
64
- })),
352
+ instances: instances.map(toPublic),
65
353
  mcpServerActive: isMCPServerActive(),
66
354
  uptime: mcpServerActive ? Date.now() - mcpServerStartTime : 0,
67
355
  pendingRequests: bridge.getPendingRequestCount(),
@@ -70,22 +358,42 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig) {
70
358
  });
71
359
  });
72
360
  app.post("/ready", (req, res) => {
73
- const { instanceId, role } = req.body;
74
- if (instanceId && role) {
75
- const assignedRole = bridge.registerInstance(instanceId, role);
76
- res.json({ success: true, assignedRole });
77
- } else {
78
- bridge.registerInstance("legacy", "edit");
79
- res.json({ success: true, assignedRole: "edit" });
361
+ const { pluginSessionId, instanceId, role, placeId, placeName, dataModelName, isRunning } = req.body;
362
+ if (!pluginSessionId || !instanceId || !role) {
363
+ res.status(400).json({
364
+ success: false,
365
+ error: "pluginSessionId, instanceId, and role are required"
366
+ });
367
+ return;
368
+ }
369
+ const result = bridge.registerInstance({
370
+ pluginSessionId,
371
+ instanceId,
372
+ role,
373
+ placeId: typeof placeId === "number" ? placeId : 0,
374
+ placeName: typeof placeName === "string" ? placeName : "",
375
+ dataModelName: typeof dataModelName === "string" ? dataModelName : "",
376
+ isRunning: !!isRunning
377
+ });
378
+ if (!result.ok) {
379
+ res.status(409).json({
380
+ success: false,
381
+ error: result.error.code,
382
+ message: result.error.message,
383
+ existing: result.error.existing
384
+ });
385
+ return;
80
386
  }
387
+ res.json({
388
+ success: true,
389
+ assignedRole: result.assignedRole,
390
+ instanceId: result.instanceId
391
+ });
81
392
  });
82
393
  app.post("/disconnect", (req, res) => {
83
- const { instanceId } = req.body;
84
- if (instanceId) {
85
- bridge.unregisterInstance(instanceId);
86
- } else {
87
- bridge.unregisterInstance("legacy");
88
- bridge.clearAllPendingRequests();
394
+ const { pluginSessionId } = req.body;
395
+ if (pluginSessionId) {
396
+ bridge.unregisterInstance(pluginSessionId);
89
397
  }
90
398
  res.json({ success: true });
91
399
  });
@@ -94,7 +402,7 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig) {
94
402
  res.json({
95
403
  pluginConnected: instances.length > 0,
96
404
  instanceCount: instances.length,
97
- instances: instances.map((i) => ({ instanceId: i.instanceId, role: i.role })),
405
+ instances: instances.map(toPublic),
98
406
  mcpServerActive: isMCPServerActive(),
99
407
  lastMCPActivity,
100
408
  uptime: mcpServerActive ? Date.now() - mcpServerStartTime : 0
@@ -104,15 +412,17 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig) {
104
412
  res.json({ instances: bridge.getInstances() });
105
413
  });
106
414
  app.get("/poll", (req, res) => {
107
- const instanceId = req.query.instanceId;
108
- if (instanceId) {
109
- bridge.updateInstanceActivity(instanceId);
415
+ const pluginSessionId = req.query.pluginSessionId;
416
+ if (pluginSessionId) {
417
+ bridge.updateInstanceActivity(pluginSessionId);
110
418
  }
111
- let callerRole = "edit";
419
+ let callerInstanceId;
420
+ let callerRole;
112
421
  let knownInstance = false;
113
- if (instanceId) {
114
- const inst = bridge.getInstances().find((i) => i.instanceId === instanceId);
422
+ if (pluginSessionId) {
423
+ const inst = bridge.getInstanceBySessionId(pluginSessionId);
115
424
  if (inst) {
425
+ callerInstanceId = inst.instanceId;
116
426
  callerRole = inst.role;
117
427
  knownInstance = true;
118
428
  }
@@ -127,7 +437,7 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig) {
127
437
  });
128
438
  return;
129
439
  }
130
- const pendingRequest = bridge.getPendingRequest(callerRole);
440
+ const pendingRequest = knownInstance && callerInstanceId && callerRole ? bridge.getPendingRequest(callerInstanceId, callerRole) : null;
131
441
  if (pendingRequest) {
132
442
  res.json({
133
443
  request: pendingRequest.request,
@@ -157,16 +467,16 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig) {
157
467
  res.json({ success: true });
158
468
  });
159
469
  app.post("/proxy", async (req, res) => {
160
- const { endpoint, data, target, proxyInstanceId } = req.body;
161
- if (!endpoint) {
162
- res.status(400).json({ error: "endpoint is required" });
470
+ const { endpoint, data, targetInstanceId, targetRole, proxyInstanceId } = req.body;
471
+ if (!endpoint || !targetInstanceId || !targetRole) {
472
+ res.status(400).json({ error: "endpoint, targetInstanceId, and targetRole are required" });
163
473
  return;
164
474
  }
165
475
  if (proxyInstanceId) {
166
476
  proxyInstances.add(proxyInstanceId);
167
477
  }
168
478
  try {
169
- const response = await bridge.sendRequest(endpoint, data, target || "edit");
479
+ const response = await bridge.sendRequest(endpoint, data, targetInstanceId, targetRole);
170
480
  res.json({ response });
171
481
  } catch (err) {
172
482
  res.status(500).json({ error: err.message || "Proxy request failed" });
@@ -197,6 +507,19 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig) {
197
507
  try {
198
508
  return await handler(tools, args || {});
199
509
  } catch (error) {
510
+ if (error instanceof RoutingFailure) {
511
+ return {
512
+ content: [{
513
+ type: "text",
514
+ text: JSON.stringify({
515
+ error: error.routingError.code,
516
+ message: error.routingError.message,
517
+ data: error.routingError.data
518
+ })
519
+ }],
520
+ isError: true
521
+ };
522
+ }
200
523
  if (error instanceof McpError)
201
524
  throw error;
202
525
  throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -248,6 +571,14 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig) {
248
571
  const result = await handler(tools, req.body);
249
572
  res.json(result);
250
573
  } catch (error) {
574
+ if (error instanceof RoutingFailure) {
575
+ res.status(400).json({
576
+ error: error.routingError.code,
577
+ message: error.routingError.message,
578
+ data: error.routingError.data
579
+ });
580
+ return;
581
+ }
251
582
  res.status(500).json({ error: error instanceof Error ? error.message : "Unknown error" });
252
583
  }
253
584
  });
@@ -296,26 +627,27 @@ var TOOL_HANDLERS;
296
627
  var init_http_server = __esm({
297
628
  "../core/dist/http-server.js"() {
298
629
  "use strict";
630
+ init_bridge_service();
299
631
  TOOL_HANDLERS = {
300
- get_file_tree: (tools, body) => tools.getFileTree(body.path),
301
- search_files: (tools, body) => tools.searchFiles(body.query, body.searchType),
302
- get_place_info: (tools) => tools.getPlaceInfo(),
303
- get_services: (tools, body) => tools.getServices(body.serviceName),
304
- search_objects: (tools, body) => tools.searchObjects(body.query, body.searchType, body.propertyName),
305
- get_instance_properties: (tools, body) => tools.getInstanceProperties(body.instancePath, body.excludeSource),
306
- get_instance_children: (tools, body) => tools.getInstanceChildren(body.instancePath),
307
- search_by_property: (tools, body) => tools.searchByProperty(body.propertyName, body.propertyValue),
308
- get_class_info: (tools, body) => tools.getClassInfo(body.className),
309
- get_project_structure: (tools, body) => tools.getProjectStructure(body.path, body.maxDepth, body.scriptsOnly),
310
- set_property: (tools, body) => tools.setProperty(body.instancePath, body.propertyName, body.propertyValue),
311
- set_properties: (tools, body) => tools.setProperties(body.instancePath, body.properties),
312
- mass_set_property: (tools, body) => tools.massSetProperty(body.paths, body.propertyName, body.propertyValue),
313
- mass_get_property: (tools, body) => tools.massGetProperty(body.paths, body.propertyName),
314
- create_object: (tools, body) => tools.createObject(body.className, body.parent, body.name, body.properties),
315
- mass_create_objects: (tools, body) => tools.massCreateObjects(body.objects),
316
- delete_object: (tools, body) => tools.deleteObject(body.instancePath),
317
- smart_duplicate: (tools, body) => tools.smartDuplicate(body.instancePath, body.count, body.options),
318
- mass_duplicate: (tools, body) => tools.massDuplicate(body.duplications),
632
+ get_file_tree: (tools, body) => tools.getFileTree(body.path, body.instance_id),
633
+ search_files: (tools, body) => tools.searchFiles(body.query, body.searchType, body.instance_id),
634
+ get_place_info: (tools, body) => tools.getPlaceInfo(body.instance_id),
635
+ get_services: (tools, body) => tools.getServices(body.serviceName, body.instance_id),
636
+ search_objects: (tools, body) => tools.searchObjects(body.query, body.searchType, body.propertyName, body.instance_id),
637
+ get_instance_properties: (tools, body) => tools.getInstanceProperties(body.instancePath, body.excludeSource, body.instance_id),
638
+ get_instance_children: (tools, body) => tools.getInstanceChildren(body.instancePath, body.instance_id),
639
+ search_by_property: (tools, body) => tools.searchByProperty(body.propertyName, body.propertyValue, body.instance_id),
640
+ get_class_info: (tools, body) => tools.getClassInfo(body.className, body.instance_id),
641
+ get_project_structure: (tools, body) => tools.getProjectStructure(body.path, body.maxDepth, body.scriptsOnly, body.instance_id),
642
+ set_property: (tools, body) => tools.setProperty(body.instancePath, body.propertyName, body.propertyValue, body.instance_id),
643
+ set_properties: (tools, body) => tools.setProperties(body.instancePath, body.properties, body.instance_id),
644
+ mass_set_property: (tools, body) => tools.massSetProperty(body.paths, body.propertyName, body.propertyValue, body.instance_id),
645
+ mass_get_property: (tools, body) => tools.massGetProperty(body.paths, body.propertyName, body.instance_id),
646
+ create_object: (tools, body) => tools.createObject(body.className, body.parent, body.name, body.properties, body.instance_id),
647
+ mass_create_objects: (tools, body) => tools.massCreateObjects(body.objects, body.instance_id),
648
+ delete_object: (tools, body) => tools.deleteObject(body.instancePath, body.instance_id),
649
+ smart_duplicate: (tools, body) => tools.smartDuplicate(body.instancePath, body.count, body.options, body.instance_id),
650
+ mass_duplicate: (tools, body) => tools.massDuplicate(body.duplications, body.instance_id),
319
651
  grep_scripts: (tools, body) => tools.grepScripts(body.pattern, {
320
652
  caseSensitive: body.caseSensitive,
321
653
  usePattern: body.usePattern,
@@ -325,56 +657,56 @@ var init_http_server = __esm({
325
657
  filesOnly: body.filesOnly,
326
658
  path: body.path,
327
659
  classFilter: body.classFilter
328
- }),
329
- get_script_source: (tools, body) => tools.getScriptSource(body.instancePath, body.startLine, body.endLine),
330
- set_script_source: (tools, body) => tools.setScriptSource(body.instancePath, body.source),
331
- edit_script_lines: (tools, body) => tools.editScriptLines(body.instancePath, body.old_string, body.new_string, body.startLine),
332
- insert_script_lines: (tools, body) => tools.insertScriptLines(body.instancePath, body.afterLine, body.newContent),
333
- delete_script_lines: (tools, body) => tools.deleteScriptLines(body.instancePath, body.startLine, body.endLine),
334
- set_attribute: (tools, body) => tools.setAttribute(body.instancePath, body.attributeName, body.attributeValue, body.valueType),
335
- get_attributes: (tools, body) => tools.getAttributes(body.instancePath),
336
- delete_attribute: (tools, body) => tools.deleteAttribute(body.instancePath, body.attributeName),
337
- get_tags: (tools, body) => tools.getTags(body.instancePath),
338
- add_tag: (tools, body) => tools.addTag(body.instancePath, body.tagName),
339
- remove_tag: (tools, body) => tools.removeTag(body.instancePath, body.tagName),
340
- get_tagged: (tools, body) => tools.getTagged(body.tagName),
341
- get_selection: (tools) => tools.getSelection(),
342
- execute_luau: (tools, body) => tools.executeLuau(body.code, body.target),
343
- eval_server_runtime: (tools, body) => tools.evalServerRuntime(body.code),
344
- eval_client_runtime: (tools, body) => tools.evalClientRuntime(body.code, body.target),
345
- start_playtest: (tools, body) => tools.startPlaytest(body.mode, body.numPlayers),
346
- stop_playtest: (tools) => tools.stopPlaytest(),
347
- get_playtest_output: (tools, body) => tools.getPlaytestOutput(body.target),
348
- get_runtime_logs: (tools, body) => tools.getRuntimeLogs(body.target, body.since, body.tail, body.filter),
660
+ }, body.instance_id),
661
+ get_script_source: (tools, body) => tools.getScriptSource(body.instancePath, body.startLine, body.endLine, body.instance_id),
662
+ set_script_source: (tools, body) => tools.setScriptSource(body.instancePath, body.source, body.instance_id),
663
+ edit_script_lines: (tools, body) => tools.editScriptLines(body.instancePath, body.old_string, body.new_string, body.startLine, body.instance_id),
664
+ insert_script_lines: (tools, body) => tools.insertScriptLines(body.instancePath, body.afterLine, body.newContent, body.instance_id),
665
+ delete_script_lines: (tools, body) => tools.deleteScriptLines(body.instancePath, body.startLine, body.endLine, body.instance_id),
666
+ set_attribute: (tools, body) => tools.setAttribute(body.instancePath, body.attributeName, body.attributeValue, body.valueType, body.instance_id),
667
+ get_attributes: (tools, body) => tools.getAttributes(body.instancePath, body.instance_id),
668
+ delete_attribute: (tools, body) => tools.deleteAttribute(body.instancePath, body.attributeName, body.instance_id),
669
+ get_tags: (tools, body) => tools.getTags(body.instancePath, body.instance_id),
670
+ add_tag: (tools, body) => tools.addTag(body.instancePath, body.tagName, body.instance_id),
671
+ remove_tag: (tools, body) => tools.removeTag(body.instancePath, body.tagName, body.instance_id),
672
+ get_tagged: (tools, body) => tools.getTagged(body.tagName, body.instance_id),
673
+ get_selection: (tools, body) => tools.getSelection(body.instance_id),
674
+ execute_luau: (tools, body) => tools.executeLuau(body.code, body.target, body.instance_id),
675
+ eval_server_runtime: (tools, body) => tools.evalServerRuntime(body.code, body.instance_id),
676
+ eval_client_runtime: (tools, body) => tools.evalClientRuntime(body.code, body.target, body.instance_id),
677
+ start_playtest: (tools, body) => tools.startPlaytest(body.mode, body.numPlayers, body.instance_id),
678
+ stop_playtest: (tools, body) => tools.stopPlaytest(body.instance_id),
679
+ get_playtest_output: (tools, body) => tools.getPlaytestOutput(body.target, body.instance_id),
680
+ get_runtime_logs: (tools, body) => tools.getRuntimeLogs(body.target, body.since, body.tail, body.filter, body.instance_id),
349
681
  get_connected_instances: (tools) => tools.getConnectedInstances(),
350
- export_build: (tools, body) => tools.exportBuild(body.instancePath, body.outputId, body.style),
682
+ export_build: (tools, body) => tools.exportBuild(body.instancePath, body.outputId, body.style, body.instance_id),
351
683
  create_build: (tools, body) => tools.createBuild(body.id, body.style, body.palette, body.parts, body.bounds),
352
684
  generate_build: (tools, body) => tools.generateBuild(body.id, body.style, body.palette, body.code, body.seed),
353
- import_build: (tools, body) => tools.importBuild(body.buildData, body.targetPath, body.position),
685
+ import_build: (tools, body) => tools.importBuild(body.buildData, body.targetPath, body.position, body.instance_id),
354
686
  list_library: (tools, body) => tools.listLibrary(body.style),
355
- search_materials: (tools, body) => tools.searchMaterials(body.query, body.maxResults),
687
+ search_materials: (tools, body) => tools.searchMaterials(body.query, body.maxResults, body.instance_id),
356
688
  get_build: (tools, body) => tools.getBuild(body.id),
357
- import_scene: (tools, body) => tools.importScene(body.sceneData, body.targetPath),
358
- undo: (tools) => tools.undo(),
359
- redo: (tools) => tools.redo(),
689
+ import_scene: (tools, body) => tools.importScene(body.sceneData, body.targetPath, body.instance_id),
690
+ undo: (tools, body) => tools.undo(body.instance_id),
691
+ redo: (tools, body) => tools.redo(body.instance_id),
360
692
  search_assets: (tools, body) => tools.searchAssets(body.assetType, body.query, body.maxResults, body.sortBy, body.verifiedCreatorsOnly),
361
693
  get_asset_details: (tools, body) => tools.getAssetDetails(body.assetId),
362
694
  get_asset_thumbnail: (tools, body) => tools.getAssetThumbnail(body.assetId, body.size),
363
- insert_asset: (tools, body) => tools.insertAsset(body.assetId, body.parentPath, body.position),
364
- preview_asset: (tools, body) => tools.previewAsset(body.assetId, body.includeProperties, body.maxDepth),
695
+ insert_asset: (tools, body) => tools.insertAsset(body.assetId, body.parentPath, body.position, body.instance_id),
696
+ preview_asset: (tools, body) => tools.previewAsset(body.assetId, body.includeProperties, body.maxDepth, body.instance_id),
365
697
  upload_asset: (tools, body) => tools.uploadAsset(body.filePath, body.assetType, body.displayName, body.description, body.userId, body.groupId),
366
- clone_object: (tools, body) => tools.cloneObject(body.instancePath, body.targetParentPath),
367
- get_descendants: (tools, body) => tools.getDescendants(body.instancePath, body.maxDepth, body.classFilter),
368
- compare_instances: (tools, body) => tools.compareInstances(body.instancePathA, body.instancePathB),
369
- get_output_log: (tools, body) => tools.getOutputLog(body.maxEntries, body.messageType),
370
- bulk_set_attributes: (tools, body) => tools.bulkSetAttributes(body.instancePath, body.attributes),
371
- capture_screenshot: (tools) => tools.captureScreenshot(),
372
- simulate_mouse_input: (tools, body) => tools.simulateMouseInput(body.action, body.x, body.y, body.button, body.scrollDirection, body.target),
373
- simulate_keyboard_input: (tools, body) => tools.simulateKeyboardInput(body.keyCode, body.action, body.duration, body.target),
374
- character_navigation: (tools, body) => tools.characterNavigation(body.position, body.instancePath, body.waitForCompletion, body.timeout, body.target),
375
- get_memory_breakdown: (tools, body) => tools.getMemoryBreakdown(body.target, body.tags),
376
- export_rbxm: (tools, body) => tools.exportRbxm(body.instance_paths, body.output_path, body.target),
377
- import_rbxm: (tools, body) => tools.importRbxm(body.source, body.parent_path, body.target),
698
+ clone_object: (tools, body) => tools.cloneObject(body.instancePath, body.targetParentPath, body.instance_id),
699
+ get_descendants: (tools, body) => tools.getDescendants(body.instancePath, body.maxDepth, body.classFilter, body.instance_id),
700
+ compare_instances: (tools, body) => tools.compareInstances(body.instancePathA, body.instancePathB, body.instance_id),
701
+ get_output_log: (tools, body) => tools.getOutputLog(body.maxEntries, body.messageType, body.instance_id),
702
+ bulk_set_attributes: (tools, body) => tools.bulkSetAttributes(body.instancePath, body.attributes, body.instance_id),
703
+ capture_screenshot: (tools, body) => tools.captureScreenshot(body.instance_id, body.format, body.quality),
704
+ simulate_mouse_input: (tools, body) => tools.simulateMouseInput(body.action, body.x, body.y, body.button, body.scrollDirection, body.target, body.instance_id),
705
+ simulate_keyboard_input: (tools, body) => tools.simulateKeyboardInput(body.keyCode, body.action, body.duration, body.text, body.target, body.instance_id),
706
+ character_navigation: (tools, body) => tools.characterNavigation(body.position, body.instancePath, body.waitForCompletion, body.timeout, body.target, body.instance_id),
707
+ get_memory_breakdown: (tools, body) => tools.getMemoryBreakdown(body.target, body.tags, body.instance_id),
708
+ export_rbxm: (tools, body) => tools.exportRbxm(body.instance_paths, body.output_path, body.target, body.instance_id),
709
+ import_rbxm: (tools, body) => tools.importRbxm(body.source, body.parent_path, body.target, body.instance_id),
378
710
  find_and_replace_in_scripts: (tools, body) => tools.findAndReplaceInScripts(body.pattern, body.replacement, {
379
711
  caseSensitive: body.caseSensitive,
380
712
  usePattern: body.usePattern,
@@ -382,7 +714,7 @@ var init_http_server = __esm({
382
714
  classFilter: body.classFilter,
383
715
  dryRun: body.dryRun,
384
716
  maxReplacements: body.maxReplacements
385
- })
717
+ }, body.instance_id)
386
718
  };
387
719
  }
388
720
  });
@@ -397,9 +729,9 @@ var init_studio_client = __esm({
397
729
  constructor(bridge) {
398
730
  this.bridge = bridge;
399
731
  }
400
- async request(endpoint, data, target = "edit") {
732
+ async request(endpoint, data, targetInstanceId, targetRole) {
401
733
  try {
402
- const response = await this.bridge.sendRequest(endpoint, data, target);
734
+ const response = await this.bridge.sendRequest(endpoint, data, targetInstanceId, targetRole);
403
735
  return response;
404
736
  } catch (error) {
405
737
  if (error instanceof Error && error.message === "Request timeout") {
@@ -1136,6 +1468,963 @@ var init_roblox_cookie_client = __esm({
1136
1468
  }
1137
1469
  });
1138
1470
 
1471
+ // ../core/dist/jpeg-encoder.js
1472
+ function rgbaToJpeg(rgba, width, height, quality = 80) {
1473
+ if (width <= 0 || height <= 0)
1474
+ throw new Error(`Invalid JPEG dimensions: ${width}x${height}`);
1475
+ const expected = width * height * 4;
1476
+ if (rgba.length < expected)
1477
+ throw new Error(`Buffer too small: got ${rgba.length}, need ${expected}`);
1478
+ const encoder = new JpegEncoder(quality);
1479
+ return encoder.encode(rgba, width, height);
1480
+ }
1481
+ var ZIGZAG, STD_DC_LUMINANCE_NRCODES, STD_DC_LUMINANCE_VALUES, STD_AC_LUMINANCE_NRCODES, STD_AC_LUMINANCE_VALUES, STD_DC_CHROMINANCE_NRCODES, STD_DC_CHROMINANCE_VALUES, STD_AC_CHROMINANCE_NRCODES, STD_AC_CHROMINANCE_VALUES, JpegEncoder;
1482
+ var init_jpeg_encoder = __esm({
1483
+ "../core/dist/jpeg-encoder.js"() {
1484
+ "use strict";
1485
+ ZIGZAG = [
1486
+ 0,
1487
+ 1,
1488
+ 5,
1489
+ 6,
1490
+ 14,
1491
+ 15,
1492
+ 27,
1493
+ 28,
1494
+ 2,
1495
+ 4,
1496
+ 7,
1497
+ 13,
1498
+ 16,
1499
+ 26,
1500
+ 29,
1501
+ 42,
1502
+ 3,
1503
+ 8,
1504
+ 12,
1505
+ 17,
1506
+ 25,
1507
+ 30,
1508
+ 41,
1509
+ 43,
1510
+ 9,
1511
+ 11,
1512
+ 18,
1513
+ 24,
1514
+ 31,
1515
+ 40,
1516
+ 44,
1517
+ 53,
1518
+ 10,
1519
+ 19,
1520
+ 23,
1521
+ 32,
1522
+ 39,
1523
+ 45,
1524
+ 52,
1525
+ 54,
1526
+ 20,
1527
+ 22,
1528
+ 33,
1529
+ 38,
1530
+ 46,
1531
+ 51,
1532
+ 55,
1533
+ 60,
1534
+ 21,
1535
+ 34,
1536
+ 37,
1537
+ 47,
1538
+ 50,
1539
+ 56,
1540
+ 59,
1541
+ 61,
1542
+ 35,
1543
+ 36,
1544
+ 48,
1545
+ 49,
1546
+ 57,
1547
+ 58,
1548
+ 62,
1549
+ 63
1550
+ ];
1551
+ STD_DC_LUMINANCE_NRCODES = [0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0];
1552
+ STD_DC_LUMINANCE_VALUES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
1553
+ STD_AC_LUMINANCE_NRCODES = [0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125];
1554
+ STD_AC_LUMINANCE_VALUES = [
1555
+ 1,
1556
+ 2,
1557
+ 3,
1558
+ 0,
1559
+ 4,
1560
+ 17,
1561
+ 5,
1562
+ 18,
1563
+ 33,
1564
+ 49,
1565
+ 65,
1566
+ 6,
1567
+ 19,
1568
+ 81,
1569
+ 97,
1570
+ 7,
1571
+ 34,
1572
+ 113,
1573
+ 20,
1574
+ 50,
1575
+ 129,
1576
+ 145,
1577
+ 161,
1578
+ 8,
1579
+ 35,
1580
+ 66,
1581
+ 177,
1582
+ 193,
1583
+ 21,
1584
+ 82,
1585
+ 209,
1586
+ 240,
1587
+ 36,
1588
+ 51,
1589
+ 98,
1590
+ 114,
1591
+ 130,
1592
+ 9,
1593
+ 10,
1594
+ 22,
1595
+ 23,
1596
+ 24,
1597
+ 25,
1598
+ 26,
1599
+ 37,
1600
+ 38,
1601
+ 39,
1602
+ 40,
1603
+ 41,
1604
+ 42,
1605
+ 52,
1606
+ 53,
1607
+ 54,
1608
+ 55,
1609
+ 56,
1610
+ 57,
1611
+ 58,
1612
+ 67,
1613
+ 68,
1614
+ 69,
1615
+ 70,
1616
+ 71,
1617
+ 72,
1618
+ 73,
1619
+ 74,
1620
+ 83,
1621
+ 84,
1622
+ 85,
1623
+ 86,
1624
+ 87,
1625
+ 88,
1626
+ 89,
1627
+ 90,
1628
+ 99,
1629
+ 100,
1630
+ 101,
1631
+ 102,
1632
+ 103,
1633
+ 104,
1634
+ 105,
1635
+ 106,
1636
+ 115,
1637
+ 116,
1638
+ 117,
1639
+ 118,
1640
+ 119,
1641
+ 120,
1642
+ 121,
1643
+ 122,
1644
+ 131,
1645
+ 132,
1646
+ 133,
1647
+ 134,
1648
+ 135,
1649
+ 136,
1650
+ 137,
1651
+ 138,
1652
+ 146,
1653
+ 147,
1654
+ 148,
1655
+ 149,
1656
+ 150,
1657
+ 151,
1658
+ 152,
1659
+ 153,
1660
+ 154,
1661
+ 162,
1662
+ 163,
1663
+ 164,
1664
+ 165,
1665
+ 166,
1666
+ 167,
1667
+ 168,
1668
+ 169,
1669
+ 170,
1670
+ 178,
1671
+ 179,
1672
+ 180,
1673
+ 181,
1674
+ 182,
1675
+ 183,
1676
+ 184,
1677
+ 185,
1678
+ 186,
1679
+ 194,
1680
+ 195,
1681
+ 196,
1682
+ 197,
1683
+ 198,
1684
+ 199,
1685
+ 200,
1686
+ 201,
1687
+ 202,
1688
+ 210,
1689
+ 211,
1690
+ 212,
1691
+ 213,
1692
+ 214,
1693
+ 215,
1694
+ 216,
1695
+ 217,
1696
+ 218,
1697
+ 225,
1698
+ 226,
1699
+ 227,
1700
+ 228,
1701
+ 229,
1702
+ 230,
1703
+ 231,
1704
+ 232,
1705
+ 233,
1706
+ 234,
1707
+ 241,
1708
+ 242,
1709
+ 243,
1710
+ 244,
1711
+ 245,
1712
+ 246,
1713
+ 247,
1714
+ 248,
1715
+ 249,
1716
+ 250
1717
+ ];
1718
+ STD_DC_CHROMINANCE_NRCODES = [0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0];
1719
+ STD_DC_CHROMINANCE_VALUES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
1720
+ STD_AC_CHROMINANCE_NRCODES = [0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119];
1721
+ STD_AC_CHROMINANCE_VALUES = [
1722
+ 0,
1723
+ 1,
1724
+ 2,
1725
+ 3,
1726
+ 17,
1727
+ 4,
1728
+ 5,
1729
+ 33,
1730
+ 49,
1731
+ 6,
1732
+ 18,
1733
+ 65,
1734
+ 81,
1735
+ 7,
1736
+ 97,
1737
+ 113,
1738
+ 19,
1739
+ 34,
1740
+ 50,
1741
+ 129,
1742
+ 8,
1743
+ 20,
1744
+ 66,
1745
+ 145,
1746
+ 161,
1747
+ 177,
1748
+ 193,
1749
+ 9,
1750
+ 35,
1751
+ 51,
1752
+ 82,
1753
+ 240,
1754
+ 21,
1755
+ 98,
1756
+ 114,
1757
+ 209,
1758
+ 10,
1759
+ 22,
1760
+ 36,
1761
+ 52,
1762
+ 225,
1763
+ 37,
1764
+ 241,
1765
+ 23,
1766
+ 24,
1767
+ 25,
1768
+ 26,
1769
+ 38,
1770
+ 39,
1771
+ 40,
1772
+ 41,
1773
+ 42,
1774
+ 53,
1775
+ 54,
1776
+ 55,
1777
+ 56,
1778
+ 57,
1779
+ 58,
1780
+ 67,
1781
+ 68,
1782
+ 69,
1783
+ 70,
1784
+ 71,
1785
+ 72,
1786
+ 73,
1787
+ 74,
1788
+ 83,
1789
+ 84,
1790
+ 85,
1791
+ 86,
1792
+ 87,
1793
+ 88,
1794
+ 89,
1795
+ 90,
1796
+ 99,
1797
+ 100,
1798
+ 101,
1799
+ 102,
1800
+ 103,
1801
+ 104,
1802
+ 105,
1803
+ 106,
1804
+ 115,
1805
+ 116,
1806
+ 117,
1807
+ 118,
1808
+ 119,
1809
+ 120,
1810
+ 121,
1811
+ 122,
1812
+ 130,
1813
+ 131,
1814
+ 132,
1815
+ 133,
1816
+ 134,
1817
+ 135,
1818
+ 136,
1819
+ 137,
1820
+ 138,
1821
+ 146,
1822
+ 147,
1823
+ 148,
1824
+ 149,
1825
+ 150,
1826
+ 151,
1827
+ 152,
1828
+ 153,
1829
+ 154,
1830
+ 162,
1831
+ 163,
1832
+ 164,
1833
+ 165,
1834
+ 166,
1835
+ 167,
1836
+ 168,
1837
+ 169,
1838
+ 170,
1839
+ 178,
1840
+ 179,
1841
+ 180,
1842
+ 181,
1843
+ 182,
1844
+ 183,
1845
+ 184,
1846
+ 185,
1847
+ 186,
1848
+ 194,
1849
+ 195,
1850
+ 196,
1851
+ 197,
1852
+ 198,
1853
+ 199,
1854
+ 200,
1855
+ 201,
1856
+ 202,
1857
+ 210,
1858
+ 211,
1859
+ 212,
1860
+ 213,
1861
+ 214,
1862
+ 215,
1863
+ 216,
1864
+ 217,
1865
+ 218,
1866
+ 226,
1867
+ 227,
1868
+ 228,
1869
+ 229,
1870
+ 230,
1871
+ 231,
1872
+ 232,
1873
+ 233,
1874
+ 234,
1875
+ 242,
1876
+ 243,
1877
+ 244,
1878
+ 245,
1879
+ 246,
1880
+ 247,
1881
+ 248,
1882
+ 249,
1883
+ 250
1884
+ ];
1885
+ JpegEncoder = class {
1886
+ YTable = new Int32Array(64);
1887
+ UVTable = new Int32Array(64);
1888
+ fdtbl_Y = new Float32Array(64);
1889
+ fdtbl_UV = new Float32Array(64);
1890
+ YDC_HT;
1891
+ UVDC_HT;
1892
+ YAC_HT;
1893
+ UVAC_HT;
1894
+ bitcode = new Array(65535);
1895
+ category = new Int32Array(65535);
1896
+ outputfDCTQuant = new Float32Array(64);
1897
+ DU = new Int32Array(64);
1898
+ // RGB->YUV lookup tables
1899
+ RGB_YUV_TABLE = new Int32Array(2048);
1900
+ byteout = [];
1901
+ bytenew = 0;
1902
+ bytepos = 7;
1903
+ constructor(quality) {
1904
+ this.initHuffmanTbl();
1905
+ this.initCategoryNumber();
1906
+ this.initRGBYUVTable();
1907
+ this.setQuality(quality);
1908
+ }
1909
+ initQuantTables(sf) {
1910
+ const YQT = [
1911
+ 16,
1912
+ 11,
1913
+ 10,
1914
+ 16,
1915
+ 24,
1916
+ 40,
1917
+ 51,
1918
+ 61,
1919
+ 12,
1920
+ 12,
1921
+ 14,
1922
+ 19,
1923
+ 26,
1924
+ 58,
1925
+ 60,
1926
+ 55,
1927
+ 14,
1928
+ 13,
1929
+ 16,
1930
+ 24,
1931
+ 40,
1932
+ 57,
1933
+ 69,
1934
+ 56,
1935
+ 14,
1936
+ 17,
1937
+ 22,
1938
+ 29,
1939
+ 51,
1940
+ 87,
1941
+ 80,
1942
+ 62,
1943
+ 18,
1944
+ 22,
1945
+ 37,
1946
+ 56,
1947
+ 68,
1948
+ 109,
1949
+ 103,
1950
+ 77,
1951
+ 24,
1952
+ 35,
1953
+ 55,
1954
+ 64,
1955
+ 81,
1956
+ 104,
1957
+ 113,
1958
+ 92,
1959
+ 49,
1960
+ 64,
1961
+ 78,
1962
+ 87,
1963
+ 103,
1964
+ 121,
1965
+ 120,
1966
+ 101,
1967
+ 72,
1968
+ 92,
1969
+ 95,
1970
+ 98,
1971
+ 112,
1972
+ 100,
1973
+ 103,
1974
+ 99
1975
+ ];
1976
+ for (let i = 0; i < 64; i++) {
1977
+ let t = Math.floor((YQT[i] * sf + 50) / 100);
1978
+ if (t < 1)
1979
+ t = 1;
1980
+ else if (t > 255)
1981
+ t = 255;
1982
+ this.YTable[ZIGZAG[i]] = t;
1983
+ }
1984
+ const UVQT = [
1985
+ 17,
1986
+ 18,
1987
+ 24,
1988
+ 47,
1989
+ 99,
1990
+ 99,
1991
+ 99,
1992
+ 99,
1993
+ 18,
1994
+ 21,
1995
+ 26,
1996
+ 66,
1997
+ 99,
1998
+ 99,
1999
+ 99,
2000
+ 99,
2001
+ 24,
2002
+ 26,
2003
+ 56,
2004
+ 99,
2005
+ 99,
2006
+ 99,
2007
+ 99,
2008
+ 99,
2009
+ 47,
2010
+ 66,
2011
+ 99,
2012
+ 99,
2013
+ 99,
2014
+ 99,
2015
+ 99,
2016
+ 99,
2017
+ 99,
2018
+ 99,
2019
+ 99,
2020
+ 99,
2021
+ 99,
2022
+ 99,
2023
+ 99,
2024
+ 99,
2025
+ 99,
2026
+ 99,
2027
+ 99,
2028
+ 99,
2029
+ 99,
2030
+ 99,
2031
+ 99,
2032
+ 99,
2033
+ 99,
2034
+ 99,
2035
+ 99,
2036
+ 99,
2037
+ 99,
2038
+ 99,
2039
+ 99,
2040
+ 99,
2041
+ 99,
2042
+ 99,
2043
+ 99,
2044
+ 99,
2045
+ 99,
2046
+ 99,
2047
+ 99,
2048
+ 99
2049
+ ];
2050
+ for (let j = 0; j < 64; j++) {
2051
+ let u = Math.floor((UVQT[j] * sf + 50) / 100);
2052
+ if (u < 1)
2053
+ u = 1;
2054
+ else if (u > 255)
2055
+ u = 255;
2056
+ this.UVTable[ZIGZAG[j]] = u;
2057
+ }
2058
+ const aasf = [
2059
+ 1,
2060
+ 1.387039845,
2061
+ 1.306562965,
2062
+ 1.175875602,
2063
+ 1,
2064
+ 0.785694958,
2065
+ 0.5411961,
2066
+ 0.275899379
2067
+ ];
2068
+ let k = 0;
2069
+ for (let row = 0; row < 8; row++) {
2070
+ for (let col = 0; col < 8; col++) {
2071
+ this.fdtbl_Y[k] = 1 / (this.YTable[ZIGZAG[k]] * aasf[row] * aasf[col] * 8);
2072
+ this.fdtbl_UV[k] = 1 / (this.UVTable[ZIGZAG[k]] * aasf[row] * aasf[col] * 8);
2073
+ k++;
2074
+ }
2075
+ }
2076
+ }
2077
+ computeHuffmanTbl(nrcodes, std_table) {
2078
+ let codevalue = 0;
2079
+ let pos_in_table = 0;
2080
+ const HT = [];
2081
+ for (let k = 1; k <= 16; k++) {
2082
+ for (let j = 1; j <= nrcodes[k]; j++) {
2083
+ HT[std_table[pos_in_table]] = { code: codevalue, length: k };
2084
+ pos_in_table++;
2085
+ codevalue++;
2086
+ }
2087
+ codevalue *= 2;
2088
+ }
2089
+ return HT;
2090
+ }
2091
+ initHuffmanTbl() {
2092
+ this.YDC_HT = this.computeHuffmanTbl(STD_DC_LUMINANCE_NRCODES, STD_DC_LUMINANCE_VALUES);
2093
+ this.UVDC_HT = this.computeHuffmanTbl(STD_DC_CHROMINANCE_NRCODES, STD_DC_CHROMINANCE_VALUES);
2094
+ this.YAC_HT = this.computeHuffmanTbl(STD_AC_LUMINANCE_NRCODES, STD_AC_LUMINANCE_VALUES);
2095
+ this.UVAC_HT = this.computeHuffmanTbl(STD_AC_CHROMINANCE_NRCODES, STD_AC_CHROMINANCE_VALUES);
2096
+ }
2097
+ initCategoryNumber() {
2098
+ let nrlower = 1;
2099
+ let nrupper = 2;
2100
+ for (let cat = 1; cat <= 15; cat++) {
2101
+ for (let nr = nrlower; nr < nrupper; nr++) {
2102
+ this.category[32767 + nr] = cat;
2103
+ this.bitcode[32767 + nr] = { length: cat, code: nr };
2104
+ }
2105
+ for (let nrneg = -(nrupper - 1); nrneg <= -nrlower; nrneg++) {
2106
+ this.category[32767 + nrneg] = cat;
2107
+ this.bitcode[32767 + nrneg] = { length: cat, code: nrupper - 1 + nrneg };
2108
+ }
2109
+ nrlower <<= 1;
2110
+ nrupper <<= 1;
2111
+ }
2112
+ }
2113
+ initRGBYUVTable() {
2114
+ for (let i = 0; i < 256; i++) {
2115
+ this.RGB_YUV_TABLE[i] = 19595 * i;
2116
+ this.RGB_YUV_TABLE[i + 256 >> 0] = 38470 * i;
2117
+ this.RGB_YUV_TABLE[i + 512 >> 0] = 7471 * i + 32768;
2118
+ this.RGB_YUV_TABLE[i + 768 >> 0] = -11059 * i;
2119
+ this.RGB_YUV_TABLE[i + 1024 >> 0] = -21709 * i;
2120
+ this.RGB_YUV_TABLE[i + 1280 >> 0] = 32768 * i + 8421375;
2121
+ this.RGB_YUV_TABLE[i + 1536 >> 0] = -27439 * i;
2122
+ this.RGB_YUV_TABLE[i + 1792 >> 0] = -5329 * i;
2123
+ }
2124
+ }
2125
+ setQuality(quality) {
2126
+ let q = quality;
2127
+ if (q <= 0)
2128
+ q = 1;
2129
+ if (q > 100)
2130
+ q = 100;
2131
+ const sf = q < 50 ? Math.floor(5e3 / q) : Math.floor(200 - q * 2);
2132
+ this.initQuantTables(sf);
2133
+ }
2134
+ writeBits(bs) {
2135
+ const value = bs.code;
2136
+ let posval = bs.length - 1;
2137
+ while (posval >= 0) {
2138
+ if (value & 1 << posval) {
2139
+ this.bytenew |= 1 << this.bytepos;
2140
+ }
2141
+ posval--;
2142
+ this.bytepos--;
2143
+ if (this.bytepos < 0) {
2144
+ if (this.bytenew === 255) {
2145
+ this.writeByte(255);
2146
+ this.writeByte(0);
2147
+ } else {
2148
+ this.writeByte(this.bytenew);
2149
+ }
2150
+ this.bytepos = 7;
2151
+ this.bytenew = 0;
2152
+ }
2153
+ }
2154
+ }
2155
+ writeByte(value) {
2156
+ this.byteout.push(value & 255);
2157
+ }
2158
+ writeWord(value) {
2159
+ this.writeByte(value >> 8 & 255);
2160
+ this.writeByte(value & 255);
2161
+ }
2162
+ fDCTQuant(data, fdtbl) {
2163
+ let d0, d1, d2, d3, d4, d5, d6, d7;
2164
+ let dataOff = 0;
2165
+ const I8 = 8;
2166
+ const I64 = 64;
2167
+ for (let i = 0; i < I8; ++i) {
2168
+ d0 = data[dataOff];
2169
+ d1 = data[dataOff + 1];
2170
+ d2 = data[dataOff + 2];
2171
+ d3 = data[dataOff + 3];
2172
+ d4 = data[dataOff + 4];
2173
+ d5 = data[dataOff + 5];
2174
+ d6 = data[dataOff + 6];
2175
+ d7 = data[dataOff + 7];
2176
+ const tmp0 = d0 + d7;
2177
+ const tmp7 = d0 - d7;
2178
+ const tmp1 = d1 + d6;
2179
+ const tmp6 = d1 - d6;
2180
+ const tmp2 = d2 + d5;
2181
+ const tmp5 = d2 - d5;
2182
+ const tmp3 = d3 + d4;
2183
+ const tmp4 = d3 - d4;
2184
+ let tmp10 = tmp0 + tmp3;
2185
+ const tmp13 = tmp0 - tmp3;
2186
+ let tmp11 = tmp1 + tmp2;
2187
+ let tmp12 = tmp1 - tmp2;
2188
+ data[dataOff] = tmp10 + tmp11;
2189
+ data[dataOff + 4] = tmp10 - tmp11;
2190
+ const z1 = (tmp12 + tmp13) * 0.707106781;
2191
+ data[dataOff + 2] = tmp13 + z1;
2192
+ data[dataOff + 6] = tmp13 - z1;
2193
+ tmp10 = tmp4 + tmp5;
2194
+ tmp11 = tmp5 + tmp6;
2195
+ tmp12 = tmp6 + tmp7;
2196
+ const z5 = (tmp10 - tmp12) * 0.382683433;
2197
+ const z2 = 0.5411961 * tmp10 + z5;
2198
+ const z4 = 1.306562965 * tmp12 + z5;
2199
+ const z3 = tmp11 * 0.707106781;
2200
+ const z11 = tmp7 + z3;
2201
+ const z13 = tmp7 - z3;
2202
+ data[dataOff + 5] = z13 + z2;
2203
+ data[dataOff + 3] = z13 - z2;
2204
+ data[dataOff + 1] = z11 + z4;
2205
+ data[dataOff + 7] = z11 - z4;
2206
+ dataOff += 8;
2207
+ }
2208
+ dataOff = 0;
2209
+ for (let i = 0; i < I8; ++i) {
2210
+ d0 = data[dataOff];
2211
+ d1 = data[dataOff + 8];
2212
+ d2 = data[dataOff + 16];
2213
+ d3 = data[dataOff + 24];
2214
+ d4 = data[dataOff + 32];
2215
+ d5 = data[dataOff + 40];
2216
+ d6 = data[dataOff + 48];
2217
+ d7 = data[dataOff + 56];
2218
+ const tmp0p2 = d0 + d7;
2219
+ const tmp7p2 = d0 - d7;
2220
+ const tmp1p2 = d1 + d6;
2221
+ const tmp6p2 = d1 - d6;
2222
+ const tmp2p2 = d2 + d5;
2223
+ const tmp5p2 = d2 - d5;
2224
+ const tmp3p2 = d3 + d4;
2225
+ const tmp4p2 = d3 - d4;
2226
+ let tmp10p2 = tmp0p2 + tmp3p2;
2227
+ const tmp13p2 = tmp0p2 - tmp3p2;
2228
+ let tmp11p2 = tmp1p2 + tmp2p2;
2229
+ let tmp12p2 = tmp1p2 - tmp2p2;
2230
+ data[dataOff] = tmp10p2 + tmp11p2;
2231
+ data[dataOff + 32] = tmp10p2 - tmp11p2;
2232
+ const z1p2 = (tmp12p2 + tmp13p2) * 0.707106781;
2233
+ data[dataOff + 16] = tmp13p2 + z1p2;
2234
+ data[dataOff + 48] = tmp13p2 - z1p2;
2235
+ tmp10p2 = tmp4p2 + tmp5p2;
2236
+ tmp11p2 = tmp5p2 + tmp6p2;
2237
+ tmp12p2 = tmp6p2 + tmp7p2;
2238
+ const z5p2 = (tmp10p2 - tmp12p2) * 0.382683433;
2239
+ const z2p2 = 0.5411961 * tmp10p2 + z5p2;
2240
+ const z4p2 = 1.306562965 * tmp12p2 + z5p2;
2241
+ const z3p2 = tmp11p2 * 0.707106781;
2242
+ const z11p2 = tmp7p2 + z3p2;
2243
+ const z13p2 = tmp7p2 - z3p2;
2244
+ data[dataOff + 40] = z13p2 + z2p2;
2245
+ data[dataOff + 24] = z13p2 - z2p2;
2246
+ data[dataOff + 8] = z11p2 + z4p2;
2247
+ data[dataOff + 56] = z11p2 - z4p2;
2248
+ dataOff++;
2249
+ }
2250
+ for (let i = 0; i < I64; ++i) {
2251
+ const fDCTVal = data[i] * fdtbl[i];
2252
+ this.outputfDCTQuant[i] = fDCTVal > 0 ? fDCTVal + 0.5 | 0 : fDCTVal - 0.5 | 0;
2253
+ }
2254
+ return this.outputfDCTQuant;
2255
+ }
2256
+ processDU(CDU, fdtbl, DC, HTDC, HTAC) {
2257
+ const EOB = HTAC[0];
2258
+ const M16zeroes = HTAC[240];
2259
+ let pos;
2260
+ const I16 = 16;
2261
+ const I63 = 63;
2262
+ const I64 = 64;
2263
+ const DU_DCT = this.fDCTQuant(CDU, fdtbl);
2264
+ for (let j = 0; j < I64; ++j) {
2265
+ this.DU[ZIGZAG[j]] = DU_DCT[j];
2266
+ }
2267
+ const Diff = this.DU[0] - DC;
2268
+ DC = this.DU[0];
2269
+ if (Diff === 0) {
2270
+ this.writeBits(HTDC[0]);
2271
+ } else {
2272
+ pos = 32767 + Diff;
2273
+ this.writeBits(HTDC[this.category[pos]]);
2274
+ this.writeBits(this.bitcode[pos]);
2275
+ }
2276
+ let end0pos = 63;
2277
+ for (; end0pos > 0 && this.DU[end0pos] === 0; end0pos--) {
2278
+ }
2279
+ if (end0pos === 0) {
2280
+ this.writeBits(EOB);
2281
+ return DC;
2282
+ }
2283
+ let i = 1;
2284
+ let lng;
2285
+ while (i <= end0pos) {
2286
+ const startpos = i;
2287
+ for (; this.DU[i] === 0 && i <= end0pos; ++i) {
2288
+ }
2289
+ let nrzeroes = i - startpos;
2290
+ if (nrzeroes >= I16) {
2291
+ lng = nrzeroes >> 4;
2292
+ for (let nrmarker = 1; nrmarker <= lng; ++nrmarker)
2293
+ this.writeBits(M16zeroes);
2294
+ nrzeroes = nrzeroes & 15;
2295
+ }
2296
+ pos = 32767 + this.DU[i];
2297
+ this.writeBits(HTAC[(nrzeroes << 4) + this.category[pos]]);
2298
+ this.writeBits(this.bitcode[pos]);
2299
+ i++;
2300
+ }
2301
+ if (end0pos !== I63) {
2302
+ this.writeBits(EOB);
2303
+ }
2304
+ return DC;
2305
+ }
2306
+ encode(rgba, width, height) {
2307
+ this.byteout = [];
2308
+ this.bytenew = 0;
2309
+ this.bytepos = 7;
2310
+ this.writeWord(65496);
2311
+ this.writeWord(65504);
2312
+ this.writeWord(16);
2313
+ this.writeByte(74);
2314
+ this.writeByte(70);
2315
+ this.writeByte(73);
2316
+ this.writeByte(70);
2317
+ this.writeByte(0);
2318
+ this.writeByte(1);
2319
+ this.writeByte(1);
2320
+ this.writeByte(0);
2321
+ this.writeWord(1);
2322
+ this.writeWord(1);
2323
+ this.writeByte(0);
2324
+ this.writeByte(0);
2325
+ this.writeWord(65499);
2326
+ this.writeWord(132);
2327
+ this.writeByte(0);
2328
+ for (let i = 0; i < 64; i++)
2329
+ this.writeByte(this.YTable[i]);
2330
+ this.writeByte(1);
2331
+ for (let j = 0; j < 64; j++)
2332
+ this.writeByte(this.UVTable[j]);
2333
+ this.writeWord(65472);
2334
+ this.writeWord(17);
2335
+ this.writeByte(8);
2336
+ this.writeWord(height);
2337
+ this.writeWord(width);
2338
+ this.writeByte(3);
2339
+ this.writeByte(1);
2340
+ this.writeByte(17);
2341
+ this.writeByte(0);
2342
+ this.writeByte(2);
2343
+ this.writeByte(17);
2344
+ this.writeByte(1);
2345
+ this.writeByte(3);
2346
+ this.writeByte(17);
2347
+ this.writeByte(1);
2348
+ this.writeWord(65476);
2349
+ this.writeWord(418);
2350
+ this.writeByte(0);
2351
+ for (let i = 0; i < 16; i++)
2352
+ this.writeByte(STD_DC_LUMINANCE_NRCODES[i + 1]);
2353
+ for (let i = 0; i <= 11; i++)
2354
+ this.writeByte(STD_DC_LUMINANCE_VALUES[i]);
2355
+ this.writeByte(16);
2356
+ for (let i = 0; i < 16; i++)
2357
+ this.writeByte(STD_AC_LUMINANCE_NRCODES[i + 1]);
2358
+ for (let i = 0; i <= 161; i++)
2359
+ this.writeByte(STD_AC_LUMINANCE_VALUES[i]);
2360
+ this.writeByte(1);
2361
+ for (let i = 0; i < 16; i++)
2362
+ this.writeByte(STD_DC_CHROMINANCE_NRCODES[i + 1]);
2363
+ for (let i = 0; i <= 11; i++)
2364
+ this.writeByte(STD_DC_CHROMINANCE_VALUES[i]);
2365
+ this.writeByte(17);
2366
+ for (let i = 0; i < 16; i++)
2367
+ this.writeByte(STD_AC_CHROMINANCE_NRCODES[i + 1]);
2368
+ for (let i = 0; i <= 161; i++)
2369
+ this.writeByte(STD_AC_CHROMINANCE_VALUES[i]);
2370
+ this.writeWord(65498);
2371
+ this.writeWord(12);
2372
+ this.writeByte(3);
2373
+ this.writeByte(1);
2374
+ this.writeByte(0);
2375
+ this.writeByte(2);
2376
+ this.writeByte(17);
2377
+ this.writeByte(3);
2378
+ this.writeByte(17);
2379
+ this.writeByte(0);
2380
+ this.writeByte(63);
2381
+ this.writeByte(0);
2382
+ let DCY = 0;
2383
+ let DCU = 0;
2384
+ let DCV = 0;
2385
+ this.bytenew = 0;
2386
+ this.bytepos = 7;
2387
+ const YDU = new Float32Array(64);
2388
+ const UDU = new Float32Array(64);
2389
+ const VDU = new Float32Array(64);
2390
+ const quadWidth = width * 4;
2391
+ const fdtbl_Y = this.fdtbl_Y;
2392
+ const fdtbl_UV = this.fdtbl_UV;
2393
+ const RGB_YUV_TABLE = this.RGB_YUV_TABLE;
2394
+ for (let y = 0; y < height; y += 8) {
2395
+ for (let x = 0; x < width; x += 8) {
2396
+ let start = quadWidth * y + x * 4;
2397
+ for (let pos = 0; pos < 64; pos++) {
2398
+ const row = pos >> 3;
2399
+ const col = (pos & 7) * 4;
2400
+ let p = start + row * quadWidth + col;
2401
+ if (y + row >= height)
2402
+ p -= quadWidth * (y + row - height + 1);
2403
+ if (x + (col >> 2) >= width)
2404
+ p -= 4 * (x + (col >> 2) - width + 1);
2405
+ const r = rgba[p];
2406
+ const g = rgba[p + 1];
2407
+ const b = rgba[p + 2];
2408
+ YDU[pos] = (RGB_YUV_TABLE[r] + RGB_YUV_TABLE[g + 256] + RGB_YUV_TABLE[b + 512] >> 16) - 128;
2409
+ UDU[pos] = (RGB_YUV_TABLE[r + 768] + RGB_YUV_TABLE[g + 1024] + RGB_YUV_TABLE[b + 1280] >> 16) - 128;
2410
+ VDU[pos] = (RGB_YUV_TABLE[r + 1280] + RGB_YUV_TABLE[g + 1536] + RGB_YUV_TABLE[b + 1792] >> 16) - 128;
2411
+ }
2412
+ DCY = this.processDU(YDU, fdtbl_Y, DCY, this.YDC_HT, this.YAC_HT);
2413
+ DCU = this.processDU(UDU, fdtbl_UV, DCU, this.UVDC_HT, this.UVAC_HT);
2414
+ DCV = this.processDU(VDU, fdtbl_UV, DCV, this.UVDC_HT, this.UVAC_HT);
2415
+ }
2416
+ }
2417
+ if (this.bytepos >= 0) {
2418
+ const fillbits = { length: this.bytepos + 1, code: (1 << this.bytepos + 1) - 1 };
2419
+ this.writeBits(fillbits);
2420
+ }
2421
+ this.writeWord(65497);
2422
+ return Buffer.from(this.byteout);
2423
+ }
2424
+ };
2425
+ }
2426
+ });
2427
+
1139
2428
  // ../core/dist/png-encoder.js
1140
2429
  import { deflateSync } from "zlib";
1141
2430
  function crc32(buf) {
@@ -1199,12 +2488,18 @@ var init_png_encoder = __esm({
1199
2488
  import * as fs from "fs";
1200
2489
  import * as os from "os";
1201
2490
  import * as path from "path";
1202
- function encodePngFromRgbaResponse(response) {
2491
+ function encodeImageFromRgbaResponse(response, format, quality) {
1203
2492
  if (!response.data || response.width === void 0 || response.height === void 0) {
1204
2493
  throw new Error("Render response missing data, width, or height");
1205
2494
  }
1206
2495
  const rgbaBuffer = Buffer.from(response.data, "base64");
1207
- return rgbaToPng(rgbaBuffer, response.width, response.height);
2496
+ if (format === "png") {
2497
+ return { buffer: rgbaToPng(rgbaBuffer, response.width, response.height), mimeType: "image/png" };
2498
+ }
2499
+ return {
2500
+ buffer: rgbaToJpeg(rgbaBuffer, response.width, response.height, quality),
2501
+ mimeType: "image/jpeg"
2502
+ };
1208
2503
  }
1209
2504
  function luaLongQuote(s) {
1210
2505
  let level = 0;
@@ -1215,8 +2510,16 @@ function luaLongQuote(s) {
1215
2510
  ${s}
1216
2511
  ]${eq}]`;
1217
2512
  }
2513
+ function evalCountLines(s) {
2514
+ return s.split("\n").length;
2515
+ }
1218
2516
  function buildModuleScriptInvokeWrapper(opts) {
2517
+ const userLines = evalCountLines(opts.userCode);
1219
2518
  const wrapped = `return ((function()
2519
+ local __mcp_traceback
2520
+ local __mcp_remap
2521
+ local __mcp_LINE_OFFSET = ${EVAL_WRAPPER_LINE_OFFSET}
2522
+ local __mcp_USER_LINES = ${userLines}
1220
2523
  local __mcp_output = {}
1221
2524
  local __mcp_real_print = print
1222
2525
  local __mcp_real_warn = warn
@@ -1237,7 +2540,65 @@ function buildModuleScriptInvokeWrapper(opts) {
1237
2540
  local function __mcp_run()
1238
2541
  ${opts.userCode}
1239
2542
  end
1240
- local ok, errOrValue = xpcall(__mcp_run, debug.traceback)
2543
+ __mcp_remap = function(s)
2544
+ -- Two chunk-name formats can reference our payload: the
2545
+ -- ModuleScript path "Workspace.__MCPEvalPayload:N" and the
2546
+ -- loadstring chunk "[string \\"return ((function()...\\"]:N" (if
2547
+ -- the IIFE happens to compile via loadstring). Normalize both to
2548
+ -- "user_code:N" with the offset stripped AND clamped to user
2549
+ -- range, otherwise unclosed constructs report nonsense lines deep
2550
+ -- in the wrapper. Strip the "Workspace." parent prefix too so the
2551
+ -- final output reads "user_code:N" not "Workspace.user_code:N".
2552
+ local function __mcp_user_line(payload_n)
2553
+ local user_n = payload_n - __mcp_LINE_OFFSET
2554
+ if user_n < 1 then return "1" end
2555
+ if user_n > __mcp_USER_LINES then return tostring(__mcp_USER_LINES) .. " (at end of input)" end
2556
+ return tostring(user_n)
2557
+ end
2558
+ s = string.gsub(s, "Workspace%.__MCPEvalPayload:(%d+)", function(num)
2559
+ local n = tonumber(num)
2560
+ if n then return "user_code:" .. __mcp_user_line(n) end
2561
+ return "user_code:" .. num
2562
+ end)
2563
+ s = string.gsub(s, "__MCPEvalPayload:(%d+)", function(num)
2564
+ local n = tonumber(num)
2565
+ if n then return "user_code:" .. __mcp_user_line(n) end
2566
+ return "user_code:" .. num
2567
+ end)
2568
+ s = string.gsub(s, '%[string "[^"]+"%]:(%d+)', function(num)
2569
+ local n = tonumber(num)
2570
+ if n then return "user_code:" .. __mcp_user_line(n) end
2571
+ return "user_code:" .. num
2572
+ end)
2573
+ return s
2574
+ end
2575
+ __mcp_traceback = function(err)
2576
+ local raw = debug.traceback(tostring(err), 2)
2577
+ local kept = {}
2578
+ for line in string.gmatch(raw, "[^\\n]+") do
2579
+ local num_str = string.match(line, "__MCPEvalPayload:(%d+)")
2580
+ or string.match(line, '%[string "[^"]+"%]:(%d+)')
2581
+ local n = num_str and tonumber(num_str)
2582
+ -- Strip "in function '__mcp_run'" annotation BEFORE filtering:
2583
+ -- user-code frames all carry that suffix (their source is
2584
+ -- hosted inside __mcp_run), so a naive "__mcp_" filter would
2585
+ -- drop every user frame and leave only the error header.
2586
+ line = (string.gsub(line, " in function '__mcp_run'", ""))
2587
+ local skip = string.find(line, "MCPPlugin", 1, true)
2588
+ or string.find(line, "__mcp_", 1, true)
2589
+ or string.find(line, "in function 'xpcall'", 1, true)
2590
+ -- Drop wrapper preamble/postamble frames whose line falls
2591
+ -- outside the user-code range \u2014 those are wrapper internals.
2592
+ if n and (n <= __mcp_LINE_OFFSET or n > __mcp_LINE_OFFSET + __mcp_USER_LINES) then
2593
+ skip = true
2594
+ end
2595
+ if not skip then
2596
+ table.insert(kept, __mcp_remap(line))
2597
+ end
2598
+ end
2599
+ return table.concat(kept, "\\n")
2600
+ end
2601
+ local ok, errOrValue = xpcall(__mcp_run, __mcp_traceback)
1241
2602
  return { ok = ok, value = errOrValue, output = __mcp_output }
1242
2603
  end)())`;
1243
2604
  return `
@@ -1249,6 +2610,46 @@ if not bf then
1249
2610
  error = ${luaLongQuote(opts.missingError)},
1250
2611
  })
1251
2612
  end
2613
+ -- Outer-scope mirror of the in-IIFE __mcp_remap. Applied to parser errors
2614
+ -- we pull out of LogService (those never pass through the IIFE) and to
2615
+ -- the canned engine error string. Same offset as the IIFE's
2616
+ -- __mcp_LINE_OFFSET; covers both chunk-name formats.
2617
+ local __mcp_USER_LINES_OUTER = ${userLines}
2618
+ local function __mcp_outer_user_line(payload_n)
2619
+ local user_n = payload_n - ${EVAL_WRAPPER_LINE_OFFSET}
2620
+ if user_n < 1 then return "1" end
2621
+ if user_n > __mcp_USER_LINES_OUTER then return tostring(__mcp_USER_LINES_OUTER) .. " (at end of input)" end
2622
+ return tostring(user_n)
2623
+ end
2624
+ local function __mcp_outer_remap(s)
2625
+ s = string.gsub(s, "Workspace%.__MCPEvalPayload:(%d+)", function(num)
2626
+ local n = tonumber(num)
2627
+ if n then return "user_code:" .. __mcp_outer_user_line(n) end
2628
+ return "user_code:" .. num
2629
+ end)
2630
+ s = string.gsub(s, "__MCPEvalPayload:(%d+)", function(num)
2631
+ local n = tonumber(num)
2632
+ if n then return "user_code:" .. __mcp_outer_user_line(n) end
2633
+ return "user_code:" .. num
2634
+ end)
2635
+ s = string.gsub(s, '%[string "[^"]+"%]:(%d+)', function(num)
2636
+ local n = tonumber(num)
2637
+ if n then return "user_code:" .. __mcp_outer_user_line(n) end
2638
+ return "user_code:" .. num
2639
+ end)
2640
+ return s
2641
+ end
2642
+ -- JSON-encode tables; otherwise tostring. Cycles or non-serializable
2643
+ -- values fall back to tostring instead of erroring. This is what makes
2644
+ -- eval_server_runtime / eval_client_runtime return structured table data
2645
+ -- (matching execute_luau) instead of "table: 0xaddr".
2646
+ local function __mcp_format(v)
2647
+ if typeof(v) == "table" then
2648
+ local ok, encoded = pcall(function() return HttpService:JSONEncode(v) end)
2649
+ if ok then return encoded end
2650
+ end
2651
+ return tostring(v)
2652
+ end
1252
2653
  local USER_CODE = ${luaLongQuote(wrapped)}
1253
2654
  local m = Instance.new("ModuleScript")
1254
2655
  m.Name = "__MCPEvalPayload"
@@ -1282,7 +2683,7 @@ if not bridgeOk then
1282
2683
  end
1283
2684
  end
1284
2685
  end
1285
- return HttpService:JSONEncode({ bridge = "ok", ok = false, error = errMsg })
2686
+ return HttpService:JSONEncode({ bridge = "ok", ok = false, error = __mcp_outer_remap(errMsg) })
1286
2687
  end
1287
2688
  -- inner is the {ok, value, output} table from our IIFE. Defensive: if it's
1288
2689
  -- somehow not a table (caller bypassed the wrapper), fall back to old shape.
@@ -1290,13 +2691,13 @@ if typeof(inner) ~= "table" then
1290
2691
  return HttpService:JSONEncode({
1291
2692
  bridge = "ok",
1292
2693
  ok = true,
1293
- result = if inner == nil then nil else tostring(inner),
2694
+ result = if inner == nil then nil else __mcp_format(inner),
1294
2695
  })
1295
2696
  end
1296
2697
  return HttpService:JSONEncode({
1297
2698
  bridge = "ok",
1298
2699
  ok = inner.ok == true,
1299
- result = if inner.ok and inner.value ~= nil then tostring(inner.value) else nil,
2700
+ result = if inner.ok and inner.value ~= nil then __mcp_format(inner.value) else nil,
1300
2701
  error = if not inner.ok then tostring(inner.value) else nil,
1301
2702
  output = inner.output or {},
1302
2703
  })
@@ -1313,17 +2714,20 @@ function parseBridgeResponse(response) {
1313
2714
  }
1314
2715
  return JSON.stringify(response);
1315
2716
  }
1316
- var SERVER_LOCAL_NAME, CLIENT_LOCAL_NAME, RobloxStudioTools;
2717
+ var SERVER_LOCAL_NAME, CLIENT_LOCAL_NAME, EVAL_WRAPPER_LINE_OFFSET, RobloxStudioTools;
1317
2718
  var init_tools = __esm({
1318
2719
  "../core/dist/tools/index.js"() {
1319
2720
  "use strict";
1320
2721
  init_studio_client();
2722
+ init_bridge_service();
1321
2723
  init_build_executor();
1322
2724
  init_opencloud_client();
1323
2725
  init_roblox_cookie_client();
2726
+ init_jpeg_encoder();
1324
2727
  init_png_encoder();
1325
2728
  SERVER_LOCAL_NAME = "__MCP_ServerEvalLocal";
1326
2729
  CLIENT_LOCAL_NAME = "__MCP_ClientEvalBridge";
2730
+ EVAL_WRAPPER_LINE_OFFSET = 23;
1327
2731
  RobloxStudioTools = class _RobloxStudioTools {
1328
2732
  client;
1329
2733
  bridge;
@@ -1335,8 +2739,43 @@ var init_tools = __esm({
1335
2739
  this.openCloudClient = new OpenCloudClient();
1336
2740
  this.cookieClient = new RobloxCookieClient();
1337
2741
  }
1338
- async getFileTree(path2 = "") {
1339
- const response = await this.client.request("/api/file-tree", { path: path2 });
2742
+ // Resolve (instance_id, target-role) → concrete (instanceId, role) and
2743
+ // dispatch a single request. Throws RoutingFailure if the resolution is
2744
+ // ambiguous, missing, or asks for fanout on a non-fanout-capable tool —
2745
+ // the MCP transport layer surfaces it as a structured error result so
2746
+ // the LLM can recover via the embedded data.instances list.
2747
+ async _callSingle(endpoint, data, target, instance_id) {
2748
+ const r = this.bridge.resolveTarget({ instance_id, target });
2749
+ if (!r.ok)
2750
+ throw new RoutingFailure(r.error);
2751
+ if (r.mode !== "single") {
2752
+ throw new RoutingFailure({
2753
+ code: "target_role_not_present_on_instance",
2754
+ message: "This tool does not support target=all. Pick a specific role or omit target.",
2755
+ data: {
2756
+ instances: this.bridge.getPublicInstances(),
2757
+ count: this.bridge.getInstances().length
2758
+ }
2759
+ });
2760
+ }
2761
+ return this.client.request(endpoint, data, r.targetInstanceId, r.targetRole);
2762
+ }
2763
+ // Resolves which connected place a tool should target and whether a playtest
2764
+ // CLIENT peer is present on it. Used by capture/input to auto-route to the
2765
+ // running client (where the live viewport + input pipeline are) without the
2766
+ // caller having to pass target. Throws RoutingFailure with the standard
2767
+ // instance list if the place is ambiguous (multiple connected, no instance_id).
2768
+ _resolveRuntime(instance_id) {
2769
+ const r = this.bridge.resolveTarget({ instance_id, target: void 0 });
2770
+ if (!r.ok)
2771
+ throw new RoutingFailure(r.error);
2772
+ const resolvedId = r.targetInstanceId;
2773
+ const roles = this.bridge.getInstances().filter((i) => i.instanceId === resolvedId).map((i) => i.role);
2774
+ const clientRoles = roles.filter((role) => role.startsWith("client")).sort();
2775
+ return { instanceId: resolvedId, clientRole: clientRoles[0] };
2776
+ }
2777
+ async getFileTree(path2 = "", instance_id) {
2778
+ const response = await this._callSingle("/api/file-tree", { path: path2 }, void 0, instance_id);
1340
2779
  return {
1341
2780
  content: [
1342
2781
  {
@@ -1346,8 +2785,8 @@ var init_tools = __esm({
1346
2785
  ]
1347
2786
  };
1348
2787
  }
1349
- async searchFiles(query, searchType = "name") {
1350
- const response = await this.client.request("/api/search-files", { query, searchType });
2788
+ async searchFiles(query, searchType = "name", instance_id) {
2789
+ const response = await this._callSingle("/api/search-files", { query, searchType }, void 0, instance_id);
1351
2790
  return {
1352
2791
  content: [
1353
2792
  {
@@ -1357,8 +2796,8 @@ var init_tools = __esm({
1357
2796
  ]
1358
2797
  };
1359
2798
  }
1360
- async getPlaceInfo() {
1361
- const response = await this.client.request("/api/place-info", {});
2799
+ async getPlaceInfo(instance_id) {
2800
+ const response = await this._callSingle("/api/place-info", {}, void 0, instance_id);
1362
2801
  return {
1363
2802
  content: [
1364
2803
  {
@@ -1368,8 +2807,8 @@ var init_tools = __esm({
1368
2807
  ]
1369
2808
  };
1370
2809
  }
1371
- async getServices(serviceName) {
1372
- const response = await this.client.request("/api/services", { serviceName });
2810
+ async getServices(serviceName, instance_id) {
2811
+ const response = await this._callSingle("/api/services", { serviceName }, void 0, instance_id);
1373
2812
  return {
1374
2813
  content: [
1375
2814
  {
@@ -1379,12 +2818,12 @@ var init_tools = __esm({
1379
2818
  ]
1380
2819
  };
1381
2820
  }
1382
- async searchObjects(query, searchType = "name", propertyName) {
1383
- const response = await this.client.request("/api/search-objects", {
2821
+ async searchObjects(query, searchType = "name", propertyName, instance_id) {
2822
+ const response = await this._callSingle("/api/search-objects", {
1384
2823
  query,
1385
2824
  searchType,
1386
2825
  propertyName
1387
- });
2826
+ }, void 0, instance_id);
1388
2827
  return {
1389
2828
  content: [
1390
2829
  {
@@ -1394,11 +2833,11 @@ var init_tools = __esm({
1394
2833
  ]
1395
2834
  };
1396
2835
  }
1397
- async getInstanceProperties(instancePath, excludeSource) {
2836
+ async getInstanceProperties(instancePath, excludeSource, instance_id) {
1398
2837
  if (!instancePath) {
1399
2838
  throw new Error("Instance path is required for get_instance_properties");
1400
2839
  }
1401
- const response = await this.client.request("/api/instance-properties", { instancePath, excludeSource });
2840
+ const response = await this._callSingle("/api/instance-properties", { instancePath, excludeSource }, void 0, instance_id);
1402
2841
  return {
1403
2842
  content: [
1404
2843
  {
@@ -1408,11 +2847,11 @@ var init_tools = __esm({
1408
2847
  ]
1409
2848
  };
1410
2849
  }
1411
- async getInstanceChildren(instancePath) {
2850
+ async getInstanceChildren(instancePath, instance_id) {
1412
2851
  if (!instancePath) {
1413
2852
  throw new Error("Instance path is required for get_instance_children");
1414
2853
  }
1415
- const response = await this.client.request("/api/instance-children", { instancePath });
2854
+ const response = await this._callSingle("/api/instance-children", { instancePath }, void 0, instance_id);
1416
2855
  return {
1417
2856
  content: [
1418
2857
  {
@@ -1422,14 +2861,14 @@ var init_tools = __esm({
1422
2861
  ]
1423
2862
  };
1424
2863
  }
1425
- async searchByProperty(propertyName, propertyValue) {
2864
+ async searchByProperty(propertyName, propertyValue, instance_id) {
1426
2865
  if (!propertyName || !propertyValue) {
1427
2866
  throw new Error("Property name and value are required for search_by_property");
1428
2867
  }
1429
- const response = await this.client.request("/api/search-by-property", {
2868
+ const response = await this._callSingle("/api/search-by-property", {
1430
2869
  propertyName,
1431
2870
  propertyValue
1432
- });
2871
+ }, void 0, instance_id);
1433
2872
  return {
1434
2873
  content: [
1435
2874
  {
@@ -1439,11 +2878,11 @@ var init_tools = __esm({
1439
2878
  ]
1440
2879
  };
1441
2880
  }
1442
- async getClassInfo(className) {
2881
+ async getClassInfo(className, instance_id) {
1443
2882
  if (!className) {
1444
2883
  throw new Error("Class name is required for get_class_info");
1445
2884
  }
1446
- const response = await this.client.request("/api/class-info", { className });
2885
+ const response = await this._callSingle("/api/class-info", { className }, void 0, instance_id);
1447
2886
  return {
1448
2887
  content: [
1449
2888
  {
@@ -1453,12 +2892,12 @@ var init_tools = __esm({
1453
2892
  ]
1454
2893
  };
1455
2894
  }
1456
- async getProjectStructure(path2, maxDepth, scriptsOnly) {
1457
- const response = await this.client.request("/api/project-structure", {
2895
+ async getProjectStructure(path2, maxDepth, scriptsOnly, instance_id) {
2896
+ const response = await this._callSingle("/api/project-structure", {
1458
2897
  path: path2,
1459
2898
  maxDepth,
1460
2899
  scriptsOnly
1461
- });
2900
+ }, void 0, instance_id);
1462
2901
  return {
1463
2902
  content: [
1464
2903
  {
@@ -1468,15 +2907,15 @@ var init_tools = __esm({
1468
2907
  ]
1469
2908
  };
1470
2909
  }
1471
- async setProperty(instancePath, propertyName, propertyValue) {
2910
+ async setProperty(instancePath, propertyName, propertyValue, instance_id) {
1472
2911
  if (!instancePath || !propertyName) {
1473
2912
  throw new Error("Instance path and property name are required for set_property");
1474
2913
  }
1475
- const response = await this.client.request("/api/set-property", {
2914
+ const response = await this._callSingle("/api/set-property", {
1476
2915
  instancePath,
1477
2916
  propertyName,
1478
2917
  propertyValue
1479
- });
2918
+ }, void 0, instance_id);
1480
2919
  return {
1481
2920
  content: [
1482
2921
  {
@@ -1486,22 +2925,22 @@ var init_tools = __esm({
1486
2925
  ]
1487
2926
  };
1488
2927
  }
1489
- async setProperties(instancePath, properties) {
2928
+ async setProperties(instancePath, properties, instance_id) {
1490
2929
  if (!instancePath || !properties) {
1491
2930
  throw new Error("instancePath and properties are required for set_properties");
1492
2931
  }
1493
- const response = await this.client.request("/api/set-properties", { instancePath, properties });
2932
+ const response = await this._callSingle("/api/set-properties", { instancePath, properties }, void 0, instance_id);
1494
2933
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
1495
2934
  }
1496
- async massSetProperty(paths, propertyName, propertyValue) {
2935
+ async massSetProperty(paths, propertyName, propertyValue, instance_id) {
1497
2936
  if (!paths || paths.length === 0 || !propertyName) {
1498
2937
  throw new Error("Paths array and property name are required for mass_set_property");
1499
2938
  }
1500
- const response = await this.client.request("/api/mass-set-property", {
2939
+ const response = await this._callSingle("/api/mass-set-property", {
1501
2940
  paths,
1502
2941
  propertyName,
1503
2942
  propertyValue
1504
- });
2943
+ }, void 0, instance_id);
1505
2944
  return {
1506
2945
  content: [
1507
2946
  {
@@ -1511,14 +2950,14 @@ var init_tools = __esm({
1511
2950
  ]
1512
2951
  };
1513
2952
  }
1514
- async massGetProperty(paths, propertyName) {
2953
+ async massGetProperty(paths, propertyName, instance_id) {
1515
2954
  if (!paths || paths.length === 0 || !propertyName) {
1516
2955
  throw new Error("Paths array and property name are required for mass_get_property");
1517
2956
  }
1518
- const response = await this.client.request("/api/mass-get-property", {
2957
+ const response = await this._callSingle("/api/mass-get-property", {
1519
2958
  paths,
1520
2959
  propertyName
1521
- });
2960
+ }, void 0, instance_id);
1522
2961
  return {
1523
2962
  content: [
1524
2963
  {
@@ -1528,16 +2967,16 @@ var init_tools = __esm({
1528
2967
  ]
1529
2968
  };
1530
2969
  }
1531
- async createObject(className, parent, name, properties) {
2970
+ async createObject(className, parent, name, properties, instance_id) {
1532
2971
  if (!className || !parent) {
1533
2972
  throw new Error("Class name and parent are required for create_object");
1534
2973
  }
1535
- const response = await this.client.request("/api/create-object", {
2974
+ const response = await this._callSingle("/api/create-object", {
1536
2975
  className,
1537
2976
  parent,
1538
2977
  name,
1539
2978
  properties
1540
- });
2979
+ }, void 0, instance_id);
1541
2980
  return {
1542
2981
  content: [
1543
2982
  {
@@ -1547,11 +2986,11 @@ var init_tools = __esm({
1547
2986
  ]
1548
2987
  };
1549
2988
  }
1550
- async massCreateObjects(objects) {
2989
+ async massCreateObjects(objects, instance_id) {
1551
2990
  if (!objects || objects.length === 0) {
1552
2991
  throw new Error("Objects array is required for mass_create_objects");
1553
2992
  }
1554
- const response = await this.client.request("/api/mass-create-objects", { objects });
2993
+ const response = await this._callSingle("/api/mass-create-objects", { objects }, void 0, instance_id);
1555
2994
  return {
1556
2995
  content: [
1557
2996
  {
@@ -1561,11 +3000,11 @@ var init_tools = __esm({
1561
3000
  ]
1562
3001
  };
1563
3002
  }
1564
- async deleteObject(instancePath) {
3003
+ async deleteObject(instancePath, instance_id) {
1565
3004
  if (!instancePath) {
1566
3005
  throw new Error("Instance path is required for delete_object");
1567
3006
  }
1568
- const response = await this.client.request("/api/delete-object", { instancePath });
3007
+ const response = await this._callSingle("/api/delete-object", { instancePath }, void 0, instance_id);
1569
3008
  return {
1570
3009
  content: [
1571
3010
  {
@@ -1575,15 +3014,15 @@ var init_tools = __esm({
1575
3014
  ]
1576
3015
  };
1577
3016
  }
1578
- async smartDuplicate(instancePath, count, options) {
3017
+ async smartDuplicate(instancePath, count, options, instance_id) {
1579
3018
  if (!instancePath || count < 1) {
1580
3019
  throw new Error("Instance path and count > 0 are required for smart_duplicate");
1581
3020
  }
1582
- const response = await this.client.request("/api/smart-duplicate", {
3021
+ const response = await this._callSingle("/api/smart-duplicate", {
1583
3022
  instancePath,
1584
3023
  count,
1585
3024
  options
1586
- });
3025
+ }, void 0, instance_id);
1587
3026
  return {
1588
3027
  content: [
1589
3028
  {
@@ -1593,11 +3032,11 @@ var init_tools = __esm({
1593
3032
  ]
1594
3033
  };
1595
3034
  }
1596
- async massDuplicate(duplications) {
3035
+ async massDuplicate(duplications, instance_id) {
1597
3036
  if (!duplications || duplications.length === 0) {
1598
3037
  throw new Error("Duplications array is required for mass_duplicate");
1599
3038
  }
1600
- const response = await this.client.request("/api/mass-duplicate", { duplications });
3039
+ const response = await this._callSingle("/api/mass-duplicate", { duplications }, void 0, instance_id);
1601
3040
  return {
1602
3041
  content: [
1603
3042
  {
@@ -1607,11 +3046,11 @@ var init_tools = __esm({
1607
3046
  ]
1608
3047
  };
1609
3048
  }
1610
- async getScriptSource(instancePath, startLine, endLine) {
3049
+ async getScriptSource(instancePath, startLine, endLine, instance_id) {
1611
3050
  if (!instancePath) {
1612
3051
  throw new Error("Instance path is required for get_script_source");
1613
3052
  }
1614
- const response = await this.client.request("/api/get-script-source", { instancePath, startLine, endLine });
3053
+ const response = await this._callSingle("/api/get-script-source", { instancePath, startLine, endLine }, void 0, instance_id);
1615
3054
  if (response.error) {
1616
3055
  return { content: [{ type: "text", text: `Error: ${response.error}` }] };
1617
3056
  }
@@ -1658,11 +3097,11 @@ ${code}`
1658
3097
  }]
1659
3098
  };
1660
3099
  }
1661
- async setScriptSource(instancePath, source) {
3100
+ async setScriptSource(instancePath, source, instance_id) {
1662
3101
  if (!instancePath || typeof source !== "string") {
1663
3102
  throw new Error("Instance path and source code string are required for set_script_source");
1664
3103
  }
1665
- const response = await this.client.request("/api/set-script-source", { instancePath, source });
3104
+ const response = await this._callSingle("/api/set-script-source", { instancePath, source }, void 0, instance_id);
1666
3105
  return {
1667
3106
  content: [
1668
3107
  {
@@ -1672,14 +3111,14 @@ ${code}`
1672
3111
  ]
1673
3112
  };
1674
3113
  }
1675
- async editScriptLines(instancePath, oldString, newString, startLine) {
3114
+ async editScriptLines(instancePath, oldString, newString, startLine, instance_id) {
1676
3115
  if (!instancePath || typeof oldString !== "string" || typeof newString !== "string") {
1677
3116
  throw new Error("Instance path, old_string, and new_string are required for edit_script_lines");
1678
3117
  }
1679
3118
  const payload = { instancePath, old_string: oldString, new_string: newString };
1680
3119
  if (startLine !== void 0)
1681
3120
  payload.startLine = startLine;
1682
- const response = await this.client.request("/api/edit-script-lines", payload);
3121
+ const response = await this._callSingle("/api/edit-script-lines", payload, void 0, instance_id);
1683
3122
  return {
1684
3123
  content: [
1685
3124
  {
@@ -1689,11 +3128,11 @@ ${code}`
1689
3128
  ]
1690
3129
  };
1691
3130
  }
1692
- async insertScriptLines(instancePath, afterLine, newContent) {
3131
+ async insertScriptLines(instancePath, afterLine, newContent, instance_id) {
1693
3132
  if (!instancePath || typeof newContent !== "string") {
1694
3133
  throw new Error("Instance path and newContent are required for insert_script_lines");
1695
3134
  }
1696
- const response = await this.client.request("/api/insert-script-lines", { instancePath, afterLine: afterLine || 0, newContent });
3135
+ const response = await this._callSingle("/api/insert-script-lines", { instancePath, afterLine: afterLine || 0, newContent }, void 0, instance_id);
1697
3136
  return {
1698
3137
  content: [
1699
3138
  {
@@ -1703,11 +3142,11 @@ ${code}`
1703
3142
  ]
1704
3143
  };
1705
3144
  }
1706
- async deleteScriptLines(instancePath, startLine, endLine) {
3145
+ async deleteScriptLines(instancePath, startLine, endLine, instance_id) {
1707
3146
  if (!instancePath || !startLine || !endLine) {
1708
3147
  throw new Error("Instance path, startLine, and endLine are required for delete_script_lines");
1709
3148
  }
1710
- const response = await this.client.request("/api/delete-script-lines", { instancePath, startLine, endLine });
3149
+ const response = await this._callSingle("/api/delete-script-lines", { instancePath, startLine, endLine }, void 0, instance_id);
1711
3150
  return {
1712
3151
  content: [
1713
3152
  {
@@ -1717,14 +3156,14 @@ ${code}`
1717
3156
  ]
1718
3157
  };
1719
3158
  }
1720
- async grepScripts(pattern, options) {
3159
+ async grepScripts(pattern, options, instance_id) {
1721
3160
  if (!pattern) {
1722
3161
  throw new Error("Pattern is required for grep_scripts");
1723
3162
  }
1724
- const response = await this.client.request("/api/grep-scripts", {
3163
+ const response = await this._callSingle("/api/grep-scripts", {
1725
3164
  pattern,
1726
3165
  ...options
1727
- });
3166
+ }, void 0, instance_id);
1728
3167
  return {
1729
3168
  content: [
1730
3169
  {
@@ -1734,11 +3173,11 @@ ${code}`
1734
3173
  ]
1735
3174
  };
1736
3175
  }
1737
- async setAttribute(instancePath, attributeName, attributeValue, valueType) {
3176
+ async setAttribute(instancePath, attributeName, attributeValue, valueType, instance_id) {
1738
3177
  if (!instancePath || !attributeName) {
1739
3178
  throw new Error("Instance path and attribute name are required for set_attribute");
1740
3179
  }
1741
- const response = await this.client.request("/api/set-attribute", { instancePath, attributeName, attributeValue, valueType });
3180
+ const response = await this._callSingle("/api/set-attribute", { instancePath, attributeName, attributeValue, valueType }, void 0, instance_id);
1742
3181
  return {
1743
3182
  content: [
1744
3183
  {
@@ -1748,11 +3187,11 @@ ${code}`
1748
3187
  ]
1749
3188
  };
1750
3189
  }
1751
- async getAttributes(instancePath) {
3190
+ async getAttributes(instancePath, instance_id) {
1752
3191
  if (!instancePath) {
1753
3192
  throw new Error("Instance path is required for get_attributes");
1754
3193
  }
1755
- const response = await this.client.request("/api/get-attributes", { instancePath });
3194
+ const response = await this._callSingle("/api/get-attributes", { instancePath }, void 0, instance_id);
1756
3195
  return {
1757
3196
  content: [
1758
3197
  {
@@ -1762,11 +3201,11 @@ ${code}`
1762
3201
  ]
1763
3202
  };
1764
3203
  }
1765
- async deleteAttribute(instancePath, attributeName) {
3204
+ async deleteAttribute(instancePath, attributeName, instance_id) {
1766
3205
  if (!instancePath || !attributeName) {
1767
3206
  throw new Error("Instance path and attribute name are required for delete_attribute");
1768
3207
  }
1769
- const response = await this.client.request("/api/delete-attribute", { instancePath, attributeName });
3208
+ const response = await this._callSingle("/api/delete-attribute", { instancePath, attributeName }, void 0, instance_id);
1770
3209
  return {
1771
3210
  content: [
1772
3211
  {
@@ -1776,11 +3215,11 @@ ${code}`
1776
3215
  ]
1777
3216
  };
1778
3217
  }
1779
- async getTags(instancePath) {
3218
+ async getTags(instancePath, instance_id) {
1780
3219
  if (!instancePath) {
1781
3220
  throw new Error("Instance path is required for get_tags");
1782
3221
  }
1783
- const response = await this.client.request("/api/get-tags", { instancePath });
3222
+ const response = await this._callSingle("/api/get-tags", { instancePath }, void 0, instance_id);
1784
3223
  return {
1785
3224
  content: [
1786
3225
  {
@@ -1790,11 +3229,11 @@ ${code}`
1790
3229
  ]
1791
3230
  };
1792
3231
  }
1793
- async addTag(instancePath, tagName) {
3232
+ async addTag(instancePath, tagName, instance_id) {
1794
3233
  if (!instancePath || !tagName) {
1795
3234
  throw new Error("Instance path and tag name are required for add_tag");
1796
3235
  }
1797
- const response = await this.client.request("/api/add-tag", { instancePath, tagName });
3236
+ const response = await this._callSingle("/api/add-tag", { instancePath, tagName }, void 0, instance_id);
1798
3237
  return {
1799
3238
  content: [
1800
3239
  {
@@ -1804,11 +3243,11 @@ ${code}`
1804
3243
  ]
1805
3244
  };
1806
3245
  }
1807
- async removeTag(instancePath, tagName) {
3246
+ async removeTag(instancePath, tagName, instance_id) {
1808
3247
  if (!instancePath || !tagName) {
1809
3248
  throw new Error("Instance path and tag name are required for remove_tag");
1810
3249
  }
1811
- const response = await this.client.request("/api/remove-tag", { instancePath, tagName });
3250
+ const response = await this._callSingle("/api/remove-tag", { instancePath, tagName }, void 0, instance_id);
1812
3251
  return {
1813
3252
  content: [
1814
3253
  {
@@ -1818,11 +3257,11 @@ ${code}`
1818
3257
  ]
1819
3258
  };
1820
3259
  }
1821
- async getTagged(tagName) {
3260
+ async getTagged(tagName, instance_id) {
1822
3261
  if (!tagName) {
1823
3262
  throw new Error("Tag name is required for get_tagged");
1824
3263
  }
1825
- const response = await this.client.request("/api/get-tagged", { tagName });
3264
+ const response = await this._callSingle("/api/get-tagged", { tagName }, void 0, instance_id);
1826
3265
  return {
1827
3266
  content: [
1828
3267
  {
@@ -1832,8 +3271,8 @@ ${code}`
1832
3271
  ]
1833
3272
  };
1834
3273
  }
1835
- async getSelection() {
1836
- const response = await this.client.request("/api/get-selection", {});
3274
+ async getSelection(instance_id) {
3275
+ const response = await this._callSingle("/api/get-selection", {}, void 0, instance_id);
1837
3276
  return {
1838
3277
  content: [
1839
3278
  {
@@ -1843,11 +3282,11 @@ ${code}`
1843
3282
  ]
1844
3283
  };
1845
3284
  }
1846
- async executeLuau(code, target) {
3285
+ async executeLuau(code, target, instance_id) {
1847
3286
  if (!code) {
1848
3287
  throw new Error("Code is required for execute_luau");
1849
3288
  }
1850
- const response = await this.client.request("/api/execute-luau", { code }, target || "edit");
3289
+ const response = await this._callSingle("/api/execute-luau", { code }, target || "edit", instance_id);
1851
3290
  return {
1852
3291
  content: [
1853
3292
  {
@@ -1857,17 +3296,17 @@ ${code}`
1857
3296
  ]
1858
3297
  };
1859
3298
  }
1860
- async evalServerRuntime(code) {
3299
+ async evalServerRuntime(code, instance_id) {
1861
3300
  if (!code) {
1862
3301
  throw new Error("Code is required for eval_server_runtime");
1863
3302
  }
1864
3303
  const wrapper = buildModuleScriptInvokeWrapper({
1865
3304
  service: "ServerScriptService",
1866
3305
  bridgeName: SERVER_LOCAL_NAME,
1867
- missingError: "ServerEvalBridge not installed. Bridges are auto-installed at start_playtest and removed at stop_playtest. Start a playtest before calling eval_server_runtime.",
3306
+ missingError: "ServerEvalBridge not found. The bridge runs inside the play DM, so a playtest must be running. The bridge installs automatically (including for manually-started playtests); if a playtest is running and you still see this, reconnect the plugin in the edit window so the bridge reinstalls, then start the playtest again.",
1868
3307
  userCode: code
1869
3308
  });
1870
- const response = await this.client.request("/api/execute-luau", { code: wrapper }, "server");
3309
+ const response = await this._callSingle("/api/execute-luau", { code: wrapper }, "server", instance_id);
1871
3310
  return {
1872
3311
  content: [
1873
3312
  {
@@ -1877,7 +3316,7 @@ ${code}`
1877
3316
  ]
1878
3317
  };
1879
3318
  }
1880
- async evalClientRuntime(code, target) {
3319
+ async evalClientRuntime(code, target, instance_id) {
1881
3320
  if (!code) {
1882
3321
  throw new Error("Code is required for eval_client_runtime");
1883
3322
  }
@@ -1888,10 +3327,10 @@ ${code}`
1888
3327
  const wrapper = buildModuleScriptInvokeWrapper({
1889
3328
  service: "ReplicatedStorage",
1890
3329
  bridgeName: CLIENT_LOCAL_NAME,
1891
- missingError: "ClientEvalBridge not installed. Bridges are auto-installed at start_playtest and removed at stop_playtest. Start a playtest before calling eval_client_runtime.",
3330
+ missingError: "ClientEvalBridge not found. The bridge runs inside the play DM, so a playtest must be running. The bridge installs automatically (including for manually-started playtests); if a playtest is running and you still see this, reconnect the plugin in the edit window so the bridge reinstalls, then start the playtest again.",
1892
3331
  userCode: code
1893
3332
  });
1894
- const response = await this.client.request("/api/execute-luau", { code: wrapper }, clientTarget);
3333
+ const response = await this._callSingle("/api/execute-luau", { code: wrapper }, clientTarget, instance_id);
1895
3334
  return {
1896
3335
  content: [
1897
3336
  {
@@ -1901,7 +3340,7 @@ ${code}`
1901
3340
  ]
1902
3341
  };
1903
3342
  }
1904
- async getRuntimeLogs(target, since, tail, filter) {
3343
+ async getRuntimeLogs(target, since, tail, filter, instance_id) {
1905
3344
  const tgt = target ?? "all";
1906
3345
  const data = {};
1907
3346
  if (since !== void 0)
@@ -1910,16 +3349,26 @@ ${code}`
1910
3349
  data.tail = tail;
1911
3350
  if (filter !== void 0)
1912
3351
  data.filter = filter;
1913
- if (tgt !== "all") {
1914
- const response = await this.client.request("/api/get-runtime-logs", data, tgt);
3352
+ const resolved = this.bridge.resolveTarget({ instance_id, target: tgt });
3353
+ if (!resolved.ok)
3354
+ throw new RoutingFailure(resolved.error);
3355
+ if (resolved.mode === "single") {
3356
+ const response = await this.client.request("/api/get-runtime-logs", data, resolved.targetInstanceId, resolved.targetRole);
3357
+ response.peer = resolved.targetRole;
3358
+ if (Array.isArray(response.entries)) {
3359
+ for (const e of response.entries) {
3360
+ if (e.peer !== void 0)
3361
+ e.peer = resolved.targetRole;
3362
+ }
3363
+ }
1915
3364
  return {
1916
3365
  content: [{ type: "text", text: JSON.stringify(response) }]
1917
3366
  };
1918
3367
  }
1919
- const targets = this.bridge.getInstances().filter((i) => i.role !== "edit-proxy").map((i) => i.role);
3368
+ const targets = resolved.targets.filter((t) => t.targetRole !== "edit-proxy");
1920
3369
  const responses = await Promise.allSettled(targets.map(async (t) => {
1921
- const r = await this.client.request("/api/get-runtime-logs", data, t);
1922
- return { ...r, peer: t };
3370
+ const r = await this.client.request("/api/get-runtime-logs", data, t.targetInstanceId, t.targetRole);
3371
+ return { ...r, peer: t.targetRole };
1923
3372
  }));
1924
3373
  const merged = [];
1925
3374
  const perPeerNextSince = {};
@@ -1965,7 +3414,7 @@ ${code}`
1965
3414
  content: [{ type: "text", text: JSON.stringify(body) }]
1966
3415
  };
1967
3416
  }
1968
- async startPlaytest(mode, numPlayers) {
3417
+ async startPlaytest(mode, numPlayers, instance_id) {
1969
3418
  if (mode !== "play" && mode !== "run") {
1970
3419
  throw new Error('mode must be "play" or "run"');
1971
3420
  }
@@ -1973,7 +3422,7 @@ ${code}`
1973
3422
  if (numPlayers !== void 0) {
1974
3423
  data.numPlayers = numPlayers;
1975
3424
  }
1976
- const response = await this.client.request("/api/start-playtest", data);
3425
+ const response = await this._callSingle("/api/start-playtest", data, void 0, instance_id);
1977
3426
  return {
1978
3427
  content: [
1979
3428
  {
@@ -1983,14 +3432,14 @@ ${code}`
1983
3432
  ]
1984
3433
  };
1985
3434
  }
1986
- async stopPlaytest() {
1987
- const response = await this.client.request("/api/stop-playtest", {}, "edit");
3435
+ async stopPlaytest(instance_id) {
3436
+ const response = await this._callSingle("/api/stop-playtest", {}, "edit", instance_id);
1988
3437
  return {
1989
3438
  content: [{ type: "text", text: JSON.stringify(response) }]
1990
3439
  };
1991
3440
  }
1992
- async getPlaytestOutput(target) {
1993
- const response = await this.client.request("/api/get-playtest-output", {}, target || "edit");
3441
+ async getPlaytestOutput(target, instance_id) {
3442
+ const response = await this._callSingle("/api/get-playtest-output", {}, target || "edit", instance_id);
1994
3443
  return {
1995
3444
  content: [
1996
3445
  {
@@ -2001,7 +3450,7 @@ ${code}`
2001
3450
  };
2002
3451
  }
2003
3452
  async getConnectedInstances() {
2004
- const instances = this.bridge.getInstances();
3453
+ const instances = this.bridge.getPublicInstances();
2005
3454
  return {
2006
3455
  content: [
2007
3456
  {
@@ -2011,8 +3460,8 @@ ${code}`
2011
3460
  ]
2012
3461
  };
2013
3462
  }
2014
- async undo() {
2015
- const response = await this.client.request("/api/undo", {});
3463
+ async undo(instance_id) {
3464
+ const response = await this._callSingle("/api/undo", {}, void 0, instance_id);
2016
3465
  return {
2017
3466
  content: [
2018
3467
  {
@@ -2022,8 +3471,8 @@ ${code}`
2022
3471
  ]
2023
3472
  };
2024
3473
  }
2025
- async redo() {
2026
- const response = await this.client.request("/api/redo", {});
3474
+ async redo(instance_id) {
3475
+ const response = await this._callSingle("/api/redo", {}, void 0, instance_id);
2027
3476
  return {
2028
3477
  content: [
2029
3478
  {
@@ -2109,15 +3558,15 @@ ${code}`
2109
3558
  _RobloxStudioTools._cachedLibraryPath = result;
2110
3559
  return result;
2111
3560
  }
2112
- async exportBuild(instancePath, outputId, style = "misc") {
3561
+ async exportBuild(instancePath, outputId, style = "misc", instance_id) {
2113
3562
  if (!instancePath) {
2114
3563
  throw new Error("Instance path is required for export_build");
2115
3564
  }
2116
- const response = await this.client.request("/api/export-build", {
3565
+ const response = await this._callSingle("/api/export-build", {
2117
3566
  instancePath,
2118
3567
  outputId,
2119
3568
  style
2120
- });
3569
+ }, void 0, instance_id);
2121
3570
  if (response && response.success && response.buildData) {
2122
3571
  const buildData = response.buildData;
2123
3572
  const buildId = buildData.id || `${style}/exported`;
@@ -2305,7 +3754,7 @@ ${code}`
2305
3754
  ]
2306
3755
  };
2307
3756
  }
2308
- async importBuild(buildData, targetPath, position) {
3757
+ async importBuild(buildData, targetPath, position, instance_id) {
2309
3758
  if (!buildData || !targetPath) {
2310
3759
  throw new Error("buildData (or library ID string) and targetPath are required for import_build");
2311
3760
  }
@@ -2325,11 +3774,11 @@ ${code}`
2325
3774
  } else {
2326
3775
  resolved = buildData;
2327
3776
  }
2328
- const response = await this.client.request("/api/import-build", {
3777
+ const response = await this._callSingle("/api/import-build", {
2329
3778
  buildData: resolved,
2330
3779
  targetPath,
2331
3780
  position
2332
- });
3781
+ }, void 0, instance_id);
2333
3782
  return {
2334
3783
  content: [
2335
3784
  {
@@ -2371,11 +3820,11 @@ ${code}`
2371
3820
  ]
2372
3821
  };
2373
3822
  }
2374
- async searchMaterials(query, maxResults) {
2375
- const response = await this.client.request("/api/search-materials", {
3823
+ async searchMaterials(query, maxResults, instance_id) {
3824
+ const response = await this._callSingle("/api/search-materials", {
2376
3825
  query: query ?? "",
2377
3826
  maxResults: maxResults ?? 50
2378
- });
3827
+ }, void 0, instance_id);
2379
3828
  return {
2380
3829
  content: [
2381
3830
  {
@@ -2415,7 +3864,7 @@ ${code}`
2415
3864
  ]
2416
3865
  };
2417
3866
  }
2418
- async importScene(sceneData, targetPath = "game.Workspace") {
3867
+ async importScene(sceneData, targetPath = "game.Workspace", instance_id) {
2419
3868
  if (!sceneData) {
2420
3869
  throw new Error("sceneData is required for import_scene");
2421
3870
  }
@@ -2505,10 +3954,10 @@ ${code}`
2505
3954
  if (expandedBuilds.length === 0) {
2506
3955
  throw new Error("No builds to import - check model references and library");
2507
3956
  }
2508
- const response = await this.client.request("/api/import-scene", {
3957
+ const response = await this._callSingle("/api/import-scene", {
2509
3958
  expandedBuilds,
2510
3959
  targetPath
2511
- });
3960
+ }, void 0, instance_id);
2512
3961
  return {
2513
3962
  content: [
2514
3963
  {
@@ -2599,15 +4048,15 @@ ${code}`
2599
4048
  }]
2600
4049
  };
2601
4050
  }
2602
- async insertAsset(assetId, parentPath, position) {
4051
+ async insertAsset(assetId, parentPath, position, instance_id) {
2603
4052
  if (!assetId) {
2604
4053
  throw new Error("Asset ID is required for insert_asset");
2605
4054
  }
2606
- const response = await this.client.request("/api/insert-asset", {
4055
+ const response = await this._callSingle("/api/insert-asset", {
2607
4056
  assetId,
2608
4057
  parentPath: parentPath || "game.Workspace",
2609
4058
  position
2610
- });
4059
+ }, void 0, instance_id);
2611
4060
  return {
2612
4061
  content: [{
2613
4062
  type: "text",
@@ -2615,15 +4064,15 @@ ${code}`
2615
4064
  }]
2616
4065
  };
2617
4066
  }
2618
- async previewAsset(assetId, includeProperties, maxDepth) {
4067
+ async previewAsset(assetId, includeProperties, maxDepth, instance_id) {
2619
4068
  if (!assetId) {
2620
4069
  throw new Error("Asset ID is required for preview_asset");
2621
4070
  }
2622
- const response = await this.client.request("/api/preview-asset", {
4071
+ const response = await this._callSingle("/api/preview-asset", {
2623
4072
  assetId,
2624
4073
  includeProperties: includeProperties ?? true,
2625
4074
  maxDepth: maxDepth ?? 10
2626
- });
4075
+ }, void 0, instance_id);
2627
4076
  return {
2628
4077
  content: [{
2629
4078
  type: "text",
@@ -2645,7 +4094,7 @@ ${code}`
2645
4094
  return id
2646
4095
  `;
2647
4096
  try {
2648
- const response = await this.client.request("/api/execute-luau", { code }, "edit");
4097
+ const response = await this._callSingle("/api/execute-luau", { code }, "edit", void 0);
2649
4098
  const returnValue = response?.returnValue;
2650
4099
  if (returnValue !== void 0 && returnValue !== null && /^\d+$/.test(String(returnValue))) {
2651
4100
  return String(returnValue);
@@ -2720,17 +4169,17 @@ ${code}`
2720
4169
  }]
2721
4170
  };
2722
4171
  }
2723
- async simulateMouseInput(action, x, y, button, scrollDirection, target) {
4172
+ async simulateMouseInput(action, x, y, button, scrollDirection, target, instance_id) {
2724
4173
  if (!action) {
2725
4174
  throw new Error("action is required for simulate_mouse_input");
2726
4175
  }
2727
- const response = await this.client.request("/api/simulate-mouse-input", {
4176
+ const { instanceId, clientRole } = this._resolveRuntime(instance_id);
4177
+ const response = await this._callSingle("/api/simulate-mouse-input", {
2728
4178
  action,
2729
4179
  x,
2730
4180
  y,
2731
- button,
2732
- scrollDirection
2733
- }, target || "edit");
4181
+ button
4182
+ }, target || clientRole || "edit", instanceId);
2734
4183
  return {
2735
4184
  content: [{
2736
4185
  type: "text",
@@ -2738,15 +4187,17 @@ ${code}`
2738
4187
  }]
2739
4188
  };
2740
4189
  }
2741
- async simulateKeyboardInput(keyCode, action, duration, target) {
2742
- if (!keyCode) {
2743
- throw new Error("keyCode is required for simulate_keyboard_input");
4190
+ async simulateKeyboardInput(keyCode, action, duration, text, target, instance_id) {
4191
+ if (!keyCode && text === void 0) {
4192
+ throw new Error("keyCode or text is required for simulate_keyboard_input");
2744
4193
  }
2745
- const response = await this.client.request("/api/simulate-keyboard-input", {
4194
+ const { instanceId, clientRole } = this._resolveRuntime(instance_id);
4195
+ const response = await this._callSingle("/api/simulate-keyboard-input", {
2746
4196
  keyCode,
2747
4197
  action,
2748
- duration
2749
- }, target || "edit");
4198
+ duration,
4199
+ text
4200
+ }, target || clientRole || "edit", instanceId);
2750
4201
  return {
2751
4202
  content: [{
2752
4203
  type: "text",
@@ -2754,16 +4205,16 @@ ${code}`
2754
4205
  }]
2755
4206
  };
2756
4207
  }
2757
- async characterNavigation(position, instancePath, waitForCompletion, timeout, target) {
4208
+ async characterNavigation(position, instancePath, waitForCompletion, timeout, target, instance_id) {
2758
4209
  if (!position && !instancePath) {
2759
4210
  throw new Error("Either position or instancePath is required for character_navigation");
2760
4211
  }
2761
- const response = await this.client.request("/api/character-navigation", {
4212
+ const response = await this._callSingle("/api/character-navigation", {
2762
4213
  position,
2763
4214
  instancePath,
2764
4215
  waitForCompletion,
2765
4216
  timeout
2766
- }, target || "edit");
4217
+ }, target || "edit", instance_id);
2767
4218
  return {
2768
4219
  content: [{
2769
4220
  type: "text",
@@ -2771,50 +4222,50 @@ ${code}`
2771
4222
  }]
2772
4223
  };
2773
4224
  }
2774
- async cloneObject(instancePath, targetParentPath) {
4225
+ async cloneObject(instancePath, targetParentPath, instance_id) {
2775
4226
  if (!instancePath || !targetParentPath) {
2776
4227
  throw new Error("instancePath and targetParentPath are required for clone_object");
2777
4228
  }
2778
- const response = await this.client.request("/api/clone-object", { instancePath, targetParentPath });
4229
+ const response = await this._callSingle("/api/clone-object", { instancePath, targetParentPath }, void 0, instance_id);
2779
4230
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2780
4231
  }
2781
- async getDescendants(instancePath, maxDepth, classFilter) {
4232
+ async getDescendants(instancePath, maxDepth, classFilter, instance_id) {
2782
4233
  if (!instancePath) {
2783
4234
  throw new Error("instancePath is required for get_descendants");
2784
4235
  }
2785
- const response = await this.client.request("/api/get-descendants", { instancePath, maxDepth, classFilter });
4236
+ const response = await this._callSingle("/api/get-descendants", { instancePath, maxDepth, classFilter }, void 0, instance_id);
2786
4237
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2787
4238
  }
2788
- async compareInstances(instancePathA, instancePathB) {
4239
+ async compareInstances(instancePathA, instancePathB, instance_id) {
2789
4240
  if (!instancePathA || !instancePathB) {
2790
4241
  throw new Error("instancePathA and instancePathB are required for compare_instances");
2791
4242
  }
2792
- const response = await this.client.request("/api/compare-instances", { instancePathA, instancePathB });
4243
+ const response = await this._callSingle("/api/compare-instances", { instancePathA, instancePathB }, void 0, instance_id);
2793
4244
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2794
4245
  }
2795
- async getOutputLog(maxEntries, messageType) {
2796
- const response = await this.client.request("/api/get-output-log", { maxEntries, messageType });
4246
+ async getOutputLog(maxEntries, messageType, instance_id) {
4247
+ const response = await this._callSingle("/api/get-output-log", { maxEntries, messageType }, void 0, instance_id);
2797
4248
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2798
4249
  }
2799
- async bulkSetAttributes(instancePath, attributes) {
4250
+ async bulkSetAttributes(instancePath, attributes, instance_id) {
2800
4251
  if (!instancePath || !attributes) {
2801
4252
  throw new Error("instancePath and attributes are required for bulk_set_attributes");
2802
4253
  }
2803
- const response = await this.client.request("/api/bulk-set-attributes", { instancePath, attributes });
4254
+ const response = await this._callSingle("/api/bulk-set-attributes", { instancePath, attributes }, void 0, instance_id);
2804
4255
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2805
4256
  }
2806
- async findAndReplaceInScripts(pattern, replacement, options) {
4257
+ async findAndReplaceInScripts(pattern, replacement, options, instance_id) {
2807
4258
  if (!pattern) {
2808
4259
  throw new Error("pattern is required for find_and_replace_in_scripts");
2809
4260
  }
2810
4261
  if (replacement === void 0 || replacement === null) {
2811
4262
  throw new Error("replacement is required for find_and_replace_in_scripts");
2812
4263
  }
2813
- const response = await this.client.request("/api/find-and-replace-in-scripts", {
4264
+ const response = await this._callSingle("/api/find-and-replace-in-scripts", {
2814
4265
  pattern,
2815
4266
  replacement,
2816
4267
  ...options
2817
- });
4268
+ }, void 0, instance_id);
2818
4269
  return {
2819
4270
  content: [{
2820
4271
  type: "text",
@@ -2822,24 +4273,27 @@ ${code}`
2822
4273
  }]
2823
4274
  };
2824
4275
  }
2825
- async getMemoryBreakdown(target, tags) {
4276
+ async getMemoryBreakdown(target, tags, instance_id) {
2826
4277
  const tgt = target ?? "all";
2827
4278
  const data = {};
2828
4279
  if (tags !== void 0)
2829
4280
  data.tags = tags;
2830
- if (tgt !== "all") {
2831
- const response = await this.client.request("/api/get-memory-breakdown", data, tgt);
4281
+ const resolved = this.bridge.resolveTarget({ instance_id, target: tgt });
4282
+ if (!resolved.ok)
4283
+ throw new RoutingFailure(resolved.error);
4284
+ if (resolved.mode === "single") {
4285
+ const response = await this.client.request("/api/get-memory-breakdown", data, resolved.targetInstanceId, resolved.targetRole);
2832
4286
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2833
4287
  }
2834
- const targets = this.bridge.getInstances().filter((i) => i.role !== "edit-proxy").map((i) => i.role);
4288
+ const targets = resolved.targets.filter((t) => t.targetRole !== "edit-proxy");
2835
4289
  const responses = await Promise.allSettled(targets.map(async (t) => ({
2836
- peer: t,
2837
- result: await this.client.request("/api/get-memory-breakdown", data, t)
4290
+ peer: t.targetRole,
4291
+ result: await this.client.request("/api/get-memory-breakdown", data, t.targetInstanceId, t.targetRole)
2838
4292
  })));
2839
4293
  const body = {};
2840
4294
  for (let i = 0; i < responses.length; i++) {
2841
4295
  const r = responses[i];
2842
- const peer = targets[i];
4296
+ const peer = targets[i].targetRole;
2843
4297
  if (r.status === "fulfilled") {
2844
4298
  body[peer] = r.value.result;
2845
4299
  } else {
@@ -2848,7 +4302,7 @@ ${code}`
2848
4302
  }
2849
4303
  return { content: [{ type: "text", text: JSON.stringify(body) }] };
2850
4304
  }
2851
- async exportRbxm(instancePaths, outputPath, target) {
4305
+ async exportRbxm(instancePaths, outputPath, target, instance_id) {
2852
4306
  if (!Array.isArray(instancePaths) || instancePaths.length === 0) {
2853
4307
  throw new Error("instance_paths must be a non-empty array for export_rbxm");
2854
4308
  }
@@ -2859,7 +4313,7 @@ ${code}`
2859
4313
  if (tgt !== "edit" && tgt !== "server") {
2860
4314
  throw new Error(`export_rbxm target must be "edit" or "server" (got: ${tgt})`);
2861
4315
  }
2862
- const response = await this.client.request("/api/export-rbxm", { instance_paths: instancePaths }, tgt);
4316
+ const response = await this._callSingle("/api/export-rbxm", { instance_paths: instancePaths }, tgt, instance_id);
2863
4317
  if (response.error) {
2864
4318
  return { content: [{ type: "text", text: JSON.stringify({ error: response.error }) }] };
2865
4319
  }
@@ -2885,7 +4339,7 @@ ${code}`
2885
4339
  }]
2886
4340
  };
2887
4341
  }
2888
- async importRbxm(source, parentPath, target) {
4342
+ async importRbxm(source, parentPath, target, instance_id) {
2889
4343
  if (!source || typeof source !== "object") {
2890
4344
  throw new Error("source is required for import_rbxm");
2891
4345
  }
@@ -2948,15 +4402,28 @@ ${code}`
2948
4402
  }
2949
4403
  sourceLabel = `base64(${bytes.length}B)`;
2950
4404
  }
2951
- const response = await this.client.request("/api/import-rbxm", {
4405
+ const response = await this._callSingle("/api/import-rbxm", {
2952
4406
  base64: bytes.toString("base64"),
2953
4407
  parent_path: parentPath,
2954
4408
  source_label: sourceLabel
2955
- }, tgt);
4409
+ }, tgt, instance_id);
2956
4410
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2957
4411
  }
2958
- async captureScreenshot() {
2959
- const response = await this.client.request("/api/capture-screenshot", {});
4412
+ async captureScreenshot(instance_id, format, quality) {
4413
+ const { instanceId, clientRole } = this._resolveRuntime(instance_id);
4414
+ let response;
4415
+ if (clientRole) {
4416
+ const begin = await this._callSingle("/api/capture-begin", {}, clientRole, instanceId);
4417
+ if (begin.error) {
4418
+ return { content: [{ type: "text", text: begin.error }] };
4419
+ }
4420
+ if (!begin.contentId) {
4421
+ return { content: [{ type: "text", text: "Screenshot capture failed: no content id returned from client." }] };
4422
+ }
4423
+ response = await this._callSingle("/api/capture-read", { contentId: begin.contentId }, "edit", instanceId);
4424
+ } else {
4425
+ response = await this._callSingle("/api/capture-screenshot", {}, "edit", instanceId);
4426
+ }
2960
4427
  if (response.error) {
2961
4428
  return {
2962
4429
  content: [{
@@ -2965,158 +4432,46 @@ ${code}`
2965
4432
  }]
2966
4433
  };
2967
4434
  }
2968
- const pngBuffer = encodePngFromRgbaResponse(response);
2969
- return {
2970
- content: [{
2971
- type: "image",
2972
- data: pngBuffer.toString("base64"),
2973
- mimeType: "image/png"
2974
- }]
2975
- };
2976
- }
2977
- };
2978
- }
2979
- });
2980
-
2981
- // ../core/dist/bridge-service.js
2982
- import { v4 as uuidv4 } from "uuid";
2983
- var STALE_INSTANCE_MS, BridgeService;
2984
- var init_bridge_service = __esm({
2985
- "../core/dist/bridge-service.js"() {
2986
- "use strict";
2987
- STALE_INSTANCE_MS = 3e4;
2988
- BridgeService = class {
2989
- pendingRequests = /* @__PURE__ */ new Map();
2990
- instances = /* @__PURE__ */ new Map();
2991
- requestTimeout = 3e4;
2992
- registerInstance(instanceId, role) {
2993
- let assignedRole = role;
2994
- if (role === "client") {
2995
- const used = /* @__PURE__ */ new Set();
2996
- for (const inst of this.instances.values()) {
2997
- const match = inst.role.match(/^client-(\d+)$/);
2998
- if (match)
2999
- used.add(Number(match[1]));
4435
+ const w = response.width;
4436
+ const h = response.height;
4437
+ if (w === void 0 || h === void 0) {
4438
+ return { content: [{ type: "text", text: "Screenshot response missing dimensions." }] };
4439
+ }
4440
+ const fmt = format === "png" ? "png" : "jpeg";
4441
+ const q = quality === void 0 ? 92 : Math.max(1, Math.min(100, Math.floor(quality)));
4442
+ const MAX_IMAGE_BYTES = 6e6;
4443
+ let { buffer, mimeType } = encodeImageFromRgbaResponse(response, fmt, q);
4444
+ let usedQ = q;
4445
+ let note = "";
4446
+ if (buffer.length > MAX_IMAGE_BYTES) {
4447
+ if (fmt === "png") {
4448
+ const mb = (buffer.length / 1048576).toFixed(1);
4449
+ return {
4450
+ content: [{
4451
+ type: "text",
4452
+ text: `PNG screenshot is ${mb}MB, over the ~${(MAX_IMAGE_BYTES / 1048576).toFixed(0)}MB inline image limit. Use the default jpeg format (optionally with a "quality" value) or make the Studio window smaller for a lossless capture.`
4453
+ }]
4454
+ };
3000
4455
  }
3001
- let idx = 1;
3002
- while (used.has(idx))
3003
- idx++;
3004
- assignedRole = `client-${idx}`;
3005
- }
3006
- this.instances.set(instanceId, {
3007
- instanceId,
3008
- role: assignedRole,
3009
- lastActivity: Date.now(),
3010
- connectedAt: Date.now()
3011
- });
3012
- return assignedRole;
3013
- }
3014
- unregisterInstance(instanceId) {
3015
- this.instances.delete(instanceId);
3016
- for (const [id, req] of this.pendingRequests.entries()) {
3017
- const targetRole = req.target;
3018
- const hasHandler = Array.from(this.instances.values()).some((i) => i.role === targetRole);
3019
- if (!hasHandler) {
3020
- clearTimeout(req.timeoutId);
3021
- this.pendingRequests.delete(id);
3022
- req.reject(new Error(`Target instance "${targetRole}" disconnected`));
4456
+ while (buffer.length > MAX_IMAGE_BYTES && usedQ > 25) {
4457
+ usedQ = Math.max(25, usedQ - 20);
4458
+ buffer = encodeImageFromRgbaResponse(response, "jpeg", usedQ).buffer;
3023
4459
  }
4460
+ note = ` \u2014 auto-reduced to q${usedQ} to fit the inline size limit; enlarge the Studio window or capture a smaller region for finer detail`;
3024
4461
  }
3025
- }
3026
- getInstances() {
3027
- return Array.from(this.instances.values());
3028
- }
3029
- getPendingRequestCount() {
3030
- return this.pendingRequests.size;
3031
- }
3032
- updateInstanceActivity(instanceId) {
3033
- const inst = this.instances.get(instanceId);
3034
- if (inst) {
3035
- inst.lastActivity = Date.now();
3036
- }
3037
- }
3038
- cleanupStaleInstances() {
3039
- const now = Date.now();
3040
- for (const [id, inst] of this.instances.entries()) {
3041
- if (now - inst.lastActivity > STALE_INSTANCE_MS) {
3042
- this.unregisterInstance(id);
3043
- }
3044
- }
3045
- }
3046
- async sendRequest(endpoint, data, target = "edit") {
3047
- const requestId = uuidv4();
3048
- return new Promise((resolve2, reject) => {
3049
- const timeoutId = setTimeout(() => {
3050
- if (this.pendingRequests.has(requestId)) {
3051
- this.pendingRequests.delete(requestId);
3052
- reject(new Error("Request timeout"));
3053
- }
3054
- }, this.requestTimeout);
3055
- const request = {
3056
- id: requestId,
3057
- endpoint,
3058
- data,
3059
- target,
3060
- timestamp: Date.now(),
3061
- resolve: resolve2,
3062
- reject,
3063
- timeoutId
3064
- };
3065
- this.pendingRequests.set(requestId, request);
3066
- });
3067
- }
3068
- getPendingRequest(callerRole = "edit") {
3069
- let oldestRequest = null;
3070
- for (const request of this.pendingRequests.values()) {
3071
- if (request.target !== callerRole)
3072
- continue;
3073
- if (!oldestRequest || request.timestamp < oldestRequest.timestamp) {
3074
- oldestRequest = request;
3075
- }
3076
- }
3077
- if (oldestRequest) {
3078
- return {
3079
- requestId: oldestRequest.id,
3080
- request: {
3081
- endpoint: oldestRequest.endpoint,
3082
- data: oldestRequest.data
4462
+ return {
4463
+ content: [
4464
+ {
4465
+ type: "text",
4466
+ text: `Screenshot ${w}x${h}px (${fmt}${fmt === "jpeg" ? ` q${usedQ}` : ""})${note}. For simulate_mouse_input, x/y are pixel coordinates in this exact image with (0,0) at the top-left; it is not downscaled, so use coordinates as you read them off the image.`
4467
+ },
4468
+ {
4469
+ type: "image",
4470
+ data: buffer.toString("base64"),
4471
+ mimeType
3083
4472
  }
3084
- };
3085
- }
3086
- return null;
3087
- }
3088
- resolveRequest(requestId, response) {
3089
- const request = this.pendingRequests.get(requestId);
3090
- if (request) {
3091
- clearTimeout(request.timeoutId);
3092
- this.pendingRequests.delete(requestId);
3093
- request.resolve(response);
3094
- }
3095
- }
3096
- rejectRequest(requestId, error) {
3097
- const request = this.pendingRequests.get(requestId);
3098
- if (request) {
3099
- clearTimeout(request.timeoutId);
3100
- this.pendingRequests.delete(requestId);
3101
- request.reject(error);
3102
- }
3103
- }
3104
- cleanupOldRequests() {
3105
- const now = Date.now();
3106
- for (const [id, request] of this.pendingRequests.entries()) {
3107
- if (now - request.timestamp > this.requestTimeout) {
3108
- clearTimeout(request.timeoutId);
3109
- this.pendingRequests.delete(id);
3110
- request.reject(new Error("Request timeout"));
3111
- }
3112
- }
3113
- }
3114
- clearAllPendingRequests() {
3115
- for (const [, request] of this.pendingRequests.entries()) {
3116
- clearTimeout(request.timeoutId);
3117
- request.reject(new Error("Connection closed"));
3118
- }
3119
- this.pendingRequests.clear();
4473
+ ]
4474
+ };
3120
4475
  }
3121
4476
  };
3122
4477
  }
@@ -3166,14 +4521,20 @@ var init_proxy_bridge_service = __esm({
3166
4521
  this.refreshTimer = void 0;
3167
4522
  }
3168
4523
  }
3169
- async sendRequest(endpoint, data, target = "edit") {
4524
+ async sendRequest(endpoint, data, targetInstanceId, targetRole) {
3170
4525
  const controller = new AbortController();
3171
4526
  const timeoutId = setTimeout(() => controller.abort(), this.proxyRequestTimeout);
3172
4527
  try {
3173
4528
  const response = await fetch(`${this.primaryBaseUrl}/proxy`, {
3174
4529
  method: "POST",
3175
4530
  headers: { "Content-Type": "application/json" },
3176
- body: JSON.stringify({ endpoint, data, target, proxyInstanceId: this.proxyInstanceId }),
4531
+ body: JSON.stringify({
4532
+ endpoint,
4533
+ data,
4534
+ targetInstanceId,
4535
+ targetRole,
4536
+ proxyInstanceId: this.proxyInstanceId
4537
+ }),
3177
4538
  signal: controller.signal
3178
4539
  });
3179
4540
  clearTimeout(timeoutId);
@@ -3257,6 +4618,19 @@ var init_server = __esm({
3257
4618
  try {
3258
4619
  return await handler(this.tools, args ?? {});
3259
4620
  } catch (error) {
4621
+ if (error instanceof RoutingFailure) {
4622
+ return {
4623
+ content: [{
4624
+ type: "text",
4625
+ text: JSON.stringify({
4626
+ error: error.routingError.code,
4627
+ message: error.routingError.message,
4628
+ data: error.routingError.data
4629
+ })
4630
+ }],
4631
+ isError: true
4632
+ };
4633
+ }
3260
4634
  if (error instanceof McpError2)
3261
4635
  throw error;
3262
4636
  throw new McpError2(ErrorCode2.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -3398,6 +4772,10 @@ var init_definitions = __esm({
3398
4772
  path: {
3399
4773
  type: "string",
3400
4774
  description: "Root path (default: game root)"
4775
+ },
4776
+ instance_id: {
4777
+ type: "string",
4778
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3401
4779
  }
3402
4780
  }
3403
4781
  }
@@ -3417,6 +4795,10 @@ var init_definitions = __esm({
3417
4795
  type: "string",
3418
4796
  enum: ["name", "type", "content"],
3419
4797
  description: "Search mode (default: name)"
4798
+ },
4799
+ instance_id: {
4800
+ type: "string",
4801
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3420
4802
  }
3421
4803
  },
3422
4804
  required: ["query"]
@@ -3429,7 +4811,12 @@ var init_definitions = __esm({
3429
4811
  description: "Get place ID, name, and game settings",
3430
4812
  inputSchema: {
3431
4813
  type: "object",
3432
- properties: {}
4814
+ properties: {
4815
+ instance_id: {
4816
+ type: "string",
4817
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4818
+ }
4819
+ }
3433
4820
  }
3434
4821
  },
3435
4822
  {
@@ -3442,6 +4829,10 @@ var init_definitions = __esm({
3442
4829
  serviceName: {
3443
4830
  type: "string",
3444
4831
  description: "Specific service name"
4832
+ },
4833
+ instance_id: {
4834
+ type: "string",
4835
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3445
4836
  }
3446
4837
  }
3447
4838
  }
@@ -3465,6 +4856,10 @@ var init_definitions = __esm({
3465
4856
  propertyName: {
3466
4857
  type: "string",
3467
4858
  description: 'Property name when searchType is "property"'
4859
+ },
4860
+ instance_id: {
4861
+ type: "string",
4862
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3468
4863
  }
3469
4864
  },
3470
4865
  required: ["query"]
@@ -3485,6 +4880,10 @@ var init_definitions = __esm({
3485
4880
  excludeSource: {
3486
4881
  type: "boolean",
3487
4882
  description: "For scripts, return SourceLength/LineCount instead of full source (default: false)"
4883
+ },
4884
+ instance_id: {
4885
+ type: "string",
4886
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3488
4887
  }
3489
4888
  },
3490
4889
  required: ["instancePath"]
@@ -3500,6 +4899,10 @@ var init_definitions = __esm({
3500
4899
  instancePath: {
3501
4900
  type: "string",
3502
4901
  description: "Instance path (dot notation)"
4902
+ },
4903
+ instance_id: {
4904
+ type: "string",
4905
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3503
4906
  }
3504
4907
  },
3505
4908
  required: ["instancePath"]
@@ -3519,6 +4922,10 @@ var init_definitions = __esm({
3519
4922
  propertyValue: {
3520
4923
  type: "string",
3521
4924
  description: "Value to match"
4925
+ },
4926
+ instance_id: {
4927
+ type: "string",
4928
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3522
4929
  }
3523
4930
  },
3524
4931
  required: ["propertyName", "propertyValue"]
@@ -3534,6 +4941,10 @@ var init_definitions = __esm({
3534
4941
  className: {
3535
4942
  type: "string",
3536
4943
  description: "Roblox class name"
4944
+ },
4945
+ instance_id: {
4946
+ type: "string",
4947
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3537
4948
  }
3538
4949
  },
3539
4950
  required: ["className"]
@@ -3558,6 +4969,10 @@ var init_definitions = __esm({
3558
4969
  scriptsOnly: {
3559
4970
  type: "boolean",
3560
4971
  description: "Show only scripts (default: false)"
4972
+ },
4973
+ instance_id: {
4974
+ type: "string",
4975
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3561
4976
  }
3562
4977
  }
3563
4978
  }
@@ -3580,6 +4995,10 @@ var init_definitions = __esm({
3580
4995
  },
3581
4996
  propertyValue: {
3582
4997
  description: "Value to set (string, number, boolean, or object for Vector3/Color3/UDim2)"
4998
+ },
4999
+ instance_id: {
5000
+ type: "string",
5001
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3583
5002
  }
3584
5003
  },
3585
5004
  required: ["instancePath", "propertyName", "propertyValue"]
@@ -3603,6 +5022,10 @@ var init_definitions = __esm({
3603
5022
  },
3604
5023
  propertyValue: {
3605
5024
  description: "Value to set (string, number, boolean, or object for Vector3/Color3/UDim2)"
5025
+ },
5026
+ instance_id: {
5027
+ type: "string",
5028
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3606
5029
  }
3607
5030
  },
3608
5031
  required: ["paths", "propertyName", "propertyValue"]
@@ -3623,6 +5046,10 @@ var init_definitions = __esm({
3623
5046
  propertyName: {
3624
5047
  type: "string",
3625
5048
  description: "Property name"
5049
+ },
5050
+ instance_id: {
5051
+ type: "string",
5052
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3626
5053
  }
3627
5054
  },
3628
5055
  required: ["paths", "propertyName"]
@@ -3642,6 +5069,10 @@ var init_definitions = __esm({
3642
5069
  properties: {
3643
5070
  type: "object",
3644
5071
  description: "Map of property name to value"
5072
+ },
5073
+ instance_id: {
5074
+ type: "string",
5075
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3645
5076
  }
3646
5077
  },
3647
5078
  required: ["instancePath", "properties"]
@@ -3670,6 +5101,10 @@ var init_definitions = __esm({
3670
5101
  properties: {
3671
5102
  type: "object",
3672
5103
  description: "Properties to set on creation"
5104
+ },
5105
+ instance_id: {
5106
+ type: "string",
5107
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3673
5108
  }
3674
5109
  },
3675
5110
  required: ["className", "parent"]
@@ -3707,6 +5142,10 @@ var init_definitions = __esm({
3707
5142
  required: ["className", "parent"]
3708
5143
  },
3709
5144
  description: "Objects to create"
5145
+ },
5146
+ instance_id: {
5147
+ type: "string",
5148
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3710
5149
  }
3711
5150
  },
3712
5151
  required: ["objects"]
@@ -3722,6 +5161,10 @@ var init_definitions = __esm({
3722
5161
  instancePath: {
3723
5162
  type: "string",
3724
5163
  description: "Instance path (dot notation)"
5164
+ },
5165
+ instance_id: {
5166
+ type: "string",
5167
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3725
5168
  }
3726
5169
  },
3727
5170
  required: ["instancePath"]
@@ -3775,6 +5218,10 @@ var init_definitions = __esm({
3775
5218
  description: "Different parent per duplicate"
3776
5219
  }
3777
5220
  }
5221
+ },
5222
+ instance_id: {
5223
+ type: "string",
5224
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3778
5225
  }
3779
5226
  },
3780
5227
  required: ["instancePath", "count"]
@@ -3837,6 +5284,10 @@ var init_definitions = __esm({
3837
5284
  required: ["instancePath", "count"]
3838
5285
  },
3839
5286
  description: "Duplication operations"
5287
+ },
5288
+ instance_id: {
5289
+ type: "string",
5290
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3840
5291
  }
3841
5292
  },
3842
5293
  required: ["duplications"]
@@ -3862,6 +5313,10 @@ var init_definitions = __esm({
3862
5313
  endLine: {
3863
5314
  type: "number",
3864
5315
  description: "End line (inclusive)"
5316
+ },
5317
+ instance_id: {
5318
+ type: "string",
5319
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3865
5320
  }
3866
5321
  },
3867
5322
  required: ["instancePath"]
@@ -3881,6 +5336,10 @@ var init_definitions = __esm({
3881
5336
  source: {
3882
5337
  type: "string",
3883
5338
  description: "New source code"
5339
+ },
5340
+ instance_id: {
5341
+ type: "string",
5342
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3884
5343
  }
3885
5344
  },
3886
5345
  required: ["instancePath", "source"]
@@ -3908,6 +5367,10 @@ var init_definitions = __esm({
3908
5367
  startLine: {
3909
5368
  type: "number",
3910
5369
  description: "Optional 1-indexed line where old_string begins. When provided, skips uniqueness check and requires old_string to match starting at that exact line."
5370
+ },
5371
+ instance_id: {
5372
+ type: "string",
5373
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3911
5374
  }
3912
5375
  },
3913
5376
  required: ["instancePath", "old_string", "new_string"]
@@ -3931,6 +5394,10 @@ var init_definitions = __esm({
3931
5394
  newContent: {
3932
5395
  type: "string",
3933
5396
  description: "Content to insert"
5397
+ },
5398
+ instance_id: {
5399
+ type: "string",
5400
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3934
5401
  }
3935
5402
  },
3936
5403
  required: ["instancePath", "newContent"]
@@ -3954,6 +5421,10 @@ var init_definitions = __esm({
3954
5421
  endLine: {
3955
5422
  type: "number",
3956
5423
  description: "End line (inclusive)"
5424
+ },
5425
+ instance_id: {
5426
+ type: "string",
5427
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3957
5428
  }
3958
5429
  },
3959
5430
  required: ["instancePath", "startLine", "endLine"]
@@ -3981,6 +5452,10 @@ var init_definitions = __esm({
3981
5452
  valueType: {
3982
5453
  type: "string",
3983
5454
  description: "Type hint if needed"
5455
+ },
5456
+ instance_id: {
5457
+ type: "string",
5458
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3984
5459
  }
3985
5460
  },
3986
5461
  required: ["instancePath", "attributeName", "attributeValue"]
@@ -3996,6 +5471,10 @@ var init_definitions = __esm({
3996
5471
  instancePath: {
3997
5472
  type: "string",
3998
5473
  description: "Instance path (dot notation)"
5474
+ },
5475
+ instance_id: {
5476
+ type: "string",
5477
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3999
5478
  }
4000
5479
  },
4001
5480
  required: ["instancePath"]
@@ -4015,6 +5494,10 @@ var init_definitions = __esm({
4015
5494
  attributeName: {
4016
5495
  type: "string",
4017
5496
  description: "Attribute name"
5497
+ },
5498
+ instance_id: {
5499
+ type: "string",
5500
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4018
5501
  }
4019
5502
  },
4020
5503
  required: ["instancePath", "attributeName"]
@@ -4031,6 +5514,10 @@ var init_definitions = __esm({
4031
5514
  instancePath: {
4032
5515
  type: "string",
4033
5516
  description: "Instance path (dot notation)"
5517
+ },
5518
+ instance_id: {
5519
+ type: "string",
5520
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4034
5521
  }
4035
5522
  },
4036
5523
  required: ["instancePath"]
@@ -4050,6 +5537,10 @@ var init_definitions = __esm({
4050
5537
  tagName: {
4051
5538
  type: "string",
4052
5539
  description: "Tag name"
5540
+ },
5541
+ instance_id: {
5542
+ type: "string",
5543
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4053
5544
  }
4054
5545
  },
4055
5546
  required: ["instancePath", "tagName"]
@@ -4069,6 +5560,10 @@ var init_definitions = __esm({
4069
5560
  tagName: {
4070
5561
  type: "string",
4071
5562
  description: "Tag name"
5563
+ },
5564
+ instance_id: {
5565
+ type: "string",
5566
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4072
5567
  }
4073
5568
  },
4074
5569
  required: ["instancePath", "tagName"]
@@ -4084,6 +5579,10 @@ var init_definitions = __esm({
4084
5579
  tagName: {
4085
5580
  type: "string",
4086
5581
  description: "Tag name"
5582
+ },
5583
+ instance_id: {
5584
+ type: "string",
5585
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4087
5586
  }
4088
5587
  },
4089
5588
  required: ["tagName"]
@@ -4096,7 +5595,12 @@ var init_definitions = __esm({
4096
5595
  description: "Get all currently selected objects",
4097
5596
  inputSchema: {
4098
5597
  type: "object",
4099
- properties: {}
5598
+ properties: {
5599
+ instance_id: {
5600
+ type: "string",
5601
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
5602
+ }
5603
+ }
4100
5604
  }
4101
5605
  },
4102
5606
  // === Luau Execution ===
@@ -4114,6 +5618,10 @@ var init_definitions = __esm({
4114
5618
  target: {
4115
5619
  type: "string",
4116
5620
  description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
5621
+ },
5622
+ instance_id: {
5623
+ type: "string",
5624
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4117
5625
  }
4118
5626
  },
4119
5627
  required: ["code"]
@@ -4122,13 +5630,17 @@ var init_definitions = __esm({
4122
5630
  {
4123
5631
  name: "eval_server_runtime",
4124
5632
  category: "write",
4125
- description: "Execute Luau on the server peer in the running game's Script VM (shares require cache with user game scripts). Use this instead of execute_luau target=server when you need to see runtime-mutated module state. Auto-installed at start_playtest, removed at stop_playtest.",
5633
+ description: "Execute Luau on the server peer in the running game's Script VM (shares require cache with user game scripts). Use this instead of execute_luau target=server when you need to see runtime-mutated module state. Requires a running playtest; the bridge is installed automatically (including for playtests started manually via the Studio Play button).",
4126
5634
  inputSchema: {
4127
5635
  type: "object",
4128
5636
  properties: {
4129
5637
  code: {
4130
5638
  type: "string",
4131
5639
  description: "Luau code to execute. Use return ... to get a value back."
5640
+ },
5641
+ instance_id: {
5642
+ type: "string",
5643
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4132
5644
  }
4133
5645
  },
4134
5646
  required: ["code"]
@@ -4137,7 +5649,7 @@ var init_definitions = __esm({
4137
5649
  {
4138
5650
  name: "eval_client_runtime",
4139
5651
  category: "write",
4140
- description: "Execute Luau on a client peer in the running game's LocalScript VM (shares require cache with user game scripts). Use this instead of execute_luau target=client-N when you need to see runtime-mutated module state. Auto-installed at start_playtest, removed at stop_playtest.",
5652
+ description: "Execute Luau on a client peer in the running game's LocalScript VM (shares require cache with user game scripts). Use this instead of execute_luau target=client-N when you need to see runtime-mutated module state. Requires a running playtest; the bridge is installed automatically (including for playtests started manually via the Studio Play button).",
4141
5653
  inputSchema: {
4142
5654
  type: "object",
4143
5655
  properties: {
@@ -4148,6 +5660,10 @@ var init_definitions = __esm({
4148
5660
  target: {
4149
5661
  type: "string",
4150
5662
  description: 'Client target: "client-1" (default), "client-2", etc.'
5663
+ },
5664
+ instance_id: {
5665
+ type: "string",
5666
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4151
5667
  }
4152
5668
  },
4153
5669
  required: ["code"]
@@ -4197,6 +5713,10 @@ var init_definitions = __esm({
4197
5713
  type: "string",
4198
5714
  enum: ["Script", "LocalScript", "ModuleScript"],
4199
5715
  description: "Only search scripts of this class type"
5716
+ },
5717
+ instance_id: {
5718
+ type: "string",
5719
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4200
5720
  }
4201
5721
  },
4202
5722
  required: ["pattern"]
@@ -4218,6 +5738,10 @@ var init_definitions = __esm({
4218
5738
  numPlayers: {
4219
5739
  type: "number",
4220
5740
  description: "Number of client players (1-8). Triggers server + clients mode via TestService."
5741
+ },
5742
+ instance_id: {
5743
+ type: "string",
5744
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4221
5745
  }
4222
5746
  },
4223
5747
  required: ["mode"]
@@ -4229,7 +5753,12 @@ var init_definitions = __esm({
4229
5753
  description: "Stop playtest and return all captured output.",
4230
5754
  inputSchema: {
4231
5755
  type: "object",
4232
- properties: {}
5756
+ properties: {
5757
+ instance_id: {
5758
+ type: "string",
5759
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
5760
+ }
5761
+ }
4233
5762
  }
4234
5763
  },
4235
5764
  {
@@ -4242,6 +5771,10 @@ var init_definitions = __esm({
4242
5771
  target: {
4243
5772
  type: "string",
4244
5773
  description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
5774
+ },
5775
+ instance_id: {
5776
+ type: "string",
5777
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4245
5778
  }
4246
5779
  }
4247
5780
  }
@@ -4268,6 +5801,10 @@ var init_definitions = __esm({
4268
5801
  filter: {
4269
5802
  type: "string",
4270
5803
  description: "Plain substring matched against each entry's message (no pattern semantics; literal text). Applied after since, before tail."
5804
+ },
5805
+ instance_id: {
5806
+ type: "string",
5807
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4271
5808
  }
4272
5809
  }
4273
5810
  }
@@ -4289,7 +5826,12 @@ var init_definitions = __esm({
4289
5826
  description: "Undo the last change in Roblox Studio. Uses ChangeHistoryService to reverse the most recent operation.",
4290
5827
  inputSchema: {
4291
5828
  type: "object",
4292
- properties: {}
5829
+ properties: {
5830
+ instance_id: {
5831
+ type: "string",
5832
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
5833
+ }
5834
+ }
4293
5835
  }
4294
5836
  },
4295
5837
  {
@@ -4298,7 +5840,12 @@ var init_definitions = __esm({
4298
5840
  description: "Redo the last undone change in Roblox Studio. Uses ChangeHistoryService to reapply the most recently undone operation.",
4299
5841
  inputSchema: {
4300
5842
  type: "object",
4301
- properties: {}
5843
+ properties: {
5844
+ instance_id: {
5845
+ type: "string",
5846
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
5847
+ }
5848
+ }
4302
5849
  }
4303
5850
  },
4304
5851
  // === Build Library ===
@@ -4321,6 +5868,10 @@ var init_definitions = __esm({
4321
5868
  type: "string",
4322
5869
  enum: ["medieval", "modern", "nature", "scifi", "misc"],
4323
5870
  description: "Style category for the build (default: misc)"
5871
+ },
5872
+ instance_id: {
5873
+ type: "string",
5874
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4324
5875
  }
4325
5876
  },
4326
5877
  required: ["instancePath"]
@@ -4470,6 +6021,10 @@ part(0,2,0,2,1,1,"b")`,
4470
6021
  type: "array",
4471
6022
  items: { type: "number" },
4472
6023
  description: "World position offset [X, Y, Z]"
6024
+ },
6025
+ instance_id: {
6026
+ type: "string",
6027
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4473
6028
  }
4474
6029
  },
4475
6030
  required: ["buildData", "targetPath"]
@@ -4504,6 +6059,10 @@ part(0,2,0,2,1,1,"b")`,
4504
6059
  maxResults: {
4505
6060
  type: "number",
4506
6061
  description: "Max results to return (default: 50)"
6062
+ },
6063
+ instance_id: {
6064
+ type: "string",
6065
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4507
6066
  }
4508
6067
  }
4509
6068
  }
@@ -4588,6 +6147,10 @@ part(0,2,0,2,1,1,"b")`,
4588
6147
  targetPath: {
4589
6148
  type: "string",
4590
6149
  description: "Parent instance path for the scene (default: game.Workspace)"
6150
+ },
6151
+ instance_id: {
6152
+ type: "string",
6153
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4591
6154
  }
4592
6155
  },
4593
6156
  required: ["sceneData"]
@@ -4685,6 +6248,10 @@ part(0,2,0,2,1,1,"b")`,
4685
6248
  z: { type: "number" }
4686
6249
  },
4687
6250
  description: "Optional world position to place the asset"
6251
+ },
6252
+ instance_id: {
6253
+ type: "string",
6254
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4688
6255
  }
4689
6256
  },
4690
6257
  required: ["assetId"]
@@ -4708,6 +6275,10 @@ part(0,2,0,2,1,1,"b")`,
4708
6275
  maxDepth: {
4709
6276
  type: "number",
4710
6277
  description: "Max hierarchy traversal depth (default: 10)"
6278
+ },
6279
+ instance_id: {
6280
+ type: "string",
6281
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4711
6282
  }
4712
6283
  },
4713
6284
  required: ["assetId"]
@@ -4752,46 +6323,59 @@ part(0,2,0,2,1,1,"b")`,
4752
6323
  {
4753
6324
  name: "capture_screenshot",
4754
6325
  category: "read",
4755
- description: 'Capture a screenshot of the Roblox Studio viewport and return it as a PNG image. Requires EditableImage API to be enabled: Game Settings > Security > "Allow Mesh / Image APIs". Only works in Edit mode with the viewport visible.',
6326
+ description: 'Capture the Roblox Studio viewport at native resolution and return it as an image, plus a text line stating the exact pixel dimensions. Works in Edit mode and during a playtest (auto-detects a running client and captures the live play viewport). The returned image is never downscaled, so its pixel grid is exactly the coordinate space simulate_mouse_input uses \u2014 read click positions straight off this image. For reading fine text/UI, use format="png" (lossless) or a higher quality; enlarging the Studio window raises resolution. Requires EditableImage API enabled (Game Settings > Security > "Allow Mesh / Image APIs") and the window to be visible.',
4756
6327
  inputSchema: {
4757
6328
  type: "object",
4758
- properties: {}
6329
+ properties: {
6330
+ format: {
6331
+ type: "string",
6332
+ enum: ["jpeg", "png"],
6333
+ description: 'Image format. "jpeg" (default) is compact and crisp at high quality. "png" is lossless \u2014 best for reading dense text/UI, but larger (a busy 3D scene may be big).'
6334
+ },
6335
+ quality: {
6336
+ type: "number",
6337
+ description: "JPEG quality 1-100 (default 92). Higher = sharper text, larger size. Ignored for png."
6338
+ },
6339
+ instance_id: {
6340
+ type: "string",
6341
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
6342
+ }
6343
+ }
4759
6344
  }
4760
6345
  },
4761
6346
  // === Input Simulation ===
4762
6347
  {
4763
6348
  name: "simulate_mouse_input",
4764
6349
  category: "write",
4765
- description: "Simulate mouse input in the Roblox Studio viewport via VirtualInputManager. Use during playtest to click UI buttons, interact with objects, or navigate menus. Coordinates are viewport pixels (top-left is 0,0). Use capture_screenshot to identify UI element positions before clicking.",
6350
+ description: "Simulate a mouse click in the running game via UserInputService:CreateVirtualInput. Use during a playtest to click UI buttons, interact with objects, or aim. Fires real UserInputService input and activates GUI buttons. Coordinates are viewport pixels matching capture_screenshot (top-left is 0,0) \u2014 take a screenshot first to find positions. Auto-targets the running client; only works during a playtest. Note: only click/mouseDown/mouseUp are supported (the API has no mouse-move or scroll).",
4766
6351
  inputSchema: {
4767
6352
  type: "object",
4768
6353
  properties: {
4769
6354
  action: {
4770
6355
  type: "string",
4771
- enum: ["click", "mouseDown", "mouseUp", "move", "scroll"],
4772
- description: 'Mouse action to perform. "click" does mouseDown + short delay + mouseUp.'
6356
+ enum: ["click", "mouseDown", "mouseUp"],
6357
+ description: 'Mouse action. "click" does mouseDown + short delay + mouseUp.'
4773
6358
  },
4774
6359
  x: {
4775
6360
  type: "number",
4776
- description: "Viewport pixel X coordinate"
6361
+ description: "Viewport pixel X coordinate (as seen in capture_screenshot)"
4777
6362
  },
4778
6363
  y: {
4779
6364
  type: "number",
4780
- description: "Viewport pixel Y coordinate"
6365
+ description: "Viewport pixel Y coordinate (as seen in capture_screenshot)"
4781
6366
  },
4782
6367
  button: {
4783
6368
  type: "string",
4784
6369
  enum: ["Left", "Right", "Middle"],
4785
6370
  description: "Mouse button (default: Left)"
4786
6371
  },
4787
- scrollDirection: {
6372
+ target: {
4788
6373
  type: "string",
4789
- enum: ["up", "down"],
4790
- description: 'Scroll direction (only for "scroll" action)'
6374
+ description: 'Instance target. Defaults to the running playtest client (client-1) when present, else "edit". Override with "server", "client-2", etc.'
4791
6375
  },
4792
- target: {
6376
+ instance_id: {
4793
6377
  type: "string",
4794
- description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
6378
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4795
6379
  }
4796
6380
  },
4797
6381
  required: ["action", "x", "y"]
@@ -4800,13 +6384,13 @@ part(0,2,0,2,1,1,"b")`,
4800
6384
  {
4801
6385
  name: "simulate_keyboard_input",
4802
6386
  category: "write",
4803
- description: 'Simulate keyboard input via VirtualInputManager. Use during playtest for character movement (W/A/S/D), jumping (Space), interactions (E), or any key-driven action. For sustained movement, use "press" to hold and "release" to let go.',
6387
+ description: 'Simulate keyboard input in the running game via UserInputService:CreateVirtualInput. Use during a playtest for character movement (W/A/S/D walks at full WalkSpeed with player controls intact), jumping (Space), interactions (E), or any key-driven action. Drives the real input pipeline so game scripts and control modules respond. For sustained movement use action="press" to hold and "release" to let go. Pass "text" instead of keyCode to type a string into the focused TextBox. Auto-targets the running client; only works during a playtest.',
4804
6388
  inputSchema: {
4805
6389
  type: "object",
4806
6390
  properties: {
4807
6391
  keyCode: {
4808
6392
  type: "string",
4809
- description: 'Enum.KeyCode name: "W", "A", "S", "D", "Space", "E", "F", "LeftShift", "LeftControl", "Return", "Tab", "Escape", "One", "Two", etc.'
6393
+ description: 'Enum.KeyCode name: "W", "A", "S", "D", "Space", "E", "F", "LeftShift", "LeftControl", "Return", "Tab", "Escape", "One", "Two", etc. Omit if using "text".'
4810
6394
  },
4811
6395
  action: {
4812
6396
  type: "string",
@@ -4817,12 +6401,19 @@ part(0,2,0,2,1,1,"b")`,
4817
6401
  type: "number",
4818
6402
  description: 'Hold duration in seconds for "tap" action (default: 0.1). Use longer values for sustained input like walking.'
4819
6403
  },
6404
+ text: {
6405
+ type: "string",
6406
+ description: "Type this string into the currently focused TextBox (uses SendTextInput). When provided, keyCode/action are ignored."
6407
+ },
4820
6408
  target: {
4821
6409
  type: "string",
4822
- description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
6410
+ description: 'Instance target. Defaults to the running playtest client (client-1) when present, else "edit". Override with "server", "client-2", etc.'
6411
+ },
6412
+ instance_id: {
6413
+ type: "string",
6414
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4823
6415
  }
4824
- },
4825
- required: ["keyCode"]
6416
+ }
4826
6417
  }
4827
6418
  },
4828
6419
  // === Character Navigation ===
@@ -4853,6 +6444,10 @@ part(0,2,0,2,1,1,"b")`,
4853
6444
  target: {
4854
6445
  type: "string",
4855
6446
  description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
6447
+ },
6448
+ instance_id: {
6449
+ type: "string",
6450
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4856
6451
  }
4857
6452
  }
4858
6453
  }
@@ -4872,6 +6467,10 @@ part(0,2,0,2,1,1,"b")`,
4872
6467
  targetParentPath: {
4873
6468
  type: "string",
4874
6469
  description: "Path of the parent to place the clone under"
6470
+ },
6471
+ instance_id: {
6472
+ type: "string",
6473
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4875
6474
  }
4876
6475
  },
4877
6476
  required: ["instancePath", "targetParentPath"]
@@ -4896,6 +6495,10 @@ part(0,2,0,2,1,1,"b")`,
4896
6495
  classFilter: {
4897
6496
  type: "string",
4898
6497
  description: 'Only include instances of this class (uses IsA, so "BasePart" matches Part, MeshPart, etc.)'
6498
+ },
6499
+ instance_id: {
6500
+ type: "string",
6501
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4899
6502
  }
4900
6503
  },
4901
6504
  required: ["instancePath"]
@@ -4915,6 +6518,10 @@ part(0,2,0,2,1,1,"b")`,
4915
6518
  instancePathB: {
4916
6519
  type: "string",
4917
6520
  description: "Second instance path"
6521
+ },
6522
+ instance_id: {
6523
+ type: "string",
6524
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4918
6525
  }
4919
6526
  },
4920
6527
  required: ["instancePathA", "instancePathB"]
@@ -4935,6 +6542,10 @@ part(0,2,0,2,1,1,"b")`,
4935
6542
  messageType: {
4936
6543
  type: "string",
4937
6544
  description: 'Filter by message type (e.g. "Enum.MessageType.MessageOutput", "Enum.MessageType.MessageWarning", "Enum.MessageType.MessageError")'
6545
+ },
6546
+ instance_id: {
6547
+ type: "string",
6548
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4938
6549
  }
4939
6550
  }
4940
6551
  }
@@ -4954,6 +6565,10 @@ part(0,2,0,2,1,1,"b")`,
4954
6565
  attributes: {
4955
6566
  type: "object",
4956
6567
  description: "Map of attribute names to values. Supports Vector3, Color3, UDim2 via _type convention."
6568
+ },
6569
+ instance_id: {
6570
+ type: "string",
6571
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4957
6572
  }
4958
6573
  },
4959
6574
  required: ["instancePath", "attributes"]
@@ -4975,6 +6590,10 @@ part(0,2,0,2,1,1,"b")`,
4975
6590
  type: "array",
4976
6591
  items: { type: "string" },
4977
6592
  description: "Optional DeveloperMemoryTag whitelist. Unknown tag names return 0 + unknown_tags list."
6593
+ },
6594
+ instance_id: {
6595
+ type: "string",
6596
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4978
6597
  }
4979
6598
  }
4980
6599
  }
@@ -5000,6 +6619,10 @@ part(0,2,0,2,1,1,"b")`,
5000
6619
  type: "string",
5001
6620
  enum: ["edit", "server"],
5002
6621
  description: 'Which DataModel to read from (default: "edit"). "server" serializes live runtime state during a playtest.'
6622
+ },
6623
+ instance_id: {
6624
+ type: "string",
6625
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
5003
6626
  }
5004
6627
  },
5005
6628
  required: ["instance_paths", "output_path"]
@@ -5034,6 +6657,10 @@ part(0,2,0,2,1,1,"b")`,
5034
6657
  type: "string",
5035
6658
  enum: ["edit", "server"],
5036
6659
  description: 'Which DataModel to import into (default: "edit"). "server" parents into the live play-server DM.'
6660
+ },
6661
+ instance_id: {
6662
+ type: "string",
6663
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
5037
6664
  }
5038
6665
  },
5039
6666
  required: ["source", "parent_path"]
@@ -5079,6 +6706,10 @@ part(0,2,0,2,1,1,"b")`,
5079
6706
  maxReplacements: {
5080
6707
  type: "number",
5081
6708
  description: "Safety limit on total replacements (default: 1000)"
6709
+ },
6710
+ instance_id: {
6711
+ type: "string",
6712
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
5082
6713
  }
5083
6714
  },
5084
6715
  required: ["pattern", "replacement"]