@mcp-abap-adt/proxy 0.1.5 → 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 -65
- 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 +414 -277
- package/dist/lib/config.d.ts +13 -4
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +110 -93
- 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 +12 -9
- package/dist/lib/stores.d.ts +7 -7
- package/dist/lib/stores.d.ts.map +1 -1
- package/dist/lib/stores.js +46 -80
- 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 -65
- package/dist/proxy/cloudLlmHubProxy.d.ts.map +1 -1
- package/dist/proxy/cloudLlmHubProxy.js +1 -441
- package/dist/router/headerAnalyzer.d.ts +9 -18
- package/dist/router/headerAnalyzer.d.ts.map +1 -1
- package/dist/router/headerAnalyzer.js +35 -63
- 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 -15
- package/dist/router/directCloudRouter.d.ts +0 -32
- package/dist/router/directCloudRouter.d.ts.map +0 -1
- package/dist/router/directCloudRouter.js +0 -146
- package/dist/router/localBasicRouter.d.ts +0 -23
- package/dist/router/localBasicRouter.d.ts.map +0 -1
- package/dist/router/localBasicRouter.js +0 -118
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.RoutingStrategy = void 0;
|
|
7
7
|
exports.analyzeHeaders = analyzeHeaders;
|
|
8
8
|
exports.shouldProxy = shouldProxy;
|
|
9
|
-
const
|
|
9
|
+
const interfaces_1 = require("@mcp-abap-adt/interfaces");
|
|
10
10
|
const logger_js_1 = require("../lib/logger.js");
|
|
11
11
|
var RoutingStrategy;
|
|
12
12
|
(function (RoutingStrategy) {
|
|
@@ -18,102 +18,74 @@ var RoutingStrategy;
|
|
|
18
18
|
/**
|
|
19
19
|
* Analyze headers and extract routing information
|
|
20
20
|
*
|
|
21
|
-
* Proxy validates only
|
|
21
|
+
* Proxy validates only one header for authentication:
|
|
22
22
|
* - x-btp-destination (or --btp): destination for BTP Cloud authorization token (Authorization: Bearer) and MCP server URL
|
|
23
|
-
* - x-mcp-destination (or --mcp): destination for SAP ABAP connection (SAP headers)
|
|
24
23
|
*
|
|
25
|
-
|
|
26
|
-
* - x-mcp-url (or --mcp-url): direct MCP server URL (optional, for local testing)
|
|
24
|
+
|
|
27
25
|
*
|
|
28
26
|
* Command-line overrides (take precedence over headers):
|
|
29
27
|
* - --btp=<destination>: overrides x-btp-destination header
|
|
30
|
-
* - --mcp=<destination>: overrides x-mcp-destination header
|
|
31
|
-
* - --mcp-url=<url>: overrides x-mcp-url header
|
|
32
28
|
*
|
|
33
|
-
* Other headers
|
|
29
|
+
* Other headers are passed directly to MCP server without validation.
|
|
30
|
+
*
|
|
31
|
+
* MPC server URL is obtained from:
|
|
32
|
+
* - service key for x-btp-destination (via auth-broker)
|
|
34
33
|
*
|
|
35
|
-
* MCP server URL is obtained from:
|
|
36
|
-
* - x-mcp-url header or --mcp-url parameter (if provided) - takes precedence
|
|
37
|
-
* - service key for x-btp-destination (via auth-broker) if BTP destination is present
|
|
38
|
-
* - service key for x-mcp-destination (via auth-broker) if only MCP destination is present
|
|
39
34
|
*
|
|
40
35
|
* Note: Proxy does NOT use .env files for connection configuration. Only destinations via auth-broker (service key files) are used.
|
|
41
36
|
*/
|
|
42
37
|
function analyzeHeaders(headers, configOverrides) {
|
|
43
|
-
//
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
// Helper function to extract string value from header (handles arrays and case-insensitive lookup)
|
|
39
|
+
const getHeaderValue = (headerName) => {
|
|
40
|
+
let headerValue = headers[headerName.toLowerCase()];
|
|
41
|
+
if (!headerValue) {
|
|
42
|
+
headerValue = headers[headerName];
|
|
43
|
+
}
|
|
48
44
|
if (!headerValue)
|
|
49
45
|
return undefined;
|
|
50
46
|
if (Array.isArray(headerValue)) {
|
|
51
47
|
return headerValue[0]?.trim();
|
|
52
48
|
}
|
|
53
|
-
return typeof headerValue ===
|
|
49
|
+
return typeof headerValue === 'string' ? headerValue.trim() : undefined;
|
|
54
50
|
};
|
|
55
51
|
// Extract authorization destination for BTP Cloud (x-btp-destination)
|
|
56
52
|
// Command-line parameter --btp takes precedence over header
|
|
57
|
-
const btpDestinationHeader = getHeaderValue(
|
|
53
|
+
const btpDestinationHeader = getHeaderValue(interfaces_1.HEADER_BTP_DESTINATION);
|
|
58
54
|
const extractedBtpDestination = configOverrides?.btpDestination
|
|
59
55
|
? configOverrides.btpDestination
|
|
60
56
|
: btpDestinationHeader;
|
|
61
|
-
// Extract
|
|
62
|
-
// Command-line parameter --mcp takes precedence over header
|
|
63
|
-
const mcpDestinationHeader = getHeaderValue(headers["x-mcp-destination"]);
|
|
64
|
-
const extractedMcpDestination = configOverrides?.mcpDestination
|
|
65
|
-
? configOverrides.mcpDestination
|
|
66
|
-
: mcpDestinationHeader;
|
|
67
|
-
// Extract direct MCP server URL (x-mcp-url)
|
|
57
|
+
// Extract direct MCP server URL (x-mcp-url) - REMOVED
|
|
68
58
|
// Command-line parameter --mcp-url takes precedence over header
|
|
69
|
-
const mcpUrlHeader = getHeaderValue(
|
|
70
|
-
const extractedMcpUrl = configOverrides?.mcpUrl
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
59
|
+
// const mcpUrlHeader = getHeaderValue(HEADER_MCP_URL);
|
|
60
|
+
// const extractedMcpUrl = configOverrides?.mcpUrl
|
|
61
|
+
// ? configOverrides.mcpUrl
|
|
62
|
+
// : mcpUrlHeader;
|
|
63
|
+
// Extract target URL (x-target-url)
|
|
64
|
+
// Command-line parameter --target-url takes precedence over header
|
|
65
|
+
const targetUrlHeader = getHeaderValue('x-target-url');
|
|
66
|
+
const extractedTargetUrl = configOverrides?.targetUrl
|
|
67
|
+
? configOverrides.targetUrl
|
|
68
|
+
: targetUrlHeader;
|
|
69
|
+
// Check if proxy headers exist in the actual HTTP request (not in config overrides)
|
|
70
|
+
const _hasBtpInRequest = !!btpDestinationHeader;
|
|
71
|
+
// If no proxy headers in the actual request, default to what's available or error out
|
|
72
|
+
if (!extractedBtpDestination) {
|
|
83
73
|
return {
|
|
84
74
|
strategy: RoutingStrategy.UNKNOWN,
|
|
85
|
-
reason:
|
|
86
|
-
validationResult,
|
|
75
|
+
reason: 'No BTP destination provided (missing x-btp-destination or --btp)',
|
|
87
76
|
};
|
|
88
77
|
}
|
|
89
|
-
// If only MCP URL is provided without destinations, that's OK - other headers will be passed directly
|
|
90
|
-
if (!extractedBtpDestination && !extractedMcpDestination && extractedMcpUrl) {
|
|
91
|
-
logger_js_1.logger.debug("Using MCP URL directly (no destinations - headers will be passed as-is)", {
|
|
92
|
-
type: "MCP_URL_DIRECT",
|
|
93
|
-
mcpUrl: extractedMcpUrl,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
78
|
// If x-btp-destination is present, we can proxy with BTP authentication (URL will be obtained from service key)
|
|
97
|
-
// If only x-mcp-destination is present, we can proxy without BTP authentication (for local testing)
|
|
98
79
|
// If x-mcp-url is provided, we can use it directly (for local testing)
|
|
99
|
-
logger_js_1.logger
|
|
80
|
+
logger_js_1.logger?.debug('Routing decision: PROXY', {
|
|
100
81
|
btpDestination: extractedBtpDestination,
|
|
101
|
-
mcpDestination: extractedMcpDestination,
|
|
102
|
-
mcpUrl: extractedMcpUrl || "not provided",
|
|
103
|
-
priority: validatedConfig?.priority,
|
|
104
82
|
});
|
|
105
|
-
const reason = extractedBtpDestination
|
|
106
|
-
? `Proxying to MCP server from BTP destination "${extractedBtpDestination}"${extractedMcpDestination ? ` with MCP destination "${extractedMcpDestination}"` : ''}`
|
|
107
|
-
: extractedMcpUrl
|
|
108
|
-
? `Proxying to MCP server at "${extractedMcpUrl}" (no BTP authentication - local testing mode)${extractedMcpDestination ? ` with MCP destination "${extractedMcpDestination}"` : ''}`
|
|
109
|
-
: `Proxying to MCP server with MCP destination "${extractedMcpDestination}" (no BTP authentication - local testing mode)`;
|
|
83
|
+
const reason = `Proxying to MCP server from BTP destination "${extractedBtpDestination}"`;
|
|
110
84
|
return {
|
|
111
85
|
strategy: RoutingStrategy.PROXY,
|
|
112
|
-
btpDestination: extractedBtpDestination,
|
|
113
|
-
|
|
114
|
-
mcpUrl: extractedMcpUrl, // Optional - direct URL if provided (takes precedence)
|
|
86
|
+
btpDestination: extractedBtpDestination,
|
|
87
|
+
targetUrl: extractedTargetUrl,
|
|
115
88
|
reason,
|
|
116
|
-
validationResult,
|
|
117
89
|
};
|
|
118
90
|
}
|
|
119
91
|
/**
|
|
@@ -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,24 +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",
|
|
55
|
+
"cross-env": "^10.1.0",
|
|
45
56
|
"express": "^5.1.0",
|
|
46
|
-
"jest": "^
|
|
47
|
-
"
|
|
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",
|
|
48
64
|
"typescript": "^5.9.2"
|
|
49
65
|
},
|
|
50
66
|
"dependencies": {
|
|
51
|
-
"@mcp-abap-adt/auth-broker": "^0.
|
|
52
|
-
"@mcp-abap-adt/auth-providers": "^0.
|
|
53
|
-
"@mcp-abap-adt/auth-stores": "^0.
|
|
54
|
-
"@mcp-abap-adt/
|
|
55
|
-
"@mcp-abap-adt/
|
|
56
|
-
"@
|
|
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",
|
|
57
74
|
"axios": "^1.11.0",
|
|
75
|
+
"js-yaml": "^4.1.1",
|
|
58
76
|
"zod": "^3.25.76"
|
|
59
77
|
},
|
|
60
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,CAS1B;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,146 +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
|
-
// Proxy validates only x-btp-destination and x-mcp-destination headers
|
|
18
|
-
// This function is not used in the current proxy implementation
|
|
19
|
-
// as proxy passes all other headers directly to MCP server
|
|
20
|
-
logger_js_1.logger.warn("Direct cloud routing is not supported in proxy mode", {
|
|
21
|
-
type: "DIRECT_CLOUD_CONFIG_ERROR",
|
|
22
|
-
strategy: routingDecision.strategy,
|
|
23
|
-
});
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Connection cache for direct cloud connections
|
|
28
|
-
*/
|
|
29
|
-
const connectionCache = new Map();
|
|
30
|
-
/**
|
|
31
|
-
* Generate cache key for connection
|
|
32
|
-
*/
|
|
33
|
-
function generateConnectionCacheKey(sessionId, config) {
|
|
34
|
-
const crypto = require("crypto");
|
|
35
|
-
const hash = crypto.createHash("sha256");
|
|
36
|
-
hash.update(sessionId);
|
|
37
|
-
hash.update(config.sapUrl);
|
|
38
|
-
hash.update(config.destination || "");
|
|
39
|
-
hash.update(config.authType);
|
|
40
|
-
hash.update(config.sapClient || "");
|
|
41
|
-
return hash.digest("hex");
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Clean up old connections from cache
|
|
45
|
-
*/
|
|
46
|
-
function cleanupConnectionCache() {
|
|
47
|
-
const now = new Date();
|
|
48
|
-
const maxAge = 60 * 60 * 1000; // 1 hour
|
|
49
|
-
for (const [key, entry] of connectionCache.entries()) {
|
|
50
|
-
const age = now.getTime() - entry.lastUsed.getTime();
|
|
51
|
-
if (age > maxAge) {
|
|
52
|
-
logger_js_1.logger.debug("Cleaning up old connection cache entry", {
|
|
53
|
-
type: "CONNECTION_CACHE_CLEANUP",
|
|
54
|
-
key: key.substring(0, 16),
|
|
55
|
-
});
|
|
56
|
-
connectionCache.delete(key);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Get or create ABAP connection for direct cloud routing
|
|
62
|
-
*/
|
|
63
|
-
async function getDirectCloudConnection(sessionId, config, authBroker) {
|
|
64
|
-
// Clean up old entries periodically
|
|
65
|
-
if (connectionCache.size > 100) {
|
|
66
|
-
cleanupConnectionCache();
|
|
67
|
-
}
|
|
68
|
-
const cacheKey = generateConnectionCacheKey(sessionId, config);
|
|
69
|
-
let entry = connectionCache.get(cacheKey);
|
|
70
|
-
// If we have a destination and no SAP URL, load it from AuthBroker
|
|
71
|
-
if (config.destination && !config.sapUrl && authBroker) {
|
|
72
|
-
try {
|
|
73
|
-
// Get token from AuthBroker (this will also provide the SAP URL from destination)
|
|
74
|
-
const token = await authBroker.getToken(config.destination);
|
|
75
|
-
config.jwtToken = token;
|
|
76
|
-
// Load destination config to get SAP URL from AuthBroker
|
|
77
|
-
// AuthBroker stores session data, we can access it through the broker
|
|
78
|
-
// For now, we'll rely on the token being valid and the URL being set from destination
|
|
79
|
-
// The actual URL will be loaded when we create the connection
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
logger_js_1.logger.error("Failed to load destination config from AuthBroker", {
|
|
83
|
-
type: "AUTH_BROKER_LOAD_ERROR",
|
|
84
|
-
destination: config.destination,
|
|
85
|
-
error: error instanceof Error ? error.message : String(error),
|
|
86
|
-
});
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (!entry || entry.configSignature !== cacheKey) {
|
|
91
|
-
logger_js_1.logger.debug("Creating new direct cloud connection", {
|
|
92
|
-
type: "DIRECT_CLOUD_CONNECTION_CREATE",
|
|
93
|
-
sessionId: sessionId.substring(0, 8),
|
|
94
|
-
destination: config.destination,
|
|
95
|
-
sapUrl: config.sapUrl,
|
|
96
|
-
});
|
|
97
|
-
// Dispose old connection if exists
|
|
98
|
-
if (entry) {
|
|
99
|
-
// Connection disposal will be handled by connection package
|
|
100
|
-
}
|
|
101
|
-
// Create SAP config for connection
|
|
102
|
-
const sapConfig = {
|
|
103
|
-
url: config.sapUrl,
|
|
104
|
-
authType: config.authType,
|
|
105
|
-
jwtToken: config.jwtToken,
|
|
106
|
-
refreshToken: config.refreshToken,
|
|
107
|
-
username: config.username,
|
|
108
|
-
password: config.password,
|
|
109
|
-
sapClient: config.sapClient,
|
|
110
|
-
uaaUrl: config.uaaUrl,
|
|
111
|
-
uaaClientId: config.uaaClientId,
|
|
112
|
-
uaaClientSecret: config.uaaClientSecret,
|
|
113
|
-
};
|
|
114
|
-
// Create connection
|
|
115
|
-
const connectionSessionId = `mcp-proxy-direct-${sessionId}`;
|
|
116
|
-
const sessionStorage = new connection_1.FileSessionStorage();
|
|
117
|
-
const connection = (0, connection_1.createAbapConnection)(sapConfig, logger_js_1.logger, // Use our logger
|
|
118
|
-
sessionStorage, connectionSessionId);
|
|
119
|
-
// Connect
|
|
120
|
-
try {
|
|
121
|
-
await connection.connect();
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
logger_js_1.logger.error("Failed to connect to cloud ABAP", {
|
|
125
|
-
type: "DIRECT_CLOUD_CONNECTION_ERROR",
|
|
126
|
-
error: error instanceof Error ? error.message : String(error),
|
|
127
|
-
destination: config.destination,
|
|
128
|
-
});
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
131
|
-
entry = {
|
|
132
|
-
connection,
|
|
133
|
-
configSignature: cacheKey,
|
|
134
|
-
lastUsed: new Date(),
|
|
135
|
-
};
|
|
136
|
-
connectionCache.set(cacheKey, entry);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
entry.lastUsed = new Date();
|
|
140
|
-
logger_js_1.logger.debug("Reusing cached direct cloud connection", {
|
|
141
|
-
type: "DIRECT_CLOUD_CONNECTION_REUSE",
|
|
142
|
-
sessionId: sessionId.substring(0, 8),
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
return entry.connection;
|
|
146
|
-
}
|
|
@@ -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;AAEtD,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,CASzB;AA2CD;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,cAAc,CAAC,CAoEzB"}
|