@mcp-abap-adt/auth-broker 1.0.3 → 1.0.6

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,31 @@ Thank you to all contributors! See [CONTRIBUTORS.md](CONTRIBUTORS.md) for the co
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [1.0.6] - 2026-06-02
15
+
16
+ ### Fixed
17
+ - `getToken`: no longer retries via the `serviceKey` strategy after the `session` strategy fails with an *interactive* browser-login error (timeout / `BROWSER_AUTH_ERROR`). Both strategies call the same `tokenProvider.getTokens()`, so the retry could not succeed and merely started a duplicate browser login on the same redirect port — surfacing a misleading `Port <n> is already in use` instead of the real cause. Transient/non-interactive session failures still fall through to the `serviceKey` attempt.
18
+
19
+ ## [1.0.5] - 2026-02-12
20
+
21
+ ### Added
22
+ - `mcp-auth` subcommands: `auth-code`, `oidc`, `saml2-pure`, `saml2-bearer` (delegates to `mcp-sso`).
23
+ - `mcp-auth saml2-bearer` requires `--dev` and warns otherwise (in progress).
24
+ - `mcp-sso` subcommands: `oidc`, `saml2`, `bearer`.
25
+ - `mcp-sso`: `--saml-metadata` to resolve XSUAA token alias endpoint from SP metadata.
26
+ - `tests/sso-demo`: service key helper script with `--service/--key/--out` overrides.
27
+
28
+ ### Changed
29
+ - `mcp-sso`: allow `--token-endpoint` with `--service-key` for SAML bearer.
30
+ - Docs and test scripts updated for new subcommands.
31
+ - Bumped `@mcp-abap-adt/auth-providers` to `^1.0.5`.
32
+
33
+ ## [1.0.4] - 2026-02-11
34
+
35
+ ### Fixed
36
+ - `mcp-sso`: ensure auth config is seeded for broker and avoid writing placeholder secrets.
37
+
38
+
14
39
  ## [1.0.3] - 2026-02-11
15
40
 
16
41
  ### Fixed
package/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # @mcp-abap-adt/auth-broker
2
+ [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua)
2
3
 
3
4
  JWT authentication broker for MCP ABAP ADT server. Manages authentication tokens based on destination headers, automatically loading tokens from `.env` files and refreshing them using service keys when needed.
4
5
 
@@ -644,6 +645,7 @@ const btpBrokerFull = new AuthBroker({
644
645
  Generate or refresh `.env`/JSON output using AuthBroker + stores:
645
646
 
646
647
  ```bash
648
+ mcp-auth <auth-code|oidc|saml2-pure|saml2-bearer> [options]
647
649
  mcp-auth --service-key <path> --output <path> [--env <path>] [--type abap|xsuaa] [--credential] [--browser auto|none|system|chrome|edge|firefox] [--format json|env]
648
650
  ```
649
651
 
@@ -660,6 +662,18 @@ mcp-auth --service-key <path> --output <path> [--env <path>] [--type abap|xsuaa]
660
662
 
661
663
  **Examples:**
662
664
  ```bash
665
+ # Auth code (default via service key)
666
+ mcp-auth auth-code --service-key ./abap.json --output ./abap.env --type abap
667
+
668
+ # OIDC SSO (device flow example)
669
+ mcp-auth oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
670
+
671
+ # SAML2 pure (cookie)
672
+ mcp-auth saml2-pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --output ./saml.env --type abap
673
+
674
+ # SAML2 bearer (in progress, requires --dev)
675
+ mcp-auth saml2-bearer --dev --service-key ./mcp.json --assertion <base64> --output ./sso.env --type xsuaa
676
+
663
677
  # ABAP: authorization_code (default, opens browser)
664
678
  mcp-auth --service-key ./abap.json --output ./abap.env --type abap
665
679
 
@@ -681,6 +695,7 @@ mcp-auth --env ./mcp.env --service-key ./mcp.json --output ./mcp.env --type xsua
681
695
  Get tokens via SSO providers (OIDC/SAML) and generate `.env`/JSON output:
682
696
 
683
697
  ```bash
698
+ mcp-sso <oidc|saml2|bearer> [options]
684
699
  mcp-sso --protocol <oidc|saml2> --flow <flow> --output <path> [--type abap|xsuaa] [--format env|json] [--env <path>] [--config <path>]
685
700
  ```
686
701
 
@@ -691,27 +706,64 @@ mcp-sso --protocol <oidc|saml2> --flow <flow> --output <path> [--type abap|xsuaa
691
706
  **Examples:**
692
707
  ```bash
693
708
  # OIDC browser flow
694
- mcp-sso --protocol oidc --flow browser --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
709
+ mcp-sso oidc --flow browser --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
695
710
 
696
711
  # OIDC browser flow (manual code / OOB)
697
- mcp-sso --protocol 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
712
+ 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
698
713
 
699
714
  # OIDC device flow
700
- mcp-sso --protocol oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
715
+ mcp-sso oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
701
716
 
702
- # OIDC password flow (CF passcode)
703
- mcp-sso --protocol oidc --flow password --cf-api https://api.cf.eu10-004.hana.ondemand.com --client-id cf --passcode <code> --output ./sso.env --type xsuaa
717
+ # OIDC password flow
718
+ mcp-sso oidc --flow password --token-endpoint https://issuer/oauth/token --client-id my-client --username user --password pass --output ./sso.env --type xsuaa
704
719
 
705
720
  # OIDC token exchange
706
- mcp-sso --protocol oidc --flow token_exchange --issuer https://issuer --client-id my-client --subject-token <token> --output ./sso.env --type xsuaa
721
+ mcp-sso oidc --flow token_exchange --issuer https://issuer --client-id my-client --subject-token <token> --output ./sso.env --type xsuaa
707
722
 
708
723
  # SAML bearer flow (assertion -> token)
709
- mcp-sso --protocol saml2 --flow bearer --idp-sso-url https://idp/sso --sp-entity-id my-sp --token-endpoint https://uaa.example/oauth/token --assertion <base64> --output ./sso.env --type xsuaa
724
+ mcp-sso bearer --idp-sso-url https://idp/sso --sp-entity-id my-sp --token-endpoint https://uaa.example/oauth/token --assertion <base64> --output ./sso.env --type xsuaa
710
725
 
711
726
  # SAML pure flow (cookie)
712
- mcp-sso --protocol saml2 --flow pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --assertion <base64> --cookie "SAP_SESSION=..." --output ./sso.env --type abap
727
+ mcp-sso saml2 --flow pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --assertion <base64> --cookie "SAP_SESSION=..." --output ./sso.env --type abap
728
+ ```
729
+
730
+ **SAML token alias (XSUAA):**
731
+ If your IdP requires the token alias endpoint, pass SAML metadata XML:
732
+
733
+ ```bash
734
+ mcp-sso bearer --saml-metadata ./saml-sp.xml --assertion <base64> --service-key ./service-key.json --output ./sso.env --type xsuaa
713
735
  ```
714
736
 
737
+ ### Local Keycloak (OIDC + SAML Tests)
738
+
739
+ For local testing of `mcp-sso`, a ready-to-run Keycloak setup is included
740
+ (OIDC browser/password/device + SAML assertion capture).
741
+
742
+ ```bash
743
+ cd tests/keycloak
744
+ docker compose up -d
745
+ ```
746
+
747
+ Then use:
748
+ ```bash
749
+ node dist/bin/mcp-sso.js \
750
+ oidc \
751
+ --flow browser \
752
+ --issuer http://localhost:8080/realms/mcp-sso \
753
+ --client-id mcp-sso-cli \
754
+ --scopes openid,profile,email \
755
+ --output /tmp/keycloak.env \
756
+ --type xsuaa
757
+ ```
758
+
759
+ See `tests/keycloak/README.md` for device flow and SAML examples.
760
+
761
+ ### XSUAA Demo (CAP)
762
+
763
+ A minimal CAP app for testing XSUAA flows is included at `tests/sso-demo`.
764
+ It enables `authorization_code` and `saml2-bearer` grant types and provides a
765
+ simple `CatalogService`. See `tests/sso-demo/readme.md` for deploy steps.
766
+
715
767
  **Config file:**
716
768
  You can pass a JSON file with provider config:
717
769
 
@@ -1 +1 @@
1
- {"version":3,"file":"AuthBroker.d.ts","sourceRoot":"","sources":["../src/AuthBroker.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,eAAe,EAGrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACd,MAAM,qBAAqB,CAAC;AA6C7B;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,YAAY,EAAE,aAAa,CAAC;IAC5B,uEAAuE;IACvE,eAAe,CAAC,EAAE,gBAAgB,CAAC;IACnC,4IAA4I;IAC5I,aAAa,EAAE,cAAc,CAAC;IAC9B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,aAAa,CAAiB;IACtC,OAAO,CAAC,gBAAgB,CAAU;IAElC;;;;;;;;;;;OAWG;gBACS,MAAM,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO;IAsFxE;;OAEG;YACW,eAAe;IA0D7B;;OAEG;YACW,aAAa;IAoD3B;;OAEG;YACW,oCAAoC;IA4ClD;;OAEG;YACW,kBAAkB;YAkClB,aAAa;YA0Db,kBAAkB;IAkDhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACG,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsJpD;;;;;OAKG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASxD;;;;OAIG;IACG,sBAAsB,CAC1B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAoEvC;;;;OAIG;IACG,mBAAmB,CACvB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAuEpC;;;;;;;;;;;;;;;;OAgBG;IACH,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe;CAqB3D"}
1
+ {"version":3,"file":"AuthBroker.d.ts","sourceRoot":"","sources":["../src/AuthBroker.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,eAAe,EAGrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACd,MAAM,qBAAqB,CAAC;AA8D7B;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,YAAY,EAAE,aAAa,CAAC;IAC5B,uEAAuE;IACvE,eAAe,CAAC,EAAE,gBAAgB,CAAC;IACnC,4IAA4I;IAC5I,aAAa,EAAE,cAAc,CAAC;IAC9B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,aAAa,CAAiB;IACtC,OAAO,CAAC,gBAAgB,CAAU;IAElC;;;;;;;;;;;OAWG;gBACS,MAAM,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO;IAsFxE;;OAEG;YACW,eAAe;IA0D7B;;OAEG;YACW,aAAa;IAoD3B;;OAEG;YACW,oCAAoC;IA4ClD;;OAEG;YACW,kBAAkB;YA4ClB,aAAa;YA0Db,kBAAkB;IAkDhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACG,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsLpD;;;;;OAKG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASxD;;;;OAIG;IACG,sBAAsB,CAC1B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAoEvC;;;;OAIG;IACG,mBAAmB,CACvB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAuEpC;;;;;;;;;;;;;;;;OAgBG;IACH,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe;CAqB3D"}
@@ -32,6 +32,20 @@ function hasErrorCode(error) {
32
32
  function getErrorMessage(error) {
33
33
  return error instanceof Error ? error.message : String(error);
34
34
  }
35
+ /**
36
+ * Whether an error represents a failed *interactive* browser login (the user
37
+ * did not complete the OAuth flow / it timed out), as opposed to a transient or
38
+ * configuration error. Such failures must not be retried via a second
39
+ * provider.getTokens() — that would start a duplicate browser login on the same
40
+ * redirect port and mask the real cause with a "Port in use" error.
41
+ */
42
+ function isInteractiveAuthFailure(error) {
43
+ if (hasErrorCode(error) && error.code === 'BROWSER_AUTH_ERROR') {
44
+ return true;
45
+ }
46
+ const message = getErrorMessage(error);
47
+ return /authentication timeout|browser authentication|already in use/i.test(message);
48
+ }
35
49
  /**
36
50
  * AuthBroker manages JWT authentication tokens for destinations
37
51
  */
@@ -244,12 +258,19 @@ class AuthBroker {
244
258
  this.logger?.error(`Failed to save connection config to session for ${destination}: ${getErrorMessage(error)}`);
245
259
  throw new Error(`Failed to save connection config for destination "${destination}": ${getErrorMessage(error)}`);
246
260
  }
247
- try {
248
- await this.sessionStore.setAuthorizationConfig(destination, authorizationConfig);
261
+ if (authorizationConfig.uaaUrl &&
262
+ authorizationConfig.uaaClientId &&
263
+ authorizationConfig.uaaClientSecret) {
264
+ try {
265
+ await this.sessionStore.setAuthorizationConfig(destination, authorizationConfig);
266
+ }
267
+ catch (error) {
268
+ this.logger?.error(`Failed to save authorization config to session for ${destination}: ${getErrorMessage(error)}`);
269
+ throw new Error(`Failed to save authorization config for destination "${destination}": ${getErrorMessage(error)}`);
270
+ }
249
271
  }
250
- catch (error) {
251
- this.logger?.error(`Failed to save authorization config to session for ${destination}: ${getErrorMessage(error)}`);
252
- throw new Error(`Failed to save authorization config for destination "${destination}": ${getErrorMessage(error)}`);
272
+ else {
273
+ this.logger?.debug(`Skipping authorization config save for ${destination}: missing UAA fields`);
253
274
  }
254
275
  }
255
276
  async requestTokens(destination, sourceLabel) {
@@ -433,11 +454,31 @@ class AuthBroker {
433
454
  throw error;
434
455
  }
435
456
  if (!this.serviceKeyStore) {
457
+ const tokenType = this.tokenProvider?.tokenType;
458
+ if (tokenType === 'saml') {
459
+ const tokenResult = await this.requestTokens(destination, 'session');
460
+ await this.persistTokenResult(destination, serviceUrl, connConfig, authConfig || {}, tokenResult);
461
+ this.logger?.info(`[AuthBroker] Token retrieved for ${destination} (SAML without auth config)`, {
462
+ authorizationToken: (0, formatting_1.formatToken)(tokenResult.authorizationToken),
463
+ });
464
+ return tokenResult.authorizationToken;
465
+ }
436
466
  if (lastError) {
437
467
  throw lastError;
438
468
  }
439
469
  throw new Error(`Authorization config not found for ${destination}. Session has no auth config and serviceKeyStore is not available.`);
440
470
  }
471
+ // If the session attempt already performed an *interactive* browser login
472
+ // and it failed (the user didn't complete it / it timed out), the serviceKey
473
+ // strategy would call the SAME provider.getTokens() again and start a
474
+ // duplicate browser login on the same redirect port — surfacing a misleading
475
+ // "Port in use" instead of the real cause. Don't retry interactive failures;
476
+ // propagate the original error. Transient/non-interactive session failures
477
+ // still fall through to the serviceKey attempt below.
478
+ if (lastError && isInteractiveAuthFailure(lastError)) {
479
+ this.logger?.debug(`Step 2: session login failed interactively for ${destination}; not retrying via service key (${getErrorMessage(lastError)})`);
480
+ throw lastError;
481
+ }
441
482
  const serviceKeyAuthConfig = await this.getAuthorizationConfigFromServiceKey(destination);
442
483
  const tokenResult = await this.requestTokens(destination, 'serviceKey');
443
484
  await this.persistTokenResult(destination, serviceUrl, connConfig, serviceKeyAuthConfig, tokenResult);
@@ -53,6 +53,7 @@ var __importStar = (this && this.__importStar) || (function () {
53
53
  };
54
54
  })();
55
55
  Object.defineProperty(exports, "__esModule", { value: true });
56
+ const child_process_1 = require("child_process");
56
57
  const fs = __importStar(require("fs"));
57
58
  const path = __importStar(require("path"));
58
59
  // Use require for CommonJS dist files with absolute path
@@ -86,6 +87,7 @@ function showHelp() {
86
87
  console.log('MCP Auth - Get tokens and generate .env files from service keys');
87
88
  console.log('');
88
89
  console.log('Usage:');
90
+ console.log(' mcp-auth <auth-code|oidc|saml2-pure|saml2-bearer> [options]');
89
91
  console.log(' mcp-auth --service-key <path> --output <path> [options]');
90
92
  console.log('');
91
93
  console.log('Required Options:');
@@ -97,6 +99,7 @@ function showHelp() {
97
99
  console.log('');
98
100
  console.log('Optional Options:');
99
101
  console.log(' --type <type> Auth type: abap or xsuaa (default: abap)');
102
+ console.log(' --dev Enable in-progress commands (saml2-bearer)');
100
103
  console.log(' --credential Use client_credentials flow (clientId/clientSecret, no browser)');
101
104
  console.log(' By default uses authorization_code flow');
102
105
  console.log(' --browser <browser> Browser for authorization_code flow (default: auto):');
@@ -111,6 +114,18 @@ function showHelp() {
111
114
  console.log(' --help, -h Show this help message');
112
115
  console.log('');
113
116
  console.log('Examples:');
117
+ console.log(' # Auth code (default flow via service key)');
118
+ console.log(' mcp-auth auth-code --service-key ./service-key.json --output ./mcp.env --type xsuaa');
119
+ console.log('');
120
+ console.log(' # OIDC SSO (device/password/browser/token-exchange)');
121
+ console.log(' mcp-auth oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa');
122
+ console.log('');
123
+ console.log(' # SAML2 pure (cookies)');
124
+ console.log(' mcp-auth saml2-pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --output ./saml.env --type abap');
125
+ console.log('');
126
+ console.log(' # SAML2 bearer (in progress, requires --dev)');
127
+ console.log(' mcp-auth saml2-bearer --dev --service-key ./service-key.json --assertion <base64> --output ./sso.env --type xsuaa');
128
+ console.log('');
114
129
  console.log(' # XSUAA with authorization_code (default, opens browser)');
115
130
  console.log(' mcp-auth --service-key ./service-key.json --output ./mcp.env --type xsuaa');
116
131
  console.log('');
@@ -151,8 +166,7 @@ function showHelp() {
151
166
  console.log(' - For ABAP, serviceUrl (SAP URL) is required - can be provided via --service-url or service key');
152
167
  console.log(' - SAP_URL/XSUAA_MCP_URL is written to .env (from --service-url, service key, or placeholder)');
153
168
  }
154
- function parseArgs() {
155
- const args = process.argv.slice(2);
169
+ function parseArgs(args = process.argv.slice(2)) {
156
170
  // Handle --version and --help first
157
171
  if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
158
172
  showHelp();
@@ -348,8 +362,76 @@ function writeJsonFile(outputPath, token, refreshToken, serviceUrl, uaaUrl, uaaC
348
362
  // Write to file
349
363
  fs.writeFileSync(outputPath, JSON.stringify(outputData, null, 2), 'utf8');
350
364
  }
365
+ function runMcpSso(args) {
366
+ const mcpSsoPath = path.resolve(__dirname, 'mcp-sso.js');
367
+ const result = (0, child_process_1.spawnSync)(process.execPath, [mcpSsoPath, ...args], {
368
+ stdio: 'inherit',
369
+ });
370
+ if (result.error) {
371
+ throw result.error;
372
+ }
373
+ process.exit(result.status ?? 1);
374
+ }
351
375
  async function main() {
352
- const options = parseArgs();
376
+ const rawArgs = process.argv.slice(2);
377
+ const subcommand = rawArgs[0];
378
+ const hasSubcommand = subcommand && !subcommand.startsWith('-') && subcommand.length > 0;
379
+ if (hasSubcommand) {
380
+ const remaining = rawArgs.slice(1);
381
+ const ensureNoProtocol = () => {
382
+ if (remaining.includes('--protocol')) {
383
+ console.error('❌ --protocol is not supported with subcommands.');
384
+ process.exit(1);
385
+ }
386
+ };
387
+ switch (subcommand) {
388
+ case 'auth-code': {
389
+ break;
390
+ }
391
+ case 'oidc': {
392
+ ensureNoProtocol();
393
+ runMcpSso(['oidc', ...remaining]);
394
+ return;
395
+ }
396
+ case 'saml2-pure': {
397
+ ensureNoProtocol();
398
+ if (remaining.includes('--flow')) {
399
+ const idx = remaining.indexOf('--flow');
400
+ const flow = remaining[idx + 1];
401
+ if (flow && flow !== 'pure') {
402
+ console.error('❌ saml2-pure requires --flow pure.');
403
+ process.exit(1);
404
+ }
405
+ }
406
+ else {
407
+ remaining.unshift('pure');
408
+ remaining.unshift('--flow');
409
+ }
410
+ runMcpSso(['saml2', ...remaining]);
411
+ return;
412
+ }
413
+ case 'saml2-bearer': {
414
+ ensureNoProtocol();
415
+ if (!remaining.includes('--dev')) {
416
+ console.error('⚠️ saml2-bearer is in progress. Re-run with --dev to enable.');
417
+ process.exit(1);
418
+ }
419
+ const filtered = remaining.filter((arg) => arg !== '--dev');
420
+ if (filtered.includes('--flow')) {
421
+ console.error('❌ saml2-bearer does not accept --flow.');
422
+ process.exit(1);
423
+ }
424
+ runMcpSso(['bearer', ...filtered]);
425
+ return;
426
+ }
427
+ default: {
428
+ console.error(`Unknown command: ${subcommand}`);
429
+ showHelp();
430
+ process.exit(1);
431
+ }
432
+ }
433
+ }
434
+ const options = parseArgs(hasSubcommand ? rawArgs.slice(1) : rawArgs);
353
435
  if (!options) {
354
436
  // Help or version was shown, exit already handled
355
437
  return;
@@ -4,26 +4,27 @@
4
4
  * MCP SSO - Get tokens via SSO providers and generate .env files
5
5
  *
6
6
  * Usage:
7
+ * mcp-sso <oidc|saml2|bearer> [options]
7
8
  * mcp-sso --protocol <oidc|saml2> --flow <flow> --output <path> [options]
8
9
  *
9
10
  * Examples:
10
11
  * # OIDC browser flow (authorization code with local callback)
11
- * mcp-sso --protocol oidc --flow browser --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
12
+ * mcp-sso oidc --flow browser --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
12
13
  *
13
14
  * # OIDC device flow
14
- * mcp-sso --protocol oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
15
+ * mcp-sso oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa
15
16
  *
16
- * # OIDC password flow (passcode)
17
- * mcp-sso --protocol oidc --flow password --token-endpoint https://uaa.example/oauth/token --client-id cf --passcode <code> --output ./sso.env --type xsuaa
17
+ * # OIDC password flow
18
+ * mcp-sso oidc --flow password --token-endpoint https://issuer/oauth/token --client-id my-client --username user --password pass --output ./sso.env --type xsuaa
18
19
  *
19
20
  * # OIDC token exchange
20
- * mcp-sso --protocol oidc --flow token_exchange --issuer https://issuer --client-id my-client --subject-token <token> --output ./sso.env --type xsuaa
21
+ * mcp-sso oidc --flow token_exchange --issuer https://issuer --client-id my-client --subject-token <token> --output ./sso.env --type xsuaa
21
22
  *
22
23
  * # SAML bearer flow
23
- * mcp-sso --protocol saml2 --flow bearer --idp-sso-url https://idp/sso --sp-entity-id my-sp --token-endpoint https://uaa.example/oauth/token --assertion <base64> --output ./sso.env --type xsuaa
24
+ * mcp-sso bearer --idp-sso-url https://idp/sso --sp-entity-id my-sp --token-endpoint https://uaa.example/oauth/token --assertion <base64> --output ./sso.env --type xsuaa
24
25
  *
25
26
  * # SAML pure flow (cookie)
26
- * mcp-sso --protocol saml2 --flow pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --assertion <base64> --cookie "SAP_SESSION=..." --output ./sso.env --type abap
27
+ * mcp-sso saml2 --flow pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --assertion <base64> --cookie "SAP_SESSION=..." --output ./sso.env --type abap
27
28
  */
28
29
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
29
30
  if (k2 === undefined) k2 = k;
@@ -58,17 +59,14 @@ var __importStar = (this && this.__importStar) || (function () {
58
59
  return result;
59
60
  };
60
61
  })();
61
- var __importDefault = (this && this.__importDefault) || function (mod) {
62
- return (mod && mod.__esModule) ? mod : { "default": mod };
63
- };
64
62
  Object.defineProperty(exports, "__esModule", { value: true });
65
63
  const node_readline_1 = require("node:readline");
66
- const axios_1 = __importDefault(require("axios"));
67
64
  const fs = __importStar(require("fs"));
68
65
  const path = __importStar(require("path"));
69
66
  // Use require for CommonJS dist files with absolute path
70
67
  const distPath = path.resolve(__dirname, '..', 'index.js');
71
68
  const { AuthBroker } = require(distPath);
69
+ const logger_1 = require("@mcp-abap-adt/logger");
72
70
  const auth_providers_1 = require("@mcp-abap-adt/auth-providers");
73
71
  const auth_stores_1 = require("@mcp-abap-adt/auth-stores");
74
72
  function getVersion() {
@@ -97,14 +95,16 @@ function showHelp() {
97
95
  console.log('MCP SSO - Get tokens via SSO providers and generate .env files');
98
96
  console.log('');
99
97
  console.log('Usage:');
98
+ console.log(' mcp-sso <oidc|saml2|bearer> [options]');
100
99
  console.log(' mcp-sso --protocol <oidc|saml2> --flow <flow> --output <path> [options]');
101
100
  console.log('');
102
101
  console.log('Required Options:');
103
102
  console.log(' --output <path> Output file path');
104
- console.log(' --protocol <oidc|saml2> Protocol');
105
- console.log(' --flow <flow> Flow for protocol');
103
+ console.log(' --protocol <oidc|saml2> Protocol (if no subcommand)');
104
+ console.log(' --flow <flow> Flow for protocol (if no subcommand)');
106
105
  console.log('');
107
106
  console.log('Common Options:');
107
+ console.log(' --service-key <path> Service key JSON (XSUAA/ABAP)');
108
108
  console.log(' --type <abap|xsuaa> Output type (default: abap)');
109
109
  console.log(' --format <env|json> Output format (default: env)');
110
110
  console.log(' --env <path> Optional existing env file (used for refresh)');
@@ -134,12 +134,12 @@ function showHelp() {
134
134
  console.log(' --actor-token <token> Actor token for token exchange');
135
135
  console.log(' --actor-token-type <type> Actor token type for token exchange');
136
136
  console.log(' --uaa-url <url> UAA base URL (used to build token endpoint)');
137
- console.log(' --cf-api <url> CF API URL (resolve login/uaa endpoints)');
138
137
  console.log('');
139
138
  console.log('SAML Options:');
140
139
  console.log(' --idp-sso-url <url> IdP SSO URL');
141
140
  console.log(' --sp-entity-id <id> SP Entity ID');
142
141
  console.log(' --acs-url <url> ACS URL (default: http://localhost:<port>/callback)');
142
+ console.log(' --saml-metadata <path> SAML metadata XML (to resolve token alias)');
143
143
  console.log(' --relay-state <value> RelayState (optional)');
144
144
  console.log(' --assertion-flow <flow> browser|manual|assertion (default: browser)');
145
145
  console.log(' --assertion <base64> SAMLResponse (base64)');
@@ -170,8 +170,47 @@ function readManualInput(prompt) {
170
170
  });
171
171
  });
172
172
  }
173
+ function createCliLogger(prefix = 'SSO') {
174
+ const isEnabled = () => {
175
+ if (process.env.DEBUG_SSO === 'false' ||
176
+ process.env.DEBUG_AUTH_SSO === 'false') {
177
+ return false;
178
+ }
179
+ if (process.env.DEBUG_SSO === 'true' ||
180
+ process.env.DEBUG_AUTH_SSO === 'true' ||
181
+ process.env.DEBUG === 'true' ||
182
+ process.env.DEBUG?.includes('sso') === true ||
183
+ process.env.DEBUG?.includes('auth-sso') === true) {
184
+ return true;
185
+ }
186
+ return false;
187
+ };
188
+ const baseLogger = new logger_1.DefaultLogger((0, logger_1.getLogLevel)());
189
+ return {
190
+ debug: (message, meta) => {
191
+ if (isEnabled()) {
192
+ baseLogger.debug(`[${prefix}] ${message}`, meta);
193
+ }
194
+ },
195
+ info: (message, meta) => {
196
+ if (isEnabled()) {
197
+ baseLogger.info(`[${prefix}] ${message}`, meta);
198
+ }
199
+ },
200
+ warn: (message, meta) => {
201
+ if (isEnabled()) {
202
+ baseLogger.warn(`[${prefix}] ${message}`, meta);
203
+ }
204
+ },
205
+ error: (message, meta) => {
206
+ if (isEnabled()) {
207
+ baseLogger.error(`[${prefix}] ${message}`, meta);
208
+ }
209
+ },
210
+ };
211
+ }
173
212
  function parseArgs() {
174
- const args = process.argv.slice(2);
213
+ let args = process.argv.slice(2);
175
214
  if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
176
215
  showHelp();
177
216
  process.exit(0);
@@ -183,6 +222,7 @@ function parseArgs() {
183
222
  let outputFile;
184
223
  let envFilePath;
185
224
  let destination;
225
+ let serviceKeyPath;
186
226
  let authType = 'abap';
187
227
  let format = 'env';
188
228
  let protocol;
@@ -217,7 +257,26 @@ function parseArgs() {
217
257
  let assertion;
218
258
  let cookie;
219
259
  let uaaUrl;
220
- let cfApi;
260
+ let samlMetadataPath;
261
+ const firstArg = args[0];
262
+ if (firstArg && !firstArg.startsWith('-')) {
263
+ if (firstArg === 'oidc') {
264
+ protocol = 'oidc';
265
+ }
266
+ else if (firstArg === 'saml2') {
267
+ protocol = 'saml2';
268
+ }
269
+ else if (firstArg === 'bearer') {
270
+ protocol = 'saml2';
271
+ flow = 'bearer';
272
+ }
273
+ else {
274
+ console.error(`Unknown command: ${firstArg}`);
275
+ showHelp();
276
+ process.exit(1);
277
+ }
278
+ args = args.slice(1);
279
+ }
221
280
  for (let i = 0; i < args.length; i++) {
222
281
  const arg = args[i];
223
282
  const next = i + 1 < args.length ? args[i + 1] : undefined;
@@ -230,6 +289,10 @@ function parseArgs() {
230
289
  envFilePath = next;
231
290
  i++;
232
291
  break;
292
+ case '--service-key':
293
+ serviceKeyPath = next;
294
+ i++;
295
+ break;
233
296
  case '--destination':
234
297
  destination = next;
235
298
  i++;
@@ -402,8 +465,8 @@ function parseArgs() {
402
465
  uaaUrl = next;
403
466
  i++;
404
467
  break;
405
- case '--cf-api':
406
- cfApi = next;
468
+ case '--saml-metadata':
469
+ samlMetadataPath = next;
407
470
  i++;
408
471
  break;
409
472
  default:
@@ -414,6 +477,7 @@ function parseArgs() {
414
477
  outputFile,
415
478
  envFilePath,
416
479
  destination,
480
+ serviceKeyPath,
417
481
  authType,
418
482
  format,
419
483
  protocol,
@@ -448,9 +512,14 @@ function parseArgs() {
448
512
  assertion,
449
513
  cookie,
450
514
  uaaUrl,
451
- cfApi,
515
+ samlMetadataPath,
452
516
  };
453
517
  }
518
+ function resolveSamlTokenAlias(metadataXml) {
519
+ const regex = /<md:AssertionConsumerService[^>]*Location="([^"]*\/oauth\/token\/alias\/[^"]+)"/i;
520
+ const match = metadataXml.match(regex);
521
+ return match?.[1];
522
+ }
454
523
  function normalizeProviderConfig(raw) {
455
524
  if (!raw || typeof raw !== 'object') {
456
525
  return null;
@@ -520,10 +589,13 @@ function buildSamlConfig(options) {
520
589
  : assertionFlow === 'assertion'
521
590
  ? async () => readManualInput('Paste SAMLResponse: ')
522
591
  : undefined;
523
- const cookieProvider = async () => {
592
+ const cookieProvider = async (samlResponse) => {
524
593
  if (options.cookie) {
525
594
  return options.cookie;
526
595
  }
596
+ if (assertionFlow === 'assertion') {
597
+ return `SAMLResponse=${samlResponse}`;
598
+ }
527
599
  return readManualInput('Paste session cookies: ');
528
600
  };
529
601
  return {
@@ -632,18 +704,12 @@ function buildProviderConfig(options, existingAuth, existingConn, fileConfig) {
632
704
  }
633
705
  return result;
634
706
  }
635
- async function resolveCfEndpoints(apiUrl) {
636
- const response = await axios_1.default.get(apiUrl);
637
- const data = response.data;
638
- const loginUrl = data?.links?.login?.href;
639
- const uaaUrl = data?.links?.uaa?.href;
640
- return { loginUrl, uaaUrl };
641
- }
642
707
  async function main() {
643
708
  const options = parseArgs();
644
709
  if (!options) {
645
710
  return;
646
711
  }
712
+ const logger = createCliLogger();
647
713
  if (!options.outputFile) {
648
714
  console.error('❌ Missing required --output');
649
715
  process.exit(1);
@@ -653,6 +719,19 @@ async function main() {
653
719
  ? path.resolve(options.envFilePath)
654
720
  : undefined;
655
721
  let destination = options.destination;
722
+ if (options.serviceKeyPath) {
723
+ const resolvedServiceKeyPath = path.resolve(options.serviceKeyPath);
724
+ if (!fs.existsSync(resolvedServiceKeyPath)) {
725
+ console.error(`❌ Service key file not found: ${resolvedServiceKeyPath}`);
726
+ process.exit(1);
727
+ }
728
+ const serviceKeyFileName = path.basename(resolvedServiceKeyPath, path.extname(resolvedServiceKeyPath));
729
+ if (destination && destination !== serviceKeyFileName) {
730
+ console.error(`❌ Destination mismatch: service key (${serviceKeyFileName}) vs output (${destination})`);
731
+ process.exit(1);
732
+ }
733
+ destination = serviceKeyFileName;
734
+ }
656
735
  if (!destination) {
657
736
  destination = path.basename(resolvedOutputPath, path.extname(resolvedOutputPath));
658
737
  }
@@ -663,6 +742,24 @@ async function main() {
663
742
  process.exit(1);
664
743
  }
665
744
  }
745
+ const allowTokenEndpointWithServiceKey = options.protocol === 'saml2' && options.flow === 'bearer';
746
+ const serviceKeyConflicts = options.serviceKeyPath &&
747
+ (options.configPath ||
748
+ options.issuerUrl ||
749
+ options.authorizationEndpoint ||
750
+ (!allowTokenEndpointWithServiceKey && options.tokenEndpoint) ||
751
+ options.deviceAuthorizationEndpoint ||
752
+ options.clientId ||
753
+ options.clientSecret ||
754
+ options.uaaUrl);
755
+ if (serviceKeyConflicts) {
756
+ console.error('❌ Use either --service-key or explicit OIDC/SAML parameters (issuer/token/client/uaa).');
757
+ process.exit(1);
758
+ }
759
+ if (options.serviceKeyPath && options.authType !== 'xsuaa') {
760
+ console.error('❌ --service-key is supported only for XSUAA flows.');
761
+ process.exit(1);
762
+ }
666
763
  let providerConfigFromFile = null;
667
764
  if (options.configPath) {
668
765
  const resolvedConfigPath = path.resolve(options.configPath);
@@ -677,13 +774,50 @@ async function main() {
677
774
  process.exit(1);
678
775
  }
679
776
  }
680
- if (options.cfApi) {
681
- const cfInfo = await resolveCfEndpoints(options.cfApi);
682
- if (cfInfo.uaaUrl && !options.uaaUrl) {
683
- options.uaaUrl = cfInfo.uaaUrl;
777
+ if (options.serviceKeyPath) {
778
+ const resolvedServiceKeyPath = path.resolve(options.serviceKeyPath);
779
+ const serviceKeyDir = path.dirname(resolvedServiceKeyPath);
780
+ const serviceKeyStore = new auth_stores_1.XsuaaServiceKeyStore(serviceKeyDir);
781
+ const authConfig = await serviceKeyStore.getAuthorizationConfig(destination);
782
+ if (!authConfig) {
783
+ console.error(`❌ Authorization config not found for ${destination}. Service key must contain clientid, clientsecret, and url fields.`);
784
+ process.exit(1);
684
785
  }
685
- if (cfInfo.loginUrl) {
686
- console.log(`🔗 CF passcode URL: ${cfInfo.loginUrl.replace(/\/+$/, '')}/passcode`);
786
+ const uaaUrl = authConfig.uaaUrl;
787
+ if (!uaaUrl) {
788
+ console.error(`❌ Service key missing UAA URL for ${destination}.`);
789
+ process.exit(1);
790
+ }
791
+ options.uaaUrl = uaaUrl;
792
+ options.clientId = authConfig.uaaClientId;
793
+ options.clientSecret = authConfig.uaaClientSecret;
794
+ if (!options.issuerUrl) {
795
+ options.issuerUrl = uaaUrl;
796
+ }
797
+ if (!options.tokenEndpoint) {
798
+ options.tokenEndpoint = `${uaaUrl.replace(/\/+$/, '')}/oauth/token`;
799
+ }
800
+ if (!options.authorizationEndpoint) {
801
+ options.authorizationEndpoint = `${uaaUrl.replace(/\/+$/, '')}/oauth/authorize`;
802
+ }
803
+ }
804
+ if (options.samlMetadataPath) {
805
+ const resolvedMetadataPath = path.resolve(options.samlMetadataPath);
806
+ if (!fs.existsSync(resolvedMetadataPath)) {
807
+ console.error(`❌ SAML metadata file not found: ${resolvedMetadataPath}`);
808
+ process.exit(1);
809
+ }
810
+ const metadataXml = fs.readFileSync(resolvedMetadataPath, 'utf8');
811
+ const aliasUrl = resolveSamlTokenAlias(metadataXml);
812
+ if (!aliasUrl) {
813
+ console.error('❌ SAML metadata does not contain token alias endpoint.');
814
+ process.exit(1);
815
+ }
816
+ options.tokenEndpoint = aliasUrl;
817
+ }
818
+ if (options.protocol === 'oidc' && options.flow === 'password') {
819
+ if (options.uaaUrl && !options.tokenEndpoint) {
820
+ options.tokenEndpoint = `${options.uaaUrl.replace(/\/+$/, '')}/oauth/token`;
687
821
  }
688
822
  }
689
823
  if (options.protocol === 'oidc' && options.flow) {
@@ -713,10 +847,13 @@ async function main() {
713
847
  const tempEnvPath = path.join(tempSessionDir, `${destination}.env`);
714
848
  fs.copyFileSync(resolvedEnvPath, tempEnvPath);
715
849
  }
716
- const defaultServiceUrl = options.serviceUrl || '';
850
+ const placeholderServiceUrl = '<SERVICE_URL>';
851
+ const defaultServiceUrl = options.authType === 'xsuaa'
852
+ ? options.serviceUrl || placeholderServiceUrl
853
+ : options.serviceUrl || '';
717
854
  const sessionStore = options.authType === 'xsuaa'
718
855
  ? new auth_stores_1.XsuaaSessionStore(tempSessionDir, defaultServiceUrl)
719
- : new auth_stores_1.AbapSessionStore(tempSessionDir);
856
+ : new auth_stores_1.AbapSessionStore(tempSessionDir, undefined, options.serviceUrl);
720
857
  const existingConn = await sessionStore.getConnectionConfig(destination);
721
858
  const existingAuth = await sessionStore.getAuthorizationConfig(destination);
722
859
  const serviceUrl = options.serviceUrl || existingConn?.serviceUrl;
@@ -730,18 +867,47 @@ async function main() {
730
867
  console.error('❌ SAML pure flow is only supported for ABAP sessions (cookies)');
731
868
  process.exit(1);
732
869
  }
870
+ const isSamlPureAbap = options.authType === 'abap' &&
871
+ options.protocol === 'saml2' &&
872
+ options.flow === 'pure';
733
873
  await sessionStore.setConnectionConfig(destination, {
734
874
  serviceUrl: serviceUrl ||
735
875
  (options.authType === 'xsuaa' ? defaultServiceUrl : undefined),
736
- authorizationToken: existingConn?.authorizationToken,
876
+ authorizationToken: isSamlPureAbap
877
+ ? existingConn?.authorizationToken || '__init__'
878
+ : existingConn?.authorizationToken,
737
879
  sessionCookies: existingConn?.sessionCookies,
738
880
  });
881
+ let stripClientSecret = false;
882
+ const authUaaUrl = options.uaaUrl || options.tokenEndpoint || options.issuerUrl || undefined;
883
+ if (options.clientId && authUaaUrl) {
884
+ let clientSecret = options.clientSecret;
885
+ if (!clientSecret) {
886
+ clientSecret = '__public__';
887
+ stripClientSecret = true;
888
+ }
889
+ await sessionStore.setAuthorizationConfig(destination, {
890
+ uaaUrl: authUaaUrl,
891
+ uaaClientId: options.clientId,
892
+ uaaClientSecret: clientSecret,
893
+ refreshToken: existingAuth?.refreshToken,
894
+ });
895
+ }
739
896
  const providerConfig = buildProviderConfig(options, existingAuth, existingConn, providerConfigFromFile);
740
- const tokenProvider = auth_providers_1.SsoProviderFactory.create(providerConfig);
897
+ const providerConfigWithLogger = providerConfig.config
898
+ ? {
899
+ ...providerConfig,
900
+ config: {
901
+ ...providerConfig.config,
902
+ logger: providerConfig.config?.logger ?? logger,
903
+ },
904
+ }
905
+ : providerConfig;
906
+ const tokenProvider = auth_providers_1.SsoProviderFactory.create(providerConfigWithLogger);
741
907
  const broker = new AuthBroker({
742
908
  sessionStore,
743
909
  tokenProvider,
744
- }, options.browser);
910
+ }, options.browser, logger);
745
911
  console.log(`🔐 Getting token for destination "${destination}"...`);
746
912
  await broker.getToken(destination);
747
913
  console.log(`✅ Token obtained successfully`);
@@ -766,7 +932,21 @@ async function main() {
766
932
  if (!fs.existsSync(outputDir)) {
767
933
  fs.mkdirSync(outputDir, { recursive: true });
768
934
  }
769
- fs.copyFileSync(tempEnvPath, resolvedOutputPath);
935
+ let envContent = fs.readFileSync(tempEnvPath, 'utf8');
936
+ if (options.authType === 'xsuaa' && !serviceUrl) {
937
+ const lines = envContent
938
+ .split('\n')
939
+ .filter((line) => !line.startsWith('XSUAA_MCP_URL='));
940
+ envContent = `${lines.join('\n')}\n`;
941
+ }
942
+ if (stripClientSecret) {
943
+ const lines = envContent
944
+ .split('\n')
945
+ .filter((line) => !line.startsWith('XSUAA_UAA_CLIENT_SECRET=') &&
946
+ !line.startsWith('SAP_UAA_CLIENT_SECRET='));
947
+ envContent = `${lines.join('\n')}\n`;
948
+ }
949
+ fs.writeFileSync(resolvedOutputPath, envContent, 'utf8');
770
950
  console.log(`✅ .env file created: ${resolvedOutputPath}`);
771
951
  }
772
952
  else {
@@ -792,7 +972,9 @@ async function main() {
792
972
  outputData.uaaClientId = authConfig.uaaClientId;
793
973
  }
794
974
  if (authConfig?.uaaClientSecret) {
795
- outputData.uaaClientSecret = authConfig.uaaClientSecret;
975
+ if (!stripClientSecret) {
976
+ outputData.uaaClientSecret = authConfig.uaaClientSecret;
977
+ }
796
978
  }
797
979
  const outputDir = path.dirname(resolvedOutputPath);
798
980
  if (!fs.existsSync(outputDir)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/auth-broker",
3
- "version": "1.0.3",
3
+ "version": "1.0.6",
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",
@@ -45,6 +45,11 @@
45
45
  "build:fast": "tsc -p tsconfig.json && tsc -p tsconfig.cli.json",
46
46
  "test": "NODE_OPTIONS=--experimental-vm-modules jest",
47
47
  "test:check": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
48
+ "test:mcp-auth": "sh tests/sso-demo/run-mcp-auth.sh",
49
+ "test:mcp-sso": "sh tests/sso-demo/run-mcp-sso.sh",
50
+ "test:device-code": "sh tests/keycloak/run-oidc.sh",
51
+ "test:saml-pure": "sh tests/keycloak/run-saml.sh",
52
+ "test:sso": "sh tests/keycloak/run-interactive.sh",
48
53
  "prepublishOnly": "npm run build",
49
54
  "prepack": "npm run build",
50
55
  "generate-env": "tsx bin/generate-env-from-service-key.ts"
@@ -57,8 +62,8 @@
57
62
  "node": ">=18.0.0"
58
63
  },
59
64
  "dependencies": {
60
- "@mcp-abap-adt/auth-providers": "^1.0.2",
61
- "@mcp-abap-adt/auth-stores": "^1.0.1",
65
+ "@mcp-abap-adt/auth-providers": "^1.0.5",
66
+ "@mcp-abap-adt/auth-stores": "^1.0.2",
62
67
  "@mcp-abap-adt/interfaces": "^2.3.0",
63
68
  "axios": "^1.13.5",
64
69
  "tsx": "^4.21.0"