sap-ai-dev-toolkit 0.8.0 → 0.8.2
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 +38 -5
- package/package.json +1 -1
- package/src/launcher.mjs +8 -2
- package/src/setup.mjs +82 -20
package/README.md
CHANGED
|
@@ -242,11 +242,17 @@ Once the destination server is enabled in your chat's tools picker, ask for outc
|
|
|
242
242
|
|
|
243
243
|
## 📦 Installation
|
|
244
244
|
|
|
245
|
-
Install globally from SAP Business Application Studio or from a local workstation with SAP GUI installed:
|
|
245
|
+
Install globally from SAP Business Application Studio or from a local workstation with SAP GUI installed, then run setup explicitly for your environment:
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
| Environment / device | What to run | What setup does |
|
|
248
|
+
| --- | --- | --- |
|
|
249
|
+
| **SAP Business Application Studio dev space** | `npm install --global sap-ai-dev-toolkit`<br>`sap-ai-dev --setup` | Discovers BAS destinations, optionally imports Cloud Foundry Destination service records, and writes VS Code/BAS MCP entries. |
|
|
250
|
+
| **Windows workstation with SAP GUI** | `npm install --global sap-ai-dev-toolkit`<br>`sap-ai-dev --setup` | Discovers SAP GUI landscape entries, asks for SAP client(s) and ADT URL, then writes local VS Code MCP entries. Windows SSO can be selected if the ADT endpoint supports Negotiate/SPNEGO. |
|
|
251
|
+
| **macOS workstation with SAP GUI** | `npm install --global sap-ai-dev-toolkit`<br>`sap-ai-dev --setup` | Discovers SAP GUI landscape entries, asks for SAP client(s) and ADT URL, then writes local VS Code MCP entries with secure login prompts. |
|
|
252
|
+
| **Any local interactive terminal, already installed** | `sap-ai-dev --setup` | Re-runs the wizard to add, update, or remove generated MCP entries. Plain `sap-ai-dev` with no arguments also starts local setup when no BAS environment is detected. |
|
|
253
|
+
| **npm install hook did not show a wizard** | `sap-ai-dev --setup` | This is expected on some npm/Windows combinations because lifecycle scripts may run without an interactive console. The explicit setup command is the reliable path. |
|
|
254
|
+
| **Force npm to show install-time script output** | `npm install --global --foreground-scripts sap-ai-dev-toolkit` | Optional troubleshooting mode if you specifically want to see postinstall output during installation. Still run `sap-ai-dev --setup` afterwards if no destinations were configured. |
|
|
255
|
+
| **No global install / temporary setup** | `npx --yes --ignore-scripts --package=sap-ai-dev-toolkit sap-ai-dev --setup --npx` | Runs the guided setup through npm and writes generated entries that launch the pinned package through `npx`. |
|
|
250
256
|
|
|
251
257
|
If you previously installed the old package, remove it first because the legacy `bas-vsp-mcp` command is no longer shipped:
|
|
252
258
|
|
|
@@ -272,8 +278,35 @@ The installer handles VSP provisioning automatically:
|
|
|
272
278
|
|
|
273
279
|
Setup detects where it is running. With `H2O_URL` set, it treats the environment as SAP Business Application Studio and opens a checkbox picker for BAS destinations, with no destinations selected by default. Use **Space** to choose destinations and **Enter** to confirm. Press **a** to toggle all destinations (select all if any are unchecked; otherwise clear the selection). Confirming with none selected removes this add-on's managed MCP entries. When the `cf` CLI 8.18 or newer is authenticated to a targeted space, setup first offers an optional import from that space's Destination service; type **y** then **Enter** to include it, or press **Enter** to skip. Accepted CF and BAS destinations appear together in the picker.
|
|
274
280
|
|
|
275
|
-
Without `H2O_URL`, interactive setup looks for local SAP GUI system configuration on macOS and Windows (for example SAP GUI landscape XML and `saplogon.ini`). SAP GUI landscape files describe SAP GUI/DIAG connectivity, not the ADT HTTP(S) endpoint, so setup asks you to confirm the ADT URL
|
|
281
|
+
Without `H2O_URL`, interactive setup looks for local SAP GUI system configuration on macOS and Windows (for example SAP GUI landscape XML and `saplogon.ini`). SAP GUI landscape files describe SAP GUI/DIAG connectivity, not the ADT HTTP(S) endpoint or a complete ABAP client catalog, so setup asks for the SAP client or comma-separated clients to configure for each selected system (for example `100,200`), proposes an ADT URL from the SAP GUI host and instance when it can, and asks you to confirm or edit that value. Multiple entered clients create separate isolated MCP entries, one per system/client pair. When the ADT URL uses an IP address, setup automatically inspects the SAP HTTPS certificate, extracts DNS subjectAltName entries, and offers to enable secure TLS self-healing with a simple yes/no confirmation; you do not need to type certificate names. The generated MCP entry uses VS Code input prompts for the SAP user and password at server start; raw SAP credentials are not stored in `mcp.json`. On Windows, setup can instead write an experimental `SAP_AUTH_MODE=windows-sso` entry without username/password prompts; this requires the ADT endpoint to support HTTP Negotiate/SPNEGO. Runtime uses the bundled Windows Negotiate helper by default, or a trusted custom helper configured with `SAP_AI_DEV_TOOLKIT_NEGOTIATE_HELPER`. npm may run its install hook without an interactive terminal, even when the shell is interactive; in that case, destination selection is skipped without changing MCP config, and you should run `sap-ai-dev --setup` afterwards.
|
|
282
|
+
|
|
283
|
+
### 🖥️ Local machine SAP GUI support
|
|
284
|
+
|
|
285
|
+
Local setup is intended for developers running the MCP server from their own workstation rather than SAP Business Application Studio.
|
|
286
|
+
|
|
287
|
+
Supported discovery and runtime behavior:
|
|
288
|
+
|
|
289
|
+
- **Discovery sources:** SAP GUI landscape XML and `saplogon.ini` style configuration on macOS and Windows. Setup de-duplicates entries, ignores incomplete DIAG records, and shows the discovered SAP systems in the same checkbox picker used for BAS destinations.
|
|
290
|
+
- **Client selection:** SAP GUI entries may contain one default client but cannot reliably list every ABAP client available through ADT. Setup asks for one or more 3-digit clients per selected system; enter comma-separated values such as `100,200` to create separate MCP servers for each client.
|
|
291
|
+
- **ADT endpoint derivation:** SAP GUI entries usually contain DIAG/router information, not the HTTP(S) ADT endpoint. Setup derives the common ADT port from the SAP instance when possible, for example instance `00` → `https://host:44300`, and asks for confirmation only when configuring the selected entry.
|
|
292
|
+
- **Credential handling:** username/password local entries use VS Code MCP input prompts (`${input:...}`) at server start. Plain SAP credentials are not written to `mcp.json`.
|
|
293
|
+
- **Windows SSO:** local Windows entries can use `SAP_AUTH_MODE=windows-sso` instead of username/password prompts when the ABAP ADT endpoint supports HTTP Negotiate/SPNEGO. The runtime starts a loopback ADT proxy and uses the bundled Windows SSPI helper unless `SAP_AI_DEV_TOOLKIT_NEGOTIATE_HELPER` points to a trusted custom helper. NTLM fallback is rejected.
|
|
294
|
+
- **IP-address HTTPS self-healing:** if the confirmed ADT URL is `https://<ip>:<port>` and the certificate is issued to DNS names, setup reads the certificate DNS SANs automatically and stores them as `SAP_TLS_SERVER_NAMES`. At runtime a loopback proxy dials the IP address but verifies TLS against those discovered DNS names, trying them one by one. Certificate verification stays enabled; there is no `--insecure` mode.
|
|
295
|
+
- **Private CAs:** if the SAP HTTPS certificate chains to an internal CA that is not trusted by the local Node runtime, set `SAP_TLS_CA_FILE` or `SAP_AI_DEV_TOOLKIT_TLS_CA_FILE` to a PEM CA bundle. This adds trust roots for the self-healing proxy without disabling certificate validation.
|
|
296
|
+
- **Redirect and request safety:** local loopback proxies reject cross-origin forward-form requests and rewrite same-origin SAP redirects back through the loopback listener, so VSP stays on the safe route.
|
|
297
|
+
|
|
298
|
+
Typical local setup flow:
|
|
299
|
+
|
|
300
|
+
```sh
|
|
301
|
+
sap-ai-dev --setup
|
|
302
|
+
```
|
|
276
303
|
|
|
304
|
+
1. Select the discovered SAP GUI system with **Space** and confirm with **Enter**.
|
|
305
|
+
2. Confirm the detected/default SAP client, or enter multiple clients such as `100,200`.
|
|
306
|
+
3. Accept the proposed ADT URL when it is correct, or edit it if your landscape exposes ADT through a different HTTP(S) host/port.
|
|
307
|
+
4. If setup discovers certificate DNS names for an IP-based HTTPS URL, accept the yes/no TLS self-healing prompt.
|
|
308
|
+
5. Choose password or Windows SSO when the prompt is available.
|
|
309
|
+
6. Start each generated MCP server from VS Code's **MCP: List Servers** command.
|
|
277
310
|
|
|
278
311
|
The same postinstall offers the bundled agents and skills for several AI coding harnesses in a checkbox picker. **GitHub Copilot** and **Claude Code** start pre-checked, so pressing **Enter** alone installs both; **Space** selects or deselects a harness, **a** toggles all seven, and confirming with none selected skips the step without changing files. When destination MCP setup succeeds, the generated managed MCP servers are also mirrored into selected harnesses that have a stable user-level JSON MCP configuration (Claude Code, Cursor, Gemini CLI, and Pi Coding Agent). GitHub Copilot continues to use the BAS/VS Code MCP configuration written during destination setup. With no interactive terminal, the assets are installed for GitHub Copilot only; set `SAP_AI_DEV_TOOLKIT_HARNESSES` to a comma-separated list of harness ids (`github-copilot`, `claude-code`, `codex`, `cursor`, `gemini-cli`, `opencode`, `pi-coding-agent`) to choose non-interactively.
|
|
279
312
|
|
package/package.json
CHANGED
package/src/launcher.mjs
CHANGED
|
@@ -18,7 +18,8 @@ const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
|
18
18
|
const pkg = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
|
|
19
19
|
const runtimeEnv = withBrandedEnvironment(process.env);
|
|
20
20
|
|
|
21
|
-
function
|
|
21
|
+
function args() { return process.argv.slice(2); }
|
|
22
|
+
function hasFlag(name) { return args().includes(name); }
|
|
22
23
|
function json(value) { process.stdout.write(`${JSON.stringify(value)}\n`); }
|
|
23
24
|
// Timestamped stderr lines so MCP server output shows when each event
|
|
24
25
|
// happened, not just that it did.
|
|
@@ -28,7 +29,7 @@ function usage() {
|
|
|
28
29
|
return [
|
|
29
30
|
'Usage: sap-ai-dev [options]',
|
|
30
31
|
'',
|
|
31
|
-
'With H2O_URL set, starts the SAP AI Dev Toolkit stdio server for selected BAS destinations; with local SAP GUI setup, starts the selected local ADT system.',
|
|
32
|
+
'With H2O_URL set, starts the SAP AI Dev Toolkit stdio server for selected BAS destinations; with local SAP GUI setup, starts the selected local ADT system. With no BAS environment and no options in an interactive terminal, starts local setup.',
|
|
32
33
|
'Options:',
|
|
33
34
|
' --setup Configure generated BAS MCP servers',
|
|
34
35
|
' --tools Also offer full-stack SAP companion MCP servers (use with --setup)',
|
|
@@ -294,6 +295,11 @@ async function main() {
|
|
|
294
295
|
}
|
|
295
296
|
|
|
296
297
|
if (!runtimeEnv.H2O_URL) {
|
|
298
|
+
if (args().length === 0 && process.stdin.isTTY && process.stdout.isTTY) {
|
|
299
|
+
console.error('sap-ai-dev: no BAS environment detected; starting local interactive setup. Use --help for other commands.');
|
|
300
|
+
await runSetup();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
297
303
|
const binary = await binaryOrError();
|
|
298
304
|
const child = spawn(binary, process.argv.slice(2), { env: runtimeEnv, stdio: 'inherit' });
|
|
299
305
|
process.once('SIGINT', () => child.kill('SIGINT'));
|
package/src/setup.mjs
CHANGED
|
@@ -45,6 +45,51 @@ async function confirmCloudFoundryImport({ input = stdin, output = stdout } = {}
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
export function parseSapClientList(value, fallback = '001') {
|
|
49
|
+
const supplied = String(value ?? '').trim();
|
|
50
|
+
const raw = supplied || String(fallback || '001').trim() || '001';
|
|
51
|
+
const clients = raw.split(',').map(client => client.trim()).filter(Boolean);
|
|
52
|
+
const unique = [];
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
for (const client of clients) {
|
|
55
|
+
if (!/^\d{3}$/.test(client)) throw new Error('Enter one or more SAP clients as 3 digits, separated by commas, for example 100 or 100,200.');
|
|
56
|
+
if (!seen.has(client)) {
|
|
57
|
+
seen.add(client);
|
|
58
|
+
unique.push(client);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!unique.length) throw new Error('Enter at least one SAP client.');
|
|
62
|
+
return unique;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function promptSapClients(destination, { input = stdin, output = stdout } = {}) {
|
|
66
|
+
const fallback = destination.client || '001';
|
|
67
|
+
const answer = await textPrompt({
|
|
68
|
+
message: `SAP client(s) for ${destination.name}`,
|
|
69
|
+
placeholder: fallback,
|
|
70
|
+
required: false,
|
|
71
|
+
validate: value => {
|
|
72
|
+
try { parseSapClientList(value, fallback); return true; }
|
|
73
|
+
catch (error) { return error.message; }
|
|
74
|
+
}
|
|
75
|
+
}, { input, output });
|
|
76
|
+
return parseSapClientList(answer, fallback);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function localSystemDiscriminator(destination) {
|
|
80
|
+
return [destination.systemId, destination.host, destination.instance].map(value => String(value || '').trim()).filter(Boolean).join(' ');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function localClientDestinationName(destination, client, { multipleClients = false, duplicateSystemName = false } = {}) {
|
|
84
|
+
const suffix = [];
|
|
85
|
+
if (multipleClients || duplicateSystemName) suffix.push(client);
|
|
86
|
+
if (duplicateSystemName) {
|
|
87
|
+
const discriminator = localSystemDiscriminator(destination);
|
|
88
|
+
if (discriminator) suffix.push(discriminator);
|
|
89
|
+
}
|
|
90
|
+
return suffix.length ? `${destination.name} ${suffix.join(' ')}` : destination.name;
|
|
91
|
+
}
|
|
92
|
+
|
|
48
93
|
|
|
49
94
|
function safeProbe(probe) {
|
|
50
95
|
const result = {};
|
|
@@ -293,7 +338,7 @@ export async function runSetup({
|
|
|
293
338
|
print(output, ' Space = select/deselect · a = toggle all · Enter = confirm.');
|
|
294
339
|
print(output, ' Nothing selected removes this add-on’s MCP entries.');
|
|
295
340
|
print(output, '');
|
|
296
|
-
|
|
341
|
+
let selected = await checkboxPrompt({
|
|
297
342
|
message: colorText('🧭 Select destinations', 'cyan', output),
|
|
298
343
|
choices,
|
|
299
344
|
required: false,
|
|
@@ -302,12 +347,23 @@ export async function runSetup({
|
|
|
302
347
|
|
|
303
348
|
if (!isBas && selected.length) {
|
|
304
349
|
print(output, '');
|
|
305
|
-
print(output, formatStatus('SAP GUI landscapes do not contain ADT HTTP(S) endpoints. Confirm the ADT URL and authentication for each selected system.', 'step', output, 'Local SAP GUI'));
|
|
350
|
+
print(output, formatStatus('SAP GUI landscapes do not contain ADT HTTP(S) endpoints or a complete client catalog. Confirm the SAP client(s), ADT URL, and authentication for each selected system.', 'step', output, 'Local SAP GUI'));
|
|
351
|
+
print(output, ' Enter multiple clients as comma-separated 3-digit values, for example 100,200. Setup creates one isolated MCP server per system/client pair.');
|
|
306
352
|
if (windowsSsoSetupAvailable(env)) print(output, ' Windows SSO is experimental and only applies to ADT systems configured for HTTP Integrated Authentication. Username/password remains the default.');
|
|
353
|
+
const selectedNameCounts = selected.reduce((counts, destination) => {
|
|
354
|
+
const key = String(destination.name).toLowerCase();
|
|
355
|
+
counts.set(key, (counts.get(key) || 0) + 1);
|
|
356
|
+
return counts;
|
|
357
|
+
}, new Map());
|
|
358
|
+
const expanded = [];
|
|
307
359
|
for (const destination of selected) {
|
|
360
|
+
const duplicateSystemName = (selectedNameCounts.get(String(destination.name).toLowerCase()) || 0) > 1;
|
|
361
|
+
const discriminator = duplicateSystemName ? localSystemDiscriminator(destination) : '';
|
|
362
|
+
const promptLabel = discriminator ? `${destination.name} (${discriminator})` : destination.name;
|
|
363
|
+
const clients = await promptSapClients({ ...destination, name: promptLabel }, { input, output });
|
|
308
364
|
const fallback = defaultAdtUrl(destination);
|
|
309
365
|
const url = await textPrompt({
|
|
310
|
-
message: `ADT URL for ${
|
|
366
|
+
message: `ADT URL for ${promptLabel}`,
|
|
311
367
|
placeholder: fallback || 'https://host:44300',
|
|
312
368
|
validate: value => {
|
|
313
369
|
const candidate = value || fallback;
|
|
@@ -333,7 +389,7 @@ export async function runSetup({
|
|
|
333
389
|
if (discoveredNames.length) {
|
|
334
390
|
const preview = discoveredNames.slice(0, 5).join(', ');
|
|
335
391
|
const answer = await textPrompt({
|
|
336
|
-
message: `Use discovered certificate DNS name${discoveredNames.length === 1 ? '' : 's'} for ${
|
|
392
|
+
message: `Use discovered certificate DNS name${discoveredNames.length === 1 ? '' : 's'} for ${promptLabel}: ${preview}${discoveredNames.length > 5 ? ', ...' : ''}? (y/Enter=yes, n=no)`,
|
|
337
393
|
placeholder: 'y',
|
|
338
394
|
required: false,
|
|
339
395
|
validate: value => /^(?:|y|yes|n|no)$/i.test(String(value || '').trim()) || 'Enter y or n.'
|
|
@@ -346,26 +402,32 @@ export async function runSetup({
|
|
|
346
402
|
print(output, formatStatus('No DNS subjectAltName entries could be read from the SAP HTTPS certificate. TLS self-healing was not configured automatically.', 'warning', output, 'TLS self-heal'));
|
|
347
403
|
}
|
|
348
404
|
}
|
|
349
|
-
const authMode = await chooseLocalAuthMode(destination, { input, output, env });
|
|
405
|
+
const authMode = await chooseLocalAuthMode({ ...destination, name: promptLabel }, { input, output, env });
|
|
350
406
|
destination.authentication = authMode === 'windows-sso' ? 'WindowsSSO' : 'Basic';
|
|
351
407
|
destination.authMode = authMode;
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
408
|
+
for (const client of clients) {
|
|
409
|
+
const name = localClientDestinationName(destination, client, { multipleClients: clients.length > 1, duplicateSystemName });
|
|
410
|
+
const localDestination = { ...destination, name, serverName: name, client };
|
|
411
|
+
if (authMode === 'windows-sso') {
|
|
412
|
+
localDestination.childEnv = { SAP_AUTH_MODE: 'windows-sso' };
|
|
413
|
+
localDestination.inputs = [];
|
|
414
|
+
} else {
|
|
415
|
+
const userInputId = `sap-ai-dev-${localDestination.serverName || localDestination.name}-user`.toLowerCase().replace(/[^a-z0-9_-]+/g, '-');
|
|
416
|
+
const passwordInputId = `sap-ai-dev-${localDestination.serverName || localDestination.name}-password`.toLowerCase().replace(/[^a-z0-9_-]+/g, '-');
|
|
417
|
+
localDestination.childEnv = {
|
|
418
|
+
SAP_AUTH_MODE: 'basic',
|
|
419
|
+
SAP_USER: `\${input:${userInputId}}`,
|
|
420
|
+
SAP_PASSWORD: `\${input:${passwordInputId}}`
|
|
421
|
+
};
|
|
422
|
+
localDestination.inputs = [
|
|
423
|
+
{ id: userInputId, type: 'promptString', description: `SAP user for ${localDestination.name}` },
|
|
424
|
+
{ id: passwordInputId, type: 'promptString', description: `SAP password for ${localDestination.name}`, password: true }
|
|
425
|
+
];
|
|
426
|
+
}
|
|
427
|
+
expanded.push(localDestination);
|
|
367
428
|
}
|
|
368
429
|
}
|
|
430
|
+
selected = expanded;
|
|
369
431
|
}
|
|
370
432
|
|
|
371
433
|
let sapDevelopmentServers = [];
|