genexus-mcp 3.4.2 → 3.5.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.
@@ -1522,7 +1522,8 @@ async function runInteractiveInit(ctx) {
1522
1522
  meta: {
1523
1523
  patchedClients: patchResult.patched,
1524
1524
  failedClients: patchResult.failed,
1525
- skippedClients: patchResult.skipped || []
1525
+ skippedClients: patchResult.skipped || [],
1526
+ verifiedClients: patchResult.verified || []
1526
1527
  }
1527
1528
  }
1528
1529
  };
@@ -1844,6 +1845,7 @@ async function handleInit(options, ctx) {
1844
1845
  patchedClients: patchResult.patched,
1845
1846
  failedClients: patchResult.failed,
1846
1847
  skippedClients: patchResult.skipped || [],
1848
+ verifiedClients: patchResult.verified || [],
1847
1849
  smokeSkipped: !!options.noSmoke,
1848
1850
  warmed: !!warm && warm.status === 'pass'
1849
1851
  }
@@ -2137,9 +2139,25 @@ async function handleClients(subcommand, options, ctx) {
2137
2139
  help.push(`Register installed-but-unregistered agents: genexus-mcp clients add --clients ${installedUnregistered.map((r) => r.id).join(',')}${serverName !== DEFAULT_MCP_SERVER_NAME ? ` --server-name ${serverName}` : ''}`);
2138
2140
  }
2139
2141
  const stale = rows.filter((r) => r.commandStale);
2140
- if (stale.length > 0) {
2141
- help.push(`These clients point at a missing gateway exe (will fail to connect) — re-register: genexus-mcp clients add --clients ${stale.map((r) => r.id).join(',')}${serverName !== DEFAULT_MCP_SERVER_NAME ? ` --server-name ${serverName}` : ''}`);
2142
+ const missingLaunchers = stale.filter((r) => r.launcherStructuralState === 'missing');
2143
+ const invalidLaunchers = stale.filter((r) =>
2144
+ r.launcherSemanticState === 'invalid' && r.launcherStructuralState !== 'missing'
2145
+ );
2146
+ if (missingLaunchers.length > 0) {
2147
+ help.push(`These clients point at a missing gateway exe (will fail to connect) — re-register: genexus-mcp clients add --clients ${missingLaunchers.map((r) => r.id).join(',')}${serverName !== DEFAULT_MCP_SERVER_NAME ? ` --server-name ${serverName}` : ''}`);
2142
2148
  }
2149
+ if (invalidLaunchers.length > 0) {
2150
+ help.push(`These clients have a known-invalid MCP launcher (check command and args) — re-register: genexus-mcp clients add --clients ${invalidLaunchers.map((r) => r.id).join(',')}${serverName !== DEFAULT_MCP_SERVER_NAME ? ` --server-name ${serverName}` : ''}`);
2151
+ }
2152
+ const otherStale = stale.filter((r) =>
2153
+ r.launcherStructuralState !== 'missing' && r.launcherSemanticState !== 'invalid'
2154
+ );
2155
+ if (otherStale.length > 0) {
2156
+ help.push(`These clients have a stale launcher — re-register: genexus-mcp clients add --clients ${otherStale.map((r) => r.id).join(',')}${serverName !== DEFAULT_MCP_SERVER_NAME ? ` --server-name ${serverName}` : ''}`);
2157
+ }
2158
+ const semanticValidCount = rows.filter((r) => r.launcherSemanticState === 'valid').length;
2159
+ const semanticInvalidCount = rows.filter((r) => r.launcherSemanticState === 'invalid').length;
2160
+ const semanticUnknownCount = rows.filter((r) => r.launcherSemanticState === 'unknown').length;
2143
2161
  for (const r of rows) {
2144
2162
  if (r.installed && !r.writeSupported && r.note) help.push(`${r.name}: ${r.note}`);
2145
2163
  }
@@ -2148,7 +2166,15 @@ async function handleClients(subcommand, options, ctx) {
2148
2166
  envelope: {
2149
2167
  ok: {
2150
2168
  clients: rows,
2151
- summary: { total: rows.length, installed: installedCount, registered: registeredCount, serverName }
2169
+ summary: {
2170
+ total: rows.length,
2171
+ installed: installedCount,
2172
+ registered: registeredCount,
2173
+ semanticValid: semanticValidCount,
2174
+ semanticInvalid: semanticInvalidCount,
2175
+ semanticUnknown: semanticUnknownCount,
2176
+ serverName
2177
+ }
2152
2178
  },
2153
2179
  help
2154
2180
  }
@@ -2214,7 +2240,11 @@ async function handleClients(subcommand, options, ctx) {
2214
2240
  envelope: {
2215
2241
  ok: { action: 'clients.add', configPath, serverName, patchedClients: patch.patched, patchedCount: patch.patched.length },
2216
2242
  help,
2217
- meta: { failedClients: patch.failed, skippedClients: patch.skipped }
2243
+ meta: {
2244
+ failedClients: patch.failed,
2245
+ skippedClients: patch.skipped,
2246
+ verifiedClients: patch.verified || []
2247
+ }
2218
2248
  }
2219
2249
  };
2220
2250
  }
package/cli/lib/config.js CHANGED
@@ -876,29 +876,217 @@ function listSupportedClientIds() {
876
876
  return getClientConfigTargets().map((c) => c.id);
877
877
  }
878
878
 
879
- // Judge whether a registered launcher command is healthy. npx/node/genexus-mcp
880
- // shims resolve at runtime so we can't fault them; any other launcher referenced
881
- // by an explicit path (a separator in the command) that no longer exists on disk
882
- // is the classic "Failed to connect / still on old version" cause after an
883
- // install dir moved or was cleaned — covers .exe, .bat, .cmd, .sh, extensionless.
884
- function clientCommandHealth(entry, client = null) {
885
- if (!entry || !entry.command) return { stale: false, reason: null };
886
- const cmd = String(entry.command);
887
- if (/(^|[\\/])(npx|npx\.cmd|node|node\.exe|genexus-mcp|genexus-mcp\.cmd)$/i.test(cmd)) {
888
- return { stale: false, reason: null };
879
+ // The list command is intentionally local: it does not spawn a configured
880
+ // launcher or perform an MCP handshake. These states keep registration, path
881
+ // presence, and known command semantics separate while retaining commandStale
882
+ // as the actionable compatibility flag for a missing/known-invalid launcher.
883
+ function normalizeLauncherToken(value) {
884
+ return String(value || '').trim().replace(/^['"]|['"]$/g, '');
885
+ }
886
+
887
+ function launcherBaseName(command) {
888
+ const token = normalizeLauncherToken(command);
889
+ const lastSlash = Math.max(token.lastIndexOf('/'), token.lastIndexOf('\\'));
890
+ return token.slice(lastSlash + 1).toLowerCase();
891
+ }
892
+
893
+ function launcherHasExplicitPath(command) {
894
+ const token = normalizeLauncherToken(command);
895
+ return token.includes('/') || token.includes('\\');
896
+ }
897
+
898
+ function hasKnownNpxPackage(args) {
899
+ return Array.isArray(args) && args.some((arg) =>
900
+ typeof arg === 'string' && /^genexus-mcp(?:@[^\s]+)?$/i.test(arg.trim())
901
+ );
902
+ }
903
+
904
+ function nodeEntrypointInfo(args) {
905
+ if (!Array.isArray(args) || args.length === 0) return { state: 'missing', value: null };
906
+
907
+ for (let i = 0; i < args.length; i += 1) {
908
+ const arg = typeof args[i] === 'string' ? args[i].trim() : '';
909
+ if (!arg) continue;
910
+ if (arg === '--') {
911
+ return args[i + 1]
912
+ ? { state: 'entrypoint', value: args[i + 1] }
913
+ : { state: 'missing', value: null };
914
+ }
915
+ if (arg.startsWith('-')) {
916
+ // Inline/evaluated Node programs may start MCP, but their semantics
917
+ // cannot be established without executing them. Keep them unknown.
918
+ if (/^(?:-e|-p|--eval(?:=|$)|--print(?:=|$)|--check(?:=|$))/.test(arg)) {
919
+ return { state: 'indeterminate', value: null };
920
+ }
921
+ continue;
922
+ }
923
+ return { state: 'entrypoint', value: arg };
924
+ }
925
+
926
+ return { state: 'missing', value: null };
927
+ }
928
+
929
+ function isKnownNodeEntrypoint(entrypoint) {
930
+ const normalized = normalizeLauncherToken(entrypoint).replace(/\\/g, '/').toLowerCase();
931
+ return normalized === 'cli/run.js' || normalized.endsWith('/cli/run.js');
932
+ }
933
+
934
+ function clientCommandHealth(entry, client = null, { fs: fileSystem = fs } = {}) {
935
+ const unknown = {
936
+ stale: false,
937
+ reason: null,
938
+ structuralState: 'indeterminate',
939
+ semanticState: 'unknown',
940
+ semanticReason: null
941
+ };
942
+ const invalid = (reason, structuralState = 'present') => ({
943
+ stale: true,
944
+ reason,
945
+ structuralState,
946
+ semanticState: 'invalid',
947
+ semanticReason: reason
948
+ });
949
+ const valid = (structuralState = 'present') => ({
950
+ stale: false,
951
+ reason: null,
952
+ structuralState,
953
+ semanticState: 'valid',
954
+ semanticReason: null
955
+ });
956
+ const missingLauncherReason = 'configured launcher does not exist on disk';
957
+
958
+ if (entry === null || entry === undefined) {
959
+ return {
960
+ stale: false,
961
+ reason: null,
962
+ structuralState: 'not-registered',
963
+ semanticState: 'not-applicable',
964
+ semanticReason: null
965
+ };
966
+ }
967
+ if (typeof entry !== 'object') {
968
+ return invalid('configured local MCP entry has no command or URL', 'invalid');
969
+ }
970
+ if (entry.url || entry.type === 'http' || entry.type === 'sse' || entry.type === 'remote') {
971
+ return {
972
+ stale: false,
973
+ reason: null,
974
+ structuralState: 'not-applicable',
975
+ semanticState: 'not-applicable',
976
+ semanticReason: null
977
+ };
978
+ }
979
+
980
+ const command = normalizeLauncherToken(entry.command);
981
+ if (!command) return invalid('configured local MCP entry has no command', 'invalid');
982
+
983
+ const args = Array.isArray(entry.args) ? entry.args : [];
984
+ const baseName = launcherBaseName(command);
985
+ const explicitPath = launcherHasExplicitPath(command);
986
+ const exists = explicitPath ? fileSystem.existsSync(command) : null;
987
+ const knownRuntimeLauncher = baseName === 'npx'
988
+ || baseName === 'npx.cmd'
989
+ || baseName === 'node'
990
+ || baseName === 'node.exe'
991
+ || baseName === 'genexus-mcp'
992
+ || baseName === 'genexus-mcp.cmd'
993
+ || baseName === 'start_mcp.bat'
994
+ || baseName === 'gxmcp.gateway.exe';
995
+ const structuralState = explicitPath
996
+ ? (exists ? 'present' : 'missing')
997
+ : (knownRuntimeLauncher ? 'present' : 'indeterminate');
998
+
999
+ if (baseName === 'gxmcp.gateway.exe') {
1000
+ if (!explicitPath) {
1001
+ return {
1002
+ ...unknown,
1003
+ semanticReason: 'direct Gateway launcher must use an explicit path that can be checked locally'
1004
+ };
1005
+ }
1006
+ if (!exists) return invalid(missingLauncherReason, 'missing');
1007
+ if (client && client.preferDirectGateway) {
1008
+ const currentPackageGateway = getGatewayExePath();
1009
+ if (fs.existsSync(currentPackageGateway) && normalizeExePath(command) !== normalizeExePath(currentPackageGateway)) {
1010
+ return {
1011
+ ...valid('present'),
1012
+ stale: true,
1013
+ reason: 'configured launcher points at a different package gateway; re-register the client'
1014
+ };
1015
+ }
1016
+ }
1017
+ return valid('present');
889
1018
  }
890
- if (/[\\/]/.test(cmd) && !fs.existsSync(cmd)) {
891
- return { stale: true, reason: 'configured launcher does not exist on disk' };
1019
+
1020
+ if (baseName === 'npx' || baseName === 'npx.cmd') {
1021
+ if (!exists && explicitPath) return invalid(missingLauncherReason, 'missing');
1022
+ if (hasKnownNpxPackage(args)) return valid(structuralState);
1023
+ if (args.length === 0) {
1024
+ return invalid('npx launcher requires the genexus-mcp package in args (for example -y genexus-mcp@latest)', structuralState);
1025
+ }
1026
+ return {
1027
+ ...unknown,
1028
+ structuralState,
1029
+ semanticReason: 'npx launcher package is not recognized as genexus-mcp'
1030
+ };
892
1031
  }
893
- if (client && client.preferDirectGateway && /GxMcp\.Gateway\.exe$/i.test(cmd)) {
894
- const currentPackageGateway = getGatewayExePath();
895
- if (fs.existsSync(currentPackageGateway) && normalizeExePath(cmd) !== normalizeExePath(currentPackageGateway)) {
896
- return { stale: true, reason: 'configured launcher points at a different package gateway; re-register the client' };
1032
+
1033
+ if (baseName === 'node' || baseName === 'node.exe') {
1034
+ if (!exists && explicitPath) return invalid(missingLauncherReason, 'missing');
1035
+ const entrypoint = nodeEntrypointInfo(args);
1036
+ if (entrypoint.state === 'missing') {
1037
+ return invalid('node launcher requires a CLI entrypoint in args (for example cli/run.js)', structuralState);
1038
+ }
1039
+ if (entrypoint.state === 'indeterminate') {
1040
+ return {
1041
+ ...unknown,
1042
+ structuralState,
1043
+ semanticReason: 'node launcher uses an evaluated entrypoint that cannot be verified locally'
1044
+ };
1045
+ }
1046
+ if (!isKnownNodeEntrypoint(entrypoint.value)) {
1047
+ return {
1048
+ ...unknown,
1049
+ structuralState,
1050
+ semanticReason: 'node entrypoint is not recognized as the genexus-mcp CLI'
1051
+ };
897
1052
  }
1053
+ if (!fileSystem.existsSync(normalizeLauncherToken(entrypoint.value))) {
1054
+ return invalid(`configured node entrypoint does not exist on disk: ${entrypoint.value}`, structuralState);
1055
+ }
1056
+ return valid(structuralState);
1057
+ }
1058
+
1059
+ if (baseName === 'genexus-mcp' || baseName === 'genexus-mcp.cmd') {
1060
+ if (!exists && explicitPath) return invalid(missingLauncherReason, 'missing');
1061
+ return valid(structuralState);
1062
+ }
1063
+ if (baseName === 'start_mcp.bat' && explicitPath) {
1064
+ if (!exists) return invalid(missingLauncherReason, 'missing');
1065
+ return valid('present');
898
1066
  }
899
- return { stale: false, reason: null };
900
- }
901
1067
 
1068
+ if (explicitPath && !exists) {
1069
+ return {
1070
+ ...unknown,
1071
+ stale: true,
1072
+ reason: missingLauncherReason,
1073
+ structuralState: 'missing',
1074
+ semanticReason: 'launcher command is not recognized locally; run doctor for runtime validation'
1075
+ };
1076
+ }
1077
+ if (explicitPath) {
1078
+ return {
1079
+ ...unknown,
1080
+ structuralState: 'present',
1081
+ semanticReason: 'launcher command is not recognized locally; run doctor for runtime validation'
1082
+ };
1083
+ }
1084
+ return {
1085
+ ...unknown,
1086
+ structuralState,
1087
+ semanticReason: 'launcher command cannot be classified without a known local command shape'
1088
+ };
1089
+ }
902
1090
  function buildManualClientSetup(client, targetConfigPath = null) {
903
1091
  return {
904
1092
  mode: 'manual',
@@ -945,7 +1133,11 @@ function clientsStatus(opts = {}) {
945
1133
  writeSupported: client.writeSupported !== false,
946
1134
  configPath: client.path,
947
1135
  command: entry && entry.command ? entry.command : null,
1136
+ args: entry && Array.isArray(entry.args) ? entry.args : [],
948
1137
  url: entry && entry.url ? entry.url : null,
1138
+ launcherStructuralState: health.structuralState,
1139
+ launcherSemanticState: health.semanticState,
1140
+ launcherSemanticReason: health.semanticReason,
949
1141
  commandStale: health.stale,
950
1142
  commandStaleReason: health.reason || (isThirdParty ? 'configured as third-party / HTTP MCP server (e.g. official GeneXus MCP)' : null),
951
1143
  detectedAt: det.markerHit || (det.hasConfig ? client.path : null),
@@ -1000,6 +1192,7 @@ function patchClientConfig(targetConfigPath, opts = {}) {
1000
1192
  const patched = [];
1001
1193
  const failed = [];
1002
1194
  const skipped = [];
1195
+ const verified = [];
1003
1196
 
1004
1197
  for (const client of candidates) {
1005
1198
  // Detect-only agents can't be auto-written; surface
@@ -1029,24 +1222,38 @@ function patchClientConfig(targetConfigPath, opts = {}) {
1029
1222
  }
1030
1223
  try {
1031
1224
  fileSystem.mkdirSync(path.dirname(client.path), { recursive: true });
1032
- applyClientEntry(client, getLauncher(client), targetConfigPath, {
1225
+ const launcher = getLauncher(client);
1226
+ applyClientEntry(client, launcher, targetConfigPath, {
1033
1227
  serverName,
1034
1228
  force,
1035
1229
  globalConfig: Boolean(opts.globalConfig),
1036
1230
  fs: fileSystem
1037
1231
  });
1038
- // Read-back: confirm the entry is actually present and the file still
1039
- // parses, so a silently-corrupted write is reported as a failure.
1040
- if (!readClientCommandEntry(client, serverName, { fs: fileSystem })) {
1232
+ // Read-back: confirm the entry is actually present, the file still
1233
+ // parses, and the generated command is a known valid local launcher.
1234
+ const writtenEntry = readClientCommandEntry(client, serverName, { fs: fileSystem });
1235
+ if (!writtenEntry) {
1041
1236
  throw new Error(`post-write verification failed (${serverName} entry not found after write)`);
1042
1237
  }
1238
+ const health = clientCommandHealth(writtenEntry, client, { fs: fileSystem });
1239
+ if (health.semanticState !== 'valid') {
1240
+ const reason = health.semanticReason || health.reason || `launcher semantic state is ${health.semanticState}`;
1241
+ throw new Error(`post-write verification failed (${reason})`);
1242
+ }
1243
+ verified.push({
1244
+ client: client.name,
1245
+ command: writtenEntry.command || null,
1246
+ args: Array.isArray(writtenEntry.args) ? writtenEntry.args : [],
1247
+ launcherStructuralState: health.structuralState,
1248
+ launcherSemanticState: health.semanticState
1249
+ });
1043
1250
  patched.push(client.name);
1044
1251
  } catch (err) {
1045
1252
  failed.push({ client: client.name, reason: err && err.message ? err.message : 'Unknown error' });
1046
1253
  }
1047
1254
  }
1048
1255
 
1049
- return { patched, failed, skipped };
1256
+ return { patched, failed, skipped, verified };
1050
1257
  }
1051
1258
 
1052
1259
  function unpatchClientConfig(opts = {}) {
package/cli/run.test.js CHANGED
@@ -30,8 +30,19 @@ const { handleInit, resolveMcpSmokeTarget } = require('./commands/axi');
30
30
  const cliPath = path.join(__dirname, 'run.js');
31
31
  const testGxPath = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-gx-'));
32
32
  fs.writeFileSync(path.join(testGxPath, 'genexus.exe'), '');
33
- const testGatewayEnv = { GENEXUS_MCP_GATEWAY_EXE: process.execPath };
34
- test.after(() => fs.rmSync(testGxPath, { recursive: true, force: true }));
33
+ const testGatewayPath = path.join(os.tmpdir(), `genexus-mcp-test-${process.pid}`, 'GxMcp.Gateway.exe');
34
+ fs.mkdirSync(path.dirname(testGatewayPath), { recursive: true });
35
+ if (process.platform === 'win32') {
36
+ fs.copyFileSync(process.env.ComSpec || 'C:\\Windows\\System32\\cmd.exe', testGatewayPath);
37
+ } else {
38
+ fs.writeFileSync(testGatewayPath, '#!/bin/sh\nexit 0\n');
39
+ fs.chmodSync(testGatewayPath, 0o755);
40
+ }
41
+ const testGatewayEnv = { GENEXUS_MCP_GATEWAY_EXE: testGatewayPath };
42
+ test.after(() => {
43
+ fs.rmSync(testGxPath, { recursive: true, force: true });
44
+ fs.rmSync(testGatewayPath, { force: true });
45
+ });
35
46
 
36
47
  function runCli(args, opts = {}) {
37
48
  const spawnOptions = {
@@ -1641,6 +1652,125 @@ test('clients list flags a registered command pointing at a missing launcher as
1641
1652
  fs.rmSync(tempRoot, { recursive: true, force: true });
1642
1653
  });
1643
1654
 
1655
+ test('clients list separates a registered node launcher without an entrypoint from semantic validity', () => {
1656
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-node-no-entrypoint-'));
1657
+ try {
1658
+ const env = sandboxHomeEnv(tempRoot);
1659
+ const codexCfg = path.join(env.HOME, '.codex', 'config.toml');
1660
+ fs.mkdirSync(path.dirname(codexCfg), { recursive: true });
1661
+ const nodeCommand = process.execPath.replace(/\\/g, '\\\\').replace(/"/g, '\\\\"');
1662
+ fs.writeFileSync(codexCfg, [
1663
+ '[mcp_servers.genexus18mcp]',
1664
+ `command = "${nodeCommand}"`,
1665
+ 'args = []',
1666
+ ''
1667
+ ].join('\n'));
1668
+
1669
+ const result = runCli(['clients', '--format', 'json'], { env });
1670
+ assert.equal(result.status, 0, 'clients diagnostics retain the successful list exit code');
1671
+ const parsed = JSON.parse(result.stdout);
1672
+ const codex = parsed.ok.clients.find((client) => client.id === 'codex-cli');
1673
+ assert.ok(codex, 'Codex CLI should be listed');
1674
+ assert.equal(codex.registered, true, 'the config entry remains registered');
1675
+ assert.equal(codex.command, process.execPath, 'the effective command is reported');
1676
+ assert.deepEqual(codex.args, [], 'the effective args are reported');
1677
+ assert.equal(codex.launcherStructuralState, 'present', 'node.exe exists locally');
1678
+ assert.equal(codex.launcherSemanticState, 'invalid', 'node.exe without an entrypoint cannot start MCP');
1679
+ assert.match(codex.launcherSemanticReason, /entrypoint/i, 'the reason tells the operator what is missing');
1680
+ assert.equal(codex.commandStale, true, 'known invalid launcher is actionable through the existing stale flag');
1681
+ assert.match(codex.commandStaleReason, /entrypoint/i);
1682
+ } finally {
1683
+ fs.rmSync(tempRoot, { recursive: true, force: true });
1684
+ }
1685
+ });
1686
+
1687
+ test('clients list validates existing Gateway and known npx launchers locally', () => {
1688
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-known-launchers-'));
1689
+ try {
1690
+ const env = sandboxHomeEnv(tempRoot);
1691
+ const gatewayPath = path.join(tempRoot, 'publish', 'GxMcp.Gateway.exe');
1692
+ const antigravityCfg = path.join(tempRoot, '.gemini', 'antigravity', 'mcp_config.json');
1693
+ const cursorCfg = path.join(tempRoot, '.cursor', 'mcp.json');
1694
+ const codexCfg = path.join(tempRoot, '.codex', 'config.toml');
1695
+ const nodeEntrypoint = path.join(__dirname, 'run.js');
1696
+ fs.mkdirSync(path.dirname(gatewayPath), { recursive: true });
1697
+ fs.mkdirSync(path.dirname(antigravityCfg), { recursive: true });
1698
+ fs.mkdirSync(path.dirname(cursorCfg), { recursive: true });
1699
+ fs.mkdirSync(path.dirname(codexCfg), { recursive: true });
1700
+ fs.writeFileSync(gatewayPath, 'gateway');
1701
+ fs.writeFileSync(antigravityCfg, JSON.stringify({
1702
+ mcpServers: { genexus18mcp: { command: gatewayPath, args: [] } }
1703
+ }, null, 2));
1704
+ fs.writeFileSync(cursorCfg, JSON.stringify({
1705
+ mcpServers: { genexus18mcp: { command: 'npx.cmd', args: ['-y', 'genexus-mcp@latest'] } }
1706
+ }, null, 2));
1707
+ const nodeCommand = process.execPath.replace(/\\/g, '\\\\');
1708
+ const nodeScript = nodeEntrypoint.replace(/\\/g, '\\\\');
1709
+ fs.writeFileSync(codexCfg, [
1710
+ '[mcp_servers.genexus18mcp]',
1711
+ `command = "${nodeCommand}"`,
1712
+ `args = ["${nodeScript}"]`,
1713
+ ''
1714
+ ].join('\n'));
1715
+
1716
+ const result = runCli(['clients', '--format', 'json'], {
1717
+ env: { ...env, GENEXUS_MCP_GATEWAY_EXE: gatewayPath }
1718
+ });
1719
+ assert.equal(result.status, 0, `${result.stderr}\n${result.stdout}`);
1720
+ const parsed = JSON.parse(result.stdout);
1721
+ const gateway = parsed.ok.clients.find((client) => client.id === 'antigravity');
1722
+ const npx = parsed.ok.clients.find((client) => client.id === 'cursor');
1723
+ assert.ok(gateway && npx, 'both configured clients should be listed');
1724
+
1725
+ assert.equal(gateway.registered, true);
1726
+ assert.equal(gateway.command, gatewayPath);
1727
+ assert.deepEqual(gateway.args, []);
1728
+ assert.equal(gateway.launcherStructuralState, 'present');
1729
+ assert.equal(gateway.launcherSemanticState, 'valid');
1730
+ assert.equal(gateway.commandStale, false);
1731
+
1732
+ assert.equal(npx.registered, true);
1733
+ assert.equal(npx.command, 'npx.cmd');
1734
+ assert.deepEqual(npx.args, ['-y', 'genexus-mcp@latest']);
1735
+ assert.equal(npx.launcherStructuralState, 'present');
1736
+ assert.equal(npx.launcherSemanticState, 'valid');
1737
+ assert.equal(npx.commandStale, false);
1738
+
1739
+ const node = parsed.ok.clients.find((client) => client.id === 'codex-cli');
1740
+ assert.ok(node, 'Codex CLI should be listed');
1741
+ assert.equal(node.command, process.execPath);
1742
+ assert.deepEqual(node.args, [nodeEntrypoint]);
1743
+ assert.equal(node.launcherStructuralState, 'present');
1744
+ assert.equal(node.launcherSemanticState, 'valid');
1745
+ assert.equal(node.commandStale, false);
1746
+ assert.equal(parsed.ok.summary.semanticInvalid, 0);
1747
+ } finally {
1748
+ fs.rmSync(tempRoot, { recursive: true, force: true });
1749
+ }
1750
+ });
1751
+
1752
+ test('clients list keeps an existing unrecognized launcher indeterminate', () => {
1753
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-unknown-launcher-'));
1754
+ try {
1755
+ const env = sandboxHomeEnv(tempRoot);
1756
+ const cursorCfg = path.join(tempRoot, '.cursor', 'mcp.json');
1757
+ fs.mkdirSync(path.dirname(cursorCfg), { recursive: true });
1758
+ fs.writeFileSync(cursorCfg, JSON.stringify({
1759
+ mcpServers: { genexus18mcp: { command: 'custom-mcp-launcher', args: ['--stdio'] } }
1760
+ }, null, 2));
1761
+
1762
+ const result = runCli(['clients', '--format', 'json'], { env });
1763
+ assert.equal(result.status, 0);
1764
+ const cursor = JSON.parse(result.stdout).ok.clients.find((client) => client.id === 'cursor');
1765
+ assert.equal(cursor.registered, true);
1766
+ assert.equal(cursor.launcherStructuralState, 'indeterminate');
1767
+ assert.equal(cursor.launcherSemanticState, 'unknown');
1768
+ assert.equal(cursor.commandStale, false, 'unknown commands are not treated as invalid');
1769
+ } finally {
1770
+ fs.rmSync(tempRoot, { recursive: true, force: true });
1771
+ }
1772
+ });
1773
+
1644
1774
  test('clients list marks an Antigravity launcher from an old package cache as stale', () => {
1645
1775
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-antigravity-stale-'));
1646
1776
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genexus-mcp",
3
- "version": "3.4.2",
3
+ "version": "3.5.0",
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": [
@@ -35,6 +35,7 @@
35
35
  "test": "node --test cli/run.test.js cli/lib/client-adapters.test.js cli/docs.test.js scripts/lint.test.js && npm run test:live-contract",
36
36
  "lint": "node scripts/lint.js",
37
37
  "test:live-contract": "pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/tests/test-live.test.ps1",
38
+ "test:integration": "pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/integration-preflight.ps1",
38
39
  "test:upstream-drift": "pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/tests/test-upstream-drift.ps1",
39
40
  "test:one": "node scripts/test-one.js",
40
41
  "prepack": "node scripts/clean-package.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.4.2": {
10
+ "GxMcp.Gateway/3.5.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.4.2": {
69
+ "GxMcp.Gateway/3.5.0": {
70
70
  "type": "project",
71
71
  "serviceable": false,
72
72
  "sha512": ""
Binary file
Binary file
@@ -1,19 +1,19 @@
1
1
  {
2
+ "ConfigSchemaVersion": 2,
2
3
  "Environment": {
3
4
  "ResolutionPolicy": "strict"
4
5
  },
5
- "ConfigSchemaVersion": 2,
6
- "GeneXus": {
7
- "WorkerExecutable": "worker\\\\GxMcp.Worker.exe",
8
- "InstallationPath": "C:\\Program Files (x86)\\GeneXus\\GeneXus18"
9
- },
10
- "GatewayMode": "stdio-isolated",
11
6
  "Server": {
12
- "McpStdio": true,
13
7
  "HttpPort": 0,
14
- "EmitStructuredContent": false,
15
- "SessionIdleTimeoutMinutes": 10,
8
+ "TerseResponses": true,
9
+ "McpStdio": true,
16
10
  "WorkerIdleTimeoutMinutes": 5,
17
- "TerseResponses": true
11
+ "EmitStructuredContent": false,
12
+ "SessionIdleTimeoutMinutes": 10
13
+ },
14
+ "GatewayMode": "stdio-isolated",
15
+ "GeneXus": {
16
+ "InstallationPath": "C:\\Program Files (x86)\\GeneXus\\GeneXus18",
17
+ "WorkerExecutable": "worker\\\\GxMcp.Worker.exe"
18
18
  }
19
19
  }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": "gxmcp-release-manifest/1",
3
- "version": "3.4.2",
4
- "sourceCommit": "5d38111f28d81e59ea31f5f792950809a8090f3e",
3
+ "version": "3.5.0",
4
+ "sourceCommit": "30cbc75a0bf7edb088879395ba9ea82857ce60f2",
5
5
  "sourceCommitPolicy": "exact-tag",
6
- "generatedAtUtc": "2026-09-13T07:17:16.7652929Z",
6
+ "generatedAtUtc": "2026-09-14T18:52:46.1727079Z",
7
7
  "runtime": {
8
8
  "gateway": "net10.0-windows",
9
9
  "worker": "net48-x86",
@@ -15,28 +15,28 @@
15
15
  "2026-07-28"
16
16
  ],
17
17
  "schema": "tool_definitions.json",
18
- "schemaSha256": "33938ec15d5eaa6d00507d37dbb6d8bdbb005f823b98f8c9894fd9d89dceb640",
18
+ "schemaSha256": "05b92690c10919de5059d00ab28fb4e91a67643436d0be186cecec6d962c3652",
19
19
  "provenance": "gxmcp-sbom.json",
20
20
  "artifacts": [
21
21
  {
22
22
  "path": "gxmcp-sbom.json",
23
23
  "size": 590,
24
- "sha256": "124327c27bb2ae764f1e41d379d145192c39e8f0f0049d045fae1e8e05b1f44e"
24
+ "sha256": "929178a25b44d92d554aeaf85ac7479671a8d72aea5c4ebd8b78944154a6179d"
25
25
  },
26
26
  {
27
27
  "path": "GxMcp.Gateway.exe",
28
28
  "size": 162304,
29
- "sha256": "0f4838234342836b84f71de8bac102883a799ff9990d1856c49043711fbaac7c"
29
+ "sha256": "6a41baf3616dacd48c28897126eb54f9fd90bfc6769b3b0b56f74958d8ac0b15"
30
30
  },
31
31
  {
32
32
  "path": "tool_definitions.json",
33
- "size": 112865,
34
- "sha256": "33938ec15d5eaa6d00507d37dbb6d8bdbb005f823b98f8c9894fd9d89dceb640"
33
+ "size": 112952,
34
+ "sha256": "05b92690c10919de5059d00ab28fb4e91a67643436d0be186cecec6d962c3652"
35
35
  },
36
36
  {
37
37
  "path": "worker/GxMcp.Worker.exe",
38
- "size": 3106304,
39
- "sha256": "b2a19ddba99b48df656709aca60079d17a544a5edd4a61e6e2f48f24d0accb44"
38
+ "size": 3118080,
39
+ "sha256": "67d74b177474aa6bd36c35d596e182c78ef13644712641a548e2ee4f72b7e62b"
40
40
  }
41
41
  ]
42
42
  }
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "schemaVersion": "gxmcp-provenance/1",
3
- "version": "3.4.2",
4
- "sourceCommit": "5d38111f28d81e59ea31f5f792950809a8090f3e",
3
+ "version": "3.5.0",
4
+ "sourceCommit": "30cbc75a0bf7edb088879395ba9ea82857ce60f2",
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": "b334c1359b3bbf02ca0a94cec034a50154bcce11621c71347c80f6253918f346"
11
+ "sha256": "5f9c6a7eb099831205bd7d1d40fee00420b044ad7459ae6d119eb2027b456dca"
12
12
  },
13
13
  {
14
14
  "name": "nexus-ide",
15
15
  "lockfile": "src/nexus-ide/package-lock.json",
16
16
  "size": 274983,
17
- "sha256": "dec6919a7db31734eadc784d8143eb93d59420990c2b57987f695f683358d5ad"
17
+ "sha256": "48726bc21de60b115e70287234d18dae2fbe845871b20d0d131c032c6210b6aa"
18
18
  }
19
19
  ]
20
20
  }
@@ -39,7 +39,7 @@
39
39
  {"name":"genexus_kb_version","description":"KB model-version management (Create Version/Branch/Activate/Revert) over the SDK's KBVersionHelper — the IDE Version menu's code path. action=list is read-only; freeze/branch/set_active/revert mutate the KB's version tree. Version results keep lastUpdate separate from createdAt; createdAt is null with createdAtAvailable=false when the SDK has no reliable creation timestamp.","inputSchema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["list","freeze","branch","set_active","revert"],"description":"KB model-version or branch operation to perform."},"name":{"type":"string","description":"New version/branch name (freeze/branch)."},"description":{"type":"string","description":"New version/branch description (freeze/branch)."},"parentVersion":{"type":"string","description":"Parent version name (freeze/branch). Defaults to the active version."},"targetVersion":{"type":"string","description":"Version to activate (set_active) or revert to (revert)."},"fromVersion":{"type":"string","description":"revert: source version. Defaults to the active version."},"backupModel":{"type":"boolean","description":"freeze: back up the KB model. Default false."},"includeEnvironments":{"type":"boolean","description":"branch: include environments. Default false."},"autoUpdate":{"type":"boolean","description":"set_active: auto-update the working model. Default false."},"kb":{"type":"string","description":"KB alias."}},"examples":[{"action":"list"},{"action":"freeze","name":"v1.0","description":"Release 1.0"},{"action":"set_active","targetVersion":"Trunk"}]},"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false}},
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
- {"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."},"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}},
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
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}},
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}},
Binary file