@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
package/dist/tools/docs.js
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 { z } from 'zod';
|
|
7
7
|
import { getClient } from '../storage/httpClient.js';
|
|
@@ -9,52 +9,24 @@ import { decodeObjectStrings } from '../utils.js';
|
|
|
9
9
|
import { wrap, safeTool, crossPrefix, formatDocMarkdown, formatTreeText, countTreeDocs } from './shared.js';
|
|
10
10
|
export function registerDocTools(server, projectId) {
|
|
11
11
|
const client = () => getClient();
|
|
12
|
-
//
|
|
13
|
-
server.tool('
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
bugfixes: [],
|
|
25
|
-
status: d.status || '已完成'
|
|
26
|
-
});
|
|
27
|
-
return wrap(`✅ 文档已创建: ${d.path}\n\n${JSON.stringify(doc, null, 2)}`);
|
|
28
|
-
}));
|
|
29
|
-
// 删除文档或目录 (支持批量)
|
|
30
|
-
server.tool('kg_delete_node', '删除文档或目录(支持批量,根不可删除,目录会递归删除)', { nodeId: z.union([z.string(), z.array(z.string())]).describe('文档路径或路径数组') }, async (args) => safeTool(async () => {
|
|
31
|
-
const paths = Array.isArray(args.nodeId) ? args.nodeId : [args.nodeId];
|
|
32
|
-
const results = [];
|
|
33
|
-
for (const p of paths) {
|
|
34
|
-
results.push({ path: p, success: await client().deleteDoc(p) });
|
|
35
|
-
}
|
|
36
|
-
const successCount = results.filter(r => r.success).length;
|
|
37
|
-
if (paths.length === 1) {
|
|
38
|
-
return wrap(results[0].success ? '删除成功' : '删除失败(文档不存在或是根文档)');
|
|
39
|
-
}
|
|
40
|
-
const failedPaths = results.filter(r => !r.success).map(r => r.path);
|
|
41
|
-
let msg = `✅ 批量删除完成: ${successCount}/${paths.length} 成功`;
|
|
42
|
-
if (failedPaths.length > 0)
|
|
43
|
-
msg += `\n❌ 失败: ${failedPaths.join(', ')}`;
|
|
44
|
-
return wrap(msg);
|
|
45
|
-
}));
|
|
46
|
-
// 更新文档 (支持单个/批量)
|
|
47
|
-
server.tool('kg_update_node', '更新文档内容。【单个模式】nodeId+字段;【批量模式】仅传updates数组。两种模式二选一', {
|
|
48
|
-
nodeId: z.string().optional().describe('【单个模式】文档路径,如"/前端/组件/Button"'),
|
|
49
|
-
summary: z.string().optional().describe('【单个模式】一句话简介'),
|
|
50
|
-
content: z.string().optional().describe('【单个模式】Markdown内容'),
|
|
51
|
-
status: z.string().optional().describe('【单个模式】文档状态'),
|
|
12
|
+
// ========== kg_doc: 文档 CRUD 统一入口 ==========
|
|
13
|
+
server.tool('kg_doc', '📄 知识文档操作 — 创建、读取、更新、删除、复制文档。action: create(创建)|read(读取)|update(更新)|delete(删除)|batch_update(批量更新)|copy(跨项目复制)', {
|
|
14
|
+
action: z.enum(['create', 'read', 'update', 'delete', 'batch_update', 'copy'])
|
|
15
|
+
.describe('操作类型'),
|
|
16
|
+
path: z.string().optional()
|
|
17
|
+
.describe('文档路径(如"/前端/组件/Modal")。create/read/update/delete 必填'),
|
|
18
|
+
content: z.string().optional()
|
|
19
|
+
.describe('Markdown内容 (create/update)'),
|
|
20
|
+
summary: z.string().optional()
|
|
21
|
+
.describe('一句话简介 (create/update)'),
|
|
22
|
+
status: z.string().optional()
|
|
23
|
+
.describe('文档状态 (create/update, 默认"已完成")'),
|
|
52
24
|
versions: z.array(z.object({
|
|
53
25
|
version: z.number(), date: z.string(), changes: z.string()
|
|
54
|
-
})).optional().describe('
|
|
26
|
+
})).optional().describe('版本记录 (update)'),
|
|
55
27
|
bugfixes: z.array(z.object({
|
|
56
28
|
date: z.string(), issue: z.string(), solution: z.string()
|
|
57
|
-
})).optional().describe('
|
|
29
|
+
})).optional().describe('修复记录 (update)'),
|
|
58
30
|
updates: z.array(z.object({
|
|
59
31
|
nodeId: z.string().describe('文档路径'),
|
|
60
32
|
summary: z.string().optional(),
|
|
@@ -66,83 +38,201 @@ export function registerDocTools(server, projectId) {
|
|
|
66
38
|
bugfixes: z.array(z.object({
|
|
67
39
|
date: z.string(), issue: z.string(), solution: z.string()
|
|
68
40
|
})).optional()
|
|
69
|
-
})).optional().describe('
|
|
41
|
+
})).optional().describe('批量更新数组 (batch_update)'),
|
|
42
|
+
targetProject: z.string().optional()
|
|
43
|
+
.describe('跨项目读取时的目标项目ID (read)'),
|
|
44
|
+
sourceProject: z.string().optional()
|
|
45
|
+
.describe('复制源项目ID (copy)'),
|
|
46
|
+
sourcePath: z.union([z.string(), z.array(z.string())]).optional()
|
|
47
|
+
.describe('复制源路径 (copy, 单个或数组)'),
|
|
48
|
+
targetPath: z.string().optional()
|
|
49
|
+
.describe('复制目标路径前缀 (copy)'),
|
|
70
50
|
}, async (args) => safeTool(async () => {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
51
|
+
const d = decodeObjectStrings(args);
|
|
52
|
+
switch (d.action) {
|
|
53
|
+
// ---- CREATE ----
|
|
54
|
+
case 'create': {
|
|
55
|
+
if (!d.path)
|
|
56
|
+
return wrap('❌ create 需要 path');
|
|
57
|
+
if (!d.content)
|
|
58
|
+
return wrap('❌ create 需要 content');
|
|
59
|
+
const doc = await client().createDoc(d.path, {
|
|
60
|
+
summary: d.summary || '',
|
|
61
|
+
content: d.content,
|
|
62
|
+
versions: [{ version: 0.1, date: new Date().toISOString(), changes: '初始创建' }],
|
|
63
|
+
bugfixes: [],
|
|
64
|
+
status: d.status || '已完成'
|
|
65
|
+
});
|
|
66
|
+
return wrap(`✅ 文档已创建: ${d.path}\n\n${JSON.stringify(doc, null, 2)}`);
|
|
67
|
+
}
|
|
68
|
+
// ---- READ ----
|
|
69
|
+
case 'read': {
|
|
70
|
+
if (!d.path)
|
|
71
|
+
return wrap('❌ read 需要 path');
|
|
72
|
+
const doc = d.targetProject
|
|
73
|
+
? await client().crossGetDoc(d.targetProject, d.path)
|
|
74
|
+
: await client().getDoc(d.path);
|
|
75
|
+
if (!doc)
|
|
76
|
+
return wrap(d.targetProject ? `文档不存在(项目: ${d.targetProject})` : '文档不存在');
|
|
77
|
+
const lines = formatDocMarkdown(doc, d.path);
|
|
78
|
+
return wrap(d.targetProject ? crossPrefix(d.targetProject) + lines.join('\n') : lines.join('\n'));
|
|
79
|
+
}
|
|
80
|
+
// ---- UPDATE ----
|
|
81
|
+
case 'update': {
|
|
82
|
+
if (!d.path)
|
|
83
|
+
return wrap('❌ update 需要 path');
|
|
84
|
+
if (d.path === '/' || d.path === '_root') {
|
|
85
|
+
// 根文档更新
|
|
86
|
+
const existing = await client().getDoc('/');
|
|
87
|
+
if (!existing)
|
|
88
|
+
return wrap('更新失败(根文档不存在)');
|
|
89
|
+
const updates = {};
|
|
90
|
+
if (d.summary !== undefined)
|
|
91
|
+
updates.summary = d.summary;
|
|
92
|
+
if (d.content !== undefined)
|
|
93
|
+
updates.content = d.content;
|
|
94
|
+
const upd = await client().updateDoc('/', updates);
|
|
95
|
+
return wrap(upd ? '✅ 项目介绍已更新' : '更新失败');
|
|
84
96
|
}
|
|
97
|
+
const existing = await client().getDoc(d.path);
|
|
98
|
+
if (!existing)
|
|
99
|
+
return wrap('更新失败(文档不存在)');
|
|
85
100
|
const updates = {};
|
|
86
|
-
if (
|
|
87
|
-
updates.summary =
|
|
88
|
-
if (
|
|
89
|
-
updates.content =
|
|
90
|
-
if (
|
|
91
|
-
updates.status =
|
|
92
|
-
if (
|
|
93
|
-
updates.versions =
|
|
94
|
-
if (
|
|
95
|
-
updates.bugfixes =
|
|
96
|
-
const doc = await client().updateDoc(
|
|
97
|
-
|
|
101
|
+
if (d.summary !== undefined)
|
|
102
|
+
updates.summary = d.summary;
|
|
103
|
+
if (d.content !== undefined)
|
|
104
|
+
updates.content = d.content;
|
|
105
|
+
if (d.status !== undefined)
|
|
106
|
+
updates.status = d.status;
|
|
107
|
+
if (d.versions !== undefined)
|
|
108
|
+
updates.versions = d.versions;
|
|
109
|
+
if (d.bugfixes !== undefined)
|
|
110
|
+
updates.bugfixes = d.bugfixes;
|
|
111
|
+
const doc = await client().updateDoc(d.path, updates);
|
|
112
|
+
return wrap(doc ? JSON.stringify(doc, null, 2) : '更新失败');
|
|
113
|
+
}
|
|
114
|
+
// ---- DELETE ----
|
|
115
|
+
case 'delete': {
|
|
116
|
+
if (!d.path)
|
|
117
|
+
return wrap('❌ delete 需要 path');
|
|
118
|
+
const paths = Array.isArray(d.path) ? d.path : [d.path];
|
|
119
|
+
const results = [];
|
|
120
|
+
for (const p of paths) {
|
|
121
|
+
results.push({ path: p, success: await client().deleteDoc(p) });
|
|
122
|
+
}
|
|
123
|
+
if (paths.length === 1) {
|
|
124
|
+
return wrap(results[0].success ? '删除成功' : '删除失败(文档不存在或是根文档)');
|
|
125
|
+
}
|
|
126
|
+
const ok = results.filter(r => r.success).length;
|
|
127
|
+
const failed = results.filter(r => !r.success).map(r => r.path);
|
|
128
|
+
let msg = `✅ 批量删除: ${ok}/${paths.length} 成功`;
|
|
129
|
+
if (failed.length > 0)
|
|
130
|
+
msg += `\n❌ 失败: ${failed.join(', ')}`;
|
|
131
|
+
return wrap(msg);
|
|
132
|
+
}
|
|
133
|
+
// ---- BATCH UPDATE ----
|
|
134
|
+
case 'batch_update': {
|
|
135
|
+
if (!d.updates || !Array.isArray(d.updates))
|
|
136
|
+
return wrap('❌ batch_update 需要 updates 数组');
|
|
137
|
+
const results = [];
|
|
138
|
+
for (const item of d.updates) {
|
|
139
|
+
if (item.nodeId === '/' || item.nodeId === '_root') {
|
|
140
|
+
results.push({ path: item.nodeId, success: false });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const existing = await client().getDoc(item.nodeId);
|
|
144
|
+
if (!existing) {
|
|
145
|
+
results.push({ path: item.nodeId, success: false });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const upd = {};
|
|
149
|
+
if (item.summary !== undefined)
|
|
150
|
+
upd.summary = item.summary;
|
|
151
|
+
if (item.content !== undefined)
|
|
152
|
+
upd.content = item.content;
|
|
153
|
+
if (item.status !== undefined)
|
|
154
|
+
upd.status = item.status;
|
|
155
|
+
if (item.versions !== undefined)
|
|
156
|
+
upd.versions = item.versions;
|
|
157
|
+
if (item.bugfixes !== undefined)
|
|
158
|
+
upd.bugfixes = item.bugfixes;
|
|
159
|
+
const doc = await client().updateDoc(item.nodeId, upd);
|
|
160
|
+
results.push({ path: item.nodeId, success: !!doc });
|
|
161
|
+
}
|
|
162
|
+
const ok = results.filter(r => r.success).length;
|
|
163
|
+
const failed = results.filter(r => !r.success).map(r => r.path);
|
|
164
|
+
let msg = `✅ 批量更新: ${ok}/${d.updates.length} 成功`;
|
|
165
|
+
if (failed.length > 0)
|
|
166
|
+
msg += `\n❌ 失败: ${failed.join(', ')}`;
|
|
167
|
+
return wrap(msg);
|
|
168
|
+
}
|
|
169
|
+
// ---- COPY ----
|
|
170
|
+
case 'copy': {
|
|
171
|
+
if (!d.sourceProject)
|
|
172
|
+
return wrap('❌ copy 需要 sourceProject');
|
|
173
|
+
if (!d.sourcePath)
|
|
174
|
+
return wrap('❌ copy 需要 sourcePath');
|
|
175
|
+
const paths = Array.isArray(d.sourcePath) ? d.sourcePath : [d.sourcePath];
|
|
176
|
+
const results = [];
|
|
177
|
+
for (const srcPath of paths) {
|
|
178
|
+
try {
|
|
179
|
+
const doc = await client().crossGetDoc(d.sourceProject, srcPath);
|
|
180
|
+
if (!doc) {
|
|
181
|
+
results.push({ path: srcPath, success: false, error: '源文档不存在' });
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const destPath = d.targetPath
|
|
185
|
+
? `${d.targetPath.replace(/\/$/, '')}${srcPath}`
|
|
186
|
+
: srcPath;
|
|
187
|
+
const existing = await client().getDoc(destPath);
|
|
188
|
+
if (existing) {
|
|
189
|
+
await client().updateDoc(destPath, { summary: doc.summary, content: doc.content });
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
await client().createDoc(destPath, { summary: doc.summary, content: doc.content, status: doc.status });
|
|
193
|
+
}
|
|
194
|
+
results.push({ path: destPath, success: true });
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
results.push({ path: srcPath, success: false, error: String(e) });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const ok = results.filter(r => r.success).length;
|
|
201
|
+
if (paths.length === 1) {
|
|
202
|
+
return wrap(results[0].success
|
|
203
|
+
? `✅ 已复制: ${results[0].path}\n来源: [${d.sourceProject}] ${paths[0]}`
|
|
204
|
+
: `❌ 复制失败: ${results[0].error}`);
|
|
205
|
+
}
|
|
206
|
+
const failed = results.filter(r => !r.success);
|
|
207
|
+
let msg = `✅ 批量复制: ${ok}/${paths.length} 成功`;
|
|
208
|
+
if (failed.length > 0)
|
|
209
|
+
msg += `\n❌ 失败:\n${failed.map(f => ` - ${f.path}: ${f.error}`).join('\n')}`;
|
|
210
|
+
return wrap(msg);
|
|
98
211
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
let msg = `✅ 批量更新完成: ${successCount}/${decoded.updates.length} 成功`;
|
|
102
|
-
if (failedPaths.length > 0)
|
|
103
|
-
msg += `\n❌ 失败: ${failedPaths.join(', ')}`;
|
|
104
|
-
return wrap(msg);
|
|
212
|
+
default:
|
|
213
|
+
return wrap(`❌ 未知 action: ${d.action}`);
|
|
105
214
|
}
|
|
106
|
-
// 单个更新模式
|
|
107
|
-
const { nodeId, summary, content, status, versions, bugfixes } = decoded;
|
|
108
|
-
if (!nodeId)
|
|
109
|
-
return wrap('❌ 请提供 nodeId(单个模式)或 updates 数组(批量模式)');
|
|
110
|
-
if (nodeId === '/' || nodeId === '_root')
|
|
111
|
-
return wrap('❌ 根文档请使用 kg_update_root 方法更新');
|
|
112
|
-
const existing = await client().getDoc(nodeId);
|
|
113
|
-
if (!existing)
|
|
114
|
-
return wrap('更新失败(文档不存在)');
|
|
115
|
-
const updates = {};
|
|
116
|
-
if (summary !== undefined)
|
|
117
|
-
updates.summary = summary;
|
|
118
|
-
if (content !== undefined)
|
|
119
|
-
updates.content = content;
|
|
120
|
-
if (status !== undefined)
|
|
121
|
-
updates.status = status;
|
|
122
|
-
if (versions !== undefined)
|
|
123
|
-
updates.versions = versions;
|
|
124
|
-
if (bugfixes !== undefined)
|
|
125
|
-
updates.bugfixes = bugfixes;
|
|
126
|
-
const doc = await client().updateDoc(nodeId, updates);
|
|
127
|
-
return wrap(doc ? JSON.stringify(doc, null, 2) : '更新失败');
|
|
128
215
|
}));
|
|
129
|
-
//
|
|
130
|
-
server.tool('
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
? await client().crossGetDoc(args.targetProject, args.nodeId)
|
|
136
|
-
: await client().getDoc(args.nodeId);
|
|
137
|
-
if (!doc)
|
|
138
|
-
return wrap(args.targetProject ? `文档不存在(项目: ${args.targetProject})` : '文档不存在');
|
|
139
|
-
const lines = formatDocMarkdown(doc, args.nodeId);
|
|
140
|
-
return wrap(args.targetProject ? crossPrefix(args.targetProject) + lines.join('\n') : lines.join('\n'));
|
|
141
|
-
}));
|
|
142
|
-
// 获取知识库树状图
|
|
143
|
-
server.tool('kg_get_tree', '获取知识库目录树。格式: 📄 文档名 # 简介。支持跨项目只读访问(需先用kg_list_projects获取项目ID)', {
|
|
144
|
-
targetProject: z.string().optional().describe('目标项目ID或名称(不填=当前项目)。ID可从kg_list_projects获取')
|
|
216
|
+
// ========== kg_tree: 知识全景图 ==========
|
|
217
|
+
server.tool('kg_tree', '🏗️ 项目知识全景图 ★最快了解项目的方式★ 获取完整知识库目录树(含文档数统计)。传 statusFilter 可筛选特定状态文档。建议任何操作前先调用获取全局视图', {
|
|
218
|
+
statusFilter: z.array(z.string()).optional()
|
|
219
|
+
.describe('按状态筛选(如["未完成","待修复"]),不传则展示全景目录树'),
|
|
220
|
+
targetProject: z.string().optional()
|
|
221
|
+
.describe('跨项目查看,填目标项目ID或名称'),
|
|
145
222
|
}, async (args) => safeTool(async () => {
|
|
223
|
+
// 状态筛选模式
|
|
224
|
+
if (args.statusFilter && args.statusFilter.length > 0) {
|
|
225
|
+
const docs = args.targetProject
|
|
226
|
+
? await client().crossGetDocsByStatus(args.targetProject, args.statusFilter)
|
|
227
|
+
: await client().getDocsByStatus(args.statusFilter);
|
|
228
|
+
if (docs.length === 0) {
|
|
229
|
+
return wrap(`未找到状态为 [${args.statusFilter.join(', ')}] 的文档${args.targetProject ? `(项目: ${args.targetProject})` : ''}`);
|
|
230
|
+
}
|
|
231
|
+
const lines = docs.map(d => `- ${d.path} (${d.status || '已完成'}): ${d.summary || '无简介'}`);
|
|
232
|
+
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
233
|
+
return wrap(`${prefix}找到 ${docs.length} 个文档:\n\n${lines.join('\n')}`);
|
|
234
|
+
}
|
|
235
|
+
// 全景目录树模式
|
|
146
236
|
const tree = args.targetProject
|
|
147
237
|
? await client().crossGetTree(args.targetProject)
|
|
148
238
|
: await client().getTree();
|
|
@@ -154,64 +244,4 @@ export function registerDocTools(server, projectId) {
|
|
|
154
244
|
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
155
245
|
return wrap(`${prefix}共 ${docCount} 个文档\n\n${treeText}`);
|
|
156
246
|
}));
|
|
157
|
-
// 按状态筛选文档
|
|
158
|
-
server.tool('kg_get_docs_by_status', '获取指定状态的文档列表。支持跨项目只读访问(需先用kg_list_projects获取项目ID)', {
|
|
159
|
-
statusList: z.array(z.string()).describe('状态列表(如["未完成","待修复"])'),
|
|
160
|
-
targetProject: z.string().optional().describe('目标项目ID或名称(不填=当前项目)。ID可从kg_list_projects获取')
|
|
161
|
-
}, async (args) => safeTool(async () => {
|
|
162
|
-
const docs = args.targetProject
|
|
163
|
-
? await client().crossGetDocsByStatus(args.targetProject, args.statusList)
|
|
164
|
-
: await client().getDocsByStatus(args.statusList);
|
|
165
|
-
if (docs.length === 0) {
|
|
166
|
-
return wrap(`未找到状态为 [${args.statusList.join(', ')}] 的文档${args.targetProject ? `(项目: ${args.targetProject})` : ''}`);
|
|
167
|
-
}
|
|
168
|
-
const lines = docs.map(d => `- ${d.path} (${d.status || '已完成'}): ${d.summary || '无简介'}`);
|
|
169
|
-
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
170
|
-
return wrap(`${prefix}找到 ${docs.length} 个文档:\n\n${lines.join('\n')}`);
|
|
171
|
-
}));
|
|
172
|
-
// 跨项目复制文档
|
|
173
|
-
server.tool('kg_copy_node', '从其他项目复制文档到当前项目。支持单个或批量复制,可指定新路径。用于跨项目协作:拉取别的项目的知识文档到当前项目', {
|
|
174
|
-
sourceProject: z.string().describe('源项目ID或名称(从kg_list_projects获取)'),
|
|
175
|
-
sourcePath: z.union([z.string(), z.array(z.string())]).describe('源文档路径(单个或数组)'),
|
|
176
|
-
targetPath: z.string().optional().describe('目标路径前缀(如"/参考/项目B")。不填则保持原路径'),
|
|
177
|
-
}, async (args) => safeTool(async () => {
|
|
178
|
-
const decoded = decodeObjectStrings(args);
|
|
179
|
-
const paths = Array.isArray(decoded.sourcePath) ? decoded.sourcePath : [decoded.sourcePath];
|
|
180
|
-
const results = [];
|
|
181
|
-
for (const srcPath of paths) {
|
|
182
|
-
try {
|
|
183
|
-
const doc = await client().crossGetDoc(decoded.sourceProject, srcPath);
|
|
184
|
-
if (!doc) {
|
|
185
|
-
results.push({ path: srcPath, success: false, error: '源文档不存在' });
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
const destPath = decoded.targetPath
|
|
189
|
-
? `${decoded.targetPath.replace(/\/$/, '')}${srcPath}`
|
|
190
|
-
: srcPath;
|
|
191
|
-
const existing = await client().getDoc(destPath);
|
|
192
|
-
if (existing) {
|
|
193
|
-
await client().updateDoc(destPath, { summary: doc.summary, content: doc.content });
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
await client().createDoc(destPath, { summary: doc.summary, content: doc.content, status: doc.status });
|
|
197
|
-
}
|
|
198
|
-
results.push({ path: destPath, success: true });
|
|
199
|
-
}
|
|
200
|
-
catch (e) {
|
|
201
|
-
results.push({ path: srcPath, success: false, error: String(e) });
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
const successCount = results.filter(r => r.success).length;
|
|
205
|
-
if (paths.length === 1) {
|
|
206
|
-
return wrap(results[0].success
|
|
207
|
-
? `✅ 已复制文档: ${results[0].path}\n\n来源: [${decoded.sourceProject}] ${paths[0]}`
|
|
208
|
-
: `❌ 复制失败: ${results[0].error}`);
|
|
209
|
-
}
|
|
210
|
-
const failedItems = results.filter(r => !r.success);
|
|
211
|
-
let msg = `✅ 批量复制完成: ${successCount}/${paths.length} 成功`;
|
|
212
|
-
if (failedItems.length > 0) {
|
|
213
|
-
msg += `\n❌ 失败:\n${failedItems.map(f => ` - ${f.path}: ${f.error}`).join('\n')}`;
|
|
214
|
-
}
|
|
215
|
-
return wrap(msg);
|
|
216
|
-
}));
|
|
217
247
|
}
|
package/dist/tools/files.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* 📁 kg_files (5→1)
|
|
3
|
+
* 合并: project_list_files, project_read_file, project_upload, project_download
|
|
4
|
+
* 删除: project_clear_files (危险操作)
|
|
5
5
|
*/
|
|
6
6
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
7
|
export declare function registerFileTools(server: McpServer): void;
|
package/dist/tools/files.js
CHANGED
|
@@ -1,66 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* 📁 kg_files (5→1)
|
|
3
|
+
* 合并: project_list_files, project_read_file, project_upload, project_download
|
|
4
|
+
* 删除: project_clear_files (危险操作)
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
server.tool('kg_files', '📁 项目文件操作 — 浏览目录、读取文件、上传下载。action: list(目录浏览)|read(读取文件)|upload(上传目录)|download(下载文件)', {
|
|
12
|
+
action: z.enum(['list', 'read', 'upload', 'download'])
|
|
13
|
+
.describe('操作类型'),
|
|
14
|
+
path: z.string().optional()
|
|
15
|
+
.describe('文件路径 (read/download, 如"src/main.ts")'),
|
|
16
|
+
dir: z.string().optional()
|
|
17
|
+
.describe('子目录路径 (list, 如"src/components")'),
|
|
18
|
+
localDir: z.string().optional()
|
|
19
|
+
.describe('本地目录绝对路径 (upload)'),
|
|
20
|
+
localPath: z.string().optional()
|
|
21
|
+
.describe('本地保存路径 (download)'),
|
|
22
|
+
remoteDir: z.string().optional()
|
|
23
|
+
.describe('远程目标子目录 (upload)'),
|
|
24
|
+
targetProject: z.string().optional()
|
|
25
|
+
.describe('跨项目操作的目标项目ID'),
|
|
15
26
|
}, async (args) => safeTool(async () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}, async (args) => safeTool(async () => {
|
|
60
|
-
const result = args.targetProject
|
|
61
|
-
? await client().crossDownload(args.targetProject, args.remotePath, args.localPath)
|
|
62
|
-
: await client().download(args.remotePath, args.localPath);
|
|
63
|
-
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
64
|
-
return wrap(`${prefix}✅ 已下载并解压\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}`);
|
|
27
|
+
switch (args.action) {
|
|
28
|
+
case 'list': {
|
|
29
|
+
const files = args.targetProject
|
|
30
|
+
? await client().crossListFiles(args.targetProject, args.dir)
|
|
31
|
+
: await client().listFiles(args.dir);
|
|
32
|
+
if (files.length === 0)
|
|
33
|
+
return wrap('目录为空');
|
|
34
|
+
const lines = files.map(f => {
|
|
35
|
+
const icon = f.isDir ? '📁' : '📄';
|
|
36
|
+
const size = f.isDir ? '' : ` (${formatFileSize(f.size)})`;
|
|
37
|
+
return `${icon} ${f.name}${size}`;
|
|
38
|
+
});
|
|
39
|
+
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
40
|
+
const dirLabel = args.dir || '/';
|
|
41
|
+
return wrap(`${prefix}📂 ${dirLabel} (${files.length} 项)\n\n${lines.join('\n')}`);
|
|
42
|
+
}
|
|
43
|
+
case 'read': {
|
|
44
|
+
if (!args.path)
|
|
45
|
+
return wrap('❌ read 需要 path');
|
|
46
|
+
const content = args.targetProject
|
|
47
|
+
? await client().crossReadFile(args.targetProject, args.path)
|
|
48
|
+
: await client().readFile(args.path);
|
|
49
|
+
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
50
|
+
return wrap(`${prefix}📄 ${args.path}\n\n\`\`\`\n${content}\n\`\`\``);
|
|
51
|
+
}
|
|
52
|
+
case 'upload': {
|
|
53
|
+
if (!args.localDir)
|
|
54
|
+
return wrap('❌ upload 需要 localDir');
|
|
55
|
+
const result = await client().uploadFiles(args.localDir, args.remoteDir);
|
|
56
|
+
return wrap(`✅ 上传成功\n\n- 文件数量: ${result.fileCount}\n- 目标目录: ${args.remoteDir || '/'}`);
|
|
57
|
+
}
|
|
58
|
+
case 'download': {
|
|
59
|
+
if (!args.path)
|
|
60
|
+
return wrap('❌ download 需要 path');
|
|
61
|
+
const result = args.targetProject
|
|
62
|
+
? await client().crossDownload(args.targetProject, args.path, args.localPath)
|
|
63
|
+
: await client().download(args.path, args.localPath);
|
|
64
|
+
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
65
|
+
return wrap(`${prefix}✅ 已下载\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}`);
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
return wrap(`❌ 未知 action: ${args.action}`);
|
|
69
|
+
}
|
|
65
70
|
}));
|
|
66
71
|
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP 工具注册入口
|
|
3
|
-
*
|
|
3
|
+
* 精简后: 12 个工具, 5 个子模块
|
|
4
|
+
*
|
|
5
|
+
* 📊 导航: kg_status, kg_tree (2个)
|
|
6
|
+
* 📚 知识: kg_doc, kg_projects, kg_rules (3个)
|
|
7
|
+
* 📝 工作流: kg_task, kg_files, kg_discuss (3个)
|
|
8
|
+
* 🔬 代码分析: code_scan, code_query, code_impact, code_context (4个)
|
|
4
9
|
*/
|
|
5
10
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
11
|
export declare function registerTools(server: McpServer, projectId: string, user: string): void;
|
package/dist/tools/index.js
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP 工具注册入口
|
|
3
|
-
*
|
|
3
|
+
* 精简后: 12 个工具, 5 个子模块
|
|
4
|
+
*
|
|
5
|
+
* 📊 导航: kg_status, kg_tree (2个)
|
|
6
|
+
* 📚 知识: kg_doc, kg_projects, kg_rules (3个)
|
|
7
|
+
* 📝 工作流: kg_task, kg_files, kg_discuss (3个)
|
|
8
|
+
* 🔬 代码分析: code_scan, code_query, code_impact, code_context (4个)
|
|
4
9
|
*/
|
|
10
|
+
import { registerStatusTool } from './kg_status.js';
|
|
5
11
|
import { registerDocTools } from './docs.js';
|
|
12
|
+
import { registerProjectTools } from './projects.js';
|
|
6
13
|
import { registerRuleTools } from './rules.js';
|
|
7
14
|
import { registerTaskTools } from './tasks.js';
|
|
8
15
|
import { registerFileTools } from './files.js';
|
|
9
|
-
import {
|
|
16
|
+
import { registerDiscussionTools } from './discussion.js';
|
|
17
|
+
import { registerAnalyzerTools } from './analyzer.js';
|
|
10
18
|
export function registerTools(server, projectId, user) {
|
|
11
|
-
|
|
19
|
+
// 📊 导航 (kg_status + kg_tree在docs中)
|
|
20
|
+
registerStatusTool(server, projectId);
|
|
21
|
+
// 📚 知识 (kg_doc + kg_tree + kg_projects + kg_rules)
|
|
12
22
|
registerDocTools(server, projectId);
|
|
23
|
+
registerProjectTools(server, projectId);
|
|
13
24
|
registerRuleTools(server, projectId);
|
|
25
|
+
// 📝 工作流 (kg_task + kg_files + kg_discuss)
|
|
14
26
|
registerTaskTools(server, projectId, user);
|
|
15
27
|
registerFileTools(server);
|
|
28
|
+
registerDiscussionTools(server, projectId);
|
|
29
|
+
// 🔬 代码分析 (不变)
|
|
30
|
+
registerAnalyzerTools(server, projectId);
|
|
16
31
|
}
|