@mcp-abap-adt/connection 1.7.0 → 1.8.1

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.
@@ -83,6 +83,14 @@ declare abstract class AbstractAbapConnection implements AbapConnection {
83
83
  private updateCookiesFromResponse;
84
84
  private getAxiosInstance;
85
85
  private ensureFreshCsrfToken;
86
+ /**
87
+ * Clear SAP-side session state when SAP rejects the cached CSRF token + session
88
+ * cookies (HTTP 401 on a mutation while a cached token exists). This forces the
89
+ * next request path to fetch a fresh token and a fresh SAP_SESSIONID cookie.
90
+ *
91
+ * Distinct from reset(): this leaves the axios instance and interceptors in place.
92
+ */
93
+ private invalidateSession;
86
94
  private shouldRetryCsrf;
87
95
  }
88
96
  export { AbstractAbapConnection };
@@ -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;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"}
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;IA+R9B,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;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,eAAe;CA+BxB;AAGD,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
@@ -263,22 +263,51 @@ class AbstractAbapConnection {
263
263
  else {
264
264
  this.logger?.error(errorDetails.message, errorDetails);
265
265
  }
266
- // Retry logic for CSRF token errors (403 with CSRF message)
267
- if (this.shouldRetryCsrf(error)) {
268
- this.logger?.debug('CSRF token validation failed, fetching new token and retrying request', {
266
+ // Detect the "login-form 401" pattern: SAP returned 401 for a mutation while
267
+ // we have a cached CSRF token. The token and its bound SAP session must be
268
+ // discarded before the retry. Basic auth only JWT/SAML lifecycles are
269
+ // managed elsewhere.
270
+ const isCachedTokenStale = error instanceof axios_1.AxiosError &&
271
+ this.config.authType === 'basic' &&
272
+ (normalizedMethod === 'POST' ||
273
+ normalizedMethod === 'PUT' ||
274
+ normalizedMethod === 'DELETE') &&
275
+ error.response?.status === 401 &&
276
+ this.getCsrfToken() !== null;
277
+ // Retry logic for CSRF token errors (403 with CSRF message) and the
278
+ // login-form 401 pattern.
279
+ if (this.shouldRetryCsrf(error) || isCachedTokenStale) {
280
+ this.logger?.debug(isCachedTokenStale
281
+ ? 'Stale CSRF token / SAP session — invalidating and retrying'
282
+ : 'CSRF token validation failed, fetching new token and retrying request', {
269
283
  url: requestUrl,
270
284
  method: normalizedMethod,
271
285
  });
272
- this.csrfToken = await this.fetchCsrfToken(requestUrl, 5, 2000);
273
- if (this.csrfToken) {
274
- requestHeaders['x-csrf-token'] = this.csrfToken;
286
+ if (isCachedTokenStale) {
287
+ this.invalidateSession();
288
+ delete requestHeaders.Cookie;
289
+ delete requestHeaders.cookie;
275
290
  }
276
- if (this.cookies) {
277
- requestHeaders.Cookie = this.cookies;
291
+ try {
292
+ this.setCsrfToken(await this.fetchCsrfToken(requestUrl, 5, 2000));
293
+ const refreshedToken = this.getCsrfToken();
294
+ if (refreshedToken) {
295
+ requestHeaders['x-csrf-token'] = refreshedToken;
296
+ }
297
+ const refreshedCookies = this.getCookies();
298
+ if (refreshedCookies) {
299
+ requestHeaders.Cookie = refreshedCookies;
300
+ }
301
+ const retryResponse = await this.getAxiosInstance()(requestConfig);
302
+ this.updateCookiesFromResponse(retryResponse.headers);
303
+ return retryResponse;
304
+ }
305
+ catch (retryError) {
306
+ this.logger?.debug(`CSRF retry failed; rethrowing original error: ${retryError instanceof Error
307
+ ? retryError.message
308
+ : String(retryError)}`);
309
+ throw error;
278
310
  }
279
- const retryResponse = await this.getAxiosInstance()(requestConfig);
280
- this.updateCookiesFromResponse(retryResponse.headers);
281
- return retryResponse;
282
311
  }
283
312
  // Retry logic for 401 errors on GET requests (authentication issue - need cookies)
284
313
  // Only for basic auth - JWT auth will be handled by refresh logic below
@@ -585,6 +614,18 @@ class AbstractAbapConnection {
585
614
  throw error;
586
615
  }
587
616
  }
617
+ /**
618
+ * Clear SAP-side session state when SAP rejects the cached CSRF token + session
619
+ * cookies (HTTP 401 on a mutation while a cached token exists). This forces the
620
+ * next request path to fetch a fresh token and a fresh SAP_SESSIONID cookie.
621
+ *
622
+ * Distinct from reset(): this leaves the axios instance and interceptors in place.
623
+ */
624
+ invalidateSession() {
625
+ this.setCsrfToken(null);
626
+ this.cookies = null;
627
+ this.cookieStore.clear();
628
+ }
588
629
  shouldRetryCsrf(error) {
589
630
  if (!(error instanceof axios_1.AxiosError)) {
590
631
  return false;
@@ -1 +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;AA0F1D;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IASpD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IATzB,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;IAChD,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,aAAa,CAAuB;gBAGzB,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,IAAI,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;IAU9C,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IA8L9B;;;OAGG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAc5B,OAAO,CAAC,MAAM,CAAC,cAAc;CAqB9B"}
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;AAkG1D;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IASpD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IATzB,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;IAChD,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,aAAa,CAAuB;gBAGzB,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,IAAI,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;IAU9C,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IA8L9B;;;OAGG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,OAAO,CAAC,MAAM,CAAC,cAAc;CAqB9B"}
@@ -2,6 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RfcAbapConnection = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
+ function rfcErrorMessage(e) {
6
+ if (e instanceof Error)
7
+ return e.message;
8
+ if (e && typeof e === 'object')
9
+ return JSON.stringify(e);
10
+ return String(e);
11
+ }
5
12
  /**
6
13
  * Derive RFC connection parameters from ISapConfig.
7
14
  * Parses hostname from config.url, system number from port (80XX → XX).
@@ -14,8 +21,10 @@ const node_crypto_1 = require("node:crypto");
14
21
  function buildRfcParams(config) {
15
22
  const parsed = new URL(config.url);
16
23
  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');
24
+ // SAP HTTP port convention: 80XX where XX = system number.
25
+ // SAP_SYSNR env var overrides derivation for non-standard ports (e.g. 50400).
26
+ const derivedSysnr = String(port - 8000).padStart(2, '0');
27
+ const sysnr = process.env.SAP_SYSNR?.trim() || derivedSysnr;
19
28
  return {
20
29
  ashost: parsed.hostname,
21
30
  sysnr,
@@ -105,7 +114,7 @@ class RfcAbapConnection {
105
114
  catch (e) {
106
115
  throw new Error('@mcp-abap-adt/sap-rfc-lite is not available. To use RFC connections, install SAP NW RFC SDK ' +
107
116
  'and run: npm install @mcp-abap-adt/sap-rfc-lite. ' +
108
- `Details: ${e instanceof Error ? e.message : String(e)}`);
117
+ `Details: ${rfcErrorMessage(e)}`);
109
118
  }
110
119
  this.rfcClient = new Client(this.rfcParams);
111
120
  try {
@@ -114,7 +123,7 @@ class RfcAbapConnection {
114
123
  }
115
124
  catch (e) {
116
125
  this.rfcClient = null;
117
- const msg = e instanceof Error ? e.message : String(e);
126
+ const msg = rfcErrorMessage(e);
118
127
  this.logger?.error(`RFC connection failed: ${msg}`);
119
128
  throw new Error(`Failed to open RFC connection: ${msg}`);
120
129
  }
@@ -277,7 +286,7 @@ class RfcAbapConnection {
277
286
  throw e;
278
287
  }
279
288
  // RFC-level error
280
- const msg = e instanceof Error ? e.message : String(e);
289
+ const msg = rfcErrorMessage(e);
281
290
  this.logger?.error(`RFC call failed: ${msg}`);
282
291
  throw new Error(`RFC call to SADT_REST_RFC_ENDPOINT failed: ${msg}`);
283
292
  }
@@ -299,7 +308,7 @@ class RfcAbapConnection {
299
308
  this.logger?.debug('RFC connection closed');
300
309
  }
301
310
  catch (e) {
302
- this.logger?.debug(`RFC close error: ${e instanceof Error ? e.message : String(e)}`);
311
+ this.logger?.debug(`RFC close error: ${rfcErrorMessage(e)}`);
303
312
  }
304
313
  this.rfcClient = null;
305
314
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/connection",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "ABAP connection layer for MCP ABAP ADT server",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",