sap-adt-mcp 0.8.47 → 0.8.49
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 +2 -2
- package/src/adt-client.js +8 -1
- package/src/config.js +34 -7
- package/src/reporter.js +8 -2
- package/src/tools/cds.js +4 -1
- package/src/tools/quality.js +29 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sap-adt-mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.49",
|
|
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",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"start": "node src/server.js",
|
|
24
|
-
"test": "node --test test/adt-error.test.js test/atc-bulk.test.js test/audit.test.js test/cds.test.js test/data-preview.test.js test/diff.test.js test/dump-feed.test.js test/grep-source.test.js test/jobs.test.js test/lifecycle-activate.test.js test/lock.test.js test/mcp-bug-fixes.test.js test/node-structure.test.js test/notes.test.js test/object-create.test.js test/object-references.test.js test/object-uris.test.js test/panel.test.js test/prompts.test.js test/provenance.test.js test/rap-scaffold.test.js test/reporter.test.js test/security.test.js test/source-chunked.test.js test/source-guard.test.js test/source-pagination.test.js test/syntax-context.test.js test/tools-shape.test.js test/versions.test.js test/worklist.test.js",
|
|
24
|
+
"test": "node --test test/adt-error.test.js test/atc-bulk.test.js test/audit.test.js test/cds.test.js test/config.test.js test/data-preview.test.js test/diff.test.js test/dump-feed.test.js test/grep-source.test.js test/jobs.test.js test/lifecycle-activate.test.js test/lock.test.js test/mcp-bug-fixes.test.js test/node-structure.test.js test/notes.test.js test/object-create.test.js test/object-references.test.js test/object-uris.test.js test/panel.test.js test/prompts.test.js test/provenance.test.js test/rap-scaffold.test.js test/reporter.test.js test/security.test.js test/source-chunked.test.js test/source-guard.test.js test/source-pagination.test.js test/syntax-context.test.js test/tools-shape.test.js test/versions.test.js test/worklist.test.js",
|
|
25
25
|
"lint": "eslint src test"
|
|
26
26
|
},
|
|
27
27
|
"keywords": [
|
package/src/adt-client.js
CHANGED
|
@@ -168,8 +168,15 @@ export class AdtClient {
|
|
|
168
168
|
const token = res.headers.get("x-csrf-token");
|
|
169
169
|
if (!token || token.toLowerCase() === "required") {
|
|
170
170
|
const body = await res.text().catch(() => "");
|
|
171
|
+
// An HTML body means the host answered with a web page (SSO/login form or a
|
|
172
|
+
// web-dispatcher error), not the ADT service — usually a wrong host/system
|
|
173
|
+
// or a system fronted by SSO that basic auth can't pass.
|
|
174
|
+
const looksHtml = /^\s*<(!doctype html|html\b)/i.test(body);
|
|
175
|
+
const hint = looksHtml
|
|
176
|
+
? " — the host returned an HTML page (likely an SSO/login page or wrong host), not the ADT service. Check the system's host and that it accepts basic-auth ADT access."
|
|
177
|
+
: "";
|
|
171
178
|
throw new Error(
|
|
172
|
-
`Failed to fetch CSRF token (status ${res.status}): ${body.slice(0, 200)}`
|
|
179
|
+
`Failed to fetch CSRF token (status ${res.status})${hint}: ${body.slice(0, 200)}`
|
|
173
180
|
);
|
|
174
181
|
}
|
|
175
182
|
this.csrfToken = token;
|
package/src/config.js
CHANGED
|
@@ -2,14 +2,18 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
// Resolved at call time (not import time) so SAP_ADT_MCP_CONFIG is honoured
|
|
6
|
+
// whenever loadConfig runs, not just whatever it was when this module loaded.
|
|
7
|
+
function candidatePaths() {
|
|
8
|
+
return [
|
|
9
|
+
process.env.SAP_ADT_MCP_CONFIG,
|
|
10
|
+
path.join(os.homedir(), ".sap-adt-mcp", "config.json"),
|
|
11
|
+
path.join(process.cwd(), "config.json"),
|
|
12
|
+
].filter(Boolean);
|
|
13
|
+
}
|
|
10
14
|
|
|
11
15
|
export function loadConfig() {
|
|
12
|
-
const configPath =
|
|
16
|
+
const configPath = candidatePaths().find((p) => fs.existsSync(p));
|
|
13
17
|
if (!configPath) {
|
|
14
18
|
throw new Error(
|
|
15
19
|
"No config found. Set SAP_ADT_MCP_CONFIG or create ~/.sap-adt-mcp/config.json " +
|
|
@@ -31,7 +35,7 @@ export function loadConfig() {
|
|
|
31
35
|
const systems = {};
|
|
32
36
|
for (const [name, profile] of Object.entries(raw.systems ?? {})) {
|
|
33
37
|
systems[name] = {
|
|
34
|
-
host:
|
|
38
|
+
host: requireHost(profile.host, name),
|
|
35
39
|
client: profile.client != null ? String(profile.client) : undefined,
|
|
36
40
|
language: profile.language != null ? String(profile.language) : undefined,
|
|
37
41
|
user: requireString(profile.user, `${name}.user`),
|
|
@@ -122,6 +126,29 @@ function requireString(value, key) {
|
|
|
122
126
|
return value;
|
|
123
127
|
}
|
|
124
128
|
|
|
129
|
+
// A host without an http(s):// scheme (or an otherwise unparseable one) makes
|
|
130
|
+
// every request throw the cryptic "ADT path is not a valid URL component" at
|
|
131
|
+
// call time — once per tool call, far from the real cause. Validate the scheme
|
|
132
|
+
// up front so a bad config fails loudly at load with an actionable message.
|
|
133
|
+
function requireHost(value, systemName) {
|
|
134
|
+
const host = requireString(value, `${systemName}.host`);
|
|
135
|
+
let url;
|
|
136
|
+
try {
|
|
137
|
+
url = new URL(host);
|
|
138
|
+
} catch {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Config: ${systemName}.host must be a full URL including scheme, ` +
|
|
141
|
+
`e.g. "https://sap.example.com:44300" (got "${host}").`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Config: ${systemName}.host must use http:// or https:// (got "${url.protocol}//" in "${host}").`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return host;
|
|
150
|
+
}
|
|
151
|
+
|
|
125
152
|
function resolveSecret(value, systemName) {
|
|
126
153
|
if (typeof value !== "string") {
|
|
127
154
|
throw new Error(`System ${systemName}: password must be a string`);
|
package/src/reporter.js
CHANGED
|
@@ -32,7 +32,11 @@ const SKIP_NAMES = new Set(["ReadOnlyViolationError", "AbortError"]);
|
|
|
32
32
|
// Configuration / setup mistakes and input-validation errors — the user's
|
|
33
33
|
// environment or a mis-shaped tool call, not a bug in the tool.
|
|
34
34
|
const SKIP_MESSAGE_RE =
|
|
35
|
-
|
|
35
|
+
// "Failed to fetch CSRF token": the host didn't behave like an ADT endpoint
|
|
36
|
+
// (SSO/login page, web dispatcher, wrong host/system) — environmental/auth, not
|
|
37
|
+
// a tool defect. The CSRF fetch is a fixed simple GET, so it never mis-shapes a
|
|
38
|
+
// request on our side (#62, and the deleted #58-60 cluster).
|
|
39
|
+
/(No config found|must be a non-empty string|env var .* is not set|No system specified|Unknown system '|No systems configured|password must be a string|Failed to parse config|is required\b|Unsupported object type|ADT request failed|ADT request timed out|fetch failed|Failed to fetch CSRF token)/i;
|
|
36
40
|
|
|
37
41
|
// Network / TLS problems live on the user's side (firewall, VPN, cert, host down).
|
|
38
42
|
const NETWORK_CODES = new Set([
|
|
@@ -177,7 +181,9 @@ const BUSINESS_T100 = new Set([
|
|
|
177
181
|
"ADT_DATAPREVIEW_MSG",
|
|
178
182
|
]);
|
|
179
183
|
const BUSINESS_TYPE_RE =
|
|
180
|
-
|
|
184
|
+
// NoDependencyGraphDataCalculationPossible: SAP can't compute the dependency
|
|
185
|
+
// graph for a given CDS entity (system/data-side, not a tool defect — see #22/#61).
|
|
186
|
+
/SaveFailure|Lock|Enqueue|CTS_|ExceptionResourceAlreadyExists|NotFound|NoDependencyGraphDataCalculation/i;
|
|
181
187
|
|
|
182
188
|
// Decide whether a non-2xx ADT response that a handler returned (not threw) is
|
|
183
189
|
// likely a defect in *this* tool rather than a user/business condition. Errs
|
package/src/tools/cds.js
CHANGED
|
@@ -101,7 +101,10 @@ export function register({ getClient }) {
|
|
|
101
101
|
method: "POST",
|
|
102
102
|
path: CDS_PREVIEW_PATH,
|
|
103
103
|
query: { ddlSourceName: entity.toUpperCase(), rowNumber: String(max) },
|
|
104
|
-
|
|
104
|
+
// The Data Preview endpoint only serializes a result set as this media
|
|
105
|
+
// type; plain application/xml 406s (ExceptionResourceNotAcceptable),
|
|
106
|
+
// same as the adt_read_table fix.
|
|
107
|
+
accept: "application/vnd.sap.adt.datapreview.table.v1+xml",
|
|
105
108
|
});
|
|
106
109
|
const text = await res.text();
|
|
107
110
|
if (!res.ok) return errorResult(sys, res.status, text, res.headers.get("content-type"));
|
package/src/tools/quality.js
CHANGED
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
import { objectUri, sourceUri, normalizeType } from "../object-uris.js";
|
|
2
2
|
import { escapeXml } from "../xml.js";
|
|
3
3
|
import { parseObjectReferences } from "../object-references.js";
|
|
4
|
-
import { errorResult, jsonResult } from "../result.js";
|
|
4
|
+
import { errorResult, jsonResult, textResult } from "../result.js";
|
|
5
5
|
import { OBJECT_TYPE_HINT, SYSTEM_HINT } from "./_shared.js";
|
|
6
6
|
|
|
7
|
+
// Guard the `objects` array before the handlers iterate it. Callers frequently
|
|
8
|
+
// reach for the singular `object`/`type` shape (as adt_get_source uses), which
|
|
9
|
+
// left `args.objects` undefined and crashed on `.map`. Return a clean tool
|
|
10
|
+
// error pointing at the right shape instead of throwing.
|
|
11
|
+
function validateObjects(tool, objects) {
|
|
12
|
+
if (!Array.isArray(objects) || objects.length === 0) {
|
|
13
|
+
return textResult(
|
|
14
|
+
`${tool}: \`objects\` is required — a non-empty array of { name, type[, group] }. ` +
|
|
15
|
+
"Note the plural: pass `objects: [{ name: '…', type: '…' }]`, not a singular `object`/`type`.",
|
|
16
|
+
true
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
for (let i = 0; i < objects.length; i++) {
|
|
20
|
+
const o = objects[i];
|
|
21
|
+
if (!o || typeof o.name !== "string" || typeof o.type !== "string") {
|
|
22
|
+
return textResult(
|
|
23
|
+
`${tool}: objects[${i}] must be { name: string, type: string }.`,
|
|
24
|
+
true
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
7
31
|
// Parse <atcfinding .../> elements from an ATC worklist result. Attribute names
|
|
8
32
|
// vary slightly across releases — collect them all, normalized without prefix.
|
|
9
33
|
const FINDING_RE = /<(?:atcfinding|atcworklist:finding|finding)\b([\s\S]*?)(?:\/>|>)/gi;
|
|
@@ -343,6 +367,8 @@ export function register({ getClient }) {
|
|
|
343
367
|
},
|
|
344
368
|
|
|
345
369
|
adt_run_unit_tests: async (args) => {
|
|
370
|
+
const objectsError = validateObjects("adt_run_unit_tests", args.objects);
|
|
371
|
+
if (objectsError) return objectsError;
|
|
346
372
|
const { client, name: sys } = getClient(args.system);
|
|
347
373
|
const refs = args.objects
|
|
348
374
|
.map((o) => {
|
|
@@ -369,6 +395,8 @@ export function register({ getClient }) {
|
|
|
369
395
|
},
|
|
370
396
|
|
|
371
397
|
adt_run_atc: async (args) => {
|
|
398
|
+
const objectsError = validateObjects("adt_run_atc", args.objects);
|
|
399
|
+
if (objectsError) return objectsError;
|
|
372
400
|
const { client, name: sys } = getClient(args.system);
|
|
373
401
|
const refs = args.objects
|
|
374
402
|
.map((o) => {
|