Content
# ConnectWise MCP
Local stdio MCP server exposing **23 read-only ConnectWise Manage tools** to Claude. It wraps the
[`xssentials`](https://github.com/XssentialsAI/xssentials) ConnectWise client and speaks MCP over
stdio. Credentials live in 1Password and are resolved at launch via `op run` — plaintext secrets
never touch disk. Read-only by design (see [Read-Only Invariant](#read-only-invariant)).
These instructions target **Claude Desktop on macOS** (used inside the **Cowork** tab).
> **In a hurry?** The [ELI5 quickstart](#-install-it-like-im-5) below is all most people need. The
> numbered reference sections come after it.
---
## 🧸 Install it like I'm 5
Do these seven steps in order. Copy-paste the gray boxes into the **Terminal** app
(press `⌘ + Space`, type "Terminal", hit Enter).
### Step 1 — Install the two helper apps
This server needs **1Password** (to hold your secret keys safely) and **Homebrew** (a tool
installer). If you don't have Homebrew yet, paste this and follow the prompts:
```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
Then install 1Password and its command-line helper:
```bash
brew install --cask 1password
brew install 1password-cli
```
Open the **1Password app → Settings → Developer** and turn on **"Integrate with 1Password CLI."**
(This is what lets the server unlock secrets with your fingerprint instead of asking you to log in.)
### Step 2 — Download the code
```bash
mkdir -p ~/mcp && cd ~/mcp
git clone https://github.com/XssentialsAI/xssentials.git
git clone https://github.com/XssentialsAI/connectwise-mcp.git
```
### Step 3 — Set it up (copy this whole block)
```bash
cd ~/mcp/connectwise-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -e ../xssentials
pip install -e .
```
### Step 4 — Make your secret-keys file
You need a ConnectWise **API Member** with a public/private key pair (see
[Credentials](#4-credentials-your-own-connectwise-api-keys) for where to click). Put those in a
1Password item, then:
```bash
cp cw.env.example cw.env
open -e cw.env
```
In the file that opens, point each `op://...` line at your 1Password item, save, and close. Don't
paste raw keys — use the `op://` references. (`cw.env` is git-ignored, so it never gets uploaded.)
### Step 5 — Get the two paths Claude needs
Run this and **keep the two lines it prints** — you'll paste them in Step 6:
```bash
echo "op path: $(which op)"
echo "python path: $PWD/.venv/bin/python"
```
### Step 6 — Tell Claude Desktop about the server
In Claude Desktop: **Settings → Developer → Edit Config**. That opens a file called
`claude_desktop_config.json`. Make it look like this, swapping in **your** two paths from Step 5 and
your real home folder:
```json
{
"mcpServers": {
"connectwise": {
"command": "/opt/homebrew/bin/op",
"args": [
"run",
"--env-file=/Users/YOU/mcp/connectwise-mcp/cw.env",
"--",
"/Users/YOU/mcp/connectwise-mcp/.venv/bin/python",
"-m",
"connectwise_mcp"
]
}
}
}
```
Save the file, make sure the **1Password app is unlocked**, then **fully quit and reopen Claude
Desktop** (⌘Q, then open it again).
> ⚠️ **Don't** use the **"+ Add custom connector"** button in Cowork for this. That button is for
> *web* servers on the internet. This server runs **on your Mac**, so it goes in **Edit Config**
> (above) instead.
### Step 7 — Turn it on in Cowork and test it
1. Open the **Cowork** tab in Claude Desktop.
2. Go to **Customize → Connectors**. You'll see **connectwise** in the list — flip its switch **On**.
3. (Optional) Click **Tool permissions** to choose Allow / Ask / Block per tool. ConnectWise is
read-only, so **Allow** is safe for everything.
4. In a Cowork chat, type: **"Run cw_ping"**. You should get a card with the ConnectWise version and
region in a couple of seconds. 🎉
If something breaks, see [Troubleshooting](#troubleshooting).
---
## Reference
### 1. Prerequisites
- **macOS** with **Python 3.10+** (`python3 --version`)
- **1Password app** (desktop) + **1Password CLI** (`op`) with *Developer → Integrate with 1Password
CLI* enabled
- **Claude Desktop** — https://claude.ai/download
### 2. Get the code
Clone all three repos as siblings (the server depends on `xssentials`):
```bash
mkdir -p ~/mcp && cd ~/mcp
git clone https://github.com/XssentialsAI/xssentials.git
git clone https://github.com/XssentialsAI/connectwise-mcp.git
```
### 3. Install
```bash
cd ~/mcp/connectwise-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -e ../xssentials # satisfies the xssentials dependency, no network fetch
pip install -e .
```
### 4. Credentials (your own ConnectWise API keys)
1. In ConnectWise Manage: **System → Members → API Members** → create an API Member.
2. On that member, generate a **public/private key pair** (the private key is shown once — save it).
3. Your **clientId** is your developer-registration UUID from https://developer.connectwise.com.
Store these in 1Password, then `cp cw.env.example cw.env` and point each `op://vault/item/field`
reference at your item. The five required fields and the auth note are documented inline in
`cw.env.example`. Verify the references resolve:
```bash
op run --env-file=cw.env -- printenv CW_COMPANY_ID # should print your company id
```
### 5. Install into Claude Desktop (Edit Config)
**Settings → Developer → Edit Config** opens `~/Library/Application Support/Claude/claude_desktop_config.json`.
Add the `connectwise` entry shown in [Step 6](#step-6--tell-claude-desktop-about-the-server).
**Use absolute paths** — Claude Desktop launches servers with a minimal `PATH` that excludes
Homebrew, so a bare `"op"` or `"python"` will fail. Find your `op` with `which op` (Apple Silicon →
`/opt/homebrew/bin/op`, Intel → `/usr/local/bin/op`). Unlock 1Password, then quit and reopen Claude
Desktop.
### 6. Enable in Cowork (Customize tab)
Local servers are *installed* via Edit Config but *toggled on* in **Cowork → Customize →
Connectors**, alongside web and custom connectors. Flip **connectwise** on. Use **Tool permissions**
(Allow / Ask / Block) to control individual tools — all read-only here, so Allow is safe.
### 7. Verify
In a Cowork chat: **"Run cw_ping"** → a markdown card with CW version, region (e.g. `NA`), and
`isCloud: True`, in under ~2s.
## Troubleshooting
- **Server won't start / "command not found"** — you used a bare `op` or `python`. Both must be
**absolute paths** in the config (Claude Desktop doesn't inherit your shell `PATH`).
- **Secrets don't resolve / `op` prompts** — open and unlock the 1Password desktop app, and confirm
*Developer → Integrate with 1Password CLI* is enabled.
- **Server isn't in the Customize list** — confirm the JSON is valid and you fully quit (⌘Q) and
reopened Claude Desktop. Check **Settings → Developer** for the server's status/logs.
- **`pip install -e .` can't find xssentials** — run `pip install -e ../xssentials` first.
- **401 from ConnectWise** — re-check the five fields in 1Password; the Basic header is
`<company_id>+<public_key>:<private_key>` and all five fields are required.
## Tool Index
23 tools registered in `connectwise_mcp/__main__.py`:
| Tool | CW Resource | Use |
|---|---|---|
| `cw_ping` | `/system/info` | Auth canary |
| `cw_find` | parallel cross-resource | Fuzzy lookup across companies, projects, tickets, opps, invoices, POs |
| `cw_company` | `/company/companies` | Single company card by id or name |
| `cw_project_list` | `/project/projects` | Project search with filters |
| `cw_project_status` | `/project/projects/{id}` + AR + open tickets | Single-project rollup |
| `cw_invoice_lookup` | `/finance/invoices/{id}` + product lines | Single-invoice consolidator |
| `cw_invoice_list` | `/finance/invoices` | Invoice search with filters |
| `cw_ar_aging` | `/finance/invoices` | AR aging buckets per company/project |
| `cw_service_tickets` | `/service/tickets` | Service ticket search across boards |
| `cw_ticket_detail` | `/service/tickets/{id}` + notes + time | Single-ticket consolidator |
| `cw_time_entries` | `/time/entries` | Time entry search with filters |
| `cw_opportunity_status` | `/sales/opportunities/{id}` | Single opportunity rollup |
| `cw_purchasing_status` | `/procurement/purchaseorders` | PO header + line items, or per-vendor PO list |
| `cw_pick_ship_status` | `/procurement/products` | Procurement-readiness flags |
| `cw_sales_orders` | `/sales/salesOrders` | Sales order search |
| `cw_purchase_approvals` | n/a | Structured gap message (no approvals endpoint) |
| `cw_inventory` | `/procurement/warehouseBins` | Per-warehouse-bin on-hand quantities |
| `cw_product_catalog` | `/procurement/catalog` | Catalog search by query / manufacturer |
| `cw_configurations` | `/company/configurations` | Configuration items |
| `cw_expense_reports` | `/expense/entries` | Expense entry search |
| `cw_query` | arbitrary GET | Universal escape hatch; nested-scalar projection via `fields="parent/child"` |
| `cw_report` | `/system/reports/{name}` | Run a CW system report as a flat Markdown table |
| `cw_schedule_entries` | `/schedule/entries` | Schedule entry search (assigned-tech bookings) |
## Read-Only Invariant
Read-only is enforced at two layers:
1. **Client-level** — `xssentials.connectwise.client._request` raises `NotImplementedError` on any
non-GET HTTP method.
2. **Validators-level** — `connectwise_mcp._validators.enforce_get_only` guards the `cw_query` path.
Verify on any commit:
```bash
grep -nE '_request\("(POST|PUT|PATCH|DELETE)"' connectwise_mcp/__main__.py # expect zero hits
```
## License
Internal Xssentials tooling. ConnectWise is a trademark of ConnectWise, LLC.