@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
@@ -2,26 +2,35 @@
2
2
  * Configuration management for MCP ABAP ADT Proxy
3
3
  */
4
4
  export interface ProxyConfig {
5
- cloudLlmHubUrl: string;
6
5
  httpPort: number;
7
6
  ssePort: number;
8
7
  httpHost: string;
9
8
  sseHost: string;
10
9
  logLevel: string;
11
10
  btpDestination?: string;
12
- mcpDestination?: string;
13
- mcpUrl?: string;
11
+ targetUrl?: string;
14
12
  unsafe?: boolean;
15
13
  maxRetries?: number;
16
14
  retryDelay?: number;
17
15
  requestTimeout?: number;
18
16
  circuitBreakerThreshold?: number;
19
17
  circuitBreakerTimeout?: number;
18
+ browser?: 'system' | 'headless' | 'chrome' | 'edge' | 'firefox' | 'none';
19
+ browserAuthPort?: number;
20
20
  }
21
21
  /**
22
- * Load configuration from file if exists, otherwise from environment variables
22
+ * Load configuration from file if --config is provided, otherwise from environment variables and command line
23
+ * Supports both JSON and YAML formats
24
+ *
25
+ * IMPORTANT: YAML config and command line parameters are mutually exclusive:
26
+ * - If --config is provided, load ONLY from that file
27
+ * - If --config is NOT provided, load ONLY from command line parameters and environment variables
23
28
  */
24
29
  export declare function loadConfig(configPath?: string): ProxyConfig;
30
+ /**
31
+ * Get config file path from command line (--config or -c)
32
+ */
33
+ export declare function getConfigPath(): string | undefined;
25
34
  /**
26
35
  * Validate configuration
27
36
  */
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IAEjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAoC3D;AA+ED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA6C5G"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IAEjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAG/B,OAAO,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAqC3D;AAuHD;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAElD;AAUD;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG;IACnD,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAoDA"}
@@ -37,73 +37,113 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.loadConfig = loadConfig;
40
+ exports.getConfigPath = getConfigPath;
40
41
  exports.validateConfig = validateConfig;
41
- const fs = __importStar(require("fs"));
42
- const path = __importStar(require("path"));
42
+ const fs = __importStar(require("node:fs"));
43
+ const path = __importStar(require("node:path"));
44
+ const yaml = __importStar(require("js-yaml"));
43
45
  /**
44
- * Load configuration from file if exists, otherwise from environment variables
46
+ * Load configuration from file if --config is provided, otherwise from environment variables and command line
47
+ * Supports both JSON and YAML formats
48
+ *
49
+ * IMPORTANT: YAML config and command line parameters are mutually exclusive:
50
+ * - If --config is provided, load ONLY from that file
51
+ * - If --config is NOT provided, load ONLY from command line parameters and environment variables
45
52
  */
46
53
  function loadConfig(configPath) {
47
- // Try to load from config file first
48
- if (configPath || process.env.MCP_PROXY_CONFIG) {
49
- const filePath = configPath || process.env.MCP_PROXY_CONFIG;
50
- try {
51
- if (fs.existsSync(filePath)) {
52
- const configContent = fs.readFileSync(filePath, "utf-8");
53
- const fileConfig = JSON.parse(configContent);
54
- return mergeConfig(fileConfig, loadFromEnv());
55
- }
56
- }
57
- catch (error) {
58
- console.warn(`Failed to load config from file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
54
+ // Get config path from parameter or command line (--config/-c)
55
+ // Do NOT use environment variable MCP_PROXY_CONFIG - only explicit --config parameter
56
+ const finalConfigPath = configPath || getConfigPath();
57
+ // If --config is provided, load ONLY from that file (no merge with command line params)
58
+ if (finalConfigPath) {
59
+ // Warn if other CLI parameters are also provided (they will be ignored)
60
+ const conflictingParams = ['--btp', '--unsafe'].filter((param) => hasArg(param));
61
+ if (conflictingParams.length > 0) {
62
+ console.warn(`Warning: --config is specified, but the following CLI parameters will be ignored: ${conflictingParams.join(', ')}. ` +
63
+ `Configuration will be loaded ONLY from ${finalConfigPath}. ` +
64
+ `To use CLI parameters, do not specify --config.`);
59
65
  }
60
- }
61
- // Try default config file locations
62
- const defaultPaths = [
63
- path.join(process.cwd(), "mcp-proxy-config.json"),
64
- path.join(process.cwd(), ".mcp-proxy-config.json"),
65
- path.join(process.env.HOME || process.env.USERPROFILE || "", ".mcp-proxy-config.json"),
66
- ];
67
- for (const configPath of defaultPaths) {
68
66
  try {
69
- if (fs.existsSync(configPath)) {
70
- const configContent = fs.readFileSync(configPath, "utf-8");
71
- const fileConfig = JSON.parse(configContent);
72
- return mergeConfig(fileConfig, loadFromEnv());
67
+ if (fs.existsSync(finalConfigPath)) {
68
+ const fileConfig = loadConfigFile(finalConfigPath);
69
+ // Apply defaults for missing values in file config
70
+ return applyDefaults(fileConfig);
71
+ }
72
+ else {
73
+ throw new Error(`Config file not found: ${finalConfigPath}`);
73
74
  }
74
75
  }
75
76
  catch (error) {
76
- // Continue to next path
77
+ throw new Error(`Failed to load config from file ${finalConfigPath}: ${error instanceof Error ? error.message : String(error)}`);
77
78
  }
78
79
  }
79
- // Fall back to environment variables only
80
+ // No --config provided: load ONLY from command line parameters and environment variables
80
81
  return loadFromEnv();
81
82
  }
83
+ /**
84
+ * Load configuration from file (supports JSON and YAML)
85
+ */
86
+ function loadConfigFile(filePath) {
87
+ const configContent = fs.readFileSync(filePath, 'utf-8');
88
+ const ext = path.extname(filePath).toLowerCase();
89
+ if (ext === '.yaml' || ext === '.yml') {
90
+ return yaml.load(configContent);
91
+ }
92
+ else {
93
+ return JSON.parse(configContent);
94
+ }
95
+ }
96
+ /**
97
+ * Apply default values to partial config (used when loading from file)
98
+ */
99
+ function applyDefaults(fileConfig) {
100
+ const result = {
101
+ httpPort: fileConfig.httpPort ?? 3001,
102
+ ssePort: fileConfig.ssePort ?? 3002,
103
+ httpHost: fileConfig.httpHost || '0.0.0.0',
104
+ sseHost: fileConfig.sseHost || '0.0.0.0',
105
+ logLevel: fileConfig.logLevel || 'info',
106
+ btpDestination: fileConfig.btpDestination,
107
+ unsafe: fileConfig.unsafe ?? false,
108
+ maxRetries: fileConfig.maxRetries ?? 3,
109
+ retryDelay: fileConfig.retryDelay ?? 1000,
110
+ requestTimeout: fileConfig.requestTimeout ?? 60000,
111
+ circuitBreakerThreshold: fileConfig.circuitBreakerThreshold ?? 5,
112
+ circuitBreakerTimeout: fileConfig.circuitBreakerTimeout ?? 60000,
113
+ browser: fileConfig.browser || 'system',
114
+ browserAuthPort: fileConfig.browserAuthPort,
115
+ };
116
+ return result;
117
+ }
82
118
  /**
83
119
  * Load configuration from environment variables and command line
84
120
  */
85
121
  function loadFromEnv() {
86
- // Parse command line arguments for --btp, --mcp, --mcp-url, and --unsafe
87
- const btpDestination = getArgValue("--btp");
88
- const mcpDestination = getArgValue("--mcp");
89
- const mcpUrl = getArgValue("--mcp-url");
90
- const unsafe = hasArg("--unsafe") || process.env.MCP_PROXY_UNSAFE === "true";
122
+ // Parse command line arguments for --btp, --mcp-url, and --unsafe
123
+ const btpDestination = getArgValue('--btp');
124
+ const targetUrl = getArgValue('--target-url') || getArgValue('--url');
125
+ const unsafe = hasArg('--unsafe') || process.env.MCP_PROXY_UNSAFE === 'true';
126
+ const browser = getArgValue('--browser') || 'system';
127
+ const browserAuthPortStr = getArgValue('--browser-auth-port');
128
+ const browserAuthPort = browserAuthPortStr
129
+ ? parseInt(browserAuthPortStr, 10)
130
+ : undefined;
91
131
  return {
92
- cloudLlmHubUrl: process.env.CLOUD_LLM_HUB_URL || "",
93
- httpPort: parseInt(process.env.MCP_HTTP_PORT || "3001", 10),
94
- ssePort: parseInt(process.env.MCP_SSE_PORT || "3002", 10),
95
- httpHost: process.env.MCP_HTTP_HOST || "0.0.0.0",
96
- sseHost: process.env.MCP_SSE_HOST || "0.0.0.0",
97
- logLevel: process.env.LOG_LEVEL || "info",
132
+ httpPort: parseInt(process.env.MCP_HTTP_PORT || '3001', 10),
133
+ ssePort: parseInt(process.env.MCP_SSE_PORT || '3002', 10),
134
+ httpHost: process.env.MCP_HTTP_HOST || '0.0.0.0',
135
+ sseHost: process.env.MCP_SSE_HOST || '0.0.0.0',
136
+ logLevel: process.env.LOG_LEVEL || 'info',
98
137
  btpDestination,
99
- mcpDestination,
100
- mcpUrl: mcpUrl || process.env.MCP_URL,
138
+ targetUrl,
101
139
  unsafe,
102
- maxRetries: parseInt(process.env.MCP_PROXY_MAX_RETRIES || "3", 10),
103
- retryDelay: parseInt(process.env.MCP_PROXY_RETRY_DELAY || "1000", 10),
104
- requestTimeout: parseInt(process.env.MCP_PROXY_REQUEST_TIMEOUT || "60000", 10),
105
- circuitBreakerThreshold: parseInt(process.env.MCP_PROXY_CIRCUIT_BREAKER_THRESHOLD || "5", 10),
106
- circuitBreakerTimeout: parseInt(process.env.MCP_PROXY_CIRCUIT_BREAKER_TIMEOUT || "60000", 10),
140
+ maxRetries: parseInt(process.env.MCP_PROXY_MAX_RETRIES || '3', 10),
141
+ retryDelay: parseInt(process.env.MCP_PROXY_RETRY_DELAY || '1000', 10),
142
+ requestTimeout: parseInt(process.env.MCP_PROXY_REQUEST_TIMEOUT || '60000', 10),
143
+ circuitBreakerThreshold: parseInt(process.env.MCP_PROXY_CIRCUIT_BREAKER_THRESHOLD || '5', 10),
144
+ circuitBreakerTimeout: parseInt(process.env.MCP_PROXY_CIRCUIT_BREAKER_TIMEOUT || '60000', 10),
145
+ browser,
146
+ browserAuthPort,
107
147
  };
108
148
  }
109
149
  /**
@@ -116,40 +156,23 @@ function getArgValue(argName) {
116
156
  return args[i + 1];
117
157
  }
118
158
  if (args[i].startsWith(`${argName}=`)) {
119
- return args[i].split("=")[1];
159
+ return args[i].split('=')[1];
120
160
  }
121
161
  }
122
162
  return undefined;
123
163
  }
124
164
  /**
125
- * Check if argument exists in command line
165
+ * Get config file path from command line (--config or -c)
126
166
  */
127
- function hasArg(argName) {
128
- const args = process.argv;
129
- return args.some(arg => arg === argName || arg.startsWith(`${argName}=`));
167
+ function getConfigPath() {
168
+ return getArgValue('--config') || getArgValue('-c');
130
169
  }
131
170
  /**
132
- * Merge file config with environment config (env takes precedence)
171
+ * Check if argument exists in command line
133
172
  */
134
- function mergeConfig(fileConfig, envConfig) {
135
- return {
136
- cloudLlmHubUrl: envConfig.cloudLlmHubUrl || fileConfig.cloudLlmHubUrl || "",
137
- httpPort: envConfig.httpPort || fileConfig.httpPort || 3001,
138
- ssePort: envConfig.ssePort || fileConfig.ssePort || 3002,
139
- httpHost: envConfig.httpHost || fileConfig.httpHost || "0.0.0.0",
140
- sseHost: envConfig.sseHost || fileConfig.sseHost || "0.0.0.0",
141
- logLevel: envConfig.logLevel || fileConfig.logLevel || "info",
142
- // Command line overrides take precedence
143
- btpDestination: envConfig.btpDestination ?? fileConfig.btpDestination,
144
- mcpDestination: envConfig.mcpDestination ?? fileConfig.mcpDestination,
145
- mcpUrl: envConfig.mcpUrl ?? fileConfig.mcpUrl,
146
- unsafe: envConfig.unsafe ?? fileConfig.unsafe ?? false,
147
- maxRetries: envConfig.maxRetries ?? fileConfig.maxRetries ?? 3,
148
- retryDelay: envConfig.retryDelay ?? fileConfig.retryDelay ?? 1000,
149
- requestTimeout: envConfig.requestTimeout ?? fileConfig.requestTimeout ?? 60000,
150
- circuitBreakerThreshold: envConfig.circuitBreakerThreshold ?? fileConfig.circuitBreakerThreshold ?? 5,
151
- circuitBreakerTimeout: envConfig.circuitBreakerTimeout ?? fileConfig.circuitBreakerTimeout ?? 60000,
152
- };
173
+ function hasArg(argName) {
174
+ const args = process.argv;
175
+ return args.some((arg) => arg === argName || arg.startsWith(`${argName}=`));
153
176
  }
154
177
  /**
155
178
  * Validate configuration
@@ -157,36 +180,31 @@ function mergeConfig(fileConfig, envConfig) {
157
180
  function validateConfig(config) {
158
181
  const errors = [];
159
182
  const warnings = [];
160
- // Cloud LLM Hub URL is only required if we're using proxy functionality
161
- // For direct cloud and basic auth, it's not needed
162
- if (!config.cloudLlmHubUrl) {
163
- warnings.push("CLOUD_LLM_HUB_URL is not set - proxy to cloud-llm-hub will not work");
164
- }
165
- else {
166
- try {
167
- new URL(config.cloudLlmHubUrl);
168
- }
169
- catch {
170
- errors.push("CLOUD_LLM_HUB_URL must be a valid URL");
171
- }
183
+ // mcpUrl is required (either from yaml config via --config or --mcp-url parameter)
184
+ // Check if BTP destination is provided
185
+ const hasDestination = config.btpDestination;
186
+ if (!hasDestination) {
187
+ warnings.push('No BTP destination provided (--btp). Proxy will not work unless requests include x-btp-destination header.');
172
188
  }
173
189
  if (config.httpPort < 1 || config.httpPort > 65535) {
174
- errors.push("MCP_HTTP_PORT must be between 1 and 65535");
190
+ errors.push('MCP_HTTP_PORT must be between 1 and 65535');
175
191
  }
176
192
  if (config.ssePort < 1 || config.ssePort > 65535) {
177
- errors.push("MCP_SSE_PORT must be between 1 and 65535");
193
+ errors.push('MCP_SSE_PORT must be between 1 and 65535');
178
194
  }
179
195
  if (config.httpPort === config.ssePort) {
180
- errors.push("MCP_HTTP_PORT and MCP_SSE_PORT must be different");
196
+ errors.push('MCP_HTTP_PORT and MCP_SSE_PORT must be different');
181
197
  }
182
198
  if (config.maxRetries && (config.maxRetries < 0 || config.maxRetries > 10)) {
183
- warnings.push("MCP_PROXY_MAX_RETRIES should be between 0 and 10");
199
+ warnings.push('MCP_PROXY_MAX_RETRIES should be between 0 and 10');
184
200
  }
185
- if (config.retryDelay && (config.retryDelay < 0 || config.retryDelay > 60000)) {
186
- warnings.push("MCP_PROXY_RETRY_DELAY should be between 0 and 60000ms");
201
+ if (config.retryDelay &&
202
+ (config.retryDelay < 0 || config.retryDelay > 60000)) {
203
+ warnings.push('MCP_PROXY_RETRY_DELAY should be between 0 and 60000ms');
187
204
  }
188
- if (config.requestTimeout && (config.requestTimeout < 1000 || config.requestTimeout > 300000)) {
189
- warnings.push("MCP_PROXY_REQUEST_TIMEOUT should be between 1000 and 300000ms");
205
+ if (config.requestTimeout &&
206
+ (config.requestTimeout < 1000 || config.requestTimeout > 300000)) {
207
+ warnings.push('MCP_PROXY_REQUEST_TIMEOUT should be between 1000 and 300000ms');
190
208
  }
191
209
  return {
192
210
  valid: errors.length === 0,
@@ -44,7 +44,7 @@ export declare class CircuitBreaker {
44
44
  /**
45
45
  * Get current state
46
46
  */
47
- getState(): "closed" | "open" | "half-open";
47
+ getState(): 'closed' | 'open' | 'half-open';
48
48
  /**
49
49
  * Reset circuit breaker
50
50
  */
@@ -57,13 +57,13 @@ export declare function isTokenExpirationError(error: unknown): boolean;
57
57
  /**
58
58
  * Create error response in MCP format
59
59
  */
60
- export declare function createErrorResponse(id: string | number | null, code: number, message: string, data?: any): {
60
+ export declare function createErrorResponse(id: string | number | null, code: number, message: string, data?: unknown): {
61
61
  jsonrpc: string;
62
62
  id: string | number | null;
63
63
  error: {
64
64
  code: number;
65
65
  message: string;
66
- data?: any;
66
+ data?: unknown;
67
67
  };
68
68
  };
69
69
  //# sourceMappingURL=errorHandler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/lib/errorHandler.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,oBAAoB,GAAE,MAAM,EAAyB,GAAG,OAAO,CAsB/G;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,CAAC,CAAC,CAgDZ;AAED;;GAEG;AACH,qBAAa,cAAc;IAMvB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,OAAO;IANjB,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,KAAK,CAA6C;gBAGhD,SAAS,GAAE,MAAU,EACrB,OAAO,GAAE,MAAc;IAGjC;;OAEG;IACH,UAAU,IAAI,OAAO;IAkBrB;;OAEG;IACH,aAAa,IAAI,IAAI;IAYrB;;OAEG;IACH,aAAa,IAAI,IAAI;IAcrB;;OAEG;IACH,QAAQ,IAAI,QAAQ,GAAG,MAAM,GAAG,WAAW;IAI3C;;OAEG;IACH,KAAK,IAAI,IAAI;CAQd;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAiB9D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,EAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,GAAG,GACT;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,GAAG,CAAC;KACZ,CAAC;CACH,CAUA"}
1
+ {"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/lib/errorHandler.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,oBAAoB,GAAE,MAAM,EAAyB,GACpD,OAAO,CAyBT;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,CAAC,CAAC,CAoDZ;AAED;;GAEG;AACH,qBAAa,cAAc;IAMvB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,OAAO;IANjB,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,KAAK,CAA6C;gBAGhD,SAAS,GAAE,MAAU,EACrB,OAAO,GAAE,MAAc;IAGjC;;OAEG;IACH,UAAU,IAAI,OAAO;IAkBrB;;OAEG;IACH,aAAa,IAAI,IAAI;IAYrB;;OAEG;IACH,aAAa,IAAI,IAAI;IAcrB;;OAEG;IACH,QAAQ,IAAI,QAAQ,GAAG,MAAM,GAAG,WAAW;IAI3C;;OAEG;IACH,KAAK,IAAI,IAAI;CAQd;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmB9D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,EAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,OAAO,GACb;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH,CAUA"}
@@ -30,7 +30,8 @@ function isRetryableError(error, retryableStatusCodes = [500, 502, 503, 504]) {
30
30
  // Token expiration errors are retryable (will be handled separately)
31
31
  if (error instanceof Error) {
32
32
  const message = error.message.toLowerCase();
33
- if (message.includes("token") && (message.includes("expired") || message.includes("invalid"))) {
33
+ if (message.includes('token') &&
34
+ (message.includes('expired') || message.includes('invalid'))) {
34
35
  return true;
35
36
  }
36
37
  }
@@ -46,7 +47,7 @@ function sleep(ms) {
46
47
  * Retry function with exponential backoff
47
48
  */
48
49
  async function retryWithBackoff(fn, options) {
49
- const { maxRetries, retryDelay, retryableStatusCodes = [500, 502, 503, 504] } = options;
50
+ const { maxRetries, retryDelay, retryableStatusCodes = [500, 502, 503, 504], } = options;
50
51
  let lastError;
51
52
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
52
53
  try {
@@ -60,17 +61,17 @@ async function retryWithBackoff(fn, options) {
60
61
  }
61
62
  // Check if error is retryable
62
63
  if (!isRetryableError(error, retryableStatusCodes)) {
63
- logger_js_1.logger.debug("Error is not retryable, stopping retries", {
64
- type: "RETRY_STOPPED",
64
+ logger_js_1.logger?.debug('Error is not retryable, stopping retries', {
65
+ type: 'RETRY_STOPPED',
65
66
  attempt,
66
67
  error: error instanceof Error ? error.message : String(error),
67
68
  });
68
69
  throw error;
69
70
  }
70
71
  // Calculate backoff delay (exponential backoff)
71
- const delay = retryDelay * Math.pow(2, attempt);
72
- logger_js_1.logger.warn("Retrying after error", {
73
- type: "RETRY_ATTEMPT",
72
+ const delay = retryDelay * 2 ** attempt;
73
+ logger_js_1.logger?.warn('Retrying after error', {
74
+ type: 'RETRY_ATTEMPT',
74
75
  attempt: attempt + 1,
75
76
  maxRetries,
76
77
  delay,
@@ -80,8 +81,8 @@ async function retryWithBackoff(fn, options) {
80
81
  }
81
82
  }
82
83
  // All retries exhausted
83
- logger_js_1.logger.error("All retry attempts exhausted", {
84
- type: "RETRY_EXHAUSTED",
84
+ logger_js_1.logger?.error('All retry attempts exhausted', {
85
+ type: 'RETRY_EXHAUSTED',
85
86
  maxRetries,
86
87
  error: lastError instanceof Error ? lastError.message : String(lastError),
87
88
  });
@@ -95,7 +96,7 @@ class CircuitBreaker {
95
96
  timeout;
96
97
  failures = 0;
97
98
  lastFailureTime = 0;
98
- state = "closed";
99
+ state = 'closed';
99
100
  constructor(threshold = 5, timeout = 60000) {
100
101
  this.threshold = threshold;
101
102
  this.timeout = timeout;
@@ -105,12 +106,12 @@ class CircuitBreaker {
105
106
  */
106
107
  canProceed() {
107
108
  const now = Date.now();
108
- if (this.state === "open") {
109
+ if (this.state === 'open') {
109
110
  // Check if timeout has passed
110
111
  if (now - this.lastFailureTime >= this.timeout) {
111
- this.state = "half-open";
112
- logger_js_1.logger.info("Circuit breaker entering half-open state", {
113
- type: "CIRCUIT_BREAKER_HALF_OPEN",
112
+ this.state = 'half-open';
113
+ logger_js_1.logger?.info('Circuit breaker entering half-open state', {
114
+ type: 'CIRCUIT_BREAKER_HALF_OPEN',
114
115
  });
115
116
  return true;
116
117
  }
@@ -122,14 +123,14 @@ class CircuitBreaker {
122
123
  * Record success
123
124
  */
124
125
  recordSuccess() {
125
- if (this.state === "half-open") {
126
- this.state = "closed";
126
+ if (this.state === 'half-open') {
127
+ this.state = 'closed';
127
128
  this.failures = 0;
128
- logger_js_1.logger.info("Circuit breaker closed after successful request", {
129
- type: "CIRCUIT_BREAKER_CLOSED",
129
+ logger_js_1.logger?.info('Circuit breaker closed after successful request', {
130
+ type: 'CIRCUIT_BREAKER_CLOSED',
130
131
  });
131
132
  }
132
- else if (this.state === "closed") {
133
+ else if (this.state === 'closed') {
133
134
  this.failures = 0;
134
135
  }
135
136
  }
@@ -140,9 +141,9 @@ class CircuitBreaker {
140
141
  this.failures++;
141
142
  this.lastFailureTime = Date.now();
142
143
  if (this.failures >= this.threshold) {
143
- this.state = "open";
144
- logger_js_1.logger.error("Circuit breaker opened due to failures", {
145
- type: "CIRCUIT_BREAKER_OPENED",
144
+ this.state = 'open';
145
+ logger_js_1.logger?.error('Circuit breaker opened due to failures', {
146
+ type: 'CIRCUIT_BREAKER_OPENED',
146
147
  failures: this.failures,
147
148
  threshold: this.threshold,
148
149
  });
@@ -160,9 +161,9 @@ class CircuitBreaker {
160
161
  reset() {
161
162
  this.failures = 0;
162
163
  this.lastFailureTime = 0;
163
- this.state = "closed";
164
- logger_js_1.logger.info("Circuit breaker reset", {
165
- type: "CIRCUIT_BREAKER_RESET",
164
+ this.state = 'closed';
165
+ logger_js_1.logger?.info('Circuit breaker reset', {
166
+ type: 'CIRCUIT_BREAKER_RESET',
166
167
  });
167
168
  }
168
169
  }
@@ -179,8 +180,10 @@ function isTokenExpirationError(error) {
179
180
  }
180
181
  if (error instanceof Error) {
181
182
  const message = error.message.toLowerCase();
182
- return (message.includes("token") &&
183
- (message.includes("expired") || message.includes("invalid") || message.includes("unauthorized")));
183
+ return (message.includes('token') &&
184
+ (message.includes('expired') ||
185
+ message.includes('invalid') ||
186
+ message.includes('unauthorized')));
184
187
  }
185
188
  return false;
186
189
  }
@@ -189,7 +192,7 @@ function isTokenExpirationError(error) {
189
192
  */
190
193
  function createErrorResponse(id, code, message, data) {
191
194
  return {
192
- jsonrpc: "2.0",
195
+ jsonrpc: '2.0',
193
196
  id,
194
197
  error: {
195
198
  code,
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oBAAY,QAAQ;IAClB,KAAK,IAAI;IACT,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;CACV;AAED,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9D;AAqCD,eAAO,MAAM,MAAM,EAAE,MAIpB,CAAC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oBAAY,QAAQ;IAClB,KAAK,IAAI;IACT,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;CACV;AAED,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9D;AAwDD,eAAO,MAAM,MAAM,EAAE,MAKpB,CAAC"}
@@ -13,10 +13,23 @@ var LogLevel;
13
13
  })(LogLevel || (exports.LogLevel = LogLevel = {}));
14
14
  class SimpleLogger {
15
15
  level;
16
+ verbose;
16
17
  constructor(level = LogLevel.INFO) {
17
18
  this.level = level;
19
+ // DEBUG logs are only shown if MCP_PROXY_VERBOSE or DEBUG environment variable is set
20
+ // INFO, WARN, and ERROR logs are always shown (even without verbose mode)
21
+ this.verbose =
22
+ process.env.MCP_PROXY_VERBOSE === 'true' ||
23
+ process.env.DEBUG === 'true' ||
24
+ process.env.DEBUG?.includes('mcp-proxy') === true;
18
25
  }
19
26
  log(level, prefix, message, meta) {
27
+ // Always output INFO, WARN, and ERROR logs (even without verbose mode)
28
+ // Only suppress DEBUG logs in non-verbose mode
29
+ if (level === LogLevel.DEBUG && !this.verbose) {
30
+ return;
31
+ }
32
+ // Respect the log level
20
33
  if (level < this.level) {
21
34
  return;
22
35
  }
@@ -39,5 +52,6 @@ class SimpleLogger {
39
52
  }
40
53
  // Default logger instance
41
54
  exports.logger = new SimpleLogger(process.env.LOG_LEVEL?.toUpperCase()
42
- ? (LogLevel[process.env.LOG_LEVEL.toUpperCase()] ?? LogLevel.INFO)
55
+ ? (LogLevel[process.env.LOG_LEVEL.toUpperCase()] ??
56
+ LogLevel.INFO)
43
57
  : LogLevel.INFO);
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * Platform-specific stores for proxy
3
- * Uses FileServiceKeyStore and either FileSessionStore or SafeSessionStore
3
+ * Uses XsuaaServiceKeyStore and either XsuaaSessionStore or SafeXsuaaSessionStore
4
4
  * depending on the unsafe configuration parameter
5
5
  */
6
- import { IServiceKeyStore, ISessionStore } from "@mcp-abap-adt/auth-broker";
6
+ import type { IServiceKeyStore, ISessionStore } from '@mcp-abap-adt/auth-broker';
7
7
  /**
8
8
  * Get platform-specific stores
9
- * Returns stores based on configuration:
10
- * - If unsafe=true: uses FileSessionStore (persists to disk)
11
- * - If unsafe=false: uses SafeSessionStore (in-memory, secure)
12
- * @param unsafe If true, use FileSessionStore. If false, use SafeSessionStore (default).
9
+ * Returns XSUAA stores for BTP authentication:
10
+ * - If unsafe=true: uses XsuaaSessionStore (persists to disk)
11
+ * - If unsafe=false: uses SafeXsuaaSessionStore (in-memory, secure)
12
+ * - Service key store: XsuaaServiceKeyStore
13
+ * @param unsafe If true, use XsuaaSessionStore. If false, use SafeXsuaaSessionStore (default).
13
14
  */
14
15
  export declare function getPlatformStores(unsafe?: boolean): Promise<{
15
16
  serviceKeyStore: IServiceKeyStore;
@@ -1 +1 @@
1
- {"version":3,"file":"stores.d.ts","sourceRoot":"","sources":["../../src/lib/stores.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAIL,gBAAgB,EAChB,aAAa,EACd,MAAM,2BAA2B,CAAC;AAEnC;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC;IACxE,eAAe,EAAE,gBAAgB,CAAC;IAClC,YAAY,EAAE,aAAa,CAAC;CAC7B,CAAC,CAKD"}
1
+ {"version":3,"file":"stores.d.ts","sourceRoot":"","sources":["../../src/lib/stores.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACd,MAAM,2BAA2B,CAAC;AA4EnC;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC;IACxE,eAAe,EAAE,gBAAgB,CAAC;IAClC,YAAY,EAAE,aAAa,CAAC;CAC7B,CAAC,CAqBD"}