@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +66 -1
  2. package/README.md +73 -65
  3. package/bin/mcp-abap-adt-proxy.js +15 -42
  4. package/dist/index.d.ts +3 -12
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +414 -277
  7. package/dist/lib/config.d.ts +13 -4
  8. package/dist/lib/config.d.ts.map +1 -1
  9. package/dist/lib/config.js +110 -93
  10. package/dist/lib/errorHandler.d.ts +3 -3
  11. package/dist/lib/errorHandler.d.ts.map +1 -1
  12. package/dist/lib/errorHandler.js +31 -28
  13. package/dist/lib/logger.d.ts.map +1 -1
  14. package/dist/lib/logger.js +12 -9
  15. package/dist/lib/stores.d.ts +7 -7
  16. package/dist/lib/stores.d.ts.map +1 -1
  17. package/dist/lib/stores.js +46 -80
  18. package/dist/lib/transportConfig.d.ts +1 -1
  19. package/dist/lib/transportConfig.d.ts.map +1 -1
  20. package/dist/lib/transportConfig.js +58 -38
  21. package/dist/proxy/btpProxy.d.ts +102 -0
  22. package/dist/proxy/btpProxy.d.ts.map +1 -0
  23. package/dist/proxy/btpProxy.js +833 -0
  24. package/dist/proxy/cloudLlmHubProxy.d.ts +1 -65
  25. package/dist/proxy/cloudLlmHubProxy.d.ts.map +1 -1
  26. package/dist/proxy/cloudLlmHubProxy.js +1 -441
  27. package/dist/router/headerAnalyzer.d.ts +9 -18
  28. package/dist/router/headerAnalyzer.d.ts.map +1 -1
  29. package/dist/router/headerAnalyzer.js +35 -63
  30. package/dist/router/requestInterceptor.d.ts +5 -11
  31. package/dist/router/requestInterceptor.d.ts.map +1 -1
  32. package/dist/router/requestInterceptor.js +33 -30
  33. package/package.json +33 -15
  34. package/dist/router/directCloudRouter.d.ts +0 -32
  35. package/dist/router/directCloudRouter.d.ts.map +0 -1
  36. package/dist/router/directCloudRouter.js +0 -146
  37. package/dist/router/localBasicRouter.d.ts +0 -23
  38. package/dist/router/localBasicRouter.d.ts.map +0 -1
  39. 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 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
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 two headers:
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
- * Also supports:
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 (x-sap-url, x-sap-jwt-token, etc.) are passed directly to MCP server without validation.
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
- // Validate headers using header-validator
44
- const validationResult = (0, header_validator_1.validateAuthHeaders)(headers);
45
- const validatedConfig = validationResult.config;
46
- // Helper function to extract string value from header (handles arrays)
47
- const getHeaderValue = (headerValue) => {
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 === "string" ? headerValue.trim() : undefined;
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(headers["x-btp-destination"]);
53
+ const btpDestinationHeader = getHeaderValue(interfaces_1.HEADER_BTP_DESTINATION);
58
54
  const extractedBtpDestination = configOverrides?.btpDestination
59
55
  ? configOverrides.btpDestination
60
56
  : btpDestinationHeader;
61
- // Extract destination for SAP ABAP connection (x-mcp-destination)
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(headers["x-mcp-url"]);
70
- const extractedMcpUrl = configOverrides?.mcpUrl
71
- ? configOverrides.mcpUrl
72
- : mcpUrlHeader;
73
- // Validate: at least one destination or MCP URL must be provided
74
- // We validate only x-btp-destination and x-mcp-destination headers
75
- // Other headers (x-sap-url, x-sap-jwt-token, etc.) are passed directly to MCP server
76
- if (!extractedBtpDestination && !extractedMcpDestination && !extractedMcpUrl) {
77
- logger_js_1.logger.warn("Neither x-btp-destination/x-mcp-destination header nor --btp/--mcp parameter is provided, and no x-mcp-url/--mcp-url", {
78
- headers: Object.keys(headers).filter(k => k.toLowerCase().startsWith("x-")),
79
- hasBtpConfigOverride: !!configOverrides?.btpDestination,
80
- hasMcpConfigOverride: !!configOverrides?.mcpDestination,
81
- hasMcpUrlConfigOverride: !!configOverrides?.mcpUrl,
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: "Either x-btp-destination/--btp, x-mcp-destination/--mcp, or x-mcp-url/--mcp-url parameter is required for proxying",
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.debug("Routing decision: PROXY", {
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, // Optional - only used if provided
113
- mcpDestination: extractedMcpDestination, // Optional - only used if provided
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 "http";
5
- import { RoutingDecision } from "./headerAnalyzer.js";
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?: any;
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?: any, configOverrides?: {
18
+ export declare function interceptRequest(req: IncomingMessage, body?: unknown, configOverrides?: {
19
19
  btpDestination?: string;
20
- mcpDestination?: string;
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,MAAM,CAAC;AACvC,OAAO,EAAkB,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtE,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,GAAG,CAAC;IACX,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,GAAG,EACV,eAAe,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACtF,kBAAkB,CAuCpB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAOpD;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,CAqBxB"}
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 headerAnalyzer_js_1 = require("./headerAnalyzer.js");
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["x-session-id"] ||
24
- req.headers["mcp-session-id"] ||
25
- req.headers["x-mcp-session-id"]);
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.debug("Request intercepted", {
30
- type: "REQUEST_INTERCEPTED",
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 || "GET",
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
- "authorization",
66
- "x-sap-jwt-token",
67
- "x-sap-refresh-token",
68
- "x-sap-password",
69
- "x-sap-uaa-client-secret",
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] = "[REDACTED]";
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.5",
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
- "test": "jest --passWithNoTests",
9
- "build": "npx tsc -p tsconfig.json",
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": "rm -rf dist tsconfig.tsbuildinfo",
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
- "@modelcontextprotocol/inspector": "^0.17.2",
41
- "@types/jest": "^29.5.14",
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": "^29.7.0",
47
- "ts-jest": "^29.2.0",
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.1.9",
52
- "@mcp-abap-adt/auth-providers": "^0.1.2",
53
- "@mcp-abap-adt/auth-stores": "^0.1.4",
54
- "@mcp-abap-adt/connection": "^0.1.15",
55
- "@mcp-abap-adt/header-validator": "^0.1.6",
56
- "@modelcontextprotocol/sdk": "^1.23.0",
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"}