@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 CHANGED
@@ -6521,12 +6521,40 @@ class BunWorkerSandbox {
6521
6521
  globalThis[key] = value;
6522
6522
  }
6523
6523
 
6524
- // Create adapter proxies and freeze them to prevent user code modification
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 adapterObj = {};
6555
+ const methodsImpl = {};
6528
6556
  for (const methodName of methods) {
6529
- adapterObj[methodName] = async (...args) => {
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
- // Freeze individual adapter to prevent method tampering
6544
- Object.freeze(adapterObj);
6545
- adaptersObj[adapterName] = adapterObj;
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: adapterObj,
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
- return adapters.map((adapter) => {
10871
- const count = Object.keys(adapter.tools).length;
10872
- return `- ${adapter.name} (${count} methods)`;
10873
- }).join(`
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 === false ? "?" : "";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papicandela/mcx-core",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "MCX Core - MCP Code Execution Framework",
5
5
  "author": "papicandela",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -78,6 +78,7 @@ export { configBuilder, defineConfig, mergeConfigs } from "./config.js";
78
78
  export {
79
79
  generateTypes,
80
80
  generateTypesSummary,
81
+ inferDomain,
81
82
  sanitizeIdentifier,
82
83
  type TypeGeneratorOptions,
83
84
  } from "./type-generator.js";
@@ -318,12 +318,40 @@ export class BunWorkerSandbox implements ISandbox {
318
318
  globalThis[key] = value;
319
319
  }
320
320
 
321
- // Create adapter proxies and freeze them to prevent user code modification
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 adapterObj = {};
352
+ const methodsImpl = {};
325
353
  for (const methodName of methods) {
326
- adapterObj[methodName] = async (...args) => {
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
- // Freeze individual adapter to prevent method tampering
341
- Object.freeze(adapterObj);
342
- adaptersObj[adapterName] = adapterObj;
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: adapterObj,
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,
@@ -85,19 +85,76 @@ export function generateTypes(
85
85
 
86
86
  /**
87
87
  * Generate a compact type summary for token-constrained contexts.
88
- * Only shows adapter names and method count to minimize context usage.
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
- return adapters
96
- .map((adapter) => {
97
- const count = Object.keys(adapter.tools).length;
98
- return `- ${adapter.name} (${count} methods)`;
99
- })
100
- .join("\n");
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 === false ? "?" : "";
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
  }