@mcp-abap-adt/connection 1.3.1 → 1.4.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.
@@ -63,6 +63,10 @@ declare abstract class AbstractAbapConnection implements AbapConnection {
63
63
  * Protected method for use by concrete implementations in their connect() method
64
64
  */
65
65
  protected fetchCsrfToken(url: string, retryCount?: number, retryDelay?: number): Promise<string>;
66
+ /**
67
+ * Fetch CSRF token from a specific endpoint with retries
68
+ */
69
+ private fetchCsrfTokenFromEndpoint;
66
70
  /**
67
71
  * Get CSRF token (protected for use by subclasses)
68
72
  */
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,0BAA0B,CAAC;AAM7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG9E,uBAAe,sBAAuB,YAAW,cAAc;IAW3D,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAX3C,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,eAAe,CAAU;IAEjC,SAAS,aACU,MAAM,EAAE,SAAS,EACf,MAAM,EAAE,OAAO,GAAG,IAAI,EACzC,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE;IAqBzC;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;IAUpD;;OAEG;IACH,cAAc,IAAI,WAAW,GAAG,UAAU;IAI1C;;;OAGG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKrC;;OAEG;IACH,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,SAAS,IAAI,SAAS;IAItB,KAAK,IAAI,IAAI;IAYP,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAI7B,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAevD;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAE3B,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IA4P9B,SAAS,CAAC,QAAQ,CAAC,wBAAwB,IAAI,MAAM;IAErD;;;OAGG;cACa,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAAgC,EAC5C,UAAU,GAAE,MAAgC,GAC3C,OAAO,CAAC,MAAM,CAAC;IAgLlB;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,MAAM,GAAG,IAAI;IAIvC;;OAEG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIlD;;OAEG;IACH,SAAS,CAAC,UAAU,IAAI,MAAM,GAAG,IAAI;IAIrC,SAAS,CAAC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIlD,OAAO,CAAC,yBAAyB;IAkEjC,OAAO,CAAC,gBAAgB;YAqBV,oBAAoB;IAiClC,OAAO,CAAC,eAAe;CA+BxB;AAGD,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
1
+ {"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,0BAA0B,CAAC;AAM7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG9E,uBAAe,sBAAuB,YAAW,cAAc;IAW3D,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAX3C,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,eAAe,CAAU;IAEjC,SAAS,aACU,MAAM,EAAE,SAAS,EACf,MAAM,EAAE,OAAO,GAAG,IAAI,EACzC,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE;IAqBzC;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;IAUpD;;OAEG;IACH,cAAc,IAAI,WAAW,GAAG,UAAU;IAI1C;;;OAGG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKrC;;OAEG;IACH,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,SAAS,IAAI,SAAS;IAItB,KAAK,IAAI,IAAI;IAYP,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAI7B,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAevD;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAE3B,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IA4P9B,SAAS,CAAC,QAAQ,CAAC,wBAAwB,IAAI,MAAM;IAErD;;;OAGG;cACa,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAAgC,EAC5C,UAAU,GAAE,MAAgC,GAC3C,OAAO,CAAC,MAAM,CAAC;IA2ClB;;OAEG;YACW,0BAA0B;IAsKxC;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,MAAM,GAAG,IAAI;IAIvC;;OAEG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIlD;;OAEG;IACH,SAAS,CAAC,UAAU,IAAI,MAAM,GAAG,IAAI;IAIrC,SAAS,CAAC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIlD,OAAO,CAAC,yBAAyB;IAkEjC,OAAO,CAAC,gBAAgB;YAqBV,oBAAoB;IAiClC,OAAO,CAAC,eAAe;CA+BxB;AAGD,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
@@ -321,20 +321,43 @@ class AbstractAbapConnection {
321
321
  * Protected method for use by concrete implementations in their connect() method
322
322
  */
323
323
  async fetchCsrfToken(url, retryCount = csrfConfig_js_1.CSRF_CONFIG.RETRY_COUNT, retryDelay = csrfConfig_js_1.CSRF_CONFIG.RETRY_DELAY) {
324
- let csrfUrl = url;
325
- // Build CSRF endpoint URL from base URL
326
- if (!url.includes('/sap/bc/adt/')) {
327
- // If URL doesn't contain ADT path, append endpoint
328
- csrfUrl = url.endsWith('/')
329
- ? `${url}${csrfConfig_js_1.CSRF_CONFIG.ENDPOINT.slice(1)}`
330
- : `${url}${csrfConfig_js_1.CSRF_CONFIG.ENDPOINT}`;
331
- }
332
- else if (!url.includes(csrfConfig_js_1.CSRF_CONFIG.ENDPOINT)) {
333
- // If URL contains ADT path but not our endpoint, extract base and append endpoint
334
- const base = url.split('/sap/bc/adt')[0];
335
- csrfUrl = `${base}${csrfConfig_js_1.CSRF_CONFIG.ENDPOINT}`;
336
- }
337
- // If URL already contains the endpoint, use it as is
324
+ // Try primary endpoint first, then fallback for older systems
325
+ const baseUrl = url.includes('/sap/bc/adt/')
326
+ ? url.split('/sap/bc/adt')[0]
327
+ : url.endsWith('/')
328
+ ? url.slice(0, -1)
329
+ : url;
330
+ let endpoints;
331
+ // If the URL already contains a specific endpoint, use only that
332
+ if (url.includes(csrfConfig_js_1.CSRF_CONFIG.ENDPOINT)) {
333
+ endpoints = [url];
334
+ }
335
+ else if (url.includes(csrfConfig_js_1.CSRF_CONFIG.FALLBACK_ENDPOINT)) {
336
+ endpoints = [url];
337
+ }
338
+ else {
339
+ endpoints = [
340
+ `${baseUrl}${csrfConfig_js_1.CSRF_CONFIG.ENDPOINT}`,
341
+ `${baseUrl}${csrfConfig_js_1.CSRF_CONFIG.FALLBACK_ENDPOINT}`,
342
+ ];
343
+ }
344
+ let lastError;
345
+ for (const csrfUrl of endpoints) {
346
+ try {
347
+ return await this.fetchCsrfTokenFromEndpoint(csrfUrl, retryCount, retryDelay);
348
+ }
349
+ catch (error) {
350
+ lastError = error instanceof Error ? error : new Error(String(error));
351
+ this.logger?.debug(`CSRF token not available from ${csrfUrl}, trying next endpoint...`);
352
+ }
353
+ }
354
+ // All endpoints exhausted
355
+ throw lastError ?? new Error('CSRF token fetch failed unexpectedly');
356
+ }
357
+ /**
358
+ * Fetch CSRF token from a specific endpoint with retries
359
+ */
360
+ async fetchCsrfTokenFromEndpoint(csrfUrl, retryCount, retryDelay) {
338
361
  this.logger?.debug(`Fetching CSRF token from: ${csrfUrl}`);
339
362
  for (let attempt = 0; attempt <= retryCount; attempt++) {
340
363
  try {
@@ -0,0 +1,42 @@
1
+ import type { IAbapRequestOptions, IAdtResponse } from '@mcp-abap-adt/interfaces';
2
+ import type { SapConfig } from '../config/sapConfig.js';
3
+ import type { ILogger } from '../logger.js';
4
+ import type { AbapConnection } from './AbapConnection.js';
5
+ /**
6
+ * RFC-based connection for on-premise SAP systems.
7
+ *
8
+ * Uses node-rfc to call SADT_REST_RFC_ENDPOINT — the same standard SAP FM
9
+ * that Eclipse ADT uses for all on-premise ADT operations via JCo.
10
+ *
11
+ * RFC connections are inherently stateful: one ABAP session persists for the
12
+ * entire connection lifetime. This solves the HTTP 423 "invalid lock handle"
13
+ * problem on legacy systems (BASIS < 7.50) where HTTP stateful sessions
14
+ * are not supported.
15
+ *
16
+ * Connection parameters are derived from the standard ISapConfig.url field:
17
+ * http://saphost:8000 → ashost=saphost, sysnr=00
18
+ *
19
+ * Prerequisites:
20
+ * - SAP NW RFC SDK installed on the machine
21
+ * - node-rfc package installed: npm install node-rfc
22
+ */
23
+ export declare class RfcAbapConnection implements AbapConnection {
24
+ private readonly config;
25
+ private readonly logger;
26
+ private rfcClient;
27
+ private readonly sessionId;
28
+ private readonly baseUrl;
29
+ private readonly rfcParams;
30
+ constructor(config: SapConfig, logger?: ILogger | null);
31
+ connect(): Promise<void>;
32
+ getBaseUrl(): Promise<string>;
33
+ getSessionId(): string | null;
34
+ setSessionType(_type: 'stateful' | 'stateless'): void;
35
+ makeAdtRequest<T = any, D = any>(options: IAbapRequestOptions): Promise<IAdtResponse<T, D>>;
36
+ /**
37
+ * Close the RFC connection and release the ABAP session.
38
+ */
39
+ close(): Promise<void>;
40
+ private static validateConfig;
41
+ }
42
+ //# sourceMappingURL=RfcAbapConnection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RfcAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/RfcAbapConnection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EACb,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAqD1D;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IAOpD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;gBAG7B,MAAM,EAAE,SAAS,EACjB,MAAM,GAAE,OAAO,GAAG,IAAW;IAa1C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA8BxB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAInC,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;IAI/C,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IA6G9B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAc5B,OAAO,CAAC,MAAM,CAAC,cAAc;CAqB9B"}
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RfcAbapConnection = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ /**
6
+ * Derive RFC connection parameters from ISapConfig.
7
+ * Parses hostname from config.url, system number from port (80XX → XX).
8
+ *
9
+ * Examples:
10
+ * http://saphost:8000 → ashost=saphost, sysnr=00
11
+ * http://saphost:8001 → ashost=saphost, sysnr=01
12
+ * http://saphost:8042 → ashost=saphost, sysnr=42
13
+ */
14
+ function buildRfcParams(config) {
15
+ const parsed = new URL(config.url);
16
+ const port = Number.parseInt(parsed.port || '8000', 10);
17
+ // SAP HTTP port convention: 80XX where XX = system number
18
+ const sysnr = String(port - 8000).padStart(2, '0');
19
+ return {
20
+ ashost: parsed.hostname,
21
+ sysnr,
22
+ client: config.client || '000',
23
+ user: config.username || '',
24
+ passwd: config.password || '',
25
+ lang: 'EN',
26
+ };
27
+ }
28
+ /**
29
+ * RFC-based connection for on-premise SAP systems.
30
+ *
31
+ * Uses node-rfc to call SADT_REST_RFC_ENDPOINT — the same standard SAP FM
32
+ * that Eclipse ADT uses for all on-premise ADT operations via JCo.
33
+ *
34
+ * RFC connections are inherently stateful: one ABAP session persists for the
35
+ * entire connection lifetime. This solves the HTTP 423 "invalid lock handle"
36
+ * problem on legacy systems (BASIS < 7.50) where HTTP stateful sessions
37
+ * are not supported.
38
+ *
39
+ * Connection parameters are derived from the standard ISapConfig.url field:
40
+ * http://saphost:8000 → ashost=saphost, sysnr=00
41
+ *
42
+ * Prerequisites:
43
+ * - SAP NW RFC SDK installed on the machine
44
+ * - node-rfc package installed: npm install node-rfc
45
+ */
46
+ class RfcAbapConnection {
47
+ config;
48
+ logger;
49
+ rfcClient = null;
50
+ sessionId;
51
+ baseUrl;
52
+ rfcParams;
53
+ constructor(config, logger = null) {
54
+ this.config = config;
55
+ this.logger = logger;
56
+ RfcAbapConnection.validateConfig(config);
57
+ this.sessionId = (0, node_crypto_1.randomUUID)();
58
+ this.baseUrl = config.url;
59
+ this.rfcParams = buildRfcParams(config);
60
+ this.logger?.debug(`RfcAbapConnection created for ${this.rfcParams.ashost}:${this.rfcParams.sysnr}, client ${this.rfcParams.client}`);
61
+ }
62
+ async connect() {
63
+ let Client;
64
+ try {
65
+ // Dynamic require — node-rfc is NOT a declared dependency.
66
+ // Users who need RFC connections must install it manually:
67
+ // npm install node-rfc (+ SAP NW RFC SDK on the machine)
68
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
69
+ const noderfc = require('node-rfc');
70
+ Client = noderfc.Client;
71
+ }
72
+ catch (e) {
73
+ throw new Error('node-rfc is not available. To use RFC connections, install SAP NW RFC SDK ' +
74
+ 'and run: npm install node-rfc. ' +
75
+ `Details: ${e instanceof Error ? e.message : String(e)}`);
76
+ }
77
+ this.rfcClient = new Client(this.rfcParams);
78
+ try {
79
+ await this.rfcClient.open();
80
+ this.logger?.debug('RFC connection opened (stateful session)');
81
+ }
82
+ catch (e) {
83
+ this.rfcClient = null;
84
+ const msg = e instanceof Error ? e.message : String(e);
85
+ this.logger?.error(`RFC connection failed: ${msg}`);
86
+ throw new Error(`Failed to open RFC connection: ${msg}`);
87
+ }
88
+ }
89
+ async getBaseUrl() {
90
+ return this.baseUrl;
91
+ }
92
+ getSessionId() {
93
+ return this.sessionId;
94
+ }
95
+ setSessionType(_type) {
96
+ // No-op — RFC connections are always stateful
97
+ }
98
+ async makeAdtRequest(options) {
99
+ if (!this.rfcClient?.alive) {
100
+ throw new Error('RFC connection is not open. Call connect() first.');
101
+ }
102
+ const method = options.method.toUpperCase();
103
+ let uri = options.url;
104
+ // Add sap-client to URI if not present
105
+ if (this.config.client && !uri.includes('sap-client')) {
106
+ uri +=
107
+ (uri.includes('?') ? '&' : '?') + `sap-client=${this.config.client}`;
108
+ }
109
+ // Build header fields
110
+ const headerFields = [];
111
+ if (options.headers) {
112
+ for (const [name, value] of Object.entries(options.headers)) {
113
+ headerFields.push({ NAME: name, VALUE: value });
114
+ }
115
+ }
116
+ // Ensure Content-Type for body
117
+ const body = options.data !== undefined && options.data !== null
118
+ ? String(options.data)
119
+ : '';
120
+ if (body &&
121
+ !headerFields.some((h) => h.NAME.toLowerCase() === 'content-type')) {
122
+ headerFields.push({
123
+ NAME: 'Content-Type',
124
+ VALUE: 'text/plain; charset=utf-8',
125
+ });
126
+ }
127
+ this.logger?.debug(`RFC → ${method} ${uri}`);
128
+ try {
129
+ const result = await this.rfcClient.call('SADT_REST_RFC_ENDPOINT', {
130
+ REQUEST: {
131
+ REQUEST_LINE: {
132
+ METHOD: method,
133
+ URI: uri,
134
+ VERSION: 'HTTP/1.1',
135
+ },
136
+ HEADER_FIELDS: headerFields,
137
+ MESSAGE_BODY: body ? Buffer.from(body, 'utf-8') : Buffer.alloc(0),
138
+ },
139
+ });
140
+ const resp = result.RESPONSE || result;
141
+ // Parse status — RFC returns status in STATUS_LINE structure
142
+ const statusCode = resp.STATUS_LINE?.CODE || resp.STATUS_CODE || 200;
143
+ const statusText = resp.STATUS_LINE?.REASON || resp.STATUS_TEXT || 'OK';
144
+ // Parse response body
145
+ const respBody = resp.MESSAGE_BODY
146
+ ? Buffer.isBuffer(resp.MESSAGE_BODY)
147
+ ? resp.MESSAGE_BODY.toString('utf-8')
148
+ : String(resp.MESSAGE_BODY)
149
+ : '';
150
+ // Parse response headers
151
+ const respHeaders = {};
152
+ const respHeaderFields = resp.HEADER_FIELDS || [];
153
+ for (const field of respHeaderFields) {
154
+ if (field.NAME && field.VALUE !== undefined) {
155
+ respHeaders[field.NAME.toLowerCase()] = field.VALUE;
156
+ }
157
+ }
158
+ this.logger?.debug(`RFC ← ${statusCode} ${statusText} (${respBody.length} bytes)`);
159
+ const response = {
160
+ data: respBody,
161
+ status: statusCode,
162
+ statusText,
163
+ headers: respHeaders,
164
+ };
165
+ // Throw for error status codes (matching HTTP/axios behavior)
166
+ if (statusCode >= 400) {
167
+ const error = new Error(`Request failed with status ${statusCode}: ${method} ${uri}`);
168
+ error.response = response;
169
+ throw error;
170
+ }
171
+ return response;
172
+ }
173
+ catch (e) {
174
+ // Re-throw our own errors (status >= 400)
175
+ if (e?.response) {
176
+ throw e;
177
+ }
178
+ // RFC-level error
179
+ const msg = e instanceof Error ? e.message : String(e);
180
+ this.logger?.error(`RFC call failed: ${msg}`);
181
+ throw new Error(`RFC call to SADT_REST_RFC_ENDPOINT failed: ${msg}`);
182
+ }
183
+ }
184
+ /**
185
+ * Close the RFC connection and release the ABAP session.
186
+ */
187
+ async close() {
188
+ if (this.rfcClient) {
189
+ try {
190
+ await this.rfcClient.close();
191
+ this.logger?.debug('RFC connection closed');
192
+ }
193
+ catch (e) {
194
+ this.logger?.debug(`RFC close error: ${e instanceof Error ? e.message : String(e)}`);
195
+ }
196
+ this.rfcClient = null;
197
+ }
198
+ }
199
+ static validateConfig(config) {
200
+ if (config.authType !== 'rfc') {
201
+ throw new Error(`RFC connection expects authType "rfc", got "${config.authType}"`);
202
+ }
203
+ if (!config.url) {
204
+ throw new Error('RFC connection requires url (hostname is parsed from it)');
205
+ }
206
+ if (!config.username || !config.password) {
207
+ throw new Error('RFC connection requires both username and password');
208
+ }
209
+ if (!config.client) {
210
+ throw new Error('RFC connection requires SAP client');
211
+ }
212
+ }
213
+ }
214
+ exports.RfcAbapConnection = RfcAbapConnection;
@@ -1 +1 @@
1
- {"version":3,"file":"connectionFactory.d.ts","sourceRoot":"","sources":["../../src/connection/connectionFactory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAK1D,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,eAAe,EAChC,OAAO,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GACtC,cAAc,CAahB"}
1
+ {"version":3,"file":"connectionFactory.d.ts","sourceRoot":"","sources":["../../src/connection/connectionFactory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAM1D,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,eAAe,EAChC,OAAO,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GACtC,cAAc,CAehB"}
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createAbapConnection = createAbapConnection;
4
4
  const BaseAbapConnection_js_1 = require("./BaseAbapConnection.js");
5
5
  const JwtAbapConnection_js_1 = require("./JwtAbapConnection.js");
6
+ const RfcAbapConnection_js_1 = require("./RfcAbapConnection.js");
6
7
  const SamlAbapConnection_js_1 = require("./SamlAbapConnection.js");
7
8
  function createAbapConnection(config, logger, sessionId, tokenRefresher, options) {
8
9
  switch (config.authType) {
@@ -12,6 +13,8 @@ function createAbapConnection(config, logger, sessionId, tokenRefresher, options
12
13
  return new JwtAbapConnection_js_1.JwtAbapConnection(config, logger, sessionId, tokenRefresher);
13
14
  case 'saml':
14
15
  return new SamlAbapConnection_js_1.SamlAbapConnection(config, logger, sessionId, options);
16
+ case 'rfc':
17
+ return new RfcAbapConnection_js_1.RfcAbapConnection(config, logger);
15
18
  default:
16
19
  throw new Error(`Unsupported SAP authentication type: ${config.authType}`);
17
20
  }
@@ -16,10 +16,16 @@ export declare const CSRF_CONFIG: {
16
16
  */
17
17
  readonly RETRY_DELAY: 1000;
18
18
  /**
19
- * CSRF token endpoint path
20
- * Standard SAP ADT core discovery endpoint (available on all systems, returns smaller response)
19
+ * CSRF token endpoint path (primary)
20
+ * Standard SAP ADT core discovery endpoint (newer systems, returns smaller response)
21
21
  */
22
22
  readonly ENDPOINT: "/sap/bc/adt/core/discovery";
23
+ /**
24
+ * CSRF token endpoint path (fallback)
25
+ * Legacy SAP ADT discovery endpoint for older systems (e.g. BASIS < 7.52)
26
+ * that don't have /sap/bc/adt/core/discovery
27
+ */
28
+ readonly FALLBACK_ENDPOINT: "/sap/bc/adt/discovery";
23
29
  /**
24
30
  * Required headers for CSRF token fetch
25
31
  */
@@ -1 +1 @@
1
- {"version":3,"file":"csrfConfig.d.ts","sourceRoot":"","sources":["../../src/connection/csrfConfig.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,WAAW;IACtB;;;OAGG;;IAGH;;;OAGG;;IAGH;;;OAGG;;IAGH;;OAEG;;;;;CAKK,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,mBAAmB;sCACL,MAAM,SAAS,MAAM;;;CAOtC,CAAC"}
1
+ {"version":3,"file":"csrfConfig.d.ts","sourceRoot":"","sources":["../../src/connection/csrfConfig.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,WAAW;IACtB;;;OAGG;;IAGH;;;OAGG;;IAGH;;;OAGG;;IAGH;;;;OAIG;;IAGH;;OAEG;;;;;CAKK,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,mBAAmB;sCACL,MAAM,SAAS,MAAM;;;CAOtC,CAAC"}
@@ -19,10 +19,16 @@ exports.CSRF_CONFIG = {
19
19
  */
20
20
  RETRY_DELAY: 1000,
21
21
  /**
22
- * CSRF token endpoint path
23
- * Standard SAP ADT core discovery endpoint (available on all systems, returns smaller response)
22
+ * CSRF token endpoint path (primary)
23
+ * Standard SAP ADT core discovery endpoint (newer systems, returns smaller response)
24
24
  */
25
25
  ENDPOINT: '/sap/bc/adt/core/discovery',
26
+ /**
27
+ * CSRF token endpoint path (fallback)
28
+ * Legacy SAP ADT discovery endpoint for older systems (e.g. BASIS < 7.52)
29
+ * that don't have /sap/bc/adt/core/discovery
30
+ */
31
+ FALLBACK_ENDPOINT: '/sap/bc/adt/discovery',
26
32
  /**
27
33
  * Required headers for CSRF token fetch
28
34
  */
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export { createAbapConnection } from './connection/connectionFactory.js';
7
7
  export { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from './connection/csrfConfig.js';
8
8
  export { GenericWebSocketTransport, type IWebSocketFactory, type IWebSocketLike, } from './connection/GenericWebSocketTransport.js';
9
9
  export { JwtAbapConnection, JwtAbapConnection as CloudAbapConnection, } from './connection/JwtAbapConnection.js';
10
+ export { RfcAbapConnection } from './connection/RfcAbapConnection.js';
10
11
  export { SamlAbapConnection } from './connection/SamlAbapConnection.js';
11
12
  export type { ILogger } from './logger.js';
12
13
  export { getTimeout, getTimeoutConfig, type TimeoutConfig, } from './utils/timeouts.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,WAAW,EACX,SAAS,GACV,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,YAAY,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,IAAI,oBAAoB,GAC3C,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EACL,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,IAAI,mBAAmB,GACzC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,WAAW,EACX,SAAS,GACV,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,YAAY,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,IAAI,oBAAoB,GAC3C,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EACL,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,IAAI,mBAAmB,GACzC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // Types - re-exported from interfaces package with backward compatibility aliases
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.getTimeoutConfig = exports.getTimeout = exports.SamlAbapConnection = exports.CloudAbapConnection = exports.JwtAbapConnection = exports.GenericWebSocketTransport = exports.CSRF_ERROR_MESSAGES = exports.CSRF_CONFIG = exports.createAbapConnection = exports.OnPremAbapConnection = exports.BaseAbapConnection = exports.sapConfigSignature = void 0;
4
+ exports.getTimeoutConfig = exports.getTimeout = exports.SamlAbapConnection = exports.RfcAbapConnection = exports.CloudAbapConnection = exports.JwtAbapConnection = exports.GenericWebSocketTransport = exports.CSRF_ERROR_MESSAGES = exports.CSRF_CONFIG = exports.createAbapConnection = exports.OnPremAbapConnection = exports.BaseAbapConnection = exports.sapConfigSignature = void 0;
5
5
  // Config utilities
6
6
  var sapConfig_js_1 = require("./config/sapConfig.js");
7
7
  Object.defineProperty(exports, "sapConfigSignature", { enumerable: true, get: function () { return sapConfig_js_1.sapConfigSignature; } });
@@ -22,6 +22,8 @@ Object.defineProperty(exports, "GenericWebSocketTransport", { enumerable: true,
22
22
  var JwtAbapConnection_js_1 = require("./connection/JwtAbapConnection.js");
23
23
  Object.defineProperty(exports, "JwtAbapConnection", { enumerable: true, get: function () { return JwtAbapConnection_js_1.JwtAbapConnection; } });
24
24
  Object.defineProperty(exports, "CloudAbapConnection", { enumerable: true, get: function () { return JwtAbapConnection_js_1.JwtAbapConnection; } });
25
+ var RfcAbapConnection_js_1 = require("./connection/RfcAbapConnection.js");
26
+ Object.defineProperty(exports, "RfcAbapConnection", { enumerable: true, get: function () { return RfcAbapConnection_js_1.RfcAbapConnection; } });
25
27
  var SamlAbapConnection_js_1 = require("./connection/SamlAbapConnection.js");
26
28
  Object.defineProperty(exports, "SamlAbapConnection", { enumerable: true, get: function () { return SamlAbapConnection_js_1.SamlAbapConnection; } });
27
29
  // Timeouts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/connection",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "ABAP connection layer for MCP ABAP ADT server",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -46,7 +46,7 @@
46
46
  "node": ">=18.0.0"
47
47
  },
48
48
  "dependencies": {
49
- "@mcp-abap-adt/interfaces": "^2.4.0",
49
+ "@mcp-abap-adt/interfaces": "^2.7.0",
50
50
  "axios": "^1.13.5",
51
51
  "commander": "^14.0.3",
52
52
  "express": "^5.1.0",