内容
# mcp-connectwise-psa
MCP([模型上下文协议](https://modelcontextprotocol.io))服务器,用于 [ConnectWise PSA](https://www.connectwise.com/platform/psa)(Manage)— **精选的 7 个工具集**,涵盖技术人员、调度员和计费,以及一个可选的逃生通道,用于访问其余 API,以便 AI 助手按照每个角色的方式使用 PSA:
- **工单** — 搜索 / 我的工单 / 详细资料,附带备注,创建,更新状态/优先级/负责人,添加讨论/内部备注,以及板块·状态·优先级发现和每个工单的时间和任务
- **时间** — 记录工单时间,查看自己的时间,工作角色查找,以及 **列出和提交时间表**
- **公司和联系人** — 快速查找,联系人详情(电话/邮箱),公司网站
- **配置** — 设备/资产,附带序列号,IP,操作系统,保修(只读)
- **调度** *(日程)* — 日程条目(列表/我的/创建/重新调度/取消),以及 **成员及其时区,工作时间和可用性**
- **发票** *(财务,只读)* — 发票,协议和 **未计费的可计费时间**,准备计费
- **工具集和角色** — 通过 `x-cw-toolsets` 头部(或 `CW_TOOLSETS`)启用会话所需的工具;预设 `tech` / `dispatch` / `invoicing` / `all`。默认 `tech` 保持界面简洁
- **每个成员的 API 密钥(BYOK)** — 每个用户提供自己的 ConnectWise 成员密钥;ConnectWise 强制执行该成员的安全角色,并且每次写入都归属于 *实际人员*
- **传输** — 标准输入输出用于本地使用,可流式传输的 HTTP 用于共享部署;包含 Docker 镜像
## 快速开始(本地,标准输入输出)
```bash
npm install && npm run build
CW_SITE=na.myconnectwise.net \
CW_COMPANY_ID=yourcompany \
CW_CLIENT_ID=<integration clientId> \
CW_PUBLIC_KEY=xxxx CW_PRIVATE_KEY=yyyy \
CW_MEMBER_IDENTIFIER=jdoe \
node dist/index.js
```
Claude Desktop / Claude Code 配置:
```json
{
"mcpServers": {
"connectwise": {
"command": "node",
"args": ["/path/to/mcp-connectwise-psa/dist/index.js"],
"env": {
"CW_SITE": "na.myconnectwise.net",
"CW_COMPANY_ID": "yourcompany",
"CW_CLIENT_ID": "<clientId>",
"CW_PUBLIC_KEY": "xxxx",
"CW_PRIVATE_KEY": "yyyy",
"CW_MEMBER_IDENTIFIER": "jdoe"
}
}
}
}
```
`clientId` 是 ConnectWise API 所需的 — 在 [developer.connectwise.com](https://developer.connectwise.com) 注册一个(免费)集成。API 成员密钥在 ConnectWise 下创建 **我的账户 → API 密钥** (每个成员)或 **系统 → 成员 → API 成员** (集成账户)。
## HTTP 部署
```bash
CW_SITE=… CW_COMPANY_ID=… CW_CLIENT_ID=… \
node dist/index.js --transport http --port 3000
```
或使用 Docker:`docker build -t mcp-connectwise-psa . && docker run -p 3000:3000 -e CW_SITE -e CW_COMPANY_ID -e CW_CLIENT_ID mcp-connectwise-psa`
| 路由 | 目的 |
|---|---|
| `POST/GET/DELETE /mcp` | MCP 流式 HTTP 端点 |
| `GET /health` | 存活探测 |
> 会话存储在内存中 — 运行单个实例(或粘性会话)。
## 访问控制 — 自带密钥(BYOK)
通过 HTTP 没有 **MCP 级角色系统**。每个会话都呈现自己的 ConnectWise 成员 API 密钥,并且 ConnectWise 本身是访问控制:成员的安全角色决定了什么成功,并且每个备注和时间条目都归属于该成员。
在初始化请求上发送您的密钥(以及在会话中的后续请求):
```http
x-cw-public-key: <public key>
x-cw-private-key: <private key>
x-cw-member-id: <your member identifier> (optional — 启用 "我的工单"/"我的时间")
```
- 没有密钥的请求会被拒绝,状态码为 `401`;两个密钥头部必须同时存在。
- 密钥永远不会被记录。会话与密钥对的 SHA-256 哈希绑定;在相同的会话 ID 上呈现不同的密钥对 → `403`。
- 在 ConnectWise 下创建成员 API 密钥 **我的账户 → API 密钥**。每个技术人员都使用自己的密钥。
本地 **标准输入输出** 是单用户,并使用环境中的 `CW_PUBLIC_KEY`/`CW_PRIVATE_KEY` 而不是头部。
## 工具集
工具被分组到 **工具集** 中,以便会话仅看到所需的权限 — 调度员不需要发票工具,小型工具界面使助手保持专注(及其上下文便宜)。写入是否成功仍由成员的 ConnectWise 安全角色控制。
| 工具集密钥 | 工具 |
|---|---|
| `tickets` | `cw_search_tickets`,`cw_my_tickets`,`cw_get_ticket`,`cw_create_ticket`,`cw_update_ticket`,`cw_add_ticket_note`,`cw_list_boards`,`cw_get_board`,`cw_list_priorities`,`cw_list_ticket_time`,`cw_list_ticket_tasks` |
| `time` | `cw_create_time_entry`,`cw_update_time_entry`,`cw_list_my_time`,`cw_list_work_roles`,`cw_list_my_timesheets`,`cw_submit_timesheet` |
| `companies` | `cw_search_companies`,`cw_get_company`,`cw_search_contacts`,`cw_get_contact`,`cw_list_company_sites` |
| `configurations` | `cw_list_configurations`,`cw_get_configuration` |
| `schedule` | `cw_list_schedule_entries`,`cw_my_schedule`,`cw_schedule_ticket`,`cw_update_schedule_entry`,`cw_delete_schedule_entry`,`cw_member_availability`,`cw_list_members`,`cw_get_member` |
| `finance` | `cw_list_invoices`,`cw_get_invoice`,`cw_list_agreements`,`cw_get_agreement`,`cw_list_unbilled_time` |
| `advanced` | `cw_find_endpoint`(搜索整个 CW API — 约 1,150 个端点),`cw_get`(只读 GET 请求,任意路径) |
**预设** 捆绑每个角色的密钥:`tech` = 工单 + 时间 + 公司 + 配置 · `dispatch` = 工单 + 调度 + 公司 + 配置 · `invoicing` = 财务 + 时间 + 公司 · `all` = 所有内容(包括 `advanced`)。
**高级** 工具集是一个可选的逃生通道(不包含在任何角色预设中):`cw_find_endpoint` 搜索整个 ConnectWise API 的捆绑目录,`cw_get` 对任意路径执行只读 GET 请求 — 以便助手可以访问长尾(采购,销售,项目,系统...)精选工具未包装的内容。显式启用它(`x-cw-toolsets: advanced` 或 `all`)。
用逗号列表选择工具集,混合密钥和预设:
- **HTTP** — `x-cw-toolsets` 头部,每个会话:`x-cw-toolsets: dispatch` 或 `x-cw-toolsets: tech,finance`。
- **标准输入输出** — `CW_TOOLSETS` 环境变量或 `--toolsets` 标志:`CW_TOOLSETS=invoicing`。
**默认是 `tech` 预设** — 与工具集存在之前此服务器公开的工具相同,因此在现有客户端选择之前不会更改任何内容。`CW_TOOLSETS`/`--toolsets` 中的未知密钥快速失败;`x-cw-toolsets` 头部中的未知令牌被忽略。唯一破坏性的工具是 `cw_delete_schedule_entry`(调度);财务是只读的。
## 配置参考
| 变量 | 默认值 | 目的 |
|---|---|---|
| `CW_SITE` | — | ConnectWise 主机(云或本地;接受完整 URL) |
| `CW_COMPANY_ID` | — | 登录公司 ID |
| `CW_CLIENT_ID` | — | 集成 clientId |
| `CW_PUBLIC_KEY` / `CW_PRIVATE_KEY` | — | API 成员密钥 — 标准输入输出所需;HTTP 上未使用(BYOK) |
| `CW_MEMBER_IDENTIFIER` | — | 成员的标准输入输出密钥属于(我的工单/我的时间) |
| `TRANSPORT` / `PORT` | `stdio` / `3000` | 传输选择 |
| `CW_TOOLSETS` | `tech` | 启用的工具集(密钥/预设);HTTP 通过 `x-cw-toolsets` 覆盖每个会话 |
## 备注和限制
- 工单搜索默认为打开的工单;状态/板块名称完全匹配,文本筛选器为子字符串。
- 时间戳必须有整秒 — 服务器规范化(ConnectWise 拒绝分数秒)。
- 时间条目需要 ConnectWise 中的 **打开的时间报告期**,用于条目日期;如果不存在,则传递 API 消息。
- `/system/myAccount` 在某些本地版本中缺失 — 显式提供成员标识符(`CW_MEMBER_IDENTIFIER` 或 `x-cw-member-id`)用于“我的工单”/“我的时间”。
- 讨论备注对客户可见;内部备注不可见 — 工具对此明确。
## 开发
```bash
npm install
npm run dev # 标准输入输出 via tsx
npm run dev:http # http via tsx
npm test # vitest
npm run build # tsc → dist/
```
## 许可证
[MIT](LICENSE)