genexus-mcp 3.0.4 → 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/cli/run.test.js CHANGED
@@ -1,12 +1,23 @@
1
1
  const test = require('node:test');
2
2
  const assert = require('node:assert/strict');
3
3
  const { spawnSync } = require('node:child_process');
4
+ const { Readable, Writable } = require('node:stream');
4
5
  const path = require('node:path');
5
6
  const os = require('node:os');
6
7
  const fs = require('node:fs');
7
8
  const { renderOutput } = require('./lib/output');
8
9
  const { compareSemver, detectInstallMethod, upgradePlanFor } = require('./lib/update-check');
9
- const { detectClientInstalled, readJsonFileSafe, getLauncher } = require('./lib/config');
10
+ const {
11
+ detectClientInstalled,
12
+ readJsonFileSafe,
13
+ getLauncher,
14
+ getGeneXusMajor,
15
+ getGeneXusCatalogEntries,
16
+ readGeneXusInstallationIdentity,
17
+ readGeneXusKbIdentity,
18
+ compareGeneXusKbAndInstallation
19
+ } = require('./lib/config');
20
+ const { handleInit } = require('./commands/axi');
10
21
 
11
22
  const cliPath = path.join(__dirname, 'run.js');
12
23
  const testGxPath = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-gx-'));
@@ -15,11 +26,12 @@ const testGatewayEnv = { GENEXUS_MCP_GATEWAY_EXE: process.execPath };
15
26
  test.after(() => fs.rmSync(testGxPath, { recursive: true, force: true }));
16
27
 
17
28
  function runCli(args, opts = {}) {
18
- return spawnSync(process.execPath, [cliPath, ...args], {
29
+ const spawnOptions = {
19
30
  encoding: 'utf8',
20
31
  cwd: opts.cwd || process.cwd(),
21
32
  env: { ...process.env, ...(opts.env || {}) }
22
- });
33
+ };
34
+ return spawnSync(process.execPath, [cliPath, ...args], spawnOptions);
23
35
  }
24
36
 
25
37
  test('status returns structured json envelope with schema version', () => {
@@ -250,6 +262,155 @@ test('init auto-discovers KB from cwd when --kb is omitted', () => {
250
262
  fs.rmSync(tempRoot, { recursive: true, force: true });
251
263
  });
252
264
 
265
+ test('GeneXus installation identity falls back to executable metadata', () => {
266
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-version-'));
267
+ try {
268
+ fs.writeFileSync(path.join(tempRoot, 'GeneXus.exe'), 'not-a-real-executable');
269
+ const identity = readGeneXusInstallationIdentity(tempRoot, {
270
+ readExecutableVersion: () => '18.0.10.184260'
271
+ });
272
+ assert.deepEqual(identity, {
273
+ version: '18.0.10.184260',
274
+ major: '18',
275
+ source: 'executable-metadata'
276
+ });
277
+ } finally {
278
+ fs.rmSync(tempRoot, { recursive: true, force: true });
279
+ }
280
+ });
281
+
282
+ test('GeneXus installation identity ignores an invalid version file when executable metadata is valid', () => {
283
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-invalid-version-'));
284
+ try {
285
+ fs.writeFileSync(path.join(tempRoot, 'version.txt'), 'not-a-version');
286
+ fs.writeFileSync(path.join(tempRoot, 'GeneXus.exe'), 'not-a-real-executable');
287
+ const identity = readGeneXusInstallationIdentity(tempRoot, {
288
+ readExecutableVersion: () => '17.0.11.163677'
289
+ });
290
+ assert.deepEqual(identity, {
291
+ version: '17.0.11.163677',
292
+ major: '17',
293
+ source: 'executable-metadata'
294
+ });
295
+ } finally {
296
+ fs.rmSync(tempRoot, { recursive: true, force: true });
297
+ }
298
+ });
299
+
300
+ test('GeneXus installation identity does not infer a major from a missing folder', () => {
301
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-missing-install-'));
302
+ try {
303
+ const identity = readGeneXusInstallationIdentity(path.join(tempRoot, 'GeneXus17'));
304
+ assert.deepEqual(identity, {
305
+ version: null,
306
+ major: null,
307
+ source: 'unavailable'
308
+ });
309
+ } finally {
310
+ fs.rmSync(tempRoot, { recursive: true, force: true });
311
+ }
312
+ });
313
+
314
+ test('KB identity reads the GeneXus major from its gxw metadata', () => {
315
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-kb-'));
316
+ try {
317
+ fs.writeFileSync(
318
+ path.join(tempRoot, 'KnowledgeBase.gxw'),
319
+ '<KnowledgeBase><FriendlyVersion>17.0.11 U11</FriendlyVersion><VersionNumber>17.0.11.163677</VersionNumber></KnowledgeBase>'
320
+ );
321
+ const identity = readGeneXusKbIdentity(tempRoot);
322
+ assert.equal(identity.version, '17.0.11.163677');
323
+ assert.equal(identity.major, '17');
324
+ assert.equal(identity.source, 'gxw-version');
325
+ } finally {
326
+ fs.rmSync(tempRoot, { recursive: true, force: true });
327
+ }
328
+ });
329
+
330
+ test('KB identity fails closed for malformed gxw metadata', () => {
331
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-kb-malformed-'));
332
+ try {
333
+ fs.writeFileSync(
334
+ path.join(tempRoot, 'KnowledgeBase.gxw'),
335
+ '<KnowledgeBase><VersionNumber>17.0.11.163677</KnowledgeBase>'
336
+ );
337
+ const identity = readGeneXusKbIdentity(tempRoot);
338
+ assert.equal(identity.major, null);
339
+ assert.equal(identity.source, 'unavailable');
340
+ assert.equal(identity.reason, 'malformed-gxw');
341
+ } finally {
342
+ fs.rmSync(tempRoot, { recursive: true, force: true });
343
+ }
344
+ });
345
+
346
+ test('catalog discovery ordering can prefer the KB major', () => {
347
+ assert.equal(getGeneXusMajor('17.0.11.163677'), '17');
348
+ assert.equal(getGeneXusCatalogEntries('17')[0].major, '17');
349
+ assert.equal(getGeneXusCatalogEntries('18')[0].major, '18');
350
+ });
351
+
352
+ test('init rejects a known KB and SDK major mismatch before writing config', () => {
353
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-mismatch-'));
354
+ const kbDir = path.join(tempRoot, 'kb17');
355
+ const gxDir = path.join(tempRoot, 'GeneXus18');
356
+ fs.mkdirSync(kbDir, { recursive: true });
357
+ fs.mkdirSync(gxDir, { recursive: true });
358
+ fs.writeFileSync(
359
+ path.join(kbDir, 'KnowledgeBase.gxw'),
360
+ '<KnowledgeBase><VersionNumber>17.0.11.163677</VersionNumber></KnowledgeBase>'
361
+ );
362
+ fs.writeFileSync(path.join(gxDir, 'GeneXus.exe'), 'not-a-real-executable');
363
+
364
+ try {
365
+ const result = runCli(
366
+ ['init', '--kb', kbDir, '--gx', gxDir, '--no-smoke', '--no-write-clients', '--format', 'json'],
367
+ { env: testGatewayEnv }
368
+ );
369
+ assert.equal(result.status, 1);
370
+ const parsed = JSON.parse(result.stdout);
371
+ assert.equal(parsed.error.code, 'sdk_kb_mismatch');
372
+ assert.equal(fs.existsSync(path.join(kbDir, 'config.json')), false);
373
+ } finally {
374
+ fs.rmSync(tempRoot, { recursive: true, force: true });
375
+ }
376
+ });
377
+
378
+ test('interactive init does not silently choose the primary SDK when KB metadata is unresolved', async () => {
379
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-interactive-unresolved-'));
380
+ const kbDir = path.join(tempRoot, 'kb');
381
+ const gxDir = path.join(tempRoot, 'GeneXus18');
382
+ fs.mkdirSync(kbDir, { recursive: true });
383
+ fs.mkdirSync(gxDir, { recursive: true });
384
+ fs.writeFileSync(path.join(kbDir, 'KnowledgeBase.gxw'), '');
385
+ fs.writeFileSync(path.join(kbDir, 'knowledgebase.connection'), 'connection');
386
+ fs.writeFileSync(path.join(gxDir, 'GeneXus.exe'), 'not-a-real-executable');
387
+
388
+ const previousGeneXusHome = process.env.GENEXUS_HOME;
389
+ process.env.GENEXUS_HOME = gxDir;
390
+ const input = new Readable({ read() { } });
391
+ setTimeout(() => input.push('\n'), 0);
392
+ setTimeout(() => input.push('\n'), 25);
393
+ setTimeout(() => input.push(null), 100);
394
+ try {
395
+ const result = await handleInit(
396
+ { interactive: true, quiet: true },
397
+ {
398
+ cwd: kbDir,
399
+ input,
400
+ stderr: new Writable({ write(_chunk, _encoding, callback) { callback(); } }),
401
+ EXIT_CODES: { OK: 0, ERROR: 1, USAGE: 2 }
402
+ }
403
+ );
404
+ assert.equal(result.exitCode, 1);
405
+ assert.equal(result.envelope.error.code, 'sdk_selection_required');
406
+ assert.equal(fs.existsSync(path.join(kbDir, 'config.json')), false);
407
+ } finally {
408
+ if (previousGeneXusHome === undefined) delete process.env.GENEXUS_HOME;
409
+ else process.env.GENEXUS_HOME = previousGeneXusHome;
410
+ fs.rmSync(tempRoot, { recursive: true, force: true });
411
+ }
412
+ });
413
+
253
414
  test('init fails clearly when paths cannot be auto-discovered', () => {
254
415
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-test-'));
255
416
 
@@ -622,6 +783,75 @@ test('doctor --mcp-smoke adds explicit mcp_smoke check', () => {
622
783
  assert.ok(['pass', 'warn', 'fail'].includes(smoke.status));
623
784
  });
624
785
 
786
+ test('doctor reports a KB and SDK major mismatch', () => {
787
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-doctor-major-'));
788
+ const kbDir = path.join(tempRoot, 'kb17');
789
+ const gxDir = path.join(tempRoot, 'GeneXus18');
790
+ fs.mkdirSync(kbDir, { recursive: true });
791
+ fs.mkdirSync(gxDir, { recursive: true });
792
+ fs.writeFileSync(path.join(kbDir, 'KnowledgeBase.gxw'), '<KnowledgeBase><VersionNumber>17.0.11.163677</VersionNumber></KnowledgeBase>');
793
+ fs.writeFileSync(path.join(gxDir, 'GeneXus.exe'), 'not-a-real-executable');
794
+ const configPath = path.join(tempRoot, 'config.json');
795
+ fs.writeFileSync(configPath, JSON.stringify({
796
+ GeneXus: { InstallationPath: gxDir },
797
+ Environment: { KBPath: kbDir }
798
+ }));
799
+
800
+ try {
801
+ const compatibility = compareGeneXusKbAndInstallation(kbDir, gxDir);
802
+ assert.equal(compatibility.status, 'mismatch');
803
+ const result = runCli(['doctor', '--format', 'json'], {
804
+ env: {
805
+ GX_CONFIG_PATH: configPath,
806
+ GENEXUS_MCP_GATEWAY_EXE: process.execPath,
807
+ LOCALAPPDATA: tempRoot
808
+ }
809
+ });
810
+ assert.equal(result.status, 0);
811
+ const parsed = JSON.parse(result.stdout);
812
+ const check = parsed.ok.checks.find((row) => row.id === 'kb_sdk_compatibility');
813
+ assert.ok(check);
814
+ assert.equal(check.status, 'fail');
815
+ assert.match(check.detail, /KB major 17.*SDK major 18/);
816
+ } finally {
817
+ fs.rmSync(tempRoot, { recursive: true, force: true });
818
+ }
819
+ });
820
+
821
+ test('doctor rejects a KB and SDK major that is outside the compatibility catalog', () => {
822
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-doctor-unsupported-major-'));
823
+ const kbDir = path.join(tempRoot, 'kb19');
824
+ const gxDir = path.join(tempRoot, 'GeneXus19');
825
+ fs.mkdirSync(kbDir, { recursive: true });
826
+ fs.mkdirSync(gxDir, { recursive: true });
827
+ fs.writeFileSync(path.join(kbDir, 'KnowledgeBase.gxw'), '<KnowledgeBase><VersionNumber>19.0.0.0</VersionNumber></KnowledgeBase>');
828
+ fs.writeFileSync(path.join(gxDir, 'GeneXus.exe'), 'not-a-real-executable');
829
+ const configPath = path.join(tempRoot, 'config.json');
830
+ fs.writeFileSync(configPath, JSON.stringify({
831
+ GeneXus: { InstallationPath: gxDir },
832
+ Environment: { KBPath: kbDir }
833
+ }));
834
+
835
+ try {
836
+ const result = runCli(['doctor', '--format', 'json'], {
837
+ env: {
838
+ GX_CONFIG_PATH: configPath,
839
+ GENEXUS_MCP_GATEWAY_EXE: process.execPath,
840
+ LOCALAPPDATA: tempRoot
841
+ }
842
+ });
843
+ assert.equal(result.status, 0);
844
+ const parsed = JSON.parse(result.stdout);
845
+ const check = parsed.ok.checks.find((row) => row.id === 'kb_sdk_compatibility');
846
+ assert.ok(check);
847
+ assert.equal(check.status, 'fail');
848
+ assert.match(check.detail, /KB major 19 is not supported/);
849
+ assert.match(check.detail, /Supported majors: 17, 18/);
850
+ } finally {
851
+ fs.rmSync(tempRoot, { recursive: true, force: true });
852
+ }
853
+ });
854
+
625
855
  test('invalid format returns usage exit code 2', () => {
626
856
  const result = runCli(['status', '--format', 'yaml']);
627
857
  assert.equal(result.status, 2);
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "genexus-mcp/version-catalog/1",
3
+ "primaryMajor": "18",
4
+ "supportedMajors": [
5
+ {
6
+ "major": "17",
7
+ "displayName": "GeneXus 17",
8
+ "defaultInstallPath": "C:\\Program Files (x86)\\GeneXus\\GeneXus17Trial",
9
+ "registryNames": ["GeneXus 17", "GeneXus 17 Trial"],
10
+ "legacyRegistryVersions": ["17.0"]
11
+ },
12
+ {
13
+ "major": "18",
14
+ "displayName": "GeneXus 18",
15
+ "defaultInstallPath": "C:\\Program Files (x86)\\GeneXus\\GeneXus18",
16
+ "registryNames": ["GeneXus 18"],
17
+ "legacyRegistryVersions": ["18.0"]
18
+ }
19
+ ]
20
+ }
@@ -20,6 +20,13 @@ Use MCP when:
20
20
  - You need long-running operation tracking (`genexus_lifecycle` + `op:<operationId>`).
21
21
  - You are inside an MCP-native client loop.
22
22
 
23
+ ## Official Nexa grounding
24
+
25
+ Before modeling objects, changing properties, or using Object Text workflows,
26
+ read `genexus://kb/skills/nexa` and then the narrow reference selected from
27
+ `genexus://kb/skills/nexa/references/{name}`. Treat Nexa as design guidance and
28
+ confirm the final object state and supported behavior with the live KB tools.
29
+
23
30
  ## AXI CLI Contract (LLM-facing)
24
31
 
25
32
  Entry points:
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "genexus-mcp",
3
- "version": "3.0.4",
3
+ "version": "3.2.0",
4
4
  "mcpName": "io.github.lennix1337/genexus",
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.",
5
+ "description": "GeneXus 17, GeneXus 18 MCP server — read, edit, and analyze Knowledge Base objects directly from Claude, Cursor, and other AI agents over the Model Context Protocol.",
6
6
  "keywords": [
7
7
  "mcp",
8
8
  "mcp-server",
9
9
  "model-context-protocol",
10
10
  "genexus",
11
+ "genexus-17",
12
+ "genexus17",
11
13
  "genexus-18",
12
14
  "genexus18",
13
15
  "claude",
@@ -41,6 +43,7 @@
41
43
  },
42
44
  "files": [
43
45
  "cli",
46
+ "config/gx-versions.json",
44
47
  "publish",
45
48
  "docs/llm_cli_mcp_playbook.md",
46
49
  "scripts/verify-install.js",
@@ -7,7 +7,7 @@
7
7
  "targets": {
8
8
  ".NETCoreApp,Version=v10.0": {},
9
9
  ".NETCoreApp,Version=v10.0/win-x64": {
10
- "GxMcp.Gateway/3.0.4": {
10
+ "GxMcp.Gateway/3.2.0": {
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/3.0.4": {
69
+ "GxMcp.Gateway/3.2.0": {
70
70
  "type": "project",
71
71
  "serviceable": false,
72
72
  "sha512": ""
Binary file
Binary file
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "genexus-mcp/version-catalog/1",
3
+ "primaryMajor": "18",
4
+ "supportedMajors": [
5
+ {
6
+ "major": "17",
7
+ "displayName": "GeneXus 17",
8
+ "defaultInstallPath": "C:\\Program Files (x86)\\GeneXus\\GeneXus17Trial",
9
+ "registryNames": ["GeneXus 17", "GeneXus 17 Trial"],
10
+ "legacyRegistryVersions": ["17.0"]
11
+ },
12
+ {
13
+ "major": "18",
14
+ "displayName": "GeneXus 18",
15
+ "defaultInstallPath": "C:\\Program Files (x86)\\GeneXus\\GeneXus18",
16
+ "registryNames": ["GeneXus 18"],
17
+ "legacyRegistryVersions": ["18.0"]
18
+ }
19
+ ]
20
+ }
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "GeneXus": {
3
- "InstallationPath": "C:\\\\Program Files (x86)\\\\GeneXus\\\\GeneXus18",
3
+ "InstallationPath": "C:\\Program Files (x86)\\GeneXus\\GeneXus18",
4
4
  "WorkerExecutable": "worker\\\\GxMcp.Worker.exe"
5
5
  },
6
6
  "Server": {
7
- "HttpPort": 5000,
8
- "McpStdio": true
7
+ "McpStdio": true,
8
+ "HttpPort": 5000
9
9
  },
10
10
  "Logging": {
11
11
  "Level": "Debug",
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": "gxmcp-release-manifest/1",
3
- "version": "3.0.4",
4
- "sourceCommit": "158935499b117726db1688bf29217a4b3f508bad",
3
+ "version": "3.2.0",
4
+ "sourceCommit": "d0ed6ff43d48cc5d62ba7e09f36f28477d161710",
5
5
  "sourceCommitPolicy": "exact-tag",
6
- "generatedAtUtc": "2026-09-08T18:47:30.9019243Z",
6
+ "generatedAtUtc": "2026-09-09T16:51:27.9077164Z",
7
7
  "runtime": {
8
8
  "gateway": "net10.0-windows",
9
9
  "worker": "net48-x86",
@@ -15,33 +15,33 @@
15
15
  "2026-07-28"
16
16
  ],
17
17
  "schema": "tool_definitions.json",
18
- "schemaSha256": "df37664020c005ee75c7fa9b43b1546d90cc92f2ea8a42c236b4ccb999c32afd",
18
+ "schemaSha256": "e4a3043ade9e565599dabed109ade92db73360c02bc5ee0d28f21eec25d89d68",
19
19
  "provenance": "gxmcp-sbom.json",
20
20
  "artifacts": [
21
21
  {
22
22
  "path": "gxmcp-sbom.json",
23
23
  "size": 590,
24
- "sha256": "9b0b60c9e1cab98d49e83ac9282cf10fb1caf0a6e59bcb4fdd7bc92597c6bbce"
24
+ "sha256": "02cdc88c7dff83608a1f157be94f45b914a6b7d319bd701d4ce54a4d621a6e71"
25
25
  },
26
26
  {
27
27
  "path": "GxMcp.Gateway.exe",
28
28
  "size": 162304,
29
- "sha256": "15dd3003740a645a902af39c0f37a51c6bffb54ff4edf7f04a41473923cda952"
29
+ "sha256": "f15a4622bc1b0b552a480964e32b1defee7fa240d1ded2bb27852dd44a645977"
30
30
  },
31
31
  {
32
32
  "path": "nexus-ide.vsix",
33
33
  "size": 1318667,
34
- "sha256": "521d63c951e129104a72138913fe3008305648d272fc66d9fb2a159b8a6c15f6"
34
+ "sha256": "e665685cd085a7d5af4c23e7704371167a02ba0ae6a2ed74a5d6681be016e44e"
35
35
  },
36
36
  {
37
37
  "path": "tool_definitions.json",
38
- "size": 105044,
39
- "sha256": "df37664020c005ee75c7fa9b43b1546d90cc92f2ea8a42c236b4ccb999c32afd"
38
+ "size": 106423,
39
+ "sha256": "e4a3043ade9e565599dabed109ade92db73360c02bc5ee0d28f21eec25d89d68"
40
40
  },
41
41
  {
42
42
  "path": "worker/GxMcp.Worker.exe",
43
- "size": 2859520,
44
- "sha256": "9050b618d876dcafd24996c4da3a59e59cfca21564ff00397e8811636e327a7a"
43
+ "size": 2926080,
44
+ "sha256": "903e41245f740f88a6d3e6f8ada00bd82a18b6ea07259ebb7dd9dbe90b1a550e"
45
45
  }
46
46
  ]
47
47
  }
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "schemaVersion": "gxmcp-provenance/1",
3
- "version": "3.0.4",
4
- "sourceCommit": "158935499b117726db1688bf29217a4b3f508bad",
3
+ "version": "3.2.0",
4
+ "sourceCommit": "d0ed6ff43d48cc5d62ba7e09f36f28477d161710",
5
5
  "sourceCommitPolicy": "exact-tag",
6
6
  "components": [
7
7
  {
8
8
  "name": "genexus-mcp",
9
9
  "lockfile": "package-lock.json",
10
10
  "size": 39914,
11
- "sha256": "e95fb66231b2e8185448750b16795432a9d49c3e96d85edf457a3e58c91ad49e"
11
+ "sha256": "4ddd59d46b934fafe177482aa8d769b0cd041a7f0205cd6cc52d6fe3a825de88"
12
12
  },
13
13
  {
14
14
  "name": "nexus-ide",
15
15
  "lockfile": "src/nexus-ide/package-lock.json",
16
16
  "size": 274983,
17
- "sha256": "577c995d86ffca4a3c809423250a870b9266fc4d59ee05ef64e4c12aa5590d81"
17
+ "sha256": "000a3e5e18fe604470bb99fe853acecfbf92712b98d74c7e6f379e0593ded562"
18
18
  }
19
19
  ]
20
20
  }
Binary file