@justanothermldude/mcp-exec 1.1.0 → 1.2.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/README.md +56 -0
- package/dist/codegen/index.d.ts +1 -1
- package/dist/codegen/index.d.ts.map +1 -1
- package/dist/codegen/index.js +1 -1
- package/dist/codegen/index.js.map +1 -1
- package/dist/codegen/wrapper-generator.d.ts +27 -0
- package/dist/codegen/wrapper-generator.d.ts.map +1 -1
- package/dist/codegen/wrapper-generator.js +83 -4
- package/dist/codegen/wrapper-generator.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1539 -106
- package/dist/index.js.map +1 -1
- package/dist/sandbox/executor.js +1 -1
- package/dist/tools/execute-with-wrappers.d.ts.map +1 -1
- package/dist/tools/execute-with-wrappers.js +5 -4
- package/dist/tools/execute-with-wrappers.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,67 +1,1475 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// dist/index.js
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
|
|
7
|
+
// dist/server.js
|
|
8
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
|
|
11
|
+
// dist/tools/list-servers.js
|
|
12
|
+
import { listServers } from "@justanothermldude/meta-mcp-core";
|
|
13
|
+
var listAvailableMcpServersTool = {
|
|
14
|
+
name: "list_available_mcp_servers",
|
|
15
|
+
description: "List available MCP servers with their names, descriptions, and tags. Optionally filter by name or tag.",
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
filter: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Optional filter string to match server names, descriptions, or tags"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
required: []
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function createListServersHandler() {
|
|
28
|
+
return /* @__PURE__ */ __name(async function listServersHandler(args2) {
|
|
29
|
+
const { filter } = args2;
|
|
30
|
+
try {
|
|
31
|
+
let servers = listServers();
|
|
32
|
+
if (filter && typeof filter === "string") {
|
|
33
|
+
const filterLower = filter.toLowerCase();
|
|
34
|
+
servers = servers.filter((server) => {
|
|
35
|
+
if (server.name.toLowerCase().includes(filterLower)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (server.description?.toLowerCase().includes(filterLower)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (server.tags?.some((tag) => tag.toLowerCase().includes(filterLower))) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
content: [{ type: "text", text: JSON.stringify(servers, null, 2) }],
|
|
49
|
+
isError: false
|
|
50
|
+
};
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: `Error listing servers: ${errorMessage}` }],
|
|
55
|
+
isError: true
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}, "listServersHandler");
|
|
59
|
+
}
|
|
60
|
+
__name(createListServersHandler, "createListServersHandler");
|
|
61
|
+
function isListServersInput(args2) {
|
|
62
|
+
if (typeof args2 !== "object" || args2 === null) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const input = args2;
|
|
66
|
+
if ("filter" in input && typeof input.filter !== "string") {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
__name(isListServersInput, "isListServersInput");
|
|
72
|
+
|
|
73
|
+
// dist/tools/get-tool-schema.js
|
|
74
|
+
var getMcpToolSchemaTool = {
|
|
75
|
+
name: "get_mcp_tool_schema",
|
|
76
|
+
description: "Get the full schema for a specific MCP tool, or list all tools on a server. Omit tool param to discover available tools.",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
server: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "The name of the MCP server that has the tool"
|
|
83
|
+
},
|
|
84
|
+
tool: {
|
|
85
|
+
type: "string",
|
|
86
|
+
description: "The name of the tool to get the schema for. Omit to list all tools."
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
required: ["server"]
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
function createGetToolSchemaHandler(pool) {
|
|
93
|
+
return /* @__PURE__ */ __name(async function getToolSchemaHandler(args2) {
|
|
94
|
+
const { server, tool } = args2;
|
|
95
|
+
try {
|
|
96
|
+
const connection = await pool.getConnection(server);
|
|
97
|
+
const tools = await connection.getTools();
|
|
98
|
+
if (!tool) {
|
|
99
|
+
const summary = tools.map((t) => ({
|
|
100
|
+
name: t.name,
|
|
101
|
+
description: t.description || ""
|
|
102
|
+
}));
|
|
103
|
+
return {
|
|
104
|
+
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }],
|
|
105
|
+
isError: false
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const matchedTool = tools.find((t) => t.name === tool);
|
|
109
|
+
if (!matchedTool) {
|
|
110
|
+
const availableTools = tools.map((t) => t.name).sort();
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: `Tool '${tool}' not found on server '${server}'. Available tools: ${availableTools.join(", ")}`
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
isError: true
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: "text", text: JSON.stringify(matchedTool, null, 2) }],
|
|
123
|
+
isError: false
|
|
124
|
+
};
|
|
125
|
+
} catch (error) {
|
|
126
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
127
|
+
if (errorMessage.includes("not found") || errorMessage.includes("unknown server")) {
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: `Error connecting to server '${server}': ${errorMessage}. Use list_available_mcp_servers to see available servers.`
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
isError: true
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
content: [
|
|
140
|
+
{
|
|
141
|
+
type: "text",
|
|
142
|
+
text: `Error getting tool schema: ${errorMessage}`
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
isError: true
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}, "getToolSchemaHandler");
|
|
149
|
+
}
|
|
150
|
+
__name(createGetToolSchemaHandler, "createGetToolSchemaHandler");
|
|
151
|
+
function isGetToolSchemaInput(args2) {
|
|
152
|
+
if (typeof args2 !== "object" || args2 === null) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const input = args2;
|
|
156
|
+
if (typeof input.server !== "string") {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
if (input.tool !== void 0 && typeof input.tool !== "string") {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
__name(isGetToolSchemaInput, "isGetToolSchemaInput");
|
|
165
|
+
|
|
166
|
+
// dist/codegen/wrapper-generator.js
|
|
167
|
+
var BRIDGE_ENDPOINT = "http://127.0.0.1:3000/call";
|
|
168
|
+
function jsonSchemaToTs(prop, _required = true) {
|
|
169
|
+
if (!prop) {
|
|
170
|
+
return "unknown";
|
|
171
|
+
}
|
|
172
|
+
if (Array.isArray(prop.type)) {
|
|
173
|
+
const types = prop.type.map((t) => primitiveToTs(t));
|
|
174
|
+
return types.join(" | ");
|
|
175
|
+
}
|
|
176
|
+
if (prop.enum) {
|
|
177
|
+
return prop.enum.map((v) => JSON.stringify(v)).join(" | ");
|
|
178
|
+
}
|
|
179
|
+
if (prop.type === "object" && prop.properties) {
|
|
180
|
+
const propLines = Object.entries(prop.properties).map(([key, value]) => {
|
|
181
|
+
const isRequired = prop.required?.includes(key) ?? false;
|
|
182
|
+
const tsType = jsonSchemaToTs(value, isRequired);
|
|
183
|
+
const optionalMark = isRequired ? "" : "?";
|
|
184
|
+
return `${key}${optionalMark}: ${tsType}`;
|
|
185
|
+
});
|
|
186
|
+
return `{ ${propLines.join("; ")} }`;
|
|
187
|
+
}
|
|
188
|
+
if (prop.type === "array") {
|
|
189
|
+
const itemType = prop.items ? jsonSchemaToTs(prop.items, true) : "unknown";
|
|
190
|
+
return `${itemType}[]`;
|
|
191
|
+
}
|
|
192
|
+
return primitiveToTs(prop.type ?? "unknown");
|
|
193
|
+
}
|
|
194
|
+
__name(jsonSchemaToTs, "jsonSchemaToTs");
|
|
195
|
+
function primitiveToTs(type) {
|
|
196
|
+
switch (type) {
|
|
197
|
+
case "string":
|
|
198
|
+
return "string";
|
|
199
|
+
case "number":
|
|
200
|
+
case "integer":
|
|
201
|
+
return "number";
|
|
202
|
+
case "boolean":
|
|
203
|
+
return "boolean";
|
|
204
|
+
case "null":
|
|
205
|
+
return "null";
|
|
206
|
+
case "object":
|
|
207
|
+
return "Record<string, unknown>";
|
|
208
|
+
case "array":
|
|
209
|
+
return "unknown[]";
|
|
210
|
+
default:
|
|
211
|
+
return "unknown";
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
__name(primitiveToTs, "primitiveToTs");
|
|
215
|
+
function generateInterface(name, schema) {
|
|
216
|
+
if (!schema.properties || Object.keys(schema.properties).length === 0) {
|
|
217
|
+
return `interface ${name} {}`;
|
|
218
|
+
}
|
|
219
|
+
const lines = [];
|
|
220
|
+
lines.push(`interface ${name} {`);
|
|
221
|
+
for (const [propName, propValue] of Object.entries(schema.properties)) {
|
|
222
|
+
const prop = propValue;
|
|
223
|
+
const isRequired = schema.required?.includes(propName) ?? false;
|
|
224
|
+
const optionalMark = isRequired ? "" : "?";
|
|
225
|
+
const tsType = jsonSchemaToTs(prop, isRequired);
|
|
226
|
+
if (prop.description) {
|
|
227
|
+
lines.push(` /** ${prop.description} */`);
|
|
228
|
+
}
|
|
229
|
+
lines.push(` ${propName}${optionalMark}: ${tsType};`);
|
|
230
|
+
}
|
|
231
|
+
lines.push("}");
|
|
232
|
+
return lines.join("\n");
|
|
233
|
+
}
|
|
234
|
+
__name(generateInterface, "generateInterface");
|
|
235
|
+
function sanitizeIdentifier(name) {
|
|
236
|
+
let sanitized = name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
237
|
+
if (/^[0-9]/.test(sanitized)) {
|
|
238
|
+
sanitized = "_" + sanitized;
|
|
239
|
+
}
|
|
240
|
+
return sanitized;
|
|
241
|
+
}
|
|
242
|
+
__name(sanitizeIdentifier, "sanitizeIdentifier");
|
|
243
|
+
function normalizeName(name) {
|
|
244
|
+
return name.toLowerCase().replace(/[_-]/g, "");
|
|
245
|
+
}
|
|
246
|
+
__name(normalizeName, "normalizeName");
|
|
247
|
+
function generateFuzzyProxy(targetVarName, contextName) {
|
|
248
|
+
const safeContextName = JSON.stringify(contextName);
|
|
249
|
+
return `new Proxy(${targetVarName}, {
|
|
250
|
+
get(target, prop) {
|
|
251
|
+
if (typeof prop !== 'string') return undefined;
|
|
252
|
+
|
|
253
|
+
// Fast-path: exact match
|
|
254
|
+
if (prop in target) return target[prop];
|
|
255
|
+
|
|
256
|
+
// Fuzzy match: normalize and search
|
|
257
|
+
const normalizedProp = prop.toLowerCase().replace(/[_-]/g, '');
|
|
258
|
+
for (const key of Object.keys(target)) {
|
|
259
|
+
const normalizedKey = key.toLowerCase().replace(/[_-]/g, '');
|
|
260
|
+
if (normalizedKey === normalizedProp) {
|
|
261
|
+
return target[key];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// No match found - throw helpful error
|
|
266
|
+
const available = Object.keys(target).join(', ');
|
|
267
|
+
throw new TypeError(\`Property "\${prop}" not found on ${safeContextName}. Available: \${available}\`);
|
|
268
|
+
}
|
|
269
|
+
})`;
|
|
270
|
+
}
|
|
271
|
+
__name(generateFuzzyProxy, "generateFuzzyProxy");
|
|
272
|
+
function toPascalCase(name) {
|
|
273
|
+
return name.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
274
|
+
}
|
|
275
|
+
__name(toPascalCase, "toPascalCase");
|
|
276
|
+
function generateToolInterface(tool) {
|
|
277
|
+
const interfaceName = `${toPascalCase(tool.name)}Input`;
|
|
278
|
+
if (tool.inputSchema?.properties && Object.keys(tool.inputSchema.properties).length > 0) {
|
|
279
|
+
return generateInterface(interfaceName, tool.inputSchema);
|
|
280
|
+
}
|
|
281
|
+
return "";
|
|
282
|
+
}
|
|
283
|
+
__name(generateToolInterface, "generateToolInterface");
|
|
284
|
+
function generateMethodDefinition(tool, serverName, bridgePort) {
|
|
285
|
+
const methodName = sanitizeIdentifier(tool.name);
|
|
286
|
+
const interfaceName = `${toPascalCase(tool.name)}Input`;
|
|
287
|
+
const hasInput = tool.inputSchema?.properties && Object.keys(tool.inputSchema.properties).length > 0;
|
|
288
|
+
const inputParam = hasInput ? `input: ${interfaceName}` : "";
|
|
289
|
+
const inputArg = hasInput ? "input" : "{}";
|
|
290
|
+
const lines = [];
|
|
291
|
+
if (tool.description) {
|
|
292
|
+
lines.push(" /**");
|
|
293
|
+
lines.push(` * ${tool.description}`);
|
|
294
|
+
if (tool.inputSchema?.properties) {
|
|
295
|
+
for (const [propName, propValue] of Object.entries(tool.inputSchema.properties)) {
|
|
296
|
+
const prop = propValue;
|
|
297
|
+
if (prop.description) {
|
|
298
|
+
lines.push(` * @param input.${propName} - ${prop.description}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
lines.push(" */");
|
|
303
|
+
}
|
|
304
|
+
const safeServerName = JSON.stringify(serverName);
|
|
305
|
+
const safeToolName = JSON.stringify(tool.name);
|
|
306
|
+
lines.push(` ${methodName}: async (${inputParam}): Promise<unknown> => {`);
|
|
307
|
+
lines.push(` const response = await fetch('http://127.0.0.1:${bridgePort}/call', {`);
|
|
308
|
+
lines.push(` method: 'POST',`);
|
|
309
|
+
lines.push(` headers: { 'Content-Type': 'application/json' },`);
|
|
310
|
+
lines.push(` body: JSON.stringify({`);
|
|
311
|
+
lines.push(` server: ${safeServerName},`);
|
|
312
|
+
lines.push(` tool: ${safeToolName},`);
|
|
313
|
+
lines.push(` args: ${inputArg},`);
|
|
314
|
+
lines.push(` }),`);
|
|
315
|
+
lines.push(` });`);
|
|
316
|
+
lines.push(` if (!response.ok) {`);
|
|
317
|
+
lines.push(" throw new Error(`Tool call failed: ${response.statusText}`);");
|
|
318
|
+
lines.push(` }`);
|
|
319
|
+
lines.push(` const data = await response.json() as { success: boolean; content?: unknown; error?: string };`);
|
|
320
|
+
lines.push(` if (!data.success) {`);
|
|
321
|
+
lines.push(` throw new Error(data.error || 'Tool call failed');`);
|
|
322
|
+
lines.push(` }`);
|
|
323
|
+
lines.push(` // Auto-parse JSON from MCP text content blocks for convenience`);
|
|
324
|
+
lines.push(` const content = data.content;`);
|
|
325
|
+
lines.push(` if (Array.isArray(content) && content.length === 1 && content[0]?.type === 'text') {`);
|
|
326
|
+
lines.push(` try {`);
|
|
327
|
+
lines.push(` return JSON.parse(content[0].text);`);
|
|
328
|
+
lines.push(` } catch {`);
|
|
329
|
+
lines.push(` return content[0].text;`);
|
|
330
|
+
lines.push(` }`);
|
|
331
|
+
lines.push(` }`);
|
|
332
|
+
lines.push(` return content;`);
|
|
333
|
+
lines.push(` },`);
|
|
334
|
+
return lines.join("\n");
|
|
335
|
+
}
|
|
336
|
+
__name(generateMethodDefinition, "generateMethodDefinition");
|
|
337
|
+
function generateToolWrapper(tool, serverName) {
|
|
338
|
+
const funcName = sanitizeIdentifier(tool.name);
|
|
339
|
+
const interfaceName = `${toPascalCase(tool.name)}Input`;
|
|
340
|
+
const lines = [];
|
|
341
|
+
if (tool.inputSchema?.properties && Object.keys(tool.inputSchema.properties).length > 0) {
|
|
342
|
+
lines.push(generateInterface(interfaceName, tool.inputSchema));
|
|
343
|
+
lines.push("");
|
|
344
|
+
}
|
|
345
|
+
if (tool.description) {
|
|
346
|
+
lines.push("/**");
|
|
347
|
+
lines.push(` * ${tool.description}`);
|
|
348
|
+
if (tool.inputSchema?.properties) {
|
|
349
|
+
lines.push(" *");
|
|
350
|
+
for (const [propName, propValue] of Object.entries(tool.inputSchema.properties)) {
|
|
351
|
+
const prop = propValue;
|
|
352
|
+
if (prop.description) {
|
|
353
|
+
lines.push(` * @param input.${propName} - ${prop.description}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
lines.push(" * @returns Promise resolving to tool result");
|
|
358
|
+
lines.push(" */");
|
|
359
|
+
}
|
|
360
|
+
const hasInput = tool.inputSchema?.properties && Object.keys(tool.inputSchema.properties).length > 0;
|
|
361
|
+
const inputParam = hasInput ? `input: ${interfaceName}` : "";
|
|
362
|
+
const inputArg = hasInput ? "input" : "{}";
|
|
363
|
+
lines.push(`async function ${funcName}(${inputParam}): Promise<unknown> {`);
|
|
364
|
+
lines.push(` const response = await fetch('${BRIDGE_ENDPOINT}', {`);
|
|
365
|
+
lines.push(` method: 'POST',`);
|
|
366
|
+
lines.push(` headers: { 'Content-Type': 'application/json' },`);
|
|
367
|
+
lines.push(` body: JSON.stringify({`);
|
|
368
|
+
lines.push(` server: '${serverName}',`);
|
|
369
|
+
lines.push(` tool: '${tool.name}',`);
|
|
370
|
+
lines.push(` args: ${inputArg},`);
|
|
371
|
+
lines.push(` }),`);
|
|
372
|
+
lines.push(` });`);
|
|
373
|
+
lines.push("");
|
|
374
|
+
lines.push(" if (!response.ok) {");
|
|
375
|
+
lines.push(" throw new Error(`Tool call failed: ${response.statusText}`);");
|
|
376
|
+
lines.push(" }");
|
|
377
|
+
lines.push("");
|
|
378
|
+
lines.push(" return response.json();");
|
|
379
|
+
lines.push("}");
|
|
380
|
+
return lines.join("\n");
|
|
381
|
+
}
|
|
382
|
+
__name(generateToolWrapper, "generateToolWrapper");
|
|
383
|
+
function generateServerModule(tools, serverName, bridgePort = 3e3) {
|
|
384
|
+
const lines = [];
|
|
385
|
+
const namespaceName = sanitizeIdentifier(serverName);
|
|
386
|
+
lines.push("/**");
|
|
387
|
+
lines.push(` * Auto-generated TypeScript wrappers for ${serverName} MCP server tools.`);
|
|
388
|
+
lines.push(` * Case-insensitive: methodName, method_name, and method-name all work.`);
|
|
389
|
+
lines.push(` * Access tools via: ${namespaceName}.methodName()`);
|
|
390
|
+
lines.push(" */");
|
|
391
|
+
lines.push("");
|
|
392
|
+
for (const tool of tools) {
|
|
393
|
+
const interfaceCode = generateToolInterface(tool);
|
|
394
|
+
if (interfaceCode) {
|
|
395
|
+
lines.push(interfaceCode);
|
|
396
|
+
lines.push("");
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
lines.push(`const ${namespaceName}_raw = {`);
|
|
400
|
+
for (let i = 0; i < tools.length; i++) {
|
|
401
|
+
const tool = tools[i];
|
|
402
|
+
lines.push(generateMethodDefinition(tool, serverName, bridgePort));
|
|
403
|
+
}
|
|
404
|
+
lines.push("};");
|
|
405
|
+
lines.push("");
|
|
406
|
+
lines.push(`const ${namespaceName} = ${generateFuzzyProxy(`${namespaceName}_raw`, serverName)};`);
|
|
407
|
+
lines.push("");
|
|
408
|
+
return lines.join("\n");
|
|
409
|
+
}
|
|
410
|
+
__name(generateServerModule, "generateServerModule");
|
|
411
|
+
function generateMcpDictionary(serverNames) {
|
|
412
|
+
const lines = [];
|
|
413
|
+
const sanitizedNames = serverNames.map(sanitizeIdentifier);
|
|
414
|
+
lines.push("/**");
|
|
415
|
+
lines.push(" * MCP Server Dictionary - Access all MCP servers via case-agnostic lookup.");
|
|
416
|
+
lines.push(` * Available: ${serverNames.map((n) => `mcp['${n}']`).join(", ")}`);
|
|
417
|
+
lines.push(` * Aliases: ${sanitizedNames.join(", ")}`);
|
|
418
|
+
lines.push(" */");
|
|
419
|
+
lines.push("");
|
|
420
|
+
lines.push("const mcp_servers_raw: Record<string, unknown> = {");
|
|
421
|
+
for (const serverName of serverNames) {
|
|
422
|
+
const sanitized = sanitizeIdentifier(serverName);
|
|
423
|
+
lines.push(` ${JSON.stringify(serverName)}: ${sanitized},`);
|
|
424
|
+
}
|
|
425
|
+
lines.push("};");
|
|
426
|
+
lines.push("");
|
|
427
|
+
lines.push(`const mcp = ${generateFuzzyProxy("mcp_servers_raw", "mcp")};`);
|
|
428
|
+
lines.push("");
|
|
429
|
+
return lines.join("\n");
|
|
430
|
+
}
|
|
431
|
+
__name(generateMcpDictionary, "generateMcpDictionary");
|
|
432
|
+
|
|
433
|
+
// dist/codegen/module-resolver.js
|
|
434
|
+
import { loadServerManifest } from "@justanothermldude/meta-mcp-core";
|
|
435
|
+
function sanitizeFileName(name) {
|
|
436
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_").toLowerCase();
|
|
437
|
+
}
|
|
438
|
+
__name(sanitizeFileName, "sanitizeFileName");
|
|
439
|
+
var VirtualModuleResolver = class {
|
|
440
|
+
static {
|
|
441
|
+
__name(this, "VirtualModuleResolver");
|
|
442
|
+
}
|
|
443
|
+
constructor() {
|
|
444
|
+
this.modules = /* @__PURE__ */ new Map();
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Register a virtual module at the given path
|
|
448
|
+
* @param path - Virtual path (e.g., ./servers/github/create_issue.ts)
|
|
449
|
+
* @param code - Generated TypeScript code
|
|
450
|
+
*/
|
|
451
|
+
registerModule(path, code) {
|
|
452
|
+
this.modules.set(this.normalizePath(path), code);
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Resolve a virtual module path to its generated code
|
|
456
|
+
* @param path - Virtual path to resolve
|
|
457
|
+
* @returns Generated TypeScript code or undefined if not found
|
|
458
|
+
*/
|
|
459
|
+
resolve(path) {
|
|
460
|
+
return this.modules.get(this.normalizePath(path));
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Check if a virtual module exists at the given path
|
|
464
|
+
* @param path - Virtual path to check
|
|
465
|
+
* @returns True if module exists
|
|
466
|
+
*/
|
|
467
|
+
has(path) {
|
|
468
|
+
return this.modules.has(this.normalizePath(path));
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Clear all registered modules
|
|
472
|
+
*/
|
|
473
|
+
clear() {
|
|
474
|
+
this.modules.clear();
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Get the number of registered modules
|
|
478
|
+
*/
|
|
479
|
+
size() {
|
|
480
|
+
return this.modules.size;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Get all registered module paths
|
|
484
|
+
*/
|
|
485
|
+
listModules() {
|
|
486
|
+
return Array.from(this.modules.keys());
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Normalize a path for consistent lookup
|
|
490
|
+
*/
|
|
491
|
+
normalizePath(path) {
|
|
492
|
+
return path.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Generate the virtual path for a tool
|
|
496
|
+
* @param serverName - Name of the MCP server
|
|
497
|
+
* @param toolName - Name of the tool
|
|
498
|
+
* @returns Virtual path in format servers/<name>/<tool>.ts
|
|
499
|
+
*/
|
|
500
|
+
static getToolPath(serverName, toolName) {
|
|
501
|
+
return `servers/${serverName}/${sanitizeFileName(toolName)}.ts`;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Generate the virtual path for a server's index module
|
|
505
|
+
* @param serverName - Name of the MCP server
|
|
506
|
+
* @returns Virtual path in format servers/<name>/index.ts
|
|
507
|
+
*/
|
|
508
|
+
static getServerIndexPath(serverName) {
|
|
509
|
+
return `servers/${serverName}/index.ts`;
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
async function generateFromManifest(resolver, options) {
|
|
513
|
+
const { toolCache, manifest = loadServerManifest() } = options;
|
|
514
|
+
let moduleCount = 0;
|
|
515
|
+
for (const serverName of Object.keys(manifest.servers)) {
|
|
516
|
+
const tools = toolCache.get(serverName);
|
|
517
|
+
if (!tools || tools.length === 0) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
for (const tool of tools) {
|
|
521
|
+
const path = VirtualModuleResolver.getToolPath(serverName, tool.name);
|
|
522
|
+
const code = generateToolWrapper(tool, serverName);
|
|
523
|
+
resolver.registerModule(path, code);
|
|
524
|
+
moduleCount++;
|
|
525
|
+
}
|
|
526
|
+
const indexPath = VirtualModuleResolver.getServerIndexPath(serverName);
|
|
527
|
+
const indexCode = generateServerIndexModule(serverName, tools);
|
|
528
|
+
resolver.registerModule(indexPath, indexCode);
|
|
529
|
+
moduleCount++;
|
|
530
|
+
}
|
|
531
|
+
return moduleCount;
|
|
532
|
+
}
|
|
533
|
+
__name(generateFromManifest, "generateFromManifest");
|
|
534
|
+
function generateServerIndexModule(serverName, tools) {
|
|
535
|
+
const lines = [];
|
|
536
|
+
lines.push("/**");
|
|
537
|
+
lines.push(` * Auto-generated index for ${serverName} MCP server tools.`);
|
|
538
|
+
lines.push(" * Re-exports all tool wrappers for convenient imports.");
|
|
539
|
+
lines.push(" */");
|
|
540
|
+
lines.push("");
|
|
541
|
+
for (const tool of tools) {
|
|
542
|
+
const fileName = sanitizeFileName(tool.name);
|
|
543
|
+
lines.push(`export * from './${fileName}.js';`);
|
|
544
|
+
}
|
|
545
|
+
return lines.join("\n");
|
|
546
|
+
}
|
|
547
|
+
__name(generateServerIndexModule, "generateServerIndexModule");
|
|
548
|
+
async function createModuleResolver(options) {
|
|
549
|
+
const resolver = new VirtualModuleResolver();
|
|
550
|
+
await generateFromManifest(resolver, options);
|
|
551
|
+
return resolver;
|
|
552
|
+
}
|
|
553
|
+
__name(createModuleResolver, "createModuleResolver");
|
|
554
|
+
|
|
555
|
+
// dist/sandbox/executor.js
|
|
556
|
+
import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
|
|
557
|
+
import { spawn } from "node:child_process";
|
|
558
|
+
import { writeFile, unlink, mkdir } from "node:fs/promises";
|
|
559
|
+
import { join } from "node:path";
|
|
560
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
561
|
+
import { randomUUID } from "node:crypto";
|
|
562
|
+
import { transformSync } from "esbuild";
|
|
563
|
+
|
|
564
|
+
// dist/sandbox/config.js
|
|
565
|
+
import { tmpdir } from "node:os";
|
|
566
|
+
var DEFAULT_MCP_BRIDGE_PORT = 3e3;
|
|
567
|
+
function createDefaultNetworkConfig(mcpBridgePort = DEFAULT_MCP_BRIDGE_PORT) {
|
|
568
|
+
return {
|
|
569
|
+
allowedDomains: [`localhost:${mcpBridgePort}`],
|
|
570
|
+
deniedDomains: [],
|
|
571
|
+
allowLocalBinding: true
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
__name(createDefaultNetworkConfig, "createDefaultNetworkConfig");
|
|
575
|
+
function createDefaultFilesystemConfig(additionalWritePaths = []) {
|
|
576
|
+
return {
|
|
577
|
+
denyRead: [],
|
|
578
|
+
allowWrite: [tmpdir(), ...additionalWritePaths],
|
|
579
|
+
denyWrite: []
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
__name(createDefaultFilesystemConfig, "createDefaultFilesystemConfig");
|
|
583
|
+
function createSandboxRuntimeConfig(options = {}) {
|
|
584
|
+
const { mcpBridgePort = DEFAULT_MCP_BRIDGE_PORT, additionalWritePaths = [], networkConfig, filesystemConfig } = options;
|
|
585
|
+
return {
|
|
586
|
+
network: networkConfig ?? createDefaultNetworkConfig(mcpBridgePort),
|
|
587
|
+
filesystem: filesystemConfig ?? createDefaultFilesystemConfig(additionalWritePaths)
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
__name(createSandboxRuntimeConfig, "createSandboxRuntimeConfig");
|
|
591
|
+
|
|
592
|
+
// dist/sandbox/executor.js
|
|
593
|
+
var SandboxExecutor = class {
|
|
594
|
+
static {
|
|
595
|
+
__name(this, "SandboxExecutor");
|
|
596
|
+
}
|
|
597
|
+
constructor(options = {}) {
|
|
598
|
+
this.initialized = false;
|
|
599
|
+
this.executorConfig = options;
|
|
600
|
+
this.config = createSandboxRuntimeConfig(options);
|
|
601
|
+
this.tempDir = join(tmpdir2(), "mcp-exec");
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Initialize the sandbox manager with configured restrictions
|
|
605
|
+
*/
|
|
606
|
+
async initialize() {
|
|
607
|
+
if (this.initialized) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
await mkdir(this.tempDir, { recursive: true });
|
|
611
|
+
await SandboxManager.initialize(
|
|
612
|
+
this.config,
|
|
613
|
+
void 0,
|
|
614
|
+
// No ask callback - deny by default
|
|
615
|
+
this.executorConfig.enableLogMonitor ?? false
|
|
616
|
+
);
|
|
617
|
+
this.initialized = true;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Generate the MCP helper preamble that provides the global `mcp` object
|
|
621
|
+
* for calling MCP tools via the HTTP bridge
|
|
622
|
+
*/
|
|
623
|
+
getMcpPreamble() {
|
|
624
|
+
const bridgePort = this.executorConfig.mcpBridgePort ?? 3e3;
|
|
625
|
+
return `
|
|
626
|
+
// ============================================================================
|
|
627
|
+
// MCP-EXEC SANDBOX API
|
|
628
|
+
// ============================================================================
|
|
629
|
+
// Available: mcp.callTool(serverName, toolName, args)
|
|
630
|
+
//
|
|
631
|
+
// Usage:
|
|
632
|
+
// const result = await mcp.callTool('github', 'list_repos', { org: 'foo' });
|
|
633
|
+
// const issues = await mcp.callTool('jira', 'search_issues', { jql: 'project=X' });
|
|
634
|
+
//
|
|
635
|
+
// For long-running queries, prefer async patterns:
|
|
636
|
+
// const job = await mcp.callTool('splunk-async', 'create_search_job', { query: '...' });
|
|
637
|
+
// const results = await mcp.callTool('splunk-async', 'get_search_results', { job_id: job.sid });
|
|
638
|
+
// ============================================================================
|
|
639
|
+
|
|
640
|
+
// MCP helper for calling tools via HTTP bridge
|
|
641
|
+
declare global {
|
|
642
|
+
var mcp: {
|
|
643
|
+
callTool: (server: string, tool: string, args?: Record<string, unknown>) => Promise<unknown[]>;
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
globalThis.mcp = {
|
|
648
|
+
callTool: async (server: string, tool: string, args: Record<string, unknown> = {}) => {
|
|
649
|
+
const response = await fetch('http://127.0.0.1:${bridgePort}/call', {
|
|
650
|
+
method: 'POST',
|
|
651
|
+
headers: { 'Content-Type': 'application/json' },
|
|
652
|
+
body: JSON.stringify({ server, tool, args }),
|
|
653
|
+
});
|
|
654
|
+
const data = await response.json() as { success: boolean; content?: unknown[]; error?: string };
|
|
655
|
+
if (!data.success) {
|
|
656
|
+
throw new Error(data.error || 'MCP tool call failed');
|
|
657
|
+
}
|
|
658
|
+
return data.content || [];
|
|
659
|
+
},
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
`;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Execute TypeScript/JavaScript code in the sandbox
|
|
666
|
+
*
|
|
667
|
+
* @param code - The code to execute
|
|
668
|
+
* @param timeoutMs - Maximum execution time in milliseconds
|
|
669
|
+
* @returns ExecutionResult with output, error, and duration
|
|
670
|
+
*/
|
|
671
|
+
async execute(code, timeoutMs) {
|
|
672
|
+
const startTime = Date.now();
|
|
673
|
+
if (!this.initialized) {
|
|
674
|
+
await this.initialize();
|
|
675
|
+
}
|
|
676
|
+
const fileId = randomUUID();
|
|
677
|
+
const tsFilePath = join(this.tempDir, `${fileId}.ts`);
|
|
678
|
+
const jsFilePath = join(this.tempDir, `${fileId}.js`);
|
|
679
|
+
const fullCode = this.getMcpPreamble() + code;
|
|
680
|
+
try {
|
|
681
|
+
await writeFile(tsFilePath, fullCode, "utf-8");
|
|
682
|
+
const transpiled = transformSync(fullCode, {
|
|
683
|
+
loader: "ts",
|
|
684
|
+
format: "esm",
|
|
685
|
+
target: "node20"
|
|
686
|
+
});
|
|
687
|
+
await writeFile(jsFilePath, transpiled.code, "utf-8");
|
|
688
|
+
const abortController = new AbortController();
|
|
689
|
+
const timeoutId = setTimeout(() => {
|
|
690
|
+
abortController.abort();
|
|
691
|
+
}, timeoutMs);
|
|
692
|
+
try {
|
|
693
|
+
const baseCommand = `node "${jsFilePath}"`;
|
|
694
|
+
const sandboxedCommand = await SandboxManager.wrapWithSandbox(
|
|
695
|
+
baseCommand,
|
|
696
|
+
void 0,
|
|
697
|
+
// Use default shell
|
|
698
|
+
void 0,
|
|
699
|
+
// Use default config
|
|
700
|
+
abortController.signal
|
|
701
|
+
);
|
|
702
|
+
const result = await this.executeCommand(sandboxedCommand, abortController.signal);
|
|
703
|
+
clearTimeout(timeoutId);
|
|
704
|
+
return {
|
|
705
|
+
output: result.stdout,
|
|
706
|
+
error: result.stderr || void 0,
|
|
707
|
+
durationMs: Date.now() - startTime
|
|
708
|
+
};
|
|
709
|
+
} catch (execError) {
|
|
710
|
+
clearTimeout(timeoutId);
|
|
711
|
+
if (abortController.signal.aborted) {
|
|
712
|
+
return {
|
|
713
|
+
output: [],
|
|
714
|
+
error: `Execution timed out after ${timeoutMs}ms`,
|
|
715
|
+
durationMs: Date.now() - startTime
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
const errorMessage = execError instanceof Error ? execError.message : String(execError);
|
|
719
|
+
const annotatedError = SandboxManager.annotateStderrWithSandboxFailures(`node "${jsFilePath}"`, errorMessage);
|
|
720
|
+
return {
|
|
721
|
+
output: [],
|
|
722
|
+
error: annotatedError,
|
|
723
|
+
durationMs: Date.now() - startTime
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
} finally {
|
|
727
|
+
await this.cleanup(tsFilePath, jsFilePath);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Execute a command and capture its output
|
|
732
|
+
*/
|
|
733
|
+
executeCommand(command, abortSignal) {
|
|
734
|
+
return new Promise((resolve, reject) => {
|
|
735
|
+
const child = spawn("sh", ["-c", command], {
|
|
736
|
+
signal: abortSignal
|
|
737
|
+
});
|
|
738
|
+
const stdout = [];
|
|
739
|
+
let stderr = "";
|
|
740
|
+
child.stdout.on("data", (data) => {
|
|
741
|
+
const lines = data.toString().split("\n").filter((line) => line.length > 0);
|
|
742
|
+
stdout.push(...lines);
|
|
743
|
+
});
|
|
744
|
+
child.stderr.on("data", (data) => {
|
|
745
|
+
stderr += data.toString();
|
|
746
|
+
});
|
|
747
|
+
child.on("close", (code) => {
|
|
748
|
+
if (code === 0) {
|
|
749
|
+
resolve({ stdout, stderr: stderr || null });
|
|
750
|
+
} else {
|
|
751
|
+
resolve({ stdout, stderr: stderr || `Process exited with code ${code}` });
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
child.on("error", (error) => {
|
|
755
|
+
if (error.name === "AbortError") {
|
|
756
|
+
child.kill("SIGKILL");
|
|
757
|
+
reject(new Error("Execution aborted"));
|
|
758
|
+
} else {
|
|
759
|
+
reject(error);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Cleanup temporary files
|
|
766
|
+
*/
|
|
767
|
+
async cleanup(...filePaths) {
|
|
768
|
+
await Promise.all(filePaths.map(async (filePath) => {
|
|
769
|
+
try {
|
|
770
|
+
await unlink(filePath);
|
|
771
|
+
} catch {
|
|
772
|
+
}
|
|
773
|
+
}));
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Reset the sandbox manager (useful for testing)
|
|
777
|
+
*/
|
|
778
|
+
async reset() {
|
|
779
|
+
await SandboxManager.reset();
|
|
780
|
+
this.initialized = false;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Check if sandbox dependencies are available
|
|
784
|
+
*/
|
|
785
|
+
checkDependencies() {
|
|
786
|
+
return SandboxManager.checkDependencies();
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Check if sandboxing is enabled on this platform
|
|
790
|
+
*/
|
|
791
|
+
isSandboxingEnabled() {
|
|
792
|
+
return SandboxManager.isSandboxingEnabled();
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Get the current sandbox configuration
|
|
796
|
+
*/
|
|
797
|
+
getConfig() {
|
|
798
|
+
return this.config;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Update sandbox configuration (requires re-initialization)
|
|
802
|
+
*/
|
|
803
|
+
updateConfig(newConfig) {
|
|
804
|
+
this.executorConfig = { ...this.executorConfig, ...newConfig };
|
|
805
|
+
this.config = createSandboxRuntimeConfig(this.executorConfig);
|
|
806
|
+
SandboxManager.updateConfig(this.config);
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
// dist/bridge/server.js
|
|
811
|
+
import { createServer } from "http";
|
|
812
|
+
import getPort from "get-port";
|
|
813
|
+
import { getServerConfig, listServers as listServers2 } from "@justanothermldude/meta-mcp-core";
|
|
814
|
+
|
|
815
|
+
// dist/bridge/port-cleanup.js
|
|
816
|
+
import { execSync } from "child_process";
|
|
817
|
+
function isMcpExecProcess(pid) {
|
|
818
|
+
try {
|
|
819
|
+
const cmdline = execSync(`ps -p ${pid} -o command=`, { encoding: "utf8" }).trim();
|
|
820
|
+
return cmdline.includes("mcp-exec") || cmdline.includes("meta-mcp");
|
|
821
|
+
} catch {
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
__name(isMcpExecProcess, "isMcpExecProcess");
|
|
826
|
+
function getProcessesOnPort(port) {
|
|
827
|
+
try {
|
|
828
|
+
const result = execSync(`lsof -ti :${port}`, { encoding: "utf8" }).trim();
|
|
829
|
+
if (!result)
|
|
830
|
+
return [];
|
|
831
|
+
return result.split("\n").filter(Boolean).map((pid) => parseInt(pid, 10)).filter((pid) => !isNaN(pid));
|
|
832
|
+
} catch {
|
|
833
|
+
return [];
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
__name(getProcessesOnPort, "getProcessesOnPort");
|
|
837
|
+
async function cleanupStaleProcess(port) {
|
|
838
|
+
const pids = getProcessesOnPort(port);
|
|
839
|
+
if (pids.length === 0)
|
|
840
|
+
return false;
|
|
841
|
+
let killedAny = false;
|
|
842
|
+
for (const pid of pids) {
|
|
843
|
+
if (isMcpExecProcess(pid)) {
|
|
844
|
+
try {
|
|
845
|
+
process.kill(pid, "SIGTERM");
|
|
846
|
+
killedAny = true;
|
|
847
|
+
} catch {
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (killedAny) {
|
|
852
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
853
|
+
}
|
|
854
|
+
return killedAny;
|
|
855
|
+
}
|
|
856
|
+
__name(cleanupStaleProcess, "cleanupStaleProcess");
|
|
857
|
+
function isPortInUse(port) {
|
|
858
|
+
return getProcessesOnPort(port).length > 0;
|
|
859
|
+
}
|
|
860
|
+
__name(isPortInUse, "isPortInUse");
|
|
861
|
+
|
|
862
|
+
// dist/bridge/server.js
|
|
863
|
+
var DEFAULT_PORT = 3e3;
|
|
864
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
865
|
+
var MAX_REQUEST_BODY_SIZE = 10 * 1024 * 1024;
|
|
866
|
+
function stringSimilarity(a, b) {
|
|
867
|
+
const aLower = a.toLowerCase();
|
|
868
|
+
const bLower = b.toLowerCase();
|
|
869
|
+
if (aLower === bLower)
|
|
870
|
+
return 1;
|
|
871
|
+
if (aLower.includes(bLower) || bLower.includes(aLower)) {
|
|
872
|
+
return 0.8;
|
|
873
|
+
}
|
|
874
|
+
let prefixLen = 0;
|
|
875
|
+
const minLen = Math.min(aLower.length, bLower.length);
|
|
876
|
+
for (let i = 0; i < minLen; i++) {
|
|
877
|
+
if (aLower[i] === bLower[i])
|
|
878
|
+
prefixLen++;
|
|
879
|
+
else
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
let suffixLen = 0;
|
|
883
|
+
for (let i = 0; i < minLen; i++) {
|
|
884
|
+
if (aLower[aLower.length - 1 - i] === bLower[bLower.length - 1 - i])
|
|
885
|
+
suffixLen++;
|
|
886
|
+
else
|
|
887
|
+
break;
|
|
888
|
+
}
|
|
889
|
+
const maxLen = Math.max(aLower.length, bLower.length);
|
|
890
|
+
return (prefixLen + suffixLen) / (2 * maxLen);
|
|
891
|
+
}
|
|
892
|
+
__name(stringSimilarity, "stringSimilarity");
|
|
893
|
+
function findClosestMatch(target, candidates) {
|
|
894
|
+
if (candidates.length === 0)
|
|
895
|
+
return null;
|
|
896
|
+
let bestMatch = null;
|
|
897
|
+
let bestScore = 0;
|
|
898
|
+
const threshold = 0.3;
|
|
899
|
+
for (const candidate of candidates) {
|
|
900
|
+
const score = stringSimilarity(target, candidate);
|
|
901
|
+
if (score > bestScore && score >= threshold) {
|
|
902
|
+
bestScore = score;
|
|
903
|
+
bestMatch = candidate;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return bestMatch;
|
|
907
|
+
}
|
|
908
|
+
__name(findClosestMatch, "findClosestMatch");
|
|
909
|
+
function buildServerNotFoundError(requestedServer) {
|
|
910
|
+
const servers = listServers2();
|
|
911
|
+
const serverNames = servers.map((s) => s.name);
|
|
912
|
+
let errorMsg = `Server '${requestedServer}' not found.`;
|
|
913
|
+
if (serverNames.length === 0) {
|
|
914
|
+
errorMsg += " No servers are configured. Check that servers.json is properly set up.";
|
|
915
|
+
return errorMsg;
|
|
916
|
+
}
|
|
917
|
+
const displayServers = serverNames.slice(0, 5);
|
|
918
|
+
const hasMore = serverNames.length > 5;
|
|
919
|
+
errorMsg += ` Available: ${displayServers.join(", ")}${hasMore ? ` (+${serverNames.length - 5} more)` : ""}.`;
|
|
920
|
+
const closest = findClosestMatch(requestedServer, serverNames);
|
|
921
|
+
if (closest) {
|
|
922
|
+
errorMsg += ` Did you mean '${closest}'?`;
|
|
923
|
+
}
|
|
924
|
+
return errorMsg;
|
|
925
|
+
}
|
|
926
|
+
__name(buildServerNotFoundError, "buildServerNotFoundError");
|
|
927
|
+
function buildToolNotFoundError(serverName, requestedTool, availableTools) {
|
|
928
|
+
let errorMsg = `Tool '${requestedTool}' not found on server '${serverName}'.`;
|
|
929
|
+
if (availableTools.length === 0) {
|
|
930
|
+
errorMsg += " No tools available on this server.";
|
|
931
|
+
return errorMsg;
|
|
932
|
+
}
|
|
933
|
+
const displayTools = availableTools.slice(0, 5);
|
|
934
|
+
const hasMore = availableTools.length > 5;
|
|
935
|
+
errorMsg += ` Available tools: ${displayTools.join(", ")}${hasMore ? ` (+${availableTools.length - 5} more)` : ""}.`;
|
|
936
|
+
const closest = findClosestMatch(requestedTool, availableTools);
|
|
937
|
+
if (closest) {
|
|
938
|
+
errorMsg += ` Did you mean '${closest}'?`;
|
|
939
|
+
}
|
|
940
|
+
return errorMsg;
|
|
941
|
+
}
|
|
942
|
+
__name(buildToolNotFoundError, "buildToolNotFoundError");
|
|
943
|
+
function buildConnectionError(serverName, originalError) {
|
|
944
|
+
let errorMsg = `Failed to connect to server '${serverName}': ${originalError}`;
|
|
945
|
+
const hints = [];
|
|
946
|
+
if (originalError.includes("ENOENT") || originalError.includes("not found")) {
|
|
947
|
+
hints.push("Check that the server command/binary exists and is in PATH");
|
|
948
|
+
}
|
|
949
|
+
if (originalError.includes("ECONNREFUSED")) {
|
|
950
|
+
hints.push("Check that the server process is running");
|
|
951
|
+
}
|
|
952
|
+
if (originalError.includes("timeout") || originalError.includes("Timeout")) {
|
|
953
|
+
hints.push("Server may be slow to start - try increasing timeout in servers.json");
|
|
954
|
+
}
|
|
955
|
+
if (originalError.includes("spawn")) {
|
|
956
|
+
hints.push("Verify the server configuration in servers.json");
|
|
957
|
+
}
|
|
958
|
+
hints.push("Is the server configured in servers.json?");
|
|
959
|
+
if (hints.length > 0) {
|
|
960
|
+
errorMsg += ` Troubleshooting: ${hints.join("; ")}.`;
|
|
961
|
+
}
|
|
962
|
+
return errorMsg;
|
|
963
|
+
}
|
|
964
|
+
__name(buildConnectionError, "buildConnectionError");
|
|
965
|
+
var MCPBridge = class {
|
|
966
|
+
static {
|
|
967
|
+
__name(this, "MCPBridge");
|
|
968
|
+
}
|
|
969
|
+
constructor(pool, config = {}) {
|
|
970
|
+
this.server = null;
|
|
971
|
+
this.pool = pool;
|
|
972
|
+
this.preferredPort = config.port ?? DEFAULT_PORT;
|
|
973
|
+
this.port = this.preferredPort;
|
|
974
|
+
this.host = config.host ?? DEFAULT_HOST;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Start the HTTP bridge server with dynamic port allocation
|
|
978
|
+
* Prefers the configured port but falls back to any available port
|
|
979
|
+
* @returns Promise that resolves when server is listening
|
|
980
|
+
*/
|
|
981
|
+
async start() {
|
|
982
|
+
this.port = await getPort({ port: this.preferredPort });
|
|
983
|
+
return new Promise((resolve, reject) => {
|
|
984
|
+
this.server = createServer((req, res) => {
|
|
985
|
+
this.handleRequest(req, res);
|
|
986
|
+
});
|
|
987
|
+
this.server.on("error", async (err) => {
|
|
988
|
+
if (err.code === "EADDRINUSE") {
|
|
989
|
+
const cleaned = await cleanupStaleProcess(this.port);
|
|
990
|
+
if (cleaned) {
|
|
991
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
992
|
+
this.port = await getPort({ port: this.preferredPort });
|
|
993
|
+
try {
|
|
994
|
+
this.server?.listen(this.port, this.host, () => {
|
|
995
|
+
resolve();
|
|
996
|
+
});
|
|
997
|
+
return;
|
|
998
|
+
} catch {
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
reject(err);
|
|
1003
|
+
});
|
|
1004
|
+
this.server.listen(this.port, this.host, () => {
|
|
1005
|
+
resolve();
|
|
1006
|
+
});
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Stop the HTTP bridge server
|
|
1011
|
+
* @returns Promise that resolves when server is closed
|
|
1012
|
+
*/
|
|
1013
|
+
async stop() {
|
|
1014
|
+
return new Promise((resolve, reject) => {
|
|
1015
|
+
if (!this.server) {
|
|
1016
|
+
resolve();
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
this.server.close((err) => {
|
|
1020
|
+
if (err) {
|
|
1021
|
+
reject(err);
|
|
1022
|
+
} else {
|
|
1023
|
+
this.server = null;
|
|
1024
|
+
resolve();
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Get the port the server is listening on
|
|
1031
|
+
*/
|
|
1032
|
+
getPort() {
|
|
1033
|
+
return this.port;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Get the host the server is bound to
|
|
1037
|
+
*/
|
|
1038
|
+
getHost() {
|
|
1039
|
+
return this.host;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Check if the server is running
|
|
1043
|
+
*/
|
|
1044
|
+
isRunning() {
|
|
1045
|
+
return this.server !== null && this.server.listening;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Handle incoming HTTP requests
|
|
1049
|
+
*/
|
|
1050
|
+
handleRequest(req, res) {
|
|
1051
|
+
res.setHeader("Content-Type", "application/json");
|
|
1052
|
+
res.setHeader("Access-Control-Allow-Origin", `http://${this.host}:${this.port}`);
|
|
1053
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
1054
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1055
|
+
if (req.method === "OPTIONS") {
|
|
1056
|
+
res.writeHead(204);
|
|
1057
|
+
res.end();
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
if (req.method === "POST" && req.url === "/call") {
|
|
1061
|
+
this.handleCallRequest(req, res);
|
|
1062
|
+
} else if (req.method === "GET" && req.url === "/health") {
|
|
1063
|
+
this.handleHealthRequest(res);
|
|
1064
|
+
} else {
|
|
1065
|
+
this.sendError(res, 404, "Not Found");
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Handle POST /call endpoint for tool invocation
|
|
1070
|
+
*/
|
|
1071
|
+
handleCallRequest(req, res) {
|
|
1072
|
+
let body = "";
|
|
1073
|
+
let bodySize = 0;
|
|
1074
|
+
req.on("data", (chunk) => {
|
|
1075
|
+
bodySize += chunk.length;
|
|
1076
|
+
if (bodySize > MAX_REQUEST_BODY_SIZE) {
|
|
1077
|
+
req.destroy();
|
|
1078
|
+
this.sendError(res, 413, `Request body too large. Maximum size is ${MAX_REQUEST_BODY_SIZE / 1024 / 1024}MB`);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
body += chunk.toString();
|
|
1082
|
+
});
|
|
1083
|
+
req.on("end", async () => {
|
|
1084
|
+
try {
|
|
1085
|
+
let request;
|
|
1086
|
+
try {
|
|
1087
|
+
request = JSON.parse(body);
|
|
1088
|
+
} catch {
|
|
1089
|
+
this.sendError(res, 400, "Invalid JSON body");
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
if (!request.server || typeof request.server !== "string") {
|
|
1093
|
+
this.sendError(res, 400, 'Missing or invalid "server" field');
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
if (!request.tool || typeof request.tool !== "string") {
|
|
1097
|
+
this.sendError(res, 400, 'Missing or invalid "tool" field');
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (request.args !== void 0 && (typeof request.args !== "object" || request.args === null || Array.isArray(request.args))) {
|
|
1101
|
+
this.sendError(res, 400, '"args" must be an object');
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
let connection;
|
|
1105
|
+
try {
|
|
1106
|
+
connection = await this.pool.getConnection(request.server);
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1109
|
+
if (errorMsg.includes("not found") || errorMsg.includes("No server configured") || errorMsg.includes("Unknown server")) {
|
|
1110
|
+
this.sendError(res, 404, buildServerNotFoundError(request.server));
|
|
1111
|
+
} else {
|
|
1112
|
+
this.sendError(res, 502, buildConnectionError(request.server, errorMsg));
|
|
1113
|
+
}
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
try {
|
|
1117
|
+
const serverConfig = getServerConfig(request.server);
|
|
1118
|
+
const defaultTimeout = process.env.MCP_DEFAULT_TIMEOUT ? parseInt(process.env.MCP_DEFAULT_TIMEOUT, 10) : void 0;
|
|
1119
|
+
const timeout = serverConfig?.timeout ?? defaultTimeout;
|
|
1120
|
+
const result = await connection.client.callTool(
|
|
1121
|
+
{
|
|
1122
|
+
name: request.tool,
|
|
1123
|
+
arguments: request.args ?? {}
|
|
1124
|
+
},
|
|
1125
|
+
void 0,
|
|
1126
|
+
// resultSchema
|
|
1127
|
+
timeout ? { timeout } : void 0
|
|
1128
|
+
);
|
|
1129
|
+
const response = {
|
|
1130
|
+
success: true,
|
|
1131
|
+
content: result.content,
|
|
1132
|
+
isError: result.isError
|
|
1133
|
+
};
|
|
1134
|
+
res.writeHead(200);
|
|
1135
|
+
res.end(JSON.stringify(response));
|
|
1136
|
+
} catch (err) {
|
|
1137
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1138
|
+
if (errorMsg.includes("not found") || errorMsg.includes("Unknown tool") || errorMsg.includes("no such tool")) {
|
|
1139
|
+
try {
|
|
1140
|
+
const tools = await connection.getTools();
|
|
1141
|
+
const toolNames = tools.map((t) => t.name);
|
|
1142
|
+
this.sendError(res, 404, buildToolNotFoundError(request.server, request.tool, toolNames));
|
|
1143
|
+
} catch {
|
|
1144
|
+
this.sendError(res, 500, `Tool '${request.tool}' not found on server '${request.server}'. Unable to fetch available tools.`);
|
|
1145
|
+
}
|
|
1146
|
+
} else {
|
|
1147
|
+
this.sendError(res, 500, `Tool execution failed: ${errorMsg}`);
|
|
1148
|
+
}
|
|
1149
|
+
} finally {
|
|
1150
|
+
this.pool.releaseConnection(request.server);
|
|
1151
|
+
}
|
|
1152
|
+
} catch (err) {
|
|
1153
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1154
|
+
this.sendError(res, 500, `Internal error: ${errorMsg}`);
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
req.on("error", (err) => {
|
|
1158
|
+
this.sendError(res, 400, `Request error: ${err.message}`);
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Handle GET /health endpoint
|
|
1163
|
+
*/
|
|
1164
|
+
handleHealthRequest(res) {
|
|
1165
|
+
const response = {
|
|
1166
|
+
status: "ok",
|
|
1167
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1168
|
+
};
|
|
1169
|
+
res.writeHead(200);
|
|
1170
|
+
res.end(JSON.stringify(response));
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Send an error response
|
|
1174
|
+
*/
|
|
1175
|
+
sendError(res, statusCode, message) {
|
|
1176
|
+
const response = {
|
|
1177
|
+
success: false,
|
|
1178
|
+
error: message
|
|
1179
|
+
};
|
|
1180
|
+
res.writeHead(statusCode);
|
|
1181
|
+
res.end(JSON.stringify(response));
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
// dist/types/execution.js
|
|
1186
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
1187
|
+
|
|
1188
|
+
// dist/tools/execute-with-wrappers.js
|
|
1189
|
+
var executeCodeWithWrappersTool = {
|
|
1190
|
+
name: "execute_code_with_wrappers",
|
|
1191
|
+
description: '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.',
|
|
1192
|
+
inputSchema: {
|
|
1193
|
+
type: "object",
|
|
1194
|
+
properties: {
|
|
1195
|
+
code: {
|
|
1196
|
+
type: "string",
|
|
1197
|
+
description: "The TypeScript/JavaScript code to execute. Multi-line supported - format for readability."
|
|
1198
|
+
},
|
|
1199
|
+
wrappers: {
|
|
1200
|
+
type: "array",
|
|
1201
|
+
items: { type: "string" },
|
|
1202
|
+
description: "Array of MCP server names to generate typed wrappers for"
|
|
1203
|
+
},
|
|
1204
|
+
timeout_ms: {
|
|
1205
|
+
type: "number",
|
|
1206
|
+
description: `Maximum execution time in milliseconds (default: ${DEFAULT_TIMEOUT_MS})`
|
|
1207
|
+
}
|
|
1208
|
+
},
|
|
1209
|
+
required: ["code", "wrappers"]
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
function isExecuteWithWrappersInput(args2) {
|
|
1213
|
+
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");
|
|
1214
|
+
}
|
|
1215
|
+
__name(isExecuteWithWrappersInput, "isExecuteWithWrappersInput");
|
|
1216
|
+
function getMcpPreamble(bridgePort) {
|
|
1217
|
+
return `
|
|
1218
|
+
// MCP helper for calling tools via HTTP bridge
|
|
1219
|
+
declare global {
|
|
1220
|
+
var mcp: {
|
|
1221
|
+
callTool: (server: string, tool: string, args?: Record<string, unknown>) => Promise<unknown[]>;
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
globalThis.mcp = {
|
|
1226
|
+
callTool: async (server: string, tool: string, args: Record<string, unknown> = {}) => {
|
|
1227
|
+
const response = await fetch('http://127.0.0.1:${bridgePort}/call', {
|
|
1228
|
+
method: 'POST',
|
|
1229
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1230
|
+
body: JSON.stringify({ server, tool, args }),
|
|
1231
|
+
});
|
|
1232
|
+
const data = await response.json() as { success: boolean; content?: unknown[]; error?: string };
|
|
1233
|
+
if (!data.success) {
|
|
1234
|
+
throw new Error(data.error || 'MCP tool call failed');
|
|
1235
|
+
}
|
|
1236
|
+
return data.content || [];
|
|
1237
|
+
},
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
`;
|
|
1241
|
+
}
|
|
1242
|
+
__name(getMcpPreamble, "getMcpPreamble");
|
|
1243
|
+
function createExecuteWithWrappersHandler(pool, config = {}) {
|
|
1244
|
+
const preferredPort = config.bridgeConfig?.port ?? 3e3;
|
|
1245
|
+
return /* @__PURE__ */ __name(async function executeWithWrappersHandler(args2) {
|
|
1246
|
+
const { code, wrappers, timeout_ms = DEFAULT_TIMEOUT_MS } = args2;
|
|
1247
|
+
if (!code || typeof code !== "string") {
|
|
1248
|
+
return {
|
|
1249
|
+
content: [{ type: "text", text: "Error: code parameter is required and must be a string" }],
|
|
1250
|
+
isError: true
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
if (!wrappers || !Array.isArray(wrappers)) {
|
|
1254
|
+
return {
|
|
1255
|
+
content: [{ type: "text", text: "Error: wrappers parameter is required and must be an array of strings" }],
|
|
1256
|
+
isError: true
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
if (wrappers.length === 0) {
|
|
1260
|
+
return {
|
|
1261
|
+
content: [{ type: "text", text: "Error: wrappers array must contain at least one server name" }],
|
|
1262
|
+
isError: true
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
if (timeout_ms !== void 0 && (typeof timeout_ms !== "number" || timeout_ms <= 0)) {
|
|
1266
|
+
return {
|
|
1267
|
+
content: [{ type: "text", text: "Error: timeout_ms must be a positive number" }],
|
|
1268
|
+
isError: true
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
let result = null;
|
|
1272
|
+
const bridge = new MCPBridge(pool, {
|
|
1273
|
+
...config.bridgeConfig,
|
|
1274
|
+
port: preferredPort
|
|
1275
|
+
});
|
|
1276
|
+
try {
|
|
1277
|
+
await bridge.start();
|
|
1278
|
+
const actualPort = bridge.getPort();
|
|
1279
|
+
const wrapperModules = [];
|
|
1280
|
+
for (const serverName of wrappers) {
|
|
1281
|
+
try {
|
|
1282
|
+
const connection = await pool.getConnection(serverName);
|
|
1283
|
+
const tools = await connection.getTools();
|
|
1284
|
+
const moduleCode = generateServerModule(tools, serverName, actualPort);
|
|
1285
|
+
wrapperModules.push(moduleCode);
|
|
1286
|
+
pool.releaseConnection(serverName);
|
|
1287
|
+
} catch (serverError) {
|
|
1288
|
+
const errorMessage = serverError instanceof Error ? serverError.message : String(serverError);
|
|
1289
|
+
await bridge.stop();
|
|
1290
|
+
return {
|
|
1291
|
+
content: [{ type: "text", text: `Error generating wrapper for server '${serverName}': ${errorMessage}` }],
|
|
1292
|
+
isError: true
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
const generatedWrappers = wrapperModules.join("\n\n");
|
|
1297
|
+
const mcpDictionary = generateMcpDictionary(wrappers);
|
|
1298
|
+
const mcpPreamble = getMcpPreamble(actualPort);
|
|
1299
|
+
const fullCode = `${generatedWrappers}
|
|
1300
|
+
|
|
1301
|
+
${mcpDictionary}
|
|
1302
|
+
|
|
1303
|
+
${mcpPreamble}
|
|
1304
|
+
${code}`;
|
|
1305
|
+
const sandboxConfig = {
|
|
1306
|
+
...config.sandboxConfig,
|
|
1307
|
+
mcpBridgePort: actualPort
|
|
1308
|
+
};
|
|
1309
|
+
const executor = new SandboxExecutor(sandboxConfig);
|
|
1310
|
+
result = await executor.execute(fullCode, timeout_ms);
|
|
1311
|
+
await bridge.stop();
|
|
1312
|
+
return formatResult(result);
|
|
1313
|
+
} catch (error) {
|
|
1314
|
+
try {
|
|
1315
|
+
if (bridge.isRunning()) {
|
|
1316
|
+
await bridge.stop();
|
|
1317
|
+
}
|
|
1318
|
+
} catch {
|
|
1319
|
+
}
|
|
1320
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1321
|
+
return formatErrorResult(errorMessage, result);
|
|
1322
|
+
}
|
|
1323
|
+
}, "executeWithWrappersHandler");
|
|
1324
|
+
}
|
|
1325
|
+
__name(createExecuteWithWrappersHandler, "createExecuteWithWrappersHandler");
|
|
1326
|
+
function formatResult(result) {
|
|
1327
|
+
const lines = [];
|
|
1328
|
+
if (result.output.length > 0) {
|
|
1329
|
+
lines.push(...result.output);
|
|
1330
|
+
}
|
|
1331
|
+
if (result.error) {
|
|
1332
|
+
lines.push(`[stderr]: ${result.error}`);
|
|
1333
|
+
}
|
|
1334
|
+
lines.push(`[Execution completed in ${result.durationMs}ms]`);
|
|
1335
|
+
const hasError = !!result.error;
|
|
1336
|
+
return {
|
|
1337
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
1338
|
+
isError: hasError
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
__name(formatResult, "formatResult");
|
|
1342
|
+
function formatErrorResult(errorMessage, partialResult) {
|
|
1343
|
+
const lines = [];
|
|
1344
|
+
if (partialResult?.output.length) {
|
|
1345
|
+
lines.push("[Partial output]:");
|
|
1346
|
+
lines.push(...partialResult.output);
|
|
1347
|
+
lines.push("");
|
|
1348
|
+
}
|
|
1349
|
+
lines.push(`Error: ${errorMessage}`);
|
|
1350
|
+
if (partialResult?.durationMs) {
|
|
1351
|
+
lines.push(`[Execution failed after ${partialResult.durationMs}ms]`);
|
|
1352
|
+
}
|
|
1353
|
+
return {
|
|
1354
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
1355
|
+
isError: true
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
__name(formatErrorResult, "formatErrorResult");
|
|
1359
|
+
|
|
1360
|
+
// dist/server.js
|
|
1361
|
+
var VERSION = "0.1.0";
|
|
1362
|
+
function createMcpExecServer(pool, config = {}) {
|
|
1363
|
+
const server = new Server({
|
|
1364
|
+
name: "mcp-exec",
|
|
1365
|
+
version: VERSION
|
|
1366
|
+
}, {
|
|
1367
|
+
capabilities: {
|
|
1368
|
+
tools: {}
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
const listServersHandler = createListServersHandler();
|
|
1372
|
+
const getToolSchemaHandler = createGetToolSchemaHandler(pool);
|
|
1373
|
+
const executeWithWrappersHandler = createExecuteWithWrappersHandler(pool, config.handlerConfig);
|
|
1374
|
+
const tools = [
|
|
1375
|
+
listAvailableMcpServersTool,
|
|
1376
|
+
getMcpToolSchemaTool,
|
|
1377
|
+
executeCodeWithWrappersTool
|
|
1378
|
+
];
|
|
1379
|
+
const listToolsHandler = /* @__PURE__ */ __name(async () => ({ tools }), "listToolsHandler");
|
|
1380
|
+
const callToolRequestHandler = /* @__PURE__ */ __name(async (params) => {
|
|
1381
|
+
const { name, arguments: args2 = {} } = params;
|
|
1382
|
+
switch (name) {
|
|
1383
|
+
case "list_available_mcp_servers": {
|
|
1384
|
+
if (!isListServersInput(args2)) {
|
|
1385
|
+
return {
|
|
1386
|
+
content: [{ type: "text", text: "Error: Invalid arguments for list_available_mcp_servers" }],
|
|
1387
|
+
isError: true
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
const result = await listServersHandler(args2);
|
|
1391
|
+
return {
|
|
1392
|
+
content: result.content,
|
|
1393
|
+
isError: result.isError
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
case "get_mcp_tool_schema": {
|
|
1397
|
+
if (!isGetToolSchemaInput(args2)) {
|
|
1398
|
+
return {
|
|
1399
|
+
content: [{ type: "text", text: "Error: Invalid arguments for get_mcp_tool_schema. Required: server (string), tool (string)" }],
|
|
1400
|
+
isError: true
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
const result = await getToolSchemaHandler(args2);
|
|
1404
|
+
return {
|
|
1405
|
+
content: result.content,
|
|
1406
|
+
isError: result.isError
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
case "execute_code_with_wrappers": {
|
|
1410
|
+
if (!isExecuteWithWrappersInput(args2)) {
|
|
1411
|
+
return {
|
|
1412
|
+
content: [{ type: "text", text: "Error: Invalid arguments for execute_code_with_wrappers. Required: code (string), wrappers (string[])" }],
|
|
1413
|
+
isError: true
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
const result = await executeWithWrappersHandler(args2);
|
|
1417
|
+
return {
|
|
1418
|
+
content: result.content,
|
|
1419
|
+
isError: result.isError
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
default:
|
|
1423
|
+
return {
|
|
1424
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
1425
|
+
isError: true
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
}, "callToolRequestHandler");
|
|
1429
|
+
server.setRequestHandler(ListToolsRequestSchema, listToolsHandler);
|
|
1430
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1431
|
+
return callToolRequestHandler(request.params);
|
|
1432
|
+
});
|
|
1433
|
+
const shutdown = /* @__PURE__ */ __name(async () => {
|
|
1434
|
+
}, "shutdown");
|
|
1435
|
+
return {
|
|
1436
|
+
server,
|
|
1437
|
+
listToolsHandler,
|
|
1438
|
+
callToolHandler: callToolRequestHandler,
|
|
1439
|
+
shutdown
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
__name(createMcpExecServer, "createMcpExecServer");
|
|
1443
|
+
|
|
1444
|
+
// dist/index.js
|
|
1445
|
+
import { ServerPool, createConnection, getServerConfig as getServerConfig2, loadServerManifest as loadServerManifest2 } from "@justanothermldude/meta-mcp-core";
|
|
1446
|
+
var APP_NAME = "mcp-exec";
|
|
1447
|
+
var VERSION2 = "1.2.0";
|
|
1448
|
+
var defaultExecutor = null;
|
|
31
1449
|
function getDefaultExecutor() {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* @returns New SandboxExecutor instance
|
|
53
|
-
*/
|
|
54
|
-
export function createExecutor(config) {
|
|
55
|
-
return new SandboxExecutor(config);
|
|
56
|
-
}
|
|
57
|
-
// Handle --version and --help flags
|
|
58
|
-
const args = process.argv.slice(2);
|
|
59
|
-
if (args.includes('--version') || args.includes('-v')) {
|
|
60
|
-
console.log(VERSION);
|
|
61
|
-
process.exit(0);
|
|
1450
|
+
if (!defaultExecutor) {
|
|
1451
|
+
defaultExecutor = new SandboxExecutor();
|
|
1452
|
+
}
|
|
1453
|
+
return defaultExecutor;
|
|
1454
|
+
}
|
|
1455
|
+
__name(getDefaultExecutor, "getDefaultExecutor");
|
|
1456
|
+
async function executeCode(input) {
|
|
1457
|
+
const { code, timeout_ms = DEFAULT_TIMEOUT_MS } = input;
|
|
1458
|
+
const executor = getDefaultExecutor();
|
|
1459
|
+
return executor.execute(code, timeout_ms);
|
|
1460
|
+
}
|
|
1461
|
+
__name(executeCode, "executeCode");
|
|
1462
|
+
function createExecutor(config) {
|
|
1463
|
+
return new SandboxExecutor(config);
|
|
1464
|
+
}
|
|
1465
|
+
__name(createExecutor, "createExecutor");
|
|
1466
|
+
var args = process.argv.slice(2);
|
|
1467
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
1468
|
+
console.log(VERSION2);
|
|
1469
|
+
process.exit(0);
|
|
62
1470
|
}
|
|
63
|
-
if (args.includes(
|
|
64
|
-
|
|
1471
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1472
|
+
console.log(`${APP_NAME} v${VERSION2}
|
|
65
1473
|
|
|
66
1474
|
An MCP server for executing TypeScript/JavaScript code in sandboxed environments
|
|
67
1475
|
with access to MCP tools via HTTP bridge.
|
|
@@ -75,51 +1483,76 @@ Environment:
|
|
|
75
1483
|
SERVERS_CONFIG Path to servers.json config file
|
|
76
1484
|
Default: ~/.meta-mcp/servers.json
|
|
77
1485
|
`);
|
|
78
|
-
|
|
1486
|
+
process.exit(0);
|
|
79
1487
|
}
|
|
80
1488
|
async function main() {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return createConnection({ ...config, name: serverId }, {
|
|
95
|
-
gatewayAuth: { useCursorToken: true }
|
|
96
|
-
});
|
|
97
|
-
};
|
|
98
|
-
// Initialize pool
|
|
99
|
-
const pool = new ServerPool(connectionFactory);
|
|
100
|
-
// Create server
|
|
101
|
-
const { server, shutdown } = createMcpExecServer(pool);
|
|
102
|
-
// Graceful shutdown handlers
|
|
103
|
-
const handleShutdown = async () => {
|
|
104
|
-
process.stderr.write('Shutting down...\n');
|
|
105
|
-
await shutdown();
|
|
106
|
-
await pool.shutdown();
|
|
107
|
-
await server.close();
|
|
108
|
-
process.exit(0);
|
|
109
|
-
};
|
|
110
|
-
process.on('SIGINT', handleShutdown);
|
|
111
|
-
process.on('SIGTERM', handleShutdown);
|
|
112
|
-
// Connect via stdio
|
|
113
|
-
const transport = new StdioServerTransport();
|
|
114
|
-
await server.connect(transport);
|
|
115
|
-
process.stderr.write('mcp-exec server running on stdio\n');
|
|
116
|
-
}
|
|
117
|
-
// Only run main if this is the entry point (not when imported as a module)
|
|
118
|
-
const isMainModule = process.argv[1]?.endsWith('index.js') || process.argv[1]?.endsWith('mcp-exec');
|
|
119
|
-
if (isMainModule && !args.includes('--no-server')) {
|
|
120
|
-
main().catch((error) => {
|
|
121
|
-
console.error('Fatal error:', error);
|
|
122
|
-
process.exit(1);
|
|
1489
|
+
const configPath = process.env.SERVERS_CONFIG;
|
|
1490
|
+
if (configPath) {
|
|
1491
|
+
process.stderr.write(`Loading config from: ${configPath}
|
|
1492
|
+
`);
|
|
1493
|
+
}
|
|
1494
|
+
loadServerManifest2();
|
|
1495
|
+
const connectionFactory = /* @__PURE__ */ __name(async (serverId) => {
|
|
1496
|
+
const config = getServerConfig2(serverId);
|
|
1497
|
+
if (!config) {
|
|
1498
|
+
throw new Error(`Server config not found: ${serverId}`);
|
|
1499
|
+
}
|
|
1500
|
+
return createConnection({ ...config, name: serverId }, {
|
|
1501
|
+
gatewayAuth: { useCursorToken: true }
|
|
123
1502
|
});
|
|
1503
|
+
}, "connectionFactory");
|
|
1504
|
+
const pool = new ServerPool(connectionFactory);
|
|
1505
|
+
const { server, shutdown } = createMcpExecServer(pool);
|
|
1506
|
+
const handleShutdown = /* @__PURE__ */ __name(async () => {
|
|
1507
|
+
process.stderr.write("Shutting down...\n");
|
|
1508
|
+
await shutdown();
|
|
1509
|
+
await pool.shutdown();
|
|
1510
|
+
await server.close();
|
|
1511
|
+
process.exit(0);
|
|
1512
|
+
}, "handleShutdown");
|
|
1513
|
+
process.on("SIGINT", handleShutdown);
|
|
1514
|
+
process.on("SIGTERM", handleShutdown);
|
|
1515
|
+
const transport = new StdioServerTransport();
|
|
1516
|
+
await server.connect(transport);
|
|
1517
|
+
process.stderr.write("mcp-exec server running on stdio\n");
|
|
1518
|
+
}
|
|
1519
|
+
__name(main, "main");
|
|
1520
|
+
var isMainModule = process.argv[1]?.endsWith("index.js") || process.argv[1]?.endsWith("mcp-exec");
|
|
1521
|
+
if (isMainModule && !args.includes("--no-server")) {
|
|
1522
|
+
main().catch((error) => {
|
|
1523
|
+
console.error("Fatal error:", error);
|
|
1524
|
+
process.exit(1);
|
|
1525
|
+
});
|
|
124
1526
|
}
|
|
125
|
-
|
|
1527
|
+
export {
|
|
1528
|
+
APP_NAME,
|
|
1529
|
+
DEFAULT_MCP_BRIDGE_PORT,
|
|
1530
|
+
DEFAULT_TIMEOUT_MS,
|
|
1531
|
+
MCPBridge,
|
|
1532
|
+
SandboxExecutor,
|
|
1533
|
+
VERSION2 as VERSION,
|
|
1534
|
+
VirtualModuleResolver,
|
|
1535
|
+
cleanupStaleProcess,
|
|
1536
|
+
createDefaultFilesystemConfig,
|
|
1537
|
+
createDefaultNetworkConfig,
|
|
1538
|
+
createExecuteWithWrappersHandler,
|
|
1539
|
+
createExecutor,
|
|
1540
|
+
createGetToolSchemaHandler,
|
|
1541
|
+
createListServersHandler,
|
|
1542
|
+
createMcpExecServer,
|
|
1543
|
+
createModuleResolver,
|
|
1544
|
+
createSandboxRuntimeConfig,
|
|
1545
|
+
executeCode,
|
|
1546
|
+
executeCodeWithWrappersTool,
|
|
1547
|
+
generateFromManifest,
|
|
1548
|
+
generateMcpDictionary,
|
|
1549
|
+
generateServerModule,
|
|
1550
|
+
generateToolWrapper,
|
|
1551
|
+
getMcpToolSchemaTool,
|
|
1552
|
+
isExecuteWithWrappersInput,
|
|
1553
|
+
isGetToolSchemaInput,
|
|
1554
|
+
isListServersInput,
|
|
1555
|
+
isPortInUse,
|
|
1556
|
+
listAvailableMcpServersTool,
|
|
1557
|
+
normalizeName
|
|
1558
|
+
};
|