@mcp-abap-adt/auth-broker 1.0.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,89 @@ Thank you to all contributors! See [CONTRIBUTORS.md](CONTRIBUTORS.md) for the co
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [2.0.0] - 2026-08-02
15
+
16
+ ### Breaking Changes
17
+
18
+ **⚠️ The default OAuth callback port for `mcp-auth` and `mcp-sso` moves from `3001` to
19
+ whatever `@mcp-abap-adt/auth-providers` supplies (currently `61001`).** `3001` was never a
20
+ default this package chose: neither CLI listens on a port of its own — `auth-broker` is a
21
+ library plus these two CLIs, not a server — so `3001` only ever arrived by accident, carried in
22
+ from inside the token-provider library's old internal default. That default was itself moved,
23
+ deliberately, in `auth-providers` 2.0.0, to sit above the ephemeral port range and clear of the
24
+ `3001`/`3333` range application servers and proxies typically use. **If you registered
25
+ `http://localhost:3001/callback` (or any other specific port) with your identity provider, pass
26
+ `--redirect-port <that-port>` explicitly from now on** — omitting it no longer lands on `3001`.
27
+
28
+ - Requires `@mcp-abap-adt/auth-providers` `^2.0.0` (was `^1.2.0`). Every provider config's
29
+ `browser`/`redirectPort` fields are gone; how a login is conducted is now an
30
+ `IAuthorizationStrategy` passed as `authorization`. `AuthBroker` itself never imported those
31
+ fields — the exposure was entirely in the `mcp-auth`/`mcp-sso` CLIs and
32
+ `generate-env-from-service-key` (`bin/`), which construct providers directly.
33
+
34
+ ### Changed
35
+ - **`mcp-auth`, `mcp-sso`, `npm run generate-env`**: every browser-opening flow (`mcp-auth`'s
36
+ default `authorization_code` flow, `mcp-sso oidc --flow browser`, `mcp-sso saml2`/`bearer`
37
+ with the default browser assertion flow) now routes `--browser` and `--redirect-port` into
38
+ `browserCallbackStrategy` / `oidcCallbackStrategy` / `samlCallbackStrategy` instead of a
39
+ removed provider field. `--redirect-port` still overrides the port when given; see Breaking
40
+ Changes above for what happens when it is omitted.
41
+
42
+ - **Login timeout for these three CLIs raised from 30s to 5 minutes.** 30s was the token
43
+ provider's own default, sized for an unattended caller. These CLIs hand the login to a person
44
+ at a keyboard, who needs time to reach the browser, authenticate — possibly through SSO/MFA —
45
+ and get redirected back.
46
+
47
+ - `mcp-sso oidc --flow browser --code <value>`: manual/OOB code paste now goes through the
48
+ library's `staticCodeStrategy` (adapted with `asOidcResult`) instead of a removed
49
+ `authorizationCode` field. Behaviour for this combination is unchanged.
50
+
51
+ - `--redirect-uri` now only takes effect on the manual/static (OOB) code path. Combining it
52
+ with the listening browser flow is no longer meaningful: the local callback server always
53
+ derives its own redirect URI from `--redirect-port`.
54
+
55
+ - OIDC `device`/`password`/`token_exchange` flows never accepted `--browser`/`--redirect-port`
56
+ in the underlying library, before or after this change — `mcp-sso` was building them into the
57
+ config regardless, where they were silently ignored (masked by a cast, see below). They are
58
+ no longer passed at all; passing them for these flows now has visibly no effect, same as it
59
+ always did.
60
+
61
+ - `mcp-sso`'s per-flow config builders now construct the real `@mcp-abap-adt/auth-providers`
62
+ config types directly (`OidcBrowserProviderConfig`, `Saml2BearerProviderConfig`, etc.)
63
+ instead of building an untyped bag and force-casting it with `as unknown as X`. That cast is
64
+ exactly what let the `browser`/`redirectPort` regression above compile silently. Required
65
+ fields (`--client-id`, `--idp-sso-url`, `--sp-entity-id`, `--subject-token`,
66
+ `--username`/`--password`) are now checked and reported per flow instead of reaching the
67
+ provider as `undefined` behind the cast.
68
+
69
+ ### Fixed
70
+ - **`mcp-sso --config <path.json>` now reaches the strategy-building code at all.** Until this
71
+ fix, `buildProviderConfig` only built a config when `--protocol`/`--flow` were given on the
72
+ command line; a run driven by `--config` alone (the documented, supported way to use this flag)
73
+ left the file's fields completely untouched, and they reached `SsoProviderFactory.create()`
74
+ exactly as written — including a `browser`, `redirectPort`, `authorizationCode`, or
75
+ `assertionFlow` a 2.0.0 provider no longer accepts directly, with no error and no warning. The
76
+ merge between the file and CLI flags now happens once, before any validation or strategy
77
+ building, so `--config` alone, `--protocol`/`--flow` alone, and any combination of the two all
78
+ go through the same code. `--protocol`/`--flow`/CLI option flags still override the
79
+ corresponding value from the file. Every field a pre-2.0.0 config could carry either converts
80
+ onto the strategy (`browser`, `redirectPort`, `authorizationCode`, `assertionFlow`) or the run
81
+ is refused with a message naming the replacement (`authorizationCodeProvider`,
82
+ `assertionProvider`, `manualInput` — all function-valued, so a JSON file could never carry a
83
+ working one regardless).
84
+ - Relatedly, required-field validation (`--client-id`, `--idp-sso-url`, etc.) now runs against
85
+ the *merged* result. Previously it ran only against CLI options before the file was merged in,
86
+ so `--config file-with-credentials.json --protocol oidc --flow browser --redirect-port 3001`
87
+ demanded credentials that were already in the file.
88
+ - `mcp-sso saml2`/`bearer --assertion-flow manual`, and an `--assertion-flow assertion` with no
89
+ `--assertion` value supplied, now announce the authorization URL before prompting for a pasted
90
+ `SAMLResponse` (via `manualSamlResponseStrategy`). The old fallback for the latter combination
91
+ prompted without ever showing the URL the user needed to visit.
92
+
93
+ ### Notes
94
+ - `AuthBroker`'s own constructor and public API are unchanged; this is a realignment with the
95
+ provider library plus the CLI-level fixes above.
96
+
14
97
  ## [1.0.8] - 2026-07-29
15
98
 
16
99
  ### Changed
package/README.md CHANGED
@@ -26,13 +26,16 @@ AuthBroker requires a token provider configured for the destination:
26
26
 
27
27
  ```typescript
28
28
  import { AuthBroker, AbapSessionStore } from '@mcp-abap-adt/auth-broker';
29
- import { AuthorizationCodeProvider } from '@mcp-abap-adt/auth-providers';
29
+ import {
30
+ AuthorizationCodeProvider,
31
+ browserCallbackStrategy,
32
+ } from '@mcp-abap-adt/auth-providers';
30
33
 
31
34
  const tokenProvider = new AuthorizationCodeProvider({
32
35
  uaaUrl: 'https://...authentication...hana.ondemand.com',
33
36
  clientId: '...',
34
37
  clientSecret: '...',
35
- browser: 'system',
38
+ authorization: browserCallbackStrategy({ browser: 'system' }),
36
39
  });
37
40
 
38
41
  const broker = new AuthBroker({
@@ -53,7 +56,10 @@ import {
53
56
  AbapServiceKeyStore,
54
57
  AbapSessionStore,
55
58
  } from '@mcp-abap-adt/auth-broker';
56
- import { AuthorizationCodeProvider } from '@mcp-abap-adt/auth-providers';
59
+ import {
60
+ AuthorizationCodeProvider,
61
+ browserCallbackStrategy,
62
+ } from '@mcp-abap-adt/auth-providers';
57
63
 
58
64
  const broker = new AuthBroker({
59
65
  sessionStore: new AbapSessionStore('/path/to/destinations'),
@@ -62,7 +68,7 @@ const broker = new AuthBroker({
62
68
  uaaUrl: 'https://...authentication...hana.ondemand.com',
63
69
  clientId: '...',
64
70
  clientSecret: '...',
65
- browser: 'system',
71
+ authorization: browserCallbackStrategy({ browser: 'system' }),
66
72
  }),
67
73
  }, 'chrome', logger);
68
74
 
@@ -74,7 +80,7 @@ const brokerNoBrowser = new AuthBroker({
74
80
  uaaUrl: 'https://...authentication...hana.ondemand.com',
75
81
  clientId: '...',
76
82
  clientSecret: '...',
77
- browser: 'none',
83
+ authorization: browserCallbackStrategy({ browser: 'none' }),
78
84
  }),
79
85
  allowBrowserAuth: false, // Throws BROWSER_AUTH_REQUIRED if browser auth needed
80
86
  }, 'chrome', logger);
@@ -92,7 +98,7 @@ const broker = new AuthBroker({
92
98
  uaaUrl: 'https://...authentication...hana.ondemand.com',
93
99
  clientId: '...',
94
100
  clientSecret: '...',
95
- browser: 'system',
101
+ authorization: browserCallbackStrategy({ browser: 'system' }),
96
102
  }),
97
103
  });
98
104
  ```
@@ -111,7 +117,13 @@ const broker = new AuthBroker({
111
117
 
112
118
  ### Custom Browser Auth Port
113
119
 
114
- To avoid port conflicts with browser authentication:
120
+ How a login is conducted — including which port the local OAuth2 callback
121
+ listens on — is an `IAuthorizationStrategy` passed as `authorization`, not a
122
+ field on the provider config. `browserCallbackStrategy` from
123
+ `@mcp-abap-adt/auth-providers` builds the ready-made one; its default port is
124
+ `61001`, chosen to sit well clear of the range application servers and
125
+ proxies typically use (e.g. `3001`/`3333`). Pass `port` to avoid conflicts
126
+ with a specific redirect URI registered at the identity provider:
115
127
 
116
128
  ```typescript
117
129
  const broker = new AuthBroker({
@@ -121,8 +133,7 @@ const broker = new AuthBroker({
121
133
  uaaUrl: 'https://...authentication...hana.ondemand.com',
122
134
  clientId: '...',
123
135
  clientSecret: '...',
124
- browser: 'system',
125
- redirectPort: 4001,
136
+ authorization: browserCallbackStrategy({ browser: 'system', port: 4001 }),
126
137
  }),
127
138
  }, 'chrome');
128
139
  ```
@@ -572,10 +583,13 @@ The package uses the `ITokenProvider` interface for token acquisition. Provider
572
583
  - No refresh token provided
573
584
 
574
585
  - **`AuthorizationCodeProvider`** - For BTP/ABAP authentication (full scope)
575
- - Constructor accepts optional `browserAuthPort?: number` parameter (default: 3001)
576
- - Automatically finds an available port if the requested port is in use (prevents `EADDRINUSE` errors)
577
- - Server properly closes all connections and frees the port after authentication completes
578
- - Use custom port to avoid conflicts when running alongside other services (e.g., proxy server)
586
+ - How the login is conducted is an `authorization?: IAuthorizationStrategy<string>`, not a
587
+ provider field. Omitted, it defaults to a browser callback on port `61001`
588
+ - `browserCallbackStrategy({ browser?, port?, timeoutMs? })` from `@mcp-abap-adt/auth-providers`
589
+ builds the ready-made strategy; pass `port` to avoid conflicts when running alongside other
590
+ services (e.g. a proxy server) or to match a redirect URI registered at the identity provider
591
+ - The callback port is held only for the duration of a login and released when it ends —
592
+ by success, failure, timeout, or cancellation
579
593
  - Uses browser-based OAuth2 flow (if no refresh token)
580
594
  - Uses refresh token if available
581
595
  - Provides refresh token for future use
@@ -593,6 +607,7 @@ import {
593
607
  import {
594
608
  ClientCredentialsProvider,
595
609
  AuthorizationCodeProvider,
610
+ browserCallbackStrategy,
596
611
  } from '@mcp-abap-adt/auth-providers';
597
612
 
598
613
  // XSUAA authentication
@@ -623,7 +638,7 @@ const btpBroker = new AuthBroker({
623
638
  uaaUrl: 'https://auth.example.com',
624
639
  clientId: '...',
625
640
  clientSecret: '...',
626
- browser: 'system',
641
+ authorization: browserCallbackStrategy({ browser: 'system' }),
627
642
  }),
628
643
  });
629
644
 
@@ -635,7 +650,7 @@ const btpBrokerFull = new AuthBroker({
635
650
  uaaUrl: 'https://auth.example.com',
636
651
  clientId: '...',
637
652
  clientSecret: '...',
638
- browser: 'system',
653
+ authorization: browserCallbackStrategy({ browser: 'system' }),
639
654
  }),
640
655
  });
641
656
  ```
@@ -660,6 +675,14 @@ mcp-auth --service-key <path> --output <path> [--env <path>] [--type abap|xsuaa]
660
675
  - `none`: Show URL in console and wait for callback (no browser)
661
676
  - `system/chrome/edge/firefox`: Open specific browser
662
677
 
678
+ `--browser` and `--redirect-port` are routed into `browserCallbackStrategy`. This CLI has no
679
+ default of its own for the callback port: `--redirect-port` overrides it when given; omitted,
680
+ the port comes from `auth-providers` (currently `61001`, chosen to sit above the ephemeral range
681
+ and clear of the `3001`/`3333` range servers and proxies typically use). If you registered a
682
+ redirect URI with a specific port at your identity provider, pass `--redirect-port` to match it.
683
+ A login is given 5 minutes to complete (this is a person switching to a browser and signing in
684
+ by hand, not an unattended caller).
685
+
663
686
  **Examples:**
664
687
  ```bash
665
688
  # Auth code (default via service key)
@@ -703,12 +726,22 @@ mcp-sso --protocol <oidc|saml2> --flow <flow> --output <path> [--type abap|xsuaa
703
726
  - OIDC: `browser`, `device`, `password`, `token_exchange`
704
727
  - SAML2: `bearer`, `pure`
705
728
 
729
+ Only the flows that actually open a browser (OIDC `browser`; SAML2 `bearer`/`pure` with the
730
+ default `--assertion-flow browser`) use `--browser` and `--redirect-port` — they are routed
731
+ into `browserCallbackStrategy`/`oidcCallbackStrategy`/`samlCallbackStrategy`. This CLI has no
732
+ default of its own for the callback port: `--redirect-port` overrides it when given; omitted,
733
+ the port comes from `auth-providers` (currently `61001`). A login is given 5 minutes to
734
+ complete. `device`, `password`, and `token_exchange` never open a browser from this process, so
735
+ `--browser`/`--redirect-port` have no effect for them. This applies the same way whether
736
+ `--protocol`/`--flow` come from CLI flags or from `--config` (below) — a field's origin doesn't
737
+ change how it's handled, and CLI flags always take precedence over the same field in a file.
738
+
706
739
  **Examples:**
707
740
  ```bash
708
741
  # OIDC browser flow
709
742
  mcp-sso oidc --flow browser --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
710
743
 
711
- # OIDC browser flow (manual code / OOB)
744
+ # OIDC browser flow (manual code / OOB — no callback server is opened for this combination)
712
745
  mcp-sso oidc --flow browser --token-endpoint https://issuer/token --client-id my-client --code <auth_code> --redirect-uri urn:ietf:wg:oauth:2.0:oob --output ./sso.env --type xsuaa
713
746
 
714
747
  # OIDC device flow
@@ -765,7 +798,8 @@ It enables `authorization_code` and `saml2-bearer` grant types and provides a
765
798
  simple `CatalogService`. See `tests/sso-demo/readme.md` for deploy steps.
766
799
 
767
800
  **Config file:**
768
- You can pass a JSON file with provider config:
801
+ You can pass a JSON file with provider config instead of (or alongside) `--protocol`/`--flow`
802
+ and the OIDC/SAML flags — `--config` alone is enough to run a flow, with no other flags required:
769
803
 
770
804
  ```json
771
805
  {
@@ -777,6 +811,14 @@ You can pass a JSON file with provider config:
777
811
  }
778
812
  ```
779
813
 
814
+ Any CLI flag given alongside `--config` overrides the same field in the file; a field the file
815
+ sets and no flag overrides is used as-is. A file written for a pre-2.0.0 config still works:
816
+ `browser` and `redirectPort` are routed into the strategy exactly as the equivalent CLI flags
817
+ are, and `authorizationCode`/`assertionFlow` are honored the same way `--code`/`--assertion-flow`
818
+ are. A file that sets `authorizationCodeProvider`, `assertionProvider`, or `manualInput` — all
819
+ functions, which JSON cannot express — is refused with an error naming the CLI flag to use
820
+ instead, rather than having the field silently dropped.
821
+
780
822
  ### Utility Script
781
823
 
782
824
  Generate `.env` files from service keys:
@@ -61,6 +61,11 @@ const distPath = path.resolve(__dirname, '..', 'index.js');
61
61
  const { AuthBroker } = require(distPath);
62
62
  const auth_providers_1 = require("@mcp-abap-adt/auth-providers");
63
63
  const auth_stores_1 = require("@mcp-abap-adt/auth-stores");
64
+ /**
65
+ * A person completes this login at a browser; the provider's own default
66
+ * (30s) is sized for an unattended caller instead.
67
+ */
68
+ const INTERACTIVE_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
64
69
  function getVersion() {
65
70
  try {
66
71
  const candidates = [
@@ -108,7 +113,8 @@ function showHelp() {
108
113
  console.log(' - system/chrome/edge/firefox: Open specific browser');
109
114
  console.log(' --format <format> Output format: json or env (default: env)');
110
115
  console.log(' --service-url <url> Service URL (SAP URL for ABAP, MCP URL for XSUAA). For XSUAA, optional.');
111
- console.log(' --redirect-port <port> Port for OAuth redirect URI (default: 3001). Must match XSUAA redirect-uris config.');
116
+ console.log(' --redirect-port <port> Port for OAuth redirect URI (default: from auth-providers, currently 61001).');
117
+ console.log(' Must match XSUAA redirect-uris config.');
112
118
  console.log('');
113
119
  console.log(' --version, -v Show version number');
114
120
  console.log(' --help, -h Show this help message');
@@ -159,9 +165,9 @@ function showHelp() {
159
165
  console.log(' * none/headless: Show URL in console and wait for callback');
160
166
  console.log(' * system/chrome/edge/firefox: Open specific browser');
161
167
  console.log(' - Both providers (xsuaa and abap) support both flows');
162
- console.log(' - --redirect-port: Port for OAuth redirect URI (default: 3001)');
168
+ console.log(' - --redirect-port: Port for OAuth redirect URI (default: from auth-providers, currently 61001)');
163
169
  console.log(' * Must match redirect_uri configured in XSUAA/ABAP OAuth2 settings');
164
- console.log(' * Common values: 3001 (default), 8080 (SAP examples)');
170
+ console.log(' * If your registered redirect URI uses a different port (e.g. 3001, 8080), pass it explicitly');
165
171
  console.log(' - For XSUAA, serviceUrl (MCP URL) is optional - can be provided via --service-url or service key');
166
172
  console.log(' - For ABAP, serviceUrl (SAP URL) is required - can be provided via --service-url or service key');
167
173
  console.log(' - SAP_URL/XSUAA_MCP_URL is written to .env (from --service-url, service key, or placeholder)');
@@ -599,7 +605,12 @@ async function main() {
599
605
  }
600
606
  // Default: authorization_code flow with browser
601
607
  // --credential: client_credentials flow (no browser needed)
602
- const redirectPort = options.redirectPort || 3001;
608
+ // The callback port is the provider's own choice (DEFAULT_CALLBACK_PORT,
609
+ // currently 61001) unless the user overrides it with --redirect-port.
610
+ // Only resolved to a concrete number here for the URL preview below —
611
+ // the strategy itself re-derives the same default independently when
612
+ // `port` is left undefined.
613
+ const redirectPort = options.redirectPort ?? auth_providers_1.DEFAULT_CALLBACK_PORT;
603
614
  // Log authorization URL for debugging
604
615
  if (!options.credential) {
605
616
  const redirectUri = `http://localhost:${redirectPort}/callback`;
@@ -618,8 +629,14 @@ async function main() {
618
629
  clientId: authConfig.uaaClientId,
619
630
  clientSecret: authConfig.uaaClientSecret,
620
631
  refreshToken: authConfig.refreshToken,
621
- browser: options.browser,
622
- redirectPort: redirectPort,
632
+ // Passing `options.redirectPort` (not the resolved `redirectPort`
633
+ // above) so an omitted --redirect-port lets the strategy bind its
634
+ // own default rather than this CLI pinning a number it doesn't own.
635
+ authorization: (0, auth_providers_1.browserCallbackStrategy)({
636
+ browser: options.browser,
637
+ port: options.redirectPort,
638
+ timeoutMs: INTERACTIVE_LOGIN_TIMEOUT_MS,
639
+ }),
623
640
  });
624
641
  const broker = new AuthBroker({
625
642
  sessionStore,
@@ -60,15 +60,15 @@ var __importStar = (this && this.__importStar) || (function () {
60
60
  };
61
61
  })();
62
62
  Object.defineProperty(exports, "__esModule", { value: true });
63
- const node_readline_1 = require("node:readline");
64
63
  const fs = __importStar(require("fs"));
65
64
  const path = __importStar(require("path"));
66
65
  // Use require for CommonJS dist files with absolute path
67
66
  const distPath = path.resolve(__dirname, '..', 'index.js');
68
67
  const { AuthBroker } = require(distPath);
69
- const logger_1 = require("@mcp-abap-adt/logger");
70
68
  const auth_providers_1 = require("@mcp-abap-adt/auth-providers");
71
69
  const auth_stores_1 = require("@mcp-abap-adt/auth-stores");
70
+ const logger_1 = require("@mcp-abap-adt/logger");
71
+ const mcpSsoConfig_1 = require("./mcpSsoConfig");
72
72
  function getVersion() {
73
73
  try {
74
74
  const candidates = [
@@ -112,7 +112,7 @@ function showHelp() {
112
112
  console.log(' --service-url <url> Service URL (ABAP: SAP URL, XSUAA: MCP URL)');
113
113
  console.log(' --config <path> JSON config file (SSO provider config)');
114
114
  console.log(' --browser <browser> Browser: auto|none|system|chrome|edge|firefox');
115
- console.log(' --redirect-port <port> Redirect port for browser flows (default: 3001)');
115
+ console.log(' --redirect-port <port> Redirect port for browser flows (default: from auth-providers, currently 61001)');
116
116
  console.log(' --redirect-uri <uri> Custom redirect URI (OOB/manual code flows)');
117
117
  console.log('');
118
118
  console.log('OIDC Options:');
@@ -158,18 +158,6 @@ function parseScopes(value) {
158
158
  .filter(Boolean);
159
159
  return parts.length > 0 ? parts : undefined;
160
160
  }
161
- function readManualInput(prompt) {
162
- const rl = (0, node_readline_1.createInterface)({
163
- input: process.stdin,
164
- output: process.stdout,
165
- });
166
- return new Promise((resolve) => {
167
- rl.question(prompt, (answer) => {
168
- rl.close();
169
- resolve(answer.trim());
170
- });
171
- });
172
- }
173
161
  function createCliLogger(prefix = 'SSO') {
174
162
  const isEnabled = () => {
175
163
  if (process.env.DEBUG_SSO === 'false' ||
@@ -520,190 +508,6 @@ function resolveSamlTokenAlias(metadataXml) {
520
508
  const match = metadataXml.match(regex);
521
509
  return match?.[1];
522
510
  }
523
- function normalizeProviderConfig(raw) {
524
- if (!raw || typeof raw !== 'object') {
525
- return null;
526
- }
527
- if (raw.provider) {
528
- return raw.provider;
529
- }
530
- if (raw.protocol && raw.flow) {
531
- const { protocol, flow, config, ...rest } = raw;
532
- return {
533
- protocol,
534
- flow,
535
- config: config ?? rest,
536
- };
537
- }
538
- return null;
539
- }
540
- function mergeConfig(target, source) {
541
- const result = { ...target };
542
- for (const [key, value] of Object.entries(source)) {
543
- if (value !== undefined) {
544
- result[key] = value;
545
- }
546
- }
547
- return result;
548
- }
549
- function buildOidcConfig(options) {
550
- const scopeList = options.scopes;
551
- const tokenEndpoint = options.tokenEndpoint ||
552
- (options.uaaUrl
553
- ? `${options.uaaUrl.replace(/\/+$/, '')}/oauth/token`
554
- : undefined);
555
- const passcode = options.passcode;
556
- const username = options.username || (passcode ? 'passcode' : undefined);
557
- const password = options.password || passcode;
558
- return {
559
- issuerUrl: options.issuerUrl,
560
- authorizationEndpoint: options.authorizationEndpoint,
561
- tokenEndpoint,
562
- deviceAuthorizationEndpoint: options.deviceAuthorizationEndpoint,
563
- clientId: options.clientId,
564
- clientSecret: options.clientSecret,
565
- scopes: scopeList,
566
- scope: options.scope,
567
- authorizationCode: options.code,
568
- username,
569
- password,
570
- subjectToken: options.subjectToken,
571
- subjectTokenType: options.subjectTokenType ||
572
- 'urn:ietf:params:oauth:token-type:access_token',
573
- audience: options.audience,
574
- actorToken: options.actorToken,
575
- actorTokenType: options.actorTokenType,
576
- browser: options.browser,
577
- redirectPort: options.redirectPort,
578
- redirectUri: options.redirectUri,
579
- };
580
- }
581
- function buildSamlConfig(options) {
582
- const assertionFlow = options.assertionFlow || (options.assertion ? 'assertion' : 'browser');
583
- const tokenUrl = options.tokenEndpoint ||
584
- (options.uaaUrl
585
- ? `${options.uaaUrl.replace(/\/+$/, '')}/oauth/token`
586
- : undefined);
587
- const assertionProvider = options.assertion
588
- ? async () => options.assertion
589
- : assertionFlow === 'assertion'
590
- ? async () => readManualInput('Paste SAMLResponse: ')
591
- : undefined;
592
- const cookieProvider = async (samlResponse) => {
593
- if (options.cookie) {
594
- return options.cookie;
595
- }
596
- if (assertionFlow === 'assertion') {
597
- return `SAMLResponse=${samlResponse}`;
598
- }
599
- return readManualInput('Paste session cookies: ');
600
- };
601
- return {
602
- idpSsoUrl: options.idpSsoUrl,
603
- spEntityId: options.spEntityId,
604
- acsUrl: options.acsUrl,
605
- relayState: options.relayState,
606
- assertionFlow,
607
- assertionProvider,
608
- tokenUrl,
609
- uaaUrl: options.uaaUrl,
610
- clientId: options.clientId,
611
- clientSecret: options.clientSecret,
612
- browser: options.browser,
613
- redirectPort: options.redirectPort,
614
- cookieProvider,
615
- };
616
- }
617
- function buildProviderConfig(options, existingAuth, existingConn, fileConfig) {
618
- let configFromCli = null;
619
- if (options.protocol && options.flow) {
620
- if (options.protocol === 'oidc') {
621
- switch (options.flow) {
622
- case 'browser':
623
- configFromCli = {
624
- protocol: 'oidc',
625
- flow: 'browser',
626
- config: buildOidcConfig(options),
627
- };
628
- break;
629
- case 'device':
630
- configFromCli = {
631
- protocol: 'oidc',
632
- flow: 'device',
633
- config: buildOidcConfig(options),
634
- };
635
- break;
636
- case 'password':
637
- configFromCli = {
638
- protocol: 'oidc',
639
- flow: 'password',
640
- config: buildOidcConfig(options),
641
- };
642
- break;
643
- case 'token_exchange':
644
- configFromCli = {
645
- protocol: 'oidc',
646
- flow: 'token_exchange',
647
- config: buildOidcConfig(options),
648
- };
649
- break;
650
- default:
651
- throw new Error(`Unsupported OIDC flow: ${options.flow}`);
652
- }
653
- }
654
- else if (options.protocol === 'saml2') {
655
- switch (options.flow) {
656
- case 'bearer':
657
- configFromCli = {
658
- protocol: 'saml2',
659
- flow: 'bearer',
660
- config: buildSamlConfig(options),
661
- };
662
- break;
663
- case 'pure':
664
- configFromCli = {
665
- protocol: 'saml2',
666
- flow: 'pure',
667
- config: buildSamlConfig(options),
668
- };
669
- break;
670
- default:
671
- throw new Error(`Unsupported SAML flow: ${options.flow}`);
672
- }
673
- }
674
- }
675
- const base = fileConfig ?? configFromCli;
676
- if (!base) {
677
- throw new Error('Provider config is missing. Use --config or --protocol/--flow options.');
678
- }
679
- let result = base;
680
- if (options.protocol) {
681
- result = { ...result, protocol: options.protocol };
682
- }
683
- if (options.flow) {
684
- result = { ...result, flow: options.flow };
685
- }
686
- if (configFromCli) {
687
- result = {
688
- ...result,
689
- config: mergeConfig(result.config || {}, configFromCli.config || {}),
690
- };
691
- }
692
- const accessToken = existingConn?.authorizationToken;
693
- const refreshToken = existingAuth?.refreshToken;
694
- if ((result.protocol === 'oidc' ||
695
- (result.protocol === 'saml2' && result.flow === 'bearer')) &&
696
- (accessToken || refreshToken)) {
697
- result = {
698
- ...result,
699
- config: mergeConfig(result.config || {}, {
700
- accessToken,
701
- refreshToken,
702
- }),
703
- };
704
- }
705
- return result;
706
- }
707
511
  async function main() {
708
512
  const options = parseArgs();
709
513
  if (!options) {
@@ -768,12 +572,21 @@ async function main() {
768
572
  process.exit(1);
769
573
  }
770
574
  const raw = JSON.parse(fs.readFileSync(resolvedConfigPath, 'utf8'));
771
- providerConfigFromFile = normalizeProviderConfig(raw);
575
+ providerConfigFromFile = (0, mcpSsoConfig_1.normalizeProviderConfig)(raw);
772
576
  if (!providerConfigFromFile) {
773
577
  console.error(`❌ Config file does not contain provider config`);
774
578
  process.exit(1);
775
579
  }
776
580
  }
581
+ // Merge the file into `options` *before* anything downstream reads
582
+ // options.protocol/flow or builds a strategy from them — a run driven by
583
+ // --config alone must reach exactly the same validation and
584
+ // strategy-building code a --protocol/--flow run does, or a field the file
585
+ // carries (including a legacy one 2.0.0 removed) can reach the provider
586
+ // untouched with no error and no warning. CLI flags already parsed above
587
+ // are left alone; the file only fills what they didn't set. A no-op when
588
+ // --config wasn't given.
589
+ (0, mcpSsoConfig_1.applyFileConfig)(options, providerConfigFromFile);
777
590
  if (options.serviceKeyPath) {
778
591
  const resolvedServiceKeyPath = path.resolve(options.serviceKeyPath);
779
592
  const serviceKeyDir = path.dirname(resolvedServiceKeyPath);
@@ -836,7 +649,7 @@ async function main() {
836
649
  }
837
650
  if (options.protocol === 'oidc' && options.flow === 'password') {
838
651
  if (!options.passcode && !options.password) {
839
- options.passcode = await readManualInput('Paste passcode: ');
652
+ options.passcode = await (0, mcpSsoConfig_1.readManualInput)('Paste passcode: ');
840
653
  }
841
654
  }
842
655
  const tempSessionDir = path.join(path.dirname(resolvedOutputPath), '.tmp');
@@ -893,7 +706,7 @@ async function main() {
893
706
  refreshToken: existingAuth?.refreshToken,
894
707
  });
895
708
  }
896
- const providerConfig = buildProviderConfig(options, existingAuth, existingConn, providerConfigFromFile);
709
+ const providerConfig = (0, mcpSsoConfig_1.buildProviderConfig)(options, existingAuth, existingConn);
897
710
  const providerConfigWithLogger = providerConfig.config
898
711
  ? {
899
712
  ...providerConfig,
@@ -0,0 +1,398 @@
1
+ "use strict";
2
+ /**
3
+ * Pure config-building logic for `mcp-sso`, split out from bin/mcp-sso.ts so
4
+ * it can be imported by tests directly.
5
+ *
6
+ * bin/mcp-sso.ts itself cannot be `import`ed safely: it `require()`s the
7
+ * compiled `dist/index.js` at module load time (assuming it is always run
8
+ * from `dist/bin/`) and calls `main()` at the bottom of the file. Neither is
9
+ * true when a test loads the TypeScript source directly, so anything that
10
+ * needs unit coverage — building an `SsoProviderConfig` from CLI options and
11
+ * an optional `--config` file, and merging the two — lives here instead.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.INTERACTIVE_LOGIN_TIMEOUT_MS = void 0;
15
+ exports.readManualInput = readManualInput;
16
+ exports.normalizeProviderConfig = normalizeProviderConfig;
17
+ exports.applyFileConfig = applyFileConfig;
18
+ exports.buildOidcBrowserConfig = buildOidcBrowserConfig;
19
+ exports.buildOidcDeviceConfig = buildOidcDeviceConfig;
20
+ exports.buildOidcPasswordConfig = buildOidcPasswordConfig;
21
+ exports.buildOidcTokenExchangeConfig = buildOidcTokenExchangeConfig;
22
+ exports.buildSamlBearerConfig = buildSamlBearerConfig;
23
+ exports.buildSamlPureConfig = buildSamlPureConfig;
24
+ exports.buildProviderConfig = buildProviderConfig;
25
+ const node_readline_1 = require("node:readline");
26
+ const auth_providers_1 = require("@mcp-abap-adt/auth-providers");
27
+ /**
28
+ * A person completes these logins at a browser; the library's own default
29
+ * (30s) is sized for an unattended caller instead.
30
+ */
31
+ exports.INTERACTIVE_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
32
+ function readManualInput(prompt) {
33
+ const rl = (0, node_readline_1.createInterface)({
34
+ input: process.stdin,
35
+ output: process.stdout,
36
+ });
37
+ return new Promise((resolve) => {
38
+ rl.question(prompt, (answer) => {
39
+ rl.close();
40
+ resolve(answer.trim());
41
+ });
42
+ });
43
+ }
44
+ function normalizeProviderConfig(raw) {
45
+ if (!raw || typeof raw !== 'object') {
46
+ return null;
47
+ }
48
+ if (raw.provider) {
49
+ return raw.provider;
50
+ }
51
+ if (raw.protocol && raw.flow) {
52
+ const { protocol, flow, config, ...rest } = raw;
53
+ return {
54
+ protocol,
55
+ flow,
56
+ config: config ?? rest,
57
+ };
58
+ }
59
+ return null;
60
+ }
61
+ /**
62
+ * Pre-2.0.0 provider config fields that named a JavaScript function
63
+ * (`() => Promise<string>` or similar) rather than a value. JSON cannot
64
+ * carry a function, so a `--config` file can never legitimately supply one —
65
+ * if a key like this shows up, the file is stale in a way no conversion can
66
+ * fix. Refuse rather than silently drop it, and say what to write instead.
67
+ */
68
+ const UNSUPPORTED_LEGACY_CONFIG_FIELDS = {
69
+ authorizationCodeProvider: "'authorizationCodeProvider' (a function) cannot be expressed in a --config file. Use --code for a value you already hold, or drive the OIDC browser flow interactively without it.",
70
+ assertionProvider: "'assertionProvider' (a function) cannot be expressed in a --config file. Use --assertion for a value you already hold, or --assertion-flow to choose how it is obtained.",
71
+ manualInput: "'manualInput' (a function) cannot be expressed in a --config file. Use --assertion-flow manual to prompt on this CLI's own stdin instead.",
72
+ };
73
+ /**
74
+ * `--config` file fields that map 1:1 onto a `McpSsoOptions` field of the
75
+ * same name — every pre-2.0.0 config field the strategies still need, other
76
+ * than `authorizationCode` (see `applyFileConfig`), lands here.
77
+ */
78
+ const CONFIG_BACKFILL_FIELDS = [
79
+ 'browser',
80
+ 'redirectPort',
81
+ 'redirectUri',
82
+ 'issuerUrl',
83
+ 'authorizationEndpoint',
84
+ 'tokenEndpoint',
85
+ 'deviceAuthorizationEndpoint',
86
+ 'clientId',
87
+ 'clientSecret',
88
+ 'scopes',
89
+ 'scope',
90
+ 'username',
91
+ 'password',
92
+ 'passcode',
93
+ 'subjectToken',
94
+ 'subjectTokenType',
95
+ 'audience',
96
+ 'actorToken',
97
+ 'actorTokenType',
98
+ 'idpSsoUrl',
99
+ 'spEntityId',
100
+ 'acsUrl',
101
+ 'relayState',
102
+ 'assertionFlow',
103
+ 'assertion',
104
+ 'cookie',
105
+ 'uaaUrl',
106
+ ];
107
+ /**
108
+ * Backfills `options` in place from a `--config` file's raw `config` object.
109
+ * CLI flags always win — a field already set on `options` is left alone; the
110
+ * file only fills gaps.
111
+ *
112
+ * This is what makes a config-file-only run (no --protocol/--flow on the
113
+ * CLI) reach the same strategy-building code as a CLI-flag run: without it,
114
+ * the file's fields — including a `browser`/`redirectPort` a 2.0.0 provider
115
+ * no longer accepts directly — would reach `SsoProviderFactory.create()`
116
+ * untouched and be silently ignored.
117
+ *
118
+ * Every field a pre-2.0.0 config could carry either lands on its
119
+ * `McpSsoOptions` equivalent here (which the strategy built from `options`
120
+ * then consumes) or the run is refused via `process.exit(1)` naming what to
121
+ * write instead. Nothing may reach the provider without one of those two
122
+ * happening.
123
+ *
124
+ * Also backfills `options.protocol`/`options.flow` from the file when the
125
+ * CLI didn't set them — a `--config`-only run (no `--protocol`/`--flow`
126
+ * flags at all) must still resolve to a real flow, or nothing downstream
127
+ * ever calls a builder at all.
128
+ *
129
+ * A no-op when `fileConfig` is `null` (no `--config` was given), so callers
130
+ * can invoke this unconditionally.
131
+ */
132
+ function applyFileConfig(options, fileConfig) {
133
+ if (!fileConfig) {
134
+ return;
135
+ }
136
+ options.protocol = options.protocol ?? fileConfig.protocol;
137
+ options.flow = options.flow ?? fileConfig.flow;
138
+ const fields = (fileConfig.config ?? {});
139
+ for (const [field, guidance] of Object.entries(UNSUPPORTED_LEGACY_CONFIG_FIELDS)) {
140
+ if (fields[field] !== undefined) {
141
+ console.error(`❌ --config: ${guidance}`);
142
+ process.exit(1);
143
+ }
144
+ }
145
+ // Pre-2.0.0 field name for the OIDC manual/OOB code paste path; maps onto
146
+ // the same slot --code fills.
147
+ if (options.code === undefined &&
148
+ typeof fields.authorizationCode === 'string') {
149
+ options.code = fields.authorizationCode;
150
+ }
151
+ for (const field of CONFIG_BACKFILL_FIELDS) {
152
+ if (options[field] === undefined && fields[field] !== undefined) {
153
+ options[field] = fields[field];
154
+ }
155
+ }
156
+ }
157
+ function requireOption(value, flagName) {
158
+ if (!value) {
159
+ console.error(`❌ ${flagName} is required for this flow.`);
160
+ process.exit(1);
161
+ }
162
+ return value;
163
+ }
164
+ function resolveOidcTokenEndpoint(options) {
165
+ return (options.tokenEndpoint ||
166
+ (options.uaaUrl
167
+ ? `${options.uaaUrl.replace(/\/+$/, '')}/oauth/token`
168
+ : undefined));
169
+ }
170
+ /**
171
+ * Only the OIDC 'browser' flow opens a browser; routes `--browser`,
172
+ * `--redirect-port` and manual/OOB code paste into the strategy that
173
+ * replaces them.
174
+ */
175
+ function buildOidcBrowserAuthorization(options) {
176
+ if (options.code) {
177
+ // The consumer already holds the code (manual paste / OOB redirect
178
+ // URI); no callback server is opened at all.
179
+ return (0, auth_providers_1.asOidcResult)((0, auth_providers_1.staticCodeStrategy)({
180
+ redirectUri: options.redirectUri,
181
+ payload: options.code,
182
+ }));
183
+ }
184
+ // No fallback: an omitted --redirect-port lets the strategy bind its own
185
+ // default port rather than this CLI pinning a number it doesn't own.
186
+ return (0, auth_providers_1.oidcCallbackStrategy)({
187
+ port: options.redirectPort,
188
+ browser: options.browser,
189
+ timeoutMs: exports.INTERACTIVE_LOGIN_TIMEOUT_MS,
190
+ });
191
+ }
192
+ function buildOidcCommon(options) {
193
+ return {
194
+ issuerUrl: options.issuerUrl,
195
+ clientId: requireOption(options.clientId, '--client-id'),
196
+ clientSecret: options.clientSecret,
197
+ };
198
+ }
199
+ function buildOidcBrowserConfig(options) {
200
+ return {
201
+ ...buildOidcCommon(options),
202
+ authorizationEndpoint: options.authorizationEndpoint,
203
+ tokenEndpoint: resolveOidcTokenEndpoint(options),
204
+ scopes: options.scopes,
205
+ authorization: buildOidcBrowserAuthorization(options),
206
+ };
207
+ }
208
+ function buildOidcDeviceConfig(options) {
209
+ // Device flow never opens a browser from this process; --browser and
210
+ // --redirect-port have nothing to attach to here.
211
+ return {
212
+ ...buildOidcCommon(options),
213
+ deviceAuthorizationEndpoint: options.deviceAuthorizationEndpoint,
214
+ tokenEndpoint: resolveOidcTokenEndpoint(options),
215
+ scopes: options.scopes,
216
+ };
217
+ }
218
+ function buildOidcPasswordConfig(options) {
219
+ // Password flow never opens a browser; --browser and --redirect-port are
220
+ // not meaningful here either.
221
+ const passcode = options.passcode;
222
+ const username = options.username || (passcode ? 'passcode' : undefined);
223
+ const password = options.password || passcode;
224
+ return {
225
+ ...buildOidcCommon(options),
226
+ username: requireOption(username, '--username (or --passcode)'),
227
+ password: requireOption(password, '--password (or --passcode)'),
228
+ tokenEndpoint: resolveOidcTokenEndpoint(options),
229
+ scopes: options.scopes,
230
+ };
231
+ }
232
+ function buildOidcTokenExchangeConfig(options) {
233
+ // Token exchange never opens a browser; --browser and --redirect-port are
234
+ // not meaningful here either.
235
+ return {
236
+ ...buildOidcCommon(options),
237
+ subjectToken: requireOption(options.subjectToken, '--subject-token'),
238
+ subjectTokenType: options.subjectTokenType ||
239
+ 'urn:ietf:params:oauth:token-type:access_token',
240
+ scope: options.scope,
241
+ audience: options.audience,
242
+ actorToken: options.actorToken,
243
+ actorTokenType: options.actorTokenType,
244
+ tokenEndpoint: resolveOidcTokenEndpoint(options),
245
+ };
246
+ }
247
+ /**
248
+ * Both SAML flows (bearer, pure) can open a browser; routes `--browser`,
249
+ * `--redirect-port` and the manual/static assertion options into the
250
+ * strategy that replaces them.
251
+ */
252
+ function buildSamlAuthorization(options) {
253
+ if (options.assertion) {
254
+ // The consumer already holds the assertion; nothing is opened or asked.
255
+ return (0, auth_providers_1.staticCodeStrategy)({
256
+ redirectUri: options.acsUrl,
257
+ payload: options.assertion,
258
+ });
259
+ }
260
+ const assertionFlow = options.assertionFlow || 'browser';
261
+ if (assertionFlow !== 'browser') {
262
+ // 'manual', and an 'assertion' flow given no value, both need a human to
263
+ // lift the SAMLResponse out of the POST body by hand.
264
+ return (0, auth_providers_1.manualSamlResponseStrategy)({
265
+ redirectUri: options.acsUrl,
266
+ read: readManualInput,
267
+ });
268
+ }
269
+ // No fallback: an omitted --redirect-port lets the strategy bind its own
270
+ // default port rather than this CLI pinning a number it doesn't own.
271
+ return (0, auth_providers_1.samlCallbackStrategy)({
272
+ port: options.redirectPort,
273
+ browser: options.browser,
274
+ timeoutMs: exports.INTERACTIVE_LOGIN_TIMEOUT_MS,
275
+ });
276
+ }
277
+ function buildSamlCookieProvider(options) {
278
+ const assertionFlow = options.assertionFlow || (options.assertion ? 'assertion' : 'browser');
279
+ return async (samlResponse) => {
280
+ if (options.cookie) {
281
+ return options.cookie;
282
+ }
283
+ if (assertionFlow === 'assertion') {
284
+ return `SAMLResponse=${samlResponse}`;
285
+ }
286
+ return readManualInput('Paste session cookies: ');
287
+ };
288
+ }
289
+ function buildSamlBearerConfig(options) {
290
+ return {
291
+ idpSsoUrl: requireOption(options.idpSsoUrl, '--idp-sso-url'),
292
+ spEntityId: requireOption(options.spEntityId, '--sp-entity-id'),
293
+ acsUrl: options.acsUrl,
294
+ relayState: options.relayState,
295
+ tokenUrl: options.tokenEndpoint,
296
+ uaaUrl: options.uaaUrl,
297
+ clientId: options.clientId,
298
+ clientSecret: options.clientSecret,
299
+ authorization: buildSamlAuthorization(options),
300
+ };
301
+ }
302
+ function buildSamlPureConfig(options) {
303
+ return {
304
+ idpSsoUrl: requireOption(options.idpSsoUrl, '--idp-sso-url'),
305
+ spEntityId: requireOption(options.spEntityId, '--sp-entity-id'),
306
+ acsUrl: options.acsUrl,
307
+ relayState: options.relayState,
308
+ authorization: buildSamlAuthorization(options),
309
+ cookieProvider: buildSamlCookieProvider(options),
310
+ };
311
+ }
312
+ /**
313
+ * Builds the effective `SsoProviderConfig` from `options` alone.
314
+ *
315
+ * `options` must already be the *merged* view — `--config` file fields
316
+ * backfilled beneath whatever the CLI flags set, via `applyFileConfig` — so
317
+ * this never needs a separate file/CLI merge step, and required-field
318
+ * validation (`requireOption`, inside each builder above) sees the same
319
+ * merged data regardless of whether a value came from the file or a flag.
320
+ */
321
+ function buildProviderConfig(options, existingAuth, existingConn) {
322
+ if (!options.protocol || !options.flow) {
323
+ throw new Error('Provider config is missing. Use --config or --protocol/--flow options.');
324
+ }
325
+ let result;
326
+ if (options.protocol === 'oidc') {
327
+ switch (options.flow) {
328
+ case 'browser':
329
+ result = {
330
+ protocol: 'oidc',
331
+ flow: 'browser',
332
+ config: buildOidcBrowserConfig(options),
333
+ };
334
+ break;
335
+ case 'device':
336
+ result = {
337
+ protocol: 'oidc',
338
+ flow: 'device',
339
+ config: buildOidcDeviceConfig(options),
340
+ };
341
+ break;
342
+ case 'password':
343
+ result = {
344
+ protocol: 'oidc',
345
+ flow: 'password',
346
+ config: buildOidcPasswordConfig(options),
347
+ };
348
+ break;
349
+ case 'token_exchange':
350
+ result = {
351
+ protocol: 'oidc',
352
+ flow: 'token_exchange',
353
+ config: buildOidcTokenExchangeConfig(options),
354
+ };
355
+ break;
356
+ default:
357
+ throw new Error(`Unsupported OIDC flow: ${options.flow}`);
358
+ }
359
+ }
360
+ else if (options.protocol === 'saml2') {
361
+ switch (options.flow) {
362
+ case 'bearer':
363
+ result = {
364
+ protocol: 'saml2',
365
+ flow: 'bearer',
366
+ config: buildSamlBearerConfig(options),
367
+ };
368
+ break;
369
+ case 'pure':
370
+ result = {
371
+ protocol: 'saml2',
372
+ flow: 'pure',
373
+ config: buildSamlPureConfig(options),
374
+ };
375
+ break;
376
+ default:
377
+ throw new Error(`Unsupported SAML flow: ${options.flow}`);
378
+ }
379
+ }
380
+ else {
381
+ throw new Error(`Unsupported protocol: ${options.protocol}`);
382
+ }
383
+ const accessToken = existingConn?.authorizationToken;
384
+ const refreshToken = existingAuth?.refreshToken;
385
+ if ((result.protocol === 'oidc' ||
386
+ (result.protocol === 'saml2' && result.flow === 'bearer')) &&
387
+ (accessToken || refreshToken)) {
388
+ result = {
389
+ ...result,
390
+ config: {
391
+ ...result.config,
392
+ accessToken,
393
+ refreshToken,
394
+ },
395
+ };
396
+ }
397
+ return result;
398
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/auth-broker",
3
- "version": "1.0.8",
3
+ "version": "2.0.0",
4
4
  "description": "JWT authentication broker for MCP ABAP ADT - manages tokens based on destination headers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -62,7 +62,7 @@
62
62
  "node": ">=18.2.0"
63
63
  },
64
64
  "dependencies": {
65
- "@mcp-abap-adt/auth-providers": "^1.2.0",
65
+ "@mcp-abap-adt/auth-providers": "^2.0.0",
66
66
  "@mcp-abap-adt/auth-stores": "^1.0.2",
67
67
  "@mcp-abap-adt/interfaces": "^2.3.0",
68
68
  "axios": "^1.13.5"