genexus-mcp 3.8.0 → 3.9.1
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 +65 -0
- package/cli/index.js +19 -1
- package/cli/lib/client-adapters.test.js +1 -0
- package/cli/lib/config.js +11 -0
- package/cli/lib/runtime-stager.js +645 -0
- package/cli/lib/runtime-stager.test.js +507 -0
- package/cli/run.test.js +18 -3
- package/docs/llm_cli_mcp_playbook.md +28 -0
- package/package.json +2 -2
- package/publish/GxMcp.Gateway.deps.json +2 -2
- package/publish/GxMcp.Gateway.dll +0 -0
- package/publish/GxMcp.Gateway.exe +0 -0
- package/publish/config.json +13 -13
- package/publish/gxmcp-manifest.json +15 -10
- package/publish/gxmcp-sbom.json +6 -6
- package/publish/nexus-ide.vsix +0 -0
- package/publish/tool_definitions.json +56 -56
- package/publish/worker/GxMcp.Worker.exe +0 -0
package/cli/commands/axi.js
CHANGED
|
@@ -813,6 +813,70 @@ function buildLegacyIdeLockCheck(gxMajor) {
|
|
|
813
813
|
return { id: 'legacy_ide_lock', status: 'pass', detail: 'No conflicting GeneXus IDE process (gx.exe) detected.' };
|
|
814
814
|
}
|
|
815
815
|
|
|
816
|
+
function buildRuntimeStagingCheck() {
|
|
817
|
+
try {
|
|
818
|
+
const {
|
|
819
|
+
resolveDefaultRuntimeRoot,
|
|
820
|
+
listStagedRuntimes,
|
|
821
|
+
probeRunningGxMcpProcesses,
|
|
822
|
+
getRuntimeProcessUsage,
|
|
823
|
+
classifyProcessRuntime,
|
|
824
|
+
shouldStage
|
|
825
|
+
} = require('../lib/runtime-stager');
|
|
826
|
+
|
|
827
|
+
const runtimeRoot = resolveDefaultRuntimeRoot();
|
|
828
|
+
const stagedList = listStagedRuntimes(runtimeRoot);
|
|
829
|
+
const processProbe = probeRunningGxMcpProcesses();
|
|
830
|
+
const runningProcs = processProbe.processes || [];
|
|
831
|
+
if (processProbe.ok === false) {
|
|
832
|
+
return {
|
|
833
|
+
id: 'runtime_staging',
|
|
834
|
+
status: 'warn',
|
|
835
|
+
detail: `Runtime process inspection was incomplete; GC is fail-closed. ${processProbe.error || 'The process probe did not return executable paths.'}`
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
const npxProcs = runningProcs.filter((p) => classifyProcessRuntime(p.exePath, runtimeRoot) === 'npx-cache');
|
|
839
|
+
const stagedProcs = runningProcs.filter((p) => classifyProcessRuntime(p.exePath, runtimeRoot) === 'staged');
|
|
840
|
+
const inUseRuntimes = getRuntimeProcessUsage(runtimeRoot, runningProcs);
|
|
841
|
+
const inUseDetail = inUseRuntimes.length > 0
|
|
842
|
+
? ` Retained in-use runtime(s): ${inUseRuntimes.map((entry) => `${entry.name} (PIDs: ${entry.pids.join(', ')})`).join('; ')}.`
|
|
843
|
+
: '';
|
|
844
|
+
|
|
845
|
+
if (npxProcs.length > 0) {
|
|
846
|
+
return {
|
|
847
|
+
id: 'runtime_staging',
|
|
848
|
+
status: 'warn',
|
|
849
|
+
detail: `${npxProcs.length} process(es) running directly from npx cache (PIDs: ${npxProcs.map((p) => p.pid).join(', ')}). Terminate them to prevent EBUSY during upgrades.${inUseDetail}`
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
if (shouldStage()) {
|
|
853
|
+
return {
|
|
854
|
+
id: 'runtime_staging',
|
|
855
|
+
status: 'pass',
|
|
856
|
+
detail: `Staging active at ${runtimeRoot}. ${stagedList.length} staged version(s) found, ${stagedProcs.length} active process(es).${inUseDetail}`
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
if (process.env.GENEXUS_MCP_GATEWAY_EXE) {
|
|
860
|
+
return {
|
|
861
|
+
id: 'runtime_staging',
|
|
862
|
+
status: 'pass',
|
|
863
|
+
detail: `Fixed-path runtime override active via GENEXUS_MCP_GATEWAY_EXE (${process.env.GENEXUS_MCP_GATEWAY_EXE}). Staging bypassed.${inUseDetail}`
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
return {
|
|
867
|
+
id: 'runtime_staging',
|
|
868
|
+
status: 'pass',
|
|
869
|
+
detail: `Development checkout active. Runtime staging bypassed.${inUseDetail}`
|
|
870
|
+
};
|
|
871
|
+
} catch {
|
|
872
|
+
return {
|
|
873
|
+
id: 'runtime_staging',
|
|
874
|
+
status: 'not_applicable',
|
|
875
|
+
detail: 'Runtime staging inspection unavailable.'
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
816
880
|
function redactConfig(cfg) {
|
|
817
881
|
// Replace absolute paths with `<redacted:hash8>` so the structure is preserved
|
|
818
882
|
// but filesystem layout, usernames, and KB names are not leaked. Hash is stable
|
|
@@ -1124,6 +1188,7 @@ async function handleDoctor(options, ctx) {
|
|
|
1124
1188
|
checks.push({ id: 'gxpublic_com_registration', status: comCheck.status, detail: comCheck.detail });
|
|
1125
1189
|
const ideLockCheck = buildLegacyIdeLockCheck(gxMajor);
|
|
1126
1190
|
checks.push({ id: 'legacy_ide_lock', status: ideLockCheck.status, detail: ideLockCheck.detail });
|
|
1191
|
+
checks.push(buildRuntimeStagingCheck());
|
|
1127
1192
|
|
|
1128
1193
|
// Client registration summary — one line answering "are my AI agents wired up?".
|
|
1129
1194
|
const installedRows = clientRows.filter((r) => r.installed);
|
package/cli/index.js
CHANGED
|
@@ -450,7 +450,25 @@ async function launchGateway(passthroughArgs, options) {
|
|
|
450
450
|
return EXIT_CODES.ERROR;
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
-
|
|
453
|
+
let gatewayExePath = getGatewayExePath();
|
|
454
|
+
try {
|
|
455
|
+
const { ensureStagedGateway } = require('./lib/runtime-stager');
|
|
456
|
+
const staged = ensureStagedGateway();
|
|
457
|
+
gatewayExePath = staged.gatewayExePath;
|
|
458
|
+
const cleanup = staged.cleanup;
|
|
459
|
+
if (cleanup && (cleanup.skipped?.length || cleanup.failed?.length || cleanup.processProbeAvailable === false)) {
|
|
460
|
+
const summary = [
|
|
461
|
+
cleanup.processProbeAvailable === false ? 'process probe unavailable' : null,
|
|
462
|
+
cleanup.skipped?.length ? `${cleanup.skipped.length} skipped` : null,
|
|
463
|
+
cleanup.failed?.length ? `${cleanup.failed.length} failed` : null
|
|
464
|
+
].filter(Boolean).join(', ');
|
|
465
|
+
if (!options.quiet) launcherStderr.write(`[genexus-mcp] Runtime cleanup incomplete: ${summary}.\n`);
|
|
466
|
+
}
|
|
467
|
+
} catch (stageErr) {
|
|
468
|
+
if (!options.quiet) {
|
|
469
|
+
launcherStderr.write(`[genexus-mcp] Warning: Runtime staging failed (${stageErr.message}), falling back to direct binary.\n`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
454
472
|
if (!require('fs').existsSync(gatewayExePath)) {
|
|
455
473
|
const message = `[genexus-mcp] ERROR: Gateway executable not found at ${gatewayExePath}`;
|
|
456
474
|
launcherStderr.write(`${message}\n`);
|
|
@@ -151,6 +151,7 @@ test('generateNeutralConfig produces valid shape without KB fields', () => {
|
|
|
151
151
|
assert.equal(cfg.GeneXus.InstallationPath, 'C:\\GeneXus18');
|
|
152
152
|
assert.ok(cfg.GeneXus.WorkerExecutable.endsWith(path.join('worker', 'GxMcp.Worker.exe')));
|
|
153
153
|
assert.equal(cfg.Server.McpStdio, true);
|
|
154
|
+
assert.equal(cfg.Server.ToolProfile, 'standard');
|
|
154
155
|
assert.equal(cfg.Server.TransportMode, undefined);
|
|
155
156
|
assert.equal(cfg.Server.HttpPort, 0);
|
|
156
157
|
assert.equal(cfg.Environment.ResolutionPolicy, 'strict');
|
package/cli/lib/config.js
CHANGED
|
@@ -8,6 +8,7 @@ function generateConfig(gxPath, kbPath) {
|
|
|
8
8
|
Server: {
|
|
9
9
|
HttpPort: 5000,
|
|
10
10
|
McpStdio: true,
|
|
11
|
+
ToolProfile: 'standard',
|
|
11
12
|
SessionIdleTimeoutMinutes: 10,
|
|
12
13
|
WorkerIdleTimeoutMinutes: 5,
|
|
13
14
|
// Lean + terse defaults: the MCP client is an LLM agent that reads
|
|
@@ -33,6 +34,7 @@ function generateNeutralConfig(gxPath, { workerPath, gatewayMode = 'stdio-isolat
|
|
|
33
34
|
Server: {
|
|
34
35
|
HttpPort: 0,
|
|
35
36
|
McpStdio: true,
|
|
37
|
+
ToolProfile: 'standard',
|
|
36
38
|
WorkerSharingMode: 'isolated',
|
|
37
39
|
SessionIdleTimeoutMinutes: 10,
|
|
38
40
|
WorkerIdleTimeoutMinutes: 5,
|
|
@@ -657,9 +659,18 @@ function createConfigFile(kbPath, gxPath) {
|
|
|
657
659
|
}
|
|
658
660
|
const nextConfig = {
|
|
659
661
|
...baseConfig,
|
|
662
|
+
Server: { ...baseConfig.Server },
|
|
660
663
|
Environment: { ...baseConfig.Environment, ...preservedEnv }
|
|
661
664
|
};
|
|
662
665
|
|
|
666
|
+
if (existing) {
|
|
667
|
+
if (existing.Server && Object.prototype.hasOwnProperty.call(existing.Server, 'ToolProfile')) {
|
|
668
|
+
nextConfig.Server.ToolProfile = existing.Server.ToolProfile;
|
|
669
|
+
} else {
|
|
670
|
+
delete nextConfig.Server.ToolProfile;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
663
674
|
const changed = !existing || JSON.stringify(existing) !== JSON.stringify(nextConfig);
|
|
664
675
|
if (changed) {
|
|
665
676
|
writeFileAtomic(targetConfigPath, JSON.stringify(nextConfig, null, 2));
|