@ppdocs/mcp 2.6.29 → 2.6.31

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.
@@ -2,7 +2,7 @@ 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
4
  import * as vector from '../vector/index.js';
5
- import * as vectorManager from '../vector/manager.js';
5
+ // vectorManager 已弃用,向量索引由 Tauri 后端自动维护
6
6
  import { wrap, fetchRelations, mergeSearchResults, formatNodeMarkdown, formatRelationsMarkdown, buildDirectoryTree, formatTreeText, countTreeNodes } from './helpers.js';
7
7
  export function registerTools(server, projectId, _user) {
8
8
  // 1. 创建节点
@@ -35,27 +35,13 @@ export function registerTools(server, projectId, _user) {
35
35
  dependencies: decoded.dependencies || [],
36
36
  relatedFiles: decoded.relatedFiles || []
37
37
  });
38
- // 自动更新向量索引
39
- try {
40
- await vector.upsertNode(projectId, node.id, node.title, node.description, node.categories);
41
- }
42
- catch (e) {
43
- console.warn('[kg_create_node] Vector index update failed:', e);
44
- }
38
+ // 向量索引由 Tauri 后端自动维护,MCP 不再手动更新
45
39
  return wrap(JSON.stringify(node, null, 2));
46
40
  });
47
41
  // 2. 删除节点
48
42
  server.tool('kg_delete_node', '删除节点(锁定节点和根节点不可删除)', { nodeId: z.string().describe('节点ID') }, async (args) => {
49
43
  const success = await storage.deleteNode(projectId, args.nodeId);
50
- // 同步删除向量索引
51
- if (success) {
52
- try {
53
- await vector.removeNode(projectId, args.nodeId);
54
- }
55
- catch (e) {
56
- console.warn('[kg_delete_node] Vector index removal failed:', e);
57
- }
58
- }
44
+ // 向量索引由 Tauri 后端自动维护,MCP 不再手动删除
59
45
  return wrap(success ? '删除成功' : '删除失败(节点不存在/已锁定/是根节点)');
60
46
  });
61
47
  // 3. 更新节点
@@ -110,15 +96,7 @@ export function registerTools(server, projectId, _user) {
110
96
  if (lastSyncAt !== undefined)
111
97
  updates.lastSyncAt = lastSyncAt;
112
98
  const node = await storage.updateNode(projectId, nodeId, updates);
113
- // 自动更新向量索引 (标题或描述变更时)
114
- if (node && (rest.title || rest.description || tags)) {
115
- try {
116
- await vector.upsertNode(projectId, node.id, node.title, node.description, node.categories);
117
- }
118
- catch (e) {
119
- console.warn('[kg_update_node] Vector index update failed:', e);
120
- }
121
- }
99
+ // 向量索引由 Tauri 后端自动维护,MCP 不再手动更新
122
100
  return wrap(node ? JSON.stringify(node, null, 2) : '更新失败(节点不存在或已锁定)');
123
101
  });
124
102
  // 3.5 更新根节点 (项目介绍)
@@ -207,8 +185,7 @@ export function registerTools(server, projectId, _user) {
207
185
  let semanticResults = [];
208
186
  if (mode === 'hybrid' || mode === 'semantic') {
209
187
  try {
210
- // 自动检测索引状态,缺失/不匹配则自动构建
211
- await vectorManager.ensureIndex(projectId);
188
+ // 向量索引由 Tauri 后端自动维护,MCP 直接读取
212
189
  const rawSemantic = await vector.semanticSearch(projectId, query, limit * 2);
213
190
  // 补全节点信息 (关键词结果中已有的复用,没有的获取)
214
191
  const keywordMap = new Map(keywordResults.map(r => [r.id, r]));
@@ -241,18 +218,6 @@ export function registerTools(server, projectId, _user) {
241
218
  const json = JSON.stringify(output, null, 2);
242
219
  return wrap(`${json}\n\n💡 需要详细内容请使用 kg_read_node(nodeId) 获取节点详情\n🔍 搜索模式: ${mode}`);
243
220
  });
244
- // 5.5 列出所有标签
245
- server.tool('kg_list_tags', '获取所有节点使用过的标签列表(去重)', {}, async () => {
246
- const nodes = await storage.listNodes(projectId);
247
- const tagSet = new Set();
248
- for (const node of nodes) {
249
- if (node.categories && Array.isArray(node.categories)) {
250
- node.categories.forEach(cat => tagSet.add(cat));
251
- }
252
- }
253
- const tags = Array.from(tagSet).sort();
254
- return wrap(JSON.stringify({ total: tags.length, tags }, null, 2));
255
- });
256
221
  // 6. 路径查找
257
222
  server.tool('kg_find_path', '查找两节点间的依赖路径', {
258
223
  startId: z.string().describe('起点节点ID'),
@@ -438,22 +403,13 @@ export function registerTools(server, projectId, _user) {
438
403
  return wrap(JSON.stringify(result, null, 2));
439
404
  });
440
405
  // ===================== 向量索引管理 =====================
441
- // 16. 构建向量索引
442
- server.tool('kg_build_index', '构建/重建向量语义索引(首次使用或索引损坏时调用)', {}, async () => {
443
- const count = await vectorManager.rebuildIndex(projectId);
444
- return wrap(`✅ 向量索引构建完成,共 ${count} 个节点`);
445
- });
446
- // 17. 查看索引状态
406
+ // 注意: 向量索引由 Tauri 后端自动维护,MCP 只提供只读查询
407
+ // 16. 查看索引状态 (只读)
447
408
  server.tool('kg_index_stats', '查看向量索引状态', {}, async () => {
448
409
  const stats = await vector.getIndexStats(projectId);
449
- const rebuildCheck = await vectorManager.needsRebuild(projectId);
450
410
  return wrap(JSON.stringify({
451
411
  ...stats,
452
- needsRebuild: rebuildCheck.needed,
453
- rebuildReason: rebuildCheck.reason,
454
- tip: rebuildCheck.needed
455
- ? `索引需要重建,原因: ${rebuildCheck.reason}`
456
- : '索引正常 (搜索时会自动检测并重建)'
412
+ tip: '向量索引由软件端自动维护,节点增删改时自动更新'
457
413
  }, null, 2));
458
414
  });
459
415
  }
@@ -1,11 +1,16 @@
1
1
  /**
2
- * 向量索引管理器
3
- * 负责自动检测、重建索引逻辑
4
- * @version 0.1
2
+ * 向量索引管理器 (已弃用)
3
+ *
4
+ * ⚠️ 自 v2.7.0 起,向量索引由 Tauri 后端自动维护
5
+ * 此模块保留仅用于向后兼容,不再被主动调用
6
+ *
7
+ * @deprecated 请使用 Tauri 后端的 vector 模块
8
+ * @version 0.2
5
9
  */
6
10
  interface RebuildReason {
7
11
  needed: boolean;
8
- reason: 'missing' | 'model_mismatch' | 'empty' | 'none';
12
+ reason: 'missing' | 'model_mismatch' | 'empty' | 'count_mismatch' | 'none';
13
+ missingCount?: number;
9
14
  }
10
15
  interface RebuildResult {
11
16
  projectId: string;
@@ -21,7 +26,11 @@ export declare function needsRebuild(projectId: string): Promise<RebuildReason>;
21
26
  */
22
27
  export declare function rebuildIndex(projectId: string): Promise<number>;
23
28
  /**
24
- * 确保索引存在 (自动检测 + 按需构建)
29
+ * 增量同步缺失节点 (仅补漏,不全量重建)
30
+ */
31
+ export declare function syncMissingNodes(projectId: string): Promise<number>;
32
+ /**
33
+ * 确保索引存在 (自动检测 + 按需构建/增量补漏)
25
34
  */
26
35
  export declare function ensureIndex(projectId: string): Promise<void>;
27
36
  /**
@@ -1,12 +1,46 @@
1
1
  /**
2
- * 向量索引管理器
3
- * 负责自动检测、重建索引逻辑
4
- * @version 0.1
2
+ * 向量索引管理器 (已弃用)
3
+ *
4
+ * ⚠️ 自 v2.7.0 起,向量索引由 Tauri 后端自动维护
5
+ * 此模块保留仅用于向后兼容,不再被主动调用
6
+ *
7
+ * @deprecated 请使用 Tauri 后端的 vector 模块
8
+ * @version 0.2
5
9
  */
6
10
  import * as fs from 'fs';
7
11
  import * as path from 'path';
8
12
  import * as vectorCore from './index.js';
9
13
  // ==================== 检测函数 ====================
14
+ /**
15
+ * 获取节点目录下的节点ID列表
16
+ */
17
+ function getNodeIdsFromDisk(projectId) {
18
+ const baseDir = process.env.PPDOCS_DATA_DIR || path.join(process.env.HOME || process.env.USERPROFILE || '', '.ppdocs');
19
+ const nodesDir = path.join(baseDir, 'projects', projectId, 'nodes');
20
+ const nodeIds = new Set();
21
+ if (!fs.existsSync(nodesDir))
22
+ return nodeIds;
23
+ try {
24
+ const files = fs.readdirSync(nodesDir).filter(f => f.endsWith('.json'));
25
+ for (const file of files) {
26
+ try {
27
+ const content = fs.readFileSync(path.join(nodesDir, file), 'utf-8');
28
+ const node = JSON.parse(content);
29
+ // 跳过根节点
30
+ if (node.isOrigin || node.id === 'root')
31
+ continue;
32
+ nodeIds.add(node.id);
33
+ }
34
+ catch {
35
+ // 跳过无法解析的文件
36
+ }
37
+ }
38
+ }
39
+ catch {
40
+ // 忽略读取错误
41
+ }
42
+ return nodeIds;
43
+ }
10
44
  /**
11
45
  * 检测索引是否需要重建
12
46
  */
@@ -28,6 +62,13 @@ export async function needsRebuild(projectId) {
28
62
  if (!data.entries || data.entries.length === 0) {
29
63
  return { needed: true, reason: 'empty' };
30
64
  }
65
+ // 5. 检测节点数量不匹配 (新增)
66
+ const diskNodeIds = getNodeIdsFromDisk(projectId);
67
+ const indexedIds = new Set(data.entries.map(e => e.id));
68
+ const missingIds = [...diskNodeIds].filter(id => !indexedIds.has(id));
69
+ if (missingIds.length > 0) {
70
+ return { needed: true, reason: 'count_mismatch', missingCount: missingIds.length };
71
+ }
31
72
  return { needed: false, reason: 'none' };
32
73
  }
33
74
  catch {
@@ -87,11 +128,54 @@ export async function rebuildIndex(projectId) {
87
128
  return vectorCore.buildIndex(projectId, nodes);
88
129
  }
89
130
  /**
90
- * 确保索引存在 (自动检测 + 按需构建)
131
+ * 增量同步缺失节点 (仅补漏,不全量重建)
132
+ */
133
+ export async function syncMissingNodes(projectId) {
134
+ const indexPath = vectorCore.getIndexPath(projectId);
135
+ // 读取当前索引
136
+ let indexedIds = new Set();
137
+ try {
138
+ if (fs.existsSync(indexPath)) {
139
+ const data = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
140
+ indexedIds = new Set(data.entries.map(e => e.id));
141
+ }
142
+ }
143
+ catch {
144
+ // 索引损坏,需要全量重建
145
+ return rebuildIndex(projectId);
146
+ }
147
+ // 找出缺失的节点
148
+ const allNodes = await fetchNodesForIndex(projectId);
149
+ const missingNodes = allNodes.filter(n => !indexedIds.has(n.id));
150
+ if (missingNodes.length === 0) {
151
+ console.log(`[VectorManager] No missing nodes to sync`);
152
+ return 0;
153
+ }
154
+ console.log(`[VectorManager] Syncing ${missingNodes.length} missing nodes...`);
155
+ // 增量添加缺失节点
156
+ for (let i = 0; i < missingNodes.length; i++) {
157
+ const node = missingNodes[i];
158
+ await vectorCore.upsertNode(projectId, node.id, node.title, node.description, node.categories || []);
159
+ if ((i + 1) % 10 === 0) {
160
+ console.log(`[VectorManager] Synced ${i + 1}/${missingNodes.length}`);
161
+ }
162
+ }
163
+ console.log(`[VectorManager] Sync complete: ${missingNodes.length} nodes added`);
164
+ return missingNodes.length;
165
+ }
166
+ /**
167
+ * 确保索引存在 (自动检测 + 按需构建/增量补漏)
91
168
  */
92
169
  export async function ensureIndex(projectId) {
93
170
  const check = await needsRebuild(projectId);
94
- if (check.needed) {
171
+ if (!check.needed)
172
+ return;
173
+ // count_mismatch 使用增量补漏,其他情况全量重建
174
+ if (check.reason === 'count_mismatch') {
175
+ console.log(`[VectorManager] Auto-syncing ${check.missingCount} missing nodes`);
176
+ await syncMissingNodes(projectId);
177
+ }
178
+ else {
95
179
  console.log(`[VectorManager] Auto-rebuilding index, reason: ${check.reason}`);
96
180
  await rebuildIndex(projectId);
97
181
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "2.6.29",
3
+ "version": "2.6.31",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",