@mcp-abap-adt/connection 0.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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -0
  3. package/bin/sap-abap-auth.js +600 -0
  4. package/dist/config/sapConfig.d.ts +43 -0
  5. package/dist/config/sapConfig.d.ts.map +1 -0
  6. package/dist/config/sapConfig.js +202 -0
  7. package/dist/connection/AbapConnection.d.ts +22 -0
  8. package/dist/connection/AbapConnection.d.ts.map +1 -0
  9. package/dist/connection/AbapConnection.js +2 -0
  10. package/dist/connection/AbstractAbapConnection.d.ts +115 -0
  11. package/dist/connection/AbstractAbapConnection.d.ts.map +1 -0
  12. package/dist/connection/AbstractAbapConnection.js +716 -0
  13. package/dist/connection/BaseAbapConnection.d.ts +17 -0
  14. package/dist/connection/BaseAbapConnection.d.ts.map +1 -0
  15. package/dist/connection/BaseAbapConnection.js +68 -0
  16. package/dist/connection/JwtAbapConnection.d.ts +33 -0
  17. package/dist/connection/JwtAbapConnection.d.ts.map +1 -0
  18. package/dist/connection/JwtAbapConnection.js +305 -0
  19. package/dist/connection/connectionFactory.d.ts +5 -0
  20. package/dist/connection/connectionFactory.d.ts.map +1 -0
  21. package/dist/connection/connectionFactory.js +15 -0
  22. package/dist/index.d.ts +13 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +29 -0
  25. package/dist/logger.d.ts +67 -0
  26. package/dist/logger.d.ts.map +1 -0
  27. package/dist/logger.js +2 -0
  28. package/dist/utils/FileSessionStorage.d.ts +73 -0
  29. package/dist/utils/FileSessionStorage.d.ts.map +1 -0
  30. package/dist/utils/FileSessionStorage.js +191 -0
  31. package/dist/utils/timeouts.d.ts +8 -0
  32. package/dist/utils/timeouts.d.ts.map +1 -0
  33. package/dist/utils/timeouts.js +21 -0
  34. package/dist/utils/tokenRefresh.d.ts +17 -0
  35. package/dist/utils/tokenRefresh.d.ts.map +1 -0
  36. package/dist/utils/tokenRefresh.js +53 -0
  37. package/package.json +63 -0
@@ -0,0 +1,73 @@
1
+ /**
2
+ * File-based session storage implementation
3
+ * Stores session state (cookies, CSRF tokens) in JSON files on disk
4
+ */
5
+ import { ISessionStorage, SessionState } from '../logger.js';
6
+ export interface FileSessionStorageOptions {
7
+ /**
8
+ * Directory to store session files
9
+ * @default '.sessions'
10
+ */
11
+ sessionDir?: string;
12
+ /**
13
+ * Whether to create session directory if it doesn't exist
14
+ * @default true
15
+ */
16
+ createDir?: boolean;
17
+ /**
18
+ * Pretty-print JSON files (for debugging)
19
+ * @default false
20
+ */
21
+ prettyPrint?: boolean;
22
+ }
23
+ /**
24
+ * File-based session storage
25
+ * Stores each session in a separate JSON file: .sessions/<sessionId>.json
26
+ */
27
+ export declare class FileSessionStorage implements ISessionStorage {
28
+ private readonly sessionDir;
29
+ private readonly prettyPrint;
30
+ constructor(options?: FileSessionStorageOptions);
31
+ /**
32
+ * Get file path for session
33
+ */
34
+ private getSessionFilePath;
35
+ /**
36
+ * Save session state to file
37
+ */
38
+ save(sessionId: string, state: SessionState): Promise<void>;
39
+ /**
40
+ * Load session state from file
41
+ */
42
+ load(sessionId: string): Promise<SessionState | null>;
43
+ /**
44
+ * Delete session state file
45
+ */
46
+ delete(sessionId: string): Promise<void>;
47
+ /**
48
+ * List all session IDs
49
+ */
50
+ listSessions(): Promise<string[]>;
51
+ /**
52
+ * Get session metadata (without loading full state)
53
+ */
54
+ getSessionMetadata(sessionId: string): Promise<{
55
+ sessionId: string;
56
+ timestamp: number;
57
+ pid: number;
58
+ age: number;
59
+ } | null>;
60
+ /**
61
+ * Clean up stale sessions (older than maxAge)
62
+ */
63
+ cleanupStaleSessions(maxAgeMs?: number): Promise<string[]>;
64
+ /**
65
+ * Clean up sessions from dead processes
66
+ */
67
+ cleanupDeadProcessSessions(): Promise<string[]>;
68
+ /**
69
+ * Clear all sessions
70
+ */
71
+ clearAll(): Promise<void>;
72
+ }
73
+ //# sourceMappingURL=FileSessionStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileSessionStorage.d.ts","sourceRoot":"","sources":["../../src/utils/FileSessionStorage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE7D,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IACxD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;gBAE1B,OAAO,GAAE,yBAA8B;IAenD;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBjE;;OAEG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAiB3D;;OAEG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ9C;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAWvC;;OAEG;IACG,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QACnD,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;KACb,GAAG,IAAI,CAAC;IAqBT;;OAEG;IACG,oBAAoB,CAAC,QAAQ,GAAE,MAAuB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAgBhF;;OAEG;IACG,0BAA0B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAqBrD;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAMhC"}
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ /**
3
+ * File-based session storage implementation
4
+ * Stores session state (cookies, CSRF tokens) in JSON files on disk
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.FileSessionStorage = void 0;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ /**
44
+ * File-based session storage
45
+ * Stores each session in a separate JSON file: .sessions/<sessionId>.json
46
+ */
47
+ class FileSessionStorage {
48
+ sessionDir;
49
+ prettyPrint;
50
+ constructor(options = {}) {
51
+ const { sessionDir = '.sessions', createDir = true, prettyPrint = false } = options;
52
+ this.sessionDir = path.resolve(process.cwd(), sessionDir);
53
+ this.prettyPrint = prettyPrint;
54
+ if (createDir && !fs.existsSync(this.sessionDir)) {
55
+ fs.mkdirSync(this.sessionDir, { recursive: true });
56
+ }
57
+ }
58
+ /**
59
+ * Get file path for session
60
+ */
61
+ getSessionFilePath(sessionId) {
62
+ // Sanitize session ID to prevent path traversal
63
+ const sanitizedId = sessionId.replace(/[^a-zA-Z0-9_-]/g, '_');
64
+ return path.join(this.sessionDir, `${sanitizedId}.json`);
65
+ }
66
+ /**
67
+ * Save session state to file
68
+ */
69
+ async save(sessionId, state) {
70
+ const filePath = this.getSessionFilePath(sessionId);
71
+ const data = {
72
+ sessionId,
73
+ timestamp: Date.now(),
74
+ pid: process.pid,
75
+ state
76
+ };
77
+ const json = this.prettyPrint
78
+ ? JSON.stringify(data, null, 2)
79
+ : JSON.stringify(data);
80
+ await fs.promises.writeFile(filePath, json, 'utf-8');
81
+ }
82
+ /**
83
+ * Load session state from file
84
+ */
85
+ async load(sessionId) {
86
+ const filePath = this.getSessionFilePath(sessionId);
87
+ if (!fs.existsSync(filePath)) {
88
+ return null;
89
+ }
90
+ try {
91
+ const json = await fs.promises.readFile(filePath, 'utf-8');
92
+ const data = JSON.parse(json);
93
+ return data.state || null;
94
+ }
95
+ catch (error) {
96
+ // File corrupted or invalid JSON
97
+ return null;
98
+ }
99
+ }
100
+ /**
101
+ * Delete session state file
102
+ */
103
+ async delete(sessionId) {
104
+ const filePath = this.getSessionFilePath(sessionId);
105
+ if (fs.existsSync(filePath)) {
106
+ await fs.promises.unlink(filePath);
107
+ }
108
+ }
109
+ /**
110
+ * List all session IDs
111
+ */
112
+ async listSessions() {
113
+ if (!fs.existsSync(this.sessionDir)) {
114
+ return [];
115
+ }
116
+ const files = await fs.promises.readdir(this.sessionDir);
117
+ return files
118
+ .filter(f => f.endsWith('.json'))
119
+ .map(f => f.replace('.json', ''));
120
+ }
121
+ /**
122
+ * Get session metadata (without loading full state)
123
+ */
124
+ async getSessionMetadata(sessionId) {
125
+ const filePath = this.getSessionFilePath(sessionId);
126
+ if (!fs.existsSync(filePath)) {
127
+ return null;
128
+ }
129
+ try {
130
+ const json = await fs.promises.readFile(filePath, 'utf-8');
131
+ const data = JSON.parse(json);
132
+ return {
133
+ sessionId: data.sessionId,
134
+ timestamp: data.timestamp,
135
+ pid: data.pid,
136
+ age: Date.now() - data.timestamp
137
+ };
138
+ }
139
+ catch (error) {
140
+ return null;
141
+ }
142
+ }
143
+ /**
144
+ * Clean up stale sessions (older than maxAge)
145
+ */
146
+ async cleanupStaleSessions(maxAgeMs = 30 * 60 * 1000) {
147
+ const sessions = await this.listSessions();
148
+ const stale = [];
149
+ const now = Date.now();
150
+ for (const sessionId of sessions) {
151
+ const metadata = await this.getSessionMetadata(sessionId);
152
+ if (metadata && (now - metadata.timestamp) > maxAgeMs) {
153
+ await this.delete(sessionId);
154
+ stale.push(sessionId);
155
+ }
156
+ }
157
+ return stale;
158
+ }
159
+ /**
160
+ * Clean up sessions from dead processes
161
+ */
162
+ async cleanupDeadProcessSessions() {
163
+ const sessions = await this.listSessions();
164
+ const dead = [];
165
+ for (const sessionId of sessions) {
166
+ const metadata = await this.getSessionMetadata(sessionId);
167
+ if (metadata) {
168
+ try {
169
+ // Check if process is still running
170
+ process.kill(metadata.pid, 0);
171
+ }
172
+ catch (e) {
173
+ // Process doesn't exist
174
+ await this.delete(sessionId);
175
+ dead.push(sessionId);
176
+ }
177
+ }
178
+ }
179
+ return dead;
180
+ }
181
+ /**
182
+ * Clear all sessions
183
+ */
184
+ async clearAll() {
185
+ const sessions = await this.listSessions();
186
+ for (const sessionId of sessions) {
187
+ await this.delete(sessionId);
188
+ }
189
+ }
190
+ }
191
+ exports.FileSessionStorage = FileSessionStorage;
@@ -0,0 +1,8 @@
1
+ export interface TimeoutConfig {
2
+ default: number;
3
+ csrf: number;
4
+ long: number;
5
+ }
6
+ export declare function getTimeoutConfig(): TimeoutConfig;
7
+ export declare function getTimeout(type?: "default" | "csrf" | "long" | number): number;
8
+ //# sourceMappingURL=timeouts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeouts.d.ts","sourceRoot":"","sources":["../../src/utils/timeouts.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAUhD;AAED,wBAAgB,UAAU,CAAC,IAAI,GAAE,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAkB,GAAG,MAAM,CAOzF"}
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTimeoutConfig = getTimeoutConfig;
4
+ exports.getTimeout = getTimeout;
5
+ function getTimeoutConfig() {
6
+ const defaultTimeout = parseInt(process.env.SAP_TIMEOUT_DEFAULT || "45000", 10);
7
+ const csrfTimeout = parseInt(process.env.SAP_TIMEOUT_CSRF || "15000", 10);
8
+ const longTimeout = parseInt(process.env.SAP_TIMEOUT_LONG || "60000", 10);
9
+ return {
10
+ default: defaultTimeout,
11
+ csrf: csrfTimeout,
12
+ long: longTimeout
13
+ };
14
+ }
15
+ function getTimeout(type = "default") {
16
+ if (typeof type === "number") {
17
+ return type;
18
+ }
19
+ const config = getTimeoutConfig();
20
+ return config[type];
21
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Token refresh utilities for JWT authentication
3
+ */
4
+ export interface TokenRefreshResult {
5
+ accessToken: string;
6
+ refreshToken?: string;
7
+ }
8
+ /**
9
+ * Refreshes the access token using refresh token
10
+ * @param refreshToken Refresh token
11
+ * @param uaaUrl UAA URL (e.g., https://your-account.authentication.eu10.hana.ondemand.com)
12
+ * @param clientId UAA client ID
13
+ * @param clientSecret UAA client secret
14
+ * @returns Promise that resolves to new tokens
15
+ */
16
+ export declare function refreshJwtToken(refreshToken: string, uaaUrl: string, clientId: string, clientSecret: string): Promise<TokenRefreshResult>;
17
+ //# sourceMappingURL=tokenRefresh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenRefresh.d.ts","sourceRoot":"","sources":["../../src/utils/tokenRefresh.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,kBAAkB,CAAC,CAqC7B"}
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /**
3
+ * Token refresh utilities for JWT authentication
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.refreshJwtToken = refreshJwtToken;
10
+ const axios_1 = __importDefault(require("axios"));
11
+ /**
12
+ * Refreshes the access token using refresh token
13
+ * @param refreshToken Refresh token
14
+ * @param uaaUrl UAA URL (e.g., https://your-account.authentication.eu10.hana.ondemand.com)
15
+ * @param clientId UAA client ID
16
+ * @param clientSecret UAA client secret
17
+ * @returns Promise that resolves to new tokens
18
+ */
19
+ async function refreshJwtToken(refreshToken, uaaUrl, clientId, clientSecret) {
20
+ try {
21
+ const tokenUrl = `${uaaUrl}/oauth/token`;
22
+ const params = new URLSearchParams();
23
+ params.append('grant_type', 'refresh_token');
24
+ params.append('refresh_token', refreshToken);
25
+ const authString = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
26
+ const response = await (0, axios_1.default)({
27
+ method: 'post',
28
+ url: tokenUrl,
29
+ headers: {
30
+ Authorization: `Basic ${authString}`,
31
+ 'Content-Type': 'application/x-www-form-urlencoded',
32
+ },
33
+ data: params.toString(),
34
+ });
35
+ if (response.data && response.data.access_token) {
36
+ return {
37
+ accessToken: response.data.access_token,
38
+ refreshToken: response.data.refresh_token || refreshToken, // Use new refresh token if provided, otherwise keep old one
39
+ };
40
+ }
41
+ else {
42
+ throw new Error('Response does not contain access_token');
43
+ }
44
+ }
45
+ catch (error) {
46
+ if (error.response) {
47
+ throw new Error(`Token refresh failed (${error.response.status}): ${JSON.stringify(error.response.data)}`);
48
+ }
49
+ else {
50
+ throw new Error(`Token refresh failed: ${error.message}`);
51
+ }
52
+ }
53
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@mcp-abap-adt/connection",
3
+ "version": "0.1.0",
4
+ "description": "ABAP connection layer for MCP ABAP ADT server",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "bin",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "bin": {
14
+ "sap-abap-auth": "./bin/sap-abap-auth.js"
15
+ },
16
+ "keywords": [
17
+ "abap",
18
+ "sap",
19
+ "adt",
20
+ "connection",
21
+ "mcp",
22
+ "abap-adt"
23
+ ],
24
+ "author": "Oleksii Kyslytsia <oleksij.kyslytsja@gmail.com>",
25
+ "license": "MIT",
26
+ "homepage": "https://github.com/fr0ster/mcp-abap-adt#readme",
27
+ "bugs": {
28
+ "url": "https://github.com/fr0ster/mcp-abap-adt/issues"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/fr0ster/mcp-abap-adt.git",
33
+ "directory": "packages/connection"
34
+ },
35
+ "scripts": {
36
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
37
+ "build": "npm run clean --silent && npx tsc -p tsconfig.json",
38
+ "build:fast": "npx tsc -p tsconfig.json",
39
+ "install:local": "npm install --no-workspaces",
40
+ "pack": "npm run build && npm pack",
41
+ "test": "jest",
42
+ "prepublishOnly": "npm run build"
43
+ },
44
+ "engines": {
45
+ "node": ">=18.0.0"
46
+ },
47
+ "dependencies": {
48
+ "axios": "^1.11.0",
49
+ "commander": "^14.0.2",
50
+ "express": "^5.1.0",
51
+ "open": "^11.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/jest": "^30.0.0",
55
+ "@types/node": "^24.10.1",
56
+ "jest": "^30.2.0",
57
+ "ts-jest": "^29.2.5",
58
+ "typescript": "^5.9.2"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ }
63
+ }