@minicor/mcp-server 3.3.1 → 3.3.4
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 +36 -3
- package/dist/__tests__/codemode.test.d.ts +2 -0
- package/dist/__tests__/codemode.test.d.ts.map +1 -0
- package/dist/__tests__/codemode.test.js +129 -0
- package/dist/__tests__/codemode.test.js.map +1 -0
- package/dist/codemode.d.ts +60 -0
- package/dist/codemode.d.ts.map +1 -0
- package/dist/codemode.js +726 -0
- package/dist/codemode.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -2
- package/dist/index.js.map +1 -1
- package/dist/laminar-client.d.ts.map +1 -1
- package/dist/laminar-client.js +3 -1
- package/dist/laminar-client.js.map +1 -1
- package/dist/lib.d.ts +2 -0
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +17 -2
- package/dist/lib.js.map +1 -1
- package/dist/tools/core.js +2 -2
- package/dist/tools/core.js.map +1 -1
- package/dist/tools/workflow-ops.js +1 -1
- package/dist/tools/workflow-ops.js.map +1 -1
- package/package.json +1 -1
package/dist/codemode.js
ADDED
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { json } from "./helpers.js";
|
|
4
|
+
const RESERVED_WORDS = new Set([
|
|
5
|
+
"abstract",
|
|
6
|
+
"arguments",
|
|
7
|
+
"await",
|
|
8
|
+
"boolean",
|
|
9
|
+
"break",
|
|
10
|
+
"byte",
|
|
11
|
+
"case",
|
|
12
|
+
"catch",
|
|
13
|
+
"char",
|
|
14
|
+
"class",
|
|
15
|
+
"const",
|
|
16
|
+
"continue",
|
|
17
|
+
"debugger",
|
|
18
|
+
"default",
|
|
19
|
+
"delete",
|
|
20
|
+
"do",
|
|
21
|
+
"double",
|
|
22
|
+
"else",
|
|
23
|
+
"enum",
|
|
24
|
+
"eval",
|
|
25
|
+
"export",
|
|
26
|
+
"extends",
|
|
27
|
+
"false",
|
|
28
|
+
"final",
|
|
29
|
+
"finally",
|
|
30
|
+
"float",
|
|
31
|
+
"for",
|
|
32
|
+
"function",
|
|
33
|
+
"goto",
|
|
34
|
+
"if",
|
|
35
|
+
"implements",
|
|
36
|
+
"import",
|
|
37
|
+
"in",
|
|
38
|
+
"instanceof",
|
|
39
|
+
"int",
|
|
40
|
+
"interface",
|
|
41
|
+
"let",
|
|
42
|
+
"long",
|
|
43
|
+
"native",
|
|
44
|
+
"new",
|
|
45
|
+
"null",
|
|
46
|
+
"package",
|
|
47
|
+
"private",
|
|
48
|
+
"protected",
|
|
49
|
+
"public",
|
|
50
|
+
"return",
|
|
51
|
+
"short",
|
|
52
|
+
"static",
|
|
53
|
+
"super",
|
|
54
|
+
"switch",
|
|
55
|
+
"synchronized",
|
|
56
|
+
"this",
|
|
57
|
+
"throw",
|
|
58
|
+
"throws",
|
|
59
|
+
"transient",
|
|
60
|
+
"true",
|
|
61
|
+
"try",
|
|
62
|
+
"typeof",
|
|
63
|
+
"var",
|
|
64
|
+
"void",
|
|
65
|
+
"volatile",
|
|
66
|
+
"while",
|
|
67
|
+
"with",
|
|
68
|
+
"yield",
|
|
69
|
+
]);
|
|
70
|
+
function sanitizeToolName(name) {
|
|
71
|
+
let sanitized = name.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
72
|
+
if (!sanitized || /^[0-9]/.test(sanitized))
|
|
73
|
+
sanitized = `_${sanitized}`;
|
|
74
|
+
if (RESERVED_WORDS.has(sanitized))
|
|
75
|
+
sanitized = `${sanitized}_`;
|
|
76
|
+
return sanitized;
|
|
77
|
+
}
|
|
78
|
+
function getDef(schema) {
|
|
79
|
+
return schema?._def ?? schema?.def;
|
|
80
|
+
}
|
|
81
|
+
function getTypeName(schema) {
|
|
82
|
+
const def = getDef(schema);
|
|
83
|
+
const typeName = def?.typeName ?? def?.type;
|
|
84
|
+
return typeof typeName === "string" ? typeName : "";
|
|
85
|
+
}
|
|
86
|
+
function isOptionalSchema(schema) {
|
|
87
|
+
const typeName = getTypeName(schema);
|
|
88
|
+
return (typeName === "optional" ||
|
|
89
|
+
typeName === "default" ||
|
|
90
|
+
typeName === "ZodOptional" ||
|
|
91
|
+
typeName === "ZodDefault" ||
|
|
92
|
+
typeof schema?.isOptional === "function" && schema.isOptional());
|
|
93
|
+
}
|
|
94
|
+
function unwrapSchema(schema) {
|
|
95
|
+
let current = schema;
|
|
96
|
+
for (let i = 0; i < 8; i += 1) {
|
|
97
|
+
const typeName = getTypeName(current);
|
|
98
|
+
const def = getDef(current);
|
|
99
|
+
if (typeName === "optional" ||
|
|
100
|
+
typeName === "nullable" ||
|
|
101
|
+
typeName === "default" ||
|
|
102
|
+
typeName === "catch" ||
|
|
103
|
+
typeName === "ZodOptional" ||
|
|
104
|
+
typeName === "ZodNullable" ||
|
|
105
|
+
typeName === "ZodDefault" ||
|
|
106
|
+
typeName === "ZodCatch") {
|
|
107
|
+
current = def?.innerType ?? def?.type ?? def?.schema ?? current;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
return current;
|
|
111
|
+
}
|
|
112
|
+
return current;
|
|
113
|
+
}
|
|
114
|
+
function literalValue(schema) {
|
|
115
|
+
const def = getDef(schema);
|
|
116
|
+
if ("value" in (def ?? {}))
|
|
117
|
+
return def.value;
|
|
118
|
+
if (Array.isArray(def?.values))
|
|
119
|
+
return def.values[0];
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
function enumValues(schema) {
|
|
123
|
+
const def = getDef(schema);
|
|
124
|
+
if (Array.isArray(def?.values))
|
|
125
|
+
return def.values.map(String);
|
|
126
|
+
if (Array.isArray(def?.options))
|
|
127
|
+
return def.options.map(String);
|
|
128
|
+
if (def?.entries && typeof def.entries === "object") {
|
|
129
|
+
return Object.values(def.entries).map(String);
|
|
130
|
+
}
|
|
131
|
+
if (schema?.enum && typeof schema.enum === "object") {
|
|
132
|
+
return Object.values(schema.enum).map(String);
|
|
133
|
+
}
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
function objectShape(schema) {
|
|
137
|
+
const def = getDef(schema);
|
|
138
|
+
const shape = def?.shape;
|
|
139
|
+
if (typeof shape === "function")
|
|
140
|
+
return shape();
|
|
141
|
+
if (shape && typeof shape === "object")
|
|
142
|
+
return shape;
|
|
143
|
+
return {};
|
|
144
|
+
}
|
|
145
|
+
function schemaToType(schema) {
|
|
146
|
+
const rawTypeName = getTypeName(schema);
|
|
147
|
+
const unwrapped = unwrapSchema(schema);
|
|
148
|
+
const typeName = getTypeName(unwrapped) || rawTypeName;
|
|
149
|
+
const def = getDef(unwrapped);
|
|
150
|
+
switch (typeName) {
|
|
151
|
+
case "string":
|
|
152
|
+
case "ZodString":
|
|
153
|
+
return "string";
|
|
154
|
+
case "number":
|
|
155
|
+
case "ZodNumber":
|
|
156
|
+
return "number";
|
|
157
|
+
case "boolean":
|
|
158
|
+
case "ZodBoolean":
|
|
159
|
+
return "boolean";
|
|
160
|
+
case "bigint":
|
|
161
|
+
case "ZodBigInt":
|
|
162
|
+
return "bigint";
|
|
163
|
+
case "date":
|
|
164
|
+
case "ZodDate":
|
|
165
|
+
return "Date";
|
|
166
|
+
case "unknown":
|
|
167
|
+
case "any":
|
|
168
|
+
case "ZodUnknown":
|
|
169
|
+
case "ZodAny":
|
|
170
|
+
return "unknown";
|
|
171
|
+
case "never":
|
|
172
|
+
case "ZodNever":
|
|
173
|
+
return "never";
|
|
174
|
+
case "null":
|
|
175
|
+
case "ZodNull":
|
|
176
|
+
return "null";
|
|
177
|
+
case "literal":
|
|
178
|
+
case "ZodLiteral":
|
|
179
|
+
return JSON.stringify(literalValue(unwrapped));
|
|
180
|
+
case "enum":
|
|
181
|
+
case "nativeEnum":
|
|
182
|
+
case "ZodEnum": {
|
|
183
|
+
const values = enumValues(unwrapped);
|
|
184
|
+
return values.length > 0 ? values.map((v) => JSON.stringify(v)).join(" | ") : "string";
|
|
185
|
+
}
|
|
186
|
+
case "array":
|
|
187
|
+
case "ZodArray": {
|
|
188
|
+
const item = def?.element ?? def?.type ?? def?.items;
|
|
189
|
+
return `${schemaToType(item)}[]`;
|
|
190
|
+
}
|
|
191
|
+
case "record":
|
|
192
|
+
case "ZodRecord": {
|
|
193
|
+
const valueType = def?.valueType ?? def?.valueSchema;
|
|
194
|
+
return `Record<string, ${valueType ? schemaToType(valueType) : "unknown"}>`;
|
|
195
|
+
}
|
|
196
|
+
case "object":
|
|
197
|
+
case "ZodObject": {
|
|
198
|
+
const entries = Object.entries(objectShape(unwrapped));
|
|
199
|
+
if (entries.length === 0)
|
|
200
|
+
return "Record<string, unknown>";
|
|
201
|
+
const props = entries.map(([key, value]) => {
|
|
202
|
+
const optional = isOptionalSchema(value);
|
|
203
|
+
return `${key}${optional ? "?" : ""}: ${schemaToType(value)}`;
|
|
204
|
+
});
|
|
205
|
+
return `{ ${props.join("; ")} }`;
|
|
206
|
+
}
|
|
207
|
+
case "union":
|
|
208
|
+
case "ZodUnion": {
|
|
209
|
+
const options = Array.isArray(def?.options) ? def.options : [];
|
|
210
|
+
return options.length > 0
|
|
211
|
+
? options.map((option) => schemaToType(option)).join(" | ")
|
|
212
|
+
: "unknown";
|
|
213
|
+
}
|
|
214
|
+
case "intersection":
|
|
215
|
+
case "ZodIntersection":
|
|
216
|
+
return `${schemaToType(def?.left)} & ${schemaToType(def?.right)}`;
|
|
217
|
+
case "tuple":
|
|
218
|
+
case "ZodTuple": {
|
|
219
|
+
const items = Array.isArray(def?.items) ? def.items : [];
|
|
220
|
+
return `[${items.map((item) => schemaToType(item)).join(", ")}]`;
|
|
221
|
+
}
|
|
222
|
+
default:
|
|
223
|
+
return "unknown";
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function getDescription(schema) {
|
|
227
|
+
const description = schema?.description ?? getDef(schema)?.description;
|
|
228
|
+
return typeof description === "string" && description.length > 0
|
|
229
|
+
? description
|
|
230
|
+
: undefined;
|
|
231
|
+
}
|
|
232
|
+
function buildToolDoc(tool) {
|
|
233
|
+
const parameters = Object.entries(tool.inputSchema ?? {}).map(([name, schema]) => ({
|
|
234
|
+
name,
|
|
235
|
+
optional: isOptionalSchema(schema),
|
|
236
|
+
type: schemaToType(schema),
|
|
237
|
+
description: getDescription(schema),
|
|
238
|
+
}));
|
|
239
|
+
const inputTypeName = `${toPascalCase(tool.apiName)}Input`;
|
|
240
|
+
const lines = [
|
|
241
|
+
`interface ${inputTypeName} {`,
|
|
242
|
+
...parameters.map((param) => {
|
|
243
|
+
const comment = param.description ? ` /** ${param.description.replace(/\*\//g, "* /")} */\n` : "";
|
|
244
|
+
return `${comment} ${param.name}${param.optional ? "?" : ""}: ${param.type};`;
|
|
245
|
+
}),
|
|
246
|
+
"}",
|
|
247
|
+
`declare const minicor: { ${tool.apiName}: (input: ${inputTypeName}) => Promise<unknown>; };`,
|
|
248
|
+
];
|
|
249
|
+
return {
|
|
250
|
+
name: tool.name,
|
|
251
|
+
apiName: tool.apiName,
|
|
252
|
+
qualifiedName: `minicor.${tool.apiName}`,
|
|
253
|
+
description: tool.description,
|
|
254
|
+
parameters,
|
|
255
|
+
signature: lines.join("\n"),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function toPascalCase(value) {
|
|
259
|
+
return value
|
|
260
|
+
.split(/[^A-Za-z0-9]+|_/g)
|
|
261
|
+
.filter(Boolean)
|
|
262
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
263
|
+
.join("") || "Tool";
|
|
264
|
+
}
|
|
265
|
+
function compactDoc(doc) {
|
|
266
|
+
return {
|
|
267
|
+
name: doc.qualifiedName,
|
|
268
|
+
tool: doc.name,
|
|
269
|
+
description: doc.description,
|
|
270
|
+
parameters: doc.parameters.map((param) => ({
|
|
271
|
+
name: param.name,
|
|
272
|
+
type: param.type,
|
|
273
|
+
optional: param.optional,
|
|
274
|
+
description: param.description,
|
|
275
|
+
})),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function parseTextValue(value) {
|
|
279
|
+
const trimmed = value.trim();
|
|
280
|
+
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
281
|
+
(trimmed.startsWith("[") && trimmed.endsWith("]")) ||
|
|
282
|
+
trimmed === "null" ||
|
|
283
|
+
trimmed === "true" ||
|
|
284
|
+
trimmed === "false") {
|
|
285
|
+
try {
|
|
286
|
+
return JSON.parse(trimmed);
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return value;
|
|
293
|
+
}
|
|
294
|
+
function toolResultToValue(result) {
|
|
295
|
+
if (!result || typeof result !== "object")
|
|
296
|
+
return result;
|
|
297
|
+
const maybeTool = result;
|
|
298
|
+
if (!Array.isArray(maybeTool.content))
|
|
299
|
+
return result;
|
|
300
|
+
const textItems = maybeTool.content.filter((item) => item.type === "text" && typeof item.text === "string");
|
|
301
|
+
if (maybeTool.content.length === 1 && textItems.length === 1) {
|
|
302
|
+
return parseTextValue(textItems[0].text ?? "");
|
|
303
|
+
}
|
|
304
|
+
if (textItems.length === maybeTool.content.length) {
|
|
305
|
+
return textItems.map((item) => item.text ?? "").join("\n");
|
|
306
|
+
}
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
function extractRenderableContent(result) {
|
|
310
|
+
if (!result || typeof result !== "object")
|
|
311
|
+
return [];
|
|
312
|
+
const maybeTool = result;
|
|
313
|
+
if (!Array.isArray(maybeTool.content))
|
|
314
|
+
return [];
|
|
315
|
+
return maybeTool.content.filter((item) => item.type !== "text");
|
|
316
|
+
}
|
|
317
|
+
function sanitizeForText(value, seen = new WeakSet()) {
|
|
318
|
+
if (!value || typeof value !== "object")
|
|
319
|
+
return value;
|
|
320
|
+
if (seen.has(value))
|
|
321
|
+
return "[Circular]";
|
|
322
|
+
seen.add(value);
|
|
323
|
+
if (Array.isArray(value)) {
|
|
324
|
+
return value.map((item) => sanitizeForText(item, seen));
|
|
325
|
+
}
|
|
326
|
+
const record = value;
|
|
327
|
+
const copy = {};
|
|
328
|
+
for (const [key, item] of Object.entries(record)) {
|
|
329
|
+
if (key === "data" &&
|
|
330
|
+
typeof item === "string" &&
|
|
331
|
+
(record.type === "image" || record.type === "audio" || record.type === "resource")) {
|
|
332
|
+
copy[key] = `[${item.length} base64 chars omitted from text summary]`;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
copy[key] = sanitizeForText(item, seen);
|
|
336
|
+
}
|
|
337
|
+
return copy;
|
|
338
|
+
}
|
|
339
|
+
const WORKER_SOURCE = `
|
|
340
|
+
const { parentPort, workerData } = require("node:worker_threads");
|
|
341
|
+
const vm = require("node:vm");
|
|
342
|
+
|
|
343
|
+
const pending = new Map();
|
|
344
|
+
let seq = 0;
|
|
345
|
+
|
|
346
|
+
function serializeError(error) {
|
|
347
|
+
if (!error) return "Unknown error";
|
|
348
|
+
if (error && typeof error.stack === "string") return error.stack;
|
|
349
|
+
if (error && typeof error.message === "string") return error.message;
|
|
350
|
+
return String(error);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function send(type, payload) {
|
|
354
|
+
parentPort.postMessage({ type, ...payload });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function hostCall(tool, args) {
|
|
358
|
+
return new Promise((resolve, reject) => {
|
|
359
|
+
const id = ++seq;
|
|
360
|
+
pending.set(id, { resolve, reject });
|
|
361
|
+
send("toolCall", { id, tool, args: args ?? {} });
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
parentPort.on("message", (message) => {
|
|
366
|
+
const entry = pending.get(message.id);
|
|
367
|
+
if (!entry) return;
|
|
368
|
+
pending.delete(message.id);
|
|
369
|
+
if (message.type === "toolResult") {
|
|
370
|
+
entry.resolve(message.result);
|
|
371
|
+
} else if (message.type === "toolError") {
|
|
372
|
+
entry.reject(new Error(message.error));
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
function searchTools(query, limit = 12) {
|
|
377
|
+
const q = String(query ?? "").toLowerCase();
|
|
378
|
+
const terms = q.split(/\\s+/).filter(Boolean);
|
|
379
|
+
const scored = workerData.tools.map((tool) => {
|
|
380
|
+
const haystack = [tool.qualifiedName, tool.name, tool.description, ...tool.parameters.map((p) => [p.name, p.description, p.type].filter(Boolean).join(" "))].join(" ").toLowerCase();
|
|
381
|
+
const score = terms.length === 0 ? 1 : terms.reduce((sum, term) => sum + (haystack.includes(term) ? 1 : 0), 0);
|
|
382
|
+
return { tool, score };
|
|
383
|
+
}).filter((entry) => entry.score > 0);
|
|
384
|
+
scored.sort((a, b) => b.score - a.score || a.tool.qualifiedName.localeCompare(b.tool.qualifiedName));
|
|
385
|
+
return scored.slice(0, limit).map((entry) => ({
|
|
386
|
+
name: entry.tool.qualifiedName,
|
|
387
|
+
tool: entry.tool.name,
|
|
388
|
+
description: entry.tool.description,
|
|
389
|
+
parameters: entry.tool.parameters
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function describeTool(name) {
|
|
394
|
+
const needle = String(name ?? "");
|
|
395
|
+
const normalized = needle.startsWith("minicor.") ? needle.slice("minicor.".length) : needle;
|
|
396
|
+
const tool = workerData.tools.find((candidate) => candidate.qualifiedName === needle || candidate.apiName === normalized || candidate.name === normalized);
|
|
397
|
+
if (!tool) {
|
|
398
|
+
return {
|
|
399
|
+
error: "Tool not found",
|
|
400
|
+
query: needle,
|
|
401
|
+
hint: "Call await codemode.search(query) to find available tools."
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
return tool;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function makeConnector(includeDiscovery) {
|
|
408
|
+
return new Proxy({}, {
|
|
409
|
+
get(_target, prop) {
|
|
410
|
+
if (typeof prop !== "string") return undefined;
|
|
411
|
+
if (prop === "then") return undefined;
|
|
412
|
+
if (includeDiscovery) {
|
|
413
|
+
if (prop === "search") return (query, limit) => Promise.resolve(searchTools(query, limit));
|
|
414
|
+
if (prop === "describe") return (name) => Promise.resolve(describeTool(name));
|
|
415
|
+
if (prop === "list") return () => Promise.resolve(workerData.tools.map((tool) => ({
|
|
416
|
+
name: tool.qualifiedName,
|
|
417
|
+
tool: tool.name,
|
|
418
|
+
description: tool.description
|
|
419
|
+
})));
|
|
420
|
+
}
|
|
421
|
+
return (args = {}) => hostCall(prop, args);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function normalizeCode(code) {
|
|
427
|
+
const trimmed = String(code ?? "").trim();
|
|
428
|
+
const unwrapped = trimmed.startsWith("(") && trimmed.endsWith(")")
|
|
429
|
+
? trimmed.slice(1, -1).trim()
|
|
430
|
+
: trimmed;
|
|
431
|
+
if (/^(async\\s*)?\\(?\\s*(?:[A-Za-z_$][\\w$]*\\s*)?\\)?\\s*=>/.test(unwrapped) || unwrapped.startsWith("async function") || unwrapped.startsWith("function")) {
|
|
432
|
+
return "__codemodeRun((" + trimmed + ")());";
|
|
433
|
+
}
|
|
434
|
+
return "__codemodeRun((async () => {\\n" + code + "\\n})());";
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const logs = [];
|
|
438
|
+
const consoleProxy = {};
|
|
439
|
+
for (const level of ["log", "info", "warn", "error"]) {
|
|
440
|
+
consoleProxy[level] = (...args) => {
|
|
441
|
+
const rendered = args.map((arg) => {
|
|
442
|
+
if (typeof arg === "string") return arg;
|
|
443
|
+
try {
|
|
444
|
+
return JSON.stringify(arg);
|
|
445
|
+
} catch {
|
|
446
|
+
return String(arg);
|
|
447
|
+
}
|
|
448
|
+
}).join(" ");
|
|
449
|
+
logs.push({ level, message: rendered });
|
|
450
|
+
send("log", { level, message: rendered });
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const context = vm.createContext({
|
|
455
|
+
console: consoleProxy,
|
|
456
|
+
codemode: makeConnector(true),
|
|
457
|
+
minicor: makeConnector(false),
|
|
458
|
+
setTimeout,
|
|
459
|
+
clearTimeout,
|
|
460
|
+
Promise,
|
|
461
|
+
JSON,
|
|
462
|
+
Math,
|
|
463
|
+
Date,
|
|
464
|
+
RegExp,
|
|
465
|
+
String,
|
|
466
|
+
Number,
|
|
467
|
+
Boolean,
|
|
468
|
+
Array,
|
|
469
|
+
Object,
|
|
470
|
+
Map,
|
|
471
|
+
Set,
|
|
472
|
+
URL,
|
|
473
|
+
URLSearchParams,
|
|
474
|
+
TextEncoder,
|
|
475
|
+
TextDecoder,
|
|
476
|
+
__codemodeRun: async (promise) => {
|
|
477
|
+
try {
|
|
478
|
+
const result = await promise;
|
|
479
|
+
send("result", { result, logs });
|
|
480
|
+
} catch (error) {
|
|
481
|
+
send("error", { error: serializeError(error), logs });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}, {
|
|
485
|
+
codeGeneration: { strings: false, wasm: false }
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
const script = new vm.Script(normalizeCode(workerData.code), {
|
|
490
|
+
filename: "minicor-codemode.js",
|
|
491
|
+
displayErrors: true,
|
|
492
|
+
});
|
|
493
|
+
script.runInContext(context, { timeout: workerData.syncTimeoutMs });
|
|
494
|
+
} catch (error) {
|
|
495
|
+
send("error", { error: serializeError(error), logs });
|
|
496
|
+
}
|
|
497
|
+
`;
|
|
498
|
+
export class CodeModeToolRegistry {
|
|
499
|
+
server;
|
|
500
|
+
tools = [];
|
|
501
|
+
byApiName = new Map();
|
|
502
|
+
exposeLegacyTools;
|
|
503
|
+
defaultTimeoutMs;
|
|
504
|
+
maxTimeoutMs;
|
|
505
|
+
constructor(server, options = {}) {
|
|
506
|
+
this.server = server;
|
|
507
|
+
this.exposeLegacyTools = options.exposeLegacyTools ?? false;
|
|
508
|
+
this.defaultTimeoutMs = options.defaultTimeoutMs ?? 30_000;
|
|
509
|
+
this.maxTimeoutMs = options.maxTimeoutMs ?? 120_000;
|
|
510
|
+
}
|
|
511
|
+
tool(name, ...args) {
|
|
512
|
+
const handler = args[args.length - 1];
|
|
513
|
+
if (typeof handler !== "function") {
|
|
514
|
+
throw new Error(`Tool ${name} did not provide a handler.`);
|
|
515
|
+
}
|
|
516
|
+
let description = "";
|
|
517
|
+
let inputSchema = {};
|
|
518
|
+
if (typeof args[0] === "string") {
|
|
519
|
+
description = args[0];
|
|
520
|
+
inputSchema = args[1] && typeof args[1] === "object" ? args[1] : {};
|
|
521
|
+
}
|
|
522
|
+
else if (args[0] && typeof args[0] === "object") {
|
|
523
|
+
inputSchema = args[0];
|
|
524
|
+
}
|
|
525
|
+
const apiName = this.uniqueApiName(name);
|
|
526
|
+
const tool = {
|
|
527
|
+
name,
|
|
528
|
+
apiName,
|
|
529
|
+
description,
|
|
530
|
+
inputSchema,
|
|
531
|
+
handler,
|
|
532
|
+
};
|
|
533
|
+
this.tools.push(tool);
|
|
534
|
+
this.byApiName.set(apiName, tool);
|
|
535
|
+
if (this.exposeLegacyTools) {
|
|
536
|
+
return this.server.tool(name, ...args);
|
|
537
|
+
}
|
|
538
|
+
return undefined;
|
|
539
|
+
}
|
|
540
|
+
prompt(...args) {
|
|
541
|
+
return this.server.prompt(...args);
|
|
542
|
+
}
|
|
543
|
+
registerCodeModeTool() {
|
|
544
|
+
const description = [
|
|
545
|
+
"Run JavaScript code that orchestrates the Minicor MCP API.",
|
|
546
|
+
"Inside the sandbox, call Minicor tools as async functions on the `minicor` global, for example:",
|
|
547
|
+
"`const workspaces = await minicor.list_workspaces({});`",
|
|
548
|
+
"Use `await codemode.search(\"workflow\")` to discover methods and `await codemode.describe(\"minicor.create_rpa_flow\")` for parameter docs.",
|
|
549
|
+
"Return a small JSON-serializable value or use console.log for progress. Tool calls should be awaited sequentially when actions depend on previous results.",
|
|
550
|
+
"The sandbox has no require/process/fetch access; effects go through Minicor tool methods.",
|
|
551
|
+
].join("\n");
|
|
552
|
+
this.server.tool("codemode", description, {
|
|
553
|
+
code: z
|
|
554
|
+
.string()
|
|
555
|
+
.describe("JavaScript code to run. It is wrapped in an async function, so top-level await and return are supported. Example: `const ws = await minicor.list_workspaces({}); return ws;`"),
|
|
556
|
+
timeoutMs: z
|
|
557
|
+
.number()
|
|
558
|
+
.int()
|
|
559
|
+
.positive()
|
|
560
|
+
.max(this.maxTimeoutMs)
|
|
561
|
+
.optional()
|
|
562
|
+
.describe(`Optional execution timeout in milliseconds. Default ${this.defaultTimeoutMs}.`),
|
|
563
|
+
}, async ({ code, timeoutMs }) => {
|
|
564
|
+
const result = await this.execute(code, timeoutMs);
|
|
565
|
+
const renderable = extractRenderableContent(result.result);
|
|
566
|
+
const textResult = {
|
|
567
|
+
...result,
|
|
568
|
+
...(result.result !== undefined
|
|
569
|
+
? { result: sanitizeForText(result.result) }
|
|
570
|
+
: {}),
|
|
571
|
+
};
|
|
572
|
+
return {
|
|
573
|
+
content: [
|
|
574
|
+
{
|
|
575
|
+
type: "text",
|
|
576
|
+
text: json(textResult),
|
|
577
|
+
},
|
|
578
|
+
...renderable.map((item) => ({
|
|
579
|
+
...item,
|
|
580
|
+
type: item.type,
|
|
581
|
+
})),
|
|
582
|
+
],
|
|
583
|
+
};
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
docs() {
|
|
587
|
+
return this.tools.map(buildToolDoc);
|
|
588
|
+
}
|
|
589
|
+
async execute(code, timeoutMs) {
|
|
590
|
+
const timeout = Math.min(timeoutMs ?? this.defaultTimeoutMs, this.maxTimeoutMs);
|
|
591
|
+
const logs = [];
|
|
592
|
+
const startedAt = Date.now();
|
|
593
|
+
return new Promise((resolve) => {
|
|
594
|
+
let settled = false;
|
|
595
|
+
const worker = new Worker(WORKER_SOURCE, {
|
|
596
|
+
eval: true,
|
|
597
|
+
workerData: {
|
|
598
|
+
code,
|
|
599
|
+
tools: this.docs(),
|
|
600
|
+
syncTimeoutMs: Math.min(timeout, 5_000),
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
const finish = (payload) => {
|
|
604
|
+
if (settled)
|
|
605
|
+
return;
|
|
606
|
+
settled = true;
|
|
607
|
+
clearTimeout(timer);
|
|
608
|
+
void worker.terminate();
|
|
609
|
+
resolve({
|
|
610
|
+
status: payload.status,
|
|
611
|
+
executionMs: Date.now() - startedAt,
|
|
612
|
+
...(payload.result !== undefined ? { result: payload.result } : {}),
|
|
613
|
+
...(payload.error ? { error: payload.error } : {}),
|
|
614
|
+
logs: payload.logs ?? logs,
|
|
615
|
+
});
|
|
616
|
+
};
|
|
617
|
+
const timer = setTimeout(() => {
|
|
618
|
+
finish({
|
|
619
|
+
status: "error",
|
|
620
|
+
error: `Code mode execution timed out after ${timeout}ms.`,
|
|
621
|
+
logs,
|
|
622
|
+
});
|
|
623
|
+
}, timeout);
|
|
624
|
+
worker.on("message", (message) => {
|
|
625
|
+
if (message?.type === "log") {
|
|
626
|
+
logs.push({ level: message.level, message: message.message });
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (message?.type === "toolCall") {
|
|
630
|
+
void this.invoke(message.tool, message.args)
|
|
631
|
+
.then((result) => {
|
|
632
|
+
worker.postMessage({ type: "toolResult", id: message.id, result });
|
|
633
|
+
})
|
|
634
|
+
.catch((error) => {
|
|
635
|
+
worker.postMessage({
|
|
636
|
+
type: "toolError",
|
|
637
|
+
id: message.id,
|
|
638
|
+
error: error?.message ?? String(error),
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (message?.type === "result") {
|
|
644
|
+
finish({
|
|
645
|
+
status: "completed",
|
|
646
|
+
result: message.result,
|
|
647
|
+
logs: message.logs ?? logs,
|
|
648
|
+
});
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (message?.type === "error") {
|
|
652
|
+
finish({
|
|
653
|
+
status: "error",
|
|
654
|
+
error: message.error,
|
|
655
|
+
logs: message.logs ?? logs,
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
worker.on("error", (error) => {
|
|
660
|
+
finish({
|
|
661
|
+
status: "error",
|
|
662
|
+
error: error.stack ?? error.message,
|
|
663
|
+
logs,
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
worker.on("exit", (codeValue) => {
|
|
667
|
+
if (!settled && codeValue !== 0) {
|
|
668
|
+
finish({
|
|
669
|
+
status: "error",
|
|
670
|
+
error: `Code mode worker exited with code ${codeValue}.`,
|
|
671
|
+
logs,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
async invoke(apiName, args) {
|
|
678
|
+
const tool = this.byApiName.get(apiName);
|
|
679
|
+
if (!tool) {
|
|
680
|
+
throw new Error(`Unknown Minicor code-mode method "${apiName}". Use await codemode.search("...") to discover available methods.`);
|
|
681
|
+
}
|
|
682
|
+
const result = await tool.handler(args ?? {});
|
|
683
|
+
return toolResultToValue(result);
|
|
684
|
+
}
|
|
685
|
+
uniqueApiName(name) {
|
|
686
|
+
const base = sanitizeToolName(name);
|
|
687
|
+
let candidate = base;
|
|
688
|
+
let suffix = 2;
|
|
689
|
+
while (this.byApiName.has(candidate)) {
|
|
690
|
+
candidate = `${base}_${suffix}`;
|
|
691
|
+
suffix += 1;
|
|
692
|
+
}
|
|
693
|
+
return candidate;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
export function shouldUseCodeMode() {
|
|
697
|
+
const explicit = process.env.MINICOR_MCP_CODE_MODE;
|
|
698
|
+
if (explicit != null) {
|
|
699
|
+
return !["0", "false", "off", "no"].includes(explicit.toLowerCase());
|
|
700
|
+
}
|
|
701
|
+
return !["1", "true", "on", "yes"].includes((process.env.MINICOR_MCP_LEGACY_TOOLS ?? "").toLowerCase());
|
|
702
|
+
}
|
|
703
|
+
export function buildCodeModeInstructions(baseInstructions) {
|
|
704
|
+
return `${baseInstructions}
|
|
705
|
+
|
|
706
|
+
CODE MODE:
|
|
707
|
+
This MCP exposes Minicor capabilities through a code-mode surface by default. Use the \`codemode\` tool to run JavaScript that calls Minicor methods as \`await minicor.tool_name({ ... })\`.
|
|
708
|
+
|
|
709
|
+
If the server starts unauthenticated and only exposes \`setup_account\`, call \`setup_account\` directly, authenticate, and restart the MCP server. Code mode is enabled for authenticated sessions.
|
|
710
|
+
|
|
711
|
+
Discovery inside code mode:
|
|
712
|
+
- \`await codemode.search("workflow")\` finds relevant Minicor methods.
|
|
713
|
+
- \`await codemode.describe("minicor.session_start")\` returns TypeScript-style parameter docs.
|
|
714
|
+
- \`await codemode.list()\` lists all available methods.
|
|
715
|
+
|
|
716
|
+
If the current directory contains a minicor.json file, your first MCP action should be a \`codemode\` call whose code invokes \`await minicor.session_start({ directory: "<current directory>" })\`. Before ending work, invoke \`await minicor.session_end(...)\` through code mode.
|
|
717
|
+
|
|
718
|
+
Set MINICOR_MCP_LEGACY_TOOLS=1 or MINICOR_MCP_CODE_MODE=0 to expose the legacy one-tool-per-operation MCP surface.`;
|
|
719
|
+
}
|
|
720
|
+
export function asMcpServer(registry) {
|
|
721
|
+
return registry;
|
|
722
|
+
}
|
|
723
|
+
export function compactToolDocs(tools) {
|
|
724
|
+
return tools.map(compactDoc);
|
|
725
|
+
}
|
|
726
|
+
//# sourceMappingURL=codemode.js.map
|