@maxenlin/mcp-zentao-11-3 1.0.0-patch.1 → 1.0.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @maxenlin/mcp-zentao-11-3
2
2
 
3
- 禅道 11.3 Legacy 版 MCP 服务器,专为 Cursor IDE 设计,只支持旧版 Session API。
3
+ 禅道 11.3 Legacy 版 MCP 服务器,支持所有兼容 MCP 协议的 IDE 和工具(如 Cursor IDE、Claude Desktop、Continue 等),只支持旧版 Session API。
4
4
 
5
5
  ## ✨ 特性
6
6
 
@@ -15,33 +15,13 @@
15
15
 
16
16
  ## 📦 安装
17
17
 
18
- ### 方法 1:使用 npx(推荐)
19
-
20
- 在 Cursor IDE 配置文件中添加:
21
-
22
- ```json
23
- {
24
- "mcpServers": {
25
- "zentao-11-3": {
26
- "command": "npx",
27
- "args": ["-y", "@maxenlin/mcp-zentao-11-3"],
28
- "env": {
29
- "ZENTAO_URL": "http://your-zentao-url/zentao",
30
- "ZENTAO_USERNAME": "your-username",
31
- "ZENTAO_PASSWORD": "your-password"
32
- }
33
- }
34
- }
35
- }
36
- ```
37
-
38
- ### 方法 2:本地安装
18
+ ### 方法 1:本地安装
39
19
 
40
20
  ```bash
41
21
  npm install -g @maxenlin/mcp-zentao-11-3
42
22
  ```
43
23
 
44
- 然后在 Cursor IDE 配置文件中添加:
24
+ 然后在支持 MCP 的 IDE/工具配置文件中添加(以 Cursor IDE 为例):
45
25
 
46
26
  ```json
47
27
  {
@@ -59,18 +39,21 @@ npm install -g @maxenlin/mcp-zentao-11-3
59
39
  }
60
40
  ```
61
41
 
62
- ## ⚙️ 配置
42
+ **配置说明:**
43
+ - `ZENTAO_URL`: 禅道服务器地址(必须包含 `/zentao` 路径)
44
+ - `ZENTAO_USERNAME`: 禅道用户名
45
+ - `ZENTAO_PASSWORD`: 禅道密码
63
46
 
64
- ### 方法 1:在 Cursor 配置中直接设置(推荐)
47
+ ### 方法 2:使用 npx
65
48
 
66
- Cursor IDE MCP 配置文件中,直接在 `env` 字段中填入配置:
49
+ 在支持 MCPIDE/工具配置文件中添加(以 Cursor IDE 为例):
67
50
 
68
51
  ```json
69
52
  {
70
53
  "mcpServers": {
71
54
  "zentao-11-3": {
72
- "command": "mcp-zentao-11-3",
73
- "args": [],
55
+ "command": "npx",
56
+ "args": ["-y", "@maxenlin/mcp-zentao-11-3"],
74
57
  "env": {
75
58
  "ZENTAO_URL": "http://your-zentao-url/zentao",
76
59
  "ZENTAO_USERNAME": "your-username",
@@ -86,36 +69,9 @@ npm install -g @maxenlin/mcp-zentao-11-3
86
69
  - `ZENTAO_USERNAME`: 禅道用户名
87
70
  - `ZENTAO_PASSWORD`: 禅道密码
88
71
 
89
- ### 方法 2:使用配置文件(备选)
90
-
91
- 如果不想在 Cursor 配置中直接填写密码,可以创建配置文件:
92
-
93
- **Windows:**
94
- ```
95
- C:\Users\你的用户名\.zentao\config.json
96
- ```
97
-
98
- **macOS/Linux:**
99
- ```
100
- ~/.zentao/config.json
101
- ```
102
-
103
- 配置文件内容:
104
-
105
- ```json
106
- {
107
- "url": "http://your-zentao-url/zentao",
108
- "username": "your-username",
109
- "password": "your-password",
110
- "apiVersion": "legacy"
111
- }
112
- ```
113
-
114
- **注意:** 环境变量配置优先于配置文件。如果同时设置了环境变量和配置文件,将使用环境变量中的配置。
115
-
116
72
  ## 🚀 使用
117
73
 
118
- 配置完成后,重启 Cursor IDE 即可使用。
74
+ 配置完成后,重启您的 IDE/工具即可使用。
119
75
 
120
76
  ### 基础功能
121
77
 
@@ -176,70 +132,84 @@ C:\Users\你的用户名\.zentao\config.json
176
132
  ## 📋 可用工具
177
133
 
178
134
  ### 配置管理
135
+
179
136
  - `initZentao` - 初始化禅道连接
180
137
  - `getConfig` - 查看配置信息
181
138
 
182
139
  ### 任务管理
140
+
183
141
  - `getMyTasks` - 获取我的任务列表
184
142
  - `getTaskDetail` - 获取任务详情
185
143
  - `updateTask` - 更新任务
186
144
  - `finishTask` - 完成任务
187
145
 
188
146
  ### Bug 管理
147
+
189
148
  - `getMyBugs` - 获取我的Bug列表
190
149
  - `getBugDetail` - 获取Bug详情
191
150
  - `resolveBug` - 解决Bug
192
151
 
193
152
  ### 产品管理
153
+
194
154
  - `getProducts` - 获取产品列表
195
155
 
196
156
  ### 需求管理
157
+
197
158
  - `getProductStories` - 获取产品的需求列表
198
159
  - `getStoryDetail` - 获取需求详情
199
160
  - `searchStories` - 搜索需求
200
161
  - `searchStoriesByProductName` - 按产品名称搜索需求
201
162
 
202
163
  ### 测试用例管理
164
+
203
165
  - `getProductTestCases` - 获取产品的测试用例
204
166
  - `getTestCaseDetail` - 获取测试用例详情
205
167
  - `createTestCase` - 创建测试用例
206
168
  - `getStoryTestCases` - 获取需求的测试用例
207
169
 
208
170
  ### 测试单管理
171
+
209
172
  - `getTestTasks` - 获取测试单列表
210
173
  - `getTestTaskDetail` - 获取测试单详情
211
174
  - `getTestTaskResults` - 获取测试单的测试结果
212
175
  - `runTestCase` - 执行测试用例
213
176
 
214
177
  ### 关联关系查询
178
+
215
179
  - `getStoryRelatedBugs` - 获取需求关联的 Bug 列表
216
180
  - `getBugRelatedStory` - 获取 Bug 关联的需求
217
181
 
218
182
  ### 批量操作
183
+
219
184
  - `batchUpdateTasks` - 批量更新任务
220
185
  - `batchResolveBugs` - 批量解决 Bug
221
186
 
222
187
  ### 数据统计
188
+
223
189
  - `getMyTaskStatistics` - 获取我的任务统计信息
224
190
  - `getMyBugStatistics` - 获取我的 Bug 统计信息
225
191
 
226
192
  ### AI 编程辅助功能
193
+
227
194
  - `getDevelopmentContext` - 获取需求/Bug 的完整开发上下文(包含关联信息)
228
195
  - `generateStorySummary` - 生成需求摘要(支持 JSON/Markdown/文本格式)
229
196
  - `generateBugSummary` - 生成 Bug 摘要(支持 JSON/Markdown/文本格式)
230
197
  - `formatTaskAsMarkdown` - 将任务格式化为 Markdown
231
198
 
232
199
  ### 智能分析功能
200
+
233
201
  - `analyzeStoryComplexity` - 分析需求复杂度(评分、工时估算、优先级建议)
234
202
  - `analyzeBugPriority` - 分析 Bug 优先级(评分、优先级建议)
235
203
  - `analyzeTaskWorkload` - 分析任务工作量(工时估算、难度评估)
236
204
 
237
205
  ### 代码生成提示
206
+
238
207
  - `generateCodePromptFromStory` - 根据需求生成代码框架提示
239
208
  - `generateTestPromptFromBug` - 根据 Bug 生成测试用例提示
240
209
  - `generateCodeReviewChecklist` - 生成代码审查检查清单
241
210
 
242
211
  ### 根据需求/Bug创建任务
212
+
243
213
  - `createTaskFromStory` - 根据需求创建任务(提供手动操作指南)
244
214
  - `createTaskFromBug` - 根据Bug创建修复任务(提供手动操作指南)
245
215
 
@@ -251,4 +221,3 @@ MIT
251
221
 
252
222
  - [禅道开源版 GitHub](https://github.com/easysoft/zentaopms) - 禅道官方 GitHub 仓库
253
223
  - [禅道官网](https://www.zentao.net/)
254
-
package/dist/index.js CHANGED
@@ -9,6 +9,66 @@ import { ZentaoError, ErrorCode, createError } from './errors.js';
9
9
  import { formatStoryAsMarkdown, formatBugAsMarkdown, formatTaskAsMarkdown, generateStorySummary, generateBugSummary } from './utils/formatter.js';
10
10
  import { analyzeStoryComplexity, analyzeBugPriority, analyzeTaskWorkload } from './utils/analyzer.js';
11
11
  import { suggestNextActionsForStory, suggestNextActionsForBug, suggestNextActionsForTask, formatSuggestionsAsMarkdown } from './utils/suggestions.js';
12
+ /**
13
+ * 解析自然语言时间表达式
14
+ * 支持:今年、今年1月、最近3个月、今天、昨天、上个月等
15
+ */
16
+ function parseNaturalDate(dateStr) {
17
+ if (!dateStr) {
18
+ return undefined;
19
+ }
20
+ // 如果已经是标准格式(YYYY-MM-DD),直接返回
21
+ if (/^\d{4}-\d{2}-\d{2}/.test(dateStr)) {
22
+ return dateStr;
23
+ }
24
+ const now = new Date();
25
+ const year = now.getFullYear();
26
+ const month = now.getMonth(); // 0-11
27
+ const date = now.getDate();
28
+ const lowerStr = dateStr.toLowerCase().trim();
29
+ // 今年
30
+ if (lowerStr === '今年' || lowerStr === 'this year') {
31
+ return `${year}-01-01`;
32
+ }
33
+ // 今年X月
34
+ const monthMatch = lowerStr.match(/今年(\d+)月/);
35
+ if (monthMatch) {
36
+ const m = parseInt(monthMatch[1]);
37
+ if (m >= 1 && m <= 12) {
38
+ return `${year}-${String(m).padStart(2, '0')}-01`;
39
+ }
40
+ }
41
+ // 最近N个月
42
+ const monthsMatch = lowerStr.match(/最近(\d+)个月/);
43
+ if (monthsMatch) {
44
+ const months = parseInt(monthsMatch[1]);
45
+ const targetDate = new Date(year, month - months, date);
46
+ return targetDate.toISOString().split('T')[0];
47
+ }
48
+ // 最近N天
49
+ const daysMatch = lowerStr.match(/最近(\d+)天/);
50
+ if (daysMatch) {
51
+ const days = parseInt(daysMatch[1]);
52
+ const targetDate = new Date(year, month, date - days);
53
+ return targetDate.toISOString().split('T')[0];
54
+ }
55
+ // 今天
56
+ if (lowerStr === '今天' || lowerStr === 'today') {
57
+ return `${year}-${String(month + 1).padStart(2, '0')}-${String(date).padStart(2, '0')}`;
58
+ }
59
+ // 昨天
60
+ if (lowerStr === '昨天' || lowerStr === 'yesterday') {
61
+ const yesterday = new Date(year, month, date - 1);
62
+ return yesterday.toISOString().split('T')[0];
63
+ }
64
+ // 上个月
65
+ if (lowerStr === '上个月' || lowerStr === 'last month') {
66
+ const lastMonth = new Date(year, month - 1, 1);
67
+ return lastMonth.toISOString().split('T')[0];
68
+ }
69
+ // 如果无法解析,返回原字符串(让后续的日期解析函数处理)
70
+ return dateStr;
71
+ }
12
72
  // Create an MCP server
13
73
  const server = new McpServer({
14
74
  name: "Zentao 11.3 Legacy API",
@@ -379,17 +439,26 @@ server.tool("getStoryDetail", {
379
439
  });
380
440
  // Add searchStories tool
381
441
  server.tool("searchStories", {
382
- keyword: z.string(),
383
- productId: z.number().optional(),
384
- status: z.enum(['draft', 'active', 'closed', 'changed', 'all']).optional(),
385
- limit: z.number().optional().default(20)
386
- }, async ({ keyword, productId, status, limit = 20 }) => {
442
+ keyword: z.string().describe("搜索关键字,支持中英文,会在需求标题和描述中搜索。例如:'大R促活'、'音视频'、'每日任务'等"),
443
+ productId: z.number().optional().describe("产品ID(可选),如果指定则只搜索该产品的需求"),
444
+ status: z.enum(['draft', 'active', 'closed', 'changed', 'all']).optional().describe("需求状态(可选):draft(草稿)、active(激活)、closed(已关闭)、changed(已变更)、all(全部)"),
445
+ limit: z.number().optional().default(20).describe("返回结果数量限制(默认20条)"),
446
+ deepSearch: z.boolean().optional().default(false).describe("是否启用深度搜索(获取需求详情以获取完整描述,速度较慢但结果更准确)"),
447
+ startDate: z.string().optional().describe("开始时间(可选),格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss。用于过滤需求的创建时间,例如:'2024-01-01' 表示2024年1月1日之后的需求。支持自然语言理解:'今年'、'今年1月'、'最近3个月'等"),
448
+ endDate: z.string().optional().describe("结束时间(可选),格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss。用于过滤需求的创建时间,例如:'2024-12-31' 表示2024年12月31日之前的需求。支持自然语言理解:'今年'、'今年12月'、'今天'等")
449
+ }, async ({ keyword, productId, status, limit = 20, deepSearch = false, startDate, endDate }) => {
387
450
  await ensureInitialized();
388
451
  try {
452
+ // 解析自然语言时间表达式(如果 AI 传递的是自然语言)
453
+ const parsedStartDate = startDate ? parseNaturalDate(startDate) : undefined;
454
+ const parsedEndDate = endDate ? parseNaturalDate(endDate) : undefined;
389
455
  const stories = await zentaoApi.searchStories(keyword, {
390
456
  productId,
391
457
  status: status,
392
- limit
458
+ limit,
459
+ deepSearch,
460
+ startDate: parsedStartDate || startDate,
461
+ endDate: parsedEndDate || endDate
393
462
  });
394
463
  return {
395
464
  content: [{ type: "text", text: JSON.stringify(stories, null, 2) }]
@@ -90,12 +90,55 @@ export declare class ZentaoLegacyAPI {
90
90
  /**
91
91
  * 搜索需求(通过关键字)
92
92
  * 由于禅道11.3的搜索API权限限制,我们通过获取所有产品的需求然后本地过滤
93
+ *
94
+ * 搜索范围:
95
+ * - 如果指定 productId:搜索该产品的所有需求(全量)
96
+ * - 如果未指定 productId:搜索所有产品的所有需求(全量)
97
+ *
98
+ * 优化:
99
+ * 1. 支持分词搜索(将关键字拆分为多个词进行匹配)
100
+ * 2. 增强匹配逻辑(标题、描述、模块名、产品名)
101
+ * 3. 智能排序(匹配度评分:标题完全匹配 > 标题包含 > 描述匹配 > 其他字段匹配)
102
+ * 4. 如果列表接口的spec不完整,对标题匹配的需求进行深度搜索(获取详情)
103
+ * 5. 支持时间范围过滤(按创建时间 openedDate)
93
104
  */
94
105
  searchStories(keyword: string, options?: {
95
106
  productId?: number;
96
107
  status?: StoryStatus;
97
108
  limit?: number;
109
+ deepSearch?: boolean;
110
+ startDate?: string;
111
+ endDate?: string;
98
112
  }): Promise<Story[]>;
113
+ /**
114
+ * 分词:将关键字拆分为多个词
115
+ * 支持中英文混合,中文按字符拆分,英文按单词拆分
116
+ */
117
+ private splitKeywords;
118
+ /**
119
+ * 按时间范围过滤需求
120
+ * @param stories 需求列表
121
+ * @param startDate 开始时间(可选,格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss)
122
+ * @param endDate 结束时间(可选,格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss)
123
+ * @returns 过滤后的需求列表
124
+ */
125
+ private filterByDateRange;
126
+ /**
127
+ * 解析日期字符串
128
+ * 支持格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss
129
+ */
130
+ private parseDate;
131
+ /**
132
+ * 计算匹配度评分
133
+ * 评分规则:
134
+ * - 标题完全匹配:100分
135
+ * - 标题包含关键字:80分
136
+ * - 标题包含部分关键字(分词匹配):60分
137
+ * - 描述包含关键字:40分
138
+ * - 描述包含部分关键字:20分
139
+ * - 模块名/产品名匹配:10分
140
+ */
141
+ private calculateMatchScore;
99
142
  /**
100
143
  * 按产品名称搜索需求
101
144
  */
@@ -450,9 +450,20 @@ export class ZentaoLegacyAPI {
450
450
  /**
451
451
  * 搜索需求(通过关键字)
452
452
  * 由于禅道11.3的搜索API权限限制,我们通过获取所有产品的需求然后本地过滤
453
+ *
454
+ * 搜索范围:
455
+ * - 如果指定 productId:搜索该产品的所有需求(全量)
456
+ * - 如果未指定 productId:搜索所有产品的所有需求(全量)
457
+ *
458
+ * 优化:
459
+ * 1. 支持分词搜索(将关键字拆分为多个词进行匹配)
460
+ * 2. 增强匹配逻辑(标题、描述、模块名、产品名)
461
+ * 3. 智能排序(匹配度评分:标题完全匹配 > 标题包含 > 描述匹配 > 其他字段匹配)
462
+ * 4. 如果列表接口的spec不完整,对标题匹配的需求进行深度搜索(获取详情)
463
+ * 5. 支持时间范围过滤(按创建时间 openedDate)
453
464
  */
454
465
  async searchStories(keyword, options) {
455
- const { productId, status, limit = 50 } = options || {};
466
+ const { productId, status, limit = 50, deepSearch = false, startDate, endDate } = options || {};
456
467
  try {
457
468
  let allStories = [];
458
469
  if (productId) {
@@ -460,47 +471,190 @@ export class ZentaoLegacyAPI {
460
471
  allStories = await this.getProductStories(productId, status);
461
472
  }
462
473
  else {
463
- // 搜索所有产品的需求
474
+ // 搜索所有产品的需求(全量搜索)
464
475
  const products = await this.getProducts();
465
- // 限制搜索范围,避免请求过多
466
- const searchProducts = products.slice(0, 20); // 只搜索前20个产品
467
- for (const product of searchProducts) {
476
+ // 全量搜索:遍历所有产品
477
+ for (const product of products) {
468
478
  try {
469
479
  const stories = await this.getProductStories(product.id, status);
470
480
  allStories.push(...stories);
471
481
  }
472
482
  catch (error) {
483
+ // 某个产品获取失败,继续处理其他产品
484
+ console.warn(`获取产品 ${product.id} (${product.name}) 的需求失败:`, error);
473
485
  continue;
474
486
  }
475
487
  }
476
488
  }
477
- // 本地过滤:按关键字搜索
489
+ // 时间范围过滤(如果指定了时间范围,先过滤再搜索,提高性能)
490
+ if (startDate || endDate) {
491
+ allStories = this.filterByDateRange(allStories, startDate, endDate);
492
+ }
493
+ // 分词:将关键字拆分为多个词(支持中英文)
494
+ const keywords = this.splitKeywords(keyword);
478
495
  const keyword_lower = keyword.toLowerCase();
479
- const matchedStories = allStories.filter(story => {
480
- // 在标题、描述中搜索关键字
481
- const titleMatch = story.title.toLowerCase().includes(keyword_lower);
482
- const specMatch = story.spec && story.spec.toLowerCase().includes(keyword_lower);
483
- return titleMatch || specMatch;
484
- });
485
- // 按相关性排序(标题匹配优先)
486
- matchedStories.sort((a, b) => {
487
- const aTitle = a.title.toLowerCase().includes(keyword_lower);
488
- const bTitle = b.title.toLowerCase().includes(keyword_lower);
489
- if (aTitle && !bTitle)
490
- return -1;
491
- if (!aTitle && bTitle)
492
- return 1;
493
- // 都匹配标题或都不匹配标题,按ID倒序(新的在前)
494
- return b.id - a.id;
496
+ // 计算匹配度评分
497
+ const scoredStories = allStories.map(story => {
498
+ const score = this.calculateMatchScore(story, keyword_lower, keywords);
499
+ return { story, score };
500
+ }).filter(item => item.score > 0); // 只保留有匹配的
501
+ // 如果启用深度搜索,对标题匹配但描述可能不完整的需求获取详情
502
+ if (deepSearch) {
503
+ const titleMatchedButLowScore = scoredStories
504
+ .filter(item => {
505
+ const titleMatch = item.story.title.toLowerCase().includes(keyword_lower);
506
+ const specMatch = item.story.spec && item.story.spec.toLowerCase().includes(keyword_lower);
507
+ return titleMatch && !specMatch && item.score < 50; // 标题匹配但描述不匹配,且评分较低
508
+ })
509
+ .slice(0, 10); // 最多深度搜索10个
510
+ for (const item of titleMatchedButLowScore) {
511
+ try {
512
+ const detail = await this.getStoryDetail(item.story.id);
513
+ // 使用完整描述重新计算评分
514
+ const newScore = this.calculateMatchScore(detail, keyword_lower, keywords);
515
+ if (newScore > item.score) {
516
+ item.story = detail;
517
+ item.score = newScore;
518
+ }
519
+ }
520
+ catch (error) {
521
+ // 忽略获取详情失败的情况
522
+ continue;
523
+ }
524
+ }
525
+ }
526
+ // 按评分降序排序
527
+ scoredStories.sort((a, b) => {
528
+ if (b.score !== a.score) {
529
+ return b.score - a.score;
530
+ }
531
+ // 评分相同,按ID倒序(新的在前)
532
+ return b.story.id - a.story.id;
495
533
  });
496
534
  // 限制返回数量
497
- return matchedStories.slice(0, limit);
535
+ return scoredStories.slice(0, limit).map(item => item.story);
498
536
  }
499
537
  catch (error) {
500
538
  console.error('搜索需求失败:', error);
501
539
  throw new Error(`搜索需求失败: ${error instanceof Error ? error.message : String(error)}`);
502
540
  }
503
541
  }
542
+ /**
543
+ * 分词:将关键字拆分为多个词
544
+ * 支持中英文混合,中文按字符拆分,英文按单词拆分
545
+ */
546
+ splitKeywords(keyword) {
547
+ const keywords = [];
548
+ const lowerKeyword = keyword.toLowerCase();
549
+ // 英文单词(字母、数字、连字符)
550
+ const englishWords = lowerKeyword.match(/[a-z0-9]+(?:-[a-z0-9]+)*/g) || [];
551
+ keywords.push(...englishWords);
552
+ // 中文字符(每个字符作为一个词)
553
+ const chineseChars = lowerKeyword.match(/[\u4e00-\u9fa5]/g) || [];
554
+ keywords.push(...chineseChars);
555
+ // 如果分词后没有结果,返回原始关键字
556
+ if (keywords.length === 0) {
557
+ keywords.push(lowerKeyword);
558
+ }
559
+ return keywords;
560
+ }
561
+ /**
562
+ * 按时间范围过滤需求
563
+ * @param stories 需求列表
564
+ * @param startDate 开始时间(可选,格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss)
565
+ * @param endDate 结束时间(可选,格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss)
566
+ * @returns 过滤后的需求列表
567
+ */
568
+ filterByDateRange(stories, startDate, endDate) {
569
+ if (!startDate && !endDate) {
570
+ return stories;
571
+ }
572
+ const start = startDate ? this.parseDate(startDate) : null;
573
+ const end = endDate ? this.parseDate(endDate) : null;
574
+ return stories.filter(story => {
575
+ if (!story.openedDate) {
576
+ return false; // 没有创建时间的需求不包含在时间范围内
577
+ }
578
+ const storyDate = this.parseDate(story.openedDate);
579
+ if (!storyDate) {
580
+ return false;
581
+ }
582
+ // 检查是否在时间范围内
583
+ if (start && storyDate < start) {
584
+ return false;
585
+ }
586
+ if (end && storyDate > end) {
587
+ return false;
588
+ }
589
+ return true;
590
+ });
591
+ }
592
+ /**
593
+ * 解析日期字符串
594
+ * 支持格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss
595
+ */
596
+ parseDate(dateStr) {
597
+ if (!dateStr) {
598
+ return null;
599
+ }
600
+ // 尝试解析常见格式
601
+ // 格式1: YYYY-MM-DD
602
+ // 格式2: YYYY-MM-DD HH:mm:ss
603
+ // 格式3: YYYY-MM-DDTHH:mm:ss (ISO格式)
604
+ const date = new Date(dateStr);
605
+ if (isNaN(date.getTime())) {
606
+ return null;
607
+ }
608
+ return date;
609
+ }
610
+ /**
611
+ * 计算匹配度评分
612
+ * 评分规则:
613
+ * - 标题完全匹配:100分
614
+ * - 标题包含关键字:80分
615
+ * - 标题包含部分关键字(分词匹配):60分
616
+ * - 描述包含关键字:40分
617
+ * - 描述包含部分关键字:20分
618
+ * - 模块名/产品名匹配:10分
619
+ */
620
+ calculateMatchScore(story, keyword, keywords) {
621
+ let score = 0;
622
+ const title_lower = story.title.toLowerCase();
623
+ const spec_lower = (story.spec || '').toLowerCase();
624
+ const moduleName_lower = (story.moduleName || '').toLowerCase();
625
+ const productName_lower = (story.productName || '').toLowerCase();
626
+ // 标题完全匹配(最高优先级)
627
+ if (title_lower === keyword) {
628
+ score += 100;
629
+ }
630
+ // 标题包含完整关键字
631
+ else if (title_lower.includes(keyword)) {
632
+ score += 80;
633
+ }
634
+ // 标题包含部分关键字(分词匹配)
635
+ else {
636
+ const titleKeywordMatches = keywords.filter(k => title_lower.includes(k)).length;
637
+ if (titleKeywordMatches > 0) {
638
+ score += 60 * (titleKeywordMatches / keywords.length); // 按匹配比例计算
639
+ }
640
+ }
641
+ // 描述包含完整关键字
642
+ if (spec_lower.includes(keyword)) {
643
+ score += 40;
644
+ }
645
+ // 描述包含部分关键字
646
+ else if (spec_lower) {
647
+ const specKeywordMatches = keywords.filter(k => spec_lower.includes(k)).length;
648
+ if (specKeywordMatches > 0) {
649
+ score += 20 * (specKeywordMatches / keywords.length);
650
+ }
651
+ }
652
+ // 模块名/产品名匹配(加分项)
653
+ if (moduleName_lower.includes(keyword) || productName_lower.includes(keyword)) {
654
+ score += 10;
655
+ }
656
+ return score;
657
+ }
504
658
  /**
505
659
  * 按产品名称搜索需求
506
660
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxenlin/mcp-zentao-11-3",
3
- "version": "1.0.0-patch.1",
3
+ "version": "1.0.2",
4
4
  "description": "Zentao 11.3 legacy 版 MCP 服务器,只支持旧版 Session API,纯净无 REST v1",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -25,13 +25,14 @@
25
25
  "11.3",
26
26
  "legacy",
27
27
  "session-api",
28
- "cursor-ide"
28
+ "mcp-server",
29
+ "model-context-protocol"
29
30
  ],
30
31
  "author": "maxenlin",
31
32
  "license": "MIT",
32
33
  "repository": {
33
34
  "type": "git",
34
- "url": "https://github.com/maxenlin/mcp-zentao-11-3.git"
35
+ "url": "https://github.com/MaxenLin/mcp-zentao-11-3.git"
35
36
  },
36
37
  "dependencies": {
37
38
  "@modelcontextprotocol/sdk": "^1.6.1",
@@ -49,4 +50,3 @@
49
50
  "access": "public"
50
51
  }
51
52
  }
52
-