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.
- package/.github/agents/abap-developer.agent.md +2 -2
- package/.github/agents/abap-runtime-debugger.agent.md +1 -1
- package/.github/agents/hana-cloud-hdi-specialist.agent.md +3 -2
- package/.github/agents/rap-service-developer.agent.md +4 -4
- package/.github/agents/sap-solution-architect.agent.md +3 -3
- package/.github/skills/abap-debugging/SKILL.md +1 -1
- package/.github/skills/abap-development/SKILL.md +1 -1
- package/.github/skills/abap-runtime-analysis/SKILL.md +1 -1
- package/.github/skills/abap-testing-quality/SKILL.md +2 -2
- package/.github/skills/cds-development/SKILL.md +1 -1
- package/.github/skills/clean-core-extensibility/SKILL.md +1 -1
- package/.github/skills/hana-cloud-native-development/SKILL.md +1 -1
- package/.github/skills/hana-cloud-validation/SKILL.md +1 -1
- package/.github/skills/rap-development/SKILL.md +2 -2
- package/.github/skills/rap-service-delivery/SKILL.md +2 -2
- package/.github/skills/sap-sdlc-orchestration/SKILL.md +1 -1
- package/.github/skills/sap-transport-release/SKILL.md +1 -1
- package/README.md +61 -70
- package/dist/checksums.txt +5 -5
- package/dist/vsp-darwin-arm64 +0 -0
- package/dist/vsp-darwin-x64 +0 -0
- package/dist/vsp-linux-arm64 +0 -0
- package/dist/vsp-linux-x64 +0 -0
- package/dist/vsp-win32-x64.exe +0 -0
- package/inventory.md +25 -15
- package/package.json +1 -1
- package/patches/vsp-bas-proxy-auth.patch +342 -0
- package/scripts/build-vsp.mjs +2 -2
- package/scripts/postinstall.mjs +22 -11
- package/scripts/publish-npm.mjs +4 -4
- package/src/binary.mjs +14 -14
- package/src/launcher.mjs +52 -37
- package/src/local-sap-gui.mjs +46 -21
- package/src/mcp-config.mjs +73 -11
- package/src/mcp-proxy.mjs +26 -16
- package/src/setup.mjs +4 -1
- package/src/sso-browsers.mjs +1 -1
- package/src/terminal-ui.mjs +5 -3
- 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
|
|
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
|
-
//
|
|
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
|
|
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}]
|
|
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}]
|
|
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}]
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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(`
|
|
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}]
|
|
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}]
|
|
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
|
|
557
|
+
this.eventSink(`[${destination.name}] initializing runtime MCP session`);
|
|
548
558
|
for (let attempt = 1; ; attempt += 1) {
|
|
549
559
|
const attemptStartedAt = Date.now();
|
|
550
|
-
//
|
|
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}]
|
|
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;
|
package/src/sso-browsers.mjs
CHANGED
|
@@ -59,7 +59,7 @@ async function fileExists(path) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
// Every Chromium-based browser
|
|
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');
|
package/src/terminal-ui.mjs
CHANGED
|
@@ -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,
|
|
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
|
|
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,
|
|
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
|
|
1
|
+
# Curated SAP tools exposed through the BAS proxy
|
|
2
2
|
|
|
3
|
-
The proxy no longer advertises every
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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.
|