@ppdocs/mcp 3.2.20 → 3.2.22

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.
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * API URL 格式: http://localhost:20001/api/:projectId/:password/...
6
6
  */
7
- import type { DocData, DocNode, DocSearchResult, Task, TaskSummary, TaskLogType, TaskExperience, FileInfo, RuleMeta } from './types.js';
7
+ import type { DocData, DocNode, Task, TaskSummary, TaskLogType, TaskExperience, FileInfo, RuleMeta } from './types.js';
8
8
  interface TreeNode {
9
9
  path: string;
10
10
  name: string;
@@ -22,7 +22,6 @@ export declare class PpdocsApiClient {
22
22
  createDoc(docPath: string, doc: Partial<DocData>): Promise<DocData>;
23
23
  updateDoc(docPath: string, updates: Partial<DocData>): Promise<DocData | null>;
24
24
  deleteDoc(docPath: string): Promise<boolean>;
25
- searchDocs(keywords: string[], limit?: number): Promise<DocSearchResult[]>;
26
25
  /** 按状态筛选文档 */
27
26
  getDocsByStatus(statusList: string[]): Promise<DocNode[]>;
28
27
  /** 获取目录树结构 */
@@ -43,14 +42,6 @@ export declare class PpdocsApiClient {
43
42
  }, creator: string): Promise<Task>;
44
43
  addTaskLog(taskId: string, logType: TaskLogType, content: string): Promise<Task | null>;
45
44
  completeTask(taskId: string, experience: TaskExperience): Promise<Task | null>;
46
- /** 创建项目 (公开路由,无需认证) */
47
- createProject(id: string, name: string, description?: string, projectPath?: string): Promise<{
48
- project: {
49
- id: string;
50
- name: string;
51
- };
52
- password: string;
53
- }>;
54
45
  /** 列出所有可访问的项目 */
55
46
  crossListProjects(): Promise<{
56
47
  id: string;
@@ -77,12 +68,10 @@ export declare class PpdocsApiClient {
77
68
  localPath: string;
78
69
  fileCount: number;
79
70
  }>;
80
- /** 上传本地目录到中心服务器 (打包 zip → POST) */
81
- uploadFiles(localDir: string, remoteDir?: string): Promise<{
71
+ /** 上传本地目录或单文件到中心服务器 (打包 zip → POST) */
72
+ uploadFiles(localPath: string, remoteDir?: string): Promise<{
82
73
  fileCount: number;
83
74
  }>;
84
- /** 清空项目文件存储区 */
85
- clearFiles(): Promise<void>;
86
75
  /** 上传已打包好的 zip 数据到本项目 files/ (供 Code Beacon 使用) */
87
76
  uploadRawZip(zipData: Buffer | Uint8Array | any, remoteDir?: string): Promise<{
88
77
  fileCount: number;
@@ -98,12 +87,6 @@ export declare class PpdocsApiClient {
98
87
  }>;
99
88
  /** 扫描项目代码, 构建/更新索引 */
100
89
  analyzerScan(projectPath: string, force?: boolean): Promise<unknown>;
101
- /** 查询代码符号 (模糊匹配) */
102
- analyzerQuery(projectPath: string, query: string): Promise<unknown>;
103
- /** 分层影响分析 (BFS 递归, 可指定深度) */
104
- analyzerImpactTree(projectPath: string, symbolName: string, depth?: number): Promise<unknown>;
105
- /** 获取文件 360° 上下文 */
106
- analyzerContext(projectPath: string, filePath: string): Promise<unknown>;
107
90
  /** 智能上下文: 代码+文档+规则+任务全关联 */
108
91
  analyzerSmartContext(projectPath: string, symbolName: string): Promise<unknown>;
109
92
  /** 全关联路径: 两个符号之间的代码+文档路径 */
@@ -112,19 +95,42 @@ export declare class PpdocsApiClient {
112
95
  /** 列出活跃讨论 (公开路由) */
113
96
  discussionList(): Promise<unknown>;
114
97
  /** 创建讨论 (公开路由) */
115
- discussionCreate(title: string, initiator: string, participants: string[], content: string): Promise<{
98
+ discussionCreate(title: string, initiator: string, participants: string[], content: string, msgSummary?: string): Promise<{
116
99
  id: string;
117
100
  }>;
118
- /** 批量读取讨论 (公开路由) */
119
- discussionReadByIds(ids: string[]): Promise<unknown>;
101
+ /** 批量读取讨论 (公开路由, 支持已读追踪) */
102
+ discussionReadByIds(ids: string[], reader?: string, mode?: string): Promise<unknown>;
103
+ /** 列出所有讨论 (含历史, 按参与方筛选) */
104
+ discussionListAll(participant?: string): Promise<unknown>;
120
105
  /** 回复讨论 (公开路由) */
121
- discussionReply(id: string, sender: string, content: string, newSummary?: string): Promise<boolean>;
106
+ discussionReply(id: string, sender: string, content: string, msgSummary?: string, newSummary?: string): Promise<boolean>;
107
+ /** 完成讨论 (公开路由, 标记为 completed) */
108
+ discussionComplete(id: string): Promise<boolean>;
122
109
  /** 删除讨论 (公开路由) */
123
110
  discussionDelete(id: string): Promise<boolean>;
124
111
  /** 结案归档讨论 (认证路由) */
125
112
  discussionClose(id: string, conclusion: string): Promise<{
126
113
  archived_path: string;
127
114
  }>;
115
+ /** 列出公共文件 */
116
+ publicFilesList(dir?: string): Promise<FileInfo[]>;
117
+ /** 读取公共文件 (文本) */
118
+ publicFilesRead(filePath: string): Promise<string>;
119
+ /** 创建公共文件池子目录 */
120
+ publicFilesMkdir(dirPath: string): Promise<boolean>;
121
+ /** 重命名公共文件或目录 */
122
+ publicFilesRename(filePath: string, newName: string): Promise<boolean>;
123
+ /** 删除公共文件 */
124
+ publicFilesDelete(filePath: string): Promise<boolean>;
125
+ /** 下载公共文件或目录 */
126
+ publicFilesDownload(remotePath: string, localPath?: string): Promise<{
127
+ localPath: string;
128
+ fileCount: number;
129
+ }>;
130
+ /** 上传本地目录或单文件到公共文件池 */
131
+ publicFilesUpload(localPath: string, remoteDir?: string): Promise<{
132
+ fileCount: number;
133
+ }>;
128
134
  meetingJoin(agentId: string, agentType: string): Promise<unknown>;
129
135
  meetingLeave(agentId: string): Promise<unknown>;
130
136
  meetingPost(agentId: string, content: string, msgType?: string): Promise<unknown>;
@@ -191,12 +191,6 @@ export class PpdocsApiClient {
191
191
  return false;
192
192
  }
193
193
  }
194
- async searchDocs(keywords, limit = 20) {
195
- return this.request('/docs/search', {
196
- method: 'POST',
197
- body: JSON.stringify({ keywords, limit })
198
- });
199
- }
200
194
  /** 按状态筛选文档 */
201
195
  async getDocsByStatus(statusList) {
202
196
  return this.request('/docs/by-status', {
@@ -328,22 +322,6 @@ export class PpdocsApiClient {
328
322
  return null;
329
323
  }
330
324
  }
331
- // ============ 项目管理 ============
332
- /** 创建项目 (公开路由,无需认证) */
333
- async createProject(id, name, description, projectPath) {
334
- const url = `${this.serverUrl}/api/projects`;
335
- const response = await fetch(url, {
336
- method: 'POST',
337
- headers: { 'Content-Type': 'application/json' },
338
- body: JSON.stringify({ id, name, description: description || '', project_path: projectPath || '' }),
339
- });
340
- if (!response.ok) {
341
- const error = await response.json().catch(() => ({ error: response.statusText }));
342
- throw new Error(error.error || `HTTP ${response.status}`);
343
- }
344
- const json = await response.json();
345
- return json.data;
346
- }
347
325
  // ============ 跨项目只读访问 ============
348
326
  /** 列出所有可访问的项目 */
349
327
  async crossListProjects() {
@@ -397,33 +375,41 @@ export class PpdocsApiClient {
397
375
  async download(remotePath, localPath) {
398
376
  return fetchAndExtractZip(`${this.baseUrl}/files-download/${cleanPath(remotePath)}`, localPath);
399
377
  }
400
- /** 上传本地目录到中心服务器 (打包 zip → POST) */
401
- async uploadFiles(localDir, remoteDir) {
378
+ /** 上传本地目录或单文件到中心服务器 (打包 zip → POST) */
379
+ async uploadFiles(localPath, remoteDir) {
402
380
  const fs = await import('fs');
403
381
  const path = await import('path');
404
382
  const AdmZip = (await import('adm-zip')).default;
405
- if (!fs.existsSync(localDir) || !fs.statSync(localDir).isDirectory()) {
406
- throw new Error(`本地目录不存在: ${localDir}`);
383
+ if (!fs.existsSync(localPath)) {
384
+ throw new Error(`本地路径不存在: ${localPath}`);
407
385
  }
408
386
  const zip = new AdmZip();
409
- const addDir = (dirPath, zipPath) => {
410
- for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
411
- if (entry.name.startsWith('.'))
412
- continue;
413
- const fullPath = path.join(dirPath, entry.name);
414
- if (entry.isDirectory()) {
415
- if (EXCLUDED_DIRS.has(entry.name))
416
- continue;
417
- addDir(fullPath, zipPath ? `${zipPath}/${entry.name}` : entry.name);
418
- }
419
- else {
420
- if (fs.statSync(fullPath).size > 10_485_760)
387
+ const stat = fs.statSync(localPath);
388
+ if (stat.isDirectory()) {
389
+ const addDir = (dirPath, zipPath) => {
390
+ for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
391
+ if (entry.name.startsWith('.'))
421
392
  continue;
422
- zip.addLocalFile(fullPath, zipPath || undefined);
393
+ const fullPath = path.join(dirPath, entry.name);
394
+ if (entry.isDirectory()) {
395
+ if (EXCLUDED_DIRS.has(entry.name))
396
+ continue;
397
+ addDir(fullPath, zipPath ? `${zipPath}/${entry.name}` : entry.name);
398
+ }
399
+ else {
400
+ if (fs.statSync(fullPath).size > 10_485_760)
401
+ continue;
402
+ zip.addLocalFile(fullPath, zipPath || undefined);
403
+ }
423
404
  }
424
- }
425
- };
426
- addDir(localDir, '');
405
+ };
406
+ addDir(localPath, '');
407
+ }
408
+ else {
409
+ if (stat.size > 10_485_760)
410
+ throw new Error('文件超过 10MB 限制');
411
+ zip.addLocalFile(localPath);
412
+ }
427
413
  const buffer = zip.toBuffer();
428
414
  if (buffer.length > 104_857_600) {
429
415
  throw new Error('打包后超过 100MB 限制,请缩小目录范围');
@@ -441,15 +427,6 @@ export class PpdocsApiClient {
441
427
  const result = await response.json();
442
428
  return { fileCount: result.data?.fileCount || 0 };
443
429
  }
444
- /** 清空项目文件存储区 */
445
- async clearFiles() {
446
- const url = `${this.baseUrl}/files`;
447
- const response = await fetch(url, { method: 'DELETE' });
448
- if (!response.ok) {
449
- const error = await response.json().catch(() => ({ error: response.statusText }));
450
- throw new Error(error.error || `HTTP ${response.status}`);
451
- }
452
- }
453
430
  // ============ 直接上传已打包的 ZIP ============
454
431
  /** 上传已打包好的 zip 数据到本项目 files/ (供 Code Beacon 使用) */
455
432
  async uploadRawZip(zipData, remoteDir) {
@@ -488,27 +465,6 @@ export class PpdocsApiClient {
488
465
  body: JSON.stringify({ project_path: projectPath, force }),
489
466
  });
490
467
  }
491
- /** 查询代码符号 (模糊匹配) */
492
- async analyzerQuery(projectPath, query) {
493
- return this.request('/analyzer/query', {
494
- method: 'POST',
495
- body: JSON.stringify({ project_path: projectPath, query }),
496
- });
497
- }
498
- /** 分层影响分析 (BFS 递归, 可指定深度) */
499
- async analyzerImpactTree(projectPath, symbolName, depth = 2) {
500
- return this.request('/analyzer/impact-tree', {
501
- method: 'POST',
502
- body: JSON.stringify({ project_path: projectPath, symbol_name: symbolName, depth }),
503
- });
504
- }
505
- /** 获取文件 360° 上下文 */
506
- async analyzerContext(projectPath, filePath) {
507
- return this.request('/analyzer/context', {
508
- method: 'POST',
509
- body: JSON.stringify({ project_path: projectPath, file_path: filePath }),
510
- });
511
- }
512
468
  /** 智能上下文: 代码+文档+规则+任务全关联 */
513
469
  async analyzerSmartContext(projectPath, symbolName) {
514
470
  return this.request('/analyzer/smart-context', {
@@ -544,24 +500,37 @@ export class PpdocsApiClient {
544
500
  return this.publicRequest('/api/discussions');
545
501
  }
546
502
  /** 创建讨论 (公开路由) */
547
- async discussionCreate(title, initiator, participants, content) {
503
+ async discussionCreate(title, initiator, participants, content, msgSummary) {
548
504
  return this.publicRequest('/api/discussions', {
549
505
  method: 'POST',
550
- body: JSON.stringify({ title, initiator, participants, content }),
506
+ body: JSON.stringify({ title, initiator, participants, content, msg_summary: msgSummary }),
551
507
  });
552
508
  }
553
- /** 批量读取讨论 (公开路由) */
554
- async discussionReadByIds(ids) {
509
+ /** 批量读取讨论 (公开路由, 支持已读追踪) */
510
+ async discussionReadByIds(ids, reader, mode) {
555
511
  return this.publicRequest('/api/discussions/read', {
556
512
  method: 'POST',
557
- body: JSON.stringify({ ids }),
513
+ body: JSON.stringify({ ids, reader, mode }),
514
+ });
515
+ }
516
+ /** 列出所有讨论 (含历史, 按参与方筛选) */
517
+ async discussionListAll(participant) {
518
+ return this.publicRequest('/api/discussions/all', {
519
+ method: 'POST',
520
+ body: JSON.stringify({ participant }),
558
521
  });
559
522
  }
560
523
  /** 回复讨论 (公开路由) */
561
- async discussionReply(id, sender, content, newSummary) {
524
+ async discussionReply(id, sender, content, msgSummary, newSummary) {
562
525
  return this.publicRequest(`/api/discussions/${encodeURIComponent(id)}/reply`, {
563
526
  method: 'POST',
564
- body: JSON.stringify({ sender, content, new_summary: newSummary }),
527
+ body: JSON.stringify({ sender, content, msg_summary: msgSummary, new_summary: newSummary }),
528
+ });
529
+ }
530
+ /** 完成讨论 (公开路由, 标记为 completed) */
531
+ async discussionComplete(id) {
532
+ return this.publicRequest(`/api/discussions/${encodeURIComponent(id)}/complete`, {
533
+ method: 'POST',
565
534
  });
566
535
  }
567
536
  /** 删除讨论 (公开路由) */
@@ -577,6 +546,92 @@ export class PpdocsApiClient {
577
546
  body: JSON.stringify({ conclusion }),
578
547
  });
579
548
  }
549
+ // ============ 公共文件池 ============
550
+ /** 列出公共文件 */
551
+ async publicFilesList(dir) {
552
+ const query = dir ? `?dir=${encodeURIComponent(dir)}` : '';
553
+ return this.publicRequest(`/api/public-files${query}`);
554
+ }
555
+ /** 读取公共文件 (文本) */
556
+ async publicFilesRead(filePath) {
557
+ return this.publicRequest(`/api/public-files/read/${cleanPath(filePath)}`);
558
+ }
559
+ /** 创建公共文件池子目录 */
560
+ async publicFilesMkdir(dirPath) {
561
+ return this.publicRequest(`/api/public-files/mkdir`, {
562
+ method: 'POST',
563
+ body: JSON.stringify({ path: dirPath }),
564
+ });
565
+ }
566
+ /** 重命名公共文件或目录 */
567
+ async publicFilesRename(filePath, newName) {
568
+ return this.publicRequest(`/api/public-files/rename`, {
569
+ method: 'POST',
570
+ body: JSON.stringify({ path: filePath, new_name: newName }),
571
+ });
572
+ }
573
+ /** 删除公共文件 */
574
+ async publicFilesDelete(filePath) {
575
+ return this.publicRequest(`/api/public-files/${cleanPath(filePath)}`, {
576
+ method: 'DELETE',
577
+ });
578
+ }
579
+ /** 下载公共文件或目录 */
580
+ async publicFilesDownload(remotePath, localPath) {
581
+ return fetchAndExtractZip(`${this.serverUrl}/api/public-files/download/${cleanPath(remotePath)}`, localPath);
582
+ }
583
+ /** 上传本地目录或单文件到公共文件池 */
584
+ async publicFilesUpload(localPath, remoteDir) {
585
+ const fs = await import('fs');
586
+ const path = await import('path');
587
+ const AdmZip = (await import('adm-zip')).default;
588
+ if (!fs.existsSync(localPath)) {
589
+ throw new Error(`本地路径不存在: ${localPath}`);
590
+ }
591
+ const zip = new AdmZip();
592
+ const stat = fs.statSync(localPath);
593
+ if (stat.isDirectory()) {
594
+ const addDir = (dirPath, zipPath) => {
595
+ for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
596
+ if (entry.name.startsWith('.'))
597
+ continue;
598
+ const fullPath = path.join(dirPath, entry.name);
599
+ if (entry.isDirectory()) {
600
+ if (EXCLUDED_DIRS.has(entry.name))
601
+ continue;
602
+ addDir(fullPath, zipPath ? `${zipPath}/${entry.name}` : entry.name);
603
+ }
604
+ else {
605
+ if (fs.statSync(fullPath).size > 10_485_760)
606
+ continue;
607
+ zip.addLocalFile(fullPath, zipPath || undefined);
608
+ }
609
+ }
610
+ };
611
+ addDir(localPath, '');
612
+ }
613
+ else {
614
+ if (stat.size > 10_485_760)
615
+ throw new Error('文件超过 10MB 限制');
616
+ zip.addLocalFile(localPath);
617
+ }
618
+ const buffer = zip.toBuffer();
619
+ if (buffer.length > 104_857_600) {
620
+ throw new Error('打包后超过 100MB 限制');
621
+ }
622
+ const query = remoteDir ? `?dir=${encodeURIComponent(remoteDir)}` : '';
623
+ const response = await fetch(`${this.serverUrl}/api/public-files/upload${query}`, {
624
+ method: 'POST',
625
+ headers: { 'Content-Type': 'application/octet-stream' },
626
+ body: new Uint8Array(buffer),
627
+ });
628
+ if (!response.ok) {
629
+ const error = await response.json().catch(() => ({ error: response.statusText }));
630
+ throw new Error(error.error || `HTTP ${response.status}`);
631
+ }
632
+ const result = await response.json();
633
+ return { fileCount: result.data?.fileCount || 0 };
634
+ }
580
635
  // ============ 多AI协作会议 ============
581
636
  async meetingJoin(agentId, agentType) {
582
637
  return this.request('/meeting/join', {
@@ -26,27 +26,6 @@ export interface DocNode {
26
26
  isDir: boolean;
27
27
  status?: string;
28
28
  }
29
- /** 文档搜索结果 */
30
- export interface DocSearchResult {
31
- path: string;
32
- name: string;
33
- summary: string;
34
- score: number;
35
- }
36
- export interface ProjectMeta {
37
- projectId: string;
38
- projectName: string;
39
- updatedAt: string;
40
- password?: string;
41
- }
42
- export interface Project {
43
- id: string;
44
- name: string;
45
- description?: string;
46
- updatedAt: string;
47
- createdAt?: string;
48
- projectPath?: string;
49
- }
50
29
  export interface RuleMeta {
51
30
  label: string;
52
31
  keywords: string[];
@@ -1,8 +1,9 @@
1
1
  /**
2
- * 代码分析引擎工具 (4个)
3
- * code_scan, code_query, code_impact, code_context
2
+ * 代码分析引擎工具 (3个)
3
+ * code_scan, code_smart_context, code_full_path
4
4
  *
5
- * AI Agent 通过 MCP 工具直接查询代码结构、依赖关系和影响范围
5
+ * code_smart_context: 代码+KG文档全关联 (含影响范围)
6
+ * code_full_path: 两点间代码链路+共享KG文档
6
7
  */
7
8
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
9
  import { type McpContext } from './shared.js';
@@ -1,8 +1,9 @@
1
1
  /**
2
- * 代码分析引擎工具 (4个)
3
- * code_scan, code_query, code_impact, code_context
2
+ * 代码分析引擎工具 (3个)
3
+ * code_scan, code_smart_context, code_full_path
4
4
  *
5
- * AI Agent 通过 MCP 工具直接查询代码结构、依赖关系和影响范围
5
+ * code_smart_context: 代码+KG文档全关联 (含影响范围)
6
+ * code_full_path: 两点间代码链路+共享KG文档
6
7
  */
7
8
  import { z } from 'zod';
8
9
  import { getClient } from '../storage/httpClient.js';
@@ -13,17 +14,14 @@ const SYMBOL_ICONS = {
13
14
  type_alias: '𝐓', enum: '𝐄', struct: '𝐒', trait: '⚡',
14
15
  constant: '𝐊', variable: '𝑣',
15
16
  };
16
- const SEVERITY_ICONS = {
17
- critical: '🔴', warning: '🟡', info: '🟢',
18
- };
19
17
  export function registerAnalyzerTools(server, ctx) {
20
18
  const client = () => getClient();
21
19
  // ===== code_scan: 扫描项目代码 =====
22
- server.tool('code_scan', '📡 扫描项目代码, 构建索引。返回文件数、符号数、语言统计。★首次使用 code_query/code_impact/code_context 前必须先执行★', {
23
- projectPath: z.string().describe('项目源码的绝对路径(如"D:/projects/myapp")'),
20
+ server.tool('code_scan', '📡 扫描项目代码, 构建索引。返回文件数、符号数、语言统计。★首次使用 code_smart_context/code_full_path 前必须先执行★', {
21
+ projectPath: z.string().optional().describe('项目源码的绝对路径(如"D:/projects/myapp")。不传则自动从Beacon同步目录或项目配置解析'),
24
22
  force: z.boolean().optional().describe('是否强制全量重建(默认false, 增量更新)'),
25
23
  }, async (args) => safeTool(async () => {
26
- const result = await client().analyzerScan(args.projectPath, args.force ?? false);
24
+ const result = await client().analyzerScan(args.projectPath || '', args.force ?? false);
27
25
  return wrap([
28
26
  `✅ 代码扫描完成`,
29
27
  ``,
@@ -34,238 +32,12 @@ export function registerAnalyzerTools(server, ctx) {
34
32
  `- 索引时间: ${result.indexedAt}`,
35
33
  ].join('\n'));
36
34
  }));
37
- // ===== code_query: 搜索代码符号 =====
38
- server.tool('code_query', '🔤 搜索代码符号(函数/类/方法/接口/类型)。返回匹配列表+文件路径+行号。定位代码位置的最快方式。需先运行 code_scan', {
39
- projectPath: z.string().describe('项目源码的绝对路径'),
40
- query: z.string().describe('搜索关键词(函数名/类名/方法名等)'),
41
- }, async (args) => safeTool(async () => {
42
- const results = await client().analyzerQuery(args.projectPath, args.query);
43
- if (!results || results.length === 0) {
44
- return wrap(`未找到匹配 "${args.query}" 的符号。请确认已运行 code_scan`);
45
- }
46
- const lines = results.map(r => {
47
- const icon = SYMBOL_ICONS[r.symbol.kind] || '?';
48
- const exp = r.symbol.exported ? ' [export]' : '';
49
- const parent = r.symbol.parent ? ` ◀ ${r.symbol.parent}` : '';
50
- return ` ${icon} ${r.symbol.name}${parent}${exp} → ${r.filePath}:${r.symbol.lineStart}`;
51
- });
52
- return wrap([
53
- `🔤 找到 ${results.length} 个匹配符号:`,
54
- ``,
55
- ...lines,
56
- ].join('\n'));
57
- }));
58
- // ===== code_impact: 分层影响分析 =====
59
- server.tool('code_impact', '💥 爆炸半径分析 ★修改代码前必查★ 分析修改一个函数/类/类型会影响多少文件。BFS分层输出: L1🔴直接引用=必须检查, L2🟡间接引用=建议检查, L3🟢传递引用=注意。修改任何公共接口、函数签名、类型定义前务必先运行!', {
60
- projectPath: z.string().describe('项目源码的绝对路径'),
61
- symbolName: z.string().describe('要分析的符号名称(如"AuthService", "handleLogin")'),
62
- depth: z.number().optional().describe('分析深度层级(1-5, 默认2)。1=仅直接引用, 3=深度追踪'),
63
- }, async (args) => safeTool(async () => {
64
- const result = await client().analyzerImpactTree(args.projectPath, args.symbolName, args.depth ?? 2);
65
- if (!result) {
66
- return wrap(`未找到符号 "${args.symbolName}"。请确认名称正确且已运行 code_scan`);
67
- }
68
- if (result.levels.length === 0) {
69
- return wrap(`✅ "${args.symbolName}" 没有被其他文件引用, 修改安全`);
70
- }
71
- // 生成分层树形输出
72
- const lines = [
73
- `🎯 ${result.symbolName} (${result.symbolKind})`,
74
- `📍 定义: ${result.definedIn}:${result.lineStart}`,
75
- ``,
76
- `📊 ${result.summary}`,
77
- ``,
78
- ];
79
- for (const level of result.levels) {
80
- const icon = SEVERITY_ICONS[level.severity] || '⚪';
81
- const label = level.depth === 1 ? '直接引用 (必须检查)' :
82
- level.depth === 2 ? '间接引用 (建议检查)' :
83
- '传递引用 (注意)';
84
- lines.push(`### ${icon} L${level.depth} ${label} (${level.count}个)`);
85
- for (const entry of level.entries) {
86
- lines.push(` ${entry.filePath}:${entry.line} [${entry.refKind}] ${entry.context}`);
87
- }
88
- lines.push('');
89
- }
90
- return wrap(lines.join('\n'));
91
- }));
92
- // ===== code_context: 文件360°上下文 =====
93
- server.tool('code_context', '🔍 文件360°上下文 — 定义了什么符号、导入了什么、被谁引用。修改文件前使用, 快速了解所有依赖关系, 避免遗漏', {
94
- projectPath: z.string().describe('项目源码的绝对路径'),
95
- filePath: z.string().describe('目标文件的相对路径(如"src/services/auth.ts")'),
96
- }, async (args) => safeTool(async () => {
97
- const fileCtx = await client().analyzerContext(args.projectPath, args.filePath);
98
- if (!fileCtx) {
99
- return wrap(`未找到文件 "${args.filePath}"。请确认路径正确, 路径格式为相对路径(如 src/main.ts)`);
100
- }
101
- const lines = [
102
- `📄 ${fileCtx.filePath}`,
103
- `语言: ${fileCtx.language} | ${fileCtx.linesTotal} 行`,
104
- ``,
105
- ];
106
- // 定义的符号
107
- if (fileCtx.symbolsDefined.length > 0) {
108
- lines.push(`### 🔤 定义的符号 (${fileCtx.symbolsDefined.length})`);
109
- for (const sym of fileCtx.symbolsDefined) {
110
- const icon = SYMBOL_ICONS[sym.kind] || '?';
111
- const exp = sym.exported ? ' [export]' : '';
112
- lines.push(` ${icon} ${sym.name}${exp} L${sym.lineStart}-L${sym.lineEnd}`);
113
- }
114
- lines.push('');
115
- }
116
- // 导入
117
- if (fileCtx.imports.length > 0) {
118
- lines.push(`### 📥 导入 (${fileCtx.imports.length})`);
119
- for (const imp of fileCtx.imports) {
120
- const specs = imp.specifiers.length > 0 ? `{ ${imp.specifiers.join(', ')} }` : '*';
121
- lines.push(` → ${imp.source} ${specs} L${imp.line}`);
122
- }
123
- lines.push('');
124
- }
125
- // 被谁引用
126
- if (fileCtx.importedBy.length > 0) {
127
- lines.push(`### 📤 被引用 (${fileCtx.importedBy.length})`);
128
- for (const by of fileCtx.importedBy) {
129
- const specs = by.specifiers.length > 0 ? `{ ${by.specifiers.join(', ')} }` : '';
130
- lines.push(` ← ${by.filePath} ${specs}`);
131
- }
132
- lines.push('');
133
- }
134
- return wrap(lines.join('\n'));
135
- }));
136
- // ===== code_path: 两点间链路追踪 =====
137
- server.tool('code_path', '🔗 两点链路追踪 — 给定两个符号(如 fileA.funcA 和 fileB.funcB),自动搜索中间的引用链路,返回完整路径 + 绑定的知识图谱文档', {
138
- projectPath: z.string().describe('项目源码的绝对路径'),
139
- symbolA: z.string().describe('起点符号名(如 "handleLogin")'),
140
- symbolB: z.string().describe('终点符号名(如 "AuthService")'),
141
- maxDepth: z.number().optional().describe('最大搜索深度(1-5, 默认3)'),
142
- }, async (args) => safeTool(async () => {
143
- const depth = Math.min(5, Math.max(1, args.maxDepth ?? 3));
144
- // 1. 获取两个符号的影响树
145
- const [treeA, treeB] = await Promise.all([
146
- client().analyzerImpactTree(args.projectPath, args.symbolA, depth),
147
- client().analyzerImpactTree(args.projectPath, args.symbolB, depth),
148
- ]);
149
- if (!treeA)
150
- return wrap(`❌ 未找到起点符号 "${args.symbolA}"。请确认名称正确且已运行 code_scan`);
151
- if (!treeB)
152
- return wrap(`❌ 未找到终点符号 "${args.symbolB}"。请确认名称正确且已运行 code_scan`);
153
- const graphA = new Map(); // 从 A 出发可达的文件
154
- const graphB = new Map(); // 从 B 出发可达的文件
155
- const buildGraph = (tree, graph) => {
156
- graph.set(tree.definedIn, []);
157
- for (const level of tree.levels) {
158
- for (const entry of level.entries) {
159
- if (!graph.has(entry.filePath))
160
- graph.set(entry.filePath, []);
161
- graph.get(entry.filePath).push({
162
- file: entry.filePath,
163
- line: entry.line,
164
- depth: level.depth,
165
- from: tree.symbolName,
166
- });
167
- }
168
- }
169
- };
170
- buildGraph(treeA, graphA);
171
- buildGraph(treeB, graphB);
172
- // 3. 查找交集: A 和 B 都能到达的文件
173
- const intersection = [];
174
- for (const [fileA] of graphA) {
175
- if (graphB.has(fileA)) {
176
- const refsA = graphA.get(fileA);
177
- const refsB = graphB.get(fileA);
178
- const minDepthA = refsA.length > 0 ? Math.min(...refsA.map(r => r.depth)) : 0;
179
- const minDepthB = refsB.length > 0 ? Math.min(...refsB.map(r => r.depth)) : 0;
180
- intersection.push({ file: fileA, depthFromA: minDepthA, depthFromB: minDepthB });
181
- }
182
- }
183
- // 4. 尝试获取 KG 文档绑定
184
- let docBindings = {};
185
- try {
186
- const docClient = client();
187
- const allDocs = await docClient.listDocs();
188
- for (const doc of allDocs) {
189
- if (!doc.isDir && doc.summary) {
190
- docBindings[doc.name] = doc.summary;
191
- }
192
- }
193
- }
194
- catch { /* KG 不可用时忽略 */ }
195
- // 5. 格式化输出
196
- const lines = [
197
- `🔗 两点链路追踪`,
198
- ``,
199
- `📍 起点: ${treeA.symbolName} (${treeA.symbolKind}) → ${treeA.definedIn}:${treeA.lineStart}`,
200
- `📍 终点: ${treeB.symbolName} (${treeB.symbolKind}) → ${treeB.definedIn}:${treeB.lineStart}`,
201
- ``,
202
- ];
203
- // 直接引用关系
204
- if (treeA.definedIn === treeB.definedIn) {
205
- lines.push(`✅ 两个符号定义在同一文件中!`);
206
- lines.push('');
207
- }
208
- // 检查 A 的影响树是否直接包含 B 的文件
209
- const directAtoB = graphA.has(treeB.definedIn);
210
- const directBtoA = graphB.has(treeA.definedIn);
211
- if (directAtoB || directBtoA) {
212
- lines.push(`### ✅ 发现直接链路`);
213
- if (directAtoB) {
214
- const refs = graphA.get(treeB.definedIn);
215
- lines.push(` ${treeA.symbolName} → [L${refs[0]?.depth || 1}] → ${treeB.definedIn} (含 ${treeB.symbolName})`);
216
- }
217
- if (directBtoA) {
218
- const refs = graphB.get(treeA.definedIn);
219
- lines.push(` ${treeB.symbolName} → [L${refs[0]?.depth || 1}] → ${treeA.definedIn} (含 ${treeA.symbolName})`);
220
- }
221
- lines.push('');
222
- }
223
- if (intersection.length > 0) {
224
- // 按总深度排序
225
- intersection.sort((a, b) => (a.depthFromA + a.depthFromB) - (b.depthFromA + b.depthFromB));
226
- lines.push(`### 🔗 共经文件 (${intersection.length}个)`);
227
- lines.push(`| 文件 | 距${treeA.symbolName} | 距${treeB.symbolName} | 📚 绑定文档 |`);
228
- lines.push(`|:---|:---:|:---:|:---|`);
229
- for (const node of intersection.slice(0, 20)) {
230
- const fileName = node.file.split('/').pop() || node.file;
231
- const docBind = docBindings[fileName] || '—';
232
- lines.push(`| ${node.file} | L${node.depthFromA} | L${node.depthFromB} | ${docBind} |`);
233
- }
234
- if (intersection.length > 20) {
235
- lines.push(`| ... | | | (还有 ${intersection.length - 20} 个) |`);
236
- }
237
- lines.push('');
238
- }
239
- else if (!directAtoB && !directBtoA) {
240
- lines.push(`⚠️ 在 ${depth} 层深度内未发现 ${treeA.symbolName} 与 ${treeB.symbolName} 的引用链路`);
241
- lines.push(`建议: 增加 maxDepth 参数(当前=${depth}, 最大=5) 重试`);
242
- lines.push('');
243
- }
244
- // 附加: 各符号的影响概要
245
- lines.push(`### 📊 影响概要`);
246
- lines.push(`- ${treeA.symbolName}: ${treeA.summary}`);
247
- lines.push(`- ${treeB.symbolName}: ${treeB.summary}`);
248
- // 附加: 绑定的 KG 文档
249
- const relevantDocs = [];
250
- const symbolNames = [treeA.symbolName, treeB.symbolName];
251
- for (const [docName, docSummary] of Object.entries(docBindings)) {
252
- if (symbolNames.some(s => docName.toLowerCase().includes(s.toLowerCase()) || s.toLowerCase().includes(docName.toLowerCase()))) {
253
- relevantDocs.push(` 📚 ${docName}: ${docSummary}`);
254
- }
255
- }
256
- if (relevantDocs.length > 0) {
257
- lines.push('');
258
- lines.push(`### 📚 相关知识图谱文档`);
259
- lines.push(...relevantDocs);
260
- }
261
- return wrap(lines.join('\n'));
262
- }));
263
35
  // ===== code_smart_context: 代码+文档全关联上下文 =====
264
- server.tool('code_smart_context', '🔍 代码+文档全关联上下文 — 输入一个函数名,一次调用返回:代码依赖、关联文档、匹配规则、活跃任务、影响范围摘要。需先运行 code_scan', {
265
- projectPath: z.string().describe('项目源码的绝对路径'),
36
+ server.tool('code_smart_context', '🔍 代码+文档全关联上下文 — 输入一个函数名,一次调用返回:代码依赖、关联文档、匹配规则、活跃任务、影响范围摘要。需先运行 code_scan', {
37
+ projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
266
38
  symbolName: z.string().describe('要查询的符号名称(如"handleLogin", "AuthService")'),
267
39
  }, async (args) => safeTool(async () => {
268
- const smartCtx = await client().analyzerSmartContext(args.projectPath, args.symbolName);
40
+ const smartCtx = await client().analyzerSmartContext(args.projectPath || '', args.symbolName);
269
41
  if (!smartCtx) {
270
42
  return wrap(`未找到符号 "${args.symbolName}"。请确认名称正确且已运行 code_scan`);
271
43
  }
@@ -321,13 +93,13 @@ export function registerAnalyzerTools(server, ctx) {
321
93
  return wrap(lines.join('\n'));
322
94
  }));
323
95
  // ===== code_full_path: 全关联路径 =====
324
- server.tool('code_full_path', '🔗 全关联路径 — 两个符号之间不仅返回代码引用链路,还返回共享的KG文档、共同导入、祖先模块。需先运行 code_scan', {
325
- projectPath: z.string().describe('项目源码的绝对路径'),
96
+ server.tool('code_full_path', '🔗 全关联路径 — 两个符号之间不仅返回代码引用链路,还返回共享的KG文档、共同导入、祖先模块。需先运行 code_scan', {
97
+ projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
326
98
  symbolA: z.string().describe('起点符号名(如 "handleLogin")'),
327
99
  symbolB: z.string().describe('终点符号名(如 "AuthService")'),
328
100
  maxDepth: z.number().optional().describe('最大搜索深度(1-5, 默认3)'),
329
101
  }, async (args) => safeTool(async () => {
330
- const result = await client().analyzerFullPath(args.projectPath, args.symbolA, args.symbolB, args.maxDepth ?? 3);
102
+ const result = await client().analyzerFullPath(args.projectPath || '', args.symbolA, args.symbolB, args.maxDepth ?? 3);
331
103
  if (!result) {
332
104
  return wrap(`❌ 无法构建路径。请确认两个符号名称正确且已运行 code_scan`);
333
105
  }
@@ -41,40 +41,52 @@ function formatList(items, ctx) {
41
41
  }
42
42
  return lines.join('\n');
43
43
  }
44
- function formatDetail(d) {
44
+ function formatDetailView(d) {
45
45
  const msgs = d.messages || [];
46
46
  const lines = [
47
47
  `💬 ${d.title}`,
48
48
  `发起: ${d.initiator} | 参与: ${d.participants.length}方 | 状态: ${d.status}`,
49
49
  `📌 ${d.summary}`,
50
+ `📊 总消息: ${d.totalCount} | 未读: ${d.unreadCount}`,
50
51
  ``,
51
52
  ];
52
53
  msgs.forEach((m, i) => {
53
- lines.push(`--- 消息 ${i + 1}/${msgs.length} [${m.sender}] ${relativeTime(m.timestamp)} ---`);
54
- lines.push(m.content);
54
+ const readTag = m.isRead ? '📖' : '🆕';
55
+ lines.push(`--- ${readTag} 消息 ${i + 1}/${msgs.length} [${m.sender}] ${relativeTime(m.timestamp)} ---`);
56
+ // 已读且无 content → 显示摘要
57
+ if (m.isRead && !m.content) {
58
+ lines.push(`[摘要] ${m.summary}`);
59
+ }
60
+ else {
61
+ lines.push(m.content || m.summary || '(无内容)');
62
+ }
55
63
  lines.push('');
56
64
  });
57
65
  return lines.join('\n');
58
66
  }
59
67
  export function registerDiscussionTools(server, ctx) {
60
68
  const client = () => getClient();
61
- server.tool('kg_discuss', '💬 跨项目讨论 — 发起、回复、归档协同讨论。action: list(列出活跃讨论)|read(读取详情)|create(发起)|reply(回复)|close(结案归档)|delete(删除)', {
62
- action: z.enum(['list', 'read', 'create', 'reply', 'close', 'delete'])
69
+ server.tool('kg_discuss', '💬 跨项目讨论 — 发起、回复、归档协同讨论。action: list(列出活跃讨论)|read(读取详情,自动追踪已读)|create(发起)|reply(回复)|complete(标记完成)|close(结案归档)|delete(删除)|history(查看历史讨论)', {
70
+ action: z.enum(['list', 'read', 'create', 'reply', 'complete', 'close', 'delete', 'history'])
63
71
  .describe('操作类型'),
64
72
  id: z.string().optional()
65
- .describe('讨论哈希ID (read/reply/close/delete)'),
73
+ .describe('讨论哈希ID (read/reply/complete/close/delete)'),
66
74
  ids: z.array(z.string()).optional()
67
75
  .describe('批量读取的讨论ID数组 (read)'),
68
76
  title: z.string().optional()
69
77
  .describe('讨论标题 (create)'),
70
78
  participants: z.array(z.string()).optional()
71
79
  .describe('参与项目ID数组 (create, 不含自己)'),
80
+ summary: z.string().optional()
81
+ .describe('消息摘要 (create/reply, 不传则自动截取content前50字)'),
72
82
  content: z.string().optional()
73
- .describe('消息内容Markdown (create/reply)'),
83
+ .describe('消息详细内容Markdown (create/reply)'),
74
84
  conclusion: z.string().optional()
75
85
  .describe('结案总结 (close)'),
76
86
  newSummary: z.string().optional()
77
- .describe('更新进展摘要 (reply)'),
87
+ .describe('更新讨论进展摘要 (reply)'),
88
+ mode: z.enum(['auto', 'full']).optional()
89
+ .describe('读取模式 (read, 默认auto: 已读消息仅返回摘要)'),
78
90
  }, async (args) => safeTool(async () => {
79
91
  const decoded = decodeObjectStrings(args);
80
92
  const me = sender(ctx);
@@ -89,10 +101,11 @@ export function registerDiscussionTools(server, ctx) {
89
101
  const readIds = decoded.ids || (decoded.id ? [decoded.id] : []);
90
102
  if (readIds.length === 0)
91
103
  return wrap('❌ read 需要 id 或 ids');
92
- const discussions = await client().discussionReadByIds(readIds);
104
+ const readMode = decoded.mode || 'auto';
105
+ const discussions = await client().discussionReadByIds(readIds, me, readMode);
93
106
  if (!Array.isArray(discussions) || discussions.length === 0)
94
107
  return wrap('未找到对应的讨论记录');
95
- return wrap(discussions.map(formatDetail).join('\n\n━━━━━━━━━━━━━━━━━━━━\n\n'));
108
+ return wrap(discussions.map(formatDetailView).join('\n\n━━━━━━━━━━━━━━━━━━━━\n\n'));
96
109
  }
97
110
  case 'create': {
98
111
  if (!decoded.title)
@@ -101,7 +114,7 @@ export function registerDiscussionTools(server, ctx) {
101
114
  return wrap('❌ create 需要 participants');
102
115
  if (!decoded.content)
103
116
  return wrap('❌ create 需要 content');
104
- const result = await client().discussionCreate(decoded.title, me, decoded.participants, decoded.content);
117
+ const result = await client().discussionCreate(decoded.title, me, decoded.participants, decoded.content, decoded.summary);
105
118
  return wrap(`✅ 讨论已发起\nID: ${result.id}\n发起方: ${me}\n参与方: ${decoded.participants.join(', ')}`);
106
119
  }
107
120
  case 'reply': {
@@ -109,9 +122,33 @@ export function registerDiscussionTools(server, ctx) {
109
122
  return wrap('❌ reply 需要 id');
110
123
  if (!decoded.content)
111
124
  return wrap('❌ reply 需要 content');
112
- await client().discussionReply(decoded.id, me, decoded.content, decoded.newSummary);
125
+ await client().discussionReply(decoded.id, me, decoded.content, decoded.summary, decoded.newSummary);
113
126
  return wrap(`✅ 回复成功 (ID: ${decoded.id}, 身份: ${me})`);
114
127
  }
128
+ case 'history': {
129
+ const all = await client().discussionListAll(me);
130
+ if (!Array.isArray(all) || all.length === 0)
131
+ return wrap(`暂无参与过的讨论记录 (身份: ${me})`);
132
+ const lines = [
133
+ `本项目: ${me}`,
134
+ ``,
135
+ `📋 讨论历史 (${all.length}个)`,
136
+ ``,
137
+ `| ID | 标题 | 发起方 | 状态 | 消息数 | 更新 |`,
138
+ `|:---|:---|:---|:---|:---:|:---|`,
139
+ ];
140
+ for (const d of all) {
141
+ const statusIcon = d.status === 'active' ? '🟢' : '⚪';
142
+ lines.push(`| ${d.id} | ${d.title} | ${d.initiator} | ${statusIcon} ${d.status} | ${d.messageCount ?? 0} | ${relativeTime(d.updatedAt)} |`);
143
+ }
144
+ return wrap(lines.join('\n'));
145
+ }
146
+ case 'complete': {
147
+ if (!decoded.id)
148
+ return wrap('❌ complete 需要 id');
149
+ await client().discussionComplete(decoded.id);
150
+ return wrap(`✅ 讨论已标记完成 (ID: ${decoded.id})`);
151
+ }
115
152
  case 'close': {
116
153
  if (!decoded.id)
117
154
  return wrap('❌ close 需要 id');
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 📁 kg_files (5→1)
3
3
  * 合并: project_list_files, project_read_file, project_upload, project_download
4
- * 删除: project_clear_files (危险操作)
4
+ * + 公共文件池: public_list, public_read, public_upload, public_download, public_delete
5
5
  */
6
6
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
7
  export declare function registerFileTools(server: McpServer): void;
@@ -1,26 +1,28 @@
1
1
  /**
2
2
  * 📁 kg_files (5→1)
3
3
  * 合并: project_list_files, project_read_file, project_upload, project_download
4
- * 删除: project_clear_files (危险操作)
4
+ * + 公共文件池: public_list, public_read, public_upload, public_download, public_delete
5
5
  */
6
6
  import { z } from 'zod';
7
7
  import { getClient } from '../storage/httpClient.js';
8
8
  import { wrap, safeTool, crossPrefix, formatFileSize } from './shared.js';
9
9
  export function registerFileTools(server) {
10
10
  const client = () => getClient();
11
- server.tool('kg_files', '📁 项目文件操作 — 浏览目录、读取文件、上传下载。action: list(目录浏览)|read(读取文件)|upload(上传目录)|download(下载文件)', {
12
- action: z.enum(['list', 'read', 'upload', 'download'])
11
+ server.tool('kg_files', '📁 项目文件操作 — 浏览目录、读取文件、上传下载。action: list(目录浏览)|read(读取文件)|upload(上传)|download(下载文件)|public_list(公共文件池浏览)|public_read(读取公共文件)|public_upload(上传到公共池)|public_download(下载公共文件)|public_delete(删除公共文件)|public_mkdir(创建公共池子目录)|public_rename(重命名公共文件)', {
12
+ action: z.enum(['list', 'read', 'upload', 'download', 'public_list', 'public_read', 'public_upload', 'public_download', 'public_delete', 'public_mkdir', 'public_rename'])
13
13
  .describe('操作类型'),
14
14
  path: z.string().optional()
15
- .describe('文件路径 (read/download, 如"src/main.ts")'),
15
+ .describe('文件路径 (read/download/public_read/public_download/public_delete/public_mkdir, 如"src/main.ts")'),
16
16
  dir: z.string().optional()
17
- .describe('子目录路径 (list, 如"src/components")'),
17
+ .describe('子目录路径 (list/public_list, 如"src/components")'),
18
18
  localDir: z.string().optional()
19
- .describe('本地目录绝对路径 (upload)'),
19
+ .describe('本地路径(目录或单文件) (upload/public_upload)'),
20
20
  localPath: z.string().optional()
21
- .describe('本地保存路径 (download)'),
21
+ .describe('本地保存路径 (download/public_download)'),
22
22
  remoteDir: z.string().optional()
23
- .describe('远程目标子目录 (upload)'),
23
+ .describe('远程目标子目录 (upload/public_upload)'),
24
+ newName: z.string().optional()
25
+ .describe('新名称 (public_rename)'),
24
26
  targetProject: z.string().optional()
25
27
  .describe('跨项目操作的目标项目ID'),
26
28
  }, async (args) => safeTool(async () => {
@@ -64,6 +66,57 @@ export function registerFileTools(server) {
64
66
  const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
65
67
  return wrap(`${prefix}✅ 已下载\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}`);
66
68
  }
69
+ // ===== 公共文件池 =====
70
+ case 'public_list': {
71
+ const files = await client().publicFilesList(args.dir);
72
+ if (files.length === 0)
73
+ return wrap('📦 公共文件池' + (args.dir ? ` /${args.dir}` : '') + ' 为空');
74
+ const lines = files.map(f => {
75
+ const icon = f.isDir ? '📁' : '📄';
76
+ const size = f.isDir ? '' : ` (${formatFileSize(f.size)})`;
77
+ return `${icon} ${f.name}${size}`;
78
+ });
79
+ const dirLabel = args.dir || '/';
80
+ return wrap(`📦 公共文件池 ${dirLabel} (${files.length} 项)\n\n${lines.join('\n')}`);
81
+ }
82
+ case 'public_read': {
83
+ if (!args.path)
84
+ return wrap('❌ public_read 需要 path');
85
+ const content = await client().publicFilesRead(args.path);
86
+ return wrap(`📦 公共文件 ${args.path}\n\n\`\`\`\n${content}\n\`\`\``);
87
+ }
88
+ case 'public_upload': {
89
+ if (!args.localDir)
90
+ return wrap('❌ public_upload 需要 localDir');
91
+ const result = await client().publicFilesUpload(args.localDir, args.remoteDir);
92
+ return wrap(`✅ 已上传到公共文件池\n\n- 文件数量: ${result.fileCount}\n- 目标目录: ${args.remoteDir || '/'}`);
93
+ }
94
+ case 'public_download': {
95
+ if (!args.path)
96
+ return wrap('❌ public_download 需要 path');
97
+ const result = await client().publicFilesDownload(args.path, args.localPath);
98
+ return wrap(`✅ 已从公共文件池下载\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}`);
99
+ }
100
+ case 'public_delete': {
101
+ if (!args.path)
102
+ return wrap('❌ public_delete 需要 path');
103
+ await client().publicFilesDelete(args.path);
104
+ return wrap(`✅ 已从公共文件池删除: ${args.path}`);
105
+ }
106
+ case 'public_mkdir': {
107
+ if (!args.path)
108
+ return wrap('❌ public_mkdir 需要 path');
109
+ await client().publicFilesMkdir(args.path);
110
+ return wrap(`✅ 已创建公共文件池目录: /${args.path}`);
111
+ }
112
+ case 'public_rename': {
113
+ if (!args.path)
114
+ return wrap('❌ public_rename 需要 path');
115
+ if (!args.newName)
116
+ return wrap('❌ public_rename 需要 newName');
117
+ await client().publicFilesRename(args.path, args.newName);
118
+ return wrap(`✅ 已重命名: ${args.path} → ${args.newName}`);
119
+ }
67
120
  default:
68
121
  return wrap(`❌ 未知 action: ${args.action}`);
69
122
  }
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * MCP 工具注册入口
3
- * 18 个工具, 7 个子模块
3
+ * 14 个工具, 7 个子模块
4
4
  *
5
5
  * 🔗 初始化: kg_init (1个)
6
6
  * 📊 导航: kg_status, kg_tree (2个)
7
7
  * 📚 知识: kg_doc, kg_projects, kg_rules (3个)
8
8
  * 📝 工作流: kg_task, kg_files, kg_discuss (3个)
9
- * 🔬 代码分析: code_scan, code_query, code_impact, code_context, code_path, code_smart_context, code_full_path (7个)
9
+ * 🔬 代码分析: code_scan, code_smart_context, code_full_path (3个)
10
10
  * 🏛️ 协作: kg_meeting (1个)
11
11
  */
12
12
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * MCP 工具注册入口
3
- * 18 个工具, 7 个子模块
3
+ * 14 个工具, 7 个子模块
4
4
  *
5
5
  * 🔗 初始化: kg_init (1个)
6
6
  * 📊 导航: kg_status, kg_tree (2个)
7
7
  * 📚 知识: kg_doc, kg_projects, kg_rules (3个)
8
8
  * 📝 工作流: kg_task, kg_files, kg_discuss (3个)
9
- * 🔬 代码分析: code_scan, code_query, code_impact, code_context, code_path, code_smart_context, code_full_path (7个)
9
+ * 🔬 代码分析: code_scan, code_smart_context, code_full_path (3个)
10
10
  * 🏛️ 协作: kg_meeting (1个)
11
11
  */
12
12
  import { createContext } from './shared.js';
@@ -35,7 +35,7 @@ export function registerTools(server, projectId, user, onProjectChange) {
35
35
  registerTaskTools(server, ctx);
36
36
  registerFileTools(server);
37
37
  registerDiscussionTools(server, ctx);
38
- // 🔬 代码分析 (code_scan, code_query, code_impact, code_context, code_path, code_smart_context, code_full_path)
38
+ // 🔬 代码分析 (code_scan, code_smart_context, code_full_path)
39
39
  registerAnalyzerTools(server, ctx);
40
40
  // 🏛️ 多AI协作 (kg_meeting)
41
41
  registerMeetingTools(server, ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "3.2.20",
3
+ "version": "3.2.22",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",