cnv-lab-mcp
[](https://m8ven.ai/mcp/galkremer1-cnv-lab-mcp-6vag9f)
# cnv-lab-mcp
MCP server for reserving lab hardware in GLPI, triggering CNV cluster deployments in Jenkins, and importing ACM spoke clusters. No instance URLs, hardware names, or tokens are hardcoded — everything comes from tool arguments or environment variables.
## Add to Cursor
[](https://cursor.com/en/install-mcp?name=cnv-lab-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImdpdGh1YjpnYWxrcmVtZXIxL2Nudi1sYWItbWNwIl0sImVudiI6eyJHTFBJX0JBU0VfVVJMIjoiIiwiR0xQSV9VU0VSX1RPS0VOIjoiIiwiSkVOS0lOU19CQVNFX1VSTCI6IiIsIkpFTktJTlNfVVNFUiI6IiIsIkpFTktJTlNfQVBJX1RPS0VOIjoiIn19)
## Prerequisites
- Node.js >= 20
- A GLPI personal API token (`My Settings` → `API tokens`). Optional `GLPI_APP_TOKEN` if the instance requires one.
- A Jenkins API token (`User` → `Configure` → `API Token`) plus the **short username**, not an email. `user@example.com` returns 401; `user` works.
- GLPI profile must have a non-zero `reservation` right on the target entity.
- Jenkins account needs Job/Build and Job/Read on jobs you will trigger.
### Internal TLS / `NODE_EXTRA_CA_CERTS`
Node's `fetch` (undici) does **not** use the OS/keychain CA store the way `curl` does. If `curl` works but the MCP server reports `fetch failed`, set `NODE_EXTRA_CA_CERTS` to your internal CA bundle and restart Cursor:
```bash
export NODE_EXTRA_CA_CERTS=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
```
TLS errors are reported separately from generic network failures so this is not mysterious.
## Configure
Copy [`.env.example`](.env.example) and set:
| Variable | Required | Notes |
| --- | --- | --- |
| `GLPI_BASE_URL` | yes | With or without `/apirest.php` |
| `GLPI_USER_TOKEN` | yes | Personal API token |
| `GLPI_APP_TOKEN` | no | Only if the instance requires it |
| `JENKINS_BASE_URL` | yes | Jenkins root URL |
| `JENKINS_USER` | yes | Short username (not email) |
| `JENKINS_API_TOKEN` | yes | API token |
Example `mcp.json` entry (secrets via env interpolation):
```json
{
"mcpServers": {
"cnv-lab-mcp": {
"command": "npx",
"args": ["-y", "cnv-lab-mcp"],
"env": {
"GLPI_BASE_URL": "https://glpi.example.internal",
"GLPI_USER_TOKEN": "${env:GLPI_USER_TOKEN}",
"JENKINS_BASE_URL": "https://jenkins.example.internal",
"JENKINS_USER": "${env:JENKINS_USER}",
"JENKINS_API_TOKEN": "${env:JENKINS_API_TOKEN}",
"NODE_EXTRA_CA_CERTS": "${env:NODE_EXTRA_CA_CERTS}"
}
}
}
}
```
Until the package is on npm, point `args` at this repo instead:
```json
"args": ["-y", "github:galkremer1/cnv-lab-mcp"]
```
## Tools
Reservation:
- `list_reservable_items` — browse GLPI reservable assets
- `find_available_cluster` — match requirements (structured + free-text comments) and rank by earliest free slot. **Does not book.**
- `check_availability` — reservations + gaps for one item
- `reserve_cluster` — book a window; always sets `users_id` from the session
- `cancel_reservation` — release a booking
Jenkins:
- `trigger_jenkins_job` — generic trigger; `start_at` becomes `delay=NNNsec`
- `get_build_status` — queue/build + console tail
- `abort_build` — stop a build
Cluster access:
- `get_cluster_info` — non-sensitive metadata and console/API URLs from `*-data.zip`
- `get_cluster_kubeconfig` — `auth/kubeconfig` + `auth/kubeadmin-password` in memory only. Cluster-admin credentials; do not echo them back in chat.
ACM:
- `import_spoke_cluster` — ManagedCluster + KlusterletAddonConfig, apply import manifests to the spoke, poll join
- `get_managed_cluster_status` — hub-side join/available conditions
Scenarios (confirm-before-booking):
- `list_scenarios`
- `plan_scenario` — propose clusters/windows/parameters, book nothing
- `execute_scenario` — reserve + trigger from a plan
Built-in scenarios live in [`src/scenarios/catalog.json`](src/scenarios/catalog.json) and can be extended by PR: `basic-bm-cnv`, `acm-hub-and-spoke`, `sriov-cnv-cluster`.
## Development
```bash
npm install
npm test
npm run lint
npm run typecheck
npm run build
```
Manual smoke test against real instances (not run in CI):
```bash
npx tsx scripts/smoke.ts
SMOKE_JOB=deploy-ocp-bare-metal-cluster-with-abi-cnv-5.0 SMOKE_CLUSTER=example-cluster npx tsx scripts/smoke.ts
```
## Security
- `.env` is gitignored. Examples use obviously fake placeholders.
- CI runs gitleaks on every push/PR.
- Client errors redact tokens and never log `Authorization` / `user_token` values.
- `get_cluster_kubeconfig` downloads the zip into a Buffer and extracts with JSZip in memory. It never writes the archive or `auth/*` to disk.
- `get_cluster_info` only reads `deploydata.json`, `metadata.json`, and `install-config.backup.yaml`.
TDQS
Scored across 15 tools
Most tools have distinct resource-action targets, but a few boundaries could be sharper: check_availability and find_available_cluster both deal with availability, and get_cluster_info vs. get_cluster_kubeconfig both extract from the same deploy artifact. Descriptions clarify the difference well enough that an agent can usually choose correctly.
All 15 tool names follow a consistent snake_case verb_noun convention (list, check, find, reserve, cancel, trigger, get, abort, import, plan, execute). There are no camelCase names, vague single verbs, or arbitrary suffixes.
Fifteen tools is at the upper end of the ideal range but appropriate for the server's scope: reservation management, Jenkins build control, cluster artifact access, ACM import/monitoring, and scenario orchestration. Each tool covers a distinct step and no tool feels redundant.
The core path from finding/reserving assets, triggering and monitoring builds, extracting cluster credentials, and importing a spoke into ACM is covered. However, the lifecycle lacks cleanup operations (e.g., deleting/detaching a ManagedCluster, removing an imported spoke) and there is no dedicated way to list or look up existing reservations before canceling.