@mcp-abap-adt/proxy 1.2.1 → 1.5.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 +30 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +184 -7
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +62 -14
- package/dist/proxy/btpProxy.d.ts.map +1 -1
- package/dist/proxy/btpProxy.js +12 -3
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.5.0] - 2026-06-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Exit on incomplete authentication.** When authentication is requested but not completed — at startup (eager auth) or on a request that needs a token — the proxy now logs a categorised reason (timeout / invalid credentials / service key / network) and exits with code 1, so it can be restarted to retry (mirroring the one-shot `mcp-auth` command) instead of running in a permanently unauthenticated state. A downstream/forward error (after auth succeeds) is still returned to the client without exiting.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Bump `@mcp-abap-adt/auth-broker` `^1.0.5` → `^1.0.6`: the broker no longer starts a redundant second browser login after an interactive session-login failure, so the real cause (e.g. login timeout) is reported instead of a misleading `Port <n> is already in use`.
|
|
17
|
+
|
|
18
|
+
## [1.4.0] - 2026-06-02
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **Manual paste login for `--browser none` / `headless`** (via `@mcp-abap-adt/auth-providers@1.1.0`). When the browser runs on a different machine than the proxy, login can be completed by opening the paste form at `http://<host>:<browser-auth-port>/` and pasting the `code` (or the whole redirected URL), or by pasting the code in the terminal — in addition to the existing automatic localhost callback.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **`--browser none` now prints the authorization URL.** The per-destination `AuthorizationCodeProvider` was created without a logger, so the "Open this URL" prompt from `browserAuth` was silently dropped and login could never be completed in headless mode. The proxy now passes its logger to the provider; `auth-providers@1.1.0` additionally falls back to `stderr` when no logger is supplied.
|
|
25
|
+
- **Platform-correct service-key path in `--help` and errors.** The "service key not found" hint and `--help` hard-coded the Unix `~/.config/...` path, which is wrong on Windows (`%USERPROFILE%\Documents\mcp-abap-adt\service-keys`). Both now show the real resolved directory and mention the `AUTH_BROKER_PATH` override.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- Bump `@mcp-abap-adt/auth-providers` `^1.0.5` → `^1.1.0`.
|
|
29
|
+
|
|
30
|
+
## [1.3.0] - 2026-05-28
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- **CLI flags override values from `--config`** — when a config file is loaded via `--config`/`-c`, explicit CLI flags (`--btp`, `--target-url`/`--url`, `--unsafe`, `--browser`, `--browser-auth-port`, `--header`) now override matching values from the file instead of being silently dropped. `--header` entries merge with `defaultHeaders` (CLI keys win on conflict). The proxy logs which keys were overridden so the precedence is visible.
|
|
34
|
+
- **`--help` / `-h`** — full CLI usage printer covering auth, browser, transport flags and the documented environment variables. Previously the proxy had no help output at all, making the `--browser none` / `--browser-auth-port` flags effectively undiscoverable.
|
|
35
|
+
- **Graceful shutdown on `SIGINT` / `SIGTERM`** — the proxy now wires both signals to `shutdown()`, which closes the MCP server and HTTP listener, clears pending JWT refresh timers, drops the token cache, and releases per-destination auth brokers. Previously these resources were only released on hard process exit.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- `BtpProxy.dispose()` now clears the token cache and the per-destination broker map in addition to refresh timers, so a clean shutdown actually releases all auth state.
|
|
39
|
+
|
|
10
40
|
## [1.2.1] - 2026-05-26
|
|
11
41
|
|
|
12
42
|
### Security
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare class McpAbapAdtProxyServer {
|
|
|
17
17
|
private config;
|
|
18
18
|
private httpServer?;
|
|
19
19
|
private btpProxy?;
|
|
20
|
+
private authExiting;
|
|
20
21
|
constructor(transportConfig?: TransportConfig, configPath?: string);
|
|
21
22
|
/**
|
|
22
23
|
* Setup MCP handlers
|
|
@@ -38,6 +39,13 @@ export declare class McpAbapAdtProxyServer {
|
|
|
38
39
|
* Start SSE server
|
|
39
40
|
*/
|
|
40
41
|
private startSseServer;
|
|
42
|
+
/**
|
|
43
|
+
* Authentication was requested but could not be completed. Report the reason
|
|
44
|
+
* clearly, release resources, and exit(1) so the proxy is restarted (and the
|
|
45
|
+
* user gets a fresh login prompt) instead of running unauthenticated. Mirrors
|
|
46
|
+
* the one-shot `mcp-auth` command, which exits on a failed/incomplete login.
|
|
47
|
+
*/
|
|
48
|
+
private fatalAuthFailure;
|
|
41
49
|
/**
|
|
42
50
|
* Shutdown server
|
|
43
51
|
*/
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAwBH,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,0BAA0B,CAAC;AAgElC;;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,QAAQ,CAAC,CAAW;IAC5B,OAAO,CAAC,WAAW,CAAS;gBAEhB,eAAe,CAAC,EAAE,eAAe,EAAE,UAAU,CAAC,EAAE,MAAM;IAoDlE;;OAEG;IACH,OAAO,CAAC,aAAa;IAMrB;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA2E1B;;OAEG;YACW,eAAe;IAgG7B;;OAEG;YACW,kBAAkB;IAgEhC;;OAEG;YACW,cAAc;IA6T5B;;;;;OAKG;YACW,gBAAgB;IAiC9B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAoBhC;AAGD,eAAe,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
|
18
18
|
const reverseProxy_js_1 = require("./proxy/reverseProxy.js");
|
|
19
19
|
const config_js_1 = require("./lib/config.js");
|
|
20
20
|
const logger_js_1 = require("./lib/logger.js");
|
|
21
|
+
const stores_js_1 = require("./lib/stores.js");
|
|
21
22
|
const transportConfig_js_1 = require("./lib/transportConfig.js");
|
|
22
23
|
const btpProxy_js_1 = require("./proxy/btpProxy.js");
|
|
23
24
|
const headerAnalyzer_js_1 = require("./router/headerAnalyzer.js");
|
|
@@ -31,6 +32,38 @@ function getBodyId(body) {
|
|
|
31
32
|
}
|
|
32
33
|
return null;
|
|
33
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Classify an authentication failure into a human-readable reason so the proxy
|
|
37
|
+
* can report *why* it is exiting (timeout vs bad credentials vs network ...).
|
|
38
|
+
*/
|
|
39
|
+
function classifyAuthFailure(error) {
|
|
40
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
41
|
+
if (/timeout/i.test(msg)) {
|
|
42
|
+
return {
|
|
43
|
+
category: 'timeout',
|
|
44
|
+
reason: 'the login URL was not completed in time',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (/service key|missing required fields/i.test(msg)) {
|
|
48
|
+
return {
|
|
49
|
+
category: 'service-key',
|
|
50
|
+
reason: 'service key missing or incomplete',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (/invalid_client|unauthorized|\b401\b|invalid credentials/i.test(msg)) {
|
|
54
|
+
return {
|
|
55
|
+
category: 'credentials',
|
|
56
|
+
reason: 'invalid credentials — UAA rejected the client',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (/ENOTFOUND|ECONNREFUSED|ETIMEDOUT|network|ENETUNREACH/i.test(msg)) {
|
|
60
|
+
return {
|
|
61
|
+
category: 'network',
|
|
62
|
+
reason: 'network error reaching the authentication server',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return { category: 'other', reason: msg };
|
|
66
|
+
}
|
|
34
67
|
/**
|
|
35
68
|
* MCP ABAP ADT Proxy Server
|
|
36
69
|
*/
|
|
@@ -40,6 +73,7 @@ class McpAbapAdtProxyServer {
|
|
|
40
73
|
config;
|
|
41
74
|
httpServer;
|
|
42
75
|
btpProxy;
|
|
76
|
+
authExiting = false;
|
|
43
77
|
constructor(transportConfig, configPath) {
|
|
44
78
|
// Load config first to extract transport overrides from YAML/JSON file
|
|
45
79
|
const resolvedConfigPath = configPath || (0, config_js_1.getConfigPath)();
|
|
@@ -125,20 +159,24 @@ Authorization source: BTP destination "${this.config.btpDestination}"`;
|
|
|
125
159
|
});
|
|
126
160
|
return;
|
|
127
161
|
}
|
|
128
|
-
// Eager initialization of BTP authentication
|
|
162
|
+
// Eager initialization of BTP authentication. Kept in the background so the
|
|
163
|
+
// server starts listening immediately (and, for --browser none, the auth URL
|
|
164
|
+
// is printed right away). If auth is requested but never completed, exit(1)
|
|
165
|
+
// so the proxy is restarted and the user gets a fresh login prompt.
|
|
129
166
|
if (this.config.btpDestination) {
|
|
167
|
+
const dest = this.config.btpDestination;
|
|
130
168
|
try {
|
|
131
169
|
if (!this.btpProxy) {
|
|
132
170
|
this.btpProxy = await (0, btpProxy_js_1.createBtpProxy)(this.config);
|
|
133
171
|
}
|
|
134
|
-
// Trigger initialization (background) - this will open the browser
|
|
135
|
-
this.btpProxy.initialize(this.config.btpDestination);
|
|
136
172
|
}
|
|
137
173
|
catch (error) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
});
|
|
174
|
+
await this.fatalAuthFailure(error, dest, 'startup');
|
|
175
|
+
return;
|
|
141
176
|
}
|
|
177
|
+
this.btpProxy
|
|
178
|
+
.initialize(dest)
|
|
179
|
+
.catch((error) => this.fatalAuthFailure(error, dest, 'startup'));
|
|
142
180
|
}
|
|
143
181
|
if (this.transportConfig.type === 'streamable-http') {
|
|
144
182
|
await this.startHttpServer();
|
|
@@ -246,9 +284,28 @@ Authorization source: ${authSourceObj}`;
|
|
|
246
284
|
if (!this.btpProxy) {
|
|
247
285
|
this.btpProxy = await (0, btpProxy_js_1.createBtpProxy)(this.config);
|
|
248
286
|
}
|
|
287
|
+
// Authentication is separated from forwarding: an auth failure is fatal
|
|
288
|
+
// (the proxy exits so it can be restarted), while a downstream/forward error
|
|
289
|
+
// is returned to the client without killing the proxy.
|
|
290
|
+
let jwtToken;
|
|
249
291
|
try {
|
|
250
292
|
// Get JWT token (cached, auto-refresh)
|
|
251
|
-
|
|
293
|
+
jwtToken = await this.btpProxy.getJwtToken(destination);
|
|
294
|
+
}
|
|
295
|
+
catch (authError) {
|
|
296
|
+
logger_js_1.logger?.error('Proxy request failed: authentication error', {
|
|
297
|
+
type: 'PROXY_REQUEST_AUTH_ERROR',
|
|
298
|
+
destination,
|
|
299
|
+
error: authError instanceof Error ? authError.message : String(authError),
|
|
300
|
+
});
|
|
301
|
+
if (!res.headersSent) {
|
|
302
|
+
res.writeHead(502, { 'Content-Type': 'application/json' });
|
|
303
|
+
res.end(JSON.stringify({ error: 'Authentication failed' }));
|
|
304
|
+
}
|
|
305
|
+
await this.fatalAuthFailure(authError, destination, 'request');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
252
309
|
// Get target URL
|
|
253
310
|
const targetUrl = intercepted.routingDecision.targetUrl ||
|
|
254
311
|
(await this.btpProxy.getTargetUrl(destination));
|
|
@@ -540,6 +597,37 @@ Authorization source: ${authSourceObj}`;
|
|
|
540
597
|
});
|
|
541
598
|
});
|
|
542
599
|
}
|
|
600
|
+
/**
|
|
601
|
+
* Authentication was requested but could not be completed. Report the reason
|
|
602
|
+
* clearly, release resources, and exit(1) so the proxy is restarted (and the
|
|
603
|
+
* user gets a fresh login prompt) instead of running unauthenticated. Mirrors
|
|
604
|
+
* the one-shot `mcp-auth` command, which exits on a failed/incomplete login.
|
|
605
|
+
*/
|
|
606
|
+
async fatalAuthFailure(error, destination, phase) {
|
|
607
|
+
if (this.authExiting)
|
|
608
|
+
return;
|
|
609
|
+
this.authExiting = true;
|
|
610
|
+
const { category, reason } = classifyAuthFailure(error);
|
|
611
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
612
|
+
logger_js_1.logger?.error('Authentication not completed — exiting', {
|
|
613
|
+
type: 'AUTH_FATAL_EXIT',
|
|
614
|
+
destination,
|
|
615
|
+
phase,
|
|
616
|
+
category,
|
|
617
|
+
reason,
|
|
618
|
+
error: detail,
|
|
619
|
+
});
|
|
620
|
+
console.error(`[MCP Proxy] ✗ Authentication failed for destination "${destination}" (${phase}): ${reason}.\n` +
|
|
621
|
+
`[MCP Proxy] ${detail}\n` +
|
|
622
|
+
`[MCP Proxy] Exiting (code 1) so the proxy can be restarted to retry authentication.`);
|
|
623
|
+
try {
|
|
624
|
+
await this.shutdown();
|
|
625
|
+
}
|
|
626
|
+
catch {
|
|
627
|
+
// best-effort cleanup; exit regardless
|
|
628
|
+
}
|
|
629
|
+
process.exit(1);
|
|
630
|
+
}
|
|
543
631
|
/**
|
|
544
632
|
* Shutdown server
|
|
545
633
|
*/
|
|
@@ -567,9 +655,98 @@ Authorization source: ${authSourceObj}`;
|
|
|
567
655
|
exports.McpAbapAdtProxyServer = McpAbapAdtProxyServer;
|
|
568
656
|
// Export for use in bin script
|
|
569
657
|
exports.default = McpAbapAdtProxyServer;
|
|
658
|
+
/**
|
|
659
|
+
* Print CLI usage and exit.
|
|
660
|
+
*/
|
|
661
|
+
function printHelp() {
|
|
662
|
+
// Show the real, platform-specific directory where service keys are read from
|
|
663
|
+
// (Windows: %USERPROFILE%\Documents\mcp-abap-adt\service-keys; Unix: ~/.config/...)
|
|
664
|
+
const serviceKeyDir = (0, stores_js_1.getPlatformPaths)('service-keys')[0];
|
|
665
|
+
const help = `Usage: mcp-abap-adt-proxy [options]
|
|
666
|
+
|
|
667
|
+
Authorization proxy for MCP clients (Cline, Claude Code, etc.) that cannot
|
|
668
|
+
authenticate on their own to MCP servers deployed on SAP BTP.
|
|
669
|
+
|
|
670
|
+
CONFIGURATION
|
|
671
|
+
-c, --config <path> Load config from YAML/JSON file. CLI flags below
|
|
672
|
+
override values from the file.
|
|
673
|
+
|
|
674
|
+
AUTHENTICATION (BTP / XSUAA)
|
|
675
|
+
--btp <destination> BTP destination name. Service key is read from
|
|
676
|
+
${serviceKeyDir}${require('node:path').sep}<dest>.json
|
|
677
|
+
(override the base dir with AUTH_BROKER_PATH)
|
|
678
|
+
--target-url <url> Override target service URL (default: from service key)
|
|
679
|
+
--url <url> Alias for --target-url
|
|
680
|
+
--unsafe Persist session/tokens to disk (default: in-memory)
|
|
681
|
+
--header <key=value> Inject a default header into every forwarded
|
|
682
|
+
request. Repeatable. Client-supplied headers win.
|
|
683
|
+
|
|
684
|
+
BROWSER AUTHORIZATION
|
|
685
|
+
--browser <mode> Browser used for OAuth login. One of:
|
|
686
|
+
system - OS default browser (default)
|
|
687
|
+
chrome - Google Chrome
|
|
688
|
+
edge - Microsoft Edge
|
|
689
|
+
firefox - Firefox
|
|
690
|
+
none - Print URL to console; complete login
|
|
691
|
+
via callback OR by pasting the code
|
|
692
|
+
(for SSH/headless/remote sessions)
|
|
693
|
+
headless - Alias for 'none'
|
|
694
|
+
--browser-auth-port <port> Local OAuth callback port. Must match the
|
|
695
|
+
redirect_uri registered on XSUAA (default: 3333).
|
|
696
|
+
|
|
697
|
+
Run without auto-opening a browser (prints the URL, then completes via the
|
|
698
|
+
localhost callback, the paste form at http://<host>:<port>/, or by pasting
|
|
699
|
+
the code in the terminal):
|
|
700
|
+
mcp-abap-adt-proxy --btp <dest> --browser none --browser-auth-port 3333
|
|
701
|
+
|
|
702
|
+
TRANSPORT
|
|
703
|
+
--transport <type> stdio | streamable-http | http | sse
|
|
704
|
+
(auto-detected from stdin TTY if not set)
|
|
705
|
+
--stdio Shortcut for --transport stdio
|
|
706
|
+
--http Shortcut for --transport streamable-http
|
|
707
|
+
--sse Shortcut for --transport sse
|
|
708
|
+
--http-port <port> HTTP transport port (default: 3001)
|
|
709
|
+
--http-host <host> HTTP transport host (default: 127.0.0.1)
|
|
710
|
+
--sse-port <port> SSE transport port (default: 3002)
|
|
711
|
+
--sse-host <host> SSE transport host (default: 127.0.0.1)
|
|
712
|
+
|
|
713
|
+
-h, --help Show this help and exit
|
|
714
|
+
|
|
715
|
+
ENVIRONMENT VARIABLES
|
|
716
|
+
MCP_TRANSPORT, MCP_HTTP_PORT, MCP_HTTP_HOST, MCP_SSE_PORT, MCP_SSE_HOST,
|
|
717
|
+
LOG_LEVEL, MCP_PROXY_UNSAFE, MCP_PROXY_MAX_RETRIES, MCP_PROXY_RETRY_DELAY,
|
|
718
|
+
MCP_PROXY_REQUEST_TIMEOUT, MCP_PROXY_CIRCUIT_BREAKER_THRESHOLD,
|
|
719
|
+
MCP_PROXY_CIRCUIT_BREAKER_TIMEOUT
|
|
720
|
+
|
|
721
|
+
See docs/CONFIGURATION.md for the full configuration reference.
|
|
722
|
+
`;
|
|
723
|
+
process.stdout.write(help);
|
|
724
|
+
}
|
|
570
725
|
// Auto-start server when run directly (unless MCP_SKIP_AUTO_START is set)
|
|
571
726
|
if (process.env.MCP_SKIP_AUTO_START !== 'true') {
|
|
727
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
728
|
+
printHelp();
|
|
729
|
+
process.exit(0);
|
|
730
|
+
}
|
|
572
731
|
const server = new McpAbapAdtProxyServer();
|
|
732
|
+
// Graceful shutdown: release the OAuth callback port, MCP server, and HTTP listener.
|
|
733
|
+
let shuttingDown = false;
|
|
734
|
+
const onSignal = (signal) => {
|
|
735
|
+
if (shuttingDown)
|
|
736
|
+
return;
|
|
737
|
+
shuttingDown = true;
|
|
738
|
+
logger_js_1.logger?.info('Received shutdown signal, releasing resources', { signal });
|
|
739
|
+
server
|
|
740
|
+
.shutdown()
|
|
741
|
+
.catch((error) => {
|
|
742
|
+
logger_js_1.logger?.error('Error during shutdown', {
|
|
743
|
+
error: error instanceof Error ? error.message : String(error),
|
|
744
|
+
});
|
|
745
|
+
})
|
|
746
|
+
.finally(() => process.exit(0));
|
|
747
|
+
};
|
|
748
|
+
process.on('SIGINT', onSignal);
|
|
749
|
+
process.on('SIGTERM', onSignal);
|
|
573
750
|
server.run().catch((error) => {
|
|
574
751
|
logger_js_1.logger?.error('Fatal error while running MCP proxy server', {
|
|
575
752
|
type: 'SERVER_FATAL_ERROR',
|
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;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,
|
|
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,CAyB3D;AAwED;;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
|
@@ -56,24 +56,17 @@ function loadConfig(configPath) {
|
|
|
56
56
|
// Get config path from parameter or command line (--config/-c)
|
|
57
57
|
// Do NOT use environment variable MCP_PROXY_CONFIG - only explicit --config parameter
|
|
58
58
|
const finalConfigPath = configPath || getConfigPath();
|
|
59
|
-
// If --config is provided
|
|
59
|
+
// If --config is provided: load from file, apply defaults, then overlay explicit CLI flags.
|
|
60
|
+
// CLI flags override YAML values (YAML is just the baseline).
|
|
60
61
|
if (finalConfigPath) {
|
|
61
|
-
// Warn if other CLI parameters are also provided (they will be ignored)
|
|
62
|
-
const conflictingParams = ['--btp', '--unsafe'].filter((param) => hasArg(param));
|
|
63
|
-
if (conflictingParams.length > 0) {
|
|
64
|
-
console.warn(`Warning: --config is specified, but the following CLI parameters will be ignored: ${conflictingParams.join(', ')}. ` +
|
|
65
|
-
`Configuration will be loaded ONLY from ${finalConfigPath}. ` +
|
|
66
|
-
`To use CLI parameters, do not specify --config.`);
|
|
67
|
-
}
|
|
68
62
|
try {
|
|
69
|
-
if (fs.existsSync(finalConfigPath)) {
|
|
70
|
-
const fileConfig = loadConfigFile(finalConfigPath);
|
|
71
|
-
// Apply defaults for missing values in file config
|
|
72
|
-
return applyDefaults(fileConfig);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
63
|
+
if (!fs.existsSync(finalConfigPath)) {
|
|
75
64
|
throw new Error(`Config file not found: ${finalConfigPath}`);
|
|
76
65
|
}
|
|
66
|
+
const fileConfig = loadConfigFile(finalConfigPath);
|
|
67
|
+
const base = applyDefaults(fileConfig);
|
|
68
|
+
const cli = readCliOverrides();
|
|
69
|
+
return mergeCliOverrides(base, cli, finalConfigPath);
|
|
77
70
|
}
|
|
78
71
|
catch (error) {
|
|
79
72
|
throw new Error(`Failed to load config from file ${finalConfigPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -82,6 +75,61 @@ function loadConfig(configPath) {
|
|
|
82
75
|
// No --config provided: load ONLY from command line parameters and environment variables
|
|
83
76
|
return loadFromEnv();
|
|
84
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Read CLI flags that should override file/YAML configuration.
|
|
80
|
+
* Only includes keys that were explicitly provided on the command line.
|
|
81
|
+
*/
|
|
82
|
+
function readCliOverrides() {
|
|
83
|
+
const overrides = {};
|
|
84
|
+
const btp = getArgValue('--btp');
|
|
85
|
+
if (btp !== undefined)
|
|
86
|
+
overrides.btpDestination = btp;
|
|
87
|
+
const targetUrl = getArgValue('--target-url') ?? getArgValue('--url');
|
|
88
|
+
if (targetUrl !== undefined)
|
|
89
|
+
overrides.targetUrl = targetUrl;
|
|
90
|
+
if (hasArg('--unsafe'))
|
|
91
|
+
overrides.unsafe = true;
|
|
92
|
+
const browser = getArgValue('--browser');
|
|
93
|
+
if (browser !== undefined) {
|
|
94
|
+
overrides.browser = browser;
|
|
95
|
+
}
|
|
96
|
+
const browserAuthPortStr = getArgValue('--browser-auth-port');
|
|
97
|
+
if (browserAuthPortStr !== undefined) {
|
|
98
|
+
overrides.browserAuthPort = parseInt(browserAuthPortStr, 10);
|
|
99
|
+
}
|
|
100
|
+
const headerArgs = getAllArgValues('--header');
|
|
101
|
+
if (headerArgs.length > 0) {
|
|
102
|
+
const headers = {};
|
|
103
|
+
for (const arg of headerArgs) {
|
|
104
|
+
const eqIndex = arg.indexOf('=');
|
|
105
|
+
if (eqIndex > 0) {
|
|
106
|
+
const key = arg.substring(0, eqIndex).toLowerCase();
|
|
107
|
+
const value = arg.substring(eqIndex + 1);
|
|
108
|
+
headers[key] = value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
overrides.defaultHeaders = headers;
|
|
112
|
+
}
|
|
113
|
+
return overrides;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Merge CLI overrides on top of file-based config. Logs which keys were overridden.
|
|
117
|
+
* `defaultHeaders` is merged per-key (CLI keys override YAML keys); other fields are replaced.
|
|
118
|
+
*/
|
|
119
|
+
function mergeCliOverrides(base, cli, configPath) {
|
|
120
|
+
const merged = { ...base, ...cli };
|
|
121
|
+
if (cli.defaultHeaders) {
|
|
122
|
+
merged.defaultHeaders = {
|
|
123
|
+
...(base.defaultHeaders || {}),
|
|
124
|
+
...cli.defaultHeaders,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const overriddenKeys = Object.keys(cli);
|
|
128
|
+
if (overriddenKeys.length > 0) {
|
|
129
|
+
console.warn(`Note: CLI flags override values from ${configPath}: ${overriddenKeys.join(', ')}`);
|
|
130
|
+
}
|
|
131
|
+
return merged;
|
|
132
|
+
}
|
|
85
133
|
/**
|
|
86
134
|
* Extract transport-related fields from a raw config object
|
|
87
135
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"btpProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/btpProxy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAgB,MAAM,2BAA2B,CAAC;AAYrE,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA4CnE;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAU3C;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAGD,MAAM,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,oBAAoB,CAAa;IACzC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,UAAU,CACN;IACZ,OAAO,CAAC,aAAa,CAA0C;IAC/D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAU;gBAEZ,oBAAoB,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IAqF3E;;;OAGG;IACU,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"btpProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/btpProxy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAgB,MAAM,2BAA2B,CAAC;AAYrE,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA4CnE;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAU3C;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAGD,MAAM,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,oBAAoB,CAAa;IACzC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,UAAU,CACN;IACZ,OAAO,CAAC,aAAa,CAA0C;IAC/D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAU;gBAEZ,oBAAoB,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IAqF3E;;;OAGG;IACU,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB3D;;;;OAIG;YACW,wBAAwB;IA0FtC;;OAEG;YACW,uBAAuB;IAyFrC;;;;OAIG;IACG,WAAW,CACf,WAAW,EAAE,MAAM,EACnB,YAAY,GAAE,OAAe,GAC5B,OAAO,CAAC,MAAM,CAAC;IAiHlB;;OAEG;IACH,OAAO,CAAC,YAAY;IAapB;;;OAGG;IACH,OAAO,CAAC,UAAU;IASlB;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA6BhC;;OAEG;YACW,gBAAgB;IAU9B;;OAEG;IACI,OAAO,IAAI,IAAI;IAStB;;;OAGG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAexD;;OAEG;IACH,OAAO,CAAC,cAAc;IAUtB;;;;;;;;;;;;;;;;OAgBG;YACW,iBAAiB;IAmT/B;;OAEG;IACG,YAAY,CAChB,eAAe,EAAE,YAAY,EAC7B,eAAe,EAAE,eAAe,EAChC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAC7D,OAAO,CAAC,aAAa,CAAC;IAiLzB;;OAEG;WACiB,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;CA0B7E;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,QAAQ,CAAC,CAEnB"}
|
package/dist/proxy/btpProxy.js
CHANGED
|
@@ -155,8 +155,9 @@ class BtpProxy {
|
|
|
155
155
|
destination,
|
|
156
156
|
error: error instanceof Error ? error.message : String(error),
|
|
157
157
|
});
|
|
158
|
-
//
|
|
159
|
-
//
|
|
158
|
+
// Re-throw so the server can decide to exit (auth requested but not
|
|
159
|
+
// completed) instead of running in a permanently unauthenticated state.
|
|
160
|
+
throw error;
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
/**
|
|
@@ -199,9 +200,10 @@ class BtpProxy {
|
|
|
199
200
|
});
|
|
200
201
|
}
|
|
201
202
|
if (!authConfig) {
|
|
203
|
+
const serviceKeyDir = (0, stores_js_1.getPlatformPaths)('service-keys')[0];
|
|
202
204
|
logger_js_1.logger?.error('Service key not found for destination', {
|
|
203
205
|
destination,
|
|
204
|
-
hint:
|
|
206
|
+
hint: `Ensure service key file "${destination}.json" exists in ${serviceKeyDir} (override the base dir with AUTH_BROKER_PATH)`,
|
|
205
207
|
});
|
|
206
208
|
// We cannot proceed without config, but we'll let it fail with a clear message
|
|
207
209
|
// Or we could throw here.
|
|
@@ -213,6 +215,10 @@ class BtpProxy {
|
|
|
213
215
|
const providerConfig = {
|
|
214
216
|
browser: this.config.browser,
|
|
215
217
|
redirectPort: this.config.browserAuthPort || 3333,
|
|
218
|
+
// Pass a logger so browserAuth can surface the "Open this URL" prompt in
|
|
219
|
+
// 'none'/'headless' mode. Without it, startBrowserAuth gets a null logger
|
|
220
|
+
// and silently drops the authorization URL.
|
|
221
|
+
logger: loggerAdapter,
|
|
216
222
|
...(authConfig
|
|
217
223
|
? {
|
|
218
224
|
uaaUrl: authConfig.uaaUrl,
|
|
@@ -474,6 +480,8 @@ class BtpProxy {
|
|
|
474
480
|
clearTimeout(timer);
|
|
475
481
|
}
|
|
476
482
|
this.refreshTimers.clear();
|
|
483
|
+
this.tokenCache.clear();
|
|
484
|
+
this.btpAuthBrokers.clear();
|
|
477
485
|
}
|
|
478
486
|
/**
|
|
479
487
|
* Get the target service URL for a destination.
|
|
@@ -922,6 +930,7 @@ class BtpProxy {
|
|
|
922
930
|
clientSecret: 'placeholder',
|
|
923
931
|
browser: loadedConfig.browser,
|
|
924
932
|
redirectPort: loadedConfig.browserAuthPort,
|
|
933
|
+
logger: loggerAdapter,
|
|
925
934
|
}),
|
|
926
935
|
}, 'none', loggerAdapter);
|
|
927
936
|
return new BtpProxy(defaultBtpAuthBroker, loadedConfig);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/proxy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.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",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"typescript": "^6.0.3"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@mcp-abap-adt/auth-broker": "^1.0.
|
|
67
|
-
"@mcp-abap-adt/auth-providers": "^1.0
|
|
66
|
+
"@mcp-abap-adt/auth-broker": "^1.0.6",
|
|
67
|
+
"@mcp-abap-adt/auth-providers": "^1.1.0",
|
|
68
68
|
"@mcp-abap-adt/auth-stores": "^1.0.4",
|
|
69
69
|
"@mcp-abap-adt/header-validator": "^0.1.8",
|
|
70
70
|
"@mcp-abap-adt/interfaces": "^7.0.0",
|