sap-adt-mcp 0.8.47 → 0.8.48
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/config.js +34 -7
- 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.48",
|
|
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/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/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) => {
|