genexus-mcp 2.6.3 → 2.6.5

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.
@@ -21,7 +21,10 @@ const {
21
21
  readKbCatalog,
22
22
  addKbToConfig,
23
23
  removeKbFromConfig,
24
- switchActiveKb
24
+ switchActiveKb,
25
+ isPathLikelyAppLockerBlocked,
26
+ normalizeExePath,
27
+ readClientCommandEntry
25
28
  } = require('../lib/config');
26
29
 
27
30
  function resolveClientIds(options) {
@@ -176,7 +179,17 @@ async function spawnGatewayProbe({ env = process.env, spawnHoldMs, timeoutMs, la
176
179
  });
177
180
 
178
181
  child.once('error', (err) => {
179
- finish({ status: 'fail', detail: `${label} failed: ${err.message}` });
182
+ const code = err && (err.code || err.errno);
183
+ const accessDenied = code === 'EACCES' || code === 'EPERM' || /access is denied|access denied|acesso negado/i.test(err.message || '');
184
+ if (accessDenied) {
185
+ const zone = isPathLikelyAppLockerBlocked(gatewayExePath);
186
+ const hint = zone
187
+ ? ` Gateway exe is under %${zone}% — likely blocked by AppLocker/SRP. Reinstall via scripts/install.ps1 to a whitelisted path.`
188
+ : ' Access denied. AppLocker/SRP or AV may be blocking execution. Move the exe to a whitelisted path (see scripts/install.ps1).';
189
+ finish({ status: 'fail', detail: `${label} failed: ${err.message}.${hint}` });
190
+ } else {
191
+ finish({ status: 'fail', detail: `${label} failed: ${err.message}` });
192
+ }
180
193
  });
181
194
 
182
195
  child.once('spawn', () => {
@@ -304,6 +317,7 @@ async function runMcpSmokeProbe(cwd) {
304
317
 
305
318
  async function handleStatus(options, ctx) {
306
319
  const data = buildStatusData(ctx.cwd);
320
+ const riskyZone = isPathLikelyAppLockerBlocked(data.gatewayExePath);
307
321
 
308
322
  const ok = options.full
309
323
  ? {
@@ -311,6 +325,7 @@ async function handleStatus(options, ctx) {
311
325
  configFound: data.configFound,
312
326
  gatewayExeFound: data.gatewayExeFound,
313
327
  kbLooksValid: data.kbLooksValid,
328
+ pathSafetyWarn: !!riskyZone,
314
329
  configSource: data.configSource,
315
330
  configPath: data.configPath,
316
331
  gatewayExePath: data.gatewayExePath,
@@ -321,20 +336,86 @@ async function handleStatus(options, ctx) {
321
336
  ready: data.ready,
322
337
  configFound: data.configFound,
323
338
  gatewayExeFound: data.gatewayExeFound,
324
- kbLooksValid: data.kbLooksValid
339
+ kbLooksValid: data.kbLooksValid,
340
+ pathSafetyWarn: !!riskyZone
325
341
  };
326
342
 
343
+ const help = data.ready
344
+ ? ['Run `genexus-mcp tools list --limit 10` to inspect available MCP tools.']
345
+ : [
346
+ 'Run `genexus-mcp doctor` for expanded checks.',
347
+ 'Run `genexus-mcp init --kb "<kbPath>" --gx "<geneXusPath>"` to create config.'
348
+ ];
349
+ if (riskyZone) {
350
+ help.unshift(`Gateway exe is under %${riskyZone}% — likely AppLocker-blocked on corporate Windows. Reinstall to a whitelisted path via scripts/install.ps1.`);
351
+ }
352
+
327
353
  return {
328
354
  exitCode: ctx.EXIT_CODES.OK,
329
- envelope: {
330
- ok,
331
- help: data.ready
332
- ? ['Run `genexus-mcp tools list --limit 10` to inspect available MCP tools.']
333
- : [
334
- 'Run `genexus-mcp doctor --full` for expanded checks.',
335
- 'Run `genexus-mcp init --kb "<kbPath>" --gx "<geneXusPath>"` to create config.'
336
- ]
355
+ envelope: { ok, help }
356
+ };
357
+ }
358
+
359
+ function buildClientExeCrossCheck(packageExePath) {
360
+ const packageNorm = normalizeExePath(packageExePath);
361
+ const targets = filterClientTargets(getClientConfigTargets(), { platform: process.platform });
362
+
363
+ const mismatches = [];
364
+ const matches = [];
365
+ const errors = [];
366
+ let inspected = 0;
367
+
368
+ for (const client of targets) {
369
+ if (!fs.existsSync(client.path)) continue;
370
+ let entry;
371
+ try {
372
+ entry = readClientCommandEntry(client);
373
+ } catch (err) {
374
+ errors.push(`${client.name}: ${err.message || 'read failed'}`);
375
+ continue;
337
376
  }
377
+ if (!entry || !entry.command) continue;
378
+ inspected += 1;
379
+
380
+ const cmd = entry.command;
381
+ const lowerCmd = cmd.toLowerCase();
382
+
383
+ // Skip launchers that always resolve via npm at runtime (npx, node CLI shim).
384
+ if (/(^|[\\/])(npx|npx\.cmd|node|node\.exe)$/i.test(lowerCmd) || /[\\/]genexus-mcp(\.cmd)?$/i.test(lowerCmd)) {
385
+ continue;
386
+ }
387
+
388
+ // Only compare when the configured command points to an .exe.
389
+ if (!/\.exe$/i.test(lowerCmd)) continue;
390
+
391
+ const configuredNorm = normalizeExePath(cmd);
392
+ if (configuredNorm === packageNorm) {
393
+ matches.push(client.name);
394
+ } else {
395
+ const configuredExists = fs.existsSync(cmd);
396
+ mismatches.push({
397
+ client: client.name,
398
+ configured: cmd,
399
+ exists: configuredExists
400
+ });
401
+ }
402
+ }
403
+
404
+ if (inspected === 0) {
405
+ return { status: 'warn', detail: 'No AI client config with a direct gateway exe found (or all clients use npx). Run `genexus-mcp init --write-clients` if you want to register one.' };
406
+ }
407
+
408
+ if (mismatches.length === 0) {
409
+ return {
410
+ status: 'pass',
411
+ detail: `All inspected client configs (${matches.join(', ')}) point at the npm-package gateway exe.`
412
+ };
413
+ }
414
+
415
+ const detailParts = mismatches.map((m) => `${m.client} -> ${m.configured}${m.exists ? '' : ' (missing)'}`);
416
+ return {
417
+ status: 'warn',
418
+ detail: `Client(s) reference a gateway exe that is NOT this npm package's bundled exe. \`npm install -g genexus-mcp@latest\` will NOT update those instances. Bundled: ${packageExePath}. Mismatches: ${detailParts.join('; ')}. Re-run scripts/install.ps1 (or genexus-mcp init --write-clients) to resync.`
338
419
  };
339
420
  }
340
421
 
@@ -359,21 +440,32 @@ async function handleDoctor(options, ctx) {
359
440
  const kbExists = !!(kbPath && fs.existsSync(kbPath));
360
441
  const gxExeExists = !!(gxPath && fs.existsSync(path.join(gxPath, 'genexus.exe')));
361
442
 
443
+ const riskyZone = isPathLikelyAppLockerBlocked(gatewayExePath);
444
+ const clientCrossCheck = buildClientExeCrossCheck(gatewayExePath);
445
+
362
446
  const checks = [
363
447
  { id: 'config_file', status: data.configFound ? 'pass' : 'fail', detail: data.configFound ? 'GX config file was found.' : 'GX config file is missing.' },
364
448
  { id: 'gateway_exe', status: data.gatewayExeFound ? 'pass' : 'fail', detail: data.gatewayExeFound ? 'Gateway executable is available.' : 'Gateway executable is missing.' },
449
+ {
450
+ id: 'gateway_exe_path_safety',
451
+ status: riskyZone ? 'warn' : 'pass',
452
+ detail: riskyZone
453
+ ? `Gateway exe is under %${riskyZone}%, which is commonly blocked by AppLocker/SRP in Windows domains. If clients show "Failed to connect" / "Access denied", reinstall to a whitelisted path via scripts/install.ps1 (defaults to C:\\Tools\\GenexusMCP or %LOCALAPPDATA%\\Programs\\GenexusMCP).`
454
+ : 'Gateway exe is in a path unlikely to be blocked by AppLocker/SRP.'
455
+ },
365
456
  { id: 'kb_path_exists', status: kbExists ? 'pass' : 'warn', detail: kbExists ? 'Configured KB path exists.' : 'Configured KB path does not exist.' },
366
457
  { id: 'kb_shape', status: data.kbLooksValid ? 'pass' : 'warn', detail: data.kbLooksValid ? 'KB folder shape looks valid.' : 'KB markers were not found in configured KB path.' },
367
458
  { id: 'gx_installation', status: gxExeExists ? 'pass' : 'warn', detail: gxExeExists ? 'GeneXus installation has genexus.exe.' : 'Configured GeneXus installation is missing genexus.exe.' },
368
459
  { id: 'tool_definitions', status: toolDefsExists ? 'pass' : 'warn', detail: toolDefsExists ? `Tool definition file found (${toolCount} tools).` : 'tool_definitions.json is missing.' },
369
- { id: 'gx_env', status: process.env.GX_CONFIG_PATH ? 'pass' : 'warn', detail: process.env.GX_CONFIG_PATH ? 'GX_CONFIG_PATH env var is set.' : 'GX_CONFIG_PATH env var is not set for this process.' }
460
+ { id: 'gx_env', status: process.env.GX_CONFIG_PATH ? 'pass' : 'warn', detail: process.env.GX_CONFIG_PATH ? 'GX_CONFIG_PATH env var is set.' : 'GX_CONFIG_PATH env var is not set for this process.' },
461
+ { id: 'client_config_sync', status: clientCrossCheck.status, detail: clientCrossCheck.detail }
370
462
  ];
371
463
 
372
- if (options.full) {
464
+ if (data.gatewayExeFound) {
373
465
  const probe = await probeGatewaySpawn();
374
466
  checks.push({ id: 'gateway_spawn_probe', status: probe.status, detail: probe.detail });
375
467
  } else {
376
- checks.push({ id: 'gateway_spawn_probe', status: 'warn', detail: 'Spawn probe skipped by default. Run doctor with --full.' });
468
+ checks.push({ id: 'gateway_spawn_probe', status: 'warn', detail: 'Spawn probe skipped: gateway exe not found.' });
377
469
  }
378
470
 
379
471
  if (options.mcpSmoke) {
@@ -399,9 +491,6 @@ async function handleDoctor(options, ctx) {
399
491
  if (checks.length > options.limit) {
400
492
  help.push(`Run 'genexus-mcp doctor --limit ${checks.length}' for all checks.`);
401
493
  }
402
- if (!options.full) {
403
- help.push('Run `genexus-mcp doctor --full` to include runtime spawn probe.');
404
- }
405
494
  if (!options.mcpSmoke) {
406
495
  help.push('Run `genexus-mcp doctor --mcp-smoke` to execute MCP protocol smoke checks.');
407
496
  }
@@ -774,6 +863,16 @@ async function handleLayout(subcommand, options, ctx) {
774
863
  };
775
864
  }
776
865
 
866
+ function buildInteractiveInitHelp(patchResult) {
867
+ const help = [];
868
+ if (patchResult.patched.length === 0) {
869
+ help.push('Set `GX_CONFIG_PATH` in your MCP client env to the generated config path.');
870
+ } else if (process.platform === 'win32' && !process.env.GENEXUS_MCP_GATEWAY_EXE) {
871
+ help.push('Windows + corporate AppLocker: the npx launcher resolves the gateway from %LOCALAPPDATA%\\npm-cache, which is commonly blocked. If clients fail with "Failed to connect" / Access denied, reinstall to a whitelisted path via scripts/install.ps1.');
872
+ }
873
+ return help;
874
+ }
875
+
777
876
  async function runInteractiveInit(ctx) {
778
877
  const defaultGx = discoverGeneXusInstallation() || 'C:\\Program Files (x86)\\GeneXus\\GeneXus18';
779
878
 
@@ -820,7 +919,7 @@ async function runInteractiveInit(ctx) {
820
919
  noOp: !created.changed,
821
920
  clientsPatchedCount: patchResult.patched.length
822
921
  },
823
- help: patchResult.patched.length === 0 ? ['Set `GX_CONFIG_PATH` in your MCP client env to the generated config path.'] : [],
922
+ help: buildInteractiveInitHelp(patchResult),
824
923
  meta: {
825
924
  patchedClients: patchResult.patched,
826
925
  failedClients: patchResult.failed,
@@ -828,10 +927,26 @@ async function runInteractiveInit(ctx) {
828
927
  }
829
928
  }
830
929
  };
831
- } catch {
930
+ } catch (err) {
931
+ if (err && err.code === 'GATEWAY_EXE_MISSING') {
932
+ return {
933
+ exitCode: ctx.EXIT_CODES.ERROR,
934
+ envelope: operationalErrorEnvelope(
935
+ 'GENEXUS_MCP_GATEWAY_EXE points to a path that does not exist. Client configs were NOT modified.',
936
+ ctx.EXIT_CODES.ERROR,
937
+ [
938
+ `Path checked: ${process.env.GENEXUS_MCP_GATEWAY_EXE}`,
939
+ 'Either unset GENEXUS_MCP_GATEWAY_EXE (then init writes the npx-based launcher) or re-run scripts/install.ps1 to materialize the exe at that path.'
940
+ ]
941
+ )
942
+ };
943
+ }
832
944
  return {
833
945
  exitCode: ctx.EXIT_CODES.ERROR,
834
- envelope: operationalErrorEnvelope('Interactive init failed.', ctx.EXIT_CODES.ERROR)
946
+ envelope: operationalErrorEnvelope(
947
+ sanitizeOperationalMessage(`Interactive init failed: ${err && err.message ? err.message : 'unknown error'}`),
948
+ ctx.EXIT_CODES.ERROR
949
+ )
835
950
  };
836
951
  } finally {
837
952
  rl.close();
@@ -913,8 +1028,11 @@ async function handleInit(options, ctx) {
913
1028
  if (patchResult.patched.length === 0 && !options.writeClients) {
914
1029
  help.push('Run `genexus-mcp init --kb "<kbPath>" --gx "<geneXusPath>" --write-clients` to patch supported clients.');
915
1030
  }
1031
+ if (patchResult.patched.length > 0 && process.platform === 'win32' && !process.env.GENEXUS_MCP_GATEWAY_EXE) {
1032
+ help.push('Windows + corporate AppLocker: the npx launcher resolves the gateway from %LOCALAPPDATA%\\npm-cache, which is commonly blocked. If clients fail with "Failed to connect" / Access denied, reinstall to a whitelisted path via scripts/install.ps1.');
1033
+ }
916
1034
  if (verification.summary.fail > 0 || verification.summary.warn > 0) {
917
- help.push('Some verification checks did not pass. Run `genexus-mcp doctor --full --mcp-smoke` for details.');
1035
+ help.push('Some verification checks did not pass. Run `genexus-mcp doctor --mcp-smoke` for details.');
918
1036
  }
919
1037
  if (options.noSmoke) {
920
1038
  help.push('MCP protocol smoke was skipped (--no-smoke). Re-run `genexus-mcp doctor --mcp-smoke` to validate end-to-end.');
@@ -950,10 +1068,27 @@ async function handleInit(options, ctx) {
950
1068
  }
951
1069
  }
952
1070
  };
953
- } catch {
1071
+ } catch (err) {
1072
+ if (err && err.code === 'GATEWAY_EXE_MISSING') {
1073
+ return {
1074
+ exitCode: ctx.EXIT_CODES.ERROR,
1075
+ envelope: operationalErrorEnvelope(
1076
+ 'GENEXUS_MCP_GATEWAY_EXE points to a path that does not exist. Client configs were NOT modified.',
1077
+ ctx.EXIT_CODES.ERROR,
1078
+ [
1079
+ `Path checked: ${process.env.GENEXUS_MCP_GATEWAY_EXE}`,
1080
+ 'Either unset GENEXUS_MCP_GATEWAY_EXE (then init writes the npx-based launcher) or re-run scripts/install.ps1 to materialize the exe at that path.'
1081
+ ]
1082
+ )
1083
+ };
1084
+ }
954
1085
  return {
955
1086
  exitCode: ctx.EXIT_CODES.ERROR,
956
- envelope: operationalErrorEnvelope('Failed to write configuration.', ctx.EXIT_CODES.ERROR)
1087
+ envelope: operationalErrorEnvelope(
1088
+ sanitizeOperationalMessage(`Failed to write configuration: ${err && err.message ? err.message : 'unknown error'}`),
1089
+ ctx.EXIT_CODES.ERROR,
1090
+ ['Check write permissions on the target directory; on Windows, run from a path outside protected areas.']
1091
+ )
957
1092
  };
958
1093
  }
959
1094
  }
package/cli/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  const { spawn } = require('child_process');
2
2
  const {
3
3
  getGatewayExePath,
4
- applyLauncherConfigOrExit
4
+ applyLauncherConfigOrExit,
5
+ isPathLikelyAppLockerBlocked
5
6
  } = require('./lib/config');
6
7
  const {
7
8
  SUPPORTED_FORMATS,
@@ -294,6 +295,17 @@ function parseArgs(argv) {
294
295
  return result;
295
296
  }
296
297
 
298
+ function writeAppLockerHint(stderr, gatewayExePath) {
299
+ const riskyZone = isPathLikelyAppLockerBlocked(gatewayExePath);
300
+ stderr.write('[genexus-mcp] Likely cause: Windows AppLocker / SRP is blocking execution from this path.\n');
301
+ if (riskyZone) {
302
+ stderr.write(`[genexus-mcp] The gateway exe lives under %${riskyZone}% (typically restricted by corporate policy):\n[genexus-mcp] ${gatewayExePath}\n`);
303
+ }
304
+ stderr.write('[genexus-mcp] Remediation: install to a whitelisted path with:\n');
305
+ stderr.write('[genexus-mcp] iex (irm https://raw.githubusercontent.com/lennix1337/Genexus18MCP/main/scripts/install.ps1)\n');
306
+ stderr.write('[genexus-mcp] Or copy the package `publish/` folder to a path outside %APPDATA%/%LOCALAPPDATA% and point the MCP client at that exe.\n');
307
+ }
308
+
297
309
  async function launchGateway(passthroughArgs, options) {
298
310
  const setup = applyLauncherConfigOrExit({
299
311
  cwd: process.cwd(),
@@ -323,6 +335,11 @@ async function launchGateway(passthroughArgs, options) {
323
335
  child.on('error', (err) => {
324
336
  if (!options.quiet) {
325
337
  process.stderr.write(`[genexus-mcp] ERROR: Failed to start gateway process: ${err.message}\n`);
338
+ const code = err && (err.code || err.errno);
339
+ const accessDenied = code === 'EACCES' || code === 'EPERM' || /access is denied|access denied|acesso negado/i.test(err.message || '');
340
+ if (accessDenied) {
341
+ writeAppLockerHint(process.stderr, gatewayExePath);
342
+ }
326
343
  }
327
344
  resolve(EXIT_CODES.ERROR);
328
345
  });
package/cli/lib/config.js CHANGED
@@ -278,6 +278,18 @@ function filterClientTargets(targets, opts = {}) {
278
278
  }
279
279
 
280
280
  function patchClientConfig(targetConfigPath, opts = {}) {
281
+ // Validate corporate-install env var before we write it into N client configs.
282
+ // Otherwise we silently propagate a broken path to every AI client and the
283
+ // user only finds out when each one fails with "Failed to connect".
284
+ if (process.env.GENEXUS_MCP_GATEWAY_EXE && !fs.existsSync(process.env.GENEXUS_MCP_GATEWAY_EXE)) {
285
+ const err = new Error(
286
+ `GENEXUS_MCP_GATEWAY_EXE points to a path that does not exist: ${process.env.GENEXUS_MCP_GATEWAY_EXE}. ` +
287
+ `Refusing to write this into client configs. Unset the env var (to use the npx launcher) or re-run scripts/install.ps1 to materialize the exe.`
288
+ );
289
+ err.code = 'GATEWAY_EXE_MISSING';
290
+ throw err;
291
+ }
292
+
281
293
  const launcher = getLauncher();
282
294
  const onlyExisting = opts.onlyExisting !== false;
283
295
  const candidates = filterClientTargets(getClientConfigTargets(), {
@@ -456,6 +468,65 @@ function tomlString(value) {
456
468
  return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
457
469
  }
458
470
 
471
+ function isPathLikelyAppLockerBlocked(exePath) {
472
+ if (process.platform !== 'win32' || !exePath) return null;
473
+ const norm = String(exePath).toLowerCase().replace(/\\/g, '/');
474
+ const candidates = [
475
+ { name: 'APPDATA', base: process.env.APPDATA },
476
+ { name: 'LOCALAPPDATA', base: process.env.LOCALAPPDATA },
477
+ { name: 'TEMP', base: process.env.TEMP },
478
+ { name: 'TMP', base: process.env.TMP }
479
+ ];
480
+ for (const { name, base } of candidates) {
481
+ if (!base) continue;
482
+ const baseNorm = base.toLowerCase().replace(/\\/g, '/').replace(/\/$/, '');
483
+ if (norm.startsWith(baseNorm + '/')) return name;
484
+ }
485
+ return null;
486
+ }
487
+
488
+ function normalizeExePath(p) {
489
+ if (!p) return '';
490
+ let s = String(p).trim().replace(/^"|"$/g, '');
491
+ s = s.replace(/\\/g, '/').replace(/\/+$/, '');
492
+ if (process.platform === 'win32') s = s.toLowerCase();
493
+ return s;
494
+ }
495
+
496
+ function readClientCommandEntry(client) {
497
+ if (!fs.existsSync(client.path)) return null;
498
+ try {
499
+ if (client.format === 'mcpServers') {
500
+ const parsed = readJsonFileSafe(client.path);
501
+ if (!parsed || typeof parsed !== 'object') return null;
502
+ const entry = parsed.mcpServers && parsed.mcpServers.genexus;
503
+ if (!entry) return null;
504
+ return { command: entry.command || null, args: Array.isArray(entry.args) ? entry.args : [] };
505
+ }
506
+ if (client.format === 'opencode') {
507
+ const parsed = readJsonFileSafe(client.path);
508
+ if (!parsed || typeof parsed !== 'object') return null;
509
+ const entry = parsed.mcp && parsed.mcp.genexus;
510
+ if (!entry || !Array.isArray(entry.command) || entry.command.length === 0) return null;
511
+ return { command: entry.command[0], args: entry.command.slice(1) };
512
+ }
513
+ if (client.format === 'codex-toml') {
514
+ const raw = fs.readFileSync(client.path, 'utf8');
515
+ // Minimal extraction: find [mcp_servers.genexus] block and pull command = "..."
516
+ const blockRe = /\[mcp_servers\.genexus\]([\s\S]*?)(?=\n\[|$)/;
517
+ const m = raw.match(blockRe);
518
+ if (!m) return null;
519
+ const cmdMatch = m[1].match(/^\s*command\s*=\s*"((?:[^"\\]|\\.)*)"/m);
520
+ if (!cmdMatch) return null;
521
+ const command = cmdMatch[1].replace(/\\\\/g, '\\').replace(/\\"/g, '"');
522
+ return { command, args: [] };
523
+ }
524
+ } catch {
525
+ return null;
526
+ }
527
+ return null;
528
+ }
529
+
459
530
  function getLocalAppDataCacheDir() {
460
531
  if (process.platform !== 'win32') return null;
461
532
  const base = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
@@ -628,5 +699,8 @@ module.exports = {
628
699
  addKbToConfig,
629
700
  removeKbFromConfig,
630
701
  switchActiveKb,
631
- applyLauncherConfigOrExit
702
+ applyLauncherConfigOrExit,
703
+ isPathLikelyAppLockerBlocked,
704
+ normalizeExePath,
705
+ readClientCommandEntry
632
706
  };
@@ -2,6 +2,13 @@ const fs = require('fs');
2
2
  const os = require('os');
3
3
  const path = require('path');
4
4
  const https = require('https');
5
+ const {
6
+ getGatewayExePath,
7
+ getClientConfigTargets,
8
+ filterClientTargets,
9
+ readClientCommandEntry,
10
+ normalizeExePath
11
+ } = require('./config');
5
12
 
6
13
  const REPO = 'lennix1337/Genexus18MCP';
7
14
  const NPM_PACKAGE = 'genexus-mcp';
@@ -159,9 +166,37 @@ function startBackgroundUpdateCheck(opts) {
159
166
  scheduleBackgroundFetch();
160
167
  }
161
168
 
169
+ function detectClientExeDrift() {
170
+ try {
171
+ const packageNorm = normalizeExePath(getGatewayExePath());
172
+ const targets = filterClientTargets(getClientConfigTargets(), { platform: process.platform });
173
+ const mismatches = [];
174
+ for (const client of targets) {
175
+ if (!fs.existsSync(client.path)) continue;
176
+ const entry = readClientCommandEntry(client);
177
+ if (!entry || !entry.command) continue;
178
+ const cmd = entry.command;
179
+ // Skip launchers that resolve via npm at runtime.
180
+ if (/(^|[\\/])(npx|npx\.cmd|node|node\.exe)$/i.test(cmd) || /[\\/]genexus-mcp(\.cmd)?$/i.test(cmd)) continue;
181
+ if (!/\.exe$/i.test(cmd)) continue;
182
+ if (normalizeExePath(cmd) !== packageNorm) {
183
+ mismatches.push({ client: client.name, configured: cmd });
184
+ }
185
+ }
186
+ return mismatches;
187
+ } catch {
188
+ return [];
189
+ }
190
+ }
191
+
162
192
  async function handleUpdate(_options, ctx) {
163
193
  const current = getPackageVersion();
164
194
  const result = await fetchLatestRelease();
195
+ const mismatches = detectClientExeDrift();
196
+
197
+ const driftHelp = mismatches.length
198
+ ? [`WARNING: ${mismatches.length} AI client(s) point at a gateway exe that is NOT this npm package — \`npm install -g ${NPM_PACKAGE}@latest\` will NOT update them. Mismatches: ${mismatches.map((m) => `${m.client} -> ${m.configured}`).join('; ')}. Re-run scripts/install.ps1 (or genexus-mcp init --write-clients) to resync.`]
199
+ : [];
165
200
 
166
201
  if (!result) {
167
202
  return {
@@ -171,9 +206,10 @@ async function handleUpdate(_options, ctx) {
171
206
  current,
172
207
  latest: null,
173
208
  updateAvailable: false,
174
- fetched: false
209
+ fetched: false,
210
+ clientDrift: mismatches
175
211
  },
176
- help: ['Could not reach GitHub releases API. Check connectivity or retry later.']
212
+ help: ['Could not reach GitHub releases API. Check connectivity or retry later.', ...driftHelp]
177
213
  }
178
214
  };
179
215
  }
@@ -185,7 +221,7 @@ async function handleUpdate(_options, ctx) {
185
221
  });
186
222
 
187
223
  const updateAvailable = compareSemver(result.latestVersion, current || '0.0.0') > 0;
188
- const help = updateAvailable
224
+ const baseHelp = updateAvailable
189
225
  ? [`Run: npm install -g ${NPM_PACKAGE}@latest`, result.releaseUrl ? `Release: ${result.releaseUrl}` : null].filter(Boolean)
190
226
  : ['Already on latest version.'];
191
227
 
@@ -198,9 +234,10 @@ async function handleUpdate(_options, ctx) {
198
234
  releaseUrl: result.releaseUrl,
199
235
  updateAvailable,
200
236
  installCommand: `npm install -g ${NPM_PACKAGE}@latest`,
201
- fetched: true
237
+ fetched: true,
238
+ clientDrift: mismatches
202
239
  },
203
- help
240
+ help: [...baseHelp, ...driftHelp]
204
241
  }
205
242
  };
206
243
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genexus-mcp",
3
- "version": "2.6.3",
3
+ "version": "2.6.5",
4
4
  "mcpName": "io.github.lennix1337/genexus",
5
5
  "description": "GeneXus 18 MCP server — read, edit, and analyze GeneXus knowledge base objects (transactions, web panels, procedures, SDTs) directly from Claude, Cursor, and other AI agents over the Model Context Protocol.",
6
6
  "keywords": [
@@ -7,7 +7,7 @@
7
7
  "targets": {
8
8
  ".NETCoreApp,Version=v8.0": {},
9
9
  ".NETCoreApp,Version=v8.0/win-x64": {
10
- "GxMcp.Gateway/2.6.3": {
10
+ "GxMcp.Gateway/2.6.5": {
11
11
  "dependencies": {
12
12
  "Newtonsoft.Json": "13.0.3",
13
13
  "System.Management": "10.0.5",
@@ -66,7 +66,7 @@
66
66
  }
67
67
  },
68
68
  "libraries": {
69
- "GxMcp.Gateway/2.6.3": {
69
+ "GxMcp.Gateway/2.6.5": {
70
70
  "type": "project",
71
71
  "serviceable": false,
72
72
  "sha512": ""
Binary file
Binary file
Binary file
@@ -1,16 +1,17 @@
1
1
  [
2
- {"name":"genexus_whoami","description":"Return current KB context, GeneXus install, and MCP version.","inputSchema":{"type":"object","properties":{}}},
2
+ {"name":"genexus_whoami","description":"Return current KB context, GeneXus install, MCP version, AND inline playbooks for common flows (WWP, popup, layout). Call this FIRST on every session — the `playbooks` block routes you to the right tool without exploration.","inputSchema":{"type":"object","properties":{}}},
3
+ {"name":"genexus_recipe","description":"Fetch a step-by-step playbook for a named flow (e.g. 'wwp_on_transaction', 'wwp_on_webpanel', 'create_popup', 'edit_pattern_instance', 'add_custom_button'). Returns prereq/steps/pitfalls. Call name='list' to enumerate available recipes.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Recipe key, or 'list' to enumerate."}},"required":["name"]}},
3
4
  {"name":"genexus_query","description":"Search objects in the active KB. Supports prefixes (type:, usedby:, parent:, parentPath:, description:). Compact projection by default. See genexus://kb/tool-help/genexus_query for examples.","inputSchema":{"type":"object","properties":{"query":{"type":"string"},"typeFilter":{"type":"string"},"domainFilter":{"type":"string"},"limit":{"type":"integer"},"inline_read_top":{"type":"integer","description":"0-3. Inline reads of top N in response."},"kb":{"type":"string","description":"Target KB. Required when 2+ open."},"axiCompact":{"type":"boolean","description":"Default true. Returns compact projection (name,type,path). Pass false for full payload.","default":true}},"required":["query"]}},
4
5
  {"name":"genexus_list_objects","description":"List objects with pagination. Feed nextOffset until hasMore=false. Returns minimal shape by default (name, type, path, parent); verbose=true for full shape.","inputSchema":{"type":"object","properties":{"filter":{"type":"string","description":"Legacy: matches name OR description. Prefer nameFilter/descriptionFilter."},"nameFilter":{"type":"string","description":"Substring match on object name only."},"descriptionFilter":{"type":"string","description":"Substring match on description only."},"pathPrefix":{"type":"string","description":"Folder path prefix, e.g. 'Root Module/ClickSign/'."},"limit":{"type":"integer"},"offset":{"type":"integer"},"parent":{"type":"string"},"parentPath":{"type":"string"},"typeFilter":{"type":"string"},"verbose":{"type":"boolean","description":"When true, returns full item shape (default false)."},"inline_read_top":{"type":"integer","description":"0-3. Inline reads of top N in response."},"kb":{"type":"string","description":"Target KB. Required when 2+ open."},"axiCompact":{"type":"boolean","description":"Default true. Returns compact projection (name,type,path,parentPath). Pass false for full payload.","default":true}}}},
5
6
  {"name":"genexus_read","description":"Read source/metadata parts of one or more objects. Pass name or targets, plus parts=[...]. Paginate large source with offset/limit. See genexus://kb/tool-help/genexus_read for examples.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"targets":{"type":"array","items":{"type":"string"}},"part":{"type":"string"},"parts":{"type":"array","items":{"type":"string"},"description":"When set, only the listed parts are returned in a combined response. Mutually exclusive with part/offset/limit."},"offset":{"type":"integer"},"limit":{"type":"integer"},"type":{"type":"string"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}}}},
6
- {"name":"genexus_edit","description":"Edit an object part. Pass name or targets (mutually exclusive). Mode: full | patch. Use dryRun before persisting. See genexus://kb/tool-help/genexus_edit for examples.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"part":{"type":"string"},"mode":{"type":"string","enum":["full","patch","ops"]},"content":{"type":"string"},"ops":{"type":"array","description":"RFC 6902 JSON-Patch ops. Supported: add, remove, replace, test.","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","remove","replace","test"]},"path":{"type":"string"},"value":{}},"required":["op","path"]}},"patch":{"description":"Legacy string replacement OR {find, replace} JSON object. Prefer separate operation/context/content params for new code.","anyOf":[{"type":"string"},{"type":"object","properties":{"find":{"type":"string"},"replace":{"type":"string"}},"required":["find","replace"]}]},"context":{"type":"string"},"operation":{"type":"string","enum":["Replace","Insert_After","Append"]},"expectedCount":{"type":"integer"},"dryRun":{"type":"boolean"},"verifyRollback":{"type":"boolean"},"targets":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"content":{"type":"string"}},"required":["name","content"]}},"type":{"type":"string"},"return_post_state":{"type":"boolean","description":"Set false to omit post_state.diff from the response (default true)."},"verbose":{"type":"boolean","description":"When true, adds slices (±15 lines around each hunk) to post_state (default false)."},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}}}},
7
+ {"name":"genexus_edit","description":"Edit an object part. Pass name or targets (mutually exclusive). Mode: full | patch. Use dryRun before persisting. For WWP screens edit the host WorkWithPlus<X>.PatternInstance — NOT the WebForm of the parent (gets overwritten on reapply). See recipe 'edit_pattern_instance' and 'add_custom_button' via genexus_recipe.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"part":{"type":"string"},"mode":{"type":"string","enum":["full","patch","ops"]},"content":{"type":"string"},"ops":{"type":"array","description":"RFC 6902 JSON-Patch ops. Supported: add, remove, replace, test.","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","remove","replace","test"]},"path":{"type":"string"},"value":{}},"required":["op","path"]}},"patch":{"description":"Legacy string replacement OR {find, replace} JSON object. Prefer separate operation/context/content params for new code.","anyOf":[{"type":"string"},{"type":"object","properties":{"find":{"type":"string"},"replace":{"type":"string"}},"required":["find","replace"]}]},"context":{"type":"string"},"operation":{"type":"string","enum":["Replace","Insert_After","Append"]},"expectedCount":{"type":"integer"},"dryRun":{"type":"boolean"},"verifyRollback":{"type":"boolean"},"targets":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"content":{"type":"string"}},"required":["name","content"]}},"type":{"type":"string"},"return_post_state":{"type":"boolean","description":"Set false to omit post_state.diff from the response (default true)."},"verbose":{"type":"boolean","description":"When true, adds slices (±15 lines around each hunk) to post_state (default false)."},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}}}},
7
8
  {"name":"genexus_inspect","description":"Snapshot of an object: metadata, variables, structure, signature. Raw object shape without source text. For source/code use genexus_read.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"include":{"type":"array","items":{"type":"string","enum":["metadata","variables","signature","structure","parts","controls","events_repertoire","callers"]}},"type":{"type":"string"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["name"]}},
8
- {"name":"genexus_analyze","description":"Cross-object semantic analysis: impact, dependencies, complexity, naming, summary, explain. See genexus://kb/tool-help/genexus_analyze for mode selection.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"mode":{"type":"string","enum":["linter","navigation","hierarchy","impact","data_context","ui_context","pattern_metadata","summary","explain"]},"kb":{"type":"string","description":"Target KB. Required when 2+ open."},"code":{"type":"string","description":"Required when mode=explain."},"waitForIndex":{"type":"boolean","description":"For mode=impact: when true (default) block up to 30s for the index to be Ready; when false return a status envelope immediately if the index is Cold/Reindexing.","default":true}},"required":["name","mode"]}},
9
+ {"name":"genexus_analyze","description":"Cross-object semantic analysis: impact, dependencies, complexity, naming, summary. See genexus://kb/tool-help/genexus_analyze for mode selection.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"mode":{"type":"string","enum":["linter","navigation","hierarchy","impact","data_context","ui_context","pattern_metadata","summary"]},"kb":{"type":"string","description":"Target KB. Required when 2+ open."},"waitForIndex":{"type":"boolean","description":"For mode=impact: when true (default) block up to 30s for the index to be Ready; when false return a status envelope immediately if the index is Cold/Reindexing.","default":true}},"required":["name","mode"]}},
9
10
  {"name":"genexus_inject_context","description":"Inject SDT structures and Procedure signatures of called objects.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"recursive":{"type":"boolean"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["name"]}},
10
11
  {"name":"genexus_lifecycle","description":"Build, validate, index, or poll the KB. Long ops are async with operationId. See genexus://kb/tool-help/genexus_lifecycle for actions and target formats.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["build","cancel","rebuild","reorg","validate","validate-kb","sync","index","status","result","snapshots-list","snapshots-restore"]},"target":{"type":"string","description":"Object name(s), taskId, job_id, or op:<operationId>."},"code":{"type":"string"},"limit":{"type":"integer"},"snapshotPath":{"type":"string"},"estimated_seconds":{"type":"integer","description":"Build: <20 forces sync fast-path (one turn); >=20 (default 60) goes async."},"wait_seconds":{"type":"integer","description":"Status: 0-25. >0 long-polls server-side until terminal or timeout."},"compact":{"type":"boolean","description":"Status: when true (default) returns counts + top-10 errors + warning dedup. Set false for legacy full Output/Errors[]."},"force":{"type":"boolean","description":"Index: when true, clears in-memory + on-disk snapshot and runs a full SDK rescan. Use when impact analyze reports indexEdgesMissing or after adding/renaming objects."},"includeCallees":{"type":"string","enum":["none","direct","transitive"],"description":"Build: auto-expand target via call graph so callees compile before callers (fixes CS0246 from missing DLLs). Default transitive."},"buildPlanCap":{"type":"integer","description":"Build: max expanded nodes before BuildPlanTooLarge (default 200)."},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["action"]}},
11
12
  {"name":"genexus_forge","description":"Generate new code or structures from templates or translations.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["scaffold","translate","sample"]},"type":{"type":"string"},"name":{"type":"string"},"content":{"type":"string"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["action"]}},
12
13
  {"name":"genexus_test","description":"Execute native GeneXus tests (GXtest).","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["name"]}},
13
- {"name":"genexus_create_object","description":"Create any IDE-creatable GeneXus object (Transaction, Procedure, WebPanel, SDPanel, SDT, DataProvider, Domain, Dashboard, WorkflowDiagram, etc.). Domain takes dataType+length or enumValues. See genexus://kb/tool-help/genexus_create_object.","inputSchema":{"type":"object","properties":{"type":{"type":"string"},"name":{"type":"string"},"dataType":{"type":"string"},"length":{"type":"integer"},"decimals":{"type":"integer"},"signed":{"type":"boolean"},"description":{"type":"string"},"basedOn":{"type":"string"},"enumValues":{"type":"array","items":{"type":"object"}},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["type","name"]}},
14
+ {"name":"genexus_create_object","description":"Create any IDE-creatable GeneXus object (Transaction, Procedure, WebPanel, SDPanel, SDT, DataProvider, Domain, Dashboard, WorkflowDiagram, etc.). Domain takes dataType+length or enumValues. NOT for WorkWithPlus/WWP — that is a pattern applied via genexus_apply_pattern (see recipe 'wwp_on_transaction' or 'wwp_on_webpanel'). NOT for popups with editable inputs — use genexus_create_popup. See genexus://kb/tool-help/genexus_create_object.","inputSchema":{"type":"object","properties":{"type":{"type":"string"},"name":{"type":"string"},"dataType":{"type":"string"},"length":{"type":"integer"},"decimals":{"type":"integer"},"signed":{"type":"boolean"},"description":{"type":"string"},"basedOn":{"type":"string"},"enumValues":{"type":"array","items":{"type":"object"}},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["type","name"]}},
14
15
  {"name":"genexus_logs","description":"Read worker_debug.log tail for error diagnosis or correlation.","inputSchema":{"type":"object","properties":{"lines":{"type":"integer"},"filterCorrelation":{"type":"string"},"grep":{"type":"string"}}}},
15
16
  {"name":"genexus_sdk_probe","description":"Scan loaded GeneXus SDK assemblies and dump a structured map of every public type, method, property to docs/sdk-probe/. Use when investigating new SDK surface or hunting for an entry point. Outputs raw.json + INDEX.md + generators.md.","inputSchema":{"type":"object","properties":{"outputDir":{"type":"string","description":"Optional absolute path. Defaults to <repo>/docs/sdk-probe/ or %TEMP%/gxmcp_sdk_probe/."}}}},
16
17
  {"name":"genexus_worker_reload","description":"Hot-reload worker: copy new binaries and respawn.","inputSchema":{"type":"object","properties":{"sourceDir":{"type":"string","description":"Absolute path to freshly built bin/Release."}},"required":["sourceDir"]}},
@@ -37,7 +38,7 @@
37
38
  {"name":"genexus_kb","description":"Manage open KBs: list (with PID/RSS/idle), open (acquire Worker), close (release), set_default (persist alias to config.json).","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["list","open","close","set_default"]},"alias":{"type":"string","description":"KB alias (for open/close). For open, auto-generated from path basename if omitted."},"path":{"type":"string","description":"Absolute path to the KB (required for action=open if alias is not declared in config)."}},"required":["action"]}},
38
39
  {"name":"genexus_sql","description":"SQL for a Transaction/Table (DDL) or a procedure For Each navigation.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["ddl","navigation"]},"name":{"type":"string"},"includeSubordinated":{"type":"boolean","description":"action=ddl only."},"levelNumber":{"type":"integer","description":"action=navigation only."},"type":{"type":"string"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["action","name"]}},
39
40
  {"name":"genexus_preview","description":"Render preview of a WebPanel via headless Chrome (chrome-devtools-axi). Opens the launcher page, fills required parms, navigates to the target, captures HTML/a11y/screenshot, optionally diffs vs baseline. Returns status + captures.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Target WebPanel name."},"parms":{"type":"object","description":"Per-call launcher parms (merged over config defaults and objectParms)."},"launcher":{"type":"string","description":"Launcher page (default 'auto' = config.launcher)."},"buildFirst":{"type":"boolean","default":false,"description":"If true, dispatch a build before opening the browser."},"waitMs":{"type":"integer","default":3000,"description":"Milliseconds to wait after navigation before capture."},"capture":{"type":"array","items":{"type":"string","enum":["html","a11y","screenshot","console"]},"description":"Capture set (default ['html','a11y'])."},"diffBaseline":{"type":"boolean","default":false,"description":"Compute structural diff vs stored a11y baseline."},"updateBaseline":{"type":"boolean","default":false,"description":"Persist current a11y snapshot as the new baseline."},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["name"]}},
40
- {"name":"genexus_apply_pattern","description":"Apply a GeneXus pattern (e.g. WorkWithPlus) to a parent object. Equivalent to IDE 'Right-click → Apply Pattern'.","inputSchema":{"type":"object","required":["name","pattern"],"properties":{"name":{"type":"string","description":"Target KBObject name (Transaction, WebPanel, etc.)"},"pattern":{"type":"string","description":"Pattern key ('WorkWithPlus') or GUID"},"settings":{"type":"object","description":"Optional pattern-instance settings (deeply nested config tree from the pattern's settings dialog)"},"reapply":{"type":"boolean","default":false,"description":"Re-run on existing instance instead of first-time apply"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}}}},
41
+ {"name":"genexus_apply_pattern","description":"Apply a GeneXus pattern (e.g. WorkWithPlus) to a parent object. Equivalent to IDE 'Right-click → Apply Pattern'. ALWAYS run genexus_inspect first to confirm parentType — Transaction (family-gen, no template) vs WebPanel/SDPanel (direct-attach, pass settings.template). Other types are rejected. See recipe 'wwp_on_transaction' or 'wwp_on_webpanel' via genexus_recipe.","inputSchema":{"type":"object","required":["name","pattern"],"properties":{"name":{"type":"string","description":"Target KBObject name (Transaction, WebPanel, etc.)"},"pattern":{"type":"string","description":"Pattern key ('WorkWithPlus') or GUID"},"settings":{"type":"object","description":"Optional pattern-instance settings (deeply nested config tree from the pattern's settings dialog)"},"reapply":{"type":"boolean","default":false,"description":"Re-run on existing instance instead of first-time apply"},"validate":{"type":"boolean","default":false,"description":"After apply, build the generated host to verify it compiles cleanly. Response gains a `validation` block: {status: ok|failed|timeout, errorCount, warningCount, errors[], warnings[], durationMs}. Adds 60-180s wall time but catches binding/wiring errors the LLM would only see by opening the IDE."},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}}}},
41
42
  {"name":"genexus_edit_and_build","description":"Edit an object then rebuild its callers in one call. Returns edit diff + impact + build operationId. See genexus://kb/tool-help/genexus_edit_and_build for examples.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Target object name."},"part":{"type":"string","description":"Part to edit (Source, Rules, ...)."},"content":{"type":"string","description":"New content or unified diff."},"mode":{"type":"string","enum":["full","patch"],"default":"patch"},"type":{"type":"string","description":"Disambiguates when name matches multiple objects."},"dryRun":{"type":"boolean","default":false},"buildIncludeCallees":{"type":"string","enum":["none","direct","transitive"],"default":"direct"},"buildPlanCap":{"type":"integer","default":200},"waitForIndex":{"type":"boolean","default":true},"waitTimeoutMs":{"type":"integer","default":30000}},"required":["name","part","content"]}},
42
43
  {"name":"genexus_create_popup","description":"W3: create a popup WebPanel with a Form type=\"layout\" body so radio/combo bindings render editable. Spec carries title, description, inputs (radio|combo|text with options), buttons, inParms, outParms.","inputSchema":{"type":"object","required":["name","spec"],"properties":{"name":{"type":"string","description":"WebPanel name. Created if missing; updated if exists."},"spec":{"type":"object","description":"Popup spec.","properties":{"title":{"type":"string"},"description":{"type":"string"},"inputs":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["radio","combo","text"]},"varName":{"type":"string"},"label":{"type":"string"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}}}},"showWhen":{"type":"string","description":"e.g. \"RespRegProf == 'S'\"; emits Refresh event toggling group visibility."}},"required":["type","varName"]}},"buttons":{"type":"array","items":{"type":"object","properties":{"caption":{"type":"string"},"event":{"type":"string"}}}},"inParms":{"type":"array","items":{"type":"string"},"description":"e.g. [\"AluAnoCad:Numeric(2)\"]"},"outParms":{"type":"array","items":{"type":"string"}}}},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}}}}
43
44
  ]
Binary file
Binary file