genexus-mcp 2.8.2 → 2.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -175,13 +175,22 @@ Auto-detected and auto-configured by the installer:
175
175
  | Claude Desktop | ✅ | Restart required after install |
176
176
  | Claude Code (CLI) | ✅ | Reload session |
177
177
  | Cursor | ✅ | Restart required |
178
- | Antigravity | ✅ | Restart required |
178
+ | Antigravity | ✅ | Restart required; detected even before its MCP config exists |
179
+ | Gemini CLI | ✅ | — |
180
+ | OpenCode (CLI) | ✅ | Reads `opencode.json` / `opencode.jsonc` |
181
+ | Codex CLI | ✅ | Writes `~/.codex/config.toml` |
182
+ | VS Code / VS Code Insiders | ✅ | Native MCP (`User/mcp.json`); restart required |
183
+ | OpenCode Desktop | Detect-only | Reported as installed; add the server from the app's settings |
179
184
  | Any MCP client | Manual | Use the JSON snippet printed by `init` |
180
185
 
186
+ Run **`npx genexus-mcp clients`** at any time to see which agents are installed, which have `genexus` registered, and whether any point at a stale gateway exe. To (re)register specific ones: `npx genexus-mcp clients add --clients antigravity,vscode`.
187
+
181
188
  ---
182
189
 
183
190
  ## Troubleshooting
184
191
 
192
+ First stop for any "the agent doesn't see GeneXus" problem: **`npx genexus-mcp clients`** (is it registered? does it point at a gateway exe that still exists?) and **`npx genexus-mcp doctor --mcp-smoke`**.
193
+
185
194
  Most install issues fall into a handful of buckets — see **[TROUBLESHOOTING.md](TROUBLESHOOTING.md)** for fixes:
186
195
 
187
196
  - Installer can't find GeneXus or the KB
@@ -375,12 +384,17 @@ This repo ships a set of **agent skills** under `.gemini/skills/` that any MCP-c
375
384
 
376
385
  Third-party skills are Apache 2.0 (see [`.gemini/skills/NOTICE.md`](.gemini/skills/NOTICE.md)). To refresh against upstream, follow the steps in `NOTICE.md`.
377
386
 
378
- ### Nexus-IDE (VS Code extension)
387
+ ### Nexus-IDE (VS Code extension — optional, not auto-installed)
388
+
389
+ `src/nexus-ide` is a lightweight, experimental VS Code extension in the repo. The installer **no longer packages or installs it** — VS Code is wired up as a native MCP client instead (see [Supported AI clients](#supported-ai-clients)). If you want the extension, build and install it manually:
390
+
391
+ ```powershell
392
+ cd src/nexus-ide; npm ci; npm run compile
393
+ npx --yes @vscode/vsce package --out nexus-ide.vsix
394
+ code --install-extension nexus-ide.vsix --force
395
+ ```
379
396
 
380
- `src/nexus-ide` is a lightweight VS Code extension that ships with the repo:
381
- - Virtual filesystem using the `genexus://` scheme
382
- - Dynamic KB explorer with multi-part editing (Source, Rules, Events, Variables)
383
- - Built-in MCP discovery commands (tools, resources, prompts)
397
+ It provides a virtual filesystem (`genexus://` scheme), a KB explorer with multi-part editing, and MCP discovery commands.
384
398
 
385
399
  ### Automated release
386
400
 
@@ -12,6 +12,8 @@ const {
12
12
  patchClientConfig,
13
13
  unpatchClientConfig,
14
14
  getClientConfigTargets,
15
+ detectClientInstalled,
16
+ clientsStatus,
15
17
  filterClientTargets,
16
18
  listSupportedClientIds,
17
19
  getLocalAppDataCacheDir,
@@ -701,6 +703,25 @@ async function handleDoctor(options, ctx) {
701
703
  const inProcessLoad = buildInProcessBuildAssemblyLoadCheck(gxPath);
702
704
  checks.push({ id: 'in_process_build_assembly_load', status: inProcessLoad.status, detail: inProcessLoad.detail });
703
705
 
706
+ // Client registration summary — one line answering "are my AI agents wired up?".
707
+ const clientRows = clientsStatus();
708
+ const installedRows = clientRows.filter((r) => r.installed);
709
+ const staleRows = clientRows.filter((r) => r.commandStale);
710
+ const installedUnregistered = installedRows.filter((r) => !r.registered && r.writeSupported);
711
+ let clientsStatusLevel = 'pass';
712
+ let clientsDetail;
713
+ if (staleRows.length > 0) {
714
+ clientsStatusLevel = 'warn';
715
+ clientsDetail = `${staleRows.map((r) => r.name).join(', ')} point at a missing gateway exe. Re-register: genexus-mcp clients add --clients ${staleRows.map((r) => r.id).join(',')}.`;
716
+ } else if (installedUnregistered.length > 0) {
717
+ clientsStatusLevel = 'warn';
718
+ clientsDetail = `${installedUnregistered.length} installed agent(s) not registered (${installedUnregistered.map((r) => r.name).join(', ')}). Run: genexus-mcp clients add --clients ${installedUnregistered.map((r) => r.id).join(',')}.`;
719
+ } else {
720
+ const reg = clientRows.filter((r) => r.registered).length;
721
+ clientsDetail = `${reg} agent(s) registered; ${installedRows.length} installed. Run \`genexus-mcp clients\` for the full table.`;
722
+ }
723
+ checks.push({ id: 'clients_registered', status: clientsStatusLevel, detail: clientsDetail });
724
+
704
725
  if (data.gatewayExeFound) {
705
726
  const probe = await probeGatewaySpawn();
706
727
  checks.push({ id: 'gateway_spawn_probe', status: probe.status, detail: probe.detail });
@@ -1191,10 +1212,16 @@ async function runInteractiveInit(ctx) {
1191
1212
  ctx.stderr.write('\n3) Select AI agents to register (y/N per agent; Enter accepts default):\n');
1192
1213
  const selectedIds = [];
1193
1214
  for (const target of platformTargets) {
1194
- const installed = fs.existsSync(target.path);
1195
- const defaultYes = installed;
1196
- const tag = installed ? 'detected' : 'not detected';
1197
- const prompt = ` - ${target.name} [${tag}] (${defaultYes ? 'Y/n' : 'y/N'}): `;
1215
+ const detection = detectClientInstalled(target);
1216
+ const defaultYes = detection.installed;
1217
+ const tag = detection.installed ? 'detected' : 'not detected';
1218
+ // When not detected, show where we looked so the user understands why
1219
+ // (and can still type `y` to register a freshly-installed agent).
1220
+ let hint = '';
1221
+ if (!detection.installed && detection.markersChecked.length) {
1222
+ hint = ` — looked in ${detection.markersChecked[0]}`;
1223
+ }
1224
+ const prompt = ` - ${target.name} [${tag}${hint}] (${defaultYes ? 'Y/n' : 'y/N'}): `;
1198
1225
  const ans = (await question(prompt)).trim().toLowerCase();
1199
1226
  const yes = ans === '' ? defaultYes : (ans === 'y' || ans === 'yes');
1200
1227
  if (yes) selectedIds.push(target.id);
@@ -1715,6 +1742,107 @@ async function handleUninstall(options, ctx) {
1715
1742
  };
1716
1743
  }
1717
1744
 
1745
+ async function handleClients(subcommand, options, ctx) {
1746
+ const sub = subcommand || 'list';
1747
+
1748
+ if (sub === 'list') {
1749
+ const rows = clientsStatus();
1750
+ const installedCount = rows.filter((r) => r.installed).length;
1751
+ const registeredCount = rows.filter((r) => r.registered).length;
1752
+ const help = [];
1753
+ const installedUnregistered = rows.filter((r) => r.installed && !r.registered && r.writeSupported);
1754
+ if (installedUnregistered.length > 0) {
1755
+ help.push(`Register installed-but-unregistered agents: genexus-mcp clients add --clients ${installedUnregistered.map((r) => r.id).join(',')}`);
1756
+ }
1757
+ const stale = rows.filter((r) => r.commandStale);
1758
+ if (stale.length > 0) {
1759
+ 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(',')}`);
1760
+ }
1761
+ for (const r of rows) {
1762
+ if (r.installed && !r.writeSupported && r.note) help.push(`${r.name}: ${r.note}`);
1763
+ }
1764
+ return {
1765
+ exitCode: ctx.EXIT_CODES.OK,
1766
+ envelope: {
1767
+ ok: {
1768
+ clients: rows,
1769
+ summary: { total: rows.length, installed: installedCount, registered: registeredCount }
1770
+ },
1771
+ help
1772
+ }
1773
+ };
1774
+ }
1775
+
1776
+ if (sub === 'add' || sub === 'remove') {
1777
+ const ids = resolveClientIds(options);
1778
+ if (sub === 'add' && (!ids || ids.length === 0)) {
1779
+ return {
1780
+ exitCode: ctx.EXIT_CODES.USAGE,
1781
+ envelope: usageEnvelope('`clients add` requires --clients <csv> (e.g. --clients antigravity,vscode).', ctx.EXIT_CODES.USAGE)
1782
+ };
1783
+ }
1784
+ const validation = validateClientIds(ids);
1785
+ if (!validation.ok) {
1786
+ return { exitCode: ctx.EXIT_CODES.USAGE, envelope: usageEnvelope(validation.message, ctx.EXIT_CODES.USAGE) };
1787
+ }
1788
+
1789
+ if (sub === 'add') {
1790
+ const configPath = resolveConfigPathNoMutate(ctx.cwd);
1791
+ if (!configPath) {
1792
+ return {
1793
+ exitCode: ctx.EXIT_CODES.ERROR,
1794
+ envelope: operationalErrorEnvelope(
1795
+ 'No config.json found to point the clients at. Run `genexus-mcp init` first (or run from a KB folder).',
1796
+ ctx.EXIT_CODES.ERROR
1797
+ )
1798
+ };
1799
+ }
1800
+ let patch;
1801
+ try {
1802
+ // Explicit add: write even if install markers are absent (the user asked for it).
1803
+ patch = patchClientConfig(configPath, { ids, onlyExisting: false });
1804
+ } catch (err) {
1805
+ return {
1806
+ exitCode: ctx.EXIT_CODES.ERROR,
1807
+ envelope: operationalErrorEnvelope(
1808
+ sanitizeOperationalMessage(`Client registration failed: ${err && err.message ? err.message : 'unknown error'}`),
1809
+ ctx.EXIT_CODES.ERROR
1810
+ )
1811
+ };
1812
+ }
1813
+ const help = [];
1814
+ if (patch.patched.length > 0) help.push('Restart the affected AI client(s) to load the new MCP config.');
1815
+ if (patch.failed.length > 0) help.push('Some clients failed (see meta.failedClients).');
1816
+ return {
1817
+ exitCode: ctx.EXIT_CODES.OK,
1818
+ envelope: {
1819
+ ok: { action: 'clients.add', configPath, patchedClients: patch.patched, patchedCount: patch.patched.length },
1820
+ help,
1821
+ meta: { failedClients: patch.failed, skippedClients: patch.skipped }
1822
+ }
1823
+ };
1824
+ }
1825
+
1826
+ // remove
1827
+ const unpatch = unpatchClientConfig(ids ? { ids } : {});
1828
+ const help = [];
1829
+ if (unpatch.removed.length > 0) help.push('Restart the affected AI client(s) to drop the stale MCP connection.');
1830
+ return {
1831
+ exitCode: ctx.EXIT_CODES.OK,
1832
+ envelope: {
1833
+ ok: { action: 'clients.remove', removedClients: unpatch.removed, removedCount: unpatch.removed.length },
1834
+ help,
1835
+ meta: { skippedClients: unpatch.skipped, failedClients: unpatch.failed }
1836
+ }
1837
+ };
1838
+ }
1839
+
1840
+ return {
1841
+ exitCode: ctx.EXIT_CODES.USAGE,
1842
+ envelope: usageEnvelope('clients supports subcommands `list`, `add`, `remove`.', ctx.EXIT_CODES.USAGE)
1843
+ };
1844
+ }
1845
+
1718
1846
  async function handleKb(subcommand, options, ctx) {
1719
1847
  const data = buildStatusData(ctx.cwd);
1720
1848
  if (!data.configPath) {
@@ -1900,13 +2028,27 @@ function commandHelpMap() {
1900
2028
  'genexus-mcp kb remove --name sales'
1901
2029
  ]
1902
2030
  },
2031
+ clients: {
2032
+ usage: 'genexus-mcp clients [list] [--format ...] OR genexus-mcp clients add --clients <csv> OR genexus-mcp clients remove [--clients <csv>]',
2033
+ examples: [
2034
+ 'genexus-mcp clients # show every AI agent: installed? registered? where?',
2035
+ 'genexus-mcp clients --format json',
2036
+ 'genexus-mcp clients add --clients antigravity,vscode',
2037
+ 'genexus-mcp clients remove --clients cursor'
2038
+ ]
2039
+ },
1903
2040
  llm: {
1904
2041
  usage: 'genexus-mcp llm help [--full] [--fields f1,f2] [--format toon|json|text]',
1905
2042
  examples: ['genexus-mcp llm help --format json', 'genexus-mcp llm help --full --format json']
1906
2043
  },
1907
2044
  update: {
1908
- usage: 'genexus-mcp update [--format toon|json|text]',
1909
- examples: ['genexus-mcp update', 'genexus-mcp update --format json']
2045
+ usage: 'genexus-mcp update [--apply] [--yes] [--channel latest|next] [--format toon|json|text]',
2046
+ examples: [
2047
+ 'genexus-mcp update # check; reports your install method + the right upgrade step',
2048
+ 'genexus-mcp update --apply # perform the upgrade for your install method (confirms first)',
2049
+ 'genexus-mcp update --apply --yes # unattended (CI/automation)',
2050
+ 'genexus-mcp update --channel next # check the @next dist-tag'
2051
+ ]
1910
2052
  },
1911
2053
  layout: {
1912
2054
  usage: 'genexus-mcp layout status [--title "GeneXus"] [--format ...] OR genexus-mcp layout run --action <focus|activate-layout|activate-tab|send-keys|type-text|click> [--tab "Layout"] [--keys "..."] [--text "..."] [--x N --y N] [--title "..."] [--format ...] OR genexus-mcp layout inspect [--tab "Layout"] [--limit N] [--full] [--title "..."] [--format ...]',
@@ -1935,8 +2077,8 @@ async function handleHome(_options, ctx) {
1935
2077
  description: 'GeneXus MCP launcher and AXI-oriented utility CLI',
1936
2078
  ready: data.ready,
1937
2079
  next: data.ready
1938
- ? ['genexus-mcp status', 'genexus-mcp doctor --mcp-smoke', 'genexus-mcp tools list --limit 10', 'genexus-mcp layout status', 'genexus-mcp layout inspect --tab Layout']
1939
- : ['genexus-mcp status', 'genexus-mcp doctor --full', 'genexus-mcp init --kb "<kbPath>" --gx "<geneXusPath>"']
2080
+ ? ['genexus-mcp status', 'genexus-mcp clients', 'genexus-mcp doctor --mcp-smoke', 'genexus-mcp tools list --limit 10', 'genexus-mcp layout status']
2081
+ : ['genexus-mcp status', 'genexus-mcp clients', 'genexus-mcp doctor --full', 'genexus-mcp init --kb "<kbPath>" --gx "<geneXusPath>"']
1940
2082
  },
1941
2083
  help: []
1942
2084
  }
@@ -2078,6 +2220,7 @@ module.exports = {
2078
2220
  handleWhoami,
2079
2221
  handleUninstall,
2080
2222
  handleKb,
2223
+ handleClients,
2081
2224
  handleHome,
2082
2225
  handleLlmHelp,
2083
2226
  handleLayout,
package/cli/index.js CHANGED
@@ -19,6 +19,7 @@ const {
19
19
  handleWhoami,
20
20
  handleUninstall,
21
21
  handleKb,
22
+ handleClients,
22
23
  handleHome,
23
24
  handleLlmHelp,
24
25
  handleLayout,
@@ -47,6 +48,8 @@ const GLOBAL_DEFAULTS = {
47
48
  noSmoke: false,
48
49
  warm: false,
49
50
  yes: false,
51
+ apply: false,
52
+ channel: null,
50
53
  name: null,
51
54
  limit: 100,
52
55
  query: null,
@@ -55,7 +58,7 @@ const GLOBAL_DEFAULTS = {
55
58
  help: false
56
59
  };
57
60
 
58
- const KNOWN_COMMANDS = new Set(['status', 'doctor', 'tools', 'config', 'init', 'setup', 'whoami', 'uninstall', 'kb', 'help', 'home', 'axi', 'llm', 'layout', 'update']);
61
+ const KNOWN_COMMANDS = new Set(['status', 'doctor', 'tools', 'config', 'init', 'setup', 'whoami', 'uninstall', 'kb', 'clients', 'help', 'home', 'axi', 'llm', 'layout', 'update']);
59
62
 
60
63
  function parseArgs(argv) {
61
64
  const result = {
@@ -115,6 +118,11 @@ function parseArgs(argv) {
115
118
  tokens.shift();
116
119
  }
117
120
 
121
+ if (result.command === 'clients' && ['list', 'add', 'remove'].includes(tokens[0])) {
122
+ result.subcommand = tokens[0];
123
+ tokens.shift();
124
+ }
125
+
118
126
  if (result.command === 'layout' && (tokens[0] === 'status' || tokens[0] === 'run' || tokens[0] === 'inspect')) {
119
127
  result.subcommand = tokens[0];
120
128
  tokens.shift();
@@ -281,6 +289,15 @@ function parseArgs(argv) {
281
289
  case 'yes':
282
290
  result.options.yes = true;
283
291
  break;
292
+ case 'apply':
293
+ result.options.apply = true;
294
+ break;
295
+ case 'channel': {
296
+ const val = takeValue();
297
+ if (val) result.options.channel = val;
298
+ else result.unknownFlags.push('--channel requires a value');
299
+ break;
300
+ }
284
301
  case 'quiet':
285
302
  result.options.quiet = true;
286
303
  break;
@@ -395,6 +412,9 @@ function resolveMetaCommand(parsed, targetHelp) {
395
412
  if (parsed.command === 'kb') {
396
413
  return parsed.subcommand ? `kb.${parsed.subcommand}` : 'kb';
397
414
  }
415
+ if (parsed.command === 'clients') {
416
+ return parsed.subcommand ? `clients.${parsed.subcommand}` : 'clients.list';
417
+ }
398
418
  if (parsed.command === 'update') return 'update';
399
419
  return parsed.command || 'unknown';
400
420
  }
@@ -523,6 +543,9 @@ async function main(argv) {
523
543
  }
524
544
  result = await handleKb(parsed.subcommand, parsed.options, ctx);
525
545
  break;
546
+ case 'clients':
547
+ result = await handleClients(parsed.subcommand, parsed.options, ctx);
548
+ break;
526
549
  case 'update':
527
550
  result = await handleUpdate(parsed.options, ctx);
528
551
  break;