@ppdocs/mcp 3.1.9 → 3.2.0
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/agent.d.ts +6 -0
- package/dist/agent.js +130 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +49 -14
- package/dist/config.d.ts +12 -6
- package/dist/config.js +106 -42
- package/dist/index.js +13 -3
- package/dist/storage/discussion.d.ts +33 -0
- package/dist/storage/discussion.js +116 -0
- package/dist/storage/httpClient.d.ts +8 -0
- package/dist/storage/httpClient.js +29 -0
- package/dist/tools/analyzer.d.ts +8 -0
- package/dist/tools/analyzer.js +136 -0
- package/dist/tools/discussion.d.ts +7 -0
- package/dist/tools/discussion.js +111 -0
- package/dist/tools/docs.d.ts +3 -3
- package/dist/tools/docs.js +205 -175
- package/dist/tools/files.d.ts +3 -3
- package/dist/tools/files.js +61 -56
- package/dist/tools/index.d.ts +6 -1
- package/dist/tools/index.js +18 -3
- package/dist/tools/kg_status.d.ts +5 -0
- package/dist/tools/kg_status.js +42 -0
- package/dist/tools/projects.d.ts +3 -2
- package/dist/tools/projects.js +4 -36
- package/dist/tools/rules.d.ts +3 -3
- package/dist/tools/rules.js +88 -110
- package/dist/tools/tasks.d.ts +2 -2
- package/dist/tools/tasks.js +96 -73
- package/dist/web/server.d.ts +43 -0
- package/dist/web/server.js +611 -0
- package/dist/web/ui.d.ts +5 -0
- package/dist/web/ui.js +474 -0
- package/package.json +6 -3
|
@@ -96,6 +96,14 @@ export declare class PpdocsApiClient {
|
|
|
96
96
|
localPath: string;
|
|
97
97
|
fileCount: number;
|
|
98
98
|
}>;
|
|
99
|
+
/** 扫描项目代码, 构建/更新索引 */
|
|
100
|
+
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>;
|
|
99
107
|
}
|
|
100
108
|
export declare function initClient(apiUrl: string): void;
|
|
101
109
|
export declare function getClient(): PpdocsApiClient;
|
|
@@ -480,6 +480,35 @@ export class PpdocsApiClient {
|
|
|
480
480
|
async crossDownload(target, remotePath, localPath) {
|
|
481
481
|
return fetchAndExtractZip(`${this.baseUrl}/cross/${encodeURIComponent(target)}/files-download/${cleanPath(remotePath)}`, localPath);
|
|
482
482
|
}
|
|
483
|
+
// ============ 代码分析引擎 ============
|
|
484
|
+
/** 扫描项目代码, 构建/更新索引 */
|
|
485
|
+
async analyzerScan(projectPath, force = false) {
|
|
486
|
+
return this.request('/analyzer/scan', {
|
|
487
|
+
method: 'POST',
|
|
488
|
+
body: JSON.stringify({ project_path: projectPath, force }),
|
|
489
|
+
});
|
|
490
|
+
}
|
|
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
|
+
}
|
|
483
512
|
}
|
|
484
513
|
// ============ 模块级 API ============
|
|
485
514
|
let client = null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 代码分析引擎工具 (4个)
|
|
3
|
+
* code_scan, code_query, code_impact, code_context
|
|
4
|
+
*
|
|
5
|
+
* 让 AI Agent 通过 MCP 工具直接查询代码结构、依赖关系和影响范围
|
|
6
|
+
*/
|
|
7
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
export declare function registerAnalyzerTools(server: McpServer, projectId: string): void;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 代码分析引擎工具 (4个)
|
|
3
|
+
* code_scan, code_query, code_impact, code_context
|
|
4
|
+
*
|
|
5
|
+
* 让 AI Agent 通过 MCP 工具直接查询代码结构、依赖关系和影响范围
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { getClient } from '../storage/httpClient.js';
|
|
9
|
+
import { wrap, safeTool } from './shared.js';
|
|
10
|
+
// 符号图标
|
|
11
|
+
const SYMBOL_ICONS = {
|
|
12
|
+
function: '𝑓', class: '𝐂', method: '𝑚', interface: '𝐈',
|
|
13
|
+
type_alias: '𝐓', enum: '𝐄', struct: '𝐒', trait: '⚡',
|
|
14
|
+
constant: '𝐊', variable: '𝑣',
|
|
15
|
+
};
|
|
16
|
+
const SEVERITY_ICONS = {
|
|
17
|
+
critical: '🔴', warning: '🟡', info: '🟢',
|
|
18
|
+
};
|
|
19
|
+
export function registerAnalyzerTools(server, projectId) {
|
|
20
|
+
const client = () => getClient();
|
|
21
|
+
// ===== code_scan: 扫描项目代码 =====
|
|
22
|
+
server.tool('code_scan', '📡 扫描项目代码, 构建索引。返回文件数、符号数、语言统计。★首次使用 code_query/code_impact/code_context 前必须先执行★', {
|
|
23
|
+
projectPath: z.string().describe('项目源码的绝对路径(如"D:/projects/myapp")'),
|
|
24
|
+
force: z.boolean().optional().describe('是否强制全量重建(默认false, 增量更新)'),
|
|
25
|
+
}, async (args) => safeTool(async () => {
|
|
26
|
+
const result = await client().analyzerScan(args.projectPath, args.force ?? false);
|
|
27
|
+
return wrap([
|
|
28
|
+
`✅ 代码扫描完成`,
|
|
29
|
+
``,
|
|
30
|
+
`📊 索引摘要:`,
|
|
31
|
+
`- 文件数: ${result.fileCount}`,
|
|
32
|
+
`- 符号数: ${result.symbolCount}`,
|
|
33
|
+
`- 语言: ${result.languages.join(', ')}`,
|
|
34
|
+
`- 索引时间: ${result.indexedAt}`,
|
|
35
|
+
].join('\n'));
|
|
36
|
+
}));
|
|
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 ctx = await client().analyzerContext(args.projectPath, args.filePath);
|
|
98
|
+
if (!ctx) {
|
|
99
|
+
return wrap(`未找到文件 "${args.filePath}"。请确认路径正确, 路径格式为相对路径(如 src/main.ts)`);
|
|
100
|
+
}
|
|
101
|
+
const lines = [
|
|
102
|
+
`📄 ${ctx.filePath}`,
|
|
103
|
+
`语言: ${ctx.language} | ${ctx.linesTotal} 行`,
|
|
104
|
+
``,
|
|
105
|
+
];
|
|
106
|
+
// 定义的符号
|
|
107
|
+
if (ctx.symbolsDefined.length > 0) {
|
|
108
|
+
lines.push(`### 🔤 定义的符号 (${ctx.symbolsDefined.length})`);
|
|
109
|
+
for (const sym of ctx.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 (ctx.imports.length > 0) {
|
|
118
|
+
lines.push(`### 📥 导入 (${ctx.imports.length})`);
|
|
119
|
+
for (const imp of ctx.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 (ctx.importedBy.length > 0) {
|
|
127
|
+
lines.push(`### 📤 被引用 (${ctx.importedBy.length})`);
|
|
128
|
+
for (const by of ctx.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
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
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
|
|
5
|
+
*/
|
|
6
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
+
export declare function registerDiscussionTools(server: McpServer, projectId: string): void;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
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
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { getClient } from '../storage/httpClient.js';
|
|
8
|
+
import { decodeObjectStrings } from '../utils.js';
|
|
9
|
+
import { wrap, safeTool } from './shared.js';
|
|
10
|
+
import { DiscussionManager } from '../storage/discussion.js';
|
|
11
|
+
export function registerDiscussionTools(server, projectId) {
|
|
12
|
+
const client = () => getClient();
|
|
13
|
+
server.tool('kg_discuss', '💬 跨项目讨论 — 发起、回复、归档协同讨论。action: list(列出活跃讨论)|read(读取详情)|create(发起)|reply(回复)|close(结案归档)|delete(删除)', {
|
|
14
|
+
action: z.enum(['list', 'read', 'create', 'reply', 'close', 'delete'])
|
|
15
|
+
.describe('操作类型'),
|
|
16
|
+
id: z.string().optional()
|
|
17
|
+
.describe('讨论哈希ID (read/reply/close/delete)'),
|
|
18
|
+
ids: z.array(z.string()).optional()
|
|
19
|
+
.describe('批量读取的讨论ID数组 (read)'),
|
|
20
|
+
title: z.string().optional()
|
|
21
|
+
.describe('讨论标题 (create)'),
|
|
22
|
+
participants: z.array(z.string()).optional()
|
|
23
|
+
.describe('参与项目ID数组 (create, 不含自己)'),
|
|
24
|
+
content: z.string().optional()
|
|
25
|
+
.describe('消息内容Markdown (create/reply)'),
|
|
26
|
+
conclusion: z.string().optional()
|
|
27
|
+
.describe('结案总结 (close)'),
|
|
28
|
+
newSummary: z.string().optional()
|
|
29
|
+
.describe('更新进展摘要 (reply)'),
|
|
30
|
+
}, async (args) => safeTool(async () => {
|
|
31
|
+
const decoded = decodeObjectStrings(args);
|
|
32
|
+
switch (decoded.action) {
|
|
33
|
+
case 'list': {
|
|
34
|
+
const cleaned = DiscussionManager.cleanExpired(7);
|
|
35
|
+
const active = DiscussionManager.listActive();
|
|
36
|
+
const cleanMsg = cleaned > 0 ? `\n⚠️ 已自动清理 ${cleaned} 条超过7天不活跃的讨论` : '';
|
|
37
|
+
if (active.length === 0)
|
|
38
|
+
return wrap(`当前无活跃的讨论 (本项目ID: ${projectId})${cleanMsg}`);
|
|
39
|
+
return wrap(`本项目ID: ${projectId}\n活跃讨论区 (${active.length} 个):${cleanMsg}\n\n` + JSON.stringify(active, null, 2));
|
|
40
|
+
}
|
|
41
|
+
case 'read': {
|
|
42
|
+
const readIds = decoded.ids || (decoded.id ? [decoded.id] : []);
|
|
43
|
+
if (readIds.length === 0)
|
|
44
|
+
return wrap('❌ read 需要 id 或 ids');
|
|
45
|
+
const discussions = DiscussionManager.readByIds(readIds);
|
|
46
|
+
if (discussions.length === 0)
|
|
47
|
+
return wrap('未找到对应的讨论记录');
|
|
48
|
+
return wrap(JSON.stringify(discussions, null, 2));
|
|
49
|
+
}
|
|
50
|
+
case 'create': {
|
|
51
|
+
if (!decoded.title)
|
|
52
|
+
return wrap('❌ create 需要 title');
|
|
53
|
+
if (!decoded.participants)
|
|
54
|
+
return wrap('❌ create 需要 participants');
|
|
55
|
+
if (!decoded.content)
|
|
56
|
+
return wrap('❌ create 需要 content');
|
|
57
|
+
const count = DiscussionManager.activeCount();
|
|
58
|
+
if (count >= 10)
|
|
59
|
+
return wrap('❌ 活跃讨论已达上限(10条)');
|
|
60
|
+
const id = DiscussionManager.create(decoded.title, projectId, decoded.participants, decoded.content);
|
|
61
|
+
return wrap(`✅ 讨论已发起,ID: ${id}\n发起方: ${projectId}\n参与方: ${decoded.participants.join(', ')}`);
|
|
62
|
+
}
|
|
63
|
+
case 'reply': {
|
|
64
|
+
if (!decoded.id)
|
|
65
|
+
return wrap('❌ reply 需要 id');
|
|
66
|
+
if (!decoded.content)
|
|
67
|
+
return wrap('❌ reply 需要 content');
|
|
68
|
+
const success = DiscussionManager.reply(decoded.id, projectId, decoded.content, decoded.newSummary);
|
|
69
|
+
return wrap(success ? `✅ 回复成功 (ID: ${decoded.id})` : `❌ 回复失败,讨论不存在或已关闭`);
|
|
70
|
+
}
|
|
71
|
+
case 'close': {
|
|
72
|
+
if (!decoded.id)
|
|
73
|
+
return wrap('❌ close 需要 id');
|
|
74
|
+
if (!decoded.conclusion)
|
|
75
|
+
return wrap('❌ close 需要 conclusion');
|
|
76
|
+
const topic = DiscussionManager.getAndRemove(decoded.id);
|
|
77
|
+
if (!topic)
|
|
78
|
+
return wrap('❌ 讨论不存在或已被归档清理');
|
|
79
|
+
let md = `# 跨项目协同: ${topic.title}\n\n`;
|
|
80
|
+
md += `**发起方**: ${topic.initiator}\n`;
|
|
81
|
+
md += `**参与方**: ${topic.participants.join(', ')}\n`;
|
|
82
|
+
md += `**结案总结**: ${decoded.conclusion}\n\n`;
|
|
83
|
+
md += `## 讨论还原 (Timeline)\n\n`;
|
|
84
|
+
for (const msg of topic.messages) {
|
|
85
|
+
md += `### [${msg.sender}] (${msg.timestamp})\n${msg.content}\n\n`;
|
|
86
|
+
}
|
|
87
|
+
const safeTitle = topic.title.replace(/[\/\\?%*:|"<>]/g, '_');
|
|
88
|
+
const path = `/跨项目协同记录/${decoded.id}_${safeTitle}`;
|
|
89
|
+
try {
|
|
90
|
+
await client().createDoc(path, {
|
|
91
|
+
summary: decoded.conclusion, content: md,
|
|
92
|
+
versions: [{ version: 1.0, date: new Date().toISOString(), changes: '结案归档' }],
|
|
93
|
+
bugfixes: [], status: '已完成'
|
|
94
|
+
});
|
|
95
|
+
return wrap(`✅ 讨论已结案并归档: \`${path}\``);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
return wrap(`⚠️ 讨论已清除,但归档失败: ${err}\n\n${md}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
case 'delete': {
|
|
102
|
+
if (!decoded.id)
|
|
103
|
+
return wrap('❌ delete 需要 id');
|
|
104
|
+
const success = DiscussionManager.delete(decoded.id);
|
|
105
|
+
return wrap(success ? `✅ 讨论已删除 (ID: ${decoded.id})` : `❌ 讨论不存在或已被删除`);
|
|
106
|
+
}
|
|
107
|
+
default:
|
|
108
|
+
return wrap(`❌ 未知 action: ${decoded.action}`);
|
|
109
|
+
}
|
|
110
|
+
}));
|
|
111
|
+
}
|
package/dist/tools/docs.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* kg_create_node, kg_delete_node, kg_update_node, kg_read_node,
|
|
4
|
-
*
|
|
2
|
+
* 📄 kg_doc (5合1) + 🏗️ kg_tree (2合1)
|
|
3
|
+
* 合并: kg_create_node, kg_delete_node, kg_update_node, kg_read_node, kg_copy_node
|
|
4
|
+
* kg_get_tree, kg_get_docs_by_status
|
|
5
5
|
*/
|
|
6
6
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
7
|
export declare function registerDocTools(server: McpServer, projectId: string): void;
|