@justanothermldude/mcp-exec 1.3.6 → 1.4.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
@@ -11,6 +11,10 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprot
11
11
 
12
12
  // dist/tools/list-servers.js
13
13
  import { listServers } from "@justanothermldude/meta-mcp-core";
14
+ function escapeMarkdownCell(text) {
15
+ return text.replace(/\|/g, "\\|");
16
+ }
17
+ __name(escapeMarkdownCell, "escapeMarkdownCell");
14
18
  var listAvailableMcpServersTool = {
15
19
  name: "list_available_mcp_servers",
16
20
  description: "List available MCP servers with their names, descriptions, and tags. Optionally filter by name or tag.",
@@ -45,8 +49,37 @@ function createListServersHandler() {
45
49
  return false;
46
50
  });
47
51
  }
52
+ if (servers.length === 0) {
53
+ const noMatchMsg = filter ? `No servers matched filter: '${filter}'. Try list_available_mcp_servers without a filter.` : "No servers are configured. Check that servers.json is properly set up.";
54
+ return {
55
+ content: [{ type: "text", text: noMatchMsg }],
56
+ isError: false
57
+ };
58
+ }
59
+ const rows = servers.map((server) => {
60
+ const desc = (() => {
61
+ if (!server.description)
62
+ return "";
63
+ if (server.description.length <= 80)
64
+ return server.description;
65
+ let truncated = server.description.substring(0, 77);
66
+ truncated = truncated.replace(/[\uD800-\uDBFF]$/, "");
67
+ return truncated + "...";
68
+ })();
69
+ let tagsStr = "";
70
+ if (server.tags && server.tags.length > 0) {
71
+ const displayTags = server.tags.slice(0, 5);
72
+ const hasMore = server.tags.length > 5;
73
+ tagsStr = displayTags.join(", ") + (hasMore ? ` +${server.tags.length - 5} more` : "");
74
+ }
75
+ const escapedName = escapeMarkdownCell(server.name);
76
+ const escapedDesc = escapeMarkdownCell(desc);
77
+ const escapedTags = escapeMarkdownCell(tagsStr);
78
+ return `| \`${escapedName}\` | ${escapedDesc} | ${escapedTags} |`;
79
+ });
80
+ const table = "## Available MCP Servers\n\n| Name | Description | Tags |\n|------|-------------|------|\n" + rows.join("\n") + '\n\nTo use a server, pass its exact name in the `wrappers` array of `execute_code_with_wrappers`.\nExample: `wrappers: ["adobe-mcp-gateway"]`\n\nTo see tools on a server: `get_mcp_tool_schema({ server: "adobe-mcp-gateway" })`';
48
81
  return {
49
- content: [{ type: "text", text: JSON.stringify(servers, null, 2) }],
82
+ content: [{ type: "text", text: table }],
50
83
  isError: false
51
84
  };
52
85
  } catch (error) {
@@ -164,12 +197,29 @@ function isGetToolSchemaInput(args2) {
164
197
  }
165
198
  __name(isGetToolSchemaInput, "isGetToolSchemaInput");
166
199
 
200
+ // dist/tools/execute-with-wrappers.js
201
+ import { listServers as listServers3 } from "@justanothermldude/meta-mcp-core";
202
+
167
203
  // dist/codegen/wrapper-generator.js
168
204
  var BRIDGE_ENDPOINT = "http://127.0.0.1:3000/call";
169
- function jsonSchemaToTs(prop, _required = true) {
205
+ function jsonSchemaToTs(prop, _required = true, definitions, visited = /* @__PURE__ */ new Set()) {
170
206
  if (!prop) {
171
207
  return "unknown";
172
208
  }
209
+ if (prop.$ref) {
210
+ const refKey = prop.$ref.replace(/^#\/(definitions|\$defs)\//, "");
211
+ if (definitions && refKey in definitions) {
212
+ if (visited.has(refKey))
213
+ return "unknown";
214
+ const nextVisited = new Set(visited);
215
+ nextVisited.add(refKey);
216
+ return jsonSchemaToTs(definitions[refKey], _required, definitions, nextVisited);
217
+ }
218
+ return "unknown";
219
+ }
220
+ if ("const" in prop && prop.const !== void 0) {
221
+ return JSON.stringify(prop.const);
222
+ }
173
223
  if (Array.isArray(prop.type)) {
174
224
  const types = prop.type.map((t) => primitiveToTs(t));
175
225
  return types.join(" | ");
@@ -177,20 +227,60 @@ function jsonSchemaToTs(prop, _required = true) {
177
227
  if (prop.enum) {
178
228
  return prop.enum.map((v) => JSON.stringify(v)).join(" | ");
179
229
  }
230
+ if (prop.oneOf) {
231
+ const types = prop.oneOf.map((s) => jsonSchemaToTs(s, _required, definitions, visited)).filter((t) => t !== "unknown");
232
+ if (types.length > 0) {
233
+ return types.join(" | ");
234
+ }
235
+ }
236
+ if (prop.anyOf) {
237
+ const types = prop.anyOf.map((s) => jsonSchemaToTs(s, _required, definitions, visited)).filter((t) => t !== "unknown");
238
+ if (types.length > 0) {
239
+ return types.join(" | ");
240
+ }
241
+ }
242
+ if (prop.allOf) {
243
+ const types = prop.allOf.map((s) => jsonSchemaToTs(s, _required, definitions, visited)).filter((t) => t !== "unknown");
244
+ if (types.length > 0) {
245
+ const primitives = ["string", "number", "boolean", "null"];
246
+ const incompatible = types.filter((t) => primitives.includes(t)).length > 1;
247
+ if (incompatible)
248
+ return "unknown";
249
+ return types.join(" & ");
250
+ }
251
+ }
180
252
  if (prop.type === "object" && prop.properties) {
181
253
  const propLines = Object.entries(prop.properties).map(([key, value]) => {
182
254
  const isRequired = prop.required?.includes(key) ?? false;
183
- const tsType = jsonSchemaToTs(value, isRequired);
255
+ const tsType = jsonSchemaToTs(value, isRequired, definitions, visited);
184
256
  const optionalMark = isRequired ? "" : "?";
185
257
  return `${key}${optionalMark}: ${tsType}`;
186
258
  });
259
+ if (prop.additionalProperties === true) {
260
+ propLines.push("[key: string]: unknown");
261
+ } else if (typeof prop.additionalProperties === "object" && prop.additionalProperties !== null) {
262
+ propLines.push("[key: string]: unknown");
263
+ } else if (Object.keys(prop.properties).length === 0 && !prop.additionalProperties) {
264
+ return "Record<string, unknown>";
265
+ }
187
266
  return `{ ${propLines.join("; ")} }`;
188
267
  }
268
+ if (prop.type === "object") {
269
+ if (prop.additionalProperties === true || typeof prop.additionalProperties === "object" && prop.additionalProperties !== null) {
270
+ return "Record<string, unknown>";
271
+ }
272
+ }
189
273
  if (prop.type === "array") {
190
- const itemType = prop.items ? jsonSchemaToTs(prop.items, true) : "unknown";
274
+ const itemType = prop.items ? jsonSchemaToTs(prop.items, true, definitions, visited) : "unknown";
191
275
  return `${itemType}[]`;
192
276
  }
193
- return primitiveToTs(prop.type ?? "unknown");
277
+ let result = primitiveToTs(prop.type ?? "unknown");
278
+ if (prop.nullable === true) {
279
+ if (result !== "unknown" && !result.includes("| null") && !result.includes("null |")) {
280
+ result = `${result} | null`;
281
+ }
282
+ }
283
+ return result;
194
284
  }
195
285
  __name(jsonSchemaToTs, "jsonSchemaToTs");
196
286
  function primitiveToTs(type) {
@@ -213,7 +303,8 @@ function primitiveToTs(type) {
213
303
  }
214
304
  }
215
305
  __name(primitiveToTs, "primitiveToTs");
216
- function generateInterface(name, schema) {
306
+ function generateInterface(name, schema, definitions) {
307
+ const defs = definitions || schema.definitions;
217
308
  if (!schema.properties || Object.keys(schema.properties).length === 0) {
218
309
  return `interface ${name} {}`;
219
310
  }
@@ -223,7 +314,7 @@ function generateInterface(name, schema) {
223
314
  const prop = propValue;
224
315
  const isRequired = schema.required?.includes(propName) ?? false;
225
316
  const optionalMark = isRequired ? "" : "?";
226
- const tsType = jsonSchemaToTs(prop, isRequired);
317
+ const tsType = jsonSchemaToTs(prop, isRequired, defs);
227
318
  if (prop.description) {
228
319
  lines.push(` /** ${sanitizeJsDoc(prop.description)} */`);
229
320
  }
@@ -318,10 +409,10 @@ function toPascalCase(name) {
318
409
  return name.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
319
410
  }
320
411
  __name(toPascalCase, "toPascalCase");
321
- function generateToolInterface(tool) {
412
+ function generateToolInterface(tool, rootDefinitions) {
322
413
  const interfaceName = `${toPascalCase(tool.name)}Input`;
323
414
  if (tool.inputSchema?.properties && Object.keys(tool.inputSchema.properties).length > 0) {
324
- return generateInterface(interfaceName, tool.inputSchema);
415
+ return generateInterface(interfaceName, tool.inputSchema, rootDefinitions);
325
416
  }
326
417
  return "";
327
418
  }
@@ -333,17 +424,41 @@ function generateMethodDefinition(tool, serverName, bridgePort) {
333
424
  const inputParam = hasInput ? `input: ${interfaceName}` : "";
334
425
  const inputArg = hasInput ? "input" : "{}";
335
426
  const lines = [];
427
+ const requiredParams = [];
428
+ const optionalParams = [];
429
+ if (tool.inputSchema?.properties) {
430
+ for (const propName of Object.keys(tool.inputSchema.properties)) {
431
+ if (tool.inputSchema.required?.includes(propName)) {
432
+ requiredParams.push(propName);
433
+ } else {
434
+ optionalParams.push(propName);
435
+ }
436
+ }
437
+ }
336
438
  if (tool.description) {
337
439
  lines.push(" /**");
338
440
  lines.push(` * ${sanitizeJsDoc(tool.description)}`);
339
441
  if (tool.inputSchema?.properties) {
340
442
  for (const [propName, propValue] of Object.entries(tool.inputSchema.properties)) {
341
443
  const prop = propValue;
444
+ const isRequired = tool.inputSchema.required?.includes(propName) ?? false;
445
+ const marker = isRequired ? "[required]" : "[optional]";
342
446
  if (prop.description) {
343
- lines.push(` * @param input.${propName} - ${sanitizeJsDoc(prop.description)}`);
447
+ lines.push(` * @param input.${propName} ${marker} - ${sanitizeJsDoc(prop.description)}`);
448
+ } else {
449
+ lines.push(` * @param input.${propName} ${marker}`);
344
450
  }
345
451
  }
346
452
  }
453
+ if (requiredParams.length > 0 || optionalParams.length > 0) {
454
+ lines.push(` * Required: ${requiredParams.length > 0 ? requiredParams.join(", ") : "none"}`);
455
+ if (optionalParams.length > 0) {
456
+ lines.push(` * Optional: ${optionalParams.join(", ")}`);
457
+ }
458
+ }
459
+ const safeServerName2 = JSON.stringify(serverName);
460
+ const safeToolName2 = JSON.stringify(tool.name);
461
+ lines.push(` * @returns Promise<unknown>. To inspect response shape: get_mcp_tool_schema({ server: ${safeServerName2}, tool: ${safeToolName2} })`);
347
462
  lines.push(" */");
348
463
  }
349
464
  const safeServerName = JSON.stringify(serverName);
@@ -359,15 +474,15 @@ function generateMethodDefinition(tool, serverName, bridgePort) {
359
474
  lines.push(` }),`);
360
475
  lines.push(` });`);
361
476
  lines.push(` if (!response.ok) {`);
362
- lines.push(" throw new Error(`Tool call failed: ${response.statusText}`);");
477
+ lines.push(` throw new Error(\`[${safeServerName}.${safeToolName}] HTTP \${response.status}: \${response.statusText}\`);`);
363
478
  lines.push(` }`);
364
479
  lines.push(` const data = await response.json() as { success: boolean; content?: unknown; error?: string; isError?: boolean };`);
365
480
  lines.push(` if (!data.success) {`);
366
- lines.push(` throw new Error(data.error || 'Tool call failed');`);
481
+ lines.push(` throw new Error(\`[${safeServerName}.${safeToolName}] \${data.error || 'Tool call failed'}\`);`);
367
482
  lines.push(` }`);
368
483
  lines.push(` if (data.isError) {`);
369
484
  lines.push(` const errText = Array.isArray(data.content) && data.content[0]?.text ? data.content[0].text : 'Tool returned isError';`);
370
- lines.push(` throw new Error(errText);`);
485
+ lines.push(` throw new Error(\`[${safeServerName}.${safeToolName}] \${errText}\`);`);
371
486
  lines.push(` }`);
372
487
  lines.push(` // Auto-parse JSON from MCP text content blocks for convenience`);
373
488
  lines.push(` const content = data.content;`);
@@ -435,9 +550,9 @@ function generateToolWrapper(tool, serverName) {
435
550
  return lines.join("\n");
436
551
  }
437
552
  __name(generateToolWrapper, "generateToolWrapper");
438
- function generateServerModule(tools, serverName, bridgePort = 3e3) {
553
+ function generateServerModule(tools, serverName, bridgePort = 3e3, variableNameOverride) {
439
554
  const lines = [];
440
- const namespaceName = sanitizeIdentifier(serverName);
555
+ const namespaceName = variableNameOverride ?? sanitizeIdentifier(serverName);
441
556
  lines.push("/**");
442
557
  lines.push(` * Auto-generated TypeScript wrappers for ${serverName} MCP server tools.`);
443
558
  lines.push(` * Case-insensitive: methodName, method_name, and method-name all work.`);
@@ -446,7 +561,8 @@ function generateServerModule(tools, serverName, bridgePort = 3e3) {
446
561
  lines.push("");
447
562
  lines.push(generateFieldGuard());
448
563
  for (const tool of tools) {
449
- const interfaceCode = generateToolInterface(tool);
564
+ const rootDefs = tool.inputSchema?.definitions ?? tool.inputSchema?.$defs;
565
+ const interfaceCode = generateToolInterface(tool, rootDefs);
450
566
  if (interfaceCode) {
451
567
  lines.push(interfaceCode);
452
568
  lines.push("");
@@ -464,8 +580,51 @@ function generateServerModule(tools, serverName, bridgePort = 3e3) {
464
580
  return lines.join("\n");
465
581
  }
466
582
  __name(generateServerModule, "generateServerModule");
583
+ function generateMcpDictionaryFromMap(serverNames, nameMap) {
584
+ const lines = [];
585
+ lines.push("/**");
586
+ lines.push(" * MCP Server Dictionary - Access all MCP servers via case-agnostic lookup.");
587
+ lines.push(` * Available: ${serverNames.map((n) => `mcp['${n}']`).join(", ")}`);
588
+ lines.push(" */");
589
+ lines.push("");
590
+ lines.push("const mcp_servers_raw: Record<string, unknown> = {");
591
+ for (const serverName of serverNames) {
592
+ const varName = nameMap.get(serverName) ?? sanitizeIdentifier(serverName);
593
+ lines.push(` ${JSON.stringify(serverName)}: ${varName},`);
594
+ }
595
+ lines.push("};");
596
+ lines.push("");
597
+ lines.push(`const mcp = ${generateFuzzyProxy("mcp_servers_raw", "mcp")};`);
598
+ lines.push("");
599
+ return lines.join("\n");
600
+ }
601
+ __name(generateMcpDictionaryFromMap, "generateMcpDictionaryFromMap");
467
602
  function generateMcpDictionary(serverNames) {
468
603
  const lines = [];
604
+ const sanitizedToOriginal = /* @__PURE__ */ new Map();
605
+ const uniqueNames = /* @__PURE__ */ new Map();
606
+ for (let i = 0; i < serverNames.length; i++) {
607
+ const originalName = serverNames[i];
608
+ const sanitized = sanitizeIdentifier(originalName);
609
+ if (!sanitizedToOriginal.has(sanitized)) {
610
+ sanitizedToOriginal.set(sanitized, []);
611
+ }
612
+ sanitizedToOriginal.get(sanitized).push(originalName);
613
+ }
614
+ let collisionIndex = 0;
615
+ for (const originalName of serverNames) {
616
+ const sanitized = sanitizeIdentifier(originalName);
617
+ const conflictingNames = sanitizedToOriginal.get(sanitized);
618
+ if (conflictingNames.length > 1) {
619
+ const uniqueName = `${sanitized}_${collisionIndex}`;
620
+ uniqueNames.set(originalName, uniqueName);
621
+ lines.push(`// WARNING: Server name collision: '${originalName}' and previous server both sanitize to '${sanitized}'. Using '${uniqueName}'.`);
622
+ collisionIndex++;
623
+ } else {
624
+ uniqueNames.set(originalName, sanitized);
625
+ }
626
+ }
627
+ lines.push("");
469
628
  const sanitizedNames = serverNames.map(sanitizeIdentifier);
470
629
  lines.push("/**");
471
630
  lines.push(" * MCP Server Dictionary - Access all MCP servers via case-agnostic lookup.");
@@ -475,8 +634,8 @@ function generateMcpDictionary(serverNames) {
475
634
  lines.push("");
476
635
  lines.push("const mcp_servers_raw: Record<string, unknown> = {");
477
636
  for (const serverName of serverNames) {
478
- const sanitized = sanitizeIdentifier(serverName);
479
- lines.push(` ${JSON.stringify(serverName)}: ${sanitized},`);
637
+ const uniqueName = uniqueNames.get(serverName);
638
+ lines.push(` ${JSON.stringify(serverName)}: ${uniqueName},`);
480
639
  }
481
640
  lines.push("};");
482
641
  lines.push("");
@@ -1128,45 +1287,70 @@ var MCPBridge = class {
1128
1287
  handleCallRequest(req, res) {
1129
1288
  let body = "";
1130
1289
  let bodySize = 0;
1290
+ let responseSent = false;
1131
1291
  req.on("data", (chunk) => {
1132
1292
  bodySize += chunk.length;
1133
1293
  if (bodySize > MAX_REQUEST_BODY_SIZE) {
1134
1294
  req.destroy();
1135
- this.sendError(res, 413, `Request body too large. Maximum size is ${MAX_REQUEST_BODY_SIZE / 1024 / 1024}MB`);
1295
+ if (!responseSent) {
1296
+ responseSent = true;
1297
+ this.sendError(res, 413, `Request body too large. Maximum size is ${MAX_REQUEST_BODY_SIZE / 1024 / 1024}MB`);
1298
+ }
1136
1299
  return;
1137
1300
  }
1138
1301
  body += chunk.toString();
1139
1302
  });
1140
1303
  req.on("end", async () => {
1304
+ if (responseSent)
1305
+ return;
1141
1306
  try {
1142
1307
  let request;
1143
1308
  try {
1144
1309
  request = JSON.parse(body);
1145
1310
  } catch {
1146
- this.sendError(res, 400, "Invalid JSON body");
1311
+ if (!responseSent) {
1312
+ responseSent = true;
1313
+ this.sendError(res, 400, "Invalid JSON body");
1314
+ }
1147
1315
  return;
1148
1316
  }
1149
1317
  if (!request.server || typeof request.server !== "string") {
1150
- this.sendError(res, 400, 'Missing or invalid "server" field');
1318
+ if (!responseSent) {
1319
+ responseSent = true;
1320
+ this.sendError(res, 400, 'Missing or invalid "server" field');
1321
+ }
1151
1322
  return;
1152
1323
  }
1153
1324
  if (!request.tool || typeof request.tool !== "string") {
1154
- this.sendError(res, 400, 'Missing or invalid "tool" field');
1325
+ if (!responseSent) {
1326
+ responseSent = true;
1327
+ this.sendError(res, 400, 'Missing or invalid "tool" field');
1328
+ }
1155
1329
  return;
1156
1330
  }
1157
1331
  if (request.args !== void 0 && (typeof request.args !== "object" || request.args === null || Array.isArray(request.args))) {
1158
- this.sendError(res, 400, '"args" must be an object');
1332
+ if (!responseSent) {
1333
+ responseSent = true;
1334
+ this.sendError(res, 400, '"args" must be an object');
1335
+ }
1159
1336
  return;
1160
1337
  }
1161
1338
  let connection;
1162
1339
  try {
1163
1340
  connection = await this.pool.getConnection(request.server);
1164
1341
  } catch (err) {
1165
- const errorMsg = err instanceof Error ? err.message : String(err);
1166
- if (errorMsg.includes("not found") || errorMsg.includes("No server configured") || errorMsg.includes("Unknown server")) {
1167
- this.sendError(res, 404, buildServerNotFoundError(request.server));
1168
- } else {
1169
- this.sendError(res, 502, buildConnectionError(request.server, errorMsg));
1342
+ if (!responseSent) {
1343
+ responseSent = true;
1344
+ const errorMsg = err instanceof Error ? err.message : String(err);
1345
+ if (errorMsg.includes("not found") || errorMsg.includes("No server configured") || errorMsg.includes("Unknown server")) {
1346
+ this.sendError(res, 404, buildServerNotFoundError(request.server));
1347
+ } else if (errorMsg.includes("403") || errorMsg.includes("Forbidden") || errorMsg.includes("PermissionError") || errorMsg.includes("Unauthorized")) {
1348
+ this.sendError(res, 403, `Authentication failed for server '${request.server}'. Check X-MCP-Client header and backend auth config.`);
1349
+ } else if (errorMsg.includes("503") || errorMsg.includes("Service Unavailable")) {
1350
+ this.sendError(res, 503, `Server '${request.server}' is unavailable. It may be starting up \u2014 retry in a few seconds.`);
1351
+ } else {
1352
+ this.sendError(res, 502, buildConnectionError(request.server, errorMsg));
1353
+ }
1170
1354
  }
1171
1355
  return;
1172
1356
  }
@@ -1183,36 +1367,52 @@ var MCPBridge = class {
1183
1367
  // resultSchema
1184
1368
  timeout ? { timeout } : void 0
1185
1369
  );
1186
- const response = {
1187
- success: true,
1188
- content: result.content,
1189
- isError: result.isError
1190
- };
1191
- res.writeHead(200);
1192
- res.end(JSON.stringify(response));
1370
+ if (!responseSent) {
1371
+ responseSent = true;
1372
+ const response = {
1373
+ success: true,
1374
+ content: result.content,
1375
+ isError: result.isError
1376
+ };
1377
+ res.writeHead(200);
1378
+ res.end(JSON.stringify(response));
1379
+ }
1193
1380
  } catch (err) {
1194
- const errorMsg = err instanceof Error ? err.message : String(err);
1195
- if (errorMsg.includes("not found") || errorMsg.includes("Unknown tool") || errorMsg.includes("no such tool")) {
1196
- try {
1197
- const tools = await connection.getTools();
1198
- const toolNames = tools.map((t) => t.name);
1199
- this.sendError(res, 404, buildToolNotFoundError(request.server, request.tool, toolNames));
1200
- } catch {
1201
- this.sendError(res, 500, `Tool '${request.tool}' not found on server '${request.server}'. Unable to fetch available tools.`);
1381
+ if (!responseSent) {
1382
+ responseSent = true;
1383
+ const errorMsg = err instanceof Error ? err.message : String(err);
1384
+ if (errorMsg.includes("not found") || errorMsg.includes("Unknown tool") || errorMsg.includes("no such tool")) {
1385
+ try {
1386
+ const tools = await connection.getTools();
1387
+ const toolNames = tools.map((t) => t.name);
1388
+ this.sendError(res, 404, buildToolNotFoundError(request.server, request.tool, toolNames));
1389
+ } catch {
1390
+ this.sendError(res, 500, `Tool '${request.tool}' not found on server '${request.server}'. Unable to fetch available tools.`);
1391
+ }
1392
+ } else if (errorMsg.includes("403") || errorMsg.includes("Forbidden") || errorMsg.includes("PermissionError") || errorMsg.includes("Unauthorized")) {
1393
+ this.sendError(res, 403, `Authorization failed calling '${request.tool}' on '${request.server}': ${errorMsg}. Ensure X-MCP-Client header is being sent.`);
1394
+ } else if (errorMsg.includes("503") || errorMsg.includes("504") || errorMsg.includes("Gateway")) {
1395
+ this.sendError(res, 503, `Server '${request.server}' temporarily unavailable during tool '${request.tool}'. Retry after a short delay.`);
1396
+ } else {
1397
+ this.sendError(res, 500, `[${request.server}.${request.tool}] Tool execution failed: ${errorMsg}`);
1202
1398
  }
1203
- } else {
1204
- this.sendError(res, 500, `Tool execution failed: ${errorMsg}`);
1205
1399
  }
1206
1400
  } finally {
1207
1401
  this.pool.releaseConnection(request.server);
1208
1402
  }
1209
1403
  } catch (err) {
1210
- const errorMsg = err instanceof Error ? err.message : String(err);
1211
- this.sendError(res, 500, `Internal error: ${errorMsg}`);
1404
+ if (!responseSent) {
1405
+ responseSent = true;
1406
+ const errorMsg = err instanceof Error ? err.message : String(err);
1407
+ this.sendError(res, 500, `Internal error: ${errorMsg}`);
1408
+ }
1212
1409
  }
1213
1410
  });
1214
1411
  req.on("error", (err) => {
1215
- this.sendError(res, 400, `Request error: ${err.message}`);
1412
+ if (!responseSent) {
1413
+ responseSent = true;
1414
+ this.sendError(res, 400, `Request error: ${err.message}`);
1415
+ }
1216
1416
  });
1217
1417
  }
1218
1418
  /**
@@ -1266,6 +1466,89 @@ var executeCodeWithWrappersTool = {
1266
1466
  required: ["code", "wrappers"]
1267
1467
  }
1268
1468
  };
1469
+ function buildServerListString() {
1470
+ try {
1471
+ const servers = listServers3();
1472
+ if (!servers || servers.length === 0) {
1473
+ return "";
1474
+ }
1475
+ const serverLines = servers.slice(0, 7).map((s) => {
1476
+ const desc = s.description || "No description";
1477
+ return ` \u2022 ${s.name} \u2014 ${desc}`;
1478
+ });
1479
+ const moreCount = Math.max(0, servers.length - 7);
1480
+ const moreStr = moreCount > 0 ? `
1481
+ [+${moreCount} more]` : "";
1482
+ return `
1483
+ Available MCP servers (use exact names in wrappers array):
1484
+ ${serverLines.join("\n")}${moreStr}
1485
+
1486
+ Use list_available_mcp_servers to discover tools on each server before calling execute_code_with_wrappers.`;
1487
+ } catch {
1488
+ return "";
1489
+ }
1490
+ }
1491
+ __name(buildServerListString, "buildServerListString");
1492
+ function createExecuteCodeWithWrappersToolDefinition() {
1493
+ const baseDescription = 'Execute TypeScript/JavaScript code with auto-generated typed wrappers for specified MCP servers. Provides a typed API like github.createIssue({ title: "..." }) instead of raw mcp.callTool(). Multi-line code is supported - format naturally for readability.';
1494
+ const serverList = buildServerListString();
1495
+ const environmentNote = '\n\nExecution environment: Node.js (not browser). Top-level await is supported.\n- Use ES modules syntax (import/export NOT supported \u2014 modules are pre-injected as namespace vars)\n- DO NOT use require(), __dirname, __filename, or browser APIs (window, document, localStorage)\n- Available globals: fetch (Node 18+), console, process, Buffer, setTimeout, clearTimeout\n- Server namespaces are pre-injected: use serverName.toolName(args) directly\n- mcp is the server dictionary: mcp["server-name"].toolName(args) for dynamic lookup\n- Low-level fallback (avoid if typed wrapper exists): globalThis.mcp.callTool(serverName, toolName, args)';
1496
+ return {
1497
+ name: "execute_code_with_wrappers",
1498
+ description: baseDescription + serverList + environmentNote,
1499
+ inputSchema: {
1500
+ type: "object",
1501
+ properties: {
1502
+ code: {
1503
+ type: "string",
1504
+ description: "The TypeScript/JavaScript code to execute. Multi-line supported - format for readability."
1505
+ },
1506
+ wrappers: {
1507
+ type: "array",
1508
+ items: { type: "string" },
1509
+ description: "Array of MCP server names to generate typed wrappers for"
1510
+ },
1511
+ timeout_ms: {
1512
+ type: "number",
1513
+ description: `Maximum execution time in milliseconds (default: ${DEFAULT_TIMEOUT_MS})`
1514
+ }
1515
+ },
1516
+ required: ["code", "wrappers"]
1517
+ }
1518
+ };
1519
+ }
1520
+ __name(createExecuteCodeWithWrappersToolDefinition, "createExecuteCodeWithWrappersToolDefinition");
1521
+ function sanitizeIdentifier2(name) {
1522
+ let s = name.replace(/[^a-zA-Z0-9_]/g, "_");
1523
+ if (/^[0-9]/.test(s)) {
1524
+ s = "_" + s;
1525
+ }
1526
+ return s;
1527
+ }
1528
+ __name(sanitizeIdentifier2, "sanitizeIdentifier");
1529
+ function buildUniqueNameMap(names) {
1530
+ const sanitizedToOriginals = /* @__PURE__ */ new Map();
1531
+ for (const name of names) {
1532
+ const s = sanitizeIdentifier2(name);
1533
+ if (!sanitizedToOriginals.has(s)) {
1534
+ sanitizedToOriginals.set(s, []);
1535
+ }
1536
+ sanitizedToOriginals.get(s).push(name);
1537
+ }
1538
+ const nameMap = /* @__PURE__ */ new Map();
1539
+ for (const name of names) {
1540
+ const s = sanitizeIdentifier2(name);
1541
+ const group = sanitizedToOriginals.get(s);
1542
+ if (group.length === 1) {
1543
+ nameMap.set(name, s);
1544
+ } else {
1545
+ const idx = group.indexOf(name);
1546
+ nameMap.set(name, `${s}_${idx}`);
1547
+ }
1548
+ }
1549
+ return nameMap;
1550
+ }
1551
+ __name(buildUniqueNameMap, "buildUniqueNameMap");
1269
1552
  function isExecuteWithWrappersInput(args2) {
1270
1553
  return typeof args2 === "object" && args2 !== null && "code" in args2 && typeof args2.code === "string" && "wrappers" in args2 && Array.isArray(args2.wrappers) && args2.wrappers.every((w) => typeof w === "string");
1271
1554
  }
@@ -1300,33 +1583,6 @@ ${indent}}`;
1300
1583
  return lines.join("\n");
1301
1584
  }
1302
1585
  __name(wrapUserCodeForReturnCapture, "wrapUserCodeForReturnCapture");
1303
- function getMcpPreamble(bridgePort) {
1304
- return `
1305
- // MCP helper for calling tools via HTTP bridge
1306
- declare global {
1307
- var mcp: {
1308
- callTool: (server: string, tool: string, args?: Record<string, unknown>) => Promise<unknown[]>;
1309
- };
1310
- }
1311
-
1312
- globalThis.mcp = {
1313
- callTool: async (server: string, tool: string, args: Record<string, unknown> = {}) => {
1314
- const response = await fetch('http://127.0.0.1:${bridgePort}/call', {
1315
- method: 'POST',
1316
- headers: { 'Content-Type': 'application/json' },
1317
- body: JSON.stringify({ server, tool, args }),
1318
- });
1319
- const data = await response.json() as { success: boolean; content?: unknown[]; error?: string };
1320
- if (!data.success) {
1321
- throw new Error(data.error || 'MCP tool call failed');
1322
- }
1323
- return data.content || [];
1324
- },
1325
- };
1326
-
1327
- `;
1328
- }
1329
- __name(getMcpPreamble, "getMcpPreamble");
1330
1586
  function createExecuteWithWrappersHandler(pool, config = {}) {
1331
1587
  const preferredPort = config.bridgeConfig?.port ?? 3e3;
1332
1588
  let activeBridge = null;
@@ -1341,6 +1597,7 @@ function createExecuteWithWrappersHandler(pool, config = {}) {
1341
1597
  }
1342
1598
  __name(stopActiveBridge, "stopActiveBridge");
1343
1599
  async function executeWithWrappersHandler(args2) {
1600
+ const TOOLS_FETCH_TIMEOUT_MS = 15e3;
1344
1601
  const { code, wrappers, timeout_ms = DEFAULT_TIMEOUT_MS } = args2;
1345
1602
  if (!code || typeof code !== "string") {
1346
1603
  return {
@@ -1375,17 +1632,36 @@ function createExecuteWithWrappersHandler(pool, config = {}) {
1375
1632
  try {
1376
1633
  await bridge.start();
1377
1634
  const actualPort = bridge.getPort();
1635
+ const uniqueNameMap = buildUniqueNameMap(wrappers);
1378
1636
  const wrapperModules = [];
1379
1637
  for (const serverName of wrappers) {
1638
+ let connection;
1380
1639
  try {
1381
- const connection = await pool.getConnection(serverName);
1382
- const tools = await connection.getTools();
1383
- const moduleCode = generateServerModule(tools, serverName, actualPort);
1640
+ connection = await pool.getConnection(serverName);
1641
+ const conn = connection;
1642
+ const tools = await new Promise((resolve, reject) => {
1643
+ const timeoutHandle = setTimeout(() => {
1644
+ reject(new Error(`Timed out fetching tools from server '${serverName}' after ${TOOLS_FETCH_TIMEOUT_MS}ms`));
1645
+ }, TOOLS_FETCH_TIMEOUT_MS);
1646
+ conn.getTools().then((result2) => {
1647
+ clearTimeout(timeoutHandle);
1648
+ resolve(result2);
1649
+ }, (err) => {
1650
+ clearTimeout(timeoutHandle);
1651
+ reject(err);
1652
+ });
1653
+ });
1654
+ const uniqueName = uniqueNameMap.get(serverName) ?? sanitizeIdentifier2(serverName);
1655
+ const moduleCode = generateServerModule(tools, serverName, actualPort, uniqueName);
1384
1656
  wrapperModules.push(moduleCode);
1385
1657
  pool.releaseConnection(serverName);
1386
1658
  } catch (serverError) {
1387
1659
  const errorMessage = serverError instanceof Error ? serverError.message : String(serverError);
1660
+ if (connection !== void 0) {
1661
+ pool.releaseConnection(serverName);
1662
+ }
1388
1663
  await bridge.stop();
1664
+ activeBridge = null;
1389
1665
  return {
1390
1666
  content: [{ type: "text", text: `Error generating wrapper for server '${serverName}': ${errorMessage}` }],
1391
1667
  isError: true
@@ -1393,14 +1669,12 @@ function createExecuteWithWrappersHandler(pool, config = {}) {
1393
1669
  }
1394
1670
  }
1395
1671
  const generatedWrappers = wrapperModules.join("\n\n");
1396
- const mcpDictionary = generateMcpDictionary(wrappers);
1397
- const mcpPreamble = getMcpPreamble(actualPort);
1672
+ const mcpDictionary = generateMcpDictionaryFromMap(wrappers, uniqueNameMap);
1398
1673
  const instrumentedCode = wrapUserCodeForReturnCapture(code);
1399
1674
  const fullCode = `${generatedWrappers}
1400
1675
 
1401
1676
  ${mcpDictionary}
1402
1677
 
1403
- ${mcpPreamble}
1404
1678
  ${instrumentedCode}`;
1405
1679
  const sandboxConfig = {
1406
1680
  ...config.sandboxConfig,
@@ -1478,7 +1752,7 @@ function createMcpExecServer(pool, config = {}) {
1478
1752
  const tools = [
1479
1753
  listAvailableMcpServersTool,
1480
1754
  getMcpToolSchemaTool,
1481
- executeCodeWithWrappersTool
1755
+ createExecuteCodeWithWrappersToolDefinition()
1482
1756
  ];
1483
1757
  const listToolsHandler = /* @__PURE__ */ __name(async () => ({ tools }), "listToolsHandler");
1484
1758
  const callToolRequestHandler = /* @__PURE__ */ __name(async (params) => {
@@ -1549,7 +1823,7 @@ __name(createMcpExecServer, "createMcpExecServer");
1549
1823
  // dist/index.js
1550
1824
  import { ServerPool, createConnection, getServerConfig as getServerConfig2, loadServerManifest as loadServerManifest2, cleanupOrphanedProcesses } from "@justanothermldude/meta-mcp-core";
1551
1825
  var APP_NAME = "mcp-exec";
1552
- var VERSION2 = "1.3.6";
1826
+ var VERSION2 = "1.3.5";
1553
1827
  var defaultExecutor = null;
1554
1828
  function getDefaultExecutor() {
1555
1829
  if (!defaultExecutor) {
@@ -1651,6 +1925,7 @@ export {
1651
1925
  cleanupStaleProcess,
1652
1926
  createDefaultFilesystemConfig,
1653
1927
  createDefaultNetworkConfig,
1928
+ createExecuteCodeWithWrappersToolDefinition,
1654
1929
  createExecuteWithWrappersHandler,
1655
1930
  createExecutor,
1656
1931
  createGetToolSchemaHandler,
@@ -1662,6 +1937,7 @@ export {
1662
1937
  executeCodeWithWrappersTool,
1663
1938
  generateFromManifest,
1664
1939
  generateMcpDictionary,
1940
+ generateMcpDictionaryFromMap,
1665
1941
  generateServerModule,
1666
1942
  generateToolWrapper,
1667
1943
  getMcpToolSchemaTool,