@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
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  "use strict";
3
2
  /**
4
3
  * MCP ABAP ADT Proxy Server
@@ -6,24 +5,31 @@
6
5
  * Proxies local MCP requests to MCP servers with optional JWT authentication.
7
6
  * Routes requests based on authentication headers:
8
7
  * - x-btp-destination (or --btp): BTP Cloud authorization with JWT token
9
- * - x-mcp-destination (or --mcp): SAP ABAP connection configuration
10
8
  *
11
- * Supports two modes:
12
- * 1. BTP authentication mode: Requires x-btp-destination or --btp parameter
13
- * 2. Local testing mode: Works with only x-mcp-destination or --mcp parameter (no BTP authentication)
9
+ * Supports BTP authentication mode: Requires x-btp-destination or --btp parameter
14
10
  */
15
11
  Object.defineProperty(exports, "__esModule", { value: true });
16
12
  exports.McpAbapAdtProxyServer = void 0;
13
+ const node_http_1 = require("node:http");
14
+ const interfaces_1 = require("@mcp-abap-adt/interfaces");
17
15
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
18
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
19
16
  const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
20
- const http_1 = require("http");
21
- const transportConfig_js_1 = require("./lib/transportConfig.js");
17
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
22
18
  const config_js_1 = require("./lib/config.js");
23
19
  const logger_js_1 = require("./lib/logger.js");
24
- const requestInterceptor_js_1 = require("./router/requestInterceptor.js");
20
+ const transportConfig_js_1 = require("./lib/transportConfig.js");
21
+ const btpProxy_js_1 = require("./proxy/btpProxy.js");
25
22
  const headerAnalyzer_js_1 = require("./router/headerAnalyzer.js");
26
- const cloudLlmHubProxy_js_1 = require("./proxy/cloudLlmHubProxy.js");
23
+ const requestInterceptor_js_1 = require("./router/requestInterceptor.js");
24
+ /**
25
+ * Safely extract id from unknown body object
26
+ */
27
+ function getBodyId(body) {
28
+ if (body && typeof body === 'object' && body !== null && 'id' in body) {
29
+ return body.id;
30
+ }
31
+ return null;
32
+ }
27
33
  /**
28
34
  * MCP ABAP ADT Proxy Server
29
35
  */
@@ -32,30 +38,31 @@ class McpAbapAdtProxyServer {
32
38
  transportConfig;
33
39
  config;
34
40
  httpServer;
35
- cloudLlmHubProxy;
41
+ btpProxy;
36
42
  constructor(transportConfig, configPath) {
37
43
  this.transportConfig = transportConfig || (0, transportConfig_js_1.parseTransportConfig)();
38
44
  this.config = (0, config_js_1.loadConfig)(configPath);
39
45
  // Validate configuration
40
- if (this.transportConfig.type === "streamable-http" || this.transportConfig.type === "sse") {
46
+ if (this.transportConfig.type === 'streamable-http' ||
47
+ this.transportConfig.type === 'sse') {
41
48
  const validation = (0, config_js_1.validateConfig)(this.config);
42
49
  if (!validation.valid) {
43
- logger_js_1.logger.error("Configuration validation failed", {
44
- type: "CONFIG_VALIDATION_ERROR",
50
+ logger_js_1.logger?.error('Configuration validation failed', {
51
+ type: 'CONFIG_VALIDATION_ERROR',
45
52
  errors: validation.errors,
46
53
  });
47
- throw new Error(`Configuration validation failed: ${validation.errors.join(", ")}`);
54
+ throw new Error(`Configuration validation failed: ${validation.errors.join(', ')}`);
48
55
  }
49
56
  if (validation.warnings.length > 0) {
50
- logger_js_1.logger.warn("Configuration validation warnings", {
51
- type: "CONFIG_VALIDATION_WARNINGS",
57
+ logger_js_1.logger?.warn('Configuration validation warnings', {
58
+ type: 'CONFIG_VALIDATION_WARNINGS',
52
59
  warnings: validation.warnings,
53
60
  });
54
61
  }
55
62
  }
56
63
  this.server = new mcp_js_1.McpServer({
57
- name: "@mcp-abap-adt/proxy",
58
- version: "0.1.0",
64
+ name: '@mcp-abap-adt/proxy',
65
+ version: '0.1.0',
59
66
  }, {
60
67
  capabilities: {
61
68
  tools: {},
@@ -71,92 +78,21 @@ class McpAbapAdtProxyServer {
71
78
  // The server automatically handles initialize and tools/list requests
72
79
  // For stdio, we'll intercept requests through server.server message handler
73
80
  }
74
- /**
75
- * Handle stdio request by proxying to MCP server
76
- * Supports both BTP authentication mode (--btp) and local testing mode (--mcp without --btp)
77
- */
78
- async handleStdioRequest(method, params, id) {
79
- // Create fake headers with config overrides (--btp, --mcp, and/or --mcp-url)
80
- const fakeHeaders = {};
81
- if (this.config.btpDestination) {
82
- fakeHeaders["x-btp-destination"] = this.config.btpDestination;
83
- }
84
- if (this.config.mcpDestination) {
85
- fakeHeaders["x-mcp-destination"] = this.config.mcpDestination;
86
- }
87
- if (this.config.mcpUrl) {
88
- fakeHeaders["x-mcp-url"] = this.config.mcpUrl;
89
- }
90
- // Create routing decision using config overrides
91
- const configOverrides = {
92
- btpDestination: this.config.btpDestination,
93
- mcpDestination: this.config.mcpDestination,
94
- mcpUrl: this.config.mcpUrl,
95
- };
96
- const { analyzeHeaders } = await import("./router/headerAnalyzer.js");
97
- const routingDecision = analyzeHeaders(fakeHeaders, configOverrides);
98
- // Check if routing is valid
99
- if (routingDecision.strategy !== headerAnalyzer_js_1.RoutingStrategy.PROXY) {
100
- logger_js_1.logger.error("Routing decision failed for stdio request", {
101
- type: "STDIO_ROUTING_DECISION_FAILED",
102
- reason: routingDecision.reason,
103
- });
104
- return {
105
- jsonrpc: "2.0",
106
- id: id || null,
107
- error: {
108
- code: -32602,
109
- message: routingDecision.reason || "Routing decision failed",
110
- },
111
- };
112
- }
113
- // Ensure proxy is initialized
114
- if (!this.cloudLlmHubProxy) {
115
- const baseUrl = this.config.cloudLlmHubUrl || "https://default.example.com";
116
- this.cloudLlmHubProxy = await (0, cloudLlmHubProxy_js_1.createCloudLlmHubProxy)(baseUrl, this.config);
117
- }
118
- // Build MCP request
119
- const mcpRequest = {
120
- method: method || "",
121
- params: params || {},
122
- id: id || null,
123
- jsonrpc: "2.0",
124
- };
125
- // Proxy request to cloud-llm-hub
126
- try {
127
- const proxyResponse = await this.cloudLlmHubProxy.proxyRequest(mcpRequest, routingDecision, fakeHeaders);
128
- return proxyResponse;
129
- }
130
- catch (error) {
131
- logger_js_1.logger.error("Failed to proxy stdio request", {
132
- type: "STDIO_PROXY_ERROR",
133
- error: error instanceof Error ? error.message : String(error),
134
- });
135
- return {
136
- jsonrpc: "2.0",
137
- id: id || null,
138
- error: {
139
- code: -32000,
140
- message: error instanceof Error ? error.message : "Unknown error",
141
- },
142
- };
143
- }
144
- }
145
81
  /**
146
82
  * Run the proxy server
147
83
  */
148
84
  async run() {
149
- if (this.transportConfig.type === "stdio") {
150
- // Check if either --btp, --mcp, or --mcp-url parameter is provided (required for stdio)
151
- if (!this.config.btpDestination && !this.config.mcpDestination && !this.config.mcpUrl) {
152
- logger_js_1.logger.error("Either --btp, --mcp, or --mcp-url parameter is required for stdio transport", {
153
- type: "STDIO_DESTINATION_REQUIRED",
85
+ if (this.transportConfig.type === 'stdio') {
86
+ // Check if --btp parameter is provided (required for stdio)
87
+ if (!this.config.btpDestination) {
88
+ logger_js_1.logger?.error('--btp parameter is required for stdio transport', {
89
+ type: 'STDIO_DESTINATION_REQUIRED',
154
90
  });
155
- throw new Error("Either --btp, --mcp, or --mcp-url parameter is required for stdio transport. Use --btp=<destination> for BTP destination, --mcp=<destination> for MCP destination, or --mcp-url=<url> for direct MCP server URL (local testing).");
91
+ throw new Error('--btp parameter is required for stdio transport. Use --btp=<destination> for BTP destination.');
156
92
  }
157
93
  // For stdio, we need to register a proxy tool that forwards all requests
158
94
  // Since we can't intercept all requests directly, we'll register a tool
159
- // that the client can call, which will proxy to cloud-llm-hub
95
+ // that the client can call, which will proxy to BtpProxy
160
96
  // Note: This requires the client to know about the proxy tool
161
97
  // Alternatively, we could use a custom transport wrapper
162
98
  // Simple stdio setup - MCP SDK will handle JSON-RPC protocol
@@ -166,23 +102,38 @@ class McpAbapAdtProxyServer {
166
102
  // For now, we'll just connect and let the SDK handle it
167
103
  // The actual proxying will need to be done through registered tools
168
104
  const transport = new stdio_js_1.StdioServerTransport();
169
- await this.server.server.connect(transport);
170
- logger_js_1.logger.info("MCP Proxy Server started (stdio transport)", {
171
- type: "SERVER_STARTED",
172
- transport: "stdio",
105
+ const msg = `MCP Proxy Server started (Transport: stdio)
106
+ Authorization source: BTP destination "${this.config.btpDestination}"`;
107
+ console.error(msg);
108
+ logger_js_1.logger?.info('MCP Proxy Server started (stdio transport)', {
109
+ type: 'SERVER_STARTED',
110
+ transport: 'stdio',
173
111
  btpDestination: this.config.btpDestination,
174
- mcpDestination: this.config.mcpDestination,
175
- mcpUrl: this.config.mcpUrl,
176
- note: "For stdio transport, --btp, --mcp, and/or --mcp-url parameters are used as default destinations",
177
- mode: this.config.btpDestination ? "BTP authentication" : "Local testing (no BTP authentication)",
112
+ note: 'For stdio transport, --btp parameter is used as default destination',
113
+ mode: 'BTP authentication',
178
114
  });
179
115
  return;
180
116
  }
181
- if (this.transportConfig.type === "streamable-http") {
117
+ // Eager initialization of BTP authentication
118
+ if (this.config.btpDestination) {
119
+ try {
120
+ if (!this.btpProxy) {
121
+ this.btpProxy = await (0, btpProxy_js_1.createBtpProxy)(this.config);
122
+ }
123
+ // Trigger initialization (background) - this will open the browser
124
+ this.btpProxy.initialize(this.config.btpDestination);
125
+ }
126
+ catch (error) {
127
+ logger_js_1.logger?.error('Failed to initialize BTP proxy for eager auth', {
128
+ error: error instanceof Error ? error.message : String(error),
129
+ });
130
+ }
131
+ }
132
+ if (this.transportConfig.type === 'streamable-http') {
182
133
  await this.startHttpServer();
183
134
  return;
184
135
  }
185
- if (this.transportConfig.type === "sse") {
136
+ if (this.transportConfig.type === 'sse') {
186
137
  await this.startSseServer();
187
138
  return;
188
139
  }
@@ -193,25 +144,23 @@ class McpAbapAdtProxyServer {
193
144
  */
194
145
  async startHttpServer() {
195
146
  const httpConfig = this.transportConfig;
196
- this.httpServer = (0, http_1.createServer)(async (req, res) => {
197
- // Log incoming HTTP request (if debug enabled)
198
- const debugHttpEnabled = process.env.DEBUG_HTTP_REQUESTS === "true";
199
- if (debugHttpEnabled) {
200
- const sanitizedHeaders = (0, requestInterceptor_js_1.sanitizeHeadersForLogging)(req.headers);
201
- logger_js_1.logger.info("HTTP Request received", {
202
- type: "HTTP_REQUEST",
203
- method: req.method,
204
- url: req.url,
205
- headers: sanitizedHeaders,
206
- remoteAddress: req.socket.remoteAddress,
207
- });
208
- }
209
- // Only handle POST requests
210
- if (req.method !== "POST") {
211
- res.writeHead(405, { "Content-Type": "text/plain" });
212
- res.end("Method not allowed");
213
- return;
214
- }
147
+ this.httpServer = (0, node_http_1.createServer)(async (req, res) => {
148
+ // Log incoming HTTP request (always log in DEBUG mode)
149
+ const _debugEnabled = process.env.DEBUG === 'true' ||
150
+ process.env.DEBUG_HTTP_REQUESTS === 'true';
151
+ const sanitizedHeaders = (0, requestInterceptor_js_1.sanitizeHeadersForLogging)(req.headers);
152
+ logger_js_1.logger?.info('=== HTTP REQUEST RECEIVED ===', {
153
+ type: 'HTTP_REQUEST_RECEIVED',
154
+ method: req.method,
155
+ url: req.url,
156
+ headers: sanitizedHeaders,
157
+ remoteAddress: req.socket.remoteAddress,
158
+ });
159
+ // Allow all HTTP methods (GET, POST, PUT, DELETE, etc.)
160
+ // if (req.method !== 'POST') {
161
+ // logger?.warn('Non-POST request rejected', { ... });
162
+ // ...
163
+ // }
215
164
  // Read request body
216
165
  let body;
217
166
  try {
@@ -219,36 +168,93 @@ class McpAbapAdtProxyServer {
219
168
  for await (const chunk of req) {
220
169
  chunks.push(chunk);
221
170
  }
222
- const bodyString = Buffer.concat(chunks).toString("utf-8");
223
- body = JSON.parse(bodyString);
171
+ const bodyString = Buffer.concat(chunks).toString('utf-8');
172
+ logger_js_1.logger?.info('=== PARSING REQUEST BODY ===', {
173
+ type: 'REQUEST_BODY_PARSING',
174
+ bodyLength: bodyString.length,
175
+ bodyPreview: bodyString.substring(0, 200),
176
+ });
177
+ if (bodyString.trim().length > 0) {
178
+ body = JSON.parse(bodyString);
179
+ }
180
+ else {
181
+ body = undefined;
182
+ }
183
+ // Sanitize body for logging
184
+ const sanitizedBody = {};
185
+ if (body && typeof body === 'object' && body !== null) {
186
+ const bodyObj = body;
187
+ sanitizedBody.method = bodyObj.method;
188
+ sanitizedBody.id = bodyObj.id;
189
+ sanitizedBody.jsonrpc = bodyObj.jsonrpc;
190
+ if (bodyObj.params &&
191
+ typeof bodyObj.params === 'object' &&
192
+ bodyObj.params !== null) {
193
+ const params = bodyObj.params;
194
+ sanitizedBody.params = {};
195
+ const sanitizedParams = sanitizedBody.params;
196
+ if (params.arguments &&
197
+ typeof params.arguments === 'object' &&
198
+ params.arguments !== null) {
199
+ sanitizedParams.arguments = {};
200
+ const sanitizedArgs = sanitizedParams.arguments;
201
+ for (const [key, value] of Object.entries(params.arguments)) {
202
+ const lowerKey = key.toLowerCase();
203
+ if (lowerKey.includes('password') ||
204
+ lowerKey.includes('token') ||
205
+ lowerKey.includes('secret')) {
206
+ sanitizedArgs[key] = '[REDACTED]';
207
+ }
208
+ else {
209
+ sanitizedArgs[key] = value;
210
+ }
211
+ }
212
+ }
213
+ for (const [key, value] of Object.entries(params)) {
214
+ if (key === 'arguments')
215
+ continue;
216
+ sanitizedParams[key] = value;
217
+ }
218
+ }
219
+ }
220
+ logger_js_1.logger?.info('=== REQUEST BODY PARSED ===', {
221
+ type: 'REQUEST_BODY_PARSED',
222
+ body: sanitizedBody,
223
+ });
224
224
  }
225
225
  catch (error) {
226
- logger_js_1.logger.error("Failed to parse request body", {
227
- type: "REQUEST_PARSE_ERROR",
226
+ logger_js_1.logger?.error('=== FAILED TO PARSE REQUEST BODY ===', {
227
+ type: 'REQUEST_PARSE_ERROR',
228
228
  error: error instanceof Error ? error.message : String(error),
229
+ errorStack: error instanceof Error ? error.stack : undefined,
229
230
  });
230
- res.writeHead(400, { "Content-Type": "text/plain" });
231
- res.end("Invalid request body");
231
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
232
+ res.end('Invalid request body');
232
233
  return;
233
234
  }
234
235
  // Intercept and analyze request
235
- // Pass config overrides (--btp, --mcp, and --mcp-url) to header analyzer
236
236
  const configOverrides = {
237
237
  btpDestination: this.config.btpDestination,
238
- mcpDestination: this.config.mcpDestination,
239
- mcpUrl: this.config.mcpUrl,
238
+ targetUrl: this.config.targetUrl,
240
239
  };
240
+ logger_js_1.logger?.info('=== INTERCEPTING REQUEST ===', {
241
+ type: 'REQUEST_INTERCEPTING',
242
+ configOverrides,
243
+ });
241
244
  const intercepted = (0, requestInterceptor_js_1.interceptRequest)(req, body, configOverrides);
242
245
  // Check routing decision
243
246
  if (intercepted.routingDecision.strategy === headerAnalyzer_js_1.RoutingStrategy.UNKNOWN) {
244
- logger_js_1.logger.error("Routing decision failed", {
245
- type: "ROUTING_DECISION_FAILED",
247
+ logger_js_1.logger?.error('Routing decision failed', {
248
+ type: 'ROUTING_DECISION_FAILED',
246
249
  reason: intercepted.routingDecision.reason,
247
250
  });
248
- res.writeHead(400, { "Content-Type": "application/json" });
251
+ res.writeHead(400, { 'Content-Type': 'application/json' });
252
+ const bodyId = body && typeof body === 'object' && body !== null && 'id' in body
253
+ ? body.id
254
+ : null;
249
255
  res.end(JSON.stringify({
250
- jsonrpc: "2.0",
251
- id: body?.id || null,
256
+ jsonrpc: '2.0',
257
+ id: bodyId || null,
252
258
  error: {
253
259
  code: -32602,
254
260
  message: intercepted.routingDecision.reason,
@@ -257,43 +263,47 @@ class McpAbapAdtProxyServer {
257
263
  return;
258
264
  }
259
265
  // Log proxy request
260
- logger_js_1.logger.debug("Proxying request", {
261
- type: "PROXY_REQUEST",
266
+ logger_js_1.logger?.info('=== PROXYING REQUEST ===', {
267
+ type: 'PROXY_REQUEST_START',
262
268
  btpDestination: intercepted.routingDecision.btpDestination,
263
- mcpDestination: intercepted.routingDecision.mcpDestination,
264
- requiresSapConfig: (0, requestInterceptor_js_1.requiresSapConfig)(body),
265
269
  });
266
270
  // Handle proxy request - add JWT and forward to x-mcp-url
267
271
  try {
268
272
  await this.handleProxyRequest(intercepted, req, res);
269
273
  }
270
274
  catch (error) {
271
- logger_js_1.logger.error("Failed to process request", {
272
- type: "REQUEST_PROCESS_ERROR",
275
+ logger_js_1.logger?.error('Failed to process request', {
276
+ type: 'REQUEST_PROCESS_ERROR',
273
277
  error: error instanceof Error ? error.message : String(error),
274
278
  strategy: intercepted.routingDecision.strategy,
275
279
  });
276
280
  if (!res.headersSent) {
277
- res.writeHead(500, { "Content-Type": "text/plain" });
278
- res.end("Internal server error");
281
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
282
+ res.end('Internal server error');
279
283
  }
280
284
  }
281
285
  });
282
286
  const port = httpConfig.port || 3001;
283
- const host = httpConfig.host || "0.0.0.0";
287
+ const host = httpConfig.host || '0.0.0.0';
284
288
  return new Promise((resolve, reject) => {
285
- this.httpServer.listen(port, host, () => {
286
- logger_js_1.logger.info("MCP Proxy Server started (HTTP transport)", {
287
- type: "SERVER_STARTED",
288
- transport: "streamable-http",
289
+ this.httpServer?.listen(port, host, () => {
290
+ const authSourceObj = this.config.btpDestination
291
+ ? `BTP destination "${this.config.btpDestination}"`
292
+ : 'Waiting for "x-btp-destination" header in requests';
293
+ const msg = `MCP Proxy Server started on port ${port} (Transport: http)
294
+ Authorization source: ${authSourceObj}`;
295
+ console.log(msg);
296
+ logger_js_1.logger?.info('MCP Proxy Server started (HTTP transport)', {
297
+ type: 'SERVER_STARTED',
298
+ transport: 'streamable-http',
289
299
  host,
290
300
  port,
291
301
  });
292
302
  resolve();
293
303
  });
294
- this.httpServer.on("error", (error) => {
295
- logger_js_1.logger.error("Failed to start HTTP server", {
296
- type: "SERVER_START_ERROR",
304
+ this.httpServer?.on('error', (error) => {
305
+ logger_js_1.logger?.error('Failed to start HTTP server', {
306
+ type: 'SERVER_START_ERROR',
297
307
  error: error.message,
298
308
  });
299
309
  reject(error);
@@ -305,57 +315,184 @@ class McpAbapAdtProxyServer {
305
315
  */
306
316
  async handleProxyRequest(intercepted, req, res) {
307
317
  const btpDestination = intercepted.routingDecision.btpDestination;
308
- const mcpDestination = intercepted.routingDecision.mcpDestination;
309
- const mcpUrl = intercepted.routingDecision.mcpUrl;
310
- logger_js_1.logger.info("Handling proxy request", {
311
- type: "PROXY_REQUEST",
318
+ // Log incoming request details
319
+ const incomingHeaders = {};
320
+ for (const [key, value] of Object.entries(intercepted.headers)) {
321
+ const lowerKey = key.toLowerCase();
322
+ if (lowerKey.includes('token') ||
323
+ lowerKey.includes('authorization') ||
324
+ lowerKey.includes('password') ||
325
+ lowerKey.includes('secret')) {
326
+ incomingHeaders[key] = '[REDACTED]';
327
+ }
328
+ else {
329
+ incomingHeaders[key] = Array.isArray(value)
330
+ ? value.join(', ')
331
+ : value || '';
332
+ }
333
+ }
334
+ const sanitizedBody = {};
335
+ if (intercepted.body &&
336
+ typeof intercepted.body === 'object' &&
337
+ intercepted.body !== null) {
338
+ const bodyObj = intercepted.body;
339
+ if (bodyObj.params &&
340
+ typeof bodyObj.params === 'object' &&
341
+ bodyObj.params !== null) {
342
+ const params = bodyObj.params;
343
+ sanitizedBody.params = {};
344
+ const sanitizedParams = sanitizedBody.params;
345
+ // For tools/call, log arguments
346
+ if (params.arguments &&
347
+ typeof params.arguments === 'object' &&
348
+ params.arguments !== null) {
349
+ sanitizedParams.arguments = {};
350
+ const sanitizedArgs = sanitizedParams.arguments;
351
+ for (const [key, value] of Object.entries(params.arguments)) {
352
+ const lowerKey = key.toLowerCase();
353
+ if (lowerKey.includes('password') ||
354
+ lowerKey.includes('token') ||
355
+ lowerKey.includes('secret')) {
356
+ sanitizedArgs[key] = '[REDACTED]';
357
+ }
358
+ else {
359
+ sanitizedArgs[key] = value;
360
+ }
361
+ }
362
+ }
363
+ // Log other params
364
+ for (const [key, value] of Object.entries(params)) {
365
+ if (key === 'arguments')
366
+ continue;
367
+ sanitizedParams[key] = value;
368
+ }
369
+ }
370
+ sanitizedBody.method = bodyObj.method;
371
+ sanitizedBody.id = bodyObj.id;
372
+ sanitizedBody.jsonrpc = bodyObj.jsonrpc;
373
+ }
374
+ // Use original request body structure for proxying (generic support)
375
+ // We do NOT enforce JSON-RPC structure here anymore, to support REST/OData
376
+ const proxyRequestData = intercepted.body;
377
+ logger_js_1.logger?.info('=== INCOMING REQUEST ===', {
378
+ type: 'PROXY_REQUEST_INCOMING',
379
+ method: req.method,
380
+ url: req.url,
312
381
  btpDestination,
313
- mcpDestination,
314
- mcpUrl,
315
382
  sessionId: intercepted.sessionId,
316
- mode: btpDestination ? "BTP authentication" : "Local testing (no BTP authentication)",
383
+ headers: incomingHeaders,
384
+ body: sanitizedBody,
385
+ mode: 'BTP authentication',
317
386
  });
318
387
  try {
319
388
  // Ensure proxy is initialized (URL will be obtained from service key for btpDestination or mcpDestination)
320
- if (!this.cloudLlmHubProxy) {
321
- // Use default base URL from config as fallback (actual URL comes from service key)
322
- const baseUrl = this.config.cloudLlmHubUrl || "https://default.example.com";
323
- this.cloudLlmHubProxy = await (0, cloudLlmHubProxy_js_1.createCloudLlmHubProxy)(baseUrl, this.config);
389
+ if (!this.btpProxy) {
390
+ this.btpProxy = await (0, btpProxy_js_1.createBtpProxy)(this.config);
324
391
  }
325
392
  // Build MCP request from intercepted request
326
- const mcpRequest = {
327
- method: intercepted.body?.method || "",
328
- params: intercepted.body?.params || {},
329
- id: intercepted.body?.id || null,
330
- jsonrpc: intercepted.body?.jsonrpc || "2.0",
393
+ // Note: Use ?? instead of || to preserve falsy values like 0
394
+ const interceptedBodyId = getBodyId(intercepted.body);
395
+ const interceptedBodyIdType = typeof interceptedBodyId;
396
+ const interceptedBodyIdUndefined = interceptedBodyId === undefined;
397
+ logger_js_1.logger?.info('=== BEFORE BUILDING MCP REQUEST ===', {
398
+ type: 'BEFORE_BUILD_MCP_REQUEST',
399
+ interceptedBodyId,
400
+ interceptedBodyIdType,
401
+ interceptedBodyIdUndefined,
402
+ interceptedBodyIdNull: interceptedBodyId === null,
403
+ interceptedBodyIdZero: interceptedBodyId === 0,
404
+ interceptedBodyIdFalsy: !interceptedBodyId,
405
+ fullInterceptedBody: JSON.stringify(intercepted.body),
406
+ });
407
+ // Preserve id correctly - 0 is a valid id value
408
+ let mcpRequestId;
409
+ if (interceptedBodyId === undefined) {
410
+ mcpRequestId = null;
411
+ }
412
+ else if (typeof interceptedBodyId === 'string' ||
413
+ typeof interceptedBodyId === 'number') {
414
+ mcpRequestId = interceptedBodyId;
415
+ }
416
+ else {
417
+ mcpRequestId = null;
418
+ }
419
+ // Proxy request to BtpProxy
420
+ // Pass the original request data directly
421
+ const proxyRequest = {
422
+ method: intercepted.method, // Use original HTTP method (GET, POST, etc.)
423
+ url: intercepted.url, // Pass request URL (path)
424
+ data: proxyRequestData, // Use original body (or undefined)
425
+ id: mcpRequestId, // Keep ID for logging context if available
426
+ };
427
+ const proxyResponse = await this.btpProxy.proxyRequest(proxyRequest, intercepted.routingDecision, intercepted.headers);
428
+ // Fix: If backend returned null ID but we entered with a valid ID (e.g. 0), restore it.
429
+ // Cline/Zod schemas reject null ID for successful responses if request had an ID.
430
+ if ((proxyResponse.id === null || proxyResponse.id === undefined) &&
431
+ mcpRequestId !== null &&
432
+ mcpRequestId !== undefined) {
433
+ logger_js_1.logger?.debug('Restoring original request ID in response', {
434
+ originalId: mcpRequestId,
435
+ receivedId: proxyResponse.id,
436
+ });
437
+ proxyResponse.id = mcpRequestId;
438
+ }
439
+ logger_js_1.logger?.info('=== FORWARDING REQUEST ===', {
440
+ type: 'PROXY_REQUEST_FORWARDING',
441
+ interceptedBodyIdAtForward: getBodyId(intercepted.body),
442
+ proxyRequest,
443
+ routingDecision: intercepted.routingDecision,
444
+ headers: intercepted.headers,
445
+ });
446
+ // Log response
447
+ const sanitizedResponse = {
448
+ jsonrpc: proxyResponse.jsonrpc,
449
+ id: proxyResponse.id,
331
450
  };
332
- // Proxy request to cloud-llm-hub
333
- const proxyResponse = await this.cloudLlmHubProxy.proxyRequest(mcpRequest, intercepted.routingDecision, intercepted.headers);
451
+ if (proxyResponse.result) {
452
+ sanitizedResponse.result = proxyResponse.result;
453
+ }
454
+ if (proxyResponse.error) {
455
+ sanitizedResponse.error = {
456
+ code: proxyResponse.error.code,
457
+ message: proxyResponse.error.message,
458
+ data: proxyResponse.error.data,
459
+ };
460
+ }
461
+ logger_js_1.logger?.info('=== RESPONSE FROM TARGET SERVICE ===', {
462
+ type: 'PROXY_RESPONSE_RECEIVED',
463
+ response: sanitizedResponse,
464
+ hasResult: !!proxyResponse.result,
465
+ hasError: !!proxyResponse.error,
466
+ });
334
467
  // Send response back to client
335
- res.writeHead(200, { "Content-Type": "application/json" });
468
+ res.writeHead(200, { 'Content-Type': 'application/json' });
336
469
  res.end(JSON.stringify(proxyResponse));
337
- logger_js_1.logger.debug("Cloud-llm-hub proxy request completed", {
338
- type: "CLOUD_LLM_HUB_PROXY_COMPLETED",
470
+ logger_js_1.logger?.info('=== RESPONSE SENT TO CLIENT ===', {
471
+ type: 'PROXY_RESPONSE_SENT',
472
+ statusCode: 200,
473
+ });
474
+ logger_js_1.logger?.debug('BtpProxy request completed', {
475
+ type: 'BTP_PROXY_COMPLETED',
339
476
  hasResult: !!proxyResponse.result,
340
477
  hasError: !!proxyResponse.error,
341
478
  });
342
479
  }
343
480
  catch (error) {
344
481
  const errorMessage = error instanceof Error ? error.message : String(error);
345
- logger_js_1.logger.error("Failed to handle cloud-llm-hub proxy request", {
346
- type: "CLOUD_LLM_HUB_PROXY_ERROR",
482
+ logger_js_1.logger?.error('Failed to handle BtpProxy request', {
483
+ type: 'BTP_PROXY_ERROR',
347
484
  error: errorMessage,
348
485
  stack: error instanceof Error ? error.stack : undefined,
349
486
  });
350
487
  // Output error to stderr for user visibility (only if verbose mode is enabled)
351
- if ((0, cloudLlmHubProxy_js_1.shouldWriteStderr)()) {
488
+ if ((0, btpProxy_js_1.shouldWriteStderr)()) {
352
489
  process.stderr.write(`[MCP Proxy] ✗ Connection error: ${errorMessage}\n`);
353
490
  }
354
491
  if (!res.headersSent) {
355
- res.writeHead(500, { "Content-Type": "application/json" });
492
+ res.writeHead(500, { 'Content-Type': 'application/json' });
356
493
  res.end(JSON.stringify({
357
- jsonrpc: "2.0",
358
- id: intercepted.body?.id || null,
494
+ jsonrpc: '2.0',
495
+ id: getBodyId(intercepted.body) || null,
359
496
  error: {
360
497
  code: -32000,
361
498
  message: errorMessage,
@@ -370,36 +507,38 @@ class McpAbapAdtProxyServer {
370
507
  async startSseServer() {
371
508
  const sseConfig = this.transportConfig;
372
509
  const streamPathMap = new Map([
373
- ["/", "/messages"],
374
- ["/mcp/events", "/mcp/messages"],
375
- ["/sse", "/messages"],
510
+ ['/', '/messages'],
511
+ ['/mcp/events', '/mcp/messages'],
512
+ ['/sse', '/messages'],
376
513
  ]);
377
- const streamPaths = Array.from(streamPathMap.keys());
514
+ const _streamPaths = Array.from(streamPathMap.keys());
378
515
  const postPathSet = new Set(streamPathMap.values());
379
- postPathSet.add("/messages");
380
- postPathSet.add("/mcp/messages");
381
- const httpServer = (0, http_1.createServer)(async (req, res) => {
516
+ postPathSet.add('/messages');
517
+ postPathSet.add('/mcp/messages');
518
+ const httpServer = (0, node_http_1.createServer)(async (req, res) => {
382
519
  // SSE: Always restrict to local connections only
383
520
  const remoteAddress = req.socket.remoteAddress;
384
- const isLocal = remoteAddress === "127.0.0.1" ||
385
- remoteAddress === "::1" ||
386
- remoteAddress === "localhost" ||
387
- (remoteAddress && remoteAddress.startsWith("127."));
521
+ const isLocal = remoteAddress === '127.0.0.1' ||
522
+ remoteAddress === '::1' ||
523
+ remoteAddress === 'localhost' ||
524
+ remoteAddress?.startsWith('127.');
388
525
  if (!isLocal) {
389
- logger_js_1.logger.warn("SSE: Non-local connection rejected", {
390
- type: "SSE_NON_LOCAL_REJECTED",
526
+ logger_js_1.logger?.warn('SSE: Non-local connection rejected', {
527
+ type: 'SSE_NON_LOCAL_REJECTED',
391
528
  remoteAddress,
392
529
  });
393
- res.writeHead(403, { "Content-Type": "text/plain" });
394
- res.end("Forbidden: SSE transport only accepts local connections");
530
+ res.writeHead(403, { 'Content-Type': 'text/plain' });
531
+ res.end('Forbidden: SSE transport only accepts local connections');
395
532
  return;
396
533
  }
397
- const requestUrl = req.url ? new URL(req.url, `http://${req.headers.host ?? `${sseConfig.host}:${sseConfig.port}`}`) : undefined;
398
- let pathname = requestUrl?.pathname ?? "/";
399
- if (pathname.length > 1 && pathname.endsWith("/")) {
534
+ const requestUrl = req.url
535
+ ? new URL(req.url, `http://${req.headers.host ?? `${sseConfig.host}:${sseConfig.port}`}`)
536
+ : undefined;
537
+ let pathname = requestUrl?.pathname ?? '/';
538
+ if (pathname.length > 1 && pathname.endsWith('/')) {
400
539
  pathname = pathname.slice(0, -1);
401
540
  }
402
- // Apply config overrides (--btp, --mcp, and --mcp-url) to headers if not present
541
+ // Apply config overrides (--btp) to headers if not present
403
542
  const headers = {};
404
543
  for (const [key, value] of Object.entries(req.headers)) {
405
544
  if (value) {
@@ -407,33 +546,27 @@ class McpAbapAdtProxyServer {
407
546
  }
408
547
  }
409
548
  // Add config overrides if not present in headers
410
- if (this.config.btpDestination && !headers["x-btp-destination"]) {
411
- headers["x-btp-destination"] = this.config.btpDestination;
412
- }
413
- if (this.config.mcpDestination && !headers["x-mcp-destination"]) {
414
- headers["x-mcp-destination"] = this.config.mcpDestination;
415
- }
416
- if (this.config.mcpUrl && !headers["x-mcp-url"]) {
417
- headers["x-mcp-url"] = this.config.mcpUrl;
549
+ if (this.config.btpDestination && !headers[interfaces_1.HEADER_BTP_DESTINATION]) {
550
+ headers[interfaces_1.HEADER_BTP_DESTINATION] = this.config.btpDestination;
418
551
  }
419
- logger_js_1.logger.debug("SSE request received", {
420
- type: "SSE_HTTP_REQUEST",
552
+ logger_js_1.logger?.debug('SSE request received', {
553
+ type: 'SSE_HTTP_REQUEST',
421
554
  method: req.method,
422
555
  pathname,
423
556
  originalUrl: req.url,
424
557
  });
425
558
  // GET /sse, /mcp/events, or / - establish SSE connection
426
- if (req.method === "GET" && streamPathMap.has(pathname)) {
427
- const postEndpoint = streamPathMap.get(pathname) ?? "/messages";
428
- logger_js_1.logger.debug("SSE client connecting", {
429
- type: "SSE_CLIENT_CONNECTING",
559
+ if (req.method === 'GET' && streamPathMap.has(pathname)) {
560
+ const postEndpoint = streamPathMap.get(pathname) ?? '/messages';
561
+ logger_js_1.logger?.debug('SSE client connecting', {
562
+ type: 'SSE_CLIENT_CONNECTING',
430
563
  pathname,
431
564
  postEndpoint,
432
565
  });
433
566
  // Create new McpServer instance for this session
434
567
  const sessionServer = new mcp_js_1.McpServer({
435
- name: "@mcp-abap-adt/proxy",
436
- version: "0.1.0",
568
+ name: '@mcp-abap-adt/proxy',
569
+ version: '0.1.0',
437
570
  }, {
438
571
  capabilities: {
439
572
  tools: {},
@@ -446,29 +579,29 @@ class McpAbapAdtProxyServer {
446
579
  enableDnsRebindingProtection: sseConfig.enableDnsRebindingProtection,
447
580
  });
448
581
  const sessionId = transport.sessionId;
449
- logger_js_1.logger.info("New SSE session created", {
450
- type: "SSE_SESSION_CREATED",
582
+ logger_js_1.logger?.info('New SSE session created', {
583
+ type: 'SSE_SESSION_CREATED',
451
584
  sessionId,
452
585
  pathname,
453
586
  });
454
587
  // Connect transport to server
455
588
  try {
456
589
  await sessionServer.server.connect(transport);
457
- logger_js_1.logger.info("SSE transport connected", {
458
- type: "SSE_CONNECTION_READY",
590
+ logger_js_1.logger?.info('SSE transport connected', {
591
+ type: 'SSE_CONNECTION_READY',
459
592
  sessionId,
460
593
  pathname,
461
594
  postEndpoint,
462
595
  });
463
596
  }
464
597
  catch (error) {
465
- logger_js_1.logger.error("Failed to connect SSE transport", {
466
- type: "SSE_CONNECT_ERROR",
598
+ logger_js_1.logger?.error('Failed to connect SSE transport', {
599
+ type: 'SSE_CONNECT_ERROR',
467
600
  error: error instanceof Error ? error.message : String(error),
468
601
  sessionId,
469
602
  });
470
603
  if (!res.headersSent) {
471
- res.writeHead(500).end("Internal Server Error");
604
+ res.writeHead(500).end('Internal Server Error');
472
605
  }
473
606
  else {
474
607
  res.end();
@@ -476,17 +609,17 @@ class McpAbapAdtProxyServer {
476
609
  return;
477
610
  }
478
611
  // Cleanup on connection close
479
- res.on("close", () => {
480
- logger_js_1.logger.info("SSE connection closed", {
481
- type: "SSE_CONNECTION_CLOSED",
612
+ res.on('close', () => {
613
+ logger_js_1.logger?.info('SSE connection closed', {
614
+ type: 'SSE_CONNECTION_CLOSED',
482
615
  sessionId,
483
616
  pathname,
484
617
  });
485
618
  sessionServer.server.close();
486
619
  });
487
620
  transport.onerror = (error) => {
488
- logger_js_1.logger.error("SSE transport error", {
489
- type: "SSE_TRANSPORT_ERROR",
621
+ logger_js_1.logger?.error('SSE transport error', {
622
+ type: 'SSE_TRANSPORT_ERROR',
490
623
  error: error instanceof Error ? error.message : String(error),
491
624
  sessionId,
492
625
  });
@@ -494,30 +627,30 @@ class McpAbapAdtProxyServer {
494
627
  return;
495
628
  }
496
629
  // POST /messages or /mcp/messages - handle client messages
497
- if (req.method === "POST" && postPathSet.has(pathname)) {
630
+ if (req.method === 'POST' && postPathSet.has(pathname)) {
498
631
  // Extract sessionId from query string or header
499
632
  let sessionId;
500
633
  if (requestUrl) {
501
- sessionId = requestUrl.searchParams.get("sessionId") || undefined;
634
+ sessionId = requestUrl.searchParams.get('sessionId') || undefined;
502
635
  }
503
636
  if (!sessionId) {
504
- sessionId = req.headers["x-session-id"];
637
+ sessionId = req.headers['x-session-id'];
505
638
  }
506
- logger_js_1.logger.debug("SSE POST request received", {
507
- type: "SSE_POST_REQUEST",
639
+ logger_js_1.logger?.debug('SSE POST request received', {
640
+ type: 'SSE_POST_REQUEST',
508
641
  sessionId,
509
642
  pathname,
510
643
  });
511
644
  if (!sessionId) {
512
- logger_js_1.logger.error("Missing sessionId in SSE POST request", {
513
- type: "SSE_MISSING_SESSION_ID",
645
+ logger_js_1.logger?.error('Missing sessionId in SSE POST request', {
646
+ type: 'SSE_MISSING_SESSION_ID',
514
647
  pathname,
515
648
  });
516
- res.writeHead(400, { "Content-Type": "application/json" }).end(JSON.stringify({
517
- jsonrpc: "2.0",
649
+ res.writeHead(400, { 'Content-Type': 'application/json' }).end(JSON.stringify({
650
+ jsonrpc: '2.0',
518
651
  error: {
519
652
  code: -32000,
520
- message: "Missing sessionId",
653
+ message: 'Missing sessionId',
521
654
  },
522
655
  id: null,
523
656
  }));
@@ -531,20 +664,20 @@ class McpAbapAdtProxyServer {
531
664
  chunks.push(chunk);
532
665
  }
533
666
  if (chunks.length > 0) {
534
- const bodyString = Buffer.concat(chunks).toString("utf-8");
667
+ const bodyString = Buffer.concat(chunks).toString('utf-8');
535
668
  body = JSON.parse(bodyString);
536
669
  }
537
670
  }
538
671
  catch (error) {
539
- logger_js_1.logger.error("Failed to parse SSE POST request body", {
540
- type: "SSE_POST_PARSE_ERROR",
672
+ logger_js_1.logger?.error('Failed to parse SSE POST request body', {
673
+ type: 'SSE_POST_PARSE_ERROR',
541
674
  error: error instanceof Error ? error.message : String(error),
542
675
  });
543
- res.writeHead(400, { "Content-Type": "application/json" }).end(JSON.stringify({
544
- jsonrpc: "2.0",
676
+ res.writeHead(400, { 'Content-Type': 'application/json' }).end(JSON.stringify({
677
+ jsonrpc: '2.0',
545
678
  error: {
546
679
  code: -32700,
547
- message: "Parse error",
680
+ message: 'Parse error',
548
681
  },
549
682
  id: null,
550
683
  }));
@@ -553,19 +686,17 @@ class McpAbapAdtProxyServer {
553
686
  // Intercept and analyze request with config overrides
554
687
  const configOverrides = {
555
688
  btpDestination: this.config.btpDestination,
556
- mcpDestination: this.config.mcpDestination,
557
- mcpUrl: this.config.mcpUrl,
558
689
  };
559
690
  const intercepted = (0, requestInterceptor_js_1.interceptRequest)(req, body, configOverrides);
560
691
  // Check routing decision
561
692
  if (intercepted.routingDecision.strategy === headerAnalyzer_js_1.RoutingStrategy.UNKNOWN) {
562
- logger_js_1.logger.error("Routing decision failed for SSE POST", {
563
- type: "SSE_POST_ROUTING_FAILED",
693
+ logger_js_1.logger?.error('Routing decision failed for SSE POST', {
694
+ type: 'SSE_POST_ROUTING_FAILED',
564
695
  reason: intercepted.routingDecision.reason,
565
696
  });
566
- res.writeHead(400, { "Content-Type": "application/json" }).end(JSON.stringify({
567
- jsonrpc: "2.0",
568
- id: body?.id || null,
697
+ res.writeHead(400, { 'Content-Type': 'application/json' }).end(JSON.stringify({
698
+ jsonrpc: '2.0',
699
+ id: getBodyId(body) || null,
569
700
  error: {
570
701
  code: -32602,
571
702
  message: intercepted.routingDecision.reason,
@@ -578,18 +709,18 @@ class McpAbapAdtProxyServer {
578
709
  await this.handleProxyRequest(intercepted, req, res);
579
710
  }
580
711
  catch (error) {
581
- logger_js_1.logger.error("Failed to process SSE POST request", {
582
- type: "SSE_POST_PROCESS_ERROR",
712
+ logger_js_1.logger?.error('Failed to process SSE POST request', {
713
+ type: 'SSE_POST_PROCESS_ERROR',
583
714
  error: error instanceof Error ? error.message : String(error),
584
715
  });
585
716
  if (!res.headersSent) {
586
- res.writeHead(500, { "Content-Type": "application/json" });
717
+ res.writeHead(500, { 'Content-Type': 'application/json' });
587
718
  res.end(JSON.stringify({
588
- jsonrpc: "2.0",
589
- id: body?.id || null,
719
+ jsonrpc: '2.0',
720
+ id: getBodyId(body) || null,
590
721
  error: {
591
722
  code: -32000,
592
- message: error instanceof Error ? error.message : "Unknown error",
723
+ message: error instanceof Error ? error.message : 'Unknown error',
593
724
  },
594
725
  }));
595
726
  }
@@ -597,25 +728,31 @@ class McpAbapAdtProxyServer {
597
728
  return;
598
729
  }
599
730
  // Unknown path or method
600
- res.writeHead(404, { "Content-Type": "text/plain" });
601
- res.end("Not found");
731
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
732
+ res.end('Not found');
602
733
  });
603
734
  const port = sseConfig.port || 3002;
604
- const host = sseConfig.host || "0.0.0.0";
735
+ const host = sseConfig.host || '0.0.0.0';
605
736
  return new Promise((resolve, reject) => {
606
737
  httpServer.listen(port, host, () => {
607
- logger_js_1.logger.info("MCP Proxy Server started (SSE transport)", {
608
- type: "SERVER_STARTED",
609
- transport: "sse",
738
+ const authSourceObj = this.config.btpDestination
739
+ ? `BTP destination "${this.config.btpDestination}"`
740
+ : 'Waiting for "x-btp-destination" header in requests';
741
+ const msg = `MCP Proxy Server started on port ${port} (Transport: sse)
742
+ Authorization source: ${authSourceObj}`;
743
+ console.log(msg);
744
+ logger_js_1.logger?.info('MCP Proxy Server started (SSE transport)', {
745
+ type: 'SERVER_STARTED',
746
+ transport: 'sse',
610
747
  host,
611
748
  port,
612
749
  });
613
750
  this.httpServer = httpServer;
614
751
  resolve();
615
752
  });
616
- httpServer.on("error", (error) => {
617
- logger_js_1.logger.error("Failed to start SSE server", {
618
- type: "SERVER_START_ERROR",
753
+ httpServer.on('error', (error) => {
754
+ logger_js_1.logger?.error('Failed to start SSE server', {
755
+ type: 'SERVER_START_ERROR',
619
756
  error: error.message,
620
757
  });
621
758
  reject(error);
@@ -630,15 +767,15 @@ class McpAbapAdtProxyServer {
630
767
  await this.server.close();
631
768
  }
632
769
  catch (error) {
633
- logger_js_1.logger.error("Failed to close MCP server", {
634
- type: "SERVER_SHUTDOWN_ERROR",
770
+ logger_js_1.logger?.error('Failed to close MCP server', {
771
+ type: 'SERVER_SHUTDOWN_ERROR',
635
772
  error: error instanceof Error ? error.message : String(error),
636
773
  });
637
774
  }
638
775
  if (this.httpServer) {
639
776
  return new Promise((resolve) => {
640
- this.httpServer.close(() => {
641
- logger_js_1.logger.info("HTTP server closed");
777
+ this.httpServer?.close(() => {
778
+ logger_js_1.logger?.info('HTTP server closed');
642
779
  resolve();
643
780
  });
644
781
  });
@@ -649,18 +786,18 @@ exports.McpAbapAdtProxyServer = McpAbapAdtProxyServer;
649
786
  // Export for use in bin script
650
787
  exports.default = McpAbapAdtProxyServer;
651
788
  // Auto-start server when run directly (unless MCP_SKIP_AUTO_START is set)
652
- if (process.env.MCP_SKIP_AUTO_START !== "true") {
789
+ if (process.env.MCP_SKIP_AUTO_START !== 'true') {
653
790
  const server = new McpAbapAdtProxyServer();
654
791
  server.run().catch((error) => {
655
- logger_js_1.logger.error("Fatal error while running MCP proxy server", {
656
- type: "SERVER_FATAL_ERROR",
792
+ logger_js_1.logger?.error('Fatal error while running MCP proxy server', {
793
+ type: 'SERVER_FATAL_ERROR',
657
794
  error: error instanceof Error ? error.message : String(error),
658
795
  });
659
796
  // Always write fatal errors to stderr (even in non-verbose mode, as these are critical)
660
797
  // But skip in test environment
661
- const isTestEnv = process.env.NODE_ENV === "test" ||
798
+ const isTestEnv = process.env.NODE_ENV === 'test' ||
662
799
  process.env.JEST_WORKER_ID !== undefined ||
663
- typeof globalThis.jest !== "undefined";
800
+ typeof globalThis.jest !== 'undefined';
664
801
  if (!isTestEnv) {
665
802
  process.stderr.write(`[MCP Proxy] ✗ Fatal error: ${error instanceof Error ? error.message : String(error)}\n`);
666
803
  }