@mcp-abap-adt/proxy 0.1.3 → 0.1.5
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 +23 -0
- package/README.md +9 -1
- package/bin/mcp-abap-adt-proxy.js +43 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -2
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +6 -5
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +11 -0
- package/dist/lib/stores.d.ts +6 -5
- package/dist/lib/stores.d.ts.map +1 -1
- package/dist/lib/stores.js +154 -7
- package/dist/proxy/cloudLlmHubProxy.d.ts +5 -0
- package/dist/proxy/cloudLlmHubProxy.d.ts.map +1 -1
- package/dist/proxy/cloudLlmHubProxy.js +76 -13
- package/dist/router/directCloudRouter.d.ts.map +1 -1
- package/dist/router/directCloudRouter.js +8 -41
- package/dist/router/headerAnalyzer.d.ts +10 -5
- package/dist/router/headerAnalyzer.d.ts.map +1 -1
- package/dist/router/headerAnalyzer.js +16 -15
- package/dist/router/localBasicRouter.d.ts.map +1 -1
- package/dist/router/localBasicRouter.js +8 -30
- package/package.json +7 -6
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.4] - 2025-12-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Documentation for BTP-Only authentication mode**
|
|
14
|
+
- Added "Scenario 2: BTP Authentication Only (No SAP Configuration)" in `doc/USAGE.md`
|
|
15
|
+
- Documented use case for connecting to any BTP service without SAP ABAP configuration
|
|
16
|
+
- Explained that `x-mcp-destination` is optional when using `x-btp-destination` or `--btp`
|
|
17
|
+
- Updated README.md with "BTP-Only Mode" section explaining behavior when only `--btp` is provided
|
|
18
|
+
- **Enhanced help text in `bin/mcp-abap-adt-proxy.js`**
|
|
19
|
+
- Added detailed "Usage Modes" section with three modes:
|
|
20
|
+
1. BTP Authentication Mode (with `--btp` only)
|
|
21
|
+
2. BTP + SAP Configuration Mode (`--btp + --mcp`)
|
|
22
|
+
3. Local Testing Mode (only `--mcp` or `--mcp-url`)
|
|
23
|
+
- Added examples for each usage mode
|
|
24
|
+
- Added information about headers for HTTP/SSE transports
|
|
25
|
+
- Added service keys location and structure documentation
|
|
26
|
+
- Clarified that `abap.url` in service key is used as MCP server URL (even for non-SAP services)
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
- Improved clarity on when to use each authentication mode
|
|
30
|
+
- Added examples showing BTP-only usage (without SAP configuration)
|
|
31
|
+
- Documented that proxy works with any BTP service, not just SAP ABAP
|
|
32
|
+
|
|
10
33
|
## [0.1.3] - 2025-12-02
|
|
11
34
|
|
|
12
35
|
### Fixed
|
package/README.md
CHANGED
|
@@ -81,9 +81,15 @@ The proxy uses two service keys (URLs are obtained from service keys):
|
|
|
81
81
|
|
|
82
82
|
**BTP Authentication Mode** (with `x-btp-destination` or `--btp`):
|
|
83
83
|
1. `x-btp-destination` (or `--btp`) → Gets JWT token from auth-broker → Adds `Authorization: Bearer <token>` header
|
|
84
|
-
2. `x-mcp-destination` (or `--mcp`) → Gets JWT token and SAP config from auth-broker → Adds SAP headers (`x-sap-jwt-token`, `x-sap-url`, etc.)
|
|
84
|
+
2. `x-mcp-destination` (or `--mcp`) → Gets JWT token and SAP config from auth-broker → Adds SAP headers (`x-sap-jwt-token`, `x-sap-url`, etc.) - **optional**
|
|
85
85
|
3. MCP server URL obtained from service key for `x-btp-destination`
|
|
86
86
|
|
|
87
|
+
**BTP-Only Mode** (with only `x-btp-destination`/`--btp`, without `x-mcp-destination`):
|
|
88
|
+
- Works with any BTP service, not just SAP ABAP
|
|
89
|
+
- Only adds `Authorization: Bearer <token>` header (no SAP headers)
|
|
90
|
+
- MCP server URL obtained from BTP destination service key
|
|
91
|
+
- Suitable for BTP services that don't require SAP ABAP configuration
|
|
92
|
+
|
|
87
93
|
**Local Testing Mode** (with only `x-mcp-destination`/`--mcp` or `x-mcp-url`/`--mcp-url`):
|
|
88
94
|
1. `x-mcp-url` (or `--mcp-url`) → Direct URL to MCP server (no authentication)
|
|
89
95
|
2. `x-mcp-destination` (or `--mcp`) → Gets MCP server URL from service key for MCP destination (optional token)
|
|
@@ -96,6 +102,8 @@ The proxy uses two service keys (URLs are obtained from service keys):
|
|
|
96
102
|
- **[API Documentation](./doc/API.md)** - API reference and interfaces
|
|
97
103
|
- **[Architecture](./doc/ARCHITECTURE.md)** - System architecture and design
|
|
98
104
|
- **[Troubleshooting](./doc/TROUBLESHOOTING.md)** - Common issues and solutions
|
|
105
|
+
- **[Routing Logic Specification](./doc/ROUTING_LOGIC.md)** - Detailed routing logic and scenarios
|
|
106
|
+
- **[Implementation Analysis](./doc/IMPLEMENTATION_ANALYSIS.md)** - Current implementation analysis
|
|
99
107
|
- **[Roadmap](./ROADMAP.md)** - Development roadmap and progress
|
|
100
108
|
|
|
101
109
|
## How It Works
|
|
@@ -94,6 +94,49 @@ Transport Modes:
|
|
|
94
94
|
- http / streamable-http: HTTP server (default if stdin is TTY, port 3001)
|
|
95
95
|
- sse: Server-Sent Events server (port 3002)
|
|
96
96
|
|
|
97
|
+
Usage Modes:
|
|
98
|
+
|
|
99
|
+
1. BTP Authentication Mode (with --btp):
|
|
100
|
+
- Adds Authorization: Bearer <token> header from BTP destination
|
|
101
|
+
- Gets MCP server URL from BTP destination service key
|
|
102
|
+
- Works with any BTP service, not just SAP ABAP
|
|
103
|
+
Example: mcp-abap-adt-proxy --btp=ai
|
|
104
|
+
|
|
105
|
+
2. BTP + SAP Configuration Mode (--btp + --mcp):
|
|
106
|
+
- Adds Authorization: Bearer <token> header from BTP destination
|
|
107
|
+
- Adds SAP headers (x-sap-jwt-token, x-sap-url, etc.) from MCP destination
|
|
108
|
+
- Gets MCP server URL from BTP destination service key
|
|
109
|
+
- Suitable for SAP ABAP systems that require both BTP and SAP authentication
|
|
110
|
+
Example: mcp-abap-adt-proxy --btp=ai --mcp=trial
|
|
111
|
+
|
|
112
|
+
3. Local Testing Mode (only --mcp or --mcp-url):
|
|
113
|
+
- No BTP authentication required
|
|
114
|
+
- Gets MCP server URL from MCP destination service key (--mcp) or uses direct URL (--mcp-url)
|
|
115
|
+
- Optional SAP token (won't fail if unavailable)
|
|
116
|
+
- Enables local integration testing without BTP authentication
|
|
117
|
+
Example: mcp-abap-adt-proxy --mcp=trial
|
|
118
|
+
Example: mcp-abap-adt-proxy --mcp-url=http://localhost:3000/mcp
|
|
119
|
+
|
|
120
|
+
Headers (for HTTP/SSE transports):
|
|
121
|
+
- x-btp-destination: Destination for BTP Cloud authorization (optional if --btp is used)
|
|
122
|
+
- x-mcp-destination: Destination for SAP ABAP connection (optional if --mcp is used)
|
|
123
|
+
- x-mcp-url: Direct MCP server URL (optional if --mcp-url is used)
|
|
124
|
+
|
|
125
|
+
At least one of the above headers (or corresponding --btp/--mcp/--mcp-url parameter) is required.
|
|
126
|
+
|
|
127
|
+
Service Keys:
|
|
128
|
+
Service keys should be placed in:
|
|
129
|
+
- Unix: ~/.config/mcp-abap-adt/service-keys/<destination>.json
|
|
130
|
+
- Windows: %USERPROFILE%\\Documents\\mcp-abap-adt\\service-keys\\<destination>.json
|
|
131
|
+
|
|
132
|
+
For BTP destination, service key should contain:
|
|
133
|
+
{
|
|
134
|
+
"uaa": { "url": "...", "clientid": "...", "clientsecret": "..." },
|
|
135
|
+
"abap": { "url": "https://your-mcp-server.com" }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
The "abap.url" field is used as the MCP server URL (even for non-SAP services).
|
|
139
|
+
|
|
97
140
|
For more information, see: https://github.com/fr0ster/mcp-abap-adt-proxy
|
|
98
141
|
`);
|
|
99
142
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;GAWG;AAOH,OAAO,EAAwB,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAOjF;;GAEG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,gBAAgB,CAAC,CAAmB;gBAEhC,eAAe,CAAC,EAAE,eAAe,EAAE,UAAU,CAAC,EAAE,MAAM;IAqClE;;OAEG;IACH,OAAO,CAAC,aAAa;IAMrB;;;OAGG;YACW,kBAAkB;IA4EhC;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD1B;;OAEG;YACW,eAAe;IAsH7B;;OAEG;YACW,kBAAkB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;GAWG;AAOH,OAAO,EAAwB,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAOjF;;GAEG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,gBAAgB,CAAC,CAAmB;gBAEhC,eAAe,CAAC,EAAE,eAAe,EAAE,UAAU,CAAC,EAAE,MAAM;IAqClE;;OAEG;IACH,OAAO,CAAC,aAAa;IAMrB;;;OAGG;YACW,kBAAkB;IA4EhC;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD1B;;OAEG;YACW,eAAe;IAsH7B;;OAEG;YACW,kBAAkB;IA6EhC;;OAEG;YACW,cAAc;IA+R5B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAmBhC;AAGD,eAAe,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -341,11 +341,16 @@ class McpAbapAdtProxyServer {
|
|
|
341
341
|
});
|
|
342
342
|
}
|
|
343
343
|
catch (error) {
|
|
344
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
344
345
|
logger_js_1.logger.error("Failed to handle cloud-llm-hub proxy request", {
|
|
345
346
|
type: "CLOUD_LLM_HUB_PROXY_ERROR",
|
|
346
|
-
error:
|
|
347
|
+
error: errorMessage,
|
|
347
348
|
stack: error instanceof Error ? error.stack : undefined,
|
|
348
349
|
});
|
|
350
|
+
// Output error to stderr for user visibility (only if verbose mode is enabled)
|
|
351
|
+
if ((0, cloudLlmHubProxy_js_1.shouldWriteStderr)()) {
|
|
352
|
+
process.stderr.write(`[MCP Proxy] ✗ Connection error: ${errorMessage}\n`);
|
|
353
|
+
}
|
|
349
354
|
if (!res.headersSent) {
|
|
350
355
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
351
356
|
res.end(JSON.stringify({
|
|
@@ -353,7 +358,7 @@ class McpAbapAdtProxyServer {
|
|
|
353
358
|
id: intercepted.body?.id || null,
|
|
354
359
|
error: {
|
|
355
360
|
code: -32000,
|
|
356
|
-
message:
|
|
361
|
+
message: errorMessage,
|
|
357
362
|
},
|
|
358
363
|
}));
|
|
359
364
|
}
|
|
@@ -643,3 +648,28 @@ class McpAbapAdtProxyServer {
|
|
|
643
648
|
exports.McpAbapAdtProxyServer = McpAbapAdtProxyServer;
|
|
644
649
|
// Export for use in bin script
|
|
645
650
|
exports.default = McpAbapAdtProxyServer;
|
|
651
|
+
// Auto-start server when run directly (unless MCP_SKIP_AUTO_START is set)
|
|
652
|
+
if (process.env.MCP_SKIP_AUTO_START !== "true") {
|
|
653
|
+
const server = new McpAbapAdtProxyServer();
|
|
654
|
+
server.run().catch((error) => {
|
|
655
|
+
logger_js_1.logger.error("Fatal error while running MCP proxy server", {
|
|
656
|
+
type: "SERVER_FATAL_ERROR",
|
|
657
|
+
error: error instanceof Error ? error.message : String(error),
|
|
658
|
+
});
|
|
659
|
+
// Always write fatal errors to stderr (even in non-verbose mode, as these are critical)
|
|
660
|
+
// But skip in test environment
|
|
661
|
+
const isTestEnv = process.env.NODE_ENV === "test" ||
|
|
662
|
+
process.env.JEST_WORKER_ID !== undefined ||
|
|
663
|
+
typeof globalThis.jest !== "undefined";
|
|
664
|
+
if (!isTestEnv) {
|
|
665
|
+
process.stderr.write(`[MCP Proxy] ✗ Fatal error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
666
|
+
}
|
|
667
|
+
// On Windows, add a small delay before exit to allow error message to be visible
|
|
668
|
+
if (process.platform === 'win32') {
|
|
669
|
+
setTimeout(() => process.exit(1), 100);
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
process.exit(1);
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
}
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IAEjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAoC3D;AA+ED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IAEjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAoC3D;AA+ED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA+C5G"}
|
package/dist/lib/config.js
CHANGED
|
@@ -157,12 +157,13 @@ function mergeConfig(fileConfig, envConfig) {
|
|
|
157
157
|
function validateConfig(config) {
|
|
158
158
|
const errors = [];
|
|
159
159
|
const warnings = [];
|
|
160
|
-
// Cloud LLM Hub URL is only required if we're using
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
// Cloud LLM Hub URL is only required if we're not using BTP/MCP destinations
|
|
161
|
+
// If --btp, --mcp, or --mcp-url is provided, URL will be obtained from service keys
|
|
162
|
+
const hasDestination = config.btpDestination || config.mcpDestination || config.mcpUrl;
|
|
163
|
+
if (!config.cloudLlmHubUrl && !hasDestination) {
|
|
164
|
+
warnings.push("CLOUD_LLM_HUB_URL is not set and no destination provided (--btp, --mcp, or --mcp-url) - proxy will not work without a destination");
|
|
164
165
|
}
|
|
165
|
-
else {
|
|
166
|
+
else if (config.cloudLlmHubUrl) {
|
|
166
167
|
try {
|
|
167
168
|
new URL(config.cloudLlmHubUrl);
|
|
168
169
|
}
|
package/dist/lib/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oBAAY,QAAQ;IAClB,KAAK,IAAI;IACT,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;CACV;AAED,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9D;
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oBAAY,QAAQ;IAClB,KAAK,IAAI;IACT,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;CACV;AAED,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9D;AAiDD,eAAO,MAAM,MAAM,EAAE,MAIpB,CAAC"}
|
package/dist/lib/logger.js
CHANGED
|
@@ -13,10 +13,21 @@ var LogLevel;
|
|
|
13
13
|
})(LogLevel || (exports.LogLevel = LogLevel = {}));
|
|
14
14
|
class SimpleLogger {
|
|
15
15
|
level;
|
|
16
|
+
verbose;
|
|
16
17
|
constructor(level = LogLevel.INFO) {
|
|
17
18
|
this.level = level;
|
|
19
|
+
// Enable logging only if MCP_PROXY_VERBOSE or DEBUG environment variable is set
|
|
20
|
+
// In default mode (without env vars), no logs are output
|
|
21
|
+
this.verbose = process.env.MCP_PROXY_VERBOSE === "true" ||
|
|
22
|
+
process.env.DEBUG === "true" ||
|
|
23
|
+
process.env.DEBUG?.includes("mcp-proxy") === true;
|
|
18
24
|
}
|
|
19
25
|
log(level, prefix, message, meta) {
|
|
26
|
+
// In non-verbose mode, don't output any logs
|
|
27
|
+
if (!this.verbose) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// In verbose mode, respect the log level
|
|
20
31
|
if (level < this.level) {
|
|
21
32
|
return;
|
|
22
33
|
}
|
package/dist/lib/stores.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Platform-specific stores for proxy
|
|
3
|
-
* Uses
|
|
3
|
+
* Uses AbapServiceKeyStore/XsuaaServiceKeyStore and either AbapSessionStore or SafeAbapSessionStore
|
|
4
4
|
* depending on the unsafe configuration parameter
|
|
5
5
|
*/
|
|
6
|
-
import { IServiceKeyStore, ISessionStore } from "@mcp-abap-adt/auth-broker";
|
|
6
|
+
import type { IServiceKeyStore, ISessionStore } from "@mcp-abap-adt/auth-broker";
|
|
7
7
|
/**
|
|
8
8
|
* Get platform-specific stores
|
|
9
9
|
* Returns stores based on configuration:
|
|
10
|
-
* - If unsafe=true: uses
|
|
11
|
-
* - If unsafe=false: uses
|
|
12
|
-
*
|
|
10
|
+
* - If unsafe=true: uses AbapSessionStore (persists to disk)
|
|
11
|
+
* - If unsafe=false: uses SafeAbapSessionStore (in-memory, secure)
|
|
12
|
+
* - Service key store: Combined store that tries both standard and XSUAA loaders
|
|
13
|
+
* @param unsafe If true, use AbapSessionStore. If false, use SafeAbapSessionStore (default).
|
|
13
14
|
*/
|
|
14
15
|
export declare function getPlatformStores(unsafe?: boolean): Promise<{
|
|
15
16
|
serviceKeyStore: IServiceKeyStore;
|
package/dist/lib/stores.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stores.d.ts","sourceRoot":"","sources":["../../src/lib/stores.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"stores.d.ts","sourceRoot":"","sources":["../../src/lib/stores.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EAId,MAAM,2BAA2B,CAAC;AAuHnC;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC;IACxE,eAAe,EAAE,gBAAgB,CAAC;IAClC,YAAY,EAAE,aAAa,CAAC;CAC7B,CAAC,CAeD"}
|
package/dist/lib/stores.js
CHANGED
|
@@ -1,22 +1,169 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Platform-specific stores for proxy
|
|
4
|
-
* Uses
|
|
4
|
+
* Uses AbapServiceKeyStore/XsuaaServiceKeyStore and either AbapSessionStore or SafeAbapSessionStore
|
|
5
5
|
* depending on the unsafe configuration parameter
|
|
6
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
|
+
})();
|
|
7
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
41
|
exports.getPlatformStores = getPlatformStores;
|
|
9
|
-
const
|
|
42
|
+
const auth_stores_1 = require("@mcp-abap-adt/auth-stores");
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
/**
|
|
46
|
+
* Get platform-specific default paths for service keys and sessions
|
|
47
|
+
*
|
|
48
|
+
* Priority:
|
|
49
|
+
* 1. AUTH_BROKER_PATH environment variable
|
|
50
|
+
* 2. Platform-specific standard paths:
|
|
51
|
+
* - Unix: ~/.config/mcp-abap-adt/service-keys
|
|
52
|
+
* - Windows: %USERPROFILE%\Documents\mcp-abap-adt\service-keys
|
|
53
|
+
* 3. Current working directory (process.cwd())
|
|
54
|
+
*
|
|
55
|
+
* @param subfolder Subfolder name ('service-keys' or 'sessions')
|
|
56
|
+
* @returns Array of resolved absolute paths
|
|
57
|
+
*/
|
|
58
|
+
function getPlatformPaths(subfolder) {
|
|
59
|
+
const paths = [];
|
|
60
|
+
const isWindows = process.platform === 'win32';
|
|
61
|
+
// Priority 1: AUTH_BROKER_PATH environment variable
|
|
62
|
+
const envPath = process.env.AUTH_BROKER_PATH;
|
|
63
|
+
if (envPath) {
|
|
64
|
+
// Support both colon (Unix) and semicolon (Windows) separators
|
|
65
|
+
const envPaths = envPath.split(/[:;]/).map(p => p.trim()).filter(p => p.length > 0);
|
|
66
|
+
paths.push(...envPaths.map(p => path.resolve(p)));
|
|
67
|
+
}
|
|
68
|
+
// Priority 2: Platform-specific standard paths (always added, unless AUTH_BROKER_PATH is set)
|
|
69
|
+
// If AUTH_BROKER_PATH is set, platform paths are still added but with lower priority
|
|
70
|
+
const homeDir = os.homedir();
|
|
71
|
+
if (isWindows) {
|
|
72
|
+
// Windows: %USERPROFILE%\Documents\mcp-abap-adt\{subfolder}
|
|
73
|
+
const basePath = path.join(homeDir, 'Documents', 'mcp-abap-adt');
|
|
74
|
+
if (subfolder) {
|
|
75
|
+
paths.push(path.join(basePath, subfolder));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
paths.push(basePath);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Unix (Linux/macOS): ~/.config/mcp-abap-adt/{subfolder}
|
|
83
|
+
const basePath = path.join(homeDir, '.config', 'mcp-abap-adt');
|
|
84
|
+
if (subfolder) {
|
|
85
|
+
paths.push(path.join(basePath, subfolder));
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
paths.push(basePath);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Priority 3: Current working directory (always added as fallback)
|
|
92
|
+
paths.push(process.cwd());
|
|
93
|
+
// Remove duplicates while preserving order
|
|
94
|
+
const uniquePaths = [];
|
|
95
|
+
const seen = new Set();
|
|
96
|
+
for (const p of paths) {
|
|
97
|
+
const normalized = path.normalize(p);
|
|
98
|
+
if (!seen.has(normalized)) {
|
|
99
|
+
seen.add(normalized);
|
|
100
|
+
uniquePaths.push(normalized);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return uniquePaths;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Combined service key store that tries both standard and XSUAA loaders
|
|
107
|
+
* Tries standard ABAP service key loader first, then XSUAA loader if not found
|
|
108
|
+
*
|
|
109
|
+
* Note: Stores only support a single directory, so we use the first path from the array
|
|
110
|
+
*/
|
|
111
|
+
class CombinedServiceKeyStore {
|
|
112
|
+
abapStore;
|
|
113
|
+
xsuaaStore;
|
|
114
|
+
constructor(searchPaths) {
|
|
115
|
+
// Stores only support a single directory, use the first path
|
|
116
|
+
const firstPath = searchPaths[0] || process.cwd();
|
|
117
|
+
this.abapStore = new auth_stores_1.AbapServiceKeyStore(firstPath);
|
|
118
|
+
this.xsuaaStore = new auth_stores_1.XsuaaServiceKeyStore(firstPath);
|
|
119
|
+
}
|
|
120
|
+
async getServiceKey(destination) {
|
|
121
|
+
// Try ABAP service key store first
|
|
122
|
+
const abapKey = await this.abapStore.getServiceKey(destination);
|
|
123
|
+
if (abapKey) {
|
|
124
|
+
return abapKey;
|
|
125
|
+
}
|
|
126
|
+
// If not found, try XSUAA store (for BTP XSUAA service keys)
|
|
127
|
+
return await this.xsuaaStore.getServiceKey(destination);
|
|
128
|
+
}
|
|
129
|
+
async getAuthorizationConfig(destination) {
|
|
130
|
+
// Try ABAP service key store first
|
|
131
|
+
const abapConfig = await this.abapStore.getAuthorizationConfig(destination);
|
|
132
|
+
if (abapConfig) {
|
|
133
|
+
return abapConfig;
|
|
134
|
+
}
|
|
135
|
+
// If not found, try XSUAA store (for BTP XSUAA service keys)
|
|
136
|
+
return await this.xsuaaStore.getAuthorizationConfig(destination);
|
|
137
|
+
}
|
|
138
|
+
async getConnectionConfig(destination) {
|
|
139
|
+
// Try ABAP service key store first
|
|
140
|
+
const abapConfig = await this.abapStore.getConnectionConfig(destination);
|
|
141
|
+
if (abapConfig) {
|
|
142
|
+
return abapConfig;
|
|
143
|
+
}
|
|
144
|
+
// If not found, try XSUAA store (for BTP XSUAA service keys)
|
|
145
|
+
return await this.xsuaaStore.getConnectionConfig(destination);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
10
148
|
/**
|
|
11
149
|
* Get platform-specific stores
|
|
12
150
|
* Returns stores based on configuration:
|
|
13
|
-
* - If unsafe=true: uses
|
|
14
|
-
* - If unsafe=false: uses
|
|
15
|
-
*
|
|
151
|
+
* - If unsafe=true: uses AbapSessionStore (persists to disk)
|
|
152
|
+
* - If unsafe=false: uses SafeAbapSessionStore (in-memory, secure)
|
|
153
|
+
* - Service key store: Combined store that tries both standard and XSUAA loaders
|
|
154
|
+
* @param unsafe If true, use AbapSessionStore. If false, use SafeAbapSessionStore (default).
|
|
16
155
|
*/
|
|
17
156
|
async function getPlatformStores(unsafe = false) {
|
|
157
|
+
// Get platform-specific paths for service keys and sessions
|
|
158
|
+
const serviceKeyPaths = getPlatformPaths('service-keys');
|
|
159
|
+
const sessionPaths = getPlatformPaths('sessions');
|
|
160
|
+
// Stores only support a single directory, use the first path
|
|
161
|
+
const firstServiceKeyPath = serviceKeyPaths[0] || process.cwd();
|
|
162
|
+
const firstSessionPath = sessionPaths[0] || process.cwd();
|
|
18
163
|
return {
|
|
19
|
-
serviceKeyStore: new
|
|
20
|
-
sessionStore: unsafe
|
|
164
|
+
serviceKeyStore: new CombinedServiceKeyStore(serviceKeyPaths),
|
|
165
|
+
sessionStore: unsafe
|
|
166
|
+
? new auth_stores_1.AbapSessionStore(firstSessionPath)
|
|
167
|
+
: new auth_stores_1.SafeAbapSessionStore(),
|
|
21
168
|
};
|
|
22
169
|
}
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
import { AuthBroker } from "@mcp-abap-adt/auth-broker";
|
|
8
8
|
import { RoutingDecision } from "../router/headerAnalyzer.js";
|
|
9
9
|
import { ProxyConfig } from "../lib/config.js";
|
|
10
|
+
/**
|
|
11
|
+
* Check if error messages should be written to stderr
|
|
12
|
+
* Only output in verbose mode and not in test environment
|
|
13
|
+
*/
|
|
14
|
+
export declare function shouldWriteStderr(): boolean;
|
|
10
15
|
export interface ProxyRequest {
|
|
11
16
|
method: string;
|
|
12
17
|
params?: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudLlmHubProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/cloudLlmHubProxy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"cloudLlmHubProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/cloudLlmHubProxy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAGvD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAc,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAU3D;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAQ3C;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,GAAG,CAAC;KACZ,CAAC;CACH;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,UAAU,CAAgE;IAClF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAc;gBAEhB,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IA2EzF;;OAEG;YACW,WAAW;IA6DzB;;;;;;;;OAQG;YACW,iBAAiB;IAkL/B;;OAEG;IACG,YAAY,CAChB,eAAe,EAAE,YAAY,EAC7B,eAAe,EAAE,eAAe,EAChC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAC7D,OAAO,CAAC,aAAa,CAAC;CAuH1B;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,cAAc,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAc3B"}
|
|
@@ -10,13 +10,28 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.CloudLlmHubProxy = void 0;
|
|
13
|
+
exports.shouldWriteStderr = shouldWriteStderr;
|
|
13
14
|
exports.createCloudLlmHubProxy = createCloudLlmHubProxy;
|
|
14
15
|
const axios_1 = __importDefault(require("axios"));
|
|
15
16
|
const auth_broker_1 = require("@mcp-abap-adt/auth-broker");
|
|
17
|
+
const auth_providers_1 = require("@mcp-abap-adt/auth-providers");
|
|
16
18
|
const logger_js_1 = require("../lib/logger.js");
|
|
17
19
|
const config_js_1 = require("../lib/config.js");
|
|
18
20
|
const stores_js_1 = require("../lib/stores.js");
|
|
19
21
|
const errorHandler_js_1 = require("../lib/errorHandler.js");
|
|
22
|
+
/**
|
|
23
|
+
* Check if error messages should be written to stderr
|
|
24
|
+
* Only output in verbose mode and not in test environment
|
|
25
|
+
*/
|
|
26
|
+
function shouldWriteStderr() {
|
|
27
|
+
const verboseMode = process.env.MCP_PROXY_VERBOSE === "true" ||
|
|
28
|
+
process.env.DEBUG === "true" ||
|
|
29
|
+
process.env.DEBUG?.includes("mcp-proxy") === true;
|
|
30
|
+
const isTestEnv = process.env.NODE_ENV === "test" ||
|
|
31
|
+
process.env.JEST_WORKER_ID !== undefined ||
|
|
32
|
+
typeof globalThis.jest !== "undefined";
|
|
33
|
+
return verboseMode && !isTestEnv;
|
|
34
|
+
}
|
|
20
35
|
/**
|
|
21
36
|
* Cloud LLM Hub Proxy Client
|
|
22
37
|
*/
|
|
@@ -40,11 +55,19 @@ class CloudLlmHubProxy {
|
|
|
40
55
|
// Initialize circuit breaker
|
|
41
56
|
this.circuitBreaker = new errorHandler_js_1.CircuitBreaker(this.config.circuitBreakerThreshold || 5, this.config.circuitBreakerTimeout || 60000);
|
|
42
57
|
// Create axios instance without baseURL - we'll use full URLs from x-mcp-url
|
|
58
|
+
// Configure HTTPS agent for proper SSL/TLS handling
|
|
59
|
+
const https = require('https');
|
|
43
60
|
this.axiosInstance = axios_1.default.create({
|
|
44
61
|
timeout: this.config.requestTimeout || 60000,
|
|
45
62
|
headers: {
|
|
46
63
|
"Content-Type": "application/json",
|
|
47
64
|
},
|
|
65
|
+
httpsAgent: new https.Agent({
|
|
66
|
+
// Allow self-signed certificates if needed (for development)
|
|
67
|
+
rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0',
|
|
68
|
+
// Keep connections alive for better performance
|
|
69
|
+
keepAlive: true,
|
|
70
|
+
}),
|
|
48
71
|
});
|
|
49
72
|
// Add request interceptor for logging
|
|
50
73
|
this.axiosInstance.interceptors.request.use((config) => {
|
|
@@ -124,11 +147,16 @@ class CloudLlmHubProxy {
|
|
|
124
147
|
return token;
|
|
125
148
|
}
|
|
126
149
|
catch (error) {
|
|
150
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
127
151
|
logger_js_1.logger.error("Failed to get JWT token from auth-broker after retries", {
|
|
128
152
|
type: "JWT_TOKEN_ERROR",
|
|
129
153
|
destination,
|
|
130
|
-
error:
|
|
154
|
+
error: errorMessage,
|
|
131
155
|
});
|
|
156
|
+
// Output error to stderr for user visibility (only if verbose mode is enabled)
|
|
157
|
+
if (shouldWriteStderr()) {
|
|
158
|
+
process.stderr.write(`[MCP Proxy] ✗ Failed to get token for destination "${destination}": ${errorMessage}\n`);
|
|
159
|
+
}
|
|
132
160
|
throw error;
|
|
133
161
|
}
|
|
134
162
|
}
|
|
@@ -167,7 +195,8 @@ class CloudLlmHubProxy {
|
|
|
167
195
|
// Only used if x-mcp-destination or --mcp is provided
|
|
168
196
|
// For local testing (without BTP), token retrieval is optional - URL is still needed
|
|
169
197
|
if (routingDecision.mcpDestination) {
|
|
170
|
-
const
|
|
198
|
+
const connConfig = await this.authBroker.getConnectionConfig(routingDecision.mcpDestination);
|
|
199
|
+
const sapUrl = connConfig?.serviceUrl;
|
|
171
200
|
// Try to get token, but don't fail if it's not available (for local testing)
|
|
172
201
|
try {
|
|
173
202
|
const sapToken = await this.getJwtToken(routingDecision.mcpDestination, forceTokenRefresh);
|
|
@@ -225,9 +254,14 @@ class CloudLlmHubProxy {
|
|
|
225
254
|
}
|
|
226
255
|
else if (routingDecision.btpDestination) {
|
|
227
256
|
// Get URL from BTP destination service key
|
|
228
|
-
|
|
257
|
+
const connConfig = await this.authBroker.getConnectionConfig(routingDecision.btpDestination);
|
|
258
|
+
baseUrl = connConfig?.serviceUrl;
|
|
229
259
|
if (!baseUrl) {
|
|
230
|
-
|
|
260
|
+
const errorMsg = `Failed to get MCP server URL from BTP destination "${routingDecision.btpDestination}". Check service key file.`;
|
|
261
|
+
if (shouldWriteStderr()) {
|
|
262
|
+
process.stderr.write(`[MCP Proxy] ✗ ${errorMsg}\n`);
|
|
263
|
+
}
|
|
264
|
+
throw new Error(errorMsg);
|
|
231
265
|
}
|
|
232
266
|
logger_js_1.logger.debug("Using MCP URL from BTP destination service key", {
|
|
233
267
|
type: "MCP_URL_FROM_BTP",
|
|
@@ -237,9 +271,14 @@ class CloudLlmHubProxy {
|
|
|
237
271
|
}
|
|
238
272
|
else if (routingDecision.mcpDestination) {
|
|
239
273
|
// Get URL from MCP destination service key (for local testing without BTP)
|
|
240
|
-
|
|
274
|
+
const connConfig = await this.authBroker.getConnectionConfig(routingDecision.mcpDestination);
|
|
275
|
+
baseUrl = connConfig?.serviceUrl;
|
|
241
276
|
if (!baseUrl) {
|
|
242
|
-
|
|
277
|
+
const errorMsg = `Failed to get MCP server URL from MCP destination "${routingDecision.mcpDestination}". Check service key file.`;
|
|
278
|
+
if (shouldWriteStderr()) {
|
|
279
|
+
process.stderr.write(`[MCP Proxy] ✗ ${errorMsg}\n`);
|
|
280
|
+
}
|
|
281
|
+
throw new Error(errorMsg);
|
|
243
282
|
}
|
|
244
283
|
logger_js_1.logger.debug("Using MCP URL from MCP destination service key (local testing mode)", {
|
|
245
284
|
type: "MCP_URL_FROM_MCP",
|
|
@@ -254,9 +293,15 @@ class CloudLlmHubProxy {
|
|
|
254
293
|
// If baseUrl already contains the path, use it as-is
|
|
255
294
|
// Otherwise, append default endpoint /mcp/stream/http
|
|
256
295
|
let fullUrl;
|
|
257
|
-
if (
|
|
258
|
-
|
|
296
|
+
// Check if URL already contains MCP path (more flexible check)
|
|
297
|
+
if (baseUrl.includes("/mcp/") || baseUrl.endsWith("/mcp") || baseUrl.includes("/mcp/stream/")) {
|
|
298
|
+
// URL already contains MCP path - use as-is (but remove trailing slash if present)
|
|
259
299
|
fullUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
300
|
+
logger_js_1.logger.debug("Using MCP URL as-is (already contains path)", {
|
|
301
|
+
type: "MCP_URL_AS_IS",
|
|
302
|
+
original: baseUrl,
|
|
303
|
+
final: fullUrl,
|
|
304
|
+
});
|
|
260
305
|
}
|
|
261
306
|
else {
|
|
262
307
|
// Append default endpoint
|
|
@@ -264,6 +309,11 @@ class CloudLlmHubProxy {
|
|
|
264
309
|
fullUrl = baseUrl.endsWith("/")
|
|
265
310
|
? `${baseUrl.slice(0, -1)}${mcpPath}`
|
|
266
311
|
: `${baseUrl}${mcpPath}`;
|
|
312
|
+
logger_js_1.logger.debug("Appended MCP path to base URL", {
|
|
313
|
+
type: "MCP_URL_APPENDED",
|
|
314
|
+
original: baseUrl,
|
|
315
|
+
final: fullUrl,
|
|
316
|
+
});
|
|
267
317
|
}
|
|
268
318
|
logger_js_1.logger.debug("Built proxy request", {
|
|
269
319
|
type: "PROXY_REQUEST_BUILT",
|
|
@@ -347,16 +397,27 @@ class CloudLlmHubProxy {
|
|
|
347
397
|
});
|
|
348
398
|
}
|
|
349
399
|
}
|
|
400
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
401
|
+
const statusCode = axios_1.default.isAxiosError(error) && error.response?.status
|
|
402
|
+
? error.response.status
|
|
403
|
+
: undefined;
|
|
350
404
|
logger_js_1.logger.error("Failed to proxy request to cloud-llm-hub", {
|
|
351
405
|
type: "PROXY_REQUEST_ERROR",
|
|
352
|
-
error:
|
|
353
|
-
status:
|
|
406
|
+
error: errorMessage,
|
|
407
|
+
status: statusCode,
|
|
354
408
|
circuitBreakerState: this.circuitBreaker.getState(),
|
|
355
409
|
});
|
|
410
|
+
// Output error to stderr for user visibility (only if verbose mode is enabled)
|
|
411
|
+
if (shouldWriteStderr()) {
|
|
412
|
+
if (statusCode) {
|
|
413
|
+
process.stderr.write(`[MCP Proxy] ✗ Connection failed: ${errorMessage} (HTTP ${statusCode})\n`);
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
process.stderr.write(`[MCP Proxy] ✗ Connection failed: ${errorMessage}\n`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
356
419
|
// Return error response in MCP format
|
|
357
|
-
return (0, errorHandler_js_1.createErrorResponse)(originalRequest.id || null,
|
|
358
|
-
? error.response.status
|
|
359
|
-
: -32000, error instanceof Error ? error.message : "Unknown error", {
|
|
420
|
+
return (0, errorHandler_js_1.createErrorResponse)(originalRequest.id || null, statusCode || -32000, errorMessage, {
|
|
360
421
|
circuitBreakerState: this.circuitBreaker.getState(),
|
|
361
422
|
originalError: axios_1.default.isAxiosError(error) && error.response?.data
|
|
362
423
|
? error.response.data
|
|
@@ -372,9 +433,11 @@ exports.CloudLlmHubProxy = CloudLlmHubProxy;
|
|
|
372
433
|
async function createCloudLlmHubProxy(cloudLlmHubUrl, config) {
|
|
373
434
|
const unsafe = config?.unsafe ?? false;
|
|
374
435
|
const { serviceKeyStore, sessionStore } = await (0, stores_js_1.getPlatformStores)(unsafe);
|
|
436
|
+
const tokenProvider = new auth_providers_1.BtpTokenProvider();
|
|
375
437
|
const authBroker = new auth_broker_1.AuthBroker({
|
|
376
438
|
serviceKeyStore,
|
|
377
439
|
sessionStore,
|
|
440
|
+
tokenProvider,
|
|
378
441
|
}, "system");
|
|
379
442
|
return new CloudLlmHubProxy(cloudLlmHubUrl, authBroker, config);
|
|
380
443
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directCloudRouter.d.ts","sourceRoot":"","sources":["../../src/router/directCloudRouter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAwB,cAAc,EAAsB,MAAM,0BAA0B,CAAC;AAEpG,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,KAAK,GAAG,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,mBAAmB,GAC3B,iBAAiB,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"directCloudRouter.d.ts","sourceRoot":"","sources":["../../src/router/directCloudRouter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAwB,cAAc,EAAsB,MAAM,0BAA0B,CAAC;AAEpG,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,KAAK,GAAG,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,mBAAmB,GAC3B,iBAAiB,GAAG,IAAI,CAS1B;AA4CD;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,iBAAiB,EACzB,UAAU,CAAC,EAAE,UAAU,GACtB,OAAO,CAAC,cAAc,CAAC,CA+FzB"}
|
|
@@ -14,47 +14,14 @@ const logger_js_1 = require("../lib/logger.js");
|
|
|
14
14
|
* Convert validated auth config to direct cloud config
|
|
15
15
|
*/
|
|
16
16
|
function createDirectCloudConfig(routingDecision, headers) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Extract SAP URL
|
|
26
|
-
let sapUrl = validatedConfig.sapUrl;
|
|
27
|
-
const destination = routingDecision.btpDestination || routingDecision.mcpDestination;
|
|
28
|
-
if (!sapUrl && destination) {
|
|
29
|
-
// For destination-based auth, URL comes from destination service key
|
|
30
|
-
// We'll need to load it from AuthBroker
|
|
31
|
-
// For now, return null and let the caller handle it
|
|
32
|
-
logger_js_1.logger.debug("SAP URL not in validated config, will load from destination", {
|
|
33
|
-
destination: destination,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
// Convert xsuaa to jwt (they're the same for connection purposes)
|
|
37
|
-
const authType = validatedConfig.authType === "xsuaa" ? "jwt" : validatedConfig.authType;
|
|
38
|
-
const config = {
|
|
39
|
-
sapUrl: sapUrl || "", // Will be loaded from destination if needed
|
|
40
|
-
destination: validatedConfig.destination || destination,
|
|
41
|
-
authType: authType,
|
|
42
|
-
sapClient: validatedConfig.sapClient,
|
|
43
|
-
};
|
|
44
|
-
// Add JWT-specific fields
|
|
45
|
-
if (validatedConfig.authType === "jwt" || validatedConfig.authType === "xsuaa") {
|
|
46
|
-
config.jwtToken = validatedConfig.jwtToken;
|
|
47
|
-
config.refreshToken = validatedConfig.refreshToken;
|
|
48
|
-
config.uaaUrl = validatedConfig.uaaUrl;
|
|
49
|
-
config.uaaClientId = validatedConfig.uaaClientId;
|
|
50
|
-
config.uaaClientSecret = validatedConfig.uaaClientSecret;
|
|
51
|
-
}
|
|
52
|
-
// Add basic auth fields
|
|
53
|
-
if (validatedConfig.authType === "basic") {
|
|
54
|
-
config.username = validatedConfig.username;
|
|
55
|
-
config.password = validatedConfig.password;
|
|
56
|
-
}
|
|
57
|
-
return config;
|
|
17
|
+
// Proxy validates only x-btp-destination and x-mcp-destination headers
|
|
18
|
+
// This function is not used in the current proxy implementation
|
|
19
|
+
// as proxy passes all other headers directly to MCP server
|
|
20
|
+
logger_js_1.logger.warn("Direct cloud routing is not supported in proxy mode", {
|
|
21
|
+
type: "DIRECT_CLOUD_CONFIG_ERROR",
|
|
22
|
+
strategy: routingDecision.strategy,
|
|
23
|
+
});
|
|
24
|
+
return null;
|
|
58
25
|
}
|
|
59
26
|
/**
|
|
60
27
|
* Connection cache for direct cloud connections
|
|
@@ -19,22 +19,27 @@ export interface RoutingDecision {
|
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Analyze headers and extract routing information
|
|
22
|
-
*
|
|
22
|
+
*
|
|
23
|
+
* Proxy validates only two headers:
|
|
23
24
|
* - x-btp-destination (or --btp): destination for BTP Cloud authorization token (Authorization: Bearer) and MCP server URL
|
|
24
|
-
* - x-mcp-destination (or --mcp): destination for SAP ABAP connection (SAP headers)
|
|
25
|
+
* - x-mcp-destination (or --mcp): destination for SAP ABAP connection (SAP headers)
|
|
25
26
|
*
|
|
26
27
|
* Also supports:
|
|
27
|
-
* - x-mcp-url (or --mcp-url): direct MCP server URL (
|
|
28
|
+
* - x-mcp-url (or --mcp-url): direct MCP server URL (optional, for local testing)
|
|
28
29
|
*
|
|
29
|
-
* Command-line overrides:
|
|
30
|
+
* Command-line overrides (take precedence over headers):
|
|
30
31
|
* - --btp=<destination>: overrides x-btp-destination header
|
|
31
32
|
* - --mcp=<destination>: overrides x-mcp-destination header
|
|
32
|
-
* - --mcp-url=<url>: overrides x-mcp-url header
|
|
33
|
+
* - --mcp-url=<url>: overrides x-mcp-url header
|
|
34
|
+
*
|
|
35
|
+
* Other headers (x-sap-url, x-sap-jwt-token, etc.) are passed directly to MCP server without validation.
|
|
33
36
|
*
|
|
34
37
|
* MCP server URL is obtained from:
|
|
35
38
|
* - x-mcp-url header or --mcp-url parameter (if provided) - takes precedence
|
|
36
39
|
* - service key for x-btp-destination (via auth-broker) if BTP destination is present
|
|
37
40
|
* - service key for x-mcp-destination (via auth-broker) if only MCP destination is present
|
|
41
|
+
*
|
|
42
|
+
* Note: Proxy does NOT use .env files for connection configuration. Only destinations via auth-broker (service key files) are used.
|
|
38
43
|
*/
|
|
39
44
|
export declare function analyzeHeaders(headers: IncomingHttpHeaders, configOverrides?: {
|
|
40
45
|
btpDestination?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headerAnalyzer.d.ts","sourceRoot":"","sources":["../../src/router/headerAnalyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAA2C,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAGjH,oBAAY,eAAe;IACzB,4CAA4C;IAC5C,KAAK,UAAU;IACf,6CAA6C;IAC7C,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,eAAe,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;CAC3C;AAED
|
|
1
|
+
{"version":3,"file":"headerAnalyzer.d.ts","sourceRoot":"","sources":["../../src/router/headerAnalyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAA2C,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAGjH,oBAAY,eAAe;IACzB,4CAA4C;IAC5C,KAAK,UAAU;IACf,6CAA6C;IAC7C,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,eAAe,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,mBAAmB,EAC5B,eAAe,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACtF,eAAe,CAqFjB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAGjE"}
|
|
@@ -17,22 +17,27 @@ var RoutingStrategy;
|
|
|
17
17
|
})(RoutingStrategy || (exports.RoutingStrategy = RoutingStrategy = {}));
|
|
18
18
|
/**
|
|
19
19
|
* Analyze headers and extract routing information
|
|
20
|
-
*
|
|
20
|
+
*
|
|
21
|
+
* Proxy validates only two headers:
|
|
21
22
|
* - x-btp-destination (or --btp): destination for BTP Cloud authorization token (Authorization: Bearer) and MCP server URL
|
|
22
|
-
* - x-mcp-destination (or --mcp): destination for SAP ABAP connection (SAP headers)
|
|
23
|
+
* - x-mcp-destination (or --mcp): destination for SAP ABAP connection (SAP headers)
|
|
23
24
|
*
|
|
24
25
|
* Also supports:
|
|
25
|
-
* - x-mcp-url (or --mcp-url): direct MCP server URL (
|
|
26
|
+
* - x-mcp-url (or --mcp-url): direct MCP server URL (optional, for local testing)
|
|
26
27
|
*
|
|
27
|
-
* Command-line overrides:
|
|
28
|
+
* Command-line overrides (take precedence over headers):
|
|
28
29
|
* - --btp=<destination>: overrides x-btp-destination header
|
|
29
30
|
* - --mcp=<destination>: overrides x-mcp-destination header
|
|
30
|
-
* - --mcp-url=<url>: overrides x-mcp-url header
|
|
31
|
+
* - --mcp-url=<url>: overrides x-mcp-url header
|
|
32
|
+
*
|
|
33
|
+
* Other headers (x-sap-url, x-sap-jwt-token, etc.) are passed directly to MCP server without validation.
|
|
31
34
|
*
|
|
32
35
|
* MCP server URL is obtained from:
|
|
33
36
|
* - x-mcp-url header or --mcp-url parameter (if provided) - takes precedence
|
|
34
37
|
* - service key for x-btp-destination (via auth-broker) if BTP destination is present
|
|
35
38
|
* - service key for x-mcp-destination (via auth-broker) if only MCP destination is present
|
|
39
|
+
*
|
|
40
|
+
* Note: Proxy does NOT use .env files for connection configuration. Only destinations via auth-broker (service key files) are used.
|
|
36
41
|
*/
|
|
37
42
|
function analyzeHeaders(headers, configOverrides) {
|
|
38
43
|
// Validate headers using header-validator
|
|
@@ -65,13 +70,11 @@ function analyzeHeaders(headers, configOverrides) {
|
|
|
65
70
|
const extractedMcpUrl = configOverrides?.mcpUrl
|
|
66
71
|
? configOverrides.mcpUrl
|
|
67
72
|
: mcpUrlHeader;
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
// 3. MCP URL is provided (for local testing without BTP)
|
|
72
|
-
// This enables local testing without BTP authentication
|
|
73
|
+
// Validate: at least one destination or MCP URL must be provided
|
|
74
|
+
// We validate only x-btp-destination and x-mcp-destination headers
|
|
75
|
+
// Other headers (x-sap-url, x-sap-jwt-token, etc.) are passed directly to MCP server
|
|
73
76
|
if (!extractedBtpDestination && !extractedMcpDestination && !extractedMcpUrl) {
|
|
74
|
-
logger_js_1.logger.warn("Neither x-btp-destination/x-mcp-destination
|
|
77
|
+
logger_js_1.logger.warn("Neither x-btp-destination/x-mcp-destination header nor --btp/--mcp parameter is provided, and no x-mcp-url/--mcp-url", {
|
|
75
78
|
headers: Object.keys(headers).filter(k => k.toLowerCase().startsWith("x-")),
|
|
76
79
|
hasBtpConfigOverride: !!configOverrides?.btpDestination,
|
|
77
80
|
hasMcpConfigOverride: !!configOverrides?.mcpDestination,
|
|
@@ -83,11 +86,9 @@ function analyzeHeaders(headers, configOverrides) {
|
|
|
83
86
|
validationResult,
|
|
84
87
|
};
|
|
85
88
|
}
|
|
86
|
-
// If only MCP URL is provided without
|
|
87
|
-
// unless mcpUrl is a full URL that can be used directly
|
|
89
|
+
// If only MCP URL is provided without destinations, that's OK - other headers will be passed directly
|
|
88
90
|
if (!extractedBtpDestination && !extractedMcpDestination && extractedMcpUrl) {
|
|
89
|
-
|
|
90
|
-
logger_js_1.logger.debug("Using MCP URL directly for local testing (no destination required)", {
|
|
91
|
+
logger_js_1.logger.debug("Using MCP URL directly (no destinations - headers will be passed as-is)", {
|
|
91
92
|
type: "MCP_URL_DIRECT",
|
|
92
93
|
mcpUrl: extractedMcpUrl,
|
|
93
94
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localBasicRouter.d.ts","sourceRoot":"","sources":["../../src/router/localBasicRouter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAwB,cAAc,EAAsB,MAAM,0BAA0B,CAAC;AAEpG,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"localBasicRouter.d.ts","sourceRoot":"","sources":["../../src/router/localBasicRouter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAwB,cAAc,EAAsB,MAAM,0BAA0B,CAAC;AAEpG,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,mBAAmB,GAC3B,gBAAgB,GAAG,IAAI,CASzB;AA2CD;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,cAAc,CAAC,CAoEzB"}
|
|
@@ -13,36 +13,14 @@ const logger_js_1 = require("../lib/logger.js");
|
|
|
13
13
|
* Convert validated auth config to local basic config
|
|
14
14
|
*/
|
|
15
15
|
function createLocalBasicConfig(routingDecision, headers) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (validatedConfig.authType !== "basic") {
|
|
25
|
-
logger_js_1.logger.warn("Config is not basic auth type", {
|
|
26
|
-
type: "LOCAL_BASIC_CONFIG_TYPE_ERROR",
|
|
27
|
-
authType: validatedConfig.authType,
|
|
28
|
-
});
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
if (!validatedConfig.sapUrl || !validatedConfig.username || !validatedConfig.password) {
|
|
32
|
-
logger_js_1.logger.warn("Missing required fields for basic auth", {
|
|
33
|
-
type: "LOCAL_BASIC_CONFIG_MISSING",
|
|
34
|
-
hasUrl: !!validatedConfig.sapUrl,
|
|
35
|
-
hasUsername: !!validatedConfig.username,
|
|
36
|
-
hasPassword: !!validatedConfig.password,
|
|
37
|
-
});
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
return {
|
|
41
|
-
sapUrl: validatedConfig.sapUrl,
|
|
42
|
-
username: validatedConfig.username,
|
|
43
|
-
password: validatedConfig.password,
|
|
44
|
-
sapClient: validatedConfig.sapClient,
|
|
45
|
-
};
|
|
16
|
+
// Proxy validates only x-btp-destination and x-mcp-destination headers
|
|
17
|
+
// This function is not used in the current proxy implementation
|
|
18
|
+
// as proxy passes all other headers directly to MCP server
|
|
19
|
+
logger_js_1.logger.warn("Local basic routing is not supported in proxy mode", {
|
|
20
|
+
type: "LOCAL_BASIC_CONFIG_ERROR",
|
|
21
|
+
strategy: routingDecision.strategy,
|
|
22
|
+
});
|
|
23
|
+
return null;
|
|
46
24
|
}
|
|
47
25
|
/**
|
|
48
26
|
* Connection cache for local basic connections
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/proxy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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",
|
|
@@ -38,20 +38,21 @@
|
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@modelcontextprotocol/inspector": "^0.17.2",
|
|
41
|
-
"@types/jest": "^29.5.
|
|
41
|
+
"@types/jest": "^29.5.14",
|
|
42
42
|
"@types/node": "^24.2.1",
|
|
43
43
|
"commander": "^14.0.2",
|
|
44
44
|
"cors": "^2.8.5",
|
|
45
|
-
"dotenv": "^17.2.1",
|
|
46
45
|
"express": "^5.1.0",
|
|
47
46
|
"jest": "^29.7.0",
|
|
48
47
|
"ts-jest": "^29.2.0",
|
|
49
48
|
"typescript": "^5.9.2"
|
|
50
49
|
},
|
|
51
50
|
"dependencies": {
|
|
52
|
-
"@mcp-abap-adt/auth-broker": "^0.1.
|
|
53
|
-
"@mcp-abap-adt/
|
|
54
|
-
"@mcp-abap-adt/
|
|
51
|
+
"@mcp-abap-adt/auth-broker": "^0.1.9",
|
|
52
|
+
"@mcp-abap-adt/auth-providers": "^0.1.2",
|
|
53
|
+
"@mcp-abap-adt/auth-stores": "^0.1.4",
|
|
54
|
+
"@mcp-abap-adt/connection": "^0.1.15",
|
|
55
|
+
"@mcp-abap-adt/header-validator": "^0.1.6",
|
|
55
56
|
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
56
57
|
"axios": "^1.11.0",
|
|
57
58
|
"zod": "^3.25.76"
|