@mcp-abap-adt/proxy 0.2.0 → 1.0.1
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 +45 -7
- package/README.md +31 -9
- package/bin/mcp-abap-adt-proxy.js +5 -2
- package/dist/index.js +2 -2
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +33 -1
- package/dist/proxy/btpProxy.d.ts +2 -2
- package/dist/proxy/btpProxy.js +5 -5
- package/dist/proxy/reverseProxy.d.ts +1 -1
- package/dist/proxy/reverseProxy.d.ts.map +1 -1
- package/dist/proxy/reverseProxy.js +9 -2
- package/dist/router/headerAnalyzer.d.ts +3 -3
- package/dist/router/headerAnalyzer.js +7 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.1] - 2026-04-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Default headers injection (`defaultHeaders` / `--header`)**
|
|
14
|
+
- Proxy can inject configurable headers into every forwarded request
|
|
15
|
+
- YAML config: `defaultHeaders` map (e.g., `x-sap-destination: S4HANA_E19`)
|
|
16
|
+
- CLI: repeatable `--header key=value` flag
|
|
17
|
+
- Client headers override defaults (low priority injection)
|
|
18
|
+
- Enables MCP clients (Cline, Claude Code) that cannot set custom headers to pass SAP headers to the MCP server on BTP
|
|
19
|
+
|
|
20
|
+
## [1.0.0] - 2026-04-07
|
|
21
|
+
|
|
22
|
+
### Breaking Changes
|
|
23
|
+
- **Header renamed: `x-btp-destination` → `x-sap-destination`**
|
|
24
|
+
- The proxy now uses `x-sap-destination` header (same as MCP server) instead of the proxy-specific `x-btp-destination`
|
|
25
|
+
- The `--btp` CLI parameter is unchanged
|
|
26
|
+
- Clients sending `x-btp-destination` header must update to `x-sap-destination`
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- **Transparent reverse proxy for BTP request forwarding**
|
|
30
|
+
- Replaced buffered axios-based proxy with Node.js native HTTP streaming reverse proxy
|
|
31
|
+
- Supports any HTTP API including OpenAI-compatible streaming endpoints
|
|
32
|
+
- `targetUrl` parameter for explicit target URL override (`--target-url`/`--url` or `x-target-url` header)
|
|
33
|
+
- **Interactive configuration wizard (TUI)**
|
|
34
|
+
- `npx @mcp-abap-adt/proxy --tui` launches interactive setup wizard
|
|
35
|
+
- Service key existence checker validates BTP destination before configuration
|
|
36
|
+
- Generates YAML config files with inline comments
|
|
37
|
+
- Supports BTP and Direct URL scenarios with advanced settings
|
|
38
|
+
- **Dependencies updated**
|
|
39
|
+
- `@mcp-abap-adt/interfaces` bumped to v5
|
|
40
|
+
- `@mcp-abap-adt/auth-stores` bumped to v1.0.4
|
|
41
|
+
- Added `@inquirer/prompts` for TUI wizard
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
- **Header validation skipped in reverse proxy mode** when config provides destination
|
|
45
|
+
- **YAML config file now correctly loads transport/port settings** (`--config` flag)
|
|
46
|
+
- **targetUrl loading from YAML config** now works correctly
|
|
47
|
+
|
|
10
48
|
## [0.1.9] - 2026-02-12
|
|
11
49
|
|
|
12
50
|
### Changed
|
|
@@ -45,7 +83,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
45
83
|
|
|
46
84
|
### Added
|
|
47
85
|
- **Separate AuthBroker instances for BTP and ABAP destinations**
|
|
48
|
-
- `btpAuthBroker` with `XsuaaTokenProvider` for BTP destinations (`--btp` or `x-
|
|
86
|
+
- `btpAuthBroker` with `XsuaaTokenProvider` for BTP destinations (`--btp` or `x-sap-destination`)
|
|
49
87
|
- `abapAuthBroker` with `BtpTokenProvider` for ABAP destinations (`--mcp` or `x-mcp-destination`)
|
|
50
88
|
- Proper separation ensures correct token provider is used for each destination type
|
|
51
89
|
- **Client Setup Guide** (`docs/CLIENT_SETUP.md`)
|
|
@@ -89,7 +127,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
89
127
|
- **Documentation for BTP-Only authentication mode**
|
|
90
128
|
- Added "Scenario 2: BTP Authentication Only (No SAP Configuration)" in `docs/USAGE.md`
|
|
91
129
|
- Documented use case for connecting to any BTP service without SAP ABAP configuration
|
|
92
|
-
- Explained that `x-mcp-destination` is optional when using `x-
|
|
130
|
+
- Explained that `x-mcp-destination` is optional when using `x-sap-destination` or `--btp`
|
|
93
131
|
- Updated README.md with "BTP-Only Mode" section explaining behavior when only `--btp` is provided
|
|
94
132
|
- **Enhanced help text in `bin/mcp-abap-adt-proxy.js`**
|
|
95
133
|
- Added detailed "Usage Modes" section with three modes:
|
|
@@ -111,7 +149,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
111
149
|
### Fixed
|
|
112
150
|
- **Unit tests updated to match new routing logic**
|
|
113
151
|
- Fixed tests that expected `UNKNOWN` strategy when only `x-mcp-destination` or `x-mcp-url` is provided
|
|
114
|
-
- Updated test expectations to reflect that `x-
|
|
152
|
+
- Updated test expectations to reflect that `x-sap-destination` is now optional
|
|
115
153
|
- Added tests for `x-mcp-url` header support
|
|
116
154
|
- Updated `shouldProxy` function tests to cover all routing scenarios (BTP, MCP destination, MCP URL)
|
|
117
155
|
- All 51 unit tests now pass successfully
|
|
@@ -134,7 +172,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
134
172
|
|
|
135
173
|
### Added
|
|
136
174
|
- **Local testing mode without BTP authentication**
|
|
137
|
-
- Proxy can now run with only `--mcp` or `x-mcp-destination` (without `--btp` or `x-
|
|
175
|
+
- Proxy can now run with only `--mcp` or `x-mcp-destination` (without `--btp` or `x-sap-destination`)
|
|
138
176
|
- Enables local integration testing without BTP authentication
|
|
139
177
|
- MCP server URL can be obtained from MCP destination service key or specified directly via `--mcp-url`
|
|
140
178
|
- **`--mcp-url` parameter support**
|
|
@@ -149,16 +187,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
149
187
|
- Default configuration: ADT on port 3000 (HTTP), Proxy on port 3001 (HTTP)
|
|
150
188
|
- Available via `npm run test:servers`
|
|
151
189
|
- **Flexible routing requirements**
|
|
152
|
-
- Proxy accepts either `x-
|
|
190
|
+
- Proxy accepts either `x-sap-destination/--btp`, `x-mcp-destination/--mcp`, or `x-mcp-url/--mcp-url`
|
|
153
191
|
- Removed strict requirement for BTP destination when using MCP destination or URL
|
|
154
192
|
|
|
155
193
|
### Changed
|
|
156
194
|
- **Routing logic updated**
|
|
157
|
-
- `x-
|
|
195
|
+
- `x-sap-destination` is now optional (was required)
|
|
158
196
|
- Proxy can work with only `x-mcp-destination` or `x-mcp-url` for local testing
|
|
159
197
|
- MCP URL can be obtained from multiple sources (priority order):
|
|
160
198
|
1. `x-mcp-url` header or `--mcp-url` parameter (direct URL)
|
|
161
|
-
2. Service key for `x-
|
|
199
|
+
2. Service key for `x-sap-destination` (if provided)
|
|
162
200
|
3. Service key for `x-mcp-destination` (if only MCP destination is provided)
|
|
163
201
|
- **Token retrieval made optional for MCP destination**
|
|
164
202
|
- Token retrieval for `mcpDestination` is now optional (won't fail if token unavailable)
|
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ For detailed setup instructions for Cline and GitHub Copilot, see the **[Client
|
|
|
76
76
|
"type": "streamableHttp",
|
|
77
77
|
"url": "http://localhost:3001/mcp/stream/http",
|
|
78
78
|
"headers": {
|
|
79
|
-
"x-
|
|
79
|
+
"x-sap-destination": "btp-cloud"
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
}
|
|
@@ -84,20 +84,42 @@ For detailed setup instructions for Cline and GitHub Copilot, see the **[Client
|
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
**Required Headers:**
|
|
87
|
-
- `x-
|
|
87
|
+
- `x-sap-destination` - Destination name for BTP Cloud authorization token and MCP server URL
|
|
88
88
|
|
|
89
89
|
**Command Line Overrides:**
|
|
90
|
-
- `--btp=<destination>` - Overrides `x-
|
|
90
|
+
- `--btp=<destination>` - Overrides `x-sap-destination` header (takes precedence)
|
|
91
91
|
- `--url=<url>` - Overrides MCP server URL (required if service key lacks URL)
|
|
92
92
|
- `--browser=<browser>` - Browser to use: `system` (default), `chrome`, `edge`, `firefox`, `headless`
|
|
93
93
|
- `--browser-auth-port=<port>` - Port for OAuth2 callback (default: 3333)
|
|
94
94
|
- `--unsafe` - Enables file-based session storage (persists tokens to disk). By default, sessions are stored in-memory (secure, lost on restart)
|
|
95
95
|
|
|
96
|
+
**Default Headers:**
|
|
97
|
+
|
|
98
|
+
MCP clients like Cline and Claude Code cannot set arbitrary request headers. Use default headers to inject SAP-specific headers (e.g. `x-sap-destination`, `x-sap-client`) that the target MCP server requires.
|
|
99
|
+
|
|
100
|
+
Client-supplied headers always take precedence over defaults.
|
|
101
|
+
|
|
102
|
+
Via YAML config (`defaultHeaders` map):
|
|
103
|
+
```yaml
|
|
104
|
+
btpDestination: mcp
|
|
105
|
+
targetUrl: https://example.com
|
|
106
|
+
defaultHeaders:
|
|
107
|
+
x-sap-destination: S4HANA_E19
|
|
108
|
+
x-sap-client: "100"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Via CLI (`--header`, repeatable):
|
|
112
|
+
```bash
|
|
113
|
+
mcp-abap-adt-proxy --btp=mcp --url=https://example.com \
|
|
114
|
+
--header x-sap-destination=S4HANA_E19 \
|
|
115
|
+
--header x-sap-client=100
|
|
116
|
+
```
|
|
117
|
+
|
|
96
118
|
**How It Works:**
|
|
97
119
|
|
|
98
120
|
The proxy uses BTP/XSUAA authentication:
|
|
99
121
|
|
|
100
|
-
1. **BTP Authentication** (if `--btp` or `x-
|
|
122
|
+
1. **BTP Authentication** (if `--btp` or `x-sap-destination` is present):
|
|
101
123
|
- Uses `AuthorizationCodeProvider` (browser-based OAuth2 flow)
|
|
102
124
|
- **Eager Authentication**: Opens browser immediately on startup to get token
|
|
103
125
|
- Injects/overwrites `Authorization: Bearer <token>` header
|
|
@@ -106,7 +128,7 @@ The proxy uses BTP/XSUAA authentication:
|
|
|
106
128
|
|
|
107
129
|
**BTP Authentication Mode** (with `--btp`):
|
|
108
130
|
1. Proxy starts → Opens browser for login (Eager Auth) → Gets/Refreshes JWT token
|
|
109
|
-
2. `x-
|
|
131
|
+
2. `x-sap-destination` (or `--btp`) → Adds `Authorization: Bearer <token>` header
|
|
110
132
|
3. MCP server URL obtained from service key OR `--url` parameter
|
|
111
133
|
|
|
112
134
|
## Documentation
|
|
@@ -126,16 +148,16 @@ The proxy uses BTP/XSUAA authentication:
|
|
|
126
148
|
|
|
127
149
|
The proxy performs the following steps for each request:
|
|
128
150
|
|
|
129
|
-
1. **Extract Headers**: Reads `x-
|
|
151
|
+
1. **Extract Headers**: Reads `x-sap-destination` header
|
|
130
152
|
2. **Apply Command Line Overrides**: `--btp` parameter overrides header (if provided)
|
|
131
|
-
3. **Validate Routing Requirements**: Requires `x-
|
|
132
|
-
4. **BTP Authentication** (if `x-
|
|
153
|
+
3. **Validate Routing Requirements**: Requires `x-sap-destination/--btp`
|
|
154
|
+
4. **BTP Authentication** (if `x-sap-destination` or `--btp` is provided):
|
|
133
155
|
- Uses `AuthorizationCodeProvider` (browser-based login)
|
|
134
156
|
- **Eagerly** obtains token on startup (if configured via `--btp`)
|
|
135
157
|
- Retrieves JWT token using cached refresh token or opens browser
|
|
136
158
|
- Injects/overwrites `Authorization: Bearer <token>` header
|
|
137
159
|
5. **Get MCP Server URL**:
|
|
138
|
-
- From service key for `x-
|
|
160
|
+
- From service key for `x-sap-destination`
|
|
139
161
|
6. **Forward Request**: Sends request to MCP server URL with all injected headers
|
|
140
162
|
7. **Return Response**: Forwards the response back to the client
|
|
141
163
|
|
|
@@ -55,13 +55,15 @@ Options:
|
|
|
55
55
|
--sse-port=<port> SSE server port (default: 3002)
|
|
56
56
|
--http-host=<host> HTTP server host (default: 0.0.0.0)
|
|
57
57
|
--sse-host=<host> SSE server host (default: 0.0.0.0)
|
|
58
|
-
--btp=<destination> Override x-
|
|
58
|
+
--btp=<destination> Override x-sap-destination header (for BTP Cloud authorization)
|
|
59
59
|
Required for stdio/SSE transports unless provided in headers
|
|
60
60
|
--target-url=<url> Override target URL (separates Auth from Connection)
|
|
61
61
|
Useful for direct OData service testing or non-standard MCP URLs
|
|
62
62
|
--config=<file>, -c Load configuration from YAML or JSON file
|
|
63
63
|
Alternative to command-line parameters
|
|
64
64
|
--unsafe Enable file-based session storage (persists tokens to disk)
|
|
65
|
+
--header key=value Add default header to every forwarded request (repeatable)
|
|
66
|
+
Example: --header x-sap-destination=S4HANA
|
|
65
67
|
--help, -h Show this help message
|
|
66
68
|
--version, -v Show version number
|
|
67
69
|
|
|
@@ -82,6 +84,7 @@ Examples:
|
|
|
82
84
|
mcp-abap-adt-proxy --transport=sse --sse-port=3002 # SSE mode on port 3002
|
|
83
85
|
mcp-abap-adt-proxy --btp=ai # With BTP destination override
|
|
84
86
|
mcp-abap-adt-proxy --btp=ai --unsafe # With file-based session storage
|
|
87
|
+
mcp-abap-adt-proxy --btp=ai --header x-sap-destination=S4HANA # With default SAP headers
|
|
85
88
|
mcp-abap-adt-proxy tui # Interactive configuration wizard
|
|
86
89
|
mcp-abap-adt-proxy --config=proxy-config.yaml # Load configuration from YAML file
|
|
87
90
|
mcp-abap-adt-proxy -c proxy-config.yml # Load configuration from YAML file (short form)
|
|
@@ -100,7 +103,7 @@ Usage Modes:
|
|
|
100
103
|
Example: mcp-abap-adt-proxy --btp=ai
|
|
101
104
|
|
|
102
105
|
Headers (for HTTP/SSE transports):
|
|
103
|
-
- x-
|
|
106
|
+
- x-sap-destination: Destination for BTP Cloud authorization (optional if --btp is used)
|
|
104
107
|
|
|
105
108
|
At least one of the above headers (or corresponding --btp parameter) is required.
|
|
106
109
|
|
package/dist/index.js
CHANGED
|
@@ -252,8 +252,8 @@ Authorization source: ${authSourceObj}`;
|
|
|
252
252
|
// Get target URL
|
|
253
253
|
const targetUrl = intercepted.routingDecision.targetUrl ||
|
|
254
254
|
(await this.btpProxy.getTargetUrl(destination));
|
|
255
|
-
// Forward request transparently
|
|
256
|
-
await (0, reverseProxy_js_1.forwardRequest)(req, res, targetUrl, jwtToken);
|
|
255
|
+
// Forward request transparently (inject default headers from config)
|
|
256
|
+
await (0, reverseProxy_js_1.forwardRequest)(req, res, targetUrl, jwtToken, this.config.defaultHeaders);
|
|
257
257
|
}
|
|
258
258
|
catch (error) {
|
|
259
259
|
logger_js_1.logger?.error('Proxy request failed', {
|
package/dist/lib/config.d.ts
CHANGED
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;AAMH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IAEjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IAEjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAGxC,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAG/B,OAAO,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAqC3D;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,mBAAmB,CAQrB;AAgBD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAYhC;AA4ID;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAElD;AAUD;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG;IACnD,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAoDA"}
|
package/dist/lib/config.js
CHANGED
|
@@ -137,6 +137,7 @@ function applyDefaults(fileConfig) {
|
|
|
137
137
|
logLevel: fileConfig.logLevel || 'info',
|
|
138
138
|
btpDestination: fileConfig.btpDestination,
|
|
139
139
|
targetUrl: fileConfig.targetUrl,
|
|
140
|
+
defaultHeaders: fileConfig.defaultHeaders,
|
|
140
141
|
unsafe: fileConfig.unsafe ?? false,
|
|
141
142
|
maxRetries: fileConfig.maxRetries ?? 3,
|
|
142
143
|
retryDelay: fileConfig.retryDelay ?? 1000,
|
|
@@ -161,6 +162,20 @@ function loadFromEnv() {
|
|
|
161
162
|
const browserAuthPort = browserAuthPortStr
|
|
162
163
|
? parseInt(browserAuthPortStr, 10)
|
|
163
164
|
: undefined;
|
|
165
|
+
// Parse repeatable --header key=value arguments
|
|
166
|
+
const headerArgs = getAllArgValues('--header');
|
|
167
|
+
let defaultHeaders;
|
|
168
|
+
if (headerArgs.length > 0) {
|
|
169
|
+
defaultHeaders = {};
|
|
170
|
+
for (const arg of headerArgs) {
|
|
171
|
+
const eqIndex = arg.indexOf('=');
|
|
172
|
+
if (eqIndex > 0) {
|
|
173
|
+
const key = arg.substring(0, eqIndex).toLowerCase();
|
|
174
|
+
const value = arg.substring(eqIndex + 1);
|
|
175
|
+
defaultHeaders[key] = value;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
164
179
|
return {
|
|
165
180
|
httpPort: parseInt(process.env.MCP_HTTP_PORT || '3001', 10),
|
|
166
181
|
ssePort: parseInt(process.env.MCP_SSE_PORT || '3002', 10),
|
|
@@ -169,6 +184,7 @@ function loadFromEnv() {
|
|
|
169
184
|
logLevel: process.env.LOG_LEVEL || 'info',
|
|
170
185
|
btpDestination,
|
|
171
186
|
targetUrl,
|
|
187
|
+
defaultHeaders,
|
|
172
188
|
unsafe,
|
|
173
189
|
maxRetries: parseInt(process.env.MCP_PROXY_MAX_RETRIES || '3', 10),
|
|
174
190
|
retryDelay: parseInt(process.env.MCP_PROXY_RETRY_DELAY || '1000', 10),
|
|
@@ -194,6 +210,22 @@ function getArgValue(argName) {
|
|
|
194
210
|
}
|
|
195
211
|
return undefined;
|
|
196
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Get all values for a repeatable argument from command line
|
|
215
|
+
*/
|
|
216
|
+
function getAllArgValues(argName) {
|
|
217
|
+
const args = process.argv;
|
|
218
|
+
const values = [];
|
|
219
|
+
for (let i = 0; i < args.length; i++) {
|
|
220
|
+
if (args[i] === argName && i + 1 < args.length) {
|
|
221
|
+
values.push(args[i + 1]);
|
|
222
|
+
}
|
|
223
|
+
if (args[i].startsWith(`${argName}=`)) {
|
|
224
|
+
values.push(args[i].substring(argName.length + 1));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return values;
|
|
228
|
+
}
|
|
197
229
|
/**
|
|
198
230
|
* Get config file path from command line (--config or -c)
|
|
199
231
|
*/
|
|
@@ -217,7 +249,7 @@ function validateConfig(config) {
|
|
|
217
249
|
// Check if BTP destination is provided
|
|
218
250
|
const hasDestination = config.btpDestination;
|
|
219
251
|
if (!hasDestination) {
|
|
220
|
-
warnings.push('No BTP destination provided (--btp). Proxy will not work unless requests include x-
|
|
252
|
+
warnings.push('No BTP destination provided (--btp). Proxy will not work unless requests include x-sap-destination header.');
|
|
221
253
|
}
|
|
222
254
|
if (config.httpPort < 1 || config.httpPort > 65535) {
|
|
223
255
|
errors.push('MCP_HTTP_PORT must be between 1 and 65535');
|
package/dist/proxy/btpProxy.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BTP Proxy - Proxies requests to MCP servers with JWT authentication
|
|
3
3
|
*
|
|
4
|
-
* For requests with x-
|
|
4
|
+
* For requests with x-sap-destination, proxies to target MCP server
|
|
5
5
|
* with JWT token from auth-broker (XSUAA/BTP)
|
|
6
6
|
*/
|
|
7
7
|
import { AuthBroker } from '@mcp-abap-adt/auth-broker';
|
|
@@ -79,7 +79,7 @@ export declare class BtpProxy {
|
|
|
79
79
|
* Process flow:
|
|
80
80
|
*
|
|
81
81
|
* 1. BTP Authentication (XSUAA):
|
|
82
|
-
* 1.1 If x-
|
|
82
|
+
* 1.1 If x-sap-destination header exists:
|
|
83
83
|
* - Check map for broker with key = destination, get or create, save to map
|
|
84
84
|
* - Get token from xsuaa broker
|
|
85
85
|
* - Add/replace Authorization: Bearer <token> header
|
package/dist/proxy/btpProxy.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* BTP Proxy - Proxies requests to MCP servers with JWT authentication
|
|
4
4
|
*
|
|
5
|
-
* For requests with x-
|
|
5
|
+
* For requests with x-sap-destination, proxies to target MCP server
|
|
6
6
|
* with JWT token from auth-broker (XSUAA/BTP)
|
|
7
7
|
*/
|
|
8
8
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
@@ -437,7 +437,7 @@ class BtpProxy {
|
|
|
437
437
|
* Process flow:
|
|
438
438
|
*
|
|
439
439
|
* 1. BTP Authentication (XSUAA):
|
|
440
|
-
* 1.1 If x-
|
|
440
|
+
* 1.1 If x-sap-destination header exists:
|
|
441
441
|
* - Check map for broker with key = destination, get or create, save to map
|
|
442
442
|
* - Get token from xsuaa broker
|
|
443
443
|
* - Add/replace Authorization: Bearer <token> header
|
|
@@ -464,21 +464,21 @@ class BtpProxy {
|
|
|
464
464
|
// ============================================
|
|
465
465
|
// BTP Authentication (XSUAA)
|
|
466
466
|
// ============================================
|
|
467
|
-
const btpDestinationHeader = this.getHeaderValue(originalHeaders[interfaces_1.
|
|
467
|
+
const btpDestinationHeader = this.getHeaderValue(originalHeaders[interfaces_1.HEADER_SAP_DESTINATION]);
|
|
468
468
|
const btpDestinationFromParam = routingDecision.btpDestination && !btpDestinationHeader
|
|
469
469
|
? routingDecision.btpDestination
|
|
470
470
|
: undefined;
|
|
471
471
|
let btpDestination;
|
|
472
472
|
if (btpDestinationHeader) {
|
|
473
473
|
btpDestination = btpDestinationHeader;
|
|
474
|
-
logger_js_1.logger?.debug('Using x-
|
|
474
|
+
logger_js_1.logger?.debug('Using x-sap-destination from header', {
|
|
475
475
|
type: 'BTP_DESTINATION_FROM_HEADER',
|
|
476
476
|
destination: btpDestination,
|
|
477
477
|
});
|
|
478
478
|
}
|
|
479
479
|
else if (btpDestinationFromParam) {
|
|
480
480
|
btpDestination = btpDestinationFromParam;
|
|
481
|
-
logger_js_1.logger?.debug('Using x-
|
|
481
|
+
logger_js_1.logger?.debug('Using x-sap-destination from parameter', {
|
|
482
482
|
type: 'BTP_DESTINATION_FROM_PARAM',
|
|
483
483
|
destination: btpDestination,
|
|
484
484
|
});
|
|
@@ -3,5 +3,5 @@ import * as http from 'node:http';
|
|
|
3
3
|
* Forward an HTTP request to a target URL with JWT injection.
|
|
4
4
|
* Streams both request and response using pipe().
|
|
5
5
|
*/
|
|
6
|
-
export declare function forwardRequest(clientReq: http.IncomingMessage, clientRes: http.ServerResponse, targetBaseUrl: string, jwtToken: string): Promise<void>;
|
|
6
|
+
export declare function forwardRequest(clientReq: http.IncomingMessage, clientRes: http.ServerResponse, targetBaseUrl: string, jwtToken: string, defaultHeaders?: Record<string, string>): Promise<void>;
|
|
7
7
|
//# sourceMappingURL=reverseProxy.d.ts.map
|
|
@@ -1 +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,
|
|
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,EAChB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAwFf"}
|
|
@@ -57,10 +57,17 @@ const HOP_BY_HOP_HEADERS = new Set([
|
|
|
57
57
|
* Forward an HTTP request to a target URL with JWT injection.
|
|
58
58
|
* Streams both request and response using pipe().
|
|
59
59
|
*/
|
|
60
|
-
async function forwardRequest(clientReq, clientRes, targetBaseUrl, jwtToken) {
|
|
60
|
+
async function forwardRequest(clientReq, clientRes, targetBaseUrl, jwtToken, defaultHeaders) {
|
|
61
61
|
const targetUrl = new node_url_1.URL(clientReq.url || '/', targetBaseUrl);
|
|
62
|
-
// Build forwarded headers
|
|
62
|
+
// Build forwarded headers: defaults first, then client headers override
|
|
63
63
|
const forwardedHeaders = {};
|
|
64
|
+
// 1. Inject default headers (low priority)
|
|
65
|
+
if (defaultHeaders) {
|
|
66
|
+
for (const [key, value] of Object.entries(defaultHeaders)) {
|
|
67
|
+
forwardedHeaders[key.toLowerCase()] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// 2. Copy client headers (high priority — overrides defaults)
|
|
64
71
|
for (const [key, value] of Object.entries(clientReq.headers)) {
|
|
65
72
|
if (HOP_BY_HOP_HEADERS.has(key.toLowerCase()))
|
|
66
73
|
continue;
|
|
@@ -18,17 +18,17 @@ export interface RoutingDecision {
|
|
|
18
18
|
* Analyze headers and extract routing information
|
|
19
19
|
*
|
|
20
20
|
* Proxy validates only one header for authentication:
|
|
21
|
-
* - x-
|
|
21
|
+
* - x-sap-destination (or --btp): destination for BTP Cloud authorization token (Authorization: Bearer) and MCP server URL
|
|
22
22
|
*
|
|
23
23
|
|
|
24
24
|
*
|
|
25
25
|
* Command-line overrides (take precedence over headers):
|
|
26
|
-
* - --btp=<destination>: overrides x-
|
|
26
|
+
* - --btp=<destination>: overrides x-sap-destination header
|
|
27
27
|
*
|
|
28
28
|
* Other headers are passed directly to MCP server without validation.
|
|
29
29
|
*
|
|
30
30
|
* MPC server URL is obtained from:
|
|
31
|
-
* - service key for x-
|
|
31
|
+
* - service key for x-sap-destination (via auth-broker)
|
|
32
32
|
*
|
|
33
33
|
*
|
|
34
34
|
* Note: Proxy does NOT use .env files for connection configuration. Only destinations via auth-broker (service key files) are used.
|
|
@@ -19,17 +19,17 @@ var RoutingStrategy;
|
|
|
19
19
|
* Analyze headers and extract routing information
|
|
20
20
|
*
|
|
21
21
|
* Proxy validates only one header for authentication:
|
|
22
|
-
* - x-
|
|
22
|
+
* - x-sap-destination (or --btp): destination for BTP Cloud authorization token (Authorization: Bearer) and MCP server URL
|
|
23
23
|
*
|
|
24
24
|
|
|
25
25
|
*
|
|
26
26
|
* Command-line overrides (take precedence over headers):
|
|
27
|
-
* - --btp=<destination>: overrides x-
|
|
27
|
+
* - --btp=<destination>: overrides x-sap-destination header
|
|
28
28
|
*
|
|
29
29
|
* Other headers are passed directly to MCP server without validation.
|
|
30
30
|
*
|
|
31
31
|
* MPC server URL is obtained from:
|
|
32
|
-
* - service key for x-
|
|
32
|
+
* - service key for x-sap-destination (via auth-broker)
|
|
33
33
|
*
|
|
34
34
|
*
|
|
35
35
|
* Note: Proxy does NOT use .env files for connection configuration. Only destinations via auth-broker (service key files) are used.
|
|
@@ -48,9 +48,9 @@ function analyzeHeaders(headers, configOverrides) {
|
|
|
48
48
|
}
|
|
49
49
|
return typeof headerValue === 'string' ? headerValue.trim() : undefined;
|
|
50
50
|
};
|
|
51
|
-
// Extract authorization destination for BTP Cloud (x-
|
|
51
|
+
// Extract authorization destination for BTP Cloud (x-sap-destination)
|
|
52
52
|
// Command-line parameter --btp takes precedence over header
|
|
53
|
-
const btpDestinationHeader = getHeaderValue(interfaces_1.
|
|
53
|
+
const btpDestinationHeader = getHeaderValue(interfaces_1.HEADER_SAP_DESTINATION);
|
|
54
54
|
const extractedBtpDestination = configOverrides?.btpDestination
|
|
55
55
|
? configOverrides.btpDestination
|
|
56
56
|
: btpDestinationHeader;
|
|
@@ -72,10 +72,10 @@ function analyzeHeaders(headers, configOverrides) {
|
|
|
72
72
|
if (!extractedBtpDestination) {
|
|
73
73
|
return {
|
|
74
74
|
strategy: RoutingStrategy.UNKNOWN,
|
|
75
|
-
reason: 'No BTP destination provided (missing x-
|
|
75
|
+
reason: 'No BTP destination provided (missing x-sap-destination or --btp)',
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
|
-
// If x-
|
|
78
|
+
// If x-sap-destination is present, we can proxy with BTP authentication (URL will be obtained from service key)
|
|
79
79
|
// If x-mcp-url is provided, we can use it directly (for local testing)
|
|
80
80
|
logger_js_1.logger?.debug('Routing decision: PROXY', {
|
|
81
81
|
btpDestination: extractedBtpDestination,
|
package/package.json
CHANGED