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 +20 -6
- package/cli/commands/axi.js +151 -8
- package/cli/index.js +24 -1
- package/cli/lib/config.js +395 -19
- package/cli/lib/update-check.js +241 -74
- package/cli/run.test.js +253 -1
- package/docs/llm_cli_mcp_playbook.md +1 -0
- package/package.json +1 -1
- package/publish/GxMcp.Gateway.deps.json +2 -2
- package/publish/GxMcp.Gateway.dll +0 -0
- package/publish/GxMcp.Gateway.exe +0 -0
- package/publish/config.json +17 -20
- package/publish/worker/GxMcp.Worker.exe +0 -0
- package/publish/GxMcp.Gateway.pdb +0 -0
- package/publish/worker/GxMcp.Worker.pdb +0 -0
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
|
-
|
|
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
|
|
package/cli/commands/axi.js
CHANGED
|
@@ -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
|
|
1195
|
-
const defaultYes = installed;
|
|
1196
|
-
const tag = installed ? 'detected' : 'not detected';
|
|
1197
|
-
|
|
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: [
|
|
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'
|
|
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;
|