A read-only Model Context Protocol (MCP) server that bridges AI assistants (Claude, GitHub Copilot, …) to SAP Cloud ALM (aka CALM). It exposes the Cloud ALM read APIs through four consolidated, intent-based tools, runs over stdio locally or Streamable HTTP remotely, and deploys to SAP BTP Cloud Foundry.
calmcp is read-only: it never creates, updates or deletes data in SAP Cloud ALM.
calmcp is my second SAP Cloud ALM MCP bridge . It succeeds an earlier
Rust implementation sap-cloud-alm-mcp and reuses the knowledge of the Cloud ALM APIs, while taking a different technical direction.
The Architecture is based on marianfoo's arc-1 as reference architecutre. See docs/PREDECESSOR.md for the project's lineage.
| Tool | Purpose |
|---|---|
calm_list |
List/query any collection — tasks (incl. requirements, user stories and defects), projects, features, documents, test cases, hierarchy nodes, cross-library objects, landscape objects, status events, code lists. OData resources accept $filter/$select/$expand/$orderby/$top/$skip; REST resources accept contextual params (project_id, task_id, task_type, timebox_id/timebox_name, …). fields projects the response on any resource. |
calm_get |
Fetch a single entity by id (a feature also by display id, e.g. 6-123). |
calm_analytics |
Query an analytics provider (Defects, Tasks, Tests, …). Supports $orderby — use it for sorted/aggregated questions. |
calm_resources |
Discovery: the catalog of resources/providers, the task type/status/priority code lists, and worked recipes. |
-
All open defects ordered by priority
calm_analytics({ "provider": "Defects", "filter": "status eq 'CIPDFCTOPEN'", "orderby": "priority desc" }) -
Assigned features for defect
Y(two steps)calm_list({ "resource": "task_feature_assignments", "task_id": "Y" }) // -> featureIds calm_get({ "resource": "feature", "id": "<featureId>" }) // -> details
-
Open user stories in a sprint
calm_list({ "resource": "tasks", "project_id": "<uuid>", "task_type": "CALMUS", "status": "CIPUSOPEN", "timebox_name": "Sprint 5", "fields": "displayId,title,status,assigneeName,dueDate" })
Call calm_resources (optionally { "topic": "recipes" }) at any time to discover valid
resource/provider values and required parameters.
A task carries 67 attributes, most of them null, and the Tasks REST endpoint supports neither
$select nor a timebox filter. Unprojected task lists therefore run to hundreds of KB and overflow
agent hosts such as Microsoft Copilot Studio. calm_list adds two options, both applied by calmcp
after fetching: fields projects the records, and timebox_id/timebox_name selects one sprint
(paging through the project so the filter is complete). Unknown field names and unknown timebox
names are rejected rather than silently ignored.
Tasks, Projects (incl. programs and program teams), Features, Documents, Process Hierarchy, Process Scopes, Custom Processes, Test Management (manual + automated), Test Plans (BETA), Analytics, BSM/Status Events, Landscape, and Cross-Library (Applications, Configurations, Developments, Interfaces).
In Cloud ALM the Tasks service is not just to-dos: requirements, user stories, defects,
sub-tasks, roadmap and project tasks, quality gates, checklist items and risks are all tasks,
distinguished by a type code. So resource: "tasks" with task_type: "CALMREQU", "CALMUS" or
"CALMDEF" is how you list requirements, user stories or defects. Call calm_resources for the
full code list.
All services are on API version v1. For the spec revision behind each one, and how to refresh
them, see docs/API_VERSIONS.md.
Configuration is read from environment variables (see .env.example). Two local
auth modes, plus a BTP destination mode:
| Variable | Description |
|---|---|
CALM_SANDBOX |
true to use the SAP Business Accelerator Hub sandbox with CALM_API_KEY. |
CALM_API_KEY |
Sandbox API key (sandbox mode). |
CALM_TENANT, CALM_REGION |
Tenant subdomain and region (e.g. eu10) for OAuth2 mode. |
CALM_CLIENT_ID, CALM_CLIENT_SECRET |
OAuth2 client-credentials from the service binding. |
CALM_DESTINATION_NAME |
Name of a bound BTP Destination (BTP mode; takes precedence). |
PORT, CALM_CORS_ORIGINS |
HTTP transport port and allowed CORS origins. |
CALM_DEBUG, CALM_TIMEOUT_SECONDS |
Verbose tracing and request timeout. |
The simplest path for a single developer: install calmcp as a Claude Desktop extension.
- Download the latest
calmcp-<version>.mcpbfrom the Releases page (or build it locally — see below). - Double-click it, or open Claude Desktop → Settings → Extensions and drag the file in.
- Claude prompts for your Cloud ALM connection. Tenant, Region, Client ID and Client Secret are required (the secret is stored in your OS keychain). To use the SAP Business Accelerator Hub sandbox instead, turn on Use Sandbox and supply a Sandbox API Key. Debug Logging and Request Timeout are optional. Fill them in and enable the extension.
- Ask Claude: "Using the SAP Cloud ALM tools, list the open defects ordered by priority." — it
should call
calm_analytics.
What the bundle is: a pure-JS, cross-platform (macOS / Windows / Linux) build of the stdio server packaged with its dependencies — calmcp has no native modules, so one bundle runs everywhere. It is read-only like the rest of calmcp. For multi-user, HTTP, or BTP deployments, use the Docker image or deploy to Cloud Foundry instead (see below).
npm run build:mcpb # → calmcp-<version>.mcpb in the repo rootThis compiles dist/, installs production dependencies, then validates and packs the bundle with
the pinned @anthropic-ai/mcpb CLI. The form Claude Desktop
shows is defined in mcpb-manifest.json; keep its version in sync with
package.json (the build fails if they differ).
Prefer to hand-edit JSON? Use the
claude_desktop_config.jsonsnippet under Run over stdio — that path also supports sandbox-only setups.
npm install
npm run build
npm test # unit tests (mocked HTTP)
npm run lint # biomeCALM_SANDBOX=true CALM_API_KEY=<key> node dist/index.jsExample client config (Claude Desktop):
{
"mcpServers": {
"calmcp": {
"command": "node",
"args": ["/absolute/path/to/calmcp/dist/index.js"],
"env": { "CALM_SANDBOX": "true", "CALM_API_KEY": "<key>" }
}
}
}CALM_SANDBOX=true CALM_API_KEY=<key> PORT=8080 node dist/index.js --http
curl http://localhost:8080/health
# MCP endpoint: POST http://localhost:8080/mcpcalmcp authenticates to Cloud ALM via a bound Destination (type OAuth2 client-credentials,
URL = your Cloud ALM API base, e.g. https://<tenant>.<region>.alm.cloud.sap/api). Set
CALM_DESTINATION_NAME to that destination's name.
The deployment needs the Cloud Foundry CLI, the MultiApps plugin (which provides cf deploy), and
the MTA build tool:
-
Cloud Foundry CLI — install for your OS (macOS, Windows, Linux) per the official guide.
-
MultiApps plugin and MTA build tool (cross-platform):
cf install-plugin multiapps -f # registers the `cf deploy` command used below npm install --global mbt # MTA build tool cf login -a https://api.cf.<region>.hana.ondemand.com --sso # then pick the org/space
If
cf install-plugin multiappsfails withbad CPU type/ architecture errors (e.g. on arm64 machines), download the matching binary for your platform from the MultiApps releases and install it from file:cf install-plugin <downloaded-binary> -f.
mbt build
cf deploy mta_archives/calmcp_<version>.mtar # <version> is the one in package.jsonThis creates and binds calmcp-xsuaa (XSUAA), calmcp-destination (Destination) and
calmcp-logs (Application Logs), and runs the HTTP transport with a /health check.
Bump the version before deploying a new build — see Releasing. Redeploying different
code under the version already running leaves cf deploy reporting the same number for both, so
there is no way to tell afterwards which build a space is on.
Create the services, build, then push (see manifest.yml):
cf create-service xsuaa application calmcp-xsuaa -c xs-security.json
cf create-service destination lite calmcp-destination
cf create-service application-logs lite calmcp-logs
npm run build
cf pushAfter deploy, assign the CALMCP_Viewer role collection to authorized users and create the
destination as described below.
Two differently-named things are involved — don't confuse them:
calmcp-destination— the destination service instance created and bound by the deploy (mta.yaml / manifest.yml). It is the container that holds destinations; you do not edit it by hand.CALM_DESTINATION_NAME(defaultSAP_CALM) — the name of the destination entry the app looks up at runtime. This is the name you give the destination you create. It must match the value ofCALM_DESTINATION_NAME; change one and change the other.
Create the entry either at the subaccount level (Connectivity → Destinations) or inside the
calmcp-destination service instance — the Cloud SDK checks both. Fill it in from your Cloud ALM
API service key (an OAuth2 client-credentials key for the Cloud ALM API):
| Field | Value |
|---|---|
| Name | the value of CALM_DESTINATION_NAME (default SAP_CALM) |
| Type | HTTP |
| Proxy Type | Internet |
| URL | your Cloud ALM API base including the /api suffix: https://<tenant>.<region>.alm.cloud.sap/api (the region-only form https://<region>.alm.cloud.sap/api works too) |
| Authentication | OAuth2ClientCredentials |
| Client ID | clientid from the service key |
| Client Secret | clientsecret from the service key |
| Token Service URL | the service key's url plus /oauth/token: https://<tenant>.authentication.<region>.hana.ondemand.com/oauth/token |
| Token Service URL Type | Dedicated |
Notes:
- The
/apisuffix on the URL is required — calmcp appends per-service paths (e.g./calm-features/v1) directly to this URL. - The destination's Check Connection button may report 401/403; that is expected for an unauthenticated probe. The real check is the deployed app calling a tool.
- No additional destination properties are needed.
The destination above is how calmcp authenticates to Cloud ALM. Separately, the /mcp endpoint
itself is protected so only authorized callers can reach it.
Standard approach: a BTP user signing in from an AI tool. When the calmcp-xsuaa service is
bound, /mcp requires a valid XSUAA token carrying the Viewer scope (granted via the
CALMCP_Viewer role collection). calmcp detects the bound service from VCAP_SERVICES and enables
this automatically. It also exposes MCP-native OAuth (RFC 8414 discovery + RFC 7591 dynamic client
registration, proxied to XSUAA), so an AI tool such as Claude Desktop, Cursor or VS Code signs the
user in interactively with no manual token handling. The OAuth flow is delegated to XSUAA; calmcp
never sees the user's password. This is the recommended path: each user authenticates as themselves
with a BTP user and the CALMCP_Viewer role collection.
Alternative: a static API key for non-interactive, server-to-server callers (for example
Microsoft Copilot Studio). Set CALM_HTTP_API_KEY and the caller sends Authorization: Bearer <key>.
This authenticates the caller, not a user. Both methods coexist on the one endpoint. See
Connecting calmcp to Microsoft Copilot Studio.
Locally, with neither configured, /mcp is left open for development and a warning is logged. Do
not expose an unauthenticated instance publicly.
Relevant environment variables (HTTP transport):
| Variable | Description |
|---|---|
CALM_PUBLIC_URL |
Public base URL used in OAuth metadata and the callback. Defaults to the first route in VCAP_APPLICATION, so it's normally not needed. |
CALM_DCR_SIGNING_SECRET |
Secret for HMAC-signing dynamic client registrations. Set it (e.g. cf set-env calmcp-srv CALM_DCR_SIGNING_SECRET "$(openssl rand -base64 48)") so registered clients survive a cf deploy (which rotates the XSUAA clientsecret). Defaults to the XSUAA clientsecret. |
CALM_HTTP_API_KEY |
Shared secret for the alternative API-key path. Generate with openssl rand -base64 48. Leave empty to rely on XSUAA only. See the Copilot Studio guide. |
This is the standard way to use the deployed server. /mcp speaks the Streamable HTTP MCP
transport, so point a remote-MCP-capable client at it:
- Claude Code:
claude mcp add --transport http calmcp https://<route>/mcp - Claude Desktop: Settings → Connectors → add a custom connector with the
/mcpURL. - Cursor / VS Code / others: add an MCP server of type HTTP (Streamable) at the
/mcpURL.
On first connect the client triggers the OAuth login; sign in with your BTP user, which must hold
the CALMCP_Viewer role collection. The four tools (calm_list, calm_get, calm_analytics,
calm_resources) then appear.
For non-interactive server-to-server callers such as Microsoft Copilot Studio, use the API-key path instead: see Connecting calmcp to Microsoft Copilot Studio.
npm test # unit (mocked HTTP via undici MockAgent)
npm run test:integration # live sandbox/destination — skipped without credentials
npm run build && npm run test:e2e # real MCP calls over stdio and HTTPPushes to main and every pull request run npm ci, the version check, lint, unit tests and the
build on Node 22 and 24 (.github/workflows/ci.yml). npm ci installs
strictly from the lockfile, so a stale local node_modules can never be mistaken for a real
failure again.
package.json is the single source of truth for the version. Four other files carry a copy —
mcpb-manifest.json, mta.yaml, src/server.ts (advertised to MCP clients) and the .mtar
filename — so bump them together, never by hand:
npm version patch # or minor / majorThe version lifecycle script runs scripts/sync-version.mjs, which
rewrites the copies and stages them; npm then makes the commit and the tag. npm run version:check
verifies the files agree and runs in CI, so drift fails the build rather than reaching a deploy.
Bump before every deploy to a shared space. The MTA version is what cf deploy reports and what
names the archive; reusing it for different code makes deploys indistinguishable after the fact.
MIT
Contributions are welcome! Please ensure your code:
- Builds without errors (
npm run build) - Passes all tests (
npm test) - Passes linting and formatting (
npm run lint, ornpm run lint:fixto auto-fix)
This software is provided "as is", without warranty of any kind, express or implied.
The author(s) and contributor(s) of this tool assume no responsibility or liability for any damages, losses, or consequences that may result from the use or misuse of this software. This includes, but is not limited to:
- Any kind of data loss
- Any damage to systems, networks, or data
- Any legal consequences resulting from unauthorized or improper use
- Any business losses or operational disruptions
- Any security incidents or breaches