sap-ai-dev-toolkit 0.9.9912 → 0.9.9913
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 +1 -1
- package/.github/skills/abap-development/SKILL.md +1 -1
- package/.github/skills/cds-development/SKILL.md +1 -1
- package/README.md +2 -0
- 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 +4 -2
- package/package.json +1 -1
- package/patches/vsp-bas-proxy-auth.patch +88 -7
- package/src/engineering-tools.mjs +105 -0
- package/tools.md +5 -3
|
@@ -24,7 +24,7 @@ You are an ABAP development agent for SAP Business Application Studio (BAS). Fol
|
|
|
24
24
|
- **Clean Core:** `PlanABAPCloudMigration` to batch-check release-state evidence for relevant ADT object URIs. Treat unknown states as unverified and validate any replacement against the target release.
|
|
25
25
|
Tool modes and system capabilities vary. Do not infer tool availability from this map or static documentation.
|
|
26
26
|
|
|
27
|
-
Use `GetStructure`, `GetTypeInfo`, and `GetMessages` for read-only DDIC shape, ABAP type, and SE91 message-text inspection. Use `CreatePackage` only when a new package is required and explicitly approved; transportable packages need a known software component and explicit transport. For transparent DDIC persistence, use `CreateTable`; it creates, writes generated DDL, and activates in one call. Neither
|
|
27
|
+
Use `GetStructure`, `GetTypeInfo`, and `GetMessages` for read-only DDIC shape, ABAP type, and SE91 message-text inspection. Use `CreatePackage` only when a new package is required and explicitly approved; transportable packages need a known software component and explicit transport. For transparent DDIC persistence, use `CreateTable`; it creates, writes generated DDL, and activates in one call. For existing Z/Y tables, use `UpdateTable` with reviewed current DDL and exact confirmation. Use `DeleteEmptyDDLS` only for a blank Z/Y DDLS shell after verifying it is empty; generic `DeleteObject` remains hidden. Neither creator creates authorization objects.
|
|
28
28
|
|
|
29
29
|
4. **Use test-driven development.** ABAP code changes require a real test plan. Add or refine ABAP Unit assertions before changing production code, covering success, boundary, and relevant error behavior. Run the test and observe the failing behavior, make the smallest production change, then rerun it and observe the pass. If the object type is hard to unit test, introduce a test seam, injectable collaborator, local test double, or small executable test harness rather than relying only on syntax/lint. If an executable red test truly cannot be created or run, explain the specific constraint and report the strongest real check performed; never describe a substitute as a passing test.
|
|
30
30
|
5. **Author locally first; check and lint before sending to SAP.** Create or change sources in the workspace as plain source files named `object.type.extension` (for example `zcl_report.clas.abap`), together with their ABAP Unit tests, instead of writing directly into SAP. Author only the plain source files; do not generate XML or other object-metadata sidecar files. Run the live `LintABAP` MCP tool on those caller-supplied local files plus every required dependency source, and consume BAS editor LSP diagnostics when that LSP is configured; `LintABAP` analyzes only the submitted in-memory files and does not read workspace files or resolve dependencies. Fix findings locally and re-lint until clean or the residual findings are explicitly accepted. Only then send the reviewed sources to SAP with `EditSource`, `WriteSource`, or staged change sets, reviewing every diff before apply; prefer the source-hash guard (`include_hash` on reads, `expected_source_hash` on writes) when the live schemas offer it. Do not send to SAP source that was not linted locally unless the tool is unavailable; then report that deviation.
|
|
@@ -11,7 +11,7 @@ description: Implement or change ABAP programs, classes, interfaces, function gr
|
|
|
11
11
|
|
|
12
12
|
Query the active MCP server's live `tools/list` once; use task-relevant tools with their returned schemas. Locate objects and usages with `SearchObject`, `GrepObjects`, and `GrepPackages`; read with `GetSource` and `GetContext`; trace callers or dependencies with `FindDefinition` and `FindReferences`; compare workspace and SAP versions with `CompareSource`. Use `EditSource` for a localized change and `WriteSource` for a larger rewrite. Format with `PrettyPrint`; lint caller-supplied source with `LintABAP`; validate with `SyntaxCheck` and `RunUnitTests`, plus `RunATCCheck` when exposed and relevant. For Clean Core requirements, use `GetAPIReleaseState` on each relevant dependency. Tool names shown in PascalCase (such as `GetSource` or `LintABAP`) are logical names; the live MCP surface exposes them as lowercase snake_case. A server scoped to a single destination (the normal case for generated `mcp.json` entries) exposes them unprefixed, so `GetSource` appears as `get_source` and `LintABAP` as `lint_abap`; when several destinations share one server, each name is destination-prefixed with its slug for disambiguation (for example `cfd_run_query`). Always call the exact names returned by `tools/list`.
|
|
13
13
|
|
|
14
|
-
Use `GetStructure`, `GetTypeInfo`, and `GetMessages` for read-only DDIC shape, ABAP type, and SE91 text inspection. If a new package is required, use live `CreatePackage` only with explicit approval; a transportable package requires its software component and transport. For transparent DDIC persistence, use `CreateTable`, not `CreateObject`; it creates the table, writes generated DDL, and activates in one SAP call. Neither creator supports authorization objects; transport release/deletion is unavailable through this addon.
|
|
14
|
+
Use `GetStructure`, `GetTypeInfo`, and `GetMessages` for read-only DDIC shape, ABAP type, and SE91 text inspection. If a new package is required, use live `CreatePackage` only with explicit approval; a transportable package requires its software component and transport. For transparent DDIC persistence, use `CreateTable`, not `CreateObject`; it creates the table, writes generated DDL, and activates in one SAP call. For an existing Z/Y table, `UpdateTable` compares the reviewed DDL and uses a hash-guarded locked update; it requires exact confirmation. `DeleteEmptyDDLS` only deletes blank Z/Y DDLS shells after checking source and exact confirmation; generic `DeleteObject` remains hidden. Neither creator supports authorization objects; transport release/deletion is unavailable through this addon.
|
|
15
15
|
|
|
16
16
|
## Release compatibility and Clean ABAP naming
|
|
17
17
|
|
|
@@ -11,7 +11,7 @@ description: Implement, change, or analyze ABAP CDS data definitions and their d
|
|
|
11
11
|
|
|
12
12
|
Query the active MCP server's live `tools/list` once; use task-relevant tools with their returned schemas. Locate views and usages with `SearchObject` and `GrepObjects`; read DDLS with `GetSource`; compare workspace and SAP versions with `CompareSource`; use `GetCDSDependencies` for upstream dependencies, `GetCDSImpactAnalysis` for consumers, and `GetCDSElementInfo` for element metadata. Trace references with `FindReferences`; edit via `EditSource` / `WriteSource`. Validate with `SyntaxCheck`, `RunATCCheck`, activation via `Activate` / `ActivateMultiple`, and read-only runtime queries via `RunQuery` or `GetTableContents` when exposed. Tool names shown in PascalCase (such as `GetSource` or `LintABAP`) are logical names; the live MCP surface exposes them as lowercase snake_case. A server scoped to a single destination (the normal case for generated `mcp.json` entries) exposes them unprefixed, so `GetSource` appears as `get_source` and `LintABAP` as `lint_abap`; when several destinations share one server, each name is destination-prefixed with its slug for disambiguation (for example `cfd_run_query`). Always call the exact names returned by `tools/list`.
|
|
13
13
|
|
|
14
|
-
Use `GetStructure` and `GetTypeInfo` to inspect DDIC-backed structure and type metadata when resolving CDS field dependencies. If a new package is needed, `CreatePackage` is state-changing: require explicit approval, and provide a software component plus transport for a transportable package. For a new transparent DDIC table backing a CDS entity, use `CreateTable`; it is not for CDS views, writes generated DDL, and activates in one SAP call. Authorization-object creation and transport release/deletion are unavailable through this addon.
|
|
14
|
+
Use `GetStructure` and `GetTypeInfo` to inspect DDIC-backed structure and type metadata when resolving CDS field dependencies. If a new package is needed, `CreatePackage` is state-changing: require explicit approval, and provide a software component plus transport for a transportable package. For a new transparent DDIC table backing a CDS entity, use `CreateTable`; it is not for CDS views, writes generated DDL, and activates in one SAP call. `UpdateTable` is limited to existing Z/Y DDIC tables; it is not a CDS-view editor. `DeleteEmptyDDLS` only removes a blank Z/Y DDLS shell after exact confirmation. Authorization-object creation and transport release/deletion are unavailable through this addon.
|
|
15
15
|
|
|
16
16
|
## Language level and naming
|
|
17
17
|
|
package/README.md
CHANGED
|
@@ -608,6 +608,8 @@ These local tools are namespaced by destination. Change-set tools appear when th
|
|
|
608
608
|
| --- | --- |
|
|
609
609
|
| `PrepareABAPChangeSet` | Stage up to 12 full-source `WriteSource` changes and return line diffs plus SHA-256 fingerprints. Proposals stay in MCP process memory. |
|
|
610
610
|
| `ApplyABAPChangeSet` | Re-read each object and write only when its source still matches the reviewed version. It stops on conflicts or errors and reports partial application; it does not roll back SAP writes. |
|
|
611
|
+
| `UpdateTable` | Compare the current Z/Y table DDL with reviewed source, then update and activate through VSP's source-hash check under lock. Requires exact confirmation. |
|
|
612
|
+
| `DeleteEmptyDDLS` | Delete only a blank Z/Y DDLS shell after checking its source and requiring exact confirmation; generic `DeleteObject` remains hidden. |
|
|
611
613
|
| `CheckTransportReadiness` | Run a caller-selected bundle of transport, dependency, inactive-object, ABAP Unit, and ATC checks. Completed calls may still contain findings. |
|
|
612
614
|
| `PlanABAPCloudMigration` | Batch `GetAPIReleaseState` checks for ADT object URIs and prioritize recognized unreleased APIs ahead of unknown and released states. SAP evidence stays attached. |
|
|
613
615
|
| `GenerateRAPRegressionSuite` | Read `$metadata` and return reusable JSON with a metadata check and one `$top=1` GET for each entity set. |
|
package/dist/checksums.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
5a8adb501a30bb4f3c3a1f2c16aee0f60de0be9a226cafbc07191ba955c9269f vsp-linux-x64
|
|
2
|
+
d7b96b63ec812949e2ab2e223b1fbac5f5a0cd1c6e068e3ddd117d3548c968a2 vsp-linux-arm64
|
|
3
|
+
7328b9fd0ceb60287b53e9d6425e7111c87cdefcec1d412544eb6b1e70f4249c vsp-darwin-x64
|
|
4
|
+
fb90f7a1b2bdca5fcc7bb8ba5623d6fb1dcbb88de02bba5686acad503f29f84f vsp-darwin-arm64
|
|
5
|
+
bd33b0a45640700bd26372355b73169632b21fb4c07df33b8cc0b5d7ff5b76e0 vsp-win32-x64.exe
|
package/dist/vsp-darwin-arm64
CHANGED
|
Binary file
|
package/dist/vsp-darwin-x64
CHANGED
|
Binary file
|
package/dist/vsp-linux-arm64
CHANGED
|
Binary file
|
package/dist/vsp-linux-x64
CHANGED
|
Binary file
|
package/dist/vsp-win32-x64.exe
CHANGED
|
Binary file
|
package/inventory.md
CHANGED
|
@@ -8,12 +8,12 @@ This inventory reflects the current SAP AI Dev Toolkit proxy behavior in `src/mc
|
|
|
8
8
|
| --- | ---: | --- |
|
|
9
9
|
| Upstream tools | Up to 64 | Curated from the child tool list and exposed in lowercase snake_case, unprefixed (`get_source`) on single-destination servers; a destination slug prefix (`demo-abap_get_source`) appears only when one server fronts multiple destinations. |
|
|
10
10
|
| Local lint tool | 1 per destination | `LintABAP` analyzes caller-supplied ABAP source in memory; it is exposed publicly as `lint_abap`. |
|
|
11
|
-
| Workflow tools | Up to
|
|
11
|
+
| Workflow tools | Up to 8 per destination | Review/apply change sets, transport evidence, Clean Core release assessment, RAP regression suites, guarded table updates, and empty-DDLS cleanup. Some require upstream capabilities. |
|
|
12
12
|
| Convenience mapping | Dynamic | `GetApplicationLog` maps to the upstream `SAP(action="analyze", type="application_log")` operation when the SAP router is registered. |
|
|
13
13
|
| Intentionally filtered tools | Dynamic | Destructive, broad-router, trace, and unsupported operations are hidden from direct calls. |
|
|
14
14
|
| Mode-dependent | Dynamic | Focused/expert mode and backend capabilities determine which tools are registered. |
|
|
15
15
|
|
|
16
|
-
The maximum fixture surface is
|
|
16
|
+
The maximum fixture surface is 74 destination-scoped tools per destination when expert-mode capabilities and all local workflows are available, plus one workspace-scoped `LintABAPProject` tool per MCP server. Multiple selected destinations create separate MCP servers; this add-on cannot inspect VS Code's aggregate tool budget or per-chat tool binding.
|
|
17
17
|
|
|
18
18
|
## Known upstream tools
|
|
19
19
|
|
|
@@ -168,6 +168,8 @@ These logical names are exposed through MCP in lowercase snake_case (`lint_abap`
|
|
|
168
168
|
- `PlanABAPCloudMigration` — batch-check API release state for supplied ADT object URIs and prioritize recognized unreleased APIs.
|
|
169
169
|
- `GenerateRAPRegressionSuite` — read service metadata and emit reusable GET-only smoke cases for entity sets.
|
|
170
170
|
- `RunRAPRegressionSuite` — execute saved cases with status, content-type, and JSON-path assertions over the connected destination.
|
|
171
|
+
- `UpdateTable` — compare the current DDL with the reviewed source, then update and activate one Z/Y table using VSP's locked source-hash check.
|
|
172
|
+
- `DeleteEmptyDDLS` — delete only a blank Z/Y DDLS source after exact confirmation; generic `DeleteObject` remains hidden.
|
|
171
173
|
|
|
172
174
|
## Notes
|
|
173
175
|
|
package/package.json
CHANGED
|
@@ -148,6 +148,23 @@ index 5007935..3b1fdd8 100755
|
|
|
148
148
|
cookieFile, _ := cmd.Flags().GetString("cookie-file")
|
|
149
149
|
cookieString, _ := cmd.Flags().GetString("cookie-string")
|
|
150
150
|
|
|
151
|
+
diff --git a/internal/mcp/handlers_source.go b/internal/mcp/handlers_source.go
|
|
152
|
+
index d23089b..8e7e5ff 100644
|
|
153
|
+
--- a/internal/mcp/handlers_source.go
|
|
154
|
+
+++ b/internal/mcp/handlers_source.go
|
|
155
|
+
@@ -132,10 +132,10 @@ func (s *Server) registerGetSource() {
|
|
156
|
+
// registerWriteSource registers the unified WriteSource tool
|
|
157
|
+
func (s *Server) registerWriteSource() {
|
|
158
|
+
s.mcpServer.AddTool(mcp.NewTool("WriteSource",
|
|
159
|
+
- mcp.WithDescription("Unified tool for writing ABAP source code with automatic create/update detection. Supports PROG, CLAS, INTF, INCL, and RAP types (DDLS, BDEF, SRVD)."),
|
|
160
|
+
+ mcp.WithDescription("Unified tool for writing ABAP source code with automatic create/update detection. Supports PROG, CLAS, INTF, INCL, RAP types (DDLS, BDEF, SRVD), and updates to existing DDIC tables (TABL). New tables must be created with CreateTable."),
|
|
161
|
+
mcp.WithString("object_type",
|
|
162
|
+
mcp.Required(),
|
|
163
|
+
- mcp.Description("Object type: PROG (program), CLAS (class), INTF (interface), INCL (include), DDLS (CDS view), BDEF (behavior definition), SRVD (service definition)"),
|
|
164
|
+
+ mcp.Description("Object type: PROG (program), CLAS (class), INTF (interface), INCL (include), DDLS (CDS view), BDEF (behavior definition), SRVD (service definition), or TABL (existing DDIC table update only)"),
|
|
165
|
+
),
|
|
166
|
+
mcp.WithString("name",
|
|
167
|
+
mcp.Required(),
|
|
151
168
|
diff --git a/internal/mcp/server.go b/internal/mcp/server.go
|
|
152
169
|
index 9337857..11fc940 100644
|
|
153
170
|
--- a/internal/mcp/server.go
|
|
@@ -290,7 +307,7 @@ index 7740e56..b4c2faf 100755
|
|
|
290
307
|
+ }
|
|
291
308
|
+}
|
|
292
309
|
diff --git a/internal/mcp/tools_register.go b/internal/mcp/tools_register.go
|
|
293
|
-
index dd433d6..
|
|
310
|
+
index dd433d6..d9ea365 100644
|
|
294
311
|
--- a/internal/mcp/tools_register.go
|
|
295
312
|
+++ b/internal/mcp/tools_register.go
|
|
296
313
|
@@ -837,6 +837,19 @@ func (s *Server) registerDevTools(shouldRegister func(string) bool) {
|
|
@@ -313,6 +330,24 @@ index dd433d6..5553b7f 100644
|
|
|
313
330
|
),
|
|
314
331
|
), s.handleActivateMultiple)
|
|
315
332
|
}
|
|
333
|
+
@@ -1070,7 +1083,7 @@ func (s *Server) registerCRUDTools(shouldRegister func(string) bool) {
|
|
334
|
+
|
|
335
|
+
if shouldRegister("CreateTable") {
|
|
336
|
+
s.mcpServer.AddTool(mcp.NewTool("CreateTable",
|
|
337
|
+
- mcp.WithDescription("Create a DDIC transparent table from a simple JSON definition. Handles full workflow: create → set source → activate. Supports common ABAP types: CHAR, NUMC, INT4, DEC, STRING, TIMESTAMPL, UUID, etc."),
|
|
338
|
+
+ mcp.WithDescription("Create a DDIC transparent table from a simple JSON definition. Handles full workflow: create → set source → activate. Supports common ABAP types: CHAR, NUMC, INT4, DEC, STRING, TIMESTAMPL, UUID, etc. Client-dependent tables get one implicit CLIENT key; supplied CLIENT or MANDT fields are treated as that same key."),
|
|
339
|
+
mcp.WithString("name",
|
|
340
|
+
mcp.Required(),
|
|
341
|
+
mcp.Description("Table name (uppercase, max 30 chars, must start with Z/Y)"),
|
|
342
|
+
@@ -1084,7 +1097,7 @@ func (s *Server) registerCRUDTools(shouldRegister func(string) bool) {
|
|
343
|
+
),
|
|
344
|
+
mcp.WithString("fields",
|
|
345
|
+
mcp.Required(),
|
|
346
|
+
- mcp.Description("JSON array of fields: [{\"name\":\"ID\",\"type\":\"CHAR32\",\"key\":true},{\"name\":\"VALUE\",\"type\":\"STRING\"}]. Types: CHAR/CHARnn, NUMC/NUMCnn, INT4, DEC, STRING, TIMESTAMPL, UUID, DATS, TIMS, or data element name."),
|
|
347
|
+
+ mcp.Description("JSON array of user fields: [{\"name\":\"ID\",\"type\":\"CHAR32\",\"key\":true},{\"name\":\"VALUE\",\"type\":\"STRING\"}]. Do not include CLIENT or MANDT; the client key is added automatically. Types: CHAR/CHARnn, NUMC/NUMCnn, INT4, DEC, STRING, TIMESTAMPL, UUID, DATS, TIMS, or data element name."),
|
|
348
|
+
),
|
|
349
|
+
mcp.WithString("transport",
|
|
350
|
+
mcp.Description("Transport request number (optional for $TMP)"),
|
|
316
351
|
diff --git a/pkg/adt/browser_auth.go b/pkg/adt/browser_auth.go
|
|
317
352
|
index ea3c1a9..5bc7a01 100644
|
|
318
353
|
--- a/pkg/adt/browser_auth.go
|
|
@@ -436,7 +471,7 @@ index 0000000..1ed3c2b
|
|
|
436
471
|
+ }
|
|
437
472
|
+}
|
|
438
473
|
diff --git a/pkg/adt/crud.go b/pkg/adt/crud.go
|
|
439
|
-
index f412fb1..
|
|
474
|
+
index f412fb1..8021a5a 100755
|
|
440
475
|
--- a/pkg/adt/crud.go
|
|
441
476
|
+++ b/pkg/adt/crud.go
|
|
442
477
|
@@ -1250,7 +1250,12 @@ func (c *Client) publishUnpublishServiceBinding(ctx context.Context, action, ser
|
|
@@ -453,6 +488,23 @@ index f412fb1..15ac38a 100755
|
|
|
453
488
|
|
|
454
489
|
resp, err := c.transport.Request(ctx, path, &RequestOptions{
|
|
455
490
|
Method: http.MethodPost,
|
|
491
|
+
@@ -1427,11 +1432,15 @@ func generateTableDDL(opts CreateTableOptions) string {
|
|
492
|
+
sb.WriteString("@AbapCatalog.dataMaintenance : #ALLOWED\n")
|
|
493
|
+
sb.WriteString(fmt.Sprintf("define table %s {\n\n", strings.ToLower(opts.Name)))
|
|
494
|
+
|
|
495
|
+
- // Auto-add MANDT as first key field (standard SAP practice)
|
|
496
|
+
+ // The table's implicit client field is named CLIENT in DDL; callers may
|
|
497
|
+
+ // provide CLIENT or MANDT, but the schema must contain it only once.
|
|
498
|
+
sb.WriteString(" key client : abap.clnt not null;\n")
|
|
499
|
+
|
|
500
|
+
// User-defined fields
|
|
501
|
+
for _, f := range opts.Fields {
|
|
502
|
+
+ if strings.EqualFold(strings.TrimSpace(f.Name), "CLIENT") || strings.EqualFold(strings.TrimSpace(f.Name), "MANDT") {
|
|
503
|
+
+ continue
|
|
504
|
+
+ }
|
|
505
|
+
fieldName := strings.ToLower(f.Name)
|
|
506
|
+
fieldType := mapFieldType(f)
|
|
507
|
+
|
|
456
508
|
diff --git a/pkg/adt/features.go b/pkg/adt/features.go
|
|
457
509
|
index 13aa4b0..8fb7376 100755
|
|
458
510
|
--- a/pkg/adt/features.go
|
|
@@ -620,7 +672,7 @@ index 0000000..5e880bd
|
|
|
620
672
|
+ }
|
|
621
673
|
+}
|
|
622
674
|
diff --git a/pkg/adt/http.go b/pkg/adt/http.go
|
|
623
|
-
index 0b4d9f1..
|
|
675
|
+
index 0b4d9f1..e420573 100755
|
|
624
676
|
--- a/pkg/adt/http.go
|
|
625
677
|
+++ b/pkg/adt/http.go
|
|
626
678
|
@@ -313,7 +313,7 @@ func (t *Transport) request(ctx context.Context, path string, opts *RequestOptio
|
|
@@ -723,7 +775,8 @@ index 0b4d9f1..f0b5b51 100755
|
|
|
723
775
|
+ }
|
|
724
776
|
// Drain the body so the connection can be reused.
|
|
725
777
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
726
|
-
|
|
778
|
+
- traceHTTPResponse(resp, nil)
|
|
779
|
+
+ traceHTTPResponse(resp, body)
|
|
727
780
|
t.adoptServerCookies(resp)
|
|
728
781
|
|
|
729
782
|
- return resp.Header.Get("X-CSRF-Token"), resp.StatusCode, t.redirectedAwayFromSAP(resp), nil
|
|
@@ -751,10 +804,10 @@ index 0b4d9f1..f0b5b51 100755
|
|
|
751
804
|
// redirectedAwayFromSAP reports whether a response was ultimately served by
|
|
752
805
|
diff --git a/pkg/adt/http_csrf_discovery_test.go b/pkg/adt/http_csrf_discovery_test.go
|
|
753
806
|
new file mode 100644
|
|
754
|
-
index 0000000..
|
|
807
|
+
index 0000000..ee932b3
|
|
755
808
|
--- /dev/null
|
|
756
809
|
+++ b/pkg/adt/http_csrf_discovery_test.go
|
|
757
|
-
@@ -0,0 +1,
|
|
810
|
+
@@ -0,0 +1,55 @@
|
|
758
811
|
+package adt
|
|
759
812
|
+
|
|
760
813
|
+import (
|
|
@@ -810,7 +863,6 @@ index 0000000..80741da
|
|
|
810
863
|
+ t.Fatalf("expected both endpoint failures, got %v", err)
|
|
811
864
|
+ }
|
|
812
865
|
+}
|
|
813
|
-
+
|
|
814
866
|
diff --git a/pkg/adt/http_sso_same_host_test.go b/pkg/adt/http_sso_same_host_test.go
|
|
815
867
|
new file mode 100644
|
|
816
868
|
index 0000000..0c39b96
|
|
@@ -1290,3 +1342,32 @@ index 0000000..1385d6d
|
|
|
1290
1342
|
+ }
|
|
1291
1343
|
+ }
|
|
1292
1344
|
+}
|
|
1345
|
+
diff --git a/pkg/adt/workflows_source_tabl_test.go b/pkg/adt/workflows_source_tabl_test.go
|
|
1346
|
+
index 220e91b..61e1691 100644
|
|
1347
|
+
--- a/pkg/adt/workflows_source_tabl_test.go
|
|
1348
|
+
+++ b/pkg/adt/workflows_source_tabl_test.go
|
|
1349
|
+
@@ -44,3 +44,24 @@ func TestCreatingATableFromSourceSaysWhatDoesWork(t *testing.T) {
|
|
1350
|
+
t.Errorf("the refusal should name the route that does work, got %q", result.Message)
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
+
|
|
1354
|
+
+func TestCreateTableEmitsOnlyTheAutomaticClientField(t *testing.T) {
|
|
1355
|
+
+ source := generateTableDDL(CreateTableOptions{
|
|
1356
|
+
+ Name: "ZADK_SESSION",
|
|
1357
|
+
+ Fields: []TableField{
|
|
1358
|
+
+ {Name: "MANDT", Type: "mandt", IsKey: true},
|
|
1359
|
+
+ {Name: "CLIENT", Type: "abap.clnt", IsKey: true},
|
|
1360
|
+
+ {Name: "SESSION_ID", Type: "CHAR", Length: 32, IsKey: true},
|
|
1361
|
+
+ },
|
|
1362
|
+
+ })
|
|
1363
|
+
+
|
|
1364
|
+
+ if got := strings.Count(source, "key client : abap.clnt not null;"); got != 1 {
|
|
1365
|
+
+ t.Fatalf("got %d client keys, want exactly 1:\n%s", got, source)
|
|
1366
|
+
+ }
|
|
1367
|
+
+ if strings.Contains(source, "key mandt :") || strings.Contains(source, "key client : abap.clnt not null;\n key client :") {
|
|
1368
|
+
+ t.Fatalf("generated DDL contains a duplicate client field:\n%s", source)
|
|
1369
|
+
+ }
|
|
1370
|
+
+ if !strings.Contains(source, "key session_id : abap.char(32) not null;") {
|
|
1371
|
+
+ t.Fatalf("generated DDL lost the business key:\n%s", source)
|
|
1372
|
+
+ }
|
|
1373
|
+
+}
|
|
@@ -69,6 +69,30 @@ const TOOLS = {
|
|
|
69
69
|
['change_set_id'],
|
|
70
70
|
{ readOnlyHint: false, destructiveHint: true, idempotentHint: false }
|
|
71
71
|
),
|
|
72
|
+
UpdateTable: textTool(
|
|
73
|
+
'UpdateTable',
|
|
74
|
+
'Update and activate an existing Z/Y DDIC table from replacement DDL. The current DDL is re-read and compared with expected_source; VSP checks its source hash again under lock. Requires confirmation exactly equal to UPDATE TABLE <TABLE_NAME>.',
|
|
75
|
+
{
|
|
76
|
+
table_name: { type: 'string', minLength: 2, maxLength: 30, pattern: '^[ZzYy][A-Za-z0-9_]{1,29}$' },
|
|
77
|
+
expected_source: { type: 'string', minLength: 1, maxLength: MAX_CHANGE_BYTES },
|
|
78
|
+
replacement_source: { type: 'string', minLength: 1, maxLength: MAX_CHANGE_BYTES },
|
|
79
|
+
transport: { type: 'string', minLength: 1 },
|
|
80
|
+
confirmation: { type: 'string', minLength: 1, description: 'Must exactly equal UPDATE TABLE <TABLE_NAME>.' }
|
|
81
|
+
},
|
|
82
|
+
['table_name', 'expected_source', 'replacement_source', 'confirmation'],
|
|
83
|
+
{ readOnlyHint: false, destructiveHint: true, idempotentHint: false }
|
|
84
|
+
),
|
|
85
|
+
DeleteEmptyDDLS: textTool(
|
|
86
|
+
'DeleteEmptyDDLS',
|
|
87
|
+
'Delete only an empty Z/Y DDLS shell. The source is read first and deletion is refused unless it is blank. Requires confirmation exactly equal to DELETE EMPTY DDLS <OBJECT_NAME>.',
|
|
88
|
+
{
|
|
89
|
+
name: { type: 'string', minLength: 2, maxLength: 30, pattern: '^[ZzYy][A-Za-z0-9_]{1,29}$' },
|
|
90
|
+
transport: { type: 'string', minLength: 1 },
|
|
91
|
+
confirmation: { type: 'string', minLength: 1, description: 'Must exactly equal DELETE EMPTY DDLS <OBJECT_NAME>.' }
|
|
92
|
+
},
|
|
93
|
+
['name', 'confirmation'],
|
|
94
|
+
{ readOnlyHint: false, destructiveHint: true, idempotentHint: false }
|
|
95
|
+
),
|
|
72
96
|
CheckTransportReadiness: textTool(
|
|
73
97
|
'CheckTransportReadiness',
|
|
74
98
|
'Collect a transport request and dependency, inactive-object, ABAP Unit, and ATC evidence bundle. Only registered read/check tools are accepted; inspect findings before handoff.',
|
|
@@ -237,6 +261,18 @@ function hashSource(source) {
|
|
|
237
261
|
return createHash('sha256').update(source).digest('hex');
|
|
238
262
|
}
|
|
239
263
|
|
|
264
|
+
function adtSourceHash(source) {
|
|
265
|
+
const canonical = source.replace(/\r\n/g, '\n').replace(/\n+$/g, '');
|
|
266
|
+
return `sha256:${createHash('sha256').update(canonical).digest('hex')}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function customerObjectName(value, label) {
|
|
270
|
+
if (typeof value !== 'string') throw new Error(`${label} must be a customer object name`);
|
|
271
|
+
const name = value.trim().toUpperCase();
|
|
272
|
+
if (!/^[ZY][A-Z0-9_]{1,29}$/.test(name)) throw new Error(`${label} must be a Z/Y customer object name of 2–30 letters, digits, or underscores`);
|
|
273
|
+
return name;
|
|
274
|
+
}
|
|
275
|
+
|
|
240
276
|
function containsSource(value, source) {
|
|
241
277
|
if (typeof value === 'string') return value === source;
|
|
242
278
|
if (Array.isArray(value)) return value.some(entry => containsSource(entry, source));
|
|
@@ -519,6 +555,75 @@ export function createEngineeringTools(entry, upstreamTools, { env = process.env
|
|
|
519
555
|
});
|
|
520
556
|
}
|
|
521
557
|
|
|
558
|
+
if (available.has('GetTable') && available.has('WriteSource')) {
|
|
559
|
+
add(TOOLS.UpdateTable, async args => {
|
|
560
|
+
validateObject(args, 'arguments', ['table_name', 'expected_source', 'replacement_source', 'transport', 'confirmation']);
|
|
561
|
+
const tableName = customerObjectName(args.table_name, 'table_name');
|
|
562
|
+
if (args.confirmation !== `UPDATE TABLE ${tableName}`) throw new Error(`confirmation must equal UPDATE TABLE ${tableName}`);
|
|
563
|
+
for (const [label, source] of [['expected_source', args.expected_source], ['replacement_source', args.replacement_source]]) {
|
|
564
|
+
if (typeof source !== 'string' || !source) throw new Error(`${label} is required`);
|
|
565
|
+
if (Buffer.byteLength(source, 'utf8') > MAX_CHANGE_BYTES) throw new Error(`${label} exceeds ${MAX_CHANGE_BYTES} bytes`);
|
|
566
|
+
if (source.split(/\r?\n/).length > 1000) throw new Error(`${label} exceeds the 1,000-line limit`);
|
|
567
|
+
}
|
|
568
|
+
if (args.transport !== undefined && (typeof args.transport !== 'string' || !args.transport.trim())) throw new Error('transport must be a nonempty string when supplied');
|
|
569
|
+
const tableDefinition = /^\s*define\s+table\s+([A-Z0-9_/]+)/im.exec(args.replacement_source);
|
|
570
|
+
if (!tableDefinition || tableDefinition[1].toUpperCase() !== tableName) throw new Error(`replacement_source must define table ${tableName}`);
|
|
571
|
+
const expectedHash = adtSourceHash(args.expected_source);
|
|
572
|
+
if (expectedHash === adtSourceHash(args.replacement_source)) throw new Error('replacement_source does not change the table DDL');
|
|
573
|
+
|
|
574
|
+
const currentResult = await remoteTool(entry, 'GetTable', { table_name: tableName }, available, log);
|
|
575
|
+
const currentSource = sourceFromResult(currentResult);
|
|
576
|
+
if (currentSource === undefined) throw new Error(`GetTable did not return source for ${tableName}`);
|
|
577
|
+
const currentHash = adtSourceHash(currentSource);
|
|
578
|
+
if (currentHash !== expectedHash) throw new Error(`table source changed since review; re-read ${tableName} and prepare a new update`);
|
|
579
|
+
|
|
580
|
+
const writeArguments = {
|
|
581
|
+
object_type: 'TABL',
|
|
582
|
+
name: tableName,
|
|
583
|
+
source: args.replacement_source,
|
|
584
|
+
mode: 'update',
|
|
585
|
+
expected_source_hash: currentHash,
|
|
586
|
+
...(args.transport ? { transport: args.transport.trim() } : {})
|
|
587
|
+
};
|
|
588
|
+
const writeResult = await remoteTool(entry, 'WriteSource', writeArguments, available, log);
|
|
589
|
+
let writeDetails;
|
|
590
|
+
try {
|
|
591
|
+
writeDetails = JSON.parse(resultData(writeResult));
|
|
592
|
+
} catch {
|
|
593
|
+
throw new Error(`WriteSource returned an unparseable result for ${tableName}; inspect SAP state before retrying`);
|
|
594
|
+
}
|
|
595
|
+
if (!object(writeDetails) || writeDetails.success !== true) throw new Error(`WriteSource did not confirm a successful table update for ${tableName}; inspect SAP state before retrying`);
|
|
596
|
+
return toolJson({
|
|
597
|
+
status: 'updated',
|
|
598
|
+
table_name: tableName,
|
|
599
|
+
expected_source_hash: currentHash,
|
|
600
|
+
target_source_hash: adtSourceHash(args.replacement_source),
|
|
601
|
+
result: writeDetails
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (available.has('GetSource') && available.has('DeleteObject')) {
|
|
607
|
+
add(TOOLS.DeleteEmptyDDLS, async args => {
|
|
608
|
+
validateObject(args, 'arguments', ['name', 'transport', 'confirmation']);
|
|
609
|
+
const name = customerObjectName(args.name, 'name');
|
|
610
|
+
if (args.confirmation !== `DELETE EMPTY DDLS ${name}`) throw new Error(`confirmation must equal DELETE EMPTY DDLS ${name}`);
|
|
611
|
+
if (args.transport !== undefined && (typeof args.transport !== 'string' || !args.transport.trim())) throw new Error('transport must be a nonempty string when supplied');
|
|
612
|
+
|
|
613
|
+
const sourceResult = await remoteTool(entry, 'GetSource', { object_type: 'DDLS', name, include_context: false }, available, log);
|
|
614
|
+
const source = sourceFromResult(sourceResult);
|
|
615
|
+
if (source === undefined) throw new Error(`GetSource did not return source for DDLS ${name}; refusing deletion`);
|
|
616
|
+
if (source.trim() !== '') throw new Error(`DDLS ${name} is not empty; refusing deletion`);
|
|
617
|
+
|
|
618
|
+
const objectUrl = `/sap/bc/adt/ddic/ddl/sources/${encodeURIComponent(name.toLowerCase())}`;
|
|
619
|
+
await remoteTool(entry, 'DeleteObject', {
|
|
620
|
+
object_url: objectUrl,
|
|
621
|
+
...(args.transport ? { transport: args.transport.trim() } : {})
|
|
622
|
+
}, available, log);
|
|
623
|
+
return toolJson({ status: 'deleted', object_type: 'DDLS', name, object_url: objectUrl });
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
|
|
522
627
|
if (available.has('GetTransport')) {
|
|
523
628
|
add(TOOLS.CheckTransportReadiness, async args => {
|
|
524
629
|
validateObject(args, 'arguments', ['transport_id', 'checks']);
|
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 (
|
|
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.
|