@chrrxs/robloxstudio-mcp 2.11.3 → 2.12.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;
80
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;
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),
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.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") {
@@ -1215,8 +1547,16 @@ function luaLongQuote(s) {
1215
1547
  ${s}
1216
1548
  ]${eq}]`;
1217
1549
  }
1550
+ function evalCountLines(s) {
1551
+ return s.split("\n").length;
1552
+ }
1218
1553
  function buildModuleScriptInvokeWrapper(opts) {
1554
+ const userLines = evalCountLines(opts.userCode);
1219
1555
  const wrapped = `return ((function()
1556
+ local __mcp_traceback
1557
+ local __mcp_remap
1558
+ local __mcp_LINE_OFFSET = ${EVAL_WRAPPER_LINE_OFFSET}
1559
+ local __mcp_USER_LINES = ${userLines}
1220
1560
  local __mcp_output = {}
1221
1561
  local __mcp_real_print = print
1222
1562
  local __mcp_real_warn = warn
@@ -1237,7 +1577,65 @@ function buildModuleScriptInvokeWrapper(opts) {
1237
1577
  local function __mcp_run()
1238
1578
  ${opts.userCode}
1239
1579
  end
1240
- local ok, errOrValue = xpcall(__mcp_run, debug.traceback)
1580
+ __mcp_remap = function(s)
1581
+ -- Two chunk-name formats can reference our payload: the
1582
+ -- ModuleScript path "Workspace.__MCPEvalPayload:N" and the
1583
+ -- loadstring chunk "[string \\"return ((function()...\\"]:N" (if
1584
+ -- the IIFE happens to compile via loadstring). Normalize both to
1585
+ -- "user_code:N" with the offset stripped AND clamped to user
1586
+ -- range, otherwise unclosed constructs report nonsense lines deep
1587
+ -- in the wrapper. Strip the "Workspace." parent prefix too so the
1588
+ -- final output reads "user_code:N" not "Workspace.user_code:N".
1589
+ local function __mcp_user_line(payload_n)
1590
+ local user_n = payload_n - __mcp_LINE_OFFSET
1591
+ if user_n < 1 then return "1" end
1592
+ if user_n > __mcp_USER_LINES then return tostring(__mcp_USER_LINES) .. " (at end of input)" end
1593
+ return tostring(user_n)
1594
+ end
1595
+ s = string.gsub(s, "Workspace%.__MCPEvalPayload:(%d+)", function(num)
1596
+ local n = tonumber(num)
1597
+ if n then return "user_code:" .. __mcp_user_line(n) end
1598
+ return "user_code:" .. num
1599
+ end)
1600
+ s = string.gsub(s, "__MCPEvalPayload:(%d+)", function(num)
1601
+ local n = tonumber(num)
1602
+ if n then return "user_code:" .. __mcp_user_line(n) end
1603
+ return "user_code:" .. num
1604
+ end)
1605
+ s = string.gsub(s, '%[string "[^"]+"%]:(%d+)', function(num)
1606
+ local n = tonumber(num)
1607
+ if n then return "user_code:" .. __mcp_user_line(n) end
1608
+ return "user_code:" .. num
1609
+ end)
1610
+ return s
1611
+ end
1612
+ __mcp_traceback = function(err)
1613
+ local raw = debug.traceback(tostring(err), 2)
1614
+ local kept = {}
1615
+ for line in string.gmatch(raw, "[^\\n]+") do
1616
+ local num_str = string.match(line, "__MCPEvalPayload:(%d+)")
1617
+ or string.match(line, '%[string "[^"]+"%]:(%d+)')
1618
+ local n = num_str and tonumber(num_str)
1619
+ -- Strip "in function '__mcp_run'" annotation BEFORE filtering:
1620
+ -- user-code frames all carry that suffix (their source is
1621
+ -- hosted inside __mcp_run), so a naive "__mcp_" filter would
1622
+ -- drop every user frame and leave only the error header.
1623
+ line = (string.gsub(line, " in function '__mcp_run'", ""))
1624
+ local skip = string.find(line, "MCPPlugin", 1, true)
1625
+ or string.find(line, "__mcp_", 1, true)
1626
+ or string.find(line, "in function 'xpcall'", 1, true)
1627
+ -- Drop wrapper preamble/postamble frames whose line falls
1628
+ -- outside the user-code range \u2014 those are wrapper internals.
1629
+ if n and (n <= __mcp_LINE_OFFSET or n > __mcp_LINE_OFFSET + __mcp_USER_LINES) then
1630
+ skip = true
1631
+ end
1632
+ if not skip then
1633
+ table.insert(kept, __mcp_remap(line))
1634
+ end
1635
+ end
1636
+ return table.concat(kept, "\\n")
1637
+ end
1638
+ local ok, errOrValue = xpcall(__mcp_run, __mcp_traceback)
1241
1639
  return { ok = ok, value = errOrValue, output = __mcp_output }
1242
1640
  end)())`;
1243
1641
  return `
@@ -1249,6 +1647,46 @@ if not bf then
1249
1647
  error = ${luaLongQuote(opts.missingError)},
1250
1648
  })
1251
1649
  end
1650
+ -- Outer-scope mirror of the in-IIFE __mcp_remap. Applied to parser errors
1651
+ -- we pull out of LogService (those never pass through the IIFE) and to
1652
+ -- the canned engine error string. Same offset as the IIFE's
1653
+ -- __mcp_LINE_OFFSET; covers both chunk-name formats.
1654
+ local __mcp_USER_LINES_OUTER = ${userLines}
1655
+ local function __mcp_outer_user_line(payload_n)
1656
+ local user_n = payload_n - ${EVAL_WRAPPER_LINE_OFFSET}
1657
+ if user_n < 1 then return "1" end
1658
+ if user_n > __mcp_USER_LINES_OUTER then return tostring(__mcp_USER_LINES_OUTER) .. " (at end of input)" end
1659
+ return tostring(user_n)
1660
+ end
1661
+ local function __mcp_outer_remap(s)
1662
+ s = string.gsub(s, "Workspace%.__MCPEvalPayload:(%d+)", function(num)
1663
+ local n = tonumber(num)
1664
+ if n then return "user_code:" .. __mcp_outer_user_line(n) end
1665
+ return "user_code:" .. num
1666
+ end)
1667
+ s = string.gsub(s, "__MCPEvalPayload:(%d+)", function(num)
1668
+ local n = tonumber(num)
1669
+ if n then return "user_code:" .. __mcp_outer_user_line(n) end
1670
+ return "user_code:" .. num
1671
+ end)
1672
+ s = string.gsub(s, '%[string "[^"]+"%]:(%d+)', function(num)
1673
+ local n = tonumber(num)
1674
+ if n then return "user_code:" .. __mcp_outer_user_line(n) end
1675
+ return "user_code:" .. num
1676
+ end)
1677
+ return s
1678
+ end
1679
+ -- JSON-encode tables; otherwise tostring. Cycles or non-serializable
1680
+ -- values fall back to tostring instead of erroring. This is what makes
1681
+ -- eval_server_runtime / eval_client_runtime return structured table data
1682
+ -- (matching execute_luau) instead of "table: 0xaddr".
1683
+ local function __mcp_format(v)
1684
+ if typeof(v) == "table" then
1685
+ local ok, encoded = pcall(function() return HttpService:JSONEncode(v) end)
1686
+ if ok then return encoded end
1687
+ end
1688
+ return tostring(v)
1689
+ end
1252
1690
  local USER_CODE = ${luaLongQuote(wrapped)}
1253
1691
  local m = Instance.new("ModuleScript")
1254
1692
  m.Name = "__MCPEvalPayload"
@@ -1261,7 +1699,28 @@ m.Parent = workspace
1261
1699
  local bridgeOk, inner = bf:Invoke(m)
1262
1700
  m:Destroy()
1263
1701
  if not bridgeOk then
1264
- return HttpService:JSONEncode({ bridge = "ok", ok = false, error = tostring(inner) })
1702
+ local errMsg = tostring(inner)
1703
+ -- pcall(require, payload) collapses parse/compile failures into the
1704
+ -- canned engine string below. The real parser diagnostic was emitted
1705
+ -- to LogService just before. Walk GetLogHistory backward for the most
1706
+ -- recent ERR entry tagged at our payload path and substitute.
1707
+ if errMsg == "Requested module experienced an error while loading" then
1708
+ -- The parser diagnostic is emitted to LogService on the next
1709
+ -- engine frame, not synchronously with pcall(require). task.wait(0)
1710
+ -- yields too early; 50ms is enough to let the frame complete and
1711
+ -- the message land in GetLogHistory.
1712
+ task.wait(0.05)
1713
+ local LogService = game:GetService("LogService")
1714
+ local hist = LogService:GetLogHistory()
1715
+ for i = #hist, 1, -1 do
1716
+ local e = hist[i]
1717
+ if e.messageType == Enum.MessageType.MessageError and string.sub(e.message, 1, 27) == "Workspace.__MCPEvalPayload:" then
1718
+ errMsg = e.message
1719
+ break
1720
+ end
1721
+ end
1722
+ end
1723
+ return HttpService:JSONEncode({ bridge = "ok", ok = false, error = __mcp_outer_remap(errMsg) })
1265
1724
  end
1266
1725
  -- inner is the {ok, value, output} table from our IIFE. Defensive: if it's
1267
1726
  -- somehow not a table (caller bypassed the wrapper), fall back to old shape.
@@ -1269,13 +1728,13 @@ if typeof(inner) ~= "table" then
1269
1728
  return HttpService:JSONEncode({
1270
1729
  bridge = "ok",
1271
1730
  ok = true,
1272
- result = if inner == nil then nil else tostring(inner),
1731
+ result = if inner == nil then nil else __mcp_format(inner),
1273
1732
  })
1274
1733
  end
1275
1734
  return HttpService:JSONEncode({
1276
1735
  bridge = "ok",
1277
1736
  ok = inner.ok == true,
1278
- result = if inner.ok and inner.value ~= nil then tostring(inner.value) else nil,
1737
+ result = if inner.ok and inner.value ~= nil then __mcp_format(inner.value) else nil,
1279
1738
  error = if not inner.ok then tostring(inner.value) else nil,
1280
1739
  output = inner.output or {},
1281
1740
  })
@@ -1292,17 +1751,19 @@ function parseBridgeResponse(response) {
1292
1751
  }
1293
1752
  return JSON.stringify(response);
1294
1753
  }
1295
- var SERVER_LOCAL_NAME, CLIENT_LOCAL_NAME, RobloxStudioTools;
1754
+ var SERVER_LOCAL_NAME, CLIENT_LOCAL_NAME, EVAL_WRAPPER_LINE_OFFSET, RobloxStudioTools;
1296
1755
  var init_tools = __esm({
1297
1756
  "../core/dist/tools/index.js"() {
1298
1757
  "use strict";
1299
1758
  init_studio_client();
1759
+ init_bridge_service();
1300
1760
  init_build_executor();
1301
1761
  init_opencloud_client();
1302
1762
  init_roblox_cookie_client();
1303
1763
  init_png_encoder();
1304
1764
  SERVER_LOCAL_NAME = "__MCP_ServerEvalLocal";
1305
1765
  CLIENT_LOCAL_NAME = "__MCP_ClientEvalBridge";
1766
+ EVAL_WRAPPER_LINE_OFFSET = 23;
1306
1767
  RobloxStudioTools = class _RobloxStudioTools {
1307
1768
  client;
1308
1769
  bridge;
@@ -1314,8 +1775,29 @@ var init_tools = __esm({
1314
1775
  this.openCloudClient = new OpenCloudClient();
1315
1776
  this.cookieClient = new RobloxCookieClient();
1316
1777
  }
1317
- async getFileTree(path2 = "") {
1318
- const response = await this.client.request("/api/file-tree", { path: path2 });
1778
+ // Resolve (instance_id, target-role) → concrete (instanceId, role) and
1779
+ // dispatch a single request. Throws RoutingFailure if the resolution is
1780
+ // ambiguous, missing, or asks for fanout on a non-fanout-capable tool —
1781
+ // the MCP transport layer surfaces it as a structured error result so
1782
+ // the LLM can recover via the embedded data.instances list.
1783
+ async _callSingle(endpoint, data, target, instance_id) {
1784
+ const r = this.bridge.resolveTarget({ instance_id, target });
1785
+ if (!r.ok)
1786
+ throw new RoutingFailure(r.error);
1787
+ if (r.mode !== "single") {
1788
+ throw new RoutingFailure({
1789
+ code: "target_role_not_present_on_instance",
1790
+ message: "This tool does not support target=all. Pick a specific role or omit target.",
1791
+ data: {
1792
+ instances: this.bridge.getPublicInstances(),
1793
+ count: this.bridge.getInstances().length
1794
+ }
1795
+ });
1796
+ }
1797
+ return this.client.request(endpoint, data, r.targetInstanceId, r.targetRole);
1798
+ }
1799
+ async getFileTree(path2 = "", instance_id) {
1800
+ const response = await this._callSingle("/api/file-tree", { path: path2 }, void 0, instance_id);
1319
1801
  return {
1320
1802
  content: [
1321
1803
  {
@@ -1325,8 +1807,8 @@ var init_tools = __esm({
1325
1807
  ]
1326
1808
  };
1327
1809
  }
1328
- async searchFiles(query, searchType = "name") {
1329
- const response = await this.client.request("/api/search-files", { query, searchType });
1810
+ async searchFiles(query, searchType = "name", instance_id) {
1811
+ const response = await this._callSingle("/api/search-files", { query, searchType }, void 0, instance_id);
1330
1812
  return {
1331
1813
  content: [
1332
1814
  {
@@ -1336,8 +1818,8 @@ var init_tools = __esm({
1336
1818
  ]
1337
1819
  };
1338
1820
  }
1339
- async getPlaceInfo() {
1340
- const response = await this.client.request("/api/place-info", {});
1821
+ async getPlaceInfo(instance_id) {
1822
+ const response = await this._callSingle("/api/place-info", {}, void 0, instance_id);
1341
1823
  return {
1342
1824
  content: [
1343
1825
  {
@@ -1347,8 +1829,8 @@ var init_tools = __esm({
1347
1829
  ]
1348
1830
  };
1349
1831
  }
1350
- async getServices(serviceName) {
1351
- const response = await this.client.request("/api/services", { serviceName });
1832
+ async getServices(serviceName, instance_id) {
1833
+ const response = await this._callSingle("/api/services", { serviceName }, void 0, instance_id);
1352
1834
  return {
1353
1835
  content: [
1354
1836
  {
@@ -1358,12 +1840,12 @@ var init_tools = __esm({
1358
1840
  ]
1359
1841
  };
1360
1842
  }
1361
- async searchObjects(query, searchType = "name", propertyName) {
1362
- const response = await this.client.request("/api/search-objects", {
1843
+ async searchObjects(query, searchType = "name", propertyName, instance_id) {
1844
+ const response = await this._callSingle("/api/search-objects", {
1363
1845
  query,
1364
1846
  searchType,
1365
1847
  propertyName
1366
- });
1848
+ }, void 0, instance_id);
1367
1849
  return {
1368
1850
  content: [
1369
1851
  {
@@ -1373,11 +1855,11 @@ var init_tools = __esm({
1373
1855
  ]
1374
1856
  };
1375
1857
  }
1376
- async getInstanceProperties(instancePath, excludeSource) {
1858
+ async getInstanceProperties(instancePath, excludeSource, instance_id) {
1377
1859
  if (!instancePath) {
1378
1860
  throw new Error("Instance path is required for get_instance_properties");
1379
1861
  }
1380
- const response = await this.client.request("/api/instance-properties", { instancePath, excludeSource });
1862
+ const response = await this._callSingle("/api/instance-properties", { instancePath, excludeSource }, void 0, instance_id);
1381
1863
  return {
1382
1864
  content: [
1383
1865
  {
@@ -1387,11 +1869,11 @@ var init_tools = __esm({
1387
1869
  ]
1388
1870
  };
1389
1871
  }
1390
- async getInstanceChildren(instancePath) {
1872
+ async getInstanceChildren(instancePath, instance_id) {
1391
1873
  if (!instancePath) {
1392
1874
  throw new Error("Instance path is required for get_instance_children");
1393
1875
  }
1394
- const response = await this.client.request("/api/instance-children", { instancePath });
1876
+ const response = await this._callSingle("/api/instance-children", { instancePath }, void 0, instance_id);
1395
1877
  return {
1396
1878
  content: [
1397
1879
  {
@@ -1401,14 +1883,14 @@ var init_tools = __esm({
1401
1883
  ]
1402
1884
  };
1403
1885
  }
1404
- async searchByProperty(propertyName, propertyValue) {
1886
+ async searchByProperty(propertyName, propertyValue, instance_id) {
1405
1887
  if (!propertyName || !propertyValue) {
1406
1888
  throw new Error("Property name and value are required for search_by_property");
1407
1889
  }
1408
- const response = await this.client.request("/api/search-by-property", {
1890
+ const response = await this._callSingle("/api/search-by-property", {
1409
1891
  propertyName,
1410
1892
  propertyValue
1411
- });
1893
+ }, void 0, instance_id);
1412
1894
  return {
1413
1895
  content: [
1414
1896
  {
@@ -1418,11 +1900,11 @@ var init_tools = __esm({
1418
1900
  ]
1419
1901
  };
1420
1902
  }
1421
- async getClassInfo(className) {
1903
+ async getClassInfo(className, instance_id) {
1422
1904
  if (!className) {
1423
1905
  throw new Error("Class name is required for get_class_info");
1424
1906
  }
1425
- const response = await this.client.request("/api/class-info", { className });
1907
+ const response = await this._callSingle("/api/class-info", { className }, void 0, instance_id);
1426
1908
  return {
1427
1909
  content: [
1428
1910
  {
@@ -1432,12 +1914,12 @@ var init_tools = __esm({
1432
1914
  ]
1433
1915
  };
1434
1916
  }
1435
- async getProjectStructure(path2, maxDepth, scriptsOnly) {
1436
- const response = await this.client.request("/api/project-structure", {
1917
+ async getProjectStructure(path2, maxDepth, scriptsOnly, instance_id) {
1918
+ const response = await this._callSingle("/api/project-structure", {
1437
1919
  path: path2,
1438
1920
  maxDepth,
1439
1921
  scriptsOnly
1440
- });
1922
+ }, void 0, instance_id);
1441
1923
  return {
1442
1924
  content: [
1443
1925
  {
@@ -1447,15 +1929,15 @@ var init_tools = __esm({
1447
1929
  ]
1448
1930
  };
1449
1931
  }
1450
- async setProperty(instancePath, propertyName, propertyValue) {
1932
+ async setProperty(instancePath, propertyName, propertyValue, instance_id) {
1451
1933
  if (!instancePath || !propertyName) {
1452
1934
  throw new Error("Instance path and property name are required for set_property");
1453
1935
  }
1454
- const response = await this.client.request("/api/set-property", {
1936
+ const response = await this._callSingle("/api/set-property", {
1455
1937
  instancePath,
1456
1938
  propertyName,
1457
1939
  propertyValue
1458
- });
1940
+ }, void 0, instance_id);
1459
1941
  return {
1460
1942
  content: [
1461
1943
  {
@@ -1465,22 +1947,22 @@ var init_tools = __esm({
1465
1947
  ]
1466
1948
  };
1467
1949
  }
1468
- async setProperties(instancePath, properties) {
1950
+ async setProperties(instancePath, properties, instance_id) {
1469
1951
  if (!instancePath || !properties) {
1470
1952
  throw new Error("instancePath and properties are required for set_properties");
1471
1953
  }
1472
- const response = await this.client.request("/api/set-properties", { instancePath, properties });
1954
+ const response = await this._callSingle("/api/set-properties", { instancePath, properties }, void 0, instance_id);
1473
1955
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
1474
1956
  }
1475
- async massSetProperty(paths, propertyName, propertyValue) {
1957
+ async massSetProperty(paths, propertyName, propertyValue, instance_id) {
1476
1958
  if (!paths || paths.length === 0 || !propertyName) {
1477
1959
  throw new Error("Paths array and property name are required for mass_set_property");
1478
1960
  }
1479
- const response = await this.client.request("/api/mass-set-property", {
1961
+ const response = await this._callSingle("/api/mass-set-property", {
1480
1962
  paths,
1481
1963
  propertyName,
1482
1964
  propertyValue
1483
- });
1965
+ }, void 0, instance_id);
1484
1966
  return {
1485
1967
  content: [
1486
1968
  {
@@ -1490,14 +1972,14 @@ var init_tools = __esm({
1490
1972
  ]
1491
1973
  };
1492
1974
  }
1493
- async massGetProperty(paths, propertyName) {
1975
+ async massGetProperty(paths, propertyName, instance_id) {
1494
1976
  if (!paths || paths.length === 0 || !propertyName) {
1495
1977
  throw new Error("Paths array and property name are required for mass_get_property");
1496
1978
  }
1497
- const response = await this.client.request("/api/mass-get-property", {
1979
+ const response = await this._callSingle("/api/mass-get-property", {
1498
1980
  paths,
1499
1981
  propertyName
1500
- });
1982
+ }, void 0, instance_id);
1501
1983
  return {
1502
1984
  content: [
1503
1985
  {
@@ -1507,16 +1989,16 @@ var init_tools = __esm({
1507
1989
  ]
1508
1990
  };
1509
1991
  }
1510
- async createObject(className, parent, name, properties) {
1992
+ async createObject(className, parent, name, properties, instance_id) {
1511
1993
  if (!className || !parent) {
1512
1994
  throw new Error("Class name and parent are required for create_object");
1513
1995
  }
1514
- const response = await this.client.request("/api/create-object", {
1996
+ const response = await this._callSingle("/api/create-object", {
1515
1997
  className,
1516
1998
  parent,
1517
1999
  name,
1518
2000
  properties
1519
- });
2001
+ }, void 0, instance_id);
1520
2002
  return {
1521
2003
  content: [
1522
2004
  {
@@ -1526,11 +2008,11 @@ var init_tools = __esm({
1526
2008
  ]
1527
2009
  };
1528
2010
  }
1529
- async massCreateObjects(objects) {
2011
+ async massCreateObjects(objects, instance_id) {
1530
2012
  if (!objects || objects.length === 0) {
1531
2013
  throw new Error("Objects array is required for mass_create_objects");
1532
2014
  }
1533
- const response = await this.client.request("/api/mass-create-objects", { objects });
2015
+ const response = await this._callSingle("/api/mass-create-objects", { objects }, void 0, instance_id);
1534
2016
  return {
1535
2017
  content: [
1536
2018
  {
@@ -1540,11 +2022,11 @@ var init_tools = __esm({
1540
2022
  ]
1541
2023
  };
1542
2024
  }
1543
- async deleteObject(instancePath) {
2025
+ async deleteObject(instancePath, instance_id) {
1544
2026
  if (!instancePath) {
1545
2027
  throw new Error("Instance path is required for delete_object");
1546
2028
  }
1547
- const response = await this.client.request("/api/delete-object", { instancePath });
2029
+ const response = await this._callSingle("/api/delete-object", { instancePath }, void 0, instance_id);
1548
2030
  return {
1549
2031
  content: [
1550
2032
  {
@@ -1554,15 +2036,15 @@ var init_tools = __esm({
1554
2036
  ]
1555
2037
  };
1556
2038
  }
1557
- async smartDuplicate(instancePath, count, options) {
2039
+ async smartDuplicate(instancePath, count, options, instance_id) {
1558
2040
  if (!instancePath || count < 1) {
1559
2041
  throw new Error("Instance path and count > 0 are required for smart_duplicate");
1560
2042
  }
1561
- const response = await this.client.request("/api/smart-duplicate", {
2043
+ const response = await this._callSingle("/api/smart-duplicate", {
1562
2044
  instancePath,
1563
2045
  count,
1564
2046
  options
1565
- });
2047
+ }, void 0, instance_id);
1566
2048
  return {
1567
2049
  content: [
1568
2050
  {
@@ -1572,11 +2054,11 @@ var init_tools = __esm({
1572
2054
  ]
1573
2055
  };
1574
2056
  }
1575
- async massDuplicate(duplications) {
2057
+ async massDuplicate(duplications, instance_id) {
1576
2058
  if (!duplications || duplications.length === 0) {
1577
2059
  throw new Error("Duplications array is required for mass_duplicate");
1578
2060
  }
1579
- const response = await this.client.request("/api/mass-duplicate", { duplications });
2061
+ const response = await this._callSingle("/api/mass-duplicate", { duplications }, void 0, instance_id);
1580
2062
  return {
1581
2063
  content: [
1582
2064
  {
@@ -1586,11 +2068,11 @@ var init_tools = __esm({
1586
2068
  ]
1587
2069
  };
1588
2070
  }
1589
- async getScriptSource(instancePath, startLine, endLine) {
2071
+ async getScriptSource(instancePath, startLine, endLine, instance_id) {
1590
2072
  if (!instancePath) {
1591
2073
  throw new Error("Instance path is required for get_script_source");
1592
2074
  }
1593
- const response = await this.client.request("/api/get-script-source", { instancePath, startLine, endLine });
2075
+ const response = await this._callSingle("/api/get-script-source", { instancePath, startLine, endLine }, void 0, instance_id);
1594
2076
  if (response.error) {
1595
2077
  return { content: [{ type: "text", text: `Error: ${response.error}` }] };
1596
2078
  }
@@ -1637,11 +2119,11 @@ ${code}`
1637
2119
  }]
1638
2120
  };
1639
2121
  }
1640
- async setScriptSource(instancePath, source) {
2122
+ async setScriptSource(instancePath, source, instance_id) {
1641
2123
  if (!instancePath || typeof source !== "string") {
1642
2124
  throw new Error("Instance path and source code string are required for set_script_source");
1643
2125
  }
1644
- const response = await this.client.request("/api/set-script-source", { instancePath, source });
2126
+ const response = await this._callSingle("/api/set-script-source", { instancePath, source }, void 0, instance_id);
1645
2127
  return {
1646
2128
  content: [
1647
2129
  {
@@ -1651,14 +2133,14 @@ ${code}`
1651
2133
  ]
1652
2134
  };
1653
2135
  }
1654
- async editScriptLines(instancePath, oldString, newString, startLine) {
2136
+ async editScriptLines(instancePath, oldString, newString, startLine, instance_id) {
1655
2137
  if (!instancePath || typeof oldString !== "string" || typeof newString !== "string") {
1656
2138
  throw new Error("Instance path, old_string, and new_string are required for edit_script_lines");
1657
2139
  }
1658
2140
  const payload = { instancePath, old_string: oldString, new_string: newString };
1659
2141
  if (startLine !== void 0)
1660
2142
  payload.startLine = startLine;
1661
- const response = await this.client.request("/api/edit-script-lines", payload);
2143
+ const response = await this._callSingle("/api/edit-script-lines", payload, void 0, instance_id);
1662
2144
  return {
1663
2145
  content: [
1664
2146
  {
@@ -1668,11 +2150,11 @@ ${code}`
1668
2150
  ]
1669
2151
  };
1670
2152
  }
1671
- async insertScriptLines(instancePath, afterLine, newContent) {
2153
+ async insertScriptLines(instancePath, afterLine, newContent, instance_id) {
1672
2154
  if (!instancePath || typeof newContent !== "string") {
1673
2155
  throw new Error("Instance path and newContent are required for insert_script_lines");
1674
2156
  }
1675
- const response = await this.client.request("/api/insert-script-lines", { instancePath, afterLine: afterLine || 0, newContent });
2157
+ const response = await this._callSingle("/api/insert-script-lines", { instancePath, afterLine: afterLine || 0, newContent }, void 0, instance_id);
1676
2158
  return {
1677
2159
  content: [
1678
2160
  {
@@ -1682,11 +2164,11 @@ ${code}`
1682
2164
  ]
1683
2165
  };
1684
2166
  }
1685
- async deleteScriptLines(instancePath, startLine, endLine) {
2167
+ async deleteScriptLines(instancePath, startLine, endLine, instance_id) {
1686
2168
  if (!instancePath || !startLine || !endLine) {
1687
2169
  throw new Error("Instance path, startLine, and endLine are required for delete_script_lines");
1688
2170
  }
1689
- const response = await this.client.request("/api/delete-script-lines", { instancePath, startLine, endLine });
2171
+ const response = await this._callSingle("/api/delete-script-lines", { instancePath, startLine, endLine }, void 0, instance_id);
1690
2172
  return {
1691
2173
  content: [
1692
2174
  {
@@ -1696,14 +2178,14 @@ ${code}`
1696
2178
  ]
1697
2179
  };
1698
2180
  }
1699
- async grepScripts(pattern, options) {
2181
+ async grepScripts(pattern, options, instance_id) {
1700
2182
  if (!pattern) {
1701
2183
  throw new Error("Pattern is required for grep_scripts");
1702
2184
  }
1703
- const response = await this.client.request("/api/grep-scripts", {
2185
+ const response = await this._callSingle("/api/grep-scripts", {
1704
2186
  pattern,
1705
2187
  ...options
1706
- });
2188
+ }, void 0, instance_id);
1707
2189
  return {
1708
2190
  content: [
1709
2191
  {
@@ -1713,11 +2195,11 @@ ${code}`
1713
2195
  ]
1714
2196
  };
1715
2197
  }
1716
- async setAttribute(instancePath, attributeName, attributeValue, valueType) {
2198
+ async setAttribute(instancePath, attributeName, attributeValue, valueType, instance_id) {
1717
2199
  if (!instancePath || !attributeName) {
1718
2200
  throw new Error("Instance path and attribute name are required for set_attribute");
1719
2201
  }
1720
- const response = await this.client.request("/api/set-attribute", { instancePath, attributeName, attributeValue, valueType });
2202
+ const response = await this._callSingle("/api/set-attribute", { instancePath, attributeName, attributeValue, valueType }, void 0, instance_id);
1721
2203
  return {
1722
2204
  content: [
1723
2205
  {
@@ -1727,11 +2209,11 @@ ${code}`
1727
2209
  ]
1728
2210
  };
1729
2211
  }
1730
- async getAttributes(instancePath) {
2212
+ async getAttributes(instancePath, instance_id) {
1731
2213
  if (!instancePath) {
1732
2214
  throw new Error("Instance path is required for get_attributes");
1733
2215
  }
1734
- const response = await this.client.request("/api/get-attributes", { instancePath });
2216
+ const response = await this._callSingle("/api/get-attributes", { instancePath }, void 0, instance_id);
1735
2217
  return {
1736
2218
  content: [
1737
2219
  {
@@ -1741,11 +2223,11 @@ ${code}`
1741
2223
  ]
1742
2224
  };
1743
2225
  }
1744
- async deleteAttribute(instancePath, attributeName) {
2226
+ async deleteAttribute(instancePath, attributeName, instance_id) {
1745
2227
  if (!instancePath || !attributeName) {
1746
2228
  throw new Error("Instance path and attribute name are required for delete_attribute");
1747
2229
  }
1748
- const response = await this.client.request("/api/delete-attribute", { instancePath, attributeName });
2230
+ const response = await this._callSingle("/api/delete-attribute", { instancePath, attributeName }, void 0, instance_id);
1749
2231
  return {
1750
2232
  content: [
1751
2233
  {
@@ -1755,11 +2237,11 @@ ${code}`
1755
2237
  ]
1756
2238
  };
1757
2239
  }
1758
- async getTags(instancePath) {
2240
+ async getTags(instancePath, instance_id) {
1759
2241
  if (!instancePath) {
1760
2242
  throw new Error("Instance path is required for get_tags");
1761
2243
  }
1762
- const response = await this.client.request("/api/get-tags", { instancePath });
2244
+ const response = await this._callSingle("/api/get-tags", { instancePath }, void 0, instance_id);
1763
2245
  return {
1764
2246
  content: [
1765
2247
  {
@@ -1769,11 +2251,11 @@ ${code}`
1769
2251
  ]
1770
2252
  };
1771
2253
  }
1772
- async addTag(instancePath, tagName) {
2254
+ async addTag(instancePath, tagName, instance_id) {
1773
2255
  if (!instancePath || !tagName) {
1774
2256
  throw new Error("Instance path and tag name are required for add_tag");
1775
2257
  }
1776
- const response = await this.client.request("/api/add-tag", { instancePath, tagName });
2258
+ const response = await this._callSingle("/api/add-tag", { instancePath, tagName }, void 0, instance_id);
1777
2259
  return {
1778
2260
  content: [
1779
2261
  {
@@ -1783,11 +2265,11 @@ ${code}`
1783
2265
  ]
1784
2266
  };
1785
2267
  }
1786
- async removeTag(instancePath, tagName) {
2268
+ async removeTag(instancePath, tagName, instance_id) {
1787
2269
  if (!instancePath || !tagName) {
1788
2270
  throw new Error("Instance path and tag name are required for remove_tag");
1789
2271
  }
1790
- const response = await this.client.request("/api/remove-tag", { instancePath, tagName });
2272
+ const response = await this._callSingle("/api/remove-tag", { instancePath, tagName }, void 0, instance_id);
1791
2273
  return {
1792
2274
  content: [
1793
2275
  {
@@ -1797,11 +2279,11 @@ ${code}`
1797
2279
  ]
1798
2280
  };
1799
2281
  }
1800
- async getTagged(tagName) {
2282
+ async getTagged(tagName, instance_id) {
1801
2283
  if (!tagName) {
1802
2284
  throw new Error("Tag name is required for get_tagged");
1803
2285
  }
1804
- const response = await this.client.request("/api/get-tagged", { tagName });
2286
+ const response = await this._callSingle("/api/get-tagged", { tagName }, void 0, instance_id);
1805
2287
  return {
1806
2288
  content: [
1807
2289
  {
@@ -1811,8 +2293,8 @@ ${code}`
1811
2293
  ]
1812
2294
  };
1813
2295
  }
1814
- async getSelection() {
1815
- const response = await this.client.request("/api/get-selection", {});
2296
+ async getSelection(instance_id) {
2297
+ const response = await this._callSingle("/api/get-selection", {}, void 0, instance_id);
1816
2298
  return {
1817
2299
  content: [
1818
2300
  {
@@ -1822,11 +2304,11 @@ ${code}`
1822
2304
  ]
1823
2305
  };
1824
2306
  }
1825
- async executeLuau(code, target) {
2307
+ async executeLuau(code, target, instance_id) {
1826
2308
  if (!code) {
1827
2309
  throw new Error("Code is required for execute_luau");
1828
2310
  }
1829
- const response = await this.client.request("/api/execute-luau", { code }, target || "edit");
2311
+ const response = await this._callSingle("/api/execute-luau", { code }, target || "edit", instance_id);
1830
2312
  return {
1831
2313
  content: [
1832
2314
  {
@@ -1836,7 +2318,7 @@ ${code}`
1836
2318
  ]
1837
2319
  };
1838
2320
  }
1839
- async evalServerRuntime(code) {
2321
+ async evalServerRuntime(code, instance_id) {
1840
2322
  if (!code) {
1841
2323
  throw new Error("Code is required for eval_server_runtime");
1842
2324
  }
@@ -1846,7 +2328,7 @@ ${code}`
1846
2328
  missingError: "ServerEvalBridge not installed. Bridges are auto-installed at start_playtest and removed at stop_playtest. Start a playtest before calling eval_server_runtime.",
1847
2329
  userCode: code
1848
2330
  });
1849
- const response = await this.client.request("/api/execute-luau", { code: wrapper }, "server");
2331
+ const response = await this._callSingle("/api/execute-luau", { code: wrapper }, "server", instance_id);
1850
2332
  return {
1851
2333
  content: [
1852
2334
  {
@@ -1856,7 +2338,7 @@ ${code}`
1856
2338
  ]
1857
2339
  };
1858
2340
  }
1859
- async evalClientRuntime(code, target) {
2341
+ async evalClientRuntime(code, target, instance_id) {
1860
2342
  if (!code) {
1861
2343
  throw new Error("Code is required for eval_client_runtime");
1862
2344
  }
@@ -1870,7 +2352,7 @@ ${code}`
1870
2352
  missingError: "ClientEvalBridge not installed. Bridges are auto-installed at start_playtest and removed at stop_playtest. Start a playtest before calling eval_client_runtime.",
1871
2353
  userCode: code
1872
2354
  });
1873
- const response = await this.client.request("/api/execute-luau", { code: wrapper }, clientTarget);
2355
+ const response = await this._callSingle("/api/execute-luau", { code: wrapper }, clientTarget, instance_id);
1874
2356
  return {
1875
2357
  content: [
1876
2358
  {
@@ -1880,7 +2362,7 @@ ${code}`
1880
2362
  ]
1881
2363
  };
1882
2364
  }
1883
- async getRuntimeLogs(target, since, tail, filter) {
2365
+ async getRuntimeLogs(target, since, tail, filter, instance_id) {
1884
2366
  const tgt = target ?? "all";
1885
2367
  const data = {};
1886
2368
  if (since !== void 0)
@@ -1889,16 +2371,26 @@ ${code}`
1889
2371
  data.tail = tail;
1890
2372
  if (filter !== void 0)
1891
2373
  data.filter = filter;
1892
- if (tgt !== "all") {
1893
- const response = await this.client.request("/api/get-runtime-logs", data, tgt);
2374
+ const resolved = this.bridge.resolveTarget({ instance_id, target: tgt });
2375
+ if (!resolved.ok)
2376
+ throw new RoutingFailure(resolved.error);
2377
+ if (resolved.mode === "single") {
2378
+ const response = await this.client.request("/api/get-runtime-logs", data, resolved.targetInstanceId, resolved.targetRole);
2379
+ response.peer = resolved.targetRole;
2380
+ if (Array.isArray(response.entries)) {
2381
+ for (const e of response.entries) {
2382
+ if (e.peer !== void 0)
2383
+ e.peer = resolved.targetRole;
2384
+ }
2385
+ }
1894
2386
  return {
1895
2387
  content: [{ type: "text", text: JSON.stringify(response) }]
1896
2388
  };
1897
2389
  }
1898
- const targets = this.bridge.getInstances().filter((i) => i.role !== "edit-proxy").map((i) => i.role);
2390
+ const targets = resolved.targets.filter((t) => t.targetRole !== "edit-proxy");
1899
2391
  const responses = await Promise.allSettled(targets.map(async (t) => {
1900
- const r = await this.client.request("/api/get-runtime-logs", data, t);
1901
- return { ...r, peer: t };
2392
+ const r = await this.client.request("/api/get-runtime-logs", data, t.targetInstanceId, t.targetRole);
2393
+ return { ...r, peer: t.targetRole };
1902
2394
  }));
1903
2395
  const merged = [];
1904
2396
  const perPeerNextSince = {};
@@ -1944,7 +2436,7 @@ ${code}`
1944
2436
  content: [{ type: "text", text: JSON.stringify(body) }]
1945
2437
  };
1946
2438
  }
1947
- async startPlaytest(mode, numPlayers) {
2439
+ async startPlaytest(mode, numPlayers, instance_id) {
1948
2440
  if (mode !== "play" && mode !== "run") {
1949
2441
  throw new Error('mode must be "play" or "run"');
1950
2442
  }
@@ -1952,7 +2444,7 @@ ${code}`
1952
2444
  if (numPlayers !== void 0) {
1953
2445
  data.numPlayers = numPlayers;
1954
2446
  }
1955
- const response = await this.client.request("/api/start-playtest", data);
2447
+ const response = await this._callSingle("/api/start-playtest", data, void 0, instance_id);
1956
2448
  return {
1957
2449
  content: [
1958
2450
  {
@@ -1962,14 +2454,14 @@ ${code}`
1962
2454
  ]
1963
2455
  };
1964
2456
  }
1965
- async stopPlaytest() {
1966
- const response = await this.client.request("/api/stop-playtest", {}, "edit");
2457
+ async stopPlaytest(instance_id) {
2458
+ const response = await this._callSingle("/api/stop-playtest", {}, "edit", instance_id);
1967
2459
  return {
1968
2460
  content: [{ type: "text", text: JSON.stringify(response) }]
1969
2461
  };
1970
2462
  }
1971
- async getPlaytestOutput(target) {
1972
- const response = await this.client.request("/api/get-playtest-output", {}, target || "edit");
2463
+ async getPlaytestOutput(target, instance_id) {
2464
+ const response = await this._callSingle("/api/get-playtest-output", {}, target || "edit", instance_id);
1973
2465
  return {
1974
2466
  content: [
1975
2467
  {
@@ -1980,7 +2472,7 @@ ${code}`
1980
2472
  };
1981
2473
  }
1982
2474
  async getConnectedInstances() {
1983
- const instances = this.bridge.getInstances();
2475
+ const instances = this.bridge.getPublicInstances();
1984
2476
  return {
1985
2477
  content: [
1986
2478
  {
@@ -1990,8 +2482,8 @@ ${code}`
1990
2482
  ]
1991
2483
  };
1992
2484
  }
1993
- async undo() {
1994
- const response = await this.client.request("/api/undo", {});
2485
+ async undo(instance_id) {
2486
+ const response = await this._callSingle("/api/undo", {}, void 0, instance_id);
1995
2487
  return {
1996
2488
  content: [
1997
2489
  {
@@ -2001,8 +2493,8 @@ ${code}`
2001
2493
  ]
2002
2494
  };
2003
2495
  }
2004
- async redo() {
2005
- const response = await this.client.request("/api/redo", {});
2496
+ async redo(instance_id) {
2497
+ const response = await this._callSingle("/api/redo", {}, void 0, instance_id);
2006
2498
  return {
2007
2499
  content: [
2008
2500
  {
@@ -2088,15 +2580,15 @@ ${code}`
2088
2580
  _RobloxStudioTools._cachedLibraryPath = result;
2089
2581
  return result;
2090
2582
  }
2091
- async exportBuild(instancePath, outputId, style = "misc") {
2583
+ async exportBuild(instancePath, outputId, style = "misc", instance_id) {
2092
2584
  if (!instancePath) {
2093
2585
  throw new Error("Instance path is required for export_build");
2094
2586
  }
2095
- const response = await this.client.request("/api/export-build", {
2587
+ const response = await this._callSingle("/api/export-build", {
2096
2588
  instancePath,
2097
2589
  outputId,
2098
2590
  style
2099
- });
2591
+ }, void 0, instance_id);
2100
2592
  if (response && response.success && response.buildData) {
2101
2593
  const buildData = response.buildData;
2102
2594
  const buildId = buildData.id || `${style}/exported`;
@@ -2284,7 +2776,7 @@ ${code}`
2284
2776
  ]
2285
2777
  };
2286
2778
  }
2287
- async importBuild(buildData, targetPath, position) {
2779
+ async importBuild(buildData, targetPath, position, instance_id) {
2288
2780
  if (!buildData || !targetPath) {
2289
2781
  throw new Error("buildData (or library ID string) and targetPath are required for import_build");
2290
2782
  }
@@ -2304,11 +2796,11 @@ ${code}`
2304
2796
  } else {
2305
2797
  resolved = buildData;
2306
2798
  }
2307
- const response = await this.client.request("/api/import-build", {
2799
+ const response = await this._callSingle("/api/import-build", {
2308
2800
  buildData: resolved,
2309
2801
  targetPath,
2310
2802
  position
2311
- });
2803
+ }, void 0, instance_id);
2312
2804
  return {
2313
2805
  content: [
2314
2806
  {
@@ -2350,11 +2842,11 @@ ${code}`
2350
2842
  ]
2351
2843
  };
2352
2844
  }
2353
- async searchMaterials(query, maxResults) {
2354
- const response = await this.client.request("/api/search-materials", {
2845
+ async searchMaterials(query, maxResults, instance_id) {
2846
+ const response = await this._callSingle("/api/search-materials", {
2355
2847
  query: query ?? "",
2356
2848
  maxResults: maxResults ?? 50
2357
- });
2849
+ }, void 0, instance_id);
2358
2850
  return {
2359
2851
  content: [
2360
2852
  {
@@ -2394,7 +2886,7 @@ ${code}`
2394
2886
  ]
2395
2887
  };
2396
2888
  }
2397
- async importScene(sceneData, targetPath = "game.Workspace") {
2889
+ async importScene(sceneData, targetPath = "game.Workspace", instance_id) {
2398
2890
  if (!sceneData) {
2399
2891
  throw new Error("sceneData is required for import_scene");
2400
2892
  }
@@ -2484,10 +2976,10 @@ ${code}`
2484
2976
  if (expandedBuilds.length === 0) {
2485
2977
  throw new Error("No builds to import - check model references and library");
2486
2978
  }
2487
- const response = await this.client.request("/api/import-scene", {
2979
+ const response = await this._callSingle("/api/import-scene", {
2488
2980
  expandedBuilds,
2489
2981
  targetPath
2490
- });
2982
+ }, void 0, instance_id);
2491
2983
  return {
2492
2984
  content: [
2493
2985
  {
@@ -2578,15 +3070,15 @@ ${code}`
2578
3070
  }]
2579
3071
  };
2580
3072
  }
2581
- async insertAsset(assetId, parentPath, position) {
3073
+ async insertAsset(assetId, parentPath, position, instance_id) {
2582
3074
  if (!assetId) {
2583
3075
  throw new Error("Asset ID is required for insert_asset");
2584
3076
  }
2585
- const response = await this.client.request("/api/insert-asset", {
3077
+ const response = await this._callSingle("/api/insert-asset", {
2586
3078
  assetId,
2587
3079
  parentPath: parentPath || "game.Workspace",
2588
3080
  position
2589
- });
3081
+ }, void 0, instance_id);
2590
3082
  return {
2591
3083
  content: [{
2592
3084
  type: "text",
@@ -2594,15 +3086,15 @@ ${code}`
2594
3086
  }]
2595
3087
  };
2596
3088
  }
2597
- async previewAsset(assetId, includeProperties, maxDepth) {
3089
+ async previewAsset(assetId, includeProperties, maxDepth, instance_id) {
2598
3090
  if (!assetId) {
2599
3091
  throw new Error("Asset ID is required for preview_asset");
2600
3092
  }
2601
- const response = await this.client.request("/api/preview-asset", {
3093
+ const response = await this._callSingle("/api/preview-asset", {
2602
3094
  assetId,
2603
3095
  includeProperties: includeProperties ?? true,
2604
3096
  maxDepth: maxDepth ?? 10
2605
- });
3097
+ }, void 0, instance_id);
2606
3098
  return {
2607
3099
  content: [{
2608
3100
  type: "text",
@@ -2624,7 +3116,7 @@ ${code}`
2624
3116
  return id
2625
3117
  `;
2626
3118
  try {
2627
- const response = await this.client.request("/api/execute-luau", { code }, "edit");
3119
+ const response = await this._callSingle("/api/execute-luau", { code }, "edit", void 0);
2628
3120
  const returnValue = response?.returnValue;
2629
3121
  if (returnValue !== void 0 && returnValue !== null && /^\d+$/.test(String(returnValue))) {
2630
3122
  return String(returnValue);
@@ -2699,17 +3191,17 @@ ${code}`
2699
3191
  }]
2700
3192
  };
2701
3193
  }
2702
- async simulateMouseInput(action, x, y, button, scrollDirection, target) {
3194
+ async simulateMouseInput(action, x, y, button, scrollDirection, target, instance_id) {
2703
3195
  if (!action) {
2704
3196
  throw new Error("action is required for simulate_mouse_input");
2705
3197
  }
2706
- const response = await this.client.request("/api/simulate-mouse-input", {
3198
+ const response = await this._callSingle("/api/simulate-mouse-input", {
2707
3199
  action,
2708
3200
  x,
2709
3201
  y,
2710
3202
  button,
2711
3203
  scrollDirection
2712
- }, target || "edit");
3204
+ }, target || "edit", instance_id);
2713
3205
  return {
2714
3206
  content: [{
2715
3207
  type: "text",
@@ -2717,15 +3209,15 @@ ${code}`
2717
3209
  }]
2718
3210
  };
2719
3211
  }
2720
- async simulateKeyboardInput(keyCode, action, duration, target) {
3212
+ async simulateKeyboardInput(keyCode, action, duration, target, instance_id) {
2721
3213
  if (!keyCode) {
2722
3214
  throw new Error("keyCode is required for simulate_keyboard_input");
2723
3215
  }
2724
- const response = await this.client.request("/api/simulate-keyboard-input", {
3216
+ const response = await this._callSingle("/api/simulate-keyboard-input", {
2725
3217
  keyCode,
2726
3218
  action,
2727
3219
  duration
2728
- }, target || "edit");
3220
+ }, target || "edit", instance_id);
2729
3221
  return {
2730
3222
  content: [{
2731
3223
  type: "text",
@@ -2733,16 +3225,16 @@ ${code}`
2733
3225
  }]
2734
3226
  };
2735
3227
  }
2736
- async characterNavigation(position, instancePath, waitForCompletion, timeout, target) {
3228
+ async characterNavigation(position, instancePath, waitForCompletion, timeout, target, instance_id) {
2737
3229
  if (!position && !instancePath) {
2738
3230
  throw new Error("Either position or instancePath is required for character_navigation");
2739
3231
  }
2740
- const response = await this.client.request("/api/character-navigation", {
3232
+ const response = await this._callSingle("/api/character-navigation", {
2741
3233
  position,
2742
3234
  instancePath,
2743
3235
  waitForCompletion,
2744
3236
  timeout
2745
- }, target || "edit");
3237
+ }, target || "edit", instance_id);
2746
3238
  return {
2747
3239
  content: [{
2748
3240
  type: "text",
@@ -2750,50 +3242,50 @@ ${code}`
2750
3242
  }]
2751
3243
  };
2752
3244
  }
2753
- async cloneObject(instancePath, targetParentPath) {
3245
+ async cloneObject(instancePath, targetParentPath, instance_id) {
2754
3246
  if (!instancePath || !targetParentPath) {
2755
3247
  throw new Error("instancePath and targetParentPath are required for clone_object");
2756
3248
  }
2757
- const response = await this.client.request("/api/clone-object", { instancePath, targetParentPath });
3249
+ const response = await this._callSingle("/api/clone-object", { instancePath, targetParentPath }, void 0, instance_id);
2758
3250
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2759
3251
  }
2760
- async getDescendants(instancePath, maxDepth, classFilter) {
3252
+ async getDescendants(instancePath, maxDepth, classFilter, instance_id) {
2761
3253
  if (!instancePath) {
2762
3254
  throw new Error("instancePath is required for get_descendants");
2763
3255
  }
2764
- const response = await this.client.request("/api/get-descendants", { instancePath, maxDepth, classFilter });
3256
+ const response = await this._callSingle("/api/get-descendants", { instancePath, maxDepth, classFilter }, void 0, instance_id);
2765
3257
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2766
3258
  }
2767
- async compareInstances(instancePathA, instancePathB) {
3259
+ async compareInstances(instancePathA, instancePathB, instance_id) {
2768
3260
  if (!instancePathA || !instancePathB) {
2769
3261
  throw new Error("instancePathA and instancePathB are required for compare_instances");
2770
3262
  }
2771
- const response = await this.client.request("/api/compare-instances", { instancePathA, instancePathB });
3263
+ const response = await this._callSingle("/api/compare-instances", { instancePathA, instancePathB }, void 0, instance_id);
2772
3264
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2773
3265
  }
2774
- async getOutputLog(maxEntries, messageType) {
2775
- const response = await this.client.request("/api/get-output-log", { maxEntries, messageType });
3266
+ async getOutputLog(maxEntries, messageType, instance_id) {
3267
+ const response = await this._callSingle("/api/get-output-log", { maxEntries, messageType }, void 0, instance_id);
2776
3268
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2777
3269
  }
2778
- async bulkSetAttributes(instancePath, attributes) {
3270
+ async bulkSetAttributes(instancePath, attributes, instance_id) {
2779
3271
  if (!instancePath || !attributes) {
2780
3272
  throw new Error("instancePath and attributes are required for bulk_set_attributes");
2781
3273
  }
2782
- const response = await this.client.request("/api/bulk-set-attributes", { instancePath, attributes });
3274
+ const response = await this._callSingle("/api/bulk-set-attributes", { instancePath, attributes }, void 0, instance_id);
2783
3275
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2784
3276
  }
2785
- async findAndReplaceInScripts(pattern, replacement, options) {
3277
+ async findAndReplaceInScripts(pattern, replacement, options, instance_id) {
2786
3278
  if (!pattern) {
2787
3279
  throw new Error("pattern is required for find_and_replace_in_scripts");
2788
3280
  }
2789
3281
  if (replacement === void 0 || replacement === null) {
2790
3282
  throw new Error("replacement is required for find_and_replace_in_scripts");
2791
3283
  }
2792
- const response = await this.client.request("/api/find-and-replace-in-scripts", {
3284
+ const response = await this._callSingle("/api/find-and-replace-in-scripts", {
2793
3285
  pattern,
2794
3286
  replacement,
2795
3287
  ...options
2796
- });
3288
+ }, void 0, instance_id);
2797
3289
  return {
2798
3290
  content: [{
2799
3291
  type: "text",
@@ -2801,24 +3293,27 @@ ${code}`
2801
3293
  }]
2802
3294
  };
2803
3295
  }
2804
- async getMemoryBreakdown(target, tags) {
3296
+ async getMemoryBreakdown(target, tags, instance_id) {
2805
3297
  const tgt = target ?? "all";
2806
3298
  const data = {};
2807
3299
  if (tags !== void 0)
2808
3300
  data.tags = tags;
2809
- if (tgt !== "all") {
2810
- const response = await this.client.request("/api/get-memory-breakdown", data, tgt);
3301
+ const resolved = this.bridge.resolveTarget({ instance_id, target: tgt });
3302
+ if (!resolved.ok)
3303
+ throw new RoutingFailure(resolved.error);
3304
+ if (resolved.mode === "single") {
3305
+ const response = await this.client.request("/api/get-memory-breakdown", data, resolved.targetInstanceId, resolved.targetRole);
2811
3306
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2812
3307
  }
2813
- const targets = this.bridge.getInstances().filter((i) => i.role !== "edit-proxy").map((i) => i.role);
3308
+ const targets = resolved.targets.filter((t) => t.targetRole !== "edit-proxy");
2814
3309
  const responses = await Promise.allSettled(targets.map(async (t) => ({
2815
- peer: t,
2816
- result: await this.client.request("/api/get-memory-breakdown", data, t)
3310
+ peer: t.targetRole,
3311
+ result: await this.client.request("/api/get-memory-breakdown", data, t.targetInstanceId, t.targetRole)
2817
3312
  })));
2818
3313
  const body = {};
2819
3314
  for (let i = 0; i < responses.length; i++) {
2820
3315
  const r = responses[i];
2821
- const peer = targets[i];
3316
+ const peer = targets[i].targetRole;
2822
3317
  if (r.status === "fulfilled") {
2823
3318
  body[peer] = r.value.result;
2824
3319
  } else {
@@ -2827,7 +3322,7 @@ ${code}`
2827
3322
  }
2828
3323
  return { content: [{ type: "text", text: JSON.stringify(body) }] };
2829
3324
  }
2830
- async exportRbxm(instancePaths, outputPath, target) {
3325
+ async exportRbxm(instancePaths, outputPath, target, instance_id) {
2831
3326
  if (!Array.isArray(instancePaths) || instancePaths.length === 0) {
2832
3327
  throw new Error("instance_paths must be a non-empty array for export_rbxm");
2833
3328
  }
@@ -2838,7 +3333,7 @@ ${code}`
2838
3333
  if (tgt !== "edit" && tgt !== "server") {
2839
3334
  throw new Error(`export_rbxm target must be "edit" or "server" (got: ${tgt})`);
2840
3335
  }
2841
- const response = await this.client.request("/api/export-rbxm", { instance_paths: instancePaths }, tgt);
3336
+ const response = await this._callSingle("/api/export-rbxm", { instance_paths: instancePaths }, tgt, instance_id);
2842
3337
  if (response.error) {
2843
3338
  return { content: [{ type: "text", text: JSON.stringify({ error: response.error }) }] };
2844
3339
  }
@@ -2864,7 +3359,7 @@ ${code}`
2864
3359
  }]
2865
3360
  };
2866
3361
  }
2867
- async importRbxm(source, parentPath, target) {
3362
+ async importRbxm(source, parentPath, target, instance_id) {
2868
3363
  if (!source || typeof source !== "object") {
2869
3364
  throw new Error("source is required for import_rbxm");
2870
3365
  }
@@ -2927,15 +3422,15 @@ ${code}`
2927
3422
  }
2928
3423
  sourceLabel = `base64(${bytes.length}B)`;
2929
3424
  }
2930
- const response = await this.client.request("/api/import-rbxm", {
3425
+ const response = await this._callSingle("/api/import-rbxm", {
2931
3426
  base64: bytes.toString("base64"),
2932
3427
  parent_path: parentPath,
2933
3428
  source_label: sourceLabel
2934
- }, tgt);
3429
+ }, tgt, instance_id);
2935
3430
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
2936
3431
  }
2937
- async captureScreenshot() {
2938
- const response = await this.client.request("/api/capture-screenshot", {});
3432
+ async captureScreenshot(instance_id) {
3433
+ const response = await this._callSingle("/api/capture-screenshot", {}, void 0, instance_id);
2939
3434
  if (response.error) {
2940
3435
  return {
2941
3436
  content: [{
@@ -2957,150 +3452,6 @@ ${code}`
2957
3452
  }
2958
3453
  });
2959
3454
 
2960
- // ../core/dist/bridge-service.js
2961
- import { v4 as uuidv4 } from "uuid";
2962
- var STALE_INSTANCE_MS, BridgeService;
2963
- var init_bridge_service = __esm({
2964
- "../core/dist/bridge-service.js"() {
2965
- "use strict";
2966
- STALE_INSTANCE_MS = 3e4;
2967
- BridgeService = class {
2968
- pendingRequests = /* @__PURE__ */ new Map();
2969
- instances = /* @__PURE__ */ new Map();
2970
- requestTimeout = 3e4;
2971
- registerInstance(instanceId, role) {
2972
- let assignedRole = role;
2973
- if (role === "client") {
2974
- const used = /* @__PURE__ */ new Set();
2975
- for (const inst of this.instances.values()) {
2976
- const match = inst.role.match(/^client-(\d+)$/);
2977
- if (match)
2978
- used.add(Number(match[1]));
2979
- }
2980
- let idx = 1;
2981
- while (used.has(idx))
2982
- idx++;
2983
- assignedRole = `client-${idx}`;
2984
- }
2985
- this.instances.set(instanceId, {
2986
- instanceId,
2987
- role: assignedRole,
2988
- lastActivity: Date.now(),
2989
- connectedAt: Date.now()
2990
- });
2991
- return assignedRole;
2992
- }
2993
- unregisterInstance(instanceId) {
2994
- this.instances.delete(instanceId);
2995
- for (const [id, req] of this.pendingRequests.entries()) {
2996
- const targetRole = req.target;
2997
- const hasHandler = Array.from(this.instances.values()).some((i) => i.role === targetRole);
2998
- if (!hasHandler) {
2999
- clearTimeout(req.timeoutId);
3000
- this.pendingRequests.delete(id);
3001
- req.reject(new Error(`Target instance "${targetRole}" disconnected`));
3002
- }
3003
- }
3004
- }
3005
- getInstances() {
3006
- return Array.from(this.instances.values());
3007
- }
3008
- getPendingRequestCount() {
3009
- return this.pendingRequests.size;
3010
- }
3011
- updateInstanceActivity(instanceId) {
3012
- const inst = this.instances.get(instanceId);
3013
- if (inst) {
3014
- inst.lastActivity = Date.now();
3015
- }
3016
- }
3017
- cleanupStaleInstances() {
3018
- const now = Date.now();
3019
- for (const [id, inst] of this.instances.entries()) {
3020
- if (now - inst.lastActivity > STALE_INSTANCE_MS) {
3021
- this.unregisterInstance(id);
3022
- }
3023
- }
3024
- }
3025
- async sendRequest(endpoint, data, target = "edit") {
3026
- const requestId = uuidv4();
3027
- return new Promise((resolve2, reject) => {
3028
- const timeoutId = setTimeout(() => {
3029
- if (this.pendingRequests.has(requestId)) {
3030
- this.pendingRequests.delete(requestId);
3031
- reject(new Error("Request timeout"));
3032
- }
3033
- }, this.requestTimeout);
3034
- const request = {
3035
- id: requestId,
3036
- endpoint,
3037
- data,
3038
- target,
3039
- timestamp: Date.now(),
3040
- resolve: resolve2,
3041
- reject,
3042
- timeoutId
3043
- };
3044
- this.pendingRequests.set(requestId, request);
3045
- });
3046
- }
3047
- getPendingRequest(callerRole = "edit") {
3048
- let oldestRequest = null;
3049
- for (const request of this.pendingRequests.values()) {
3050
- if (request.target !== callerRole)
3051
- continue;
3052
- if (!oldestRequest || request.timestamp < oldestRequest.timestamp) {
3053
- oldestRequest = request;
3054
- }
3055
- }
3056
- if (oldestRequest) {
3057
- return {
3058
- requestId: oldestRequest.id,
3059
- request: {
3060
- endpoint: oldestRequest.endpoint,
3061
- data: oldestRequest.data
3062
- }
3063
- };
3064
- }
3065
- return null;
3066
- }
3067
- resolveRequest(requestId, response) {
3068
- const request = this.pendingRequests.get(requestId);
3069
- if (request) {
3070
- clearTimeout(request.timeoutId);
3071
- this.pendingRequests.delete(requestId);
3072
- request.resolve(response);
3073
- }
3074
- }
3075
- rejectRequest(requestId, error) {
3076
- const request = this.pendingRequests.get(requestId);
3077
- if (request) {
3078
- clearTimeout(request.timeoutId);
3079
- this.pendingRequests.delete(requestId);
3080
- request.reject(error);
3081
- }
3082
- }
3083
- cleanupOldRequests() {
3084
- const now = Date.now();
3085
- for (const [id, request] of this.pendingRequests.entries()) {
3086
- if (now - request.timestamp > this.requestTimeout) {
3087
- clearTimeout(request.timeoutId);
3088
- this.pendingRequests.delete(id);
3089
- request.reject(new Error("Request timeout"));
3090
- }
3091
- }
3092
- }
3093
- clearAllPendingRequests() {
3094
- for (const [, request] of this.pendingRequests.entries()) {
3095
- clearTimeout(request.timeoutId);
3096
- request.reject(new Error("Connection closed"));
3097
- }
3098
- this.pendingRequests.clear();
3099
- }
3100
- };
3101
- }
3102
- });
3103
-
3104
3455
  // ../core/dist/proxy-bridge-service.js
3105
3456
  import { v4 as uuidv42 } from "uuid";
3106
3457
  var ProxyBridgeService;
@@ -3145,14 +3496,20 @@ var init_proxy_bridge_service = __esm({
3145
3496
  this.refreshTimer = void 0;
3146
3497
  }
3147
3498
  }
3148
- async sendRequest(endpoint, data, target = "edit") {
3499
+ async sendRequest(endpoint, data, targetInstanceId, targetRole) {
3149
3500
  const controller = new AbortController();
3150
3501
  const timeoutId = setTimeout(() => controller.abort(), this.proxyRequestTimeout);
3151
3502
  try {
3152
3503
  const response = await fetch(`${this.primaryBaseUrl}/proxy`, {
3153
3504
  method: "POST",
3154
3505
  headers: { "Content-Type": "application/json" },
3155
- body: JSON.stringify({ endpoint, data, target, proxyInstanceId: this.proxyInstanceId }),
3506
+ body: JSON.stringify({
3507
+ endpoint,
3508
+ data,
3509
+ targetInstanceId,
3510
+ targetRole,
3511
+ proxyInstanceId: this.proxyInstanceId
3512
+ }),
3156
3513
  signal: controller.signal
3157
3514
  });
3158
3515
  clearTimeout(timeoutId);
@@ -3236,6 +3593,19 @@ var init_server = __esm({
3236
3593
  try {
3237
3594
  return await handler(this.tools, args ?? {});
3238
3595
  } catch (error) {
3596
+ if (error instanceof RoutingFailure) {
3597
+ return {
3598
+ content: [{
3599
+ type: "text",
3600
+ text: JSON.stringify({
3601
+ error: error.routingError.code,
3602
+ message: error.routingError.message,
3603
+ data: error.routingError.data
3604
+ })
3605
+ }],
3606
+ isError: true
3607
+ };
3608
+ }
3239
3609
  if (error instanceof McpError2)
3240
3610
  throw error;
3241
3611
  throw new McpError2(ErrorCode2.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -3377,6 +3747,10 @@ var init_definitions = __esm({
3377
3747
  path: {
3378
3748
  type: "string",
3379
3749
  description: "Root path (default: game root)"
3750
+ },
3751
+ instance_id: {
3752
+ type: "string",
3753
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3380
3754
  }
3381
3755
  }
3382
3756
  }
@@ -3396,6 +3770,10 @@ var init_definitions = __esm({
3396
3770
  type: "string",
3397
3771
  enum: ["name", "type", "content"],
3398
3772
  description: "Search mode (default: name)"
3773
+ },
3774
+ instance_id: {
3775
+ type: "string",
3776
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3399
3777
  }
3400
3778
  },
3401
3779
  required: ["query"]
@@ -3408,7 +3786,12 @@ var init_definitions = __esm({
3408
3786
  description: "Get place ID, name, and game settings",
3409
3787
  inputSchema: {
3410
3788
  type: "object",
3411
- properties: {}
3789
+ properties: {
3790
+ instance_id: {
3791
+ type: "string",
3792
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3793
+ }
3794
+ }
3412
3795
  }
3413
3796
  },
3414
3797
  {
@@ -3421,6 +3804,10 @@ var init_definitions = __esm({
3421
3804
  serviceName: {
3422
3805
  type: "string",
3423
3806
  description: "Specific service name"
3807
+ },
3808
+ instance_id: {
3809
+ type: "string",
3810
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3424
3811
  }
3425
3812
  }
3426
3813
  }
@@ -3444,6 +3831,10 @@ var init_definitions = __esm({
3444
3831
  propertyName: {
3445
3832
  type: "string",
3446
3833
  description: 'Property name when searchType is "property"'
3834
+ },
3835
+ instance_id: {
3836
+ type: "string",
3837
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3447
3838
  }
3448
3839
  },
3449
3840
  required: ["query"]
@@ -3464,6 +3855,10 @@ var init_definitions = __esm({
3464
3855
  excludeSource: {
3465
3856
  type: "boolean",
3466
3857
  description: "For scripts, return SourceLength/LineCount instead of full source (default: false)"
3858
+ },
3859
+ instance_id: {
3860
+ type: "string",
3861
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3467
3862
  }
3468
3863
  },
3469
3864
  required: ["instancePath"]
@@ -3479,6 +3874,10 @@ var init_definitions = __esm({
3479
3874
  instancePath: {
3480
3875
  type: "string",
3481
3876
  description: "Instance path (dot notation)"
3877
+ },
3878
+ instance_id: {
3879
+ type: "string",
3880
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3482
3881
  }
3483
3882
  },
3484
3883
  required: ["instancePath"]
@@ -3498,6 +3897,10 @@ var init_definitions = __esm({
3498
3897
  propertyValue: {
3499
3898
  type: "string",
3500
3899
  description: "Value to match"
3900
+ },
3901
+ instance_id: {
3902
+ type: "string",
3903
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3501
3904
  }
3502
3905
  },
3503
3906
  required: ["propertyName", "propertyValue"]
@@ -3513,6 +3916,10 @@ var init_definitions = __esm({
3513
3916
  className: {
3514
3917
  type: "string",
3515
3918
  description: "Roblox class name"
3919
+ },
3920
+ instance_id: {
3921
+ type: "string",
3922
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3516
3923
  }
3517
3924
  },
3518
3925
  required: ["className"]
@@ -3537,6 +3944,10 @@ var init_definitions = __esm({
3537
3944
  scriptsOnly: {
3538
3945
  type: "boolean",
3539
3946
  description: "Show only scripts (default: false)"
3947
+ },
3948
+ instance_id: {
3949
+ type: "string",
3950
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3540
3951
  }
3541
3952
  }
3542
3953
  }
@@ -3559,6 +3970,10 @@ var init_definitions = __esm({
3559
3970
  },
3560
3971
  propertyValue: {
3561
3972
  description: "Value to set (string, number, boolean, or object for Vector3/Color3/UDim2)"
3973
+ },
3974
+ instance_id: {
3975
+ type: "string",
3976
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3562
3977
  }
3563
3978
  },
3564
3979
  required: ["instancePath", "propertyName", "propertyValue"]
@@ -3582,6 +3997,10 @@ var init_definitions = __esm({
3582
3997
  },
3583
3998
  propertyValue: {
3584
3999
  description: "Value to set (string, number, boolean, or object for Vector3/Color3/UDim2)"
4000
+ },
4001
+ instance_id: {
4002
+ type: "string",
4003
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3585
4004
  }
3586
4005
  },
3587
4006
  required: ["paths", "propertyName", "propertyValue"]
@@ -3602,6 +4021,10 @@ var init_definitions = __esm({
3602
4021
  propertyName: {
3603
4022
  type: "string",
3604
4023
  description: "Property name"
4024
+ },
4025
+ instance_id: {
4026
+ type: "string",
4027
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3605
4028
  }
3606
4029
  },
3607
4030
  required: ["paths", "propertyName"]
@@ -3621,6 +4044,10 @@ var init_definitions = __esm({
3621
4044
  properties: {
3622
4045
  type: "object",
3623
4046
  description: "Map of property name to value"
4047
+ },
4048
+ instance_id: {
4049
+ type: "string",
4050
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3624
4051
  }
3625
4052
  },
3626
4053
  required: ["instancePath", "properties"]
@@ -3649,6 +4076,10 @@ var init_definitions = __esm({
3649
4076
  properties: {
3650
4077
  type: "object",
3651
4078
  description: "Properties to set on creation"
4079
+ },
4080
+ instance_id: {
4081
+ type: "string",
4082
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3652
4083
  }
3653
4084
  },
3654
4085
  required: ["className", "parent"]
@@ -3686,6 +4117,10 @@ var init_definitions = __esm({
3686
4117
  required: ["className", "parent"]
3687
4118
  },
3688
4119
  description: "Objects to create"
4120
+ },
4121
+ instance_id: {
4122
+ type: "string",
4123
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3689
4124
  }
3690
4125
  },
3691
4126
  required: ["objects"]
@@ -3701,6 +4136,10 @@ var init_definitions = __esm({
3701
4136
  instancePath: {
3702
4137
  type: "string",
3703
4138
  description: "Instance path (dot notation)"
4139
+ },
4140
+ instance_id: {
4141
+ type: "string",
4142
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3704
4143
  }
3705
4144
  },
3706
4145
  required: ["instancePath"]
@@ -3754,6 +4193,10 @@ var init_definitions = __esm({
3754
4193
  description: "Different parent per duplicate"
3755
4194
  }
3756
4195
  }
4196
+ },
4197
+ instance_id: {
4198
+ type: "string",
4199
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3757
4200
  }
3758
4201
  },
3759
4202
  required: ["instancePath", "count"]
@@ -3816,6 +4259,10 @@ var init_definitions = __esm({
3816
4259
  required: ["instancePath", "count"]
3817
4260
  },
3818
4261
  description: "Duplication operations"
4262
+ },
4263
+ instance_id: {
4264
+ type: "string",
4265
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3819
4266
  }
3820
4267
  },
3821
4268
  required: ["duplications"]
@@ -3841,6 +4288,10 @@ var init_definitions = __esm({
3841
4288
  endLine: {
3842
4289
  type: "number",
3843
4290
  description: "End line (inclusive)"
4291
+ },
4292
+ instance_id: {
4293
+ type: "string",
4294
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3844
4295
  }
3845
4296
  },
3846
4297
  required: ["instancePath"]
@@ -3860,6 +4311,10 @@ var init_definitions = __esm({
3860
4311
  source: {
3861
4312
  type: "string",
3862
4313
  description: "New source code"
4314
+ },
4315
+ instance_id: {
4316
+ type: "string",
4317
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3863
4318
  }
3864
4319
  },
3865
4320
  required: ["instancePath", "source"]
@@ -3887,6 +4342,10 @@ var init_definitions = __esm({
3887
4342
  startLine: {
3888
4343
  type: "number",
3889
4344
  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."
4345
+ },
4346
+ instance_id: {
4347
+ type: "string",
4348
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3890
4349
  }
3891
4350
  },
3892
4351
  required: ["instancePath", "old_string", "new_string"]
@@ -3910,6 +4369,10 @@ var init_definitions = __esm({
3910
4369
  newContent: {
3911
4370
  type: "string",
3912
4371
  description: "Content to insert"
4372
+ },
4373
+ instance_id: {
4374
+ type: "string",
4375
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3913
4376
  }
3914
4377
  },
3915
4378
  required: ["instancePath", "newContent"]
@@ -3933,6 +4396,10 @@ var init_definitions = __esm({
3933
4396
  endLine: {
3934
4397
  type: "number",
3935
4398
  description: "End line (inclusive)"
4399
+ },
4400
+ instance_id: {
4401
+ type: "string",
4402
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3936
4403
  }
3937
4404
  },
3938
4405
  required: ["instancePath", "startLine", "endLine"]
@@ -3960,6 +4427,10 @@ var init_definitions = __esm({
3960
4427
  valueType: {
3961
4428
  type: "string",
3962
4429
  description: "Type hint if needed"
4430
+ },
4431
+ instance_id: {
4432
+ type: "string",
4433
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3963
4434
  }
3964
4435
  },
3965
4436
  required: ["instancePath", "attributeName", "attributeValue"]
@@ -3975,6 +4446,10 @@ var init_definitions = __esm({
3975
4446
  instancePath: {
3976
4447
  type: "string",
3977
4448
  description: "Instance path (dot notation)"
4449
+ },
4450
+ instance_id: {
4451
+ type: "string",
4452
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3978
4453
  }
3979
4454
  },
3980
4455
  required: ["instancePath"]
@@ -3994,6 +4469,10 @@ var init_definitions = __esm({
3994
4469
  attributeName: {
3995
4470
  type: "string",
3996
4471
  description: "Attribute name"
4472
+ },
4473
+ instance_id: {
4474
+ type: "string",
4475
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
3997
4476
  }
3998
4477
  },
3999
4478
  required: ["instancePath", "attributeName"]
@@ -4010,6 +4489,10 @@ var init_definitions = __esm({
4010
4489
  instancePath: {
4011
4490
  type: "string",
4012
4491
  description: "Instance path (dot notation)"
4492
+ },
4493
+ instance_id: {
4494
+ type: "string",
4495
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4013
4496
  }
4014
4497
  },
4015
4498
  required: ["instancePath"]
@@ -4029,6 +4512,10 @@ var init_definitions = __esm({
4029
4512
  tagName: {
4030
4513
  type: "string",
4031
4514
  description: "Tag name"
4515
+ },
4516
+ instance_id: {
4517
+ type: "string",
4518
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4032
4519
  }
4033
4520
  },
4034
4521
  required: ["instancePath", "tagName"]
@@ -4048,6 +4535,10 @@ var init_definitions = __esm({
4048
4535
  tagName: {
4049
4536
  type: "string",
4050
4537
  description: "Tag name"
4538
+ },
4539
+ instance_id: {
4540
+ type: "string",
4541
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4051
4542
  }
4052
4543
  },
4053
4544
  required: ["instancePath", "tagName"]
@@ -4063,6 +4554,10 @@ var init_definitions = __esm({
4063
4554
  tagName: {
4064
4555
  type: "string",
4065
4556
  description: "Tag name"
4557
+ },
4558
+ instance_id: {
4559
+ type: "string",
4560
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4066
4561
  }
4067
4562
  },
4068
4563
  required: ["tagName"]
@@ -4075,7 +4570,12 @@ var init_definitions = __esm({
4075
4570
  description: "Get all currently selected objects",
4076
4571
  inputSchema: {
4077
4572
  type: "object",
4078
- properties: {}
4573
+ properties: {
4574
+ instance_id: {
4575
+ type: "string",
4576
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4577
+ }
4578
+ }
4079
4579
  }
4080
4580
  },
4081
4581
  // === Luau Execution ===
@@ -4093,6 +4593,10 @@ var init_definitions = __esm({
4093
4593
  target: {
4094
4594
  type: "string",
4095
4595
  description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
4596
+ },
4597
+ instance_id: {
4598
+ type: "string",
4599
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4096
4600
  }
4097
4601
  },
4098
4602
  required: ["code"]
@@ -4108,6 +4612,10 @@ var init_definitions = __esm({
4108
4612
  code: {
4109
4613
  type: "string",
4110
4614
  description: "Luau code to execute. Use return ... to get a value back."
4615
+ },
4616
+ instance_id: {
4617
+ type: "string",
4618
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4111
4619
  }
4112
4620
  },
4113
4621
  required: ["code"]
@@ -4127,6 +4635,10 @@ var init_definitions = __esm({
4127
4635
  target: {
4128
4636
  type: "string",
4129
4637
  description: 'Client target: "client-1" (default), "client-2", etc.'
4638
+ },
4639
+ instance_id: {
4640
+ type: "string",
4641
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4130
4642
  }
4131
4643
  },
4132
4644
  required: ["code"]
@@ -4176,6 +4688,10 @@ var init_definitions = __esm({
4176
4688
  type: "string",
4177
4689
  enum: ["Script", "LocalScript", "ModuleScript"],
4178
4690
  description: "Only search scripts of this class type"
4691
+ },
4692
+ instance_id: {
4693
+ type: "string",
4694
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4179
4695
  }
4180
4696
  },
4181
4697
  required: ["pattern"]
@@ -4197,6 +4713,10 @@ var init_definitions = __esm({
4197
4713
  numPlayers: {
4198
4714
  type: "number",
4199
4715
  description: "Number of client players (1-8). Triggers server + clients mode via TestService."
4716
+ },
4717
+ instance_id: {
4718
+ type: "string",
4719
+ 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
4720
  }
4201
4721
  },
4202
4722
  required: ["mode"]
@@ -4208,7 +4728,12 @@ var init_definitions = __esm({
4208
4728
  description: "Stop playtest and return all captured output.",
4209
4729
  inputSchema: {
4210
4730
  type: "object",
4211
- properties: {}
4731
+ properties: {
4732
+ instance_id: {
4733
+ type: "string",
4734
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4735
+ }
4736
+ }
4212
4737
  }
4213
4738
  },
4214
4739
  {
@@ -4221,6 +4746,10 @@ var init_definitions = __esm({
4221
4746
  target: {
4222
4747
  type: "string",
4223
4748
  description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
4749
+ },
4750
+ instance_id: {
4751
+ type: "string",
4752
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4224
4753
  }
4225
4754
  }
4226
4755
  }
@@ -4247,6 +4776,10 @@ var init_definitions = __esm({
4247
4776
  filter: {
4248
4777
  type: "string",
4249
4778
  description: "Plain substring matched against each entry's message (no pattern semantics; literal text). Applied after since, before tail."
4779
+ },
4780
+ instance_id: {
4781
+ type: "string",
4782
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4250
4783
  }
4251
4784
  }
4252
4785
  }
@@ -4268,7 +4801,12 @@ var init_definitions = __esm({
4268
4801
  description: "Undo the last change in Roblox Studio. Uses ChangeHistoryService to reverse the most recent operation.",
4269
4802
  inputSchema: {
4270
4803
  type: "object",
4271
- properties: {}
4804
+ properties: {
4805
+ instance_id: {
4806
+ type: "string",
4807
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4808
+ }
4809
+ }
4272
4810
  }
4273
4811
  },
4274
4812
  {
@@ -4277,7 +4815,12 @@ var init_definitions = __esm({
4277
4815
  description: "Redo the last undone change in Roblox Studio. Uses ChangeHistoryService to reapply the most recently undone operation.",
4278
4816
  inputSchema: {
4279
4817
  type: "object",
4280
- properties: {}
4818
+ properties: {
4819
+ instance_id: {
4820
+ type: "string",
4821
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4822
+ }
4823
+ }
4281
4824
  }
4282
4825
  },
4283
4826
  // === Build Library ===
@@ -4300,6 +4843,10 @@ var init_definitions = __esm({
4300
4843
  type: "string",
4301
4844
  enum: ["medieval", "modern", "nature", "scifi", "misc"],
4302
4845
  description: "Style category for the build (default: misc)"
4846
+ },
4847
+ instance_id: {
4848
+ type: "string",
4849
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4303
4850
  }
4304
4851
  },
4305
4852
  required: ["instancePath"]
@@ -4449,6 +4996,10 @@ part(0,2,0,2,1,1,"b")`,
4449
4996
  type: "array",
4450
4997
  items: { type: "number" },
4451
4998
  description: "World position offset [X, Y, Z]"
4999
+ },
5000
+ instance_id: {
5001
+ type: "string",
5002
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4452
5003
  }
4453
5004
  },
4454
5005
  required: ["buildData", "targetPath"]
@@ -4483,6 +5034,10 @@ part(0,2,0,2,1,1,"b")`,
4483
5034
  maxResults: {
4484
5035
  type: "number",
4485
5036
  description: "Max results to return (default: 50)"
5037
+ },
5038
+ instance_id: {
5039
+ type: "string",
5040
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4486
5041
  }
4487
5042
  }
4488
5043
  }
@@ -4567,6 +5122,10 @@ part(0,2,0,2,1,1,"b")`,
4567
5122
  targetPath: {
4568
5123
  type: "string",
4569
5124
  description: "Parent instance path for the scene (default: game.Workspace)"
5125
+ },
5126
+ instance_id: {
5127
+ type: "string",
5128
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4570
5129
  }
4571
5130
  },
4572
5131
  required: ["sceneData"]
@@ -4664,6 +5223,10 @@ part(0,2,0,2,1,1,"b")`,
4664
5223
  z: { type: "number" }
4665
5224
  },
4666
5225
  description: "Optional world position to place the asset"
5226
+ },
5227
+ instance_id: {
5228
+ type: "string",
5229
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4667
5230
  }
4668
5231
  },
4669
5232
  required: ["assetId"]
@@ -4687,6 +5250,10 @@ part(0,2,0,2,1,1,"b")`,
4687
5250
  maxDepth: {
4688
5251
  type: "number",
4689
5252
  description: "Max hierarchy traversal depth (default: 10)"
5253
+ },
5254
+ instance_id: {
5255
+ type: "string",
5256
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4690
5257
  }
4691
5258
  },
4692
5259
  required: ["assetId"]
@@ -4734,7 +5301,12 @@ part(0,2,0,2,1,1,"b")`,
4734
5301
  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.',
4735
5302
  inputSchema: {
4736
5303
  type: "object",
4737
- properties: {}
5304
+ properties: {
5305
+ instance_id: {
5306
+ type: "string",
5307
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
5308
+ }
5309
+ }
4738
5310
  }
4739
5311
  },
4740
5312
  // === Input Simulation ===
@@ -4771,6 +5343,10 @@ part(0,2,0,2,1,1,"b")`,
4771
5343
  target: {
4772
5344
  type: "string",
4773
5345
  description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
5346
+ },
5347
+ instance_id: {
5348
+ type: "string",
5349
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4774
5350
  }
4775
5351
  },
4776
5352
  required: ["action", "x", "y"]
@@ -4799,6 +5375,10 @@ part(0,2,0,2,1,1,"b")`,
4799
5375
  target: {
4800
5376
  type: "string",
4801
5377
  description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
5378
+ },
5379
+ instance_id: {
5380
+ type: "string",
5381
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4802
5382
  }
4803
5383
  },
4804
5384
  required: ["keyCode"]
@@ -4832,6 +5412,10 @@ part(0,2,0,2,1,1,"b")`,
4832
5412
  target: {
4833
5413
  type: "string",
4834
5414
  description: 'Instance target: "edit" (default), "server", "client-1", "client-2", etc.'
5415
+ },
5416
+ instance_id: {
5417
+ type: "string",
5418
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4835
5419
  }
4836
5420
  }
4837
5421
  }
@@ -4851,6 +5435,10 @@ part(0,2,0,2,1,1,"b")`,
4851
5435
  targetParentPath: {
4852
5436
  type: "string",
4853
5437
  description: "Path of the parent to place the clone under"
5438
+ },
5439
+ instance_id: {
5440
+ type: "string",
5441
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4854
5442
  }
4855
5443
  },
4856
5444
  required: ["instancePath", "targetParentPath"]
@@ -4875,6 +5463,10 @@ part(0,2,0,2,1,1,"b")`,
4875
5463
  classFilter: {
4876
5464
  type: "string",
4877
5465
  description: 'Only include instances of this class (uses IsA, so "BasePart" matches Part, MeshPart, etc.)'
5466
+ },
5467
+ instance_id: {
5468
+ type: "string",
5469
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4878
5470
  }
4879
5471
  },
4880
5472
  required: ["instancePath"]
@@ -4894,6 +5486,10 @@ part(0,2,0,2,1,1,"b")`,
4894
5486
  instancePathB: {
4895
5487
  type: "string",
4896
5488
  description: "Second instance path"
5489
+ },
5490
+ instance_id: {
5491
+ type: "string",
5492
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4897
5493
  }
4898
5494
  },
4899
5495
  required: ["instancePathA", "instancePathB"]
@@ -4914,6 +5510,10 @@ part(0,2,0,2,1,1,"b")`,
4914
5510
  messageType: {
4915
5511
  type: "string",
4916
5512
  description: 'Filter by message type (e.g. "Enum.MessageType.MessageOutput", "Enum.MessageType.MessageWarning", "Enum.MessageType.MessageError")'
5513
+ },
5514
+ instance_id: {
5515
+ type: "string",
5516
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4917
5517
  }
4918
5518
  }
4919
5519
  }
@@ -4933,6 +5533,10 @@ part(0,2,0,2,1,1,"b")`,
4933
5533
  attributes: {
4934
5534
  type: "object",
4935
5535
  description: "Map of attribute names to values. Supports Vector3, Color3, UDim2 via _type convention."
5536
+ },
5537
+ instance_id: {
5538
+ type: "string",
5539
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4936
5540
  }
4937
5541
  },
4938
5542
  required: ["instancePath", "attributes"]
@@ -4954,6 +5558,10 @@ part(0,2,0,2,1,1,"b")`,
4954
5558
  type: "array",
4955
5559
  items: { type: "string" },
4956
5560
  description: "Optional DeveloperMemoryTag whitelist. Unknown tag names return 0 + unknown_tags list."
5561
+ },
5562
+ instance_id: {
5563
+ type: "string",
5564
+ 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
5565
  }
4958
5566
  }
4959
5567
  }
@@ -4979,6 +5587,10 @@ part(0,2,0,2,1,1,"b")`,
4979
5587
  type: "string",
4980
5588
  enum: ["edit", "server"],
4981
5589
  description: 'Which DataModel to read from (default: "edit"). "server" serializes live runtime state during a playtest.'
5590
+ },
5591
+ instance_id: {
5592
+ type: "string",
5593
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
4982
5594
  }
4983
5595
  },
4984
5596
  required: ["instance_paths", "output_path"]
@@ -5013,6 +5625,10 @@ part(0,2,0,2,1,1,"b")`,
5013
5625
  type: "string",
5014
5626
  enum: ["edit", "server"],
5015
5627
  description: 'Which DataModel to import into (default: "edit"). "server" parents into the live play-server DM.'
5628
+ },
5629
+ instance_id: {
5630
+ type: "string",
5631
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
5016
5632
  }
5017
5633
  },
5018
5634
  required: ["source", "parent_path"]
@@ -5058,6 +5674,10 @@ part(0,2,0,2,1,1,"b")`,
5058
5674
  maxReplacements: {
5059
5675
  type: "number",
5060
5676
  description: "Safety limit on total replacements (default: 1000)"
5677
+ },
5678
+ instance_id: {
5679
+ type: "string",
5680
+ description: "Which connected Studio place to target. Required when multiple places are connected; omit when one. Use get_connected_instances to list available IDs."
5061
5681
  }
5062
5682
  },
5063
5683
  required: ["pattern", "replacement"]