@mcp-abap-adt/proxy 1.3.0 → 1.6.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 CHANGED
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.6.0] - 2026-06-04
11
+
12
+ ### Added
13
+ - **Environment-variable interpolation in config + `.env` support.** Config string values (and `--header` values) may now contain `${VAR}` / `${VAR:-default}` placeholders, resolved from `process.env` and an explicitly-pointed `.env` file. This makes `defaultHeaders` the per-user auth-injection point for ABAP credentials (`x-sap-login` / `x-sap-password`) on shared servers like `cloud-llm-hub` — **without** storing secrets as plaintext in the YAML. (#4, #5)
14
+ - `.env` source is explicit only: `envFile: <path>` in the config (resolved relative to the config file's directory) or `--env-file <path>` on the command line (overrides `envFile`). There is no `cwd` auto-discovery.
15
+ - Resolution priority: `process.env` → `.env` → `${VAR:-default}`. `process.env` wins over the `.env` file.
16
+ - **Fail-fast:** a `${VAR}` without a default that cannot be resolved aborts startup with an error naming the variable and the field it came from, so a request never silently goes out with an empty credential.
17
+ - `Authorization` remains proxy-managed (destination JWT) and cannot be set via `defaultHeaders`.
18
+
19
+ ## [1.5.0] - 2026-06-02
20
+
21
+ ### Added
22
+ - **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.
23
+
24
+ ### Changed
25
+ - 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`.
26
+
27
+ ## [1.4.0] - 2026-06-02
28
+
29
+ ### Added
30
+ - **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.
31
+
32
+ ### Fixed
33
+ - **`--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.
34
+ - **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.
35
+
36
+ ### Changed
37
+ - Bump `@mcp-abap-adt/auth-providers` `^1.0.5` → `^1.1.0`.
38
+
10
39
  ## [1.3.0] - 2026-05-28
11
40
 
12
41
  ### Added
package/README.md CHANGED
@@ -98,7 +98,7 @@ For detailed setup instructions for Cline and GitHub Copilot, see the **[Client
98
98
 
99
99
  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.
100
100
 
101
- Client-supplied headers always take precedence over defaults.
101
+ Precedence: client-supplied request headers always win over `defaultHeaders`. `Authorization` is the exception — it is always managed by the proxy (replaced with the destination JWT) and **cannot** be set via `defaultHeaders`.
102
102
 
103
103
  Via YAML config (`defaultHeaders` map):
104
104
  ```yaml
@@ -109,6 +109,28 @@ defaultHeaders:
109
109
  x-sap-client: "100"
110
110
  ```
111
111
 
112
+ **Per-user ABAP credentials.** `defaultHeaders` is the supported place to supply your own SAP login/password for on-premise / `NoAuthentication` destinations. The upstream `cloud-llm-hub` is a **shared** server with no default service user, so it expects each caller's own `x-sap-login` / `x-sap-password` on every request. Because the proxy runs **locally on each user's machine**, it carries *your* identity.
113
+
114
+ Do not hardcode secrets. Reference environment variables with `${VAR}` (or `${VAR:-default}`); values are resolved from `process.env` or an explicitly-pointed `.env` file:
115
+
116
+ ```yaml
117
+ btpDestination: mcp
118
+ envFile: secrets.env # resolved relative to this config file's dir
119
+ defaultHeaders:
120
+ x-sap-destination: S4HANA_E19
121
+ x-sap-login: ${SAP_USER}
122
+ x-sap-password: ${SAP_PASSWORD}
123
+ ```
124
+
125
+ `secrets.env` (user-local, `chmod 600`, never committed):
126
+
127
+ ```dotenv
128
+ SAP_USER=MY_SAP_USER
129
+ SAP_PASSWORD=my-sap-password
130
+ ```
131
+
132
+ Resolution order is `process.env` → `.env` → `${VAR:-default}`. `process.env` wins; an unresolved `${VAR}` without a default fails the proxy at startup. Override the `.env` path at launch with `--env-file <path>`. This closes both auth layers from one local config: the **service layer** (`Authorization: Bearer <JWT>`, from the destination service key) and the **ABAP layer** (`x-sap-login` / `x-sap-password`).
133
+
112
134
  Via CLI (`--header`, repeatable):
113
135
  ```bash
114
136
  mcp-abap-adt-proxy --btp=mcp --url=https://example.com \
@@ -56,9 +56,13 @@ Options:
56
56
  Useful for direct OData service testing or non-standard MCP URLs
57
57
  --config=<file>, -c Load configuration from YAML or JSON file
58
58
  Alternative to command-line parameters
59
+ --env-file=<path> Load a .env file for ${VAR} interpolation in config/headers
60
+ Overrides the config's "envFile" field
59
61
  --unsafe Enable file-based session storage (persists tokens to disk)
60
62
  --header key=value Add default header to every forwarded request (repeatable)
63
+ Values support ${VAR} / ${VAR:-default} interpolation
61
64
  Example: --header x-sap-destination=S4HANA
65
+ Example: --header x-sap-password=${SAP_PASSWORD}
62
66
  --help, -h Show this help message
63
67
  --version, -v Show version number
64
68
 
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
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAuBH,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,0BAA0B,CAAC;AAqBlC;;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;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;IAuE1B;;OAEG;YACW,eAAe;IAgG7B;;OAEG;YACW,kBAAkB;IA8ChC;;OAEG;YACW,cAAc;IA6T5B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAoBhC;AAGD,eAAe,qBAAqB,CAAC"}
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
- logger_js_1.logger?.error('Failed to initialize BTP proxy for eager auth', {
139
- error: error instanceof Error ? error.message : String(error),
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
- const jwtToken = await this.btpProxy.getJwtToken(destination);
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
  */
@@ -571,6 +659,9 @@ exports.default = McpAbapAdtProxyServer;
571
659
  * Print CLI usage and exit.
572
660
  */
573
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];
574
665
  const help = `Usage: mcp-abap-adt-proxy [options]
575
666
 
576
667
  Authorization proxy for MCP clients (Cline, Claude Code, etc.) that cannot
@@ -582,7 +673,8 @@ CONFIGURATION
582
673
 
583
674
  AUTHENTICATION (BTP / XSUAA)
584
675
  --btp <destination> BTP destination name. Service key is read from
585
- ~/.config/mcp-abap-adt/service-keys/<dest>.json
676
+ ${serviceKeyDir}${require('node:path').sep}<dest>.json
677
+ (override the base dir with AUTH_BROKER_PATH)
586
678
  --target-url <url> Override target service URL (default: from service key)
587
679
  --url <url> Alias for --target-url
588
680
  --unsafe Persist session/tokens to disk (default: in-memory)
@@ -595,12 +687,18 @@ BROWSER AUTHORIZATION
595
687
  chrome - Google Chrome
596
688
  edge - Microsoft Edge
597
689
  firefox - Firefox
598
- none - Print URL to console and wait for
599
- callback (for SSH/headless sessions)
690
+ none - Print URL to console; complete login
691
+ via callback OR by pasting the code
692
+ (for SSH/headless/remote sessions)
600
693
  headless - Alias for 'none'
601
694
  --browser-auth-port <port> Local OAuth callback port. Must match the
602
695
  redirect_uri registered on XSUAA (default: 3333).
603
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
+
604
702
  TRANSPORT
605
703
  --transport <type> stdio | streamable-http | http | sse
606
704
  (auto-detected from stdin TTY if not set)
@@ -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,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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,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,CAoC3D;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;AAkCD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAYhC;AAmJD;;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"}
@@ -44,6 +44,7 @@ exports.validateConfig = validateConfig;
44
44
  const fs = __importStar(require("node:fs"));
45
45
  const path = __importStar(require("node:path"));
46
46
  const yaml = __importStar(require("js-yaml"));
47
+ const envInterpolation_js_1 = require("./envInterpolation.js");
47
48
  /**
48
49
  * Load configuration from file if --config is provided, otherwise from environment variables and command line
49
50
  * Supports both JSON and YAML formats
@@ -64,7 +65,12 @@ function loadConfig(configPath) {
64
65
  throw new Error(`Config file not found: ${finalConfigPath}`);
65
66
  }
66
67
  const fileConfig = loadConfigFile(finalConfigPath);
67
- const base = applyDefaults(fileConfig);
68
+ const envFilePath = resolveEnvFilePath(fileConfig, finalConfigPath);
69
+ const envFileMap = envFilePath ? (0, envInterpolation_js_1.loadEnvFile)(envFilePath) : {};
70
+ const lookup = (0, envInterpolation_js_1.buildLookup)(envFileMap);
71
+ const interpolated = (0, envInterpolation_js_1.interpolateConfig)(fileConfig, lookup);
72
+ delete interpolated.envFile;
73
+ const base = applyDefaults(interpolated);
68
74
  const cli = readCliOverrides();
69
75
  return mergeCliOverrides(base, cli, finalConfigPath);
70
76
  }
@@ -155,6 +161,21 @@ function loadConfigFile(filePath) {
155
161
  return JSON.parse(configContent);
156
162
  }
157
163
  }
164
+ /**
165
+ * Resolve the .env path for the file-config path: --env-file wins (relative to
166
+ * cwd); otherwise the YAML `envFile` field, resolved relative to the config
167
+ * file's directory. Returns undefined when neither is set.
168
+ */
169
+ function resolveEnvFilePath(rawConfig, configPath) {
170
+ const cliEnvFile = getArgValue('--env-file');
171
+ if (cliEnvFile !== undefined)
172
+ return path.resolve(cliEnvFile);
173
+ const yamlEnvFile = rawConfig.envFile;
174
+ if (typeof yamlEnvFile === 'string') {
175
+ return path.resolve(path.dirname(configPath), yamlEnvFile);
176
+ }
177
+ return undefined;
178
+ }
158
179
  /**
159
180
  * Load raw config from file for transport field extraction
160
181
  */
@@ -210,17 +231,20 @@ function loadFromEnv() {
210
231
  const browserAuthPort = browserAuthPortStr
211
232
  ? parseInt(browserAuthPortStr, 10)
212
233
  : undefined;
213
- // Parse repeatable --header key=value arguments
234
+ // Parse repeatable --header key=value arguments, interpolating ${VAR}
214
235
  const headerArgs = getAllArgValues('--header');
215
236
  let defaultHeaders;
216
237
  if (headerArgs.length > 0) {
238
+ const cliEnvFile = getArgValue('--env-file');
239
+ const envFileMap = cliEnvFile ? (0, envInterpolation_js_1.loadEnvFile)(path.resolve(cliEnvFile)) : {};
240
+ const lookup = (0, envInterpolation_js_1.buildLookup)(envFileMap);
217
241
  defaultHeaders = {};
218
242
  for (const arg of headerArgs) {
219
243
  const eqIndex = arg.indexOf('=');
220
244
  if (eqIndex > 0) {
221
245
  const key = arg.substring(0, eqIndex).toLowerCase();
222
- const value = arg.substring(eqIndex + 1);
223
- defaultHeaders[key] = value;
246
+ const rawValue = arg.substring(eqIndex + 1);
247
+ defaultHeaders[key] = (0, envInterpolation_js_1.interpolateString)(rawValue, lookup, `--header ${key}`);
224
248
  }
225
249
  }
226
250
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Environment-variable interpolation for proxy configuration.
3
+ * Supports ${VAR} and ${VAR:-default} in string values.
4
+ */
5
+ /**
6
+ * Resolve ${VAR} / ${VAR:-default} placeholders in a single string.
7
+ * Empty-string follows bash `:-` semantics: an empty value falls back to the
8
+ * default when one is given. A ${VAR} without a default that resolves to
9
+ * undefined throws, naming the variable and the field it came from.
10
+ */
11
+ export declare function interpolateString(input: string, lookup: (key: string) => string | undefined, fieldPath: string): string;
12
+ /**
13
+ * Recursively interpolate all string values in a parsed config object.
14
+ * Objects/arrays are walked; non-string scalars are returned unchanged.
15
+ * The field path (e.g. `defaultHeaders.x-sap-password`) is threaded through for
16
+ * error messages.
17
+ */
18
+ export declare function interpolateConfig(value: unknown, lookup: (key: string) => string | undefined, path?: string): unknown;
19
+ /**
20
+ * Parse a .env file into a flat map. Throws if the path is given but missing —
21
+ * a specified-yet-absent secret source is a configuration error, not a no-op.
22
+ */
23
+ export declare function loadEnvFile(envFilePath: string): Record<string, string>;
24
+ /**
25
+ * Build a lookup over process.env (highest priority) then the parsed .env map.
26
+ */
27
+ export declare function buildLookup(envFileMap: Record<string, string>): (key: string) => string | undefined;
28
+ //# sourceMappingURL=envInterpolation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envInterpolation.d.ts","sourceRoot":"","sources":["../../src/lib/envInterpolation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,EAC3C,SAAS,EAAE,MAAM,GAChB,MAAM,CAiBR;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,EAC3C,IAAI,SAAK,GACR,OAAO,CAiBT;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAKvE;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAGrC"}
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ /**
3
+ * Environment-variable interpolation for proxy configuration.
4
+ * Supports ${VAR} and ${VAR:-default} in string values.
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.interpolateString = interpolateString;
41
+ exports.interpolateConfig = interpolateConfig;
42
+ exports.loadEnvFile = loadEnvFile;
43
+ exports.buildLookup = buildLookup;
44
+ const fs = __importStar(require("node:fs"));
45
+ const dotenv = __importStar(require("dotenv"));
46
+ const PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
47
+ /**
48
+ * Resolve ${VAR} / ${VAR:-default} placeholders in a single string.
49
+ * Empty-string follows bash `:-` semantics: an empty value falls back to the
50
+ * default when one is given. A ${VAR} without a default that resolves to
51
+ * undefined throws, naming the variable and the field it came from.
52
+ */
53
+ function interpolateString(input, lookup, fieldPath) {
54
+ return input.replace(PLACEHOLDER, (_match, name, defaultVal) => {
55
+ const value = lookup(name);
56
+ const isEmpty = value === undefined || value === '';
57
+ if (defaultVal !== undefined) {
58
+ return isEmpty ? defaultVal : value;
59
+ }
60
+ if (value === undefined) {
61
+ throw new Error(`Config references undefined env variable: ${name} (referenced in ${fieldPath})`);
62
+ }
63
+ return value;
64
+ });
65
+ }
66
+ /**
67
+ * Recursively interpolate all string values in a parsed config object.
68
+ * Objects/arrays are walked; non-string scalars are returned unchanged.
69
+ * The field path (e.g. `defaultHeaders.x-sap-password`) is threaded through for
70
+ * error messages.
71
+ */
72
+ function interpolateConfig(value, lookup, path = '') {
73
+ if (typeof value === 'string') {
74
+ return interpolateString(value, lookup, path || '(root)');
75
+ }
76
+ if (Array.isArray(value)) {
77
+ return value.map((item, i) => interpolateConfig(item, lookup, `${path}[${i}]`));
78
+ }
79
+ if (value !== null && typeof value === 'object') {
80
+ const out = {};
81
+ for (const [key, val] of Object.entries(value)) {
82
+ out[key] = interpolateConfig(val, lookup, path ? `${path}.${key}` : key);
83
+ }
84
+ return out;
85
+ }
86
+ return value;
87
+ }
88
+ /**
89
+ * Parse a .env file into a flat map. Throws if the path is given but missing —
90
+ * a specified-yet-absent secret source is a configuration error, not a no-op.
91
+ */
92
+ function loadEnvFile(envFilePath) {
93
+ if (!fs.existsSync(envFilePath)) {
94
+ throw new Error(`env file not found: ${envFilePath}`);
95
+ }
96
+ return dotenv.parse(fs.readFileSync(envFilePath, 'utf-8'));
97
+ }
98
+ /**
99
+ * Build a lookup over process.env (highest priority) then the parsed .env map.
100
+ */
101
+ function buildLookup(envFileMap) {
102
+ return (key) => process.env[key] ?? envFileMap[key];
103
+ }
@@ -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;IAuB3D;;;;OAIG;YACW,wBAAwB;IAqFtC;;OAEG;YACW,uBAAuB;IAyFrC;;;;OAIG;IACG,WAAW,CACf,WAAW,EAAE,MAAM,EACnB,YAAY,GAAE,OAAe,GAC5B,OAAO,CAAC,MAAM,CAAC;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;CAyB7E;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,QAAQ,CAAC,CAEnB"}
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"}
@@ -155,8 +155,9 @@ class BtpProxy {
155
155
  destination,
156
156
  error: error instanceof Error ? error.message : String(error),
157
157
  });
158
- // We don't throw here to avoid crashing the server on startup,
159
- // but the user will see the error in logs/stderr (via getJwtToken)
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: 'Ensure service key file exists in ~/.config/mcp-abap-adt/service-keys/',
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,
@@ -924,6 +930,7 @@ class BtpProxy {
924
930
  clientSecret: 'placeholder',
925
931
  browser: loadedConfig.browser,
926
932
  redirectPort: loadedConfig.browserAuthPort,
933
+ logger: loggerAdapter,
927
934
  }),
928
935
  }, 'none', loggerAdapter);
929
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.0",
3
+ "version": "1.6.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,14 +63,15 @@
63
63
  "typescript": "^6.0.3"
64
64
  },
65
65
  "dependencies": {
66
- "@mcp-abap-adt/auth-broker": "^1.0.5",
67
- "@mcp-abap-adt/auth-providers": "^1.0.5",
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",
71
71
  "@mcp-abap-adt/logger": "^0.1.4",
72
72
  "@modelcontextprotocol/sdk": "^1.26.0",
73
73
  "axios": "^1.15.1",
74
+ "dotenv": "^17.4.2",
74
75
  "js-yaml": "^4.1.1",
75
76
  "zod": "^4.3.6"
76
77
  },