genexus-mcp 3.1.0 → 3.2.0

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 CHANGED
@@ -1,4 +1,4 @@
1
- # GeneXus MCP Server — GeneXus 18 for Claude, Cursor, and AI Agents
1
+ # GeneXus MCP Server — Multi-version GeneXus for Claude, Cursor, and AI Agents
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/genexus-mcp.svg)](https://www.npmjs.com/package/genexus-mcp)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/genexus-mcp.svg)](https://www.npmjs.com/package/genexus-mcp)
@@ -12,12 +12,63 @@
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 GeneXus 18 Knowledge Base. It talks to the **native GeneXus SDK**, so the agent works with the *real* KB, not a copy or a parsed approximation.
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 GeneXus SDK. It talks to the **native GeneXus SDK**, so the agent works with the *real* KB, not a copy or a parsed approximation.
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 SDK support
22
+
23
+ The same MCP distribution supports the SDK majors listed in the generated
24
+ compatibility document. Each configured MCP process selects one installed SDK
25
+ with `--gx`; no separate MCP installation is required. The commands below are
26
+ examples of switching the existing configuration, not running two majors in
27
+ the same process:
28
+
29
+ ```bash
30
+ npx genexus-mcp@latest init --kb "C:\KBs\KBTeste17" --gx "C:\Program Files (x86)\GeneXus\GeneXus17Trial"
31
+ # To switch this MCP configuration to GX18:
32
+ npx genexus-mcp@latest init --kb "C:\KBs\MyGX18KB" --gx "C:\Program Files (x86)\GeneXus\GeneXus18"
33
+ ```
34
+
35
+ After switching the SDK or KB, fully restart the AI client so it reloads the
36
+ MCP process and its tool schemas. If GX17 and GX18 must run simultaneously,
37
+ use separate MCP configurations and ports.
38
+
39
+ `init` also reads the KB `.gxw` major and the selected `GeneXus.exe` metadata.
40
+ It aborts before writing `config.json` when the majors conflict or an automatic
41
+ selection cannot be verified. `genexus-mcp doctor` exposes the same result as
42
+ the `kb_sdk_compatibility` check. For a disposable fixture, the
43
+ [live-KB harness](docs/live-kb-test-harness.md) includes a catalog-driven matrix
44
+ that checks every supported major against one published artifact.
45
+
46
+ The Gateway reports the detected SDK through `genexus_whoami`:
47
+
48
+ - `geneXus.supportedMajors`: explicitly validated SDK majors from the version catalog
49
+ - `geneXus.matchedMajor`: the major detected for the configured installation
50
+ - `geneXus.versionMatches`: whether the detected installation is in that catalog
51
+ - `geneXus.supportedMajor`: retained as the legacy single-major alias for the catalog primary
52
+
53
+ The Worker isolates version-sensitive SDK members behind compatibility adapters.
54
+ For example, Design System helper methods that differ between SDK majors are
55
+ replaced field-by-field by parsing the native `Tokens` and `Styles` parts when
56
+ needed. Existing tool names, arguments, and MCP client configuration formats do
57
+ not change.
58
+
59
+ <!-- BEGIN GENERATED: gx-compatibility -->
60
+ Supported SDK majors: **GeneXus 17, GeneXus 18**.
61
+ Primary SDK: **GeneXus 18**.
62
+ Source of truth: `config/gx-versions.json`.
63
+ <!-- END GENERATED: gx-compatibility -->
64
+
65
+ To add another GeneXus major in the future, add it to the explicit version
66
+ catalog only after compiling the Worker with that SDK and passing the focused
67
+ tests plus a live KB smoke. This prevents the server from claiming compatibility
68
+ based only on a version string.
69
+
70
+ ---
71
+
21
72
  ## What you can do with it
22
73
 
23
74
  A quick map of what the agent can do against your real KB through the **50 tools** (details in [Tool Surface](#tool-surface)):
@@ -44,8 +95,8 @@ It works through the **native GeneXus SDK** — the same code paths the IDE uses
44
95
  Before you start, make sure you have:
45
96
 
46
97
  - ✅ **Windows** (GeneXus is Windows-only)
47
- - ✅ **GeneXus 18** installed locally (default path: `C:\Program Files (x86)\GeneXus\GeneXus18`)
48
- - ✅ **A GeneXus 18 Knowledge Base** opened at least once in the IDE (so it's initialized)
98
+ - ✅ **A supported GeneXus SDK** installed locally (see [`docs/generated/supported-versions.md`](docs/generated/supported-versions.md); pass another install path explicitly when needed)
99
+ - ✅ **A Knowledge Base created with a supported GeneXus major** and opened at least once in the IDE (so it's initialized)
49
100
  - ✅ **Node.js 18+** — check with `node --version` in a terminal; install from [nodejs.org](https://nodejs.org/) if missing
50
101
  - ✅ **An MCP-compatible AI client** — [Claude Desktop](https://claude.ai/download), [Claude Code](https://claude.com/claude-code), Cursor, Antigravity, etc.
51
102
 
@@ -445,7 +496,7 @@ The installer writes a `config.json` for you. To customize networking, timeouts,
445
496
  "HttpPort": 5000,
446
497
  "BindAddress": "127.0.0.1",
447
498
  "SessionIdleTimeoutMinutes": 10,
448
- "WorkerIdleTimeoutMinutes": 5,
499
+ "WorkerIdleTimeoutMinutes": 60,
449
500
  "MaxOpenKbs": 3
450
501
  },
451
502
  "GeneXus": {
@@ -540,7 +591,7 @@ This repo ships a set of **agent skills** under `.gemini/skills/` that any MCP-c
540
591
  |---|---|
541
592
  | `genexus-mastery` | This repository's preferred MCP workflow + multi-KB usage |
542
593
  | `genexus18-guidelines` | Local engineering rules layered on top of Nexa |
543
- | `nexa` | Full GeneXus 18 reference set: every object type, command, type, property — imported from the official [`genexuslabs/genexus-skills`](https://github.com/genexuslabs/genexus-skills) |
594
+ | `nexa` | Full reference set for the primary GeneXus SDK: every object type, command, type, property — imported from the official [`genexuslabs/genexus-skills`](https://github.com/genexuslabs/genexus-skills) |
544
595
  | `frontend/chameleon-controls-library` | 58 Chameleon UI component specs |
545
596
  | `frontend/mercury-design-system` | Mercury tokens, bundles, theming |
546
597
  | `frontend/design-system-builder` | Authoring custom design systems |
@@ -17,7 +17,10 @@ const {
17
17
  filterClientTargets,
18
18
  listSupportedClientIds,
19
19
  getLocalAppDataCacheDir,
20
- readGeneXusVersionFromInstall,
20
+ readGeneXusInstallationIdentity,
21
+ readGeneXusKbIdentity,
22
+ compareGeneXusKbAndInstallation,
23
+ getGeneXusVersionCatalog,
21
24
  discoverGeneXusInstallation,
22
25
  discoverKnowledgeBase,
23
26
  discoverKnowledgeBases,
@@ -51,6 +54,11 @@ function validateClientIds(ids) {
51
54
  };
52
55
  }
53
56
 
57
+ function isSupportedCatalogMajor(catalog, major) {
58
+ if (!major) return true;
59
+ return catalog.supportedMajors.some((entry) => String(entry.major) === String(major));
60
+ }
61
+
54
62
  function parseFieldSelection(raw) {
55
63
  if (!raw) return null;
56
64
  return raw.split(',').map((v) => v.trim()).filter(Boolean);
@@ -127,9 +135,9 @@ function usageEnvelope(message, exitCode) {
127
135
  };
128
136
  }
129
137
 
130
- function operationalErrorEnvelope(message, exitCode, help = []) {
138
+ function operationalErrorEnvelope(message, exitCode, help = [], code = 'operation_error') {
131
139
  return {
132
- error: { code: 'operation_error', message: sanitizeOperationalMessage(message) },
140
+ error: { code, message: sanitizeOperationalMessage(message) },
133
141
  help,
134
142
  meta: { exitCode }
135
143
  };
@@ -572,8 +580,8 @@ async function buildSupportDump({ checks, summary, data, gatewayExePath, ctx })
572
580
  writeEntry('config.redacted.json', redactConfig(cfg));
573
581
  }
574
582
 
575
- let gxVersion = null;
576
- try { gxVersion = readGeneXusVersionFromInstall(data.gxPath); } catch { }
583
+ let gxIdentity = { version: null, major: null, source: 'unavailable' };
584
+ try { gxIdentity = readGeneXusInstallationIdentity(data.gxPath); } catch { }
577
585
 
578
586
  writeEntry('environment.json', {
579
587
  platform: process.platform,
@@ -586,7 +594,9 @@ async function buildSupportDump({ checks, summary, data, gatewayExePath, ctx })
586
594
  configSource: data.configSource,
587
595
  kbConfigured: !!data.kbPath,
588
596
  gxConfigured: !!data.gxPath,
589
- gxVersion,
597
+ gxVersion: gxIdentity.version,
598
+ gxMajor: gxIdentity.major,
599
+ gxDetectionSource: gxIdentity.source,
590
600
  envFlags: {
591
601
  GX_CONFIG_PATH: !!process.env.GX_CONFIG_PATH,
592
602
  GENEXUS_MCP_GATEWAY_EXE: !!process.env.GENEXUS_MCP_GATEWAY_EXE,
@@ -674,6 +684,34 @@ async function handleDoctor(options, ctx) {
674
684
  const gxPath = data.gxPath;
675
685
  const kbExists = !!(kbPath && fs.existsSync(kbPath));
676
686
  const gxExeExists = !!(gxPath && fs.existsSync(path.join(gxPath, 'genexus.exe')));
687
+ const kbSdkCompatibility = kbExists && gxExeExists
688
+ ? compareGeneXusKbAndInstallation(kbPath, gxPath)
689
+ : null;
690
+ const catalog = getGeneXusVersionCatalog();
691
+
692
+ let unsupportedCompatibilityMajor = null;
693
+ let compatibilityStatus = 'warn';
694
+ let compatibilityDetail = 'KB/SDK major compatibility check was skipped until both configured paths exist.';
695
+ if (kbSdkCompatibility) {
696
+ if (!isSupportedCatalogMajor(catalog, kbSdkCompatibility.kb.major)) {
697
+ unsupportedCompatibilityMajor = `KB major ${kbSdkCompatibility.kb.major}`;
698
+ } else if (!isSupportedCatalogMajor(catalog, kbSdkCompatibility.gx.major)) {
699
+ unsupportedCompatibilityMajor = `GeneXus SDK major ${kbSdkCompatibility.gx.major}`;
700
+ }
701
+
702
+ if (unsupportedCompatibilityMajor) {
703
+ compatibilityStatus = 'fail';
704
+ compatibilityDetail = `${unsupportedCompatibilityMajor} is not supported by this MCP distribution. Supported majors: ${catalog.supportedMajors.map((entry) => entry.major).join(', ')}.`;
705
+ } else if (kbSdkCompatibility.status === 'mismatch') {
706
+ compatibilityStatus = 'fail';
707
+ compatibilityDetail = `KB major ${kbSdkCompatibility.kb.major} does not match GeneXus SDK major ${kbSdkCompatibility.gx.major}. Re-run init with the matching --gx path.`;
708
+ } else if (kbSdkCompatibility.status === 'match') {
709
+ compatibilityStatus = 'pass';
710
+ compatibilityDetail = `KB major ${kbSdkCompatibility.kb.major} matches GeneXus SDK major ${kbSdkCompatibility.gx.major}.`;
711
+ } else {
712
+ compatibilityDetail = `KB/SDK major compatibility could not be verified (KB: ${kbSdkCompatibility.kb.source}; SDK: ${kbSdkCompatibility.gx.source}).`;
713
+ }
714
+ }
677
715
 
678
716
  const riskyZone = isPathLikelyAppLockerBlocked(gatewayExePath);
679
717
  const clientCrossCheck = buildClientExeCrossCheck(gatewayExePath);
@@ -703,6 +741,11 @@ async function handleDoctor(options, ctx) {
703
741
  // guarantees a worker crash on first MCP call. Promote from warn to fail so init
704
742
  // exits non-zero and the caller (install.ps1, AI client) actually sees the problem.
705
743
  { 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.') },
744
+ {
745
+ id: 'kb_sdk_compatibility',
746
+ status: compatibilityStatus,
747
+ detail: compatibilityDetail
748
+ },
706
749
  { 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.`) },
707
750
  { 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.' },
708
751
  { id: 'client_config_sync', status: clientCrossCheck.status, detail: clientCrossCheck.detail }
@@ -1188,27 +1231,60 @@ function buildInteractiveInitHelp(patchResult) {
1188
1231
  }
1189
1232
 
1190
1233
  async function runInteractiveInit(ctx) {
1191
- const defaultGx = discoverGeneXusInstallation() || 'C:\\Program Files (x86)\\GeneXus\\GeneXus18';
1234
+ const catalog = getGeneXusVersionCatalog();
1235
+ const primary = catalog.supportedMajors.find((entry) => String(entry.major) === String(catalog.primaryMajor));
1192
1236
 
1193
1237
  if (!ctx.options.quiet) {
1194
1238
  ctx.stderr.write('GeneXus MCP setup wizard\n\n');
1195
1239
  }
1196
1240
 
1197
- const rl = readline.createInterface({ input: process.stdin, output: ctx.stderr });
1241
+ const rl = readline.createInterface({ input: ctx.input || process.stdin, output: ctx.stderr });
1198
1242
  const question = (text) => new Promise((resolve) => rl.question(text, (answer) => resolve(answer)));
1199
1243
 
1200
1244
  try {
1201
1245
  const kbAnswer = await question(`1) Knowledge Base folder path (default: ${ctx.cwd}):\n> `);
1202
1246
  const finalKb = String(kbAnswer || '').trim() || ctx.cwd;
1203
1247
 
1248
+ if (!fs.existsSync(finalKb)) {
1249
+ return {
1250
+ exitCode: ctx.EXIT_CODES.ERROR,
1251
+ envelope: operationalErrorEnvelope(
1252
+ `KB path does not exist on disk. Aborted before writing config.`,
1253
+ ctx.EXIT_CODES.ERROR,
1254
+ [`Path checked: ${finalKb}`, 'Create the KB in GeneXus first, then re-run init.']
1255
+ )
1256
+ };
1257
+ }
1258
+
1259
+ const kbIdentity = readGeneXusKbIdentity(finalKb);
1260
+ const defaultGx = discoverGeneXusInstallation(kbIdentity.major)
1261
+ || primary?.defaultInstallPath
1262
+ || catalog.supportedMajors[0]?.defaultInstallPath
1263
+ || '';
1204
1264
  const gxAnswer = await question(`\n2) GeneXus installation path (default: ${defaultGx}):\n> `);
1205
- const finalGx = String(gxAnswer || '').trim() || defaultGx;
1265
+ const explicitGx = String(gxAnswer || '').trim();
1266
+ const finalGx = explicitGx || defaultGx;
1267
+
1268
+ if (!kbIdentity.major && !explicitGx) {
1269
+ return {
1270
+ exitCode: ctx.EXIT_CODES.ERROR,
1271
+ envelope: operationalErrorEnvelope(
1272
+ 'Cannot safely use the default GeneXus SDK because the KB major could not be determined.',
1273
+ ctx.EXIT_CODES.ERROR,
1274
+ [
1275
+ `KB detection reason: ${kbIdentity.reason || 'unknown'}`,
1276
+ 'Enter the installation path explicitly, or open the KB once in GeneXus so its .gxw metadata is initialized.'
1277
+ ],
1278
+ 'sdk_selection_required'
1279
+ )
1280
+ };
1281
+ }
1206
1282
 
1207
1283
  if (!fs.existsSync(path.join(finalGx, 'genexus.exe'))) {
1208
- const suggested = discoverGeneXusInstallation();
1284
+ const suggested = discoverGeneXusInstallation(kbIdentity.major);
1209
1285
  const help = [`Path checked: ${finalGx}`];
1210
1286
  if (suggested && suggested.toLowerCase() !== finalGx.toLowerCase()) {
1211
- help.push(`Detected a working GeneXus install at: ${suggested}`);
1287
+ help.push(`Detected a matching GeneXus${kbIdentity.major ? ` ${kbIdentity.major}` : ''} install at: ${suggested}`);
1212
1288
  help.push('Re-run `genexus-mcp init --interactive` and accept the detected path, or pass --gx explicitly.');
1213
1289
  }
1214
1290
  return {
@@ -1221,13 +1297,57 @@ async function runInteractiveInit(ctx) {
1221
1297
  };
1222
1298
  }
1223
1299
 
1224
- if (!fs.existsSync(finalKb)) {
1300
+ const compatibility = compareGeneXusKbAndInstallation(finalKb, finalGx);
1301
+ const supportedGxMajor = isSupportedCatalogMajor(catalog, compatibility.gx.major);
1302
+ if (!supportedGxMajor) {
1225
1303
  return {
1226
1304
  exitCode: ctx.EXIT_CODES.ERROR,
1227
1305
  envelope: operationalErrorEnvelope(
1228
- `KB path does not exist on disk. Aborted before writing config.`,
1306
+ `GeneXus SDK major ${compatibility.gx.major} is not supported by this MCP distribution.`,
1229
1307
  ctx.EXIT_CODES.ERROR,
1230
- [`Path checked: ${finalKb}`, 'Create the KB in GeneXus first, then re-run init.']
1308
+ [`Supported majors: ${catalog.supportedMajors.map((entry) => entry.major).join(', ')}`],
1309
+ 'sdk_unsupported'
1310
+ )
1311
+ };
1312
+ }
1313
+ const supportedKbMajor = isSupportedCatalogMajor(catalog, compatibility.kb.major);
1314
+ if (!supportedKbMajor) {
1315
+ return {
1316
+ exitCode: ctx.EXIT_CODES.ERROR,
1317
+ envelope: operationalErrorEnvelope(
1318
+ `KB major ${compatibility.kb.major} is not supported by this MCP distribution.`,
1319
+ ctx.EXIT_CODES.ERROR,
1320
+ [`Supported majors: ${catalog.supportedMajors.map((entry) => entry.major).join(', ')}`],
1321
+ 'sdk_kb_unsupported'
1322
+ )
1323
+ };
1324
+ }
1325
+ if (compatibility.status === 'mismatch') {
1326
+ return {
1327
+ exitCode: ctx.EXIT_CODES.ERROR,
1328
+ envelope: operationalErrorEnvelope(
1329
+ `GeneXus SDK major ${compatibility.gx.major} does not match KB major ${compatibility.kb.major}. Aborted before writing config.`,
1330
+ ctx.EXIT_CODES.ERROR,
1331
+ [
1332
+ `KB version: ${compatibility.kb.version || 'unknown'}`,
1333
+ `SDK version: ${compatibility.gx.version || 'unknown'}`,
1334
+ 'Choose the GeneXus installation that matches the KB major, or pass --gx explicitly.',
1335
+ ],
1336
+ 'sdk_kb_mismatch'
1337
+ )
1338
+ };
1339
+ }
1340
+ if (compatibility.kb.major && !compatibility.gx.major) {
1341
+ return {
1342
+ exitCode: ctx.EXIT_CODES.ERROR,
1343
+ envelope: operationalErrorEnvelope(
1344
+ `The selected GeneXus installation version could not be verified against KB major ${compatibility.kb.major}.`,
1345
+ ctx.EXIT_CODES.ERROR,
1346
+ [
1347
+ `Path checked: ${finalGx}`,
1348
+ 'Use a standard GeneXus installation containing GeneXus.exe metadata, or provide a verified matching SDK path.'
1349
+ ],
1350
+ 'sdk_identity_unresolved'
1231
1351
  )
1232
1352
  };
1233
1353
  }
@@ -1333,8 +1453,12 @@ async function handleInit(options, ctx) {
1333
1453
  }
1334
1454
  }
1335
1455
 
1456
+ const discoveredKbIdentity = resolution.kb.value
1457
+ ? readGeneXusKbIdentity(resolution.kb.value)
1458
+ : { version: null, major: null, source: 'unavailable', reason: 'missing-kb-path' };
1459
+
1336
1460
  if (!resolution.gx.value) {
1337
- const fromDisco = discoverGeneXusInstallation();
1461
+ const fromDisco = discoverGeneXusInstallation(discoveredKbIdentity.major);
1338
1462
  if (fromDisco) {
1339
1463
  resolution.gx.value = fromDisco;
1340
1464
  resolution.gx.source = 'auto-discovery';
@@ -1379,12 +1503,14 @@ async function handleInit(options, ctx) {
1379
1503
  `Path checked: ${resolution.gx.value}`,
1380
1504
  `Source: --${resolution.gx.source === 'flag' ? 'gx flag' : resolution.gx.source}`
1381
1505
  ];
1382
- const suggested = resolution.gx.source === 'flag' ? discoverGeneXusInstallation() : null;
1506
+ const suggested = resolution.gx.source === 'flag'
1507
+ ? discoverGeneXusInstallation(discoveredKbIdentity.major)
1508
+ : null;
1383
1509
  if (suggested && suggested.toLowerCase() !== resolution.gx.value.toLowerCase()) {
1384
1510
  help.push(`Detected a working GeneXus install at: ${suggested}`);
1385
1511
  help.push(`Re-run: genexus-mcp init --kb "${resolution.kb.value}" --gx "${suggested}"`);
1386
1512
  } else {
1387
- help.push('Omit --gx to let init auto-discover via registry / Program Files (matches GeneXus18, GeneXus18u7, etc.).');
1513
+ help.push('Omit --gx to let init auto-discover a catalog-listed installation via registry / Program Files.');
1388
1514
  }
1389
1515
  return {
1390
1516
  exitCode: ctx.EXIT_CODES.ERROR,
@@ -1410,6 +1536,77 @@ async function handleInit(options, ctx) {
1410
1536
  };
1411
1537
  }
1412
1538
 
1539
+ const compatibility = compareGeneXusKbAndInstallation(resolution.kb.value, resolution.gx.value);
1540
+ const catalog = getGeneXusVersionCatalog();
1541
+ const supportedGxMajor = isSupportedCatalogMajor(catalog, compatibility.gx.major);
1542
+ const supportedKbMajor = isSupportedCatalogMajor(catalog, compatibility.kb.major);
1543
+ if (!supportedGxMajor) {
1544
+ return {
1545
+ exitCode: ctx.EXIT_CODES.ERROR,
1546
+ envelope: operationalErrorEnvelope(
1547
+ `GeneXus SDK major ${compatibility.gx.major} is not supported by this MCP distribution.`,
1548
+ ctx.EXIT_CODES.ERROR,
1549
+ [`Supported majors: ${catalog.supportedMajors.map((entry) => entry.major).join(', ')}`],
1550
+ 'sdk_unsupported'
1551
+ )
1552
+ };
1553
+ }
1554
+ if (!supportedKbMajor) {
1555
+ return {
1556
+ exitCode: ctx.EXIT_CODES.ERROR,
1557
+ envelope: operationalErrorEnvelope(
1558
+ `KB major ${compatibility.kb.major} is not supported by this MCP distribution.`,
1559
+ ctx.EXIT_CODES.ERROR,
1560
+ [`Supported majors: ${catalog.supportedMajors.map((entry) => entry.major).join(', ')}`],
1561
+ 'sdk_kb_unsupported'
1562
+ )
1563
+ };
1564
+ }
1565
+ if (compatibility.status === 'mismatch') {
1566
+ return {
1567
+ exitCode: ctx.EXIT_CODES.ERROR,
1568
+ envelope: operationalErrorEnvelope(
1569
+ `GeneXus SDK major ${compatibility.gx.major} does not match KB major ${compatibility.kb.major}. Init aborted before writing config.`,
1570
+ ctx.EXIT_CODES.ERROR,
1571
+ [
1572
+ `KB version: ${compatibility.kb.version || 'unknown'}`,
1573
+ `SDK version: ${compatibility.gx.version || 'unknown'}`,
1574
+ `Path checked: ${resolution.gx.value}`,
1575
+ 'Choose the GeneXus installation that matches the KB major, or pass --gx explicitly.'
1576
+ ],
1577
+ 'sdk_kb_mismatch'
1578
+ )
1579
+ };
1580
+ }
1581
+ if (resolution.gx.source === 'auto-discovery' && !compatibility.kb.major) {
1582
+ return {
1583
+ exitCode: ctx.EXIT_CODES.ERROR,
1584
+ envelope: operationalErrorEnvelope(
1585
+ 'Cannot safely auto-select a GeneXus SDK because the KB major could not be determined.',
1586
+ ctx.EXIT_CODES.ERROR,
1587
+ [
1588
+ `KB detection reason: ${compatibility.kb.reason || 'unknown'}`,
1589
+ 'Pass --gx explicitly with the installation that created this KB.'
1590
+ ],
1591
+ 'sdk_selection_required'
1592
+ )
1593
+ };
1594
+ }
1595
+ if (compatibility.kb.major && !compatibility.gx.major) {
1596
+ return {
1597
+ exitCode: ctx.EXIT_CODES.ERROR,
1598
+ envelope: operationalErrorEnvelope(
1599
+ `The selected GeneXus installation version could not be verified against KB major ${compatibility.kb.major}.`,
1600
+ ctx.EXIT_CODES.ERROR,
1601
+ [
1602
+ `Path checked: ${resolution.gx.value}`,
1603
+ 'Use a standard GeneXus installation containing GeneXus.exe metadata, or provide a verified matching SDK path.'
1604
+ ],
1605
+ 'sdk_identity_unresolved'
1606
+ )
1607
+ };
1608
+ }
1609
+
1413
1610
  try {
1414
1611
  const created = createConfigFile(resolution.kb.value, resolution.gx.value);
1415
1612
  const kbName = path.basename(resolution.kb.value);
@@ -1492,8 +1689,19 @@ async function handleInit(options, ctx) {
1492
1689
  noOp: !created.changed,
1493
1690
  clientsPatchedCount: patchResult.patched.length,
1494
1691
  resolved: {
1495
- kb: { path: resolution.kb.value, source: resolution.kb.source },
1496
- gx: { path: resolution.gx.value, source: resolution.gx.source }
1692
+ kb: {
1693
+ path: resolution.kb.value,
1694
+ source: resolution.kb.source,
1695
+ major: compatibility.kb.major,
1696
+ version: compatibility.kb.version
1697
+ },
1698
+ gx: {
1699
+ path: resolution.gx.value,
1700
+ source: resolution.gx.source,
1701
+ major: compatibility.gx.major,
1702
+ version: compatibility.gx.version,
1703
+ detectionSource: compatibility.gx.source
1704
+ }
1497
1705
  },
1498
1706
  verification: {
1499
1707
  summary: verification.summary,
@@ -1661,7 +1869,7 @@ async function handleWhoami(options, ctx) {
1661
1869
  const kbName = kbPath ? path.basename(kbPath) : null;
1662
1870
  const kbExists = !!(kbPath && fs.existsSync(kbPath));
1663
1871
  const kbValid = data.kbLooksValid;
1664
- const gxVersion = readGeneXusVersionFromInstall(gxPath);
1872
+ const gxIdentity = readGeneXusInstallationIdentity(gxPath);
1665
1873
 
1666
1874
  const ok = {
1667
1875
  connected: true,
@@ -1673,7 +1881,9 @@ async function handleWhoami(options, ctx) {
1673
1881
  },
1674
1882
  geneXus: {
1675
1883
  installationPath: gxPath,
1676
- version: gxVersion
1884
+ version: gxIdentity.version,
1885
+ major: gxIdentity.major,
1886
+ detectionSource: gxIdentity.source
1677
1887
  },
1678
1888
  config: {
1679
1889
  path: data.configPath,
@@ -1684,7 +1894,9 @@ async function handleWhoami(options, ctx) {
1684
1894
  const help = [];
1685
1895
  if (!kbExists) help.push('Configured KB path does not exist on disk.');
1686
1896
  if (kbExists && !kbValid) help.push('KB path exists but does not look like a GeneXus KB (no `.gxw` or `KnowledgeBase.Connection`).');
1687
- if (!gxVersion) help.push('Could not read GeneXus version from installation folder (no version.txt detected).');
1897
+ if (!gxIdentity.version && !gxIdentity.major) {
1898
+ help.push('Could not determine the GeneXus major from version metadata or the installation path.');
1899
+ }
1688
1900
 
1689
1901
  return { exitCode: ctx.EXIT_CODES.OK, envelope: { ok, help } };
1690
1902
  }