@mcp-abap-adt/connection 0.2.3 → 0.2.5

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/README.md CHANGED
@@ -178,6 +178,38 @@ const response = await connection.makeAdtRequest({
178
178
  });
179
179
  ```
180
180
 
181
+ ### Cloud Usage with Automatic Token Refresh
182
+
183
+ For automatic token refresh on 401/403 errors, inject `ITokenRefresher`:
184
+
185
+ ```typescript
186
+ import { JwtAbapConnection, SapConfig } from "@mcp-abap-adt/connection";
187
+ import type { ITokenRefresher } from "@mcp-abap-adt/interfaces";
188
+
189
+ // Token refresher provides token acquisition and refresh
190
+ // (created by @mcp-abap-adt/auth-broker or custom implementation)
191
+ const tokenRefresher: ITokenRefresher = {
192
+ getToken: async () => { /* return current token */ },
193
+ refreshToken: async () => { /* refresh and return new token */ },
194
+ };
195
+
196
+ // JWT configuration
197
+ const config: SapConfig = {
198
+ url: "https://your-instance.abap.cloud.sap",
199
+ authType: "jwt",
200
+ jwtToken: await tokenRefresher.getToken(), // Get initial token
201
+ };
202
+
203
+ // Create connection with token refresher - 401/403 handled automatically
204
+ const connection = new JwtAbapConnection(config, logger, undefined, tokenRefresher);
205
+
206
+ // Requests automatically retry with refreshed token on auth errors
207
+ const response = await connection.makeAdtRequest({
208
+ method: "GET",
209
+ url: "/sap/bc/adt/programs/programs/your-program",
210
+ });
211
+ ```
212
+
181
213
  ### Stateful Sessions
182
214
 
183
215
  For operations that require session state (e.g., object modifications), you can enable stateful sessions:
@@ -3,26 +3,34 @@ import { AbstractAbapConnection } from "./AbstractAbapConnection.js";
3
3
  import { AbapRequestOptions } from "./AbapConnection.js";
4
4
  import { ILogger } from "../logger.js";
5
5
  import { AxiosResponse } from "axios";
6
+ import type { ITokenRefresher } from "@mcp-abap-adt/interfaces";
6
7
  /**
7
8
  * JWT Authentication connection for SAP BTP Cloud systems
8
- * Note: Token refresh functionality is not supported in this package.
9
- * Use @mcp-abap-adt/auth-broker for token refresh functionality.
9
+ *
10
+ * Supports automatic token refresh via ITokenRefresher injection:
11
+ * - If tokenRefresher is provided, 401/403 errors trigger automatic token refresh
12
+ * - If tokenRefresher is not provided, 401/403 errors throw an error (legacy behavior)
10
13
  */
11
14
  export declare class JwtAbapConnection extends AbstractAbapConnection {
12
- constructor(config: SapConfig, logger?: ILogger | null, sessionId?: string);
15
+ private tokenRefresher?;
16
+ private currentToken;
17
+ constructor(config: SapConfig, logger?: ILogger | null, sessionId?: string, tokenRefresher?: ITokenRefresher);
13
18
  protected buildAuthorizationHeader(): string;
19
+ /**
20
+ * Refresh the JWT token using the injected tokenRefresher
21
+ * @returns true if token was refreshed, false if no refresher available
22
+ */
23
+ private tryRefreshToken;
14
24
  /**
15
25
  * Override connect to handle JWT token refresh on errors
16
26
  */
17
27
  connect(): Promise<void>;
18
28
  /**
19
- * Override makeAdtRequest to handle JWT auth errors
20
- * Note: Token refresh is not supported in connection package - use auth-broker instead
29
+ * Override makeAdtRequest to handle JWT auth errors with automatic token refresh
21
30
  */
22
31
  makeAdtRequest(options: AbapRequestOptions): Promise<AxiosResponse>;
23
32
  /**
24
- * Override fetchCsrfToken to handle JWT auth errors
25
- * Note: Token refresh is not supported in connection package - use auth-broker instead
33
+ * Override fetchCsrfToken to handle JWT auth errors with automatic token refresh
26
34
  */
27
35
  protected fetchCsrfToken(url: string, retryCount?: number, retryDelay?: number): Promise<string>;
28
36
  private static validateConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"JwtAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/JwtAbapConnection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAc,aAAa,EAAE,MAAM,OAAO,CAAC;AAElD;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,sBAAsB;gBAGzD,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM;IAMpB,SAAS,CAAC,wBAAwB,IAAI,MAAM;IAW5C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA2C9B;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC;IAkCzE;;;OAGG;cACa,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,SAAI,EAAE,UAAU,SAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IA8B/F,OAAO,CAAC,MAAM,CAAC,cAAc;CAS9B"}
1
+ {"version":3,"file":"JwtAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/JwtAbapConnection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAc,aAAa,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,sBAAsB;IAC3D,OAAO,CAAC,cAAc,CAAC,CAAkB;IACzC,OAAO,CAAC,YAAY,CAAS;gBAG3B,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,eAAe;IAQlC,SAAS,CAAC,wBAAwB,IAAI,MAAM;IAO5C;;;OAGG;YACW,eAAe;IAmB7B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAgD9B;;OAEG;IACG,cAAc,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC;IAwCzE;;OAEG;cACa,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,SAAI,EAAE,UAAU,SAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAmC/F,OAAO,CAAC,MAAM,CAAC,cAAc;CAS9B"}
@@ -5,21 +5,46 @@ const AbstractAbapConnection_js_1 = require("./AbstractAbapConnection.js");
5
5
  const axios_1 = require("axios");
6
6
  /**
7
7
  * JWT Authentication connection for SAP BTP Cloud systems
8
- * Note: Token refresh functionality is not supported in this package.
9
- * Use @mcp-abap-adt/auth-broker for token refresh functionality.
8
+ *
9
+ * Supports automatic token refresh via ITokenRefresher injection:
10
+ * - If tokenRefresher is provided, 401/403 errors trigger automatic token refresh
11
+ * - If tokenRefresher is not provided, 401/403 errors throw an error (legacy behavior)
10
12
  */
11
13
  class JwtAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnection {
12
- constructor(config, logger, sessionId) {
14
+ tokenRefresher;
15
+ currentToken;
16
+ constructor(config, logger, sessionId, tokenRefresher) {
13
17
  JwtAbapConnection.validateConfig(config);
14
18
  super(config, logger || null, sessionId);
19
+ this.tokenRefresher = tokenRefresher;
20
+ this.currentToken = config.jwtToken;
15
21
  }
16
22
  buildAuthorizationHeader() {
17
- const config = this.getConfig();
18
- const { jwtToken } = config;
19
- // Log token preview for debugging (first 10 and last 4 chars)
20
- const tokenPreview = jwtToken ? `${jwtToken.substring(0, 10)}...${jwtToken.substring(Math.max(0, jwtToken.length - 4))}` : 'null';
23
+ // Use currentToken which may have been refreshed
24
+ const tokenPreview = this.currentToken ? `${this.currentToken.substring(0, 10)}...${this.currentToken.substring(Math.max(0, this.currentToken.length - 4))}` : 'null';
21
25
  this.logger?.debug(`[DEBUG] JwtAbapConnection.buildAuthorizationHeader - Using token: ${tokenPreview}`);
22
- return `Bearer ${jwtToken}`;
26
+ return `Bearer ${this.currentToken}`;
27
+ }
28
+ /**
29
+ * Refresh the JWT token using the injected tokenRefresher
30
+ * @returns true if token was refreshed, false if no refresher available
31
+ */
32
+ async tryRefreshToken() {
33
+ if (!this.tokenRefresher) {
34
+ this.logger?.debug(`[DEBUG] JwtAbapConnection - No tokenRefresher available, cannot refresh token`);
35
+ return false;
36
+ }
37
+ try {
38
+ this.logger?.debug(`[DEBUG] JwtAbapConnection - Refreshing token via tokenRefresher...`);
39
+ const newToken = await this.tokenRefresher.refreshToken();
40
+ this.currentToken = newToken;
41
+ this.logger?.debug(`[DEBUG] JwtAbapConnection - Token refreshed successfully`);
42
+ return true;
43
+ }
44
+ catch (error) {
45
+ this.logger?.error(`[ERROR] JwtAbapConnection - Failed to refresh token: ${error instanceof Error ? error.message : String(error)}`);
46
+ return false;
47
+ }
23
48
  }
24
49
  /**
25
50
  * Override connect to handle JWT token refresh on errors
@@ -51,8 +76,12 @@ class JwtAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnecti
51
76
  responseText.includes("Missing authorization")) {
52
77
  throw error;
53
78
  }
54
- // Token refresh is not supported in connection package
55
- // Use auth-broker for token refresh functionality
79
+ // Try to refresh token if tokenRefresher is available
80
+ if (await this.tryRefreshToken()) {
81
+ // Retry connect with new token
82
+ this.logger?.debug(`[DEBUG] JwtAbapConnection - Retrying connect after token refresh...`);
83
+ return this.connect();
84
+ }
56
85
  throw new Error("JWT token has expired. Please re-authenticate.");
57
86
  }
58
87
  // Re-throw other errors
@@ -60,8 +89,7 @@ class JwtAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnecti
60
89
  }
61
90
  }
62
91
  /**
63
- * Override makeAdtRequest to handle JWT auth errors
64
- * Note: Token refresh is not supported in connection package - use auth-broker instead
92
+ * Override makeAdtRequest to handle JWT auth errors with automatic token refresh
65
93
  */
66
94
  async makeAdtRequest(options) {
67
95
  this.logger?.debug(`[DEBUG] JwtAbapConnection.makeAdtRequest - Starting request: ${options.method} ${options.url}`);
@@ -75,7 +103,7 @@ class JwtAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnecti
75
103
  // Handle JWT auth errors (401/403)
76
104
  if (error instanceof axios_1.AxiosError &&
77
105
  (error.response?.status === 401 || error.response?.status === 403)) {
78
- this.logger?.debug(`[DEBUG] JwtAbapConnection.makeAdtRequest - Got ${error.response.status}, checking if refresh is possible...`);
106
+ this.logger?.debug(`[DEBUG] JwtAbapConnection.makeAdtRequest - Got ${error.response.status}, attempting token refresh...`);
79
107
  // Check if this is really an auth error, not a permissions error
80
108
  const responseData = error.response?.data;
81
109
  const responseText = typeof responseData === "string" ? responseData : JSON.stringify(responseData || "");
@@ -85,16 +113,20 @@ class JwtAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnecti
85
113
  responseText.includes("Missing authorization")) {
86
114
  throw error;
87
115
  }
88
- // Token refresh is not supported in connection package
89
- // Use auth-broker for token refresh functionality
116
+ // Try to refresh token if tokenRefresher is available
117
+ if (await this.tryRefreshToken()) {
118
+ // Reset connection state and retry request with new token
119
+ this.logger?.debug(`[DEBUG] JwtAbapConnection.makeAdtRequest - Retrying request after token refresh...`);
120
+ this.reset();
121
+ return super.makeAdtRequest(options);
122
+ }
90
123
  throw new Error("JWT token has expired. Please re-authenticate.");
91
124
  }
92
125
  throw error;
93
126
  }
94
127
  }
95
128
  /**
96
- * Override fetchCsrfToken to handle JWT auth errors
97
- * Note: Token refresh is not supported in connection package - use auth-broker instead
129
+ * Override fetchCsrfToken to handle JWT auth errors with automatic token refresh
98
130
  */
99
131
  async fetchCsrfToken(url, retryCount = 3, retryDelay = 1000) {
100
132
  try {
@@ -114,8 +146,12 @@ class JwtAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnecti
114
146
  responseText.includes("Missing authorization")) {
115
147
  throw error;
116
148
  }
117
- // Token refresh is not supported in connection package
118
- // Use auth-broker for token refresh functionality
149
+ // Try to refresh token if tokenRefresher is available
150
+ if (await this.tryRefreshToken()) {
151
+ // Retry CSRF token fetch with new token
152
+ this.logger?.debug(`[DEBUG] JwtAbapConnection.fetchCsrfToken - Retrying after token refresh...`);
153
+ return super.fetchCsrfToken(url, retryCount, retryDelay);
154
+ }
119
155
  throw new Error("JWT token has expired. Please re-authenticate.");
120
156
  }
121
157
  // Re-throw other errors
@@ -1,5 +1,6 @@
1
1
  import { SapConfig } from "../config/sapConfig.js";
2
2
  import { AbapConnection } from "./AbapConnection.js";
3
3
  import { ILogger } from "../logger.js";
4
- export declare function createAbapConnection(config: SapConfig, logger?: ILogger | null, sessionId?: string): AbapConnection;
4
+ import type { ITokenRefresher } from "@mcp-abap-adt/interfaces";
5
+ export declare function createAbapConnection(config: SapConfig, logger?: ILogger | null, sessionId?: string, tokenRefresher?: ITokenRefresher): AbapConnection;
5
6
  //# sourceMappingURL=connectionFactory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"connectionFactory.d.ts","sourceRoot":"","sources":["../../src/connection/connectionFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,cAAc,CAShB"}
1
+ {"version":3,"file":"connectionFactory.d.ts","sourceRoot":"","sources":["../../src/connection/connectionFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,eAAe,GAC/B,cAAc,CAShB"}
@@ -3,12 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createAbapConnection = createAbapConnection;
4
4
  const JwtAbapConnection_js_1 = require("./JwtAbapConnection.js");
5
5
  const BaseAbapConnection_js_1 = require("./BaseAbapConnection.js");
6
- function createAbapConnection(config, logger, sessionId) {
6
+ function createAbapConnection(config, logger, sessionId, tokenRefresher) {
7
7
  switch (config.authType) {
8
8
  case "basic":
9
9
  return new BaseAbapConnection_js_1.BaseAbapConnection(config, logger, sessionId);
10
10
  case "jwt":
11
- return new JwtAbapConnection_js_1.JwtAbapConnection(config, logger, sessionId);
11
+ return new JwtAbapConnection_js_1.JwtAbapConnection(config, logger, sessionId, tokenRefresher);
12
12
  default:
13
13
  throw new Error(`Unsupported SAP authentication type: ${config.authType}`);
14
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/connection",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "ABAP connection layer for MCP ABAP ADT server",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -42,7 +42,7 @@
42
42
  "node": ">=18.0.0"
43
43
  },
44
44
  "dependencies": {
45
- "@mcp-abap-adt/interfaces": "^0.2.0",
45
+ "@mcp-abap-adt/interfaces": "^0.2.5",
46
46
  "axios": "^1.11.0",
47
47
  "commander": "^14.0.2",
48
48
  "express": "^5.1.0",