sap-ai-dev-toolkit 0.9.94 → 0.9.9903

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.
Files changed (39) hide show
  1. package/.github/agents/abap-developer.agent.md +2 -2
  2. package/.github/agents/abap-runtime-debugger.agent.md +1 -1
  3. package/.github/agents/hana-cloud-hdi-specialist.agent.md +3 -2
  4. package/.github/agents/rap-service-developer.agent.md +4 -4
  5. package/.github/agents/sap-solution-architect.agent.md +3 -3
  6. package/.github/skills/abap-debugging/SKILL.md +1 -1
  7. package/.github/skills/abap-development/SKILL.md +1 -1
  8. package/.github/skills/abap-runtime-analysis/SKILL.md +1 -1
  9. package/.github/skills/abap-testing-quality/SKILL.md +2 -2
  10. package/.github/skills/cds-development/SKILL.md +1 -1
  11. package/.github/skills/clean-core-extensibility/SKILL.md +1 -1
  12. package/.github/skills/hana-cloud-native-development/SKILL.md +1 -1
  13. package/.github/skills/hana-cloud-validation/SKILL.md +1 -1
  14. package/.github/skills/rap-development/SKILL.md +2 -2
  15. package/.github/skills/rap-service-delivery/SKILL.md +2 -2
  16. package/.github/skills/sap-sdlc-orchestration/SKILL.md +1 -1
  17. package/.github/skills/sap-transport-release/SKILL.md +1 -1
  18. package/README.md +61 -70
  19. package/dist/checksums.txt +5 -5
  20. package/dist/vsp-darwin-arm64 +0 -0
  21. package/dist/vsp-darwin-x64 +0 -0
  22. package/dist/vsp-linux-arm64 +0 -0
  23. package/dist/vsp-linux-x64 +0 -0
  24. package/dist/vsp-win32-x64.exe +0 -0
  25. package/inventory.md +25 -15
  26. package/package.json +1 -1
  27. package/patches/vsp-bas-proxy-auth.patch +342 -0
  28. package/scripts/build-vsp.mjs +2 -2
  29. package/scripts/postinstall.mjs +22 -11
  30. package/scripts/publish-npm.mjs +4 -4
  31. package/src/binary.mjs +14 -14
  32. package/src/launcher.mjs +52 -37
  33. package/src/local-sap-gui.mjs +46 -21
  34. package/src/mcp-config.mjs +73 -11
  35. package/src/mcp-proxy.mjs +26 -16
  36. package/src/setup.mjs +4 -1
  37. package/src/sso-browsers.mjs +1 -1
  38. package/src/terminal-ui.mjs +5 -3
  39. package/tools.md +15 -9
package/src/mcp-proxy.mjs CHANGED
@@ -45,10 +45,13 @@ const APPLICATION_LOG_DESCRIPTION = 'Read SAP application log (SLG1) entries. Re
45
45
  const NON_RETRIABLE_VSP_TOOLS = new Set([
46
46
  'Activate',
47
47
  'ActivateMultiple',
48
+ 'CreateObject',
48
49
  'CreateTransport',
49
50
  'EditSource',
50
51
  'LockObject',
52
+ 'PublishServiceBinding',
51
53
  'UnlockObject',
54
+ 'UnpublishServiceBinding',
52
55
  'WriteSource'
53
56
  ]);
54
57
 
@@ -68,12 +71,13 @@ function requestTimeoutMs(env) {
68
71
  }
69
72
 
70
73
 
71
- // Keep the default public VSP surface small enough for developer-lifecycle use.
74
+ // Keep the default public tool surface small enough for developer-lifecycle use.
72
75
  // Hidden upstream tools can still be used by local workflow tools when needed.
73
76
  export const PUBLIC_VSP_TOOLS = new Set([
74
77
  'Activate',
75
78
  'ActivateMultiple',
76
79
  'CompareSource',
80
+ 'CreateObject',
77
81
  'CreateTransport',
78
82
  'DebuggerAttach',
79
83
  'DebuggerDetach',
@@ -116,13 +120,19 @@ export const PUBLIC_VSP_TOOLS = new Set([
116
120
  'ListTransports',
117
121
  'LockObject',
118
122
  'PrettyPrint',
123
+ 'PublishServiceBinding',
119
124
  'RunATCCheck',
120
125
  'RunQuery',
121
126
  'RunUnitTests',
122
127
  'SearchObject',
123
128
  'SetBreakpoint',
124
129
  'SyntaxCheck',
130
+ // UI5 write tools stay hidden: the ADT filestore is read-only.
131
+ 'UI5GetApp',
132
+ 'UI5GetFileContent',
133
+ 'UI5ListApps',
125
134
  'UnlockObject',
135
+ 'UnpublishServiceBinding',
126
136
  'WriteSource'
127
137
  ]);
128
138
 
@@ -175,7 +185,7 @@ function useProxyAuthentication(destination) {
175
185
  function childEnvironment(destination, env) {
176
186
  const childEnv = { ...sanitizeChildEnv(env), ...(destination.childEnv || {}) };
177
187
  if (useProxyAuthentication(destination)) {
178
- // VSP loads SAP_* values from local .env files. Set every supported local
188
+ // The runtime loads SAP_* values from local .env files. Set every supported local
179
189
  // auth variable explicitly empty so dotenv cannot supply a fallback
180
190
  // identity or session instead of the selected BAS destination identity.
181
191
  for (const key of [
@@ -200,7 +210,7 @@ export function childArguments(destination, env = process.env) {
200
210
 
201
211
  // Windows cannot exec a .js/.mjs file directly (no shebang support), so test
202
212
  // fixtures written in JavaScript are launched through the current Node binary.
203
- // The production VSP binary is a native executable and is unaffected; a
213
+ // The production runtime executable is native and unaffected; a
204
214
  // Windows .cmd/.bat override needs cmd /c because Node refuses to spawn
205
215
  // batch files directly (EINVAL since the CVE-2024-27980 hardening).
206
216
  function spawnCommand(binary) {
@@ -237,16 +247,16 @@ class Child {
237
247
  // Without a listener that is an uncaught exception that kills the whole
238
248
  // MCP server — exactly the crash window self-healing is meant to cover.
239
249
  this.process.stdin.on('error', error => {
240
- if (!this.closing && this.options.log) this.options.log(`[${destination.name}] VSP child stdin error: ${diagnosticText(error.message)}`);
250
+ if (!this.closing && this.options.log) this.options.log(`[${destination.name}] runtime process stdin error: ${diagnosticText(error.message)}`);
241
251
  this.fail(new Error(`child stdin failed: ${error.message}`));
242
252
  });
243
253
  this.process.on('error', error => {
244
- if (!this.closing && !this.pending.size && this.options.log) this.options.log(`[${destination.name}] VSP child process error: ${diagnosticText(error.message)}`);
254
+ if (!this.closing && !this.pending.size && this.options.log) this.options.log(`[${destination.name}] runtime process error: ${diagnosticText(error.message)}`);
245
255
  this.fail(error);
246
256
  void closeDestinationRoute(destination);
247
257
  });
248
258
  this.process.on('exit', (code, signal) => {
249
- if (!this.closing && !this.pending.size && this.options.log) this.options.log(`[${destination.name}] VSP child exited (${code ?? signal})`);
259
+ if (!this.closing && !this.pending.size && this.options.log) this.options.log(`[${destination.name}] runtime process exited (${code ?? signal})`);
250
260
  this.fail(new Error(`child exited (${code ?? signal})`));
251
261
  void closeDestinationRoute(destination);
252
262
  });
@@ -327,7 +337,7 @@ class Child {
327
337
  try { this.process.stdin.write(`${JSON.stringify({ jsonrpc: JSONRPC, method, ...(params === undefined ? {} : { params })})}\n`); } catch {}
328
338
  }
329
339
 
330
- // VSP's own "Error:" line explains an exit, and stderr can still be draining when 'exit' fires.
340
+ // The runtime's own "Error:" line explains an exit, and stderr can still be draining when 'exit' fires.
331
341
  async stopReason(waitMs = 1000) {
332
342
  let timer;
333
343
  await Promise.race([this.stderrClosed, new Promise(resolve => { timer = setTimeout(resolve, waitMs); })]);
@@ -352,7 +362,7 @@ class Child {
352
362
  return tools;
353
363
  }
354
364
 
355
- // The VSP tool surface is fixed for the process lifetime (--mode is a
365
+ // The runtime tool surface is fixed for the process lifetime (--mode is a
356
366
  // launch argument), so listings are memoized per child. The memo is
357
367
  // cleared on failure, on child exit, and on upstream list_changed.
358
368
  listToolsCached() {
@@ -465,7 +475,7 @@ export class MCPProxy {
465
475
  // keeping serverInfo's children[0] fallback and merged slug numbering deterministic.
466
476
  const started = await Promise.all(this.destinations.map(destination => (async () => {
467
477
  try {
468
- this.eventSink(`[${destination.name}] starting VSP child (client=${destination.client || '001'})`);
478
+ this.eventSink(`[${destination.name}] starting runtime process (client=${destination.client || '001'})`);
469
479
  return {
470
480
  destination,
471
481
  child: new Child(this.binary, destination, this.childSpawnOptions(destination))
@@ -491,7 +501,7 @@ export class MCPProxy {
491
501
  return this;
492
502
  }
493
503
 
494
- // Self-healing mode 3: transparently restart a crashed VSP child so an
504
+ // Self-healing mode 3: transparently restart a crashed child so an
495
505
  // MCP tools/call does not permanently fail after a single crash. The
496
506
  // restarted child re-initializes and re-registers tools before the call
497
507
  // is retried once. Concurrent triggers share one restart (no orphaned
@@ -505,11 +515,11 @@ export class MCPProxy {
505
515
  const now = Date.now();
506
516
  state.timestamps = state.timestamps.filter(at => now - at < RESTART_WINDOW_MS);
507
517
  if (state.timestamps.length >= MAX_RESTARTS_PER_WINDOW) {
508
- throw new Error(`VSP child restart budget exhausted (${MAX_RESTARTS_PER_WINDOW} in ${RESTART_WINDOW_MS / 1000}s); surfacing the failure instead of respawning`);
518
+ throw new Error(`Runtime process restart budget exhausted (${MAX_RESTARTS_PER_WINDOW} in ${RESTART_WINDOW_MS / 1000}s); surfacing the failure instead of respawning`);
509
519
  }
510
520
  state.inFlight = (async () => {
511
521
  const name = entry.destination.name;
512
- this.eventSink(`[${name}] VSP child crashed; self-healing restart in progress`, 'warning');
522
+ this.eventSink(`[${name}] runtime process crashed; self-healing restart in progress`, 'warning');
513
523
  try {
514
524
  await entry.child.close().catch(() => {});
515
525
  const child = new Child(this.binary, entry.destination, this.childSpawnOptions(entry.destination));
@@ -518,7 +528,7 @@ export class MCPProxy {
518
528
  this.toolsCache = null;
519
529
  await child.listToolsCached().catch(() => {});
520
530
  state.timestamps.push(Date.now());
521
- this.eventSink(`[${name}] VSP child self-healing restart complete`);
531
+ this.eventSink(`[${name}] runtime process self-healing restart complete`);
522
532
  this.emitToolsListChanged();
523
533
  return entry;
524
534
  } finally {
@@ -544,16 +554,16 @@ export class MCPProxy {
544
554
  const initialized = await Promise.all(this.children.map(async entry => {
545
555
  const { destination } = entry;
546
556
  const startedAt = Date.now();
547
- this.eventSink(`[${destination.name}] initializing VSP MCP session`);
557
+ this.eventSink(`[${destination.name}] initializing runtime MCP session`);
548
558
  for (let attempt = 1; ; attempt += 1) {
549
559
  const attemptStartedAt = Date.now();
550
- // VSP answers initialize only after its browser sign-in, which waits on a person.
560
+ // The runtime answers initialize only after browser sign-in, which waits on a person.
551
561
  const signIn = destination.authentication === 'SSO'
552
562
  ? setInterval(() => this.eventSink(`[${destination.name}] still waiting for the SSO sign-in (${Math.round((Date.now() - attemptStartedAt) / 1000)} s): finish it in the browser window (it may be behind other windows); restarting the server cancels it`), this.ssoHeartbeatMs)
553
563
  : undefined;
554
564
  try {
555
565
  entry.server = await entry.child.initialize(params).finally(() => clearInterval(signIn));
556
- this.eventSink(`[${destination.name}] VSP MCP session initialized (${Date.now() - startedAt}ms)`);
566
+ this.eventSink(`[${destination.name}] runtime MCP session initialized (${Date.now() - startedAt}ms)`);
557
567
  return entry;
558
568
  } catch (error) {
559
569
  const reason = await entry.child.stopReason();
package/src/setup.mjs CHANGED
@@ -705,6 +705,7 @@ export async function runSetup({
705
705
  return { skipped: true, reason: 'no-destinations', warnings: [...warnings, ...cleanupWarnings] };
706
706
  };
707
707
  let selected;
708
+ let additiveSetup = false;
708
709
  if (!selectable.length) {
709
710
  // Nothing was detected — no BAS/CF destinations, or no SAP GUI entries.
710
711
  // Offer the manual MCP entry wizard before giving up.
@@ -721,9 +722,11 @@ export async function runSetup({
721
722
  }, { input, output });
722
723
  if (choice !== 'manual') return await exitWithoutDestinations();
723
724
  selected = await runManualEntryWizard({ env, input, output, detectAdtUrl });
725
+ additiveSetup = true;
724
726
  if (!selected.length) return await exitWithoutDestinations();
725
727
  } else {
726
728
  selected = await chooseDiscoveredDestinations(destinations, { isBas, input, output, env, detectAdtUrl, previousAdtUrls });
729
+ additiveSetup = selected.length > 0;
727
730
  }
728
731
 
729
732
  let sapDevelopmentServers = [];
@@ -746,7 +749,7 @@ export async function runSetup({
746
749
  }
747
750
 
748
751
  try {
749
- const result = await install(selected, { env, sapDevelopmentServers });
752
+ const result = await install(selected, { env, sapDevelopmentServers, additive: additiveSetup });
750
753
  const location = result?.path ? ` in ${result.path}` : '';
751
754
  print(output, '');
752
755
  const configuredCount = selected.length + sapDevelopmentServers.length;
@@ -59,7 +59,7 @@ async function fileExists(path) {
59
59
  }
60
60
  }
61
61
 
62
- // Every Chromium-based browser VSP can drive, best first; SAP_BROWSER_EXEC pins one ahead of the rest.
62
+ // Every Chromium-based browser the runtime can drive, best first; SAP_BROWSER_EXEC pins one ahead of the rest.
63
63
  export async function findSsoBrowsers({ env = process.env, platform = process.platform, exists = fileExists, appPaths = registeredAppPaths } = {}) {
64
64
  const candidates = [];
65
65
  const pinned = envValue(env, 'SAP_BROWSER_EXEC');
@@ -274,17 +274,19 @@ export async function checkboxPrompt({ message, choices: initialChoices, require
274
274
  const render = (error = '') => {
275
275
  const columns = Number.isFinite(output?.columns) ? output.columns : 80;
276
276
  const indexes = visibleIndexes();
277
+ const fixedLines = 1 + (searchable ? 1 : 0) + (Object.hasOwn(actions, 'm') ? 1 : 0) + footer().length;
277
278
  const pageSize = searchable
278
- ? Math.max(1, checkboxPageSize(output, indexes.length + 1) - 1)
279
+ ? Math.max(1, Math.min(indexes.length, (Number.isFinite(output?.rows) ? output.rows : 24) - fixedLines - 7))
279
280
  : checkboxPageSize(output, indexes.length);
280
281
  const cursorPosition = indexes.indexOf(cursor);
281
282
  ensureVisible(pageSize, indexes.length, cursorPosition);
282
283
  const lines = [];
283
284
  lines.push(truncateToColumns(`${message} (Space: select, ${shortcuts?.all || 'a'}: toggle all, Enter: confirm)`, columns));
284
285
  if (searchable) {
285
- const hint = searchQuery ? 'Backspace edits; Esc clears' : 'type prefix; / before a or m';
286
+ const hint = searchQuery ? 'Backspace edits; Esc clears' : 'type prefix to search';
286
287
  lines.push(truncateToColumns(`Search: ${searchQuery || 'type prefix'} (${hint})`, columns));
287
288
  }
289
+ if (Object.hasOwn(actions, 'm')) lines.push(truncateToColumns('m: add system manually', columns));
288
290
  const end = Math.min(indexes.length, top + pageSize);
289
291
  for (let index = top; index < end; index += 1) {
290
292
  const choiceIndex = indexes[index];
@@ -505,7 +507,7 @@ export async function textPrompt({ message, secret = false, required = true, pla
505
507
  }
506
508
 
507
509
  // Animated progress line for long-running steps (destination discovery,
508
- // ADT probes, VSP binary provisioning). TTY outputs get spinner frames on an
510
+ // ADT probes, runtime provisioning). TTY outputs get spinner frames on an
509
511
  // unref'd timer that starts after a short delay, so steps that finish quickly
510
512
  // never flash anything. Non-TTY outputs (pipes, MCP hosts, --json callers,
511
513
  // CI) stay completely silent so machine consumers never see progress noise.
package/tools.md CHANGED
@@ -1,16 +1,17 @@
1
- # Curated VSP tools exposed through the BAS proxy
1
+ # Curated SAP tools exposed through the BAS proxy
2
2
 
3
- The proxy no longer advertises every VSP tool. To keep the developer-lifecycle MCP surface manageable, each generated one-destination server exposes a cherry-picked set of VSP tools plus local workflow tools (currently 61 tools when all curated VSP capabilities are registered).
3
+ The proxy no longer advertises every tool provided by the SAP runtime. To keep the developer-lifecycle MCP surface manageable, each generated one-destination server exposes a curated set of upstream tools plus local workflow tools (currently 67 tools when all upstream capabilities are registered).
4
4
 
5
5
  The names below are the logical tool names. Publicly they are exposed in lowercase snake_case without a prefix (`get_table_contents`, `run_query`) because each generated MCP server is scoped to one destination; only when a single server fronts multiple destinations does each name gain its destination slug (`demo-abap_get_table_contents`) to stay unambiguous. BAS/VS Code chat tool references bind only lowercase identifiers, so mixed-case names never reach the model.
6
6
 
7
- Hidden upstream VSP tools are not directly callable through the proxy. Local workflow tools may still call hidden or non-advertised upstream operations internally when they are required for a bounded workflow.
7
+ Hidden upstream tools are not directly callable through the proxy. Local workflow tools may still call hidden or non-advertised operations internally when they are required for a bounded workflow.
8
8
 
9
- ## Public VSP tools
9
+ ## Public upstream tools
10
10
 
11
11
  - `Activate`
12
12
  - `ActivateMultiple`
13
13
  - `CompareSource`
14
+ - `CreateObject`
14
15
  - `CreateTransport`
15
16
  - `DebuggerAttach`
16
17
  - `DebuggerDetach`
@@ -53,22 +54,27 @@ Hidden upstream VSP tools are not directly callable through the proxy. Local wor
53
54
  - `ListTransports`
54
55
  - `LockObject`
55
56
  - `PrettyPrint`
57
+ - `PublishServiceBinding`
56
58
  - `RunATCCheck`
57
59
  - `RunQuery`
58
60
  - `RunUnitTests`
59
61
  - `SearchObject`
60
62
  - `SetBreakpoint`
61
63
  - `SyntaxCheck`
64
+ - `UI5GetApp`
65
+ - `UI5GetFileContent`
66
+ - `UI5ListApps`
62
67
  - `UnlockObject`
68
+ - `UnpublishServiceBinding`
63
69
  - `WriteSource`
64
70
 
65
71
  ## Hidden from direct proxy calls
66
72
 
67
- Broad/destructive surfaces such as object deletion, SQL trace, arbitrary SAP router calls, transport release/delete, RFC execution, debugger listen/delete-breakpoint helpers, and one-off generator/update helpers are intentionally not advertised by the BAS proxy.
73
+ Broad/destructive surfaces such as object deletion, SQL trace, arbitrary SAP router calls, transport release/delete, RFC execution, debugger listen/delete-breakpoint helpers, and one-off generator/update helpers are intentionally not advertised by the BAS proxy. UI5 repository writes are hidden too: the ADT filestore only reads UI5 apps, so deploy them with SAP Fiori tools instead.
68
74
 
69
75
  ## Destination-scoped workflow tools
70
76
 
71
- The proxy adds local multi-step tools to each destination when their required VSP capabilities are available. Like the VSP tools above, they are exposed publicly in lowercase snake_case (`lint_abap`, `get_application_log`, `prepare_abap_change_set`, and so on):
77
+ The proxy adds local multi-step tools to each destination when their required upstream capabilities are available. Like the upstream tools above, they are exposed publicly in lowercase snake_case (`lint_abap`, `get_application_log`, `prepare_abap_change_set`, and so on):
72
78
 
73
79
  - `LintABAP` — local in-memory ABAP linting.
74
80
  - `GetApplicationLog` — bounded convenience mapping to the upstream SAP application-log route when available.
@@ -77,16 +83,16 @@ The proxy adds local multi-step tools to each destination when their required VS
77
83
  - `PlanABAPCloudMigration` — batch SAP API release-state checks and prioritize recognized unreleased results.
78
84
  - `GenerateRAPRegressionSuite` and `RunRAPRegressionSuite` — create and run reusable OData GET-only checks under the selected service root.
79
85
 
80
- `sap-ai-dev --doctor` checks destination probing, VSP startup, the `get_system_info` probe, and MCP tool listing. `sap-ai-dev --demo` runs the sample tools and OData fixtures without contacting SAP; demo writes and transport creation remain in memory until that process exits.
86
+ `sap-ai-dev --doctor` checks destination probing, runtime startup, the `get_system_info` probe, and MCP tool listing. `sap-ai-dev --demo` runs the sample tools and OData fixtures without contacting SAP; demo writes and transport creation remain in memory until that process exits.
81
87
 
82
88
 
83
89
  ### Request timeouts
84
90
 
85
- Calls forwarded to a VSP child time out after `SAP_AI_DEV_TOOLKIT_REQUEST_TIMEOUT_MS` (default 600000 ms; `0` disables). A stalled request fails with a timeout error; the child keeps running so unrelated in-flight requests are unaffected.
91
+ Calls forwarded to the runtime process time out after `SAP_AI_DEV_TOOLKIT_REQUEST_TIMEOUT_MS` (default 600000 ms; `0` disables). A stalled request fails with a timeout error; the process keeps running so unrelated in-flight requests are unaffected.
86
92
 
87
93
  ## Separate optional HANA Cloud inspector
88
94
 
89
- The optional `hana-cloud-inspector` companion starts `sap-ai-hana` as a separate stdio MCP server. It does not use the VSP proxy, BAS destination prefix, or ABAP SQL `RunQuery`. When selected and configured with a dedicated read-only identity, it exposes:
95
+ The optional `hana-cloud-inspector` companion starts `sap-ai-hana` as a separate stdio MCP server. It does not use the BAS proxy, destination prefix, or ABAP SQL `RunQuery`. When selected and configured with a dedicated read-only identity, it exposes:
90
96
 
91
97
  - `hana_connection_info` — verify the configured endpoint, service/binding, current user, schema, and TLS state without returning credentials.
92
98
  - `hana_list_objects` — list tables/views in the bound HDI schema, with a 200-object maximum.