@papicandela/mcx-core 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +95 -14
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/sandbox/bun-worker.ts +47 -9
- package/src/type-generator.ts +66 -9
- package/src/types.ts +4 -0
package/dist/index.js
CHANGED
|
@@ -6521,12 +6521,40 @@ class BunWorkerSandbox {
|
|
|
6521
6521
|
globalThis[key] = value;
|
|
6522
6522
|
}
|
|
6523
6523
|
|
|
6524
|
-
//
|
|
6524
|
+
// Levenshtein distance for fuzzy matching
|
|
6525
|
+
const levenshtein = (a, b) => {
|
|
6526
|
+
if (a.length === 0) return b.length;
|
|
6527
|
+
if (b.length === 0) return a.length;
|
|
6528
|
+
const matrix = [];
|
|
6529
|
+
for (let i = 0; i <= b.length; i++) matrix[i] = [i];
|
|
6530
|
+
for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
|
|
6531
|
+
for (let i = 1; i <= b.length; i++) {
|
|
6532
|
+
for (let j = 1; j <= a.length; j++) {
|
|
6533
|
+
matrix[i][j] = b[i-1] === a[j-1]
|
|
6534
|
+
? matrix[i-1][j-1]
|
|
6535
|
+
: Math.min(matrix[i-1][j-1] + 1, matrix[i][j-1] + 1, matrix[i-1][j] + 1);
|
|
6536
|
+
}
|
|
6537
|
+
}
|
|
6538
|
+
return matrix[b.length][a.length];
|
|
6539
|
+
};
|
|
6540
|
+
|
|
6541
|
+
// Find similar method names
|
|
6542
|
+
const findSimilar = (name, methods, maxDist = 3) => {
|
|
6543
|
+
const normalized = name.toLowerCase().replace(/[-_]/g, '');
|
|
6544
|
+
return methods
|
|
6545
|
+
.map(m => ({ method: m, dist: levenshtein(normalized, m.toLowerCase().replace(/[-_]/g, '')) }))
|
|
6546
|
+
.filter(x => x.dist <= maxDist)
|
|
6547
|
+
.sort((a, b) => a.dist - b.dist)
|
|
6548
|
+
.slice(0, 3)
|
|
6549
|
+
.map(x => x.method);
|
|
6550
|
+
};
|
|
6551
|
+
|
|
6552
|
+
// Create adapter proxies with helpful error messages
|
|
6525
6553
|
const adaptersObj = {};
|
|
6526
6554
|
for (const [adapterName, methods] of Object.entries(adapterMethods)) {
|
|
6527
|
-
const
|
|
6555
|
+
const methodsImpl = {};
|
|
6528
6556
|
for (const methodName of methods) {
|
|
6529
|
-
|
|
6557
|
+
methodsImpl[methodName] = async (...args) => {
|
|
6530
6558
|
const id = ++callId;
|
|
6531
6559
|
return new Promise((resolve, reject) => {
|
|
6532
6560
|
pendingCalls.set(id, { resolve, reject });
|
|
@@ -6540,18 +6568,28 @@ class BunWorkerSandbox {
|
|
|
6540
6568
|
});
|
|
6541
6569
|
};
|
|
6542
6570
|
}
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6571
|
+
|
|
6572
|
+
// Use Proxy to intercept undefined method calls
|
|
6573
|
+
const adapterProxy = new Proxy(methodsImpl, {
|
|
6574
|
+
get(target, prop) {
|
|
6575
|
+
if (prop in target) return target[prop];
|
|
6576
|
+
if (typeof prop === 'symbol') return undefined;
|
|
6577
|
+
const similar = findSimilar(String(prop), methods);
|
|
6578
|
+
const suggestion = similar.length > 0
|
|
6579
|
+
? '. Did you mean: ' + similar.join(', ') + '?'
|
|
6580
|
+
: '. Available: ' + methods.slice(0, 5).join(', ') + (methods.length > 5 ? '...' : '');
|
|
6581
|
+
throw new Error(adapterName + '.' + String(prop) + ' is not a function' + suggestion);
|
|
6582
|
+
}
|
|
6583
|
+
});
|
|
6584
|
+
|
|
6585
|
+
adaptersObj[adapterName] = adapterProxy;
|
|
6546
6586
|
// Also expose at top level but as non-writable
|
|
6547
6587
|
Object.defineProperty(globalThis, adapterName, {
|
|
6548
|
-
value:
|
|
6588
|
+
value: adapterProxy,
|
|
6549
6589
|
writable: false,
|
|
6550
6590
|
configurable: false
|
|
6551
6591
|
});
|
|
6552
6592
|
}
|
|
6553
|
-
// Freeze the adapters namespace and make it non-writable
|
|
6554
|
-
Object.freeze(adaptersObj);
|
|
6555
6593
|
Object.defineProperty(globalThis, 'adapters', {
|
|
6556
6594
|
value: adaptersObj,
|
|
6557
6595
|
writable: false,
|
|
@@ -10867,11 +10905,53 @@ function generateTypes(adapters, options = {}) {
|
|
|
10867
10905
|
`).trim();
|
|
10868
10906
|
}
|
|
10869
10907
|
function generateTypesSummary(adapters) {
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10908
|
+
const byDomain = new Map;
|
|
10909
|
+
for (const adapter of adapters) {
|
|
10910
|
+
const domain = inferDomain(adapter);
|
|
10911
|
+
if (!byDomain.has(domain)) {
|
|
10912
|
+
byDomain.set(domain, []);
|
|
10913
|
+
}
|
|
10914
|
+
byDomain.get(domain).push(adapter);
|
|
10915
|
+
}
|
|
10916
|
+
if (byDomain.size <= 1 || adapters.length < 4) {
|
|
10917
|
+
return adapters.map((adapter) => {
|
|
10918
|
+
const count = Object.keys(adapter.tools).length;
|
|
10919
|
+
return `- ${adapter.name} (${count} methods)`;
|
|
10920
|
+
}).join(`
|
|
10874
10921
|
`);
|
|
10922
|
+
}
|
|
10923
|
+
const lines = [];
|
|
10924
|
+
for (const [domain, domainAdapters] of byDomain) {
|
|
10925
|
+
const adapterList = domainAdapters.map((a) => `${a.name}(${Object.keys(a.tools).length})`).join(", ");
|
|
10926
|
+
lines.push(`[${domain}] ${adapterList}`);
|
|
10927
|
+
}
|
|
10928
|
+
return lines.join(`
|
|
10929
|
+
`);
|
|
10930
|
+
}
|
|
10931
|
+
function inferDomain(adapter) {
|
|
10932
|
+
if (adapter.domain)
|
|
10933
|
+
return adapter.domain;
|
|
10934
|
+
const name = adapter.name.toLowerCase();
|
|
10935
|
+
const desc = (adapter.description || "").toLowerCase();
|
|
10936
|
+
const combined = `${name} ${desc}`;
|
|
10937
|
+
const domains = {
|
|
10938
|
+
payments: ["stripe", "paypal", "square", "payment", "checkout", "billing", "invoice"],
|
|
10939
|
+
database: ["supabase", "postgres", "mysql", "mongodb", "redis", "database", "sql", "query"],
|
|
10940
|
+
email: ["sendgrid", "mailgun", "postmark", "email", "smtp", "mail"],
|
|
10941
|
+
storage: ["s3", "cloudflare", "storage", "blob", "file", "upload"],
|
|
10942
|
+
auth: ["auth", "oauth", "login", "jwt", "clerk", "auth0"],
|
|
10943
|
+
ai: ["openai", "anthropic", "claude", "gpt", "llm", "ai", "ml"],
|
|
10944
|
+
messaging: ["slack", "discord", "telegram", "twilio", "sms", "chat"],
|
|
10945
|
+
crm: ["hubspot", "salesforce", "crm", "customer"],
|
|
10946
|
+
analytics: ["analytics", "metrics", "tracking", "mixpanel", "amplitude"],
|
|
10947
|
+
devtools: ["github", "gitlab", "jira", "linear", "chrome", "devtools", "ci", "cd"]
|
|
10948
|
+
};
|
|
10949
|
+
for (const [domain, keywords2] of Object.entries(domains)) {
|
|
10950
|
+
if (keywords2.some((k) => combined.includes(k))) {
|
|
10951
|
+
return domain;
|
|
10952
|
+
}
|
|
10953
|
+
}
|
|
10954
|
+
return "general";
|
|
10875
10955
|
}
|
|
10876
10956
|
function generateInputInterface(typeName, parameters, includeDescriptions) {
|
|
10877
10957
|
const lines = [`interface ${typeName} {`];
|
|
@@ -10881,7 +10961,7 @@ function generateInputInterface(typeName, parameters, includeDescriptions) {
|
|
|
10881
10961
|
lines.push(` /** ${sanitizeJSDoc(param.description)} */`);
|
|
10882
10962
|
}
|
|
10883
10963
|
const tsType = paramTypeToTS(param.type);
|
|
10884
|
-
const optional = param.required ===
|
|
10964
|
+
const optional = param.required === true ? "" : "?";
|
|
10885
10965
|
lines.push(` ${safeParamName}${optional}: ${tsType};`);
|
|
10886
10966
|
}
|
|
10887
10967
|
lines.push("}");
|
|
@@ -11170,6 +11250,7 @@ export {
|
|
|
11170
11250
|
normalizeCode,
|
|
11171
11251
|
mergeConfigs,
|
|
11172
11252
|
isUrlAllowed,
|
|
11253
|
+
inferDomain,
|
|
11173
11254
|
generateTypesSummary,
|
|
11174
11255
|
generateTypes,
|
|
11175
11256
|
formatFindings,
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -318,12 +318,40 @@ export class BunWorkerSandbox implements ISandbox {
|
|
|
318
318
|
globalThis[key] = value;
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
//
|
|
321
|
+
// Levenshtein distance for fuzzy matching
|
|
322
|
+
const levenshtein = (a, b) => {
|
|
323
|
+
if (a.length === 0) return b.length;
|
|
324
|
+
if (b.length === 0) return a.length;
|
|
325
|
+
const matrix = [];
|
|
326
|
+
for (let i = 0; i <= b.length; i++) matrix[i] = [i];
|
|
327
|
+
for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
|
|
328
|
+
for (let i = 1; i <= b.length; i++) {
|
|
329
|
+
for (let j = 1; j <= a.length; j++) {
|
|
330
|
+
matrix[i][j] = b[i-1] === a[j-1]
|
|
331
|
+
? matrix[i-1][j-1]
|
|
332
|
+
: Math.min(matrix[i-1][j-1] + 1, matrix[i][j-1] + 1, matrix[i-1][j] + 1);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return matrix[b.length][a.length];
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Find similar method names
|
|
339
|
+
const findSimilar = (name, methods, maxDist = 3) => {
|
|
340
|
+
const normalized = name.toLowerCase().replace(/[-_]/g, '');
|
|
341
|
+
return methods
|
|
342
|
+
.map(m => ({ method: m, dist: levenshtein(normalized, m.toLowerCase().replace(/[-_]/g, '')) }))
|
|
343
|
+
.filter(x => x.dist <= maxDist)
|
|
344
|
+
.sort((a, b) => a.dist - b.dist)
|
|
345
|
+
.slice(0, 3)
|
|
346
|
+
.map(x => x.method);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// Create adapter proxies with helpful error messages
|
|
322
350
|
const adaptersObj = {};
|
|
323
351
|
for (const [adapterName, methods] of Object.entries(adapterMethods)) {
|
|
324
|
-
const
|
|
352
|
+
const methodsImpl = {};
|
|
325
353
|
for (const methodName of methods) {
|
|
326
|
-
|
|
354
|
+
methodsImpl[methodName] = async (...args) => {
|
|
327
355
|
const id = ++callId;
|
|
328
356
|
return new Promise((resolve, reject) => {
|
|
329
357
|
pendingCalls.set(id, { resolve, reject });
|
|
@@ -337,18 +365,28 @@ export class BunWorkerSandbox implements ISandbox {
|
|
|
337
365
|
});
|
|
338
366
|
};
|
|
339
367
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
368
|
+
|
|
369
|
+
// Use Proxy to intercept undefined method calls
|
|
370
|
+
const adapterProxy = new Proxy(methodsImpl, {
|
|
371
|
+
get(target, prop) {
|
|
372
|
+
if (prop in target) return target[prop];
|
|
373
|
+
if (typeof prop === 'symbol') return undefined;
|
|
374
|
+
const similar = findSimilar(String(prop), methods);
|
|
375
|
+
const suggestion = similar.length > 0
|
|
376
|
+
? '. Did you mean: ' + similar.join(', ') + '?'
|
|
377
|
+
: '. Available: ' + methods.slice(0, 5).join(', ') + (methods.length > 5 ? '...' : '');
|
|
378
|
+
throw new Error(adapterName + '.' + String(prop) + ' is not a function' + suggestion);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
adaptersObj[adapterName] = adapterProxy;
|
|
343
383
|
// Also expose at top level but as non-writable
|
|
344
384
|
Object.defineProperty(globalThis, adapterName, {
|
|
345
|
-
value:
|
|
385
|
+
value: adapterProxy,
|
|
346
386
|
writable: false,
|
|
347
387
|
configurable: false
|
|
348
388
|
});
|
|
349
389
|
}
|
|
350
|
-
// Freeze the adapters namespace and make it non-writable
|
|
351
|
-
Object.freeze(adaptersObj);
|
|
352
390
|
Object.defineProperty(globalThis, 'adapters', {
|
|
353
391
|
value: adaptersObj,
|
|
354
392
|
writable: false,
|
package/src/type-generator.ts
CHANGED
|
@@ -85,19 +85,76 @@ export function generateTypes(
|
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* Generate a compact type summary for token-constrained contexts.
|
|
88
|
-
*
|
|
88
|
+
* Groups adapters by domain and shows method count.
|
|
89
89
|
* Use mcx_search to discover specific methods.
|
|
90
90
|
*
|
|
91
91
|
* @param adapters - Array of adapters
|
|
92
|
-
* @returns Compact summary string
|
|
92
|
+
* @returns Compact summary string with domain hints
|
|
93
93
|
*/
|
|
94
94
|
export function generateTypesSummary(adapters: Adapter[]): string {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
.
|
|
95
|
+
// Group adapters by domain
|
|
96
|
+
const byDomain = new Map<string, Adapter[]>();
|
|
97
|
+
|
|
98
|
+
for (const adapter of adapters) {
|
|
99
|
+
const domain = inferDomain(adapter);
|
|
100
|
+
if (!byDomain.has(domain)) {
|
|
101
|
+
byDomain.set(domain, []);
|
|
102
|
+
}
|
|
103
|
+
byDomain.get(domain)!.push(adapter);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// If only one domain or fewer than 4 adapters, simple list
|
|
107
|
+
if (byDomain.size <= 1 || adapters.length < 4) {
|
|
108
|
+
return adapters
|
|
109
|
+
.map((adapter) => {
|
|
110
|
+
const count = Object.keys(adapter.tools).length;
|
|
111
|
+
return `- ${adapter.name} (${count} methods)`;
|
|
112
|
+
})
|
|
113
|
+
.join("\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Group by domain for better discoverability
|
|
117
|
+
const lines: string[] = [];
|
|
118
|
+
for (const [domain, domainAdapters] of byDomain) {
|
|
119
|
+
const adapterList = domainAdapters
|
|
120
|
+
.map((a) => `${a.name}(${Object.keys(a.tools).length})`)
|
|
121
|
+
.join(", ");
|
|
122
|
+
lines.push(`[${domain}] ${adapterList}`);
|
|
123
|
+
}
|
|
124
|
+
return lines.join("\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Infer domain from adapter name/description if not explicitly set.
|
|
129
|
+
* Returns the adapter's explicit domain if set, otherwise infers from name/description.
|
|
130
|
+
*/
|
|
131
|
+
export function inferDomain(adapter: Adapter): string {
|
|
132
|
+
if (adapter.domain) return adapter.domain;
|
|
133
|
+
|
|
134
|
+
const name = adapter.name.toLowerCase();
|
|
135
|
+
const desc = (adapter.description || "").toLowerCase();
|
|
136
|
+
const combined = `${name} ${desc}`;
|
|
137
|
+
|
|
138
|
+
const domains: Record<string, string[]> = {
|
|
139
|
+
payments: ["stripe", "paypal", "square", "payment", "checkout", "billing", "invoice"],
|
|
140
|
+
database: ["supabase", "postgres", "mysql", "mongodb", "redis", "database", "sql", "query"],
|
|
141
|
+
email: ["sendgrid", "mailgun", "postmark", "email", "smtp", "mail"],
|
|
142
|
+
storage: ["s3", "cloudflare", "storage", "blob", "file", "upload"],
|
|
143
|
+
auth: ["auth", "oauth", "login", "jwt", "clerk", "auth0"],
|
|
144
|
+
ai: ["openai", "anthropic", "claude", "gpt", "llm", "ai", "ml"],
|
|
145
|
+
messaging: ["slack", "discord", "telegram", "twilio", "sms", "chat"],
|
|
146
|
+
crm: ["hubspot", "salesforce", "crm", "customer"],
|
|
147
|
+
analytics: ["analytics", "metrics", "tracking", "mixpanel", "amplitude"],
|
|
148
|
+
devtools: ["github", "gitlab", "jira", "linear", "chrome", "devtools", "ci", "cd"],
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
for (const [domain, keywords] of Object.entries(domains)) {
|
|
152
|
+
if (keywords.some((k) => combined.includes(k))) {
|
|
153
|
+
return domain;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return "general";
|
|
101
158
|
}
|
|
102
159
|
|
|
103
160
|
/**
|
|
@@ -119,7 +176,7 @@ function generateInputInterface(
|
|
|
119
176
|
}
|
|
120
177
|
|
|
121
178
|
const tsType = paramTypeToTS(param.type);
|
|
122
|
-
const optional = param.required ===
|
|
179
|
+
const optional = param.required === true ? "" : "?";
|
|
123
180
|
lines.push(` ${safeParamName}${optional}: ${tsType};`);
|
|
124
181
|
}
|
|
125
182
|
|
package/src/types.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface ParameterDefinition {
|
|
|
12
12
|
description?: string;
|
|
13
13
|
required?: boolean;
|
|
14
14
|
default?: unknown;
|
|
15
|
+
/** Example value for this parameter (helps LLMs understand expected format) */
|
|
16
|
+
example?: unknown;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -33,6 +35,8 @@ export interface Adapter<TTools extends Record<string, AdapterTool> = Record<str
|
|
|
33
35
|
name: string;
|
|
34
36
|
description?: string;
|
|
35
37
|
version?: string;
|
|
38
|
+
/** Domain/category for tool discovery (e.g., 'payments', 'database', 'email') */
|
|
39
|
+
domain?: string;
|
|
36
40
|
tools: TTools;
|
|
37
41
|
dispose?: () => Promise<void> | void;
|
|
38
42
|
}
|