genexus-mcp 2.6.5 → 2.6.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.
@@ -18,6 +18,7 @@ const {
18
18
  readGeneXusVersionFromInstall,
19
19
  discoverGeneXusInstallation,
20
20
  discoverKnowledgeBase,
21
+ discoverKnowledgeBases,
21
22
  readKbCatalog,
22
23
  addKbToConfig,
23
24
  removeKbFromConfig,
@@ -419,6 +420,234 @@ function buildClientExeCrossCheck(packageExePath) {
419
420
  };
420
421
  }
421
422
 
423
+ // v2.6.6 — probe Stream B's per-KB worker lock. The worker writes
424
+ // `.worker-<hash>.lock` into TempPath (or the configured lockDir); the file
425
+ // holds the live PID. We list every matching file, classify it as live /
426
+ // stale, and surface stale ones so the user can clean them up.
427
+ function buildWorkerSingleInstanceLockCheck() {
428
+ const tempDir = require('os').tmpdir();
429
+ let entries;
430
+ try {
431
+ entries = fs.readdirSync(tempDir).filter((n) => /^\.worker-[0-9a-f]{16}\.lock$/i.test(n));
432
+ } catch (err) {
433
+ return { status: 'warn', detail: `Unable to scan ${tempDir} for worker lock files: ${err.message || err}` };
434
+ }
435
+
436
+ if (!entries || entries.length === 0) {
437
+ return { status: 'pass', detail: 'No worker single-instance lock files found (no worker currently registered).' };
438
+ }
439
+
440
+ const live = [];
441
+ const stale = [];
442
+ for (const name of entries) {
443
+ const full = path.join(tempDir, name);
444
+ let pid = null;
445
+ try {
446
+ const txt = fs.readFileSync(full, 'utf8').trim();
447
+ const parsed = Number.parseInt(txt, 10);
448
+ if (Number.isFinite(parsed) && parsed > 0) pid = parsed;
449
+ } catch {
450
+ // Lock file holds a delete-on-close handle on the owning worker —
451
+ // if reading fails with EBUSY/EACCES it's almost certainly live.
452
+ live.push({ name, pid: null, note: 'file is locked (owner alive)' });
453
+ continue;
454
+ }
455
+
456
+ if (pid === null) {
457
+ stale.push({ name, pid: null, note: 'pid file unreadable / empty' });
458
+ continue;
459
+ }
460
+
461
+ let alive = false;
462
+ try {
463
+ process.kill(pid, 0);
464
+ alive = true;
465
+ } catch (err) {
466
+ alive = err.code === 'EPERM'; // EPERM => process exists but we can't signal it
467
+ }
468
+
469
+ if (alive) live.push({ name, pid, note: null });
470
+ else stale.push({ name, pid, note: 'pid not running' });
471
+ }
472
+
473
+ if (stale.length === 0) {
474
+ const desc = live.map((l) => l.pid ? `pid=${l.pid}` : (l.note || 'locked')).join(', ');
475
+ return {
476
+ status: 'pass',
477
+ detail: `Worker single-instance lock healthy (${live.length} live owner${live.length === 1 ? '' : 's'}: ${desc}).`
478
+ };
479
+ }
480
+
481
+ const livePart = live.length ? `live: ${live.map((l) => l.pid || '?').join(', ')}` : 'no live owners';
482
+ const stalePart = `stale: ${stale.map((s) => path.join(tempDir, s.name)).join('; ')}`;
483
+ return {
484
+ status: 'warn',
485
+ detail: `Worker lock files include ${stale.length} stale entr${stale.length === 1 ? 'y' : 'ies'} (${livePart}; ${stalePart}). Delete the stale .lock file(s) to clear; Stream B's lock will recreate on next worker start.`
486
+ };
487
+ }
488
+
489
+ // v2.6.6 — probe Stream D's in-process build path. Confirms
490
+ // `Genexus.MsBuild.Tasks.dll` is reachable under GX_PROGRAM_DIR / configured
491
+ // GeneXus install. If missing, the worker falls back to spawning MSBuild.exe
492
+ // (the slow path) — that still works but loses the IDE-parity build daemon.
493
+ function buildInProcessBuildAssemblyLoadCheck(gxPath) {
494
+ const dllName = 'Genexus.MsBuild.Tasks.dll';
495
+ const candidates = [];
496
+ if (process.env.GX_PROGRAM_DIR) candidates.push(path.join(process.env.GX_PROGRAM_DIR, dllName));
497
+ if (gxPath) candidates.push(path.join(gxPath, dllName));
498
+
499
+ if (candidates.length === 0) {
500
+ return {
501
+ status: 'warn',
502
+ detail: `In-process build assembly check skipped: GX_PROGRAM_DIR not set and no configured GeneXus path. Build will fall back to MSBuild.exe spawn (the slow path).`
503
+ };
504
+ }
505
+
506
+ for (const candidate of candidates) {
507
+ try {
508
+ const stat = fs.statSync(candidate);
509
+ if (stat && stat.isFile() && stat.size > 0) {
510
+ return {
511
+ status: 'pass',
512
+ detail: `In-process build assembly is loadable from ${candidate} (${Math.round(stat.size / 1024)} KB). Stream D's build daemon is ready.`
513
+ };
514
+ }
515
+ } catch {
516
+ // try next candidate
517
+ }
518
+ }
519
+
520
+ return {
521
+ status: 'warn',
522
+ detail: `In-process build assembly ${dllName} not found at: ${candidates.join('; ')}. The worker will fall back to spawning MSBuild.exe (the slow path) — set GXMCP_INPROCESS_BUILD=0 to silence, or install/repair GeneXus.`
523
+ };
524
+ }
525
+
526
+ function redactConfig(cfg) {
527
+ // Replace absolute paths with `<redacted:hash8>` so the structure is preserved
528
+ // but filesystem layout, usernames, and KB names are not leaked. Hash is stable
529
+ // across the dump so the support engineer can still correlate "this redacted KB
530
+ // is the same as that one in worker logs."
531
+ if (!cfg || typeof cfg !== 'object') return cfg;
532
+ const crypto = require('crypto');
533
+ const hash = (s) => crypto.createHash('sha256').update(s).digest('hex').slice(0, 8);
534
+ const looksLikePath = (s) => typeof s === 'string' && /[\\/]/.test(s) && (s.length > 3);
535
+ const walk = (node) => {
536
+ if (Array.isArray(node)) return node.map(walk);
537
+ if (node && typeof node === 'object') {
538
+ const out = {};
539
+ for (const k of Object.keys(node)) out[k] = walk(node[k]);
540
+ return out;
541
+ }
542
+ if (looksLikePath(node)) return `<redacted:${hash(node)}>`;
543
+ return node;
544
+ };
545
+ return walk(cfg);
546
+ }
547
+
548
+ async function buildSupportDump({ checks, summary, data, gatewayExePath, ctx }) {
549
+ const os = require('os');
550
+ const crypto = require('crypto');
551
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
552
+ const tmpBase = path.join(os.tmpdir(), `genexus-mcp-dump-${stamp}`);
553
+ fs.mkdirSync(tmpBase, { recursive: true });
554
+
555
+ const entries = [];
556
+
557
+ const writeEntry = (relPath, content) => {
558
+ const full = path.join(tmpBase, relPath);
559
+ fs.mkdirSync(path.dirname(full), { recursive: true });
560
+ fs.writeFileSync(full, typeof content === 'string' ? content : JSON.stringify(content, null, 2));
561
+ entries.push(relPath);
562
+ };
563
+
564
+ writeEntry('doctor.json', { summary, checks, generatedAt: new Date().toISOString() });
565
+
566
+ if (data.configPath && fs.existsSync(data.configPath)) {
567
+ const cfg = readJsonFileSafe(data.configPath);
568
+ writeEntry('config.redacted.json', redactConfig(cfg));
569
+ }
570
+
571
+ let gxVersion = null;
572
+ try { gxVersion = readGeneXusVersionFromInstall(data.gxPath); } catch { }
573
+
574
+ writeEntry('environment.json', {
575
+ platform: process.platform,
576
+ arch: process.arch,
577
+ nodeVersion: process.version,
578
+ osRelease: os.release(),
579
+ cwdHash: crypto.createHash('sha256').update(ctx.cwd || '').digest('hex').slice(0, 8),
580
+ gatewayExePath,
581
+ gatewayExeExists: fs.existsSync(gatewayExePath),
582
+ configSource: data.configSource,
583
+ kbConfigured: !!data.kbPath,
584
+ gxConfigured: !!data.gxPath,
585
+ gxVersion,
586
+ envFlags: {
587
+ GX_CONFIG_PATH: !!process.env.GX_CONFIG_PATH,
588
+ GENEXUS_MCP_GATEWAY_EXE: !!process.env.GENEXUS_MCP_GATEWAY_EXE,
589
+ GXMCP_INPROCESS_BUILD: process.env.GXMCP_INPROCESS_BUILD || null,
590
+ HTTPS_PROXY: !!process.env.HTTPS_PROXY,
591
+ HTTP_PROXY: !!process.env.HTTP_PROXY
592
+ }
593
+ });
594
+
595
+ // Worker logs: scan the standard log dir (LocalAppData) for recent worker logs,
596
+ // grab the tail of each. Keep it bounded so the zip stays small.
597
+ const logRoots = [];
598
+ if (process.env.LOCALAPPDATA) logRoots.push(path.join(process.env.LOCALAPPDATA, 'GenexusMCP', 'logs'));
599
+ logRoots.push(path.join(os.tmpdir(), 'GenexusMCP', 'logs'));
600
+
601
+ let logCount = 0;
602
+ for (const root of logRoots) {
603
+ if (logCount >= 5) break;
604
+ try {
605
+ if (!fs.existsSync(root)) continue;
606
+ const files = fs.readdirSync(root)
607
+ .filter((f) => /\.log$/i.test(f))
608
+ .map((f) => ({ f, full: path.join(root, f), mtime: fs.statSync(path.join(root, f)).mtimeMs }))
609
+ .sort((a, b) => b.mtime - a.mtime)
610
+ .slice(0, 5 - logCount);
611
+ for (const entry of files) {
612
+ try {
613
+ const stat = fs.statSync(entry.full);
614
+ const tailBytes = 64 * 1024;
615
+ const start = Math.max(0, stat.size - tailBytes);
616
+ const fd = fs.openSync(entry.full, 'r');
617
+ try {
618
+ const buf = Buffer.alloc(stat.size - start);
619
+ fs.readSync(fd, buf, 0, buf.length, start);
620
+ writeEntry(`logs/${entry.f}.tail.txt`, buf.toString('utf8'));
621
+ } finally {
622
+ fs.closeSync(fd);
623
+ }
624
+ logCount += 1;
625
+ } catch { }
626
+ }
627
+ } catch { }
628
+ }
629
+
630
+ // Zip via PowerShell's built-in Compress-Archive on Windows; tar elsewhere.
631
+ const { execFileSync } = require('child_process');
632
+ let zipPath;
633
+ if (process.platform === 'win32') {
634
+ zipPath = `${tmpBase}.zip`;
635
+ execFileSync('powershell.exe', ['-NoProfile', '-Command', `Compress-Archive -Path '${tmpBase}\\*' -DestinationPath '${zipPath}' -Force`], { windowsHide: true });
636
+ } else {
637
+ zipPath = `${tmpBase}.tar.gz`;
638
+ execFileSync('tar', ['-czf', zipPath, '-C', path.dirname(tmpBase), path.basename(tmpBase)]);
639
+ }
640
+
641
+ // Clean up the staging dir; the zip is the artifact.
642
+ try { fs.rmSync(tmpBase, { recursive: true, force: true }); } catch { }
643
+
644
+ return {
645
+ zipPath,
646
+ sizeBytes: fs.statSync(zipPath).size,
647
+ entries
648
+ };
649
+ }
650
+
422
651
  async function handleDoctor(options, ctx) {
423
652
  const data = buildStatusData(ctx.cwd);
424
653
  const toolDefPath = getToolDefinitionsPath();
@@ -453,14 +682,25 @@ async function handleDoctor(options, ctx) {
453
682
  ? `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
683
  : 'Gateway exe is in a path unlikely to be blocked by AppLocker/SRP.'
455
684
  },
456
- { id: 'kb_path_exists', status: kbExists ? 'pass' : 'warn', detail: kbExists ? 'Configured KB path exists.' : 'Configured KB path does not exist.' },
685
+ // A KB path configured but absent on disk is fatal the worker can't open a KB
686
+ // that doesn't exist. Only when no KB is configured at all do we soften to warn.
687
+ { id: 'kb_path_exists', status: kbExists ? 'pass' : (kbPath ? 'fail' : 'warn'), detail: kbExists ? 'Configured KB path exists.' : (kbPath ? `Configured KB path does not exist: ${kbPath}` : 'No KB path is configured.') },
457
688
  { 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.' },
458
- { id: 'gx_installation', status: gxExeExists ? 'pass' : 'warn', detail: gxExeExists ? 'GeneXus installation has genexus.exe.' : 'Configured GeneXus installation is missing genexus.exe.' },
689
+ // Same logic for the GeneXus install: missing genexus.exe at a configured path
690
+ // guarantees a worker crash on first MCP call. Promote from warn to fail so init
691
+ // exits non-zero and the caller (install.ps1, AI client) actually sees the problem.
692
+ { id: 'gx_installation', status: gxExeExists ? 'pass' : (gxPath ? 'fail' : 'warn'), detail: gxExeExists ? 'GeneXus installation has genexus.exe.' : (gxPath ? `Configured GeneXus installation is missing genexus.exe at: ${gxPath}` : 'No GeneXus installation path is configured.') },
459
693
  { id: 'tool_definitions', status: toolDefsExists ? 'pass' : 'warn', detail: toolDefsExists ? `Tool definition file found (${toolCount} tools).` : 'tool_definitions.json is missing.' },
460
694
  { 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
695
  { id: 'client_config_sync', status: clientCrossCheck.status, detail: clientCrossCheck.detail }
462
696
  ];
463
697
 
698
+ // v2.6.6 — Stream B / Stream D doctor checks.
699
+ const lockCheck = buildWorkerSingleInstanceLockCheck();
700
+ checks.push({ id: 'worker_single_instance_lock', status: lockCheck.status, detail: lockCheck.detail });
701
+ const inProcessLoad = buildInProcessBuildAssemblyLoadCheck(gxPath);
702
+ checks.push({ id: 'in_process_build_assembly_load', status: inProcessLoad.status, detail: inProcessLoad.detail });
703
+
464
704
  if (data.gatewayExeFound) {
465
705
  const probe = await probeGatewaySpawn();
466
706
  checks.push({ id: 'gateway_spawn_probe', status: probe.status, detail: probe.detail });
@@ -478,6 +718,34 @@ async function handleDoctor(options, ctx) {
478
718
  return acc;
479
719
  }, { pass: 0, warn: 0, fail: 0 });
480
720
 
721
+ // Support bundle for handing off to support: doctor output + config (with paths
722
+ // anonymized by hash so we don't leak filesystem layout) + recent worker logs +
723
+ // version info, all zipped under TempPath. Far simpler than asking the operator
724
+ // to send 5 separate things over chat.
725
+ if (options.dump) {
726
+ try {
727
+ const dumpResult = await buildSupportDump({ checks, summary, data, gatewayExePath, ctx });
728
+ return {
729
+ exitCode: ctx.EXIT_CODES.OK,
730
+ envelope: {
731
+ ok: {
732
+ action: 'doctor.dump',
733
+ zipPath: dumpResult.zipPath,
734
+ sizeBytes: dumpResult.sizeBytes,
735
+ entries: dumpResult.entries,
736
+ summary
737
+ },
738
+ help: ['Attach the zip to your support ticket. Paths inside config.json have been redacted; sensitive values may still appear in worker logs — review before sharing if needed.']
739
+ }
740
+ };
741
+ } catch (err) {
742
+ return {
743
+ exitCode: ctx.EXIT_CODES.ERROR,
744
+ envelope: operationalErrorEnvelope(`Failed to build doctor dump: ${err && err.message ? err.message : 'unknown error'}`, ctx.EXIT_CODES.ERROR)
745
+ };
746
+ }
747
+ }
748
+
481
749
  const defaultFields = ['id', 'status', 'detail'];
482
750
  const allowedFields = ['id', 'status', 'detail'];
483
751
  const fieldSelection = validateFieldSelection(options.fields, allowedFields, 'doctor', ctx);
@@ -890,6 +1158,34 @@ async function runInteractiveInit(ctx) {
890
1158
  const gxAnswer = await question(`\n2) GeneXus installation path (default: ${defaultGx}):\n> `);
891
1159
  const finalGx = String(gxAnswer || '').trim() || defaultGx;
892
1160
 
1161
+ if (!fs.existsSync(path.join(finalGx, 'genexus.exe'))) {
1162
+ const suggested = discoverGeneXusInstallation();
1163
+ const help = [`Path checked: ${finalGx}`];
1164
+ if (suggested && suggested.toLowerCase() !== finalGx.toLowerCase()) {
1165
+ help.push(`Detected a working GeneXus install at: ${suggested}`);
1166
+ help.push('Re-run `genexus-mcp init --interactive` and accept the detected path, or pass --gx explicitly.');
1167
+ }
1168
+ return {
1169
+ exitCode: ctx.EXIT_CODES.ERROR,
1170
+ envelope: operationalErrorEnvelope(
1171
+ `GeneXus path does not contain genexus.exe. Aborted before writing config to avoid silent worker crashes.`,
1172
+ ctx.EXIT_CODES.ERROR,
1173
+ help
1174
+ )
1175
+ };
1176
+ }
1177
+
1178
+ if (!fs.existsSync(finalKb)) {
1179
+ return {
1180
+ exitCode: ctx.EXIT_CODES.ERROR,
1181
+ envelope: operationalErrorEnvelope(
1182
+ `KB path does not exist on disk. Aborted before writing config.`,
1183
+ ctx.EXIT_CODES.ERROR,
1184
+ [`Path checked: ${finalKb}`, 'Create the KB in GeneXus first, then re-run init.']
1185
+ )
1186
+ };
1187
+ }
1188
+
893
1189
  const allTargets = getClientConfigTargets();
894
1190
  const platformTargets = filterClientTargets(allTargets, { platform: process.platform });
895
1191
  ctx.stderr.write('\n3) Select AI agents to register (y/N per agent; Enter accepts default):\n');
@@ -971,6 +1267,18 @@ async function handleInit(options, ctx) {
971
1267
  }
972
1268
  }
973
1269
 
1270
+ // Broaden the search: walk-up from cwd and scan common KB roots. If exactly one
1271
+ // candidate exists, use it; if many, surface them so the operator can pass --kb
1272
+ // explicitly instead of seeing the bare "missing --kb" error.
1273
+ let kbCandidates = null;
1274
+ if (!resolution.kb.value) {
1275
+ kbCandidates = discoverKnowledgeBases(ctx.cwd);
1276
+ if (kbCandidates.length === 1) {
1277
+ resolution.kb.value = kbCandidates[0].path;
1278
+ resolution.kb.source = `auto-discovery:${kbCandidates[0].source}`;
1279
+ }
1280
+ }
1281
+
974
1282
  if (!resolution.gx.value) {
975
1283
  const fromDisco = discoverGeneXusInstallation();
976
1284
  if (fromDisco) {
@@ -981,13 +1289,69 @@ async function handleInit(options, ctx) {
981
1289
 
982
1290
  if (!resolution.kb.value || !resolution.gx.value) {
983
1291
  const missing = [];
984
- if (!resolution.kb.value) missing.push('--kb (and current directory is not a GeneXus KB)');
1292
+ const help = [];
1293
+ if (!resolution.kb.value) {
1294
+ missing.push('--kb (and current directory is not a GeneXus KB)');
1295
+ if (kbCandidates && kbCandidates.length > 1) {
1296
+ help.push(`Found ${kbCandidates.length} candidate KB folder${kbCandidates.length === 1 ? '' : 's'}. Pick one and pass it as --kb:`);
1297
+ for (const c of kbCandidates.slice(0, 10)) {
1298
+ help.push(` --kb "${c.path}" (${c.source})`);
1299
+ }
1300
+ if (kbCandidates.length > 10) {
1301
+ help.push(` ... and ${kbCandidates.length - 10} more.`);
1302
+ }
1303
+ }
1304
+ }
985
1305
  if (!resolution.gx.value) missing.push('--gx (and no GeneXus installation was auto-discovered)');
986
1306
  return {
987
1307
  exitCode: ctx.EXIT_CODES.USAGE,
988
- envelope: usageEnvelope(
989
- `Cannot resolve required paths: ${missing.join('; ')}. Pass flags explicitly or run from inside a KB folder.`,
990
- ctx.EXIT_CODES.USAGE
1308
+ envelope: {
1309
+ error: { code: 'usage_error', message: `Cannot resolve required paths: ${missing.join('; ')}. Pass flags explicitly or run from inside a KB folder.` },
1310
+ help: help.length ? help : [
1311
+ 'Run `genexus-mcp help` for command reference.',
1312
+ 'Run `genexus-mcp init --kb "<kbPath>" --gx "<geneXusPath>"` for non-interactive setup.'
1313
+ ],
1314
+ meta: { exitCode: ctx.EXIT_CODES.USAGE }
1315
+ }
1316
+ };
1317
+ }
1318
+
1319
+ // Validate the supplied GeneXus path before we commit it to disk. A non-default
1320
+ // install (e.g. C:\...\GeneXus18u7 vs the GeneXus18 default) used to slip through
1321
+ // — init wrote the config, then the worker crashed on first MCP call with no
1322
+ // useful signal back to the operator.
1323
+ if (!fs.existsSync(path.join(resolution.gx.value, 'genexus.exe'))) {
1324
+ const help = [
1325
+ `Path checked: ${resolution.gx.value}`,
1326
+ `Source: --${resolution.gx.source === 'flag' ? 'gx flag' : resolution.gx.source}`
1327
+ ];
1328
+ const suggested = resolution.gx.source === 'flag' ? discoverGeneXusInstallation() : null;
1329
+ if (suggested && suggested.toLowerCase() !== resolution.gx.value.toLowerCase()) {
1330
+ help.push(`Detected a working GeneXus install at: ${suggested}`);
1331
+ help.push(`Re-run: genexus-mcp init --kb "${resolution.kb.value}" --gx "${suggested}"`);
1332
+ } else {
1333
+ help.push('Omit --gx to let init auto-discover via registry / Program Files (matches GeneXus18, GeneXus18u7, etc.).');
1334
+ }
1335
+ return {
1336
+ exitCode: ctx.EXIT_CODES.ERROR,
1337
+ envelope: operationalErrorEnvelope(
1338
+ `Configured GeneXus path does not contain genexus.exe. Init aborted before writing config to avoid silent worker crashes.`,
1339
+ ctx.EXIT_CODES.ERROR,
1340
+ help
1341
+ )
1342
+ };
1343
+ }
1344
+
1345
+ if (!fs.existsSync(resolution.kb.value)) {
1346
+ return {
1347
+ exitCode: ctx.EXIT_CODES.ERROR,
1348
+ envelope: operationalErrorEnvelope(
1349
+ `Configured KB path does not exist on disk. Init aborted.`,
1350
+ ctx.EXIT_CODES.ERROR,
1351
+ [
1352
+ `Path checked: ${resolution.kb.value}`,
1353
+ 'Create the KB in GeneXus first, then re-run init pointing at its folder.'
1354
+ ]
991
1355
  )
992
1356
  };
993
1357
  }
@@ -1014,7 +1378,8 @@ async function handleInit(options, ctx) {
1014
1378
  }
1015
1379
 
1016
1380
  const verification = await runPostInitVerification({
1017
- cwd: ctx.cwd,
1381
+ cwd: resolution.kb.value,
1382
+ configPath: created.targetConfigPath,
1018
1383
  includeSmoke: !options.noSmoke,
1019
1384
  ctx
1020
1385
  });
@@ -1031,15 +1396,27 @@ async function handleInit(options, ctx) {
1031
1396
  if (patchResult.patched.length > 0 && process.platform === 'win32' && !process.env.GENEXUS_MCP_GATEWAY_EXE) {
1032
1397
  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
1398
  }
1034
- if (verification.summary.fail > 0 || verification.summary.warn > 0) {
1035
- help.push('Some verification checks did not pass. Run `genexus-mcp doctor --mcp-smoke` for details.');
1399
+ if (verification.summary.fail > 0) {
1400
+ const failedIds = verification.checks
1401
+ .filter((c) => c.status === 'fail')
1402
+ .map((c) => c.id)
1403
+ .join(', ');
1404
+ help.push(`Verification failed (${verification.summary.fail} check${verification.summary.fail === 1 ? '' : 's'}: ${failedIds}). The config was written but the MCP will not work until these are fixed.`);
1405
+ help.push('Run `genexus-mcp doctor --mcp-smoke` for full details.');
1406
+ } else if (verification.summary.warn > 0) {
1407
+ help.push('Some verification checks emitted warnings. Run `genexus-mcp doctor --mcp-smoke` for details.');
1036
1408
  }
1037
1409
  if (options.noSmoke) {
1038
1410
  help.push('MCP protocol smoke was skipped (--no-smoke). Re-run `genexus-mcp doctor --mcp-smoke` to validate end-to-end.');
1039
1411
  }
1040
1412
 
1413
+ // A non-zero exit when any critical check failed gives the caller (install.ps1,
1414
+ // CI, AI client) something to react to. Previously init always returned OK and
1415
+ // the failure surfaced later as a generic worker crash.
1416
+ const initExitCode = verification.summary.fail > 0 ? ctx.EXIT_CODES.ERROR : ctx.EXIT_CODES.OK;
1417
+
1041
1418
  return {
1042
- exitCode: ctx.EXIT_CODES.OK,
1419
+ exitCode: initExitCode,
1043
1420
  envelope: {
1044
1421
  ok: {
1045
1422
  action: 'init',
@@ -1103,14 +1480,95 @@ async function warmGateway({ configPath }) {
1103
1480
  });
1104
1481
  }
1105
1482
 
1106
- async function runPostInitVerification({ cwd, includeSmoke, ctx }) {
1107
- const doctorResult = await handleDoctor(
1108
- { full: false, mcpSmoke: !!includeSmoke, fields: null, limit: 100 },
1109
- { cwd, EXIT_CODES: ctx.EXIT_CODES }
1110
- );
1483
+ async function runPostInitVerification({ cwd, configPath, includeSmoke, ctx }) {
1484
+ // Doctor inspects buildStatusData(cwd), which only finds config.json if it sits
1485
+ // at cwd or if GX_CONFIG_PATH is exported. Init runs from wherever the operator
1486
+ // typed `npx genexus-mcp init` (often C:\windows\system32) — so we point doctor
1487
+ // at the freshly-written config explicitly.
1488
+ const priorEnv = process.env.GX_CONFIG_PATH;
1489
+ if (configPath) process.env.GX_CONFIG_PATH = configPath;
1490
+ try {
1491
+ const doctorResult = await handleDoctor(
1492
+ { full: false, mcpSmoke: !!includeSmoke, fields: null, limit: 100 },
1493
+ { cwd, EXIT_CODES: ctx.EXIT_CODES }
1494
+ );
1495
+
1496
+ const { checks, summary } = doctorResult.envelope.ok;
1497
+
1498
+ let workerSmoke = null;
1499
+ if (includeSmoke && configPath) {
1500
+ workerSmoke = await probeWorkerStartup({ configPath });
1501
+ checks.push({ id: 'worker_startup_smoke', status: workerSmoke.status, detail: workerSmoke.detail });
1502
+ summary[workerSmoke.status] = (summary[workerSmoke.status] || 0) + 1;
1503
+ }
1504
+
1505
+ return { checks, summary };
1506
+ } finally {
1507
+ if (priorEnv === undefined) delete process.env.GX_CONFIG_PATH;
1508
+ else process.env.GX_CONFIG_PATH = priorEnv;
1509
+ }
1510
+ }
1511
+
1512
+ // Spawn the gateway with the resolved config and watch for an early crash.
1513
+ // If the worker can't load the KB (bad GX path, missing genexus.exe, KB lock,
1514
+ // AppLocker block, etc.), the process exits fast with a non-zero code. Without
1515
+ // this probe init prints "ok" and the failure only surfaces later on the first
1516
+ // MCP call, with a generic "Worker crashed/exited" — exactly what bit the user
1517
+ // who had GeneXus18u7 instead of GeneXus18.
1518
+ async function probeWorkerStartup({ configPath, observeMs = 2500 }) {
1519
+ const gatewayExePath = getGatewayExePath();
1520
+ if (!fs.existsSync(gatewayExePath)) {
1521
+ return { status: 'warn', detail: 'Worker smoke skipped: gateway exe not found.' };
1522
+ }
1523
+
1524
+ return await new Promise((resolve) => {
1525
+ let stderr = '';
1526
+ let stdout = '';
1527
+ let resolved = false;
1528
+ const finish = (payload) => {
1529
+ if (resolved) return;
1530
+ resolved = true;
1531
+ resolve(payload);
1532
+ };
1533
+
1534
+ let child;
1535
+ try {
1536
+ child = spawn(gatewayExePath, [], {
1537
+ stdio: ['ignore', 'pipe', 'pipe'],
1538
+ windowsHide: true,
1539
+ env: { ...process.env, GX_CONFIG_PATH: configPath }
1540
+ });
1541
+ } catch (err) {
1542
+ return finish({ status: 'fail', detail: `Worker smoke: failed to launch gateway: ${err.message}` });
1543
+ }
1544
+
1545
+ child.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
1546
+ child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
1111
1547
 
1112
- const { checks, summary } = doctorResult.envelope.ok;
1113
- return { checks, summary };
1548
+ child.once('error', (err) => {
1549
+ finish({ status: 'fail', detail: `Worker smoke: gateway spawn error: ${err.message}` });
1550
+ });
1551
+
1552
+ child.once('exit', (code) => {
1553
+ if (code === 0) {
1554
+ finish({ status: 'pass', detail: 'Worker smoke: gateway exited cleanly during observation window.' });
1555
+ } else {
1556
+ const preview = sanitizeOperationalMessage((stderr || stdout || '').trim(), '');
1557
+ finish({
1558
+ status: 'fail',
1559
+ detail: preview
1560
+ ? `Worker smoke: gateway crashed (exit ${code}): ${preview}`
1561
+ : `Worker smoke: gateway crashed (exit ${code}) with no stderr. Run \`genexus-mcp doctor --mcp-smoke\` for details.`
1562
+ });
1563
+ }
1564
+ });
1565
+
1566
+ setTimeout(() => {
1567
+ try { child.kill(); } catch { }
1568
+ // Still alive after observeMs → worker bootstrapped without crashing.
1569
+ finish({ status: 'pass', detail: `Worker smoke: gateway stayed alive for ${observeMs}ms with KB and GX configured.` });
1570
+ }, observeMs);
1571
+ });
1114
1572
  }
1115
1573
 
1116
1574
  async function handleWhoami(options, ctx) {
@@ -1403,8 +1861,8 @@ function commandHelpMap() {
1403
1861
  examples: ['genexus-mcp status', 'genexus-mcp status --full --format json']
1404
1862
  },
1405
1863
  doctor: {
1406
- usage: 'genexus-mcp doctor [--full] [--mcp-smoke] [--fields f1,f2] [--limit N] [--format toon|json|text]',
1407
- examples: ['genexus-mcp doctor', 'genexus-mcp doctor --full --mcp-smoke --format json']
1864
+ usage: 'genexus-mcp doctor [--full] [--mcp-smoke] [--dump] [--fields f1,f2] [--limit N] [--format toon|json|text]',
1865
+ examples: ['genexus-mcp doctor', 'genexus-mcp doctor --full --mcp-smoke --format json', 'genexus-mcp doctor --dump # build a support bundle zip']
1408
1866
  },
1409
1867
  tools: {
1410
1868
  usage: 'genexus-mcp tools list [--query text] [--fields f1,f2] [--limit N] [--full] [--format ...]',
package/cli/index.js CHANGED
@@ -43,6 +43,7 @@ const GLOBAL_DEFAULTS = {
43
43
  clients: null,
44
44
  allClients: false,
45
45
  mcpSmoke: false,
46
+ dump: false,
46
47
  noSmoke: false,
47
48
  warm: false,
48
49
  yes: false,
@@ -268,6 +269,9 @@ function parseArgs(argv) {
268
269
  case 'mcp-smoke':
269
270
  result.options.mcpSmoke = true;
270
271
  break;
272
+ case 'dump':
273
+ result.options.dump = true;
274
+ break;
271
275
  case 'no-smoke':
272
276
  result.options.noSmoke = true;
273
277
  break;
package/cli/lib/config.js CHANGED
@@ -134,6 +134,77 @@ function discoverKnowledgeBase(cwd) {
134
134
  return null;
135
135
  }
136
136
 
137
+ // Broader KB discovery: walk up the cwd ancestry, then scan a small set of common
138
+ // roots where developers stash KBs. Returns deduped candidates with a `source` tag
139
+ // so the caller can explain its pick. Bounded so we never recurse into giant trees.
140
+ function discoverKnowledgeBases(cwd, { maxResults = 25, scanDepth = 2 } = {}) {
141
+ const results = [];
142
+ const seen = new Set();
143
+ const push = (dir, source) => {
144
+ if (!dir) return;
145
+ const key = path.resolve(dir).toLowerCase();
146
+ if (seen.has(key)) return;
147
+ if (results.length >= maxResults) return;
148
+ if (directoryLooksLikeKnowledgeBase(dir)) {
149
+ seen.add(key);
150
+ results.push({ path: path.resolve(dir), source });
151
+ }
152
+ };
153
+
154
+ // 1. cwd and ancestors (a developer running `npx init` from a KB subdirectory
155
+ // almost certainly meant that KB).
156
+ if (cwd) {
157
+ let current = path.resolve(cwd);
158
+ let lastParent = null;
159
+ while (current && current !== lastParent && results.length < maxResults) {
160
+ push(current, 'cwd-ancestor');
161
+ lastParent = current;
162
+ current = path.dirname(current);
163
+ if (current === lastParent) break;
164
+ }
165
+ }
166
+
167
+ // 2. Common KB roots — drives + user folders. Scan a shallow depth only.
168
+ const roots = [];
169
+ for (const drive of ['C', 'D', 'E']) {
170
+ roots.push(`${drive}:\\KBs`);
171
+ roots.push(`${drive}:\\KB`);
172
+ roots.push(`${drive}:\\GeneXus`);
173
+ }
174
+ if (process.env.USERPROFILE) {
175
+ roots.push(path.join(process.env.USERPROFILE, 'Documents', 'GeneXus'));
176
+ roots.push(path.join(process.env.USERPROFILE, 'KBs'));
177
+ roots.push(path.join(process.env.USERPROFILE, 'source', 'repos'));
178
+ }
179
+
180
+ const scanRoot = (root, depth) => {
181
+ if (depth < 0) return;
182
+ if (results.length >= maxResults) return;
183
+ let entries;
184
+ try {
185
+ entries = fs.readdirSync(root, { withFileTypes: true });
186
+ } catch {
187
+ return;
188
+ }
189
+ for (const entry of entries) {
190
+ if (results.length >= maxResults) return;
191
+ if (!entry.isDirectory()) continue;
192
+ const full = path.join(root, entry.name);
193
+ push(full, 'common-root');
194
+ if (depth > 0) scanRoot(full, depth - 1);
195
+ }
196
+ };
197
+
198
+ for (const root of roots) {
199
+ try {
200
+ if (fs.existsSync(root)) scanRoot(root, scanDepth);
201
+ } catch {
202
+ }
203
+ }
204
+
205
+ return results;
206
+ }
207
+
137
208
  function directoryLooksLikeKnowledgeBase(dir) {
138
209
  try {
139
210
  const files = fs.readdirSync(dir);
@@ -684,6 +755,7 @@ module.exports = {
684
755
  discoverGeneXusInstallation,
685
756
  discoverGeneXusFromRegistry,
686
757
  discoverKnowledgeBase,
758
+ discoverKnowledgeBases,
687
759
  directoryLooksLikeKnowledgeBase,
688
760
  readJsonFileSafe,
689
761
  resolveConfigPathNoMutate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genexus-mcp",
3
- "version": "2.6.5",
3
+ "version": "2.6.6",
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.5": {
10
+ "GxMcp.Gateway/2.6.6": {
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.5": {
69
+ "GxMcp.Gateway/2.6.6": {
70
70
  "type": "project",
71
71
  "serviceable": false,
72
72
  "sha512": ""
Binary file
Binary file
Binary file
@@ -4,17 +4,17 @@
4
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"]}},
5
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}}}},
6
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."}}}},
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
+ {"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)."},"validate":{"type":"string","enum":["strict","best-effort","only"],"description":"v2.6.6 FR#13. strict (default) aborts on the first op compile error. best-effort applies every op that compiles and reports per-op outcomes under result.opResults[]. only runs ops in-memory and returns diagnostics without persisting."},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}}}},
8
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"]}},
9
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"]}},
10
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"]}},
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
+ {"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>. For build/rebuild, accepts a CSV of objects (comma or semicolon, e.g. 'Foo,Bar,Baz') — compiles only those plus their callees per includeCallees. Use this to act on `suggested_retry.target` from a failed build response."},"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."},"wait":{"type":"integer","description":"Status taskId: 0-300. Event-driven block until baseline (phase/counts/done) changes."},"since":{"type":"string","description":"Status: prior _meta.snapshot for chained waits."},"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"]}},
12
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"]}},
13
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"]}},
14
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"]}},
15
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"}}}},
16
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/."}}}},
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"]}},
17
+ {"name":"genexus_worker_reload","description":"Reload worker. mode=soft (default): drain + clean exit, gateway respawns same binary, JobRegistry preserved. mode=hard: copy sourceDir over publish/worker and respawn (iterating on worker code).","inputSchema":{"type":"object","properties":{"mode":{"type":"string","enum":["soft","hard"]},"sourceDir":{"type":"string","description":"Required for mode=hard."},"drainTimeoutMs":{"type":"integer","description":"Soft only; default 30000."}}}},
18
18
  {"name":"genexus_delete_object","description":"Delete an object from the KB. Irreversible — confirm=true required.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"confirm":{"type":"boolean"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["name","confirm"]}},
19
19
  {"name":"genexus_export_object","description":"Export a GeneXus object part to a text file on disk.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"outputPath":{"type":"string"},"part":{"type":"string"},"type":{"type":"string"},"overwrite":{"type":"boolean"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["name","outputPath"]}},
20
20
  {"name":"genexus_import_object","description":"Import a text file from disk into a GeneXus object part.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"inputPath":{"type":"string"},"part":{"type":"string"},"type":{"type":"string"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["name","inputPath"]}},
@@ -30,14 +30,14 @@
30
30
  {"name":"genexus_format","description":"Format a GeneXus code snippet using worker rules.","inputSchema":{"type":"object","properties":{"code":{"type":"string"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["code"]}},
31
31
  {"name":"genexus_properties","description":"Read or update GeneXus object properties. Note: Description is the title-bar text shown when a WebPanel/Popup is opened via .Popup().","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["get","set"]},"name":{"type":"string"},"control":{"type":"string","description":"Optional. Layout control name (e.g. BtnConfirmar), variable name with & prefix (e.g. &Alu2RegProf), or attribute name."},"propertyName":{"type":"string"},"value":{"type":"string"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["action","name"]}},
32
32
  {"name":"genexus_asset","description":"Find, read, or write binary assets inside the active KB.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["find","read","write"]},"path":{"type":"string"},"includeContent":{"type":"boolean"},"maxBytes":{"type":"integer"},"pattern":{"type":"string"},"relativeRoot":{"type":"string"},"limit":{"type":"integer"},"contentBase64":{"type":"string"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["action"]}},
33
- {"name":"genexus_history","description":"List, read, save, or restore GeneXus object history snapshots.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["list","get_source","save","restore"]},"name":{"type":"string"},"versionId":{"type":"integer"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["action","name"]}},
33
+ {"name":"genexus_history","description":"List, read, save, or restore GeneXus object history snapshots. v2.6.6 (FR#28): restore now supports IDE 'Discard changes' parity — pass discard=true with part=<part> to restore the most recent EditSnapshotStore baseline. Returns {status:NoSnapshot,hint:...} when no baseline exists.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["list","get_source","save","restore"]},"name":{"type":"string"},"versionId":{"type":"integer"},"part":{"type":"string","description":"Part name for snapshot-based restore (default 'Source')."},"snapshot":{"type":"string","description":"Snapshot token (timestamp or 'latest') for explicit edit-snapshot restore."},"discard":{"type":"boolean","default":false,"description":"action=restore only. When true and no snapshot is supplied, restore the most recent EditSnapshotStore baseline for (name,part) — IDE History|Restore (Discard changes) parity."},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["action","name"]}},
34
34
  {"name":"genexus_structure","description":"Read or update visual and logical structure of GeneXus objects.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["get_visual","update_visual","get_indexes","get_logic"]},"name":{"type":"string"},"payload":{"type":"object","description":"Update payload for update_visual."},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["action","name"]}},
35
35
  {"name":"genexus_layout","description":"Native SDK layout/WebForm operations: get_tree, set_property, find_controls, inspect_surface, scan_mutators.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["get_tree","set_property","find_controls","set_properties","inspect_surface","get_preview","scan_mutators","rename_printblock","add_printblock"]},"name":{"type":"string"},"target":{"type":"string","description":"Object name alias for backward compatibility."},"control":{"type":"string"},"propertyName":{"type":"string"},"value":{"type":"string"},"query":{"type":"string"},"changes":{"type":"array","items":{"type":"object","properties":{"control":{"type":"string"},"propertyName":{"type":"string"},"value":{"type":"string"}},"required":["control","propertyName","value"]}},"limit":{"type":"integer"},"currentName":{"type":"string"},"newName":{"type":"string"},"printBlockName":{"type":"string"},"height":{"type":"integer"},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}},"required":["action"]}},
36
36
  {"name":"genexus_doc","description":"Generate structured docs (wiki, sequence diagrams, health reports) for an object/domain. Not for programmatic analysis (see genexus_analyze) or source (see genexus_read).","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["wiki","visualize","health"]},"target":{"type":"string","description":"Object or domain name."}},"required":["action"]}},
37
37
  {"name":"genexus_search_source","description":"Regex/semantic search across Procedure/DataProvider/WebPanel/Transaction source.","inputSchema":{"type":"object","properties":{"callee":{"type":"string","description":"Method/function name (qualified or unqualified)."},"argMatches":{"type":"object","description":"Positional arg index to expected literal text."},"pattern":{"type":"string"},"typeFilter":{"type":"string"},"scope":{"type":"array","items":{"type":"string"}},"maxResults":{"type":"integer"},"caseSensitive":{"type":"boolean"},"includeComments":{"type":"boolean"},"inline_read_top":{"type":"integer","description":"0-3. Inline reads of top N distinct objects in response."},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}}}},
38
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"]}},
39
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"]}},
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_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":{"action":{"type":"string","enum":["render","run"],"default":"render","description":"v2.6.6 Stream H (FR#25): 'run' resolves the KB launcher object automatically when name is omitted (IDE F5 parity). 'render' (default) requires explicit name."},"name":{"type":"string","description":"Target WebPanel name. Optional when action=run."},"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."},"fill":{"type":"object","description":"GX-aware form fills (logical attr names → values)."},"click":{"type":"string","description":"GX-aware click target (button/link logical name)."},"auth":{"type":"object","description":"GAM auth override (mode/user/pass)."},"kb":{"type":"string","description":"Target KB. Required when 2+ open."}}}},
41
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."}}}},
42
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"]}},
43
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."}}}}
Binary file
Binary file