@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 +32 -0
- package/dist/connection/JwtAbapConnection.d.ts +15 -7
- package/dist/connection/JwtAbapConnection.d.ts.map +1 -1
- package/dist/connection/JwtAbapConnection.js +55 -19
- package/dist/connection/connectionFactory.d.ts +2 -1
- package/dist/connection/connectionFactory.d.ts.map +1 -1
- package/dist/connection/connectionFactory.js +2 -2
- package/package.json +2 -2
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
|
-
*
|
|
9
|
-
*
|
|
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
|
-
|
|
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;
|
|
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
|
-
*
|
|
9
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
const {
|
|
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 ${
|
|
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
|
-
//
|
|
55
|
-
|
|
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},
|
|
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
|
-
//
|
|
89
|
-
|
|
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
|
-
//
|
|
118
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
+
"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.
|
|
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",
|