@mcp-abap-adt/proxy 0.1.4 → 0.1.8
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/CHANGELOG.md +66 -1
- package/README.md +73 -63
- package/bin/mcp-abap-adt-proxy.js +15 -42
- package/dist/index.d.ts +3 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +440 -273
- package/dist/lib/config.d.ts +13 -4
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +110 -92
- package/dist/lib/errorHandler.d.ts +3 -3
- package/dist/lib/errorHandler.d.ts.map +1 -1
- package/dist/lib/errorHandler.js +31 -28
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +15 -1
- package/dist/lib/stores.d.ts +7 -6
- package/dist/lib/stores.d.ts.map +1 -1
- package/dist/lib/stores.js +121 -8
- package/dist/lib/transportConfig.d.ts +1 -1
- package/dist/lib/transportConfig.d.ts.map +1 -1
- package/dist/lib/transportConfig.js +58 -38
- package/dist/proxy/btpProxy.d.ts +102 -0
- package/dist/proxy/btpProxy.d.ts.map +1 -0
- package/dist/proxy/btpProxy.js +833 -0
- package/dist/proxy/cloudLlmHubProxy.d.ts +1 -60
- package/dist/proxy/cloudLlmHubProxy.d.ts.map +1 -1
- package/dist/proxy/cloudLlmHubProxy.js +1 -378
- package/dist/router/headerAnalyzer.d.ts +14 -18
- package/dist/router/headerAnalyzer.d.ts.map +1 -1
- package/dist/router/headerAnalyzer.js +40 -67
- package/dist/router/requestInterceptor.d.ts +5 -11
- package/dist/router/requestInterceptor.d.ts.map +1 -1
- package/dist/router/requestInterceptor.js +33 -30
- package/package.json +33 -14
- package/dist/router/directCloudRouter.d.ts +0 -32
- package/dist/router/directCloudRouter.d.ts.map +0 -1
- package/dist/router/directCloudRouter.js +0 -179
- package/dist/router/localBasicRouter.d.ts +0 -23
- package/dist/router/localBasicRouter.d.ts.map +0 -1
- package/dist/router/localBasicRouter.js +0 -140
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Request Interceptor - Intercepts and analyzes MCP requests
|
|
3
3
|
*/
|
|
4
|
-
import { IncomingMessage } from
|
|
5
|
-
import { RoutingDecision } from
|
|
4
|
+
import type { IncomingMessage } from 'node:http';
|
|
5
|
+
import { type RoutingDecision } from './headerAnalyzer.js';
|
|
6
6
|
export interface InterceptedRequest {
|
|
7
7
|
method: string;
|
|
8
8
|
url: string;
|
|
9
9
|
headers: Record<string, string | string[] | undefined>;
|
|
10
|
-
body?:
|
|
10
|
+
body?: unknown;
|
|
11
11
|
routingDecision: RoutingDecision;
|
|
12
12
|
sessionId?: string;
|
|
13
13
|
clientId?: string;
|
|
@@ -15,16 +15,10 @@ export interface InterceptedRequest {
|
|
|
15
15
|
/**
|
|
16
16
|
* Intercept and analyze incoming HTTP request
|
|
17
17
|
*/
|
|
18
|
-
export declare function interceptRequest(req: IncomingMessage, body?:
|
|
18
|
+
export declare function interceptRequest(req: IncomingMessage, body?: unknown, configOverrides?: {
|
|
19
19
|
btpDestination?: string;
|
|
20
|
-
|
|
21
|
-
mcpUrl?: string;
|
|
20
|
+
targetUrl?: string;
|
|
22
21
|
}): InterceptedRequest;
|
|
23
|
-
/**
|
|
24
|
-
* Check if request requires SAP configuration
|
|
25
|
-
* Only tools/call requires SAP config - all other methods don't
|
|
26
|
-
*/
|
|
27
|
-
export declare function requiresSapConfig(body: any): boolean;
|
|
28
22
|
/**
|
|
29
23
|
* Sanitize headers for logging (remove sensitive data)
|
|
30
24
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"requestInterceptor.d.ts","sourceRoot":"","sources":["../../src/router/requestInterceptor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"requestInterceptor.d.ts","sourceRoot":"","sources":["../../src/router/requestInterceptor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAajD,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3E,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,eAAe,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,eAAe,EACpB,IAAI,CAAC,EAAE,OAAO,EACd,eAAe,CAAC,EAAE;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,kBAAkB,CAqDpB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GACrD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBxB"}
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.interceptRequest = interceptRequest;
|
|
7
|
-
exports.requiresSapConfig = requiresSapConfig;
|
|
8
7
|
exports.sanitizeHeadersForLogging = sanitizeHeadersForLogging;
|
|
9
|
-
const
|
|
8
|
+
const header_validator_1 = require("@mcp-abap-adt/header-validator");
|
|
9
|
+
const interfaces_1 = require("@mcp-abap-adt/interfaces");
|
|
10
10
|
const logger_js_1 = require("../lib/logger.js");
|
|
11
|
+
const headerAnalyzer_js_1 = require("./headerAnalyzer.js");
|
|
11
12
|
/**
|
|
12
13
|
* Intercept and analyze incoming HTTP request
|
|
13
14
|
*/
|
|
@@ -19,15 +20,30 @@ function interceptRequest(req, body, configOverrides) {
|
|
|
19
20
|
}
|
|
20
21
|
// Analyze headers for routing decision (with config overrides)
|
|
21
22
|
const routingDecision = (0, headerAnalyzer_js_1.analyzeHeaders)(req.headers, configOverrides);
|
|
23
|
+
// Validate proxy headers
|
|
24
|
+
const validation = (0, header_validator_1.validateProxyHeaders)(req.headers);
|
|
25
|
+
if (!validation.isValid && validation.errors.length > 0) {
|
|
26
|
+
logger_js_1.logger?.warn('Proxy header validation failed', {
|
|
27
|
+
type: 'PROXY_HEADER_VALIDATION_ERROR',
|
|
28
|
+
errors: validation.errors,
|
|
29
|
+
warnings: validation.warnings,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else if (validation.warnings.length > 0) {
|
|
33
|
+
logger_js_1.logger?.warn('Proxy header validation warnings', {
|
|
34
|
+
type: 'PROXY_HEADER_VALIDATION_WARNINGS',
|
|
35
|
+
warnings: validation.warnings,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
22
38
|
// Extract session ID if present
|
|
23
|
-
const sessionId = (req.headers[
|
|
24
|
-
req.headers[
|
|
25
|
-
req.headers[
|
|
39
|
+
const sessionId = (req.headers[interfaces_1.HEADER_SESSION_ID] ||
|
|
40
|
+
req.headers[interfaces_1.HEADER_MCP_SESSION_ID] ||
|
|
41
|
+
req.headers[interfaces_1.HEADER_X_MCP_SESSION_ID]);
|
|
26
42
|
// Generate client ID
|
|
27
43
|
const clientId = `${req.socket.remoteAddress}:${req.socket.remotePort}`;
|
|
28
44
|
// Log intercepted request
|
|
29
|
-
logger_js_1.logger
|
|
30
|
-
type:
|
|
45
|
+
logger_js_1.logger?.debug('Request intercepted', {
|
|
46
|
+
type: 'REQUEST_INTERCEPTED',
|
|
31
47
|
method: req.method,
|
|
32
48
|
url: req.url,
|
|
33
49
|
clientId,
|
|
@@ -36,8 +52,8 @@ function interceptRequest(req, body, configOverrides) {
|
|
|
36
52
|
routingReason: routingDecision.reason,
|
|
37
53
|
});
|
|
38
54
|
return {
|
|
39
|
-
method: req.method ||
|
|
40
|
-
url: req.url ||
|
|
55
|
+
method: req.method || 'GET',
|
|
56
|
+
url: req.url || '/',
|
|
41
57
|
headers,
|
|
42
58
|
body,
|
|
43
59
|
routingDecision,
|
|
@@ -45,37 +61,24 @@ function interceptRequest(req, body, configOverrides) {
|
|
|
45
61
|
clientId,
|
|
46
62
|
};
|
|
47
63
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Check if request requires SAP configuration
|
|
50
|
-
* Only tools/call requires SAP config - all other methods don't
|
|
51
|
-
*/
|
|
52
|
-
function requiresSapConfig(body) {
|
|
53
|
-
if (!body || typeof body !== "object") {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
const method = body.method;
|
|
57
|
-
return method === "tools/call";
|
|
58
|
-
}
|
|
59
64
|
/**
|
|
60
65
|
* Sanitize headers for logging (remove sensitive data)
|
|
61
66
|
*/
|
|
62
67
|
function sanitizeHeadersForLogging(headers) {
|
|
63
68
|
const sanitized = {};
|
|
64
69
|
const sensitiveKeys = [
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
];
|
|
70
|
+
interfaces_1.HEADER_AUTHORIZATION,
|
|
71
|
+
interfaces_1.HEADER_SAP_JWT_TOKEN,
|
|
72
|
+
interfaces_1.HEADER_SAP_REFRESH_TOKEN,
|
|
73
|
+
interfaces_1.HEADER_SAP_PASSWORD,
|
|
74
|
+
interfaces_1.HEADER_SAP_UAA_CLIENT_SECRET,
|
|
75
|
+
].map((k) => k.toLowerCase());
|
|
71
76
|
for (const [key, value] of Object.entries(headers)) {
|
|
72
77
|
if (sensitiveKeys.includes(key.toLowerCase())) {
|
|
73
|
-
sanitized[key] =
|
|
78
|
+
sanitized[key] = '[REDACTED]';
|
|
74
79
|
}
|
|
75
80
|
else {
|
|
76
|
-
sanitized[key] = Array.isArray(value)
|
|
77
|
-
? value.join(", ")
|
|
78
|
-
: (value || "");
|
|
81
|
+
sanitized[key] = Array.isArray(value) ? value.join(', ') : value || '';
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
return sanitized;
|
package/package.json
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/proxy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "MCP proxy server for SAP ABAP ADT - proxies local requests to cloud-llm-hub with JWT authentication",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"chrono": "./tools/version-stats.sh",
|
|
9
|
+
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests",
|
|
10
|
+
"test:check": "npx tsc --noEmit --project tsconfig.test.json",
|
|
11
|
+
"lint": "npx biome check --write src",
|
|
12
|
+
"lint:check": "npx biome check src",
|
|
13
|
+
"format": "npx biome format --write src",
|
|
14
|
+
"build": "npm run --silent clean && npx biome check src --diagnostic-level=error && npx tsc -p tsconfig.json",
|
|
15
|
+
"build:fast": "npx tsc -p tsconfig.json",
|
|
10
16
|
"start": "node ./bin/mcp-abap-adt-proxy.js",
|
|
11
17
|
"start:http": "node ./bin/mcp-abap-adt-proxy.js --transport streamable-http",
|
|
12
18
|
"start:sse": "node ./bin/mcp-abap-adt-proxy.js --transport sse",
|
|
13
19
|
"dev": "npx @modelcontextprotocol/inspector node ./dist/index.js",
|
|
14
|
-
"clean": "
|
|
20
|
+
"clean": "rimraf dist tsconfig.tsbuildinfo",
|
|
15
21
|
"prepublishOnly": "npm run build",
|
|
16
|
-
"test:servers": "node ./tools/start-servers.js"
|
|
22
|
+
"test:servers": "node ./tools/start-servers.js",
|
|
23
|
+
"test-destination": "npx tsx tools/test-destination.ts"
|
|
17
24
|
},
|
|
18
25
|
"keywords": [
|
|
19
26
|
"abap",
|
|
@@ -37,23 +44,35 @@
|
|
|
37
44
|
"access": "public"
|
|
38
45
|
},
|
|
39
46
|
"devDependencies": {
|
|
40
|
-
"@
|
|
41
|
-
"@
|
|
47
|
+
"@biomejs/biome": "^2.3.10",
|
|
48
|
+
"@jest/globals": "^30.2.0",
|
|
49
|
+
"@modelcontextprotocol/inspector": "^0.18.0",
|
|
50
|
+
"@types/jest": "^30.0.0",
|
|
51
|
+
"@types/js-yaml": "^4.0.9",
|
|
42
52
|
"@types/node": "^24.2.1",
|
|
43
53
|
"commander": "^14.0.2",
|
|
44
54
|
"cors": "^2.8.5",
|
|
45
|
-
"
|
|
55
|
+
"cross-env": "^10.1.0",
|
|
46
56
|
"express": "^5.1.0",
|
|
47
|
-
"jest": "^
|
|
48
|
-
"
|
|
57
|
+
"jest": "^30.2.0",
|
|
58
|
+
"jest-util": "^30.2.0",
|
|
59
|
+
"pino": "^10.3.0",
|
|
60
|
+
"pino-pretty": "^13.1.3",
|
|
61
|
+
"rimraf": "^6.1.2",
|
|
62
|
+
"ts-jest": "^29.2.5",
|
|
63
|
+
"tsx": "^4.21.0",
|
|
49
64
|
"typescript": "^5.9.2"
|
|
50
65
|
},
|
|
51
66
|
"dependencies": {
|
|
52
|
-
"@mcp-abap-adt/auth-broker": "^0.
|
|
53
|
-
"@mcp-abap-adt/
|
|
54
|
-
"@mcp-abap-adt/
|
|
55
|
-
"@
|
|
67
|
+
"@mcp-abap-adt/auth-broker": "^0.3.6",
|
|
68
|
+
"@mcp-abap-adt/auth-providers": "^0.2.10",
|
|
69
|
+
"@mcp-abap-adt/auth-stores": "^0.3.0",
|
|
70
|
+
"@mcp-abap-adt/header-validator": "^0.1.8",
|
|
71
|
+
"@mcp-abap-adt/interfaces": "^0.2.15",
|
|
72
|
+
"@mcp-abap-adt/logger": "^0.1.4",
|
|
73
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
56
74
|
"axios": "^1.11.0",
|
|
75
|
+
"js-yaml": "^4.1.1",
|
|
57
76
|
"zod": "^3.25.76"
|
|
58
77
|
},
|
|
59
78
|
"engines": {
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Direct Cloud Router - Routes requests directly to cloud ABAP systems
|
|
3
|
-
*
|
|
4
|
-
* For requests with x-sap-destination (not "sk"), routes directly to cloud ABAP
|
|
5
|
-
* using the same approach as mcp-abap-adt
|
|
6
|
-
*/
|
|
7
|
-
import { IncomingHttpHeaders } from "http";
|
|
8
|
-
import { AuthBroker } from "@mcp-abap-adt/auth-broker";
|
|
9
|
-
import { AbapConnection } from "@mcp-abap-adt/connection";
|
|
10
|
-
import { RoutingDecision } from "./headerAnalyzer.js";
|
|
11
|
-
export interface DirectCloudConfig {
|
|
12
|
-
sapUrl: string;
|
|
13
|
-
destination?: string;
|
|
14
|
-
authType: "jwt" | "basic";
|
|
15
|
-
jwtToken?: string;
|
|
16
|
-
refreshToken?: string;
|
|
17
|
-
username?: string;
|
|
18
|
-
password?: string;
|
|
19
|
-
sapClient?: string;
|
|
20
|
-
uaaUrl?: string;
|
|
21
|
-
uaaClientId?: string;
|
|
22
|
-
uaaClientSecret?: string;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Convert validated auth config to direct cloud config
|
|
26
|
-
*/
|
|
27
|
-
export declare function createDirectCloudConfig(routingDecision: RoutingDecision, headers: IncomingHttpHeaders): DirectCloudConfig | null;
|
|
28
|
-
/**
|
|
29
|
-
* Get or create ABAP connection for direct cloud routing
|
|
30
|
-
*/
|
|
31
|
-
export declare function getDirectCloudConnection(sessionId: string, config: DirectCloudConfig, authBroker?: AuthBroker): Promise<AbapConnection>;
|
|
32
|
-
//# sourceMappingURL=directCloudRouter.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"directCloudRouter.d.ts","sourceRoot":"","sources":["../../src/router/directCloudRouter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAwB,cAAc,EAAsB,MAAM,0BAA0B,CAAC;AAEpG,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,KAAK,GAAG,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,mBAAmB,GAC3B,iBAAiB,GAAG,IAAI,CAgD1B;AA4CD;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,iBAAiB,EACzB,UAAU,CAAC,EAAE,UAAU,GACtB,OAAO,CAAC,cAAc,CAAC,CA+FzB"}
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Direct Cloud Router - Routes requests directly to cloud ABAP systems
|
|
4
|
-
*
|
|
5
|
-
* For requests with x-sap-destination (not "sk"), routes directly to cloud ABAP
|
|
6
|
-
* using the same approach as mcp-abap-adt
|
|
7
|
-
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.createDirectCloudConfig = createDirectCloudConfig;
|
|
10
|
-
exports.getDirectCloudConnection = getDirectCloudConnection;
|
|
11
|
-
const connection_1 = require("@mcp-abap-adt/connection");
|
|
12
|
-
const logger_js_1 = require("../lib/logger.js");
|
|
13
|
-
/**
|
|
14
|
-
* Convert validated auth config to direct cloud config
|
|
15
|
-
*/
|
|
16
|
-
function createDirectCloudConfig(routingDecision, headers) {
|
|
17
|
-
const validatedConfig = routingDecision.validationResult?.config;
|
|
18
|
-
if (!validatedConfig) {
|
|
19
|
-
logger_js_1.logger.warn("No validated config available for direct cloud routing", {
|
|
20
|
-
type: "DIRECT_CLOUD_CONFIG_ERROR",
|
|
21
|
-
strategy: routingDecision.strategy,
|
|
22
|
-
});
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
// Extract SAP URL
|
|
26
|
-
let sapUrl = validatedConfig.sapUrl;
|
|
27
|
-
const destination = routingDecision.btpDestination || routingDecision.mcpDestination;
|
|
28
|
-
if (!sapUrl && destination) {
|
|
29
|
-
// For destination-based auth, URL comes from destination service key
|
|
30
|
-
// We'll need to load it from AuthBroker
|
|
31
|
-
// For now, return null and let the caller handle it
|
|
32
|
-
logger_js_1.logger.debug("SAP URL not in validated config, will load from destination", {
|
|
33
|
-
destination: destination,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
// Convert xsuaa to jwt (they're the same for connection purposes)
|
|
37
|
-
const authType = validatedConfig.authType === "xsuaa" ? "jwt" : validatedConfig.authType;
|
|
38
|
-
const config = {
|
|
39
|
-
sapUrl: sapUrl || "", // Will be loaded from destination if needed
|
|
40
|
-
destination: validatedConfig.destination || destination,
|
|
41
|
-
authType: authType,
|
|
42
|
-
sapClient: validatedConfig.sapClient,
|
|
43
|
-
};
|
|
44
|
-
// Add JWT-specific fields
|
|
45
|
-
if (validatedConfig.authType === "jwt" || validatedConfig.authType === "xsuaa") {
|
|
46
|
-
config.jwtToken = validatedConfig.jwtToken;
|
|
47
|
-
config.refreshToken = validatedConfig.refreshToken;
|
|
48
|
-
config.uaaUrl = validatedConfig.uaaUrl;
|
|
49
|
-
config.uaaClientId = validatedConfig.uaaClientId;
|
|
50
|
-
config.uaaClientSecret = validatedConfig.uaaClientSecret;
|
|
51
|
-
}
|
|
52
|
-
// Add basic auth fields
|
|
53
|
-
if (validatedConfig.authType === "basic") {
|
|
54
|
-
config.username = validatedConfig.username;
|
|
55
|
-
config.password = validatedConfig.password;
|
|
56
|
-
}
|
|
57
|
-
return config;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Connection cache for direct cloud connections
|
|
61
|
-
*/
|
|
62
|
-
const connectionCache = new Map();
|
|
63
|
-
/**
|
|
64
|
-
* Generate cache key for connection
|
|
65
|
-
*/
|
|
66
|
-
function generateConnectionCacheKey(sessionId, config) {
|
|
67
|
-
const crypto = require("crypto");
|
|
68
|
-
const hash = crypto.createHash("sha256");
|
|
69
|
-
hash.update(sessionId);
|
|
70
|
-
hash.update(config.sapUrl);
|
|
71
|
-
hash.update(config.destination || "");
|
|
72
|
-
hash.update(config.authType);
|
|
73
|
-
hash.update(config.sapClient || "");
|
|
74
|
-
return hash.digest("hex");
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Clean up old connections from cache
|
|
78
|
-
*/
|
|
79
|
-
function cleanupConnectionCache() {
|
|
80
|
-
const now = new Date();
|
|
81
|
-
const maxAge = 60 * 60 * 1000; // 1 hour
|
|
82
|
-
for (const [key, entry] of connectionCache.entries()) {
|
|
83
|
-
const age = now.getTime() - entry.lastUsed.getTime();
|
|
84
|
-
if (age > maxAge) {
|
|
85
|
-
logger_js_1.logger.debug("Cleaning up old connection cache entry", {
|
|
86
|
-
type: "CONNECTION_CACHE_CLEANUP",
|
|
87
|
-
key: key.substring(0, 16),
|
|
88
|
-
});
|
|
89
|
-
connectionCache.delete(key);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Get or create ABAP connection for direct cloud routing
|
|
95
|
-
*/
|
|
96
|
-
async function getDirectCloudConnection(sessionId, config, authBroker) {
|
|
97
|
-
// Clean up old entries periodically
|
|
98
|
-
if (connectionCache.size > 100) {
|
|
99
|
-
cleanupConnectionCache();
|
|
100
|
-
}
|
|
101
|
-
const cacheKey = generateConnectionCacheKey(sessionId, config);
|
|
102
|
-
let entry = connectionCache.get(cacheKey);
|
|
103
|
-
// If we have a destination and no SAP URL, load it from AuthBroker
|
|
104
|
-
if (config.destination && !config.sapUrl && authBroker) {
|
|
105
|
-
try {
|
|
106
|
-
// Get token from AuthBroker (this will also provide the SAP URL from destination)
|
|
107
|
-
const token = await authBroker.getToken(config.destination);
|
|
108
|
-
config.jwtToken = token;
|
|
109
|
-
// Load destination config to get SAP URL from AuthBroker
|
|
110
|
-
// AuthBroker stores session data, we can access it through the broker
|
|
111
|
-
// For now, we'll rely on the token being valid and the URL being set from destination
|
|
112
|
-
// The actual URL will be loaded when we create the connection
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
logger_js_1.logger.error("Failed to load destination config from AuthBroker", {
|
|
116
|
-
type: "AUTH_BROKER_LOAD_ERROR",
|
|
117
|
-
destination: config.destination,
|
|
118
|
-
error: error instanceof Error ? error.message : String(error),
|
|
119
|
-
});
|
|
120
|
-
throw error;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (!entry || entry.configSignature !== cacheKey) {
|
|
124
|
-
logger_js_1.logger.debug("Creating new direct cloud connection", {
|
|
125
|
-
type: "DIRECT_CLOUD_CONNECTION_CREATE",
|
|
126
|
-
sessionId: sessionId.substring(0, 8),
|
|
127
|
-
destination: config.destination,
|
|
128
|
-
sapUrl: config.sapUrl,
|
|
129
|
-
});
|
|
130
|
-
// Dispose old connection if exists
|
|
131
|
-
if (entry) {
|
|
132
|
-
// Connection disposal will be handled by connection package
|
|
133
|
-
}
|
|
134
|
-
// Create SAP config for connection
|
|
135
|
-
const sapConfig = {
|
|
136
|
-
url: config.sapUrl,
|
|
137
|
-
authType: config.authType,
|
|
138
|
-
jwtToken: config.jwtToken,
|
|
139
|
-
refreshToken: config.refreshToken,
|
|
140
|
-
username: config.username,
|
|
141
|
-
password: config.password,
|
|
142
|
-
sapClient: config.sapClient,
|
|
143
|
-
uaaUrl: config.uaaUrl,
|
|
144
|
-
uaaClientId: config.uaaClientId,
|
|
145
|
-
uaaClientSecret: config.uaaClientSecret,
|
|
146
|
-
};
|
|
147
|
-
// Create connection
|
|
148
|
-
const connectionSessionId = `mcp-proxy-direct-${sessionId}`;
|
|
149
|
-
const sessionStorage = new connection_1.FileSessionStorage();
|
|
150
|
-
const connection = (0, connection_1.createAbapConnection)(sapConfig, logger_js_1.logger, // Use our logger
|
|
151
|
-
sessionStorage, connectionSessionId);
|
|
152
|
-
// Connect
|
|
153
|
-
try {
|
|
154
|
-
await connection.connect();
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
logger_js_1.logger.error("Failed to connect to cloud ABAP", {
|
|
158
|
-
type: "DIRECT_CLOUD_CONNECTION_ERROR",
|
|
159
|
-
error: error instanceof Error ? error.message : String(error),
|
|
160
|
-
destination: config.destination,
|
|
161
|
-
});
|
|
162
|
-
throw error;
|
|
163
|
-
}
|
|
164
|
-
entry = {
|
|
165
|
-
connection,
|
|
166
|
-
configSignature: cacheKey,
|
|
167
|
-
lastUsed: new Date(),
|
|
168
|
-
};
|
|
169
|
-
connectionCache.set(cacheKey, entry);
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
entry.lastUsed = new Date();
|
|
173
|
-
logger_js_1.logger.debug("Reusing cached direct cloud connection", {
|
|
174
|
-
type: "DIRECT_CLOUD_CONNECTION_REUSE",
|
|
175
|
-
sessionId: sessionId.substring(0, 8),
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
return entry.connection;
|
|
179
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local Basic Router - Handles basic authentication requests locally
|
|
3
|
-
*
|
|
4
|
-
* For requests with x-sap-auth-type: "basic", handles locally without proxying
|
|
5
|
-
*/
|
|
6
|
-
import { IncomingHttpHeaders } from "http";
|
|
7
|
-
import { AbapConnection } from "@mcp-abap-adt/connection";
|
|
8
|
-
import { RoutingDecision } from "./headerAnalyzer.js";
|
|
9
|
-
export interface LocalBasicConfig {
|
|
10
|
-
sapUrl: string;
|
|
11
|
-
username: string;
|
|
12
|
-
password: string;
|
|
13
|
-
sapClient?: string;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Convert validated auth config to local basic config
|
|
17
|
-
*/
|
|
18
|
-
export declare function createLocalBasicConfig(routingDecision: RoutingDecision, headers: IncomingHttpHeaders): LocalBasicConfig | null;
|
|
19
|
-
/**
|
|
20
|
-
* Get or create ABAP connection for local basic auth
|
|
21
|
-
*/
|
|
22
|
-
export declare function getLocalBasicConnection(sessionId: string, config: LocalBasicConfig): Promise<AbapConnection>;
|
|
23
|
-
//# sourceMappingURL=localBasicRouter.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"localBasicRouter.d.ts","sourceRoot":"","sources":["../../src/router/localBasicRouter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAwB,cAAc,EAAsB,MAAM,0BAA0B,CAAC;AAEpG,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,mBAAmB,GAC3B,gBAAgB,GAAG,IAAI,CAkCzB;AA2CD;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,cAAc,CAAC,CAoEzB"}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Local Basic Router - Handles basic authentication requests locally
|
|
4
|
-
*
|
|
5
|
-
* For requests with x-sap-auth-type: "basic", handles locally without proxying
|
|
6
|
-
*/
|
|
7
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.createLocalBasicConfig = createLocalBasicConfig;
|
|
9
|
-
exports.getLocalBasicConnection = getLocalBasicConnection;
|
|
10
|
-
const connection_1 = require("@mcp-abap-adt/connection");
|
|
11
|
-
const logger_js_1 = require("../lib/logger.js");
|
|
12
|
-
/**
|
|
13
|
-
* Convert validated auth config to local basic config
|
|
14
|
-
*/
|
|
15
|
-
function createLocalBasicConfig(routingDecision, headers) {
|
|
16
|
-
const validatedConfig = routingDecision.validationResult?.config;
|
|
17
|
-
if (!validatedConfig) {
|
|
18
|
-
logger_js_1.logger.warn("No validated config available for local basic routing", {
|
|
19
|
-
type: "LOCAL_BASIC_CONFIG_ERROR",
|
|
20
|
-
strategy: routingDecision.strategy,
|
|
21
|
-
});
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
if (validatedConfig.authType !== "basic") {
|
|
25
|
-
logger_js_1.logger.warn("Config is not basic auth type", {
|
|
26
|
-
type: "LOCAL_BASIC_CONFIG_TYPE_ERROR",
|
|
27
|
-
authType: validatedConfig.authType,
|
|
28
|
-
});
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
if (!validatedConfig.sapUrl || !validatedConfig.username || !validatedConfig.password) {
|
|
32
|
-
logger_js_1.logger.warn("Missing required fields for basic auth", {
|
|
33
|
-
type: "LOCAL_BASIC_CONFIG_MISSING",
|
|
34
|
-
hasUrl: !!validatedConfig.sapUrl,
|
|
35
|
-
hasUsername: !!validatedConfig.username,
|
|
36
|
-
hasPassword: !!validatedConfig.password,
|
|
37
|
-
});
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
return {
|
|
41
|
-
sapUrl: validatedConfig.sapUrl,
|
|
42
|
-
username: validatedConfig.username,
|
|
43
|
-
password: validatedConfig.password,
|
|
44
|
-
sapClient: validatedConfig.sapClient,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Connection cache for local basic connections
|
|
49
|
-
*/
|
|
50
|
-
const connectionCache = new Map();
|
|
51
|
-
/**
|
|
52
|
-
* Generate cache key for connection
|
|
53
|
-
*/
|
|
54
|
-
function generateConnectionCacheKey(sessionId, config) {
|
|
55
|
-
const crypto = require("crypto");
|
|
56
|
-
const hash = crypto.createHash("sha256");
|
|
57
|
-
hash.update(sessionId);
|
|
58
|
-
hash.update(config.sapUrl);
|
|
59
|
-
hash.update(config.username);
|
|
60
|
-
hash.update(config.sapClient || "");
|
|
61
|
-
return hash.digest("hex");
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Clean up old connections from cache
|
|
65
|
-
*/
|
|
66
|
-
function cleanupConnectionCache() {
|
|
67
|
-
const now = new Date();
|
|
68
|
-
const maxAge = 60 * 60 * 1000; // 1 hour
|
|
69
|
-
for (const [key, entry] of connectionCache.entries()) {
|
|
70
|
-
const age = now.getTime() - entry.lastUsed.getTime();
|
|
71
|
-
if (age > maxAge) {
|
|
72
|
-
logger_js_1.logger.debug("Cleaning up old local basic connection cache entry", {
|
|
73
|
-
type: "LOCAL_BASIC_CACHE_CLEANUP",
|
|
74
|
-
key: key.substring(0, 16),
|
|
75
|
-
});
|
|
76
|
-
connectionCache.delete(key);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Get or create ABAP connection for local basic auth
|
|
82
|
-
*/
|
|
83
|
-
async function getLocalBasicConnection(sessionId, config) {
|
|
84
|
-
// Clean up old entries periodically
|
|
85
|
-
if (connectionCache.size > 100) {
|
|
86
|
-
cleanupConnectionCache();
|
|
87
|
-
}
|
|
88
|
-
const cacheKey = generateConnectionCacheKey(sessionId, config);
|
|
89
|
-
let entry = connectionCache.get(cacheKey);
|
|
90
|
-
if (!entry || entry.configSignature !== cacheKey) {
|
|
91
|
-
logger_js_1.logger.debug("Creating new local basic connection", {
|
|
92
|
-
type: "LOCAL_BASIC_CONNECTION_CREATE",
|
|
93
|
-
sessionId: sessionId.substring(0, 8),
|
|
94
|
-
sapUrl: config.sapUrl,
|
|
95
|
-
});
|
|
96
|
-
// Dispose old connection if exists
|
|
97
|
-
if (entry) {
|
|
98
|
-
// Connection disposal will be handled by connection package
|
|
99
|
-
}
|
|
100
|
-
// Create SAP config for connection
|
|
101
|
-
const sapConfig = {
|
|
102
|
-
url: config.sapUrl,
|
|
103
|
-
authType: "basic",
|
|
104
|
-
username: config.username,
|
|
105
|
-
password: config.password,
|
|
106
|
-
client: config.sapClient,
|
|
107
|
-
};
|
|
108
|
-
// Create connection
|
|
109
|
-
const connectionSessionId = `mcp-proxy-basic-${sessionId}`;
|
|
110
|
-
const sessionStorage = new connection_1.FileSessionStorage();
|
|
111
|
-
const connection = (0, connection_1.createAbapConnection)(sapConfig, logger_js_1.logger, // Use our logger
|
|
112
|
-
sessionStorage, connectionSessionId);
|
|
113
|
-
// Connect
|
|
114
|
-
try {
|
|
115
|
-
await connection.connect();
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
logger_js_1.logger.error("Failed to connect to ABAP with basic auth", {
|
|
119
|
-
type: "LOCAL_BASIC_CONNECTION_ERROR",
|
|
120
|
-
error: error instanceof Error ? error.message : String(error),
|
|
121
|
-
sapUrl: config.sapUrl,
|
|
122
|
-
});
|
|
123
|
-
throw error;
|
|
124
|
-
}
|
|
125
|
-
entry = {
|
|
126
|
-
connection,
|
|
127
|
-
configSignature: cacheKey,
|
|
128
|
-
lastUsed: new Date(),
|
|
129
|
-
};
|
|
130
|
-
connectionCache.set(cacheKey, entry);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
entry.lastUsed = new Date();
|
|
134
|
-
logger_js_1.logger.debug("Reusing cached local basic connection", {
|
|
135
|
-
type: "LOCAL_BASIC_CONNECTION_REUSE",
|
|
136
|
-
sessionId: sessionId.substring(0, 8),
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
return entry.connection;
|
|
140
|
-
}
|