@mcp-abap-adt/connection 1.0.0 → 1.1.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
@@ -7,6 +7,7 @@ ABAP connection layer for MCP ABAP ADT server. Provides a unified interface for
7
7
  - 🔐 **Multiple Authentication Methods**:
8
8
  - Basic Auth for on-premise SAP systems
9
9
  - JWT/OAuth2 for SAP BTP ABAP Environment
10
+ - SAML session cookies for pre-authenticated enterprise flows
10
11
  - 🔄 **Token Management**:
11
12
  - Token refresh is handled by `@mcp-abap-adt/auth-broker` package
12
13
  - Connection package focuses on HTTP communication only
@@ -15,8 +16,11 @@ ABAP connection layer for MCP ABAP ADT server. Provides a unified interface for
15
16
  - Session state persistence is handled by `@mcp-abap-adt/auth-broker` package
16
17
  - 🏗️ **Clean Architecture**:
17
18
  - Abstract base class for common HTTP/session logic
18
- - Auth-type specific implementations (BaseAbapConnection, JwtAbapConnection)
19
+ - Auth-type specific implementations (BaseAbapConnection, JwtAbapConnection, SamlAbapConnection)
19
20
  - Proper separation of concerns - no JWT logic in base class
21
+ - 🔌 **Realtime Transport Scaffold**:
22
+ - Generic `GenericWebSocketTransport` with pluggable WS factory
23
+ - Reusable for debugger/traces and other event-driven flows
20
24
  - 📝 **Custom Logging**: Pluggable logger interface for integration with any logging system
21
25
  - 🛠️ **CLI Tool**: See [JWT Auth Tools](./docs/JWT_AUTH_TOOLS.md) for obtaining SAP BTP tokens
22
26
  - 📦 **TypeScript**: Full TypeScript support with type definitions included
@@ -43,6 +47,16 @@ The package uses a clean separation of concerns:
43
47
  - Suitable for SAP BTP ABAP Environment
44
48
  - Token refresh handled by auth-broker package
45
49
 
50
+ - **`SamlAbapConnection`** (concrete, exported):
51
+ - Session-cookie-based authentication (`authType: "saml"`)
52
+ - Uses existing SSO/SAML session cookies
53
+ - Fetches CSRF token and executes ADT requests in same HTTP model
54
+
55
+ - **`GenericWebSocketTransport`** (concrete, exported):
56
+ - Transport abstraction for realtime WS message flows
57
+ - Pluggable factory, envelope-based send/receive
58
+ - Intended for higher-level debugger/trace session orchestration
59
+
46
60
  ## Responsibilities and Design Principles
47
61
 
48
62
  ### Core Development Principle
@@ -0,0 +1,46 @@
1
+ import type { IWebSocketCloseInfo, IWebSocketConnectOptions, IWebSocketMessageEnvelope, IWebSocketMessageHandler, IWebSocketTransport } from '@mcp-abap-adt/interfaces';
2
+ /**
3
+ * Minimal WS-like instance contract to avoid hard dependency on a specific WS library.
4
+ */
5
+ interface IWebSocketLike {
6
+ readyState: number;
7
+ send(data: string): void;
8
+ close(code?: number, reason?: string): void;
9
+ onopen: ((event: unknown) => void) | null;
10
+ onmessage: ((event: {
11
+ data: unknown;
12
+ }) => void) | null;
13
+ onerror: ((event: unknown) => void) | null;
14
+ onclose: ((event: {
15
+ code?: number;
16
+ reason?: string;
17
+ wasClean?: boolean;
18
+ }) => void) | null;
19
+ }
20
+ interface IWebSocketFactory {
21
+ create(url: string, protocols?: string | string[], options?: IWebSocketConnectOptions): IWebSocketLike;
22
+ }
23
+ export declare class GenericWebSocketTransport implements IWebSocketTransport {
24
+ private readonly factory;
25
+ private socket;
26
+ private messageHandlers;
27
+ private openHandlers;
28
+ private errorHandlers;
29
+ private closeHandlers;
30
+ constructor(factory: IWebSocketFactory);
31
+ connect(url: string, options?: IWebSocketConnectOptions): Promise<void>;
32
+ disconnect(code?: number, reason?: string): Promise<void>;
33
+ send<T = unknown>(message: IWebSocketMessageEnvelope<T>): Promise<void>;
34
+ onMessage<T = unknown>(handler: IWebSocketMessageHandler<T>): void;
35
+ onOpen(handler: () => void | Promise<void>): void;
36
+ onError(handler: (error: Error) => void | Promise<void>): void;
37
+ onClose(handler: (info: IWebSocketCloseInfo) => void | Promise<void>): void;
38
+ isConnected(): boolean;
39
+ private handleIncomingMessage;
40
+ private normalizeError;
41
+ private emitOpen;
42
+ private emitError;
43
+ private emitClose;
44
+ }
45
+ export type { IWebSocketFactory, IWebSocketLike };
46
+ //# sourceMappingURL=GenericWebSocketTransport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GenericWebSocketTransport.d.ts","sourceRoot":"","sources":["../../src/connection/GenericWebSocketTransport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAElC;;GAEG;AACH,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1C,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACvD,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3C,OAAO,EACH,CAAC,CAAC,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC,GACzE,IAAI,CAAC;CACV;AAED,UAAU,iBAAiB;IACzB,MAAM,CACJ,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,EAC7B,OAAO,CAAC,EAAE,wBAAwB,GACjC,cAAc,CAAC;CACnB;AAID,qBAAa,yBAA0B,YAAW,mBAAmB;IASvD,OAAO,CAAC,QAAQ,CAAC,OAAO;IARpC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,eAAe,CAAgD;IACvE,OAAO,CAAC,YAAY,CAAyC;IAC7D,OAAO,CAAC,aAAa,CAAqD;IAC1E,OAAO,CAAC,aAAa,CAEd;gBAEsB,OAAO,EAAE,iBAAiB;IAEjD,OAAO,CACX,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,IAAI,CAAC;IAqDV,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBzD,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,OAAO,EAAE,yBAAyB,CAAC,CAAC,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC;IAQhB,SAAS,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,IAAI;IAIlE,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIjD,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAI9D,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAI3E,WAAW,IAAI,OAAO;YAIR,qBAAqB;IAoBnC,OAAO,CAAC,cAAc;YAUR,QAAQ;YAMR,SAAS;YAMT,SAAS;CAKxB;AAED,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GenericWebSocketTransport = void 0;
4
+ const WS_OPEN = 1;
5
+ class GenericWebSocketTransport {
6
+ factory;
7
+ socket = null;
8
+ messageHandlers = [];
9
+ openHandlers = [];
10
+ errorHandlers = [];
11
+ closeHandlers = [];
12
+ constructor(factory) {
13
+ this.factory = factory;
14
+ }
15
+ async connect(url, options) {
16
+ if (this.socket && this.isConnected()) {
17
+ return;
18
+ }
19
+ const connectTimeoutMs = options?.connectTimeoutMs ?? 15000;
20
+ const socket = this.factory.create(url, options?.protocols, options);
21
+ this.socket = socket;
22
+ await new Promise((resolve, reject) => {
23
+ const timeoutId = setTimeout(() => {
24
+ reject(new Error(`WebSocket connect timeout after ${connectTimeoutMs}ms`));
25
+ }, connectTimeoutMs);
26
+ socket.onopen = () => {
27
+ clearTimeout(timeoutId);
28
+ this.emitOpen().catch(() => {
29
+ // no-op
30
+ });
31
+ resolve();
32
+ };
33
+ socket.onerror = (event) => {
34
+ clearTimeout(timeoutId);
35
+ const err = this.normalizeError(event, 'WebSocket connection error');
36
+ this.emitError(err).catch(() => {
37
+ // no-op
38
+ });
39
+ reject(err);
40
+ };
41
+ socket.onclose = (event) => {
42
+ clearTimeout(timeoutId);
43
+ const info = {
44
+ code: typeof event?.code === 'number' ? event.code : 1006,
45
+ reason: event?.reason,
46
+ wasClean: event?.wasClean,
47
+ };
48
+ this.emitClose(info).catch(() => {
49
+ // no-op
50
+ });
51
+ };
52
+ socket.onmessage = (event) => {
53
+ this.handleIncomingMessage(event?.data).catch(() => {
54
+ // no-op
55
+ });
56
+ };
57
+ });
58
+ }
59
+ async disconnect(code, reason) {
60
+ if (!this.socket) {
61
+ return;
62
+ }
63
+ const socket = this.socket;
64
+ this.socket = null;
65
+ try {
66
+ socket.close(code, reason);
67
+ }
68
+ catch (error) {
69
+ const err = this.normalizeError(error, 'WebSocket close failed');
70
+ await this.emitError(err);
71
+ }
72
+ }
73
+ async send(message) {
74
+ if (!this.socket || this.socket.readyState !== WS_OPEN) {
75
+ throw new Error('WebSocket is not connected');
76
+ }
77
+ this.socket.send(JSON.stringify(message));
78
+ }
79
+ onMessage(handler) {
80
+ this.messageHandlers.push(handler);
81
+ }
82
+ onOpen(handler) {
83
+ this.openHandlers.push(handler);
84
+ }
85
+ onError(handler) {
86
+ this.errorHandlers.push(handler);
87
+ }
88
+ onClose(handler) {
89
+ this.closeHandlers.push(handler);
90
+ }
91
+ isConnected() {
92
+ return !!this.socket && this.socket.readyState === WS_OPEN;
93
+ }
94
+ async handleIncomingMessage(rawData) {
95
+ try {
96
+ const text = typeof rawData === 'string'
97
+ ? rawData
98
+ : rawData instanceof Buffer
99
+ ? rawData.toString('utf8')
100
+ : String(rawData ?? '');
101
+ const parsed = JSON.parse(text);
102
+ for (const handler of this.messageHandlers) {
103
+ await handler(parsed);
104
+ }
105
+ }
106
+ catch (error) {
107
+ await this.emitError(this.normalizeError(error, 'Failed to process WS message'));
108
+ }
109
+ }
110
+ normalizeError(error, fallback) {
111
+ if (error instanceof Error) {
112
+ return error;
113
+ }
114
+ if (typeof error === 'string' && error.trim()) {
115
+ return new Error(error.trim());
116
+ }
117
+ return new Error(fallback);
118
+ }
119
+ async emitOpen() {
120
+ for (const handler of this.openHandlers) {
121
+ await handler();
122
+ }
123
+ }
124
+ async emitError(error) {
125
+ for (const handler of this.errorHandlers) {
126
+ await handler(error);
127
+ }
128
+ }
129
+ async emitClose(info) {
130
+ for (const handler of this.closeHandlers) {
131
+ await handler(info);
132
+ }
133
+ }
134
+ }
135
+ exports.GenericWebSocketTransport = GenericWebSocketTransport;
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
+ export type { IWebSocketCloseInfo, IWebSocketConnectOptions, IWebSocketMessageEnvelope, IWebSocketMessageHandler, IWebSocketTransport, } from '@mcp-abap-adt/interfaces';
1
2
  export type { SapAuthType, SapConfig, } from './config/sapConfig.js';
2
3
  export { sapConfigSignature } from './config/sapConfig.js';
3
4
  export type { AbapConnection, AbapRequestOptions, } from './connection/AbapConnection.js';
4
5
  export { BaseAbapConnection, BaseAbapConnection as OnPremAbapConnection, } from './connection/BaseAbapConnection.js';
5
6
  export { createAbapConnection } from './connection/connectionFactory.js';
6
7
  export { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from './connection/csrfConfig.js';
8
+ export { GenericWebSocketTransport, type IWebSocketFactory, type IWebSocketLike, } from './connection/GenericWebSocketTransport.js';
7
9
  export { JwtAbapConnection, JwtAbapConnection as CloudAbapConnection, } from './connection/JwtAbapConnection.js';
8
10
  export { SamlAbapConnection } from './connection/SamlAbapConnection.js';
9
11
  export type { ILogger } from './logger.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,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,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,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,6 +1,7 @@
1
1
  "use strict";
2
+ // Types - re-exported from interfaces package with backward compatibility aliases
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getTimeoutConfig = exports.getTimeout = exports.SamlAbapConnection = exports.CloudAbapConnection = exports.JwtAbapConnection = 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.CloudAbapConnection = exports.JwtAbapConnection = exports.GenericWebSocketTransport = exports.CSRF_ERROR_MESSAGES = exports.CSRF_CONFIG = exports.createAbapConnection = exports.OnPremAbapConnection = exports.BaseAbapConnection = exports.sapConfigSignature = void 0;
4
5
  // Config utilities
5
6
  var sapConfig_js_1 = require("./config/sapConfig.js");
6
7
  Object.defineProperty(exports, "sapConfigSignature", { enumerable: true, get: function () { return sapConfig_js_1.sapConfigSignature; } });
@@ -16,6 +17,8 @@ Object.defineProperty(exports, "createAbapConnection", { enumerable: true, get:
16
17
  var csrfConfig_js_1 = require("./connection/csrfConfig.js");
17
18
  Object.defineProperty(exports, "CSRF_CONFIG", { enumerable: true, get: function () { return csrfConfig_js_1.CSRF_CONFIG; } });
18
19
  Object.defineProperty(exports, "CSRF_ERROR_MESSAGES", { enumerable: true, get: function () { return csrfConfig_js_1.CSRF_ERROR_MESSAGES; } });
20
+ var GenericWebSocketTransport_js_1 = require("./connection/GenericWebSocketTransport.js");
21
+ Object.defineProperty(exports, "GenericWebSocketTransport", { enumerable: true, get: function () { return GenericWebSocketTransport_js_1.GenericWebSocketTransport; } });
19
22
  var JwtAbapConnection_js_1 = require("./connection/JwtAbapConnection.js");
20
23
  Object.defineProperty(exports, "JwtAbapConnection", { enumerable: true, get: function () { return JwtAbapConnection_js_1.JwtAbapConnection; } });
21
24
  Object.defineProperty(exports, "CloudAbapConnection", { enumerable: true, get: function () { return JwtAbapConnection_js_1.JwtAbapConnection; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/connection",
3
- "version": "1.0.0",
3
+ "version": "1.1.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,16 +46,16 @@
46
46
  "node": ">=18.0.0"
47
47
  },
48
48
  "dependencies": {
49
- "@mcp-abap-adt/interfaces": "^2.3.0",
50
- "axios": "^1.11.0",
51
- "commander": "^14.0.2",
49
+ "@mcp-abap-adt/interfaces": "^2.4.0",
50
+ "axios": "^1.13.5",
51
+ "commander": "^14.0.3",
52
52
  "express": "^5.1.0",
53
53
  "open": "^11.0.0"
54
54
  },
55
55
  "devDependencies": {
56
- "@biomejs/biome": "^2.3.10",
56
+ "@biomejs/biome": "^2.3.14",
57
57
  "@types/jest": "^30.0.0",
58
- "@types/node": "^24.2.1",
58
+ "@types/node": "^25.2.3",
59
59
  "jest": "^30.2.0",
60
60
  "jest-util": "^30.2.0",
61
61
  "ts-jest": "^29.2.5",