@mcp-abap-adt/proxy 1.1.0 → 1.2.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 +9 -0
- package/README.md +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/proxy/btpProxy.d.ts +23 -0
- package/dist/proxy/btpProxy.d.ts.map +1 -1
- package/dist/proxy/btpProxy.js +77 -6
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.0] - 2026-04-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Proactive JWT token refresh** — proxy now decodes the `exp` claim from the JWT and refreshes the token in the background 5 minutes before expiry via `authBroker.refreshToken()` (OAuth2 refresh_token grant, preserves role assignment). Eliminates latency spikes from on-demand 401 refreshes while keeping the reactive 401/403 fallback.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **@mcp-abap-adt/interfaces 7.0** — updated from v6.0 to v7.0
|
|
17
|
+
- **typescript 6.0.3**, **@biomejs/biome 2.4.12**, **@modelcontextprotocol/inspector 0.21.2**, **axios 1.15.1**, **@inquirer/prompts 8.4.2** — patch bumps
|
|
18
|
+
|
|
10
19
|
## [1.1.0] - 2026-04-12
|
|
11
20
|
|
|
12
21
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# @mcp-abap-adt/proxy
|
|
2
|
+
[](https://stand-with-ukraine.pp.ua)
|
|
2
3
|
|
|
3
4
|
MCP proxy server for SAP ABAP ADT - proxies local requests to MCP servers with JWT authentication.
|
|
4
5
|
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAuBH,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,0BAA0B,CAAC;AAqBlC;;GAEG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,QAAQ,CAAC,CAAW;gBAEhB,eAAe,CAAC,EAAE,eAAe,EAAE,UAAU,CAAC,EAAE,MAAM;IAoDlE;;OAEG;IACH,OAAO,CAAC,aAAa;IAMrB;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAuE1B;;OAEG;YACW,eAAe;IAgG7B;;OAEG;YACW,kBAAkB;IA8ChC;;OAEG;YACW,cAAc;IA6T5B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAuBH,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,0BAA0B,CAAC;AAqBlC;;GAEG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,QAAQ,CAAC,CAAW;gBAEhB,eAAe,CAAC,EAAE,eAAe,EAAE,UAAU,CAAC,EAAE,MAAM;IAoDlE;;OAEG;IACH,OAAO,CAAC,aAAa;IAMrB;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAuE1B;;OAEG;YACW,eAAe;IAgG7B;;OAEG;YACW,kBAAkB;IA8ChC;;OAEG;YACW,cAAc;IA6T5B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAoBhC;AAGD,eAAe,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/proxy/btpProxy.d.ts
CHANGED
|
@@ -38,7 +38,9 @@ export declare class BtpProxy {
|
|
|
38
38
|
private defaultBtpAuthBroker;
|
|
39
39
|
private btpAuthBrokers;
|
|
40
40
|
private tokenCache;
|
|
41
|
+
private refreshTimers;
|
|
41
42
|
private readonly TOKEN_CACHE_TTL;
|
|
43
|
+
private readonly REFRESH_LEAD_MS;
|
|
42
44
|
private circuitBreaker;
|
|
43
45
|
private config;
|
|
44
46
|
private unsafe;
|
|
@@ -64,6 +66,27 @@ export declare class BtpProxy {
|
|
|
64
66
|
* @param forceRefresh Force token refresh
|
|
65
67
|
*/
|
|
66
68
|
getJwtToken(destination: string, forceRefresh?: boolean): Promise<string>;
|
|
69
|
+
/**
|
|
70
|
+
* Decode JWT exp claim (seconds since epoch). Returns null if not parseable.
|
|
71
|
+
*/
|
|
72
|
+
private decodeJwtExp;
|
|
73
|
+
/**
|
|
74
|
+
* Cache token with expiry derived from JWT, and schedule proactive refresh
|
|
75
|
+
* 5 minutes before expiry.
|
|
76
|
+
*/
|
|
77
|
+
private cacheToken;
|
|
78
|
+
/**
|
|
79
|
+
* Schedule background refresh REFRESH_LEAD_MS before token expiry.
|
|
80
|
+
*/
|
|
81
|
+
private scheduleProactiveRefresh;
|
|
82
|
+
/**
|
|
83
|
+
* Background refresh via auth-broker's refreshToken grant.
|
|
84
|
+
*/
|
|
85
|
+
private proactiveRefresh;
|
|
86
|
+
/**
|
|
87
|
+
* Cancel all pending refresh timers. Call on shutdown.
|
|
88
|
+
*/
|
|
89
|
+
dispose(): void;
|
|
67
90
|
/**
|
|
68
91
|
* Get the target service URL for a destination.
|
|
69
92
|
* Priority: config.targetUrl > service key's serviceUrl
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"btpProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/btpProxy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAgB,MAAM,2BAA2B,CAAC;AAYrE,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA4CnE;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAU3C;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAGD,MAAM,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,oBAAoB,CAAa;IACzC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,UAAU,CACN;IACZ,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAU;gBAEZ,oBAAoB,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IAqF3E;;;OAGG;IACU,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB3D;;;;OAIG;YACW,wBAAwB;IAqFtC;;OAEG;YACW,uBAAuB;IAyFrC;;;;OAIG;IACG,WAAW,CACf,WAAW,EAAE,MAAM,EACnB,YAAY,GAAE,OAAe,GAC5B,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"btpProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/btpProxy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAgB,MAAM,2BAA2B,CAAC;AAYrE,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA4CnE;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAU3C;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAGD,MAAM,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,oBAAoB,CAAa;IACzC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,UAAU,CACN;IACZ,OAAO,CAAC,aAAa,CAA0C;IAC/D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAU;gBAEZ,oBAAoB,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IAqF3E;;;OAGG;IACU,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB3D;;;;OAIG;YACW,wBAAwB;IAqFtC;;OAEG;YACW,uBAAuB;IAyFrC;;;;OAIG;IACG,WAAW,CACf,WAAW,EAAE,MAAM,EACnB,YAAY,GAAE,OAAe,GAC5B,OAAO,CAAC,MAAM,CAAC;IAiHlB;;OAEG;IACH,OAAO,CAAC,YAAY;IAapB;;;OAGG;IACH,OAAO,CAAC,UAAU;IASlB;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA6BhC;;OAEG;YACW,gBAAgB;IAU9B;;OAEG;IACI,OAAO,IAAI,IAAI;IAOtB;;;OAGG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAexD;;OAEG;IACH,OAAO,CAAC,cAAc;IAUtB;;;;;;;;;;;;;;;;OAgBG;YACW,iBAAiB;IAmT/B;;OAEG;IACG,YAAY,CAChB,eAAe,EAAE,YAAY,EAC7B,eAAe,EAAE,eAAe,EAChC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAC7D,OAAO,CAAC,aAAa,CAAC;IAiLzB;;OAEG;WACiB,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;CAyB7E;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,QAAQ,CAAC,CAEnB"}
|
package/dist/proxy/btpProxy.js
CHANGED
|
@@ -58,7 +58,9 @@ class BtpProxy {
|
|
|
58
58
|
defaultBtpAuthBroker;
|
|
59
59
|
btpAuthBrokers = new Map();
|
|
60
60
|
tokenCache = new Map();
|
|
61
|
-
|
|
61
|
+
refreshTimers = new Map();
|
|
62
|
+
TOKEN_CACHE_TTL = 30 * 60 * 1000; // fallback if JWT has no exp
|
|
63
|
+
REFRESH_LEAD_MS = 5 * 60 * 1000; // refresh 5 min before expiry
|
|
62
64
|
circuitBreaker;
|
|
63
65
|
config;
|
|
64
66
|
unsafe;
|
|
@@ -349,11 +351,7 @@ class BtpProxy {
|
|
|
349
351
|
});
|
|
350
352
|
// Get token from auth-broker
|
|
351
353
|
const token = await authBroker.getToken(destination);
|
|
352
|
-
|
|
353
|
-
this.tokenCache.set(destination, {
|
|
354
|
-
token,
|
|
355
|
-
expiresAt: Date.now() + this.TOKEN_CACHE_TTL,
|
|
356
|
-
});
|
|
354
|
+
this.cacheToken(destination, token);
|
|
357
355
|
logger_js_1.logger?.debug('Retrieved JWT token from auth-broker', {
|
|
358
356
|
type: 'JWT_TOKEN_RETRIEVED',
|
|
359
357
|
destination,
|
|
@@ -404,6 +402,79 @@ class BtpProxy {
|
|
|
404
402
|
throw new Error(errorMessage);
|
|
405
403
|
}
|
|
406
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Decode JWT exp claim (seconds since epoch). Returns null if not parseable.
|
|
407
|
+
*/
|
|
408
|
+
decodeJwtExp(token) {
|
|
409
|
+
const parts = token.split('.');
|
|
410
|
+
if (parts.length < 2)
|
|
411
|
+
return null;
|
|
412
|
+
try {
|
|
413
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
414
|
+
return typeof payload.exp === 'number' ? payload.exp : null;
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Cache token with expiry derived from JWT, and schedule proactive refresh
|
|
422
|
+
* 5 minutes before expiry.
|
|
423
|
+
*/
|
|
424
|
+
cacheToken(destination, token) {
|
|
425
|
+
const exp = this.decodeJwtExp(token);
|
|
426
|
+
const expiresAt = exp !== null ? exp * 1000 : Date.now() + this.TOKEN_CACHE_TTL;
|
|
427
|
+
this.tokenCache.set(destination, { token, expiresAt });
|
|
428
|
+
this.scheduleProactiveRefresh(destination, expiresAt);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Schedule background refresh REFRESH_LEAD_MS before token expiry.
|
|
432
|
+
*/
|
|
433
|
+
scheduleProactiveRefresh(destination, expiresAt) {
|
|
434
|
+
const existing = this.refreshTimers.get(destination);
|
|
435
|
+
if (existing)
|
|
436
|
+
clearTimeout(existing);
|
|
437
|
+
const delay = expiresAt - Date.now() - this.REFRESH_LEAD_MS;
|
|
438
|
+
if (delay <= 0) {
|
|
439
|
+
// Token already within lead window or expired; skip scheduling.
|
|
440
|
+
this.refreshTimers.delete(destination);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const timer = setTimeout(() => {
|
|
444
|
+
this.refreshTimers.delete(destination);
|
|
445
|
+
this.proactiveRefresh(destination).catch((err) => {
|
|
446
|
+
logger_js_1.logger?.warn('Proactive token refresh failed, will retry reactively', {
|
|
447
|
+
type: 'JWT_TOKEN_PROACTIVE_REFRESH_ERROR',
|
|
448
|
+
destination,
|
|
449
|
+
error: err instanceof Error ? err.message : String(err),
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
}, delay);
|
|
453
|
+
// Don't keep the event loop alive just for refresh timers.
|
|
454
|
+
timer.unref?.();
|
|
455
|
+
this.refreshTimers.set(destination, timer);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Background refresh via auth-broker's refreshToken grant.
|
|
459
|
+
*/
|
|
460
|
+
async proactiveRefresh(destination) {
|
|
461
|
+
logger_js_1.logger?.info('Proactively refreshing JWT token', {
|
|
462
|
+
type: 'JWT_TOKEN_PROACTIVE_REFRESH',
|
|
463
|
+
destination,
|
|
464
|
+
});
|
|
465
|
+
const broker = await this.getOrCreateBtpAuthBroker(destination);
|
|
466
|
+
const token = await broker.refreshToken(destination);
|
|
467
|
+
this.cacheToken(destination, token);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Cancel all pending refresh timers. Call on shutdown.
|
|
471
|
+
*/
|
|
472
|
+
dispose() {
|
|
473
|
+
for (const timer of this.refreshTimers.values()) {
|
|
474
|
+
clearTimeout(timer);
|
|
475
|
+
}
|
|
476
|
+
this.refreshTimers.clear();
|
|
477
|
+
}
|
|
407
478
|
/**
|
|
408
479
|
* Get the target service URL for a destination.
|
|
409
480
|
* Priority: config.targetUrl > service key's serviceUrl
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/proxy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "MCP proxy server for SAP ABAP ADT - proxies local requests to cloud-llm-hub with JWT authentication",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@biomejs/biome": "^2.
|
|
47
|
+
"@biomejs/biome": "^2.4.12",
|
|
48
48
|
"@jest/globals": "^30.2.0",
|
|
49
|
-
"@modelcontextprotocol/inspector": "^0.21.
|
|
49
|
+
"@modelcontextprotocol/inspector": "^0.21.2",
|
|
50
50
|
"@types/jest": "^30.0.0",
|
|
51
51
|
"@types/js-yaml": "^4.0.9",
|
|
52
52
|
"@types/node": "^25.2.3",
|
|
@@ -61,18 +61,18 @@
|
|
|
61
61
|
"rimraf": "^6.1.2",
|
|
62
62
|
"ts-jest": "^29.2.5",
|
|
63
63
|
"tsx": "^4.21.0",
|
|
64
|
-
"typescript": "^6.0.
|
|
64
|
+
"typescript": "^6.0.3"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@inquirer/prompts": "^8.
|
|
67
|
+
"@inquirer/prompts": "^8.4.2",
|
|
68
68
|
"@mcp-abap-adt/auth-broker": "^1.0.5",
|
|
69
69
|
"@mcp-abap-adt/auth-providers": "^1.0.5",
|
|
70
70
|
"@mcp-abap-adt/auth-stores": "^1.0.4",
|
|
71
71
|
"@mcp-abap-adt/header-validator": "^0.1.8",
|
|
72
|
-
"@mcp-abap-adt/interfaces": "^
|
|
72
|
+
"@mcp-abap-adt/interfaces": "^7.0.0",
|
|
73
73
|
"@mcp-abap-adt/logger": "^0.1.4",
|
|
74
74
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
75
|
-
"axios": "^1.
|
|
75
|
+
"axios": "^1.15.1",
|
|
76
76
|
"js-yaml": "^4.1.1",
|
|
77
77
|
"zod": "^4.3.6"
|
|
78
78
|
},
|