cld-cwm-mcp
by paulbwfc
README.md
# ConnectWise Manage MCP Server (`cld-cwm-mcp`)
A self-hosted [Model Context Protocol](https://modelcontextprotocol.io) server for
**ConnectWise Manage (PSA)**, deployed to your own Azure subscription. It gives
Claude, Microsoft Copilot, GitHub Copilot and any other MCP client governed access
to service tickets, companies, contacts, configurations, projects, sales, time,
schedule, finance and procurement.
All credentials live in **Azure Key Vault** — nothing sensitive is stored in code,
container images or app settings.
## Features
- **68 tools** across every ConnectWise Manage module (52 in read-only mode):
| Module | Read | Write (opt-in) |
|---|---|---|
| Service | search/count/get tickets, notes, boards, statuses, types, priorities | create/update ticket, add note |
| Companies | search/count/get, sites, types, statuses | create/update company |
| Contacts | search/get, communications | create/update contact |
| Configurations (assets) | search/get, types, statuses | create/update configuration |
| Projects | search/get, phases, project tickets | create/update project |
| Sales | opportunities, statuses, activities | create/update opportunity, create activity |
| Time & Schedule | time entries, work types, schedule entries | create time entry, schedule entry |
| Finance | agreements, additions, invoices | via generic tool only |
| Procurement | catalog, products, purchase orders | via generic tool only |
| Members / System | members, departments, locations, system info | — |
- **`cwm_api_request`** — a generic tool that can call *any* ConnectWise Manage
REST endpoint, so coverage extends to the full API surface without bloating
every AI client with hundreds of tool definitions.
- **Full ConnectWise query syntax**: `conditions`, `childConditions`, `orderBy`,
`fields`, pagination (capped at 200 records/page), plus request pacing
(100 requests/minute towards the CWM API).
- **Read-only by default.** Write tools only exist when the server is deployed
with `CWM_ALLOW_WRITES=true`.
- **Streamable HTTP transport** — works with Claude Desktop, Claude Code,
claude.ai, GitHub Copilot (VS Code), Microsoft Copilot Studio / M365 Copilot,
Cursor, ChatGPT and any MCP-compatible client.
- **API-key protected** (`Authorization: Bearer …` or `X-API-Key` header), key
generated and stored in Key Vault.
## Architecture
```mermaid
flowchart LR
subgraph Clients
A[Claude Desktop / Code / claude.ai]
B[GitHub Copilot - VS Code]
C[Microsoft Copilot Studio / M365]
end
subgraph Azure["Azure - rg-cld-cwm-mcp"]
CA["Container App\nca-cld-cwm-mcp\n(MCP server, /mcp)"]
KV["Key Vault\nkv-cld-cwm-mcp-*\n(CWM keys, MCP API key)"]
ACR["Container Registry\ncrcldcwmmcp*"]
ID["Managed Identity\nid-cld-cwm-mcp"]
LOG["Log Analytics\nlog-cld-cwm-mcp"]
end
CWM["ConnectWise Manage\nREST API"]
A -->|HTTPS + Bearer key| CA
B -->|HTTPS + Bearer key| CA
C -->|HTTPS + X-API-Key| CA
CA -->|secret references via| ID --> KV
CA -->|Basic auth + clientId| CWM
CA --> LOG
ACR -->|image pull| CA
```
Azure resources follow the `cld-cwm-mcp` naming convention:
`rg-cld-cwm-mcp`, `ca-cld-cwm-mcp`, `cae-cld-cwm-mcp`, `kv-cld-cwm-mcp-<suffix>`,
`crcldcwmmcp<suffix>`, `id-cld-cwm-mcp`, `log-cld-cwm-mcp`.
## Quick start
1. **Create a ConnectWise API member** (System → Members → API Members) with an
appropriate security role, then generate a **public/private key pair**.
Register a **Developer Client ID** at
[developer.connectwise.com](https://developer.connectwise.com/ClientID).
2. **Deploy to Azure** (requires `az` CLI, logged in):
```bash
./scripts/deploy.sh # defaults: rg-cld-cwm-mcp, uksouth
```
The script provisions all resources, prompts for your ConnectWise
credentials (stored straight into Key Vault), builds the image in ACR and
deploys the Container App. It prints your MCP endpoint and server key.
3. **Connect a client** — see [docs/client-setup.md](docs/client-setup.md).
Quick test:
```bash
curl https://<your-app>.azurecontainerapps.io/health
```
## Documentation
| Document | Contents |
|---|---|
| [docs/deployment.md](docs/deployment.md) | Azure deployment (script and manual), CI/CD with GitHub Actions OIDC |
| [docs/client-setup.md](docs/client-setup.md) | Claude Desktop, Claude Code, claude.ai, GitHub Copilot, Microsoft Copilot Studio |
| [docs/tools.md](docs/tools.md) | Full tool reference and ConnectWise query syntax |
| [docs/security.md](docs/security.md) | Key Vault, authentication, read-only mode, key rotation |
| [docs/local-development.md](docs/local-development.md) | Running locally without Azure |
## Configuration
| Setting | Source | Default | Purpose |
|---|---|---|---|
| `CWM_COMPANY_ID` | Key Vault `cwm-company-id` | — | ConnectWise company/login ID |
| `CWM_PUBLIC_KEY` | Key Vault `cwm-public-key` | — | API member public key |
| `CWM_PRIVATE_KEY` | Key Vault `cwm-private-key` | — | API member private key |
| `CWM_CLIENT_ID` | Key Vault `cwm-client-id` | — | ConnectWise Developer Network Client ID |
| `MCP_API_KEY` | Key Vault `mcp-api-key` | — | Key clients present to this server |
| `CWM_SITE` | env | `api-eu.myconnectwise.net` | CWM region: `api-na`, `api-eu`, `api-aus`, or self-hosted FQDN |
| `CWM_ALLOW_WRITES` | env | `false` | Register create/update tools |
| `CWM_RATE_LIMIT_PER_MINUTE` | env | `100` | Pacing towards the CWM API |
| `CWM_MAX_PAGE_SIZE` | env | `200` | Hard cap on records per page |
| `KEY_VAULT_URI` | env | — | Enables direct Key Vault lookup via managed identity |
| `AUTH_DISABLED` | env | `false` | Local development only — never in Azure |
## Licence
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues