@mcp-abap-adt/connection 1.5.2 → 1.6.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.
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @mcp-abap-adt/connection
|
|
2
2
|
|
|
3
|
+
[](https://stand-with-ukraine.pp.ua)
|
|
4
|
+
|
|
3
5
|
ABAP connection layer for MCP ABAP ADT server. Provides a unified interface for connecting to SAP ABAP systems via ADT (ABAP Development Tools) protocol, supporting both on-premise (Basic Auth) and cloud (JWT/OAuth2) authentication methods.
|
|
4
6
|
|
|
5
7
|
## Key Features
|
|
@@ -5,7 +5,7 @@ import type { AbapConnection } from './AbapConnection.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* RFC-based connection for on-premise SAP systems.
|
|
7
7
|
*
|
|
8
|
-
* Uses
|
|
8
|
+
* Uses @mcp-abap-adt/sap-rfc-lite to call SADT_REST_RFC_ENDPOINT — the same standard SAP FM
|
|
9
9
|
* that Eclipse ADT uses for all on-premise ADT operations via JCo.
|
|
10
10
|
*
|
|
11
11
|
* RFC connections are inherently stateful: one ABAP session persists for the
|
|
@@ -18,7 +18,7 @@ import type { AbapConnection } from './AbapConnection.js';
|
|
|
18
18
|
*
|
|
19
19
|
* Prerequisites:
|
|
20
20
|
* - SAP NW RFC SDK installed on the machine
|
|
21
|
-
* -
|
|
21
|
+
* - @mcp-abap-adt/sap-rfc-lite package installed: npm install @mcp-abap-adt/sap-rfc-lite
|
|
22
22
|
*/
|
|
23
23
|
export declare class RfcAbapConnection implements AbapConnection {
|
|
24
24
|
private readonly config;
|
|
@@ -27,11 +27,13 @@ export declare class RfcAbapConnection implements AbapConnection {
|
|
|
27
27
|
private readonly sessionId;
|
|
28
28
|
private readonly baseUrl;
|
|
29
29
|
private readonly rfcParams;
|
|
30
|
+
private sessionType;
|
|
31
|
+
private sessionCookie;
|
|
30
32
|
constructor(config: SapConfig, logger?: ILogger | null);
|
|
31
33
|
connect(): Promise<void>;
|
|
32
34
|
getBaseUrl(): Promise<string>;
|
|
33
35
|
getSessionId(): string | null;
|
|
34
|
-
setSessionType(
|
|
36
|
+
setSessionType(type: 'stateful' | 'stateless'): void;
|
|
35
37
|
makeAdtRequest<T = any, D = any>(options: IAbapRequestOptions): Promise<IAdtResponse<T, D>>;
|
|
36
38
|
/**
|
|
37
39
|
* Reset the connection — for RFC this closes the session.
|
|
@@ -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;
|
|
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"}
|
|
@@ -59,7 +59,7 @@ function detectExceptionStatus(body) {
|
|
|
59
59
|
/**
|
|
60
60
|
* RFC-based connection for on-premise SAP systems.
|
|
61
61
|
*
|
|
62
|
-
* Uses
|
|
62
|
+
* Uses @mcp-abap-adt/sap-rfc-lite to call SADT_REST_RFC_ENDPOINT — the same standard SAP FM
|
|
63
63
|
* that Eclipse ADT uses for all on-premise ADT operations via JCo.
|
|
64
64
|
*
|
|
65
65
|
* RFC connections are inherently stateful: one ABAP session persists for the
|
|
@@ -72,7 +72,7 @@ function detectExceptionStatus(body) {
|
|
|
72
72
|
*
|
|
73
73
|
* Prerequisites:
|
|
74
74
|
* - SAP NW RFC SDK installed on the machine
|
|
75
|
-
* -
|
|
75
|
+
* - @mcp-abap-adt/sap-rfc-lite package installed: npm install @mcp-abap-adt/sap-rfc-lite
|
|
76
76
|
*/
|
|
77
77
|
class RfcAbapConnection {
|
|
78
78
|
config;
|
|
@@ -81,6 +81,8 @@ class RfcAbapConnection {
|
|
|
81
81
|
sessionId;
|
|
82
82
|
baseUrl;
|
|
83
83
|
rfcParams;
|
|
84
|
+
sessionType = 'stateless';
|
|
85
|
+
sessionCookie = null;
|
|
84
86
|
constructor(config, logger = null) {
|
|
85
87
|
this.config = config;
|
|
86
88
|
this.logger = logger;
|
|
@@ -93,16 +95,16 @@ class RfcAbapConnection {
|
|
|
93
95
|
async connect() {
|
|
94
96
|
let Client;
|
|
95
97
|
try {
|
|
96
|
-
// Dynamic require —
|
|
98
|
+
// Dynamic require — @mcp-abap-adt/sap-rfc-lite is NOT a declared dependency.
|
|
97
99
|
// Users who need RFC connections must install it manually:
|
|
98
|
-
// npm install
|
|
100
|
+
// npm install @mcp-abap-adt/sap-rfc-lite (+ SAP NW RFC SDK on the machine)
|
|
99
101
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
100
|
-
const noderfc = require('
|
|
102
|
+
const noderfc = require('@mcp-abap-adt/sap-rfc-lite');
|
|
101
103
|
Client = noderfc.Client;
|
|
102
104
|
}
|
|
103
105
|
catch (e) {
|
|
104
|
-
throw new Error('
|
|
105
|
-
'and run: npm install
|
|
106
|
+
throw new Error('@mcp-abap-adt/sap-rfc-lite is not available. To use RFC connections, install SAP NW RFC SDK ' +
|
|
107
|
+
'and run: npm install @mcp-abap-adt/sap-rfc-lite. ' +
|
|
106
108
|
`Details: ${e instanceof Error ? e.message : String(e)}`);
|
|
107
109
|
}
|
|
108
110
|
this.rfcClient = new Client(this.rfcParams);
|
|
@@ -123,8 +125,15 @@ class RfcAbapConnection {
|
|
|
123
125
|
getSessionId() {
|
|
124
126
|
return this.sessionId;
|
|
125
127
|
}
|
|
126
|
-
setSessionType(
|
|
127
|
-
|
|
128
|
+
setSessionType(type) {
|
|
129
|
+
this.sessionType = type;
|
|
130
|
+
if (type === 'stateless') {
|
|
131
|
+
this.sessionCookie = null;
|
|
132
|
+
this.logger?.debug('RFC session type: stateless (cookie cleared)');
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
this.logger?.debug('RFC session type: stateful (will capture cookies)');
|
|
136
|
+
}
|
|
128
137
|
}
|
|
129
138
|
async makeAdtRequest(options) {
|
|
130
139
|
if (!this.rfcClient?.alive) {
|
|
@@ -152,6 +161,17 @@ class RfcAbapConnection {
|
|
|
152
161
|
headerFields.push({ NAME: name, VALUE: value });
|
|
153
162
|
}
|
|
154
163
|
}
|
|
164
|
+
// Inject session type header so SAP creates/maintains an ICM session
|
|
165
|
+
if (this.sessionType === 'stateful') {
|
|
166
|
+
headerFields.push({ NAME: 'x-sap-adt-sessiontype', VALUE: 'stateful' });
|
|
167
|
+
}
|
|
168
|
+
// Replay session cookie for stateful operations (cookie captured from LOCK response)
|
|
169
|
+
if (this.sessionCookie) {
|
|
170
|
+
const existingCookie = headerFields.find((h) => h.NAME.toLowerCase() === 'cookie');
|
|
171
|
+
if (!existingCookie) {
|
|
172
|
+
headerFields.push({ NAME: 'Cookie', VALUE: this.sessionCookie });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
155
175
|
// Ensure Content-Type for body
|
|
156
176
|
const body = options.data !== undefined && options.data !== null
|
|
157
177
|
? String(options.data)
|
|
@@ -204,6 +224,21 @@ class RfcAbapConnection {
|
|
|
204
224
|
respHeaders[field.NAME.toLowerCase()] = field.VALUE;
|
|
205
225
|
}
|
|
206
226
|
}
|
|
227
|
+
// Capture session cookie from LOCK/stateful responses for cookie replay
|
|
228
|
+
if (this.sessionType === 'stateful' && respHeaders['set-cookie']) {
|
|
229
|
+
const setCookieHeader = respHeaders['set-cookie'];
|
|
230
|
+
// Extract cookie name=value pairs (strip attributes like Path, Secure, etc.)
|
|
231
|
+
const cookies = Array.isArray(setCookieHeader)
|
|
232
|
+
? setCookieHeader
|
|
233
|
+
: [setCookieHeader];
|
|
234
|
+
const cookieValues = cookies
|
|
235
|
+
.map((c) => c.split(';')[0].trim())
|
|
236
|
+
.filter(Boolean);
|
|
237
|
+
if (cookieValues.length > 0) {
|
|
238
|
+
this.sessionCookie = cookieValues.join('; ');
|
|
239
|
+
this.logger?.debug(`RFC: captured session cookie: ${this.sessionCookie}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
207
242
|
// On some systems (e.g. BASIS < 7.50), SADT_REST_RFC_ENDPOINT does not
|
|
208
243
|
// populate STATUS_LINE, returning status 0. Detect errors from the
|
|
209
244
|
// response body: ADT exception XML indicates a failed request.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/connection",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.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,8 @@
|
|
|
46
46
|
"node": ">=18.0.0"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@mcp-abap-adt/interfaces": "^
|
|
49
|
+
"@mcp-abap-adt/interfaces": "^6.1.0",
|
|
50
|
+
"@mcp-abap-adt/sap-rfc-lite": "^0.1.0",
|
|
50
51
|
"axios": "^1.13.5",
|
|
51
52
|
"commander": "^14.0.3",
|
|
52
53
|
"express": "^5.1.0",
|
|
@@ -56,6 +57,7 @@
|
|
|
56
57
|
"@biomejs/biome": "^2.3.14",
|
|
57
58
|
"@types/jest": "^30.0.0",
|
|
58
59
|
"@types/node": "^25.2.3",
|
|
60
|
+
"dotenv": "^17.3.1",
|
|
59
61
|
"jest": "^30.2.0",
|
|
60
62
|
"jest-util": "^30.2.0",
|
|
61
63
|
"ts-jest": "^29.2.5",
|