@wukongcrm/mcp-server 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -0
- package/dist/client.d.ts +31 -0
- package/dist/client.js +155 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/modules.d.ts +17 -0
- package/dist/modules.js +193 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +78 -0
- package/dist/tools.d.ts +8 -0
- package/dist/tools.js +858 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# @wukongcrm/mcp-server
|
|
2
|
+
|
|
3
|
+
WukongCRM MCP Server,用于给支持 Model Context Protocol 的本地 AI 客户端提供 CRM 查询和受控写入能力。
|
|
4
|
+
|
|
5
|
+
默认代理 `https://www.72crm.com/api-11/` 下的固定白名单接口,不提供任意 URL 透传。
|
|
6
|
+
|
|
7
|
+
## 特点
|
|
8
|
+
|
|
9
|
+
- 通过 `CRM_API_KEY` 登录,MCP 进程内部调用 `/login` 换取 `adminToken`。
|
|
10
|
+
- 业务请求按 72CRM 要求发送 `Admin-Token` 请求头,token 只缓存在本地进程内存中。
|
|
11
|
+
- API Key 登录使用独立端类型 `type=4`,避免占用 PC 端 `type=1` 登录态。
|
|
12
|
+
- 不读取 OpenAPI,不调用 `/v3/api-docs`。
|
|
13
|
+
- 不支持附件上传、删除、下载流代理。
|
|
14
|
+
- 查询能力覆盖线索、客户、联系人、商机、产品、合同、回款、发票、跟进记录等模块。
|
|
15
|
+
- 写入只开放线索、客户、联系人、商机、跟进记录,并且必须传 `confirm=true`。
|
|
16
|
+
|
|
17
|
+
## 通用 MCP 配置
|
|
18
|
+
|
|
19
|
+
推荐直接通过 `npx` 启动,无需提前全局安装:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"wukongcrm": {
|
|
25
|
+
"type": "stdio",
|
|
26
|
+
"command": "npx",
|
|
27
|
+
"args": ["-y", "@wukongcrm/mcp-server"],
|
|
28
|
+
"env": {
|
|
29
|
+
"CRM_BASE_URL": "https://www.72crm.com/api-11/",
|
|
30
|
+
"CRM_API_KEY": "你的 72CRM API Key"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
如果你的 MCP 客户端不支持 `type` 字段,可以去掉它:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"wukongcrm": {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["-y", "@wukongcrm/mcp-server"],
|
|
45
|
+
"env": {
|
|
46
|
+
"CRM_BASE_URL": "https://www.72crm.com/api-11/",
|
|
47
|
+
"CRM_API_KEY": "你的 72CRM API Key"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## pnpm 使用
|
|
55
|
+
|
|
56
|
+
不要求必须使用 npm。支持通过 `pnpm dlx` 启动:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"wukongcrm": {
|
|
62
|
+
"type": "stdio",
|
|
63
|
+
"command": "pnpm",
|
|
64
|
+
"args": ["dlx", "@wukongcrm/mcp-server"],
|
|
65
|
+
"env": {
|
|
66
|
+
"CRM_BASE_URL": "https://www.72crm.com/api-11/",
|
|
67
|
+
"CRM_API_KEY": "你的 72CRM API Key"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 全局安装
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install -g @wukongcrm/mcp-server
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
安装后可以直接启动:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
CRM_API_KEY="你的 72CRM API Key" \
|
|
84
|
+
CRM_BASE_URL="https://www.72crm.com/api-11/" \
|
|
85
|
+
wukongcrm-mcp
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
对应 MCP 配置:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"mcpServers": {
|
|
93
|
+
"wukongcrm": {
|
|
94
|
+
"type": "stdio",
|
|
95
|
+
"command": "wukongcrm-mcp",
|
|
96
|
+
"env": {
|
|
97
|
+
"CRM_BASE_URL": "https://www.72crm.com/api-11/",
|
|
98
|
+
"CRM_API_KEY": "你的 72CRM API Key"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API Key 配置
|
|
106
|
+
|
|
107
|
+
`CRM_API_KEY` 是运行时环境变量,不应在 `npm install` 阶段传入。
|
|
108
|
+
|
|
109
|
+
README 中示例里的 `CRM_API_KEY` 对应 72CRM 个人中心生成的 MCP Token。获取地址:
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
https://www.72crm.com/cloud/#/person/index?selectedIndex=1
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
MCP 客户端启动 server 时必须能把 `CRM_API_KEY` 注入到 `@wukongcrm/mcp-server` 进程环境中。常见方式是写在 MCP 客户端配置的 `env` 字段里,或由启动脚本/系统环境变量注入。
|
|
116
|
+
|
|
117
|
+
## 开发运行
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm install
|
|
121
|
+
CRM_API_KEY="你的 72CRM API Key" \
|
|
122
|
+
CRM_BASE_URL="https://www.72crm.com/api-11/" \
|
|
123
|
+
npm run build
|
|
124
|
+
node dist/index.js
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 发布
|
|
128
|
+
|
|
129
|
+
发布前验证:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npm test
|
|
133
|
+
npm run build
|
|
134
|
+
npm pack --dry-run
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
首次公开发布 scoped 包:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm publish
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`package.json` 已配置 `publishConfig.access=public`,因此不需要额外追加 `--access public`。
|
|
144
|
+
|
|
145
|
+
## 支持工具
|
|
146
|
+
|
|
147
|
+
- `crm_auth_status`
|
|
148
|
+
- `crm_list_modules`
|
|
149
|
+
- `crm_list_customer_pools`
|
|
150
|
+
- `crm_get_module_schema`
|
|
151
|
+
- `crm_validate_field`
|
|
152
|
+
- `crm_search_records`
|
|
153
|
+
- `crm_get_record`
|
|
154
|
+
- `crm_get_record_information`
|
|
155
|
+
- `crm_get_related_records`
|
|
156
|
+
- `crm_get_record_timeline`
|
|
157
|
+
- `crm_get_action_records`
|
|
158
|
+
- `crm_list_files`
|
|
159
|
+
- `crm_list_users`
|
|
160
|
+
- `crm_find_user_id`
|
|
161
|
+
- `crm_list_departments`
|
|
162
|
+
- `crm_find_dept_id`
|
|
163
|
+
- `crm_preview_write`
|
|
164
|
+
- `crm_create_record`
|
|
165
|
+
- `crm_update_customer`
|
|
166
|
+
- `crm_update_record_fields`
|
|
167
|
+
- `crm_add_followup`
|
|
168
|
+
- `crm_update_followup`
|
|
169
|
+
|
|
170
|
+
### `crm_search_records` 语义搜索
|
|
171
|
+
|
|
172
|
+
`crm_search_records` 支持直接传入明显带搜索语义的参数,并自动转换为 72CRM 高级筛选 `searchList`:
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"module": "customer",
|
|
177
|
+
"keyword": "18581241105",
|
|
178
|
+
"page": 1,
|
|
179
|
+
"limit": 15
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
客户模块会转换为“客户名称 / 手机 / 电话”三个高级筛选条件,使用 `type=3`(包含)和不同 `groupId` 做 OR 查询。也支持 `query`、`q`、`phone`、`mobile`、`name` 等字段。
|
|
184
|
+
|
|
185
|
+
### 客户更新
|
|
186
|
+
|
|
187
|
+
推荐优先使用 `crm_update_customer` 修改客户字段。它支持直接传客户 ID,也支持通过手机号、客户名或关键字唯一匹配客户;正式写入前会读取客户字段结构,自动补齐 `fieldId`、`fieldType`、`type`、`formType`,避免下拉字段或自定义字段写错结构。
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"phone": "18581241105",
|
|
192
|
+
"updates": {
|
|
193
|
+
"mobile": "16333022010",
|
|
194
|
+
"客户级别": "B"
|
|
195
|
+
},
|
|
196
|
+
"confirm": true
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
实际写入仍然调用固定接口 `/crmCustomer/updateInformation`,请求体为:
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"id": "客户ID",
|
|
205
|
+
"label": 2,
|
|
206
|
+
"list": [
|
|
207
|
+
{
|
|
208
|
+
"fieldName": "mobile",
|
|
209
|
+
"value": "16333022010"
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
写入成功后默认会再次读取 `/crmCustomer/information/{id}` 做字段级校验;如只想写入不校验,可传 `verify=false`。
|
|
216
|
+
|
|
217
|
+
## 安全边界
|
|
218
|
+
|
|
219
|
+
以下操作前期全部拒绝:删除、转移负责人、公海流转、导入导出、审批/状态流转、字段配置修改、附件上传。
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface CrmClientConfig {
|
|
2
|
+
baseUrl?: string;
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
fetchImpl?: typeof fetch;
|
|
5
|
+
}
|
|
6
|
+
export interface CrmResult<T = unknown> {
|
|
7
|
+
code: number;
|
|
8
|
+
msg?: string;
|
|
9
|
+
data?: T;
|
|
10
|
+
}
|
|
11
|
+
export interface AuthStatusResult {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
authMode?: "apiKey";
|
|
14
|
+
data?: unknown;
|
|
15
|
+
message?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class CrmClient {
|
|
18
|
+
private readonly baseUrl;
|
|
19
|
+
private readonly apiKey;
|
|
20
|
+
private readonly fetchImpl;
|
|
21
|
+
private adminToken;
|
|
22
|
+
constructor(config?: CrmClientConfig);
|
|
23
|
+
authStatus(): Promise<AuthStatusResult>;
|
|
24
|
+
call<T = unknown>(method: "GET" | "POST", path: string, body?: unknown, query?: Record<string, unknown>): Promise<T>;
|
|
25
|
+
post<T = unknown>(path: string, body?: unknown, query?: Record<string, unknown>): Promise<T>;
|
|
26
|
+
private ensureToken;
|
|
27
|
+
private loginWithApiKey;
|
|
28
|
+
private requestOnce;
|
|
29
|
+
private buildUrl;
|
|
30
|
+
private buildHeaders;
|
|
31
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const API_KEY_REQUIRED_MESSAGE = "请先配置 CRM_API_KEY,然后重新启动本地 MCP。";
|
|
2
|
+
const AUTH_FAILURE_MESSAGE = "MCP 鉴权失败,请检查 CRM_API_KEY 是否正确或是否已被重置。";
|
|
3
|
+
export class CrmClient {
|
|
4
|
+
baseUrl;
|
|
5
|
+
apiKey;
|
|
6
|
+
fetchImpl;
|
|
7
|
+
adminToken = "";
|
|
8
|
+
constructor(config = {}) {
|
|
9
|
+
this.baseUrl = normalizeBaseUrl(config.baseUrl ?? process.env.CRM_BASE_URL ?? "https://www.72crm.com/api-11/");
|
|
10
|
+
this.apiKey = config.apiKey ?? process.env.CRM_API_KEY ?? "";
|
|
11
|
+
this.fetchImpl = config.fetchImpl ?? fetch;
|
|
12
|
+
}
|
|
13
|
+
async authStatus() {
|
|
14
|
+
try {
|
|
15
|
+
const data = await this.call("POST", "/adminUser/queryLoginUser");
|
|
16
|
+
return { ok: true, authMode: "apiKey", data };
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return { ok: false, authMode: "apiKey", message: errorMessage(error) };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async call(method, path, body, query) {
|
|
23
|
+
assertFixedPath(path);
|
|
24
|
+
await this.ensureToken();
|
|
25
|
+
try {
|
|
26
|
+
return await this.requestOnce(method, path, body, query);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (!(error instanceof CrmUnauthenticatedError)) {
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
await this.ensureToken(true);
|
|
34
|
+
try {
|
|
35
|
+
return await this.requestOnce(method, path, body, query);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error instanceof CrmUnauthenticatedError) {
|
|
39
|
+
throw new CrmAuthenticationError();
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async post(path, body, query) {
|
|
45
|
+
return this.call("POST", path, body, query);
|
|
46
|
+
}
|
|
47
|
+
async ensureToken(force = false) {
|
|
48
|
+
if (!this.apiKey.trim()) {
|
|
49
|
+
throw new Error(API_KEY_REQUIRED_MESSAGE);
|
|
50
|
+
}
|
|
51
|
+
if (!force && this.adminToken.trim()) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.adminToken = await this.loginWithApiKey();
|
|
55
|
+
}
|
|
56
|
+
async loginWithApiKey() {
|
|
57
|
+
try {
|
|
58
|
+
const response = await this.fetchImpl(this.buildUrl("/login"), {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: this.buildHeaders(true),
|
|
61
|
+
body: JSON.stringify({
|
|
62
|
+
loginType: 12,
|
|
63
|
+
authorization: this.apiKey,
|
|
64
|
+
type: 4
|
|
65
|
+
})
|
|
66
|
+
});
|
|
67
|
+
if (response.status === 401 || response.status === 403) {
|
|
68
|
+
throw new CrmAuthenticationError();
|
|
69
|
+
}
|
|
70
|
+
const result = await parseJsonResult(response);
|
|
71
|
+
const adminToken = typeof result.data?.adminToken === "string" ? result.data.adminToken.trim() : "";
|
|
72
|
+
if (result.code !== 0 || !adminToken) {
|
|
73
|
+
throw new CrmAuthenticationError();
|
|
74
|
+
}
|
|
75
|
+
return adminToken;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error instanceof CrmAuthenticationError) {
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
throw new CrmAuthenticationError();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async requestOnce(method, path, body, query) {
|
|
85
|
+
const response = await this.fetchImpl(this.buildUrl(path, query), {
|
|
86
|
+
method,
|
|
87
|
+
headers: this.buildHeaders(body !== undefined, this.adminToken),
|
|
88
|
+
body: body === undefined ? undefined : JSON.stringify(body)
|
|
89
|
+
});
|
|
90
|
+
if (response.status === 401 || response.status === 403) {
|
|
91
|
+
throw new CrmUnauthenticatedError();
|
|
92
|
+
}
|
|
93
|
+
const result = await parseJsonResult(response);
|
|
94
|
+
if (result.code === 302) {
|
|
95
|
+
throw new CrmUnauthenticatedError();
|
|
96
|
+
}
|
|
97
|
+
if (result.code !== 0) {
|
|
98
|
+
throw new Error(result.msg || `72CRM 接口返回异常,code=${result.code}`);
|
|
99
|
+
}
|
|
100
|
+
return result.data;
|
|
101
|
+
}
|
|
102
|
+
buildUrl(path, query) {
|
|
103
|
+
assertFixedPath(path);
|
|
104
|
+
const url = new URL(path.slice(1), this.baseUrl);
|
|
105
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
106
|
+
if (value !== undefined && value !== null && value !== "") {
|
|
107
|
+
url.searchParams.set(key, String(value));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return url.toString();
|
|
111
|
+
}
|
|
112
|
+
buildHeaders(hasJsonBody, adminToken) {
|
|
113
|
+
const headers = new Headers();
|
|
114
|
+
if (adminToken) {
|
|
115
|
+
headers.set("Admin-Token", adminToken);
|
|
116
|
+
}
|
|
117
|
+
if (hasJsonBody) {
|
|
118
|
+
headers.set("Content-Type", "application/json;charset=UTF-8");
|
|
119
|
+
}
|
|
120
|
+
return headers;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
class CrmUnauthenticatedError extends Error {
|
|
124
|
+
constructor() {
|
|
125
|
+
super("72CRM 当前登录态已失效。");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
class CrmAuthenticationError extends Error {
|
|
129
|
+
constructor() {
|
|
130
|
+
super(AUTH_FAILURE_MESSAGE);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function parseJsonResult(response) {
|
|
134
|
+
const text = await response.text();
|
|
135
|
+
if (!text) {
|
|
136
|
+
return { code: response.ok ? 0 : response.status, msg: response.statusText, data: undefined };
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(text);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
throw new Error("72CRM 接口返回了非 JSON 内容,当前 MCP 不代理文件流或下载响应。");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function normalizeBaseUrl(baseUrl) {
|
|
146
|
+
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
147
|
+
}
|
|
148
|
+
function errorMessage(error) {
|
|
149
|
+
return error instanceof Error ? error.message : String(error);
|
|
150
|
+
}
|
|
151
|
+
function assertFixedPath(path) {
|
|
152
|
+
if (!path.startsWith("/")) {
|
|
153
|
+
throw new Error("MCP 仅允许调用固定白名单接口,不允许任意 URL 透传。");
|
|
154
|
+
}
|
|
155
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type ModuleKey = "leads" | "customer" | "contacts" | "product" | "business" | "contract" | "receivables" | "receivablesPlan" | "invoice" | "activity" | "quotation" | "returnVisit" | "visitPlan" | "customerPool" | "leadsPool";
|
|
2
|
+
export interface CrmModuleDefinition {
|
|
3
|
+
key: ModuleKey;
|
|
4
|
+
name: string;
|
|
5
|
+
label: number;
|
|
6
|
+
basePath: string;
|
|
7
|
+
primaryKey: string;
|
|
8
|
+
aliases: string[];
|
|
9
|
+
supportedOperations: string[];
|
|
10
|
+
unsupportedOperations: string[];
|
|
11
|
+
writable: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare const CRM_MODULES: CrmModuleDefinition[];
|
|
14
|
+
export declare const WRITE_MODULES: Set<ModuleKey>;
|
|
15
|
+
export declare function getModuleByKey(module: string | number): CrmModuleDefinition;
|
|
16
|
+
export declare function ensureWritableModule(module: CrmModuleDefinition): void;
|
|
17
|
+
export declare function unsupportedOperationMessage(operation: string): string;
|
package/dist/modules.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const COMMON_READ = ["搜索", "详情", "字段", "详情页信息", "附件"];
|
|
2
|
+
const UNSUPPORTED_HIGH_RISK = ["删除", "负责人转移", "导入导出", "审批/状态流转"];
|
|
3
|
+
export const CRM_MODULES = [
|
|
4
|
+
{
|
|
5
|
+
key: "leads",
|
|
6
|
+
name: "线索",
|
|
7
|
+
label: 1,
|
|
8
|
+
basePath: "/crmLeads",
|
|
9
|
+
primaryKey: "leadsId",
|
|
10
|
+
aliases: ["线索", "crmLeads", "1"],
|
|
11
|
+
supportedOperations: [...COMMON_READ, "简要实体", "新增", "更新", "跟进"],
|
|
12
|
+
unsupportedOperations: ["删除", "转移", "锁定", "分配/领取", "公海", "导入导出", "团队成员", "合并", "共享"],
|
|
13
|
+
writable: true
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
key: "customer",
|
|
17
|
+
name: "客户",
|
|
18
|
+
label: 2,
|
|
19
|
+
basePath: "/crmCustomer",
|
|
20
|
+
primaryKey: "customerId",
|
|
21
|
+
aliases: ["客户", "crmCustomer", "2"],
|
|
22
|
+
supportedOperations: [...COMMON_READ, "客户名/ID 查询", "新增", "更新", "跟进"],
|
|
23
|
+
unsupportedOperations: ["删除", "成交状态", "锁定", "负责人转移", "公海", "地址写入", "上下级客户", "客户产品", "合并", "导入导出"],
|
|
24
|
+
writable: true
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: "contacts",
|
|
28
|
+
name: "联系人",
|
|
29
|
+
label: 3,
|
|
30
|
+
basePath: "/crmContacts",
|
|
31
|
+
primaryKey: "contactsId",
|
|
32
|
+
aliases: ["联系人", "crmContacts", "3"],
|
|
33
|
+
supportedOperations: [...COMMON_READ, "关联商机只读", "新增", "更新", "跟进"],
|
|
34
|
+
unsupportedOperations: ["删除", "关联/取消关联商机写入", "负责人转移", "团队成员", "合并", "导入导出"],
|
|
35
|
+
writable: true
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: "product",
|
|
39
|
+
name: "产品",
|
|
40
|
+
label: 4,
|
|
41
|
+
basePath: "/crmProduct",
|
|
42
|
+
primaryKey: "productId",
|
|
43
|
+
aliases: ["产品", "crmProduct", "4"],
|
|
44
|
+
supportedOperations: [...COMMON_READ, "简要实体", "销售产品列表"],
|
|
45
|
+
unsupportedOperations: ["新增", "更新", "删除", "上下架", "负责人转移", "导入导出"],
|
|
46
|
+
writable: false
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "business",
|
|
50
|
+
name: "商机",
|
|
51
|
+
label: 5,
|
|
52
|
+
basePath: "/crmBusiness",
|
|
53
|
+
primaryKey: "businessId",
|
|
54
|
+
aliases: ["商机", "crmBusiness", "5"],
|
|
55
|
+
supportedOperations: [...COMMON_READ, "关联产品/联系人只读", "新增", "更新", "跟进"],
|
|
56
|
+
unsupportedOperations: ["删除", "赢单/输单状态", "负责人转移", "关联联系人写入", "团队成员", "合并", "导入导出"],
|
|
57
|
+
writable: true
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: "contract",
|
|
61
|
+
name: "合同",
|
|
62
|
+
label: 6,
|
|
63
|
+
basePath: "/crmContract",
|
|
64
|
+
primaryKey: "contractId",
|
|
65
|
+
aliases: ["合同", "crmContract", "6"],
|
|
66
|
+
supportedOperations: [...COMMON_READ, "合同产品", "回款计划", "简要实体"],
|
|
67
|
+
unsupportedOperations: ["新增", "更新", "作废", "状态更新", "负责人转移", "审批相关", "导入导出"],
|
|
68
|
+
writable: false
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: "receivables",
|
|
72
|
+
name: "回款",
|
|
73
|
+
label: 7,
|
|
74
|
+
basePath: "/crmReceivables",
|
|
75
|
+
primaryKey: "receivablesId",
|
|
76
|
+
aliases: ["回款", "crmReceivables", "7"],
|
|
77
|
+
supportedOperations: [...COMMON_READ, "产品明细", "简要实体"],
|
|
78
|
+
unsupportedOperations: ["新增", "更新", "删除", "负责人转移", "审批/财务状态", "导入导出"],
|
|
79
|
+
writable: false
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
key: "receivablesPlan",
|
|
83
|
+
name: "回款计划",
|
|
84
|
+
label: 8,
|
|
85
|
+
basePath: "/crmReceivablesPlan",
|
|
86
|
+
primaryKey: "receivablesPlanId",
|
|
87
|
+
aliases: ["回款计划", "crmReceivablesPlan", "8"],
|
|
88
|
+
supportedOperations: [...COMMON_READ, "按合同/客户查询"],
|
|
89
|
+
unsupportedOperations: ["新增", "批量保存", "更新", "删除", "负责人转移", "导入导出"],
|
|
90
|
+
writable: false
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
key: "invoice",
|
|
94
|
+
name: "发票",
|
|
95
|
+
label: 18,
|
|
96
|
+
basePath: "/crmInvoice",
|
|
97
|
+
primaryKey: "invoiceId",
|
|
98
|
+
aliases: ["发票", "crmInvoice", "18"],
|
|
99
|
+
supportedOperations: [...COMMON_READ, "发票类型"],
|
|
100
|
+
unsupportedOperations: ["新增", "更新", "开票/重置状态", "发票明细写入", "删除", "导入导出"],
|
|
101
|
+
writable: false
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
key: "activity",
|
|
105
|
+
name: "跟进记录",
|
|
106
|
+
label: 19,
|
|
107
|
+
basePath: "/crmActivity",
|
|
108
|
+
primaryKey: "activityId",
|
|
109
|
+
aliases: ["跟进记录", "跟进", "crmActivity", "19"],
|
|
110
|
+
supportedOperations: [...COMMON_READ, "新增", "更新", "评论数"],
|
|
111
|
+
unsupportedOperations: ["删除", "有效性标记", "外勤签到", "邮箱活动", "导入导出"],
|
|
112
|
+
writable: false
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
key: "quotation",
|
|
116
|
+
name: "报价单",
|
|
117
|
+
label: 26,
|
|
118
|
+
basePath: "/crmQuotation",
|
|
119
|
+
primaryKey: "quotationId",
|
|
120
|
+
aliases: ["报价单", "crmQuotation", "26"],
|
|
121
|
+
supportedOperations: [...COMMON_READ, "产品明细", "简要实体"],
|
|
122
|
+
unsupportedOperations: ["新增", "更新", "状态更新", "负责人转移", "团队成员", "导入导出"],
|
|
123
|
+
writable: false
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
key: "returnVisit",
|
|
127
|
+
name: "回访",
|
|
128
|
+
label: 17,
|
|
129
|
+
basePath: "/crmReturnVisit",
|
|
130
|
+
primaryKey: "visitId",
|
|
131
|
+
aliases: ["回访", "crmReturnVisit", "17"],
|
|
132
|
+
supportedOperations: [...COMMON_READ, "简要实体"],
|
|
133
|
+
unsupportedOperations: ["新增", "更新", "删除", "提醒配置", "团队成员", "导入导出"],
|
|
134
|
+
writable: false
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
key: "visitPlan",
|
|
138
|
+
name: "拜访计划",
|
|
139
|
+
label: 25,
|
|
140
|
+
basePath: "/crmVisit",
|
|
141
|
+
primaryKey: "visitPlanId",
|
|
142
|
+
aliases: ["拜访计划", "crmVisit", "25"],
|
|
143
|
+
supportedOperations: [...COMMON_READ],
|
|
144
|
+
unsupportedOperations: ["新增", "更新", "删除", "状态类操作"],
|
|
145
|
+
writable: false
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
key: "customerPool",
|
|
149
|
+
name: "客户公海",
|
|
150
|
+
label: 9,
|
|
151
|
+
basePath: "/crmCustomerPool",
|
|
152
|
+
primaryKey: "customerId",
|
|
153
|
+
aliases: ["客户公海", "crmCustomerPool", "9"],
|
|
154
|
+
supportedOperations: ["列表查询只读"],
|
|
155
|
+
unsupportedOperations: ["领取", "分配", "退回", "转移", "公海规则配置"],
|
|
156
|
+
writable: false
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
key: "leadsPool",
|
|
160
|
+
name: "线索池",
|
|
161
|
+
label: 21,
|
|
162
|
+
basePath: "/crmLeadsPool",
|
|
163
|
+
primaryKey: "leadsId",
|
|
164
|
+
aliases: ["线索池", "crmLeadsPool", "21"],
|
|
165
|
+
supportedOperations: ["列表查询只读"],
|
|
166
|
+
unsupportedOperations: ["领取", "分配", "退回", "转移", "公海规则配置"],
|
|
167
|
+
writable: false
|
|
168
|
+
}
|
|
169
|
+
];
|
|
170
|
+
export const WRITE_MODULES = new Set(["leads", "customer", "contacts", "business"]);
|
|
171
|
+
const MODULE_LOOKUP = new Map();
|
|
172
|
+
for (const moduleDefinition of CRM_MODULES) {
|
|
173
|
+
MODULE_LOOKUP.set(moduleDefinition.key.toLowerCase(), moduleDefinition);
|
|
174
|
+
for (const alias of moduleDefinition.aliases) {
|
|
175
|
+
MODULE_LOOKUP.set(alias.toLowerCase(), moduleDefinition);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export function getModuleByKey(module) {
|
|
179
|
+
const key = String(module).trim().toLowerCase();
|
|
180
|
+
const definition = MODULE_LOOKUP.get(key);
|
|
181
|
+
if (!definition) {
|
|
182
|
+
throw new Error(`不支持的 CRM 模块:${module}。请先调用 crm_list_modules 查看白名单。`);
|
|
183
|
+
}
|
|
184
|
+
return definition;
|
|
185
|
+
}
|
|
186
|
+
export function ensureWritableModule(module) {
|
|
187
|
+
if (!WRITE_MODULES.has(module.key)) {
|
|
188
|
+
throw new Error(`当前 MCP 前期不支持对“${module.name}”执行写入操作。`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
export function unsupportedOperationMessage(operation) {
|
|
192
|
+
return `当前 MCP 前期不支持${operation}。已禁用:${UNSUPPORTED_HIGH_RISK.join("、")}、附件上传。`;
|
|
193
|
+
}
|
package/dist/server.d.ts
ADDED