内容
# MCP ConnectWise PSA
MCP([模型上下文协议](https://modelcontextprotocol.io))服务器,用于 [ConnectWise PSA](https://www.connectwise.com/platform/psa)(Manage),专注于技术人员每天的工作:
- **工单** — 我的工单,搜索,完整详情,包含备注,创建,更新状态/优先级/负责人,添加讨论/内部备注
- **时间记录** — 记录工单时间,查看自己的时间
- **公司和联系人** — 快速查找(只读)
- **配置** — 设备/资产,包含序列号,IP地址,操作系统,保修信息(只读)
- **技术人员API密钥(BYOK)** — 每个技术人员提供自己的ConnectWise成员密钥;ConnectWise强制执行该成员的安全角色,并且备注和时间记录归因于**实际人员**
- **传输** — 标准输入输出用于本地使用,可流式传输的HTTP用于共享部署;包含Docker镜像
## 快速开始(本地,标准输入输出)
```bash
npm install && npm run build
CW_SITE=na.myconnectwise.net \
CW_COMPANY_ID=yourcompany \
CW_CLIENT_ID=<集成客户端ID> \
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": "<客户端ID>",
"CW_PUBLIC_KEY": "xxxx",
"CW_PRIVATE_KEY": "yyyy",
"CW_MEMBER_IDENTIFIER": "jdoe"
}
}
}
}
```
`客户端ID` 是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 streamable-http 端点 |
| `GET /health` | 存活探测 |
> 会话存储在内存中 — 运行单个实例(或粘性会话)。
## 访问控制 — 自带密钥(BYOK)
通过HTTP,**没有MCP级别的角色系统**。 每个会话都呈现其自己的ConnectWise成员API密钥,并且ConnectWise本身是访问控制:成员的安全角色决定了什么成功,并且每个备注和时间记录都归因于该成员。
在初始化请求(和会话中的后续每个请求)中发送您的密钥:
```http
x-cw-public-key: <公钥>
x-cw-private-key: <私钥>
x-cw-member-id: <您的成员标识符> (可选 — 启用“我的工单”/“我的时间”)
```
- 没有密钥的请求会被 `401` 拒绝;两个密钥标头必须一起存在。
- 密钥永远不会被记录。 会话绑定到密钥对的SHA-256哈希; 在相同的会话ID上呈现不同的密钥对 → `403`.
- 在ConnectWise中创建成员API密钥,在 **我的账户 → API密钥** 下。 每个技术人员都使用自己的密钥。
本地 **标准输入输出** 是单用户,并使用环境中的 `CW_PUBLIC_KEY`/`CW_PRIVATE_KEY` 代替标头。
## 工具
完整的工具界面始终可用;写入是否成功由成员的ConnectWise安全角色控制。
| 工具 | 访问 |
|---|---|
| `cw_my_tickets`, `cw_search_tickets`, `cw_get_ticket` | 读取 |
| `cw_list_my_time` | 读取 |
| `cw_search_companies`, `cw_get_company`, `cw_search_contacts` | 读取 |
| `cw_list_configurations`, `cw_get_configuration` | 读取 |
| `cw_create_ticket`, `cw_update_ticket`, `cw_add_ticket_note` | 写入 |
| `cw_create_time_entry` | 写入 |
v1中没有破坏性(删除)工具。
## 配置参考
| 变量 | 默认值 | 目的 |
|---|---|---|
| `CW_SITE` | — | ConnectWise主机(云或本地;接受完整URL) |
| `CW_COMPANY_ID` | — | 登录公司ID |
| `CW_CLIENT_ID` | — | 集成客户端ID |
| `CW_PUBLIC_KEY` / `CW_PRIVATE_KEY` | — | API成员密钥 — stdio必填;HTTP(BYOK)上未使用 |
| `CW_MEMBER_IDENTIFIER` | — | stdio密钥所属的成员(我的工单/我的时间) |
| `TRANSPORT` / `PORT` | `stdio` / `3000` | 传输选择 |
## 备注和限制
- 工单搜索默认为打开的工单;状态/看板名称完全匹配,文本筛选器为子字符串。
- 时间戳必须具有完整的秒 — 服务器规范化(ConnectWise拒绝分数秒)。
- 时间记录需要ConnectWise中的**打开的时间报告期**,用于记录日期;如果不存在,则传递API的消息。
- `/system/myAccount` 在某些本地版本中缺失 — 显式提供成员标识符(`CW_MEMBER_IDENTIFIER` 或 `x-cw-member-id`)用于“我的工单”/“我的时间”。
- 讨论备注对客户可见;内部备注不可见 — 工具对此明确说明。
## 开发
```bash
npm install
npm run dev # stdio via tsx
npm run dev:http # http via tsx
npm test # vitest
npm run build # tsc → dist/
```
## 许可证
[MIT](LICENSE)