@mcp-abap-adt/proxy 0.1.5 → 0.1.9

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 +77 -2
  2. package/README.md +83 -66
  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 +54 -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 +38 -20
  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
@@ -1,66 +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
- /**
11
- * Check if error messages should be written to stderr
12
- * Only output in verbose mode and not in test environment
13
- */
14
- export declare function shouldWriteStderr(): boolean;
15
- export interface ProxyRequest {
16
- method: string;
17
- params?: any;
18
- id?: string | number | null;
19
- jsonrpc?: string;
20
- }
21
- export interface ProxyResponse {
22
- jsonrpc: string;
23
- id?: string | number | null;
24
- result?: any;
25
- error?: {
26
- code: number;
27
- message: string;
28
- data?: any;
29
- };
30
- }
31
- /**
32
- * Cloud LLM Hub Proxy Client
33
- */
34
- export declare class CloudLlmHubProxy {
35
- private axiosInstance;
36
- private authBroker;
37
- private defaultBaseUrl;
38
- private tokenCache;
39
- private readonly TOKEN_CACHE_TTL;
40
- private circuitBreaker;
41
- private config;
42
- constructor(defaultBaseUrl: string, authBroker: AuthBroker, config?: Partial<ProxyConfig>);
43
- /**
44
- * Get JWT token for destination from auth-broker with retry and token refresh
45
- */
46
- private getJwtToken;
47
- /**
48
- * Build proxy request with JWT tokens and SAP configuration
49
- *
50
- * Uses service keys:
51
- * 1. x-btp-destination - for BTP Cloud authorization (Authorization: Bearer token) - OPTIONAL
52
- * 2. x-mcp-destination - for SAP ABAP connection (SAP headers with token and config) - OPTIONAL
53
- *
54
- * Can work with only x-mcp-destination (without BTP) for local testing without authentication
55
- */
56
- private buildProxyRequest;
57
- /**
58
- * Proxy MCP request to cloud-llm-hub with retry, circuit breaker, and error handling
59
- */
60
- proxyRequest(originalRequest: ProxyRequest, routingDecision: RoutingDecision, originalHeaders: Record<string, string | string[] | undefined>): Promise<ProxyResponse>;
61
- }
62
- /**
63
- * Create Cloud LLM Hub Proxy instance
64
- */
65
- export declare function createCloudLlmHubProxy(cloudLlmHubUrl: string, config?: Partial<ProxyConfig>): Promise<CloudLlmHubProxy>;
1
+ export {};
66
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;AAGvD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAc,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAU3D;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAQ3C;AAED,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;IA2EzF;;OAEG;YACW,WAAW;IA6DzB;;;;;;;;OAQG;YACW,iBAAiB;IAkL/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;CAuH1B;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,cAAc,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAc3B"}
1
+ {"version":3,"file":"cloudLlmHubProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/cloudLlmHubProxy.ts"],"names":[],"mappings":""}
@@ -1,443 +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.shouldWriteStderr = shouldWriteStderr;
14
- exports.createCloudLlmHubProxy = createCloudLlmHubProxy;
15
- const axios_1 = __importDefault(require("axios"));
16
- const auth_broker_1 = require("@mcp-abap-adt/auth-broker");
17
- const auth_providers_1 = require("@mcp-abap-adt/auth-providers");
18
- const logger_js_1 = require("../lib/logger.js");
19
- const config_js_1 = require("../lib/config.js");
20
- const stores_js_1 = require("../lib/stores.js");
21
- const errorHandler_js_1 = require("../lib/errorHandler.js");
22
- /**
23
- * Check if error messages should be written to stderr
24
- * Only output in verbose mode and not in test environment
25
- */
26
- function shouldWriteStderr() {
27
- const verboseMode = process.env.MCP_PROXY_VERBOSE === "true" ||
28
- process.env.DEBUG === "true" ||
29
- process.env.DEBUG?.includes("mcp-proxy") === true;
30
- const isTestEnv = process.env.NODE_ENV === "test" ||
31
- process.env.JEST_WORKER_ID !== undefined ||
32
- typeof globalThis.jest !== "undefined";
33
- return verboseMode && !isTestEnv;
34
- }
35
- /**
36
- * Cloud LLM Hub Proxy Client
37
- */
38
- class CloudLlmHubProxy {
39
- axiosInstance;
40
- authBroker;
41
- defaultBaseUrl;
42
- tokenCache = new Map();
43
- TOKEN_CACHE_TTL = 30 * 60 * 1000; // 30 minutes
44
- circuitBreaker;
45
- config;
46
- constructor(defaultBaseUrl, authBroker, config) {
47
- // Default base URL (used if x-mcp-url is relative)
48
- this.defaultBaseUrl = defaultBaseUrl.replace(/\/$/, ""); // Remove trailing slash
49
- this.authBroker = authBroker;
50
- this.config = (0, config_js_1.loadConfig)();
51
- // Merge provided config
52
- if (config) {
53
- this.config = { ...this.config, ...config };
54
- }
55
- // Initialize circuit breaker
56
- this.circuitBreaker = new errorHandler_js_1.CircuitBreaker(this.config.circuitBreakerThreshold || 5, this.config.circuitBreakerTimeout || 60000);
57
- // Create axios instance without baseURL - we'll use full URLs from x-mcp-url
58
- // Configure HTTPS agent for proper SSL/TLS handling
59
- const https = require('https');
60
- this.axiosInstance = axios_1.default.create({
61
- timeout: this.config.requestTimeout || 60000,
62
- headers: {
63
- "Content-Type": "application/json",
64
- },
65
- httpsAgent: new https.Agent({
66
- // Allow self-signed certificates if needed (for development)
67
- rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0',
68
- // Keep connections alive for better performance
69
- keepAlive: true,
70
- }),
71
- });
72
- // Add request interceptor for logging
73
- this.axiosInstance.interceptors.request.use((config) => {
74
- logger_js_1.logger.debug("Proxying request to cloud-llm-hub", {
75
- type: "CLOUD_LLM_HUB_PROXY_REQUEST",
76
- method: config.method,
77
- url: config.url,
78
- baseURL: config.baseURL,
79
- });
80
- return config;
81
- }, (error) => {
82
- logger_js_1.logger.error("Request interceptor error", {
83
- type: "CLOUD_LLM_HUB_PROXY_REQUEST_ERROR",
84
- error: error instanceof Error ? error.message : String(error),
85
- });
86
- return Promise.reject(error);
87
- });
88
- // Add response interceptor for logging
89
- this.axiosInstance.interceptors.response.use((response) => {
90
- logger_js_1.logger.debug("Received response from cloud-llm-hub", {
91
- type: "CLOUD_LLM_HUB_PROXY_RESPONSE",
92
- status: response.status,
93
- url: response.config.url,
94
- });
95
- return response;
96
- }, (error) => {
97
- logger_js_1.logger.error("Response interceptor error", {
98
- type: "CLOUD_LLM_HUB_PROXY_RESPONSE_ERROR",
99
- error: error instanceof Error ? error.message : String(error),
100
- status: error.response?.status,
101
- url: error.config?.url,
102
- });
103
- return Promise.reject(error);
104
- });
105
- }
106
- /**
107
- * Get JWT token for destination from auth-broker with retry and token refresh
108
- */
109
- async getJwtToken(destination, forceRefresh = false) {
110
- // Check cache first (unless force refresh)
111
- if (!forceRefresh) {
112
- const cached = this.tokenCache.get(destination);
113
- if (cached && cached.expiresAt > Date.now()) {
114
- logger_js_1.logger.debug("Using cached JWT token", {
115
- type: "JWT_TOKEN_CACHE_HIT",
116
- destination,
117
- });
118
- return cached.token;
119
- }
120
- }
121
- // Retry logic for token retrieval
122
- const retryOptions = {
123
- maxRetries: this.config.maxRetries || 3,
124
- retryDelay: this.config.retryDelay || 1000,
125
- retryableStatusCodes: [500, 502, 503, 504],
126
- };
127
- try {
128
- const token = await (0, errorHandler_js_1.retryWithBackoff)(async () => {
129
- // Clear cache if force refresh
130
- if (forceRefresh) {
131
- this.tokenCache.delete(destination);
132
- }
133
- // Get token from auth-broker
134
- const token = await this.authBroker.getToken(destination);
135
- // Cache token (assume it's valid for 30 minutes)
136
- this.tokenCache.set(destination, {
137
- token,
138
- expiresAt: Date.now() + this.TOKEN_CACHE_TTL,
139
- });
140
- logger_js_1.logger.debug("Retrieved JWT token from auth-broker", {
141
- type: "JWT_TOKEN_RETRIEVED",
142
- destination,
143
- forceRefresh,
144
- });
145
- return token;
146
- }, retryOptions);
147
- return token;
148
- }
149
- catch (error) {
150
- const errorMessage = error instanceof Error ? error.message : String(error);
151
- logger_js_1.logger.error("Failed to get JWT token from auth-broker after retries", {
152
- type: "JWT_TOKEN_ERROR",
153
- destination,
154
- error: errorMessage,
155
- });
156
- // Output error to stderr for user visibility (only if verbose mode is enabled)
157
- if (shouldWriteStderr()) {
158
- process.stderr.write(`[MCP Proxy] ✗ Failed to get token for destination "${destination}": ${errorMessage}\n`);
159
- }
160
- throw error;
161
- }
162
- }
163
- /**
164
- * Build proxy request with JWT tokens and SAP configuration
165
- *
166
- * Uses service keys:
167
- * 1. x-btp-destination - for BTP Cloud authorization (Authorization: Bearer token) - OPTIONAL
168
- * 2. x-mcp-destination - for SAP ABAP connection (SAP headers with token and config) - OPTIONAL
169
- *
170
- * Can work with only x-mcp-destination (without BTP) for local testing without authentication
171
- */
172
- async buildProxyRequest(originalRequest, routingDecision, originalHeaders, forceTokenRefresh = false) {
173
- // Build headers for target MCP server
174
- const proxyHeaders = {
175
- "Content-Type": "application/json",
176
- };
177
- // 1. Get authorization token for BTP Cloud (x-btp-destination or --btp) - OPTIONAL
178
- // This token is used for Authorization: Bearer header to connect to cloud MCP server
179
- // Only required if btpDestination is present
180
- if (routingDecision.btpDestination) {
181
- const authToken = await this.getJwtToken(routingDecision.btpDestination, forceTokenRefresh);
182
- proxyHeaders["Authorization"] = `Bearer ${authToken}`;
183
- logger_js_1.logger.debug("Added BTP Cloud authorization token", {
184
- type: "BTP_AUTH_TOKEN_ADDED",
185
- destination: routingDecision.btpDestination,
186
- });
187
- }
188
- else {
189
- logger_js_1.logger.debug("Skipping BTP Cloud authorization (no btpDestination - local testing mode)", {
190
- type: "BTP_AUTH_SKIPPED",
191
- });
192
- }
193
- // 2. Get SAP ABAP configuration from x-mcp-destination (OPTIONAL)
194
- // This provides token and configuration for SAP ABAP connection
195
- // Only used if x-mcp-destination or --mcp is provided
196
- // For local testing (without BTP), token retrieval is optional - URL is still needed
197
- if (routingDecision.mcpDestination) {
198
- const connConfig = await this.authBroker.getConnectionConfig(routingDecision.mcpDestination);
199
- const sapUrl = connConfig?.serviceUrl;
200
- // Try to get token, but don't fail if it's not available (for local testing)
201
- try {
202
- const sapToken = await this.getJwtToken(routingDecision.mcpDestination, forceTokenRefresh);
203
- proxyHeaders["x-sap-jwt-token"] = sapToken;
204
- logger_js_1.logger.debug("Added SAP ABAP token", {
205
- type: "SAP_TOKEN_ADDED",
206
- destination: routingDecision.mcpDestination,
207
- });
208
- }
209
- catch (error) {
210
- logger_js_1.logger.warn("Failed to get SAP ABAP token (continuing without token for local testing)", {
211
- type: "SAP_TOKEN_SKIPPED",
212
- destination: routingDecision.mcpDestination,
213
- error: error instanceof Error ? error.message : String(error),
214
- });
215
- // Continue without token - this is OK for local testing
216
- }
217
- // Add SAP ABAP headers
218
- if (sapUrl) {
219
- proxyHeaders["x-sap-url"] = sapUrl;
220
- }
221
- proxyHeaders["x-sap-destination"] = routingDecision.mcpDestination;
222
- logger_js_1.logger.debug("Added SAP ABAP configuration", {
223
- type: "SAP_CONFIG_ADDED",
224
- destination: routingDecision.mcpDestination,
225
- sapUrl: sapUrl || "not found",
226
- hasToken: !!proxyHeaders["x-sap-jwt-token"],
227
- });
228
- }
229
- // Preserve other original SAP headers if provided
230
- const sapHeaders = [
231
- "x-sap-client",
232
- "x-sap-auth-type",
233
- "x-sap-login",
234
- "x-sap-password",
235
- ];
236
- for (const headerName of sapHeaders) {
237
- const value = originalHeaders[headerName];
238
- if (value) {
239
- proxyHeaders[headerName] = Array.isArray(value) ? value[0] : value;
240
- }
241
- }
242
- // Get MCP server URL from:
243
- // 1. x-mcp-url header (if provided) - direct URL
244
- // 2. BTP destination service key (if btpDestination is present)
245
- // 3. MCP destination service key (if only mcpDestination is present)
246
- let baseUrl;
247
- if (routingDecision.mcpUrl) {
248
- // Use direct URL from x-mcp-url header
249
- baseUrl = routingDecision.mcpUrl;
250
- logger_js_1.logger.debug("Using MCP URL from x-mcp-url header", {
251
- type: "MCP_URL_FROM_HEADER",
252
- url: baseUrl,
253
- });
254
- }
255
- else if (routingDecision.btpDestination) {
256
- // Get URL from BTP destination service key
257
- const connConfig = await this.authBroker.getConnectionConfig(routingDecision.btpDestination);
258
- baseUrl = connConfig?.serviceUrl;
259
- if (!baseUrl) {
260
- const errorMsg = `Failed to get MCP server URL from BTP destination "${routingDecision.btpDestination}". Check service key file.`;
261
- if (shouldWriteStderr()) {
262
- process.stderr.write(`[MCP Proxy] ✗ ${errorMsg}\n`);
263
- }
264
- throw new Error(errorMsg);
265
- }
266
- logger_js_1.logger.debug("Using MCP URL from BTP destination service key", {
267
- type: "MCP_URL_FROM_BTP",
268
- destination: routingDecision.btpDestination,
269
- url: baseUrl,
270
- });
271
- }
272
- else if (routingDecision.mcpDestination) {
273
- // Get URL from MCP destination service key (for local testing without BTP)
274
- const connConfig = await this.authBroker.getConnectionConfig(routingDecision.mcpDestination);
275
- baseUrl = connConfig?.serviceUrl;
276
- if (!baseUrl) {
277
- const errorMsg = `Failed to get MCP server URL from MCP destination "${routingDecision.mcpDestination}". Check service key file.`;
278
- if (shouldWriteStderr()) {
279
- process.stderr.write(`[MCP Proxy] ✗ ${errorMsg}\n`);
280
- }
281
- throw new Error(errorMsg);
282
- }
283
- logger_js_1.logger.debug("Using MCP URL from MCP destination service key (local testing mode)", {
284
- type: "MCP_URL_FROM_MCP",
285
- destination: routingDecision.mcpDestination,
286
- url: baseUrl,
287
- });
288
- }
289
- else {
290
- throw new Error("Cannot determine MCP server URL: neither x-mcp-url header, btpDestination, nor mcpDestination is provided");
291
- }
292
- // Construct full MCP endpoint URL
293
- // If baseUrl already contains the path, use it as-is
294
- // Otherwise, append default endpoint /mcp/stream/http
295
- let fullUrl;
296
- // Check if URL already contains MCP path (more flexible check)
297
- if (baseUrl.includes("/mcp/") || baseUrl.endsWith("/mcp") || baseUrl.includes("/mcp/stream/")) {
298
- // URL already contains MCP path - use as-is (but remove trailing slash if present)
299
- fullUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
300
- logger_js_1.logger.debug("Using MCP URL as-is (already contains path)", {
301
- type: "MCP_URL_AS_IS",
302
- original: baseUrl,
303
- final: fullUrl,
304
- });
305
- }
306
- else {
307
- // Append default endpoint
308
- const mcpPath = "/mcp/stream/http";
309
- fullUrl = baseUrl.endsWith("/")
310
- ? `${baseUrl.slice(0, -1)}${mcpPath}`
311
- : `${baseUrl}${mcpPath}`;
312
- logger_js_1.logger.debug("Appended MCP path to base URL", {
313
- type: "MCP_URL_APPENDED",
314
- original: baseUrl,
315
- final: fullUrl,
316
- });
317
- }
318
- logger_js_1.logger.debug("Built proxy request", {
319
- type: "PROXY_REQUEST_BUILT",
320
- btpDestination: routingDecision.btpDestination,
321
- mcpDestination: routingDecision.mcpDestination,
322
- mcpUrl: routingDecision.mcpUrl,
323
- baseUrl,
324
- fullUrl,
325
- hasAuthToken: !!proxyHeaders["Authorization"],
326
- hasSapConfig: !!proxyHeaders["x-sap-jwt-token"],
327
- sapHeaders: Object.keys(proxyHeaders).filter(h => h.startsWith("x-sap")),
328
- });
329
- // Return axios config with full URL
330
- return {
331
- method: "POST",
332
- url: fullUrl,
333
- headers: proxyHeaders,
334
- data: originalRequest,
335
- };
336
- }
337
- /**
338
- * Proxy MCP request to cloud-llm-hub with retry, circuit breaker, and error handling
339
- */
340
- async proxyRequest(originalRequest, routingDecision, originalHeaders) {
341
- // Check circuit breaker
342
- if (!this.circuitBreaker.canProceed()) {
343
- logger_js_1.logger.warn("Circuit breaker is open, rejecting request", {
344
- type: "CIRCUIT_BREAKER_REJECTED",
345
- btpDestination: routingDecision.btpDestination,
346
- mcpDestination: routingDecision.mcpDestination,
347
- });
348
- return (0, errorHandler_js_1.createErrorResponse)(originalRequest.id || null, -32001, "Service temporarily unavailable (circuit breaker open)", { circuitBreakerState: this.circuitBreaker.getState() });
349
- }
350
- const retryOptions = {
351
- maxRetries: this.config.maxRetries || 3,
352
- retryDelay: this.config.retryDelay || 1000,
353
- retryableStatusCodes: [500, 502, 503, 504],
354
- };
355
- try {
356
- const response = await (0, errorHandler_js_1.retryWithBackoff)(async () => {
357
- // Check for token expiration and refresh if needed
358
- let forceTokenRefresh = false;
359
- // Build proxy request (will get fresh token if needed)
360
- const proxyConfig = await this.buildProxyRequest(originalRequest, routingDecision, originalHeaders, forceTokenRefresh);
361
- // Send request to cloud-llm-hub
362
- const response = await this.axiosInstance.request(proxyConfig);
363
- // Record success in circuit breaker
364
- this.circuitBreaker.recordSuccess();
365
- logger_js_1.logger.debug("Proxied request completed", {
366
- type: "PROXY_REQUEST_COMPLETED",
367
- status: response.status,
368
- hasResult: !!response.data?.result,
369
- hasError: !!response.data?.error,
370
- });
371
- return response;
372
- }, retryOptions);
373
- return response.data;
374
- }
375
- catch (error) {
376
- // Record failure in circuit breaker
377
- this.circuitBreaker.recordFailure();
378
- // Handle token expiration
379
- if ((0, errorHandler_js_1.isTokenExpirationError)(error)) {
380
- logger_js_1.logger.warn("Token expiration detected, will retry with fresh token", {
381
- type: "TOKEN_EXPIRATION_DETECTED",
382
- btpDestination: routingDecision.btpDestination,
383
- mcpDestination: routingDecision.mcpDestination,
384
- });
385
- // Retry once with fresh token
386
- try {
387
- const proxyConfig = await this.buildProxyRequest(originalRequest, routingDecision, originalHeaders, true // Force token refresh
388
- );
389
- const response = await this.axiosInstance.request(proxyConfig);
390
- this.circuitBreaker.recordSuccess();
391
- return response.data;
392
- }
393
- catch (retryError) {
394
- logger_js_1.logger.error("Failed to retry with fresh token", {
395
- type: "TOKEN_REFRESH_RETRY_FAILED",
396
- error: retryError instanceof Error ? retryError.message : String(retryError),
397
- });
398
- }
399
- }
400
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
401
- const statusCode = axios_1.default.isAxiosError(error) && error.response?.status
402
- ? error.response.status
403
- : undefined;
404
- logger_js_1.logger.error("Failed to proxy request to cloud-llm-hub", {
405
- type: "PROXY_REQUEST_ERROR",
406
- error: errorMessage,
407
- status: statusCode,
408
- circuitBreakerState: this.circuitBreaker.getState(),
409
- });
410
- // Output error to stderr for user visibility (only if verbose mode is enabled)
411
- if (shouldWriteStderr()) {
412
- if (statusCode) {
413
- process.stderr.write(`[MCP Proxy] ✗ Connection failed: ${errorMessage} (HTTP ${statusCode})\n`);
414
- }
415
- else {
416
- process.stderr.write(`[MCP Proxy] ✗ Connection failed: ${errorMessage}\n`);
417
- }
418
- }
419
- // Return error response in MCP format
420
- return (0, errorHandler_js_1.createErrorResponse)(originalRequest.id || null, statusCode || -32000, errorMessage, {
421
- circuitBreakerState: this.circuitBreaker.getState(),
422
- originalError: axios_1.default.isAxiosError(error) && error.response?.data
423
- ? error.response.data
424
- : undefined,
425
- });
426
- }
427
- }
428
- }
429
- exports.CloudLlmHubProxy = CloudLlmHubProxy;
430
- /**
431
- * Create Cloud LLM Hub Proxy instance
432
- */
433
- async function createCloudLlmHubProxy(cloudLlmHubUrl, config) {
434
- const unsafe = config?.unsafe ?? false;
435
- const { serviceKeyStore, sessionStore } = await (0, stores_js_1.getPlatformStores)(unsafe);
436
- const tokenProvider = new auth_providers_1.BtpTokenProvider();
437
- const authBroker = new auth_broker_1.AuthBroker({
438
- serviceKeyStore,
439
- sessionStore,
440
- tokenProvider,
441
- }, "system");
442
- return new CloudLlmHubProxy(cloudLlmHubUrl, authBroker, config);
443
- }
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,39 +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
19
  *
23
- * Proxy validates only two headers:
20
+ * Proxy validates only one header for authentication:
24
21
  * - x-btp-destination (or --btp): destination for BTP Cloud authorization token (Authorization: Bearer) and MCP server URL
25
- * - x-mcp-destination (or --mcp): destination for SAP ABAP connection (SAP headers)
26
22
  *
27
- * Also supports:
28
- * - x-mcp-url (or --mcp-url): direct MCP server URL (optional, for local testing)
23
+
29
24
  *
30
25
  * Command-line overrides (take precedence over headers):
31
26
  * - --btp=<destination>: overrides x-btp-destination header
32
- * - --mcp=<destination>: overrides x-mcp-destination header
33
- * - --mcp-url=<url>: overrides x-mcp-url header
34
27
  *
35
- * Other headers (x-sap-url, x-sap-jwt-token, etc.) are passed directly to MCP server without validation.
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)
36
32
  *
37
- * MCP server URL is obtained from:
38
- * - x-mcp-url header or --mcp-url parameter (if provided) - takes precedence
39
- * - service key for x-btp-destination (via auth-broker) if BTP destination is present
40
- * - service key for x-mcp-destination (via auth-broker) if only MCP destination is present
41
33
  *
42
34
  * Note: Proxy does NOT use .env files for connection configuration. Only destinations via auth-broker (service key files) are used.
43
35
  */
44
36
  export declare function analyzeHeaders(headers: IncomingHttpHeaders, configOverrides?: {
45
37
  btpDestination?: string;
46
- mcpDestination?: string;
47
- mcpUrl?: string;
38
+ targetUrl?: string;
48
39
  }): RoutingDecision;
49
40
  /**
50
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;;;;;;;;;;;;;;;;;;;;;;;GAuBG;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,CAqFjB;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"}