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 +25 -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 +80 -3
- package/dist/abap/released.d.ts +46 -0
- package/dist/abap/released.js +94 -0
- package/dist/abap.tools.d.ts +6 -0
- package/dist/abap.tools.js +113 -0
- package/dist/cli-commands.d.ts +2 -1
- package/dist/cli-commands.js +54 -2
- package/dist/data/released-apis.json +1 -0
- package/dist/data/table-successors.json +40 -0
- package/dist/errors.js +7 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/package.json +2 -2
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,114 @@ 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
|
+
recorded: z
|
|
477
|
+
.boolean()
|
|
478
|
+
.describe("true = explicitly present in SAP's snapshot (under the requested type, if one was given); false = absent — 'not released as of the snapshot' by omission only."),
|
|
479
|
+
applicationComponent: z
|
|
480
|
+
.string()
|
|
481
|
+
.optional()
|
|
482
|
+
.describe("Owning application component of the matched record, if found."),
|
|
483
|
+
successor: z
|
|
484
|
+
.string()
|
|
485
|
+
.optional()
|
|
486
|
+
.describe("Curated released CDS view-entity successor for a classic table, when one is known."),
|
|
487
|
+
})),
|
|
488
|
+
},
|
|
489
|
+
annotations: { readOnlyHint: true, openWorldHint: false, idempotentHint: true },
|
|
490
|
+
examples: [
|
|
491
|
+
{
|
|
492
|
+
description: "Check a classic table, a released CDS view, and a BAPI in one call.",
|
|
493
|
+
arguments: { objects: ["MARA", "I_Product", "BAPI_MATERIAL_GET_DETAIL"] },
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
description: "Disambiguate a name that exists under more than one object type.",
|
|
497
|
+
arguments: { objects: [{ name: "I_ProcurementProjectTP", type: "CDS_STOB" }] },
|
|
498
|
+
},
|
|
499
|
+
],
|
|
500
|
+
handler: (args) => {
|
|
501
|
+
const results = args.objects.map((ref) => {
|
|
502
|
+
const name = typeof ref === "string" ? ref : ref.name;
|
|
503
|
+
const type = typeof ref === "string" ? undefined : ref.type;
|
|
504
|
+
const hit = lookupReleased(name, type);
|
|
505
|
+
const successor = suggestSuccessor(name);
|
|
506
|
+
return {
|
|
507
|
+
name: hit.name,
|
|
508
|
+
objectType: hit.objectType,
|
|
509
|
+
state: hit.state,
|
|
510
|
+
recorded: hit.recorded,
|
|
511
|
+
applicationComponent: hit.applicationComponent,
|
|
512
|
+
...(successor !== undefined ? { successor } : {}),
|
|
513
|
+
};
|
|
514
|
+
});
|
|
515
|
+
const text = results
|
|
516
|
+
.map((r) => {
|
|
517
|
+
const tail = r.successor !== undefined ? ` → use ${r.successor}` : "";
|
|
518
|
+
const provenance = r.recorded ? "" : " (not in snapshot)";
|
|
519
|
+
return `${r.name}: ${r.state}${r.objectType !== undefined ? ` (${r.objectType})` : ""}${provenance}${tail}`;
|
|
520
|
+
})
|
|
521
|
+
.join("\n");
|
|
522
|
+
return {
|
|
523
|
+
content: [{ type: "text", text: `Snapshot ${RELEASED_API_SNAPSHOT.snapshotDate}\n${text}` }],
|
|
524
|
+
structuredContent: {
|
|
525
|
+
snapshotDate: RELEASED_API_SNAPSHOT.snapshotDate,
|
|
526
|
+
source: RELEASED_API_SNAPSHOT.source,
|
|
527
|
+
results,
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
},
|
|
531
|
+
});
|
|
420
532
|
/** Every tool this server exposes. (`tools` alias = the registry-export shape @mcp-kit/lint discovers.) */
|
|
421
533
|
export const ALL_TOOLS = [
|
|
422
534
|
lintAbap,
|
|
423
535
|
checkCloudReadinessTool,
|
|
424
536
|
scaffoldRapBoTool,
|
|
537
|
+
checkReleasedApiTool,
|
|
425
538
|
listAbapRules,
|
|
426
539
|
explainAbapRule,
|
|
427
540
|
formatAbapTool,
|
package/dist/cli-commands.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare function cmdReadiness(argv: string[], io: CliIo): number;
|
|
|
17
17
|
export declare function cmdScaffold(argv: string[], io: CliIo): number;
|
|
18
18
|
export declare function cmdOutline(argv: string[], io: CliIo): number;
|
|
19
19
|
export declare function cmdExplain(argv: string[], io: CliIo): number;
|
|
20
|
+
export declare function cmdReleased(argv: string[], io: CliIo): number;
|
|
20
21
|
export declare function cmdRules(argv: string[], io: CliIo): number;
|
|
21
|
-
export declare const USAGE = "abap-mcp \u2014 SAP ABAP analysis for AI agents (MCP server) and humans (CLI)\n\nUsage:\n abap-mcp start the MCP server on stdio (for AI clients)\n abap-mcp lint [paths\u2026] lint files/dirs [--abap-version v758|Cloud] [--preset style|full|syntax-only] [--json]\n abap-mcp readiness [paths\u2026] ABAP Cloud readiness diff [--baseline v758] [--fail-below N] [--json]\n abap-mcp scaffold \u2026 generate a RAP managed BO (--entity --table --key [--fields n:type,\u2026] [--no-draft] [--provided-key] [--out DIR])\n abap-mcp outline [paths\u2026] classes/methods/forms structure [--json]\n abap-mcp explain <rule> explain an abaplint rule\n abap-mcp rules list rules [--query q] [--tag Security]\n\nExit codes: 0 ok \u00B7 1 findings/validation failed \u00B7 2 usage error";
|
|
22
|
+
export declare const USAGE = "abap-mcp \u2014 SAP ABAP analysis for AI agents (MCP server) and humans (CLI)\n\nUsage:\n abap-mcp start the MCP server on stdio (for AI clients)\n abap-mcp lint [paths\u2026] lint files/dirs [--abap-version v758|Cloud] [--preset style|full|syntax-only] [--json]\n abap-mcp readiness [paths\u2026] ABAP Cloud readiness diff [--baseline v758] [--fail-below N] [--json]\n abap-mcp scaffold \u2026 generate a RAP managed BO (--entity --table --key [--fields n:type,\u2026] [--no-draft] [--provided-key] [--out DIR])\n abap-mcp outline [paths\u2026] classes/methods/forms structure [--json]\n abap-mcp released <names\u2026> released-API status from the bundled SAP snapshot [--type TABL|FUNC|\u2026] [--json]\n abap-mcp explain <rule> explain an abaplint rule\n abap-mcp rules list rules [--query q] [--tag Security]\n\nExit codes: 0 ok \u00B7 1 findings/validation failed \u00B7 2 usage error";
|
|
22
23
|
export declare function runCli(argv: string[], io: CliIo): number | null;
|
package/dist/cli-commands.js
CHANGED
|
@@ -5,22 +5,30 @@
|
|
|
5
5
|
* story); the *CLI* is a local developer tool, so reading files from disk here
|
|
6
6
|
* is fine. Both call the identical engine — one definition of "clean".
|
|
7
7
|
*/
|
|
8
|
-
import { readdirSync, readFileSync, statSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
8
|
+
import { readdirSync, readFileSync, realpathSync, statSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
9
9
|
import { basename, extname, join } from "node:path";
|
|
10
10
|
import { ABAP_VERSIONS, MAX_FILES, runAbaplint } from "./abap/engine.js";
|
|
11
11
|
import { outlineAbap } from "./abap/outline.js";
|
|
12
12
|
import { checkCloudReadiness, SCOPE_NOTE } from "./abap/readiness.js";
|
|
13
|
+
import { lookupReleased, RELEASED_API_SNAPSHOT, suggestSuccessor } from "./abap/released.js";
|
|
13
14
|
import { explainRule, listRules } from "./abap/rules.js";
|
|
14
15
|
import { scaffoldRapBo } from "./abap/scaffold.js";
|
|
15
16
|
const ABAP_FILE_RE = /\.(clas\.abap|clas\.locals_imp\.abap|clas\.locals_def\.abap|clas\.testclasses\.abap|prog\.abap|intf\.abap|fugr\.abap|ddls\.asddls|bdef\.asbdef|srvd\.srvdsrv|ddlx\.asddlx)$/;
|
|
16
17
|
/** Recursively collect analyzable sources from file/dir paths. */
|
|
17
18
|
export function collectFiles(paths, io) {
|
|
18
19
|
const found = [];
|
|
20
|
+
const visitedDirs = new Set();
|
|
19
21
|
const visit = (p) => {
|
|
20
22
|
const st = statSync(p);
|
|
21
23
|
if (st.isDirectory()) {
|
|
22
24
|
if (basename(p) === ".git" || basename(p) === "node_modules")
|
|
23
25
|
return;
|
|
26
|
+
// statSync follows symlinks — track real paths so a symlink cycle
|
|
27
|
+
// can't recurse forever.
|
|
28
|
+
const real = realpathSync(p);
|
|
29
|
+
if (visitedDirs.has(real))
|
|
30
|
+
return;
|
|
31
|
+
visitedDirs.add(real);
|
|
24
32
|
for (const entry of readdirSync(p))
|
|
25
33
|
visit(join(p, entry));
|
|
26
34
|
return;
|
|
@@ -104,9 +112,13 @@ export function mergeReadiness(reports, baseline) {
|
|
|
104
112
|
const categories = new Map();
|
|
105
113
|
let blockers = 0;
|
|
106
114
|
const broken = [];
|
|
115
|
+
const releasedApiFindings = [];
|
|
116
|
+
let snapshotDate = "";
|
|
107
117
|
for (const r of reports) {
|
|
108
118
|
blockers += r.cloudBlockerCount;
|
|
109
119
|
broken.push(...r.brokenAtBaseline);
|
|
120
|
+
releasedApiFindings.push(...r.releasedApiFindings);
|
|
121
|
+
snapshotDate = r.releasedApiSnapshotDate;
|
|
110
122
|
for (const c of r.categories) {
|
|
111
123
|
const cur = categories.get(c.category);
|
|
112
124
|
if (cur === undefined)
|
|
@@ -131,6 +143,8 @@ export function mergeReadiness(reports, baseline) {
|
|
|
131
143
|
cloudBlockerCount: blockers,
|
|
132
144
|
categories: [...categories.values()].sort((a, b) => b.count - a.count),
|
|
133
145
|
brokenAtBaseline: broken,
|
|
146
|
+
releasedApiFindings,
|
|
147
|
+
releasedApiSnapshotDate: snapshotDate,
|
|
134
148
|
baselineVersion: baseline,
|
|
135
149
|
scopeNote: SCOPE_NOTE,
|
|
136
150
|
};
|
|
@@ -155,6 +169,11 @@ export function cmdReadiness(argv, io) {
|
|
|
155
169
|
io.out(` ${c.category.padEnd(18)} ${String(c.count).padStart(4)} ${c.label}`);
|
|
156
170
|
if (merged.brokenAtBaseline.length > 0)
|
|
157
171
|
io.out(`${merged.brokenAtBaseline.length} finding(s) broken at ${baseline} regardless (fix first; not migration work)`);
|
|
172
|
+
if (merged.releasedApiFindings.length > 0) {
|
|
173
|
+
io.out(`${merged.releasedApiFindings.length} released-API note(s) (snapshot ${merged.releasedApiSnapshotDate}; informational, not scored):`);
|
|
174
|
+
for (const f of merged.releasedApiFindings)
|
|
175
|
+
io.out(` ${f.file}:${f.line} [${f.state}] ${f.object}${f.successor !== undefined ? ` → ${f.successor}` : ""}`);
|
|
176
|
+
}
|
|
158
177
|
io.out(`Note: ${merged.scopeNote}`);
|
|
159
178
|
}
|
|
160
179
|
const failBelow = flags.get("fail-below");
|
|
@@ -202,7 +221,13 @@ export function cmdScaffold(argv, io) {
|
|
|
202
221
|
writeFileSync(target, f.content, "utf8");
|
|
203
222
|
io.out(`wrote ${target} [${f.validated}]`);
|
|
204
223
|
}
|
|
205
|
-
|
|
224
|
+
const suggestionTarget = join(outDir, `${sqlTable.toLowerCase()}.tabl.suggestion.txt`);
|
|
225
|
+
if (existsSync(suggestionTarget) && !flags.has("force")) {
|
|
226
|
+
io.err(`refusing to overwrite ${suggestionTarget} (use --force)`);
|
|
227
|
+
return 1;
|
|
228
|
+
}
|
|
229
|
+
writeFileSync(suggestionTarget, result.suggestedTableDdl, "utf8");
|
|
230
|
+
io.out(`wrote ${suggestionTarget}`);
|
|
206
231
|
}
|
|
207
232
|
else {
|
|
208
233
|
for (const f of result.files) {
|
|
@@ -257,6 +282,30 @@ export function cmdExplain(argv, io) {
|
|
|
257
282
|
io.out(`${d.key} — ${d.title}\n${d.shortDescription}\n${d.extendedInformation}\nDocs: ${d.docsUrl}`);
|
|
258
283
|
return 0;
|
|
259
284
|
}
|
|
285
|
+
export function cmdReleased(argv, io) {
|
|
286
|
+
const { flags, rest } = parseFlags(argv);
|
|
287
|
+
if (rest.length === 0) {
|
|
288
|
+
io.err("Usage: abap-mcp released <object-name…> [--type TABL|CDS_STOB|FUNC|…] [--json]");
|
|
289
|
+
return 2;
|
|
290
|
+
}
|
|
291
|
+
const type = typeof flags.get("type") === "string" ? flags.get("type") : undefined;
|
|
292
|
+
const results = rest.map((name) => {
|
|
293
|
+
const hit = lookupReleased(name, type);
|
|
294
|
+
const successor = suggestSuccessor(name);
|
|
295
|
+
return { ...hit, successor };
|
|
296
|
+
});
|
|
297
|
+
if (flags.has("json")) {
|
|
298
|
+
io.out(JSON.stringify({ snapshotDate: RELEASED_API_SNAPSHOT.snapshotDate, source: RELEASED_API_SNAPSHOT.source, results }, null, 2));
|
|
299
|
+
return 0;
|
|
300
|
+
}
|
|
301
|
+
io.out(`Released-API status (SAP Cloudification snapshot ${RELEASED_API_SNAPSHOT.snapshotDate}):`);
|
|
302
|
+
for (const r of results) {
|
|
303
|
+
const tail = r.successor !== undefined ? ` → use ${r.successor}` : "";
|
|
304
|
+
const provenance = r.recorded ? "" : " (not in snapshot)";
|
|
305
|
+
io.out(` ${r.name.padEnd(34)} ${r.state.padEnd(13)} ${(r.objectType ?? "").padEnd(9)}${provenance}${tail}`);
|
|
306
|
+
}
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
260
309
|
export function cmdRules(argv, io) {
|
|
261
310
|
const { flags } = parseFlags(argv);
|
|
262
311
|
const q = flags.get("query");
|
|
@@ -275,6 +324,7 @@ Usage:
|
|
|
275
324
|
abap-mcp readiness [paths…] ABAP Cloud readiness diff [--baseline v758] [--fail-below N] [--json]
|
|
276
325
|
abap-mcp scaffold … generate a RAP managed BO (--entity --table --key [--fields n:type,…] [--no-draft] [--provided-key] [--out DIR])
|
|
277
326
|
abap-mcp outline [paths…] classes/methods/forms structure [--json]
|
|
327
|
+
abap-mcp released <names…> released-API status from the bundled SAP snapshot [--type TABL|FUNC|…] [--json]
|
|
278
328
|
abap-mcp explain <rule> explain an abaplint rule
|
|
279
329
|
abap-mcp rules list rules [--query q] [--tag Security]
|
|
280
330
|
|
|
@@ -293,6 +343,8 @@ export function runCli(argv, io) {
|
|
|
293
343
|
return cmdScaffold(rest, io);
|
|
294
344
|
case "outline":
|
|
295
345
|
return cmdOutline(rest, io);
|
|
346
|
+
case "released":
|
|
347
|
+
return cmdReleased(rest, io);
|
|
296
348
|
case "explain":
|
|
297
349
|
return cmdExplain(rest, io);
|
|
298
350
|
case "rules":
|