contract-template-mcp-server 1.0.0 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +24 -4
  2. package/package.json +1 -1
  3. package/src/index.js +30 -22
package/README.md CHANGED
@@ -11,6 +11,7 @@ CRM 合同模版库管理 MCP Server,为智能体(如 CodeBuddy/WorkBuddy)
11
11
  | `list_contract_templates` | 查询合同模板列表(支持分类/关键词筛选) | `GET /mcp/contract/template/list` |
12
12
  | `get_contract_template` | 获取模板详情及文件下载URL | `GET /mcp/contract/template/detail` |
13
13
  | `create_contract_template` | 在分类下创建新模板(含 v1.0 初版) | `POST /mcp/contract/template/create` |
14
+ | `upload_file_to_template_category` | 上传文件并一步创建模板(推荐) | `POST /mcp/contract/template/upload-and-create` |
14
15
 
15
16
  ## 安装与配置
16
17
 
@@ -88,6 +89,18 @@ AI: ① 先调用文件上传接口 /resource/nas-center/upload 上传文件,
88
89
  → 模板创建成功(v1.0 草稿状态)
89
90
  ```
90
91
 
92
+ ### 上传文件一步创建模板(推荐)
93
+
94
+ ```
95
+ 用户: 帮我把这个文件上传到销售合同分类下
96
+ AI: 调用 upload_file_to_template_category(filePath="/path/to/file.docx", categoryId=销售合同分类ID)
97
+ → 内部自动完成:文件读取 + 上传 NAS/OSS + 模板创建 + 版本初始化
98
+ → 模板创建成功(v1.0 草稿状态)
99
+ ```
100
+
101
+ > **性能说明**:MCP Server 与 WorkBuddy 同机运行,`upload_file_to_template_category` 直接通过 `filePath`
102
+ > 读取本地文件,无需客户端做 base64 编码,大文件上传速度大幅提升。
103
+
91
104
  ## 数据来源标记
92
105
 
93
106
  所有通过 MCP Server 创建的分类和模板,数据库 `data_source` 字段会自动标记为 `MCP_SERVER`,
@@ -95,11 +108,18 @@ AI: ① 先调用文件上传接口 /resource/nas-center/upload 上传文件,
95
108
 
96
109
  ## 文件上传说明
97
110
 
98
- 创建模板需要先上传文件获取 `fileId`。文件可通过以下方式上传:
99
- - **NAS 上传**:`POST /resource/nas-center/upload`(multipart/form-data)
100
- - **OSS 上传**:`POST /resource/oss/upload`(multipart/form-data)
111
+ ### 方式一:`upload_file_to_template_category`(推荐)
112
+
113
+ 直接传文件本地路径,MCP Server 自动完成文件读取、上传和模板创建:
114
+ - 参数 `filePath`:本地文件的绝对路径
115
+ - 参数 `fileName`:可选,不传则从路径自动提取
116
+
117
+ ### 方式二:手动分步上传
101
118
 
102
- 上传成功后返回的 `fileId`(或 `attachmentId`)即为创建模板时需要的参数。
119
+ 1. 先上传文件获取 `fileId`:
120
+ - **NAS 上传**:`POST /resource/nas-center/upload`(multipart/form-data)
121
+ - **OSS 上传**:`POST /resource/oss/upload`(multipart/form-data)
122
+ 2. 使用 `create_contract_template` 传入 `fileId` 创建模板
103
123
 
104
124
  ## 技术架构
105
125
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contract-template-mcp-server",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "MCP Server - CRM合同模版库管理,支持合同模版分类管理、合同模版管理、合同模版版本管理",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -28,6 +28,7 @@
28
28
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
29
29
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
30
30
  import { z } from "zod";
31
+ import fs from "fs";
31
32
  import {
32
33
  listCategories,
33
34
  createCategory,
@@ -41,7 +42,7 @@ import {
41
42
  // 创建 MCP Server 实例
42
43
  const server = new McpServer({
43
44
  name: "contract-template-mcp-server",
44
- version: "1.0.0",
45
+ version: "1.1.0",
45
46
  });
46
47
 
47
48
  // ============================================================
@@ -49,12 +50,12 @@ const server = new McpServer({
49
50
  // ============================================================
50
51
  server.tool(
51
52
  "list_contract_categories",
52
- "查询合同模板的分类列表。返回系统中所有的合同模板分类(平铺列表形式),支持按分类名称关键词模糊搜索。可用于回答"目前有哪些合同模板分类?""帮我列一下分类"等问题。",
53
+ "查询合同模板的分类列表。返回系统中所有的合同模板分类(平铺列表形式),支持按分类名称关键词模糊搜索。可用于回答\u201C目前有哪些合同模板分类?\u201D\u201C帮我列一下分类\u201D等问题。",
53
54
  {
54
55
  keyword: z
55
56
  .string()
56
57
  .optional()
57
- .describe("可选,按分类名称模糊搜索,如"劳动合同"可匹配到"劳动合同模板分类""),
58
+ .describe("可选,按分类名称模糊搜索,如\u201C劳动合同\u201D可匹配到\u201C劳动合同模板分类\u201D"),
58
59
  },
59
60
  async ({ keyword }) => {
60
61
  try {
@@ -119,7 +120,7 @@ server.tool(
119
120
  categoryName: z
120
121
  .string()
121
122
  .min(1)
122
- .describe("分类名称,如"销售合同"、"采购合同"、"劳动合同"等"),
123
+ .describe("分类名称,如\u201C销售合同\u201D、\u201C采购合同\u201D、\u201C劳动合同\u201D等"),
123
124
  parentId: z
124
125
  .string()
125
126
  .optional()
@@ -187,7 +188,7 @@ server.tool(
187
188
  // ============================================================
188
189
  server.tool(
189
190
  "list_contract_templates",
190
- "查询合同模板列表。支持按分类ID筛选指定分类下的模板,支持按模板名称关键词模糊搜索。返回模板基本信息及当前生效版本的文件名、版本号等。可用于回答"目前有哪些合同模板?""销售合同分类下有哪些模板?"等问题。",
191
+ "查询合同模板列表。支持按分类ID筛选指定分类下的模板,支持按模板名称关键词模糊搜索。返回模板基本信息及当前生效版本的文件名、版本号等。可用于回答\u201C目前有哪些合同模板?\u201D\u201C销售合同分类下有哪些模板?\u201D等问题。",
191
192
  {
192
193
  categoryId: z
193
194
  .string()
@@ -196,7 +197,7 @@ server.tool(
196
197
  keyword: z
197
198
  .string()
198
199
  .optional()
199
- .describe("可选,按模板名称模糊搜索,如"销售"可匹配所有名称包含"销售"的模板"),
200
+ .describe("可选,按模板名称模糊搜索,如\u201C销售\u201D可匹配所有名称包含\u201C销售\u201D的模板"),
200
201
  },
201
202
  async ({ categoryId, keyword }) => {
202
203
  try {
@@ -260,7 +261,7 @@ server.tool(
260
261
  // ============================================================
261
262
  server.tool(
262
263
  "get_contract_template",
263
- "获取指定合同模板的详细信息,包含当前生效版本的文件下载URL。可用于回答"我需要xxx模版,发给我一下""帮我下载xxx合同模板"等问题。返回的 currentFileUrl 即为可下载的文件地址。",
264
+ "获取指定合同模板的详细信息,包含当前生效版本的文件下载URL。可用于回答\u201C我需要xxx模版,发给我一下\u201D\u201C帮我下载xxx合同模板\u201D等问题。返回的 currentFileUrl 即为可下载的文件地址。",
264
265
  {
265
266
  templateId: z
266
267
  .string()
@@ -328,12 +329,12 @@ server.tool(
328
329
  // ============================================================
329
330
  server.tool(
330
331
  "create_contract_template",
331
- "在指定分类下创建一个新的合同模板(含初始版本 v1.0)。需要先通过文件上传接口上传合同文件获取 fileId,然后将 fileId 传入此工具完成模板创建。创建成功后模板状态为"草稿"。",
332
+ "在指定分类下创建一个新的合同模板(含初始版本 v1.0)。需要先通过文件上传接口上传合同文件获取 fileId,然后将 fileId 传入此工具完成模板创建。创建成功后模板状态为\u201C草稿\u201D。",
332
333
  {
333
334
  templateName: z
334
335
  .string()
335
336
  .min(1)
336
- .describe("模板名称,如"2025年版销售合同模板""),
337
+ .describe("模板名称,如\u201C2025年版销售合同模板\u201D"),
337
338
  categoryId: z
338
339
  .string()
339
340
  .describe("分类ID,模板所属的分类。可从 list_contract_categories 结果中获取"),
@@ -343,7 +344,7 @@ server.tool(
343
344
  fileName: z
344
345
  .string()
345
346
  .min(1)
346
- .describe("上传的合同文件名,如"销售合同模板.docx""),
347
+ .describe("上传的合同文件名,如\u201C销售合同模板.docx\u201D"),
347
348
  fileSize: z
348
349
  .number()
349
350
  .int()
@@ -424,20 +425,21 @@ server.tool(
424
425
  "适用于 WorkBuddy 等智能体客户端中「选择文件 → 指定分类」的典型场景。",
425
426
  "内部流程:1) 上传文件到 NAS/OSS 获取 fileId 2) 在指定分类下创建模板记录 3) 创建初始版本 v1.0",
426
427
  "创建成功后返回新模板的完整信息(ID、名称、分类、状态等)。",
427
- "使用示例:用户说"帮我把这个文件上传到销售合同分类下",智能体调用此工具即可一步完成。",
428
+ "使用示例:用户说\u201C帮我把这个文件上传到销售合同分类下\u201D,智能体调用此工具即可一步完成。",
428
429
  ].join("\n"),
429
430
  {
430
- fileContent: z
431
+ filePath: z
431
432
  .string()
432
433
  .describe(
433
- "文件的 Base64 编码内容(必填)。用户选择或拖拽的文件经 Base64 编码后的字符串。" +
434
- '例如从 MCP 客户端获取到的附件内容。如果是大文件,确保使用完整的 Base64 字符串。'
434
+ "文件的本地绝对路径(必填)。由于 MCP Server WorkBuddy 运行在同一台机器上," +
435
+ "直接传文件路径即可,服务端会自动读取文件内容,无需客户端做 base64 编码。" +
436
+ "例如:\"/Users/user/Desktop/销售合同模板.docx\""
435
437
  ),
436
438
  fileName: z
437
439
  .string()
438
- .min(1)
440
+ .optional()
439
441
  .describe(
440
- "原始文件名(必填),如 \"关于长春市国资国企在线监管平台上线方案_20260506_v0.1.doc\""
442
+ "文件名(可选)。不传则从 filePath 中自动提取文件名"
441
443
  ),
442
444
  categoryId: z
443
445
  .string()
@@ -462,7 +464,7 @@ server.tool(
462
464
  .describe("创建该模板的AI模型名称(可选),用于追溯数据来源"),
463
465
  },
464
466
  async ({
465
- fileContent,
467
+ filePath,
466
468
  fileName,
467
469
  categoryId,
468
470
  categoryName,
@@ -471,12 +473,13 @@ server.tool(
471
473
  modelName,
472
474
  }) => {
473
475
  try {
474
- // Base64 解码为 Buffer
475
- const fileData = Buffer.from(fileContent, "base64");
476
+ // 直接读取本地文件(MCP Server WorkBuddy 同机运行,无需 base64 编码)
477
+ const actualFileName = fileName || filePath.split("/").pop() || "unknown";
478
+ const fileData = fs.readFileSync(filePath);
476
479
 
477
480
  const result = await uploadAndCreateTemplate({
478
481
  fileData,
479
- fileName,
482
+ fileName: actualFileName,
480
483
  categoryId,
481
484
  templateName,
482
485
  description,
@@ -491,7 +494,7 @@ server.tool(
491
494
  text: JSON.stringify(
492
495
  {
493
496
  success: true,
494
- message: `文件「${fileName}」已成功上传到${categoryName || categoryId}分类下,模板创建完成(v1.0 草稿状态)`,
497
+ message: `文件「${actualFileName}」已成功上传到${categoryName || categoryId}分类下,模板创建完成(v1.0 草稿状态)`,
495
498
  data: newTemplate
496
499
  ? {
497
500
  id: newTemplate.id ? String(newTemplate.id) : undefined,
@@ -511,6 +514,11 @@ server.tool(
511
514
  ],
512
515
  };
513
516
  } catch (error) {
517
+ let errorMessage = error.message;
518
+ // 提供更友好的文件不存在错误提示
519
+ if (error.code === "ENOENT") {
520
+ errorMessage = `文件不存在或无法访问: ${filePath}`;
521
+ }
514
522
  return {
515
523
  content: [
516
524
  {
@@ -518,7 +526,7 @@ server.tool(
518
526
  text: JSON.stringify(
519
527
  {
520
528
  success: false,
521
- message: `上传文件并创建模板失败: ${error.message}`,
529
+ message: `上传文件并创建模板失败: ${errorMessage}`,
522
530
  },
523
531
  null,
524
532
  2