内容
# EspoCRM MCP 服务器
一个适用于 [EspoCRM](https://www.espocrm.com/) 的 [模型上下文协议](https://modelcontextprotocol.io) 服务器。它通过 EspoCRM 的 **公共 REST API** 将任何 EspoCRM 实例暴露给 MCP 客户端(Claude Desktop,Claude Code 等)- 因此它适用于任何版本或自定义,而无需修改 CRM。
由于它仅与 REST API 通信,因此它不是 AGPL 许可的 EspoCRM 应用程序的派生品,而是使用 MIT 许可证。
## 设计
- **每个用户的无状态身份验证**。在 `oauth` 模式下,服务器存储没有 EspoCRM 凭证,并且不保留会话存储。每个调用者都使用自己的 EspoCRM 用户名/密码登录,并且每个请求都携带一个包含该用户(加密)EspoCRM 凭证的令牌;服务器解包它,而 EspoCRM 强制执行 **该用户的 ACL**。销售代表看到的正是他们自己的线索 - 与他们在 Web UI 中看到的内容相同。
- **元数据驱动**。实体类型,字段和枚举选项在运行时从 `/api/v1/Metadata` 读取,因此通用工具适应任何实例的自定义实体和字段。
- **两种传输方式**。`stdio` 用于本地单用户使用;流式 HTTP(带有内置 OAuth 2.1)用于共享的容器化部署。
## 工具
搜索和获取工具是 **为每个实体类型生成的**,来自 `MCP_ENTITY_TYPES` 允许列表 - 模型直接选择 `search_lead` 而不是具有 `entityType` 参数的通用工具,这提高了工具选择的准确性。少数横切辅助工具保持通用,以便工具数量不会因实体而增加。
**通用帮助程序**
| 工具 | 描述 |
|------|-------------|
| `list_entity_types` | 发现实例上的实体类型 |
| `describe_entity` | 实体字段(类型,枚举选项)和关系 |
| `get_stream` | 记录的活动流(帖子,电子邮件,状态更改) |
**每个实体**(对于 `MCP_ENTITY_TYPES` 中的每个类型;默认 `Lead, Contact, Account, Opportunity`)
| 工具 | 描述 |
|------|-------------|
| `search_<entity>` | 过滤搜索 - **从实体元数据生成的类型化筛选参数**,以及通用 `where` 出口,文本搜索,分页,排序 |
| `get_<entity>` | 按 ID 获取一个记录 |
每个 `search_<entity>` 公开实体的高信号字段作为类型化参数,从 `/Metadata` 运行时派生:
- **枚举** 字段 → 一个参数限制为实际选项(例如 `status: "New" | "Assigned" | …`)
- **布尔** 字段 → 一个布尔参数
- **链接** 字段 → 一个 `<field>Id` 字符串参数
- **日期 / 日期时间 / 数字 / 货币** 字段 → `<field>From` / `<field>To` 范围参数
类型化参数与可选的 `where` 数组(原始 EspoCRM 条件)一起使用 AND 操作,用于处理未覆盖的内容。类型化集被限制以保持工具模式较小;文本字段由 `textFilter` 覆盖。
因此,默认允许列表产生 `search_lead`,`get_lead`,`search_contact`,`get_contact`,... 以及三个帮助程序。仔细管理 `MCP_ENTITY_TYPES` - 每个实体每个操作都有一个工具,操作数量迅速增加,而 MCP 客户端在工具数量非常大时性能下降。
所有结果都限定在已认证用户的访问权限内。
### 编写工具(可选)
编写工具仅在 **`MCP_READ_ONLY=false`** 时注册 - 默认情况下关闭。它们仍然由 EspoCRM 按用户进行 ACL 检查;标志是该功能上的一个全局硬开关。
**通用**
| 工具 | 描述 |
|------|-------------|
| `post_to_stream` | 向记录的活动流发布文本备注 |
**每个实体**(对于 `MCP_ENTITY_TYPES` 中的每个类型)
| 工具 | 描述 |
|------|-------------|
| `create_<entity>` | 创建记录。 正文根据元数据输入;必填字段必填,链接为 `<field>Id` |
| `update_<entity>` | 按 ID 部分更新 - 仅更改您传递的字段 |
| `delete_<entity>` | 按 ID 删除(移动到 EspoCRM 的回收站) |
创建/更新 正文不包括系统和只读字段(`id`,`createdAt`,`modifiedBy`,任何 `readOnly`/`notStorable`)并且根据与搜索筛选器和 OpenAPI 请求正文相同的元数据映射构建。
## OpenAPI
服务器生成一个 **OpenAPI 3.1** 文档,描述它为允许列表中的实体代理的 EspoCRM REST 操作,具有从相同实时元数据驱动的实体模式(枚举选项,`date-time`/`email` 格式,链接 `…Id`/`…Name` 对,必填字段)。工具和规范共享一个真实的来源,因此它们永远不会分离 - 包括写入:当 `MCP_READ_ONLY=false` 时,规范获得匹配的 `POST`/`PATCH`/`DELETE` 操作,带有来自相同字段映射的请求正文。
```bash
# 实时端点(http 传输)。在 oauth 模式下,它需要一个 bearer 令牌:
# curl -H "Authorization: Bearer <access-token>" http://localhost:3000/openapi.json
curl http://localhost:3000/openapi.json
# 代码生成/提交的快照(在 apiKey 模式下运行;需要 ESPOCRM_API_KEY)
node dist/index.js --print-openapi > espocrm.openapi.json
```
将快照提供给 `openapi-generator` 以获取类型化的客户端 SDK,或提供给 API 文档工具。
## 构建
```bash
npm install
npm run build # 编译到 dist/
```
您需要一个可以通过 HTTPS 访问的 EspoCRM 实例和至少一个凭证(请参阅 [身份验证](#身份验证))。
## 身份验证
服务器 **不包含自己的授权逻辑** - EspoCRM 始终强制执行 ACL。有两种模式:
| 模式 | `ESPOCRM_AUTH_MODE` | 调用者代表谁 | 传输方式 | 每个用户 ACL |
|------|---------------------|------------------------|------------|--------------|
| 共享密钥 | `apiKey`(默认) | 一个 API 用户,为所有人 | stdio, http | 否 |
| OAuth 2.1 | `oauth` | 每个用户,通过自己的登录 | 仅 http | 是 |
- **`apiKey`** - 服务器持有一個 EspoCRM API 密钥(`ESPOCRM_API_KEY`),并为每个请求使用它。最简单;适用于单个用户通过 stdio(Claude Desktop,本地 Claude Code)。可以访问服务器的每个人都作为该 EspoCRM 身份出现。在 EspoCRM 的 *Administration → API Users* 下创建密钥。
- **`oauth`** - 服务器是一个 OAuth 2.1 授权服务器 **和** 资源服务器。每个用户都使用他们自己的 **EspoCRM 用户名和密码** 登录到服务器托管的页面;服务器将用户名和密码交换为用户的 EspoCRM 身份令牌,并颁发自己的访问/刷新令牌。需要 `MCP_TRANSPORT=http`。
### OAuth 流的工作原理
库存 MCP 客户端(Claude Code,Claude Desktop,...)自动驱动此功能 - 您只需提供他们 URL。在后台:
1. 客户端 `POST` 到 `/mcp` 时没有令牌,并获得 **401** 及包含指向 `/.well-known/oauth-protected-resource/mcp` 的 `WWW-Authenticate` 头。
2. 它发现授权服务器,注册动态客户端注册,并打开 `/authorize`。
3. 服务器显示一个 **登录页面**;用户输入他们的 EspoCRM 用户名 + 密码。服务器将它们验证为 EspoCRM (`GET /api/v1/App/user`) 并重定向回带有授权代码(PKCE 保护)。
4. 客户端在 `/token` 处将代码交换为 **访问令牌**(默认 1 小时)和 **刷新令牌**。
5. 客户端使用 `Authorization: Bearer <access token>` 调用 `/mcp`;EspoCRM 强制执行该用户的 ACL。
**令牌是自包含且加密的**。访问令牌包含用户的 EspoCRM 身份令牌,刷新令牌包含静默生成新令牌所需的凭证,当 EspoCRM 的令牌空闲超时时(`authTokenMaxIdleTime`,默认为 48 小时)。两者都使用 `MCP_OAUTH_ENCRYPTION_KEY`(AES-256-GCM)密封,因此服务器保持 **无会话状态** - 但请将该密钥视为秘密,并始终在 TLS 后运行(密码通过登录页面传输)。
> **限制。** 令牌吊销尚未实现,因此令牌在过期之前始终有效 - 保持 `MCP_ACCESS_TOKEN_TTL` 谨慎。动态客户端注册存储在内存中,因此客户端在服务器重启后自动重新注册。
## 连接 AI 客户端
### Claude Desktop(stdio,共享密钥)
编辑 `claude_desktop_config.json`(macOS:`~/Library/Application Support/Claude/claude_desktop_config.json`;Windows:`%APPDATA%\Claude\claude_desktop_config.json`):
```jsonc
{
"mcpServers": {
"espocrm": {
"command": "node",
"args": ["/absolute/path/to/espocrm-mcp-server/dist/index.js"],
"env": {
"ESPOCRM_BASE_URL": "https://crm.example.com",
"ESPOCRM_API_KEY": "your-api-user-key"
}
}
}
}
```
重启 Claude Desktop。这在 `apiKey` 模式下运行服务器上的 stdio - 使用此桌面的每个人都作为该 API 密钥。要启用写入工具,请在 `env` 中添加 `"MCP_READ_ONLY": "false"`。
### Claude Code - 本地(stdio,共享密钥)
```bash
claude mcp add espocrm \
--env ESPOCRM_BASE_URL=https://crm.example.com \
--env ESPOCRM_API_KEY=your-api-user-key \
-- node /absolute/path/to/espocrm-mcp-server/dist/index.js
```
### Claude Code - 远程(HTTP,每个用户 OAuth)
首先以 `oauth` 模式通过 HTTP 运行服务器(参见 [部署 HTTP 服务器(OAuth)](#部署http服务器oauth)),然后添加它 **无需凭证** - 客户端本身运行 OAuth 登录流程:
```bash
claude mcp add --transport http espocrm https://mcp.crm.example.com/mcp
```
您第一次使用它时,Claude Code 会打开一个浏览器到服务器的登录页面;使用您的 EspoCRM 用户名和密码登录。每个用户执行此操作一次,然后以自己的 ACL 运行。无需分发 API 密钥或标头。
### 其他 MCP 客户端(Cursor,VS Code,...)
大多数 MCP 客户端接受其配置文件中的相同两种格式(Cursor:`~/.cursor/mcp.json`;VS Code:`.vscode/mcp.json`)。
Stdio(共享密钥):
```json
{
"mcpServers": {
"espocrm": {
"command": "node",
"args": ["/absolute/path/to/espocrm-mcp-server/dist/index.js"],
"env": {
"ESPOCRM_BASE_URL": "https://crm.example.com",
"ESPOCRM_API_KEY": "your-api-user-key"
}
}
}
}
```
远程 HTTP(每个用户 OAuth)- 无需凭证;客户端运行登录流程:
```json
{
"mcpServers": {
"espocrm": {
"url": "https://mcp.crm.example.com/mcp"
}
}
}
```
VS Code 使用顶级 `"servers"` 密钥而不是 `"mcpServers"`;每个服务器的形状相同。客户端必须支持 MCP OAuth(大多数当前客户端都支持)。
## 部署 HTTP 服务器(OAuth)
`docker-compose.yml` 预配置为 `oauth` 模式(`ESPOCRM_AUTH_MODE=oauth`,`MCP_TRANSPORT=http`)。它需要公共 URL 和加密密钥:
```bash
export ESPOCRM_BASE_URL=https://crm.example.com
export MCP_OAUTH_ISSUER_URL=https://mcp.crm.example.com # 此服务器的公共 URL
export MCP_OAUTH_ENCRYPTION_KEY=$(openssl rand -base64 32) # 保持此密钥秘密和稳定
docker compose up --build
```
将其置于 **TLS**(反向代理)后面 - 密码在登录页面输入,`MCP_OAUTH_ISSUER_URL` 必须是客户端访问的 `https://` URL。MCP 端点是 **仅 POST 且无状态** - `GET`/`DELETE` 返回 405。
无 Docker:
```bash
ESPOCRM_BASE_URL=https://crm.example.com \
ESPOCRM_AUTH_MODE=oauth \
MCP_TRANSPORT=http \
MCP_OAUTH_ISSUER_URL=https://mcp.crm.example.com \
MCP_OAUTH_ENCRYPTION_KEY=$(openssl rand -base64 32) \
node dist/index.js
```
验证 OAuth 界面是否正常:
```bash
curl https://mcp.crm.example.com/.well-known/oauth-protected-resource/mcp # → 带有 authorization_servers 的 JSON
curl -i -X POST https://mcp.crm.example.com/mcp -d '{}' -H 'Content-Type: application/json' # → 401 + WWW-Authenticate
```
## 配置
| 变量 | 默认值 | 目的 |
|----------|---------|---------|
| `ESPOCRM_BASE_URL` | - (必填) | 目标 EspoCRM 基本 URL |
| `ESPOCRM_AUTH_MODE` | `apiKey` | `apiKey`(共享)或 `oauth`(每个用户;仅 http) |
| `ESPOCRM_API_KEY` | - | `apiKey` 模式下必填 |
| `MCP_OAUTH_ISSUER_URL` | - | `oauth` 模式下必填 - 此服务器的公共 HTTPS URL(颁发者/资源标识符) |
| `MCP_OAUTH_ENCRYPTION_KEY` | - | `oauth` 模式下必填 - 32 字节密钥密封令牌(`openssl rand -base64 32`) |
| `MCP_ACCESS_TOKEN_TTL` | `3600` | `oauth` 模式下访问令牌的生命周期(秒) |
| `MCP_TRANSPORT` | `stdio` | `stdio` 或 `http` |
| `MCP_HTTP_PORT` | `3000` | HTTP 端口 |
| `MCP_HTTP_PATH` | `/mcp` | HTTP 端点路径 |
| `MCP_READ_ONLY` | `true` | 设置为 `false` 以注册写入工具(`create`/`update`/`delete`/`post_to_stream`) |
| `MCP_ENTITY_TYPES` | `Lead,Contact,Account,Opportunity` | 暴露为专用 `search_<entity>` / `get_<entity>` 工具的实体类型 |
| `ESPOCRM_METADATA_TTL` | `300` | 元数据缓存生命周期(秒) |
参见 [身份验证](#身份验证) 以了解模式和 OAuth 流的工作原理。
## 路线图
1. ✅ **MVP** - 读取工具,stdio + HTTP,元数据缓存。
2. ✅ **类型化每个实体工具 + OpenAPI** - 元数据类型的搜索筛选器,生成的 OpenAPI 3.1。
3. ✅ **写入** - `create_<entity>`,`update_<entity>`,`delete_<entity>`,`post_to_stream`,由 `MCP_READ_ONLY` 控制。
4. ✅ **MCP OAuth** - OAuth 2.1 AS + RS;每个用户登录 EspoCRM,加密自包含令牌,刷新。(剩余:令牌吊销,持久客户端存储。)
5. **工具选择/管理** - 让操作员选择要公开的工具,而不是仅仅选择哪些实体。大型工具表面会增加上下文并降低模型工具选择,因此运行服务器的人员应该仅启用他们需要的工具。正在考虑:每个操作的工具选择(例如仅公开 `search`/`get`),显式工具允许/拒绝列表(`MCP_TOOLS`)和每个实体的操作集。与现有的 `MCP_ENTITY_TYPES` 和 `MCP_READ_ONLY` 杠杆组合使用。
6. **关系** - `link_records` / `unlink_records`,写入正文中的 `linkMultiple` 字段。
7. **丰富性** - 附件,大动作,MCP 资源和提示。
8. **硬化** - 令牌吊销,结构化日志/跟踪,门户支持。
## 开发
```bash
npm install
npm run typecheck
npm run build
```
## 许可证
MIT