sap-ai-dev-toolkit 0.9.9922 → 0.9.9923

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 (53) hide show
  1. package/.github/agents/sap-btp-infrastructure-admin.agent.md +23 -0
  2. package/.github/agents/sap-bw-datasphere-migration-specialist.agent.md +22 -0
  3. package/.github/agents/sap-datasphere-developer.agent.md +22 -0
  4. package/.github/agents/sap-devops-observability-specialist.agent.md +54 -0
  5. package/.github/agents/sap-process-automation-developer.agent.md +50 -0
  6. package/.github/skills/btp-platform-administration/SKILL.md +53 -0
  7. package/.github/skills/bw-datasphere-migration/SKILL.md +66 -0
  8. package/.github/skills/cloud-alm-operations/SKILL.md +135 -0
  9. package/.github/skills/datasphere-administration/SKILL.md +40 -0
  10. package/.github/skills/datasphere-development/SKILL.md +55 -0
  11. package/.github/skills/process-automation-development/SKILL.md +113 -0
  12. package/README.md +152 -8
  13. package/inventory.md +60 -0
  14. package/package.json +8 -2
  15. package/src/btp-cli.mjs +199 -0
  16. package/src/btp-server.mjs +125 -0
  17. package/src/btp-tools.mjs +776 -0
  18. package/src/cloud-alm-client.mjs +44 -0
  19. package/src/cloud-alm-config.mjs +92 -0
  20. package/src/cloud-alm-credentials.mjs +29 -0
  21. package/src/cloud-alm-data.mjs +129 -0
  22. package/src/cloud-alm-server.mjs +115 -0
  23. package/src/cloud-alm-tools.mjs +682 -0
  24. package/src/datasphere-auth.mjs +279 -0
  25. package/src/datasphere-cli.mjs +220 -0
  26. package/src/datasphere-command.mjs +236 -0
  27. package/src/datasphere-config.mjs +154 -0
  28. package/src/datasphere-credentials.mjs +37 -0
  29. package/src/datasphere-csn.mjs +73 -0
  30. package/src/datasphere-discovery.mjs +151 -0
  31. package/src/datasphere-server.mjs +249 -0
  32. package/src/datasphere-tools.mjs +809 -0
  33. package/src/hana-database.mjs +3 -3
  34. package/src/hana-inspector.mjs +2 -2
  35. package/src/harnesses.mjs +31 -1
  36. package/src/install-user-assets.mjs +1 -1
  37. package/src/integration-common.mjs +7 -7
  38. package/src/integration-config.mjs +5 -0
  39. package/src/integration-credentials.mjs +25 -16
  40. package/src/integration-http.mjs +24 -7
  41. package/src/launcher.mjs +56 -4
  42. package/src/mcp-config.mjs +37 -1
  43. package/src/process-automation-automation-tools.mjs +125 -0
  44. package/src/process-automation-client.mjs +36 -0
  45. package/src/process-automation-common.mjs +69 -0
  46. package/src/process-automation-config.mjs +142 -0
  47. package/src/process-automation-credentials.mjs +37 -0
  48. package/src/process-automation-inbox-tools.mjs +246 -0
  49. package/src/process-automation-server.mjs +139 -0
  50. package/src/process-automation-tools.mjs +727 -0
  51. package/src/process-automation-visibility-tools.mjs +182 -0
  52. package/src/setup.mjs +623 -11
  53. package/tools.md +138 -0
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env node
2
+ // sap-ai-datasphere: stdio MCP server for SAP Datasphere. Technical (client
3
+ // credentials) or interactive (authorization code + refresh token) OAuth client;
4
+ // REST consumption/tasks APIs, the datasphere CLI child runner, and optional
5
+ // Open SQL access through a Datasphere database user.
6
+ import { readFile } from 'node:fs/promises';
7
+ import { pathToFileURL } from 'node:url';
8
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { resolveDatasphereConfig } from './datasphere-config.mjs';
11
+ import { datasphereCredentialsPath, readStoredDatasphereCredentials, updateStoredRefreshToken } from './datasphere-credentials.mjs';
12
+ import { createDatasphereTokenProvider } from './datasphere-auth.mjs';
13
+ import { createDatasphereCli } from './datasphere-cli.mjs';
14
+ import { loadDatasphereCommands } from './datasphere-discovery.mjs';
15
+ import { DATASPHERE_TOOL_NAMES, registerDatasphereTools } from './datasphere-tools.mjs';
16
+ import { createStaticServiceClient, IntegrationError } from './integration-http.mjs';
17
+ import { connectHana } from './hana-database.mjs';
18
+
19
+ const packageMetadata = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
20
+ const CSRF_PATH = '/api/v1/datasphere/consumption/catalog/spaces';
21
+ const SQL_QUERY_TIMEOUT_MS = 300_000;
22
+
23
+ function unavailableCli(error) {
24
+ const message = error?.message || 'Datasphere CLI runtime is unavailable';
25
+ const reject = () => Promise.reject(new IntegrationError(message));
26
+ return {
27
+ packageVersion: null,
28
+ init: reject,
29
+ run: reject,
30
+ version: reject,
31
+ stateDir: () => null,
32
+ close: async () => {}
33
+ };
34
+ }
35
+
36
+ /** Create an injectable server runtime for tests and the stdio executable. */
37
+ export function createDatasphereServer({ env = process.env, fetchImpl, cliFactory = createDatasphereCli, sqlFactory = connectHana } = {}) {
38
+ const config = resolveDatasphereConfig(env, { stored: readStoredDatasphereCredentials(env) });
39
+
40
+ const provider = createDatasphereTokenProvider({
41
+ config,
42
+ env,
43
+ persistRefreshToken: refreshToken => (config.source === 'stored-key' ? updateStoredRefreshToken(refreshToken, env) : undefined),
44
+ ...(fetchImpl ? { fetchImpl } : {})
45
+ });
46
+
47
+ const secrets = async () => {
48
+ if (config.auth === 'technical') {
49
+ return {
50
+ client_id: config.clientId,
51
+ client_secret: config.clientSecret,
52
+ token_url: config.tokenUrl,
53
+ authorization_flow: 'client_credentials',
54
+ host: config.url,
55
+ tenantUrl: config.url
56
+ };
57
+ }
58
+ const token = await provider.token();
59
+ return {
60
+ client_id: config.clientId,
61
+ client_secret: config.clientSecret,
62
+ token_url: config.tokenUrl,
63
+ authorization_url: config.authorizationUrl,
64
+ access_token: token.accessToken,
65
+ refresh_token: token.refreshToken,
66
+ expires_after: Math.floor(token.expiresAt / 1000),
67
+ authorization_flow: 'authorization_code',
68
+ host: config.url,
69
+ tenantUrl: config.url
70
+ };
71
+ };
72
+
73
+ const rest = createStaticServiceClient({
74
+ label: 'DATASPHERE',
75
+ url: config.url,
76
+ authorization: async () => `Bearer ${(await provider.token()).accessToken}`,
77
+ csrfPath: CSRF_PATH,
78
+ env,
79
+ ...(fetchImpl ? { fetchImpl } : {})
80
+ });
81
+
82
+ let cli;
83
+ try {
84
+ cli = cliFactory({ config, secrets, env });
85
+ } catch (error) {
86
+ cli = unavailableCli(error);
87
+ }
88
+
89
+ let commandsPromise;
90
+ const commands = () => {
91
+ if (!commandsPromise) {
92
+ commandsPromise = loadDatasphereCommands({ rest, cli }).catch(error => {
93
+ commandsPromise = undefined;
94
+ throw error;
95
+ });
96
+ }
97
+ return commandsPromise;
98
+ };
99
+
100
+ let databasePromise = null;
101
+ let sqlAccess = null;
102
+ if (config.sql) {
103
+ databasePromise = null;
104
+ const getDatabase = () => {
105
+ if (!databasePromise) {
106
+ databasePromise = Promise.resolve(sqlFactory(config.sql, { queryTimeoutMs: SQL_QUERY_TIMEOUT_MS })).catch(error => {
107
+ databasePromise = null;
108
+ throw error;
109
+ });
110
+ }
111
+ return databasePromise;
112
+ };
113
+ sqlAccess = { config: config.sql, connect: async () => getDatabase() };
114
+ }
115
+
116
+ const summary = Object.freeze({
117
+ tenant_host: new URL(config.url).host,
118
+ token_host: new URL(config.tokenUrl).host,
119
+ auth: config.auth,
120
+ source: config.source,
121
+ cli_package_version: cli.packageVersion,
122
+ sql: config.sql ? { host: config.sql.host, user: config.sql.user, schema: config.sql.schema } : null,
123
+ admin_commands_available: config.auth === 'interactive'
124
+ });
125
+
126
+ const verify = async () => {
127
+ const report = {};
128
+ try {
129
+ const payload = await rest.request('GET', '/api/v1/datasphere/consumption/catalog/spaces');
130
+ report.catalog = `ok (${(payload?.value ?? []).length} spaces)`;
131
+ } catch (error) {
132
+ report.catalog = `failed: ${error?.message || 'catalog request failed'}`;
133
+ }
134
+ try {
135
+ await cli.init();
136
+ report.cli = `ok (datasphere ${await cli.version()})`;
137
+ } catch (error) {
138
+ report.cli = `failed: ${error?.message || 'CLI initialization failed'}`;
139
+ }
140
+ if (!config.sql) {
141
+ report.sql = 'not configured';
142
+ } else {
143
+ try {
144
+ const database = await sqlAccess.connect();
145
+ const rows = await database.query('SELECT CURRENT_USER, CURRENT_SCHEMA FROM DUMMY');
146
+ const row = Array.isArray(rows) ? rows[0] : {};
147
+ report.sql = `ok (${row?.CURRENT_USER ?? config.sql.user}/${row?.CURRENT_SCHEMA ?? config.sql.schema})`;
148
+ } catch (error) {
149
+ report.sql = `failed: ${error?.message || 'SQL connection failed'}`;
150
+ }
151
+ }
152
+ return report;
153
+ };
154
+
155
+ const server = new McpServer({
156
+ name: 'sap-ai-datasphere',
157
+ version: packageMetadata.version
158
+ }, { capabilities: { tools: {} } });
159
+ registerDatasphereTools(server, { rest, cli, sql: sqlAccess, summary, verify, commands });
160
+
161
+ let closing;
162
+ const close = () => {
163
+ if (!closing) {
164
+ closing = (async () => {
165
+ await cli.close?.().catch?.(() => {});
166
+ await databasePromise?.then(database => database.close()).catch(() => {});
167
+ await server.close().catch(() => {});
168
+ })();
169
+ }
170
+ return closing;
171
+ };
172
+
173
+ return { server, close, verify, connectionSummary: summary };
174
+ }
175
+
176
+ export async function runDatasphereServer({ env = process.env, stdin = process.stdin, stdout = process.stdout, stderr = process.stderr, cliFactory, sqlFactory } = {}) {
177
+ if (process.argv.slice(2).some(argument => argument === '--help' || argument === '-h')) {
178
+ stdout.write([
179
+ 'Usage: sap-ai-datasphere',
180
+ '',
181
+ 'Starts a stdio MCP server for SAP Datasphere: spaces, users, roles, database users, workload,',
182
+ 'connections, TLS certificates, modeling objects and packages, task chains, replication flows,',
183
+ 'Data Marketplace, consumption queries and Open SQL. Writes require exact confirmation strings.',
184
+ '',
185
+ 'Credentials come from a Datasphere OAuth client (System > App Integration) in the MCP host',
186
+ 'environment, or from the user-only store filled by sap-ai-dev --setup-datasphere:',
187
+ ' DATASPHERE_URL tenant URL, for example https://<tenant>.eu10.hcs.cloud.sap',
188
+ ' DATASPHERE_TOKEN_URL OAuth token URL of the tenant',
189
+ ' DATASPHERE_CLIENT_ID OAuth client id (all four variables required together)',
190
+ ' DATASPHERE_CLIENT_SECRET OAuth client secret',
191
+ ' DATASPHERE_AUTH technical (default) | interactive',
192
+ ' DATASPHERE_AUTHORIZATION_URL OAuth authorization URL (required for interactive)',
193
+ ' DATASPHERE_REFRESH_TOKEN refresh token (required for interactive)',
194
+ 'Optional Open SQL access through a Datasphere database user:',
195
+ ' DATASPHERE_SQL_HOST, DATASPHERE_SQL_USER, DATASPHERE_SQL_PASSWORD required together',
196
+ ' DATASPHERE_SQL_PORT default 443',
197
+ ' DATASPHERE_SQL_SCHEMA default = database user name',
198
+ ` ${datasphereCredentialsPath(env)}`,
199
+ ' sign-in stored by sap-ai-dev --setup-datasphere (never mcp.json)',
200
+ '',
201
+ 'HTTP_PROXY, HTTPS_PROXY and NO_PROXY are honored.',
202
+ '',
203
+ ' --check verify catalog, CLI and SQL access; exit 1 when any check fails',
204
+ ''
205
+ ].join('\n'));
206
+ return;
207
+ }
208
+ let runtime;
209
+ try {
210
+ runtime = createDatasphereServer({ env, ...(cliFactory ? { cliFactory } : {}), ...(sqlFactory ? { sqlFactory } : {}) });
211
+ } catch (error) {
212
+ stderr.write(`[sap-ai-datasphere] ${error.message}\n`);
213
+ process.exitCode = 1;
214
+ return;
215
+ }
216
+ if (process.argv.slice(2).includes('--check')) {
217
+ const report = await runtime.verify();
218
+ const lines = Object.entries(report).map(([name, result]) => `${name}: ${result}`);
219
+ stdout.write(`${lines.join('\n')}\n`);
220
+ await runtime.close();
221
+ if (Object.values(report).some(result => String(result).startsWith('failed'))) process.exitCode = 1;
222
+ return;
223
+ }
224
+
225
+ const transport = new StdioServerTransport(stdin, stdout, { maxBufferSize: 1024 * 1024 });
226
+ let closing;
227
+ const shutdown = () => {
228
+ if (!closing) closing = runtime.close().catch(() => {});
229
+ return closing;
230
+ };
231
+ const onStdinEnd = () => { void shutdown(); };
232
+ const onSignal = signal => {
233
+ void shutdown().finally(() => process.exit(signal === 'SIGINT' ? 130 : 143));
234
+ };
235
+ stdin.once('end', onStdinEnd);
236
+ process.once('SIGINT', onSignal);
237
+ process.once('SIGTERM', onSignal);
238
+ transport.onerror = () => stderr.write('[sap-ai-datasphere] MCP stdio transport failed.\n');
239
+
240
+ try {
241
+ await runtime.server.connect(transport);
242
+ } catch {
243
+ process.exitCode = 1;
244
+ }
245
+ }
246
+
247
+ if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
248
+ await runDatasphereServer();
249
+ }