genexus-mcp 3.5.3 → 3.6.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/README.md +37 -17
- package/cli/commands/axi.js +128 -22
- package/cli/lib/client-adapters.js +8 -2
- package/cli/lib/config.js +163 -36
- package/cli/run.test.js +264 -12
- package/config/gx-versions.json +57 -0
- package/docs/llm_cli_mcp_playbook.md +23 -0
- package/package.json +3 -3
- 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/gx-versions.json +57 -0
- package/publish/config.json +8 -8
- package/publish/gxmcp-manifest.json +10 -10
- package/publish/gxmcp-sbom.json +4 -4
- package/publish/tool_definitions.json +12 -12
- package/publish/worker/GxMcp.Worker.exe +0 -0
- package/publish/worker/config/gx-versions.json +57 -0
- package/publish/worker/gx-versions.json +57 -0
- package/publish/worker/sdk-compatibility.json +1 -1
- package/scripts/verify-install.js +2 -1
package/README.md
CHANGED
|
@@ -12,19 +12,21 @@
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
**GeneXus MCP Server** lets AI agents — Claude Desktop, Claude Code, Cursor, Antigravity, and any MCP-compatible client — read, edit, analyze, and refactor objects inside a Knowledge Base supported by the selected
|
|
15
|
+
**GeneXus MCP Server** lets AI agents — Claude Desktop, Claude Code, Cursor, Antigravity, and any MCP-compatible client — read, edit, analyze, and refactor objects inside a Knowledge Base supported by the selected native SDK or legacy compatibility driver. Native SDK paths work with the **real GeneXus SDK** and legacy paths use explicit reflection/COM adapters; neither path relies on a parsed copy of the KB.
|
|
16
16
|
|
|
17
17
|
In practice: you point the MCP at your KB, then ask your AI assistant things like *"list all transactions with attribute CustomerId"*, *"add a rule to the Order transaction that validates the total"*, or *"refactor this procedure to use the new SDT"* — and it does it.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
-
## Multi-version
|
|
21
|
+
## Multi-version GeneXus support
|
|
22
22
|
|
|
23
|
-
The same MCP distribution supports the SDK majors listed in the
|
|
24
|
-
compatibility document.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
The same MCP distribution supports the official native SDK majors listed in the
|
|
24
|
+
generated compatibility document. It also includes basic, best-effort
|
|
25
|
+
compatibility for the legacy versions listed there through separate drivers;
|
|
26
|
+
that path is not equivalent to full native-SDK support. Each configured MCP
|
|
27
|
+
process selects one installed SDK or legacy driver with `--gx`; no separate MCP
|
|
28
|
+
installation is required. The commands below are examples of switching the
|
|
29
|
+
existing configuration, not running two majors in the same process:
|
|
28
30
|
|
|
29
31
|
```bash
|
|
30
32
|
npx genexus-mcp@latest init --kb "C:\KBs\KBTeste17" --gx "C:\Program Files (x86)\GeneXus\GeneXus17Trial"
|
|
@@ -45,7 +47,9 @@ that checks every supported major against one published artifact.
|
|
|
45
47
|
|
|
46
48
|
The Gateway reports the detected SDK through `genexus_whoami`:
|
|
47
49
|
|
|
48
|
-
- `geneXus.supportedMajors`: explicitly validated SDK majors from the version catalog
|
|
50
|
+
- `geneXus.supportedMajors`: explicitly validated native SDK majors from the version catalog
|
|
51
|
+
- `geneXus.legacyMajors`: catalogued legacy majors handled by their compatibility drivers
|
|
52
|
+
- `geneXus.sdkCompatibility.supportLevel`: `native-sdk` or `basic-legacy` for the detected installation
|
|
49
53
|
- `geneXus.matchedMajor`: the major detected for the configured installation
|
|
50
54
|
- `geneXus.versionMatches`: whether the detected installation is in that catalog
|
|
51
55
|
- `geneXus.supportedMajor`: retained as the legacy single-major alias for the catalog primary
|
|
@@ -57,15 +61,28 @@ needed. Existing tool names, arguments, and MCP client configuration formats do
|
|
|
57
61
|
not change.
|
|
58
62
|
|
|
59
63
|
<!-- BEGIN GENERATED: gx-compatibility -->
|
|
60
|
-
Supported SDK majors: **GeneXus 17, GeneXus 18
|
|
64
|
+
Supported SDK majors: **GeneXus 16, GeneXus 17, GeneXus 18** (native SDK).
|
|
65
|
+
Basic legacy compatibility: **GeneXus Evolution 3, GeneXus Evolution 2, GeneXus Evolution 1, GeneXus 15, GeneXus 9.0, GeneXus 8.0** via `com-gxpublic` and `dotnet-reflection` (not the native SDK build).
|
|
61
66
|
Primary SDK: **GeneXus 18**.
|
|
62
67
|
Source of truth: `config/gx-versions.json`.
|
|
63
68
|
<!-- END GENERATED: gx-compatibility -->
|
|
64
69
|
|
|
65
|
-
To add another
|
|
66
|
-
catalog only after compiling the Worker with that SDK and
|
|
67
|
-
tests plus a live KB smoke. This prevents the server from
|
|
68
|
-
based only on a version string.
|
|
70
|
+
To add another native-SDK major in the future, add it to the explicit
|
|
71
|
+
`supportedMajors` catalog only after compiling the Worker with that SDK and
|
|
72
|
+
passing the focused tests plus a live KB smoke. This prevents the server from
|
|
73
|
+
claiming native-SDK compatibility based only on a version string.
|
|
74
|
+
|
|
75
|
+
### Basic legacy compatibility (not native SDK support)
|
|
76
|
+
|
|
77
|
+
Every legacy version currently declared in `legacyMajors` uses a best-effort
|
|
78
|
+
driver rather than the native SDK build:
|
|
79
|
+
- **GeneXus Evolution 1 (10.1), Evolution 2 (10.2), Evolution 3 (10.3), and GeneXus 15**: Driven via runtime reflection (`dotnet-reflection`), dynamically adapting to missing types or structural differences (such as module-less KBs without `QualifiedName`).
|
|
80
|
+
- **GeneXus 8.0 and GeneXus 9.0**: Driven through the classic GXPublic surface (`com-gxpublic`), detected from `gx.exe`/`gxdl32.dll` and classic `.gxi` Knowledge Bases. GXPublic is a metadata-oriented OLE DB/COM surface; this path is intentionally limited to basic metadata/core operations and does not claim native-SDK source/edit parity.
|
|
81
|
+
- **Graceful degradation**: Modern tools that require features introduced in newer GeneXus versions (such as `genexus_api`, `genexus_gam`, or `genexus_module`) return structured `UNSUPPORTED_IN_GENEXUS_VERSION` errors indicating the required minimum version rather than failing ungracefully.
|
|
82
|
+
|
|
83
|
+
This legacy path is intended for basic core operations where implemented; it
|
|
84
|
+
does not claim the same feature parity as the native SDK contract for GeneXus
|
|
85
|
+
16, 17, and 18.
|
|
69
86
|
|
|
70
87
|
---
|
|
71
88
|
|
|
@@ -86,7 +103,10 @@ A quick map of what the agent can do against your real KB through the **50 tools
|
|
|
86
103
|
| 🌿 **Versioning, transfer & teams** | KB model versions/branches, **real XPZ export/import** (dependency-aware), GXserver (Team Development) sync + **CI pipelines**, git-style history, multi-KB parallel work |
|
|
87
104
|
| 🔐 **Security** | GAM / integrated-security provisioning, KB security audit + native Security Scanner |
|
|
88
105
|
|
|
89
|
-
|
|
106
|
+
Native SDK support works through the **native GeneXus SDK** — the same code paths
|
|
107
|
+
the IDE uses — so edits are real and validated, not text hacks on KB files.
|
|
108
|
+
Legacy support uses the reflection or COM driver listed in the catalog and
|
|
109
|
+
degrades unsupported modern tools explicitly.
|
|
90
110
|
|
|
91
111
|
---
|
|
92
112
|
|
|
@@ -95,9 +115,9 @@ It works through the **native GeneXus SDK** — the same code paths the IDE uses
|
|
|
95
115
|
Before you start, make sure you have:
|
|
96
116
|
|
|
97
117
|
- ✅ **Windows** (GeneXus is Windows-only)
|
|
98
|
-
- ✅ **A supported GeneXus
|
|
99
|
-
- ✅ **GeneXus 18** installed locally
|
|
100
|
-
- ✅ **A Knowledge Base created with a supported GeneXus major** and opened at least once in the IDE (so it's initialized)
|
|
118
|
+
- ✅ **A supported GeneXus installation** installed locally: GeneXus 16, 17, or 18 for native SDK support, or a catalogued legacy installation for basic compatibility (see [`docs/generated/supported-versions.md`](docs/generated/supported-versions.md); pass another install path explicitly when needed)
|
|
119
|
+
- ✅ **GeneXus 18** installed locally for the primary native-SDK path; other catalogued native and legacy versions are also supported according to their listed driver
|
|
120
|
+
- ✅ **A Knowledge Base created with a supported native or legacy GeneXus major** and opened at least once in the IDE (so it's initialized)
|
|
101
121
|
- ✅ **Node.js 22+** — check with `node --version` in a terminal; install from [nodejs.org](https://nodejs.org/) if missing
|
|
102
122
|
- ✅ **An MCP-compatible AI client** — [Claude Desktop](https://claude.ai/download), [Claude Code](https://claude.com/claude-code), Cursor, Antigravity, etc.
|
|
103
123
|
|
package/cli/commands/axi.js
CHANGED
|
@@ -59,7 +59,9 @@ function validateClientIds(ids) {
|
|
|
59
59
|
|
|
60
60
|
function isSupportedCatalogMajor(catalog, major) {
|
|
61
61
|
if (!major) return true;
|
|
62
|
-
|
|
62
|
+
const inSupported = catalog.supportedMajors && catalog.supportedMajors.some((entry) => String(entry.major) === String(major));
|
|
63
|
+
if (inSupported) return true;
|
|
64
|
+
return Array.isArray(catalog.legacyMajors) && catalog.legacyMajors.some((entry) => String(entry.major) === String(major));
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
function parseFieldSelection(raw) {
|
|
@@ -176,6 +178,37 @@ function buildStatusData(cwd) {
|
|
|
176
178
|
return { ready, configFound, gatewayExeFound, kbLooksValid, configPath, gatewayExePath, kbPath, gxPath, configSource };
|
|
177
179
|
}
|
|
178
180
|
|
|
181
|
+
const PROBE_EXIT_GRACE_MS = 2000;
|
|
182
|
+
|
|
183
|
+
// Stop a probe child and wait for it to actually exit. On Windows child.kill()
|
|
184
|
+
// only signals the direct process and returns before the OS releases the
|
|
185
|
+
// executable image, so reporting success right after kill() left the exe file
|
|
186
|
+
// handle open (Issue #211: the shared gateway stub in cli/run.test.js made the
|
|
187
|
+
// suite teardown fail with EPERM while every assertion passed). Resolves true
|
|
188
|
+
// once the child exited, false when it did not within the bounded grace.
|
|
189
|
+
function stopProbeChild(child, graceMs = PROBE_EXIT_GRACE_MS) {
|
|
190
|
+
return new Promise((resolve) => {
|
|
191
|
+
if (!child || child.exitCode !== null || child.signalCode !== null) {
|
|
192
|
+
resolve(true);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
let settled = false;
|
|
196
|
+
const finish = (exited) => {
|
|
197
|
+
if (settled) return;
|
|
198
|
+
settled = true;
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
resolve(exited);
|
|
201
|
+
};
|
|
202
|
+
const timer = setTimeout(() => finish(false), graceMs);
|
|
203
|
+
child.once('exit', () => finish(true));
|
|
204
|
+
try {
|
|
205
|
+
child.kill();
|
|
206
|
+
} catch {
|
|
207
|
+
finish(true);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
179
212
|
async function spawnGatewayProbe({ env = process.env, spawnHoldMs, timeoutMs, label, successDetail }) {
|
|
180
213
|
const gatewayExePath = getGatewayExePath();
|
|
181
214
|
|
|
@@ -209,17 +242,25 @@ async function spawnGatewayProbe({ env = process.env, spawnHoldMs, timeoutMs, la
|
|
|
209
242
|
});
|
|
210
243
|
|
|
211
244
|
child.once('spawn', () => {
|
|
212
|
-
setTimeout(() => {
|
|
213
|
-
|
|
214
|
-
|
|
245
|
+
setTimeout(async () => {
|
|
246
|
+
const exited = await stopProbeChild(child);
|
|
247
|
+
if (exited) {
|
|
248
|
+
finish({ status: 'pass', detail: successDetail });
|
|
249
|
+
} else {
|
|
250
|
+
finish({ status: 'warn', detail: `${label}: process did not exit after the stop signal; it may still hold the gateway exe.` });
|
|
251
|
+
}
|
|
215
252
|
}, spawnHoldMs);
|
|
216
253
|
});
|
|
217
254
|
|
|
218
|
-
setTimeout(() => {
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
255
|
+
setTimeout(async () => {
|
|
256
|
+
if (done) return;
|
|
257
|
+
const exited = await stopProbeChild(child);
|
|
258
|
+
finish({
|
|
259
|
+
status: 'warn',
|
|
260
|
+
detail: exited
|
|
261
|
+
? `${label} timed out; process was force-stopped.`
|
|
262
|
+
: `${label} timed out and did not exit after the stop signal; it may still hold the gateway exe.`
|
|
263
|
+
});
|
|
223
264
|
}, timeoutMs);
|
|
224
265
|
} catch (err) {
|
|
225
266
|
finish({ status: 'fail', detail: `${label} threw: ${err.message}` });
|
|
@@ -562,6 +603,50 @@ function buildInProcessBuildAssemblyLoadCheck(gxPath) {
|
|
|
562
603
|
};
|
|
563
604
|
}
|
|
564
605
|
|
|
606
|
+
function buildGxPublicComCheck(gxMajor, gxPath) {
|
|
607
|
+
if (gxMajor !== '8' && gxMajor !== '9') {
|
|
608
|
+
return { id: 'gxpublic_com_registration', status: 'not_applicable', detail: 'Not a GeneXus 8.0/9.0 installation.' };
|
|
609
|
+
}
|
|
610
|
+
const child_process = require('child_process');
|
|
611
|
+
let isRegistered = false;
|
|
612
|
+
try {
|
|
613
|
+
const res = child_process.spawnSync('reg', ['query', 'HKCR\\GXPublic.GXPublic'], { encoding: 'utf8', timeout: 2000 });
|
|
614
|
+
isRegistered = res.status === 0;
|
|
615
|
+
} catch { }
|
|
616
|
+
|
|
617
|
+
if (isRegistered) {
|
|
618
|
+
return { id: 'gxpublic_com_registration', status: 'pass', detail: 'GXPublic COM automation server (GXPublic.GXPublic) is registered.' };
|
|
619
|
+
}
|
|
620
|
+
const dllHint = gxPath ? path.join(gxPath, 'GxPublic.dll') : 'GxPublic.dll';
|
|
621
|
+
return {
|
|
622
|
+
id: 'gxpublic_com_registration',
|
|
623
|
+
status: 'warn',
|
|
624
|
+
detail: `GXPublic COM component is not registered. Run 'regsvr32 "${dllHint}"' to enable GeneXus ${gxMajor}.0 COM automation.`
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function buildLegacyIdeLockCheck(gxMajor) {
|
|
629
|
+
if (gxMajor !== '8' && gxMajor !== '9' && gxMajor !== '10.1' && gxMajor !== '10.2') {
|
|
630
|
+
return { id: 'legacy_ide_lock', status: 'not_applicable', detail: 'Lock check not applicable for modern GeneXus versions.' };
|
|
631
|
+
}
|
|
632
|
+
const child_process = require('child_process');
|
|
633
|
+
let ideRunning = false;
|
|
634
|
+
try {
|
|
635
|
+
const res = child_process.spawnSync('tasklist', ['/FI', 'IMAGENAME eq gx.exe', '/NH'], { encoding: 'utf8', timeout: 2000 });
|
|
636
|
+
if (res.stdout && res.stdout.toLowerCase().includes('gx.exe')) {
|
|
637
|
+
ideRunning = true;
|
|
638
|
+
}
|
|
639
|
+
} catch { }
|
|
640
|
+
if (ideRunning) {
|
|
641
|
+
return {
|
|
642
|
+
id: 'legacy_ide_lock',
|
|
643
|
+
status: 'warn',
|
|
644
|
+
detail: 'GeneXus IDE (gx.exe) process is currently running. Legacy engines hold exclusive file locks on the KB.'
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
return { id: 'legacy_ide_lock', status: 'pass', detail: 'No conflicting GeneXus IDE process (gx.exe) detected.' };
|
|
648
|
+
}
|
|
649
|
+
|
|
565
650
|
function redactConfig(cfg) {
|
|
566
651
|
// Replace absolute paths with `<redacted:hash8>` so the structure is preserved
|
|
567
652
|
// but filesystem layout, usernames, and KB names are not leaked. Hash is stable
|
|
@@ -710,7 +795,7 @@ async function handleDoctor(options, ctx) {
|
|
|
710
795
|
const kbPath = data.kbPath;
|
|
711
796
|
const gxPath = data.gxPath;
|
|
712
797
|
const kbExists = !!(kbPath && fs.existsSync(kbPath));
|
|
713
|
-
const gxExeExists = !!(gxPath && fs.existsSync(path.join(gxPath, 'genexus.exe')));
|
|
798
|
+
const gxExeExists = !!(gxPath && (fs.existsSync(path.join(gxPath, 'genexus.exe')) || fs.existsSync(path.join(gxPath, 'gx.exe'))));
|
|
714
799
|
const kbSdkCompatibility = kbExists && gxExeExists
|
|
715
800
|
? compareGeneXusKbAndInstallation(kbPath, gxPath)
|
|
716
801
|
: null;
|
|
@@ -731,15 +816,18 @@ async function handleDoctor(options, ctx) {
|
|
|
731
816
|
compatibilityDetail = `${unsupportedCompatibilityMajor} is not supported by this MCP distribution. Supported majors: ${catalog.supportedMajors.map((entry) => entry.major).join(', ')}.`;
|
|
732
817
|
} else if (kbSdkCompatibility.status === 'mismatch') {
|
|
733
818
|
compatibilityStatus = 'fail';
|
|
734
|
-
compatibilityDetail = `KB major ${kbSdkCompatibility.kb.major} does not match GeneXus SDK major ${kbSdkCompatibility.gx.major}. Re-run init with the matching --gx path.`;
|
|
819
|
+
compatibilityDetail = `KB major ${kbSdkCompatibility.kb.major} does not match GeneXus SDK major ${kbSdkCompatibility.gx.major}${kbSdkCompatibility.gx.version ? ` (${kbSdkCompatibility.gx.version})` : ''}. Re-run init with the matching --gx path.`;
|
|
735
820
|
} else if (kbSdkCompatibility.status === 'match') {
|
|
736
821
|
compatibilityStatus = 'pass';
|
|
737
|
-
compatibilityDetail = `KB major ${kbSdkCompatibility.kb.major} matches GeneXus SDK major ${kbSdkCompatibility.gx.major}.`;
|
|
822
|
+
compatibilityDetail = `KB major ${kbSdkCompatibility.kb.major} matches GeneXus SDK major ${kbSdkCompatibility.gx.major}${kbSdkCompatibility.gx.version ? ` (${kbSdkCompatibility.gx.version})` : ''}.`;
|
|
738
823
|
} else {
|
|
739
824
|
compatibilityDetail = `KB/SDK major compatibility could not be verified (KB: ${kbSdkCompatibility.kb.source}; SDK: ${kbSdkCompatibility.gx.source}).`;
|
|
740
825
|
}
|
|
741
826
|
}
|
|
742
827
|
|
|
828
|
+
const gxIdent = gxPath ? readGeneXusInstallationIdentity(gxPath) : null;
|
|
829
|
+
const gxVersionLabel = gxIdent && gxIdent.version ? ` (GeneXus ${gxIdent.major || ''} version ${gxIdent.version})` : (gxIdent && gxIdent.major ? ` (GeneXus ${gxIdent.major})` : '');
|
|
830
|
+
|
|
743
831
|
const riskyZone = isPathLikelyAppLockerBlocked(gatewayExePath);
|
|
744
832
|
const clientCrossCheck = buildClientExeCrossCheck(gatewayExePath);
|
|
745
833
|
|
|
@@ -767,7 +855,7 @@ async function handleDoctor(options, ctx) {
|
|
|
767
855
|
// Same logic for the GeneXus install: missing genexus.exe at a configured path
|
|
768
856
|
// guarantees a worker crash on first MCP call. Promote from warn to fail so init
|
|
769
857
|
// exits non-zero and the caller (install.ps1, AI client) actually sees the problem.
|
|
770
|
-
{ id: 'gx_installation', status: gxExeExists ? 'pass' : (gxPath ? 'fail' : 'warn'), detail: gxExeExists ?
|
|
858
|
+
{ id: 'gx_installation', status: gxExeExists ? 'pass' : (gxPath ? 'fail' : 'warn'), detail: gxExeExists ? `GeneXus installation has ${gxPath && fs.existsSync(path.join(gxPath, 'gx.exe')) && !fs.existsSync(path.join(gxPath, 'genexus.exe')) ? 'gx.exe' : 'genexus.exe'}${gxVersionLabel}.` : (gxPath ? `Configured GeneXus installation is missing executable at: ${gxPath}` : 'No GeneXus installation path is configured.') },
|
|
771
859
|
{
|
|
772
860
|
id: 'kb_sdk_compatibility',
|
|
773
861
|
status: compatibilityStatus,
|
|
@@ -784,6 +872,13 @@ async function handleDoctor(options, ctx) {
|
|
|
784
872
|
const inProcessLoad = buildInProcessBuildAssemblyLoadCheck(gxPath);
|
|
785
873
|
checks.push({ id: 'in_process_build_assembly_load', status: inProcessLoad.status, detail: inProcessLoad.detail });
|
|
786
874
|
|
|
875
|
+
// Legacy GeneXus checks (GX 8.0, 9.0, Ev1, Ev2)
|
|
876
|
+
const gxMajor = (gxIdent && gxIdent.major) || (kbSdkCompatibility && kbSdkCompatibility.gx && kbSdkCompatibility.gx.major) || null;
|
|
877
|
+
const comCheck = buildGxPublicComCheck(gxMajor, gxPath);
|
|
878
|
+
checks.push({ id: 'gxpublic_com_registration', status: comCheck.status, detail: comCheck.detail });
|
|
879
|
+
const ideLockCheck = buildLegacyIdeLockCheck(gxMajor);
|
|
880
|
+
checks.push({ id: 'legacy_ide_lock', status: ideLockCheck.status, detail: ideLockCheck.detail });
|
|
881
|
+
|
|
787
882
|
// Client registration summary — one line answering "are my AI agents wired up?".
|
|
788
883
|
const clientRows = clientsStatus();
|
|
789
884
|
const installedRows = clientRows.filter((r) => r.installed);
|
|
@@ -1344,6 +1439,7 @@ function buildClientLauncherHelp(patchResult) {
|
|
|
1344
1439
|
}
|
|
1345
1440
|
if (patched.length > 0 && process.platform === 'win32' && !process.env.GENEXUS_MCP_GATEWAY_EXE) {
|
|
1346
1441
|
help.push('Windows launcher paths may resolve under the npm cache and be blocked by AppLocker/SRP. Use scripts/install.ps1 for a stable whitelisted path.');
|
|
1442
|
+
help.push('Working from a local checkout? Register the clients with the checkout gateway from the repo root instead: `$env:GENEXUS_MCP_GATEWAY_EXE="<repoRoot>\\publish\\GxMcp.Gateway.exe"; node cli\\run.js clients add --clients <ids>` (or re-run `.\\install.ps1`). Avoid `npx @latest clients add` there, because it rewrites the registration to this npm package\'s launcher; validate with the same checkout CLI (`node cli\\run.js clients` / `doctor`).');
|
|
1347
1443
|
}
|
|
1348
1444
|
return help;
|
|
1349
1445
|
}
|
|
@@ -1939,6 +2035,7 @@ async function probeWorkerStartup({ configPath, observeMs = 2500 }) {
|
|
|
1939
2035
|
};
|
|
1940
2036
|
|
|
1941
2037
|
let child;
|
|
2038
|
+
let stopping = false;
|
|
1942
2039
|
try {
|
|
1943
2040
|
child = spawn(gatewayExePath, [], {
|
|
1944
2041
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -1957,6 +2054,9 @@ async function probeWorkerStartup({ configPath, observeMs = 2500 }) {
|
|
|
1957
2054
|
});
|
|
1958
2055
|
|
|
1959
2056
|
child.once('exit', (code) => {
|
|
2057
|
+
// The observation timeout stops the gateway itself; that deliberate
|
|
2058
|
+
// signal is reported by the timeout branch, not as a crash.
|
|
2059
|
+
if (stopping) return;
|
|
1960
2060
|
if (code === 0) {
|
|
1961
2061
|
finish({ status: 'pass', detail: 'Worker smoke: gateway exited cleanly during observation window.' });
|
|
1962
2062
|
} else {
|
|
@@ -1970,10 +2070,15 @@ async function probeWorkerStartup({ configPath, observeMs = 2500 }) {
|
|
|
1970
2070
|
}
|
|
1971
2071
|
});
|
|
1972
2072
|
|
|
1973
|
-
setTimeout(() => {
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2073
|
+
setTimeout(async () => {
|
|
2074
|
+
stopping = true;
|
|
2075
|
+
const exited = await stopProbeChild(child);
|
|
2076
|
+
if (exited) {
|
|
2077
|
+
// Still alive after observeMs → worker bootstrapped without crashing.
|
|
2078
|
+
finish({ status: 'pass', detail: `Worker smoke: gateway stayed alive for ${observeMs}ms with KB and GX configured.` });
|
|
2079
|
+
} else {
|
|
2080
|
+
finish({ status: 'warn', detail: 'Worker smoke: gateway did not exit after the stop signal; it may still hold the gateway exe.' });
|
|
2081
|
+
}
|
|
1977
2082
|
}, observeMs);
|
|
1978
2083
|
});
|
|
1979
2084
|
}
|
|
@@ -2150,11 +2255,12 @@ async function handleClients(subcommand, options, ctx) {
|
|
|
2150
2255
|
if (invalidLaunchers.length > 0) {
|
|
2151
2256
|
help.push(`These clients have a known-invalid MCP launcher (check command and args) — re-register: genexus-mcp clients add --clients ${invalidLaunchers.map((r) => r.id).join(',')}${serverName !== DEFAULT_MCP_SERVER_NAME ? ` --server-name ${serverName}` : ''}`);
|
|
2152
2257
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2258
|
+
// A launcher that exists but is not this CLI's gateway is a different working
|
|
2259
|
+
// install (checkout, fixed-path, other package cache), so it is reported as
|
|
2260
|
+
// informational drift instead of a stale registration that needs repair.
|
|
2261
|
+
const driftedLaunchers = rows.filter((r) => r.launcherPathDrift && !r.commandStale);
|
|
2262
|
+
if (driftedLaunchers.length > 0) {
|
|
2263
|
+
help.push(`Note: ${driftedLaunchers.map((r) => r.name).join(', ')} point at a gateway that is not this CLI's (informational — the launcher exists and is valid). Re-register with this CLI only if you want them to track it: genexus-mcp clients add --clients ${driftedLaunchers.map((r) => r.id).join(',')}${serverName !== DEFAULT_MCP_SERVER_NAME ? ` --server-name ${serverName}` : ''}`);
|
|
2158
2264
|
}
|
|
2159
2265
|
const semanticValidCount = rows.filter((r) => r.launcherSemanticState === 'valid').length;
|
|
2160
2266
|
const semanticInvalidCount = rows.filter((r) => r.launcherSemanticState === 'invalid').length;
|
|
@@ -78,10 +78,16 @@ class OpenCodeJsoncAdapter extends BaseClientAdapter {
|
|
|
78
78
|
this.config = configModule || require('./config');
|
|
79
79
|
}
|
|
80
80
|
apply(client, launcher, targetConfigPath, opts = {}) {
|
|
81
|
-
return this.config.applyOpenCodeJson(client.path, launcher, targetConfigPath,
|
|
81
|
+
return this.config.applyOpenCodeJson(client.path, launcher, targetConfigPath, {
|
|
82
|
+
...opts,
|
|
83
|
+
alternatePaths: client.alternatePaths || []
|
|
84
|
+
});
|
|
82
85
|
}
|
|
83
86
|
remove(client, opts = {}) {
|
|
84
|
-
return this.config.removeOpenCodeJson(client.path,
|
|
87
|
+
return this.config.removeOpenCodeJson(client.path, {
|
|
88
|
+
...opts,
|
|
89
|
+
alternatePaths: client.alternatePaths || []
|
|
90
|
+
});
|
|
85
91
|
}
|
|
86
92
|
read(client, serverName) {
|
|
87
93
|
if (!fs.existsSync(client.path)) return null;
|