@mcp-abap-adt/proxy 0.1.9 → 1.0.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/CHANGELOG.md +35 -7
- package/README.md +9 -9
- package/bin/mcp-abap-adt-proxy.js +32 -2
- 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 +34 -1
- 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 +8 -3
- package/dist/proxy/btpProxy.d.ts.map +1 -1
- package/dist/proxy/btpProxy.js +21 -5
- 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/headerAnalyzer.d.ts +3 -3
- package/dist/router/headerAnalyzer.js +7 -7
- 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
|
@@ -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"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TUI wizard for interactive proxy configuration
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.generateConfigYaml = generateConfigYaml;
|
|
40
|
+
exports.runWizard = runWizard;
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const serviceKeyCheck_js_1 = require("./serviceKeyCheck.js");
|
|
43
|
+
/** Default values for advanced settings */
|
|
44
|
+
const DEFAULTS = {
|
|
45
|
+
maxRetries: 3,
|
|
46
|
+
retryDelay: 1000,
|
|
47
|
+
requestTimeout: 60000,
|
|
48
|
+
circuitBreakerThreshold: 5,
|
|
49
|
+
circuitBreakerTimeout: 60000,
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Generate a YAML configuration string from wizard answers.
|
|
53
|
+
* The output follows the structure of docs/mcp-proxy-config.example.yaml.
|
|
54
|
+
*/
|
|
55
|
+
function generateConfigYaml(answers) {
|
|
56
|
+
const lines = [];
|
|
57
|
+
lines.push('# MCP ABAP ADT Proxy Configuration');
|
|
58
|
+
lines.push('');
|
|
59
|
+
// Transport configuration
|
|
60
|
+
lines.push('# Transport configuration');
|
|
61
|
+
lines.push(`transport: ${answers.transport} # stdio | http | sse`);
|
|
62
|
+
if (answers.transport === 'http' || answers.transport === 'sse') {
|
|
63
|
+
lines.push(`httpPort: ${answers.httpPort ?? 3001}`);
|
|
64
|
+
lines.push(`httpHost: "${answers.httpHost ?? '0.0.0.0'}"`);
|
|
65
|
+
}
|
|
66
|
+
if (answers.transport === 'sse') {
|
|
67
|
+
lines.push(`ssePort: ${answers.ssePort ?? 3002}`);
|
|
68
|
+
lines.push(`sseHost: "${answers.sseHost ?? '0.0.0.0'}"`);
|
|
69
|
+
}
|
|
70
|
+
lines.push('');
|
|
71
|
+
// Destination overrides
|
|
72
|
+
lines.push('# Destination overrides');
|
|
73
|
+
if (answers.scenario === 'btp') {
|
|
74
|
+
if (answers.btpDestination) {
|
|
75
|
+
lines.push(`btpDestination: "${answers.btpDestination}"`);
|
|
76
|
+
}
|
|
77
|
+
if (answers.targetUrl) {
|
|
78
|
+
lines.push(`targetUrl: "${answers.targetUrl}"`);
|
|
79
|
+
}
|
|
80
|
+
if (answers.mcpDestination) {
|
|
81
|
+
lines.push(`mcpDestination: "${answers.mcpDestination}"`);
|
|
82
|
+
}
|
|
83
|
+
if (answers.mcpUrl) {
|
|
84
|
+
lines.push(`# mcpUrl: "${answers.mcpUrl}"`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
lines.push('# mcpUrl:');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
lines.push('# btpDestination:');
|
|
92
|
+
lines.push('# mcpDestination:');
|
|
93
|
+
if (answers.mcpUrl) {
|
|
94
|
+
lines.push(`mcpUrl: "${answers.mcpUrl}"`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
lines.push('');
|
|
98
|
+
// Browser authentication
|
|
99
|
+
lines.push('# Browser authentication');
|
|
100
|
+
lines.push(`browser: "${answers.browser}"`);
|
|
101
|
+
lines.push(`browserAuthPort: ${answers.browserAuthPort}`);
|
|
102
|
+
lines.push('');
|
|
103
|
+
// Session storage mode
|
|
104
|
+
lines.push('# Session storage mode');
|
|
105
|
+
lines.push(`unsafe: ${answers.unsafe} # If true, persists tokens to disk`);
|
|
106
|
+
lines.push('');
|
|
107
|
+
// Error handling & resilience
|
|
108
|
+
lines.push('# Error handling & resilience');
|
|
109
|
+
lines.push(`maxRetries: ${answers.maxRetries ?? DEFAULTS.maxRetries}`);
|
|
110
|
+
lines.push(`retryDelay: ${answers.retryDelay ?? DEFAULTS.retryDelay} # milliseconds`);
|
|
111
|
+
lines.push(`requestTimeout: ${answers.requestTimeout ?? DEFAULTS.requestTimeout} # milliseconds`);
|
|
112
|
+
lines.push(`circuitBreakerThreshold: ${answers.circuitBreakerThreshold ?? DEFAULTS.circuitBreakerThreshold}`);
|
|
113
|
+
lines.push(`circuitBreakerTimeout: ${answers.circuitBreakerTimeout ?? DEFAULTS.circuitBreakerTimeout} # milliseconds`);
|
|
114
|
+
lines.push('');
|
|
115
|
+
// Logging
|
|
116
|
+
lines.push('# Logging');
|
|
117
|
+
lines.push(`logLevel: "${answers.logLevel}" # debug | info | warn | error`);
|
|
118
|
+
// Cloud LLM Hub URL
|
|
119
|
+
if (answers.cloudLlmHubUrl) {
|
|
120
|
+
lines.push('');
|
|
121
|
+
lines.push('# Cloud LLM Hub URL');
|
|
122
|
+
lines.push(`cloudLlmHubUrl: "${answers.cloudLlmHubUrl}"`);
|
|
123
|
+
}
|
|
124
|
+
lines.push('');
|
|
125
|
+
return lines.join('\n');
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Print a service key check result to the console.
|
|
129
|
+
*/
|
|
130
|
+
function printServiceKeyCheck(destination) {
|
|
131
|
+
const result = (0, serviceKeyCheck_js_1.checkServiceKeyExists)(destination);
|
|
132
|
+
if (result.found) {
|
|
133
|
+
console.log(` Found: ${result.path}`);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
console.log(` Warning: Service key file not found for "${destination}".`);
|
|
137
|
+
console.log(` Searched: ${result.searchedPaths?.join(', ') ?? '(none)'}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Run the interactive configuration wizard
|
|
142
|
+
*/
|
|
143
|
+
async function runWizard() {
|
|
144
|
+
try {
|
|
145
|
+
const { select, input, confirm, number } = await import('@inquirer/prompts');
|
|
146
|
+
console.log('\n MCP ABAP ADT Proxy — Configuration Wizard\n');
|
|
147
|
+
// 1. Scenario
|
|
148
|
+
const scenario = await select({
|
|
149
|
+
message: 'Select scenario:',
|
|
150
|
+
choices: [
|
|
151
|
+
{
|
|
152
|
+
name: 'BTP service (XSUAA auth via service key)',
|
|
153
|
+
value: 'btp',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'Direct MCP server URL (no auth)',
|
|
157
|
+
value: 'direct',
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
});
|
|
161
|
+
const answers = { scenario };
|
|
162
|
+
// 2. Scenario-specific questions
|
|
163
|
+
if (scenario === 'btp') {
|
|
164
|
+
const btpDestination = await input({
|
|
165
|
+
message: 'BTP destination name:',
|
|
166
|
+
required: true,
|
|
167
|
+
});
|
|
168
|
+
answers.btpDestination = btpDestination;
|
|
169
|
+
printServiceKeyCheck(btpDestination);
|
|
170
|
+
const targetUrl = await input({
|
|
171
|
+
message: 'Target service URL (leave empty if in service key):',
|
|
172
|
+
default: '',
|
|
173
|
+
});
|
|
174
|
+
if (targetUrl) {
|
|
175
|
+
answers.targetUrl = targetUrl;
|
|
176
|
+
}
|
|
177
|
+
const addMcpDest = await confirm({
|
|
178
|
+
message: 'Add MCP destination (for ABAP connection params on Cloud)?',
|
|
179
|
+
default: false,
|
|
180
|
+
});
|
|
181
|
+
if (addMcpDest) {
|
|
182
|
+
const mcpDestination = await input({
|
|
183
|
+
message: 'MCP destination name:',
|
|
184
|
+
required: true,
|
|
185
|
+
});
|
|
186
|
+
answers.mcpDestination = mcpDestination;
|
|
187
|
+
printServiceKeyCheck(mcpDestination);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const mcpUrl = await input({
|
|
192
|
+
message: 'MCP server URL:',
|
|
193
|
+
required: true,
|
|
194
|
+
});
|
|
195
|
+
answers.mcpUrl = mcpUrl;
|
|
196
|
+
}
|
|
197
|
+
// 3. Transport
|
|
198
|
+
const transport = await select({
|
|
199
|
+
message: 'Select transport:',
|
|
200
|
+
choices: [
|
|
201
|
+
{ name: 'stdio', value: 'stdio' },
|
|
202
|
+
{ name: 'streamable-http', value: 'http' },
|
|
203
|
+
{ name: 'sse', value: 'sse' },
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
answers.transport = transport;
|
|
207
|
+
// 4. Transport-specific settings
|
|
208
|
+
if (transport !== 'stdio') {
|
|
209
|
+
answers.httpHost = await input({
|
|
210
|
+
message: 'HTTP host:',
|
|
211
|
+
default: '0.0.0.0',
|
|
212
|
+
});
|
|
213
|
+
answers.httpPort = await number({
|
|
214
|
+
message: 'HTTP port:',
|
|
215
|
+
default: 3001,
|
|
216
|
+
min: 1,
|
|
217
|
+
max: 65535,
|
|
218
|
+
});
|
|
219
|
+
if (transport === 'sse') {
|
|
220
|
+
answers.sseHost = await input({
|
|
221
|
+
message: 'SSE host:',
|
|
222
|
+
default: '0.0.0.0',
|
|
223
|
+
});
|
|
224
|
+
answers.ssePort = await number({
|
|
225
|
+
message: 'SSE port:',
|
|
226
|
+
default: 3002,
|
|
227
|
+
min: 1,
|
|
228
|
+
max: 65535,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// 5. Browser
|
|
233
|
+
answers.browser = await select({
|
|
234
|
+
message: 'Select browser for OAuth2 login:',
|
|
235
|
+
choices: [
|
|
236
|
+
{ name: 'system (default browser)', value: 'system' },
|
|
237
|
+
{ name: 'chrome', value: 'chrome' },
|
|
238
|
+
{ name: 'edge', value: 'edge' },
|
|
239
|
+
{ name: 'firefox', value: 'firefox' },
|
|
240
|
+
{ name: 'headless', value: 'headless' },
|
|
241
|
+
{ name: 'none (disable)', value: 'none' },
|
|
242
|
+
],
|
|
243
|
+
});
|
|
244
|
+
// 6. OAuth2 callback port
|
|
245
|
+
answers.browserAuthPort = await number({
|
|
246
|
+
message: 'OAuth2 callback port:',
|
|
247
|
+
default: 3333,
|
|
248
|
+
});
|
|
249
|
+
// 7. Unsafe mode
|
|
250
|
+
answers.unsafe = await confirm({
|
|
251
|
+
message: 'Unsafe mode (persist tokens to disk)?',
|
|
252
|
+
default: false,
|
|
253
|
+
});
|
|
254
|
+
// 8. Log level
|
|
255
|
+
answers.logLevel = await select({
|
|
256
|
+
message: 'Log level:',
|
|
257
|
+
choices: [
|
|
258
|
+
{ name: 'debug', value: 'debug' },
|
|
259
|
+
{ name: 'info', value: 'info' },
|
|
260
|
+
{ name: 'warn', value: 'warn' },
|
|
261
|
+
{ name: 'error', value: 'error' },
|
|
262
|
+
],
|
|
263
|
+
default: 'info',
|
|
264
|
+
});
|
|
265
|
+
// 9. Advanced settings
|
|
266
|
+
const configureAdvanced = await confirm({
|
|
267
|
+
message: 'Configure advanced settings?',
|
|
268
|
+
default: false,
|
|
269
|
+
});
|
|
270
|
+
if (configureAdvanced) {
|
|
271
|
+
answers.maxRetries = await number({
|
|
272
|
+
message: 'Max retries:',
|
|
273
|
+
default: DEFAULTS.maxRetries,
|
|
274
|
+
});
|
|
275
|
+
answers.retryDelay = await number({
|
|
276
|
+
message: 'Retry delay (ms):',
|
|
277
|
+
default: DEFAULTS.retryDelay,
|
|
278
|
+
});
|
|
279
|
+
answers.requestTimeout = await number({
|
|
280
|
+
message: 'Request timeout (ms):',
|
|
281
|
+
default: DEFAULTS.requestTimeout,
|
|
282
|
+
});
|
|
283
|
+
answers.circuitBreakerThreshold = await number({
|
|
284
|
+
message: 'Circuit breaker threshold:',
|
|
285
|
+
default: DEFAULTS.circuitBreakerThreshold,
|
|
286
|
+
});
|
|
287
|
+
answers.circuitBreakerTimeout = await number({
|
|
288
|
+
message: 'Circuit breaker timeout (ms):',
|
|
289
|
+
default: DEFAULTS.circuitBreakerTimeout,
|
|
290
|
+
});
|
|
291
|
+
const setCloudUrl = await confirm({
|
|
292
|
+
message: 'Set Cloud LLM Hub URL?',
|
|
293
|
+
default: false,
|
|
294
|
+
});
|
|
295
|
+
if (setCloudUrl) {
|
|
296
|
+
answers.cloudLlmHubUrl = await input({
|
|
297
|
+
message: 'Cloud LLM Hub URL:',
|
|
298
|
+
required: true,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// 10. Output file
|
|
303
|
+
const outputPath = await input({
|
|
304
|
+
message: 'Output file path:',
|
|
305
|
+
default: './mcp-proxy-config.yaml',
|
|
306
|
+
});
|
|
307
|
+
// Generate and write config
|
|
308
|
+
const fullAnswers = answers;
|
|
309
|
+
const yaml = generateConfigYaml(fullAnswers);
|
|
310
|
+
fs.writeFileSync(outputPath, yaml, 'utf-8');
|
|
311
|
+
console.log(`\nConfig saved to: ${outputPath}`);
|
|
312
|
+
console.log(`Run with: mcp-abap-adt-proxy --config ${outputPath}`);
|
|
313
|
+
return fullAnswers;
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
317
|
+
process.exit(0);
|
|
318
|
+
}
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "MCP proxy server for SAP ABAP ADT - proxies local requests to cloud-llm-hub with JWT authentication",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -64,11 +64,12 @@
|
|
|
64
64
|
"typescript": "^5.9.2"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
+
"@inquirer/prompts": "^8.3.2",
|
|
67
68
|
"@mcp-abap-adt/auth-broker": "^1.0.5",
|
|
68
69
|
"@mcp-abap-adt/auth-providers": "^1.0.5",
|
|
69
|
-
"@mcp-abap-adt/auth-stores": "^1.0.
|
|
70
|
+
"@mcp-abap-adt/auth-stores": "^1.0.4",
|
|
70
71
|
"@mcp-abap-adt/header-validator": "^0.1.8",
|
|
71
|
-
"@mcp-abap-adt/interfaces": "^
|
|
72
|
+
"@mcp-abap-adt/interfaces": "^5.0.0",
|
|
72
73
|
"@mcp-abap-adt/logger": "^0.1.4",
|
|
73
74
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
74
75
|
"axios": "^1.13.5",
|