genexus-mcp 2.6.5 → 2.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/commands/axi.js +478 -20
- package/cli/index.js +4 -0
- package/cli/lib/config.js +96 -1
- package/cli/run.test.js +28 -0
- package/package.json +1 -1
- package/publish/GxMcp.Gateway.deps.json +2 -2
- package/publish/GxMcp.Gateway.dll +0 -0
- package/publish/GxMcp.Gateway.exe +0 -0
- package/publish/GxMcp.Gateway.pdb +0 -0
- package/publish/tool_definitions.json +41 -41
- package/publish/worker/GxMcp.Worker.exe +0 -0
- package/publish/worker/GxMcp.Worker.pdb +0 -0
package/cli/commands/axi.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
459
|
-
|
|
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.') },
|
|
693
|
+
{ id: 'tool_definitions', status: toolDefsExists ? 'pass' : 'warn', detail: toolDefsExists ? `Tool definition file found (${toolCount} tools) at ${toolDefPath}.` : (process.env.GENEXUS_MCP_TOOL_DEFINITIONS ? `tool_definitions.json missing at GENEXUS_MCP_TOOL_DEFINITIONS=${toolDefPath}. Unset the env var or point it at a valid file.` : `tool_definitions.json missing. Expected at ${toolDefPath} (next to the gateway exe). The csproj should copy it on publish — reinstall via scripts/install.ps1, or set GENEXUS_MCP_TOOL_DEFINITIONS to override.`) },
|
|
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
|
-
|
|
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:
|
|
989
|
-
`Cannot resolve required paths: ${missing.join('; ')}. Pass flags explicitly or run from inside a KB folder
|
|
990
|
-
|
|
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:
|
|
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
|
|
1035
|
-
|
|
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:
|
|
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
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
-
|
|
1113
|
-
|
|
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
|
@@ -18,7 +18,30 @@ function getGatewayExePath() {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function getToolDefinitionsPath() {
|
|
21
|
-
|
|
21
|
+
// The gateway loads tool_definitions.json from its own exe directory at
|
|
22
|
+
// runtime (see GxMcp.Gateway/McpRouter.cs). The packaged distribution
|
|
23
|
+
// places the file alongside publish/GxMcp.Gateway.exe via the csproj's
|
|
24
|
+
// <Content CopyToPublishDirectory="Always" /> rule. Prior versions hardcoded
|
|
25
|
+
// the dev-tree path, so `genexus-mcp doctor` reported "tool_definitions.json
|
|
26
|
+
// is missing" on every installed copy even though the file was present next
|
|
27
|
+
// to the exe.
|
|
28
|
+
if (process.env.GENEXUS_MCP_TOOL_DEFINITIONS) {
|
|
29
|
+
return process.env.GENEXUS_MCP_TOOL_DEFINITIONS;
|
|
30
|
+
}
|
|
31
|
+
const candidates = [
|
|
32
|
+
// 1. Sibling of the gateway exe (packaged install — the path the gateway itself uses).
|
|
33
|
+
path.join(path.dirname(getGatewayExePath()), 'tool_definitions.json'),
|
|
34
|
+
// 2. Dev-tree source (when running from a git checkout).
|
|
35
|
+
path.join(__dirname, '..', '..', 'src', 'GxMcp.Gateway', 'tool_definitions.json'),
|
|
36
|
+
// 3. Fallback alongside the CLI itself (defensive — for unusual layouts).
|
|
37
|
+
path.join(__dirname, '..', '..', 'publish', 'tool_definitions.json')
|
|
38
|
+
];
|
|
39
|
+
for (const candidate of candidates) {
|
|
40
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
41
|
+
}
|
|
42
|
+
// Nothing found — return the canonical packaged path so the doctor's
|
|
43
|
+
// existence check reports the location that SHOULD have the file.
|
|
44
|
+
return candidates[0];
|
|
22
45
|
}
|
|
23
46
|
|
|
24
47
|
function discoverGeneXusFromRegistry() {
|
|
@@ -134,6 +157,77 @@ function discoverKnowledgeBase(cwd) {
|
|
|
134
157
|
return null;
|
|
135
158
|
}
|
|
136
159
|
|
|
160
|
+
// Broader KB discovery: walk up the cwd ancestry, then scan a small set of common
|
|
161
|
+
// roots where developers stash KBs. Returns deduped candidates with a `source` tag
|
|
162
|
+
// so the caller can explain its pick. Bounded so we never recurse into giant trees.
|
|
163
|
+
function discoverKnowledgeBases(cwd, { maxResults = 25, scanDepth = 2 } = {}) {
|
|
164
|
+
const results = [];
|
|
165
|
+
const seen = new Set();
|
|
166
|
+
const push = (dir, source) => {
|
|
167
|
+
if (!dir) return;
|
|
168
|
+
const key = path.resolve(dir).toLowerCase();
|
|
169
|
+
if (seen.has(key)) return;
|
|
170
|
+
if (results.length >= maxResults) return;
|
|
171
|
+
if (directoryLooksLikeKnowledgeBase(dir)) {
|
|
172
|
+
seen.add(key);
|
|
173
|
+
results.push({ path: path.resolve(dir), source });
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// 1. cwd and ancestors (a developer running `npx init` from a KB subdirectory
|
|
178
|
+
// almost certainly meant that KB).
|
|
179
|
+
if (cwd) {
|
|
180
|
+
let current = path.resolve(cwd);
|
|
181
|
+
let lastParent = null;
|
|
182
|
+
while (current && current !== lastParent && results.length < maxResults) {
|
|
183
|
+
push(current, 'cwd-ancestor');
|
|
184
|
+
lastParent = current;
|
|
185
|
+
current = path.dirname(current);
|
|
186
|
+
if (current === lastParent) break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 2. Common KB roots — drives + user folders. Scan a shallow depth only.
|
|
191
|
+
const roots = [];
|
|
192
|
+
for (const drive of ['C', 'D', 'E']) {
|
|
193
|
+
roots.push(`${drive}:\\KBs`);
|
|
194
|
+
roots.push(`${drive}:\\KB`);
|
|
195
|
+
roots.push(`${drive}:\\GeneXus`);
|
|
196
|
+
}
|
|
197
|
+
if (process.env.USERPROFILE) {
|
|
198
|
+
roots.push(path.join(process.env.USERPROFILE, 'Documents', 'GeneXus'));
|
|
199
|
+
roots.push(path.join(process.env.USERPROFILE, 'KBs'));
|
|
200
|
+
roots.push(path.join(process.env.USERPROFILE, 'source', 'repos'));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const scanRoot = (root, depth) => {
|
|
204
|
+
if (depth < 0) return;
|
|
205
|
+
if (results.length >= maxResults) return;
|
|
206
|
+
let entries;
|
|
207
|
+
try {
|
|
208
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
209
|
+
} catch {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
for (const entry of entries) {
|
|
213
|
+
if (results.length >= maxResults) return;
|
|
214
|
+
if (!entry.isDirectory()) continue;
|
|
215
|
+
const full = path.join(root, entry.name);
|
|
216
|
+
push(full, 'common-root');
|
|
217
|
+
if (depth > 0) scanRoot(full, depth - 1);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
for (const root of roots) {
|
|
222
|
+
try {
|
|
223
|
+
if (fs.existsSync(root)) scanRoot(root, scanDepth);
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return results;
|
|
229
|
+
}
|
|
230
|
+
|
|
137
231
|
function directoryLooksLikeKnowledgeBase(dir) {
|
|
138
232
|
try {
|
|
139
233
|
const files = fs.readdirSync(dir);
|
|
@@ -684,6 +778,7 @@ module.exports = {
|
|
|
684
778
|
discoverGeneXusInstallation,
|
|
685
779
|
discoverGeneXusFromRegistry,
|
|
686
780
|
discoverKnowledgeBase,
|
|
781
|
+
discoverKnowledgeBases,
|
|
687
782
|
directoryLooksLikeKnowledgeBase,
|
|
688
783
|
readJsonFileSafe,
|
|
689
784
|
resolveConfigPathNoMutate,
|
package/cli/run.test.js
CHANGED
|
@@ -523,6 +523,34 @@ test('--fields validation returns usage error for invalid doctor field', () => {
|
|
|
523
523
|
assert.equal(parsed.error.code, 'usage_error');
|
|
524
524
|
});
|
|
525
525
|
|
|
526
|
+
test('doctor finds tool_definitions.json next to the gateway exe (not just dev-tree)', () => {
|
|
527
|
+
// Regression for v2.6.6 bug: getToolDefinitionsPath() hard-coded the dev-tree
|
|
528
|
+
// location, so every installed copy reported "tool_definitions.json is missing"
|
|
529
|
+
// even though the file was published alongside GxMcp.Gateway.exe.
|
|
530
|
+
const result = runCli(['doctor', '--format', 'json']);
|
|
531
|
+
assert.equal(result.status, 0);
|
|
532
|
+
|
|
533
|
+
const parsed = JSON.parse(result.stdout);
|
|
534
|
+
const check = parsed.ok.checks.find((c) => c.id === 'tool_definitions');
|
|
535
|
+
assert.ok(check, 'tool_definitions check must be present');
|
|
536
|
+
assert.equal(check.status, 'pass', `expected pass, got '${check.status}': ${check.detail}`);
|
|
537
|
+
assert.match(check.detail, /Tool definition file found \(\d+ tools\) at .+/);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test('doctor honours GENEXUS_MCP_TOOL_DEFINITIONS override and reports it on miss', () => {
|
|
541
|
+
const bogusPath = path.join(os.tmpdir(), 'nonexistent-tool-defs-' + Date.now() + '.json');
|
|
542
|
+
const result = runCli(['doctor', '--format', 'json'], { env: { GENEXUS_MCP_TOOL_DEFINITIONS: bogusPath } });
|
|
543
|
+
assert.equal(result.status, 0);
|
|
544
|
+
|
|
545
|
+
const parsed = JSON.parse(result.stdout);
|
|
546
|
+
const check = parsed.ok.checks.find((c) => c.id === 'tool_definitions');
|
|
547
|
+
assert.ok(check);
|
|
548
|
+
assert.equal(check.status, 'warn');
|
|
549
|
+
assert.match(check.detail, /GENEXUS_MCP_TOOL_DEFINITIONS=/);
|
|
550
|
+
assert.ok(check.detail.includes(bogusPath) || check.detail.includes(bogusPath.replace(/\\/g, '/')),
|
|
551
|
+
`detail should mention the bogus override path; got: ${check.detail}`);
|
|
552
|
+
});
|
|
553
|
+
|
|
526
554
|
test('doctor --mcp-smoke adds explicit mcp_smoke check', () => {
|
|
527
555
|
const result = runCli(['doctor', '--mcp-smoke', '--format', 'json']);
|
|
528
556
|
assert.equal(result.status, 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genexus-mcp",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.7",
|
|
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.
|
|
10
|
+
"GxMcp.Gateway/2.6.7": {
|
|
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.
|
|
69
|
+
"GxMcp.Gateway/2.6.7": {
|
|
70
70
|
"type": "project",
|
|
71
71
|
"serviceable": false,
|
|
72
72
|
"sha512": ""
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
[
|
|
2
|
-
{"name":"genexus_whoami","description":"
|
|
3
|
-
{"name":"genexus_recipe","description":"
|
|
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
|
|
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:
|
|
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":"
|
|
7
|
-
{"name":"genexus_edit","description":"Edit
|
|
8
|
-
{"name":"genexus_inspect","description":"
|
|
9
|
-
{"name":"genexus_analyze","description":"
|
|
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":"
|
|
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:<
|
|
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":"
|
|
13
|
-
{"name":"genexus_test","description":"Execute native GeneXus tests (GXtest).","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"kb":{"type":"string","description":"
|
|
14
|
-
{"name":"genexus_create_object","description":"Create any IDE-creatable
|
|
2
|
+
{"name":"genexus_whoami","description":"KB context + version + playbooks. Call FIRST every session — playbooks block routes you to the right tool.","inputSchema":{"type":"object","properties":{}}},
|
|
3
|
+
{"name":"genexus_recipe","description":"Step-by-step playbook for a named flow ('wwp_on_transaction', 'create_popup', etc.). name='list' to enumerate.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Recipe key, or 'list'."}},"required":["name"]}},
|
|
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."},"kb":{"type":"string","description":"KB alias (multi-KB only)."},"axiCompact":{"type":"boolean","description":"Compact projection (default true).","default":true}},"required":["query"]}},
|
|
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: name OR description. Prefer nameFilter/descriptionFilter."},"nameFilter":{"type":"string","description":"Substring on name."},"descriptionFilter":{"type":"string","description":"Substring on description."},"pathPrefix":{"type":"string","description":"Folder prefix, e.g. 'Root Module/X/'."},"limit":{"type":"integer"},"offset":{"type":"integer"},"parent":{"type":"string"},"parentPath":{"type":"string"},"typeFilter":{"type":"string"},"verbose":{"type":"boolean","description":"Full item shape."},"inline_read_top":{"type":"integer","description":"0-3. Inline reads of top N."},"kb":{"type":"string","description":"KB alias (multi-KB only)."},"axiCompact":{"type":"boolean","description":"Compact projection (default true).","default":true}}}},
|
|
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":"KB alias (multi-KB only)."}}}},
|
|
7
|
+
{"name":"genexus_edit","description":"Edit object part. name or targets (exclusive). mode: full|patch|ops. dryRun first. For WWP edit host's PatternInstance NOT the parent WebForm (gets overwritten on reapply).","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":"String or {find,replace} object. Prefer operation+context+content.","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":"Omit post_state.diff (default true)."},"verbose":{"type":"boolean","description":"Add ±15-line slices to post_state."},"validate":{"type":"string","enum":["strict","best-effort","only"],"description":"strict (default) aborts on first error. best-effort applies what compiles. only runs in-memory, no persist."},"kb":{"type":"string","description":"KB alias (multi-KB only)."}}}},
|
|
8
|
+
{"name":"genexus_inspect","description":"Use to snapshot an object: metadata, variables, structure, signature. Raw shape, no source text — use genexus_read for code.","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":"KB alias (multi-KB only)."}},"required":["name"]}},
|
|
9
|
+
{"name":"genexus_analyze","description":"Use this for cross-object semantic analysis: impact, dependencies, complexity, naming, summary. See tool-help 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":"KB alias (multi-KB only)."},"waitForIndex":{"type":"boolean","description":"mode=impact: block up to 30s for Ready index (default true).","default":true}},"required":["name","mode"]}},
|
|
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":"KB alias (multi-KB only)."}},"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:<id>. Build accepts CSV ('Foo,Bar')."},"code":{"type":"string"},"limit":{"type":"integer"},"snapshotPath":{"type":"string"},"estimated_seconds":{"type":"integer","description":"Build: <20 sync, >=20 async (default 60)."},"wait_seconds":{"type":"integer","description":"Status/build long-poll cap, 0-600s."},"wait_until_done":{"type":"boolean","description":"Build/rebuild: block in one turn until terminal (up to wait_seconds, default 600)."},"wait":{"type":"integer","description":"Status taskId event-driven block, 0-600s."},"since":{"type":"string","description":"Status: prior _meta.snapshot for chained waits."},"compact":{"type":"boolean","description":"Status: counts + top-10 errors (default true)."},"force":{"type":"boolean","description":"Index: full SDK rescan (clears snapshot)."},"includeCallees":{"type":"string","enum":["none","direct","transitive"],"description":"Build: expand call graph so callees compile first (default transitive)."},"buildPlanCap":{"type":"integer","description":"Build: max nodes before BuildPlanTooLarge (default 200)."},"skipFullDeploy":{"type":"boolean","description":"EXPERIMENTAL. Single-target Build + includeCallees=none: skip deploy step. DLL is NOT redeployed; see tool-help."},"kb":{"type":"string","description":"KB alias (multi-KB only)."}},"required":["action"]}},
|
|
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":"KB alias (multi-KB only)."}},"required":["action"]}},
|
|
13
|
+
{"name":"genexus_test","description":"Execute native GeneXus tests (GXtest).","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"kb":{"type":"string","description":"KB alias (multi-KB only)."}},"required":["name"]}},
|
|
14
|
+
{"name":"genexus_create_object","description":"Create any IDE-creatable object (Transaction, Procedure, WebPanel, SDPanel, SDT, DataProvider, Domain, Dashboard...). Domain takes dataType+length or enumValues. NOT for WorkWithPlus (use genexus_apply_pattern) or popups with inputs (use genexus_create_popup).","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":"KB alias (multi-KB only)."}},"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
|
-
{"name":"genexus_sdk_probe","description":"
|
|
17
|
-
{"name":"genexus_worker_reload","description":"
|
|
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":"
|
|
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":"
|
|
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":"
|
|
21
|
-
{"name":"genexus_refactor","description":"Run GeneXus refactor: rename, extract procedure, or WWP condition set.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["RenameAttribute","RenameVariable","RenameObject","ExtractProcedure","WWPSetCondition"]},"target":{"type":"string","description":"Primary object or symbol to refactor."},"newName":{"type":"string"},"objectName":{"type":"string"},"code":{"type":"string"},"procedureName":{"type":"string"},"controlAttribute":{"type":"string"},"value":{"type":"string"},"type":{"type":"string"},"kb":{"type":"string","description":"
|
|
22
|
-
{"name":"genexus_add_variable","description":"Add a variable to the Variables part of a GeneXus object.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"varName":{"type":"string"},"typeName":{"type":"string"},"kb":{"type":"string","description":"
|
|
23
|
-
{"name":"genexus_delete_variable","description":"Remove a variable. Idempotent. Refuses GAM/WWP+ framework-managed vars.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"varName":{"type":"string"},"kb":{"type":"string","description":"
|
|
24
|
-
{"name":"genexus_modify_variable","description":"Change
|
|
25
|
-
{"name":"genexus_validate_payload","description":"Pre-flight: parse + SDK schema scan + would-be diff vs current state. No save.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"part":{"type":"string"},"content":{"type":"string"},"kb":{"type":"string","description":"
|
|
26
|
-
{"name":"genexus_bulk_edit","description":"Apply N independent edits. Item: {name,part?,content,type?,dryRun?}. stopOnError halts at first fail.","inputSchema":{"type":"object","properties":{"targets":{"type":"array","items":{"type":"object"}},"stopOnError":{"type":"boolean"},"dryRun":{"type":"boolean"},"kb":{"type":"string","description":"
|
|
27
|
-
{"name":"genexus_apply_template","description":"Apply
|
|
28
|
-
{"name":"genexus_diff","description":"Unified text diff. mode=textVsText|currentVsText.","inputSchema":{"type":"object","properties":{"mode":{"type":"string","enum":["textVsText","currentVsText"]},"name":{"type":"string"},"part":{"type":"string"},"left":{"type":"string"},"right":{"type":"string"},"context":{"type":"integer"},"kb":{"type":"string","description":"
|
|
29
|
-
{"name":"genexus_export_unified","description":"Export an object's full state (all parts) as a single JSON envelope.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"kb":{"type":"string","description":"
|
|
30
|
-
{"name":"genexus_format","description":"Format a GeneXus code snippet using worker rules.","inputSchema":{"type":"object","properties":{"code":{"type":"string"},"kb":{"type":"string","description":"
|
|
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":"
|
|
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":"
|
|
33
|
-
{"name":"genexus_history","description":"List
|
|
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":"
|
|
35
|
-
{"name":"genexus_layout","description":"
|
|
36
|
-
{"name":"genexus_doc","description":"
|
|
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":"
|
|
38
|
-
{"name":"genexus_kb","description":"Manage open KBs: list
|
|
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":"
|
|
40
|
-
{"name":"genexus_preview","description":"Render
|
|
41
|
-
{"name":"genexus_apply_pattern","description":"Apply a
|
|
42
|
-
{"name":"genexus_edit_and_build","description":"Edit
|
|
43
|
-
{"name":"genexus_create_popup","description":"
|
|
16
|
+
{"name":"genexus_sdk_probe","description":"Dump SDK surface (types/methods/props) to docs/sdk-probe/. Use when hunting for entry points.","inputSchema":{"type":"object","properties":{"outputDir":{"type":"string","description":"Absolute path. Default <repo>/docs/sdk-probe/."}}}},
|
|
17
|
+
{"name":"genexus_worker_reload","description":"Reload worker. soft (default): drain + respawn. hard: copy sourceDir + respawn. force=true: gateway kills directly (use when worker is hung).","inputSchema":{"type":"object","properties":{"mode":{"type":"string","enum":["soft","hard"]},"force":{"type":"boolean","description":"Bypass drain; gateway kills + respawns. Use when worker is wedged."},"sourceDir":{"type":"string","description":"Required for mode=hard."},"drainTimeoutMs":{"type":"integer","description":"Soft only; default 30000."}}}},
|
|
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":"KB alias (multi-KB only)."}},"required":["name","confirm"]}},
|
|
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":"KB alias (multi-KB only)."}},"required":["name","outputPath"]}},
|
|
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":"KB alias (multi-KB only)."}},"required":["name","inputPath"]}},
|
|
21
|
+
{"name":"genexus_refactor","description":"Run GeneXus refactor: rename, extract procedure, or WWP condition set.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["RenameAttribute","RenameVariable","RenameObject","ExtractProcedure","WWPSetCondition"]},"target":{"type":"string","description":"Primary object or symbol to refactor."},"newName":{"type":"string"},"objectName":{"type":"string"},"code":{"type":"string"},"procedureName":{"type":"string"},"controlAttribute":{"type":"string"},"value":{"type":"string"},"type":{"type":"string"},"kb":{"type":"string","description":"KB alias (multi-KB only)."}},"required":["action"]}},
|
|
22
|
+
{"name":"genexus_add_variable","description":"Add a variable to the Variables part of a GeneXus object.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"varName":{"type":"string"},"typeName":{"type":"string"},"kb":{"type":"string","description":"KB alias (multi-KB only)."}},"required":["name","varName"]}},
|
|
23
|
+
{"name":"genexus_delete_variable","description":"Remove a variable. Idempotent. Refuses GAM/WWP+ framework-managed vars.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"varName":{"type":"string"},"kb":{"type":"string","description":"KB alias (multi-KB only)."}},"required":["name","varName"]}},
|
|
24
|
+
{"name":"genexus_modify_variable","description":"Change variable type atomically. Preserves name + description.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"varName":{"type":"string"},"typeName":{"type":"string"},"basedOn":{"type":"string","description":"Domain (optional)."},"kb":{"type":"string","description":"KB alias (multi-KB only)."}},"required":["name","varName","typeName"]}},
|
|
25
|
+
{"name":"genexus_validate_payload","description":"Pre-flight: parse + SDK schema scan + would-be diff vs current state. No save.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"part":{"type":"string"},"content":{"type":"string"},"kb":{"type":"string","description":"KB alias (multi-KB only)."}},"required":["name","content"]}},
|
|
26
|
+
{"name":"genexus_bulk_edit","description":"Apply N independent edits. Item: {name,part?,content,type?,dryRun?}. stopOnError halts at first fail.","inputSchema":{"type":"object","properties":{"targets":{"type":"array","items":{"type":"object"}},"stopOnError":{"type":"boolean"},"dryRun":{"type":"boolean"},"kb":{"type":"string","description":"KB alias (multi-KB only)."}},"required":["targets"]}},
|
|
27
|
+
{"name":"genexus_apply_template","description":"Apply visual template: kpi_header | empty_state | confirm_dialog. See tool-help for args per template.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"template":{"type":"string","enum":["kpi_header","empty_state","confirm_dialog"]},"part":{"type":"string"},"args":{"type":"object"},"dryRun":{"type":"boolean"},"kb":{"type":"string","description":"KB alias (multi-KB only)."}},"required":["name","template"]}},
|
|
28
|
+
{"name":"genexus_diff","description":"Unified text diff. mode=textVsText|currentVsText.","inputSchema":{"type":"object","properties":{"mode":{"type":"string","enum":["textVsText","currentVsText"]},"name":{"type":"string"},"part":{"type":"string"},"left":{"type":"string"},"right":{"type":"string"},"context":{"type":"integer"},"kb":{"type":"string","description":"KB alias (multi-KB only)."}}}},
|
|
29
|
+
{"name":"genexus_export_unified","description":"Export an object's full state (all parts) as a single JSON envelope.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"kb":{"type":"string","description":"KB alias (multi-KB only)."}},"required":["name"]}},
|
|
30
|
+
{"name":"genexus_format","description":"Format a GeneXus code snippet using worker rules.","inputSchema":{"type":"object","properties":{"code":{"type":"string"},"kb":{"type":"string","description":"KB alias (multi-KB only)."}},"required":["code"]}},
|
|
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":"KB alias (multi-KB only)."}},"required":["action","name"]}},
|
|
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":"KB alias (multi-KB only)."}},"required":["action"]}},
|
|
33
|
+
{"name":"genexus_history","description":"List/read/save/restore object history snapshots. restore + discard=true = IDE 'Discard changes' parity.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["list","get_source","save","restore"]},"name":{"type":"string"},"versionId":{"type":"integer"},"part":{"type":"string","description":"Default 'Source'."},"snapshot":{"type":"string","description":"Snapshot token (timestamp or 'latest')."},"discard":{"type":"boolean","default":false,"description":"restore-only: drop in-memory edits, restore last persisted baseline for (name,part)."},"kb":{"type":"string","description":"KB alias (multi-KB only)."}},"required":["action","name"]}},
|
|
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":"KB alias (multi-KB only)."}},"required":["action","name"]}},
|
|
35
|
+
{"name":"genexus_layout","description":"SDK layout/WebForm ops: 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":"Alias for name (backcompat)."},"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":"KB alias (multi-KB only)."}},"required":["action"]}},
|
|
36
|
+
{"name":"genexus_doc","description":"Use to generate structured docs (wiki, sequence diagrams, health reports). Don't use 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
|
+
{"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":"KB alias (multi-KB only)."}}}},
|
|
38
|
+
{"name":"genexus_kb","description":"Manage open KBs: list / open / close / set_default.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["list","open","close","set_default"]},"alias":{"type":"string","description":"KB alias. open: auto from path basename if omitted."},"path":{"type":"string","description":"Absolute KB path (required for open if alias not in config)."}},"required":["action"]}},
|
|
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":"KB alias (multi-KB only)."}},"required":["action","name"]}},
|
|
40
|
+
{"name":"genexus_preview","description":"Render WebPanel via headless Chrome. Opens launcher, fills parms, navigates, captures HTML/a11y/screenshot, optionally diffs vs baseline.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["render","run"],"default":"render","description":"run = auto-resolve launcher (IDE F5 parity). render = explicit name."},"name":{"type":"string","description":"WebPanel name. Optional for action=run."},"parms":{"type":"object","description":"Per-call launcher parms."},"launcher":{"type":"string","description":"Launcher page (default 'auto')."},"buildFirst":{"type":"boolean","default":false,"description":"Build before launch."},"waitMs":{"type":"integer","default":3000,"description":"Wait ms after nav."},"capture":{"type":"array","items":{"type":"string","enum":["html","a11y","screenshot","console"]},"description":"Default ['html','a11y']."},"diffBaseline":{"type":"boolean","default":false,"description":"Structural diff vs stored baseline."},"updateBaseline":{"type":"boolean","default":false,"description":"Persist current as new baseline."},"fill":{"type":"object","description":"GX-aware form fills."},"click":{"type":"string","description":"GX-aware click target."},"auth":{"type":"object","description":"GAM override (mode/user/pass)."},"kb":{"type":"string","description":"KB alias (multi-KB only)."}}}},
|
|
41
|
+
{"name":"genexus_apply_pattern","description":"Apply a pattern (e.g. WorkWithPlus) to a parent. IDE 'Right-click → Apply Pattern'. Inspect parentType first: Transaction=family-gen, WebPanel/SDPanel=direct-attach (pass settings.template). Others rejected.","inputSchema":{"type":"object","required":["name","pattern"],"properties":{"name":{"type":"string","description":"Target KBObject name."},"pattern":{"type":"string","description":"Pattern key ('WorkWithPlus') or GUID."},"settings":{"type":"object","description":"Pattern-instance settings tree."},"reapply":{"type":"boolean","default":false,"description":"Re-run on existing instance."},"validate":{"type":"boolean","default":false,"description":"After apply, build the generated host. Adds validation block (60-180s). Catches binding errors."},"kb":{"type":"string","description":"KB alias (multi-KB only)."}}}},
|
|
42
|
+
{"name":"genexus_edit_and_build","description":"Edit + rebuild callers in one call. Returns edit diff + impact + build operationId.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Target object name."},"part":{"type":"string","description":"Part (Source, Rules, ...)."},"content":{"type":"string","description":"New content. For mode=patch may be {find,replace} object."},"patch":{"description":"Same shape as genexus_edit.patch. Auto-sets mode=patch.","anyOf":[{"type":"string"},{"type":"object","properties":{"find":{"type":"string"},"replace":{"type":"string"}},"required":["find","replace"]}]},"mode":{"type":"string","enum":["full","patch"],"default":"patch"},"type":{"type":"string","description":"Disambiguates ambiguous names."},"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"]}},
|
|
43
|
+
{"name":"genexus_create_popup","description":"Create popup WebPanel with Form type=\"layout\" body (radio/combo render editable). Spec: title, inputs, buttons, inParms, outParms.","inputSchema":{"type":"object","required":["name","spec"],"properties":{"name":{"type":"string","description":"WebPanel name (created or updated)."},"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. \"X == 'S'\"; emits Refresh 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. [\"X:Numeric(2)\"]"},"outParms":{"type":"array","items":{"type":"string"}}}},"kb":{"type":"string","description":"KB alias (multi-KB only)."}}}}
|
|
44
44
|
]
|
|
Binary file
|
|
Binary file
|