@ppdocs/mcp 2.6.31 → 2.7.1

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.
@@ -54,6 +54,20 @@ export declare class PpdocsApiClient {
54
54
  completeTask(taskId: string, experience: TaskExperience): Promise<Task | null>;
55
55
  getRulesApi(ruleType: string): Promise<string[]>;
56
56
  saveRulesApi(ruleType: string, rules: string[]): Promise<boolean>;
57
+ vectorSearch(query: string, limit: number, mode: string): Promise<Array<{
58
+ id: string;
59
+ title: string;
60
+ score: number;
61
+ match_type: string;
62
+ keyword_score: number;
63
+ semantic_score: number;
64
+ }>>;
65
+ vectorStats(): Promise<{
66
+ model_downloaded: boolean;
67
+ model_loaded: boolean;
68
+ model_path: string;
69
+ index_count: number;
70
+ }>;
57
71
  }
58
72
  export declare function initClient(apiUrl: string): void;
59
73
  export declare function listNodes(_projectId: string, filter?: ListNodesFilter): Promise<NodeData[]>;
@@ -96,3 +110,23 @@ export declare function addTaskLog(_projectId: string, taskId: string, logType:
96
110
  export declare function completeTask(_projectId: string, taskId: string, experience: TaskExperience): Promise<Task | null>;
97
111
  export declare function getRules(_projectId: string, ruleType: string): Promise<string[]>;
98
112
  export declare function saveRules(_projectId: string, ruleType: string, rules: string[]): Promise<boolean>;
113
+ /** 向量搜索结果 */
114
+ export interface VectorSearchResult {
115
+ id: string;
116
+ title: string;
117
+ score: number;
118
+ match_type: string;
119
+ keyword_score: number;
120
+ semantic_score: number;
121
+ }
122
+ /** 向量索引状态 */
123
+ export interface VectorStats {
124
+ model_downloaded: boolean;
125
+ model_loaded: boolean;
126
+ model_path: string;
127
+ index_count: number;
128
+ }
129
+ /** 混合搜索 (关键词 + 语义) */
130
+ export declare function vectorSearch(_projectId: string, query: string, limit: number, mode: 'hybrid' | 'keyword' | 'semantic'): Promise<VectorSearchResult[]>;
131
+ /** 获取向量索引状态 */
132
+ export declare function vectorStats(_projectId: string): Promise<VectorStats>;
@@ -360,6 +360,16 @@ export class PpdocsApiClient {
360
360
  return false;
361
361
  }
362
362
  }
363
+ // ============ 向量搜索 API ============
364
+ async vectorSearch(query, limit, mode) {
365
+ return this.request('/vector/search', {
366
+ method: 'POST',
367
+ body: JSON.stringify({ query, limit, mode })
368
+ });
369
+ }
370
+ async vectorStats() {
371
+ return this.request('/vector/stats');
372
+ }
363
373
  }
364
374
  // ============ 模块级 API (兼容现有 tools/index.ts) ============
365
375
  let client = null;
@@ -429,3 +439,11 @@ export async function getRules(_projectId, ruleType) {
429
439
  export async function saveRules(_projectId, ruleType, rules) {
430
440
  return getClient().saveRulesApi(ruleType, rules);
431
441
  }
442
+ /** 混合搜索 (关键词 + 语义) */
443
+ export async function vectorSearch(_projectId, query, limit, mode) {
444
+ return getClient().vectorSearch(query, limit, mode);
445
+ }
446
+ /** 获取向量索引状态 */
447
+ export async function vectorStats(_projectId) {
448
+ return getClient().vectorStats();
449
+ }
@@ -24,33 +24,6 @@ export declare function fetchRelations(projectId: string, nodeId: string, maxDep
24
24
  outgoing: RelationItem[];
25
25
  incoming: RelationItem[];
26
26
  }>;
27
- interface SearchResultItem {
28
- id: string;
29
- title: string;
30
- tags: string[];
31
- status: string;
32
- summary: string;
33
- score: string;
34
- matchType: 'hybrid' | 'semantic' | 'keyword';
35
- }
36
- /**
37
- * 融合关键词和语义搜索结果
38
- */
39
- export declare function mergeSearchResults(keywordResults: Array<{
40
- id: string;
41
- title: string;
42
- tags: string[];
43
- status: string;
44
- summary: string;
45
- score: number;
46
- }>, semanticResults: Array<{
47
- id: string;
48
- title: string;
49
- tags: string[];
50
- status: string;
51
- summary: string;
52
- similarity: number;
53
- }>, mode: 'hybrid' | 'keyword' | 'semantic', limit: number): SearchResultItem[];
54
27
  /**
55
28
  * 格式化节点为 Markdown 输出
56
29
  */
@@ -40,62 +40,6 @@ export async function fetchRelations(projectId, nodeId, maxDepth) {
40
40
  await recurse(nodeId, 1, 'both');
41
41
  return { outgoing, incoming };
42
42
  }
43
- /**
44
- * 融合关键词和语义搜索结果
45
- */
46
- export function mergeSearchResults(keywordResults, semanticResults, mode, limit) {
47
- const scoreMap = new Map();
48
- // 合并关键词结果
49
- for (const r of keywordResults) {
50
- scoreMap.set(r.id, {
51
- id: r.id,
52
- title: r.title,
53
- tags: r.tags,
54
- status: r.status,
55
- summary: r.summary,
56
- keywordScore: r.score,
57
- semanticScore: 0
58
- });
59
- }
60
- // 合并语义结果
61
- for (const r of semanticResults) {
62
- const existing = scoreMap.get(r.id);
63
- if (existing) {
64
- existing.semanticScore = r.similarity;
65
- }
66
- else {
67
- scoreMap.set(r.id, {
68
- id: r.id,
69
- title: r.title,
70
- tags: r.tags,
71
- status: r.status,
72
- summary: r.summary,
73
- keywordScore: 0,
74
- semanticScore: r.similarity
75
- });
76
- }
77
- }
78
- // 计算综合得分并排序
79
- const combined = Array.from(scoreMap.values()).map(item => {
80
- // 混合权重: 语义 0.6 + 关键词 0.4
81
- const finalScore = mode === 'keyword' ? item.keywordScore
82
- : mode === 'semantic' ? item.semanticScore
83
- : item.semanticScore * 0.6 + item.keywordScore * 0.4;
84
- return { ...item, finalScore };
85
- });
86
- combined.sort((a, b) => b.finalScore - a.finalScore);
87
- const topResults = combined.slice(0, limit);
88
- return topResults.map(r => ({
89
- id: r.id,
90
- title: r.title,
91
- tags: r.tags,
92
- status: r.status,
93
- summary: r.summary,
94
- score: `${Math.round(r.finalScore * 100)}%`,
95
- matchType: (r.keywordScore > 0 && r.semanticScore > 0 ? 'hybrid'
96
- : r.semanticScore > 0 ? 'semantic' : 'keyword')
97
- }));
98
- }
99
43
  // ==================== 节点格式化 ====================
100
44
  /**
101
45
  * 格式化节点为 Markdown 输出
@@ -1,9 +1,8 @@
1
1
  import { z } from 'zod';
2
2
  import * as storage from '../storage/httpClient.js';
3
3
  import { decodeUnicodeEscapes, decodeObjectStrings, getRules, RULE_TYPE_LABELS } from '../utils.js';
4
- import * as vector from '../vector/index.js';
5
- // vectorManager 已弃用,向量索引由 Tauri 后端自动维护
6
- import { wrap, fetchRelations, mergeSearchResults, formatNodeMarkdown, formatRelationsMarkdown, buildDirectoryTree, formatTreeText, countTreeNodes } from './helpers.js';
4
+ // 向量搜索已迁移至 Tauri 后端,通过 HTTP API 调用
5
+ import { wrap, fetchRelations, formatNodeMarkdown, formatRelationsMarkdown, buildDirectoryTree, formatTreeText, countTreeNodes } from './helpers.js';
7
6
  export function registerTools(server, projectId, _user) {
8
7
  // 1. 创建节点
9
8
  server.tool('kg_create_node', '创建知识节点。type: logic=逻辑/函数, data=数据结构, intro=概念介绍。⚠️ tags至少提供3个分类标签', {
@@ -168,55 +167,40 @@ export function registerTools(server, projectId, _user) {
168
167
  }));
169
168
  return wrap(`${JSON.stringify(output, null, 2)}\n\n💡 需要详细内容请使用 kg_read_node(nodeId) 获取节点详情`);
170
169
  }
171
- // 获取关键词结果
172
- let keywordResults = [];
173
- if (mode === 'hybrid' || mode === 'keyword') {
174
- const kResults = await storage.searchNodes(projectId, args.keywords, limit * 2);
175
- keywordResults = kResults.map(r => ({
170
+ // 调用 Tauri 后端混合搜索 API
171
+ try {
172
+ const results = await storage.vectorSearch(projectId, query, limit, mode);
173
+ // 补全节点信息 (tags, status, summary)
174
+ const output = [];
175
+ for (const r of results) {
176
+ const node = await storage.getNode(projectId, r.id);
177
+ output.push({
178
+ id: r.id,
179
+ title: r.title,
180
+ tags: node?.categories || [],
181
+ status: node?.status || 'incomplete',
182
+ summary: node?.summary || '',
183
+ score: `${Math.round(r.score)}%`,
184
+ matchType: r.match_type
185
+ });
186
+ }
187
+ const json = JSON.stringify(output, null, 2);
188
+ return wrap(`${json}\n\n💡 需要详细内容请使用 kg_read_node(nodeId) 获取节点详情\n🔍 搜索模式: ${mode}`);
189
+ }
190
+ catch (e) {
191
+ // 后端搜索失败,降级为关键词搜索
192
+ console.warn('[kg_search] Vector search failed, fallback to keyword:', e);
193
+ const results = await storage.searchNodes(projectId, args.keywords, limit);
194
+ const output = results.map(r => ({
176
195
  id: r.node.id,
177
196
  title: r.node.title,
178
197
  tags: r.node.categories || [],
179
198
  status: r.node.status,
180
199
  summary: r.node.summary || '',
181
- score: r.score / 100
200
+ hitRate: `${Math.round(r.score)}%`
182
201
  }));
202
+ return wrap(`${JSON.stringify(output, null, 2)}\n\n💡 需要详细内容请使用 kg_read_node(nodeId) 获取节点详情\n⚠️ 语义搜索不可用,已降级为关键词搜索`);
183
203
  }
184
- // 获取语义结果 (需补全节点信息)
185
- let semanticResults = [];
186
- if (mode === 'hybrid' || mode === 'semantic') {
187
- try {
188
- // 向量索引由 Tauri 后端自动维护,MCP 直接读取
189
- const rawSemantic = await vector.semanticSearch(projectId, query, limit * 2);
190
- // 补全节点信息 (关键词结果中已有的复用,没有的获取)
191
- const keywordMap = new Map(keywordResults.map(r => [r.id, r]));
192
- for (const r of rawSemantic) {
193
- const existing = keywordMap.get(r.id);
194
- if (existing) {
195
- semanticResults.push({ ...existing, similarity: r.similarity });
196
- }
197
- else {
198
- const node = await storage.getNode(projectId, r.id);
199
- if (node) {
200
- semanticResults.push({
201
- id: r.id,
202
- title: r.title,
203
- tags: node.categories || [],
204
- status: node.status,
205
- summary: node.summary || '',
206
- similarity: r.similarity
207
- });
208
- }
209
- }
210
- }
211
- }
212
- catch (e) {
213
- console.warn('[kg_search] Semantic search failed:', e);
214
- }
215
- }
216
- // 融合排序
217
- const output = mergeSearchResults(keywordResults, semanticResults, mode, limit);
218
- const json = JSON.stringify(output, null, 2);
219
- return wrap(`${json}\n\n💡 需要详细内容请使用 kg_read_node(nodeId) 获取节点详情\n🔍 搜索模式: ${mode}`);
220
204
  });
221
205
  // 6. 路径查找
222
206
  server.tool('kg_find_path', '查找两节点间的依赖路径', {
@@ -406,10 +390,22 @@ export function registerTools(server, projectId, _user) {
406
390
  // 注意: 向量索引由 Tauri 后端自动维护,MCP 只提供只读查询
407
391
  // 16. 查看索引状态 (只读)
408
392
  server.tool('kg_index_stats', '查看向量索引状态', {}, async () => {
409
- const stats = await vector.getIndexStats(projectId);
410
- return wrap(JSON.stringify({
411
- ...stats,
412
- tip: '向量索引由软件端自动维护,节点增删改时自动更新'
413
- }, null, 2));
393
+ try {
394
+ const stats = await storage.vectorStats(projectId);
395
+ return wrap(JSON.stringify({
396
+ model_downloaded: stats.model_downloaded,
397
+ model_loaded: stats.model_loaded,
398
+ model_path: stats.model_path,
399
+ index_count: stats.index_count,
400
+ tip: '向量索引由软件端自动维护,节点增删改时自动更新'
401
+ }, null, 2));
402
+ }
403
+ catch (e) {
404
+ return wrap(JSON.stringify({
405
+ error: '无法获取索引状态',
406
+ message: String(e),
407
+ tip: '请确保软件端已启动'
408
+ }, null, 2));
409
+ }
414
410
  });
415
411
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "2.6.31",
3
+ "version": "2.7.1",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,7 +30,6 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@modelcontextprotocol/sdk": "^1.0.0",
33
- "@xenova/transformers": "^2.17.2",
34
33
  "proper-lockfile": "^4.1.2",
35
34
  "zod": "^4.1.13"
36
35
  },