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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sap-adt-mcp",
3
- "version": "0.8.47",
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
- const CANDIDATE_PATHS = [
6
- process.env.SAP_ADT_MCP_CONFIG,
7
- path.join(os.homedir(), ".sap-adt-mcp", "config.json"),
8
- path.join(process.cwd(), "config.json"),
9
- ].filter(Boolean);
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 = CANDIDATE_PATHS.find((p) => fs.existsSync(p));
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: requireString(profile.host, `${name}.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
- accept: "application/xml",
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"));
@@ -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) => {