cloud-alm-mcp-server 0.0.0-stage → 0.1.0-beta.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 (60) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +21 -0
  3. package/README.md +375 -2
  4. package/dist/approval.d.ts +41 -0
  5. package/dist/approval.js +225 -0
  6. package/dist/calm/client.d.ts +139 -0
  7. package/dist/calm/client.js +852 -0
  8. package/dist/calm/errors.d.ts +22 -0
  9. package/dist/calm/errors.js +43 -0
  10. package/dist/calm/format.d.ts +26 -0
  11. package/dist/calm/format.js +247 -0
  12. package/dist/calm/grant.d.ts +32 -0
  13. package/dist/calm/grant.js +98 -0
  14. package/dist/calm/index.d.ts +6 -0
  15. package/dist/calm/index.js +7 -0
  16. package/dist/calm/model.d.ts +316 -0
  17. package/dist/calm/model.js +24 -0
  18. package/dist/calm/transport.d.ts +70 -0
  19. package/dist/calm/transport.js +238 -0
  20. package/dist/calm/values.d.ts +18 -0
  21. package/dist/calm/values.js +94 -0
  22. package/dist/calm/writer.d.ts +25 -0
  23. package/dist/calm/writer.js +104 -0
  24. package/dist/cli/checks.d.ts +26 -0
  25. package/dist/cli/checks.js +127 -0
  26. package/dist/cli/clients.d.ts +19 -0
  27. package/dist/cli/clients.js +62 -0
  28. package/dist/cli/key.d.ts +47 -0
  29. package/dist/cli/key.js +97 -0
  30. package/dist/cli/main.d.ts +18 -0
  31. package/dist/cli/main.js +223 -0
  32. package/dist/cli/scopes.d.ts +25 -0
  33. package/dist/cli/scopes.js +116 -0
  34. package/dist/cli.d.ts +2 -0
  35. package/dist/cli.js +51 -0
  36. package/dist/http.d.ts +25 -0
  37. package/dist/http.js +71 -0
  38. package/dist/index.d.ts +6 -0
  39. package/dist/index.js +7 -0
  40. package/dist/node-http.d.ts +6 -0
  41. package/dist/node-http.js +44 -0
  42. package/dist/server.d.ts +48 -0
  43. package/dist/server.js +405 -0
  44. package/dist/stdio.d.ts +20 -0
  45. package/dist/stdio.js +45 -0
  46. package/dist/views/edit.d.ts +81 -0
  47. package/dist/views/edit.js +650 -0
  48. package/dist/views/html.d.ts +1 -0
  49. package/dist/views/html.js +8 -0
  50. package/dist/views/items.d.ts +62 -0
  51. package/dist/views/items.js +151 -0
  52. package/dist/views/snapshot.d.ts +123 -0
  53. package/dist/views/snapshot.js +390 -0
  54. package/dist/views/text.d.ts +41 -0
  55. package/dist/views/text.js +140 -0
  56. package/dist/views/tools.d.ts +61 -0
  57. package/dist/views/tools.js +883 -0
  58. package/dist/views/write.d.ts +67 -0
  59. package/dist/views/write.js +373 -0
  60. package/package.json +72 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,70 @@
1
+ # Changelog
2
+
3
+ All notable changes to this package. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0-beta.0] - 2026-11-14
10
+
11
+ ### Fixed
12
+
13
+ - `calm_create_task` takes no description when there is none (Cloud ALM
14
+ does not need one), names its priority codes, and its preview and card
15
+ say the type and priority ("Create a defect", "priority high").
16
+ - The approval ask states when it expires and also travels in
17
+ `structuredContent`, for hosts that show the model only that.
18
+ - `init` says when the key could write and how to turn writes on; for a
19
+ prerelease, its setup lines pin the version.
20
+
21
+ ### Changed
22
+
23
+ - Seven task-shaped read tools replace the 21 endpoint mirrors:
24
+ `calm_overview`, `calm_find`, `calm_get`, `calm_trace`, `calm_gaps`,
25
+ `calm_analytics`, `calm_landscape`. They accept short display ids
26
+ (`3-17`, `8-1-2`), read a whole project in a few requests, answer in
27
+ compact text, and show the latest test results. The tool list costs about
28
+ 1.8k tokens instead of 4.5k.
29
+
30
+ ### Added
31
+
32
+ - Approval of writes (`CALM_APPROVAL=prefer|required|off`): every write is
33
+ a proposal the user approves in an approval card in the chat (MCP Apps)
34
+ or, in `prefer`, by saying yes in the chat (`calm_confirm`). The client's
35
+ confirm dialog is opt-in (`CALM_APPROVAL_DIALOG=on`), as some clients
36
+ decline it unseen. Approvals are single-use, expire after an hour (the
37
+ card then shows them as expired), and are bound to
38
+ the caller.
39
+ - `doctor` checks scopes per tool, write mode, approval and the default project.
40
+
41
+ - Write mode, off by default (`CALM_WRITE=on|dry-run`):
42
+ `calm_create_task`, `calm_create_test_case`, `calm_create_feature`,
43
+ `calm_create_document`, `calm_update`, `calm_delete`, `calm_comment`,
44
+ `calm_link` (link and unlink, also documents and requirements),
45
+ `calm_tag`. Duplicates refused; every write tool takes `dryRun` and
46
+ previews each change before → after; a 403 names the missing scope.
47
+ - `CALM_TOOLS` (allow list) and `CALM_PROJECT` (default project).
48
+ - Skills `cloud-alm-explore` and `cloud-alm-test-design`.
49
+ - `cloud-alm-mcp-server init [file|-]`: checks a service key (shape, token,
50
+ `calm-api.*` scopes per area, one read), saves it mode 600 under the
51
+ user's config directory and prints configuration for Claude Code, Codex,
52
+ Claude Desktop and Cursor. `--force` replaces a saved key, `--skip-check`
53
+ saves without contacting SAP.
54
+ - `cloud-alm-mcp-server doctor`: the same checks on an existing setup, plus
55
+ key file permissions and the Node version.
56
+ - `--version` and `--help`.
57
+ - `CALM_SERVICE_KEY` takes the key JSON itself as well as a path; without
58
+ it the stdio server uses the key saved by `init`.
59
+ - Package ready for npm: `bin`, `files` (no tests, no source maps),
60
+ `exports`, `engines`, `prepublishOnly` (typecheck, test, build).
61
+ - MIT licence.
62
+ - CI on Node 22 and 24, with a check of the packed command; a release
63
+ workflow (tag `v*` → `npm publish --provenance`) that stays inert until
64
+ an `NPM_TOKEN` secret exists.
65
+
66
+ ## 0.1.0
67
+
68
+ - First version: read-only Cloud ALM reader and MCP server, stdio mode
69
+ with a service key, streamable HTTP mode with a bearer per request.
70
+ Not published.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Didro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,376 @@
1
- # Temporary Holding Version
1
+ # cloud-alm-mcp-server
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) server that lets
4
+ an agent (Claude, Codex, Cursor…) work with **SAP Cloud ALM**: projects,
5
+ requirements, user stories, defects, features, documents, test cases and
6
+ test plans, processes, landscape, cross-library, analytics and service
7
+ status events.
8
+
9
+ **Read-only by default.** The read tools are annotated `readOnlyHint` and
10
+ the reader sends GET requests only, to `*.alm.cloud.sap` hosts only.
11
+ Writing (create, change, link, tag, comment, delete) is opt-in
12
+ with `CALM_WRITE` ([Modes and governance](#modes-and-governance)).
13
+
14
+ > Status: beta (0.1.0-beta.0). MIT licence.
15
+
16
+ ## Quick start
17
+
18
+ Requires Node 22 or later.
19
+
20
+ 1. Get a Cloud ALM API service key (below) and download its JSON.
21
+ 2. Check it and save it:
22
+
23
+ ```sh
24
+ npx -y cloud-alm-mcp-server init ~/Downloads/calm-read.json
25
+ ```
26
+
27
+ `init` gets a token to prove the key works, lists which areas of Cloud
28
+ ALM its scopes can read, makes one read, then saves the key readable
29
+ only by you (mode 600) in `~/.config/cloud-alm-mcp-server/` and prints
30
+ the configuration for Claude Code, Codex, Claude Desktop and Cursor.
31
+ Paste the JSON instead of naming a file: `init` alone reads stdin.
32
+ You can delete the downloaded copy afterwards.
33
+ 3. Add the server to your client with the printed line, e.g.
34
+
35
+ ```sh
36
+ claude mcp add cloud-alm --scope user -e CALM_SERVICE_KEY=$HOME/.config/cloud-alm-mcp-server/service-key.json -- npx -y cloud-alm-mcp-server
37
+ ```
38
+
39
+ 4. Any time later: `npx -y cloud-alm-mcp-server doctor` checks the key,
40
+ token, scopes, API and Node version.
41
+
42
+ | Command | Does |
43
+ | --- | --- |
44
+ | `cloud-alm-mcp-server` | starts the stdio server (default) |
45
+ | `cloud-alm-mcp-server http` | starts the streamable HTTP server |
46
+ | `cloud-alm-mcp-server init [file\|-] [--force] [--skip-check]` | checks and saves a key, prints client config |
47
+ | `cloud-alm-mcp-server doctor` | checks the setup |
48
+ | `cloud-alm-mcp-server --version` / `--help` | |
49
+
50
+ No command prints the key's secret or a token.
51
+
52
+ > Beta. Until 0.1.0 is out, name the beta: `npx -y cloud-alm-mcp-server@beta`.
53
+ > A packed build installs with `npm install -g ./cloud-alm-mcp-server-<version>.tgz`;
54
+ > then use `cloud-alm-mcp-server` instead of `npx -y cloud-alm-mcp-server`.
55
+
56
+ ## Get a service key
57
+
58
+ Cloud ALM's APIs take an OAuth token from a **service key** of the "SAP Cloud
59
+ ALM API" service, created in the BTP subaccount where your Cloud ALM tenant
60
+ lives. Nothing is deployed; the instance only registers an OAuth client.
61
+
62
+ 1. In the BTP cockpit, open the Cloud ALM subaccount → *Services* →
63
+ *Instances and Subscriptions* → *Create*.
64
+ 2. Service **SAP Cloud ALM API**, plan **standard**, runtime environment
65
+ **Other**. Name it, e.g. `calm-read`.
66
+ 3. Parameters: give the client the read scopes it needs (the default
67
+ template has none, which gives a token that can read nothing):
68
+
69
+ ```json
70
+ {
71
+ "xs-security": {
72
+ "xsappname": "calm-read",
73
+ "authorities": [
74
+ "$XSMASTERAPPNAME.calm-api.projects.read",
75
+ "$XSMASTERAPPNAME.calm-api.tasks.read",
76
+ "$XSMASTERAPPNAME.calm-api.features.read",
77
+ "$XSMASTERAPPNAME.calm-api.documents.read",
78
+ "$XSMASTERAPPNAME.calm-api.testcases.read",
79
+ "$XSMASTERAPPNAME.calm-api.testplans.read",
80
+ "$XSMASTERAPPNAME.calm-api.processmanagement.read",
81
+ "$XSMASTERAPPNAME.calm-api.processhierarchy.read",
82
+ "$XSMASTERAPPNAME.calm-api.landscape.read",
83
+ "$XSMASTERAPPNAME.calm-api.lib.read",
84
+ "$XSMASTERAPPNAME.calm-api.analytics.read",
85
+ "$XSMASTERAPPNAME.calm-api.analytics.providers.read",
86
+ "$XSMASTERAPPNAME.calm-api.bsm.read"
87
+ ]
88
+ }
89
+ }
90
+ ```
91
+
92
+ Add only what you will read. Analytics datasets also need their area's
93
+ scope (`calm-api.tasks.read` for `Tasks`, `…defects.read`,
94
+ `…requirements.read`, `…tests.read`, …), and operations datasets their
95
+ own (`…metrics.read`, `…jobs.read`, `…exm.read` …). Personal fields
96
+ (assignee names) need `calm-api.tasks.personal.read` and its siblings.
97
+ 4. Create a **service binding** (the key) on the instance and download its
98
+ JSON. Run `cloud-alm-mcp-server init <file>` on it: it saves a copy
99
+ readable only by you. Never put it in a repository.
100
+
101
+ The key holds `uaa.url`, `uaa.clientid`, `uaa.clientsecret` and
102
+ `endpoints.Api` (e.g. `https://eu20.alm.cloud.sap/api`, regional; the token
103
+ selects your tenant). The token carries no user: calls run as a machine
104
+ with access to the whole tenant. SAP cannot limit a key to one project.
105
+
106
+ Changed the scopes? The token reflects them at once: rerun
107
+ `cloud-alm-mcp-server doctor`. Rotated the key? `init --force <new file>`.
108
+
109
+ ## Tools
110
+
111
+ Seven read tools, each answering a question in one call. Items are named by
112
+ their short **display ids** (`3-17` task, `6-1` feature, `7-1` document,
113
+ `8-1-2` test case, `8-3-1` test plan), which every tool accepts; GUIDs work
114
+ too. Answers are compact text written for a model's context.
115
+
116
+ | Tool | Answers |
117
+ | --- | --- |
118
+ | `calm_overview` | Start here: the project, its current phase, counts of requirements, user stories, defects, features, test cases and documents by status, latest test results, tags in use, gaps |
119
+ | `calm_find` | Search or list items across types (tasks, features, documents, test cases, test plans, processes, library) by words in titles and tags (`a b \| c`), type, status, tag |
120
+ | `calm_get` | Full content of 1–10 items at once: description, test steps and latest result, document text, feature transports, every link named |
121
+ | `calm_trace` | An item's links both ways: requirement → user stories → features (status, transports), test cases (with results), documents, solution process |
122
+ | `calm_gaps` | What is missing or failing: requirements and user stories without a test case, process or feature; failed test runs; features with untested tasks; test cases linked to nothing or not prepared |
123
+ | `calm_analytics` | Analytics datasets (tasks, defects, test runs, jobs, exceptions, integration messages…), counted with `groupBy` |
124
+ | `calm_landscape` | Systems and cloud services, and SAP service status events, open ones first |
125
+
126
+ With write mode on (below), ten more:
127
+
128
+ | Tool | Does |
129
+ | --- | --- |
130
+ | `calm_create_task` | Creates a requirement, user story (optionally under a requirement), defect or project task; refuses a duplicate title |
131
+ | `calm_create_test_case` | Creates a manual test case with activities and steps, linked to the requirements or user stories it covers; refuses a duplicate title |
132
+ | `calm_create_feature` | Creates a feature, optionally linked to the tasks it delivers; refuses a duplicate title |
133
+ | `calm_create_document` | Creates a document (content, type, status, priority) linked to tasks; refuses a duplicate title |
134
+ | `calm_update` | Changes 1–20 items with the same changes: tasks (title, description, status in plain words, priority, assignee, start and due date, timebox, obsolete), features (title, description, status, priority), manual test cases (title, prepared, priority, `addActivity` with steps). Sends only the fields that differ and answers before → after per field |
135
+ | `calm_delete` | Deletes tasks (`soft: true` marks them obsolete instead, as SAP recommends), manual test cases (`force: true` also deletes their runs and results) and documents |
136
+ | `calm_comment` | Adds a comment to a task |
137
+ | `calm_link` | Links, or with `unlink: true` unlinks, a test case (tests), feature (delivers) or document (documents) to tasks, and user stories or project tasks to their requirement; skips links already in that state |
138
+ | `calm_tag` | Adds or removes tags on tasks, keeping the others, and reports tags Cloud ALM silently dropped |
139
+
140
+ Every write tool takes `dryRun: true` and then answers with a complete
141
+ preview: item ids and titles, each field before → after, what gets created,
142
+ linked or deleted. Display ids work everywhere. Statuses are plain words,
143
+ mapped to Cloud ALM's codes per task type (`open`, `in progress`,
144
+ `blocked`, `in review`, `done`, `not relevant` as the type has them;
145
+ requirements also `to be approved`, `approved`, `in realization`,
146
+ `approved for deployment`, `rejected`; defects also `retest`). A field that
147
+ does not apply to an item is refused before anything is written. A 403
148
+ names the scope the key lacks. Writes are never retried.
149
+
150
+ What Cloud ALM's API itself does not allow (no scope changes it):
151
+
152
+ - Features cannot be deleted, and their status moves only along Cloud
153
+ ALM's path (in specification ↔ not planned; in specification → in
154
+ realization → in testing → successfully tested, back to in realization;
155
+ once in realization, never back to in specification or not planned).
156
+ "Ready for production" cannot be set through the API, so "deployed" is
157
+ not reachable through it either.
158
+ - A feature description holding images cannot be changed through the API.
159
+ - A document's title, content, status, priority and type cannot change
160
+ after create; only its links can.
161
+ - Test cases and features cannot be tagged; automated test cases are
162
+ read-only; a test case's project, scope and process cannot change.
163
+ - No API records manual test execution (runs, step results, pass/fail).
164
+ - A requirement's approval is final (it cannot go back to "to be approved"
165
+ or "rejected"), and an approved requirement cannot be deleted.
166
+ - A task has one requirement: unlink it before linking another.
167
+ - Every write is recorded as the key's technical user.
168
+
169
+ Deleting test cases and documents needs extra scopes on the key
170
+ (`testcases.delete`, `testcases.force-delete`, `documents.delete`).
171
+
172
+ A read the key has no scope for is named in the answer ("not readable with
173
+ this key"); the rest still answers. Errors are short text with a code
174
+ (`unauthorized`, `forbidden`, `not_found`, `rate_limited`, `unavailable`,
175
+ `bad_request`) and never carry a token.
176
+
177
+ ### Modes and governance
178
+
179
+ | Variable | Effect |
180
+ | --- | --- |
181
+ | `CALM_WRITE` | `off` (default): read tools only. `dry-run`: write tools are offered but only preview. `on`: write tools write. The key also needs the write scopes. |
182
+ | `CALM_TOOLS` | Comma-separated tool names to offer, e.g. `calm_overview,calm_find,calm_get`. Default: all (of the current mode). |
183
+ | `CALM_APPROVAL` | With `CALM_WRITE=on`: `prefer` (default) has the user approve each write in the card or the chat, `required` in the card only, `off` writes at once. |
184
+ | `CALM_APPROVAL_DIALOG` | `on` also asks in the client's confirm dialog (MCP elicitation), where it declares one. Off by default: only terminal clients show it reliably. |
185
+ | `CALM_PROJECT` | Project GUID used when a call names none. Without it, a tenant with one project uses that one; with several, tools ask for `projectId`. |
186
+
187
+ In HTTP mode the same variables apply to every request; the bearer token
188
+ decides what Cloud ALM allows.
189
+
190
+ ### Approving writes
191
+
192
+ With approval on, a write tool only proposes the change: it runs as a dry
193
+ run and answers with the preview; nothing is sent to Cloud ALM until the
194
+ user approves. Every answer carries the same proposal, whatever the client
195
+ says it supports, and the user approves it in one of these ways:
196
+
197
+ | Where | How | Clients (Nov 2026) |
198
+ | --- | --- | --- |
199
+ | the approval card | an MCP Apps card in the chat with Approve / Reject | Claude (web, Desktop chat), ChatGPT, VS Code, Goose |
200
+ | the chat (`prefer` only) | the agent shows the preview and asks; on the user's yes it calls `calm_confirm` | every client |
201
+ | the client's dialog (`CALM_APPROVAL_DIALOG=on`) | the client's own confirm dialog | Claude Code and Codex in a terminal |
202
+
203
+ The card's button calls `calm_approve`, a tool visible to the card only,
204
+ with a one-time token that travels in the result's `_meta`, never in the
205
+ text the model reads. If the host drops `_meta` (some do, e.g. on reload),
206
+ the card falls back to the id in `structuredContent`, which approves only
207
+ in `prefer`. If the host does not pass the card's calls to the server
208
+ (it does not declare `serverTools`; the Claude Code tab is one, see
209
+ anthropics/claude-code#106708), the card shows the change without
210
+ buttons and asks the user to reply yes in the chat; a failed call does
211
+ the same. An approval is single-use, expires after 60
212
+ minutes (the card then shows it as expired) and, over HTTP, must come with the same bearer token as the
213
+ request that proposed it. `dryRun: true` previews without asking.
214
+
215
+ Why the dialog is off by default: some clients declare a confirm dialog
216
+ and answer "decline" without showing it (the Claude desktop app, for one),
217
+ and some draw the card without declaring MCP Apps. With the dialog on, a
218
+ declined or failed dialog keeps the proposal open for the card or chat.
219
+
220
+ Scopes (`calm-api.` …): `projects.read`, `tasks.read`, `features.read`,
221
+ `documents.read`, `testcases.read`, `testplans.read`,
222
+ `processmanagement.read`, `landscape.read`, `lib.read`, `analytics.read`
223
+ with `tests.read` (test results), `bsm.read`; for writing `tasks.write`,
224
+ `testcases.write`, `features.write`, `documents.write` (task links of
225
+ features and documents are written through the Tasks API), and to delete
226
+ test cases and documents (or unlink a test case from a task)
227
+ `testcases.delete`, `testcases.force-delete`, `documents.delete`.
228
+
229
+ ## Two ways to run it
230
+
231
+ ### stdio, with your own key (default)
232
+
233
+ The server finds the key in `CALM_SERVICE_KEY`, either a file path or the
234
+ JSON itself, else in the file `init` saved
235
+ (`$XDG_CONFIG_HOME/cloud-alm-mcp-server/service-key.json`, by default under
236
+ `~/.config`; `%APPDATA%\cloud-alm-mcp-server\` on Windows;
237
+ `CALM_CONFIG_DIR` overrides the directory). It gets tokens by client
238
+ credentials and keeps them in memory only.
239
+
240
+ ```sh
241
+ npx -y cloud-alm-mcp-server # the key saved by init
242
+ CALM_SERVICE_KEY=~/keys/calm-read.json npx -y cloud-alm-mcp-server
243
+ CALM_SERVICE_KEY="$SECRET_FROM_YOUR_VAULT" npx -y cloud-alm-mcp-server # the JSON itself
244
+ ```
245
+
246
+ ### Streamable HTTP, a bearer token per request
247
+
248
+ For hosting behind your own gateway: the server holds **no secret**. Each
249
+ request carries `Authorization: Bearer <token>`, a Cloud ALM token the
250
+ caller obtained. The API address comes from the `X-Calm-Api-Url` header,
251
+ or `CALM_API_URL` when the header is absent; it must be an
252
+ `https://…alm.cloud.sap` address, so a token is never sent anywhere else.
253
+
254
+ ```sh
255
+ PORT=3333 CALM_API_URL=https://eu20.alm.cloud.sap/api npx -y cloud-alm-mcp-server http
256
+ # MCP endpoint: http://127.0.0.1:3333/mcp (HOST to bind elsewhere)
257
+ ```
258
+
259
+ Stateless: one server per POST, JSON answers, no session. Requests without
260
+ a bearer get 401; other methods 405. Put it behind TLS and your own
261
+ authentication of callers: the bearer authorizes Cloud ALM, not the caller.
262
+
263
+ ## Use as a library
264
+
265
+ The Cloud ALM client is exported too:
266
+
267
+ ```ts
268
+ import { CloudAlm, serviceKeyGrants, formatTasks, createServer, createHttpHandler } from "cloud-alm-mcp-server";
269
+
270
+ const calm = new CloudAlm(serviceKeyGrants(serviceKey)); // from a service key
271
+ const calm2 = new CloudAlm(async () => myBroker.grant("cloud-alm")); // from your own token source
272
+ console.log(formatTasks(await calm.listTasks({ type: "defect", limit: 20 })));
273
+ ```
274
+
275
+ A grant is `{ accessToken, baseUrl, expiresAt? }`; a callback is called once
276
+ per operation. The library never reads environment variables or files.
277
+
278
+ ## Cloud ALM quirks it hides
279
+
280
+ - **Path casing differs per service**: REST services use lower case
281
+ (`projects`, `tasks`, `landscapeObjects`, `solutionProcesses`), OData
282
+ services capitalised entity sets (`Features`, `ManualTestCases`). The
283
+ wrong case answers 404. The library holds the right paths.
284
+ - **Tasks need a project.** Cloud ALM answers 400 without `projectId`.
285
+ Without one, `listTasks` reads every project (up to 10) and merges.
286
+ - **GUID filters go unquoted** in OData (`projectId eq 1234…`); a quoted
287
+ GUID answers 400 (`Edm.Guid` vs `Edm.String`). Ids are checked as GUIDs
288
+ before they go into a path or a filter.
289
+ - **Paging differs**: OData services take `$top`/`$skip`/`$count`; tasks,
290
+ landscape and status events take `limit`/`offset`; process management
291
+ takes a literal `$top`/`$skip`. Every list here takes `{ limit, offset }`
292
+ (limit 1–200, default 50) and returns
293
+ `{ items, offset, limit, nextOffset, total }`. `nextOffset` is null on
294
+ the last page (one extra row is fetched to know). `total` is set when
295
+ Cloud ALM counts (OData).
296
+ - **Errors come in four shapes** (`{message}`, `{error:{message}}`,
297
+ `[{message}]`, `{message,status}`); the library reads all of them.
298
+ - **Analytics rows are aggregates**, one per combination of dimension
299
+ values, not records. Unknown or unfilterable dimensions in `filters` are
300
+ ignored by Cloud ALM. Some datasets refuse without a filter (428, e.g.
301
+ `ServiceLevels` needs a business service id).
302
+ - **Links live with one side.** A task's test cases are found from Test
303
+ Management (`toTaskAssignments/any(a:a/taskId eq …)`), its features
304
+ from the task's feature assignments, its requirement from relation type
305
+ 10 (`parentId` stays empty), its processes from
306
+ `tasks/solutionProcessAssignments`. A link the key has no scope for is
307
+ listed as not readable instead of failing the task. `calm_gaps`
308
+ counts direct links only: a requirement tested only through its user
309
+ stories counts as uncovered.
310
+
311
+ ## Errors
312
+
313
+ Every failure is a `CloudAlmError` with a `code` and a short `reason`:
314
+
315
+ | Code | When |
316
+ | --- | --- |
317
+ | `unauthorized` | no usable token, expired token, 401, key refused by the token endpoint |
318
+ | `forbidden` | 403: the key lacks the scope for this call |
319
+ | `not_found` | 404 (also a wrong path) |
320
+ | `rate_limited` | 429 after two short retries (Cloud ALM allows about 500 requests per 5 s per tenant) |
321
+ | `unavailable` | 5xx, network error, timeout (30 s per operation), non-JSON or oversized answer |
322
+ | `bad_request` | 400, 422, 428, or an argument this library refused (bad id, limit, type) |
323
+
324
+ A reason never carries the token, the secret or a raw response body; long
325
+ opaque strings are redacted.
326
+
327
+ ## Safety
328
+
329
+ The reader (`CloudAlm`) sends GET only, to the grant's `baseUrl` only,
330
+ which must be `https://` on a `*.alm.cloud.sap` host (`hostPattern` option
331
+ to change it for a test double). No redirects followed. At most 40 requests
332
+ and 30 seconds per operation; answers above 8 MB are refused.
333
+
334
+ The writer (`CloudAlmWriter`), used only in write mode, sends POST, PATCH
335
+ or DELETE only to a fixed allow-list of (method, path) pairs: create and
336
+ change tasks, features, documents and manual test cases (with their
337
+ activities and steps), comments, tags, links, and the deletes the API
338
+ offers. It never retries. A test case write carries its ETag (Cloud ALM
339
+ answers 428 without one).
340
+
341
+ ## Develop
342
+
343
+ Bun for development and tests, plain Node 22+ for the built package.
344
+
345
+ ```sh
346
+ bun install
347
+ bun run typecheck
348
+ bun run test # unit tests on hand-written fixtures, in-process MCP client over both modes, CLI with fakes
349
+ bun run build # dist/ for Node
350
+ node dist/cli.js doctor
351
+ CALM_SERVICE_KEY=~/keys/calm-read.json bun scripts/live-check.ts stdio # against a real tenant
352
+ CALM_SERVICE_KEY=~/keys/calm-read.json bun scripts/live-check.ts http
353
+ ```
354
+
355
+ The live check calls every tool against a real tenant and prints per tool
356
+ whether it answered and the size of the answer, never its text, the secret
357
+ or a token. Test fixtures are invented values in the shapes a real tenant
358
+ returns.
359
+
360
+ Try the package as users get it:
361
+
362
+ ```sh
363
+ npm pack --pack-destination /tmp
364
+ cd "$(mktemp -d)" && npm init -y >/dev/null && npm i /tmp/cloud-alm-mcp-server-*.tgz
365
+ npx --no-install cloud-alm-mcp-server doctor
366
+ ```
367
+
368
+ CI runs typecheck, tests, build, a start-up check and the packed command on
369
+ Node 22 and 24. Releases: bump `version` in `package.json`, add a
370
+ `CHANGELOG.md` entry, tag `vX.Y.Z` and push the tag; the release workflow
371
+ checks again and runs `npm publish --provenance`. It publishes nothing
372
+ until the repository has an `NPM_TOKEN` secret.
373
+
374
+ ## Licence
375
+
376
+ MIT: see [LICENSE](LICENSE). Use it, change it, ship it.
@@ -0,0 +1,41 @@
1
+ /** off: write at once; prefer: approve in the card or the chat; required: in the card (or an opted-in dialog) only. */
2
+ export type ApprovalMode = "off" | "prefer" | "required";
3
+ export declare const APPROVAL_MODES: ApprovalMode[];
4
+ export declare const CARD_URI = "ui://cloud-alm/approve-v1.html";
5
+ export declare const META_KEY = "cloud-alm/approval";
6
+ /** Long enough for a user who answers a turn later; still single-use and bound to one change. */
7
+ export declare const TTL_MS: number;
8
+ /** A write waiting for the user. Holds the tool name and its arguments, not a client. */
9
+ type Pending = {
10
+ tool: string;
11
+ /** Whether it may be taken without the card's nonce (mode prefer). */
12
+ allowChat: boolean;
13
+ args: Record<string, unknown>;
14
+ preview: string;
15
+ subject: string;
16
+ nonce: Buffer;
17
+ expiresAt: number;
18
+ };
19
+ /** Who may approve: the stdio user, or the holder of the same bearer token. */
20
+ export declare const subjectOf: (token: string | undefined) => string;
21
+ export type Proposal = {
22
+ id: string;
23
+ nonce: string;
24
+ expiresAt: number;
25
+ };
26
+ export declare function propose(entry: Omit<Pending, "nonce" | "expiresAt">, now?: number): Proposal;
27
+ export declare class ApprovalError extends Error {
28
+ }
29
+ /**
30
+ * Take a pending write out (single use). With `nonce`, it must match: the
31
+ * card path. Without, only entries that allow the chat (mode prefer) may.
32
+ */
33
+ export declare function take(id: string, subject: string, nonce: string | undefined, now?: number): Pending;
34
+ /** Forget every pending write (tests). */
35
+ export declare function clearPending(): void;
36
+ /**
37
+ * The approval card. Shows the server's preview (never HTML from the
38
+ * model or the tenant: text only), and Approve / Reject.
39
+ */
40
+ export declare function cardHtml(): string;
41
+ export {};