genexus-mcp 3.4.3 → 3.5.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/run.test.js CHANGED
@@ -39,9 +39,33 @@ if (process.platform === 'win32') {
39
39
  fs.chmodSync(testGatewayPath, 0o755);
40
40
  }
41
41
  const testGatewayEnv = { GENEXUS_MCP_GATEWAY_EXE: testGatewayPath };
42
+
43
+ const RETRYABLE_REMOVE_CODES = new Set(['EPERM', 'EBUSY', 'EACCES']);
44
+ function waitForRemoveRetry(delayMs) {
45
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delayMs);
46
+ }
47
+
48
+ function removeTempPath(targetPath, options = {}, deps = {}) {
49
+ const fsImpl = deps.fsImpl || fs;
50
+ const platform = deps.platform || process.platform;
51
+ const sleep = deps.sleep || waitForRemoveRetry;
52
+ const maxAttempts = platform === 'win32' ? 5 : 1;
53
+
54
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
55
+ try {
56
+ fsImpl.rmSync(targetPath, options);
57
+ return;
58
+ } catch (error) {
59
+ const retryable = RETRYABLE_REMOVE_CODES.has(error?.code);
60
+ if (!retryable || attempt === maxAttempts) throw error;
61
+ sleep(10 * (2 ** (attempt - 1)));
62
+ }
63
+ }
64
+ }
65
+
42
66
  test.after(() => {
43
- fs.rmSync(testGxPath, { recursive: true, force: true });
44
- fs.rmSync(testGatewayPath, { force: true });
67
+ removeTempPath(testGxPath, { recursive: true, force: true });
68
+ removeTempPath(testGatewayPath, { force: true });
45
69
  });
46
70
 
47
71
  function runCli(args, opts = {}) {
@@ -53,6 +77,37 @@ function runCli(args, opts = {}) {
53
77
  return spawnSync(process.execPath, [cliPath, ...args], spawnOptions);
54
78
  }
55
79
 
80
+ test('temporary cleanup retries transient Windows removal errors', () => {
81
+ let attempts = 0;
82
+ const sleeps = [];
83
+ const fakeFs = {
84
+ rmSync() {
85
+ attempts += 1;
86
+ if (attempts < 3) {
87
+ const error = new Error('file is still in use');
88
+ error.code = 'EPERM';
89
+ throw error;
90
+ }
91
+ }
92
+ };
93
+
94
+ removeTempPath('fixture', { recursive: true, force: true }, {
95
+ fsImpl: fakeFs,
96
+ platform: 'win32',
97
+ sleep: (delayMs) => sleeps.push(delayMs)
98
+ });
99
+
100
+ assert.equal(attempts, 3);
101
+ assert.deepEqual(sleeps, [10, 20]);
102
+
103
+ const permanentError = Object.assign(new Error('invalid path'), { code: 'EINVAL' });
104
+ assert.throws(() => removeTempPath('fixture', {}, {
105
+ fsImpl: { rmSync: () => { throw permanentError; } },
106
+ platform: 'win32',
107
+ sleep: () => assert.fail('non-retryable cleanup errors must not sleep')
108
+ }), permanentError);
109
+ });
110
+
56
111
  test('status returns structured json envelope with schema version', () => {
57
112
  const result = runCli(['status', '--format', 'json']);
58
113
  assert.equal(result.status, 0);
@@ -193,7 +248,7 @@ test('non-interactive init supports idempotent no-op', () => {
193
248
  const cfgPath = path.join(kbDir, 'config.json');
194
249
  assert.equal(fs.existsSync(cfgPath), true);
195
250
 
196
- fs.rmSync(tempRoot, { recursive: true, force: true });
251
+ removeTempPath(tempRoot, { recursive: true, force: true });
197
252
  });
198
253
 
199
254
  test('config create creates explicit neutral runtime without KB or client registration', () => {
@@ -214,7 +269,7 @@ test('config create creates explicit neutral runtime without KB or client regist
214
269
  assert.equal(parsed.meta.clientRegistration, 'not_attempted');
215
270
  assert.equal(parsed.meta.kbCatalog, 'not_created');
216
271
  assert.deepEqual(JSON.parse(fs.readFileSync(output, 'utf8')), parsed.ok.config);
217
- fs.rmSync(tempRoot, { recursive: true, force: true });
272
+ removeTempPath(tempRoot, { recursive: true, force: true });
218
273
  });
219
274
 
220
275
  test('config create rejects missing new-format flags', () => {
@@ -254,7 +309,7 @@ test('config create does not modify client registration', () => {
254
309
  assert.equal(result.status, 0);
255
310
  assert.equal(fs.readFileSync(marker, 'utf8'), before);
256
311
  assert.deepEqual(JSON.parse(result.stdout).ok.config, JSON.parse(fs.readFileSync(output, 'utf8')));
257
- fs.rmSync(tempRoot, { recursive: true, force: true });
312
+ removeTempPath(tempRoot, { recursive: true, force: true });
258
313
  });
259
314
  test('config migrate creates a neutral config with an atomic backup and read-back receipt', () => {
260
315
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-migrate-'));
@@ -277,7 +332,7 @@ test('config migrate creates a neutral config with an atomic backup and read-bac
277
332
  assert.equal(JSON.parse(fs.readFileSync(target, 'utf8')).ConfigSchemaVersion, 2);
278
333
  assert.deepEqual(parsed.ok.notMigrated, ['Environment.KBPath', 'Environment.KBs', 'Environment.DefaultKb']);
279
334
  } finally {
280
- fs.rmSync(tempRoot, { recursive: true, force: true });
335
+ removeTempPath(tempRoot, { recursive: true, force: true });
281
336
  }
282
337
  });
283
338
 
@@ -299,7 +354,7 @@ test('config migrate rolls back an existing destination when read-back fails', (
299
354
  assert.equal(fs.readFileSync(target, 'utf8'), original);
300
355
  assert.equal(parsed.error.rollback.rolledBack, true);
301
356
  } finally {
302
- fs.rmSync(tempRoot, { recursive: true, force: true });
357
+ removeTempPath(tempRoot, { recursive: true, force: true });
303
358
  }
304
359
  });
305
360
 
@@ -315,7 +370,7 @@ test('config migrate rejects non-migratable fields when explicitly requested', (
315
370
  assert.match(parsed.error.message, /non-migratable/i);
316
371
  assert.equal(fs.existsSync(target), false);
317
372
  } finally {
318
- fs.rmSync(tempRoot, { recursive: true, force: true });
373
+ removeTempPath(tempRoot, { recursive: true, force: true });
319
374
  }
320
375
  });
321
376
 
@@ -326,7 +381,7 @@ test('whoami without config returns disconnected state', () => {
326
381
  const parsed = JSON.parse(res.stdout);
327
382
  assert.equal(parsed.ok.connected, false);
328
383
  assert.ok(parsed.ok.reason, 'should explain why not connected');
329
- fs.rmSync(tempRoot, { recursive: true, force: true });
384
+ removeTempPath(tempRoot, { recursive: true, force: true });
330
385
  });
331
386
 
332
387
  test('whoami with config returns kb and geneXus details', () => {
@@ -345,7 +400,7 @@ test('whoami with config returns kb and geneXus details', () => {
345
400
  assert.equal(parsed.ok.geneXus.installationPath, testGxPath);
346
401
  assert.equal(parsed.meta.command, 'whoami');
347
402
 
348
- fs.rmSync(tempRoot, { recursive: true, force: true });
403
+ removeTempPath(tempRoot, { recursive: true, force: true });
349
404
  });
350
405
 
351
406
  test('uninstall --yes removes local config and reports plan', () => {
@@ -366,7 +421,7 @@ test('uninstall --yes removes local config and reports plan', () => {
366
421
  assert.equal(parsed.ok.configRemoved, true);
367
422
  assert.equal(fs.existsSync(cfgPath), false, 'config.json should be deleted');
368
423
 
369
- fs.rmSync(tempRoot, { recursive: true, force: true });
424
+ removeTempPath(tempRoot, { recursive: true, force: true });
370
425
  });
371
426
 
372
427
  test('uninstall --help returns usage entry', () => {
@@ -401,7 +456,7 @@ test('init auto-discovers KB from cwd when --kb is omitted', () => {
401
456
  assert.equal(parsed.ok.resolved.kb.source, 'cwd');
402
457
  assert.equal(parsed.ok.resolved.gx.source, 'flag');
403
458
 
404
- fs.rmSync(tempRoot, { recursive: true, force: true });
459
+ removeTempPath(tempRoot, { recursive: true, force: true });
405
460
  });
406
461
 
407
462
  test('GeneXus installation identity falls back to executable metadata', () => {
@@ -417,7 +472,7 @@ test('GeneXus installation identity falls back to executable metadata', () => {
417
472
  source: 'executable-metadata'
418
473
  });
419
474
  } finally {
420
- fs.rmSync(tempRoot, { recursive: true, force: true });
475
+ removeTempPath(tempRoot, { recursive: true, force: true });
421
476
  }
422
477
  });
423
478
 
@@ -435,7 +490,7 @@ test('GeneXus installation identity ignores an invalid version file when executa
435
490
  source: 'executable-metadata'
436
491
  });
437
492
  } finally {
438
- fs.rmSync(tempRoot, { recursive: true, force: true });
493
+ removeTempPath(tempRoot, { recursive: true, force: true });
439
494
  }
440
495
  });
441
496
 
@@ -449,7 +504,7 @@ test('GeneXus installation identity does not infer a major from a missing folder
449
504
  source: 'unavailable'
450
505
  });
451
506
  } finally {
452
- fs.rmSync(tempRoot, { recursive: true, force: true });
507
+ removeTempPath(tempRoot, { recursive: true, force: true });
453
508
  }
454
509
  });
455
510
 
@@ -465,7 +520,7 @@ test('KB identity reads the GeneXus major from its gxw metadata', () => {
465
520
  assert.equal(identity.major, '17');
466
521
  assert.equal(identity.source, 'gxw-version');
467
522
  } finally {
468
- fs.rmSync(tempRoot, { recursive: true, force: true });
523
+ removeTempPath(tempRoot, { recursive: true, force: true });
469
524
  }
470
525
  });
471
526
 
@@ -481,7 +536,7 @@ test('KB identity fails closed for malformed gxw metadata', () => {
481
536
  assert.equal(identity.source, 'unavailable');
482
537
  assert.equal(identity.reason, 'malformed-gxw');
483
538
  } finally {
484
- fs.rmSync(tempRoot, { recursive: true, force: true });
539
+ removeTempPath(tempRoot, { recursive: true, force: true });
485
540
  }
486
541
  });
487
542
 
@@ -513,7 +568,7 @@ test('init rejects a known KB and SDK major mismatch before writing config', ()
513
568
  assert.equal(parsed.error.code, 'sdk_kb_mismatch');
514
569
  assert.equal(fs.existsSync(path.join(kbDir, 'config.json')), false);
515
570
  } finally {
516
- fs.rmSync(tempRoot, { recursive: true, force: true });
571
+ removeTempPath(tempRoot, { recursive: true, force: true });
517
572
  }
518
573
  });
519
574
 
@@ -549,7 +604,7 @@ test('interactive init does not silently choose the primary SDK when KB metadata
549
604
  } finally {
550
605
  if (previousGeneXusHome === undefined) delete process.env.GENEXUS_HOME;
551
606
  else process.env.GENEXUS_HOME = previousGeneXusHome;
552
- fs.rmSync(tempRoot, { recursive: true, force: true });
607
+ removeTempPath(tempRoot, { recursive: true, force: true });
553
608
  }
554
609
  });
555
610
 
@@ -566,7 +621,7 @@ test('init fails clearly when paths cannot be auto-discovered', () => {
566
621
  assert.equal(parsed.error.code, 'usage_error');
567
622
  assert.ok(parsed.error.message.includes('--kb'), 'error should mention --kb');
568
623
 
569
- fs.rmSync(tempRoot, { recursive: true, force: true });
624
+ removeTempPath(tempRoot, { recursive: true, force: true });
570
625
  });
571
626
 
572
627
  test('kb list shows the KB auto-registered by init', () => {
@@ -585,7 +640,7 @@ test('kb list shows the KB auto-registered by init', () => {
585
640
  assert.equal(parsed.ok.kbs[0].active, true);
586
641
  assert.equal(parsed.ok.kbs[0].path, kbDir);
587
642
 
588
- fs.rmSync(tempRoot, { recursive: true, force: true });
643
+ removeTempPath(tempRoot, { recursive: true, force: true });
589
644
  });
590
645
 
591
646
  test('kb list reads gateway-style KB arrays and DefaultKb', () => {
@@ -611,7 +666,7 @@ test('kb list reads gateway-style KB arrays and DefaultKb', () => {
611
666
  assert.deepEqual(parsed.ok.kbs.map((entry) => entry.name), ['main', 'legacy']);
612
667
  assert.equal(parsed.ok.kbs[1].active, true);
613
668
 
614
- fs.rmSync(tempRoot, { recursive: true, force: true });
669
+ removeTempPath(tempRoot, { recursive: true, force: true });
615
670
  });
616
671
 
617
672
  test('kb switch preserves gateway-style array entries', () => {
@@ -640,7 +695,7 @@ test('kb switch preserves gateway-style array entries', () => {
640
695
  assert.equal(cfg.Environment.ActiveKb, 'legacy');
641
696
  assert.equal(cfg.Environment.DefaultKb, 'legacy');
642
697
 
643
- fs.rmSync(tempRoot, { recursive: true, force: true });
698
+ removeTempPath(tempRoot, { recursive: true, force: true });
644
699
  });
645
700
 
646
701
  test('kb add and switch update active KB', () => {
@@ -668,7 +723,7 @@ test('kb add and switch update active KB', () => {
668
723
  assert.equal(cfg.Environment.KBPath, kbB, 'legacy KBPath should be updated');
669
724
  assert.equal(cfg.Environment.ActiveKb, 'bravo');
670
725
 
671
- fs.rmSync(tempRoot, { recursive: true, force: true });
726
+ removeTempPath(tempRoot, { recursive: true, force: true });
672
727
  });
673
728
 
674
729
  test('kb switch rejects unknown name', () => {
@@ -684,7 +739,7 @@ test('kb switch rejects unknown name', () => {
684
739
  assert.equal(parsed.error.code, 'usage_error');
685
740
  assert.ok(parsed.error.message.includes('nonexistent'));
686
741
 
687
- fs.rmSync(tempRoot, { recursive: true, force: true });
742
+ removeTempPath(tempRoot, { recursive: true, force: true });
688
743
  });
689
744
 
690
745
  test('kb remove deletes entry and reassigns active when applicable', () => {
@@ -703,7 +758,7 @@ test('kb remove deletes entry and reassigns active when applicable', () => {
703
758
  assert.equal(parsed.ok.removed, true);
704
759
  assert.equal(parsed.ok.activeKb, 'second', 'active should fall back to remaining KB');
705
760
 
706
- fs.rmSync(tempRoot, { recursive: true, force: true });
761
+ removeTempPath(tempRoot, { recursive: true, force: true });
707
762
  });
708
763
 
709
764
  test('kb switch --kb refuses to overwrite existing entry with different path', () => {
@@ -723,7 +778,7 @@ test('kb switch --kb refuses to overwrite existing entry with different path', (
723
778
  const cfg = JSON.parse(fs.readFileSync(path.join(kbA, 'config.json'), 'utf8'));
724
779
  assert.equal(cfg.Environment.KBs.Sales, kbA, 'original entry must be preserved');
725
780
 
726
- fs.rmSync(tempRoot, { recursive: true, force: true });
781
+ removeTempPath(tempRoot, { recursive: true, force: true });
727
782
  });
728
783
 
729
784
  test('kb remove of last KB clears legacy KBPath', () => {
@@ -740,7 +795,7 @@ test('kb remove of last KB clears legacy KBPath', () => {
740
795
  assert.equal(cfg.Environment.ActiveKb, undefined, 'ActiveKb should be cleared');
741
796
  assert.equal(cfg.Environment.DefaultKb, undefined, 'DefaultKb should be cleared');
742
797
 
743
- fs.rmSync(tempRoot, { recursive: true, force: true });
798
+ removeTempPath(tempRoot, { recursive: true, force: true });
744
799
  });
745
800
 
746
801
  test('kb subcommand validation: missing subcommand returns usage error', () => {
@@ -755,7 +810,7 @@ test('kb subcommand validation: missing subcommand returns usage error', () => {
755
810
  const parsed = JSON.parse(res.stdout);
756
811
  assert.equal(parsed.error.code, 'usage_error');
757
812
 
758
- fs.rmSync(tempRoot, { recursive: true, force: true });
813
+ removeTempPath(tempRoot, { recursive: true, force: true });
759
814
  });
760
815
 
761
816
  test('tool_definitions.json is valid and disambiguation tools have use-when guidance', () => {
@@ -850,7 +905,7 @@ test('config show truncates large raw content and suggests --full', () => {
850
905
  assert.equal(parsed.meta.truncated, true);
851
906
  assert.ok(parsed.help.some((h) => h.includes('--full')));
852
907
 
853
- fs.rmSync(tempRoot, { recursive: true, force: true });
908
+ removeTempPath(tempRoot, { recursive: true, force: true });
854
909
  });
855
910
 
856
911
  test('config show suppresses truncation hint when raw field is not requested', () => {
@@ -876,7 +931,7 @@ test('config show suppresses truncation hint when raw field is not requested', (
876
931
  assert.equal(parsed.meta.truncated, false);
877
932
  assert.equal(parsed.help.length, 0);
878
933
 
879
- fs.rmSync(tempRoot, { recursive: true, force: true });
934
+ removeTempPath(tempRoot, { recursive: true, force: true });
880
935
  });
881
936
 
882
937
  test('--fields validation returns usage error for invalid doctor field', () => {
@@ -940,7 +995,7 @@ test('doctor marks HTTP smoke not applicable for stdio-isolated runtime', () =>
940
995
  assert.equal(target.baseUrl, null);
941
996
  assert.match(target.detail, /stdio-isolated|HTTP listener is disabled/);
942
997
  } finally {
943
- fs.rmSync(tempRoot, { recursive: true, force: true });
998
+ removeTempPath(tempRoot, { recursive: true, force: true });
944
999
  }
945
1000
  });
946
1001
 
@@ -975,7 +1030,7 @@ test('doctor reports a KB and SDK major mismatch', () => {
975
1030
  assert.equal(check.status, 'fail');
976
1031
  assert.match(check.detail, /KB major 17.*SDK major 18/);
977
1032
  } finally {
978
- fs.rmSync(tempRoot, { recursive: true, force: true });
1033
+ removeTempPath(tempRoot, { recursive: true, force: true });
979
1034
  }
980
1035
  });
981
1036
 
@@ -1009,7 +1064,7 @@ test('doctor rejects a KB and SDK major that is outside the compatibility catalo
1009
1064
  assert.match(check.detail, /KB major 19 is not supported/);
1010
1065
  assert.match(check.detail, /Supported majors: 17, 18/);
1011
1066
  } finally {
1012
- fs.rmSync(tempRoot, { recursive: true, force: true });
1067
+ removeTempPath(tempRoot, { recursive: true, force: true });
1013
1068
  }
1014
1069
  });
1015
1070
 
@@ -1043,7 +1098,7 @@ test('quiet flag suppresses launcher stderr noise', () => {
1043
1098
  assert.equal(result.status, 1);
1044
1099
  assert.equal(result.stderr.trim(), '');
1045
1100
  } finally {
1046
- fs.rmSync(tempRoot, { recursive: true, force: true });
1101
+ removeTempPath(tempRoot, { recursive: true, force: true });
1047
1102
  }
1048
1103
  });
1049
1104
 
@@ -1070,7 +1125,7 @@ test('stdio launcher writes a breadcrumb when the gateway is missing before spaw
1070
1125
  assert.match(log, /Gateway executable not found/);
1071
1126
  assert.match(log, /missing[\\/]GxMcp\.Gateway\.exe/);
1072
1127
  } finally {
1073
- fs.rmSync(tempRoot, { recursive: true, force: true });
1128
+ removeTempPath(tempRoot, { recursive: true, force: true });
1074
1129
  }
1075
1130
  });
1076
1131
 
@@ -1091,7 +1146,7 @@ test('doctor surfaces the stdio error breadcrumb when one exists', () => {
1091
1146
  assert.equal(check.status, 'warn');
1092
1147
  assert.ok(check.detail.includes(logPath));
1093
1148
  } finally {
1094
- fs.rmSync(tempRoot, { recursive: true, force: true });
1149
+ removeTempPath(tempRoot, { recursive: true, force: true });
1095
1150
  }
1096
1151
  });
1097
1152
 
@@ -1124,7 +1179,7 @@ test('detectClientInstalled flags an agent installed via marker even with no MCP
1124
1179
  assert.equal(det.hasConfig, false, 'config file does not exist yet');
1125
1180
  assert.equal(det.markerHit, installDir);
1126
1181
 
1127
- fs.rmSync(tempRoot, { recursive: true, force: true });
1182
+ removeTempPath(tempRoot, { recursive: true, force: true });
1128
1183
  });
1129
1184
 
1130
1185
  test('detectClientInstalled reports not-installed and lists checked paths', () => {
@@ -1140,7 +1195,7 @@ test('detectClientInstalled reports not-installed and lists checked paths', () =
1140
1195
  assert.equal(det.markerHit, null);
1141
1196
  assert.deepEqual(det.markersChecked, client.installMarkers);
1142
1197
 
1143
- fs.rmSync(tempRoot, { recursive: true, force: true });
1198
+ removeTempPath(tempRoot, { recursive: true, force: true });
1144
1199
  });
1145
1200
 
1146
1201
  test('detectClientInstalled treats an existing config file as installed', () => {
@@ -1153,7 +1208,7 @@ test('detectClientInstalled treats an existing config file as installed', () =>
1153
1208
  assert.equal(det.installed, true);
1154
1209
  assert.equal(det.hasConfig, true);
1155
1210
 
1156
- fs.rmSync(tempRoot, { recursive: true, force: true });
1211
+ removeTempPath(tempRoot, { recursive: true, force: true });
1157
1212
  });
1158
1213
 
1159
1214
  // Build a throwaway HOME so client-config writes never touch the real machine.
@@ -1222,7 +1277,7 @@ test('clients list reports OpenCode Desktop with shared opencode config', () =>
1222
1277
  assert.equal(rowRegistered.configPath, opencodeCfg);
1223
1278
  assert.equal(rowRegistered.registrationMode, 'automatic');
1224
1279
  } finally {
1225
- fs.rmSync(tempRoot, { recursive: true, force: true });
1280
+ removeTempPath(tempRoot, { recursive: true, force: true });
1226
1281
  }
1227
1282
  });
1228
1283
 
@@ -1251,7 +1306,7 @@ test('clients add patches OpenCode Desktop into shared opencode config', () => {
1251
1306
  const row = JSON.parse(listRes.stdout).ok.clients.find((client) => client.id === 'opencode-desktop');
1252
1307
  assert.ok(row && row.registered, 'OpenCode Desktop should now report registered');
1253
1308
  } finally {
1254
- fs.rmSync(tempRoot, { recursive: true, force: true });
1309
+ removeTempPath(tempRoot, { recursive: true, force: true });
1255
1310
  }
1256
1311
  });
1257
1312
 
@@ -1274,7 +1329,7 @@ test('OpenCode Desktop is not falsely reported as installed when only CLI config
1274
1329
  assert.ok(cliRow, 'OpenCode CLI should be present in targets');
1275
1330
  assert.equal(cliRow.installed, true, 'OpenCode CLI is detected installed via config file');
1276
1331
  } finally {
1277
- fs.rmSync(tempRoot, { recursive: true, force: true });
1332
+ removeTempPath(tempRoot, { recursive: true, force: true });
1278
1333
  }
1279
1334
  });
1280
1335
 
@@ -1299,7 +1354,7 @@ test('clients add still refuses a missing gateway for writable clients', () => {
1299
1354
  assert.match(parsed.error.message, /GATEWAY_EXE_MISSING|does not exist/i);
1300
1355
  assert.equal(fs.existsSync(cursorConfig), false, 'failed validation must not create a client config');
1301
1356
  } finally {
1302
- fs.rmSync(tempRoot, { recursive: true, force: true });
1357
+ removeTempPath(tempRoot, { recursive: true, force: true });
1303
1358
  }
1304
1359
  });
1305
1360
 
@@ -1323,7 +1378,7 @@ test('getLauncher prefers the packaged gateway for Antigravity only', () => {
1323
1378
  } finally {
1324
1379
  if (previous === undefined) delete process.env.GENEXUS_MCP_GATEWAY_EXE;
1325
1380
  else process.env.GENEXUS_MCP_GATEWAY_EXE = previous;
1326
- fs.rmSync(tempRoot, { recursive: true, force: true });
1381
+ removeTempPath(tempRoot, { recursive: true, force: true });
1327
1382
  }
1328
1383
  });
1329
1384
 
@@ -1353,7 +1408,7 @@ test('clients add selects a direct gateway for Antigravity and npx for other cli
1353
1408
  assert.equal(antigravity.command, 'npx.cmd', 'source checkouts without publish artifacts retain the fallback');
1354
1409
  }
1355
1410
  } finally {
1356
- fs.rmSync(tempRoot, { recursive: true, force: true });
1411
+ removeTempPath(tempRoot, { recursive: true, force: true });
1357
1412
  }
1358
1413
  });
1359
1414
 
@@ -1385,7 +1440,7 @@ test('clients add uses the unified Antigravity config when it already exists', (
1385
1440
  }
1386
1441
  assert.equal(fs.existsSync(path.join(tempRoot, '.gemini', 'antigravity', 'mcp_config.json')), false);
1387
1442
  } finally {
1388
- fs.rmSync(tempRoot, { recursive: true, force: true });
1443
+ removeTempPath(tempRoot, { recursive: true, force: true });
1389
1444
  }
1390
1445
  });
1391
1446
 
@@ -1415,7 +1470,7 @@ test('clients add registers a client into a sandbox home with backup + atomic wr
1415
1470
  const baks = fs.readdirSync(path.dirname(cursorCfg)).filter((f) => f.includes('.bak'));
1416
1471
  assert.ok(baks.length >= 1, 'a .bak backup should be created before mutating');
1417
1472
 
1418
- fs.rmSync(tempRoot, { recursive: true, force: true });
1473
+ removeTempPath(tempRoot, { recursive: true, force: true });
1419
1474
  });
1420
1475
 
1421
1476
  test('clients add tolerates a JSONC (commented) VS Code mcp.json', () => {
@@ -1439,7 +1494,7 @@ test('clients add tolerates a JSONC (commented) VS Code mcp.json', () => {
1439
1494
  assert.ok(written.servers.genexus18mcp, 'genexus18mcp server entry written');
1440
1495
  assert.ok(written.servers.foo, 'pre-existing server preserved');
1441
1496
 
1442
- fs.rmSync(tempRoot, { recursive: true, force: true });
1497
+ removeTempPath(tempRoot, { recursive: true, force: true });
1443
1498
  });
1444
1499
 
1445
1500
  test('clients add preserves OpenCode 1.x direct mcp shape', () => {
@@ -1470,7 +1525,7 @@ test('clients add preserves OpenCode 1.x direct mcp shape', () => {
1470
1525
  const row = JSON.parse(listed.stdout).ok.clients.find((client) => client.id === 'opencode');
1471
1526
  assert.ok(row && row.registered, 'clients list should read the direct OpenCode entry');
1472
1527
 
1473
- fs.rmSync(tempRoot, { recursive: true, force: true });
1528
+ removeTempPath(tempRoot, { recursive: true, force: true });
1474
1529
  });
1475
1530
 
1476
1531
  test('clients add preserves OpenCode v2 nested mcp.servers shape', () => {
@@ -1501,7 +1556,7 @@ test('clients add preserves OpenCode v2 nested mcp.servers shape', () => {
1501
1556
  const row = JSON.parse(listed.stdout).ok.clients.find((client) => client.id === 'opencode');
1502
1557
  assert.ok(row && row.registered, 'clients list should read the nested OpenCode entry');
1503
1558
 
1504
- fs.rmSync(tempRoot, { recursive: true, force: true });
1559
+ removeTempPath(tempRoot, { recursive: true, force: true });
1505
1560
  });
1506
1561
 
1507
1562
  test('init auto-registers detected OpenCode in either config layout', () => {
@@ -1534,7 +1589,7 @@ test('init auto-registers detected OpenCode in either config layout', () => {
1534
1589
  assert.equal(entry.environment?.GX_CONFIG_PATH, undefined);
1535
1590
  assert.ok(label === 'nested' ? written.mcp.servers.other : written.mcp.other);
1536
1591
  } finally {
1537
- fs.rmSync(tempRoot, { recursive: true, force: true });
1592
+ removeTempPath(tempRoot, { recursive: true, force: true });
1538
1593
  }
1539
1594
  }
1540
1595
  });
@@ -1559,7 +1614,7 @@ test('init with --global-config persists GX_CONFIG_PATH into client entry', () =
1559
1614
  assert.ok(written.mcp.genexus18mcp);
1560
1615
  assert.equal(written.mcp.genexus18mcp.environment.GX_CONFIG_PATH, path.join(kbDir, 'config.json'));
1561
1616
  } finally {
1562
- fs.rmSync(tempRoot, { recursive: true, force: true });
1617
+ removeTempPath(tempRoot, { recursive: true, force: true });
1563
1618
  }
1564
1619
  });
1565
1620
 
@@ -1581,7 +1636,7 @@ test('clients add with --global-config persists GX_CONFIG_PATH into client entry
1581
1636
  assert.ok(written.mcp.genexus18mcp);
1582
1637
  assert.equal(written.mcp.genexus18mcp.environment.GX_CONFIG_PATH, cfgPath);
1583
1638
  } finally {
1584
- fs.rmSync(tempRoot, { recursive: true, force: true });
1639
+ removeTempPath(tempRoot, { recursive: true, force: true });
1585
1640
  }
1586
1641
  });
1587
1642
 
@@ -1606,7 +1661,7 @@ test('init auto-registers detected OpenCode Desktop when its marker is present',
1606
1661
  const written = JSON.parse(fs.readFileSync(openCodeCfg, 'utf8'));
1607
1662
  assert.ok(written.mcp.genexus18mcp);
1608
1663
  } finally {
1609
- fs.rmSync(tempRoot, { recursive: true, force: true });
1664
+ removeTempPath(tempRoot, { recursive: true, force: true });
1610
1665
  }
1611
1666
  });
1612
1667
 
@@ -1629,7 +1684,7 @@ test('clients add replaces a legacy genexus18 entry instead of duplicating it',
1629
1684
  assert.ok(written.mcpServers.genexus18mcp, 'new genexus18mcp entry present');
1630
1685
  assert.equal(written.mcpServers.genexus18, undefined, 'legacy genexus18 removed (no duplicate)');
1631
1686
 
1632
- fs.rmSync(tempRoot, { recursive: true, force: true });
1687
+ removeTempPath(tempRoot, { recursive: true, force: true });
1633
1688
  });
1634
1689
 
1635
1690
  test('clients list flags a registered command pointing at a missing launcher as stale (.bat too)', () => {
@@ -1649,7 +1704,7 @@ test('clients list flags a registered command pointing at a missing launcher as
1649
1704
  assert.equal(cursor.commandStale, true, 'missing launcher => stale');
1650
1705
  assert.ok(parsed.help.some((h) => h.includes('missing gateway exe')), 'help should call out the stale client');
1651
1706
 
1652
- fs.rmSync(tempRoot, { recursive: true, force: true });
1707
+ removeTempPath(tempRoot, { recursive: true, force: true });
1653
1708
  });
1654
1709
 
1655
1710
  test('clients list separates a registered node launcher without an entrypoint from semantic validity', () => {
@@ -1680,7 +1735,7 @@ test('clients list separates a registered node launcher without an entrypoint fr
1680
1735
  assert.equal(codex.commandStale, true, 'known invalid launcher is actionable through the existing stale flag');
1681
1736
  assert.match(codex.commandStaleReason, /entrypoint/i);
1682
1737
  } finally {
1683
- fs.rmSync(tempRoot, { recursive: true, force: true });
1738
+ removeTempPath(tempRoot, { recursive: true, force: true });
1684
1739
  }
1685
1740
  });
1686
1741
 
@@ -1745,7 +1800,7 @@ test('clients list validates existing Gateway and known npx launchers locally',
1745
1800
  assert.equal(node.commandStale, false);
1746
1801
  assert.equal(parsed.ok.summary.semanticInvalid, 0);
1747
1802
  } finally {
1748
- fs.rmSync(tempRoot, { recursive: true, force: true });
1803
+ removeTempPath(tempRoot, { recursive: true, force: true });
1749
1804
  }
1750
1805
  });
1751
1806
 
@@ -1767,7 +1822,7 @@ test('clients list keeps an existing unrecognized launcher indeterminate', () =>
1767
1822
  assert.equal(cursor.launcherSemanticState, 'unknown');
1768
1823
  assert.equal(cursor.commandStale, false, 'unknown commands are not treated as invalid');
1769
1824
  } finally {
1770
- fs.rmSync(tempRoot, { recursive: true, force: true });
1825
+ removeTempPath(tempRoot, { recursive: true, force: true });
1771
1826
  }
1772
1827
  });
1773
1828
 
@@ -1797,7 +1852,7 @@ test('clients list marks an Antigravity launcher from an old package cache as st
1797
1852
  assert.equal(antigravity.commandStale, true);
1798
1853
  assert.match(antigravity.commandStaleReason, /different package gateway/);
1799
1854
  } finally {
1800
- fs.rmSync(tempRoot, { recursive: true, force: true });
1855
+ removeTempPath(tempRoot, { recursive: true, force: true });
1801
1856
  }
1802
1857
  });
1803
1858
 
@@ -1811,7 +1866,7 @@ test('readJsonFileSafe parses JSONC without corrupting string values containing
1811
1866
  assert.ok(parsed, 'should parse');
1812
1867
  assert.equal(parsed.servers.x.command, 'a, ]b', 'comma inside string value preserved');
1813
1868
  assert.deepEqual(parsed.servers.x.args, ['c,]'], 'comma inside array string preserved');
1814
- fs.rmSync(tempRoot, { recursive: true, force: true });
1869
+ removeTempPath(tempRoot, { recursive: true, force: true });
1815
1870
  });
1816
1871
 
1817
1872
  test('readJsonFileSafe strips a genuine trailing comma', () => {
@@ -1821,7 +1876,7 @@ test('readJsonFileSafe strips a genuine trailing comma', () => {
1821
1876
  const parsed = readJsonFileSafe(f);
1822
1877
  assert.deepEqual(parsed.a, [1, 2, 3]);
1823
1878
  assert.deepEqual(parsed.b, { x: 1 });
1824
- fs.rmSync(tempRoot, { recursive: true, force: true });
1879
+ removeTempPath(tempRoot, { recursive: true, force: true });
1825
1880
  });
1826
1881
 
1827
1882
  test('clients add without --clients is a usage error', () => {
@@ -1847,7 +1902,7 @@ test('clients remove drops the genexus entry (sandbox home)', () => {
1847
1902
  assert.equal(written.mcpServers.genexus18mcp, undefined, 'genexus18mcp removed');
1848
1903
  assert.equal(written.mcpServers.genexus18, undefined, 'legacy genexus18 also removed');
1849
1904
 
1850
- fs.rmSync(tempRoot, { recursive: true, force: true });
1905
+ removeTempPath(tempRoot, { recursive: true, force: true });
1851
1906
  });
1852
1907
 
1853
1908
  test('clients remove drops both OpenCode config shapes and legacy key', () => {
@@ -1880,7 +1935,7 @@ test('clients remove drops both OpenCode config shapes and legacy key', () => {
1880
1935
  assert.equal(written.mcp.servers.genexus18mcp, undefined);
1881
1936
  assert.ok(written.mcp.servers.other, 'unrelated nested MCP server should be preserved');
1882
1937
 
1883
- fs.rmSync(tempRoot, { recursive: true, force: true });
1938
+ removeTempPath(tempRoot, { recursive: true, force: true });
1884
1939
  });
1885
1940
 
1886
1941
  test('strict semver compares prerelease precedence and ignores build metadata', () => {
@@ -1988,7 +2043,7 @@ test('gateway passthrough remains intact when no AXI subcommand is used', () =>
1988
2043
  assert.equal(result.status, 0);
1989
2044
  assert.ok(result.stdout.includes('gateway:hello,world'));
1990
2045
 
1991
- fs.rmSync(tempRoot, { recursive: true, force: true });
2046
+ removeTempPath(tempRoot, { recursive: true, force: true });
1992
2047
  });
1993
2048
 
1994
2049
  test('stdio launcher persists the last child stderr when the gateway exits non-zero', () => {
@@ -2017,7 +2072,7 @@ test('stdio launcher persists the last child stderr when the gateway exits non-z
2017
2072
  assert.match(log, /gateway bootstrap failed/);
2018
2073
  assert.match(log, /second line/);
2019
2074
  } finally {
2020
- fs.rmSync(tempRoot, { recursive: true, force: true });
2075
+ removeTempPath(tempRoot, { recursive: true, force: true });
2021
2076
  }
2022
2077
  });
2023
2078
 
@@ -2108,7 +2163,7 @@ test('clients add refuses to overwrite a third-party HTTP MCP server without --f
2108
2163
  const after = JSON.parse(fs.readFileSync(cursorCfg, 'utf8'));
2109
2164
  assert.equal(after.mcpServers.genexus18mcp.url, 'http://localhost:8001/mcp');
2110
2165
 
2111
- fs.rmSync(tempRoot, { recursive: true, force: true });
2166
+ removeTempPath(tempRoot, { recursive: true, force: true });
2112
2167
  });
2113
2168
 
2114
2169
  test('clients add with --force overwrites an existing third-party entry', () => {
@@ -2136,7 +2191,7 @@ test('clients add with --force overwrites an existing third-party entry', () =>
2136
2191
  assert.equal(after.mcpServers.genexus18mcp.url, undefined);
2137
2192
  assert.ok(after.mcpServers.genexus18mcp.command);
2138
2193
 
2139
- fs.rmSync(tempRoot, { recursive: true, force: true });
2194
+ removeTempPath(tempRoot, { recursive: true, force: true });
2140
2195
  });
2141
2196
 
2142
2197
  test('clients add supports custom --server-name across all formats for multi-MCP coexistence', () => {
@@ -2195,7 +2250,7 @@ test('clients add supports custom --server-name across all formats for multi-MCP
2195
2250
  assert.ok(codexWritten.includes('[mcp_servers.genexus]'));
2196
2251
  assert.ok(codexWritten.includes('[mcp_servers.Gx18byLennix]'));
2197
2252
 
2198
- fs.rmSync(tempRoot, { recursive: true, force: true });
2253
+ removeTempPath(tempRoot, { recursive: true, force: true });
2199
2254
  });
2200
2255
 
2201
2256
  test('clients add rejects invalid --server-name characters with usage error', () => {
@@ -2250,7 +2305,7 @@ test('patchClientConfig reports backup failure without claiming that client patc
2250
2305
  assert.deepEqual(result.failed, [{ client: 'OpenCode (CLI)', reason: 'backup denied' }]);
2251
2306
  assert.equal(JSON.parse(fs.readFileSync(fixture.openCodeCfg, 'utf8')).mcp.genexus18mcp, undefined);
2252
2307
  assert.ok(JSON.parse(fs.readFileSync(fixture.vscodeCfg, 'utf8')).servers.genexus18mcp);
2253
- } finally { fs.rmSync(fixture.tempRoot, { recursive: true, force: true }); }
2308
+ } finally { removeTempPath(fixture.tempRoot, { recursive: true, force: true }); }
2254
2309
  });
2255
2310
 
2256
2311
  test('patchClientConfig preserves earlier success when a later client write fails', () => {
@@ -2264,7 +2319,7 @@ test('patchClientConfig preserves earlier success when a later client write fail
2264
2319
  assert.deepEqual(result.failed, [{ client: 'VS Code', reason: 'write denied' }]);
2265
2320
  assert.ok(JSON.parse(fs.readFileSync(fixture.openCodeCfg, 'utf8')).mcp.genexus18mcp);
2266
2321
  assert.equal(JSON.parse(fs.readFileSync(fixture.vscodeCfg, 'utf8')).servers.genexus18mcp, undefined);
2267
- } finally { fs.rmSync(fixture.tempRoot, { recursive: true, force: true }); }
2322
+ } finally { removeTempPath(fixture.tempRoot, { recursive: true, force: true }); }
2268
2323
  });
2269
2324
 
2270
2325
  test('patchClientConfig reports post-write read-back failure as partial state', () => {
@@ -2278,7 +2333,7 @@ test('patchClientConfig reports post-write read-back failure as partial state',
2278
2333
  assert.deepEqual(result.failed, [{ client: 'OpenCode (CLI)', reason: 'post-write verification failed (genexus18mcp entry not found after write)' }]);
2279
2334
  assert.ok(JSON.parse(fs.readFileSync(fixture.openCodeCfg, 'utf8')).mcp.genexus18mcp);
2280
2335
  assert.ok(JSON.parse(fs.readFileSync(fixture.vscodeCfg, 'utf8')).servers.genexus18mcp);
2281
- } finally { fs.rmSync(fixture.tempRoot, { recursive: true, force: true }); }
2336
+ } finally { removeTempPath(fixture.tempRoot, { recursive: true, force: true }); }
2282
2337
  });
2283
2338
 
2284
2339
  test('patchClientConfig keeps a stale same-second backup and writes a distinct backup', () => {
@@ -2293,5 +2348,5 @@ test('patchClientConfig keeps a stale same-second backup and writes a distinct b
2293
2348
  const backups = fs.readdirSync(path.dirname(fixture.openCodeCfg)).filter((name) => name.startsWith(path.basename(fixture.openCodeCfg)) && name.endsWith('.bak'));
2294
2349
  assert.equal(backups.length, 2);
2295
2350
  assert.ok(backups.some((name) => name !== path.basename(stale)));
2296
- } finally { fs.rmSync(fixture.tempRoot, { recursive: true, force: true }); }
2351
+ } finally { removeTempPath(fixture.tempRoot, { recursive: true, force: true }); }
2297
2352
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genexus-mcp",
3
- "version": "3.4.3",
3
+ "version": "3.5.1",
4
4
  "mcpName": "io.github.lennix1337/genexus",
5
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 +7,7 @@
7
7
  "targets": {
8
8
  ".NETCoreApp,Version=v10.0": {},
9
9
  ".NETCoreApp,Version=v10.0/win-x64": {
10
- "GxMcp.Gateway/3.4.3": {
10
+ "GxMcp.Gateway/3.5.1": {
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.4.3": {
69
+ "GxMcp.Gateway/3.5.1": {
70
70
  "type": "project",
71
71
  "serviceable": false,
72
72
  "sha512": ""
Binary file
Binary file
@@ -1,19 +1,19 @@
1
1
  {
2
- "ConfigSchemaVersion": 2,
3
- "GeneXus": {
4
- "WorkerExecutable": "worker\\\\GxMcp.Worker.exe",
5
- "InstallationPath": "C:\\Program Files (x86)\\GeneXus\\GeneXus18"
6
- },
7
2
  "GatewayMode": "stdio-isolated",
8
- "Environment": {
9
- "ResolutionPolicy": "strict"
3
+ "GeneXus": {
4
+ "InstallationPath": "C:\\Program Files (x86)\\GeneXus\\GeneXus18",
5
+ "WorkerExecutable": "worker\\\\GxMcp.Worker.exe"
10
6
  },
7
+ "ConfigSchemaVersion": 2,
11
8
  "Server": {
12
- "HttpPort": 0,
13
9
  "TerseResponses": true,
14
- "McpStdio": true,
15
- "EmitStructuredContent": false,
16
10
  "SessionIdleTimeoutMinutes": 10,
17
- "WorkerIdleTimeoutMinutes": 5
11
+ "HttpPort": 0,
12
+ "WorkerIdleTimeoutMinutes": 5,
13
+ "McpStdio": true,
14
+ "EmitStructuredContent": false
15
+ },
16
+ "Environment": {
17
+ "ResolutionPolicy": "strict"
18
18
  }
19
19
  }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": "gxmcp-release-manifest/1",
3
- "version": "3.4.3",
4
- "sourceCommit": "10e29a32d26678c9ca993f8dea6ef610ce8341d3",
3
+ "version": "3.5.1",
4
+ "sourceCommit": "90927fad376deac191b99b0697911f926342bb8d",
5
5
  "sourceCommitPolicy": "exact-tag",
6
- "generatedAtUtc": "2026-09-14T00:01:15.8745657Z",
6
+ "generatedAtUtc": "2026-09-15T03:23:14.0252619Z",
7
7
  "runtime": {
8
8
  "gateway": "net10.0-windows",
9
9
  "worker": "net48-x86",
@@ -15,28 +15,33 @@
15
15
  "2026-07-28"
16
16
  ],
17
17
  "schema": "tool_definitions.json",
18
- "schemaSha256": "05b92690c10919de5059d00ab28fb4e91a67643436d0be186cecec6d962c3652",
18
+ "schemaSha256": "9a158c22648e40301d5feca3e9702191369e126fd02e5c9f71264c69f338f270",
19
19
  "provenance": "gxmcp-sbom.json",
20
20
  "artifacts": [
21
21
  {
22
22
  "path": "gxmcp-sbom.json",
23
23
  "size": 590,
24
- "sha256": "42f9bde3ac0c688fedb7d418b4215a3157e2ed89b6f958edaa2d4ba22d05426c"
24
+ "sha256": "cee28293a4e7b478d81a39ab20f29ee01f85c163546155d24166fef87a421a0f"
25
25
  },
26
26
  {
27
27
  "path": "GxMcp.Gateway.exe",
28
28
  "size": 162304,
29
- "sha256": "76d2ce02b5938f94a58e061eb7b9f2f5b6dafd8f9a3f959a4f023411911ae69a"
29
+ "sha256": "eff8bbb15094b244dcf1db2d4a64ead18a5e4eee9636de309e0fa8bcc16907b3"
30
+ },
31
+ {
32
+ "path": "nexus-ide.vsix",
33
+ "size": 1318671,
34
+ "sha256": "0763e99319893511ba573c54289e5cba950c0c9a41a173e91fe965b0c007b781"
30
35
  },
31
36
  {
32
37
  "path": "tool_definitions.json",
33
- "size": 112952,
34
- "sha256": "05b92690c10919de5059d00ab28fb4e91a67643436d0be186cecec6d962c3652"
38
+ "size": 113007,
39
+ "sha256": "9a158c22648e40301d5feca3e9702191369e126fd02e5c9f71264c69f338f270"
35
40
  },
36
41
  {
37
42
  "path": "worker/GxMcp.Worker.exe",
38
- "size": 3113984,
39
- "sha256": "a28b91afe2f0b81e93d92854849d5bb6650bf78611426f7ada724cf8bd6b6e26"
43
+ "size": 3123712,
44
+ "sha256": "f9e01a664a8fd87a0806b9ed801615d8493e79ed8ad3ef1ddf4d42e92498107a"
40
45
  }
41
46
  ]
42
47
  }
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "schemaVersion": "gxmcp-provenance/1",
3
- "version": "3.4.3",
4
- "sourceCommit": "10e29a32d26678c9ca993f8dea6ef610ce8341d3",
3
+ "version": "3.5.1",
4
+ "sourceCommit": "90927fad376deac191b99b0697911f926342bb8d",
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": "f49b17136dbbcb51605e2f56f2c18905d3b023565bf3bc02ad8c96fc04421c4f"
11
+ "sha256": "286fe5623068b5c165d0ad7089ed7538fc18f0f670afd46b2a9f9f7f04e6ca08"
12
12
  },
13
13
  {
14
14
  "name": "nexus-ide",
15
15
  "lockfile": "src/nexus-ide/package-lock.json",
16
16
  "size": 274983,
17
- "sha256": "864c387fcd68d7dbb8651a159c0575c4f4b0089aeba93cc13ab9fc2b99a8268d"
17
+ "sha256": "8b56243a09aa8bdb04cc2f70bdcb4ccdacf68f4b591129bb8874bd117730009f"
18
18
  }
19
19
  ]
20
20
  }
Binary file
@@ -11,7 +11,7 @@
11
11
  {"name":"genexus_lifecycle","description":"Build, validate, index, or poll the KB. action=build_all runs the native incremental Build All for the entire selected KB (ForceRebuild=false); it rejects target, refuses silent reorganization, and returns completion evidence. action=rebuild remains the forced Rebuild All path. action=specify runs Spec+Gen only. action=build with mode=compile_check checks targets plus transitive callers without KB-wide DeveloperMenu regeneration. Long operations are async with operationId. dryRun=true previews without executing. Build results carry generateEvidence and effective_status. See genexus://kb/tool-help/genexus_lifecycle for actions and target formats.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["build","build_all","cancel","specify","rebuild","reorg","reorg_preview","validate","validate-kb","sync","index","status","result","snapshots-list","snapshots-restore","inspect","reconcile"],"description":"Lifecycle action to perform."},"mode":{"type":"string","enum":["compile_check"],"description":"With action=build: compile_check spec+gen+compiles target(s) plus their transitive callers and SKIPS the KB-wide DeveloperMenu regen (the dominant build-all cost). Fast 'did my edit break the build?' check; requires target."},"target":{"type":"string","description":"Object name(s), taskId, job_id, or op:<id>. Build accepts CSV ('Foo,Bar'); build_all is KB-global and rejects target."},"code":{"type":"string","description":"validate: in-memory code to check without persisting."},"limit":{"type":"integer","description":"validate-kb: maximum number of objects/conditions to validate."},"snapshotPath":{"type":"string","description":"snapshots-restore: path to pattern snapshot to restore."},"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/build_all/rebuild: block in one turn until terminal (up to wait_seconds, default 600)."},"wait":{"type":"integer","description":"Status block, 0-600s. With a build taskId (or since): event-driven, returns on change. On index status with no since: blocks until index is Ready."},"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)."},"dryRun":{"type":"boolean","default":false,"description":"build/build_all/rebuild/index: returns the plan without executing."},"environment":{"type":"string","description":"Build: target environment name/folder. Switches active environment before building."},"includeCallees":{"type":"string","enum":["none","direct","transitive"],"description":"Build: expand call graph so callees compile first (default transitive)."},"callers":{"type":"boolean","description":"compile_check: expand transitive callers of the target (default true). Set false for a target-only check when the target is a base transaction/BC with a huge caller closure."},"callerCap":{"type":"integer","description":"compile_check: max callers to pull into the check (default 40). Closure beyond this is truncated and flagged."},"deploy":{"type":"boolean","description":"Build: force the full deploy (Theme/Image/Style/Module copy to web/bin + WebAppConfig) so the built object is runnable, not just compiled. Default false (fast compile-only path). Slower; use when you need to run/preview the object."},"buildPlanCap":{"type":"integer","description":"Build: max nodes before BuildPlanTooLarge (default 200)."},"kb":{"type":"string","description":"KB alias."},"part":{"type":"string","description":"validate: specific object part to check (e.g. Source, Rules)."},"page":{"type":"integer","description":"status/result: 1-based page number for paginated results."},"pageSize":{"type":"integer","description":"status/result: page size for paginated results (default 50)."},"page_size":{"type":"integer","description":"status/result: snake_case alias for pageSize."},"notifyOnFailure":{"type":"string","description":"build: Slack/Discord webhook URL to notify on terminal build failure."},"skipFullDeploy":{"type":"boolean","description":"build: skip full deployment stage to speed up compile pass."},"fastIncremental":{"type":"boolean","description":"build: experimental fast incremental build opt-in."},"operationKey":{"type":"string","description":"inspect/reconcile: original idempotency key; journal lookup is hash-scoped to the resolved KB."},"operationTool":{"type":"string","description":"inspect/reconcile: canonical mutating tool that owns the idempotency key, e.g. genexus_edit."},"confirmed":{"type":"boolean","description":"reconcile: must be true after independently reading the affected target; this never authorizes a retry with the same key."},"verification":{"type":"string","description":"reconcile: short operator/model verification statement. Only a hash is persisted; source and KB content are never journaled."},"observedTargetIds":{"type":"array","items":{"type":"string"},"description":"reconcile: target names observed by an independent read; send one name as a single-item array. Must match the durable target fence."},"observedRevision":{"type":"string","description":"reconcile: revision/version observed by an independent read; must match the durable revision fence."}},"required":["action"],"examples":[{"action":"build"},{"action":"build_all"},{"action":"status","target":"op:abc123"},{"action":"build","mode":"compile_check","target":"MyObject"},{"action":"inspect","operationTool":"genexus_edit","operationKey":"edit_20260905_01"}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"outputSchema":{"type":"object","description":"Structured lifecycle result. Action-specific fields remain additive; text content is preserved for legacy clients.","properties":{"status":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"error":{"type":"string"},"operationId":{"type":"string"},"job_id":{"type":"string"},"pollTarget":{"type":"string"},"isError":{"type":"boolean"}},"additionalProperties":true}},
12
12
  {"name":"genexus_test","description":"Execute native GeneXus tests (GXtest).","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"kb":{"type":"string","description":"KB alias."}},"required":["name"],"examples":[{"name":"MyTestProc"}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true}},
13
13
  {"name":"genexus_sdk_probe","description":"Read the GeneXus SDK surface. mode=surface dumps types/methods/props to docs/sdk-probe/; mode=capabilities returns an in-memory capability matrix with honest signature-probe status.","inputSchema":{"type":"object","properties":{"mode":{"type":"string","enum":["surface","capabilities"],"description":"surface (default) writes the diagnostic probe; capabilities is read-only and returns status/evidence."},"outputDir":{"type":"string","description":"Absolute path for mode=surface. Default <repo>/docs/sdk-probe/."}},"examples":[{},{"mode":"capabilities"}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}},
14
- {"name":"genexus_worker_reload","description":"Reload worker. modes: soft (drain+respawn), warm (restore validated index snapshot + soft respawn), hard (copy sourceDir+respawn). force=true: gateway kills directly. See tool-help.","inputSchema":{"type":"object","properties":{"mode":{"type":"string","enum":["soft","warm","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."}},"examples":[{"mode":"soft"},{"mode":"hard","sourceDir":"C:\\Projetos\\Genexus18MCP\\src\\GxMcp.Worker\\bin\\Debug"}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true}},
14
+ {"name":"genexus_worker_reload","description":"Reload worker. modes: soft (drain+respawn), warm (restore validated index snapshot + soft respawn), hard (copy sourceDir+respawn). force=true: gateway kills directly. See tool-help.","inputSchema":{"type":"object","properties":{"mode":{"type":"string","enum":["soft","warm","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."},"alias":{"type":"string","description":"KB alias to reload when more than one worker is open."},"kb":{"type":"string","description":"Alias of alias."}},"examples":[{"mode":"soft"},{"mode":"hard","sourceDir":"C:\\Projetos\\Genexus18MCP\\src\\GxMcp.Worker\\bin\\Debug"}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true}},
15
15
  {"name":"genexus_connection_recover","description":"Self-healing recovery for a broken MCP connection (connection closed, unresponsive worker). Probes every open worker, kills and respawns only the unhealthy ones (force=true targets all), confirms SDK-ready, and clears the semantic cache. Use when calls hang or return WorkerBusy.","inputSchema":{"type":"object","properties":{"force":{"type":"boolean","default":false,"description":"Recover ALL workers, even responsive ones."}},"examples":[{},{"force":true}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true}},
16
16
  {"name":"genexus_delete_object","description":"Delete an object from the KB through its native SDK identity. Irreversible — confirm=true required. Inspects native incoming references first; dryRun=true previews without deleting it.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"confirm":{"type":"boolean"},"dryRun":{"type":"boolean","default":false,"description":"When true, returns the resolved object and native references without deleting it."},"expectedVersion":{"type":"string","description":"Optional optimistic-concurrency token returned by a prior dry run."},"kb":{"type":"string","description":"KB alias."}},"required":["name","confirm"],"examples":[{"name":"ObsoletePanel","type":"WebPanel","confirm":true},{"name":"TemporaryDomain","type":"Domain","confirm":true,"dryRun":true}]},"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false}},
17
17
  {"name":"genexus_refactor","description":"Run GeneXus refactor: rename, extract procedure, extract local subroutine, or WWP condition set. For KB-wide rename with call-site patching (RenameObject/RenameAttribute), the response includes a patched-site count from RefactorService. dryRun=true previews what would change without persisting.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["RenameAttribute","RenameVariable","RenameObject","ExtractProcedure","ExtractSubroutine","WWPSetCondition"],"description":"Refactoring operation to execute; rename and extraction actions change KB source or metadata."},"target":{"type":"string","description":"Primary object or symbol to refactor."},"newName":{"type":"string","description":"New name for the renamed object, attribute, or variable."},"objectName":{"type":"string","description":"RenameVariable: target object name containing the variable."},"code":{"type":"string","description":"ExtractProcedure/ExtractSubroutine: code block to extract."},"procedureName":{"type":"string","description":"ExtractProcedure: name for the newly created Procedure."},"subroutineName":{"type":"string","description":"ExtractSubroutine: name for the newly created Subroutine."},"controlAttribute":{"type":"string","description":"WWPSetCondition: control or attribute name to attach condition."},"value":{"type":"string","description":"WWPSetCondition: condition expression to set."},"type":{"type":"string","description":"RenameObject: object type (e.g. WebPanel, Transaction, Procedure) to disambiguate when several objects share the name — e.g. a WebPanel vs the same-named Table behind a Transaction. Ignored by RenameAttribute/RenameVariable."},"dryRun":{"type":"boolean","default":false,"description":"When true, returns what would be changed without persisting."},"kb":{"type":"string","description":"KB alias."}},"required":["action"],"examples":[{"action":"RenameObject","target":"Customer","newName":"Client"},{"action":"RenameAttribute","target":"CustomerId","newName":"ClientId"},{"action":"ExtractSubroutine","target":"Customer","code":"&Total = 0","subroutineName":"ResetTotal"}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}},
@@ -40,7 +40,7 @@
40
40
  {"name":"genexus_browser","description":"Headless-browser verification umbrella. action=smoke (HTTP 200 + clean console), a11y (axe), wcag (caption/tooltip lint), capture (console/network/exceptions), cross (multi-engine), preview (render WebPanel + capture HTML/a11y/screenshot, optionally diff baseline). preview uses mode=render|run (default render; run auto-resolves the KB launcher, IDE F5 parity).","inputSchema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["smoke","a11y","wcag","capture","cross","preview"],"description":"Browser verification operation to execute."},"name":{"type":"string","description":"WebPanel name."},"mode":{"type":"string","enum":["render","run"],"description":"preview only."},"capture":{"type":"array","items":{"type":"string"},"description":"capture|cross|preview channels: console|network|exceptions|html|a11y|screenshot."},"browsers":{"type":"array","items":{"type":"string","enum":["chrome","firefox","safari","webkit"]},"description":"cross only."},"parms":{"type":"object","description":"preview only."},"launcher":{"type":"string","description":"preview only."},"buildFirst":{"type":"boolean","description":"preview only."},"waitMs":{"type":"integer","description":"preview only."},"diffBaseline":{"type":"boolean","description":"preview only."},"updateBaseline":{"type":"boolean","description":"preview only."},"fill":{"type":"object","description":"preview only."},"click":{"type":"string","description":"preview only."},"auth":{"type":"object","description":"preview only."},"emulate":{"type":"string","enum":["iPhone12","iPhone15Pro","iPadPro","Pixel7","desktop1920","desktop1280"],"description":"preview only."},"network":{"type":"string","enum":["fast","slow3g","fast3g","offline"],"description":"preview only."},"kb":{"type":"string","description":"KB alias."}},"examples":[{"action":"preview","name":"WPMain"},{"action":"cross","name":"WPMain","browsers":["chrome","firefox"]}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true}},
41
41
  {"name":"genexus_db","description":"Database umbrella. action: drift_check|drift_report (Transaction↔DB schema drift) | optimize_analyze|optimize_suggest|optimize_report (static index advisor) | sql_ddl|sql_navigation (SQL for Transaction/Procedure) | sample_data (fake INSERTs) | records_query|records_insert|records_update (typed application rows through Transaction metadata; writes default to dryRun; direct SQL only and no GeneXus business rules) | types_list|types_describe|types_validate (Domains/SDT constraints) | reorg_impact (timestamp/deep ImpactDatabase) | reorg_preview (non-mutating logical↔physical impact diff plus exact SQL only when a current artifact is available) | translations_import.","inputSchema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["drift_check","drift_report","optimize_analyze","optimize_suggest","optimize_report","sql_ddl","sql_navigation","sample_data","records_query","records_insert","records_update","types_list","types_describe","types_validate","translations_import","reorg_impact","reorg_preview"],"description":"Database operation. records_query is read-only; records_insert/records_update default to dryRun=true and require the matching single-use v2 preview token for persistence."},"deep":{"type":"boolean","description":"reorg_impact/reorg_preview only: run ISpecifierService.ImpactDatabase (specification, build-heavy) for the authoritative AnalysisResult. Default false (cheap timestamp heuristic)."},"name":{"type":"string","description":"Object/transaction/type name."},"trn":{"type":"string","description":"sample_data: Transaction name."},"transaction":{"type":"string","description":"records_*: root-level Transaction name."},"dataStore":{"type":"string","description":"records_*: existing active-environment datastore; defaults to the SDK default."},"where":{"type":"object","description":"records_query/update: equality filters only; update requires a filter."},"values":{"type":"object","description":"records_insert/update: typed attribute values."},"fields":{"type":"array","items":{"type":"string"},"description":"records_query: optional root-attribute projection."},"databaseManagedFields":{"type":"array","items":{"type":"string"},"description":"records_*: reviewed trigger-maintained fields; excludes keys and supplied values. Included in the preview token."},"limit":{"type":"integer","description":"records_query: 1-1000; default 100."},"expectedCount":{"type":"integer","description":"records_update: defaults to exactly one matched record."},"expectedVersion":{"type":"string","description":"records_*: v2 token from the matching write dry-run; query/v1 tokens cannot authorize writes."},"versionToken":{"type":"string","description":"records_*: alias of expectedVersion."},"dryRun":{"type":"boolean","default":true,"description":"records_insert/update: validate and reread the plan without writing."},"rollbackOnFailure":{"type":"boolean","default":true,"description":"records_*: default true; validation failures roll back before commit. Post-commit compensation is refused to preserve concurrent writers."},"timeoutSeconds":{"type":"integer","description":"records_*: command timeout, 1-60 seconds."},"rows":{"type":"integer","description":"sample_data: 1-1000; default 5."},"includeSubordinated":{"type":"boolean","description":"sql_ddl only."},"levelNumber":{"type":"integer","description":"sql_navigation only."},"includeExecutionPlan":{"type":"boolean","description":"sql_navigation."},"includeIndexAdvisor":{"type":"boolean","description":"sql_navigation."},"format":{"type":"string","enum":["json","markdown"],"description":"optimize_report only."},"kind":{"type":"string","enum":["domain","sdt","all"],"description":"types_list filter."},"value":{"type":"string","description":"types_validate."},"type":{"type":"string","description":"types_validate / disambiguator."},"inputPath":{"type":"string","description":"translations_import: CSV path."},"kb":{"type":"string","description":"KB alias."}},"examples":[{"action":"drift_check"},{"action":"sql_ddl","name":"Customer"},{"action":"reorg_preview","name":"Customer","deep":true}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}},
42
42
  {"name":"genexus_versioning","description":"Versioning umbrella. action: history_list|history_get|history_save|history_restore (snapshot store; restore+discard=true is IDE 'Discard changes' parity) | undo (revert last N edits) | time_travel (recover bytes from a past git commit) | blame (git blame an object part) | diff (text: mode=textVsText|currentVsText) | diff_generated (against=last-build|git-head).","inputSchema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["history_list","history_get","history_save","history_restore","undo","time_travel","blame","diff","diff_generated"],"description":"History, diff, time-travel, or rollback operation to perform."},"name":{"type":"string","description":"Object name for history, diff, time_travel, or blame."},"part":{"type":"string","description":"Object part name, e.g. Source, Rules, Events."},"partName":{"type":"string","description":"Legacy alias for part on history actions."},"versionId":{"type":"integer","description":"history_get/history_restore: numerical version ID."},"snapshot":{"type":"string","description":"history_restore: snapshot label or path to restore."},"discard":{"type":"boolean","description":"history_restore: true for IDE 'Discard changes' parity."},"dryRun":{"type":"boolean","description":"history_restore/undo: preview without mutating the KB."},"last":{"type":"integer","description":"undo: default 1, max 20."},"at":{"type":"string","description":"time_travel: ISO-8601 or commit sha."},"line":{"type":"integer","description":"blame: 1-based line number to annotate."},"filePath":{"type":"string","description":"blame: absolute or KB-relative file path."},"context":{"type":"integer","description":"blame: surrounding lines of context."},"mode":{"type":"string","enum":["textVsText","currentVsText"],"description":"diff."},"left":{"type":"string","description":"diff: left-side text or snapshot label."},"right":{"type":"string","description":"diff: right-side text or snapshot label."},"against":{"type":"string","enum":["last-build","git-head"],"description":"diff_generated."},"kb":{"type":"string","description":"KB alias."}},"examples":[{"action":"history_list","name":"Customer"},{"action":"time_travel","name":"Customer","at":"2026-05-20T10:00:00Z"}]},"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false}},
43
- {"name":"genexus_io","description":"IO umbrella for assets, Object Text exchange, screenshots, OCR. action: asset_find|asset_read|asset_write | export_part|import_part (single part) | export_kb_to_text|import_text_to_kb|validate_kb_text_files|delete_kb_objects (official GeneXus for Agents Object Text parity) | export_unified|screenshot_publish|ocr.","inputSchema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["asset_find","asset_read","asset_write","export_part","import_part","export_kb_to_text","import_text_to_kb","validate_kb_text_files","delete_kb_objects","export_unified","screenshot_publish","ocr"],"description":"Asset, single-part, batch Object Text, screenshot, or OCR operation."},"path":{"type":"string","description":"asset/screenshot_publish/ocr: file path; Object Text import/validate may use a manifest directory."},"name":{"type":"string","description":"single object name or comma-separated selector; export_unified uses one object."},"targets":{"type":"array","items":{},"description":"Object Text selector array. Items may be names or {name,type} objects. Omit for export_kb_to_text to export the full indexed KB."},"outputPath":{"type":"string","description":"export_part: destination file; export_kb_to_text: destination directory."},"inputPath":{"type":"string","description":"import_part: source file; import_text_to_kb/validate_kb_text_files: manifest or directory."},"part":{"type":"string","description":"Object Text or part-exchange part name (e.g. Source, Rules, WebForm)."},"type":{"type":"string","description":"Object type disambiguator/filter."},"module":{"type":"string","description":"Object Text module filter."},"pathPrefix":{"type":"string","description":"Object Text KB object-path prefix filter."},"overwrite":{"type":"boolean","description":"export_part/export_kb_to_text: overwrite existing output."},"confirm":{"type":"boolean","description":"delete_kb_objects: required for a real delete; dryRun remains non-mutating."},"dryRun":{"type":"boolean","description":"Object Text import/delete preview without persisting changes."},"async":{"type":"boolean","description":"Run a batch Object Text operation as a cancellable background job when supported."},"pattern":{"type":"string","description":"asset_find: glob pattern."},"relativeRoot":{"type":"string","description":"asset_find: root for relative paths."},"limit":{"type":"integer","description":"asset_find/Object Text: max results."},"includeContent":{"type":"boolean","description":"asset_find: inline file content."},"maxBytes":{"type":"integer","description":"asset_read: max bytes to return."},"contentBase64":{"type":"string","description":"asset_write: base64-encoded file content."},"kb":{"type":"string","description":"KB alias."}},"examples":[{"action":"export_kb_to_text","outputPath":"C:\\tmp\\customer-text","targets":[{"name":"Customer","type":"Transaction"}]},{"action":"validate_kb_text_files","inputPath":"C:\\tmp\\customer-text"},{"action":"delete_kb_objects","targets":["Procedure:OldProc"],"dryRun":true}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}},
43
+ {"name":"genexus_io","description":"IO umbrella for assets, Object Text exchange, screenshots, OCR. action: asset_find|asset_read|asset_write | read_blob | export_part|import_part (single part) | export_kb_to_text|import_text_to_kb|validate_kb_text_files|delete_kb_objects (official GeneXus for Agents Object Text parity) | export_unified|screenshot_publish|ocr.","inputSchema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["asset_find","asset_read","asset_write","read_blob","export_part","import_part","export_kb_to_text","import_text_to_kb","validate_kb_text_files","delete_kb_objects","export_unified","screenshot_publish","ocr"],"description":"Asset, single-part, batch Object Text, screenshot, or OCR operation."},"path":{"type":"string","description":"asset/screenshot_publish/ocr: file path; Object Text import/validate may use a manifest directory."},"name":{"type":"string","description":"single object name or comma-separated selector; export_unified uses one object."},"targets":{"type":"array","items":{},"description":"Object Text selector array. Items may be names or {name,type} objects. Omit for export_kb_to_text to export the full indexed KB."},"outputPath":{"type":"string"},"inputPath":{"type":"string","description":"import_part: source file; import_text_to_kb/validate_kb_text_files: manifest or directory."},"part":{"type":"string","description":"Object Text or part-exchange part name (e.g. Source, Rules, WebForm)."},"type":{"type":"string","description":"Object type disambiguator/filter."},"module":{"type":"string","description":"Object Text module filter."},"pathPrefix":{"type":"string","description":"Object Text KB object-path prefix filter."},"overwrite":{"type":"boolean"},"confirm":{"type":"boolean","description":"delete_kb_objects: required for a real delete; dryRun remains non-mutating."},"dryRun":{"type":"boolean","description":"Object Text import/delete preview without persisting changes."},"async":{"type":"boolean","description":"Run a batch Object Text operation as a cancellable background job when supported."},"pattern":{"type":"string","description":"asset_find: glob pattern."},"relativeRoot":{"type":"string","description":"asset_find: root for relative paths."},"limit":{"type":"integer","description":"asset_find/Object Text: max results."},"includeContent":{"type":"boolean","description":"asset_find: inline file content."},"maxBytes":{"type":"integer","description":"asset_read/read_blob: inline byte limit."},"includeBase64":{"type":"boolean"},"contentBase64":{"type":"string","description":"asset_write: base64-encoded file content."},"kb":{"type":"string","description":"KB alias."}},"examples":[{"action":"export_kb_to_text","outputPath":"C:\\tmp\\customer-text","targets":[{"name":"Customer","type":"Transaction"}]},{"action":"validate_kb_text_files","inputPath":"C:\\tmp\\customer-text"},{"action":"delete_kb_objects","targets":["Procedure:OldProc"],"dryRun":true}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}},
44
44
  {"name":"genexus_variable","description":"Variables CRUD. add accepts varName or variables[] batch; modify changes type atomically. basedOn binds a Domain by native SDK identity. objectType=BusinessComponent with objectName+module binds a modular BC by EntityKey/GUID and verifies it after save. length/decimals override size; collection=true declares a collection; untyped add inherits a same-named attribute. dryRun validates and previews without mutation; async returns an operationId.","inputSchema":{"type":"object","required":["action","name"],"properties":{"action":{"type":"string","enum":["add","delete","modify"],"description":"Variable-part operation to add, delete, or modify a variable."},"name":{"type":"string"},"varName":{"type":"string","description":"Single-variable form (add/delete/modify). For batch add use variables[] instead."},"variables":{"type":"array","description":"Batch add only: one object per variable — {varName, typeName?, length?, decimals?, collection?}. Added before a single save.","items":{"type":"object","required":["varName"],"properties":{"varName":{"type":"string"},"typeName":{"type":"string"},"length":{"type":"integer"},"decimals":{"type":"integer"},"collection":{"type":"boolean"},"basedOn":{"type":"string","description":"Domain name; native SDK reference."}}}},"typeName":{"type":"string","description":"Primitive (Character/Numeric/VarChar/Date/...), SDT/BC/Domain name, or WebSession. VarChar keeps VARCHAR semantics."},"objectType":{"type":"string","enum":["BusinessComponent"],"description":"add/modify: resolve an object-backed variable natively instead of interpreting typeName as a Domain."},"objectName":{"type":"string","description":"Business Component Transaction name, without module when module is passed separately."},"module":{"type":"string","description":"Module owning objectName. Required to disambiguate modular Business Components."},"expectedVersion":{"type":"string","description":"Optimistic-concurrency token for an object-backed add/modify. Stale state returns VersionConflict before mutation."},"length":{"type":"integer","description":"add/modify optional: overrides the length parsed from typeName."},"decimals":{"type":"integer","description":"add/modify optional: overrides decimals parsed from typeName."},"collection":{"type":"boolean","description":"add/modify optional: declare the variable as a collection."},"basedOn":{"type":"string","description":"modify optional: Domain."},"dryRun":{"type":"boolean","default":false,"description":"Validate and return the typed diff without changing any object, Attribute, hash, or version."},"validationMode":{"type":"string","enum":["specify"],"description":"Explicitly run the inline Specify pass after a legacy variable write. Never runs unless requested."},"rollbackOnFailure":{"type":"boolean","default":true,"description":"Object-backed variables always snapshot the complete owner and restore it on save/re-read divergence."},"async":{"type":"boolean","description":"Run as a background job. Result returns operationId/job_id for genexus_lifecycle polling."},"estimated_seconds":{"type":"integer","description":"When async=true, expected runtime used for progress/job metadata (default 30)."},"kb":{"type":"string","description":"KB alias."}},"examples":[{"action":"add","name":"MyPanel","varName":"&Choice","typeName":"Numeric"},{"action":"add","name":"MyProc","varName":"&OrderRecord","objectType":"BusinessComponent","objectName":"OrderRecord","module":"Operations","dryRun":true},{"action":"add","name":"MyProc","variables":[{"varName":"&ApiKey","typeName":"VarChar","length":80},{"varName":"&CmdNro","typeName":"Numeric","length":9}]},{"action":"delete","name":"MyPanel","varName":"&OldVar"}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}},
45
45
  {"name":"genexus_telemetry","description":"Observability umbrella. action: executions (recent invocations) | watch_event (ops mentioning an event) | friction_append|friction_tail (.gx/friction.jsonl) | learning_report (aggregate friction) | logs (worker_debug.log tail) | profile_analyze|profile_hotspots|profile_correlate (GeneXus profiler XML).","inputSchema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["executions","watch_event","friction_append","friction_tail","learning_report","logs","profile_analyze","profile_hotspots","profile_correlate"],"description":"Telemetry, profiling, or friction-log operation to perform."},"target":{"type":"string","description":"executions/watch_event: object name to filter."},"event":{"type":"string","description":"watch_event: event name to match in operation payload."},"last":{"type":"integer","description":"executions/watch_event: max results."},"tool":{"type":"string","description":"friction_append: tool name that produced the friction entry."},"message":{"type":"string","description":"friction_append: description of the friction encountered."},"severity":{"type":"string","enum":["info","warn","error","critical"],"description":"friction_append: severity level."},"n":{"type":"integer","description":"friction_tail: number of recent lines to return."},"since":{"type":"string","description":"learning_report/logs: ISO-8601 start time filter."},"until":{"type":"string","description":"learning_report: ISO-8601 end time filter."},"tail":{"type":"integer","description":"logs: number of tail lines from worker_debug.log."},"filterCorrelation":{"type":"string","description":"logs: filter by correlation/operation ID."},"grep":{"type":"string","description":"logs: substring filter on log lines."},"path":{"type":"string","description":"profile_*: path to the GeneXus profiler XML file."},"top":{"type":"integer","description":"profile_hotspots: top-N hottest procedures to return."},"kb":{"type":"string","description":"KB alias."}},"examples":[{"action":"executions"},{"action":"friction_tail","n":20}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}},
46
46
  {"name":"genexus_create","description":"Creation umbrella. action: object (Transaction/Procedure/WebPanel/SDT/API/Domain/Dashboard/Folder/Module; type='API' scaffolds a REST API object — routes use ONE HTTP-verb block per object 'Verb { route => Object; }' (mixing Get+Post blocks is unsupported); type='Folder'/'Module' create containers, and folder=<name>/module=<name> place the new object there (created in Root, then moved+verified); SDT takes optional firstItem/firstItemType; Domain takes dataType+length or enumValues; NOT WWP — use genexus_apply_pattern) | popup (popup WebPanel with Form type='layout' body) | sd_panel_create|sd_panel_inspect|sd_panel_edit | save_as (clone parts) | scaffold|translate|sample (forge) | template (kpi_header|empty_state|confirm_dialog) | object_atomic (issue #62: all-or-nothing create/update of an object with variables[], rules[], parms[], properties{} and source in ONE validated call — pre-validates every field before the first save, composes the SDK write primitives, compensates on failure, dryRun previews, validate=true runs the inline Specify pass, update mode guards concurrent writes via expectedVersion). | curl_procedure imports a REST-consumer Procedure from a curl command through the GeneXus SDK.","inputSchema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["object","object_atomic","popup","sd_panel_create","sd_panel_inspect","sd_panel_edit","save_as","scaffold","translate","sample","template","curl_procedure"],"description":"Object-creation or scaffold operation to perform."},"curl":{"type":"string","description":"curl_procedure: the curl command to import as a Procedure."},"type":{"type":"string","description":"Object type to create (Transaction, Procedure, WebPanel, SDT, API, Domain, Dashboard, Folder, Module)."},"name":{"type":"string","description":"Name of the object to create, clone, or update."},"newName":{"type":"string","description":"save_as."},"description":{"type":"string","description":"Human-readable description / title of the object."},"dataType":{"type":"string","description":"object Domain."},"length":{"type":"integer","description":"object Domain."},"decimals":{"type":"integer","description":"object Domain."},"signed":{"type":"boolean","description":"object Domain."},"basedOn":{"type":"string","description":"object Domain."},"enumValues":{"type":"array","items":{"type":"object"},"description":"object Domain enum."},"firstItem":{"type":"string","description":"object SDT optional: name of the first structure item to seed (SDK requires ≥1). Defaults to Item1. Use to avoid the throwaway Item1."},"firstItemType":{"type":"string","description":"object SDT optional: type of the seeded first item (Character/Numeric/VarChar/…). Defaults to VarChar."},"folder":{"type":"string","description":"object: Folder destination (created in Root, then moved+verified)."},"module":{"type":"string","description":"object: Module destination (created in Root, then moved+verified)."},"parentPath":{"type":"string","description":"object: folder/module destination (fallback when folder/module absent)."},"mode":{"type":"string","enum":["create","update","auto"],"description":"object_atomic: create (fail if exists), update (fail if missing), auto (default — create or update by existence)."},"variables":{"type":"array","items":{"type":"object","required":["varName"],"properties":{"varName":{"type":"string"},"typeName":{"type":"string"},"length":{"type":"integer"},"decimals":{"type":"integer"},"collection":{"type":"boolean"},"name":{"type":"string"},"basedOn":{"type":"string"}}},"description":"object_atomic: one object per variable — {varName, typeName?, length?, decimals?, collection?}. Validated before any save; errors are attributed to variables[N]."},"rules":{"type":"array","items":{"type":"string"},"description":"object_atomic: rule lines (e.g. \"Parm(in:&Id);\"). Send actual line breaks; literal backslash sequences \\r\\n, \\n, or \\r are rejected before the first save."},"parms":{"type":"array","items":{"type":"string"},"description":"object_atomic: parameter entries rendered into a Parm rule — bare names (\"&Id\") or prefixed (\"out:&Msg\")."},"source":{"type":"string","description":"object_atomic: Source part text. Send actual line breaks; literal backslash sequences \\r\\n, \\n, or \\r are rejected before the first save."},"properties":{"type":"object","description":"object_atomic: object-level properties applied via the genexus_properties path (propertyName → value)."},"expectedVersion":{"type":"string","description":"object_atomic update: optimistic concurrency token returned as `version` by a prior atomic create/update/read; a mismatch fails with ConcurrentModification instead of overwriting concurrent changes."},"validate":{"type":"boolean","description":"object_atomic: run the inline Specify pass (Spec+Gen) before confirming success. On failure with rollbackOnFailure=true, a freshly-created object is deleted so the operation stays all-or-nothing."},"validationMode":{"type":"string","enum":["specify"],"description":"Issue #60: with action=object, run the inline Specify pass (Spec+Gen, no Compile) after creation, returning structured spc*/gen* diagnostics in the same call. Combines with rollbackOnFailure."},"rollbackOnFailure":{"type":"boolean","description":"Issue #60: with action=object and validationMode=specify, roll back the created object when the specify pass reports errors. A fresh object has no pre-write snapshot, so rollback reports rolledBack=false — delete it via genexus_delete_object if spec fails."},"spec":{"type":"object","description":"popup."},"part":{"type":"string","description":"sd_panel_edit/template."},"content":{"type":"string","description":"sd_panel_edit/scaffold/translate."},"includePatternInstance":{"type":"boolean","description":"save_as."},"overwrite":{"type":"boolean","description":"save_as."},"dryRun":{"type":"boolean","description":"popup/save_as/template/object."},"template":{"type":"string","enum":["kpi_header","empty_state","confirm_dialog"],"description":"template."},"args":{"type":"object","description":"template."},"kb":{"type":"string","description":"KB alias."},"baseVersion":{"type":"string","description":"Optimistic concurrency versionToken for atomic create/update."},"updateExisting":{"type":"boolean","default":false,"description":"object_atomic: allow updating existing object if it already exists."}},"examples":[{"action":"object","name":"NewPanel","type":"WebPanel"},{"action":"object","name":"CustomerApi","type":"API"},{"action":"popup","name":"MyPopup","spec":{"host":"Customer"}}]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}},
Binary file