sap-ai-dev-toolkit 0.9.94 → 0.9.97
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 +3 -1
- package/package.json +1 -1
- package/src/launcher.mjs +33 -18
- package/src/mcp-config.mjs +48 -7
package/README.md
CHANGED
|
@@ -338,7 +338,7 @@ Some current npm versions also require install hooks to be approved. If npm repo
|
|
|
338
338
|
|
|
339
339
|
The setup report uses icons and terminal colors; set `NO_COLOR=1` to disable ANSI colors. Long-running steps (BAS destination discovery, ADT probes, VSP runtime provisioning, `--doctor` checks) show an animated progress line so the terminal never looks stuck; it appears only on interactive terminals and is disabled automatically on CI — set `SAP_AI_DEV_TOOLKIT_DISABLE_SCAN_ANIMATION=true` to turn it off. The table is a weather report, not a bouncer: green **PASS** means the ADT probe responded, red **FAIL** means it failed, and yellow **SKIPPED** means it was skipped. Probe failures do not block MCP registration or startup for destinations you select.
|
|
340
340
|
|
|
341
|
-
Run `sap-ai-dev --setup` later to change the destination selection or remove generated destination entries. Run `sap-ai-dev --setup --tools` to also choose optional companion tools. Generated destination entries use the global `sap-ai-dev` command when it is on `PATH
|
|
341
|
+
Run `sap-ai-dev --setup` later to change the destination selection or remove generated destination entries. Run `sap-ai-dev --setup --tools` to also choose optional companion tools. Generated destination entries use the global `sap-ai-dev` command when it is on `PATH` and is the same package version as the setup that writes them; otherwise (for example after a pure npx setup, or when an older global install is still on `PATH`) they automatically launch the pinned package version through `npx`, routed through `cmd /c npx` on Windows so every MCP host can spawn them. Setup warns when it skips a global install of another version. Pass `--npx` to force the npx launcher even when a global command exists; companion tools already use `npx` with their own npm packages.
|
|
342
342
|
|
|
343
343
|
If a setup step is skipped or fails, rerun it from an interactive terminal:
|
|
344
344
|
|
|
@@ -824,6 +824,7 @@ The response contains a `tools` array. A `RunQuery` entry resembles this excerpt
|
|
|
824
824
|
| `SAP_AI_DEV_TOOLKIT_BINARY_URL` | Alternate VSP binary download URL (checksum-verified). Legacy `BAS_VSP_BINARY_URL` still works. |
|
|
825
825
|
| `SAP_AI_DEV_TOOLKIT_RELEASE_BASE_URL` | Override the base URL that VSP release binaries are downloaded from. |
|
|
826
826
|
| `SAP_AI_DEV_TOOLKIT_CACHE_DIR` | Binary cache directory. Legacy `BAS_VSP_CACHE_DIR` still works. |
|
|
827
|
+
| `SAP_AI_DEV_TOOLKIT_VERSION` | Written by setup into each destination entry: the toolkit version that configured it. An older `sap-ai-dev` started from that entry hands off to this version through `npx` (see Self-healing modes). |
|
|
827
828
|
| `BAS_CF_SPACE_GUID`, `BAS_CF_DESTINATION_INSTANCE_GUID`, `BAS_CF_DESTINATION_INSTANCE`, `BAS_CF_DESTINATION_KEY`, `BAS_CF_DESTINATION_NAME`, `BAS_CF_CONNECTIVITY_INSTANCE_GUID`, `BAS_CF_CONNECTIVITY_INSTANCE`, `BAS_CF_CONNECTIVITY_KEY` | Written into generated Cloud Foundry destination entries; they reference service instances and key **names** (never credentials). |
|
|
828
829
|
| `GO_BINARY` | Explicit Go executable for the repository-only `build:vsp` script; not used during installation. |
|
|
829
830
|
| `SAP_AI_DEV_TOOLKIT_SKIP_PROBE=true` | Skip destination probes; useful for controlled diagnostics or fixtures. |
|
|
@@ -834,6 +835,7 @@ The response contains a `tools` array. A `RunQuery` entry resembles this excerpt
|
|
|
834
835
|
|
|
835
836
|
VSP children connect to each configured destination URL directly. BAS destination authentication remains selected through VSP's `--proxy-auth` path; Cloud Foundry on-premise destinations continue through the separate Connectivity proxy.
|
|
836
837
|
Child crash recovery restarts a failed VSP child, re-initializes it, re-registers tools, and retries the interrupted `tools/call` once before surfacing an error. State-changing tools are not retried.
|
|
838
|
+
Stale launcher recovery: when an MCP entry starts a `sap-ai-dev` older than the `SAP_AI_DEV_TOOLKIT_VERSION` setup recorded in it (for example an outdated global install left on `PATH`), the launcher logs a warning and hands off to that version through `npx` on the same stdio, so the host never runs the stale launcher or VSP binary. A source checkout (`npm link`) always runs as-is.
|
|
837
839
|
Existing installs that still set the previous `BAS_VSP_*` environment variables remain supported. The setup wizard writes new MCP entries with the `SAP_AI_DEV_TOOLKIT_*` names.
|
|
838
840
|
|
|
839
841
|
#### Server logging
|
package/package.json
CHANGED
package/src/launcher.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
3
4
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
4
5
|
import { isIP } from 'node:net';
|
|
5
6
|
import { dirname, join } from 'node:path';
|
|
@@ -8,7 +9,7 @@ import { discoverDestinations, remediation, slugifyDestination, statusRows } fro
|
|
|
8
9
|
import { adtBaseUrl, adtPort, detectAdtUrl, discoverSapGuiSystems, hasExplicitPort, hasPathPrefix } from './local-sap-gui.mjs';
|
|
9
10
|
import { binaryTarget, cacheDirectory, downloadBinary, findBinary } from './binary.mjs';
|
|
10
11
|
import { MCPProxy } from './mcp-proxy.mjs';
|
|
11
|
-
import { installMcpConfig,
|
|
12
|
+
import { configuredVersionHandoff, installMcpConfig, npxPackageLauncher, repairManagedMcpConfig } from './mcp-config.mjs';
|
|
12
13
|
import { runSetup } from './setup.mjs';
|
|
13
14
|
import { resolveConfiguredCloudFoundryDestination } from './cf-destination.mjs';
|
|
14
15
|
import { createTlsServerNameAdtProxy } from './tls-adt-proxy.mjs';
|
|
@@ -268,6 +269,33 @@ function noProxyEnv(env, host) {
|
|
|
268
269
|
return { NO_PROXY: noProxy, no_proxy: noProxy };
|
|
269
270
|
}
|
|
270
271
|
|
|
272
|
+
// Runs a child on this process's stdio and mirrors its exit; rejects only when it cannot start.
|
|
273
|
+
async function runOnOwnStdio(command, commandArgs, env) {
|
|
274
|
+
const child = spawn(command, commandArgs, { env, stdio: 'inherit' });
|
|
275
|
+
process.once('SIGINT', () => child.kill('SIGINT'));
|
|
276
|
+
process.once('SIGTERM', () => child.kill('SIGTERM'));
|
|
277
|
+
const [code, signal] = await new Promise((resolve, reject) => {
|
|
278
|
+
child.once('error', reject);
|
|
279
|
+
child.once('exit', (exitCode, exitSignal) => resolve([exitCode, exitSignal]));
|
|
280
|
+
});
|
|
281
|
+
if (signal) process.kill(process.pid, signal);
|
|
282
|
+
process.exitCode = code ?? 1;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// A source checkout runs as-is: its developer chose that code.
|
|
286
|
+
async function handOffToConfiguredVersion() {
|
|
287
|
+
const handoff = existsSync(join(root, '.git')) ? null : configuredVersionHandoff(runtimeEnv, pkg.version);
|
|
288
|
+
if (!handoff) return false;
|
|
289
|
+
logLine(`[sap-ai-dev] sap-ai-dev-toolkit ${pkg.version} is older than ${handoff.version}, which configured this MCP entry; self-healing: handing off to sap-ai-dev-toolkit@${handoff.version} through npx. Run "npm install -g sap-ai-dev-toolkit@${handoff.version}" to update the global install.`);
|
|
290
|
+
try {
|
|
291
|
+
await runOnOwnStdio(handoff.command, handoff.args, { ...runtimeEnv, SAP_AI_DEV_TOOLKIT_HANDOFF: pkg.version });
|
|
292
|
+
return true;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
logLine(`[sap-ai-dev] handoff to sap-ai-dev-toolkit@${handoff.version} could not start (${error.message}); continuing with ${pkg.version}`);
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
271
299
|
async function discoverForCommand() {
|
|
272
300
|
const env = { ...runtimeEnv };
|
|
273
301
|
if (env.SAP_AI_DEV_TOOLKIT_DESTINATION_SOURCE === 'cloud-foundry') env.SAP_AI_DEV_TOOLKIT_DESTINATION = '';
|
|
@@ -349,6 +377,7 @@ async function main() {
|
|
|
349
377
|
process.stdout.write(usage());
|
|
350
378
|
return;
|
|
351
379
|
}
|
|
380
|
+
if (!setup && !check && !list && !doctor && !demo && await handOffToConfiguredVersion()) return;
|
|
352
381
|
if (doctor && demo) throw new Error('Use either --doctor or --demo, not both.');
|
|
353
382
|
if (demo) {
|
|
354
383
|
await runOfflineDemo();
|
|
@@ -383,14 +412,8 @@ async function main() {
|
|
|
383
412
|
detectAdtUrl: detectLocalAdtUrl
|
|
384
413
|
};
|
|
385
414
|
if (hasFlag('--npx')) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const launcher = npxMcpLauncher();
|
|
389
|
-
setupOptions.install = (selected, options) => installMcpConfig(selected, {
|
|
390
|
-
...options,
|
|
391
|
-
command: launcher.command,
|
|
392
|
-
args: [...launcher.prefixArgs, '--yes', '--ignore-scripts', `--package=sap-ai-dev-toolkit@${pkg.version}`, 'sap-ai-dev']
|
|
393
|
-
});
|
|
415
|
+
const launcher = npxPackageLauncher(pkg.version);
|
|
416
|
+
setupOptions.install = (selected, options) => installMcpConfig(selected, { ...options, ...launcher });
|
|
394
417
|
}
|
|
395
418
|
await runSetup(setupOptions);
|
|
396
419
|
return;
|
|
@@ -450,15 +473,7 @@ async function main() {
|
|
|
450
473
|
: (/\.(?:cmd|bat)$/i.test(binary)
|
|
451
474
|
? { command: 'cmd', prefixArgs: ['/c', binary] }
|
|
452
475
|
: { command: binary, prefixArgs: [] });
|
|
453
|
-
|
|
454
|
-
process.once('SIGINT', () => child.kill('SIGINT'));
|
|
455
|
-
process.once('SIGTERM', () => child.kill('SIGTERM'));
|
|
456
|
-
const [code, signal] = await new Promise((resolve, reject) => {
|
|
457
|
-
child.once('error', reject);
|
|
458
|
-
child.once('exit', (exitCode, exitSignal) => resolve([exitCode, exitSignal]));
|
|
459
|
-
});
|
|
460
|
-
if (signal) process.kill(process.pid, signal);
|
|
461
|
-
process.exitCode = code ?? 1;
|
|
476
|
+
await runOnOwnStdio(spawnTarget.command, [...spawnTarget.prefixArgs, ...process.argv.slice(2)], runtimeEnv);
|
|
462
477
|
return;
|
|
463
478
|
}
|
|
464
479
|
|
package/src/mcp-config.mjs
CHANGED
|
@@ -131,6 +131,26 @@ export function npxMcpLauncher(platform = process.platform) {
|
|
|
131
131
|
: { command: 'npx', prefixArgs: [] };
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
export function npxPackageLauncher(version, platform = process.platform) {
|
|
135
|
+
const { command, prefixArgs } = npxMcpLauncher(platform);
|
|
136
|
+
return { command, args: [...prefixArgs, '--yes', '--ignore-scripts', `--package=sap-ai-dev-toolkit${version ? `@${version}` : ''}`, 'sap-ai-dev'] };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function isOlderVersion(version, other) {
|
|
140
|
+
const parts = value => Array.from({ length: 3 }, (_, index) => Number.parseInt(String(value).split('.')[index], 10) || 0);
|
|
141
|
+
const [current, target] = [parts(version), parts(other)];
|
|
142
|
+
const index = current.findIndex((part, position) => part !== target[position]);
|
|
143
|
+
return index >= 0 && current[index] < target[index];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Setup pins its version into each entry, so a stale sap-ai-dev on PATH can hand off
|
|
147
|
+
// to that version through npx instead of running old code; the handoff happens once.
|
|
148
|
+
export function configuredVersionHandoff(env, runningVersion, platform = process.platform) {
|
|
149
|
+
const configured = brandedEnvValue(env, 'VERSION');
|
|
150
|
+
if (!configured || brandedEnvValue(env, 'HANDOFF') || !isOlderVersion(runningVersion, configured)) return null;
|
|
151
|
+
return { version: configured, ...npxPackageLauncher(configured, platform) };
|
|
152
|
+
}
|
|
153
|
+
|
|
134
154
|
// Normalizes the argument list of npx-launched entries, whether written as
|
|
135
155
|
// `npx ...` or the Windows `cmd /c npx ...` form; returns null when the entry
|
|
136
156
|
// is not npx-launched.
|
|
@@ -146,17 +166,36 @@ function npxLaunchArgs(entry) {
|
|
|
146
166
|
return null;
|
|
147
167
|
}
|
|
148
168
|
|
|
169
|
+
// The package version a PATH sap-ai-dev starts, found through npm's global
|
|
170
|
+
// layouts (Unix bin symlink, Windows .cmd shim); null when the layout is unknown.
|
|
171
|
+
async function commandPackageVersion(command) {
|
|
172
|
+
const target = await realpath(command).catch(() => command);
|
|
173
|
+
for (const manifest of [join(dirname(dirname(target)), 'package.json'), join(dirname(command), 'node_modules', 'sap-ai-dev-toolkit', 'package.json')]) {
|
|
174
|
+
try {
|
|
175
|
+
const pkg = JSON.parse(await readFile(manifest, 'utf8'));
|
|
176
|
+
if (pkg.name === 'sap-ai-dev-toolkit') return pkg.version;
|
|
177
|
+
} catch {
|
|
178
|
+
// Try the other layout.
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
149
184
|
// Resolves how generated destination entries launch the toolkit: the global
|
|
150
|
-
// sap-ai-dev command when it is on PATH, otherwise the
|
|
151
|
-
// through npx (routed through cmd /c on Windows) so entries
|
|
152
|
-
// pure `npx sap-ai-dev-toolkit --setup` run still start on every host.
|
|
185
|
+
// sap-ai-dev command when it is on PATH and is this version, otherwise the
|
|
186
|
+
// pinned package through npx (routed through cmd /c on Windows) so entries
|
|
187
|
+
// written by a pure `npx sap-ai-dev-toolkit --setup` run still start on every host.
|
|
153
188
|
async function resolveLauncherInstallCommand(env) {
|
|
154
189
|
const resolved = await resolveMcpServerCommand(env);
|
|
155
|
-
if (resolved !== 'sap-ai-dev') return { command: resolved, args: null };
|
|
156
|
-
const { command, prefixArgs } = npxMcpLauncher();
|
|
157
190
|
const selfVersion = await runningPackageVersion();
|
|
158
|
-
|
|
159
|
-
|
|
191
|
+
let warning;
|
|
192
|
+
if (resolved !== 'sap-ai-dev') {
|
|
193
|
+
const installed = await commandPackageVersion(resolved);
|
|
194
|
+
if (installed === null || !selfVersion || installed === selfVersion) return { command: resolved, args: null };
|
|
195
|
+
// A stale global install would keep starting the old launcher and VSP binary.
|
|
196
|
+
warning = `${resolved} is sap-ai-dev-toolkit ${installed}, not ${selfVersion}; MCP entries start sap-ai-dev-toolkit@${selfVersion} through npx instead. Run "npm install -g sap-ai-dev-toolkit@${selfVersion}" to use the global command.`;
|
|
197
|
+
}
|
|
198
|
+
return { ...npxPackageLauncher(selfVersion), warning };
|
|
160
199
|
}
|
|
161
200
|
|
|
162
201
|
export async function resolveMcpConfigPath(env = process.env) {
|
|
@@ -576,12 +615,14 @@ export async function installMcpConfig(destinationsOrOptions, options = {}) {
|
|
|
576
615
|
const resolved = await resolveLauncherInstallCommand(env);
|
|
577
616
|
launcherCommand = resolved.command;
|
|
578
617
|
launcherArgs = resolved.args;
|
|
618
|
+
if (resolved.warning) installWarnings.push(resolved.warning);
|
|
579
619
|
}
|
|
580
620
|
if (launcherCommand) {
|
|
581
621
|
for (const entry of Object.values(generated)) {
|
|
582
622
|
if (isCompanionServer(entry)) continue;
|
|
583
623
|
entry.command = launcherCommand;
|
|
584
624
|
if (launcherArgs?.length) entry.args = [...launcherArgs];
|
|
625
|
+
if (selfVersion) entry.env.SAP_AI_DEV_TOOLKIT_VERSION = selfVersion;
|
|
585
626
|
}
|
|
586
627
|
}
|
|
587
628
|
const servers = Object.create(null);
|