sap-adt-mcp 0.8.41 → 0.8.43
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/package.json +1 -1
- package/src/object-create.js +42 -0
- package/src/object-uris.js +7 -2
- package/src/tools/_shared.js +1 -1
- package/src/tools/lifecycle.js +4 -6
- package/src/tools/rap.js +5 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sap-adt-mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.43",
|
|
4
4
|
"mcpName": "io.github.yzonur/sap-adt-mcp",
|
|
5
5
|
"description": "MCP server giving Claude live access to SAP systems via ADT (ABAP Development Tools) REST. Read source, search, run syntax checks, and edit ABAP objects from any MCP-compatible client.",
|
|
6
6
|
"type": "module",
|
package/src/object-create.js
CHANGED
|
@@ -201,6 +201,48 @@ export function buildCreateRequest({
|
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
// Most ADT create endpoints version their media type (…v2+xml, …v3+xml). The
|
|
205
|
+
// shapes above target the newest version a modern stack accepts, but older
|
|
206
|
+
// systems only know an earlier one and answer the newer media type with
|
|
207
|
+
// 415 ExceptionUnsupportedMediaType. Given a content-type, return the chain to
|
|
208
|
+
// try: the requested version first, then every lower version down to v1.
|
|
209
|
+
// A content-type with no `.vN+xml` segment is returned unchanged (single item).
|
|
210
|
+
export function mediaTypeFallbacks(contentType) {
|
|
211
|
+
const m = /^(.*\.)v(\d+)(\+xml)$/.exec(contentType);
|
|
212
|
+
if (!m) return [contentType];
|
|
213
|
+
const [, prefix, ver, suffix] = m;
|
|
214
|
+
const chain = [];
|
|
215
|
+
for (let v = Number(ver); v >= 1; v--) {
|
|
216
|
+
chain.push(`${prefix}v${v}${suffix}`);
|
|
217
|
+
}
|
|
218
|
+
return chain;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// POST a create request, retrying with successively lower media-type versions
|
|
222
|
+
// when the server rejects the content-type with 415. Returns
|
|
223
|
+
// { res, text, contentType } for the first attempt that is not a 415 — or the
|
|
224
|
+
// last attempt if every version is rejected. The body is read once here so
|
|
225
|
+
// callers must use the returned `text` rather than calling res.text() again.
|
|
226
|
+
export async function postCreate(client, { path, contentType, body, query }) {
|
|
227
|
+
const types = mediaTypeFallbacks(contentType);
|
|
228
|
+
let res;
|
|
229
|
+
let text;
|
|
230
|
+
let used;
|
|
231
|
+
for (let i = 0; i < types.length; i++) {
|
|
232
|
+
used = types[i];
|
|
233
|
+
res = await client.request({
|
|
234
|
+
method: "POST",
|
|
235
|
+
path,
|
|
236
|
+
query,
|
|
237
|
+
headers: { "Content-Type": used },
|
|
238
|
+
body,
|
|
239
|
+
});
|
|
240
|
+
text = await res.text();
|
|
241
|
+
if (res.status !== 415 || i === types.length - 1) break;
|
|
242
|
+
}
|
|
243
|
+
return { res, text, contentType: used };
|
|
244
|
+
}
|
|
245
|
+
|
|
204
246
|
function xmlDecl() {
|
|
205
247
|
return `<?xml version="1.0" encoding="UTF-8"?>`;
|
|
206
248
|
}
|
package/src/object-uris.js
CHANGED
|
@@ -25,7 +25,10 @@ const TYPE_ALIASES = {
|
|
|
25
25
|
fugr: "FUGR",
|
|
26
26
|
table: "TABL",
|
|
27
27
|
tabl: "TABL",
|
|
28
|
-
|
|
28
|
+
// Structures live at their own ADT endpoint (/ddic/structures), NOT
|
|
29
|
+
// /ddic/tables — routing them to TABL sent reads/locks to the wrong URI.
|
|
30
|
+
structure: "STRU",
|
|
31
|
+
stru: "STRU",
|
|
29
32
|
dataelement: "DTEL",
|
|
30
33
|
dtel: "DTEL",
|
|
31
34
|
domain: "DOMA",
|
|
@@ -105,6 +108,8 @@ export function objectUri({ type, name, group }) {
|
|
|
105
108
|
}
|
|
106
109
|
case "TABL":
|
|
107
110
|
return `/sap/bc/adt/ddic/tables/${enc(n)}`;
|
|
111
|
+
case "STRU":
|
|
112
|
+
return `/sap/bc/adt/ddic/structures/${enc(n)}`;
|
|
108
113
|
case "DTEL":
|
|
109
114
|
return `/sap/bc/adt/ddic/dataelements/${enc(n)}`;
|
|
110
115
|
case "DOMA":
|
|
@@ -160,7 +165,7 @@ export function sourceUri({ type, name, group, include }) {
|
|
|
160
165
|
// via the object URI directly, returned as XML. CDS / DCLS / DDLX / BDEF do
|
|
161
166
|
// use /source/main.
|
|
162
167
|
if (t === "DTEL" || t === "DOMA" || t === "MSAG") return base;
|
|
163
|
-
if (t === "TABL") return `${base}/source/main`;
|
|
168
|
+
if (t === "TABL" || t === "STRU") return `${base}/source/main`;
|
|
164
169
|
|
|
165
170
|
return `${base}/source/main`;
|
|
166
171
|
}
|
package/src/tools/_shared.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const OBJECT_TYPE_HINT =
|
|
2
|
-
"Object type — friendly alias (program, class, interface, function, functiongroup, include, table, dataelement, domain, cds) or TADIR code (PROG, CLAS, INTF, FUGR, FUGR/FF, INCL, TABL, DTEL, DOMA, DDLS).";
|
|
2
|
+
"Object type — friendly alias (program, class, interface, function, functiongroup, include, table, structure, dataelement, domain, cds) or TADIR code (PROG, CLAS, INTF, FUGR, FUGR/FF, INCL, TABL, DTEL, DOMA, DDLS).";
|
|
3
3
|
|
|
4
4
|
export const SYSTEM_HINT = "System name. Omit for default.";
|
|
5
5
|
|
package/src/tools/lifecycle.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { objectUri, normalizeType } from "../object-uris.js";
|
|
2
2
|
import { escapeXml } from "../xml.js";
|
|
3
3
|
import { acquireLock, releaseLock } from "../lock.js";
|
|
4
|
-
import { buildCreateRequest } from "../object-create.js";
|
|
4
|
+
import { buildCreateRequest, postCreate } from "../object-create.js";
|
|
5
5
|
import { errorResult, jsonResult, textResult } from "../result.js";
|
|
6
6
|
import { OBJECT_TYPE_HINT, SYSTEM_HINT } from "./_shared.js";
|
|
7
7
|
|
|
@@ -199,14 +199,12 @@ export function register({ getClient }) {
|
|
|
199
199
|
}
|
|
200
200
|
const query = {};
|
|
201
201
|
if (args.transport) query.corrNr = args.transport;
|
|
202
|
-
const res = await client
|
|
203
|
-
method: "POST",
|
|
202
|
+
const { res, text } = await postCreate(client, {
|
|
204
203
|
path: req.path,
|
|
205
|
-
|
|
206
|
-
headers: { "Content-Type": req.contentType },
|
|
204
|
+
contentType: req.contentType,
|
|
207
205
|
body: req.body,
|
|
206
|
+
query,
|
|
208
207
|
});
|
|
209
|
-
const text = await res.text();
|
|
210
208
|
if (!res.ok) return errorResult(sys, res.status, text, res.headers.get("content-type"));
|
|
211
209
|
const newUri = objectUri({
|
|
212
210
|
type: args.type,
|
package/src/tools/rap.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildCreateRequest } from "../object-create.js";
|
|
1
|
+
import { buildCreateRequest, postCreate } from "../object-create.js";
|
|
2
2
|
import { sourceUri } from "../object-uris.js";
|
|
3
3
|
import { acquireLock, releaseLock } from "../lock.js";
|
|
4
4
|
import { escapeXml } from "../xml.js";
|
|
@@ -159,15 +159,13 @@ function createInfoFor(artifact, spec) {
|
|
|
159
159
|
|
|
160
160
|
async function createAndWrite(client, artifact, spec) {
|
|
161
161
|
const info = createInfoFor(artifact, spec);
|
|
162
|
-
// 1) create the object
|
|
163
|
-
const createRes = await client
|
|
164
|
-
method: "POST",
|
|
162
|
+
// 1) create the object (retrying with lower media-type versions on 415)
|
|
163
|
+
const { res: createRes, text: createText } = await postCreate(client, {
|
|
165
164
|
path: info.path,
|
|
166
|
-
|
|
167
|
-
query: spec.transport ? { corrNr: spec.transport } : undefined,
|
|
165
|
+
contentType: info.contentType,
|
|
168
166
|
body: info.body,
|
|
167
|
+
query: spec.transport ? { corrNr: spec.transport } : undefined,
|
|
169
168
|
});
|
|
170
|
-
const createText = await createRes.text();
|
|
171
169
|
if (!createRes.ok) {
|
|
172
170
|
return { name: artifact.name, stage: "create", status: createRes.status, error: createText.slice(0, 400) };
|
|
173
171
|
}
|