abap-mcp 0.1.1 → 0.3.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/README.md +49 -12
- package/dist/abap/engine.d.ts +24 -0
- package/dist/abap/engine.js +76 -0
- package/dist/abap/readiness.d.ts +33 -4
- package/dist/abap/readiness.js +58 -3
- package/dist/abap/released.d.ts +36 -0
- package/dist/abap/released.js +86 -0
- package/dist/abap.tools.d.ts +6 -0
- package/dist/abap.tools.js +108 -0
- package/dist/cli-commands.d.ts +23 -0
- package/dist/cli-commands.js +347 -0
- package/dist/cli.js +11 -2
- package/dist/data/released-apis.json +1 -0
- package/dist/data/table-successors.json +40 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,8 +6,9 @@ an abapGit export, a code review, CI.
|
|
|
6
6
|
|
|
7
7
|
Built on [abaplint](https://abaplint.org) (the open-source ABAP parser/linter) and the
|
|
8
8
|
[Model Context Protocol](https://modelcontextprotocol.io). TypeScript, 100% local — the server
|
|
9
|
-
makes **zero network calls** and
|
|
10
|
-
back as structured JSON.
|
|
9
|
+
makes **zero network calls** and reads **no user files**: sources go in as text, findings come
|
|
10
|
+
back as structured JSON. (The released-API list and abaplint's rule data are package-bundled
|
|
11
|
+
assets that ship inside the install — no network, no user filesystem, at runtime.)
|
|
11
12
|
|
|
12
13
|
## Why this exists
|
|
13
14
|
|
|
@@ -19,6 +20,7 @@ layer:
|
|
|
19
20
|
|
|
20
21
|
- *"Does this ABAP parse? Is it clean?"* → `lint_abap`
|
|
21
22
|
- *"How far is this classic report from ABAP Cloud?"* → `check_cloud_readiness`
|
|
23
|
+
- *"Is MARA a released API? What do I use instead?"* → `check_released_api`
|
|
22
24
|
- *"Start me a correct RAP business object."* → `scaffold_rap_bo`
|
|
23
25
|
- *"What's in this 4,000-line class?"* → `get_abap_outline`
|
|
24
26
|
|
|
@@ -46,12 +48,38 @@ claude mcp add abap-mcp -- node /path/to/abap-mcp/dist/cli.js
|
|
|
46
48
|
Then ask your agent things like *"lint this class against ABAP Cloud"*, *"is zold_report
|
|
47
49
|
cloud-ready?"*, or *"scaffold a RAP BO for entity Booking on table zbooking, draft enabled"*.
|
|
48
50
|
|
|
51
|
+
## CLI — same engine, no AI required
|
|
52
|
+
|
|
53
|
+
Every tool is also a subcommand, so it works in terminals and CI where no MCP client exists:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx abap-mcp lint src/ # lint files or whole directories
|
|
57
|
+
npx abap-mcp readiness src/ --fail-below 80 # repo-level ABAP Cloud readiness, CI-gateable
|
|
58
|
+
npx abap-mcp scaffold --entity Travel --table ztravel --key travel_id --out ./out
|
|
59
|
+
npx abap-mcp outline src/zcl_monster.clas.abap # navigate big objects
|
|
60
|
+
npx abap-mcp released MARA I_Product # released-API status + CDS successor
|
|
61
|
+
npx abap-mcp explain exit_or_check # rule rationale
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Directories are walked recursively (abapGit naming), batched automatically, and `readiness`
|
|
65
|
+
merges batches into one scored, categorized repo report. Exit codes are CI-friendly
|
|
66
|
+
(`1` on error findings / failed threshold).
|
|
67
|
+
|
|
68
|
+
## Recipes, agents & CI
|
|
69
|
+
|
|
70
|
+
**[docs/COOKBOOK.md](docs/COOKBOOK.md)** — practical recipes: the fix-until-clean loop,
|
|
71
|
+
PR review without a transport, whole-repo migration triage, CI gates, per-persona use cases.
|
|
72
|
+
**[examples/claude-code/](examples/claude-code/)** — drop-in agentic workflows: an
|
|
73
|
+
`abap-code-reviewer` subagent, an `abap-cloud-migrator` sweep loop (readiness score as the
|
|
74
|
+
loop condition), per-repo `.mcp.json`, and a GitHub Actions quality gate for abapGit repos.
|
|
75
|
+
|
|
49
76
|
## Tools
|
|
50
77
|
|
|
51
78
|
| Tool | What it does |
|
|
52
79
|
| --- | --- |
|
|
53
80
|
| `lint_abap` | abaplint static analysis over ABAP/CDS/BDEF sources → structured findings with rule docs links. Presets: `style` (default, snippet-friendly), `full`, `syntax-only`; per-rule overrides. |
|
|
54
|
-
| `check_cloud_readiness` | Dual-parse diff (classic baseline vs `Cloud`): statements that are valid today but illegal in ABAP Cloud become categorized blockers (dynpro, list output, native SQL, …) with a transparent score; code broken at the baseline is reported separately, not counted as migration work. |
|
|
81
|
+
| `check_cloud_readiness` | Dual-parse diff (classic baseline vs `Cloud`): statements that are valid today but illegal in ABAP Cloud become categorized blockers (dynpro, list output, native SQL, …) with a transparent score; code broken at the baseline is reported separately, not counted as migration work. Now also surfaces a **separate, dated released-API cross-check** (`releasedApiFindings`): direct access to non-released classic tables and deprecated-API usage found in the source, with CDS successor hints — informational, not folded into the score. |
|
|
82
|
+
| `check_released_api` | Looks up objects (tables, CDS views, function modules, classes, …) in SAP's bundled Cloudification snapshot → `released` / `deprecated` / `not-released` per object, plus a curated CDS successor for common classic tables. The released-API half of readiness, offline. |
|
|
55
83
|
| `scaffold_rap_bo` | Generates the canonical RAP managed-BO stack (root view, behavior definition `strict(2)` + optional draft, behavior class + handler locals, projection, metadata extension, OData V4 service definition) plus suggested table DDL, activation order and next steps. |
|
|
56
84
|
| `list_abap_rules` | Browse abaplint's ~180 rules (filter by text or tag). |
|
|
57
85
|
| `explain_abap_rule` | One rule in depth — rationale (often Clean ABAP), examples, docs URL. |
|
|
@@ -60,25 +88,29 @@ cloud-ready?"*, or *"scaffold a RAP BO for entity Booking on table zbooking, dra
|
|
|
60
88
|
|
|
61
89
|
## Honesty box — what this is *not*
|
|
62
90
|
|
|
63
|
-
- **Not ATC.**
|
|
64
|
-
|
|
65
|
-
`
|
|
66
|
-
|
|
91
|
+
- **Not ATC.** The objective readiness *score* is still language-level: statements ABAP Cloud
|
|
92
|
+
removed. Released-API coverage is now **partial and offline**: `check_released_api` and the
|
|
93
|
+
`releasedApiFindings` in readiness reflect SAP's published Cloudification list *as of the
|
|
94
|
+
bundled snapshot date* — they cover tables and function modules referenced in your source, not
|
|
95
|
+
every API, and are only as current as the snapshot. A target system's own released-API list
|
|
96
|
+
(ATC check `API_RELEASE_STATE_CHECK` / `SAP_CP_READINESS`) remains authoritative; treat an
|
|
97
|
+
"absent from the list" result as "not released as of the snapshot", not as proof.
|
|
67
98
|
- **Scaffold validation is tiered.** Generated classes and CDS views are round-tripped through
|
|
68
99
|
abaplint at Cloud level before they're returned (the generator and the linter share one
|
|
69
100
|
parser). Behavior/service definitions are outside abaplint's checked surface — they are
|
|
70
101
|
golden-tested canonical templates, and ADT activation is the final arbiter. Each generated
|
|
71
102
|
file is labeled `validated: "abaplint" | "template"`.
|
|
72
|
-
- **Text-in only, by design.** No filesystem walking, no network — the entire attack
|
|
73
|
-
a parser over strings you explicitly pass.
|
|
74
|
-
|
|
103
|
+
- **Text-in only, by design.** No user-filesystem walking, no network — the entire attack
|
|
104
|
+
surface is a parser over strings you explicitly pass. (The released-API snapshot and abaplint's
|
|
105
|
+
rule data are package-bundled assets imported from the install, not fetched or read from your
|
|
106
|
+
disk.) For linting whole directories, use the [abaplint CLI](https://abaplint.org) in CI, or the
|
|
75
107
|
[mcp-kit `wrap-abaplint` recipe](https://github.com/palimkarakshay/mcp-kit) this server grew out of.
|
|
76
108
|
|
|
77
109
|
## Develop
|
|
78
110
|
|
|
79
111
|
```bash
|
|
80
112
|
npm install
|
|
81
|
-
npm run check # typecheck +
|
|
113
|
+
npm run check # typecheck + 115 tests + build — the CI gate
|
|
82
114
|
node dist/cli.js # stdio MCP server
|
|
83
115
|
npx @modelcontextprotocol/inspector --cli node dist/cli.js --method tools/list
|
|
84
116
|
```
|
|
@@ -86,7 +118,7 @@ npx @modelcontextprotocol/inspector --cli node dist/cli.js --method tools/list
|
|
|
86
118
|
Tool descriptions are CI-graded (a rubric test enforces verb-first names, when-to-use,
|
|
87
119
|
non-goals, described params, worked examples — the
|
|
88
120
|
[mcp-kit](https://github.com/palimkarakshay/mcp-kit) discipline; the full mcp-kit lint scores all
|
|
89
|
-
|
|
121
|
+
eight tools 100/100).
|
|
90
122
|
|
|
91
123
|
## Design
|
|
92
124
|
|
|
@@ -98,6 +130,11 @@ scaffolder validates its own output, what was deliberately left out — lives in
|
|
|
98
130
|
|
|
99
131
|
- [abaplint](https://github.com/abaplint/abaplint) by Lars Hvam — the parser and rule engine
|
|
100
132
|
underneath every tool here (MIT).
|
|
133
|
+
- [SAP/abap-atc-cr-cv-s4hc](https://github.com/SAP/abap-atc-cr-cv-s4hc) — SAP's official ABAP
|
|
134
|
+
Cloudification Repository (object release list), **Apache-2.0**. The bundled released-API
|
|
135
|
+
snapshot (`src/data/released-apis.json`, snapshot **2026-06-10**) is a compact transform of
|
|
136
|
+
that data, redistributed under Apache-2.0 with attribution; see
|
|
137
|
+
[docs/DESIGN.md](docs/DESIGN.md) and `scripts/build-released-api-index.mjs` for the pipeline.
|
|
101
138
|
- [mcp-kit](https://github.com/palimkarakshay/mcp-kit) — the production-MCP patterns this server
|
|
102
139
|
follows (typed tool specs, transport discipline, description lint).
|
|
103
140
|
|
package/dist/abap/engine.d.ts
CHANGED
|
@@ -44,3 +44,27 @@ export interface RunResult {
|
|
|
44
44
|
fileCount: number;
|
|
45
45
|
}
|
|
46
46
|
export declare function runAbaplint(files: AbapSource[], opts: RunOptions): RunResult;
|
|
47
|
+
/** A statically-resolved reference to a repository object found in the source. */
|
|
48
|
+
export interface ObjectReference {
|
|
49
|
+
/** The referenced object name (upper-cased as written; tables are typically lowercased in source). */
|
|
50
|
+
name: string;
|
|
51
|
+
/** SAP object type: "TABL" for DB tables, "FUNC" for function modules. */
|
|
52
|
+
objectType: "TABL" | "FUNC";
|
|
53
|
+
/** How the reference appears, for the human-facing note. */
|
|
54
|
+
kind: "db-access" | "call-function";
|
|
55
|
+
file: string;
|
|
56
|
+
line: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Walk the parsed AST and collect references to repository objects we can
|
|
60
|
+
* resolve reliably: DB tables in ABAP-SQL statements (SELECT/INSERT/UPDATE/
|
|
61
|
+
* DELETE/MODIFY, including the tables in joins and FROM clauses) and function
|
|
62
|
+
* modules in CALL FUNCTION '<fm>'. Parsing is done at a classic baseline so the
|
|
63
|
+
* statements resolve even for code ABAP Cloud would reject; abaplint only
|
|
64
|
+
* parses, never executes. References are de-duplicated by name+type+location.
|
|
65
|
+
*
|
|
66
|
+
* Deliberately conservative: we extract only what the parser exposes as a
|
|
67
|
+
* first-class expression (DatabaseTable, FunctionName), not heuristic regexes,
|
|
68
|
+
* so a reference we report is a reference that exists.
|
|
69
|
+
*/
|
|
70
|
+
export declare function extractObjectReferences(files: AbapSource[], baselineVersion?: AbapVersion): ObjectReference[];
|
package/dist/abap/engine.js
CHANGED
|
@@ -141,3 +141,79 @@ export function runAbaplint(files, opts) {
|
|
|
141
141
|
});
|
|
142
142
|
return { findings, truncated: issues.length > MAX_FINDINGS, fileCount: bounded.length };
|
|
143
143
|
}
|
|
144
|
+
const SQL_STATEMENTS = new Set([
|
|
145
|
+
"Select",
|
|
146
|
+
"SelectLoop",
|
|
147
|
+
"InsertDatabase",
|
|
148
|
+
"UpdateDatabase",
|
|
149
|
+
"DeleteDatabase",
|
|
150
|
+
"ModifyDatabase",
|
|
151
|
+
]);
|
|
152
|
+
/**
|
|
153
|
+
* Walk the parsed AST and collect references to repository objects we can
|
|
154
|
+
* resolve reliably: DB tables in ABAP-SQL statements (SELECT/INSERT/UPDATE/
|
|
155
|
+
* DELETE/MODIFY, including the tables in joins and FROM clauses) and function
|
|
156
|
+
* modules in CALL FUNCTION '<fm>'. Parsing is done at a classic baseline so the
|
|
157
|
+
* statements resolve even for code ABAP Cloud would reject; abaplint only
|
|
158
|
+
* parses, never executes. References are de-duplicated by name+type+location.
|
|
159
|
+
*
|
|
160
|
+
* Deliberately conservative: we extract only what the parser exposes as a
|
|
161
|
+
* first-class expression (DatabaseTable, FunctionName), not heuristic regexes,
|
|
162
|
+
* so a reference we report is a reference that exists.
|
|
163
|
+
*/
|
|
164
|
+
export function extractObjectReferences(files, baselineVersion = "v758") {
|
|
165
|
+
const bounded = boundFiles(files);
|
|
166
|
+
const registry = new abaplint.Registry(buildConfig({ version: baselineVersion, preset: "syntax-only" }));
|
|
167
|
+
for (const f of bounded) {
|
|
168
|
+
registry.addFile(new abaplint.MemoryFile(f.filename, f.source));
|
|
169
|
+
}
|
|
170
|
+
registry.parse();
|
|
171
|
+
const refs = [];
|
|
172
|
+
const seen = new Set();
|
|
173
|
+
const push = (ref) => {
|
|
174
|
+
const dedupeKey = `${ref.objectType}:${ref.name}:${ref.file}:${ref.line}`;
|
|
175
|
+
if (seen.has(dedupeKey))
|
|
176
|
+
return;
|
|
177
|
+
seen.add(dedupeKey);
|
|
178
|
+
refs.push(ref);
|
|
179
|
+
};
|
|
180
|
+
for (const obj of registry.getObjects()) {
|
|
181
|
+
if (!(obj instanceof abaplint.ABAPObject))
|
|
182
|
+
continue;
|
|
183
|
+
for (const file of obj.getABAPFiles()) {
|
|
184
|
+
const filename = file.getFilename();
|
|
185
|
+
for (const st of file.getStatements()) {
|
|
186
|
+
const type = st.get().constructor.name;
|
|
187
|
+
if (SQL_STATEMENTS.has(type)) {
|
|
188
|
+
for (const e of st.findAllExpressions(abaplint.Expressions.DatabaseTable)) {
|
|
189
|
+
const name = e.concatTokens().trim();
|
|
190
|
+
if (name.length === 0)
|
|
191
|
+
continue;
|
|
192
|
+
push({
|
|
193
|
+
name: name.toUpperCase(),
|
|
194
|
+
objectType: "TABL",
|
|
195
|
+
kind: "db-access",
|
|
196
|
+
file: filename,
|
|
197
|
+
line: e.getFirstToken().getStart().getRow(),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (type === "CallFunction") {
|
|
202
|
+
for (const e of st.findAllExpressions(abaplint.Expressions.FunctionName)) {
|
|
203
|
+
const name = e.concatTokens().replace(/'/g, "").trim();
|
|
204
|
+
if (name.length === 0)
|
|
205
|
+
continue;
|
|
206
|
+
push({
|
|
207
|
+
name: name.toUpperCase(),
|
|
208
|
+
objectType: "FUNC",
|
|
209
|
+
kind: "call-function",
|
|
210
|
+
file: filename,
|
|
211
|
+
line: e.getFirstToken().getStart().getRow(),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return refs;
|
|
219
|
+
}
|
package/dist/abap/readiness.d.ts
CHANGED
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
* longer allows). A finding present at the baseline is just *broken code* —
|
|
8
8
|
* reporting it as a migration item would overstate the migration.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Released-API check: separately from the parser-level diff, the source is
|
|
11
|
+
* walked for object references (DB tables, function modules) which are looked
|
|
12
|
+
* up against the bundled SAP Cloudification snapshot. These land in their own
|
|
13
|
+
* `releasedApiFindings` field — they are NOT folded into cloudBlockerCount or
|
|
14
|
+
* score, because those are objective, parser-level numbers and the snapshot is
|
|
15
|
+
* only as current as its date.
|
|
14
16
|
*/
|
|
15
17
|
import type { AbapSource, AbapVersion, Finding } from "./engine.js";
|
|
16
18
|
export interface ReadinessCategory {
|
|
@@ -19,6 +21,25 @@ export interface ReadinessCategory {
|
|
|
19
21
|
count: number;
|
|
20
22
|
findings: Finding[];
|
|
21
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* A released-API observation about a referenced object. Kept SEPARATE from the
|
|
26
|
+
* parser-level blocker counts/score: it reflects the bundled SAP snapshot
|
|
27
|
+
* (dated), not abaplint's objective parse, and a system's ATC is authoritative.
|
|
28
|
+
*/
|
|
29
|
+
export interface ReleasedApiFinding {
|
|
30
|
+
/** Referenced object name (upper-cased). */
|
|
31
|
+
object: string;
|
|
32
|
+
/** SAP object type: "TABL" (DB table) or "FUNC" (function module). */
|
|
33
|
+
objectType: string;
|
|
34
|
+
/** Released-API state from the bundled snapshot. */
|
|
35
|
+
state: "deprecated" | "not-released";
|
|
36
|
+
/** Curated released CDS successor for a classic table, when known. */
|
|
37
|
+
successor?: string;
|
|
38
|
+
file: string;
|
|
39
|
+
line: number;
|
|
40
|
+
/** Human-facing explanation of why this was flagged. */
|
|
41
|
+
note: string;
|
|
42
|
+
}
|
|
22
43
|
export interface ReadinessReport {
|
|
23
44
|
verdict: "ready" | "minor-rework" | "moderate-rework" | "significant-rework";
|
|
24
45
|
score: number;
|
|
@@ -26,6 +47,14 @@ export interface ReadinessReport {
|
|
|
26
47
|
categories: ReadinessCategory[];
|
|
27
48
|
/** Findings that fail even at the classic baseline — fix these first; they are not migration items. */
|
|
28
49
|
brokenAtBaseline: Finding[];
|
|
50
|
+
/**
|
|
51
|
+
* Released-API observations from the bundled SAP Cloudification snapshot —
|
|
52
|
+
* deprecated API usage and direct access to non-released (classic) tables,
|
|
53
|
+
* with successor hints. Separate from cloudBlockerCount/score by design.
|
|
54
|
+
*/
|
|
55
|
+
releasedApiFindings: ReleasedApiFinding[];
|
|
56
|
+
/** Date of the bundled released-API snapshot the releasedApiFindings reflect. */
|
|
57
|
+
releasedApiSnapshotDate: string;
|
|
29
58
|
baselineVersion: AbapVersion;
|
|
30
59
|
scopeNote: string;
|
|
31
60
|
}
|
package/dist/abap/readiness.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { runAbaplint } from "./engine.js";
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { extractObjectReferences, runAbaplint } from "./engine.js";
|
|
2
|
+
import { lookupReleased, RELEASED_API_SNAPSHOT, suggestSuccessor } from "./released.js";
|
|
3
|
+
export const SCOPE_NOTE = "Static parser-level analysis (abaplint) PLUS a released-API cross-check against SAP's bundled Cloudification " +
|
|
4
|
+
`snapshot (dated ${RELEASED_API_SNAPSHOT.snapshotDate}). It detects statements ABAP Cloud removes (the objective ` +
|
|
5
|
+
"cloud-blocker count and score) and, separately, flags deprecated-API usage and direct access to non-released " +
|
|
6
|
+
"classic tables (releasedApiFindings — informational, NOT counted in the score). The bundled list is only as current " +
|
|
7
|
+
"as its snapshot date, and covers tables and function modules referenced here, not every API; a target system's own " +
|
|
8
|
+
"released-API list (ATC check API_RELEASE_STATE_CHECK / SAP_CP_READINESS) remains authoritative. " +
|
|
4
9
|
"Treat 'ready' as 'no language-level blockers', not as a full Clean Core certification.";
|
|
5
10
|
/** Map an offending line to a human category by its leading keyword(s). */
|
|
6
11
|
function categorize(excerpt) {
|
|
@@ -50,7 +55,57 @@ export function checkCloudReadiness(files, baselineVersion = "v758") {
|
|
|
50
55
|
cloudBlockerCount: n,
|
|
51
56
|
categories: [...byCategory.values()].sort((a, b) => b.count - a.count),
|
|
52
57
|
brokenAtBaseline: baseline.findings,
|
|
58
|
+
releasedApiFindings: computeReleasedApiFindings(files, baselineVersion),
|
|
59
|
+
releasedApiSnapshotDate: RELEASED_API_SNAPSHOT.snapshotDate,
|
|
53
60
|
baselineVersion,
|
|
54
61
|
scopeNote: SCOPE_NOTE,
|
|
55
62
|
};
|
|
56
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Cross-check statically-extracted object references against the bundled SAP
|
|
66
|
+
* Cloudification snapshot. Flags two cases:
|
|
67
|
+
* - `deprecated` — the referenced object is a deprecated released API.
|
|
68
|
+
* - `not-released` — direct access to a classic/internal table that is not a
|
|
69
|
+
* released API (the typical "SELECT … FROM mara" case), with a curated CDS
|
|
70
|
+
* successor hint when one is known.
|
|
71
|
+
* Released objects and references the snapshot does not recognise are silent —
|
|
72
|
+
* absence from the list is "not known to be a problem", not proof either way.
|
|
73
|
+
*/
|
|
74
|
+
function computeReleasedApiFindings(files, baselineVersion) {
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const ref of extractObjectReferences(files, baselineVersion)) {
|
|
77
|
+
const hit = lookupReleased(ref.name, ref.objectType);
|
|
78
|
+
if (hit.state === "released")
|
|
79
|
+
continue;
|
|
80
|
+
if (hit.state === "deprecated") {
|
|
81
|
+
out.push({
|
|
82
|
+
object: ref.name,
|
|
83
|
+
objectType: ref.objectType,
|
|
84
|
+
state: "deprecated",
|
|
85
|
+
file: ref.file,
|
|
86
|
+
line: ref.line,
|
|
87
|
+
note: ref.kind === "db-access"
|
|
88
|
+
? `${ref.name} is a deprecated released object as of the snapshot — migrate to its current successor before going to ABAP Cloud.`
|
|
89
|
+
: `Function module ${ref.name} is deprecated as of the snapshot — replace it with the released successor API.`,
|
|
90
|
+
});
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// not-released. Only flag DB tables (direct table access is the cloud
|
|
94
|
+
// anti-pattern); a CALL FUNCTION to a module simply absent from the list is
|
|
95
|
+
// too noisy to report as a finding without a system to confirm against.
|
|
96
|
+
if (ref.objectType === "TABL") {
|
|
97
|
+
const successor = suggestSuccessor(ref.name);
|
|
98
|
+
out.push({
|
|
99
|
+
object: ref.name,
|
|
100
|
+
objectType: ref.objectType,
|
|
101
|
+
state: "not-released",
|
|
102
|
+
...(successor !== undefined ? { successor } : {}),
|
|
103
|
+
file: ref.file,
|
|
104
|
+
line: ref.line,
|
|
105
|
+
note: `${ref.name} is not a released API — direct access to this classic table is not allowed in ABAP Cloud.` +
|
|
106
|
+
(successor !== undefined ? ` Use the released CDS view ${successor} instead.` : " Use a released CDS view instead."),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Snapshot metadata for the bundled released-API list. */
|
|
2
|
+
export declare const RELEASED_API_SNAPSHOT: {
|
|
3
|
+
readonly snapshotDate: string;
|
|
4
|
+
readonly source: string;
|
|
5
|
+
readonly formatVersion: string | null;
|
|
6
|
+
readonly recordCount: number;
|
|
7
|
+
};
|
|
8
|
+
export type ReleasedState = "released" | "deprecated" | "not-released";
|
|
9
|
+
export interface ReleasedLookup {
|
|
10
|
+
name: string;
|
|
11
|
+
/** The object type recorded by SAP (TABL, CDS_STOB, FUNC, CLAS, …), or the queried type when no record matched. */
|
|
12
|
+
objectType: string | undefined;
|
|
13
|
+
state: ReleasedState;
|
|
14
|
+
applicationComponent?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Look up an object in the bundled released-API list. Case-insensitive.
|
|
18
|
+
*
|
|
19
|
+
* `not-released` means "not a released API as of the snapshot": either the name
|
|
20
|
+
* is absent from SAP's list, or it is present with state `notToBeReleased`
|
|
21
|
+
* (typical for classic DDIC tables). A `released`/`deprecated` result is taken
|
|
22
|
+
* verbatim from SAP's published data.
|
|
23
|
+
*
|
|
24
|
+
* When `objectType` is given it disambiguates the few hundred names that exist
|
|
25
|
+
* under more than one object type; otherwise the first recorded entry is used,
|
|
26
|
+
* preferring a `released` or `deprecated` record over a `notToBeReleased` one
|
|
27
|
+
* so a genuinely released API is never masked by a same-named internal object.
|
|
28
|
+
*/
|
|
29
|
+
export declare function lookupReleased(objectName: string, objectType?: string): ReleasedLookup;
|
|
30
|
+
/**
|
|
31
|
+
* Suggest the canonical released CDS view-entity successor for a classic DB
|
|
32
|
+
* table, from the curated table-successors map. Case-insensitive. Returns
|
|
33
|
+
* undefined when no curated successor is known (the caller should fall back to
|
|
34
|
+
* the target system's released-API list).
|
|
35
|
+
*/
|
|
36
|
+
export declare function suggestSuccessor(tableName: string): string | undefined;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Released-API lookup against SAP's published ABAP Cloudification list.
|
|
3
|
+
*
|
|
4
|
+
* The data shipped at src/data/released-apis.json is a compact transform of
|
|
5
|
+
* SAP's official, Apache-2.0 object release list (SAP/abap-atc-cr-cv-s4hc),
|
|
6
|
+
* built offline by scripts/build-released-api-index.mjs. It is a PACKAGE-BUNDLED
|
|
7
|
+
* asset (like abaplint's own bundled rule metadata): importing it touches no
|
|
8
|
+
* network and no user filesystem at runtime, so it does not violate the
|
|
9
|
+
* server's "text in, JSON out, offline" contract.
|
|
10
|
+
*
|
|
11
|
+
* SAP's list uses three states:
|
|
12
|
+
* - "released" — a released API, safe to consume in ABAP Cloud.
|
|
13
|
+
* - "deprecated" — was released, now on the way out; move to a successor.
|
|
14
|
+
* - "notToBeReleased" — will not be released as a public API (classic DDIC
|
|
15
|
+
* tables, internal objects); direct use is a cloud
|
|
16
|
+
* blocker. We surface these (and names absent from the
|
|
17
|
+
* list entirely) as state "not-released".
|
|
18
|
+
*/
|
|
19
|
+
import index from "../data/released-apis.json" with { type: "json" };
|
|
20
|
+
import successorData from "../data/table-successors.json" with { type: "json" };
|
|
21
|
+
const data = index;
|
|
22
|
+
const successors = successorData;
|
|
23
|
+
/** Snapshot metadata for the bundled released-API list. */
|
|
24
|
+
export const RELEASED_API_SNAPSHOT = {
|
|
25
|
+
snapshotDate: data.snapshotDate,
|
|
26
|
+
source: data.source,
|
|
27
|
+
formatVersion: data.formatVersion,
|
|
28
|
+
recordCount: data.recordCount,
|
|
29
|
+
};
|
|
30
|
+
/** Map SAP's raw state to our three-value state. */
|
|
31
|
+
function toState(rawState) {
|
|
32
|
+
if (rawState === "released")
|
|
33
|
+
return "released";
|
|
34
|
+
if (rawState === "deprecated")
|
|
35
|
+
return "deprecated";
|
|
36
|
+
// "notToBeReleased" — present in the list but never a public API.
|
|
37
|
+
return "not-released";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Look up an object in the bundled released-API list. Case-insensitive.
|
|
41
|
+
*
|
|
42
|
+
* `not-released` means "not a released API as of the snapshot": either the name
|
|
43
|
+
* is absent from SAP's list, or it is present with state `notToBeReleased`
|
|
44
|
+
* (typical for classic DDIC tables). A `released`/`deprecated` result is taken
|
|
45
|
+
* verbatim from SAP's published data.
|
|
46
|
+
*
|
|
47
|
+
* When `objectType` is given it disambiguates the few hundred names that exist
|
|
48
|
+
* under more than one object type; otherwise the first recorded entry is used,
|
|
49
|
+
* preferring a `released` or `deprecated` record over a `notToBeReleased` one
|
|
50
|
+
* so a genuinely released API is never masked by a same-named internal object.
|
|
51
|
+
*/
|
|
52
|
+
export function lookupReleased(objectName, objectType) {
|
|
53
|
+
const key = objectName.trim().toUpperCase();
|
|
54
|
+
const entries = data.objects[key];
|
|
55
|
+
if (entries === undefined || entries.length === 0) {
|
|
56
|
+
return { name: objectName, objectType, state: "not-released" };
|
|
57
|
+
}
|
|
58
|
+
const wantedType = objectType?.trim().toUpperCase();
|
|
59
|
+
let chosen;
|
|
60
|
+
if (wantedType !== undefined) {
|
|
61
|
+
chosen = entries.find((e) => e[0].toUpperCase() === wantedType);
|
|
62
|
+
}
|
|
63
|
+
if (chosen === undefined) {
|
|
64
|
+
// Prefer a released/deprecated record over notToBeReleased when ambiguous.
|
|
65
|
+
chosen =
|
|
66
|
+
entries.find((e) => e[1] === "released" || e[1] === "deprecated") ?? entries[0];
|
|
67
|
+
}
|
|
68
|
+
if (chosen === undefined) {
|
|
69
|
+
return { name: objectName, objectType, state: "not-released" };
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
name: objectName,
|
|
73
|
+
objectType: chosen[0],
|
|
74
|
+
state: toState(chosen[1]),
|
|
75
|
+
applicationComponent: chosen[2],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Suggest the canonical released CDS view-entity successor for a classic DB
|
|
80
|
+
* table, from the curated table-successors map. Case-insensitive. Returns
|
|
81
|
+
* undefined when no curated successor is known (the caller should fall back to
|
|
82
|
+
* the target system's released-API list).
|
|
83
|
+
*/
|
|
84
|
+
export function suggestSuccessor(tableName) {
|
|
85
|
+
return successors.successors[tableName.trim().toUpperCase()];
|
|
86
|
+
}
|
package/dist/abap.tools.d.ts
CHANGED
|
@@ -82,6 +82,12 @@ export declare const getAbapOutline: import("./tool.js").ToolSpec<{
|
|
|
82
82
|
source: z.ZodString;
|
|
83
83
|
}, z.core.$strip>>;
|
|
84
84
|
}>;
|
|
85
|
+
export declare const checkReleasedApiTool: import("./tool.js").ToolSpec<{
|
|
86
|
+
objects: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
87
|
+
name: z.ZodString;
|
|
88
|
+
type: z.ZodOptional<z.ZodString>;
|
|
89
|
+
}, z.core.$strip>]>>;
|
|
90
|
+
}>;
|
|
85
91
|
/** Every tool this server exposes. (`tools` alias = the registry-export shape @mcp-kit/lint discovers.) */
|
|
86
92
|
export declare const ALL_TOOLS: readonly AnyToolSpec[];
|
|
87
93
|
export declare const tools: readonly AnyToolSpec[];
|
package/dist/abap.tools.js
CHANGED
|
@@ -11,6 +11,7 @@ import { ABAP_VERSIONS, runAbaplint } from "./abap/engine.js";
|
|
|
11
11
|
import { formatAbap } from "./abap/formatter.js";
|
|
12
12
|
import { outlineAbap } from "./abap/outline.js";
|
|
13
13
|
import { checkCloudReadiness } from "./abap/readiness.js";
|
|
14
|
+
import { lookupReleased, RELEASED_API_SNAPSHOT, suggestSuccessor, } from "./abap/released.js";
|
|
14
15
|
import { explainRule, listRules } from "./abap/rules.js";
|
|
15
16
|
import { scaffoldRapBo } from "./abap/scaffold.js";
|
|
16
17
|
import { invalidInput } from "./errors.js";
|
|
@@ -132,6 +133,12 @@ export const checkCloudReadinessTool = defineTool({
|
|
|
132
133
|
brokenAtBaseline: z
|
|
133
134
|
.array(z.unknown())
|
|
134
135
|
.describe("Findings that fail even at the baseline version — fix first, they are not migration items."),
|
|
136
|
+
releasedApiFindings: z
|
|
137
|
+
.array(z.unknown())
|
|
138
|
+
.describe("Released-API observations from the bundled SAP Cloudification snapshot (deprecated-API usage, direct non-released table access with successor hints). Informational — NOT counted in cloudBlockerCount or score."),
|
|
139
|
+
releasedApiSnapshotDate: z
|
|
140
|
+
.string()
|
|
141
|
+
.describe("Date of the bundled released-API snapshot the releasedApiFindings reflect."),
|
|
135
142
|
baselineVersion: z.string().describe("The baseline used."),
|
|
136
143
|
scopeNote: z.string().describe("Exactly what this check does and does not cover."),
|
|
137
144
|
},
|
|
@@ -151,6 +158,9 @@ export const checkCloudReadinessTool = defineTool({
|
|
|
151
158
|
(catLine.length > 0 ? ` [${catLine}]` : "") +
|
|
152
159
|
(report.brokenAtBaseline.length > 0
|
|
153
160
|
? `; ${report.brokenAtBaseline.length} finding(s) broken at ${report.baselineVersion} regardless`
|
|
161
|
+
: "") +
|
|
162
|
+
(report.releasedApiFindings.length > 0
|
|
163
|
+
? `; ${report.releasedApiFindings.length} released-API note(s) (snapshot ${report.releasedApiSnapshotDate})`
|
|
154
164
|
: "");
|
|
155
165
|
return {
|
|
156
166
|
content: [{ type: "text", text }],
|
|
@@ -417,11 +427,109 @@ export const getAbapOutline = defineTool({
|
|
|
417
427
|
};
|
|
418
428
|
},
|
|
419
429
|
});
|
|
430
|
+
/** Accept either a bare name string or a { name, type? } reference. */
|
|
431
|
+
const objectRefField = z
|
|
432
|
+
.array(z.union([
|
|
433
|
+
z.string().describe('A bare object name, e.g. "MARA" or "I_Product".'),
|
|
434
|
+
z.object({
|
|
435
|
+
name: z.string().describe('Object name, e.g. "MARA", "I_Product", "BAPI_MATERIAL_GET_DETAIL".'),
|
|
436
|
+
type: z
|
|
437
|
+
.string()
|
|
438
|
+
.optional()
|
|
439
|
+
.describe('Optional SAP object type to disambiguate same-named objects: "TABL" (table), "CDS_STOB" (CDS view entity), "FUNC" (function module), "CLAS", "INTF", "BDEF". Omit if unsure.'),
|
|
440
|
+
}),
|
|
441
|
+
]))
|
|
442
|
+
.min(1)
|
|
443
|
+
.max(200)
|
|
444
|
+
.describe('Objects to check, 1–200 per call. Each is a bare name string or a { name, type? } object, e.g. ["MARA", { "name": "I_Product", "type": "CDS_STOB" }].');
|
|
445
|
+
export const checkReleasedApiTool = defineTool({
|
|
446
|
+
name: "check_released_api",
|
|
447
|
+
title: "Check ABAP released-API status",
|
|
448
|
+
description: "Look up ABAP repository objects (DB tables, CDS view entities, function modules, classes, interfaces, …) in " +
|
|
449
|
+
"SAP's published ABAP Cloudification list and report, per object, whether it is a 'released' API (safe to use in " +
|
|
450
|
+
"ABAP Cloud / Clean Core), 'deprecated' (released but being retired), or 'not-released' (a classic/internal object " +
|
|
451
|
+
"that is not a public API — e.g. most classic DDIC tables) — with a curated CDS successor hint for common tables. " +
|
|
452
|
+
`This reflects SAP's official Cloudification list as bundled in this package (snapshot ${RELEASED_API_SNAPSHOT.snapshotDate}); ` +
|
|
453
|
+
"it ships offline with the server. " +
|
|
454
|
+
"Use this when you need to know if your code may reference a given object in ABAP Cloud, or which released CDS view " +
|
|
455
|
+
"to use instead of a classic table — the released-API half of readiness that check_cloud_readiness deliberately " +
|
|
456
|
+
"leaves to a system's ATC. " +
|
|
457
|
+
"It does not connect to any SAP system, does not run ATC, and is only as current as the bundled snapshot — a " +
|
|
458
|
+
`system's own released-API list (ATC check API_RELEASE_STATE_CHECK / SAP_CP_READINESS) remains authoritative; treat ` +
|
|
459
|
+
"an 'absent from the list' result as 'not-released as of the snapshot', not as proof. " +
|
|
460
|
+
'Example: check_released_api({ "objects": ["MARA", "I_Product", "BAPI_MATERIAL_GET_DETAIL"] }).',
|
|
461
|
+
inputSchema: {
|
|
462
|
+
objects: objectRefField,
|
|
463
|
+
},
|
|
464
|
+
outputSchema: {
|
|
465
|
+
snapshotDate: z.string().describe("Date of the bundled SAP Cloudification snapshot these results reflect."),
|
|
466
|
+
source: z.string().describe("URL of the SAP Apache-2.0 source the snapshot was built from."),
|
|
467
|
+
results: z.array(z.object({
|
|
468
|
+
name: z.string().describe("The object name as queried."),
|
|
469
|
+
objectType: z
|
|
470
|
+
.string()
|
|
471
|
+
.optional()
|
|
472
|
+
.describe("SAP object type of the matched record (TABL, CDS_STOB, FUNC, …), if found."),
|
|
473
|
+
state: z
|
|
474
|
+
.enum(["released", "deprecated", "not-released"])
|
|
475
|
+
.describe("'released' = safe public API; 'deprecated' = retiring; 'not-released' = not a public API."),
|
|
476
|
+
applicationComponent: z
|
|
477
|
+
.string()
|
|
478
|
+
.optional()
|
|
479
|
+
.describe("Owning application component of the matched record, if found."),
|
|
480
|
+
successor: z
|
|
481
|
+
.string()
|
|
482
|
+
.optional()
|
|
483
|
+
.describe("Curated released CDS view-entity successor for a classic table, when one is known."),
|
|
484
|
+
})),
|
|
485
|
+
},
|
|
486
|
+
annotations: { readOnlyHint: true, openWorldHint: false, idempotentHint: true },
|
|
487
|
+
examples: [
|
|
488
|
+
{
|
|
489
|
+
description: "Check a classic table, a released CDS view, and a BAPI in one call.",
|
|
490
|
+
arguments: { objects: ["MARA", "I_Product", "BAPI_MATERIAL_GET_DETAIL"] },
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
description: "Disambiguate a name that exists under more than one object type.",
|
|
494
|
+
arguments: { objects: [{ name: "I_ProcurementProjectTP", type: "CDS_STOB" }] },
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
handler: (args) => {
|
|
498
|
+
const results = args.objects.map((ref) => {
|
|
499
|
+
const name = typeof ref === "string" ? ref : ref.name;
|
|
500
|
+
const type = typeof ref === "string" ? undefined : ref.type;
|
|
501
|
+
const hit = lookupReleased(name, type);
|
|
502
|
+
const successor = suggestSuccessor(name);
|
|
503
|
+
return {
|
|
504
|
+
name: hit.name,
|
|
505
|
+
objectType: hit.objectType,
|
|
506
|
+
state: hit.state,
|
|
507
|
+
applicationComponent: hit.applicationComponent,
|
|
508
|
+
...(successor !== undefined ? { successor } : {}),
|
|
509
|
+
};
|
|
510
|
+
});
|
|
511
|
+
const text = results
|
|
512
|
+
.map((r) => {
|
|
513
|
+
const tail = r.successor !== undefined ? ` → use ${r.successor}` : "";
|
|
514
|
+
return `${r.name}: ${r.state}${r.objectType !== undefined ? ` (${r.objectType})` : ""}${tail}`;
|
|
515
|
+
})
|
|
516
|
+
.join("\n");
|
|
517
|
+
return {
|
|
518
|
+
content: [{ type: "text", text: `Snapshot ${RELEASED_API_SNAPSHOT.snapshotDate}\n${text}` }],
|
|
519
|
+
structuredContent: {
|
|
520
|
+
snapshotDate: RELEASED_API_SNAPSHOT.snapshotDate,
|
|
521
|
+
source: RELEASED_API_SNAPSHOT.source,
|
|
522
|
+
results,
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
},
|
|
526
|
+
});
|
|
420
527
|
/** Every tool this server exposes. (`tools` alias = the registry-export shape @mcp-kit/lint discovers.) */
|
|
421
528
|
export const ALL_TOOLS = [
|
|
422
529
|
lintAbap,
|
|
423
530
|
checkCloudReadinessTool,
|
|
424
531
|
scaffoldRapBoTool,
|
|
532
|
+
checkReleasedApiTool,
|
|
425
533
|
listAbapRules,
|
|
426
534
|
explainAbapRule,
|
|
427
535
|
formatAbapTool,
|