@mcp-abap-adt/proxy 0.1.9 → 0.2.0
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.
- package/bin/mcp-abap-adt-proxy.js +30 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -276
- package/dist/lib/config.d.ts +18 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +33 -0
- package/dist/lib/stores.d.ts +14 -0
- package/dist/lib/stores.d.ts.map +1 -1
- package/dist/lib/stores.js +1 -0
- package/dist/lib/transportConfig.d.ts +15 -2
- package/dist/lib/transportConfig.d.ts.map +1 -1
- package/dist/lib/transportConfig.js +41 -33
- package/dist/proxy/btpProxy.d.ts +6 -1
- package/dist/proxy/btpProxy.d.ts.map +1 -1
- package/dist/proxy/btpProxy.js +16 -0
- package/dist/proxy/reverseProxy.d.ts +7 -0
- package/dist/proxy/reverseProxy.d.ts.map +1 -0
- package/dist/proxy/reverseProxy.js +130 -0
- package/dist/router/requestInterceptor.d.ts +2 -0
- package/dist/router/requestInterceptor.d.ts.map +1 -1
- package/dist/router/requestInterceptor.js +17 -15
- package/dist/tui/index.d.ts +8 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +11 -0
- package/dist/tui/serviceKeyCheck.d.ts +25 -0
- package/dist/tui/serviceKeyCheck.d.ts.map +1 -0
- package/dist/tui/serviceKeyCheck.js +62 -0
- package/dist/tui/wizard.d.ts +38 -0
- package/dist/tui/wizard.d.ts.map +1 -0
- package/dist/tui/wizard.js +321 -0
- package/package.json +4 -3
|
@@ -5,33 +5,32 @@
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.parseTransportConfig = parseTransportConfig;
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Normalize transport value to TransportType
|
|
9
9
|
*/
|
|
10
|
-
function
|
|
10
|
+
function normalizeTransport(value) {
|
|
11
|
+
const v = value.toLowerCase();
|
|
12
|
+
if (v === 'stdio' || v === 'streamable-http' || v === 'http' || v === 'sse') {
|
|
13
|
+
return v === 'http' ? 'streamable-http' : v;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse transport configuration from command line arguments, environment variables,
|
|
19
|
+
* and optional file-based config overrides.
|
|
20
|
+
*
|
|
21
|
+
* Priority: CLI args > file config > env vars > auto-detect > defaults
|
|
22
|
+
*/
|
|
23
|
+
function parseTransportConfig(fileOverrides) {
|
|
11
24
|
const args = process.argv;
|
|
12
|
-
// Check for explicit transport type
|
|
25
|
+
// Check for explicit transport type from CLI
|
|
13
26
|
let transportType = null;
|
|
14
27
|
for (let i = 0; i < args.length; i++) {
|
|
15
28
|
const arg = args[i];
|
|
16
29
|
if (arg.startsWith('--transport=')) {
|
|
17
|
-
|
|
18
|
-
if (value === 'stdio' ||
|
|
19
|
-
value === 'streamable-http' ||
|
|
20
|
-
value === 'http' ||
|
|
21
|
-
value === 'sse') {
|
|
22
|
-
transportType =
|
|
23
|
-
value === 'http' ? 'streamable-http' : value;
|
|
24
|
-
}
|
|
30
|
+
transportType = normalizeTransport(arg.split('=')[1]);
|
|
25
31
|
}
|
|
26
32
|
if (arg === '--transport' && i + 1 < args.length) {
|
|
27
|
-
|
|
28
|
-
if (value === 'stdio' ||
|
|
29
|
-
value === 'streamable-http' ||
|
|
30
|
-
value === 'http' ||
|
|
31
|
-
value === 'sse') {
|
|
32
|
-
transportType =
|
|
33
|
-
value === 'http' ? 'streamable-http' : value;
|
|
34
|
-
}
|
|
33
|
+
transportType = normalizeTransport(args[i + 1]);
|
|
35
34
|
}
|
|
36
35
|
if (arg === '--http')
|
|
37
36
|
transportType = 'streamable-http';
|
|
@@ -40,18 +39,13 @@ function parseTransportConfig() {
|
|
|
40
39
|
if (arg === '--sse')
|
|
41
40
|
transportType = 'sse';
|
|
42
41
|
}
|
|
42
|
+
// Check file config overrides
|
|
43
|
+
if (!transportType && fileOverrides?.transport) {
|
|
44
|
+
transportType = normalizeTransport(fileOverrides.transport);
|
|
45
|
+
}
|
|
43
46
|
// Check environment variable
|
|
44
47
|
if (!transportType && process.env.MCP_TRANSPORT) {
|
|
45
|
-
|
|
46
|
-
if (envTransport === 'stdio' ||
|
|
47
|
-
envTransport === 'streamable-http' ||
|
|
48
|
-
envTransport === 'http' ||
|
|
49
|
-
envTransport === 'sse') {
|
|
50
|
-
transportType =
|
|
51
|
-
envTransport === 'http'
|
|
52
|
-
? 'streamable-http'
|
|
53
|
-
: envTransport;
|
|
54
|
-
}
|
|
48
|
+
transportType = normalizeTransport(process.env.MCP_TRANSPORT);
|
|
55
49
|
}
|
|
56
50
|
// Auto-detect stdio mode: if stdin is not a TTY, we're likely in stdio mode
|
|
57
51
|
if (!transportType && !process.stdin.isTTY) {
|
|
@@ -63,8 +57,14 @@ function parseTransportConfig() {
|
|
|
63
57
|
}
|
|
64
58
|
// Parse SSE config
|
|
65
59
|
if (transportType === 'sse') {
|
|
66
|
-
const port = parseInt(getArgValue('--sse-port') ||
|
|
67
|
-
|
|
60
|
+
const port = parseInt(getArgValue('--sse-port') ||
|
|
61
|
+
(fileOverrides?.ssePort != null ? String(fileOverrides.ssePort) : '') ||
|
|
62
|
+
process.env.MCP_SSE_PORT ||
|
|
63
|
+
'3002', 10);
|
|
64
|
+
const host = getArgValue('--sse-host') ||
|
|
65
|
+
fileOverrides?.sseHost ||
|
|
66
|
+
process.env.MCP_SSE_HOST ||
|
|
67
|
+
'0.0.0.0';
|
|
68
68
|
return {
|
|
69
69
|
type: 'sse',
|
|
70
70
|
host,
|
|
@@ -76,8 +76,16 @@ function parseTransportConfig() {
|
|
|
76
76
|
}
|
|
77
77
|
// Parse HTTP/streamable-http config
|
|
78
78
|
if (transportType === 'streamable-http') {
|
|
79
|
-
const port = parseInt(getArgValue('--http-port') ||
|
|
80
|
-
|
|
79
|
+
const port = parseInt(getArgValue('--http-port') ||
|
|
80
|
+
(fileOverrides?.httpPort != null
|
|
81
|
+
? String(fileOverrides.httpPort)
|
|
82
|
+
: '') ||
|
|
83
|
+
process.env.MCP_HTTP_PORT ||
|
|
84
|
+
'3001', 10);
|
|
85
|
+
const host = getArgValue('--http-host') ||
|
|
86
|
+
fileOverrides?.httpHost ||
|
|
87
|
+
process.env.MCP_HTTP_HOST ||
|
|
88
|
+
'0.0.0.0';
|
|
81
89
|
return {
|
|
82
90
|
type: 'streamable-http',
|
|
83
91
|
host,
|
package/dist/proxy/btpProxy.d.ts
CHANGED
|
@@ -63,7 +63,12 @@ export declare class BtpProxy {
|
|
|
63
63
|
* @param destination Destination name
|
|
64
64
|
* @param forceRefresh Force token refresh
|
|
65
65
|
*/
|
|
66
|
-
|
|
66
|
+
getJwtToken(destination: string, forceRefresh?: boolean): Promise<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Get the target service URL for a destination.
|
|
69
|
+
* Priority: config.targetUrl > service key's serviceUrl
|
|
70
|
+
*/
|
|
71
|
+
getTargetUrl(destination: string): Promise<string>;
|
|
67
72
|
/**
|
|
68
73
|
* Helper function to extract string value from header (handles arrays)
|
|
69
74
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"btpProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/btpProxy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAgB,MAAM,2BAA2B,CAAC;AAYrE,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA4CnE;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAU3C;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAGD,MAAM,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,oBAAoB,CAAa;IACzC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,UAAU,CACN;IACZ,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAU;gBAEZ,oBAAoB,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IAqF3E;;;OAGG;IACU,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB3D;;;;OAIG;YACW,wBAAwB;IAqFtC;;OAEG;YACW,uBAAuB;IAyFrC;;;;OAIG;
|
|
1
|
+
{"version":3,"file":"btpProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/btpProxy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAgB,MAAM,2BAA2B,CAAC;AAYrE,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA4CnE;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAU3C;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAGD,MAAM,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,oBAAoB,CAAa;IACzC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,UAAU,CACN;IACZ,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAU;gBAEZ,oBAAoB,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IAqF3E;;;OAGG;IACU,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB3D;;;;OAIG;YACW,wBAAwB;IAqFtC;;OAEG;YACW,uBAAuB;IAyFrC;;;;OAIG;IACG,WAAW,CACf,WAAW,EAAE,MAAM,EACnB,YAAY,GAAE,OAAe,GAC5B,OAAO,CAAC,MAAM,CAAC;IAqHlB;;;OAGG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAexD;;OAEG;IACH,OAAO,CAAC,cAAc;IAUtB;;;;;;;;;;;;;;;;OAgBG;YACW,iBAAiB;IAmT/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;IA8JzB;;OAEG;WACiB,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;CAyB7E;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,QAAQ,CAAC,CAEnB"}
|
package/dist/proxy/btpProxy.js
CHANGED
|
@@ -404,6 +404,22 @@ class BtpProxy {
|
|
|
404
404
|
throw new Error(errorMessage);
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* Get the target service URL for a destination.
|
|
409
|
+
* Priority: config.targetUrl > service key's serviceUrl
|
|
410
|
+
*/
|
|
411
|
+
async getTargetUrl(destination) {
|
|
412
|
+
if (this.config.targetUrl) {
|
|
413
|
+
return this.config.targetUrl;
|
|
414
|
+
}
|
|
415
|
+
const broker = await this.getOrCreateBtpAuthBroker(destination);
|
|
416
|
+
const connConfig = await broker.getConnectionConfig(destination);
|
|
417
|
+
if (connConfig?.serviceUrl) {
|
|
418
|
+
return connConfig.serviceUrl;
|
|
419
|
+
}
|
|
420
|
+
throw new Error(`No target URL found for destination "${destination}". ` +
|
|
421
|
+
`Set targetUrl in config or ensure service key contains abap.url.`);
|
|
422
|
+
}
|
|
407
423
|
/**
|
|
408
424
|
* Helper function to extract string value from header (handles arrays)
|
|
409
425
|
*/
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as http from 'node:http';
|
|
2
|
+
/**
|
|
3
|
+
* Forward an HTTP request to a target URL with JWT injection.
|
|
4
|
+
* Streams both request and response using pipe().
|
|
5
|
+
*/
|
|
6
|
+
export declare function forwardRequest(clientReq: http.IncomingMessage, clientRes: http.ServerResponse, targetBaseUrl: string, jwtToken: string): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=reverseProxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reverseProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/reverseProxy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAoBlC;;;GAGG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,IAAI,CAAC,eAAe,EAC/B,SAAS,EAAE,IAAI,CAAC,cAAc,EAC9B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CA+Ef"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.forwardRequest = forwardRequest;
|
|
37
|
+
// src/proxy/reverseProxy.ts
|
|
38
|
+
const http = __importStar(require("node:http"));
|
|
39
|
+
const https = __importStar(require("node:https"));
|
|
40
|
+
const node_url_1 = require("node:url");
|
|
41
|
+
const logger_js_1 = require("../lib/logger.js");
|
|
42
|
+
/**
|
|
43
|
+
* Hop-by-hop headers that should not be forwarded
|
|
44
|
+
*/
|
|
45
|
+
const HOP_BY_HOP_HEADERS = new Set([
|
|
46
|
+
'connection',
|
|
47
|
+
'keep-alive',
|
|
48
|
+
'proxy-authenticate',
|
|
49
|
+
'proxy-authorization',
|
|
50
|
+
'te',
|
|
51
|
+
'trailers',
|
|
52
|
+
'transfer-encoding',
|
|
53
|
+
'upgrade',
|
|
54
|
+
'host',
|
|
55
|
+
]);
|
|
56
|
+
/**
|
|
57
|
+
* Forward an HTTP request to a target URL with JWT injection.
|
|
58
|
+
* Streams both request and response using pipe().
|
|
59
|
+
*/
|
|
60
|
+
async function forwardRequest(clientReq, clientRes, targetBaseUrl, jwtToken) {
|
|
61
|
+
const targetUrl = new node_url_1.URL(clientReq.url || '/', targetBaseUrl);
|
|
62
|
+
// Build forwarded headers (skip hop-by-hop and replace auth)
|
|
63
|
+
const forwardedHeaders = {};
|
|
64
|
+
for (const [key, value] of Object.entries(clientReq.headers)) {
|
|
65
|
+
if (HOP_BY_HOP_HEADERS.has(key.toLowerCase()))
|
|
66
|
+
continue;
|
|
67
|
+
if (key.toLowerCase() === 'authorization')
|
|
68
|
+
continue;
|
|
69
|
+
if (value !== undefined) {
|
|
70
|
+
forwardedHeaders[key] = value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Inject JWT
|
|
74
|
+
if (jwtToken) {
|
|
75
|
+
forwardedHeaders.authorization = `Bearer ${jwtToken}`;
|
|
76
|
+
}
|
|
77
|
+
// Set correct host for target
|
|
78
|
+
forwardedHeaders.host = targetUrl.host;
|
|
79
|
+
const isHttps = targetUrl.protocol === 'https:';
|
|
80
|
+
const transport = isHttps ? https : http;
|
|
81
|
+
const options = {
|
|
82
|
+
hostname: targetUrl.hostname,
|
|
83
|
+
port: targetUrl.port || (isHttps ? 443 : 80),
|
|
84
|
+
path: targetUrl.pathname + targetUrl.search,
|
|
85
|
+
method: clientReq.method || 'GET',
|
|
86
|
+
headers: forwardedHeaders,
|
|
87
|
+
};
|
|
88
|
+
logger_js_1.logger?.info('Forwarding request', {
|
|
89
|
+
type: 'REVERSE_PROXY_FORWARD',
|
|
90
|
+
method: options.method,
|
|
91
|
+
target: targetUrl.toString(),
|
|
92
|
+
});
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
const proxyReq = transport.request(options, (proxyRes) => {
|
|
95
|
+
// Forward status code
|
|
96
|
+
const statusCode = proxyRes.statusCode || 502;
|
|
97
|
+
// Forward response headers (skip hop-by-hop)
|
|
98
|
+
const responseHeaders = {};
|
|
99
|
+
for (const [key, value] of Object.entries(proxyRes.headers)) {
|
|
100
|
+
if (HOP_BY_HOP_HEADERS.has(key.toLowerCase()))
|
|
101
|
+
continue;
|
|
102
|
+
if (value !== undefined) {
|
|
103
|
+
responseHeaders[key] = value;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
logger_js_1.logger?.info('Backend responded', {
|
|
107
|
+
type: 'REVERSE_PROXY_RESPONSE',
|
|
108
|
+
statusCode,
|
|
109
|
+
contentType: proxyRes.headers['content-type'],
|
|
110
|
+
});
|
|
111
|
+
clientRes.writeHead(statusCode, responseHeaders);
|
|
112
|
+
proxyRes.pipe(clientRes);
|
|
113
|
+
proxyRes.on('end', resolve);
|
|
114
|
+
});
|
|
115
|
+
proxyReq.on('error', (err) => {
|
|
116
|
+
logger_js_1.logger?.error('Reverse proxy connection error', {
|
|
117
|
+
type: 'REVERSE_PROXY_ERROR',
|
|
118
|
+
error: err.message,
|
|
119
|
+
target: targetUrl.toString(),
|
|
120
|
+
});
|
|
121
|
+
if (!clientRes.headersSent) {
|
|
122
|
+
clientRes.writeHead(502, { 'Content-Type': 'application/json' });
|
|
123
|
+
clientRes.end(JSON.stringify({ error: `Proxy error: ${err.message}` }));
|
|
124
|
+
}
|
|
125
|
+
resolve();
|
|
126
|
+
});
|
|
127
|
+
// Pipe client request body to backend
|
|
128
|
+
clientReq.pipe(proxyReq);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
@@ -18,6 +18,8 @@ export interface InterceptedRequest {
|
|
|
18
18
|
export declare function interceptRequest(req: IncomingMessage, body?: unknown, configOverrides?: {
|
|
19
19
|
btpDestination?: string;
|
|
20
20
|
targetUrl?: string;
|
|
21
|
+
}, options?: {
|
|
22
|
+
skipHeaderValidation?: boolean;
|
|
21
23
|
}): InterceptedRequest;
|
|
22
24
|
/**
|
|
23
25
|
* Sanitize headers for logging (remove sensitive data)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"requestInterceptor.d.ts","sourceRoot":"","sources":["../../src/router/requestInterceptor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAajD,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3E,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,eAAe,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,eAAe,EACpB,IAAI,CAAC,EAAE,OAAO,EACd,eAAe,CAAC,EAAE;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,kBAAkB,
|
|
1
|
+
{"version":3,"file":"requestInterceptor.d.ts","sourceRoot":"","sources":["../../src/router/requestInterceptor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAajD,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3E,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,eAAe,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,eAAe,EACpB,IAAI,CAAC,EAAE,OAAO,EACd,eAAe,CAAC,EAAE;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,EACD,OAAO,CAAC,EAAE;IACR,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,GACA,kBAAkB,CAuDpB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GACrD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBxB"}
|
|
@@ -12,7 +12,7 @@ const headerAnalyzer_js_1 = require("./headerAnalyzer.js");
|
|
|
12
12
|
/**
|
|
13
13
|
* Intercept and analyze incoming HTTP request
|
|
14
14
|
*/
|
|
15
|
-
function interceptRequest(req, body, configOverrides) {
|
|
15
|
+
function interceptRequest(req, body, configOverrides, options) {
|
|
16
16
|
// Extract headers
|
|
17
17
|
const headers = {};
|
|
18
18
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
@@ -20,20 +20,22 @@ function interceptRequest(req, body, configOverrides) {
|
|
|
20
20
|
}
|
|
21
21
|
// Analyze headers for routing decision (with config overrides)
|
|
22
22
|
const routingDecision = (0, headerAnalyzer_js_1.analyzeHeaders)(req.headers, configOverrides);
|
|
23
|
-
// Validate proxy headers
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
23
|
+
// Validate proxy headers (skip for reverse proxy mode where config provides destination)
|
|
24
|
+
if (!options?.skipHeaderValidation) {
|
|
25
|
+
const validation = (0, header_validator_1.validateProxyHeaders)(req.headers);
|
|
26
|
+
if (!validation.isValid && validation.errors.length > 0) {
|
|
27
|
+
logger_js_1.logger?.warn('Proxy header validation failed', {
|
|
28
|
+
type: 'PROXY_HEADER_VALIDATION_ERROR',
|
|
29
|
+
errors: validation.errors,
|
|
30
|
+
warnings: validation.warnings,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
else if (validation.warnings.length > 0) {
|
|
34
|
+
logger_js_1.logger?.warn('Proxy header validation warnings', {
|
|
35
|
+
type: 'PROXY_HEADER_VALIDATION_WARNINGS',
|
|
36
|
+
warnings: validation.warnings,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
37
39
|
}
|
|
38
40
|
// Extract session ID if present
|
|
39
41
|
const sessionId = (req.headers[interfaces_1.HEADER_SESSION_ID] ||
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI module barrel export
|
|
3
|
+
*/
|
|
4
|
+
export type { ServiceKeyCheckResult } from './serviceKeyCheck.js';
|
|
5
|
+
export { checkServiceKeyExists } from './serviceKeyCheck.js';
|
|
6
|
+
export type { WizardAnswers } from './wizard.js';
|
|
7
|
+
export { generateConfigYaml, runWizard } from './wizard.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tui/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TUI module barrel export
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runWizard = exports.generateConfigYaml = exports.checkServiceKeyExists = void 0;
|
|
7
|
+
var serviceKeyCheck_js_1 = require("./serviceKeyCheck.js");
|
|
8
|
+
Object.defineProperty(exports, "checkServiceKeyExists", { enumerable: true, get: function () { return serviceKeyCheck_js_1.checkServiceKeyExists; } });
|
|
9
|
+
var wizard_js_1 = require("./wizard.js");
|
|
10
|
+
Object.defineProperty(exports, "generateConfigYaml", { enumerable: true, get: function () { return wizard_js_1.generateConfigYaml; } });
|
|
11
|
+
Object.defineProperty(exports, "runWizard", { enumerable: true, get: function () { return wizard_js_1.runWizard; } });
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service key existence checker
|
|
3
|
+
* Checks if a service key file exists for a given BTP destination
|
|
4
|
+
* by searching platform-specific directories.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Result of a service key existence check
|
|
8
|
+
*/
|
|
9
|
+
export interface ServiceKeyCheckResult {
|
|
10
|
+
/** Whether the service key file was found */
|
|
11
|
+
found: boolean;
|
|
12
|
+
/** Absolute path to the found service key file (if found) */
|
|
13
|
+
path?: string;
|
|
14
|
+
/** Directories that were searched (when not found) */
|
|
15
|
+
searchedPaths?: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Check if a service key file exists for the given BTP destination.
|
|
19
|
+
* Searches all platform-specific directories returned by getPlatformPaths('service-keys').
|
|
20
|
+
*
|
|
21
|
+
* @param destination - BTP destination name (without .json extension)
|
|
22
|
+
* @returns ServiceKeyCheckResult indicating whether the key was found and where
|
|
23
|
+
*/
|
|
24
|
+
export declare function checkServiceKeyExists(destination: string): ServiceKeyCheckResult;
|
|
25
|
+
//# sourceMappingURL=serviceKeyCheck.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceKeyCheck.d.ts","sourceRoot":"","sources":["../../src/tui/serviceKeyCheck.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,6CAA6C;IAC7C,KAAK,EAAE,OAAO,CAAC;IACf,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,GAClB,qBAAqB,CAYvB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Service key existence checker
|
|
4
|
+
* Checks if a service key file exists for a given BTP destination
|
|
5
|
+
* by searching platform-specific directories.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.checkServiceKeyExists = checkServiceKeyExists;
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const stores_js_1 = require("../lib/stores.js");
|
|
45
|
+
/**
|
|
46
|
+
* Check if a service key file exists for the given BTP destination.
|
|
47
|
+
* Searches all platform-specific directories returned by getPlatformPaths('service-keys').
|
|
48
|
+
*
|
|
49
|
+
* @param destination - BTP destination name (without .json extension)
|
|
50
|
+
* @returns ServiceKeyCheckResult indicating whether the key was found and where
|
|
51
|
+
*/
|
|
52
|
+
function checkServiceKeyExists(destination) {
|
|
53
|
+
const searchDirs = (0, stores_js_1.getPlatformPaths)('service-keys');
|
|
54
|
+
const fileName = `${destination}.json`;
|
|
55
|
+
for (const dir of searchDirs) {
|
|
56
|
+
const filePath = path.join(dir, fileName);
|
|
57
|
+
if (fs.existsSync(filePath)) {
|
|
58
|
+
return { found: true, path: filePath };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { found: false, searchedPaths: searchDirs };
|
|
62
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI wizard for interactive proxy configuration
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Answers collected by the wizard
|
|
6
|
+
*/
|
|
7
|
+
export interface WizardAnswers {
|
|
8
|
+
scenario: 'btp' | 'direct';
|
|
9
|
+
btpDestination?: string;
|
|
10
|
+
targetUrl?: string;
|
|
11
|
+
mcpDestination?: string;
|
|
12
|
+
mcpUrl?: string;
|
|
13
|
+
transport: 'stdio' | 'http' | 'sse';
|
|
14
|
+
httpHost?: string;
|
|
15
|
+
httpPort?: number;
|
|
16
|
+
sseHost?: string;
|
|
17
|
+
ssePort?: number;
|
|
18
|
+
browser: 'system' | 'headless' | 'chrome' | 'edge' | 'firefox' | 'none';
|
|
19
|
+
browserAuthPort: number;
|
|
20
|
+
unsafe: boolean;
|
|
21
|
+
logLevel: string;
|
|
22
|
+
maxRetries?: number;
|
|
23
|
+
retryDelay?: number;
|
|
24
|
+
requestTimeout?: number;
|
|
25
|
+
circuitBreakerThreshold?: number;
|
|
26
|
+
circuitBreakerTimeout?: number;
|
|
27
|
+
cloudLlmHubUrl?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generate a YAML configuration string from wizard answers.
|
|
31
|
+
* The output follows the structure of docs/mcp-proxy-config.example.yaml.
|
|
32
|
+
*/
|
|
33
|
+
export declare function generateConfigYaml(answers: WizardAnswers): string;
|
|
34
|
+
/**
|
|
35
|
+
* Run the interactive configuration wizard
|
|
36
|
+
*/
|
|
37
|
+
export declare function runWizard(): Promise<WizardAnswers>;
|
|
38
|
+
//# sourceMappingURL=wizard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wizard.d.ts","sourceRoot":"","sources":["../../src/tui/wizard.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IACxE,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,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;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAWD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CA+FjE;AAeD;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,aAAa,CAAC,CAwMxD"}
|