@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +66 -1
  2. package/README.md +73 -63
  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 +440 -273
  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 -92
  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 +15 -1
  15. package/dist/lib/stores.d.ts +7 -6
  16. package/dist/lib/stores.d.ts.map +1 -1
  17. package/dist/lib/stores.js +121 -8
  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 -60
  25. package/dist/proxy/cloudLlmHubProxy.d.ts.map +1 -1
  26. package/dist/proxy/cloudLlmHubProxy.js +1 -378
  27. package/dist/router/headerAnalyzer.d.ts +14 -18
  28. package/dist/router/headerAnalyzer.d.ts.map +1 -1
  29. package/dist/router/headerAnalyzer.js +40 -67
  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 -14
  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 -179
  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 -140
@@ -1,61 +1,2 @@
1
- /**
2
- * Cloud LLM Hub Proxy - Proxies requests to cloud-llm-hub with JWT authentication
3
- *
4
- * For requests with x-sap-destination: "sk", proxies to cloud-llm-hub
5
- * with JWT token from auth-broker
6
- */
7
- import { AuthBroker } from "@mcp-abap-adt/auth-broker";
8
- import { RoutingDecision } from "../router/headerAnalyzer.js";
9
- import { ProxyConfig } from "../lib/config.js";
10
- export interface ProxyRequest {
11
- method: string;
12
- params?: any;
13
- id?: string | number | null;
14
- jsonrpc?: string;
15
- }
16
- export interface ProxyResponse {
17
- jsonrpc: string;
18
- id?: string | number | null;
19
- result?: any;
20
- error?: {
21
- code: number;
22
- message: string;
23
- data?: any;
24
- };
25
- }
26
- /**
27
- * Cloud LLM Hub Proxy Client
28
- */
29
- export declare class CloudLlmHubProxy {
30
- private axiosInstance;
31
- private authBroker;
32
- private defaultBaseUrl;
33
- private tokenCache;
34
- private readonly TOKEN_CACHE_TTL;
35
- private circuitBreaker;
36
- private config;
37
- constructor(defaultBaseUrl: string, authBroker: AuthBroker, config?: Partial<ProxyConfig>);
38
- /**
39
- * Get JWT token for destination from auth-broker with retry and token refresh
40
- */
41
- private getJwtToken;
42
- /**
43
- * Build proxy request with JWT tokens and SAP configuration
44
- *
45
- * Uses service keys:
46
- * 1. x-btp-destination - for BTP Cloud authorization (Authorization: Bearer token) - OPTIONAL
47
- * 2. x-mcp-destination - for SAP ABAP connection (SAP headers with token and config) - OPTIONAL
48
- *
49
- * Can work with only x-mcp-destination (without BTP) for local testing without authentication
50
- */
51
- private buildProxyRequest;
52
- /**
53
- * Proxy MCP request to cloud-llm-hub with retry, circuit breaker, and error handling
54
- */
55
- proxyRequest(originalRequest: ProxyRequest, routingDecision: RoutingDecision, originalHeaders: Record<string, string | string[] | undefined>): Promise<ProxyResponse>;
56
- }
57
- /**
58
- * Create Cloud LLM Hub Proxy instance
59
- */
60
- export declare function createCloudLlmHubProxy(cloudLlmHubUrl: string, config?: Partial<ProxyConfig>): Promise<CloudLlmHubProxy>;
1
+ export {};
61
2
  //# sourceMappingURL=cloudLlmHubProxy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloudLlmHubProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/cloudLlmHubProxy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAc,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAU3D,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,GAAG,CAAC;KACZ,CAAC;CACH;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,UAAU,CAAgE;IAClF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAc;gBAEhB,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IAmEzF;;OAEG;YACW,WAAW;IAwDzB;;;;;;;;OAQG;YACW,iBAAiB;IA4J/B;;OAEG;IACG,YAAY,CAChB,eAAe,EAAE,YAAY,EAC7B,eAAe,EAAE,eAAe,EAChC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAC7D,OAAO,CAAC,aAAa,CAAC;CA2G1B;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,cAAc,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAY3B"}
1
+ {"version":3,"file":"cloudLlmHubProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/cloudLlmHubProxy.ts"],"names":[],"mappings":""}
@@ -1,380 +1,3 @@
1
1
  "use strict";
2
- /**
3
- * Cloud LLM Hub Proxy - Proxies requests to cloud-llm-hub with JWT authentication
4
- *
5
- * For requests with x-sap-destination: "sk", proxies to cloud-llm-hub
6
- * with JWT token from auth-broker
7
- */
8
- var __importDefault = (this && this.__importDefault) || function (mod) {
9
- return (mod && mod.__esModule) ? mod : { "default": mod };
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.CloudLlmHubProxy = void 0;
13
- exports.createCloudLlmHubProxy = createCloudLlmHubProxy;
14
- const axios_1 = __importDefault(require("axios"));
15
- const auth_broker_1 = require("@mcp-abap-adt/auth-broker");
16
- const logger_js_1 = require("../lib/logger.js");
17
- const config_js_1 = require("../lib/config.js");
18
- const stores_js_1 = require("../lib/stores.js");
19
- const errorHandler_js_1 = require("../lib/errorHandler.js");
20
- /**
21
- * Cloud LLM Hub Proxy Client
22
- */
23
- class CloudLlmHubProxy {
24
- axiosInstance;
25
- authBroker;
26
- defaultBaseUrl;
27
- tokenCache = new Map();
28
- TOKEN_CACHE_TTL = 30 * 60 * 1000; // 30 minutes
29
- circuitBreaker;
30
- config;
31
- constructor(defaultBaseUrl, authBroker, config) {
32
- // Default base URL (used if x-mcp-url is relative)
33
- this.defaultBaseUrl = defaultBaseUrl.replace(/\/$/, ""); // Remove trailing slash
34
- this.authBroker = authBroker;
35
- this.config = (0, config_js_1.loadConfig)();
36
- // Merge provided config
37
- if (config) {
38
- this.config = { ...this.config, ...config };
39
- }
40
- // Initialize circuit breaker
41
- this.circuitBreaker = new errorHandler_js_1.CircuitBreaker(this.config.circuitBreakerThreshold || 5, this.config.circuitBreakerTimeout || 60000);
42
- // Create axios instance without baseURL - we'll use full URLs from x-mcp-url
43
- this.axiosInstance = axios_1.default.create({
44
- timeout: this.config.requestTimeout || 60000,
45
- headers: {
46
- "Content-Type": "application/json",
47
- },
48
- });
49
- // Add request interceptor for logging
50
- this.axiosInstance.interceptors.request.use((config) => {
51
- logger_js_1.logger.debug("Proxying request to cloud-llm-hub", {
52
- type: "CLOUD_LLM_HUB_PROXY_REQUEST",
53
- method: config.method,
54
- url: config.url,
55
- baseURL: config.baseURL,
56
- });
57
- return config;
58
- }, (error) => {
59
- logger_js_1.logger.error("Request interceptor error", {
60
- type: "CLOUD_LLM_HUB_PROXY_REQUEST_ERROR",
61
- error: error instanceof Error ? error.message : String(error),
62
- });
63
- return Promise.reject(error);
64
- });
65
- // Add response interceptor for logging
66
- this.axiosInstance.interceptors.response.use((response) => {
67
- logger_js_1.logger.debug("Received response from cloud-llm-hub", {
68
- type: "CLOUD_LLM_HUB_PROXY_RESPONSE",
69
- status: response.status,
70
- url: response.config.url,
71
- });
72
- return response;
73
- }, (error) => {
74
- logger_js_1.logger.error("Response interceptor error", {
75
- type: "CLOUD_LLM_HUB_PROXY_RESPONSE_ERROR",
76
- error: error instanceof Error ? error.message : String(error),
77
- status: error.response?.status,
78
- url: error.config?.url,
79
- });
80
- return Promise.reject(error);
81
- });
82
- }
83
- /**
84
- * Get JWT token for destination from auth-broker with retry and token refresh
85
- */
86
- async getJwtToken(destination, forceRefresh = false) {
87
- // Check cache first (unless force refresh)
88
- if (!forceRefresh) {
89
- const cached = this.tokenCache.get(destination);
90
- if (cached && cached.expiresAt > Date.now()) {
91
- logger_js_1.logger.debug("Using cached JWT token", {
92
- type: "JWT_TOKEN_CACHE_HIT",
93
- destination,
94
- });
95
- return cached.token;
96
- }
97
- }
98
- // Retry logic for token retrieval
99
- const retryOptions = {
100
- maxRetries: this.config.maxRetries || 3,
101
- retryDelay: this.config.retryDelay || 1000,
102
- retryableStatusCodes: [500, 502, 503, 504],
103
- };
104
- try {
105
- const token = await (0, errorHandler_js_1.retryWithBackoff)(async () => {
106
- // Clear cache if force refresh
107
- if (forceRefresh) {
108
- this.tokenCache.delete(destination);
109
- }
110
- // Get token from auth-broker
111
- const token = await this.authBroker.getToken(destination);
112
- // Cache token (assume it's valid for 30 minutes)
113
- this.tokenCache.set(destination, {
114
- token,
115
- expiresAt: Date.now() + this.TOKEN_CACHE_TTL,
116
- });
117
- logger_js_1.logger.debug("Retrieved JWT token from auth-broker", {
118
- type: "JWT_TOKEN_RETRIEVED",
119
- destination,
120
- forceRefresh,
121
- });
122
- return token;
123
- }, retryOptions);
124
- return token;
125
- }
126
- catch (error) {
127
- logger_js_1.logger.error("Failed to get JWT token from auth-broker after retries", {
128
- type: "JWT_TOKEN_ERROR",
129
- destination,
130
- error: error instanceof Error ? error.message : String(error),
131
- });
132
- throw error;
133
- }
134
- }
135
- /**
136
- * Build proxy request with JWT tokens and SAP configuration
137
- *
138
- * Uses service keys:
139
- * 1. x-btp-destination - for BTP Cloud authorization (Authorization: Bearer token) - OPTIONAL
140
- * 2. x-mcp-destination - for SAP ABAP connection (SAP headers with token and config) - OPTIONAL
141
- *
142
- * Can work with only x-mcp-destination (without BTP) for local testing without authentication
143
- */
144
- async buildProxyRequest(originalRequest, routingDecision, originalHeaders, forceTokenRefresh = false) {
145
- // Build headers for target MCP server
146
- const proxyHeaders = {
147
- "Content-Type": "application/json",
148
- };
149
- // 1. Get authorization token for BTP Cloud (x-btp-destination or --btp) - OPTIONAL
150
- // This token is used for Authorization: Bearer header to connect to cloud MCP server
151
- // Only required if btpDestination is present
152
- if (routingDecision.btpDestination) {
153
- const authToken = await this.getJwtToken(routingDecision.btpDestination, forceTokenRefresh);
154
- proxyHeaders["Authorization"] = `Bearer ${authToken}`;
155
- logger_js_1.logger.debug("Added BTP Cloud authorization token", {
156
- type: "BTP_AUTH_TOKEN_ADDED",
157
- destination: routingDecision.btpDestination,
158
- });
159
- }
160
- else {
161
- logger_js_1.logger.debug("Skipping BTP Cloud authorization (no btpDestination - local testing mode)", {
162
- type: "BTP_AUTH_SKIPPED",
163
- });
164
- }
165
- // 2. Get SAP ABAP configuration from x-mcp-destination (OPTIONAL)
166
- // This provides token and configuration for SAP ABAP connection
167
- // Only used if x-mcp-destination or --mcp is provided
168
- // For local testing (without BTP), token retrieval is optional - URL is still needed
169
- if (routingDecision.mcpDestination) {
170
- const sapUrl = await this.authBroker.getSapUrl(routingDecision.mcpDestination);
171
- // Try to get token, but don't fail if it's not available (for local testing)
172
- try {
173
- const sapToken = await this.getJwtToken(routingDecision.mcpDestination, forceTokenRefresh);
174
- proxyHeaders["x-sap-jwt-token"] = sapToken;
175
- logger_js_1.logger.debug("Added SAP ABAP token", {
176
- type: "SAP_TOKEN_ADDED",
177
- destination: routingDecision.mcpDestination,
178
- });
179
- }
180
- catch (error) {
181
- logger_js_1.logger.warn("Failed to get SAP ABAP token (continuing without token for local testing)", {
182
- type: "SAP_TOKEN_SKIPPED",
183
- destination: routingDecision.mcpDestination,
184
- error: error instanceof Error ? error.message : String(error),
185
- });
186
- // Continue without token - this is OK for local testing
187
- }
188
- // Add SAP ABAP headers
189
- if (sapUrl) {
190
- proxyHeaders["x-sap-url"] = sapUrl;
191
- }
192
- proxyHeaders["x-sap-destination"] = routingDecision.mcpDestination;
193
- logger_js_1.logger.debug("Added SAP ABAP configuration", {
194
- type: "SAP_CONFIG_ADDED",
195
- destination: routingDecision.mcpDestination,
196
- sapUrl: sapUrl || "not found",
197
- hasToken: !!proxyHeaders["x-sap-jwt-token"],
198
- });
199
- }
200
- // Preserve other original SAP headers if provided
201
- const sapHeaders = [
202
- "x-sap-client",
203
- "x-sap-auth-type",
204
- "x-sap-login",
205
- "x-sap-password",
206
- ];
207
- for (const headerName of sapHeaders) {
208
- const value = originalHeaders[headerName];
209
- if (value) {
210
- proxyHeaders[headerName] = Array.isArray(value) ? value[0] : value;
211
- }
212
- }
213
- // Get MCP server URL from:
214
- // 1. x-mcp-url header (if provided) - direct URL
215
- // 2. BTP destination service key (if btpDestination is present)
216
- // 3. MCP destination service key (if only mcpDestination is present)
217
- let baseUrl;
218
- if (routingDecision.mcpUrl) {
219
- // Use direct URL from x-mcp-url header
220
- baseUrl = routingDecision.mcpUrl;
221
- logger_js_1.logger.debug("Using MCP URL from x-mcp-url header", {
222
- type: "MCP_URL_FROM_HEADER",
223
- url: baseUrl,
224
- });
225
- }
226
- else if (routingDecision.btpDestination) {
227
- // Get URL from BTP destination service key
228
- baseUrl = await this.authBroker.getSapUrl(routingDecision.btpDestination);
229
- if (!baseUrl) {
230
- throw new Error(`Failed to get MCP server URL from BTP destination "${routingDecision.btpDestination}". Check service key file.`);
231
- }
232
- logger_js_1.logger.debug("Using MCP URL from BTP destination service key", {
233
- type: "MCP_URL_FROM_BTP",
234
- destination: routingDecision.btpDestination,
235
- url: baseUrl,
236
- });
237
- }
238
- else if (routingDecision.mcpDestination) {
239
- // Get URL from MCP destination service key (for local testing without BTP)
240
- baseUrl = await this.authBroker.getSapUrl(routingDecision.mcpDestination);
241
- if (!baseUrl) {
242
- throw new Error(`Failed to get MCP server URL from MCP destination "${routingDecision.mcpDestination}". Check service key file.`);
243
- }
244
- logger_js_1.logger.debug("Using MCP URL from MCP destination service key (local testing mode)", {
245
- type: "MCP_URL_FROM_MCP",
246
- destination: routingDecision.mcpDestination,
247
- url: baseUrl,
248
- });
249
- }
250
- else {
251
- throw new Error("Cannot determine MCP server URL: neither x-mcp-url header, btpDestination, nor mcpDestination is provided");
252
- }
253
- // Construct full MCP endpoint URL
254
- // If baseUrl already contains the path, use it as-is
255
- // Otherwise, append default endpoint /mcp/stream/http
256
- let fullUrl;
257
- if (baseUrl.includes("/mcp/") || baseUrl.endsWith("/mcp")) {
258
- // URL already contains MCP path
259
- fullUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
260
- }
261
- else {
262
- // Append default endpoint
263
- const mcpPath = "/mcp/stream/http";
264
- fullUrl = baseUrl.endsWith("/")
265
- ? `${baseUrl.slice(0, -1)}${mcpPath}`
266
- : `${baseUrl}${mcpPath}`;
267
- }
268
- logger_js_1.logger.debug("Built proxy request", {
269
- type: "PROXY_REQUEST_BUILT",
270
- btpDestination: routingDecision.btpDestination,
271
- mcpDestination: routingDecision.mcpDestination,
272
- mcpUrl: routingDecision.mcpUrl,
273
- baseUrl,
274
- fullUrl,
275
- hasAuthToken: !!proxyHeaders["Authorization"],
276
- hasSapConfig: !!proxyHeaders["x-sap-jwt-token"],
277
- sapHeaders: Object.keys(proxyHeaders).filter(h => h.startsWith("x-sap")),
278
- });
279
- // Return axios config with full URL
280
- return {
281
- method: "POST",
282
- url: fullUrl,
283
- headers: proxyHeaders,
284
- data: originalRequest,
285
- };
286
- }
287
- /**
288
- * Proxy MCP request to cloud-llm-hub with retry, circuit breaker, and error handling
289
- */
290
- async proxyRequest(originalRequest, routingDecision, originalHeaders) {
291
- // Check circuit breaker
292
- if (!this.circuitBreaker.canProceed()) {
293
- logger_js_1.logger.warn("Circuit breaker is open, rejecting request", {
294
- type: "CIRCUIT_BREAKER_REJECTED",
295
- btpDestination: routingDecision.btpDestination,
296
- mcpDestination: routingDecision.mcpDestination,
297
- });
298
- return (0, errorHandler_js_1.createErrorResponse)(originalRequest.id || null, -32001, "Service temporarily unavailable (circuit breaker open)", { circuitBreakerState: this.circuitBreaker.getState() });
299
- }
300
- const retryOptions = {
301
- maxRetries: this.config.maxRetries || 3,
302
- retryDelay: this.config.retryDelay || 1000,
303
- retryableStatusCodes: [500, 502, 503, 504],
304
- };
305
- try {
306
- const response = await (0, errorHandler_js_1.retryWithBackoff)(async () => {
307
- // Check for token expiration and refresh if needed
308
- let forceTokenRefresh = false;
309
- // Build proxy request (will get fresh token if needed)
310
- const proxyConfig = await this.buildProxyRequest(originalRequest, routingDecision, originalHeaders, forceTokenRefresh);
311
- // Send request to cloud-llm-hub
312
- const response = await this.axiosInstance.request(proxyConfig);
313
- // Record success in circuit breaker
314
- this.circuitBreaker.recordSuccess();
315
- logger_js_1.logger.debug("Proxied request completed", {
316
- type: "PROXY_REQUEST_COMPLETED",
317
- status: response.status,
318
- hasResult: !!response.data?.result,
319
- hasError: !!response.data?.error,
320
- });
321
- return response;
322
- }, retryOptions);
323
- return response.data;
324
- }
325
- catch (error) {
326
- // Record failure in circuit breaker
327
- this.circuitBreaker.recordFailure();
328
- // Handle token expiration
329
- if ((0, errorHandler_js_1.isTokenExpirationError)(error)) {
330
- logger_js_1.logger.warn("Token expiration detected, will retry with fresh token", {
331
- type: "TOKEN_EXPIRATION_DETECTED",
332
- btpDestination: routingDecision.btpDestination,
333
- mcpDestination: routingDecision.mcpDestination,
334
- });
335
- // Retry once with fresh token
336
- try {
337
- const proxyConfig = await this.buildProxyRequest(originalRequest, routingDecision, originalHeaders, true // Force token refresh
338
- );
339
- const response = await this.axiosInstance.request(proxyConfig);
340
- this.circuitBreaker.recordSuccess();
341
- return response.data;
342
- }
343
- catch (retryError) {
344
- logger_js_1.logger.error("Failed to retry with fresh token", {
345
- type: "TOKEN_REFRESH_RETRY_FAILED",
346
- error: retryError instanceof Error ? retryError.message : String(retryError),
347
- });
348
- }
349
- }
350
- logger_js_1.logger.error("Failed to proxy request to cloud-llm-hub", {
351
- type: "PROXY_REQUEST_ERROR",
352
- error: error instanceof Error ? error.message : String(error),
353
- status: axios_1.default.isAxiosError(error) ? error.response?.status : undefined,
354
- circuitBreakerState: this.circuitBreaker.getState(),
355
- });
356
- // Return error response in MCP format
357
- return (0, errorHandler_js_1.createErrorResponse)(originalRequest.id || null, axios_1.default.isAxiosError(error) && error.response?.status
358
- ? error.response.status
359
- : -32000, error instanceof Error ? error.message : "Unknown error", {
360
- circuitBreakerState: this.circuitBreaker.getState(),
361
- originalError: axios_1.default.isAxiosError(error) && error.response?.data
362
- ? error.response.data
363
- : undefined,
364
- });
365
- }
366
- }
367
- }
368
- exports.CloudLlmHubProxy = CloudLlmHubProxy;
369
- /**
370
- * Create Cloud LLM Hub Proxy instance
371
- */
372
- async function createCloudLlmHubProxy(cloudLlmHubUrl, config) {
373
- const unsafe = config?.unsafe ?? false;
374
- const { serviceKeyStore, sessionStore } = await (0, stores_js_1.getPlatformStores)(unsafe);
375
- const authBroker = new auth_broker_1.AuthBroker({
376
- serviceKeyStore,
377
- sessionStore,
378
- }, "system");
379
- return new CloudLlmHubProxy(cloudLlmHubUrl, authBroker, config);
380
- }
3
+ // DELETED
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Header Analyzer - Analyzes HTTP headers to determine routing strategy
3
3
  */
4
- import { IncomingHttpHeaders } from "http";
5
- import { HeaderValidationResult } from "@mcp-abap-adt/header-validator";
4
+ import type { IncomingHttpHeaders } from 'node:http';
6
5
  export declare enum RoutingStrategy {
7
6
  /** Proxy request with JWT authentication */
8
7
  PROXY = "proxy",
@@ -12,34 +11,31 @@ export declare enum RoutingStrategy {
12
11
  export interface RoutingDecision {
13
12
  strategy: RoutingStrategy;
14
13
  btpDestination?: string;
15
- mcpDestination?: string;
16
- mcpUrl?: string;
14
+ targetUrl?: string;
17
15
  reason: string;
18
- validationResult?: HeaderValidationResult;
19
16
  }
20
17
  /**
21
18
  * Analyze headers and extract routing information
22
- * Proxy needs either:
19
+ *
20
+ * Proxy validates only one header for authentication:
23
21
  * - x-btp-destination (or --btp): destination for BTP Cloud authorization token (Authorization: Bearer) and MCP server URL
24
- * - x-mcp-destination (or --mcp): destination for SAP ABAP connection (SAP headers) - can be used without BTP for local testing
25
22
  *
26
- * Also supports:
27
- * - x-mcp-url (or --mcp-url): direct MCP server URL (used when no BTP destination is provided, for local testing)
23
+
28
24
  *
29
- * Command-line overrides:
25
+ * Command-line overrides (take precedence over headers):
30
26
  * - --btp=<destination>: overrides x-btp-destination header
31
- * - --mcp=<destination>: overrides x-mcp-destination header
32
- * - --mcp-url=<url>: overrides x-mcp-url header (for local testing without BTP)
33
27
  *
34
- * MCP server URL is obtained from:
35
- * - x-mcp-url header or --mcp-url parameter (if provided) - takes precedence
36
- * - service key for x-btp-destination (via auth-broker) if BTP destination is present
37
- * - service key for x-mcp-destination (via auth-broker) if only MCP destination is present
28
+ * Other headers are passed directly to MCP server without validation.
29
+ *
30
+ * MPC server URL is obtained from:
31
+ * - service key for x-btp-destination (via auth-broker)
32
+ *
33
+ *
34
+ * Note: Proxy does NOT use .env files for connection configuration. Only destinations via auth-broker (service key files) are used.
38
35
  */
39
36
  export declare function analyzeHeaders(headers: IncomingHttpHeaders, configOverrides?: {
40
37
  btpDestination?: string;
41
- mcpDestination?: string;
42
- mcpUrl?: string;
38
+ targetUrl?: string;
43
39
  }): RoutingDecision;
44
40
  /**
45
41
  * Check if headers indicate a request that should be proxied
@@ -1 +1 @@
1
- {"version":3,"file":"headerAnalyzer.d.ts","sourceRoot":"","sources":["../../src/router/headerAnalyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAA2C,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAGjH,oBAAY,eAAe;IACzB,4CAA4C;IAC5C,KAAK,UAAU;IACf,6CAA6C;IAC7C,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,eAAe,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,mBAAmB,EAC5B,eAAe,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACtF,eAAe,CAyFjB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAGjE"}
1
+ {"version":3,"file":"headerAnalyzer.d.ts","sourceRoot":"","sources":["../../src/router/headerAnalyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAIrD,oBAAY,eAAe;IACzB,4CAA4C;IAC5C,KAAK,UAAU;IAEf,6CAA6C;IAC7C,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,eAAe,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,mBAAmB,EAC5B,eAAe,CAAC,EAAE;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,eAAe,CA6DjB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAGjE"}
@@ -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) {
@@ -17,102 +17,75 @@ var RoutingStrategy;
17
17
  })(RoutingStrategy || (exports.RoutingStrategy = RoutingStrategy = {}));
18
18
  /**
19
19
  * Analyze headers and extract routing information
20
- * Proxy needs either:
20
+ *
21
+ * Proxy validates only one header for authentication:
21
22
  * - x-btp-destination (or --btp): destination for BTP Cloud authorization token (Authorization: Bearer) and MCP server URL
22
- * - x-mcp-destination (or --mcp): destination for SAP ABAP connection (SAP headers) - can be used without BTP for local testing
23
23
  *
24
- * Also supports:
25
- * - x-mcp-url (or --mcp-url): direct MCP server URL (used when no BTP destination is provided, for local testing)
24
+
26
25
  *
27
- * Command-line overrides:
26
+ * Command-line overrides (take precedence over headers):
28
27
  * - --btp=<destination>: overrides x-btp-destination header
29
- * - --mcp=<destination>: overrides x-mcp-destination header
30
- * - --mcp-url=<url>: overrides x-mcp-url header (for local testing without BTP)
31
28
  *
32
- * MCP server URL is obtained from:
33
- * - x-mcp-url header or --mcp-url parameter (if provided) - takes precedence
34
- * - service key for x-btp-destination (via auth-broker) if BTP destination is present
35
- * - service key for x-mcp-destination (via auth-broker) if only MCP destination is present
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)
33
+ *
34
+ *
35
+ * Note: Proxy does NOT use .env files for connection configuration. Only destinations via auth-broker (service key files) are used.
36
36
  */
37
37
  function analyzeHeaders(headers, configOverrides) {
38
- // Validate headers using header-validator
39
- const validationResult = (0, header_validator_1.validateAuthHeaders)(headers);
40
- const validatedConfig = validationResult.config;
41
- // Helper function to extract string value from header (handles arrays)
42
- 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
+ }
43
44
  if (!headerValue)
44
45
  return undefined;
45
46
  if (Array.isArray(headerValue)) {
46
47
  return headerValue[0]?.trim();
47
48
  }
48
- return typeof headerValue === "string" ? headerValue.trim() : undefined;
49
+ return typeof headerValue === 'string' ? headerValue.trim() : undefined;
49
50
  };
50
51
  // Extract authorization destination for BTP Cloud (x-btp-destination)
51
52
  // Command-line parameter --btp takes precedence over header
52
- const btpDestinationHeader = getHeaderValue(headers["x-btp-destination"]);
53
+ const btpDestinationHeader = getHeaderValue(interfaces_1.HEADER_BTP_DESTINATION);
53
54
  const extractedBtpDestination = configOverrides?.btpDestination
54
55
  ? configOverrides.btpDestination
55
56
  : btpDestinationHeader;
56
- // Extract destination for SAP ABAP connection (x-mcp-destination)
57
- // Command-line parameter --mcp takes precedence over header
58
- const mcpDestinationHeader = getHeaderValue(headers["x-mcp-destination"]);
59
- const extractedMcpDestination = configOverrides?.mcpDestination
60
- ? configOverrides.mcpDestination
61
- : mcpDestinationHeader;
62
- // Extract direct MCP server URL (x-mcp-url)
57
+ // Extract direct MCP server URL (x-mcp-url) - REMOVED
63
58
  // Command-line parameter --mcp-url takes precedence over header
64
- const mcpUrlHeader = getHeaderValue(headers["x-mcp-url"]);
65
- const extractedMcpUrl = configOverrides?.mcpUrl
66
- ? configOverrides.mcpUrl
67
- : mcpUrlHeader;
68
- // Allow proxying if either:
69
- // 1. BTP destination is present, OR
70
- // 2. MCP destination is present, OR
71
- // 3. MCP URL is provided (for local testing without BTP)
72
- // This enables local testing without BTP authentication
73
- if (!extractedBtpDestination && !extractedMcpDestination && !extractedMcpUrl) {
74
- logger_js_1.logger.warn("Neither x-btp-destination/x-mcp-destination/x-mcp-url header nor --btp/--mcp/--mcp-url parameter is provided", {
75
- headers: Object.keys(headers).filter(k => k.toLowerCase().startsWith("x-")),
76
- hasBtpConfigOverride: !!configOverrides?.btpDestination,
77
- hasMcpConfigOverride: !!configOverrides?.mcpDestination,
78
- hasMcpUrlConfigOverride: !!configOverrides?.mcpUrl,
79
- });
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) {
80
73
  return {
81
74
  strategy: RoutingStrategy.UNKNOWN,
82
- reason: "Either x-btp-destination/--btp, x-mcp-destination/--mcp, or x-mcp-url/--mcp-url parameter is required for proxying",
83
- validationResult,
75
+ reason: 'No BTP destination provided (missing x-btp-destination or --btp)',
84
76
  };
85
77
  }
86
- // If only MCP URL is provided without destination, we still need either mcpDestination or btpDestination
87
- // unless mcpUrl is a full URL that can be used directly
88
- if (!extractedBtpDestination && !extractedMcpDestination && extractedMcpUrl) {
89
- // This is OK - we can use mcpUrl directly for local testing
90
- logger_js_1.logger.debug("Using MCP URL directly for local testing (no destination required)", {
91
- type: "MCP_URL_DIRECT",
92
- mcpUrl: extractedMcpUrl,
93
- });
94
- }
95
78
  // If x-btp-destination is present, we can proxy with BTP authentication (URL will be obtained from service key)
96
- // If only x-mcp-destination is present, we can proxy without BTP authentication (for local testing)
97
79
  // If x-mcp-url is provided, we can use it directly (for local testing)
98
- logger_js_1.logger.debug("Routing decision: PROXY", {
80
+ logger_js_1.logger?.debug('Routing decision: PROXY', {
99
81
  btpDestination: extractedBtpDestination,
100
- mcpDestination: extractedMcpDestination,
101
- mcpUrl: extractedMcpUrl || "not provided",
102
- priority: validatedConfig?.priority,
103
82
  });
104
- const reason = extractedBtpDestination
105
- ? `Proxying to MCP server from BTP destination "${extractedBtpDestination}"${extractedMcpDestination ? ` with MCP destination "${extractedMcpDestination}"` : ''}`
106
- : extractedMcpUrl
107
- ? `Proxying to MCP server at "${extractedMcpUrl}" (no BTP authentication - local testing mode)${extractedMcpDestination ? ` with MCP destination "${extractedMcpDestination}"` : ''}`
108
- : `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}"`;
109
84
  return {
110
85
  strategy: RoutingStrategy.PROXY,
111
- btpDestination: extractedBtpDestination, // Optional - only used if provided
112
- mcpDestination: extractedMcpDestination, // Optional - only used if provided
113
- mcpUrl: extractedMcpUrl, // Optional - direct URL if provided (takes precedence)
86
+ btpDestination: extractedBtpDestination,
87
+ targetUrl: extractedTargetUrl,
114
88
  reason,
115
- validationResult,
116
89
  };
117
90
  }
118
91
  /**