abap-mcp 0.2.0 → 0.3.1

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 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 touches **no filesystem**: sources go in as text, findings come
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
 
@@ -55,6 +57,7 @@ npx abap-mcp lint src/ # lint files or whole directorie
55
57
  npx abap-mcp readiness src/ --fail-below 80 # repo-level ABAP Cloud readiness, CI-gateable
56
58
  npx abap-mcp scaffold --entity Travel --table ztravel --key travel_id --out ./out
57
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
58
61
  npx abap-mcp explain exit_or_check # rule rationale
59
62
  ```
60
63
 
@@ -75,7 +78,8 @@ loop condition), per-repo `.mcp.json`, and a GitHub Actions quality gate for aba
75
78
  | Tool | What it does |
76
79
  | --- | --- |
77
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. |
78
- | `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. |
79
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. |
80
84
  | `list_abap_rules` | Browse abaplint's ~180 rules (filter by text or tag). |
81
85
  | `explain_abap_rule` | One rule in depth — rationale (often Clean ABAP), examples, docs URL. |
@@ -84,25 +88,29 @@ loop condition), per-repo `.mcp.json`, and a GitHub Actions quality gate for aba
84
88
 
85
89
  ## Honesty box — what this is *not*
86
90
 
87
- - **Not ATC.** Readiness here is *language-level*: statements ABAP Cloud removed. Whether your
88
- code calls **released APIs only** requires a system's released-API list (ATC check
89
- `SAP_CP_READINESS`) out of scope for an offline tool, and the readiness report says so on
90
- every call.
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.
91
98
  - **Scaffold validation is tiered.** Generated classes and CDS views are round-tripped through
92
99
  abaplint at Cloud level before they're returned (the generator and the linter share one
93
100
  parser). Behavior/service definitions are outside abaplint's checked surface — they are
94
101
  golden-tested canonical templates, and ADT activation is the final arbiter. Each generated
95
102
  file is labeled `validated: "abaplint" | "template"`.
96
- - **Text-in only, by design.** No filesystem walking, no network — the entire attack surface is
97
- a parser over strings you explicitly pass. For linting whole directories, use the
98
- [abaplint CLI](https://abaplint.org) in CI, or the
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
99
107
  [mcp-kit `wrap-abaplint` recipe](https://github.com/palimkarakshay/mcp-kit) this server grew out of.
100
108
 
101
109
  ## Develop
102
110
 
103
111
  ```bash
104
112
  npm install
105
- npm run check # typecheck + 80 tests + build — the CI gate
113
+ npm run check # typecheck + 121 tests + build — the CI gate
106
114
  node dist/cli.js # stdio MCP server
107
115
  npx @modelcontextprotocol/inspector --cli node dist/cli.js --method tools/list
108
116
  ```
@@ -110,7 +118,7 @@ npx @modelcontextprotocol/inspector --cli node dist/cli.js --method tools/list
110
118
  Tool descriptions are CI-graded (a rubric test enforces verb-first names, when-to-use,
111
119
  non-goals, described params, worked examples — the
112
120
  [mcp-kit](https://github.com/palimkarakshay/mcp-kit) discipline; the full mcp-kit lint scores all
113
- seven tools 100/100).
121
+ eight tools 100/100).
114
122
 
115
123
  ## Design
116
124
 
@@ -122,6 +130,11 @@ scaffolder validates its own output, what was deliberately left out — lives in
122
130
 
123
131
  - [abaplint](https://github.com/abaplint/abaplint) by Lars Hvam — the parser and rule engine
124
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.
125
138
  - [mcp-kit](https://github.com/palimkarakshay/mcp-kit) — the production-MCP patterns this server
126
139
  follows (typed tool specs, transport discipline, description lint).
127
140
 
@@ -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[];
@@ -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
+ }
@@ -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
- * Honest scope: this is the static, parser-level slice of readiness. The
11
- * other half "does this call only RELEASED SAP APIs?" — requires the
12
- * system's released-API list (ATC check SAP_CP_READINESS) and is out of
13
- * scope for an offline tool. The report says so.
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
  }
@@ -1,6 +1,11 @@
1
- import { runAbaplint } from "./engine.js";
2
- export const SCOPE_NOTE = "Static parser-level analysis (abaplint). It detects statements and syntax that ABAP Cloud removes, " +
3
- "but NOT usage of unreleased SAP APIs that requires a system's released-API list (ATC / SAP_CP_READINESS). " +
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,79 @@ 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` — the reference is explicitly recorded as notToBeReleased
69
+ * in SAP's list: direct access to a classic/internal table (the typical
70
+ * "SELECT … FROM mara" case, with a curated CDS successor hint when one is
71
+ * known) or a CALL FUNCTION to an internal-only function module.
72
+ * Released objects and references the snapshot does not recognise are silent —
73
+ * absence from the list (every customer Z/Y-object, for a start) is "not known
74
+ * to be a problem", not proof either way.
75
+ */
76
+ function computeReleasedApiFindings(files, baselineVersion) {
77
+ const out = [];
78
+ for (const ref of extractObjectReferences(files, baselineVersion)) {
79
+ let hit = lookupReleased(ref.name, ref.objectType);
80
+ // An ABAP-SQL FROM clause names either a DDIC table or a CDS entity, but
81
+ // the extractor labels both TABL — fall back to the CDS record so a
82
+ // deprecated CDS view in a SELECT is still caught.
83
+ if (!hit.recorded && ref.objectType === "TABL") {
84
+ hit = lookupReleased(ref.name, "CDS_STOB");
85
+ }
86
+ if (hit.state === "released")
87
+ continue;
88
+ if (hit.state === "deprecated") {
89
+ out.push({
90
+ object: ref.name,
91
+ objectType: ref.objectType,
92
+ state: "deprecated",
93
+ file: ref.file,
94
+ line: ref.line,
95
+ note: ref.kind === "db-access"
96
+ ? `${ref.name} is a deprecated released object as of the snapshot — migrate to its current successor before going to ABAP Cloud.`
97
+ : `Function module ${ref.name} is deprecated as of the snapshot — replace it with the released successor API.`,
98
+ });
99
+ continue;
100
+ }
101
+ // not-released. Only flag names SAP's snapshot explicitly records as
102
+ // notToBeReleased — a name merely absent from the list (every customer
103
+ // Z/Y-object, for a start) is silent: absence is "not known to be a
104
+ // problem", not evidence of one.
105
+ if (!hit.recorded)
106
+ continue;
107
+ if (ref.objectType === "FUNC") {
108
+ out.push({
109
+ object: ref.name,
110
+ objectType: ref.objectType,
111
+ state: "not-released",
112
+ file: ref.file,
113
+ line: ref.line,
114
+ note: `Function module ${ref.name} is recorded as not-to-be-released in SAP's Cloudification list — it will not become a public API in ABAP Cloud; use a released successor API instead.`,
115
+ });
116
+ continue;
117
+ }
118
+ if (ref.objectType === "TABL" && hit.objectType === "TABL") {
119
+ const successor = suggestSuccessor(ref.name);
120
+ out.push({
121
+ object: ref.name,
122
+ objectType: ref.objectType,
123
+ state: "not-released",
124
+ ...(successor !== undefined ? { successor } : {}),
125
+ file: ref.file,
126
+ line: ref.line,
127
+ note: `${ref.name} is not a released API — direct access to this classic table is not allowed in ABAP Cloud.` +
128
+ (successor !== undefined ? ` Use the released CDS view ${successor} instead.` : " Use a released CDS view instead."),
129
+ });
130
+ }
131
+ }
132
+ return out;
133
+ }
@@ -0,0 +1,46 @@
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
+ * true when the name was found in SAP's snapshot (under the requested type,
17
+ * if one was given). false means absent — "not released as of the snapshot"
18
+ * by omission, which is weaker evidence than an explicit notToBeReleased
19
+ * record and must not be reported as a violation on its own.
20
+ */
21
+ recorded: boolean;
22
+ }
23
+ /**
24
+ * Look up an object in the bundled released-API list. Case-insensitive.
25
+ *
26
+ * `not-released` means "not a released API as of the snapshot": either the name
27
+ * is absent from SAP's list, or it is present with state `notToBeReleased`
28
+ * (typical for classic DDIC tables). A `released`/`deprecated` result is taken
29
+ * verbatim from SAP's published data.
30
+ *
31
+ * When `objectType` is given the lookup is strict: only a record of exactly
32
+ * that type answers the query — a same-named record under a different type is
33
+ * a miss (`recorded: false`), never a substitute (a released class must not
34
+ * make a non-released table look released). Untyped lookups use the first
35
+ * recorded entry, preferring a `released` or `deprecated` record over a
36
+ * `notToBeReleased` one so a genuinely released API is never masked by a
37
+ * same-named internal object.
38
+ */
39
+ export declare function lookupReleased(objectName: string, objectType?: string): ReleasedLookup;
40
+ /**
41
+ * Suggest the canonical released CDS view-entity successor for a classic DB
42
+ * table, from the curated table-successors map. Case-insensitive. Returns
43
+ * undefined when no curated successor is known (the caller should fall back to
44
+ * the target system's released-API list).
45
+ */
46
+ export declare function suggestSuccessor(tableName: string): string | undefined;
@@ -0,0 +1,94 @@
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 the lookup is strict: only a record of exactly
48
+ * that type answers the query — a same-named record under a different type is
49
+ * a miss (`recorded: false`), never a substitute (a released class must not
50
+ * make a non-released table look released). Untyped lookups use the first
51
+ * recorded entry, preferring a `released` or `deprecated` record over a
52
+ * `notToBeReleased` one so a genuinely released API is never masked by a
53
+ * same-named internal object.
54
+ */
55
+ export function lookupReleased(objectName, objectType) {
56
+ const key = objectName.trim().toUpperCase();
57
+ const entries = data.objects[key];
58
+ const wantedType = objectType?.trim().toUpperCase();
59
+ if (entries === undefined || entries.length === 0) {
60
+ return { name: objectName, objectType: wantedType, state: "not-released", recorded: false };
61
+ }
62
+ let chosen;
63
+ if (wantedType !== undefined) {
64
+ chosen = entries.find((e) => e[0].toUpperCase() === wantedType);
65
+ if (chosen === undefined) {
66
+ // Typed query, no record of that type: a miss, not a cross-type answer.
67
+ return { name: objectName, objectType: wantedType, state: "not-released", recorded: false };
68
+ }
69
+ }
70
+ else {
71
+ // Prefer a released/deprecated record over notToBeReleased when ambiguous.
72
+ chosen =
73
+ entries.find((e) => e[1] === "released" || e[1] === "deprecated") ?? entries[0];
74
+ }
75
+ if (chosen === undefined) {
76
+ return { name: objectName, objectType: wantedType, state: "not-released", recorded: false };
77
+ }
78
+ return {
79
+ name: objectName,
80
+ objectType: chosen[0],
81
+ state: toState(chosen[1]),
82
+ applicationComponent: chosen[2],
83
+ recorded: true,
84
+ };
85
+ }
86
+ /**
87
+ * Suggest the canonical released CDS view-entity successor for a classic DB
88
+ * table, from the curated table-successors map. Case-insensitive. Returns
89
+ * undefined when no curated successor is known (the caller should fall back to
90
+ * the target system's released-API list).
91
+ */
92
+ export function suggestSuccessor(tableName) {
93
+ return successors.successors[tableName.trim().toUpperCase()];
94
+ }
@@ -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[];