Content
# ConnectWise Manage MCP Server
An MCP (Model Context Protocol) server that exposes the ConnectWise Manage PSA REST API to Claude, enabling IT engineers to work tickets, manage projects, log time, and query data through natural language.
## Features
- **120 tools** across 10 modules: Tickets, Projects, Time & Schedule, Companies, Contacts, Agreements & Finance, Configurations, Opportunities, Expenses, System
- **4 raw escape-hatch tools** (opt-in via `CWM_ENABLE_RAW_TOOLS=true`) for power users
- Production-grade auth with correct Basic + clientId header construction
- JSON Patch (RFC 6902) for all updates — never accidentally blanks fields
- Built-in pagination, retry on 429 rate limits, and structured error responses
- Strict TypeScript, zod-validated inputs, pino logging to stderr
## Prerequisites
- Node.js 20 LTS or newer
- A ConnectWise Manage API member with API keys generated (see below)
- A registered developer Client ID from [developer.connectwise.com](https://developer.connectwise.com)
### Generating Your CWM API Credentials
You need three things from ConnectWise Manage: a **Public Key**, a **Private Key**, and your **Company ID**.
**Step 1 — Find your Company ID**
Your Company ID is the login company name you type on the CWM login screen (e.g. `MyCompany`). It is **not** a number.
**Step 2 — Generate Your API Keys**
1. Log into ConnectWise Manage
2. Click your profile icon in the **top-right corner**
3. Click **My Account**
4. Switch to the **API Keys** tab
5. Click **New API Key**
6. Enter a description (e.g. `Claude Desktop`) and click **Save**
7. **Copy the Private Key immediately** — it is only displayed once and cannot be retrieved again
8. Copy the **Public Key** as well
**Step 3 — Get a Client ID**
Contact your ConnectWise administrator for the Client ID (a GUID). They manage the registered developer app at [developer.connectwise.com](https://developer.connectwise.com).
## Installation (Windows — Manual)
> **Recommended for most users:** Use the [Team Installation one-liner](#team-installation-one-liner) below instead. Manual installation is for developers or advanced users.
### 1. Install Node.js 20
Download and install from [nodejs.org](https://nodejs.org/). Verify with:
```cmd
node --version
```
You should see `v20.x.x` or higher.
### 2. Clone the repo
```cmd
git clone https://github.com/Allied-Business-Solutions/connectwise-manage-mcp.git C:\Users\YOUR_USERNAME\AppData\Local\Programs\ConnectWiseMCP
cd C:\Users\YOUR_USERNAME\AppData\Local\Programs\ConnectWiseMCP
```
Replace `YOUR_USERNAME` with your Windows username.
### 3. Install dependencies
> **Required — do not skip.** This downloads all packages the server needs to run.
```cmd
npm install
```
### 4. Build the server
> **Required — do not skip.** This compiles TypeScript to JavaScript. The server will not start without this step.
```cmd
npm run build
```
You should see no errors. A `dist\` folder will be created.
### 5. Configure Claude Desktop
Open `%APPDATA%\Claude\claude_desktop_config.json` and add the following (create the file if it doesn't exist):
```json
{
"mcpServers": {
"connectwise": {
"command": "node",
"args": ["C:\\Users\\YOUR_USERNAME\\AppData\\Local\\Programs\\ConnectWiseMCP\\dist\\index.js"],
"env": {
"CWM_SITE": "your-cwm-instance.example.com",
"CWM_COMPANY_ID": "YourCompanyId",
"CWM_PUBLIC_KEY": "your_public_key",
"CWM_PRIVATE_KEY": "your_private_key",
"CWM_CLIENT_ID": "your-client-id-guid"
}
}
}
}
```
Replace `YOUR_USERNAME` and fill in your credentials from the Prerequisites section above. Restart Claude Desktop after saving.
### Updating
#### One-liner install (most users)
Re-run the same install command — the installer handles everything:
```powershell
irm https://raw.githubusercontent.com/Allied-Business-Solutions/connectwise-manage-mcp/master/deploy/install.ps1 | iex
```
#### Manual install
```cmd
cd %LOCALAPPDATA%\Programs\ConnectWiseMCP
git pull
npm install
npm run build
```
Then **restart Claude Desktop** after either method. Your credentials in `claude_desktop_config.json` are not touched by an update.
#### Verifying the update worked
After restarting Claude Desktop, ask Claude: *"Run cw_ping and tell me the server version."* The response should reflect the version you updated to.
## Team Installation (One-Liner)
Open PowerShell and run:
```powershell
irm https://raw.githubusercontent.com/Allied-Business-Solutions/connectwise-manage-mcp/master/deploy/install.ps1 | iex
```
The installer will:
1. Install Node.js 20 LTS via `winget` (skipped if already present)
2. Download the latest release from GitHub
3. Prompt for your individual CWM API credentials
4. Write your `claude_desktop_config.json` automatically
**Prerequisites:**
- Windows 10 1709+ or Windows 11 (required for `winget`)
- Claude Desktop installed
- Your CWM API member credentials (see Prerequisites section above)
**To upgrade:** re-run the same command.
**To uninstall:**
```powershell
irm https://raw.githubusercontent.com/Allied-Business-Solutions/connectwise-manage-mcp/master/deploy/uninstall.ps1 | iex
```
**Ninja RMM:** Push `winget install OpenJS.NodeJS.LTS --silent --accept-package-agreements --accept-source-agreements` as a prerequisite policy. Technicians then run the installer above for their own credentials.
## Environment Variables
| Variable | Required | Description |
|---|---|---|
| `CWM_SITE` | Yes | CWM hostname, e.g. `your-cwm-instance.example.com` (no https://) |
| `CWM_COMPANY_ID` | Yes | Login company ID (not a numeric ID) |
| `CWM_PUBLIC_KEY` | Yes | Your API public key (from My Account > API Keys in CWM) |
| `CWM_PRIVATE_KEY` | Yes | Your API private key (only shown once at generation time) |
| `CWM_CLIENT_ID` | Yes | Developer app GUID from developer.connectwise.com |
| `CWM_ENABLE_RAW_TOOLS` | No | Set `true` to enable raw escape-hatch tools (default: `false`) |
| `CWM_MAX_PAGES` | No | Max pages for paginateAll (default: `20`) |
| `LOG_LEVEL` | No | Pino log level: `debug`, `info`, `warn`, `error` (default: `info`) |
## Known Limitations & Gotchas
### 1. Auth header format (most common source of 401s)
The username is `CompanyId+PublicKey:PrivateKey` — the `+` between company ID and public key is **literal**, not URL-encoded. Missing the `+` is the most common cause of `401 Unauthorized` with misleading error messages. The `clientId` header is also mandatory — requests without it return 401.
### 2. Conditions query language (NOT OData)
CWM uses its own filter syntax:
- Strings use **double quotes**: `status/name="Open"` ✓ (NOT single quotes)
- Booleans are unquoted lowercase: `closedFlag=false`
- Dates use **square brackets**: `dateEntered>[2025-01-01T00:00:00Z]`
- Nested field references use `/`: `status/name`, `company/id`
### 3. PATCH uses RFC 6902 JSON Patch
CWM PATCH does **not** accept a partial merge object. It requires a JSON Patch array:
```json
[{ "op": "replace", "path": "/summary", "value": "New title" }]
```
All update tools in this server handle this automatically via the `flatToJsonPatch()` helper. **Do not use PUT** — it replaces the whole record and will blank fields you don't include.
### 4. Nested resource paths use `{parentId}`
Sub-resource endpoints use `{parentId}` for the parent, not `{id}`:
- Correct: `/service/tickets/{parentId}/notes`
- Wrong: `/service/tickets/{id}/notes`
This is the spec convention — all tools in this server use the correct paths.
### 5. Status IDs are board-specific
Status IDs vary per service board. Use `cw_list_board_statuses` to get valid IDs for a board before changing status. The convenience tool `cw_change_ticket_status` handles this lookup automatically.
**Note**: Board status values are board-specific. Verify the correct status name for your service board before changing status. Use `cw_list_board_statuses` to check available statuses.
### 6. Custom fields — always send the full array
When updating custom fields, you must send the **full** `customFields` array including unchanged fields. Omitting a field from the array **clears it**. Use `mergeCustomFields()` helper when patching custom field values.
### 7. Pagination
CWM does not return a total count by default. Use `cw_count_tickets` (or equivalent count endpoints) to get totals before paginating. Default page size is 25; maximum is 1000. `paginateAll()` caps at `CWM_MAX_PAGES` (default 20) to prevent runaway requests.
### 8. 429 Rate Limits
The server automatically retries on 429 with exponential backoff (up to 3 retries, respects `Retry-After` header). If you hit sustained rate limits, increase spacing between tool calls.
### 9. Agreement Audit
When auditing agreements, use `cw_list_agreements` with appropriate `typeName` filter to enumerate agreements, then `cw_list_agreement_additions` and `cw_update_agreement_addition` to adjust quantities/prorate flags. The `prorateCurrentPeriodFlag` field on additions controls whether mid-period changes are prorated.
## Tool Index
### Service Desk — Tickets (25 tools)
| Tool | Description |
|---|---|
| `cw_list_tickets` | List tickets with full filter/pagination support |
| `cw_search_tickets` | Search tickets by board, status, assignee, company, summary |
| `cw_get_ticket` | Get full ticket detail by ID |
| `cw_count_tickets` | Count tickets matching a condition |
| `cw_create_ticket` | Create a new service ticket |
| `cw_update_ticket` | Update ticket fields via JSON Patch |
| `cw_delete_ticket` | Delete a ticket permanently |
| `cw_list_ticket_notes` | List notes on a ticket |
| `cw_add_ticket_note` | Add a note to a ticket |
| `cw_update_ticket_note` | Update an existing ticket note |
| `cw_delete_ticket_note` | Delete a ticket note permanently |
| `cw_list_ticket_tasks` | List checklist tasks on a ticket |
| `cw_add_ticket_task` | Add a task to a ticket |
| `cw_update_ticket_task` | Update a ticket task |
| `cw_complete_ticket_task` | Mark a task as completed |
| `cw_delete_ticket_task` | Delete a ticket task permanently |
| `cw_list_ticket_time_entries` | List time entries on a ticket |
| `cw_list_ticket_configurations` | List assets attached to a ticket |
| `cw_list_ticket_documents` | List documents/attachments on a ticket |
| `cw_change_ticket_status` | Change ticket status by name (auto-resolves ID) |
| `cw_assign_ticket` | Assign ticket to a member by username |
| `cw_merge_tickets` | Merge source ticket into target ticket |
| `cw_list_boards` | List all service boards |
| `cw_list_board_statuses` | List valid statuses for a board |
| `cw_list_board_types` | List ticket types for a board |
### Projects (25 tools)
| Tool | Description |
|---|---|
| `cw_list_projects` | List projects with filtering |
| `cw_get_project` | Get full project detail |
| `cw_create_project` | Create a project |
| `cw_update_project` | Update project fields |
| `cw_delete_project` | Delete a project permanently |
| `cw_list_project_phases` | List phases for a project |
| `cw_get_project_phase` | Get a single phase |
| `cw_create_project_phase` | Create a project phase |
| `cw_update_project_phase` | Update a phase |
| `cw_delete_project_phase` | Delete a phase permanently |
| `cw_list_project_tickets` | List tickets in a project (by project ID) |
| `cw_get_project_ticket` | Get a single project ticket by its ticket ID |
| `cw_create_project_ticket` | Create a ticket within a project |
| `cw_update_project_ticket` | Update a project ticket via JSON Patch |
| `cw_delete_project_ticket` | Delete a project ticket permanently |
| `cw_list_project_ticket_notes` | List notes on a project ticket |
| `cw_add_project_ticket_note` | Add a note to a project ticket |
| `cw_list_project_workplan` | Get full phase+ticket workplan tree |
| `cw_list_project_notes` | List project notes |
| `cw_add_project_note` | Add a note to a project |
| `cw_list_project_contacts` | List project contacts |
| `cw_list_project_team_members` | List project team members |
| `cw_add_project_team_member` | Add a team member to a project |
| `cw_remove_project_team_member` | Remove a team member from a project |
| `cw_list_project_documents` | List documents/attachments on a project |
### Time & Schedule (12 tools)
| Tool | Description |
|---|---|
| `cw_list_time_entries` | List time entries with filters |
| `cw_get_time_entry` | Get a time entry by ID |
| `cw_create_time_entry` | Log time against a ticket or project |
| `cw_update_time_entry` | Update a time entry |
| `cw_delete_time_entry` | Delete a time entry |
| `cw_list_schedule_entries` | List schedule/dispatch appointments |
| `cw_get_schedule_entry` | Get a schedule entry by ID |
| `cw_create_schedule_entry` | Create a schedule entry |
| `cw_update_schedule_entry` | Update a schedule entry |
| `cw_delete_schedule_entry` | Delete a schedule entry permanently |
| `cw_list_members` | List active CWM members |
| `cw_get_member` | Get a member by ID |
### Companies (7 tools)
| Tool | Description |
|---|---|
| `cw_list_companies` | List companies |
| `cw_get_company` | Get a company by ID |
| `cw_search_companies` | Search by name, identifier, city, status |
| `cw_update_company` | Update company fields |
| `cw_list_company_sites` | List sites for a company |
| `cw_list_company_notes` | List notes on a company |
| `cw_add_company_note` | Add a note to a company |
### Contacts (7 tools)
| Tool | Description |
|---|---|
| `cw_list_contacts` | List contacts |
| `cw_get_contact` | Get a contact by ID |
| `cw_search_contacts` | Search by company, name, email |
| `cw_create_contact` | Create a contact |
| `cw_update_contact` | Update a contact |
| `cw_list_contact_notes` | List notes on a contact |
| `cw_add_contact_note` | Add a note to a contact |
### Agreements & Finance (12 tools)
| Tool | Description |
|---|---|
| `cw_list_agreements` | List agreements |
| `cw_get_agreement` | Get an agreement by ID |
| `cw_create_agreement` | Create a new agreement |
| `cw_update_agreement` | Update agreement fields |
| `cw_delete_agreement` | Delete an agreement permanently |
| `cw_list_agreement_additions` | List additions (line items) for an agreement |
| `cw_get_agreement_addition` | Get a single addition by ID |
| `cw_create_agreement_addition` | Add a product line item to an agreement |
| `cw_update_agreement_addition` | Update an addition (incl. quantity, prorateFlag) |
| `cw_delete_agreement_addition` | Remove a line item from an agreement |
| `cw_list_invoices` | List invoices |
| `cw_get_invoice` | Get an invoice by ID |
### Configurations (5 tools)
| Tool | Description |
|---|---|
| `cw_list_configurations` | List managed assets |
| `cw_get_configuration` | Get an asset by ID |
| `cw_create_configuration` | Create a configuration record |
| `cw_update_configuration` | Update a configuration |
| `cw_list_configuration_types` | List configuration types |
### Opportunities (10 tools)
| Tool | Description |
|---|---|
| `cw_list_opportunities` | List sales opportunities |
| `cw_get_opportunity` | Get an opportunity by ID |
| `cw_update_opportunity` | Update an opportunity |
| `cw_delete_opportunity` | Delete an opportunity permanently |
| `cw_list_opportunity_notes` | List opportunity notes |
| `cw_add_opportunity_note` | Add a note to an opportunity |
| `cw_convert_opportunity_to_project` | Convert an opportunity into a project |
| `cw_convert_opportunity_to_ticket` | Convert an opportunity into a service ticket |
| `cw_list_opportunity_documents` | List documents/attachments on an opportunity |
| `cw_list_quote_documents` | List documents/attachments on a sales quote |
### Expenses (10 tools)
| Tool | Description |
|---|---|
| `cw_list_expense_entries` | List expense entries with filters |
| `cw_get_expense_entry` | Get an expense entry by ID |
| `cw_create_expense_entry` | Create an expense entry |
| `cw_update_expense_entry` | Update an expense entry |
| `cw_delete_expense_entry` | Delete an expense entry permanently |
| `cw_list_expense_reports` | List expense reports |
| `cw_get_expense_report` | Get an expense report by ID |
| `cw_submit_expense_report` | Submit a report for approval |
| `cw_approve_expense_report` | Approve an expense report |
| `cw_reject_expense_report` | Reject an expense report |
### System / Reference (7 tools)
| Tool | Description |
|---|---|
| `cw_ping` | Health check — verify connectivity and auth |
| `cw_list_work_types` | List time entry work types |
| `cw_list_work_roles` | List time entry work roles |
| `cw_list_priorities` | List ticket priorities |
| `cw_list_locations` | List system locations |
| `cw_list_departments` | List departments |
| `cw_download_document` | Download any document by ID — extracts text from PDFs, returns base64 for other types |
### Raw Escape Hatches (4 tools, opt-in)
| Tool | Description |
|---|---|
| `cw_raw_get` | GET any API path |
| `cw_raw_post` | POST any API path |
| `cw_raw_patch` | PATCH any API path with JSON Patch |
| `cw_raw_delete` | DELETE any API path |
## Troubleshooting
**401 Unauthorized**
- Check that `clientId` is set (required for every request)
- Confirm the Basic auth format: `CompanyId+PublicKey:PrivateKey` — the `+` must be literal
- Verify the API member is active and has API Keys configured
**404 Not Found**
- Confirm `CWM_SITE` does not include `https://` or a trailing slash
- The API base path is `/v4_6_release/apis/3.0/` — this is set automatically
**Empty results when you expect data**
- Check your conditions syntax: strings need double quotes, booleans are lowercase unquoted
- Verify field names use `/` for nested paths: `status/name` not `status.name`
**PATCH does nothing**
- All update tools use JSON Patch — verify you're passing `changes` not a full object
- Check the field path: nested fields need `/`, e.g. `"status/id": 42`
**Rate limited (429)**
- The server retries automatically up to 3 times with backoff
- If sustained, reduce request frequency or increase gaps between tool calls
## Development
```cmd
# Build
npm run build
# Watch mode
npm run dev
# Run tests
npm test
# Type check only
npm run lint
# Smoke test (requires real credentials)
set SMOKE_TEST=1 && npm run smoke
```
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available