contract-template-mcp-server 1.0.0
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 +121 -0
- package/package.json +33 -0
- package/src/client.js +280 -0
- package/src/index.js +554 -0
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Contract Template MCP Server
|
|
2
|
+
|
|
3
|
+
CRM 合同模版库管理 MCP Server,为智能体(如 CodeBuddy/WorkBuddy)提供合同模板分类管理、合同模板管理和模板文件获取能力。
|
|
4
|
+
|
|
5
|
+
## 功能概览
|
|
6
|
+
|
|
7
|
+
| 工具名称 | 功能说明 | 对应后端API |
|
|
8
|
+
|---------|---------|-----------|
|
|
9
|
+
| `list_contract_categories` | 查询合同模板分类列表(支持关键词搜索) | `GET /mcp/contract/category/list` |
|
|
10
|
+
| `create_contract_category` | 创建新的合同模板分类 | `POST /mcp/contract/category/create` |
|
|
11
|
+
| `list_contract_templates` | 查询合同模板列表(支持分类/关键词筛选) | `GET /mcp/contract/template/list` |
|
|
12
|
+
| `get_contract_template` | 获取模板详情及文件下载URL | `GET /mcp/contract/template/detail` |
|
|
13
|
+
| `create_contract_template` | 在分类下创建新模板(含 v1.0 初版) | `POST /mcp/contract/template/create` |
|
|
14
|
+
|
|
15
|
+
## 安装与配置
|
|
16
|
+
|
|
17
|
+
### 1. 安装依赖
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cd contract-template-mcp-server
|
|
21
|
+
npm install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 2. 配置 MCP Client
|
|
25
|
+
|
|
26
|
+
在 CodeBuddy/WorkBuddy 的 MCP 配置中添加:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"contract-template": {
|
|
32
|
+
"command": "contract-template-mcp-server",
|
|
33
|
+
"env": {
|
|
34
|
+
"CONTRACT_TEMPLATE_API_BASE_URL": "http://your-crm-backend:port",
|
|
35
|
+
"CRM_MCP_ACCESS_KEY": "从管理系统获取的access-key"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 3. 环境变量
|
|
43
|
+
|
|
44
|
+
| 变量名 | 必填 | 说明 |
|
|
45
|
+
|--------|------|------|
|
|
46
|
+
| `CONTRACT_TEMPLATE_API_BASE_URL` | 否 | 后端CRM服务地址,默认 `http://localhost:3000` |
|
|
47
|
+
| `CRM_MCP_ACCESS_KEY` | 是 | MCP 访问密钥,从管理系统 `agent/access-key` 获取 |
|
|
48
|
+
|
|
49
|
+
## 使用示例
|
|
50
|
+
|
|
51
|
+
### 查询分类
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
用户: 目前有哪些合同模板分类?
|
|
55
|
+
AI: 调用 list_contract_categories → 返回分类列表
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 创建分类
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
用户: 在"销售合同"下帮我创建一个"2025版"子分类
|
|
62
|
+
AI: 调用 list_contract_categories(获取"销售合同"的ID)
|
|
63
|
+
→ 调用 create_contract_category(parentId=销售合同ID, categoryName="2025版")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 查询模板
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
用户: 销售合同分类下有哪些模板?
|
|
70
|
+
AI: 调用 list_contract_templates(categoryId=销售合同分类ID)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 获取模板文件
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
用户: 我需要"2025年版销售合同模板",发给我一下
|
|
77
|
+
AI: 调用 list_contract_templates(keyword="2025年销售合同")找到目标模板
|
|
78
|
+
→ 调用 get_contract_template(templateId=模板ID)
|
|
79
|
+
→ 返回模板详情 + currentFileUrl 文件下载地址
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 创建模板(含文件上传)
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
用户: 在"2025版"分类下创建"新版销售合同模板",文件是xxx.docx
|
|
86
|
+
AI: ① 先调用文件上传接口 /resource/nas-center/upload 上传文件,获取 fileId
|
|
87
|
+
→ ② 调用 create_contract_template(categoryId, templateName, fileId, fileName, fileSize)
|
|
88
|
+
→ 模板创建成功(v1.0 草稿状态)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 数据来源标记
|
|
92
|
+
|
|
93
|
+
所有通过 MCP Server 创建的分类和模板,数据库 `data_source` 字段会自动标记为 `MCP_SERVER`,
|
|
94
|
+
可通过 `model_name` 字段追溯由哪个 AI 模型创建。
|
|
95
|
+
|
|
96
|
+
## 文件上传说明
|
|
97
|
+
|
|
98
|
+
创建模板需要先上传文件获取 `fileId`。文件可通过以下方式上传:
|
|
99
|
+
- **NAS 上传**:`POST /resource/nas-center/upload`(multipart/form-data)
|
|
100
|
+
- **OSS 上传**:`POST /resource/oss/upload`(multipart/form-data)
|
|
101
|
+
|
|
102
|
+
上传成功后返回的 `fileId`(或 `attachmentId`)即为创建模板时需要的参数。
|
|
103
|
+
|
|
104
|
+
## 技术架构
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
┌─────────────────────┐ stdio(MCP协议) ┌─────────────────────┐
|
|
108
|
+
│ CodeBuddy/智能体 │ ◄──────────────────► │ MCP Server (Node) │
|
|
109
|
+
└─────────────────────┘ └──────────┬──────────┘
|
|
110
|
+
│ HTTP + CrmMcpAccessKey
|
|
111
|
+
▼
|
|
112
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
113
|
+
│ CRM Backend (Spring Boot) │
|
|
114
|
+
│ ├── McpContractTemplateCategoryController /mcp/contract/category │
|
|
115
|
+
│ └── McpContractTemplateController /mcp/contract/template │
|
|
116
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "contract-template-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP Server - CRM合同模版库管理,支持合同模版分类管理、合同模版管理、合同模版版本管理",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"contract-template-mcp-server": "src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src/",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"start": "node src/index.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
19
|
+
"zod": "^3.23.0"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"mcp-server",
|
|
27
|
+
"contract-template",
|
|
28
|
+
"crm",
|
|
29
|
+
"codebuddy",
|
|
30
|
+
"workbuddy"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT"
|
|
33
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP 客户端 - 与后端 CRM 合同模版库管理服务通信
|
|
3
|
+
*
|
|
4
|
+
* 后端服务 API:
|
|
5
|
+
* GET /mcp/contract/category/list?keyword= - 查询分类平铺列表
|
|
6
|
+
* POST /mcp/contract/category/create - 创建分类
|
|
7
|
+
* GET /mcp/contract/template/list?categoryId=&keyword= - 查询模板列表
|
|
8
|
+
* GET /mcp/contract/template/detail?id= - 查询模板详情(含文件URL)
|
|
9
|
+
* POST /mcp/contract/template/create - 创建模板
|
|
10
|
+
*
|
|
11
|
+
* 认证方式:
|
|
12
|
+
* 通过环境变量 CRM_MCP_ACCESS_KEY 传递 access-key,
|
|
13
|
+
* 登录后端管理系统获取 key 后配置到 MCP Server 中。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const DEFAULT_BASE_URL = "http://localhost:3000";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 从环境变量获取后端服务地址
|
|
20
|
+
*/
|
|
21
|
+
function getBaseUrl() {
|
|
22
|
+
return process.env.CONTRACT_TEMPLATE_API_BASE_URL || DEFAULT_BASE_URL;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 从环境变量获取认证 token
|
|
27
|
+
*/
|
|
28
|
+
export function getAccessToken() {
|
|
29
|
+
return process.env.CRM_MCP_ACCESS_KEY || null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 检查是否已配置认证 token
|
|
34
|
+
*/
|
|
35
|
+
export function hasAuth() {
|
|
36
|
+
return !!getAccessToken();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 通用 HTTP 请求封装(自动附带认证 token)
|
|
41
|
+
* @param {string} path - API 路径
|
|
42
|
+
* @param {Object} options - fetch 选项
|
|
43
|
+
* @returns {Promise<Object>} 后端返回结果
|
|
44
|
+
*/
|
|
45
|
+
async function request(path, options = {}) {
|
|
46
|
+
const baseUrl = getBaseUrl();
|
|
47
|
+
const url = `${baseUrl}${path}`;
|
|
48
|
+
const token = getAccessToken();
|
|
49
|
+
|
|
50
|
+
const headers = {
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
...options.headers,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// 如果配置了 token,自动添加 CrmMcpAccessKey 头
|
|
56
|
+
if (token) {
|
|
57
|
+
headers["CrmMcpAccessKey"] = token;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const fetchOptions = {
|
|
61
|
+
...options,
|
|
62
|
+
headers,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const response = await fetch(url, fetchOptions);
|
|
66
|
+
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const errorText = await response.text();
|
|
69
|
+
let errorMsg = `HTTP ${response.status}: ${errorText || response.statusText}`;
|
|
70
|
+
|
|
71
|
+
// 401 给出更友好的提示
|
|
72
|
+
if (response.status === 401) {
|
|
73
|
+
errorMsg = "认证失败:access-token 无效或已过期,请重新登录管理系统获取新的 token。";
|
|
74
|
+
} else if (response.status === 403) {
|
|
75
|
+
errorMsg = "权限不足:当前用户没有操作权限。";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
throw new Error(errorMsg);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return response.json();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ============================================================
|
|
85
|
+
// 分类 API
|
|
86
|
+
// ============================================================
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 查询合同模板分类列表
|
|
90
|
+
* @param {Object} params
|
|
91
|
+
* @param {string} [params.keyword] - 关键词搜索(可选,模糊匹配分类名称)
|
|
92
|
+
* @returns {Promise<Object>} 后端返回结果(data 为分类列表数组)
|
|
93
|
+
*/
|
|
94
|
+
export async function listCategories({ keyword } = {}) {
|
|
95
|
+
const params = new URLSearchParams();
|
|
96
|
+
if (keyword) params.append("keyword", keyword);
|
|
97
|
+
|
|
98
|
+
const queryStr = params.toString();
|
|
99
|
+
const path = queryStr
|
|
100
|
+
? `/mcp/contract/category/list?${queryStr}`
|
|
101
|
+
: "/mcp/contract/category/list";
|
|
102
|
+
return request(path, { method: "GET" });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 创建合同模板分类
|
|
107
|
+
* @param {Object} params
|
|
108
|
+
* @param {string} params.categoryName - 分类名称(必填)
|
|
109
|
+
* @param {string} [params.parentId] - 父分类ID(可选,默认0=顶级分类)
|
|
110
|
+
* @param {number} [params.sortOrder] - 排序号(可选,默认0,越小越靠前)
|
|
111
|
+
* @param {string} [params.modelName] - AI模型名称(可选)
|
|
112
|
+
* @returns {Promise<Object>} 后端返回结果(data 为新创建的分类对象)
|
|
113
|
+
*/
|
|
114
|
+
export async function createCategory({ categoryName, parentId, sortOrder, modelName }) {
|
|
115
|
+
const body = { categoryName };
|
|
116
|
+
if (parentId) body.parentId = parentId;
|
|
117
|
+
if (sortOrder !== undefined && sortOrder !== null) body.sortOrder = sortOrder;
|
|
118
|
+
if (modelName) body.modelName = modelName;
|
|
119
|
+
|
|
120
|
+
return request("/mcp/contract/category/create", {
|
|
121
|
+
method: "POST",
|
|
122
|
+
body: JSON.stringify(body),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ============================================================
|
|
127
|
+
// 模板 API
|
|
128
|
+
// ============================================================
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 查询合同模板列表
|
|
132
|
+
* @param {Object} params
|
|
133
|
+
* @param {string} [params.categoryId] - 分类ID(可选,筛选指定分类下的模板)
|
|
134
|
+
* @param {string} [params.keyword] - 关键词(可选,模糊匹配模板名称)
|
|
135
|
+
* @returns {Promise<Object>} 后端返回结果(data 为模板列表数组)
|
|
136
|
+
*/
|
|
137
|
+
export async function listTemplates({ categoryId, keyword } = {}) {
|
|
138
|
+
const params = new URLSearchParams();
|
|
139
|
+
if (categoryId) params.append("categoryId", categoryId);
|
|
140
|
+
if (keyword) params.append("keyword", keyword);
|
|
141
|
+
|
|
142
|
+
const queryStr = params.toString();
|
|
143
|
+
const path = queryStr
|
|
144
|
+
? `/mcp/contract/template/list?${queryStr}`
|
|
145
|
+
: "/mcp/contract/template/list";
|
|
146
|
+
return request(path, { method: "GET" });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 查询合同模板详情(含文件下载URL)
|
|
151
|
+
* @param {string} id - 模板ID(字符串类型,避免精度丢失)
|
|
152
|
+
* @returns {Promise<Object>} 后端返回结果(data 为模板详情对象,含 currentFileUrl)
|
|
153
|
+
*/
|
|
154
|
+
export async function getTemplateDetail(id) {
|
|
155
|
+
return request(`/mcp/contract/template/detail?id=${encodeURIComponent(id)}`, {
|
|
156
|
+
method: "GET",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 创建合同模板(在指定分类下)
|
|
162
|
+
* @param {Object} params
|
|
163
|
+
* @param {string} params.templateName - 模板名称(必填)
|
|
164
|
+
* @param {string} params.categoryId - 分类ID(必填,String类型避免精度丢失)
|
|
165
|
+
* @param {string} params.fileId - 文件ID(必填,由文件上传接口返回)
|
|
166
|
+
* @param {string} params.fileName - 文件名(必填)
|
|
167
|
+
* @param {number} [params.fileSize] - 文件大小(字节,可选)
|
|
168
|
+
* @param {string} [params.description] - 模板描述(可选)
|
|
169
|
+
* @param {string} [params.modelName] - AI模型名称(可选)
|
|
170
|
+
* @returns {Promise<Object>} 后端返回结果(data 为新创建的模板对象)
|
|
171
|
+
*/
|
|
172
|
+
export async function createTemplate({
|
|
173
|
+
templateName,
|
|
174
|
+
categoryId,
|
|
175
|
+
fileId,
|
|
176
|
+
fileName,
|
|
177
|
+
fileSize,
|
|
178
|
+
description,
|
|
179
|
+
modelName,
|
|
180
|
+
}) {
|
|
181
|
+
const body = { templateName, categoryId, fileId, fileName };
|
|
182
|
+
if (fileSize !== undefined && fileSize !== null) body.fileSize = fileSize;
|
|
183
|
+
if (description) body.description = description;
|
|
184
|
+
if (modelName) body.modelName = modelName;
|
|
185
|
+
|
|
186
|
+
return request("/mcp/contract/template/create", {
|
|
187
|
+
method: "POST",
|
|
188
|
+
body: JSON.stringify(body),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 上传文件并一步创建合同模板(推荐 MCP 智能体使用)
|
|
194
|
+
*
|
|
195
|
+
* 将用户选中的合同文件上传到指定分类下,内部自动完成:
|
|
196
|
+
* 1. 文件上传到 NAS/OSS,获取 fileId
|
|
197
|
+
* 2. 在指定分类下创建合同模板记录
|
|
198
|
+
* 3. 创建初始版本 v1.0
|
|
199
|
+
*
|
|
200
|
+
* @param {Object} params
|
|
201
|
+
* @param {Buffer|ArrayBuffer|Uint8Array} params.fileData - 文件的二进制内容(必填)
|
|
202
|
+
* @param {string} params.fileName - 原始文件名,如 "销售合同模板.docx"(必填)
|
|
203
|
+
* @param {string} params.categoryId - 目标分类ID(必填)
|
|
204
|
+
* @param {string} [params.templateName] - 模板名称(可选,不传则使用文件名)
|
|
205
|
+
* @param {string} [params.description] - 模板描述(可选)
|
|
206
|
+
* @param {string} [params.modelName] - AI模型名称(可选)
|
|
207
|
+
* @returns {Promise<Object>} 后端返回结果(data 为新创建的模板对象)
|
|
208
|
+
*/
|
|
209
|
+
export async function uploadAndCreateTemplate({
|
|
210
|
+
fileData,
|
|
211
|
+
fileName,
|
|
212
|
+
categoryId,
|
|
213
|
+
templateName,
|
|
214
|
+
description,
|
|
215
|
+
modelName,
|
|
216
|
+
}) {
|
|
217
|
+
const baseUrl = getBaseUrl();
|
|
218
|
+
const url = `${baseUrl}/mcp/contract/template/upload-and-create`;
|
|
219
|
+
const token = getAccessToken();
|
|
220
|
+
|
|
221
|
+
// 构建 FormData
|
|
222
|
+
const formData = new FormData();
|
|
223
|
+
// 将二进制数据转为 Blob 再附加到 FormData
|
|
224
|
+
const blob = new Blob([fileData], { type: getMimeType(fileName) });
|
|
225
|
+
formData.append("file", blob, fileName);
|
|
226
|
+
formData.append("categoryId", categoryId);
|
|
227
|
+
if (templateName) formData.append("templateName", templateName);
|
|
228
|
+
if (description) formData.append("description", description);
|
|
229
|
+
if (modelName) formData.append("modelName", modelName);
|
|
230
|
+
|
|
231
|
+
const headers = {};
|
|
232
|
+
if (token) {
|
|
233
|
+
headers["CrmMcpAccessKey"] = token;
|
|
234
|
+
}
|
|
235
|
+
// 注意:不要手动设置 Content-Type,让浏览器/运行时自动设置 boundary
|
|
236
|
+
|
|
237
|
+
const response = await fetch(url, {
|
|
238
|
+
method: "POST",
|
|
239
|
+
headers,
|
|
240
|
+
body: formData,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
if (!response.ok) {
|
|
244
|
+
const errorText = await response.text();
|
|
245
|
+
let errorMsg = `HTTP ${response.status}: ${errorText || response.statusText}`;
|
|
246
|
+
if (response.status === 401) {
|
|
247
|
+
errorMsg = "认证失败:access-token 无效或已过期,请重新登录管理系统获取新的 token。";
|
|
248
|
+
} else if (response.status === 403) {
|
|
249
|
+
errorMsg = "权限不足:当前用户没有操作权限。";
|
|
250
|
+
}
|
|
251
|
+
throw new Error(errorMsg);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return response.json();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 根据文件扩展名推断 MIME 类型
|
|
259
|
+
* @param {string} fileName 文件名
|
|
260
|
+
* @returns {string} MIME 类型
|
|
261
|
+
*/
|
|
262
|
+
function getMimeType(fileName) {
|
|
263
|
+
if (!fileName) return "application/octet-stream";
|
|
264
|
+
const ext = fileName.split(".").pop()?.toLowerCase();
|
|
265
|
+
const mimeMap = {
|
|
266
|
+
doc: "application/msword",
|
|
267
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
268
|
+
pdf: "application/pdf",
|
|
269
|
+
xls: "application/vnd.ms-excel",
|
|
270
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
271
|
+
txt: "text/plain",
|
|
272
|
+
png: "image/png",
|
|
273
|
+
jpg: "image/jpeg",
|
|
274
|
+
jpeg: "image/jpeg",
|
|
275
|
+
gif: "image/gif",
|
|
276
|
+
zip: "application/zip",
|
|
277
|
+
rar: "application/x-rar-compressed",
|
|
278
|
+
};
|
|
279
|
+
return mimeMap[ext] || "application/octet-stream";
|
|
280
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server 入口 - CRM 合同模版库管理
|
|
5
|
+
*
|
|
6
|
+
* 提供六个工具:
|
|
7
|
+
* 1. list_contract_categories - 查询合同模板分类列表
|
|
8
|
+
* 2. create_contract_category - 创建新的合同模板分类
|
|
9
|
+
* 3. list_contract_templates - 查询合同模板列表
|
|
10
|
+
* 4. get_contract_template - 获取合同模板详情及文件下载URL
|
|
11
|
+
* 5. create_contract_template - 在分类下创建合同模板(需预传fileId)
|
|
12
|
+
* 6. upload_file_to_template_category - 上传文件并一步创建模板(推荐MCP使用)
|
|
13
|
+
*
|
|
14
|
+
* 使用方式(在 CodeBuddy/WorkBuddy 的 MCP 配置中):
|
|
15
|
+
* {
|
|
16
|
+
* "mcpServers": {
|
|
17
|
+
* "contract-template": {
|
|
18
|
+
* "command": "contract-template-mcp-server",
|
|
19
|
+
* "env": {
|
|
20
|
+
* "CONTRACT_TEMPLATE_API_BASE_URL": "http://your-backend-service:port",
|
|
21
|
+
* "CRM_MCP_ACCESS_KEY": "从管理系统获取的access-key"
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
29
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
30
|
+
import { z } from "zod";
|
|
31
|
+
import {
|
|
32
|
+
listCategories,
|
|
33
|
+
createCategory,
|
|
34
|
+
listTemplates,
|
|
35
|
+
getTemplateDetail,
|
|
36
|
+
createTemplate,
|
|
37
|
+
uploadAndCreateTemplate,
|
|
38
|
+
hasAuth,
|
|
39
|
+
} from "./client.js";
|
|
40
|
+
|
|
41
|
+
// 创建 MCP Server 实例
|
|
42
|
+
const server = new McpServer({
|
|
43
|
+
name: "contract-template-mcp-server",
|
|
44
|
+
version: "1.0.0",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ============================================================
|
|
48
|
+
// 工具 1:查询合同模板分类列表
|
|
49
|
+
// ============================================================
|
|
50
|
+
server.tool(
|
|
51
|
+
"list_contract_categories",
|
|
52
|
+
"查询合同模板的分类列表。返回系统中所有的合同模板分类(平铺列表形式),支持按分类名称关键词模糊搜索。可用于回答"目前有哪些合同模板分类?""帮我列一下分类"等问题。",
|
|
53
|
+
{
|
|
54
|
+
keyword: z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("可选,按分类名称模糊搜索,如"劳动合同"可匹配到"劳动合同模板分类""),
|
|
58
|
+
},
|
|
59
|
+
async ({ keyword }) => {
|
|
60
|
+
try {
|
|
61
|
+
const result = await listCategories({ keyword });
|
|
62
|
+
const categoryList = Array.isArray(result.data)
|
|
63
|
+
? result.data
|
|
64
|
+
: Array.isArray(result)
|
|
65
|
+
? result
|
|
66
|
+
: [];
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: JSON.stringify(
|
|
73
|
+
{
|
|
74
|
+
success: true,
|
|
75
|
+
count: categoryList.length,
|
|
76
|
+
categories: categoryList.map((item) => ({
|
|
77
|
+
id: String(item.id),
|
|
78
|
+
parentId: item.parentId != null ? String(item.parentId) : "0",
|
|
79
|
+
categoryName: item.categoryName,
|
|
80
|
+
sortOrder: item.sortOrder,
|
|
81
|
+
status: item.status,
|
|
82
|
+
createTime: item.createTime,
|
|
83
|
+
createBy: item.createBy,
|
|
84
|
+
})),
|
|
85
|
+
},
|
|
86
|
+
null,
|
|
87
|
+
2
|
|
88
|
+
),
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
} catch (error) {
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: "text",
|
|
97
|
+
text: JSON.stringify(
|
|
98
|
+
{
|
|
99
|
+
success: false,
|
|
100
|
+
message: `查询合同模板分类失败: ${error.message}`,
|
|
101
|
+
},
|
|
102
|
+
null,
|
|
103
|
+
2
|
|
104
|
+
),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// ============================================================
|
|
113
|
+
// 工具 2:创建合同模板分类
|
|
114
|
+
// ============================================================
|
|
115
|
+
server.tool(
|
|
116
|
+
"create_contract_category",
|
|
117
|
+
"创建一个新的合同模板分类。可在指定父分类下创建子分类,不指定父分类则创建为顶级分类。创建成功返回新分类的完整信息(含ID)。",
|
|
118
|
+
{
|
|
119
|
+
categoryName: z
|
|
120
|
+
.string()
|
|
121
|
+
.min(1)
|
|
122
|
+
.describe("分类名称,如"销售合同"、"采购合同"、"劳动合同"等"),
|
|
123
|
+
parentId: z
|
|
124
|
+
.string()
|
|
125
|
+
.optional()
|
|
126
|
+
.describe("父分类ID(可选),不传则创建为顶级分类。可从 list_contract_categories 结果中获取"),
|
|
127
|
+
sortOrder: z
|
|
128
|
+
.number()
|
|
129
|
+
.int()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe("排序号(可选),数字越小越靠前,默认0"),
|
|
132
|
+
modelName: z
|
|
133
|
+
.string()
|
|
134
|
+
.optional()
|
|
135
|
+
.describe("创建该分类的AI模型名称(可选),用于追溯数据来源"),
|
|
136
|
+
},
|
|
137
|
+
async ({ categoryName, parentId, sortOrder, modelName }) => {
|
|
138
|
+
try {
|
|
139
|
+
const result = await createCategory({ categoryName, parentId, sortOrder, modelName });
|
|
140
|
+
const newCategory = result.data || result;
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: JSON.stringify(
|
|
147
|
+
{
|
|
148
|
+
success: true,
|
|
149
|
+
message: `合同模板分类「${categoryName}」创建成功`,
|
|
150
|
+
data: newCategory
|
|
151
|
+
? {
|
|
152
|
+
id: newCategory.id ? String(newCategory.id) : undefined,
|
|
153
|
+
categoryName: newCategory.categoryName,
|
|
154
|
+
parentId: newCategory.parentId,
|
|
155
|
+
sortOrder: newCategory.sortOrder,
|
|
156
|
+
}
|
|
157
|
+
: undefined,
|
|
158
|
+
},
|
|
159
|
+
null,
|
|
160
|
+
2
|
|
161
|
+
),
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: "text",
|
|
170
|
+
text: JSON.stringify(
|
|
171
|
+
{
|
|
172
|
+
success: false,
|
|
173
|
+
message: `创建合同模板分类失败: ${error.message}`,
|
|
174
|
+
},
|
|
175
|
+
null,
|
|
176
|
+
2
|
|
177
|
+
),
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// ============================================================
|
|
186
|
+
// 工具 3:查询合同模板列表
|
|
187
|
+
// ============================================================
|
|
188
|
+
server.tool(
|
|
189
|
+
"list_contract_templates",
|
|
190
|
+
"查询合同模板列表。支持按分类ID筛选指定分类下的模板,支持按模板名称关键词模糊搜索。返回模板基本信息及当前生效版本的文件名、版本号等。可用于回答"目前有哪些合同模板?""销售合同分类下有哪些模板?"等问题。",
|
|
191
|
+
{
|
|
192
|
+
categoryId: z
|
|
193
|
+
.string()
|
|
194
|
+
.optional()
|
|
195
|
+
.describe("分类ID(可选),筛选指定分类下的模板。可从 list_contract_categories 结果中获取"),
|
|
196
|
+
keyword: z
|
|
197
|
+
.string()
|
|
198
|
+
.optional()
|
|
199
|
+
.describe("可选,按模板名称模糊搜索,如"销售"可匹配所有名称包含"销售"的模板"),
|
|
200
|
+
},
|
|
201
|
+
async ({ categoryId, keyword }) => {
|
|
202
|
+
try {
|
|
203
|
+
const result = await listTemplates({ categoryId, keyword });
|
|
204
|
+
const templateList = Array.isArray(result.data)
|
|
205
|
+
? result.data
|
|
206
|
+
: Array.isArray(result)
|
|
207
|
+
? result
|
|
208
|
+
: [];
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
content: [
|
|
212
|
+
{
|
|
213
|
+
type: "text",
|
|
214
|
+
text: JSON.stringify(
|
|
215
|
+
{
|
|
216
|
+
success: true,
|
|
217
|
+
count: templateList.length,
|
|
218
|
+
templates: templateList.map((item) => ({
|
|
219
|
+
id: String(item.id),
|
|
220
|
+
categoryId: item.categoryId != null ? String(item.categoryId) : "",
|
|
221
|
+
categoryName: item.categoryName,
|
|
222
|
+
templateName: item.templateName,
|
|
223
|
+
description: item.description,
|
|
224
|
+
status: item.status,
|
|
225
|
+
currentVersionNo: item.currentVersionNo,
|
|
226
|
+
currentFileName: item.currentFileName,
|
|
227
|
+
currentFileSize: item.currentFileSize,
|
|
228
|
+
createTime: item.createTime,
|
|
229
|
+
createBy: item.createBy,
|
|
230
|
+
})),
|
|
231
|
+
},
|
|
232
|
+
null,
|
|
233
|
+
2
|
|
234
|
+
),
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
};
|
|
238
|
+
} catch (error) {
|
|
239
|
+
return {
|
|
240
|
+
content: [
|
|
241
|
+
{
|
|
242
|
+
type: "text",
|
|
243
|
+
text: JSON.stringify(
|
|
244
|
+
{
|
|
245
|
+
success: false,
|
|
246
|
+
message: `查询合同模板列表失败: ${error.message}`,
|
|
247
|
+
},
|
|
248
|
+
null,
|
|
249
|
+
2
|
|
250
|
+
),
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// ============================================================
|
|
259
|
+
// 工具 4:获取合同模板详情及文件下载URL
|
|
260
|
+
// ============================================================
|
|
261
|
+
server.tool(
|
|
262
|
+
"get_contract_template",
|
|
263
|
+
"获取指定合同模板的详细信息,包含当前生效版本的文件下载URL。可用于回答"我需要xxx模版,发给我一下""帮我下载xxx合同模板"等问题。返回的 currentFileUrl 即为可下载的文件地址。",
|
|
264
|
+
{
|
|
265
|
+
templateId: z
|
|
266
|
+
.string()
|
|
267
|
+
.describe("合同模板的唯一标识ID,可从 list_contract_templates 返回结果中获取"),
|
|
268
|
+
},
|
|
269
|
+
async ({ templateId }) => {
|
|
270
|
+
try {
|
|
271
|
+
const result = await getTemplateDetail(templateId);
|
|
272
|
+
const templateInfo = result.data || result;
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
content: [
|
|
276
|
+
{
|
|
277
|
+
type: "text",
|
|
278
|
+
text: JSON.stringify(
|
|
279
|
+
{
|
|
280
|
+
success: true,
|
|
281
|
+
template: templateInfo
|
|
282
|
+
? {
|
|
283
|
+
id: templateInfo.id ? String(templateInfo.id) : templateId,
|
|
284
|
+
categoryId: templateInfo.categoryId != null ? String(templateInfo.categoryId) : "",
|
|
285
|
+
categoryName: templateInfo.categoryName,
|
|
286
|
+
templateName: templateInfo.templateName,
|
|
287
|
+
description: templateInfo.description,
|
|
288
|
+
status: templateInfo.status,
|
|
289
|
+
currentVersionNo: templateInfo.currentVersionNo,
|
|
290
|
+
currentVersionId: templateInfo.currentVersionId != null ? String(templateInfo.currentVersionId) : "",
|
|
291
|
+
currentFileName: templateInfo.currentFileName,
|
|
292
|
+
currentFileSize: templateInfo.currentFileSize,
|
|
293
|
+
currentFileUrl: templateInfo.currentFileUrl,
|
|
294
|
+
createTime: templateInfo.createTime,
|
|
295
|
+
createBy: templateInfo.createBy,
|
|
296
|
+
updateTime: templateInfo.updateTime,
|
|
297
|
+
}
|
|
298
|
+
: null,
|
|
299
|
+
},
|
|
300
|
+
null,
|
|
301
|
+
2
|
|
302
|
+
),
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
};
|
|
306
|
+
} catch (error) {
|
|
307
|
+
return {
|
|
308
|
+
content: [
|
|
309
|
+
{
|
|
310
|
+
type: "text",
|
|
311
|
+
text: JSON.stringify(
|
|
312
|
+
{
|
|
313
|
+
success: false,
|
|
314
|
+
message: `获取合同模板详情失败: ${error.message}`,
|
|
315
|
+
},
|
|
316
|
+
null,
|
|
317
|
+
2
|
|
318
|
+
),
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
// ============================================================
|
|
327
|
+
// 工具 5:创建合同模板
|
|
328
|
+
// ============================================================
|
|
329
|
+
server.tool(
|
|
330
|
+
"create_contract_template",
|
|
331
|
+
"在指定分类下创建一个新的合同模板(含初始版本 v1.0)。需要先通过文件上传接口上传合同文件获取 fileId,然后将 fileId 传入此工具完成模板创建。创建成功后模板状态为"草稿"。",
|
|
332
|
+
{
|
|
333
|
+
templateName: z
|
|
334
|
+
.string()
|
|
335
|
+
.min(1)
|
|
336
|
+
.describe("模板名称,如"2025年版销售合同模板""),
|
|
337
|
+
categoryId: z
|
|
338
|
+
.string()
|
|
339
|
+
.describe("分类ID,模板所属的分类。可从 list_contract_categories 结果中获取"),
|
|
340
|
+
fileId: z
|
|
341
|
+
.string()
|
|
342
|
+
.describe("文件ID,由文件上传接口(如 /resource/nas-center/upload)返回。先上传合同文件后获得"),
|
|
343
|
+
fileName: z
|
|
344
|
+
.string()
|
|
345
|
+
.min(1)
|
|
346
|
+
.describe("上传的合同文件名,如"销售合同模板.docx""),
|
|
347
|
+
fileSize: z
|
|
348
|
+
.number()
|
|
349
|
+
.int()
|
|
350
|
+
.optional()
|
|
351
|
+
.describe("文件大小(字节),如 204800 表示200KB"),
|
|
352
|
+
description: z
|
|
353
|
+
.string()
|
|
354
|
+
.optional()
|
|
355
|
+
.describe("模板描述,说明该模板的用途、适用场景等"),
|
|
356
|
+
modelName: z
|
|
357
|
+
.string()
|
|
358
|
+
.optional()
|
|
359
|
+
.describe("创建该模板的AI模型名称(可选),用于追溯数据来源"),
|
|
360
|
+
},
|
|
361
|
+
async ({ templateName, categoryId, fileId, fileName, fileSize, description, modelName }) => {
|
|
362
|
+
try {
|
|
363
|
+
const result = await createTemplate({
|
|
364
|
+
templateName,
|
|
365
|
+
categoryId,
|
|
366
|
+
fileId,
|
|
367
|
+
fileName,
|
|
368
|
+
fileSize,
|
|
369
|
+
description,
|
|
370
|
+
modelName,
|
|
371
|
+
});
|
|
372
|
+
const newTemplate = result.data || result;
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
content: [
|
|
376
|
+
{
|
|
377
|
+
type: "text",
|
|
378
|
+
text: JSON.stringify(
|
|
379
|
+
{
|
|
380
|
+
success: true,
|
|
381
|
+
message: `合同模板「${templateName}」创建成功(v1.0 草稿状态)`,
|
|
382
|
+
data: newTemplate
|
|
383
|
+
? {
|
|
384
|
+
id: newTemplate.id ? String(newTemplate.id) : undefined,
|
|
385
|
+
templateName: newTemplate.templateName,
|
|
386
|
+
categoryId: newTemplate.categoryId != null ? String(newTemplate.categoryId) : categoryId,
|
|
387
|
+
status: newTemplate.status,
|
|
388
|
+
}
|
|
389
|
+
: undefined,
|
|
390
|
+
},
|
|
391
|
+
null,
|
|
392
|
+
2
|
|
393
|
+
),
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
};
|
|
397
|
+
} catch (error) {
|
|
398
|
+
return {
|
|
399
|
+
content: [
|
|
400
|
+
{
|
|
401
|
+
type: "text",
|
|
402
|
+
text: JSON.stringify(
|
|
403
|
+
{
|
|
404
|
+
success: false,
|
|
405
|
+
message: `创建合同模板失败: ${error.message}`,
|
|
406
|
+
},
|
|
407
|
+
null,
|
|
408
|
+
2
|
|
409
|
+
),
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// ============================================================
|
|
418
|
+
// 工具 6:上传文件并创建合同模板(一步完成)
|
|
419
|
+
// ============================================================
|
|
420
|
+
server.tool(
|
|
421
|
+
"upload_file_to_template_category",
|
|
422
|
+
[
|
|
423
|
+
"将用户选中的合同文件上传到指定模板分类下,一步完成文件上传+模板创建+版本初始化。",
|
|
424
|
+
"适用于 WorkBuddy 等智能体客户端中「选择文件 → 指定分类」的典型场景。",
|
|
425
|
+
"内部流程:1) 上传文件到 NAS/OSS 获取 fileId 2) 在指定分类下创建模板记录 3) 创建初始版本 v1.0",
|
|
426
|
+
"创建成功后返回新模板的完整信息(ID、名称、分类、状态等)。",
|
|
427
|
+
"使用示例:用户说"帮我把这个文件上传到销售合同分类下",智能体调用此工具即可一步完成。",
|
|
428
|
+
].join("\n"),
|
|
429
|
+
{
|
|
430
|
+
fileContent: z
|
|
431
|
+
.string()
|
|
432
|
+
.describe(
|
|
433
|
+
"文件的 Base64 编码内容(必填)。用户选择或拖拽的文件经 Base64 编码后的字符串。" +
|
|
434
|
+
'例如从 MCP 客户端获取到的附件内容。如果是大文件,确保使用完整的 Base64 字符串。'
|
|
435
|
+
),
|
|
436
|
+
fileName: z
|
|
437
|
+
.string()
|
|
438
|
+
.min(1)
|
|
439
|
+
.describe(
|
|
440
|
+
"原始文件名(必填),如 \"关于长春市国资国企在线监管平台上线方案_20260506_v0.1.doc\""
|
|
441
|
+
),
|
|
442
|
+
categoryId: z
|
|
443
|
+
.string()
|
|
444
|
+
.describe(
|
|
445
|
+
"目标分类ID(必填)。可先通过 list_contract_categories 查询获取,或使用分类名称让智能体匹配"
|
|
446
|
+
),
|
|
447
|
+
categoryName: z
|
|
448
|
+
.string()
|
|
449
|
+
.optional()
|
|
450
|
+
.describe("目标分类名称(可选,用于日志记录和结果展示)"),
|
|
451
|
+
templateName: z
|
|
452
|
+
.string()
|
|
453
|
+
.optional()
|
|
454
|
+
.describe("模板名称(可选,不传则使用原始文件名作为模板名)"),
|
|
455
|
+
description: z
|
|
456
|
+
.string()
|
|
457
|
+
.optional()
|
|
458
|
+
.describe("模板描述(可选),说明该模板的用途和适用场景"),
|
|
459
|
+
modelName: z
|
|
460
|
+
.string()
|
|
461
|
+
.optional()
|
|
462
|
+
.describe("创建该模板的AI模型名称(可选),用于追溯数据来源"),
|
|
463
|
+
},
|
|
464
|
+
async ({
|
|
465
|
+
fileContent,
|
|
466
|
+
fileName,
|
|
467
|
+
categoryId,
|
|
468
|
+
categoryName,
|
|
469
|
+
templateName,
|
|
470
|
+
description,
|
|
471
|
+
modelName,
|
|
472
|
+
}) => {
|
|
473
|
+
try {
|
|
474
|
+
// 将 Base64 解码为 Buffer
|
|
475
|
+
const fileData = Buffer.from(fileContent, "base64");
|
|
476
|
+
|
|
477
|
+
const result = await uploadAndCreateTemplate({
|
|
478
|
+
fileData,
|
|
479
|
+
fileName,
|
|
480
|
+
categoryId,
|
|
481
|
+
templateName,
|
|
482
|
+
description,
|
|
483
|
+
modelName,
|
|
484
|
+
});
|
|
485
|
+
const newTemplate = result.data || result;
|
|
486
|
+
|
|
487
|
+
return {
|
|
488
|
+
content: [
|
|
489
|
+
{
|
|
490
|
+
type: "text",
|
|
491
|
+
text: JSON.stringify(
|
|
492
|
+
{
|
|
493
|
+
success: true,
|
|
494
|
+
message: `文件「${fileName}」已成功上传到${categoryName || categoryId}分类下,模板创建完成(v1.0 草稿状态)`,
|
|
495
|
+
data: newTemplate
|
|
496
|
+
? {
|
|
497
|
+
id: newTemplate.id ? String(newTemplate.id) : undefined,
|
|
498
|
+
templateName: newTemplate.templateName,
|
|
499
|
+
categoryId:
|
|
500
|
+
newTemplate.categoryId != null
|
|
501
|
+
? String(newTemplate.categoryId)
|
|
502
|
+
: categoryId,
|
|
503
|
+
status: newTemplate.status,
|
|
504
|
+
}
|
|
505
|
+
: undefined,
|
|
506
|
+
},
|
|
507
|
+
null,
|
|
508
|
+
2
|
|
509
|
+
),
|
|
510
|
+
},
|
|
511
|
+
],
|
|
512
|
+
};
|
|
513
|
+
} catch (error) {
|
|
514
|
+
return {
|
|
515
|
+
content: [
|
|
516
|
+
{
|
|
517
|
+
type: "text",
|
|
518
|
+
text: JSON.stringify(
|
|
519
|
+
{
|
|
520
|
+
success: false,
|
|
521
|
+
message: `上传文件并创建模板失败: ${error.message}`,
|
|
522
|
+
},
|
|
523
|
+
null,
|
|
524
|
+
2
|
|
525
|
+
),
|
|
526
|
+
},
|
|
527
|
+
],
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
// ============================================================
|
|
534
|
+
// 启动 MCP Server(使用 stdio 传输)
|
|
535
|
+
// ============================================================
|
|
536
|
+
async function main() {
|
|
537
|
+
const transport = new StdioServerTransport();
|
|
538
|
+
await server.connect(transport);
|
|
539
|
+
|
|
540
|
+
// 输出到 stderr(stdio 模式下 stdout 用于 MCP 协议通信)
|
|
541
|
+
console.error("✅ CRM 合同模版库管理 MCP Server 已启动");
|
|
542
|
+
console.error(` API 地址: ${process.env.CONTRACT_TEMPLATE_API_BASE_URL || "http://localhost:3000"}`);
|
|
543
|
+
if (hasAuth()) {
|
|
544
|
+
console.error(" 认证状态: ✅ 已配置 CRM_MCP_ACCESS_KEY");
|
|
545
|
+
} else {
|
|
546
|
+
console.error(" 认证状态: ⚠️ 未配置 CRM_MCP_ACCESS_KEY,请求可能被后端拒绝");
|
|
547
|
+
}
|
|
548
|
+
console.error(" 提供工具: list_contract_categories, create_contract_category, list_contract_templates, get_contract_template, create_contract_template, upload_file_to_template_category");
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
main().catch((error) => {
|
|
552
|
+
console.error("❌ MCP Server 启动失败:", error);
|
|
553
|
+
process.exit(1);
|
|
554
|
+
});
|