@mcp-abap-adt/auth-broker 0.2.16 â 0.3.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 +21 -0
- package/bin/mcp-auth.ts +120 -25
- package/package.json +3 -4
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,27 @@ Thank you to all contributors! See [CONTRIBUTORS.md](CONTRIBUTORS.md) for the co
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [0.3.0] - 2025-12-31
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **CLI**: Auto-detect service key format (ABAP vs XSUAA) based on content structure
|
|
18
|
+
- **CLI**: Support for "credentials" wrapper in service keys (common in SAP BTP)
|
|
19
|
+
- **CLI**: Fallback parsing when stores fail to parse service key
|
|
20
|
+
- **CLI**: Log authorization URL and redirect URI for easier debugging
|
|
21
|
+
- **Documentation**: Added `CLAUDE.md` for Claude Code project guidance
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **Dependencies**: Updated `@mcp-abap-adt/auth-stores` from `^0.2.10` to `^0.3.0`
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **CLI**: Filter placeholder `<SERVICE_URL>` from output when not available
|
|
28
|
+
|
|
29
|
+
## [0.2.17] - 2025-12-31
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- **Dependencies**: Depend on the published `@mcp-abap-adt/auth-broker` `^0.2.17` package to keep the CLI wiring aligned with the latest release.
|
|
33
|
+
- **Dependencies**: Bump `@mcp-abap-adt/interfaces` to `^0.2.15` so consumers pick up the current interface contracts.
|
|
34
|
+
|
|
14
35
|
## [0.2.16] - 2025-12-31
|
|
15
36
|
|
|
16
37
|
### Changed
|
package/bin/mcp-auth.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
AbapSessionStore,
|
|
30
30
|
XsuaaServiceKeyStore,
|
|
31
31
|
XsuaaSessionStore,
|
|
32
|
+
JsonFileHandler,
|
|
32
33
|
} from '@mcp-abap-adt/auth-stores';
|
|
33
34
|
import {
|
|
34
35
|
AuthorizationCodeProvider,
|
|
@@ -378,9 +379,12 @@ async function main() {
|
|
|
378
379
|
destination = path.basename(resolvedEnvPath, path.extname(resolvedEnvPath));
|
|
379
380
|
}
|
|
380
381
|
|
|
382
|
+
// Store raw service key JSON for fallback parsing
|
|
383
|
+
let rawServiceKeyJson: any = null;
|
|
384
|
+
|
|
381
385
|
if (options.serviceKeyPath) {
|
|
382
386
|
const resolvedServiceKeyPath = path.resolve(options.serviceKeyPath);
|
|
383
|
-
|
|
387
|
+
let serviceKeyDir = path.dirname(resolvedServiceKeyPath);
|
|
384
388
|
|
|
385
389
|
// Check if service key file exists
|
|
386
390
|
if (!fs.existsSync(resolvedServiceKeyPath)) {
|
|
@@ -396,11 +400,62 @@ async function main() {
|
|
|
396
400
|
process.exit(1);
|
|
397
401
|
}
|
|
398
402
|
destination = serviceKeyFileName;
|
|
399
|
-
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
403
|
+
|
|
404
|
+
// Determine which store to use based on service key content
|
|
405
|
+
// ABAP format has nested "uaa" object, XSUAA format has flat structure
|
|
406
|
+
let isAbapFormat = options.authType === 'abap'; // default fallback
|
|
407
|
+
try {
|
|
408
|
+
// Use JsonFileHandler to ensure consistent parsing behavior
|
|
409
|
+
const json = await JsonFileHandler.load(path.basename(resolvedServiceKeyPath), serviceKeyDir);
|
|
410
|
+
|
|
411
|
+
let effectiveJson = json;
|
|
412
|
+
if (json && json.credentials) {
|
|
413
|
+
console.log('đ Detected "credentials" wrapper -> unwrapping to temp file');
|
|
414
|
+
effectiveJson = json.credentials;
|
|
415
|
+
|
|
416
|
+
// Create temp file with unwrapped content to make it compatible with standard stores
|
|
417
|
+
const tempDir = path.join(path.dirname(resolvedOutputPath), '.tmp');
|
|
418
|
+
if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true });
|
|
419
|
+
const tempKeyPath = path.join(tempDir, `${destination}.json`);
|
|
420
|
+
fs.writeFileSync(tempKeyPath, JSON.stringify(effectiveJson, null, 2));
|
|
421
|
+
|
|
422
|
+
// Point store to temp directory so it reads the unwrapped file
|
|
423
|
+
serviceKeyDir = tempDir;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Store raw JSON for fallback parsing
|
|
427
|
+
rawServiceKeyJson = effectiveJson;
|
|
428
|
+
|
|
429
|
+
if (effectiveJson) {
|
|
430
|
+
// If it has uaa property (even if null/string/object), XSUAA parser rejects it.
|
|
431
|
+
// So we must use ABAP store which expects uaa object.
|
|
432
|
+
if (effectiveJson.uaa) {
|
|
433
|
+
isAbapFormat = true;
|
|
434
|
+
} else {
|
|
435
|
+
isAbapFormat = false;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
} catch (e: any) {
|
|
439
|
+
// If parsing fails here, let the store handle the error
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Create appropriate stores based on detected format
|
|
443
|
+
serviceKeyStore = isAbapFormat
|
|
444
|
+
? new AbapServiceKeyStore(serviceKeyDir)
|
|
445
|
+
: new XsuaaServiceKeyStore(serviceKeyDir);
|
|
446
|
+
|
|
447
|
+
// Patch serviceKeyStore for XSUAA to ensure serviceUrl is present (AuthBroker requirement)
|
|
448
|
+
// even if not in the file. XSUAA doesn't strictly need it, but AuthBroker enforces it.
|
|
449
|
+
if (options.authType === 'xsuaa') {
|
|
450
|
+
const originalGetConnectionConfig = serviceKeyStore.getConnectionConfig.bind(serviceKeyStore);
|
|
451
|
+
serviceKeyStore.getConnectionConfig = async (dest: string) => {
|
|
452
|
+
const config = await originalGetConnectionConfig(dest);
|
|
453
|
+
if (config && !config.serviceUrl) {
|
|
454
|
+
config.serviceUrl = '<SERVICE_URL>';
|
|
455
|
+
}
|
|
456
|
+
return config;
|
|
457
|
+
};
|
|
458
|
+
}
|
|
404
459
|
}
|
|
405
460
|
|
|
406
461
|
if (!destination) {
|
|
@@ -443,10 +498,15 @@ async function main() {
|
|
|
443
498
|
// Resolve serviceUrl from service key if not provided explicitly
|
|
444
499
|
let actualServiceUrl = options.serviceUrl;
|
|
445
500
|
if (!actualServiceUrl && serviceKeyStore) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
501
|
+
try {
|
|
502
|
+
const serviceKeyConn = await serviceKeyStore.getConnectionConfig(
|
|
503
|
+
destination,
|
|
504
|
+
);
|
|
505
|
+
actualServiceUrl = serviceKeyConn?.serviceUrl;
|
|
506
|
+
} catch {
|
|
507
|
+
// For XSUAA, serviceUrl is optional and may not exist in service key
|
|
508
|
+
// This is expected - continue without serviceUrl
|
|
509
|
+
}
|
|
450
510
|
}
|
|
451
511
|
|
|
452
512
|
// For XSUAA, serviceUrl is optional - use placeholder only for AuthBroker internal work
|
|
@@ -459,17 +519,49 @@ async function main() {
|
|
|
459
519
|
|
|
460
520
|
const sessionAuthConfig =
|
|
461
521
|
await sessionStore.getAuthorizationConfig(destination);
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
522
|
+
let serviceKeyAuthConfig = null;
|
|
523
|
+
if (serviceKeyStore?.getAuthorizationConfig) {
|
|
524
|
+
try {
|
|
525
|
+
serviceKeyAuthConfig = await serviceKeyStore.getAuthorizationConfig(destination);
|
|
526
|
+
} catch (e: any) {
|
|
527
|
+
// Service key parsing might fail - try fallback parsing from raw JSON
|
|
528
|
+
console.log(`âšī¸ Store could not parse service key, using fallback parsing`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Fallback: if stores failed, try to construct authConfig from raw service key JSON
|
|
533
|
+
let fallbackAuthConfig = null;
|
|
534
|
+
if (!sessionAuthConfig && !serviceKeyAuthConfig && rawServiceKeyJson) {
|
|
535
|
+
// XSUAA format: clientid, clientsecret, url at top level
|
|
536
|
+
// ABAP format: uaa.clientid, uaa.clientsecret, uaa.url
|
|
537
|
+
const uaa = rawServiceKeyJson.uaa || rawServiceKeyJson;
|
|
538
|
+
if (uaa.clientid && uaa.clientsecret && uaa.url) {
|
|
539
|
+
fallbackAuthConfig = {
|
|
540
|
+
uaaUrl: uaa.url,
|
|
541
|
+
uaaClientId: uaa.clientid,
|
|
542
|
+
uaaClientSecret: uaa.clientsecret,
|
|
543
|
+
};
|
|
544
|
+
console.log(`â
Constructed auth config from raw service key`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const authConfig = sessionAuthConfig || serviceKeyAuthConfig || fallbackAuthConfig;
|
|
467
549
|
if (!authConfig) {
|
|
468
|
-
throw new Error(`Authorization config not found for ${destination}
|
|
550
|
+
throw new Error(`Authorization config not found for ${destination}. Service key must contain clientid, clientsecret, and url fields.`);
|
|
469
551
|
}
|
|
470
552
|
|
|
471
553
|
// Default: authorization_code flow with browser
|
|
472
554
|
// --credential: client_credentials flow (no browser needed)
|
|
555
|
+
const redirectPort = options.redirectPort || 3001;
|
|
556
|
+
|
|
557
|
+
// Log authorization URL for debugging
|
|
558
|
+
if (!options.credential) {
|
|
559
|
+
const redirectUri = `http://localhost:${redirectPort}/callback`;
|
|
560
|
+
const authorizationUrl = `${authConfig.uaaUrl}/oauth/authorize?client_id=${encodeURIComponent(authConfig.uaaClientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code`;
|
|
561
|
+
console.log(`đ Authorization URL: ${authorizationUrl}`);
|
|
562
|
+
console.log(`đ Redirect URI: ${redirectUri}`);
|
|
563
|
+
}
|
|
564
|
+
|
|
473
565
|
const tokenProvider = options.credential
|
|
474
566
|
? new ClientCredentialsProvider({
|
|
475
567
|
uaaUrl: authConfig.uaaUrl,
|
|
@@ -482,7 +574,7 @@ async function main() {
|
|
|
482
574
|
clientSecret: authConfig.uaaClientSecret,
|
|
483
575
|
refreshToken: authConfig.refreshToken,
|
|
484
576
|
browser: options.browser,
|
|
485
|
-
redirectPort:
|
|
577
|
+
redirectPort: redirectPort,
|
|
486
578
|
});
|
|
487
579
|
|
|
488
580
|
const broker = new AuthBroker(
|
|
@@ -508,6 +600,9 @@ async function main() {
|
|
|
508
600
|
|
|
509
601
|
const outputServiceUrl =
|
|
510
602
|
options.serviceUrl || connConfig?.serviceUrl || actualServiceUrl;
|
|
603
|
+
|
|
604
|
+
// Filter out placeholder service URL
|
|
605
|
+
const finalServiceUrl = outputServiceUrl === '<SERVICE_URL>' ? undefined : outputServiceUrl;
|
|
511
606
|
|
|
512
607
|
// Write output file based on format
|
|
513
608
|
if (options.format === 'env') {
|
|
@@ -516,7 +611,7 @@ async function main() {
|
|
|
516
611
|
options.authType,
|
|
517
612
|
token,
|
|
518
613
|
authConfig?.refreshToken,
|
|
519
|
-
|
|
614
|
+
finalServiceUrl,
|
|
520
615
|
authConfig?.uaaUrl,
|
|
521
616
|
authConfig?.uaaClientId,
|
|
522
617
|
authConfig?.uaaClientSecret,
|
|
@@ -527,8 +622,8 @@ async function main() {
|
|
|
527
622
|
// Show what was written
|
|
528
623
|
console.log(`đ .env file contains:`);
|
|
529
624
|
if (options.authType === 'abap') {
|
|
530
|
-
if (
|
|
531
|
-
console.log(` - ${ABAP_CONNECTION_VARS.SERVICE_URL}=${
|
|
625
|
+
if (finalServiceUrl) {
|
|
626
|
+
console.log(` - ${ABAP_CONNECTION_VARS.SERVICE_URL}=${finalServiceUrl}`);
|
|
532
627
|
}
|
|
533
628
|
console.log(
|
|
534
629
|
` - ${ABAP_CONNECTION_VARS.AUTHORIZATION_TOKEN}=${token.substring(0, 50)}...`,
|
|
@@ -539,8 +634,8 @@ async function main() {
|
|
|
539
634
|
);
|
|
540
635
|
}
|
|
541
636
|
} else {
|
|
542
|
-
if (
|
|
543
|
-
console.log(` - XSUAA_MCP_URL=${
|
|
637
|
+
if (finalServiceUrl) {
|
|
638
|
+
console.log(` - XSUAA_MCP_URL=${finalServiceUrl}`);
|
|
544
639
|
}
|
|
545
640
|
console.log(
|
|
546
641
|
` - ${XSUAA_CONNECTION_VARS.AUTHORIZATION_TOKEN}=${token.substring(0, 50)}...`,
|
|
@@ -556,7 +651,7 @@ async function main() {
|
|
|
556
651
|
resolvedOutputPath,
|
|
557
652
|
token,
|
|
558
653
|
authConfig?.refreshToken,
|
|
559
|
-
|
|
654
|
+
finalServiceUrl,
|
|
560
655
|
authConfig?.uaaUrl,
|
|
561
656
|
authConfig?.uaaClientId,
|
|
562
657
|
authConfig?.uaaClientSecret,
|
|
@@ -570,8 +665,8 @@ async function main() {
|
|
|
570
665
|
` - refreshToken: ${authConfig.refreshToken.substring(0, 50)}...`,
|
|
571
666
|
);
|
|
572
667
|
}
|
|
573
|
-
if (
|
|
574
|
-
console.log(` - serviceUrl: ${
|
|
668
|
+
if (finalServiceUrl) {
|
|
669
|
+
console.log(` - serviceUrl: ${finalServiceUrl}`);
|
|
575
670
|
}
|
|
576
671
|
}
|
|
577
672
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/auth-broker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -56,10 +56,9 @@
|
|
|
56
56
|
"node": ">=18.0.0"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@mcp-abap-adt/auth-broker": "file:mcp-abap-adt-auth-broker-0.2.15.tgz",
|
|
60
59
|
"@mcp-abap-adt/auth-providers": "^0.2.10",
|
|
61
|
-
"@mcp-abap-adt/auth-stores": "^0.
|
|
62
|
-
"@mcp-abap-adt/interfaces": "^0.2.
|
|
60
|
+
"@mcp-abap-adt/auth-stores": "^0.3.0",
|
|
61
|
+
"@mcp-abap-adt/interfaces": "^0.2.15",
|
|
63
62
|
"axios": "^1.13.2",
|
|
64
63
|
"tsx": "^4.21.0"
|
|
65
64
|
},
|