sap-ai-dev-toolkit 0.9.9912 → 0.9.9914

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 (37) hide show
  1. package/.github/agents/abap-developer.agent.md +1 -1
  2. package/.github/agents/sap-integration-architect.agent.md +38 -0
  3. package/.github/agents/sap-integration-developer.agent.md +31 -0
  4. package/.github/agents/sap-solution-architect.agent.md +1 -0
  5. package/.github/skills/abap-development/SKILL.md +1 -1
  6. package/.github/skills/cds-development/SKILL.md +1 -1
  7. package/.github/skills/integration-suite-delivery/SKILL.md +74 -0
  8. package/.github/skills/sap-integration-architect/SKILL.md +76 -0
  9. package/NOTICE +10 -0
  10. package/README.md +36 -8
  11. package/dist/checksums.txt +5 -5
  12. package/dist/vsp-darwin-arm64 +0 -0
  13. package/dist/vsp-darwin-x64 +0 -0
  14. package/dist/vsp-linux-arm64 +0 -0
  15. package/dist/vsp-linux-x64 +0 -0
  16. package/dist/vsp-win32-x64.exe +0 -0
  17. package/inventory.md +66 -2
  18. package/package.json +5 -2
  19. package/patches/vsp-bas-proxy-auth.patch +88 -7
  20. package/scripts/run-live-integration.mjs +1079 -0
  21. package/src/apim-bundle.mjs +381 -0
  22. package/src/apim-tools.mjs +641 -0
  23. package/src/cpi-tools.mjs +1042 -0
  24. package/src/engineering-tools.mjs +105 -0
  25. package/src/iflow-bundle.mjs +282 -0
  26. package/src/integration-common.mjs +232 -0
  27. package/src/integration-config.mjs +167 -0
  28. package/src/integration-credentials.mjs +65 -0
  29. package/src/integration-http.mjs +266 -0
  30. package/src/integration-server.mjs +125 -0
  31. package/src/integration-templates/https-request-reply-http.iflw +391 -0
  32. package/src/integration-tools.mjs +27 -0
  33. package/src/integration-verify.mjs +118 -0
  34. package/src/launcher.mjs +14 -2
  35. package/src/mcp-config.mjs +18 -3
  36. package/src/setup.mjs +189 -11
  37. package/tools.md +81 -3
package/src/setup.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { readFileSync } from 'node:fs';
1
2
  import { createInterface } from 'node:readline/promises';
2
3
  import { stdin, stdout } from 'node:process';
3
4
  import { platform } from 'node:os';
@@ -7,6 +8,9 @@ import { discoverDestinations, remediation } from './bas-discovery.mjs';
7
8
  import { adtBaseUrl, adtPort, defaultAdtUrl, discoverSapGuiSystems, hasExplicitPort, hasPathPrefix, messageServerAppServers } from './local-sap-gui.mjs';
8
9
  import { discoverCloudFoundryDestinations, getCloudFoundryTarget, deleteManagedCloudFoundryServiceKeys, findOrphanedCloudFoundryServiceKeys } from './cf-destination.mjs';
9
10
  import { SAP_DEVELOPMENT_MCP_SERVERS, collectCloudFoundryKeyReferencesFromAllEntries, collectManagedCloudFoundryKeyReferences, installMcpConfig, readMcpConfig, resolveMcpConfigPath } from './mcp-config.mjs';
11
+ import { parseIntegrationServiceKey, resolveIntegrationConfig, resolveIntegrationSideFromEnvironment } from './integration-config.mjs';
12
+ import { readStoredIntegrationKeys, storeIntegrationKey } from './integration-credentials.mjs';
13
+ import { createIntegrationClients, verificationLines, verifyIntegrationClients } from './integration-verify.mjs';
10
14
  import { discoverCertificateDnsNames } from './tls-adt-proxy.mjs';
11
15
  import { findRemoteControllableSsoBrowsers } from './sso-browsers.mjs';
12
16
 
@@ -268,14 +272,167 @@ function warnSsoIpHost(destination, label, output) {
268
272
  print(output, formatStatus(`${label} uses an IP address; SSO needs the DNS host name for Kerberos and the browser certificate check. Enter the host name if the sign-in fails.`, 'warning', output, 'SSO'));
269
273
  }
270
274
 
271
- async function confirmPrompt(message, { input = stdin, output = stdout } = {}) {
275
+ async function confirmPrompt(message, { input = stdin, output = stdout, defaultYes = true } = {}) {
272
276
  const answer = await textPrompt({
273
277
  message,
274
- placeholder: 'y',
278
+ placeholder: defaultYes ? 'y' : 'n',
275
279
  required: false,
276
280
  validate: value => /^(?:|y|yes|n|no)$/i.test(String(value || '').trim()) || 'Enter y or n.'
277
281
  }, { input, output });
278
- return !/^n(?:o)?$/i.test(String(answer || '').trim());
282
+ const trimmed = String(answer || '').trim();
283
+ if (!trimmed) return defaultYes;
284
+ return !/^n(?:o)?$/i.test(trimmed);
285
+ }
286
+
287
+ // Credential status for the Integration Suite companion, without exposing any value: which sides
288
+ // (CPI, APIM) resolve from this environment or the stored-key file, and from which source.
289
+ export function integrationSuiteCredentialStatus(env = process.env) {
290
+ try {
291
+ const config = resolveIntegrationConfig(env, { stored: readStoredIntegrationKeys(env) });
292
+ const side = value => (value ? `configured from ${value.source}${value.bindingName ? ` (${value.bindingName})` : ''}, host ${new URL(value.url).host}` : 'not configured');
293
+ return { found: true, config, lines: [`CPI: ${side(config.cpi)}`, `APIM: ${side(config.apim)}`] };
294
+ } catch (error) {
295
+ const message = String(error?.message || '');
296
+ if (/^Configure CPI/.test(message)) return { found: false, config: null, lines: ['No CPI or APIM credentials were found in this environment.'] };
297
+ return { found: false, config: null, lines: [`Credentials found but not usable: ${message}`] };
298
+ }
299
+ }
300
+
301
+ function defaultVerifyIntegration(config, { env }) {
302
+ return verifyIntegrationClients(createIntegrationClients(config, { env }));
303
+ }
304
+
305
+ /** Read a pasted service key: JSON text (line breaks from a multi-line paste are dropped) or a path to a .json file. */
306
+ function pastedServiceKey(side, value, env) {
307
+ const trimmed = String(value || '').trim();
308
+ let text = trimmed;
309
+ if (!trimmed.startsWith('{')) {
310
+ const path = trimmed.replace(/^~(?=$|[\\/])/, env.HOME || env.USERPROFILE || '~');
311
+ try {
312
+ text = readFileSync(path, 'utf8');
313
+ } catch {
314
+ throw new Error('Paste the service key JSON (starting with {) or the path to a readable .json file.');
315
+ }
316
+ }
317
+ return parseIntegrationServiceKey(side, text);
318
+ }
319
+
320
+ async function printIntegrationVerification(config, { env, output, verifyIntegration }) {
321
+ const stop = startProgress('Signing in to Integration Suite and checking each tool area', { output, env, label: 'Integration Suite' });
322
+ let report;
323
+ try {
324
+ report = await verifyIntegration(config, { env });
325
+ } finally {
326
+ stop();
327
+ }
328
+ const host = side => (config[side] ? new URL(config[side].url).host : '');
329
+ for (const line of [...verificationLines('CPI', host('cpi'), report.cpi), ...verificationLines('APIM', host('apim'), report.apim)]) print(output, ` ${line}`);
330
+ const missing = [report.cpi, report.apim].flatMap(side => side?.checks || []).filter(check => check.result === 'forbidden').length;
331
+ if (report.ok) {
332
+ print(output, formatStatus(`Integration Suite credentials verified${missing ? `; ${missing} area${missing === 1 ? ' needs' : 's need'} an extra role on the service key` : ''}.`, missing ? 'warning' : 'success', output, 'Integration Suite'));
333
+ } else {
334
+ print(output, formatStatus('Integration Suite verification failed; fix or replace the service key before using the tools.', 'warning', output, 'Integration Suite'));
335
+ }
336
+ return report;
337
+ }
338
+
339
+ // Verify the detected credentials online and let the user paste CPI/APIM service keys. A pasted key is checked
340
+ // online first and saved to the user-only store (never to mcp.json); it is used when the environment has no key.
341
+ async function configureIntegrationCredentials({ env, input, output, verifyIntegration }) {
342
+ let status = integrationSuiteCredentialStatus(env);
343
+ if (status.found) await printIntegrationVerification(status.config, { env, output, verifyIntegration });
344
+ for (;;) {
345
+ print(output, '');
346
+ const choice = await selectPrompt({
347
+ message: colorText('🔑 Integration Suite credentials', 'cyan', output),
348
+ defaultValue: status.found ? 'done' : 'cpi',
349
+ choices: [
350
+ { name: status.found ? 'Continue' : 'Continue without credentials (the server will not start until a key is configured)', value: 'done', shortcut: 'c' },
351
+ { name: 'Paste a Cloud Integration (CPI) service key — Process Integration Runtime, plan api', value: 'cpi', shortcut: 'p' },
352
+ { name: 'Paste an API Management service key — API Management, plan apiportal-apiaccess', value: 'apim', shortcut: 'a' },
353
+ ...(status.found ? [{ name: 'Check the configured credentials online again', value: 'verify', shortcut: 'v' }] : [])
354
+ ]
355
+ }, { input, output });
356
+ if (choice === 'done') return status;
357
+ if (choice === 'verify') {
358
+ await printIntegrationVerification(status.config, { env, output, verifyIntegration });
359
+ continue;
360
+ }
361
+ const side = choice.toUpperCase();
362
+ const pasted = await textPrompt({
363
+ message: `Paste the ${side} service key JSON or the path to its .json file`,
364
+ secret: true,
365
+ validate: value => {
366
+ try {
367
+ pastedServiceKey(side, value, env);
368
+ return true;
369
+ } catch (error) {
370
+ return error.message;
371
+ }
372
+ }
373
+ }, { input, output });
374
+ const config = pastedServiceKey(side, pasted, env);
375
+ const report = await printIntegrationVerification({ [choice]: config }, { env, output, verifyIntegration });
376
+ if (!report[choice].signed_in && !await confirmPrompt('Save this key anyway? (y/n, Enter=no)', { input, output, defaultYes: false })) continue;
377
+ const path = await storeIntegrationKey(side, config, env);
378
+ print(output, formatStatus(`Saved the ${side} key to ${path} (readable only by you; not written to mcp.json).`, 'success', output, 'Integration Suite'));
379
+ let fromEnvironment = null;
380
+ try {
381
+ fromEnvironment = resolveIntegrationSideFromEnvironment(env, side);
382
+ } catch {
383
+ // A broken environment value is reported by the status lines below.
384
+ }
385
+ if (fromEnvironment) print(output, formatStatus(`${side} credentials from ${fromEnvironment.source} in this environment take precedence over the saved key.`, 'warning', output, 'Integration Suite'));
386
+ status = integrationSuiteCredentialStatus(env);
387
+ for (const line of status.lines) print(output, ` ${line}`);
388
+ }
389
+ }
390
+
391
+ async function promptIntegrationSuite({ env, alreadyConfigured, input, output, verifyIntegration = defaultVerifyIntegration, ask = true }) {
392
+ print(output, '');
393
+ print(output, formatStatus('SAP Integration Suite tools (Cloud Integration + API Management).', 'step', output, 'Integration Suite'));
394
+ print(output, ' Adds the sap-ai-integration MCP server: iFlows, value mappings, security material and monitoring in CPI; API providers, proxies, policies, products and key value maps in APIM.');
395
+ print(output, ' Every write needs an exact confirmation string; there are no delete tools and secrets are write-only. Agents: SAP Integration Architect, SAP Integration Developer.');
396
+ if (alreadyConfigured) print(output, ' Already configured in this MCP config; keeping it and refreshing its version.');
397
+ const status = integrationSuiteCredentialStatus(env);
398
+ for (const line of status.lines) print(output, ` ${line}`);
399
+ if (!status.found) {
400
+ print(output, ' The server needs CPI_SERVICE_KEY and/or APIM_SERVICE_KEY (or CPI_*/APIM_* variables, VCAP_SERVICES) in the MCP host environment,');
401
+ print(output, ' or a service key pasted in the next step (verified online, then saved for your user only). Credentials are never written to mcp.json.');
402
+ }
403
+ if (ask && !alreadyConfigured) {
404
+ print(output, '');
405
+ const defaultYes = status.found;
406
+ if (!await confirmPrompt(`Add the SAP Integration Suite tools? (y/n, Enter=${defaultYes ? 'yes' : 'no'})`, { input, output, defaultYes })) return false;
407
+ }
408
+ await configureIntegrationCredentials({ env, input, output, verifyIntegration });
409
+ return true;
410
+ }
411
+
412
+ /** `sap-ai-dev --setup-integration`: only the Integration Suite step (paste, verify, add the server), keeping every other MCP entry. */
413
+ export async function runIntegrationSuiteSetup({
414
+ env = process.env,
415
+ input = stdin,
416
+ output = stdout,
417
+ install = installMcpConfig,
418
+ verifyIntegration = defaultVerifyIntegration
419
+ } = {}) {
420
+ if (!input.isTTY || !output.isTTY) {
421
+ print(output, formatStatus('Interactive setup needs a terminal. Rerun sap-ai-dev --setup-integration from a terminal.', 'warning', output, 'SAP AI Dev Toolkit'));
422
+ return { skipped: true, reason: 'non-tty' };
423
+ }
424
+ let alreadyConfigured = false;
425
+ try {
426
+ alreadyConfigured = Boolean((await readMcpConfig(await resolveMcpConfigPath(env)))?.servers?.['integration-suite']);
427
+ } catch {
428
+ // A missing or unreadable config is created or healed by install.
429
+ }
430
+ await promptIntegrationSuite({ env, alreadyConfigured, input, output, verifyIntegration, ask: false });
431
+ const result = await install([], { env, sapDevelopmentServers: ['integration-suite'], additive: true });
432
+ print(output, '');
433
+ print(output, formatStatus(`Configured the Integration Suite MCP server${result?.path ? ` in ${result.path}` : ''}.`, 'success', output, 'Integration Suite'));
434
+ for (const message of Array.isArray(result?.warnings) ? result.warnings : []) print(output, formatStatus(message, 'warning', output, 'Warning'));
435
+ return { selected: [], ...(result || {}) };
279
436
  }
280
437
 
281
438
  function buildLocalClientDestination(destination, client, authMode, { multipleClients = false, duplicateSystemName = false, browserPath } = {}) {
@@ -613,7 +770,8 @@ export async function runSetup({
613
770
  install = installMcpConfig,
614
771
  includeSapDevelopmentToolsPrompt = false,
615
772
  detectAdtUrl,
616
- discoverSsoBrowsers = findRemoteControllableSsoBrowsers
773
+ discoverSsoBrowsers = findRemoteControllableSsoBrowsers,
774
+ verifyIntegration = defaultVerifyIntegration
617
775
  } = {}) {
618
776
  const isBas = Boolean(env.H2O_URL);
619
777
  if (!isBas && (!input.isTTY || !output.isTTY)) {
@@ -661,11 +819,13 @@ export async function runSetup({
661
819
  let configPath;
662
820
  let managedKeys = [];
663
821
  let previousAdtUrls = new Map();
822
+ let integrationSuiteConfigured = false;
664
823
  try {
665
824
  configPath = await resolveMcpConfigPath(env);
666
825
  const existingConfig = await readMcpConfig(configPath);
667
826
  managedKeys = collectManagedCloudFoundryKeyReferences(existingConfig);
668
827
  previousAdtUrls = previousLocalAdtUrls(existingConfig);
828
+ integrationSuiteConfigured = Boolean(existingConfig?.servers?.['integration-suite']);
669
829
  } catch {
670
830
  warnings.push('Existing MCP config could not be read before setup; existing CF service keys will not be reused.');
671
831
  }
@@ -750,6 +910,7 @@ export async function runSetup({
750
910
  };
751
911
  let selected;
752
912
  let additiveSetup = false;
913
+ let integrationOnly = false;
753
914
  if (!selectable.length) {
754
915
  // Nothing was detected — no BAS/CF destinations, or no SAP GUI entries.
755
916
  // Offer the manual MCP entry wizard before giving up.
@@ -761,23 +922,34 @@ export async function runSetup({
761
922
  defaultValue: 'manual',
762
923
  choices: [
763
924
  { name: '✍️ Start manual entry wizard (system name, ADT URL, client, login)', value: 'manual', shortcut: 'm' },
925
+ { name: '🔌 Set up only the SAP Integration Suite tools (paste and verify CPI / API Management service keys)', value: 'integration', shortcut: 'i' },
764
926
  { name: 'Exit setup without changes', value: 'exit', shortcut: 'e' }
765
927
  ]
766
928
  }, { input, output });
767
- if (choice !== 'manual') return await exitWithoutDestinations();
768
- selected = await runManualEntryWizard({ env, input, output, detectAdtUrl, selectSsoBrowser });
769
- additiveSetup = true;
770
- if (!selected.length) return await exitWithoutDestinations();
929
+ if (choice === 'integration') {
930
+ selected = [];
931
+ additiveSetup = true;
932
+ integrationOnly = true;
933
+ } else {
934
+ if (choice !== 'manual') return await exitWithoutDestinations();
935
+ selected = await runManualEntryWizard({ env, input, output, detectAdtUrl, selectSsoBrowser });
936
+ additiveSetup = true;
937
+ if (!selected.length) return await exitWithoutDestinations();
938
+ }
771
939
  } else {
772
940
  selected = await chooseDiscoveredDestinations(destinations, { isBas, input, output, env, detectAdtUrl, previousAdtUrls, selectSsoBrowser });
773
941
  additiveSetup = selected.length > 0;
774
942
  }
775
943
 
776
944
  let sapDevelopmentServers = [];
777
- if (includeSapDevelopmentToolsPrompt) {
945
+ if (integrationOnly) {
946
+ await promptIntegrationSuite({ env, alreadyConfigured: integrationSuiteConfigured, input, output, verifyIntegration, ask: false });
947
+ sapDevelopmentServers = ['integration-suite'];
948
+ } else if (includeSapDevelopmentToolsPrompt) {
778
949
  print(output, '');
779
- print(output, formatStatus('Optionally add companion MCP servers for full-stack SAP development and HANA inspection.', 'step', output, 'Tools'));
950
+ print(output, formatStatus('Optionally add companion MCP servers for full-stack SAP development, HANA inspection, and Integration Suite (CPI/API Management).', 'step', output, 'Tools'));
780
951
  print(output, ' HANA Cloud inspector is read-only and uses HANA_RO_* or VCAP_SERVICES already present in the MCP host environment; credentials are not written to mcp.json.');
952
+ print(output, ' Integration Suite uses CPI/APIM service keys from the MCP host environment or keys you paste next (verified online, saved for your user only); credentials are not written to mcp.json.');
781
953
  print(output, ' Leave empty to only configure ABAP/ADT destination servers.');
782
954
  print(output, '');
783
955
  sapDevelopmentServers = await checkboxPrompt({
@@ -785,11 +957,17 @@ export async function runSetup({
785
957
  choices: SAP_DEVELOPMENT_MCP_SERVERS.map(server => ({
786
958
  value: server.id,
787
959
  name: `${server.name} — ${server.description}`,
788
- checked: false
960
+ checked: server.id === 'integration-suite' && integrationSuiteConfigured
789
961
  })),
790
962
  required: false,
791
963
  shortcuts: { all: 'a' }
792
964
  }, { input, output });
965
+ if (sapDevelopmentServers.includes('integration-suite')) {
966
+ await promptIntegrationSuite({ env, alreadyConfigured: integrationSuiteConfigured, input, output, verifyIntegration, ask: false });
967
+ }
968
+ } else if (await promptIntegrationSuite({ env, alreadyConfigured: integrationSuiteConfigured, input, output, verifyIntegration })) {
969
+ // Without --tools, Integration Suite still gets its own yes/no step instead of being hidden.
970
+ sapDevelopmentServers = ['integration-suite'];
793
971
  }
794
972
 
795
973
  try {
package/tools.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Curated SAP tools exposed through the BAS proxy
2
2
 
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 72 tools when all upstream 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 (up to 75 tools: 74 destination-scoped tools plus one workspace-scoped project-lint tool).
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
 
@@ -73,11 +73,11 @@ Hidden upstream tools are not directly callable through the proxy. Local workflo
73
73
  - `UnpublishServiceBinding`
74
74
  - `WriteSource`
75
75
 
76
- `CreatePackage` creates a local package or a transportable package with an explicit transport; it changes SAP state and requires explicit approval. `CreateTable` creates a transparent DDIC table, writes its generated DDL source, and activates it. `GetStructure`, `GetTypeInfo`, and `GetMessages` are read-only metadata lookups. The upstream VSP package and transport mutation checks still apply. The pinned backend does not create structures, table types, or authorization objects.
76
+ `CreatePackage` creates a local package or a transportable package with an explicit transport; it changes SAP state and requires explicit approval. `CreateTable` creates a transparent DDIC table, writes its generated DDL source, and activates it. Client-dependent tables already get the implicit `CLIENT` key; supplied `CLIENT` or `MANDT` fields map to that same key and are not emitted twice. `GetStructure`, `GetTypeInfo`, and `GetMessages` are read-only metadata lookups. The upstream VSP package and transport mutation checks still apply. The pinned backend does not create structures, table types, or authorization objects.
77
77
 
78
78
  ## Hidden from direct proxy calls
79
79
 
80
- 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.
80
+ Broad/destructive surfaces such as generic 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. The local `DeleteEmptyDDLS` workflow is a narrow exception: it verifies the source is blank and requires exact confirmation before deleting a Z/Y DDLS shell. UI5 repository writes are hidden too: the ADT filestore only reads UI5 apps, so deploy them with SAP Fiori tools instead.
81
81
 
82
82
  ## Local and destination-scoped workflow tools
83
83
 
@@ -86,6 +86,8 @@ The proxy adds local tools to the MCP surface. Some are workspace-wide; others a
86
86
  - `LintABAP` — local in-memory ABAP linting.
87
87
  - `LintABAPProject` — scan serialized ABAP objects in the configured workspace root (SAP_AI_DEV_TOOLKIT_WORKSPACE_ROOT, defaulting to the MCP server working directory) using the project abaplint config and available local dependency folders. This is static analysis only; it does not contact SAP or activate objects.
88
88
  - `GetApplicationLog` — bounded convenience mapping to the upstream SAP application-log route when available.
89
+ - `UpdateTable` — update and activate an existing Z/Y DDIC table from replacement DDL after comparing expected_source with `GetTable`; the write uses VSP's hash check under lock and requires `UPDATE TABLE <TABLE_NAME>` confirmation.
90
+ - `DeleteEmptyDDLS` — delete only a blank Z/Y DDLS source after an emptiness check and exact `DELETE EMPTY DDLS <OBJECT_NAME>` confirmation. It calls the hidden generic delete operation internally.
89
91
  - `PrepareABAPChangeSet` and `ApplyABAPChangeSet` — review full-source diffs and recheck source before explicit writes.
90
92
  - `CheckTransportReadiness` — collect a whitelisted bundle of transport, dependency, inactive-object, ABAP Unit, and ATC results.
91
93
  - `PlanABAPCloudMigration` — batch SAP API release-state checks and prioritize recognized unreleased results.
@@ -108,3 +110,79 @@ The optional `hana-cloud-inspector` companion starts `sap-ai-hana` as a separate
108
110
  - `hana_read_rows` — perform parameterized reads of selected columns, capped at 200 rows; credential-like columns are blocked and arbitrary SQL is not accepted.
109
111
 
110
112
  Connection secrets are read from the MCP host environment via explicit `HANA_RO_*` variables or a selected `VCAP_SERVICES` binding and are not written to `mcp.json`. TLS certificate verification is mandatory. No HANA write, DDL/DML, deploy/undeploy, grants, or service-key tools are exposed; deployment remains a user-run project workflow.
113
+
114
+ ## Separate optional Integration Suite server
115
+
116
+ The optional `integration-suite` companion starts `sap-ai-integration` as a separate stdio MCP server for SAP Cloud Integration (CPI) and API Management (APIM). It does not use the BAS proxy or a destination prefix. Tools for a side appear only when that side is configured:
117
+
118
+ - `integration_connection_info` — show which of CPI/APIM are configured, the credential source, and tenant hosts; with `verify: true` it signs in and reports per tool area whether the service key has the needed role. Never returns client ids or secrets.
119
+
120
+ Cloud Integration — design time:
121
+
122
+ - `cpi_list_packages` — list Cloud Integration packages.
123
+ - `cpi_list_iflows` — list the iFlows of one package.
124
+ - `cpi_list_package_artifacts` — list a package's iFlows, value mappings, script collections, and message mappings.
125
+ - `cpi_get_iflow` — read iFlow metadata, externalized configuration, and bundle files (or one file by path).
126
+ - `cpi_get_artifact` — read a value mapping, script collection, or message mapping with its bundle files.
127
+ - `cpi_get_value_mappings` — read value-mapping agency pairs with their value and default pairs.
128
+ - `cpi_create_package` — create a package.
129
+ - `cpi_update_package` — change package name, short text, description, or version.
130
+ - `cpi_copy_package` — copy a Discover package into Design (new copy with suffix, or overwrite-merge keeping configuration).
131
+ - `cpi_create_iflow` — create an iFlow from the vetted HTTPS → Request Reply → HTTP template or caller-supplied BPMN.
132
+ - `cpi_update_iflow` — replace/add bundle files and parameters with an expected-version check and patch-version bump.
133
+ - `cpi_update_iflow_configuration` — set externalized configuration parameters.
134
+ - `cpi_upsert_value_mappings` — add or change value-mapping pairs.
135
+ - `cpi_update_script_collection` — add or replace scripts in a script collection.
136
+
137
+ Cloud Integration — deployment and monitoring:
138
+
139
+ - `cpi_deploy_iflow` — deploy the active iFlow version and wait for build and runtime status.
140
+ - `cpi_deploy_artifact` — deploy a value mapping, script collection, or message mapping and wait for runtime status.
141
+ - `cpi_undeploy_artifact` — undeploy any runtime artifact and wait until it is gone.
142
+ - `cpi_list_runtime_artifacts` — list deployed artifacts, optionally by status.
143
+ - `cpi_get_iflow_runtime_status` — read deployed runtime status, including error text when the iFlow failed to start.
144
+ - `cpi_list_service_endpoints` — list deployed endpoint URLs, optionally for one iFlow.
145
+ - `cpi_list_message_logs` — list message processing logs by iFlow, status, time window, correlation id, application message id, or custom header.
146
+ - `cpi_get_message_log` — read one message log with error text and custom header properties; payload attachments are never read.
147
+ - `cpi_get_message_log_steps` — read the processing steps of a message's latest run with step errors.
148
+ - `cpi_get_jms_broker_status` — read JMS broker capacity and queues that are not healthy.
149
+ - `cpi_list_data_stores` — list data stores with message and overdue counts.
150
+ - `cpi_list_data_store_entries` — list entry metadata of one data store; payloads are never read.
151
+ - `cpi_list_variables` — list global and iFlow variables (metadata only).
152
+
153
+ Cloud Integration — security material and tenant configuration:
154
+
155
+ - `cpi_list_security_material` — list user credentials, OAuth2 client credentials, or keystore entries with secret fields redacted.
156
+ - `cpi_set_user_credential` — create or update a user credential; the password is write-only.
157
+ - `cpi_set_oauth2_client_credential` — create or update an OAuth2 client credential; the client secret is write-only.
158
+ - `cpi_upload_certificate` — add or replace a public certificate in the tenant keystore.
159
+ - `cpi_list_number_ranges` — list number ranges.
160
+ - `cpi_set_number_range` — create or update a number range.
161
+ - `cpi_list_partner_parameters` — list Partner Directory string parameters and binary parameter metadata.
162
+ - `cpi_set_partner_parameter` — create or update a Partner Directory string or binary parameter.
163
+ - `cpi_odata_get` — guarded read-only GET for other CPI entity sets (for example access policies or JMS queues) and the service metadata.
164
+
165
+ API Management:
166
+
167
+ - `apim_list_api_providers` — list API providers without credential fields.
168
+ - `apim_get_api_provider` — read one API provider with credential properties removed.
169
+ - `apim_create_api_provider` — create an Internet or On-Premise (Cloud Connector) API provider without credentials.
170
+ - `apim_update_api_provider` — change host, port, type, location, path prefix, or sap-client of a credential-free provider.
171
+ - `apim_list_api_proxies` — list API proxies.
172
+ - `apim_get_api_proxy` — read one API proxy with endpoints, virtual hosts, and the computed runtime URL.
173
+ - `apim_export_api_proxy` — export an API proxy bundle and return its files (policies, endpoints, scripts).
174
+ - `apim_list_virtual_hosts` — list virtual hosts.
175
+ - `apim_create_api_proxy` — import (and thereby deploy) a policy-free API proxy for a provider or https URL target.
176
+ - `apim_update_api_proxy` — change base path, target, title, or description; existing policies and steps are kept.
177
+ - `apim_attach_api_proxy_policies` — add or replace policies (API key, quota, spike arrest, CORS, headers, OAuth, IP allow list, response cache, Basic auth from a key value map, raise fault, JavaScript, custom XML) and their flow steps, then redeploy.
178
+ - `apim_list_api_products` — list API products.
179
+ - `apim_get_api_product` — read one API product with its proxies.
180
+ - `apim_create_api_product` — create (and by default publish) an API product over existing proxies with an optional quota.
181
+ - `apim_update_api_product` — change product title, description, scope, quota, or publish state.
182
+ - `apim_list_key_value_maps` — list key value maps.
183
+ - `apim_get_key_value_map` — read one key value map; encrypted values are redacted.
184
+ - `apim_create_key_value_map` — create a key value map; values are write-only.
185
+ - `apim_set_key_value_map_entries` — add or change key value map entries; values are write-only.
186
+ - `apim_odata_get` — guarded read-only GET for other Management.svc entity sets (for example applications, developers, certificate store references) and the service metadata.
187
+
188
+ Credentials come from CPI/APIM OAuth client service keys in the MCP host environment (`CPI_SERVICE_KEY`/`APIM_SERVICE_KEY`, explicit `CPI_*`/`APIM_*` variables, or `VCAP_SERVICES`) and are never written to `mcp.json`. Every write requires an exact confirmation string (for example `CREATE IFLOW <iflow_id>` or `UNDEPLOY ARTIFACT <artifact_id>`) and is checked before any network call. Scope is create, update, deploy, and undeploy: there are no delete tools, and deletions stay manual in the Integration Suite UI. Secrets (passwords, client secrets, key value map values) are write-only: tools accept them as input and never return them. Message payloads, attachments, and data-store payloads are never read.