@mcp-abap-adt/calm-server 0.2.1 → 0.4.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +45 -0
  3. package/dist/server/auth/buildBroker.d.ts +22 -0
  4. package/dist/server/auth/buildBroker.d.ts.map +1 -0
  5. package/dist/server/auth/buildBroker.js +55 -0
  6. package/dist/server/auth/buildBroker.js.map +1 -0
  7. package/dist/server/auth/legacyEnvShim.d.ts +10 -0
  8. package/dist/server/auth/legacyEnvShim.d.ts.map +1 -0
  9. package/dist/server/auth/legacyEnvShim.js +23 -0
  10. package/dist/server/auth/legacyEnvShim.js.map +1 -0
  11. package/dist/server/buildClient.d.ts +17 -5
  12. package/dist/server/buildClient.d.ts.map +1 -1
  13. package/dist/server/buildClient.js +22 -64
  14. package/dist/server/buildClient.js.map +1 -1
  15. package/dist/server/config.d.ts +3 -0
  16. package/dist/server/config.d.ts.map +1 -1
  17. package/dist/server/config.js +19 -3
  18. package/dist/server/config.js.map +1 -1
  19. package/dist/server/connection/AbstractCalmConnection.d.ts +43 -0
  20. package/dist/server/connection/AbstractCalmConnection.d.ts.map +1 -0
  21. package/dist/server/connection/AbstractCalmConnection.js +155 -0
  22. package/dist/server/connection/AbstractCalmConnection.js.map +1 -0
  23. package/dist/server/connection/OAuth2CalmConnection.d.ts +13 -0
  24. package/dist/server/connection/OAuth2CalmConnection.d.ts.map +1 -0
  25. package/dist/server/connection/OAuth2CalmConnection.js +27 -0
  26. package/dist/server/connection/OAuth2CalmConnection.js.map +1 -0
  27. package/dist/server/connection/SandboxCalmConnection.d.ts +10 -0
  28. package/dist/server/connection/SandboxCalmConnection.d.ts.map +1 -0
  29. package/dist/server/connection/SandboxCalmConnection.js +16 -0
  30. package/dist/server/connection/SandboxCalmConnection.js.map +1 -0
  31. package/dist/server/connection/XsuaaRefresher.d.ts +18 -0
  32. package/dist/server/connection/XsuaaRefresher.d.ts.map +1 -0
  33. package/dist/server/connection/XsuaaRefresher.js +51 -0
  34. package/dist/server/connection/XsuaaRefresher.js.map +1 -0
  35. package/dist/server/connection/createCalmConnection.d.ts +13 -0
  36. package/dist/server/connection/createCalmConnection.d.ts.map +1 -0
  37. package/dist/server/connection/createCalmConnection.js +28 -0
  38. package/dist/server/connection/createCalmConnection.js.map +1 -0
  39. package/dist/server/connection/index.d.ts +10 -0
  40. package/dist/server/connection/index.d.ts.map +1 -0
  41. package/dist/server/connection/index.js +16 -0
  42. package/dist/server/connection/index.js.map +1 -0
  43. package/dist/server/connection/serviceRoutes.d.ts +16 -0
  44. package/dist/server/connection/serviceRoutes.d.ts.map +1 -0
  45. package/dist/server/connection/serviceRoutes.js +27 -0
  46. package/dist/server/connection/serviceRoutes.js.map +1 -0
  47. package/dist/server/runStdio.js +2 -2
  48. package/dist/server/runStdio.js.map +1 -1
  49. package/dist/tools/features/getFeatureByDisplayId.d.ts +1 -0
  50. package/dist/tools/features/getFeatureByDisplayId.d.ts.map +1 -1
  51. package/dist/tools/features/getFeatureByDisplayId.js +11 -5
  52. package/dist/tools/features/getFeatureByDisplayId.js.map +1 -1
  53. package/dist/tools/features/listFeatures.d.ts.map +1 -1
  54. package/dist/tools/features/listFeatures.js +5 -2
  55. package/dist/tools/features/listFeatures.js.map +1 -1
  56. package/dist/tools/tasks/listTasks.d.ts.map +1 -1
  57. package/dist/tools/tasks/listTasks.js +19 -15
  58. package/dist/tools/tasks/listTasks.js.map +1 -1
  59. package/package.json +13 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,102 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 — 2026-05-24
4
+
5
+ Follows `@mcp-abap-adt/calm-client@0.4.0` (connection moved out of the
6
+ client). Combines the auth-broker integration (M19) with a
7
+ server-owned connection layer.
8
+
9
+ ### Changed (BREAKING)
10
+
11
+ - **The server now owns the CALM connection.** New
12
+ `src/server/connection/` module on native `fetch`:
13
+ `AbstractCalmConnection`, `SandboxCalmConnection` (api-key),
14
+ `OAuth2CalmConnection` (Bearer via `ITokenRefresher`, one-shot
15
+ refresh+retry on 401/403), and the
16
+ `createCalmConnection(config, overrides?)` factory. Exposed via the
17
+ `./connection` subpath export. `@mcp-abap-adt/calm-client` no longer
18
+ ships `CalmConnection` (peer bumped to `^0.4.0`).
19
+ - **`CALM_BASE_URL` is consumed verbatim** — service routes are appended
20
+ by plain concatenation; no `/api` injection. Paste `endpoints.Api`
21
+ from the service-key as-is (it already includes `/api`). Fixes the
22
+ silent double-`/api` 404 against live tenants.
23
+
24
+ ### Added
25
+
26
+ - **Token acquisition via `@mcp-abap-adt/auth-broker`** (M19):
27
+ `buildCalmClient` is `async`, honours `CALM_AUTH_FLOW`
28
+ (`client_credentials` | `authorization_code`), and sources UAA creds
29
+ from inline `CALM_UAA_*` (legacy shim) or `./{destination}.env`
30
+ (produced by the `mcp-auth` CLI). The broker yields the
31
+ `ITokenRefresher` that is injected into `OAuth2CalmConnection` via the
32
+ factory's `tokenRefresher` override.
33
+ - **Request-lifecycle logging** in the connection via an optional
34
+ `ILogger` (debug on request/response/retry, warn on transport
35
+ failure), threaded from `runStdio` through the stderr-safe
36
+ `StderrLogger`.
37
+
38
+ ### Fixed
39
+
40
+ - `dotenv` moved from `devDependencies` to `dependencies` (imported at
41
+ runtime by `config.ts`).
42
+
43
+ ## 0.3.0 — 2026-05-13
44
+
45
+ Follows `@mcp-abap-adt/calm-client@0.3.0` (issue / PR #3 / #4 there).
46
+ Closes a wider class of the same bug fixed in 0.2.1: every list
47
+ endpoint whose Spring controller takes `@RequestParam UUID projectId`
48
+ must carry `projectId` on the URL, not in OData `$filter`. After the
49
+ earlier deliverables/workstreams fix, two more came out the moment a
50
+ sandbox `projectId` became available: `listTasks` and `listFeatures`.
51
+
52
+ ### Changed (BREAKING for two tool input schemas)
53
+
54
+ - **`calm_features_get_by_display_id`** — `projectId` is now
55
+ `required`. The `getFeatureByDisplayId` endpoint delegates through
56
+ `listFeatures`, which now needs project scope; and the displayId
57
+ itself (`6-123`) is project-scoped anyway, so this matches the
58
+ underlying data model.
59
+
60
+ ### Fixed (internal, no schema impact)
61
+
62
+ - **`calm_tasks_list` / `calm_features_list`** — projectId is now
63
+ forwarded positionally to the calm-client (not stuffed into
64
+ `$filter`), aligning with the server's `?projectId=<uuid>` URL
65
+ contract.
66
+ - **`calm_tasks_list`** — Tasks service rejects every OData query
67
+ parameter (`$select`, `$top`, `$filter` all return 400 "not
68
+ supported yet") despite returning an OData-collection shape. The
69
+ tool now calls `calm.getTasks().list(projectId)` with no query and
70
+ performs status/assignee filtering and limit/offset pagination
71
+ locally. The `fields` arg is kept for API symmetry but accepted as
72
+ a downstream hint only — the full record comes back from the wire.
73
+
74
+ ### Updated
75
+
76
+ - Peer dependency `@mcp-abap-adt/calm-client` bumped `^0.2.0` →
77
+ `^0.3.0`. Required by the projectId-positional signatures on
78
+ `list` / `getByDisplayId`.
79
+
80
+ ### Discovery / debugging
81
+
82
+ - The bug was latent until a borrowed sandbox `projectId` (leaked
83
+ through a public `test_case.projectId`) activated the
84
+ `describeWithProject` integration gate. Five tests failed with the
85
+ same HTTP 400 root cause, plus two more Tasks-only "not supported
86
+ yet" 400s on `$select` / `$top`.
87
+ - The Tasks service's non-OData nature is now documented in the
88
+ source (and via this entry) — future tools targeting `/tasks/*`
89
+ should not assume OData semantics.
90
+
91
+ ### Tests
92
+
93
+ - Updated unit tests for `listFeatures` (URL-based projectId, no
94
+ `$filter` for it), `listTasks` (no OData query at all, client-side
95
+ filter), and `getByDisplayId` (projectId+displayId positional args).
96
+ - Integration sandbox suite now exercises five project-scoped reads
97
+ end-to-end against a borrowed projectId — 230 passed / 2 skipped
98
+ (oauth2 only) / 1 todo.
99
+
3
100
  ## 0.2.1 — 2026-05-13
4
101
 
5
102
  Same-day follow-up to 0.2.0: realigns two bonus tools with
package/README.md CHANGED
@@ -83,6 +83,51 @@ npm run build && node dist/bin/stdio.js
83
83
  The server speaks MCP over stdio. Misconfiguration is reported to
84
84
  `stderr` with a non-zero exit code.
85
85
 
86
+ ## Authentication setup
87
+
88
+ `calm-mcp` supports two OAuth2 flows for live tenants, selectable via
89
+ `CALM_AUTH_FLOW`:
90
+
91
+ | Flow | Use case | Browser? | Refresh token? |
92
+ |---|---|---|---|
93
+ | `client_credentials` (default) | technical service-binding (`sb-*` client) | no | no |
94
+ | `authorization_code` | end-user dev workflow, full user scope | once | yes |
95
+
96
+ ### Option A — quick CC setup (technical user)
97
+
98
+ Plain `.env` with inline `CALM_UAA_URL` / `CALM_UAA_CLIENT_ID` /
99
+ `CALM_UAA_CLIENT_SECRET` works as before — no extra steps. The broker uses
100
+ an in-memory session shim for these inline creds.
101
+
102
+ ### Option B — broker-backed setup (CC or AC)
103
+
104
+ Use the bundled [`mcp-auth`](https://www.npmjs.com/package/@mcp-abap-adt/auth-broker)
105
+ CLI to convert a BTP service key into a token-bearing `.env`:
106
+
107
+ ```bash
108
+ # CC (no browser)
109
+ npx mcp-auth --service-key ./sk.json --output ./DEFAULT.env \
110
+ --type xsuaa --credential
111
+
112
+ # AC (browser pops once; refresh_token persists)
113
+ npx mcp-auth --service-key ./sk.json --output ./DEFAULT.env \
114
+ --type xsuaa --browser auto
115
+ ```
116
+
117
+ Then in your `.env`:
118
+
119
+ ```
120
+ CALM_MODE=oauth2
121
+ CALM_BASE_URL=https://<tenant>.<region>.alm.cloud.sap
122
+ CALM_AUTH_FLOW=authorization_code # or client_credentials
123
+ CALM_DESTINATION=DEFAULT
124
+ ```
125
+
126
+ The server's runtime auth pipeline is `@mcp-abap-adt/auth-broker`.
127
+
128
+ > Note: `buildCalmClient` is async since v0.4.0 (was sync in v0.3.x). Library
129
+ > consumers must `await` it.
130
+
86
131
  ### 3. Wire into Claude Desktop
87
132
 
88
133
  Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`
@@ -0,0 +1,22 @@
1
+ import { AuthBroker } from '@mcp-abap-adt/auth-broker';
2
+ import type { ILogger } from '@mcp-abap-adt/interfaces';
3
+ import type { ICalmServerConfig } from '../config';
4
+ /**
5
+ * Assemble an `AuthBroker` from server config.
6
+ *
7
+ * - Chooses `ClientCredentialsProvider` or `AuthorizationCodeProvider`
8
+ * based on `config.authFlow`.
9
+ * - Chooses session store: legacy `SafeXsuaaSessionStore` shim when the
10
+ * `.env` still inlines `CALM_UAA_*`, otherwise file-based
11
+ * `XsuaaSessionStore` rooted at cwd (loads `./{destination}.env`).
12
+ * - `browser: 'none'` always — the runtime server never pops a browser;
13
+ * interactive AC login is the job of the `mcp-auth` CLI.
14
+ * - `allowBrowserAuth` is flow-aware: `true` for CC (broker has a hard
15
+ * gate that fires when a session lacks a refresh_token, regardless of
16
+ * provider type — CC providers never need a browser anyway, so we let
17
+ * the gate pass), `false` for AC (fail fast when refresh_token is
18
+ * missing rather than try to launch a browser the stdio process
19
+ * cannot show).
20
+ */
21
+ export declare function buildAuthBroker(config: ICalmServerConfig, logger?: ILogger): Promise<AuthBroker>;
22
+ //# sourceMappingURL=buildBroker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildBroker.d.ts","sourceRoot":"","sources":["../../../src/server/auth/buildBroker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAMvD,OAAO,KAAK,EACV,OAAO,EAGR,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAGnD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,iBAAiB,EACzB,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,UAAU,CAAC,CA6CrB"}
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAuthBroker = buildAuthBroker;
4
+ const auth_broker_1 = require("@mcp-abap-adt/auth-broker");
5
+ const auth_providers_1 = require("@mcp-abap-adt/auth-providers");
6
+ const auth_stores_1 = require("@mcp-abap-adt/auth-stores");
7
+ const legacyEnvShim_1 = require("./legacyEnvShim");
8
+ /**
9
+ * Assemble an `AuthBroker` from server config.
10
+ *
11
+ * - Chooses `ClientCredentialsProvider` or `AuthorizationCodeProvider`
12
+ * based on `config.authFlow`.
13
+ * - Chooses session store: legacy `SafeXsuaaSessionStore` shim when the
14
+ * `.env` still inlines `CALM_UAA_*`, otherwise file-based
15
+ * `XsuaaSessionStore` rooted at cwd (loads `./{destination}.env`).
16
+ * - `browser: 'none'` always — the runtime server never pops a browser;
17
+ * interactive AC login is the job of the `mcp-auth` CLI.
18
+ * - `allowBrowserAuth` is flow-aware: `true` for CC (broker has a hard
19
+ * gate that fires when a session lacks a refresh_token, regardless of
20
+ * provider type — CC providers never need a browser anyway, so we let
21
+ * the gate pass), `false` for AC (fail fast when refresh_token is
22
+ * missing rather than try to launch a browser the stdio process
23
+ * cannot show).
24
+ */
25
+ async function buildAuthBroker(config, logger) {
26
+ const shimStore = await (0, legacyEnvShim_1.buildLegacyShimStore)(config);
27
+ const sessionStore = shimStore ?? new auth_stores_1.XsuaaSessionStore(process.cwd(), config.baseUrl, logger);
28
+ // Resolve UAA credentials: inline config wins, otherwise read from
29
+ // session store (populated from `./{destination}.env` by `mcp-auth`).
30
+ const sessionAuth = await sessionStore.getAuthorizationConfig(config.destination);
31
+ const uaaUrl = config.uaaUrl || sessionAuth?.uaaUrl;
32
+ const uaaClientId = config.uaaClientId || sessionAuth?.uaaClientId;
33
+ const uaaClientSecret = config.uaaClientSecret || sessionAuth?.uaaClientSecret;
34
+ if (!uaaUrl || !uaaClientId || !uaaClientSecret) {
35
+ throw new Error(`[calm-mcp] UAA credentials missing for destination "${config.destination}". ` +
36
+ `Either inline CALM_UAA_URL/CALM_UAA_CLIENT_ID/CALM_UAA_CLIENT_SECRET in .env, ` +
37
+ `or run 'npx mcp-auth --service-key ./sk.json --output ./${config.destination}.env --type xsuaa' first.`);
38
+ }
39
+ const tokenProvider = config.authFlow === 'authorization_code'
40
+ ? new auth_providers_1.AuthorizationCodeProvider({
41
+ uaaUrl,
42
+ clientId: uaaClientId,
43
+ clientSecret: uaaClientSecret,
44
+ browser: 'none',
45
+ logger,
46
+ })
47
+ : new auth_providers_1.ClientCredentialsProvider({
48
+ uaaUrl,
49
+ clientId: uaaClientId,
50
+ clientSecret: uaaClientSecret,
51
+ });
52
+ const allowBrowserAuth = config.authFlow !== 'authorization_code';
53
+ return new auth_broker_1.AuthBroker({ sessionStore, tokenProvider, allowBrowserAuth }, 'none', logger);
54
+ }
55
+ //# sourceMappingURL=buildBroker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildBroker.js","sourceRoot":"","sources":["../../../src/server/auth/buildBroker.ts"],"names":[],"mappings":";;AA+BA,0CAgDC;AA/ED,2DAAuD;AACvD,iEAGsC;AACtC,2DAA8D;AAO9D,mDAAuD;AAEvD;;;;;;;;;;;;;;;;GAgBG;AACI,KAAK,UAAU,eAAe,CACnC,MAAyB,EACzB,MAAgB;IAEhB,MAAM,SAAS,GAAG,MAAM,IAAA,oCAAoB,EAAC,MAAM,CAAC,CAAC;IACrD,MAAM,YAAY,GAChB,SAAS,IAAI,IAAI,+BAAiB,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAE5E,mEAAmE;IACnE,sEAAsE;IACtE,MAAM,WAAW,GAAG,MAAM,YAAY,CAAC,sBAAsB,CAC3D,MAAM,CAAC,WAAW,CACnB,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,WAAW,EAAE,MAAM,CAAC;IACpD,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,WAAW,EAAE,WAAW,CAAC;IACnE,MAAM,eAAe,GACnB,MAAM,CAAC,eAAe,IAAI,WAAW,EAAE,eAAe,CAAC;IAEzD,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,IAAI,CAAC,eAAe,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CACb,uDAAuD,MAAM,CAAC,WAAW,KAAK;YAC5E,gFAAgF;YAChF,2DAA2D,MAAM,CAAC,WAAW,2BAA2B,CAC3G,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GACjB,MAAM,CAAC,QAAQ,KAAK,oBAAoB;QACtC,CAAC,CAAC,IAAI,0CAAyB,CAAC;YAC5B,MAAM;YACN,QAAQ,EAAE,WAAW;YACrB,YAAY,EAAE,eAAe;YAC7B,OAAO,EAAE,MAAM;YACf,MAAM;SACP,CAAC;QACJ,CAAC,CAAC,IAAI,0CAAyB,CAAC;YAC5B,MAAM;YACN,QAAQ,EAAE,WAAW;YACrB,YAAY,EAAE,eAAe;SAC9B,CAAC,CAAC;IAET,MAAM,gBAAgB,GAAG,MAAM,CAAC,QAAQ,KAAK,oBAAoB,CAAC;IAElE,OAAO,IAAI,wBAAU,CACnB,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,EACjD,MAAM,EACN,MAAM,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { ISessionStore } from '@mcp-abap-adt/interfaces';
2
+ import type { ICalmServerConfig } from '../config';
3
+ /**
4
+ * If the user's `.env` still inlines `CALM_UAA_URL`/`CALM_UAA_CLIENT_ID`/
5
+ * `CALM_UAA_CLIENT_SECRET` (pre-broker convention), build an in-memory
6
+ * SafeXsuaaSessionStore preloaded with those creds. Returns null if any
7
+ * of the three are missing — caller falls back to file-based store.
8
+ */
9
+ export declare function buildLegacyShimStore(config: ICalmServerConfig): Promise<ISessionStore | null>;
10
+ //# sourceMappingURL=legacyEnvShim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacyEnvShim.d.ts","sourceRoot":"","sources":["../../../src/server/auth/legacyEnvShim.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAW/B"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildLegacyShimStore = buildLegacyShimStore;
4
+ const auth_stores_1 = require("@mcp-abap-adt/auth-stores");
5
+ /**
6
+ * If the user's `.env` still inlines `CALM_UAA_URL`/`CALM_UAA_CLIENT_ID`/
7
+ * `CALM_UAA_CLIENT_SECRET` (pre-broker convention), build an in-memory
8
+ * SafeXsuaaSessionStore preloaded with those creds. Returns null if any
9
+ * of the three are missing — caller falls back to file-based store.
10
+ */
11
+ async function buildLegacyShimStore(config) {
12
+ const { uaaUrl, uaaClientId, uaaClientSecret, baseUrl, destination } = config;
13
+ if (!uaaUrl || !uaaClientId || !uaaClientSecret)
14
+ return null;
15
+ const store = new auth_stores_1.SafeXsuaaSessionStore(baseUrl);
16
+ await store.setAuthorizationConfig(destination, {
17
+ uaaUrl,
18
+ uaaClientId,
19
+ uaaClientSecret,
20
+ });
21
+ return store;
22
+ }
23
+ //# sourceMappingURL=legacyEnvShim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacyEnvShim.js","sourceRoot":"","sources":["../../../src/server/auth/legacyEnvShim.ts"],"names":[],"mappings":";;AAUA,oDAaC;AAvBD,2DAAkE;AAIlE;;;;;GAKG;AACI,KAAK,UAAU,oBAAoB,CACxC,MAAyB;IAEzB,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAC9E,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,IAAI,CAAC,eAAe;QAAE,OAAO,IAAI,CAAC;IAE7D,MAAM,KAAK,GAAG,IAAI,mCAAqB,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,KAAK,CAAC,sBAAsB,CAAC,WAAW,EAAE;QAC9C,MAAM;QACN,WAAW;QACX,eAAe;KAChB,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -1,10 +1,22 @@
1
1
  import { CalmClient } from '@mcp-abap-adt/calm-client';
2
+ import type { ILogger } from '@mcp-abap-adt/interfaces';
2
3
  import type { ICalmServerConfig } from './config';
4
+ export interface BuildCalmClientOptions {
5
+ logger?: ILogger;
6
+ }
3
7
  /**
4
- * Build a ready-to-use `CalmClient` from a `ICalmServerConfig`. Picks
5
- * OAuth2 or sandbox mode based on config.mode. The returned connection
6
- * is stateless from the server's perspective constructing a fresh
7
- * CalmClient per process is cheap.
8
+ * Build a ready-to-use `CalmClient` from a `ICalmServerConfig`.
9
+ *
10
+ * Connection construction (fetch transport, verbatim base URL, request
11
+ * logging) lives in `./connection`. Token acquisition is delegated:
12
+ *
13
+ * - `oauth2` mode: `@mcp-abap-adt/auth-broker` produces the
14
+ * `ITokenRefresher` (honouring `CALM_AUTH_FLOW` + session stores);
15
+ * it is injected into the connection via the factory's
16
+ * `tokenRefresher` override. Pass `options.logger` (e.g.
17
+ * `StderrLogger`) in stdio mode so broker + connection logs go to
18
+ * stderr, never the MCP stdout frame stream.
19
+ * - `sandbox` mode: direct API-key auth, no broker involved.
8
20
  */
9
- export declare function buildCalmClient(config: ICalmServerConfig): CalmClient;
21
+ export declare function buildCalmClient(config: ICalmServerConfig, options?: BuildCalmClientOptions): Promise<CalmClient>;
10
22
  //# sourceMappingURL=buildClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buildClient.d.ts","sourceRoot":"","sources":["../../src/server/buildClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAkB,MAAM,2BAA2B,CAAC;AAEvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAoDlD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,UAAU,CAoBrE"}
1
+ {"version":3,"file":"buildClient.d.ts","sourceRoot":"","sources":["../../src/server/buildClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAExD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAGlD,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,UAAU,CAAC,CAcrB"}
@@ -2,73 +2,31 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildCalmClient = buildCalmClient;
4
4
  const calm_client_1 = require("@mcp-abap-adt/calm-client");
5
+ const buildBroker_1 = require("./auth/buildBroker");
6
+ const createCalmConnection_1 = require("./connection/createCalmConnection");
5
7
  /**
6
- * Minimal XSUAA `client_credentials` refresher sufficient for
7
- * standalone-mode servers where no shared auth-broker/session store is
8
- * configured. Caches the token until explicitly refreshed. Production
9
- * consumers that already run `@mcp-abap-adt/auth-broker` elsewhere
10
- * should pass their own `ITokenRefresher` via `buildCalmClient.override`.
8
+ * Build a ready-to-use `CalmClient` from a `ICalmServerConfig`.
9
+ *
10
+ * Connection construction (fetch transport, verbatim base URL, request
11
+ * logging) lives in `./connection`. Token acquisition is delegated:
12
+ *
13
+ * - `oauth2` mode: `@mcp-abap-adt/auth-broker` produces the
14
+ * `ITokenRefresher` (honouring `CALM_AUTH_FLOW` + session stores);
15
+ * it is injected into the connection via the factory's
16
+ * `tokenRefresher` override. Pass `options.logger` (e.g.
17
+ * `StderrLogger`) in stdio mode so broker + connection logs go to
18
+ * stderr, never the MCP stdout frame stream.
19
+ * - `sandbox` mode: direct API-key auth, no broker involved.
11
20
  */
12
- class XsuaaRefresher {
13
- uaaUrl;
14
- clientId;
15
- clientSecret;
16
- cached;
17
- constructor(uaaUrl, clientId, clientSecret) {
18
- this.uaaUrl = uaaUrl;
19
- this.clientId = clientId;
20
- this.clientSecret = clientSecret;
21
- }
22
- async getToken() {
23
- if (!this.cached)
24
- return this.refreshToken();
25
- return this.cached;
26
- }
27
- async refreshToken() {
28
- const url = `${this.uaaUrl.replace(/\/$/, '')}/oauth/token`;
29
- const basic = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');
30
- const response = await fetch(url, {
31
- method: 'POST',
32
- headers: {
33
- Authorization: `Basic ${basic}`,
34
- 'Content-Type': 'application/x-www-form-urlencoded',
35
- Accept: 'application/json',
36
- },
37
- body: 'grant_type=client_credentials',
38
- });
39
- if (!response.ok) {
40
- const body = await response.text().catch(() => '');
41
- throw new Error(`XSUAA token request failed: ${response.status} ${response.statusText} — ${body.slice(0, 200)}`);
42
- }
43
- const json = (await response.json());
44
- if (!json.access_token) {
45
- throw new Error('XSUAA token response missing access_token');
46
- }
47
- this.cached = json.access_token;
48
- return this.cached;
49
- }
50
- }
51
- /**
52
- * Build a ready-to-use `CalmClient` from a `ICalmServerConfig`. Picks
53
- * OAuth2 or sandbox mode based on config.mode. The returned connection
54
- * is stateless from the server's perspective — constructing a fresh
55
- * CalmClient per process is cheap.
56
- */
57
- function buildCalmClient(config) {
21
+ async function buildCalmClient(config, options = {}) {
58
22
  if (config.mode === 'oauth2') {
59
- const refresher = new XsuaaRefresher(config.uaaUrl, config.uaaClientId, config.uaaClientSecret);
60
- const connection = new calm_client_1.CalmConnection({
61
- baseUrl: config.baseUrl,
62
- tokenRefresher: refresher,
63
- defaultTimeout: config.timeoutMs,
64
- });
65
- return new calm_client_1.CalmClient(connection);
23
+ const broker = await (0, buildBroker_1.buildAuthBroker)(config, options.logger);
24
+ const tokenRefresher = broker.createTokenRefresher(config.destination);
25
+ return new calm_client_1.CalmClient((0, createCalmConnection_1.createCalmConnection)(config, {
26
+ tokenRefresher,
27
+ logger: options.logger,
28
+ }));
66
29
  }
67
- const connection = new calm_client_1.CalmConnection({
68
- baseUrl: config.baseUrl,
69
- apiKey: config.apiKey,
70
- defaultTimeout: config.timeoutMs,
71
- });
72
- return new calm_client_1.CalmClient(connection);
30
+ return new calm_client_1.CalmClient((0, createCalmConnection_1.createCalmConnection)(config, { logger: options.logger }));
73
31
  }
74
32
  //# sourceMappingURL=buildClient.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"buildClient.js","sourceRoot":"","sources":["../../src/server/buildClient.ts"],"names":[],"mappings":";;AA4DA,0CAoBC;AAhFD,2DAAuE;AAIvE;;;;;;GAMG;AACH,MAAM,cAAc;IAIC;IACA;IACA;IALX,MAAM,CAAU;IAExB,YACmB,MAAc,EACd,QAAgB,EAChB,YAAoB;QAFpB,WAAM,GAAN,MAAM,CAAQ;QACd,aAAQ,GAAR,QAAQ,CAAQ;QAChB,iBAAY,GAAZ,YAAY,CAAQ;IACpC,CAAC;IAEJ,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC;QAC5D,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,QAAQ,CACzE,QAAQ,CACT,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,SAAS,KAAK,EAAE;gBAC/B,cAAc,EAAE,mCAAmC;gBACnD,MAAM,EAAE,kBAAkB;aAC3B;YACD,IAAI,EAAE,+BAA+B;SACtC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACnD,MAAM,IAAI,KAAK,CACb,+BAA+B,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAChG,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA8B,CAAC;QAClE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC;QAChC,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;CACF;AAED;;;;;GAKG;AACH,SAAgB,eAAe,CAAC,MAAyB;IACvD,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,SAAS,GAAG,IAAI,cAAc,CAClC,MAAM,CAAC,MAAgB,EACvB,MAAM,CAAC,WAAqB,EAC5B,MAAM,CAAC,eAAyB,CACjC,CAAC;QACF,MAAM,UAAU,GAAG,IAAI,4BAAc,CAAC;YACpC,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,cAAc,EAAE,SAAS;YACzB,cAAc,EAAE,MAAM,CAAC,SAAS;SACjC,CAAC,CAAC;QACH,OAAO,IAAI,wBAAU,CAAC,UAAU,CAAC,CAAC;IACpC,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,4BAAc,CAAC;QACpC,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAgB;QAC/B,cAAc,EAAE,MAAM,CAAC,SAAS;KACjC,CAAC,CAAC;IACH,OAAO,IAAI,wBAAU,CAAC,UAAU,CAAC,CAAC;AACpC,CAAC"}
1
+ {"version":3,"file":"buildClient.js","sourceRoot":"","sources":["../../src/server/buildClient.ts"],"names":[],"mappings":";;AAwBA,0CAiBC;AAzCD,2DAAuD;AAEvD,oDAAqD;AAErD,4EAAyE;AAMzE;;;;;;;;;;;;;GAaG;AACI,KAAK,UAAU,eAAe,CACnC,MAAyB,EACzB,UAAkC,EAAE;IAEpC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,MAAM,IAAA,6BAAe,EAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAC7D,MAAM,cAAc,GAAG,MAAM,CAAC,oBAAoB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACvE,OAAO,IAAI,wBAAU,CACnB,IAAA,2CAAoB,EAAC,MAAM,EAAE;YAC3B,cAAc;YACd,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CACH,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,wBAAU,CACnB,IAAA,2CAAoB,EAAC,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CACzD,CAAC;AACJ,CAAC"}
@@ -5,6 +5,7 @@
5
5
  */
6
6
  export declare function loadEnv(): void;
7
7
  export type CalmServerMode = 'oauth2' | 'sandbox';
8
+ export type CalmAuthFlow = 'client_credentials' | 'authorization_code';
8
9
  export interface ICalmServerConfig {
9
10
  mode: CalmServerMode;
10
11
  baseUrl: string;
@@ -13,6 +14,8 @@ export interface ICalmServerConfig {
13
14
  uaaClientSecret?: string;
14
15
  apiKey?: string;
15
16
  timeoutMs: number;
17
+ authFlow: CalmAuthFlow;
18
+ destination: string;
16
19
  }
17
20
  export declare function readConfig(): ICalmServerConfig;
18
21
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,wBAAgB,OAAO,IAAI,IAAI,CAU9B;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAYD,wBAAgB,UAAU,IAAI,iBAAiB,CAkC9C"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,wBAAgB,OAAO,IAAI,IAAI,CAU9B;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;AAClD,MAAM,MAAM,YAAY,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;AAEvE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AAYD,wBAAgB,UAAU,IAAI,iBAAiB,CAsD9C"}
@@ -38,6 +38,18 @@ function readConfig() {
38
38
  if (!mode) {
39
39
  throw new Error('[calm-mcp] CALM_MODE is required (oauth2 or sandbox). See .env.example.');
40
40
  }
41
+ const rawAuthFlow = process.env.CALM_AUTH_FLOW;
42
+ let authFlow = 'client_credentials';
43
+ if (rawAuthFlow) {
44
+ if (rawAuthFlow === 'client_credentials' ||
45
+ rawAuthFlow === 'authorization_code') {
46
+ authFlow = rawAuthFlow;
47
+ }
48
+ else {
49
+ throw new Error(`[calm-mcp] CALM_AUTH_FLOW must be 'client_credentials' or 'authorization_code', got "${rawAuthFlow}".`);
50
+ }
51
+ }
52
+ const destination = process.env.CALM_DESTINATION || 'DEFAULT';
41
53
  const timeoutMs = process.env.CALM_TIMEOUT
42
54
  ? Number(process.env.CALM_TIMEOUT)
43
55
  : 30_000;
@@ -45,10 +57,12 @@ function readConfig() {
45
57
  return {
46
58
  mode,
47
59
  baseUrl: required('CALM_BASE_URL'),
48
- uaaUrl: required('CALM_UAA_URL'),
49
- uaaClientId: required('CALM_UAA_CLIENT_ID'),
50
- uaaClientSecret: required('CALM_UAA_CLIENT_SECRET'),
60
+ uaaUrl: process.env.CALM_UAA_URL,
61
+ uaaClientId: process.env.CALM_UAA_CLIENT_ID,
62
+ uaaClientSecret: process.env.CALM_UAA_CLIENT_SECRET,
51
63
  timeoutMs,
64
+ authFlow,
65
+ destination,
52
66
  };
53
67
  }
54
68
  if (mode === 'sandbox') {
@@ -57,6 +71,8 @@ function readConfig() {
57
71
  baseUrl: process.env.CALM_BASE_URL || 'https://sandbox.api.sap.com/SAPCALM',
58
72
  apiKey: required('CALM_API_KEY'),
59
73
  timeoutMs,
74
+ authFlow,
75
+ destination,
60
76
  };
61
77
  }
62
78
  throw new Error(`[calm-mcp] unknown CALM_MODE "${mode}"`);
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":";;AAWA,0BAUC;AAwBD,gCAkCC;AA/ED,qCAAqC;AACrC,yCAAoC;AACpC,mCAAgD;AAEhD,IAAI,MAAM,GAAG,KAAK,CAAC;AAEnB;;;;GAIG;AACH,SAAgB,OAAO;IACrB,IAAI,MAAM;QAAE,OAAO;IACnB,MAAM,GAAG,IAAI,CAAC;IACd,qEAAqE;IACrE,mEAAmE;IACnE,kEAAkE;IAClE,2EAA2E;IAC3E,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc;QAAE,OAAO;IACvC,MAAM,IAAI,GAAG,IAAA,mBAAO,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;IAC5C,IAAI,IAAA,oBAAU,EAAC,IAAI,CAAC;QAAE,IAAA,eAAY,EAAC,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC;AAcD,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CACb,sBAAsB,IAAI,6CAA6C,CACxE,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAgB,UAAU;IACxB,OAAO,EAAE,CAAC;IACV,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAElC,CAAC;IACd,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,yEAAyE,CAC1E,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY;QACxC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;QAClC,CAAC,CAAC,MAAM,CAAC;IAEX,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO;YACL,IAAI;YACJ,OAAO,EAAE,QAAQ,CAAC,eAAe,CAAC;YAClC,MAAM,EAAE,QAAQ,CAAC,cAAc,CAAC;YAChC,WAAW,EAAE,QAAQ,CAAC,oBAAoB,CAAC;YAC3C,eAAe,EAAE,QAAQ,CAAC,wBAAwB,CAAC;YACnD,SAAS;SACV,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO;YACL,IAAI;YACJ,OAAO,EACL,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,qCAAqC;YACpE,MAAM,EAAE,QAAQ,CAAC,cAAc,CAAC;YAChC,SAAS;SACV,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,GAAG,CAAC,CAAC;AAC5D,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":";;AAWA,0BAUC;AA2BD,gCAsDC;AAtGD,qCAAqC;AACrC,yCAAoC;AACpC,mCAAgD;AAEhD,IAAI,MAAM,GAAG,KAAK,CAAC;AAEnB;;;;GAIG;AACH,SAAgB,OAAO;IACrB,IAAI,MAAM;QAAE,OAAO;IACnB,MAAM,GAAG,IAAI,CAAC;IACd,qEAAqE;IACrE,mEAAmE;IACnE,kEAAkE;IAClE,2EAA2E;IAC3E,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc;QAAE,OAAO;IACvC,MAAM,IAAI,GAAG,IAAA,mBAAO,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;IAC5C,IAAI,IAAA,oBAAU,EAAC,IAAI,CAAC;QAAE,IAAA,eAAY,EAAC,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC;AAiBD,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CACb,sBAAsB,IAAI,6CAA6C,CACxE,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAgB,UAAU;IACxB,OAAO,EAAE,CAAC;IACV,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAElC,CAAC;IACd,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,yEAAyE,CAC1E,CAAC;IACJ,CAAC;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC/C,IAAI,QAAQ,GAAiB,oBAAoB,CAAC;IAClD,IAAI,WAAW,EAAE,CAAC;QAChB,IACE,WAAW,KAAK,oBAAoB;YACpC,WAAW,KAAK,oBAAoB,EACpC,CAAC;YACD,QAAQ,GAAG,WAAW,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CACb,wFAAwF,WAAW,IAAI,CACxG,CAAC;QACJ,CAAC;IACH,CAAC;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,SAAS,CAAC;IAE9D,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY;QACxC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;QAClC,CAAC,CAAC,MAAM,CAAC;IAEX,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO;YACL,IAAI;YACJ,OAAO,EAAE,QAAQ,CAAC,eAAe,CAAC;YAClC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY;YAChC,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB;YAC3C,eAAe,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB;YACnD,SAAS;YACT,QAAQ;YACR,WAAW;SACZ,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO;YACL,IAAI;YACJ,OAAO,EACL,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,qCAAqC;YACpE,MAAM,EAAE,QAAQ,CAAC,cAAc,CAAC;YAChC,SAAS;YACT,QAAQ;YACR,WAAW;SACZ,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,GAAG,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { CalmService, ICalmConnection, ICalmRequestOptions, ICalmResponse, ILogger } from '@mcp-abap-adt/interfaces';
2
+ import { type CalmServiceRouteMap } from './serviceRoutes';
3
+ export interface IAbstractCalmConnectionOptions {
4
+ baseUrl: string;
5
+ defaultTimeout?: number;
6
+ serviceRoutes?: Partial<CalmServiceRouteMap>;
7
+ defaultHeaders?: Record<string, string>;
8
+ /**
9
+ * Optional logger. Request lifecycle is logged at `debug`; transport
10
+ * failures at `warn`. In a stdio MCP runtime this MUST be a
11
+ * stderr-only logger (see `StderrLogger`) — never one that writes to
12
+ * stdout.
13
+ */
14
+ logger?: ILogger;
15
+ }
16
+ /**
17
+ * Shared fetch-based transport for Cloud ALM. Subclasses provide
18
+ * `attachAuth()` and may override `onAuthFailure()`. `baseUrl` is used
19
+ * verbatim — NO prefix injection.
20
+ */
21
+ export declare abstract class AbstractCalmConnection implements ICalmConnection {
22
+ protected readonly baseUrl: string;
23
+ protected readonly defaultTimeout: number;
24
+ protected readonly defaultHeaders: Record<string, string>;
25
+ protected readonly serviceRoutes: CalmServiceRouteMap;
26
+ protected readonly logger?: ILogger;
27
+ constructor(options: IAbstractCalmConnectionOptions);
28
+ /** Subclass: return auth headers for a request. */
29
+ protected abstract attachAuth(): Promise<Record<string, string>>;
30
+ /**
31
+ * Subclass hook on 401/403. Return true to retry once. Default: no
32
+ * retry.
33
+ */
34
+ protected onAuthFailure(_status: number): Promise<boolean>;
35
+ connect(): Promise<void>;
36
+ getBaseUrl(): Promise<string>;
37
+ getServiceUrl(service: CalmService): Promise<string>;
38
+ makeRequest<T = unknown, D = unknown>(options: ICalmRequestOptions): Promise<ICalmResponse<T, D>>;
39
+ private logFail;
40
+ private normalizeError;
41
+ private execute;
42
+ }
43
+ //# sourceMappingURL=AbstractCalmConnection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AbstractCalmConnection.d.ts","sourceRoot":"","sources":["../../../src/server/connection/AbstractCalmConnection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,OAAO,EACR,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAuBD;;;;GAIG;AACH,8BAAsB,sBAAuB,YAAW,eAAe;IACrE,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACnC,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAC1C,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1D,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,mBAAmB,CAAC;IACtD,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;gBAExB,OAAO,EAAE,8BAA8B;IAcnD,mDAAmD;IACnD,SAAS,CAAC,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhE;;;OAGG;cACa,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI1D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAExB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAI7B,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAIpD,WAAW,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EACxC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IA+B/B,OAAO,CAAC,OAAO;IAkBf,OAAO,CAAC,cAAc;YAQR,OAAO;CA0CtB"}