sap-ai-dev-toolkit 0.3.4 β 0.4.0
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 +5 -5
- package/.github/agents/abap-runtime-debugger.agent.md +10 -10
- package/.github/agents/hana-cloud-hdi-specialist.agent.md +22 -0
- package/.github/agents/rap-service-developer.agent.md +10 -10
- package/.github/agents/sap-solution-architect.agent.md +7 -6
- package/.github/skills/abap-debugging/SKILL.md +2 -2
- package/.github/skills/abap-runtime-analysis/SKILL.md +4 -4
- package/.github/skills/clean-core-extensibility/SKILL.md +1 -1
- package/.github/skills/hana-cloud-inspection/SKILL.md +35 -0
- package/.github/skills/hana-cloud-native-development/SKILL.md +26 -0
- package/.github/skills/hana-cloud-validation/SKILL.md +26 -0
- package/.github/skills/rap-development/SKILL.md +2 -2
- package/.github/skills/rap-service-delivery/SKILL.md +3 -3
- package/.github/skills/sap-sdlc-orchestration/SKILL.md +1 -1
- package/.github/skills/sap-standard-api-analysis/SKILL.md +1 -1
- package/.github/skills/sap-transport-release/SKILL.md +1 -1
- package/README.md +46 -22
- package/package.json +9 -4
- package/scripts/postinstall.mjs +3 -3
- package/src/hana-config.mjs +170 -0
- package/src/hana-database.mjs +150 -0
- package/src/hana-inspector.mjs +114 -0
- package/src/hana-tools.mjs +277 -0
- package/src/mcp-config.mjs +10 -1
- package/src/mcp-proxy.mjs +61 -0
- package/src/setup.mjs +3 -2
- package/test/copilot-content.test.mjs +48 -0
- package/test/hana-config.test.mjs +168 -0
- package/test/hana-inspector-stdio.test.mjs +44 -0
- package/test/hana-tools.test.mjs +206 -0
- package/test/live-s4h.test.mjs +202 -0
- package/test/mcp-config-cf.test.mjs +40 -0
- package/test/mcp-proxy.test.mjs +17 -31
- package/test/setup.test.mjs +10 -6
- package/tools.md +58 -50
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ Then connect a destination in SAP Business Application Studio:
|
|
|
62
62
|
1. Open the Command Palette.
|
|
63
63
|
2. Run **MCP: List Servers**.
|
|
64
64
|
3. Start the server named after your selected BAS destination.
|
|
65
|
-
4. In GitHub Copilot Chat, choose the best-fit bundled agent from the agent picker: **SAP Solution Architect**, **ABAP Developer**, **ABAP Runtime Debugger**,
|
|
65
|
+
4. In GitHub Copilot Chat, choose the best-fit bundled agent from the agent picker: **SAP Solution Architect**, **ABAP Developer**, **ABAP Runtime Debugger**, **RAP Service Developer**, or **HANA Cloud/HDI Specialist**.
|
|
66
66
|
5. In the Chat tools picker, enable the server for that BAS destination.
|
|
67
67
|
6. Ask Copilot to inspect, build, test, or verify something in your SAP landscape.
|
|
68
68
|
|
|
@@ -72,7 +72,7 @@ Use the attached destination-prefixed tools directly in chat. Do not launch `sap
|
|
|
72
72
|
|
|
73
73
|
## π€ Available agents and skills
|
|
74
74
|
|
|
75
|
-
**Agents:**
|
|
75
|
+
**Agents:** Five user-invocable custom agents are included. Select the best fit from the agent picker in GitHub Copilot Chat, as shown in Quick start.
|
|
76
76
|
|
|
77
77
|
| | Agent | Best for |
|
|
78
78
|
| --- | --- | --- |
|
|
@@ -80,8 +80,9 @@ Use the attached destination-prefixed tools directly in chat. Do not launch `sap
|
|
|
80
80
|
| π§βπ» | **ABAP Developer** | General ABAP, CDS, and RAP implementation, validation, and transport-preparation tasks |
|
|
81
81
|
| π | **ABAP Runtime Debugger** | Runtime incidents, dumps, logs, traces, debugger sessions, call graphs, and performance symptoms |
|
|
82
82
|
| π | **RAP Service Developer** | RAP business objects, behavior implementations, projections, service definitions, service bindings, and OData validation |
|
|
83
|
+
| ποΈ | **HANA Cloud/HDI Specialist** | Read-only HANA Cloud container inspection, CAP/HDI artifact generation, local validation, and user-run deployment handoffs |
|
|
83
84
|
|
|
84
|
-
**Included skills:** `abap-development` Β· `abap-testing-quality` Β· `cds-development` Β· `rap-development` Β· `abap-debugging` Β· `abap-runtime-analysis` Β· `rap-service-delivery` Β· `sap-standard-api-analysis` Β· `clean-core-extensibility` Β· `sap-sdlc-orchestration` Β· `sap-transport-release`
|
|
85
|
+
**Included skills:** `abap-development` Β· `abap-testing-quality` Β· `cds-development` Β· `rap-development` Β· `abap-debugging` Β· `abap-runtime-analysis` Β· `rap-service-delivery` Β· `sap-standard-api-analysis` Β· `clean-core-extensibility` Β· `sap-sdlc-orchestration` Β· `sap-transport-release` Β· `hana-cloud-inspection` Β· `hana-cloud-native-development` Β· `hana-cloud-validation`
|
|
85
86
|
|
|
86
87
|
If the agents are not listed, install the optional agents and skills under `$HOME/.copilot` when prompted during an interactive global install, then reload BAS if needed. Repository-scoped installation instructions appear below.
|
|
87
88
|
|
|
@@ -102,7 +103,8 @@ The workflow covers ABAP, CDS, RAP, repository analysis, table and query access,
|
|
|
102
103
|
| π | **Understand before editing** | Trace source, callers, definitions, package contents, dependencies, and CDS impact in one workflow. |
|
|
103
104
|
| π§© | **CDS + RAP development** | Explore models and dependencies, then build RAP business objects and services with system context. |
|
|
104
105
|
| ποΈ | **Ground decisions in SAP data** | Inspect DDIC structures, read table contents, and run controlled ABAP SQL queries. |
|
|
105
|
-
|
|
|
106
|
+
| βοΈ | **Read-only HANA Cloud inspection** | Inspect an environment-bound HDI schema, object metadata, and capped query results without exposing deployment credentials. |
|
|
107
|
+
| βοΈ | **Edit ABAP safely** | Update supported source, stage reviewed change sets, create transport requests, and run syntax checks before requested activation. |
|
|
106
108
|
| β
| **Quality built into the flow** | Pair local ABAP linting with SAP syntax checks, ABAP Unit, ATC, editor diagnostics, and formatting. |
|
|
107
109
|
| π | **Debug with system context** | Investigate dumps, traces, application logs, runtime failures, capabilities, and installed components. |
|
|
108
110
|
| π | **Transport-aware workflows** | Check request and lock context before preparing a change; create transports only when authorized. |
|
|
@@ -152,9 +154,9 @@ The catalog below covers source inspection, data, editing, quality, transports,
|
|
|
152
154
|
- Read SLG1 application logs
|
|
153
155
|
- Review and create transport requests
|
|
154
156
|
|
|
155
|
-
## π§
|
|
157
|
+
## π§ Five agents, fourteen focused skills
|
|
156
158
|
|
|
157
|
-
The package ships with
|
|
159
|
+
The package ships with five custom agents plus fourteen task-focused Copilot Agent Skills:
|
|
158
160
|
|
|
159
161
|
| | Skill | Best for |
|
|
160
162
|
| --- | --- | --- |
|
|
@@ -169,6 +171,9 @@ The package ships with four custom agents plus eleven task-focused Copilot Agent
|
|
|
169
171
|
| π¬ | `abap-runtime-analysis` | Incident triage, traces, debugger state, call graphs, and performance analysis |
|
|
170
172
|
| π | `rap-service-delivery` | RAP service activation, publication, OData validation, and end-to-end runtime checks |
|
|
171
173
|
| π | `sap-transport-release` | Dependency checks and transport preparation; release itself is intentionally unavailable here |
|
|
174
|
+
| π | `hana-cloud-inspection` | Verify the selected HANA Cloud binding and inspect bounded HDI catalog/data results |
|
|
175
|
+
| π§± | `hana-cloud-native-development` | Generate CAP-owned CDS models or required native HDI artifacts without duplicating generated sources |
|
|
176
|
+
| β
| `hana-cloud-validation` | Run local CAP/HANA builds and prepare a reviewed, user-run deployment handoff |
|
|
172
177
|
|
|
173
178
|
## πΊοΈ How it fits together
|
|
174
179
|
|
|
@@ -176,13 +181,13 @@ Each selected BAS destination becomes its own isolated MCP server identity. The
|
|
|
176
181
|
|
|
177
182
|
## π§° Optional full-stack companion MCP servers
|
|
178
183
|
|
|
179
|
-
ABAP/RAP backend access is provided by this add-on's BAS/VSP proxy. For end-to-end SAP development, setup can also add managed companion MCP entries for frontend, CAP,
|
|
184
|
+
ABAP/RAP backend access is provided by this add-on's BAS/VSP proxy. For end-to-end SAP development, setup can also add managed companion MCP entries for frontend, CAP, browser validation, and read-only HANA inspection:
|
|
180
185
|
|
|
181
186
|
```sh
|
|
182
187
|
sap-ai-dev --setup --tools
|
|
183
188
|
```
|
|
184
189
|
|
|
185
|
-
The companion entries are optional and are launched through `npx` only when the MCP client starts them. They are marked as managed by `sap-ai-dev-toolkit`, so rerunning setup can update or remove them without touching unrelated MCP servers.
|
|
190
|
+
The companion entries are optional and are launched through `npx` only when the MCP client starts them. They are marked as managed by `sap-ai-dev-toolkit`, so rerunning setup can update or remove them without touching unrelated MCP servers. The HANA companion uses `--ignore-scripts` so the toolkit postinstall wizard cannot run during MCP startup.
|
|
186
191
|
|
|
187
192
|
| MCP entry | Package | Launches | Use when the agent needs to |
|
|
188
193
|
| --- | --- | --- | --- |
|
|
@@ -190,13 +195,26 @@ The companion entries are optional and are launched through `npx` only when the
|
|
|
190
195
|
| `ui5-tools` | `@ui5/mcp-server` | `ui5mcp` | Inspect SAPUI5/OpenUI5 projects, manifests, routing, views, controllers, and UI5-specific issues |
|
|
191
196
|
| `cap-tools` | `@cap-js/mcp-server` | `cds-mcp` | Inspect CAP CDS models, services, entities, actions, and local CAP application structure |
|
|
192
197
|
| `browser-validation` | `@playwright/mcp` | `playwright-mcp` | Open BAS previews, smoke-test Fiori/UI flows, collect screenshots, and verify browser runtime behavior |
|
|
198
|
+
| `hana-cloud-inspector` | `sap-ai-dev-toolkit` | `sap-ai-hana` | Inspect a bound HANA Cloud HDI schema using read-only catalog tools and capped row reads |
|
|
193
199
|
|
|
194
200
|
Recommended profiles:
|
|
195
201
|
|
|
196
202
|
- **RAP + Fiori:** select your BAS destination plus `sap-fiori-tools`, `ui5-tools`, and `browser-validation`.
|
|
197
203
|
- **CAP on BTP:** select `cap-tools`, `sap-fiori-tools`, `ui5-tools`, and `browser-validation`.
|
|
204
|
+
- **CAP + HANA Cloud:** select `cap-tools` and `hana-cloud-inspector`; provide the inspector's read-only connection through MCP-host environment variables.
|
|
198
205
|
- **UI-only:** select `sap-fiori-tools`, `ui5-tools`, and optionally `browser-validation`.
|
|
199
206
|
|
|
207
|
+
### HANA Cloud/HDI inspector
|
|
208
|
+
|
|
209
|
+
Select `hana-cloud-inspector` in the optional companion-server setup, then start it from **MCP: List Servers**. The process reads its connection from the MCP host's environment; setup never copies HANA credentials into `mcp.json`. Make sure variables are available to the BAS/VS Code MCP host process (not only to a later terminal session), then reload/restart the MCP host if needed.
|
|
210
|
+
|
|
211
|
+
Use either:
|
|
212
|
+
|
|
213
|
+
- A dedicated read-only VCAP binding with `credentials.host`, `port`, `user`, `password`, and `schema` in `VCAP_SERVICES`; optionally set `HANA_RO_VCAP_SERVICE` to the exact VCAP service key and `HANA_RO_BINDING` to the binding/instance name when selection is ambiguous.
|
|
214
|
+
- Explicit `HANA_RO_HOST`, `HANA_RO_PORT`, `HANA_RO_USER`, `HANA_RO_PASSWORD`, and `HANA_RO_SCHEMA` variables. `HANA_RO_TRUST_STORE` is optional; TLS and certificate verification are always enabled.
|
|
215
|
+
|
|
216
|
+
The configured identity must be a separate least-privileged read-only HANA user. The server rejects bindings that offer only HDI deployment credentials and never falls back to `hdi_user`/`hdi_password`. Its tools are limited to connection identity, object listing/descriptions, and parameterized row reads capped at 200 rows; credential-like columns are blocked, and there is no arbitrary SQL, DDL/DML, grant, deployment, or undeploy tool. Use the existing project deployment workflow yourself after reviewing the exact generated artifact diff and target. The SAP HANA Node.js driver is provided under the SAP Developer License Agreement.
|
|
217
|
+
|
|
200
218
|
## π‘ Example requests
|
|
201
219
|
|
|
202
220
|
Once the destination server is enabled in Copilot Chat, ask for outcomes instead of manually orchestrating individual SAP operations:
|
|
@@ -209,6 +227,8 @@ Once the destination server is enabled in Copilot Chat, ask for outcomes instead
|
|
|
209
227
|
|
|
210
228
|
> Read company codes from `T001` for this destination and return `BUKRS`, `BUTXT`, `WAERS`, and `LAND1`.
|
|
211
229
|
|
|
230
|
+
> Inspect the HANA HDI container attached to this workspace, describe the relevant tables, and show at most 20 rows for the columns needed to explain the issue. Do not change database state.
|
|
231
|
+
|
|
212
232
|
> Check the current object's transport context, prepare the change for transport, but do not release anything.
|
|
213
233
|
|
|
214
234
|
## π Enterprise-friendly safety model
|
|
@@ -219,7 +239,7 @@ Once the destination server is enabled in Copilot Chat, ask for outcomes instead
|
|
|
219
239
|
| π¦ **Explicit state change** | Activation, service publication, and transport creation happen only when requested and authorized. |
|
|
220
240
|
| π§ͺ **Verification first** | The agent uses available lint, syntax, unit-test, ATC, and diagnostics workflows and reports what actually ran. |
|
|
221
241
|
| π **SAP authorization remains authoritative** | The add-on does not bypass backend SAP permissions. |
|
|
222
|
-
| π **
|
|
242
|
+
| π **Curated VSP tool surface** | The proxy exposes a cherry-picked developer-lifecycle set from VSP plus local workflow tools, keeping one destination below 60 tools; SAP authorizations and VSP safety checks still apply. |
|
|
223
243
|
| π§± **Per-destination isolation** | Generated MCP entries are scoped to a single `SAP_AI_DEV_TOOLKIT_DESTINATION`. |
|
|
224
244
|
| π **No credentials in `mcp.json`** | Authentication material stays in BAS destination configuration rather than MCP config. |
|
|
225
245
|
|
|
@@ -294,7 +314,7 @@ At the end of a global install, the color-coded summary shows the MCP config pat
|
|
|
294
314
|
|
|
295
315
|
## π€ GitHub Copilot ABAP agents and skills
|
|
296
316
|
|
|
297
|
-
The package includes
|
|
317
|
+
The package includes five user-invocable custom agents (**SAP Solution Architect**, **ABAP Developer**, **ABAP Runtime Debugger**, **RAP Service Developer**, and **HANA Cloud/HDI Specialist**) and fourteen task-focused Agent Skills for GitHub Copilot in BAS.
|
|
298
318
|
|
|
299
319
|
### π§ How the SAP Solution Architect and ABAP Developer agents work
|
|
300
320
|
|
|
@@ -322,10 +342,13 @@ The package includes four user-invocable custom agents (**SAP Solution Architect
|
|
|
322
342
|
| π¬ | `abap-runtime-analysis` | Analyze incidents, traces, debugger state, call graphs, and performance symptoms. |
|
|
323
343
|
| π | `rap-service-delivery` | Validate RAP service bindings, activation, publication, and end-to-end OData behavior. |
|
|
324
344
|
| π | `sap-transport-release` | Check dependencies and prepare changes for transport; release is not available here. |
|
|
345
|
+
| π | `hana-cloud-inspection` | Verify the attached HANA target and inspect bounded HDI metadata and rows. |
|
|
346
|
+
| π§± | `hana-cloud-native-development` | Create CAP CDS or required native HDI source artifacts while avoiding duplicate models. |
|
|
347
|
+
| β
| `hana-cloud-validation` | Build locally, review deployment risks, and hand off deployment to the user. |
|
|
325
348
|
|
|
326
349
|
### π₯ Install for your BAS user
|
|
327
350
|
|
|
328
|
-
After destination setup, the installer prints a π€ notice that it is waiting for confirmation, then offers to install
|
|
351
|
+
After destination setup, the installer prints a π€ notice that it is waiting for confirmation, then offers to install all bundled agents and skills under `$HOME/.copilot`. Press **Enter** to install; type **n** then **Enter** to skip. Declining leaves those files unchanged.
|
|
329
352
|
|
|
330
353
|
These user-level customizations are available across workspaces opened by the same BAS user in the same dev space. Copilot must be available in BAS and may need a window reload to discover new files. A non-interactive install skips the optional prompt; `npm install --ignore-scripts` skips the postinstall wizard entirely.
|
|
331
354
|
|
|
@@ -457,7 +480,7 @@ The file must be strict JSON with an object-valued `servers` property. Existing
|
|
|
457
480
|
|
|
458
481
|
With `H2O_URL` set, the normal command starts the MCP proxy. Each generated entry supplies one `SAP_AI_DEV_TOOLKIT_DESTINATION`, so each server stays in its own lane and exposes only its selected SAP system.
|
|
459
482
|
|
|
460
|
-
The proxy exposes
|
|
483
|
+
The proxy exposes a curated subset of VSP tools, plus local `LintABAP`, destination-scoped workflow tools, and the convenience `GetApplicationLog` mapping when the VSP SAP router is available. Hidden VSP tools remain available only behind local workflow chaining where required; they are not advertised or directly callable through the proxy. Direct VSP invocation remains unchanged.
|
|
461
484
|
|
|
462
485
|
Without `H2O_URL`, the command passes arguments directly to the installed VSP binaryβno BAS proxy detour.
|
|
463
486
|
|
|
@@ -535,7 +558,8 @@ For an ABAP Cloud assessment, get object URIs from `SearchObject` and pass them
|
|
|
535
558
|
| `GetPackage` | Read package details. |
|
|
536
559
|
| `GetAPIReleaseState` | Check whether an object is released for S/4HANA Clean Core / ABAP Cloud development; use the URI returned by `SearchObject`. |
|
|
537
560
|
| `GetFunctionGroup` | Read function-group source. |
|
|
538
|
-
| `
|
|
561
|
+
| `GetFunction`, `GetClass`, `GetClassComponents`, `GetClassInclude`, `GetProgram`, `GetInclude`, `GetInterface` | Read supported object-specific source or metadata views. |
|
|
562
|
+
| `ListDependencies` | List dependencies for impact and transport-readiness planning. |
|
|
539
563
|
| `GetInactiveObjects` | List objects changed by the current user but not yet activated. |
|
|
540
564
|
|
|
541
565
|
### ποΈ Read SAP tables and metadata
|
|
@@ -559,14 +583,13 @@ SAP authorizations and the destination's available APIs still apply.
|
|
|
559
583
|
| --- | --- |
|
|
560
584
|
| `WriteSource` | Create or update supported ABAP source objects; detects create versus update in upsert mode. |
|
|
561
585
|
| `EditSource` | Replace a specific source fragment; syntax checking is enabled by default. |
|
|
562
|
-
| `
|
|
563
|
-
| `
|
|
586
|
+
| `PrepareABAPChangeSet` | Stage reviewed full-source diffs for up to 12 `WriteSource` changes. |
|
|
587
|
+
| `ApplyABAPChangeSet` | Re-read each staged object and apply reviewed writes when the source still matches. |
|
|
564
588
|
| `SyntaxCheck` | Ask SAP to syntax-check source before saving or activation. |
|
|
565
589
|
| `Activate` | Activate one named ABAP object. |
|
|
566
|
-
| `ActivatePackage` | Activate inactive objects in dependency order. If package is omitted, it can activate all inactive objects for the current user. |
|
|
567
590
|
| `ActivateMultiple` | Activate related objects together while resolving mutual dependencies, such as an include and its main program. |
|
|
568
591
|
|
|
569
|
-
The write
|
|
592
|
+
The write and activation tools change SAP state. Confirm the target, package, and transport before using them. `EditSource` performs a focused replacement; its default syntax check prevents saving when syntax errors are reported.
|
|
570
593
|
|
|
571
594
|
### β
Test and inspect the system
|
|
572
595
|
|
|
@@ -588,8 +611,9 @@ The write, create, and activation tools change SAP state. Confirm the target, pa
|
|
|
588
611
|
| `GetTransport` | Read a transport request's details, objects, and tasks. |
|
|
589
612
|
| `GetTransportInfo` | Find eligible transports and lock status for an ABAP object or package. |
|
|
590
613
|
| `CreateTransport` | Create a transport request. |
|
|
591
|
-
| `
|
|
592
|
-
|
|
614
|
+
| `CheckTransportReadiness` | Collect a whitelisted bundle of transport, dependency, inactive-object, ABAP Unit, and ATC evidence. |
|
|
615
|
+
|
|
616
|
+
Transport release and deletion are not exposed by the curated proxy surface; release or delete transports outside this add-on after review.
|
|
593
617
|
|
|
594
618
|
The proxy starts VSP with `--enable-transports` and omits `--transport-read-only`. Generated MCP entries set `SAP_ALLOW_TRANSPORTABLE_EDITS=true` so source edits in transportable packages are permitted. VSP safety checks and SAP authorizations still apply.
|
|
595
619
|
|
|
@@ -602,9 +626,9 @@ The proxy starts VSP with `--enable-transports` and omits `--transport-read-only
|
|
|
602
626
|
- Filter by program, user, object, subobject, from, and to.
|
|
603
627
|
- `max_results` defaults to 100. Date-only `to` values include the full day.
|
|
604
628
|
- `messages: true` adds BALDAT details and T100 message text; otherwise, the tool returns log headers only.
|
|
605
|
-
- The proxy maps this convenience tool to the single `SAP(action="analyze", type="application_log")` operation. The general-purpose `SAP` router is
|
|
629
|
+
- The proxy maps this convenience tool to the single upstream `SAP(action="analyze", type="application_log")` operation. The general-purpose `SAP` router itself is hidden from direct calls.
|
|
606
630
|
|
|
607
|
-
The proxy
|
|
631
|
+
The proxy intentionally does not expose the full child VSP process. Object deletion, broad trace surfaces, arbitrary SAP router/RFC calls, debugger helper/delete-breakpoint routes, and transport release/delete tools are hidden from direct MCP calls. The curated debugger tools (`SetBreakpoint`, `GetBreakpoints`, `DebuggerAttach`, `DebuggerGetStack`, `DebuggerGetVariables`, `DebuggerStep`, and `DebuggerDetach`) remain available when VSP and the target system expose them. Direct VSP invocation without `H2O_URL` retains the VSP binary's own tool surface.
|
|
608
632
|
|
|
609
633
|
### π Ready to put the tools to work from chat?
|
|
610
634
|
|
|
@@ -700,7 +724,7 @@ The response contains a `tools` array. A `RunQuery` entry resembles this excerpt
|
|
|
700
724
|
| --- | --- |
|
|
701
725
|
| `H2O_URL` | BAS endpoint used to discover destinations. Required for BAS discovery. |
|
|
702
726
|
| `SAP_AI_DEV_TOOLKIT_DESTINATION` | Comma-separated destination allowlist for normal runtime discovery. Setup clears this temporarily so it can display all eligible systems. |
|
|
703
|
-
| `SAP_AI_DEV_TOOLKIT_MODE` | VSP child mode (`expert` by default; `focused` omits `ActivateMultiple`, `GetUserTransports`, and `GetTransportInfo`). The proxy exposes its curated
|
|
727
|
+
| `SAP_AI_DEV_TOOLKIT_MODE` | VSP child mode (`expert` by default; `focused` omits `ActivateMultiple`, `GetUserTransports`, and `GetTransportInfo`). The proxy exposes its curated tool subset and local workflow tools, including `LintABAP`. |
|
|
704
728
|
| `SAP_ALLOW_TRANSPORTABLE_EDITS` | Generated MCP entries set this to `true` to permit source edits in transportable packages; VSP safety checks and SAP authorizations still apply. |
|
|
705
729
|
| `SAP_AI_DEV_TOOLKIT_DISABLE_BAS_RELAY=true` | Disable the built-in BAS destination relay. By default the add-on self-heals `.dest` destinations through a local relay that keeps all access destination-based while handling ADT CSRF fetch/retry behavior before VSP calls SAP. |
|
|
706
730
|
| `SAP_AI_DEV_TOOLKIT_HTTP_PROXY` | Egress proxy for relay and discovery traffic (falls back to `HTTP_PROXY`/`http_proxy`; unset means the default BAS proxy for `.dest` hosts, empty means direct except for OnPremise credential overrides, which require a BAS proxy tunnel). |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sap-ai-dev-toolkit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "SAP AI development toolkit for BAS, ABAP, RAP, CAP, Fiori, UI5, and MCP",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "SAP AI development toolkit for BAS, ABAP, RAP, CAP, HANA Cloud, Fiori, UI5, and MCP",
|
|
5
5
|
"author": "Gurkan Yilmaz",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -9,16 +9,21 @@
|
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@abaplint/core": "2.119.0",
|
|
12
|
+
"@modelcontextprotocol/sdk": "1.30.1",
|
|
13
|
+
"@sap/hana-client": "2.30.27",
|
|
12
14
|
"terminal-kit": "^3.1.4",
|
|
13
|
-
"undici": "^6.21.3"
|
|
15
|
+
"undici": "^6.21.3",
|
|
16
|
+
"zod": "4.6.5"
|
|
14
17
|
},
|
|
15
18
|
"bin": {
|
|
16
|
-
"sap-ai-dev": "src/launcher.mjs"
|
|
19
|
+
"sap-ai-dev": "src/launcher.mjs",
|
|
20
|
+
"sap-ai-hana": "src/hana-inspector.mjs"
|
|
17
21
|
},
|
|
18
22
|
"scripts": {
|
|
19
23
|
"postinstall": "node scripts/postinstall.mjs",
|
|
20
24
|
"install:local": "node scripts/install-local.mjs",
|
|
21
25
|
"test": "node --test test/*.test.mjs",
|
|
26
|
+
"test:live:s4h": "SAP_AI_DEV_LIVE_S4H=1 node --test test/live-s4h.test.mjs",
|
|
22
27
|
"test:visibility": "node --test test/setup.test.mjs test/terminal-ui.test.mjs",
|
|
23
28
|
"test:mutation": "node scripts/mutation-terminal-ui.mjs",
|
|
24
29
|
"publish:npm": "node scripts/publish-npm.mjs",
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -76,9 +76,9 @@ async function runCopilotAssetInstall() {
|
|
|
76
76
|
await announce([
|
|
77
77
|
'Optional Copilot setup is waiting for your choice.',
|
|
78
78
|
'',
|
|
79
|
-
'Press Enter to install the bundled agents and
|
|
79
|
+
'Press Enter to install the bundled agents and all skills in the path shown below; type n then press Enter to skip.',
|
|
80
80
|
'',
|
|
81
|
-
`${colorText('β
Press Enter', 'green', true)} to install the bundled agents and
|
|
81
|
+
`${colorText('β
Press Enter', 'green', true)} to install the bundled agents and all skills.`,
|
|
82
82
|
`${colorText('βοΈ Type n then Enter', 'yellow', true)} to skip this optional step.`,
|
|
83
83
|
'',
|
|
84
84
|
`${colorText('π Target folder:', 'cyan', true)}`,
|
|
@@ -88,7 +88,7 @@ async function runCopilotAssetInstall() {
|
|
|
88
88
|
const prompt = createInterface({ input: terminal.input, output: terminal.output });
|
|
89
89
|
let answer;
|
|
90
90
|
try {
|
|
91
|
-
answer = await prompt.question(`${colorText('π€ Install the bundled agents and
|
|
91
|
+
answer = await prompt.question(`${colorText('π€ Install the bundled agents and all skills?', 'magenta', terminal.output)} ${colorText('[Y/n]', 'yellow', terminal.output)} `);
|
|
92
92
|
} finally {
|
|
93
93
|
prompt.close();
|
|
94
94
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const EXPLICIT_CONNECTION_KEYS = [
|
|
2
|
+
'HANA_RO_HOST',
|
|
3
|
+
'HANA_RO_PORT',
|
|
4
|
+
'HANA_RO_USER',
|
|
5
|
+
'HANA_RO_PASSWORD',
|
|
6
|
+
'HANA_RO_SCHEMA'
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const DEFAULT_SERVICE_TYPES = new Set(['hana', 'hana-cloud', 'hana-cloud-db', 'hanacloud', 'sap-hana-cloud']);
|
|
10
|
+
|
|
11
|
+
function fail(message) {
|
|
12
|
+
throw new Error(message);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeServiceType(value) {
|
|
16
|
+
return String(value || '').trim().toLowerCase().replaceAll('_', '-');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function requiredText(value, label) {
|
|
20
|
+
if (typeof value !== 'string' || !value.trim()) fail(`HANA read-only connection is missing ${label}.`);
|
|
21
|
+
return value.trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validateHost(value) {
|
|
25
|
+
const host = requiredText(value, 'host');
|
|
26
|
+
if (/[\s/@?#\\]/.test(host) || host.includes('://') || host.includes(':')) {
|
|
27
|
+
fail('HANA_RO_HOST must contain only a hostname; provide the port separately.');
|
|
28
|
+
}
|
|
29
|
+
return host;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function validatePort(value) {
|
|
33
|
+
const portText = requiredText(String(value ?? ''), 'port');
|
|
34
|
+
if (!/^\d{1,5}$/.test(portText)) fail('HANA_RO_PORT must be an integer between 1 and 65535.');
|
|
35
|
+
const port = Number(portText);
|
|
36
|
+
if (port < 1 || port > 65535) fail('HANA_RO_PORT must be an integer between 1 and 65535.');
|
|
37
|
+
return port;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function validateSchema(value) {
|
|
41
|
+
const schema = requiredText(value, 'schema');
|
|
42
|
+
if (schema.length > 127 || /[\u0000-\u001f\u007f]/.test(schema)) {
|
|
43
|
+
fail('The configured HANA schema name is invalid.');
|
|
44
|
+
}
|
|
45
|
+
return schema;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hasExplicitConnection(env) {
|
|
49
|
+
return EXPLICIT_CONNECTION_KEYS.some(key => env[key] !== undefined && env[key] !== '');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function explicitConfig(env) {
|
|
53
|
+
const host = validateHost(env.HANA_RO_HOST);
|
|
54
|
+
const port = validatePort(env.HANA_RO_PORT);
|
|
55
|
+
const user = requiredText(env.HANA_RO_USER, 'read-only user');
|
|
56
|
+
const password = requiredText(env.HANA_RO_PASSWORD, 'read-only password');
|
|
57
|
+
const schema = validateSchema(env.HANA_RO_SCHEMA);
|
|
58
|
+
return {
|
|
59
|
+
source: 'environment',
|
|
60
|
+
bindingName: undefined,
|
|
61
|
+
serviceName: undefined,
|
|
62
|
+
host,
|
|
63
|
+
port,
|
|
64
|
+
user,
|
|
65
|
+
password,
|
|
66
|
+
schema,
|
|
67
|
+
trustStore: optionalTrustStore(env.HANA_RO_TRUST_STORE)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function optionalTrustStore(value) {
|
|
72
|
+
if (value === undefined || value === '') return undefined;
|
|
73
|
+
const trustStore = requiredText(value, 'TLS trust-store path');
|
|
74
|
+
if (/[\u0000-\u001f\u007f]/.test(trustStore)) fail('The configured HANA TLS trust-store path is invalid.');
|
|
75
|
+
return trustStore;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseVcapServices(raw) {
|
|
79
|
+
if (!raw) fail('Set HANA_RO_* read-only credentials or bind a selected HANA service in VCAP_SERVICES.');
|
|
80
|
+
let services;
|
|
81
|
+
try {
|
|
82
|
+
services = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
83
|
+
} catch {
|
|
84
|
+
fail('VCAP_SERVICES is not valid JSON.');
|
|
85
|
+
}
|
|
86
|
+
if (!services || typeof services !== 'object' || Array.isArray(services)) {
|
|
87
|
+
fail('VCAP_SERVICES must contain a JSON object.');
|
|
88
|
+
}
|
|
89
|
+
return services;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function vcapCandidates(env) {
|
|
93
|
+
const services = parseVcapServices(env.VCAP_SERVICES);
|
|
94
|
+
const selectedType = env.HANA_RO_VCAP_SERVICE?.trim();
|
|
95
|
+
const bindingName = env.HANA_RO_BINDING?.trim();
|
|
96
|
+
const entries = Object.entries(services).filter(([serviceType]) => {
|
|
97
|
+
return selectedType
|
|
98
|
+
? serviceType === selectedType
|
|
99
|
+
: DEFAULT_SERVICE_TYPES.has(normalizeServiceType(serviceType));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (selectedType && !entries.length) fail('The selected HANA VCAP service type was not found.');
|
|
103
|
+
|
|
104
|
+
let candidates = entries.flatMap(([serviceName, instances]) => {
|
|
105
|
+
if (!Array.isArray(instances)) return [];
|
|
106
|
+
return instances
|
|
107
|
+
.filter(instance => instance && typeof instance === 'object' && !Array.isArray(instance))
|
|
108
|
+
.map(instance => ({ serviceName, instance }));
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (bindingName) {
|
|
112
|
+
candidates = candidates.filter(({ instance }) => {
|
|
113
|
+
return instance.name === bindingName || instance.binding_name === bindingName;
|
|
114
|
+
});
|
|
115
|
+
if (!candidates.length) fail('The selected HANA binding was not found in VCAP_SERVICES.');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (candidates.length !== 1) {
|
|
119
|
+
fail(candidates.length === 0
|
|
120
|
+
? 'No HANA VCAP binding was found; set HANA_RO_VCAP_SERVICE or explicit HANA_RO_* credentials.'
|
|
121
|
+
: 'Multiple HANA VCAP bindings were found; set HANA_RO_BINDING to select exactly one.');
|
|
122
|
+
}
|
|
123
|
+
return candidates[0];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function vcapConfig(env) {
|
|
127
|
+
const { serviceName, instance } = vcapCandidates(env);
|
|
128
|
+
const credentials = instance.credentials;
|
|
129
|
+
if (!credentials || typeof credentials !== 'object' || Array.isArray(credentials)) {
|
|
130
|
+
fail('The selected HANA binding has no usable credentials object.');
|
|
131
|
+
}
|
|
132
|
+
if ((!credentials.user || !credentials.password) && (credentials.hdi_user || credentials.hdi_password)) {
|
|
133
|
+
fail('The selected HANA binding only exposes HDI deployment credentials; configure a separate read-only binding.');
|
|
134
|
+
}
|
|
135
|
+
if (credentials.user && credentials.hdi_user && credentials.user === credentials.hdi_user) {
|
|
136
|
+
fail('The selected HANA binding aliases its read-only and HDI deployment users; configure separate identities.');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
source: 'vcap-services',
|
|
141
|
+
bindingName: typeof instance.binding_name === 'string' ? instance.binding_name : instance.name,
|
|
142
|
+
serviceName,
|
|
143
|
+
host: validateHost(credentials.host),
|
|
144
|
+
port: validatePort(credentials.port),
|
|
145
|
+
user: requiredText(credentials.user, 'read-only user'),
|
|
146
|
+
password: requiredText(credentials.password, 'read-only password'),
|
|
147
|
+
schema: validateSchema(credentials.schema),
|
|
148
|
+
trustStore: optionalTrustStore(env.HANA_RO_TRUST_STORE)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Resolve only a read-only HANA identity; HDI deployment credentials are never a fallback. */
|
|
153
|
+
export function resolveHanaReadOnlyConfig(env = process.env) {
|
|
154
|
+
if (!env || typeof env !== 'object') fail('HANA environment configuration is unavailable.');
|
|
155
|
+
const hasExplicit = hasExplicitConnection(env);
|
|
156
|
+
if (hasExplicit) {
|
|
157
|
+
const missing = EXPLICIT_CONNECTION_KEYS.filter(key => typeof env[key] !== 'string' || !env[key].trim());
|
|
158
|
+
if (missing.length) fail(`Explicit HANA read-only configuration is incomplete: ${missing.join(', ')}.`);
|
|
159
|
+
return Object.freeze(explicitConfig(env));
|
|
160
|
+
}
|
|
161
|
+
return Object.freeze(vcapConfig(env));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function quoteHanaIdentifier(value) {
|
|
165
|
+
const identifier = requiredText(value, 'identifier');
|
|
166
|
+
if (identifier.length > 127 || /[\u0000-\u001f\u007f]/.test(identifier)) {
|
|
167
|
+
fail('The HANA identifier is invalid.');
|
|
168
|
+
}
|
|
169
|
+
return `"${identifier.replaceAll('"', '""')}"`;
|
|
170
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const QUERY_TIMEOUT_MS = 15_000;
|
|
5
|
+
|
|
6
|
+
function hanaFailure(operation, error) {
|
|
7
|
+
const code = typeof error?.code === 'string' || typeof error?.code === 'number'
|
|
8
|
+
? String(error.code).replace(/[^A-Za-z0-9_-]/g, '').slice(0, 24)
|
|
9
|
+
: '';
|
|
10
|
+
const failure = new Error(`${operation} failed${code ? ` (HANA error ${code})` : ''}; verify the selected read-only binding, schema, and privileges.`);
|
|
11
|
+
failure.name = 'HanaDatabaseError';
|
|
12
|
+
return failure;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function callbackPromise(invoke, operation) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
let settled = false;
|
|
18
|
+
const done = (error, result) => {
|
|
19
|
+
if (settled) return;
|
|
20
|
+
settled = true;
|
|
21
|
+
if (error) reject(hanaFailure(operation, error));
|
|
22
|
+
else resolve(result);
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
invoke(done);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
done(error);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function executeWithTimeout(connection, sql, values, timeoutMs, onTimeout) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
let settled = false;
|
|
35
|
+
const timer = setTimeout(() => {
|
|
36
|
+
if (settled) return;
|
|
37
|
+
settled = true;
|
|
38
|
+
onTimeout();
|
|
39
|
+
const error = new Error(`HANA read exceeded the ${timeoutMs / 1000} second time limit; the connection was retired. Narrow the request and retry.`);
|
|
40
|
+
error.name = 'HanaQueryTimeoutError';
|
|
41
|
+
reject(error);
|
|
42
|
+
}, timeoutMs);
|
|
43
|
+
const done = (error, result) => {
|
|
44
|
+
if (settled) return;
|
|
45
|
+
settled = true;
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
if (error) reject(hanaFailure('HANA read', error));
|
|
48
|
+
else resolve(result);
|
|
49
|
+
};
|
|
50
|
+
try {
|
|
51
|
+
connection.exec(sql, values, {}, done);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
done(error);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function hanaConnectionOptions(config) {
|
|
59
|
+
return {
|
|
60
|
+
host: config.host,
|
|
61
|
+
port: config.port,
|
|
62
|
+
uid: config.user,
|
|
63
|
+
pwd: config.password,
|
|
64
|
+
currentSchema: config.schema,
|
|
65
|
+
encrypt: true,
|
|
66
|
+
sslValidateCertificate: true,
|
|
67
|
+
connectTimeout: 10_000,
|
|
68
|
+
...(config.trustStore ? { sslTrustStore: config.trustStore } : {})
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class HanaDatabase {
|
|
73
|
+
constructor(connection, config, { queryTimeoutMs = QUERY_TIMEOUT_MS } = {}) {
|
|
74
|
+
this.connection = connection;
|
|
75
|
+
this.config = config;
|
|
76
|
+
this.queryTimeoutMs = queryTimeoutMs;
|
|
77
|
+
this.connected = false;
|
|
78
|
+
this.closed = false;
|
|
79
|
+
this.unusable = false;
|
|
80
|
+
this.queue = Promise.resolve();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async connect() {
|
|
84
|
+
if (this.closed) throw new Error('HANA connection is closed.');
|
|
85
|
+
if (this.connected) return this;
|
|
86
|
+
await callbackPromise(
|
|
87
|
+
callback => this.connection.connect(hanaConnectionOptions(this.config), callback),
|
|
88
|
+
'HANA connection'
|
|
89
|
+
);
|
|
90
|
+
this.connected = true;
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async query(sql, values = []) {
|
|
95
|
+
if (!this.connected || this.closed || this.unusable) throw new Error('HANA connection is not available.');
|
|
96
|
+
if (typeof sql !== 'string' || !sql.trim() || !Array.isArray(values)) {
|
|
97
|
+
throw new Error('Invalid internal HANA query request.');
|
|
98
|
+
}
|
|
99
|
+
const result = this.queue.then(() => executeWithTimeout(
|
|
100
|
+
this.connection,
|
|
101
|
+
sql,
|
|
102
|
+
values,
|
|
103
|
+
this.queryTimeoutMs,
|
|
104
|
+
() => {
|
|
105
|
+
this.unusable = true;
|
|
106
|
+
try { this.connection.cancel?.(); } catch {}
|
|
107
|
+
}
|
|
108
|
+
));
|
|
109
|
+
this.queue = result.then(() => undefined, () => undefined);
|
|
110
|
+
try {
|
|
111
|
+
return await result;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (this.unusable) await this.close();
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async close() {
|
|
119
|
+
if (this.closed) return;
|
|
120
|
+
this.closed = true;
|
|
121
|
+
await this.queue.catch(() => {});
|
|
122
|
+
if (!this.connected) return;
|
|
123
|
+
this.connected = false;
|
|
124
|
+
await callbackPromise(callback => this.connection.disconnect(callback), 'HANA disconnect').catch(() => {});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function connectReadOnlyHana(config, { driver } = {}) {
|
|
129
|
+
let selectedDriver = driver;
|
|
130
|
+
if (!selectedDriver) {
|
|
131
|
+
try {
|
|
132
|
+
selectedDriver = require('@sap/hana-client');
|
|
133
|
+
} catch {
|
|
134
|
+
throw new Error('The SAP HANA Node.js driver could not be loaded for this platform.');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (typeof selectedDriver?.createConnection !== 'function') {
|
|
138
|
+
throw new Error('The configured SAP HANA Node.js driver is unavailable.');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const connection = selectedDriver.createConnection();
|
|
142
|
+
const database = new HanaDatabase(connection, config);
|
|
143
|
+
try {
|
|
144
|
+
await database.connect();
|
|
145
|
+
return database;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
await database.close();
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|