abap-mcp 0.1.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/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/abap/engine.d.ts +46 -0
- package/dist/abap/engine.js +143 -0
- package/dist/abap/formatter.d.ts +1 -0
- package/dist/abap/formatter.js +38 -0
- package/dist/abap/outline.d.ts +25 -0
- package/dist/abap/outline.js +70 -0
- package/dist/abap/readiness.d.ts +33 -0
- package/dist/abap/readiness.js +56 -0
- package/dist/abap/rules.d.ts +15 -0
- package/dist/abap/rules.js +51 -0
- package/dist/abap/scaffold.d.ts +55 -0
- package/dist/abap/scaffold.js +241 -0
- package/dist/abap.tools.d.ts +87 -0
- package/dist/abap.tools.js +435 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +16 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.js +29 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +11 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +11 -0
- package/dist/tool.d.ts +36 -0
- package/dist/tool.js +33 -0
- package/package.json +54 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { runAbaplint } from "./engine.js";
|
|
2
|
+
const ADMIN_FIELDS = [
|
|
3
|
+
{ name: "created_by", cds: "CreatedBy", semantics: "@Semantics.user.createdBy: true", ddlType: "abp_creation_user" },
|
|
4
|
+
{ name: "created_at", cds: "CreatedAt", semantics: "@Semantics.systemDateTime.createdAt: true", ddlType: "abp_creation_tstmpl" },
|
|
5
|
+
{ name: "last_changed_by", cds: "LastChangedBy", semantics: "@Semantics.user.lastChangedBy: true", ddlType: "abp_lastchange_user" },
|
|
6
|
+
{ name: "last_changed_at", cds: "LastChangedAt", semantics: "@Semantics.systemDateTime.lastChangedAt: true", ddlType: "abp_lastchange_tstmpl" },
|
|
7
|
+
{ name: "local_last_changed_at", cds: "LocalLastChangedAt", semantics: "@Semantics.systemDateTime.localInstanceLastChangedAt: true", ddlType: "abp_locinst_lastchange_tstmpl" },
|
|
8
|
+
];
|
|
9
|
+
export function snakeToCamel(snake) {
|
|
10
|
+
return snake
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.split("_")
|
|
13
|
+
.filter((p) => p.length > 0)
|
|
14
|
+
.map((p) => (p[0] ?? "").toUpperCase() + p.slice(1))
|
|
15
|
+
.join("");
|
|
16
|
+
}
|
|
17
|
+
function validateInput(opts) {
|
|
18
|
+
if (!/^[A-Za-z][A-Za-z0-9]{0,25}$/.test(opts.entityName))
|
|
19
|
+
throw new Error(`entityName "${opts.entityName}" must be alphanumeric UpperCamelCase, ≤26 chars.`);
|
|
20
|
+
if (!/^[a-z][a-z0-9_]{0,15}$/.test(opts.sqlTable.toLowerCase()))
|
|
21
|
+
throw new Error(`sqlTable "${opts.sqlTable}" must be a valid table name (≤16 chars).`);
|
|
22
|
+
if (!new RegExp(`^${opts.prefix.toLowerCase()}`).test(opts.sqlTable.toLowerCase()))
|
|
23
|
+
throw new Error(`sqlTable "${opts.sqlTable}" must start with the ${opts.prefix} namespace prefix.`);
|
|
24
|
+
const fieldRe = /^[a-z][a-z0-9_]{0,29}$/;
|
|
25
|
+
if (!fieldRe.test(opts.keyField.toLowerCase()))
|
|
26
|
+
throw new Error(`keyField "${opts.keyField}" is not a valid field name.`);
|
|
27
|
+
const seen = new Set([opts.keyField.toLowerCase()]);
|
|
28
|
+
const reserved = new Set(ADMIN_FIELDS.map((a) => a.name));
|
|
29
|
+
if (reserved.has(opts.keyField.toLowerCase()))
|
|
30
|
+
throw new Error(`keyField "${opts.keyField}" collides with a generated admin field.`);
|
|
31
|
+
for (const f of opts.fields) {
|
|
32
|
+
const n = f.name.toLowerCase();
|
|
33
|
+
if (!fieldRe.test(n))
|
|
34
|
+
throw new Error(`Field "${f.name}" is not a valid field name.`);
|
|
35
|
+
if (seen.has(n))
|
|
36
|
+
throw new Error(`Duplicate field "${f.name}".`);
|
|
37
|
+
if (reserved.has(n))
|
|
38
|
+
throw new Error(`Field "${f.name}" collides with a generated admin field (${[...reserved].join(", ")} are added automatically).`);
|
|
39
|
+
seen.add(n);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function scaffoldRapBo(opts) {
|
|
43
|
+
validateInput(opts);
|
|
44
|
+
const p = opts.prefix.toUpperCase();
|
|
45
|
+
const entity = opts.entityName.charAt(0).toUpperCase() + opts.entityName.slice(1);
|
|
46
|
+
const table = opts.sqlTable.toLowerCase();
|
|
47
|
+
const draftTable = `${table}_d`;
|
|
48
|
+
const rootView = `${p}R_${entity}`;
|
|
49
|
+
const projView = `${p}C_${entity}`;
|
|
50
|
+
const behaviorClass = `${p}BP_${entity}`.toLowerCase();
|
|
51
|
+
const serviceDef = `${p}UI_${entity.toUpperCase()}_V4`;
|
|
52
|
+
const alias = entity;
|
|
53
|
+
const keyCds = snakeToCamel(opts.keyField);
|
|
54
|
+
const userFields = opts.fields.map((f) => ({
|
|
55
|
+
name: f.name.toLowerCase(),
|
|
56
|
+
cds: snakeToCamel(f.name),
|
|
57
|
+
ddlType: f.type ?? "abap.char(30)",
|
|
58
|
+
}));
|
|
59
|
+
// ---- root view entity --------------------------------------------------
|
|
60
|
+
const rootFieldLines = [];
|
|
61
|
+
rootFieldLines.push(` key ${opts.keyField.toLowerCase()} as ${keyCds},`);
|
|
62
|
+
for (const f of userFields)
|
|
63
|
+
rootFieldLines.push(` ${f.name} as ${f.cds},`);
|
|
64
|
+
for (const a of ADMIN_FIELDS) {
|
|
65
|
+
rootFieldLines.push(` ${a.semantics}`);
|
|
66
|
+
rootFieldLines.push(` ${a.name} as ${a.cds},`);
|
|
67
|
+
}
|
|
68
|
+
const last = rootFieldLines.pop();
|
|
69
|
+
if (last !== undefined)
|
|
70
|
+
rootFieldLines.push(last.replace(/,$/, ""));
|
|
71
|
+
const rootDdls = `@AccessControl.authorizationCheck: #NOT_REQUIRED
|
|
72
|
+
@EndUserText.label: '${entity} - root view'
|
|
73
|
+
define root view entity ${rootView}
|
|
74
|
+
as select from ${table}
|
|
75
|
+
{
|
|
76
|
+
${rootFieldLines.join("\n")}
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
// ---- root behavior definition -----------------------------------------
|
|
80
|
+
const allCds = [keyCds, ...userFields.map((f) => f.cds), ...ADMIN_FIELDS.map((a) => a.cds)];
|
|
81
|
+
const mappingLines = [
|
|
82
|
+
` ${keyCds} = ${opts.keyField.toLowerCase()};`,
|
|
83
|
+
...userFields.map((f) => ` ${f.cds} = ${f.name};`),
|
|
84
|
+
...ADMIN_FIELDS.map((a) => ` ${a.cds} = ${a.name};`),
|
|
85
|
+
];
|
|
86
|
+
const keyFieldLine = opts.managedUuidKey
|
|
87
|
+
? ` field ( numbering : managed, readonly ) ${keyCds};`
|
|
88
|
+
: ` field ( readonly : update ) ${keyCds};`;
|
|
89
|
+
const readonlyAdmins = ` field ( readonly ) ${ADMIN_FIELDS.map((a) => a.cds).join(", ")};`;
|
|
90
|
+
const rootBdef = `managed implementation in class ${behaviorClass} unique;
|
|
91
|
+
strict ( 2 );${opts.draft ? "\nwith draft;" : ""}
|
|
92
|
+
|
|
93
|
+
define behavior for ${rootView} alias ${alias}
|
|
94
|
+
persistent table ${table}${opts.draft ? `\ndraft table ${draftTable}` : ""}
|
|
95
|
+
etag master LocalLastChangedAt
|
|
96
|
+
lock master${opts.draft ? " total etag LastChangedAt" : ""}
|
|
97
|
+
authorization master ( instance )
|
|
98
|
+
{
|
|
99
|
+
create;
|
|
100
|
+
update;
|
|
101
|
+
delete;
|
|
102
|
+
|
|
103
|
+
${keyFieldLine}
|
|
104
|
+
${readonlyAdmins}
|
|
105
|
+
${opts.draft
|
|
106
|
+
? `
|
|
107
|
+
draft action Edit;
|
|
108
|
+
draft action Resume;
|
|
109
|
+
draft action Activate optimized;
|
|
110
|
+
draft action Discard;
|
|
111
|
+
draft determine action Prepare;
|
|
112
|
+
`
|
|
113
|
+
: ""}
|
|
114
|
+
mapping for ${table}
|
|
115
|
+
{
|
|
116
|
+
${mappingLines.join("\n")}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
// ---- behavior implementation class ------------------------------------
|
|
121
|
+
const clasMain = `CLASS ${behaviorClass} DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF ${rootView.toLowerCase()}.
|
|
122
|
+
ENDCLASS.
|
|
123
|
+
|
|
124
|
+
CLASS ${behaviorClass} IMPLEMENTATION.
|
|
125
|
+
ENDCLASS.
|
|
126
|
+
`;
|
|
127
|
+
const clasLocals = `CLASS lhc_${entity.toLowerCase()} DEFINITION INHERITING FROM cl_abap_behavior_handler.
|
|
128
|
+
PRIVATE SECTION.
|
|
129
|
+
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
|
|
130
|
+
IMPORTING keys REQUEST requested_authorizations FOR ${alias} RESULT result.
|
|
131
|
+
ENDCLASS.
|
|
132
|
+
|
|
133
|
+
CLASS lhc_${entity.toLowerCase()} IMPLEMENTATION.
|
|
134
|
+
|
|
135
|
+
METHOD get_instance_authorizations.
|
|
136
|
+
" Grant everything until real authorization objects exist.
|
|
137
|
+
ENDMETHOD.
|
|
138
|
+
|
|
139
|
+
ENDCLASS.
|
|
140
|
+
`;
|
|
141
|
+
// ---- projection --------------------------------------------------------
|
|
142
|
+
const projFieldLines = [
|
|
143
|
+
` key ${keyCds},`,
|
|
144
|
+
...userFields.map((f) => ` ${f.cds},`),
|
|
145
|
+
...ADMIN_FIELDS.map((a) => ` ${a.cds},`),
|
|
146
|
+
];
|
|
147
|
+
const lastProj = projFieldLines.pop();
|
|
148
|
+
if (lastProj !== undefined)
|
|
149
|
+
projFieldLines.push(lastProj.replace(/,$/, ""));
|
|
150
|
+
const projDdls = `@AccessControl.authorizationCheck: #NOT_REQUIRED
|
|
151
|
+
@EndUserText.label: '${entity} - projection'
|
|
152
|
+
@Metadata.allowExtensions: true
|
|
153
|
+
define root view entity ${projView}
|
|
154
|
+
provider contract transactional_query
|
|
155
|
+
as projection on ${rootView}
|
|
156
|
+
{
|
|
157
|
+
${projFieldLines.join("\n")}
|
|
158
|
+
}
|
|
159
|
+
`;
|
|
160
|
+
const projBdef = `projection;
|
|
161
|
+
strict ( 2 );${opts.draft ? "\nuse draft;" : ""}
|
|
162
|
+
|
|
163
|
+
define behavior for ${projView} alias ${alias}
|
|
164
|
+
{
|
|
165
|
+
use create;
|
|
166
|
+
use update;
|
|
167
|
+
use delete;
|
|
168
|
+
}
|
|
169
|
+
`;
|
|
170
|
+
// ---- metadata extension -------------------------------------------------
|
|
171
|
+
const uiFields = [keyCds, ...userFields.map((f) => f.cds)];
|
|
172
|
+
const ddlxFieldBlocks = uiFields
|
|
173
|
+
.map((f, i) => ` @UI: { lineItem: [ { position: ${(i + 1) * 10} } ], identification: [ { position: ${(i + 1) * 10} } ] }\n ${f};`)
|
|
174
|
+
.join("\n");
|
|
175
|
+
const ddlx = `@Metadata.layer: #CORE
|
|
176
|
+
annotate view ${projView} with
|
|
177
|
+
{
|
|
178
|
+
@UI.facet: [ {
|
|
179
|
+
id: 'id${entity}',
|
|
180
|
+
type: #IDENTIFICATION_REFERENCE,
|
|
181
|
+
label: '${entity}',
|
|
182
|
+
position: 10
|
|
183
|
+
} ]
|
|
184
|
+
${ddlxFieldBlocks}
|
|
185
|
+
}
|
|
186
|
+
`;
|
|
187
|
+
// ---- service definition -------------------------------------------------
|
|
188
|
+
const srvd = `@EndUserText.label: 'Service definition for ${entity}'
|
|
189
|
+
define service ${serviceDef} {
|
|
190
|
+
expose ${projView} as ${alias};
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
// ---- suggested table DDL (guidance, dev adjusts types) -------------------
|
|
194
|
+
const keyDdlType = opts.managedUuidKey ? "sysuuid_x16" : "abap.char(20)";
|
|
195
|
+
const tableLines = [
|
|
196
|
+
`define table ${table} {`,
|
|
197
|
+
` key client : abap.clnt not null;`,
|
|
198
|
+
` key ${opts.keyField.toLowerCase().padEnd(17)} : ${keyDdlType} not null;`,
|
|
199
|
+
...userFields.map((f) => ` ${f.name.padEnd(21)} : ${f.ddlType};`),
|
|
200
|
+
...ADMIN_FIELDS.map((a) => ` ${a.name.padEnd(21)} : ${a.ddlType};`),
|
|
201
|
+
`}`,
|
|
202
|
+
];
|
|
203
|
+
const suggestedTableDdl = tableLines.join("\n");
|
|
204
|
+
const files = [
|
|
205
|
+
{ filename: `${rootView.toLowerCase()}.ddls.asddls`, content: rootDdls, validated: "abaplint" },
|
|
206
|
+
{ filename: `${rootView.toLowerCase()}.bdef.asbdef`, content: rootBdef, validated: "template" },
|
|
207
|
+
{ filename: `${behaviorClass}.clas.abap`, content: clasMain, validated: "abaplint" },
|
|
208
|
+
{ filename: `${behaviorClass}.clas.locals_imp.abap`, content: clasLocals, validated: "abaplint" },
|
|
209
|
+
{ filename: `${projView.toLowerCase()}.ddls.asddls`, content: projDdls, validated: "abaplint" },
|
|
210
|
+
{ filename: `${projView.toLowerCase()}.bdef.asbdef`, content: projBdef, validated: "template" },
|
|
211
|
+
{ filename: `${projView.toLowerCase()}.ddlx.asddlx`, content: ddlx, validated: "template" },
|
|
212
|
+
{ filename: `${serviceDef.toLowerCase()}.srvd.srvdsrv`, content: srvd, validated: "template" },
|
|
213
|
+
];
|
|
214
|
+
// Round-trip the machine-checkable artifacts through abaplint at Cloud.
|
|
215
|
+
const checkable = files.filter((f) => f.validated === "abaplint");
|
|
216
|
+
const { findings } = runAbaplint(checkable.map((f) => ({ filename: f.filename, source: f.content })), { version: "Cloud", preset: "syntax-only" });
|
|
217
|
+
const activationOrder = [
|
|
218
|
+
`Table ${table} (see suggestedTableDdl; adjust field types)`,
|
|
219
|
+
...(opts.draft ? [`Draft table ${draftTable} (ADT quick-fix on the behavior definition generates it)`] : []),
|
|
220
|
+
`${rootView} (root view entity)`,
|
|
221
|
+
`${rootView} behavior definition`,
|
|
222
|
+
`${behaviorClass.toUpperCase()} (behavior implementation)`,
|
|
223
|
+
`${projView} (projection view)`,
|
|
224
|
+
`${projView} behavior definition`,
|
|
225
|
+
`${projView} metadata extension`,
|
|
226
|
+
`${serviceDef} (service definition)`,
|
|
227
|
+
`Service binding (create in ADT: OData V4 - UI, then Publish)`,
|
|
228
|
+
];
|
|
229
|
+
const nextSteps = [
|
|
230
|
+
"Create the persistent table first — the CDS view selects from it (suggestedTableDdl is a starting point; adjust types and lengths).",
|
|
231
|
+
opts.draft
|
|
232
|
+
? "After activating the behavior definition, use the ADT quick-fix to generate the draft table, or create it as a copy of the table with draft admin include."
|
|
233
|
+
: "Draft is disabled; re-run with draft:true if you want Fiori draft handling.",
|
|
234
|
+
opts.managedUuidKey
|
|
235
|
+
? "The key uses managed UUID numbering — no number ranges needed; the framework fills it on create."
|
|
236
|
+
: "The key is caller-provided on create (readonly:update). Add a validation if you need format checks.",
|
|
237
|
+
"The service binding cannot be generated as source — create it in ADT (New > Service Binding, OData V4 - UI) on the service definition, then Publish.",
|
|
238
|
+
"Generated classes and CDS views were machine-validated through abaplint at ABAP-Cloud level; behavior/service definitions are canonical templates — ADT activation is the final arbiter.",
|
|
239
|
+
];
|
|
240
|
+
return { files, activationOrder, nextSteps, suggestedTableDdl, validationIssues: findings };
|
|
241
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The abap-mcp tool registry.
|
|
3
|
+
*
|
|
4
|
+
* Descriptions follow the mcp-kit rubric: verb-first snake_case name, what
|
|
5
|
+
* the tool operates on, an explicit "Use this when …", explicit non-goals,
|
|
6
|
+
* every parameter described, at least one worked example. The model's only
|
|
7
|
+
* documentation is this file — treat it as the public API surface.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import type { AnyToolSpec } from "./tool.js";
|
|
11
|
+
export declare const lintAbap: import("./tool.js").ToolSpec<{
|
|
12
|
+
files: z.ZodArray<z.ZodObject<{
|
|
13
|
+
filename: z.ZodOptional<z.ZodString>;
|
|
14
|
+
source: z.ZodString;
|
|
15
|
+
}, z.core.$strip>>;
|
|
16
|
+
abapVersion: z.ZodDefault<z.ZodEnum<{
|
|
17
|
+
Cloud: "Cloud";
|
|
18
|
+
v750: "v750";
|
|
19
|
+
v751: "v751";
|
|
20
|
+
v752: "v752";
|
|
21
|
+
v753: "v753";
|
|
22
|
+
v754: "v754";
|
|
23
|
+
v755: "v755";
|
|
24
|
+
v756: "v756";
|
|
25
|
+
v757: "v757";
|
|
26
|
+
v758: "v758";
|
|
27
|
+
}>>;
|
|
28
|
+
preset: z.ZodDefault<z.ZodEnum<{
|
|
29
|
+
style: "style";
|
|
30
|
+
full: "full";
|
|
31
|
+
"syntax-only": "syntax-only";
|
|
32
|
+
}>>;
|
|
33
|
+
rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
34
|
+
}>;
|
|
35
|
+
export declare const checkCloudReadinessTool: import("./tool.js").ToolSpec<{
|
|
36
|
+
files: z.ZodArray<z.ZodObject<{
|
|
37
|
+
filename: z.ZodOptional<z.ZodString>;
|
|
38
|
+
source: z.ZodString;
|
|
39
|
+
}, z.core.$strip>>;
|
|
40
|
+
baselineVersion: z.ZodDefault<z.ZodEnum<{
|
|
41
|
+
Cloud: "Cloud";
|
|
42
|
+
v750: "v750";
|
|
43
|
+
v751: "v751";
|
|
44
|
+
v752: "v752";
|
|
45
|
+
v753: "v753";
|
|
46
|
+
v754: "v754";
|
|
47
|
+
v755: "v755";
|
|
48
|
+
v756: "v756";
|
|
49
|
+
v757: "v757";
|
|
50
|
+
v758: "v758";
|
|
51
|
+
}>>;
|
|
52
|
+
}>;
|
|
53
|
+
export declare const scaffoldRapBoTool: import("./tool.js").ToolSpec<{
|
|
54
|
+
entityName: z.ZodString;
|
|
55
|
+
sqlTable: z.ZodString;
|
|
56
|
+
keyField: z.ZodString;
|
|
57
|
+
managedUuidKey: z.ZodDefault<z.ZodBoolean>;
|
|
58
|
+
fields: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
59
|
+
name: z.ZodString;
|
|
60
|
+
type: z.ZodOptional<z.ZodString>;
|
|
61
|
+
}, z.core.$strip>>>;
|
|
62
|
+
draft: z.ZodDefault<z.ZodBoolean>;
|
|
63
|
+
prefix: z.ZodDefault<z.ZodEnum<{
|
|
64
|
+
Z: "Z";
|
|
65
|
+
Y: "Y";
|
|
66
|
+
}>>;
|
|
67
|
+
}>;
|
|
68
|
+
export declare const listAbapRules: import("./tool.js").ToolSpec<{
|
|
69
|
+
query: z.ZodOptional<z.ZodString>;
|
|
70
|
+
tag: z.ZodOptional<z.ZodString>;
|
|
71
|
+
}>;
|
|
72
|
+
export declare const explainAbapRule: import("./tool.js").ToolSpec<{
|
|
73
|
+
rule: z.ZodString;
|
|
74
|
+
}>;
|
|
75
|
+
export declare const formatAbapTool: import("./tool.js").ToolSpec<{
|
|
76
|
+
source: z.ZodString;
|
|
77
|
+
filename: z.ZodOptional<z.ZodString>;
|
|
78
|
+
}>;
|
|
79
|
+
export declare const getAbapOutline: import("./tool.js").ToolSpec<{
|
|
80
|
+
files: z.ZodArray<z.ZodObject<{
|
|
81
|
+
filename: z.ZodOptional<z.ZodString>;
|
|
82
|
+
source: z.ZodString;
|
|
83
|
+
}, z.core.$strip>>;
|
|
84
|
+
}>;
|
|
85
|
+
/** Every tool this server exposes. (`tools` alias = the registry-export shape @mcp-kit/lint discovers.) */
|
|
86
|
+
export declare const ALL_TOOLS: readonly AnyToolSpec[];
|
|
87
|
+
export declare const tools: readonly AnyToolSpec[];
|