@ppdocs/mcp 3.2.18 → 3.2.20

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/dist/cli.js CHANGED
@@ -89,10 +89,12 @@ function showHelp() {
89
89
  ppdocs MCP CLI
90
90
 
91
91
  Commands:
92
- init Initialize ppdocs config + install workflow templates
92
+ init Full setup: .ppdocs + templates + MCP registration
93
+ bind Lightweight: only create .ppdocs (for adding projects to existing MCP)
93
94
 
94
95
  Usage:
95
96
  npx @ppdocs/mcp init -p <projectId> -k <key> [options]
97
+ npx @ppdocs/mcp bind -p <projectId> -k <key> [options]
96
98
 
97
99
  Options:
98
100
  -p, --project Project ID (required)
@@ -105,51 +107,55 @@ Options:
105
107
  --sync <id> Sync knowledge base docs from a source project to ./docs/ppdocs/
106
108
 
107
109
  Example:
108
- npx @ppdocs/mcp init -p myproject -k abc123xyz
109
- npx @ppdocs/mcp init -p myproject -k abc123xyz --codex
110
- npx @ppdocs/mcp init -p myproject -k abc123xyz --from _templates
111
- npx @ppdocs/mcp init -p myproject -k abc123xyz --sync shared-rules
110
+ npx @ppdocs/mcp init -p myproject -k abc123xyz --api 192.168.1.100
111
+ npx @ppdocs/mcp bind -p myproject -k abc123xyz --api 192.168.1.100
112
112
  `);
113
113
  }
114
114
  export async function runCli(args) {
115
115
  const cmd = args[0];
116
- if (cmd === 'init') {
116
+ if (cmd === 'init' || cmd === 'bind') {
117
117
  const opts = parseArgs(args.slice(1));
118
118
  if (!opts) {
119
119
  console.error('Error: -p (project) and -k (key) are required\n');
120
120
  showHelp();
121
121
  process.exit(1);
122
122
  }
123
- await initProject(opts);
123
+ if (cmd === 'bind') {
124
+ bindProject(opts);
125
+ }
126
+ else {
127
+ await initProject(opts);
128
+ }
124
129
  return true;
125
130
  }
126
131
  if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
127
132
  showHelp();
128
133
  return true;
129
134
  }
130
- return false; // Not a CLI command, run as MCP server
135
+ return false;
131
136
  }
132
- async function initProject(opts) {
133
- const cwd = process.cwd();
137
+ /** 轻量绑定: 仅创建 .ppdocs, 不注册 MCP 不安装模板 */
138
+ function bindProject(opts) {
139
+ writePpdocsConfig(process.cwd(), opts);
140
+ console.log(`\n🔗 Project bound: ${opts.project}\n AI will auto-connect via kg_init when opening this directory.\n`);
141
+ }
142
+ /** 写入 .ppdocs 配置文件 (init + bind 复用) */
143
+ function writePpdocsConfig(cwd, opts) {
134
144
  const apiUrl = `http://${opts.api}:${opts.port}/api/${opts.project}/${opts.key}`;
135
- // Create .ppdocs config
136
- const ppdocsConfig = {
137
- api: `http://${opts.api}:${opts.port}`,
138
- projectId: opts.project,
139
- key: opts.key,
140
- user: opts.user,
141
- };
142
145
  const ppdocsPath = path.join(cwd, '.ppdocs');
143
- // 检查是否存在同名目录,如果是则删除
144
- if (fs.existsSync(ppdocsPath)) {
145
- const stat = fs.statSync(ppdocsPath);
146
- if (stat.isDirectory()) {
147
- fs.rmSync(ppdocsPath, { recursive: true });
148
- console.log(`⚠️ Removed existing .ppdocs directory`);
149
- }
146
+ if (fs.existsSync(ppdocsPath) && fs.statSync(ppdocsPath).isDirectory()) {
147
+ fs.rmSync(ppdocsPath, { recursive: true });
148
+ console.log(`⚠️ Removed existing .ppdocs directory`);
150
149
  }
151
- fs.writeFileSync(ppdocsPath, JSON.stringify(ppdocsConfig, null, 2));
150
+ fs.writeFileSync(ppdocsPath, JSON.stringify({
151
+ api: `http://${opts.api}:${opts.port}`, projectId: opts.project, key: opts.key, user: opts.user,
152
+ }, null, 2));
152
153
  console.log(`✅ Created ${ppdocsPath}`);
154
+ return apiUrl;
155
+ }
156
+ async function initProject(opts) {
157
+ const cwd = process.cwd();
158
+ const apiUrl = writePpdocsConfig(cwd, opts);
153
159
  // Install workflow templates based on IDE detection
154
160
  const detectedIdes = detectIDEs(cwd);
155
161
  let hasIdeDir = false;
@@ -179,7 +185,7 @@ async function initProject(opts) {
179
185
  }
180
186
  }
181
187
  // 自动检测并注册 MCP (如果已写入 Antigravity 配置,跳过 gemini CLI 注册避免冲突)
182
- const registered = autoRegisterMcp(apiUrl, opts.user, detectedIdes.includes('antigravity'));
188
+ const registered = autoRegisterMcp(detectedIdes.includes('antigravity'));
183
189
  // 如果没有检测到任何 AI CLI,并且也没有检测到配置文件夹,创建 .mcp.json 作为备用
184
190
  if (!registered && !hasIdeDir) {
185
191
  createMcpJson(cwd, apiUrl, opts.user);
@@ -276,31 +282,39 @@ function execSilent(cmd) {
276
282
  }
277
283
  catch { /* ignore */ }
278
284
  }
279
- /** 自动检测 AI CLI 并注册 MCP (注入环境变量实现启动即就绪) */
280
- function autoRegisterMcp(apiUrl, user, skipGemini = false) {
285
+ /** 自动检测 AI CLI 并注册全局 MCP (不注入 env,支持多项目通过 kg_init 切换) */
286
+ function autoRegisterMcp(skipGemini = false) {
281
287
  const detected = [];
282
288
  const serverName = 'ppdocs-kg';
283
- const envFlags = `-e PPDOCS_API_URL=${apiUrl} -e PPDOCS_USER=${user}`;
284
- // Claude CLI
289
+ const addCmd = `-- npx -y @ppdocs/mcp@latest`;
290
+ // Claude CLI (幂等:已注册则跳过)
285
291
  if (commandExists('claude')) {
286
292
  detected.push('Claude');
287
293
  try {
288
- console.log(`✅ Detected Claude CLI, registering MCP...`);
289
- // 先移除旧配置(可能无 env),再重新注册
290
- execSilent(`claude mcp remove ${serverName}`);
291
- execSync(`claude mcp add ${serverName} ${envFlags} -- npx -y @ppdocs/mcp@latest`, { stdio: 'inherit' });
294
+ const list = execSync(`claude mcp list`, { encoding: 'utf-8' });
295
+ if (list.includes(serverName)) {
296
+ console.log(`✅ Claude MCP already configured`);
297
+ }
298
+ else {
299
+ execSync(`claude mcp add ${serverName} ${addCmd}`, { stdio: 'inherit' });
300
+ }
292
301
  }
293
302
  catch {
294
- console.log(`⚠️ Claude MCP registration failed`);
303
+ try {
304
+ execSilent(`claude mcp remove ${serverName}`);
305
+ execSync(`claude mcp add ${serverName} ${addCmd}`, { stdio: 'inherit' });
306
+ }
307
+ catch {
308
+ console.log(`⚠️ Claude MCP registration failed`);
309
+ }
295
310
  }
296
311
  }
297
- // Codex CLI (OpenAI)
312
+ // Codex CLI
298
313
  if (commandExists('codex')) {
299
314
  detected.push('Codex');
300
315
  try {
301
- console.log(`✅ Detected Codex CLI, registering MCP...`);
302
316
  execSilent(`codex mcp remove ${serverName}`);
303
- execSync(`codex mcp add ${serverName} ${envFlags} -- npx -y @ppdocs/mcp@latest`, { stdio: 'inherit' });
317
+ execSync(`codex mcp add ${serverName} ${addCmd}`, { stdio: 'inherit' });
304
318
  }
305
319
  catch {
306
320
  console.log(`⚠️ Codex MCP registration failed`);
@@ -310,7 +324,6 @@ function autoRegisterMcp(apiUrl, user, skipGemini = false) {
310
324
  if (commandExists('gemini') && !skipGemini) {
311
325
  detected.push('Gemini');
312
326
  try {
313
- console.log(`✅ Detected Gemini CLI, registering MCP...`);
314
327
  execSilent(`gemini mcp remove ${serverName}`);
315
328
  execSync(`gemini mcp add ${serverName} "npx -y @ppdocs/mcp@latest"`, { stdio: 'inherit' });
316
329
  }
@@ -104,6 +104,33 @@ export declare class PpdocsApiClient {
104
104
  analyzerImpactTree(projectPath: string, symbolName: string, depth?: number): Promise<unknown>;
105
105
  /** 获取文件 360° 上下文 */
106
106
  analyzerContext(projectPath: string, filePath: string): Promise<unknown>;
107
+ /** 智能上下文: 代码+文档+规则+任务全关联 */
108
+ analyzerSmartContext(projectPath: string, symbolName: string): Promise<unknown>;
109
+ /** 全关联路径: 两个符号之间的代码+文档路径 */
110
+ analyzerFullPath(projectPath: string, symbolA: string, symbolB: string, maxDepth?: number): Promise<unknown>;
111
+ private publicRequest;
112
+ /** 列出活跃讨论 (公开路由) */
113
+ discussionList(): Promise<unknown>;
114
+ /** 创建讨论 (公开路由) */
115
+ discussionCreate(title: string, initiator: string, participants: string[], content: string): Promise<{
116
+ id: string;
117
+ }>;
118
+ /** 批量读取讨论 (公开路由) */
119
+ discussionReadByIds(ids: string[]): Promise<unknown>;
120
+ /** 回复讨论 (公开路由) */
121
+ discussionReply(id: string, sender: string, content: string, newSummary?: string): Promise<boolean>;
122
+ /** 删除讨论 (公开路由) */
123
+ discussionDelete(id: string): Promise<boolean>;
124
+ /** 结案归档讨论 (认证路由) */
125
+ discussionClose(id: string, conclusion: string): Promise<{
126
+ archived_path: string;
127
+ }>;
128
+ meetingJoin(agentId: string, agentType: string): Promise<unknown>;
129
+ meetingLeave(agentId: string): Promise<unknown>;
130
+ meetingPost(agentId: string, content: string, msgType?: string): Promise<unknown>;
131
+ meetingClaim(agentId: string, filePath: string): Promise<unknown>;
132
+ meetingRelease(agentId: string, filePath: string): Promise<unknown>;
133
+ meetingStatus(): Promise<unknown>;
107
134
  }
108
135
  export declare function initClient(apiUrl: string): void;
109
136
  export declare function getClient(): PpdocsApiClient;
@@ -509,6 +509,108 @@ export class PpdocsApiClient {
509
509
  body: JSON.stringify({ project_path: projectPath, file_path: filePath }),
510
510
  });
511
511
  }
512
+ /** 智能上下文: 代码+文档+规则+任务全关联 */
513
+ async analyzerSmartContext(projectPath, symbolName) {
514
+ return this.request('/analyzer/smart-context', {
515
+ method: 'POST',
516
+ body: JSON.stringify({ project_path: projectPath, symbol_name: symbolName }),
517
+ });
518
+ }
519
+ /** 全关联路径: 两个符号之间的代码+文档路径 */
520
+ async analyzerFullPath(projectPath, symbolA, symbolB, maxDepth = 3) {
521
+ return this.request('/analyzer/full-path', {
522
+ method: 'POST',
523
+ body: JSON.stringify({ project_path: projectPath, symbol_a: symbolA, symbol_b: symbolB, max_depth: maxDepth }),
524
+ });
525
+ }
526
+ // ============ 公开路由请求工具 (无需项目认证) ============
527
+ async publicRequest(path, options) {
528
+ const response = await fetch(`${this.serverUrl}${path}`, {
529
+ headers: { 'Content-Type': 'application/json' },
530
+ ...options,
531
+ });
532
+ if (!response.ok) {
533
+ const error = await response.json().catch(() => ({ error: response.statusText }));
534
+ throw new Error(error.error || `HTTP ${response.status}`);
535
+ }
536
+ const json = await response.json();
537
+ if (!json.success)
538
+ throw new Error(json.error || 'Unknown error');
539
+ return json.data;
540
+ }
541
+ // ============ 讨论系统 ============
542
+ /** 列出活跃讨论 (公开路由) */
543
+ async discussionList() {
544
+ return this.publicRequest('/api/discussions');
545
+ }
546
+ /** 创建讨论 (公开路由) */
547
+ async discussionCreate(title, initiator, participants, content) {
548
+ return this.publicRequest('/api/discussions', {
549
+ method: 'POST',
550
+ body: JSON.stringify({ title, initiator, participants, content }),
551
+ });
552
+ }
553
+ /** 批量读取讨论 (公开路由) */
554
+ async discussionReadByIds(ids) {
555
+ return this.publicRequest('/api/discussions/read', {
556
+ method: 'POST',
557
+ body: JSON.stringify({ ids }),
558
+ });
559
+ }
560
+ /** 回复讨论 (公开路由) */
561
+ async discussionReply(id, sender, content, newSummary) {
562
+ return this.publicRequest(`/api/discussions/${encodeURIComponent(id)}/reply`, {
563
+ method: 'POST',
564
+ body: JSON.stringify({ sender, content, new_summary: newSummary }),
565
+ });
566
+ }
567
+ /** 删除讨论 (公开路由) */
568
+ async discussionDelete(id) {
569
+ return this.publicRequest(`/api/discussions/${encodeURIComponent(id)}`, {
570
+ method: 'DELETE',
571
+ });
572
+ }
573
+ /** 结案归档讨论 (认证路由) */
574
+ async discussionClose(id, conclusion) {
575
+ return this.request(`/discussions/${encodeURIComponent(id)}/close`, {
576
+ method: 'POST',
577
+ body: JSON.stringify({ conclusion }),
578
+ });
579
+ }
580
+ // ============ 多AI协作会议 ============
581
+ async meetingJoin(agentId, agentType) {
582
+ return this.request('/meeting/join', {
583
+ method: 'POST',
584
+ body: JSON.stringify({ agent_id: agentId, agent_type: agentType }),
585
+ });
586
+ }
587
+ async meetingLeave(agentId) {
588
+ return this.request('/meeting/leave', {
589
+ method: 'POST',
590
+ body: JSON.stringify({ agent_id: agentId }),
591
+ });
592
+ }
593
+ async meetingPost(agentId, content, msgType = 'status') {
594
+ return this.request('/meeting/post', {
595
+ method: 'POST',
596
+ body: JSON.stringify({ agent_id: agentId, content, msg_type: msgType }),
597
+ });
598
+ }
599
+ async meetingClaim(agentId, filePath) {
600
+ return this.request('/meeting/claim', {
601
+ method: 'POST',
602
+ body: JSON.stringify({ agent_id: agentId, file_path: filePath }),
603
+ });
604
+ }
605
+ async meetingRelease(agentId, filePath) {
606
+ return this.request('/meeting/release', {
607
+ method: 'POST',
608
+ body: JSON.stringify({ agent_id: agentId, file_path: filePath }),
609
+ });
610
+ }
611
+ async meetingStatus() {
612
+ return this.request('/meeting/status');
613
+ }
512
614
  }
513
615
  // ============ 模块级 API ============
514
616
  let client = null;
@@ -5,4 +5,5 @@
5
5
  * 让 AI Agent 通过 MCP 工具直接查询代码结构、依赖关系和影响范围
6
6
  */
7
7
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
- export declare function registerAnalyzerTools(server: McpServer, projectId: string): void;
8
+ import { type McpContext } from './shared.js';
9
+ export declare function registerAnalyzerTools(server: McpServer, ctx: McpContext): void;
@@ -16,7 +16,7 @@ const SYMBOL_ICONS = {
16
16
  const SEVERITY_ICONS = {
17
17
  critical: '🔴', warning: '🟡', info: '🟢',
18
18
  };
19
- export function registerAnalyzerTools(server, projectId) {
19
+ export function registerAnalyzerTools(server, ctx) {
20
20
  const client = () => getClient();
21
21
  // ===== code_scan: 扫描项目代码 =====
22
22
  server.tool('code_scan', '📡 扫描项目代码, 构建索引。返回文件数、符号数、语言统计。★首次使用 code_query/code_impact/code_context 前必须先执行★', {
@@ -94,19 +94,19 @@ export function registerAnalyzerTools(server, projectId) {
94
94
  projectPath: z.string().describe('项目源码的绝对路径'),
95
95
  filePath: z.string().describe('目标文件的相对路径(如"src/services/auth.ts")'),
96
96
  }, async (args) => safeTool(async () => {
97
- const ctx = await client().analyzerContext(args.projectPath, args.filePath);
98
- if (!ctx) {
97
+ const fileCtx = await client().analyzerContext(args.projectPath, args.filePath);
98
+ if (!fileCtx) {
99
99
  return wrap(`未找到文件 "${args.filePath}"。请确认路径正确, 路径格式为相对路径(如 src/main.ts)`);
100
100
  }
101
101
  const lines = [
102
- `📄 ${ctx.filePath}`,
103
- `语言: ${ctx.language} | ${ctx.linesTotal} 行`,
102
+ `📄 ${fileCtx.filePath}`,
103
+ `语言: ${fileCtx.language} | ${fileCtx.linesTotal} 行`,
104
104
  ``,
105
105
  ];
106
106
  // 定义的符号
107
- if (ctx.symbolsDefined.length > 0) {
108
- lines.push(`### 🔤 定义的符号 (${ctx.symbolsDefined.length})`);
109
- for (const sym of ctx.symbolsDefined) {
107
+ if (fileCtx.symbolsDefined.length > 0) {
108
+ lines.push(`### 🔤 定义的符号 (${fileCtx.symbolsDefined.length})`);
109
+ for (const sym of fileCtx.symbolsDefined) {
110
110
  const icon = SYMBOL_ICONS[sym.kind] || '?';
111
111
  const exp = sym.exported ? ' [export]' : '';
112
112
  lines.push(` ${icon} ${sym.name}${exp} L${sym.lineStart}-L${sym.lineEnd}`);
@@ -114,18 +114,18 @@ export function registerAnalyzerTools(server, projectId) {
114
114
  lines.push('');
115
115
  }
116
116
  // 导入
117
- if (ctx.imports.length > 0) {
118
- lines.push(`### 📥 导入 (${ctx.imports.length})`);
119
- for (const imp of ctx.imports) {
117
+ if (fileCtx.imports.length > 0) {
118
+ lines.push(`### 📥 导入 (${fileCtx.imports.length})`);
119
+ for (const imp of fileCtx.imports) {
120
120
  const specs = imp.specifiers.length > 0 ? `{ ${imp.specifiers.join(', ')} }` : '*';
121
121
  lines.push(` → ${imp.source} ${specs} L${imp.line}`);
122
122
  }
123
123
  lines.push('');
124
124
  }
125
125
  // 被谁引用
126
- if (ctx.importedBy.length > 0) {
127
- lines.push(`### 📤 被引用 (${ctx.importedBy.length})`);
128
- for (const by of ctx.importedBy) {
126
+ if (fileCtx.importedBy.length > 0) {
127
+ lines.push(`### 📤 被引用 (${fileCtx.importedBy.length})`);
128
+ for (const by of fileCtx.importedBy) {
129
129
  const specs = by.specifiers.length > 0 ? `{ ${by.specifiers.join(', ')} }` : '';
130
130
  lines.push(` ← ${by.filePath} ${specs}`);
131
131
  }
@@ -260,4 +260,117 @@ export function registerAnalyzerTools(server, projectId) {
260
260
  }
261
261
  return wrap(lines.join('\n'));
262
262
  }));
263
+ // ===== code_smart_context: 代码+文档全关联上下文 =====
264
+ server.tool('code_smart_context', '🔍 代码+文档全关联上下文 — 输入一个函数名,一次调用返回:代码依赖、关联文档、匹配规则、活跃任务、影响范围摘要。需先运行 code_scan', {
265
+ projectPath: z.string().describe('项目源码的绝对路径'),
266
+ symbolName: z.string().describe('要查询的符号名称(如"handleLogin", "AuthService")'),
267
+ }, async (args) => safeTool(async () => {
268
+ const smartCtx = await client().analyzerSmartContext(args.projectPath, args.symbolName);
269
+ if (!smartCtx) {
270
+ return wrap(`未找到符号 "${args.symbolName}"。请确认名称正确且已运行 code_scan`);
271
+ }
272
+ const lines = [
273
+ `🔍 智能上下文: ${smartCtx.symbol.name}`,
274
+ ``,
275
+ ];
276
+ // 符号信息
277
+ const icon = SYMBOL_ICONS[smartCtx.symbol.kind] || '?';
278
+ const exp = smartCtx.symbol.exported ? ' [export]' : '';
279
+ lines.push(`### ${icon} 符号信息`);
280
+ lines.push(` ${smartCtx.symbol.name}${exp} (${smartCtx.symbol.kind})`);
281
+ lines.push(` 📍 ${smartCtx.symbol.filePath}:${smartCtx.symbol.lineStart}-${smartCtx.symbol.lineEnd}`);
282
+ if (smartCtx.symbol.signature)
283
+ lines.push(` 📝 ${smartCtx.symbol.signature}`);
284
+ lines.push('');
285
+ // 文件上下文
286
+ lines.push(`### 📄 文件上下文: ${smartCtx.fileContext.filePath}`);
287
+ lines.push(` 语言: ${smartCtx.fileContext.language} | ${smartCtx.fileContext.linesTotal} 行`);
288
+ lines.push(` 定义: ${smartCtx.fileContext.symbolsDefined.length} 个符号 | 导入: ${smartCtx.fileContext.imports.length} | 被引用: ${smartCtx.fileContext.importedBy.length}`);
289
+ lines.push('');
290
+ // 影响范围
291
+ if (smartCtx.impactSummary.totalFiles > 0) {
292
+ lines.push(`### 💥 影响范围: ${smartCtx.impactSummary.totalFiles} 个文件`);
293
+ for (const f of smartCtx.impactSummary.l1Files.slice(0, 10)) {
294
+ lines.push(` 🔴 ${f}`);
295
+ }
296
+ lines.push('');
297
+ }
298
+ // 关联文档
299
+ if (smartCtx.relatedDocs.length > 0) {
300
+ lines.push(`### 📚 关联知识文档 (${smartCtx.relatedDocs.length})`);
301
+ for (const doc of smartCtx.relatedDocs) {
302
+ lines.push(` 📄 ${doc.path} — ${doc.summary}`);
303
+ }
304
+ lines.push('');
305
+ }
306
+ // 匹配规则
307
+ if (smartCtx.matchedRules.length > 0) {
308
+ lines.push(`### 📏 匹配规则 (${smartCtx.matchedRules.length})`);
309
+ for (const rule of smartCtx.matchedRules) {
310
+ lines.push(` [${rule.ruleType}] ${rule.content.substring(0, 100)}`);
311
+ }
312
+ lines.push('');
313
+ }
314
+ // 活跃任务
315
+ if (smartCtx.activeTasks.length > 0) {
316
+ lines.push(`### 📝 活跃任务 (${smartCtx.activeTasks.length})`);
317
+ for (const task of smartCtx.activeTasks) {
318
+ lines.push(` 🔵 [${task.id}] ${task.title}`);
319
+ }
320
+ }
321
+ return wrap(lines.join('\n'));
322
+ }));
323
+ // ===== code_full_path: 全关联路径 =====
324
+ server.tool('code_full_path', '🔗 全关联路径 — 两个符号之间不仅返回代码引用链路,还返回共享的KG文档、共同导入、祖先模块。需先运行 code_scan', {
325
+ projectPath: z.string().describe('项目源码的绝对路径'),
326
+ symbolA: z.string().describe('起点符号名(如 "handleLogin")'),
327
+ symbolB: z.string().describe('终点符号名(如 "AuthService")'),
328
+ maxDepth: z.number().optional().describe('最大搜索深度(1-5, 默认3)'),
329
+ }, async (args) => safeTool(async () => {
330
+ const result = await client().analyzerFullPath(args.projectPath, args.symbolA, args.symbolB, args.maxDepth ?? 3);
331
+ if (!result) {
332
+ return wrap(`❌ 无法构建路径。请确认两个符号名称正确且已运行 code_scan`);
333
+ }
334
+ const lines = [
335
+ `🔗 全关联路径`,
336
+ ``,
337
+ `📍 ${result.symbolA.name} (${result.symbolA.kind}) → ${result.symbolA.filePath}:${result.symbolA.lineStart}`,
338
+ `📍 ${result.symbolB.name} (${result.symbolB.kind}) → ${result.symbolB.filePath}:${result.symbolB.lineStart}`,
339
+ ``,
340
+ `📊 ${result.pathSummary}`,
341
+ ``,
342
+ ];
343
+ // 代码路径
344
+ if (result.codePath.length > 0) {
345
+ lines.push(`### 🔗 代码引用链路 (${result.codePath.length} 步)`);
346
+ for (const step of result.codePath.slice(0, 20)) {
347
+ lines.push(` L${step.depth} ${step.filePath} [${step.refKind}] ← ${step.symbolName}`);
348
+ }
349
+ lines.push('');
350
+ }
351
+ // 共享文档
352
+ if (result.sharedDocs.length > 0) {
353
+ lines.push(`### 📚 共享KG文档 (${result.sharedDocs.length})`);
354
+ for (const doc of result.sharedDocs) {
355
+ lines.push(` 📄 ${doc.path} — ${doc.summary}`);
356
+ }
357
+ lines.push('');
358
+ }
359
+ // 共同导入
360
+ if (result.commonImports.length > 0) {
361
+ lines.push(`### 📥 共同导入 (${result.commonImports.length})`);
362
+ for (const imp of result.commonImports) {
363
+ lines.push(` → ${imp}`);
364
+ }
365
+ lines.push('');
366
+ }
367
+ // 共同祖先
368
+ if (result.commonAncestors.length > 0) {
369
+ lines.push(`### 📁 共同祖先目录`);
370
+ for (const anc of result.commonAncestors) {
371
+ lines.push(` 📂 ${anc}`);
372
+ }
373
+ }
374
+ return wrap(lines.join('\n'));
375
+ }));
263
376
  }
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * 💬 kg_discuss (6→1)
3
- * 合并: kg_discussion_list, kg_discussion_read, kg_discussion_create,
4
- * kg_discussion_reply, kg_discussion_close_and_archive, kg_discussion_delete
3
+ * 合并: list, read, create, reply, close, delete
4
+ * 统一走 HTTP → Rust 后端 (单一写入者)
5
+ * sender 格式: "projectId:user" (如 "p-ca3sgejg:张三")
5
6
  */
6
7
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
8
  import { type McpContext } from './shared.js';