@mcp-abap-adt/connection 6.0.0 → 6.0.1
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 +110 -1
- package/bin/sap-abap-auth.js +101 -13
- package/dist/connection/RfcTransport.d.ts.map +1 -1
- package/dist/connection/RfcTransport.js +14 -0
- package/package.json +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,11 +7,119 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [6.0.1] - 2026-08-25
|
|
11
|
+
|
|
12
|
+
### Removed
|
|
13
|
+
|
|
14
|
+
- **`express` is no longer a dependency.** It was a runtime dependency for one
|
|
15
|
+
thing: the localhost server in `bin/sap-abap-auth.js` that catches the OAuth2
|
|
16
|
+
redirect. The library never touched it, and that file already imported
|
|
17
|
+
`node:http`. Rewritten on `node:http`, which sheds 5.6MB of `node_modules` and
|
|
18
|
+
the transitive tree the Dependabot bumps kept arriving for — `qs`,
|
|
19
|
+
`body-parser` and `follow-redirects` were all express's.
|
|
20
|
+
|
|
21
|
+
Two things express was doing implicitly are now stated: a request is the
|
|
22
|
+
redirect only when it is a `GET` for `/callback`, so a browser fetching
|
|
23
|
+
`/favicon.ico` no longer reaches the handler waiting for an authorization
|
|
24
|
+
code; and the success page declares `text/html`, which `res.send()` used to
|
|
25
|
+
infer. Exercised as a server: `200` with the page, `400` without a code, `404`
|
|
26
|
+
for anything else including a `POST`.
|
|
27
|
+
|
|
28
|
+
Runtime dependencies are now `@mcp-abap-adt/interfaces`, `axios`, `commander`
|
|
29
|
+
and `open`. The end-to-end flow was measured against the BTP trial — see
|
|
30
|
+
*Verified* below.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **The browser wait is the caller's to set.** `bin/sap-abap-auth.js` waited a
|
|
35
|
+
fixed five minutes for the redirect, and a real BTP login does not fit in it —
|
|
36
|
+
the identity provider, a password manager and whatever second factor the
|
|
37
|
+
tenant asks for all happen before the redirect is issued. Measured twice, and
|
|
38
|
+
both times the window closed first; the browser then had nowhere to redirect
|
|
39
|
+
to, which reads as the authentication having failed rather than as a timeout.
|
|
40
|
+
|
|
41
|
+
`--timeout <minutes>`, or `SAP_AUTH_TIMEOUT_MS` for a script that would rather
|
|
42
|
+
not pass an argument, with the flag winning when both are given. Anything that
|
|
43
|
+
is not a positive number is refused by name rather than silently becoming a
|
|
44
|
+
default. **The default is now fifteen minutes**, which covered the login this
|
|
45
|
+
was measured against with room to spare and still bounds an abandoned run.
|
|
46
|
+
|
|
47
|
+
### Verified
|
|
48
|
+
|
|
49
|
+
- **The whole chain, end to end**: `bin/sap-abap-auth.js auth` → browser
|
|
50
|
+
authorization against the tenant's UAA → redirect to `localhost:3001/callback`
|
|
51
|
+
→ code exchanged for a token → `.env` written with `SAP_JWT_TOKEN`,
|
|
52
|
+
`SAP_REFRESH_TOKEN` and the UAA fields.
|
|
53
|
+
|
|
54
|
+
- Each seam the express removal touched, measured on the real handler rather
|
|
55
|
+
than a stand-in: `GET /favicon.ico` answers `404` and does not reach the
|
|
56
|
+
handler waiting for a code — routing express used to do; `GET /callback` with
|
|
57
|
+
no code answers `400`; the success page comes back
|
|
58
|
+
`200 text/html; charset=utf-8`, the Content-Type `res.send()` used to infer
|
|
59
|
+
and `node:http` will not.
|
|
60
|
+
|
|
61
|
+
- **The issued token then drove this library** against the trial:
|
|
62
|
+
`AdtCloudConnector` with `CloudHttpTransport` connected, took a session
|
|
63
|
+
(`SAP_SESSIONID_TRL_100`), answered a `GET /sap/bc/adt/discovery` with `200`
|
|
64
|
+
and 444826 bytes of XML, and `disconnect()` left `isConnected()` false.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- **The live RFC suite skips a wire the machine did not install.** It asked for
|
|
69
|
+
four environment variables and nothing else, so a checkout with an env file
|
|
70
|
+
and no SAP NW RFC SDK ran the suite anyway and went eleven tests red, all on
|
|
71
|
+
`@mcp-abap-adt/sap-rfc-lite is not available`. The dependency is `optional` —
|
|
72
|
+
`npm ci` leaves it out WITHOUT failing, which is what optional means — so
|
|
73
|
+
nothing warns and the next run is red for a reason unrelated to the change
|
|
74
|
+
under test.
|
|
75
|
+
|
|
76
|
+
Nothing here is configured at build time the way a C package is: the installed
|
|
77
|
+
optional dependency IS the choice of wire, made at install and readable only
|
|
78
|
+
at runtime. `canRun()` now reads it with `require.resolve`, asking whether the
|
|
79
|
+
wire could be taken rather than loading a native addon to find out. Measured
|
|
80
|
+
in both states: SDK hidden — 11 skipped, 2 passed, green; SDK present — 13
|
|
81
|
+
passed.
|
|
82
|
+
|
|
83
|
+
- `RfcTransport` says why it ignores `request.timeout`. No behaviour change: the
|
|
84
|
+
file contained no occurrence of the word, and that silence read as an
|
|
85
|
+
oversight. It is deliberate — the SDK exposes no cancel, its per-call
|
|
86
|
+
`timeout` is parsed into a commented-out branch, and an abandoned call still
|
|
87
|
+
holds the conversation, so the next one would queue behind a call nobody is
|
|
88
|
+
waiting for. A deadline that reports failure while the wire stays busy is
|
|
89
|
+
worse than none, because the error reads as "safe to retry" when it is not.
|
|
90
|
+
The server bounds the call at `rdisp/max_wprun_time`.
|
|
91
|
+
|
|
10
92
|
## [6.0.0] - 2026-08-24
|
|
11
93
|
|
|
12
94
|
The wire owns what is the wire's, and the factory and per-credential classes are
|
|
13
95
|
gone. See [Migration to 6.0](./docs/MIGRATION-6.0.md).
|
|
14
96
|
|
|
97
|
+
### Added — tooling
|
|
98
|
+
|
|
99
|
+
- **CI.** Until this release the only workflow ran on a tag, and `Release` went
|
|
100
|
+
from build straight to `npm pack` — so nothing ran the gate before a merge,
|
|
101
|
+
and the artifact a tag published had been tested nowhere but on a laptop.
|
|
102
|
+
`ci.yml` runs `lint:check`, `build`, `jest --no-cache`, `check:docs` and
|
|
103
|
+
`check:pack` on push and pull request, across ubuntu and windows on Node 18
|
|
104
|
+
and 20. Windows is in the matrix because npm is `npm.cmd` there and Node will
|
|
105
|
+
not execute a `.cmd` without a shell. `release.yml` runs the same three checks
|
|
106
|
+
between build and pack, so a tag is not a route around them.
|
|
107
|
+
|
|
108
|
+
- **`check:pack`** — inspects `npm pack --dry-run --json` and refuses a tarball
|
|
109
|
+
carrying a test file, a `.tsbuildinfo`, a `.ts` source, an env file or an
|
|
110
|
+
npmrc. Both defects it was written for had already shipped once:
|
|
111
|
+
`dist/__tests__/` and 120KB of compiler bookkeeping, neither visible from
|
|
112
|
+
`files`, because it is the compiler that decides what `dist` holds.
|
|
113
|
+
|
|
114
|
+
- **`typecheck:scripts`** — `scripts/*.ts` were checked by nothing, which is how
|
|
115
|
+
three live verification scripts came to sit on a constructor signature two
|
|
116
|
+
releases old.
|
|
117
|
+
|
|
118
|
+
### Changed — packaging
|
|
119
|
+
|
|
120
|
+
- 111 files / 182KB → 95 / 147KB. Test helpers and the compiler's incremental
|
|
121
|
+
state no longer ship.
|
|
122
|
+
|
|
15
123
|
### Added
|
|
16
124
|
|
|
17
125
|
- **The transport axis is complete and public.** `IAdtTransport` now covers
|
|
@@ -1252,7 +1360,8 @@ const connection = createAbapConnection(config, logger);
|
|
|
1252
1360
|
- JWT token refresh now properly handles connection errors (401/403 during initial connect)
|
|
1253
1361
|
- Permission errors (403 with "ExceptionResourceNoAccess") no longer trigger JWT refresh loops
|
|
1254
1362
|
- Proper separation: base class handles HTTP/session, concrete classes handle auth-specific errors
|
|
1255
|
-
[Unreleased]: https://github.com/fr0ster/mcp-abap-connection/compare/v6.0.
|
|
1363
|
+
[Unreleased]: https://github.com/fr0ster/mcp-abap-connection/compare/v6.0.1...HEAD
|
|
1364
|
+
[6.0.1]: https://github.com/fr0ster/mcp-abap-connection/compare/v6.0.0...v6.0.1
|
|
1256
1365
|
[6.0.0]: https://github.com/fr0ster/mcp-abap-connection/compare/v5.0.0...v6.0.0
|
|
1257
1366
|
[5.0.0]: https://github.com/fr0ster/mcp-abap-connection/compare/v4.0.0...v5.0.0
|
|
1258
1367
|
[4.0.0]: https://github.com/fr0ster/mcp-abap-connection/compare/v3.0.0...v4.0.0
|
package/bin/sap-abap-auth.js
CHANGED
|
@@ -3,7 +3,6 @@ const fs = require("fs");
|
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const axios = require("axios");
|
|
5
5
|
const { program } = require("commander");
|
|
6
|
-
const express = require("express");
|
|
7
6
|
const open = require("open").default;
|
|
8
7
|
const http = require("http");
|
|
9
8
|
|
|
@@ -262,6 +261,57 @@ function getJwtAuthorizationUrl(serviceKey, port = 3001) {
|
|
|
262
261
|
)}&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
263
262
|
}
|
|
264
263
|
|
|
264
|
+
/**
|
|
265
|
+
* How long to wait for the browser to come back.
|
|
266
|
+
*
|
|
267
|
+
* Five minutes was the fixed value, and a real BTP login does not fit in it:
|
|
268
|
+
* the identity provider, a password manager and whatever second factor the
|
|
269
|
+
* tenant asks for all happen before the redirect is issued. Measured twice —
|
|
270
|
+
* both times the window closed first, and the browser then had nowhere to
|
|
271
|
+
* redirect to, which reads to the user as the authentication having failed.
|
|
272
|
+
*
|
|
273
|
+
* So it is a decision the caller makes: `--timeout` in minutes, or
|
|
274
|
+
* `SAP_AUTH_TIMEOUT_MS` for a script that would rather not pass an argument.
|
|
275
|
+
* The default is fifteen, which covered the login this was measured against
|
|
276
|
+
* with room to spare and still bounds a run that has been abandoned.
|
|
277
|
+
*/
|
|
278
|
+
const DEFAULT_AUTH_TIMEOUT_MS = 15 * 60 * 1000;
|
|
279
|
+
|
|
280
|
+
function resolveAuthTimeout(minutes) {
|
|
281
|
+
if (minutes !== undefined) {
|
|
282
|
+
const value = Number(minutes);
|
|
283
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
284
|
+
throw new Error(
|
|
285
|
+
`--timeout must be a positive number of minutes, got: ${minutes}`
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
return value * 60 * 1000;
|
|
289
|
+
}
|
|
290
|
+
const fromEnv = process.env.SAP_AUTH_TIMEOUT_MS;
|
|
291
|
+
if (fromEnv !== undefined && fromEnv !== "") {
|
|
292
|
+
const value = Number(fromEnv);
|
|
293
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`SAP_AUTH_TIMEOUT_MS must be a positive number of milliseconds, got: ${fromEnv}`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return value;
|
|
299
|
+
}
|
|
300
|
+
return DEFAULT_AUTH_TIMEOUT_MS;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* One reply, with the header express used to set on our behalf.
|
|
305
|
+
*
|
|
306
|
+
* `res.send()` chose a Content-Type from what it was handed; `node:http` sends
|
|
307
|
+
* exactly what it is told, so the type is stated here. Without it a browser is
|
|
308
|
+
* free to render the success page as plain text.
|
|
309
|
+
*/
|
|
310
|
+
function send(res, status, contentType, body) {
|
|
311
|
+
res.writeHead(status, { "Content-Type": contentType });
|
|
312
|
+
res.end(body);
|
|
313
|
+
}
|
|
314
|
+
|
|
265
315
|
/**
|
|
266
316
|
* Starts a local server to intercept the authentication response
|
|
267
317
|
* @param {Object} serviceKey SAP BTP service key object
|
|
@@ -269,26 +319,42 @@ function getJwtAuthorizationUrl(serviceKey, port = 3001) {
|
|
|
269
319
|
* @param {string} flow Flow type: jwt (OAuth2)
|
|
270
320
|
* @returns {Promise<{accessToken: string, refreshToken?: string}>} Promise that resolves to tokens
|
|
271
321
|
*/
|
|
272
|
-
async function startAuthServer(
|
|
322
|
+
async function startAuthServer(
|
|
323
|
+
serviceKey,
|
|
324
|
+
browser = undefined,
|
|
325
|
+
flow = "jwt",
|
|
326
|
+
timeoutMs = DEFAULT_AUTH_TIMEOUT_MS
|
|
327
|
+
) {
|
|
273
328
|
return new Promise((resolve, reject) => {
|
|
274
|
-
const app = express();
|
|
275
|
-
const server = http.createServer(app);
|
|
276
329
|
const PORT = 3001;
|
|
277
330
|
let serverInstance = null;
|
|
278
331
|
|
|
279
332
|
// Choose the authorization URL
|
|
280
333
|
const authorizationUrl = getJwtAuthorizationUrl(serviceKey, PORT);
|
|
281
334
|
|
|
282
|
-
// JWT OAuth2 flow (get code, exchange for token)
|
|
283
|
-
|
|
335
|
+
// JWT OAuth2 flow (get code, exchange for token). One route, and the
|
|
336
|
+
// routing express did is two comparisons: a browser fetching /favicon.ico
|
|
337
|
+
// must not be mistaken for the redirect we are waiting for.
|
|
338
|
+
const server = http.createServer(async (req, res) => {
|
|
339
|
+
const url = new URL(req.url, `http://localhost:${PORT}`);
|
|
340
|
+
if (req.method !== "GET" || url.pathname !== "/callback") {
|
|
341
|
+
send(res, 404, "text/plain; charset=utf-8", "Not Found");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
284
345
|
try {
|
|
285
|
-
const
|
|
346
|
+
const code = url.searchParams.get("code");
|
|
286
347
|
if (!code) {
|
|
287
|
-
|
|
348
|
+
send(
|
|
349
|
+
res,
|
|
350
|
+
400,
|
|
351
|
+
"text/plain; charset=utf-8",
|
|
352
|
+
"Error: Authorization code missing"
|
|
353
|
+
);
|
|
288
354
|
return reject(new Error("Authorization code missing"));
|
|
289
355
|
}
|
|
290
356
|
console.log("Authorization code received");
|
|
291
|
-
|
|
357
|
+
send(res, 200, "text/html; charset=utf-8", `<!DOCTYPE html>
|
|
292
358
|
<html lang="en">
|
|
293
359
|
<head>
|
|
294
360
|
<meta charset="UTF-8">
|
|
@@ -363,7 +429,12 @@ async function startAuthServer(serviceKey, browser = undefined, flow = "jwt") {
|
|
|
363
429
|
}
|
|
364
430
|
} catch (error) {
|
|
365
431
|
console.error("Error handling callback:", error);
|
|
366
|
-
|
|
432
|
+
send(
|
|
433
|
+
res,
|
|
434
|
+
500,
|
|
435
|
+
"text/plain; charset=utf-8",
|
|
436
|
+
"Error processing authentication"
|
|
437
|
+
);
|
|
367
438
|
reject(error);
|
|
368
439
|
}
|
|
369
440
|
});
|
|
@@ -395,9 +466,17 @@ async function startAuthServer(serviceKey, browser = undefined, flow = "jwt") {
|
|
|
395
466
|
setTimeout(() => {
|
|
396
467
|
if (serverInstance) {
|
|
397
468
|
serverInstance.close();
|
|
398
|
-
reject(
|
|
469
|
+
reject(
|
|
470
|
+
new Error(
|
|
471
|
+
`Authentication timeout after ${
|
|
472
|
+
timeoutMs >= 60000
|
|
473
|
+
? `${Math.round(timeoutMs / 60000)} min`
|
|
474
|
+
: `${Math.round(timeoutMs / 1000)}s`
|
|
475
|
+
}. Process aborted. Raise it with --timeout <minutes> or SAP_AUTH_TIMEOUT_MS if the login needs longer.`
|
|
476
|
+
)
|
|
477
|
+
);
|
|
399
478
|
}
|
|
400
|
-
},
|
|
479
|
+
}, timeoutMs);
|
|
401
480
|
});
|
|
402
481
|
}
|
|
403
482
|
|
|
@@ -540,6 +619,10 @@ async function main() {
|
|
|
540
619
|
"-f, --force",
|
|
541
620
|
"Force browser authentication even if valid tokens exist in .env"
|
|
542
621
|
)
|
|
622
|
+
.option(
|
|
623
|
+
"-t, --timeout <minutes>",
|
|
624
|
+
"How long to wait for the browser redirect, in minutes (default: 15, or SAP_AUTH_TIMEOUT_MS in ms)"
|
|
625
|
+
)
|
|
543
626
|
.helpOption("-h, --help", "Show help for the auth command")
|
|
544
627
|
.action(async (options) => {
|
|
545
628
|
try {
|
|
@@ -594,7 +677,12 @@ async function main() {
|
|
|
594
677
|
// Fallback to browser authentication if refresh failed or was skipped
|
|
595
678
|
if (!tokens) {
|
|
596
679
|
console.log("🌐 Starting browser authentication...");
|
|
597
|
-
tokens = await startAuthServer(
|
|
680
|
+
tokens = await startAuthServer(
|
|
681
|
+
serviceKey,
|
|
682
|
+
options.browser,
|
|
683
|
+
"jwt",
|
|
684
|
+
resolveAuthTimeout(options.timeout)
|
|
685
|
+
);
|
|
598
686
|
if (!tokens || !tokens.accessToken) {
|
|
599
687
|
console.error("JWT token was not obtained. Authentication failed.");
|
|
600
688
|
process.exit(1);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RfcTransport.d.ts","sourceRoot":"","sources":["../../src/connection/RfcTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EACV,oBAAoB,EAEpB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,oBAAoB,CAAC;AAE5B,sFAAsF;AACtF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CACF,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAmCD,qBAAa,YAAa,YAAW,gBAAgB;IA+EjD,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IA/EzB,QAAQ,CAAC,IAAI,SAAS;IAEtB,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAG,QAAQ,CAAU;IAEpC,OAAO,CAAC,YAAY,CAAiC;IACrD,kEAAkE;IAClE,OAAO,CAAC,cAAc,CAAM;IAE5B;;;;;;OAMG;IACH,MAAM,IAAI,IAAI;IAEd,2CAA2C;IAC3C,OAAO,IAAI,IAAI;IAIf;;;;;OAKG;IACG,SAAS,CAAC,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAE9D,+BAA+B;IAC/B,SAAS,IAAI,IAAI;IAIjB;;;OAGG;IACH,cAAc,IAAI,IAAI;IAEtB;;;OAGG;IACH,6EAA6E;IAC7E,kBAAkB,IAAI,OAAO;IAI7B,kBAAkB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAQzC,2EAA2E;IAC3E,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAIzC;;;;OAIG;IACH,aAAa,IAAI,IAAI;IAErB;;;;;OAKG;gBAEgB,OAAO,EAAE,MAAM,gBAAgB,EAC/B,MAAM,GAAE,OAAO,GAAG,IAAW;IAG1C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB3B,0DAA0D;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAatB,IAAI,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"RfcTransport.d.ts","sourceRoot":"","sources":["../../src/connection/RfcTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EACV,oBAAoB,EAEpB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,oBAAoB,CAAC;AAE5B,sFAAsF;AACtF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CACF,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAmCD,qBAAa,YAAa,YAAW,gBAAgB;IA+EjD,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IA/EzB,QAAQ,CAAC,IAAI,SAAS;IAEtB,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAG,QAAQ,CAAU;IAEpC,OAAO,CAAC,YAAY,CAAiC;IACrD,kEAAkE;IAClE,OAAO,CAAC,cAAc,CAAM;IAE5B;;;;;;OAMG;IACH,MAAM,IAAI,IAAI;IAEd,2CAA2C;IAC3C,OAAO,IAAI,IAAI;IAIf;;;;;OAKG;IACG,SAAS,CAAC,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAE9D,+BAA+B;IAC/B,SAAS,IAAI,IAAI;IAIjB;;;OAGG;IACH,cAAc,IAAI,IAAI;IAEtB;;;OAGG;IACH,6EAA6E;IAC7E,kBAAkB,IAAI,OAAO;IAI7B,kBAAkB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAQzC,2EAA2E;IAC3E,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAIzC;;;;OAIG;IACH,aAAa,IAAI,IAAI;IAErB;;;;;OAKG;gBAEgB,OAAO,EAAE,MAAM,gBAAgB,EAC/B,MAAM,GAAE,OAAO,GAAG,IAAW;IAG1C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB3B,0DAA0D;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAatB,IAAI,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CA4I1E"}
|
|
@@ -187,6 +187,20 @@ class RfcTransport {
|
|
|
187
187
|
});
|
|
188
188
|
}
|
|
189
189
|
this.logger?.debug(`RFC → ${method} ${uri}`);
|
|
190
|
+
// `request.timeout` is deliberately not read, and the absence of the word
|
|
191
|
+
// here is what made that look like an oversight (#42).
|
|
192
|
+
//
|
|
193
|
+
// There is nothing to enforce it with: the SDK exposes no cancel, and an
|
|
194
|
+
// abandoned call still holds the conversation — the next one would queue
|
|
195
|
+
// behind a call nobody is waiting for. A deadline that reports failure
|
|
196
|
+
// while the wire stays busy is worse than none, because the error reads as
|
|
197
|
+
// "safe to retry" when it is not.
|
|
198
|
+
//
|
|
199
|
+
// Nor is one needed. What bounds this call is the server: a dialog step
|
|
200
|
+
// ends at `rdisp/max_wprun_time`. And a call that cannot be torn down
|
|
201
|
+
// mid-flight is precisely what the connection emulates over HTTP, where
|
|
202
|
+
// a critical section raises the caller's timeout to a ten-minute ceiling so
|
|
203
|
+
// an abort cannot orphan a lock handle. Here that comes free.
|
|
190
204
|
let raw;
|
|
191
205
|
try {
|
|
192
206
|
raw = await this.conversation.call('SADT_REST_RFC_ENDPOINT', {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/connection",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "ABAP connection layer for MCP ABAP ADT server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -55,7 +55,6 @@
|
|
|
55
55
|
"@mcp-abap-adt/interfaces": "^21.0.0",
|
|
56
56
|
"axios": "^1.16.0",
|
|
57
57
|
"commander": "^14.0.3",
|
|
58
|
-
"express": "^5.1.0",
|
|
59
58
|
"open": "^11.0.0"
|
|
60
59
|
},
|
|
61
60
|
"optionalDependencies": {
|