@ppdocs/mcp 3.1.10 → 3.2.1
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.js +9 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +4 -40
- package/dist/index.js +1 -1
- 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 -40
- 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 +7 -3
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
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 📊 kg_status — 项目速览仪表盘 (新增)
|
|
3
|
+
*/
|
|
4
|
+
import { getClient } from '../storage/httpClient.js';
|
|
5
|
+
import { wrap, safeTool } from './shared.js';
|
|
6
|
+
export function registerStatusTool(server, projectId) {
|
|
7
|
+
const client = () => getClient();
|
|
8
|
+
server.tool('kg_status', '📊 项目速览仪表盘 — 一键了解项目健康。返回: 文档总数、目录数、活跃任务数、最近变更。每次对话开始建议首先调用', {}, async () => safeTool(async () => {
|
|
9
|
+
const [docs, activeTasks] = await Promise.all([
|
|
10
|
+
client().listDocs(),
|
|
11
|
+
client().listTasks('active'),
|
|
12
|
+
]);
|
|
13
|
+
const dirCount = docs.filter(d => d.isDir).length;
|
|
14
|
+
const docCount = docs.filter(d => !d.isDir).length;
|
|
15
|
+
const statusMap = new Map();
|
|
16
|
+
for (const d of docs) {
|
|
17
|
+
if (d.isDir)
|
|
18
|
+
continue;
|
|
19
|
+
const s = d.status || '已完成';
|
|
20
|
+
statusMap.set(s, (statusMap.get(s) || 0) + 1);
|
|
21
|
+
}
|
|
22
|
+
const lines = [
|
|
23
|
+
`📊 项目速览 [${projectId}]`,
|
|
24
|
+
``,
|
|
25
|
+
`📄 文档: ${docCount} 个 | 📁 目录: ${dirCount} 个`,
|
|
26
|
+
`📝 活跃任务: ${activeTasks.length} 个`,
|
|
27
|
+
];
|
|
28
|
+
if (statusMap.size > 0) {
|
|
29
|
+
const statusLine = Array.from(statusMap.entries())
|
|
30
|
+
.map(([s, c]) => `${s}(${c})`)
|
|
31
|
+
.join(' | ');
|
|
32
|
+
lines.push(`📋 状态分布: ${statusLine}`);
|
|
33
|
+
}
|
|
34
|
+
if (activeTasks.length > 0) {
|
|
35
|
+
lines.push(``, `🔧 进行中的任务:`);
|
|
36
|
+
for (const t of activeTasks.slice(0, 5)) {
|
|
37
|
+
lines.push(` - ${t.title} (${t.id})`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return wrap(lines.join('\n'));
|
|
41
|
+
}));
|
|
42
|
+
}
|
package/dist/tools/projects.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* kg_list_projects,
|
|
2
|
+
* 📋 kg_projects (3→1)
|
|
3
|
+
* 合并: kg_list_projects, kg_update_root → kg_doc 处理
|
|
4
|
+
* 删除: kg_create_project (桌面端操作)
|
|
4
5
|
*/
|
|
5
6
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
7
|
export declare function registerProjectTools(server: McpServer, projectId: string): void;
|
package/dist/tools/projects.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* kg_list_projects,
|
|
2
|
+
* 📋 kg_projects (3→1)
|
|
3
|
+
* 合并: kg_list_projects, kg_update_root → kg_doc 处理
|
|
4
|
+
* 删除: kg_create_project (桌面端操作)
|
|
4
5
|
*/
|
|
5
|
-
import { z } from 'zod';
|
|
6
6
|
import { getClient } from '../storage/httpClient.js';
|
|
7
|
-
import { decodeObjectStrings } from '../utils.js';
|
|
8
7
|
import { wrap, safeTool } from './shared.js';
|
|
9
8
|
export function registerProjectTools(server, projectId) {
|
|
10
9
|
const client = () => getClient();
|
|
11
|
-
|
|
12
|
-
server.tool('kg_list_projects', '列出所有可访问的项目(返回name和id)。跨项目操作的第一步:先调用此工具获取项目ID,再作为其他工具的targetProject参数', {}, async () => safeTool(async () => {
|
|
10
|
+
server.tool('kg_projects', '📋 列出所有可访问的项目(返回名称和ID)。跨项目操作的第一步: 获取项目ID后用于其他工具的 targetProject 参数', {}, async () => safeTool(async () => {
|
|
13
11
|
const projects = await client().crossListProjects();
|
|
14
12
|
if (projects.length === 0)
|
|
15
13
|
return wrap('暂无可访问的项目');
|
|
@@ -19,38 +17,4 @@ export function registerProjectTools(server, projectId) {
|
|
|
19
17
|
});
|
|
20
18
|
return wrap(`可访问的项目 (${projects.length} 个):\n\n${lines.join('\n')}`);
|
|
21
19
|
}));
|
|
22
|
-
// 创建项目
|
|
23
|
-
server.tool('kg_create_project', '创建新项目。返回项目ID和密码(密码用于MCP连接)', {
|
|
24
|
-
id: z.string().optional().describe('项目ID(可选,不填则自动生成随机ID)'),
|
|
25
|
-
name: z.string().describe('项目名称'),
|
|
26
|
-
description: z.string().optional().describe('项目简介'),
|
|
27
|
-
projectPath: z.string().optional().describe('项目源码路径(本地绝对路径)'),
|
|
28
|
-
}, async (args) => safeTool(async () => {
|
|
29
|
-
// 如果未提供 ID,自动生成随机ID
|
|
30
|
-
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
31
|
-
const randomPart = Array.from({ length: 8 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
|
|
32
|
-
const id = args.id || `p-${randomPart}`;
|
|
33
|
-
const result = await client().createProject(id, args.name, args.description, args.projectPath || process.cwd());
|
|
34
|
-
return wrap(`✅ 项目创建成功\n\n- 项目ID: ${result.project.id}\n- 项目名: ${result.project.name}\n- 密码: ${result.password}\n\nMCP 连接地址: http://<服务器IP>:20001/api/${result.project.id}/${result.password}`);
|
|
35
|
-
}));
|
|
36
|
-
// 更新根文档 (项目介绍)
|
|
37
|
-
server.tool('kg_update_root', '更新项目介绍(根文档)', {
|
|
38
|
-
title: z.string().optional().describe('项目标题'),
|
|
39
|
-
description: z.string().optional().describe('项目介绍(Markdown)')
|
|
40
|
-
}, async (args) => safeTool(async () => {
|
|
41
|
-
const decoded = decodeObjectStrings(args);
|
|
42
|
-
if (decoded.title === undefined && decoded.description === undefined) {
|
|
43
|
-
return wrap('❌ 请至少提供 title 或 description');
|
|
44
|
-
}
|
|
45
|
-
const existing = await client().getDoc('/');
|
|
46
|
-
if (!existing)
|
|
47
|
-
return wrap('更新失败(根文档不存在)');
|
|
48
|
-
const updates = {};
|
|
49
|
-
if (decoded.title !== undefined)
|
|
50
|
-
updates.summary = decoded.title;
|
|
51
|
-
if (decoded.description !== undefined)
|
|
52
|
-
updates.content = decoded.description;
|
|
53
|
-
const doc = await client().updateDoc('/', updates);
|
|
54
|
-
return wrap(doc ? '✅ 项目介绍已更新' : '更新失败');
|
|
55
|
-
}));
|
|
56
20
|
}
|
package/dist/tools/rules.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* kg_get_rules, kg_save_rules, kg_get_rules_meta, kg_save_rules_meta
|
|
4
|
-
* kg_get_global_rules_meta, kg_save_global_rules_meta
|
|
2
|
+
* 📏 kg_rules (6→1)
|
|
3
|
+
* 合并: kg_get_rules, kg_save_rules, kg_get_rules_meta, kg_save_rules_meta
|
|
4
|
+
* 删除: kg_get_global_rules_meta, kg_save_global_rules_meta (管理员操作)
|
|
5
5
|
*/
|
|
6
6
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
7
|
export declare function registerRuleTools(server: McpServer, projectId: string): void;
|
package/dist/tools/rules.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* kg_get_rules, kg_save_rules, kg_get_rules_meta, kg_save_rules_meta
|
|
4
|
-
* kg_get_global_rules_meta, kg_save_global_rules_meta
|
|
2
|
+
* 📏 kg_rules (6→1)
|
|
3
|
+
* 合并: kg_get_rules, kg_save_rules, kg_get_rules_meta, kg_save_rules_meta
|
|
4
|
+
* 删除: kg_get_global_rules_meta, kg_save_global_rules_meta (管理员操作)
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { getClient } from '../storage/httpClient.js';
|
|
@@ -9,117 +9,95 @@ import { decodeObjectStrings, getRules, RULE_TYPE_LABELS } from '../utils.js';
|
|
|
9
9
|
import { wrap, safeTool, crossPrefix } from './shared.js';
|
|
10
10
|
export function registerRuleTools(server, projectId) {
|
|
11
11
|
const client = () => getClient();
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (args.ruleType) {
|
|
20
|
-
const rules = await client().crossGetRules(args.targetProject, args.ruleType);
|
|
21
|
-
if (rules.length === 0) {
|
|
22
|
-
const label = crossMeta[args.ruleType]?.label || args.ruleType;
|
|
23
|
-
return wrap(`暂无${label}规则(项目: ${args.targetProject})`);
|
|
24
|
-
}
|
|
25
|
-
return wrap(`${crossPrefix(args.targetProject)}${rules.join('\n')}`);
|
|
26
|
-
}
|
|
27
|
-
const types = Object.keys(crossMeta).length > 0 ? Object.keys(crossMeta) : Object.keys(RULE_TYPE_LABELS);
|
|
28
|
-
const allRules = [];
|
|
29
|
-
for (const type of types) {
|
|
30
|
-
const rules = await client().crossGetRules(args.targetProject, type);
|
|
31
|
-
if (rules.length > 0) {
|
|
32
|
-
const label = crossMeta[type]?.label || RULE_TYPE_LABELS[type] || type;
|
|
33
|
-
allRules.push(`## ${label}\n${rules.join('\n')}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (allRules.length === 0)
|
|
37
|
-
return wrap(`暂无项目规则(项目: ${args.targetProject})`);
|
|
38
|
-
return wrap(`${crossPrefix(args.targetProject)}${allRules.join('\n\n')}`);
|
|
39
|
-
}
|
|
40
|
-
const rules = await getRules(projectId, args.ruleType || undefined);
|
|
41
|
-
if (!rules || rules.trim() === '') {
|
|
42
|
-
const meta = await client().getRulesMeta();
|
|
43
|
-
const typeName = args.ruleType ? (meta[args.ruleType]?.label || args.ruleType) : '项目';
|
|
44
|
-
return wrap(`暂无${typeName}规则`);
|
|
45
|
-
}
|
|
46
|
-
return wrap(rules);
|
|
47
|
-
}));
|
|
48
|
-
// 保存项目规则 (自动合并,去重追加)
|
|
49
|
-
server.tool('kg_save_rules', '保存单个类型的项目规则(自动合并:已有规则保留,新规则去重追加)', {
|
|
50
|
-
ruleType: z.string().describe('规则类型(如 userStyles, codeStyle, reviewRules, testRules, unitTests, 或自定义类型)'),
|
|
51
|
-
rules: z.array(z.string()).describe('规则数组')
|
|
52
|
-
}, async (args) => safeTool(async () => {
|
|
53
|
-
const decoded = decodeObjectStrings(args);
|
|
54
|
-
// appendRules 逻辑内联(原 httpClient 包装函数)
|
|
55
|
-
const existing = await client().getRulesApi(decoded.ruleType);
|
|
56
|
-
const existingSet = new Set(existing.map(r => r.trim()));
|
|
57
|
-
const toAdd = decoded.rules.filter(r => !existingSet.has(r.trim()));
|
|
58
|
-
const merged = [...existing, ...toAdd];
|
|
59
|
-
const success = await client().saveRulesApi(decoded.ruleType, merged);
|
|
60
|
-
if (!success)
|
|
61
|
-
return wrap('❌ 保存失败');
|
|
62
|
-
const meta = await client().getRulesMeta();
|
|
63
|
-
const label = meta[decoded.ruleType]?.label || decoded.ruleType;
|
|
64
|
-
return wrap(`✅ ${label}已保存 (新增${toAdd.length}条, 共${merged.length}条)`);
|
|
65
|
-
}));
|
|
66
|
-
// 获取规则触发配置
|
|
67
|
-
server.tool('kg_get_rules_meta', '获取规则触发配置(所有类型的标签、关键词、触发数)。用于查看/编辑 hooks 触发条件', {}, async () => safeTool(async () => {
|
|
68
|
-
const meta = await client().getRulesMeta();
|
|
69
|
-
if (Object.keys(meta).length === 0)
|
|
70
|
-
return wrap('暂无规则配置');
|
|
71
|
-
const lines = Object.entries(meta).map(([type, m]) => {
|
|
72
|
-
const kw = m.keywords.length > 0 ? m.keywords.join(', ') : '(无)';
|
|
73
|
-
const trigger = m.always ? '始终触发' : `关键词≥${m.min_hits}: ${kw}`;
|
|
74
|
-
return `- **${m.label}** (${type}): ${trigger}`;
|
|
75
|
-
});
|
|
76
|
-
return wrap(`规则触发配置:\n\n${lines.join('\n')}`);
|
|
77
|
-
}));
|
|
78
|
-
// 保存规则触发配置 (自动合并)
|
|
79
|
-
server.tool('kg_save_rules_meta', '保存规则触发配置(自动合并:已有类型更新,新类型创建,其他类型不受影响)', {
|
|
12
|
+
server.tool('kg_rules', '📏 项目规则管理 — 读取或保存代码风格、审查规则等。action: get(读取规则)|save(保存规则)|get_meta(读取触发配置)|save_meta(保存触发配置)', {
|
|
13
|
+
action: z.enum(['get', 'save', 'get_meta', 'save_meta'])
|
|
14
|
+
.describe('操作类型'),
|
|
15
|
+
ruleType: z.string().optional()
|
|
16
|
+
.describe('规则类型(如 userStyles, codeStyle, reviewRules)。get/save 时使用,不传则获取全部'),
|
|
17
|
+
rules: z.array(z.string()).optional()
|
|
18
|
+
.describe('save 时的规则数组'),
|
|
80
19
|
meta: z.record(z.string(), z.object({
|
|
81
20
|
label: z.string().describe('规则显示名称'),
|
|
82
21
|
keywords: z.array(z.string()).default([]).describe('触发关键词列表'),
|
|
83
22
|
min_hits: z.number().default(1).describe('最低触发关键词数'),
|
|
84
|
-
always: z.boolean().default(false).describe('是否始终触发
|
|
85
|
-
})).describe('
|
|
23
|
+
always: z.boolean().default(false).describe('是否始终触发')
|
|
24
|
+
})).optional().describe('save_meta 时的触发配置映射'),
|
|
25
|
+
targetProject: z.string().optional()
|
|
26
|
+
.describe('跨项目读取 (get)'),
|
|
86
27
|
}, async (args) => safeTool(async () => {
|
|
87
|
-
const decoded = decodeObjectStrings(args
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
28
|
+
const decoded = decodeObjectStrings(args);
|
|
29
|
+
switch (decoded.action) {
|
|
30
|
+
case 'get': {
|
|
31
|
+
if (decoded.targetProject) {
|
|
32
|
+
const crossMeta = await client().crossGetRulesMeta(decoded.targetProject);
|
|
33
|
+
if (decoded.ruleType) {
|
|
34
|
+
const rules = await client().crossGetRules(decoded.targetProject, decoded.ruleType);
|
|
35
|
+
if (rules.length === 0) {
|
|
36
|
+
const label = crossMeta[decoded.ruleType]?.label || decoded.ruleType;
|
|
37
|
+
return wrap(`暂无${label}规则(项目: ${decoded.targetProject})`);
|
|
38
|
+
}
|
|
39
|
+
return wrap(`${crossPrefix(decoded.targetProject)}${rules.join('\n')}`);
|
|
40
|
+
}
|
|
41
|
+
const types = Object.keys(crossMeta).length > 0 ? Object.keys(crossMeta) : Object.keys(RULE_TYPE_LABELS);
|
|
42
|
+
const allRules = [];
|
|
43
|
+
for (const type of types) {
|
|
44
|
+
const rules = await client().crossGetRules(decoded.targetProject, type);
|
|
45
|
+
if (rules.length > 0) {
|
|
46
|
+
const label = crossMeta[type]?.label || RULE_TYPE_LABELS[type] || type;
|
|
47
|
+
allRules.push(`## ${label}\n${rules.join('\n')}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (allRules.length === 0)
|
|
51
|
+
return wrap(`暂无项目规则(项目: ${decoded.targetProject})`);
|
|
52
|
+
return wrap(`${crossPrefix(decoded.targetProject)}${allRules.join('\n\n')}`);
|
|
53
|
+
}
|
|
54
|
+
const rules = await getRules(projectId, decoded.ruleType || undefined);
|
|
55
|
+
if (!rules || rules.trim() === '') {
|
|
56
|
+
const meta = await client().getRulesMeta();
|
|
57
|
+
const typeName = decoded.ruleType ? (meta[decoded.ruleType]?.label || decoded.ruleType) : '项目';
|
|
58
|
+
return wrap(`暂无${typeName}规则`);
|
|
59
|
+
}
|
|
60
|
+
return wrap(rules);
|
|
61
|
+
}
|
|
62
|
+
case 'save': {
|
|
63
|
+
if (!decoded.ruleType)
|
|
64
|
+
return wrap('❌ save 需要 ruleType');
|
|
65
|
+
if (!decoded.rules)
|
|
66
|
+
return wrap('❌ save 需要 rules 数组');
|
|
67
|
+
const existing = await client().getRulesApi(decoded.ruleType);
|
|
68
|
+
const existingSet = new Set(existing.map(r => r.trim()));
|
|
69
|
+
const toAdd = decoded.rules.filter((r) => !existingSet.has(r.trim()));
|
|
70
|
+
const merged = [...existing, ...toAdd];
|
|
71
|
+
const success = await client().saveRulesApi(decoded.ruleType, merged);
|
|
72
|
+
if (!success)
|
|
73
|
+
return wrap('❌ 保存失败');
|
|
74
|
+
const meta = await client().getRulesMeta();
|
|
75
|
+
const label = meta[decoded.ruleType]?.label || decoded.ruleType;
|
|
76
|
+
return wrap(`✅ ${label}已保存 (新增${toAdd.length}条, 共${merged.length}条)`);
|
|
77
|
+
}
|
|
78
|
+
case 'get_meta': {
|
|
79
|
+
const meta = await client().getRulesMeta();
|
|
80
|
+
if (Object.keys(meta).length === 0)
|
|
81
|
+
return wrap('暂无规则配置');
|
|
82
|
+
const lines = Object.entries(meta).map(([type, m]) => {
|
|
83
|
+
const kw = m.keywords.length > 0 ? m.keywords.join(', ') : '(无)';
|
|
84
|
+
const trigger = m.always ? '始终触发' : `关键词≥${m.min_hits}: ${kw}`;
|
|
85
|
+
return `- **${m.label}** (${type}): ${trigger}`;
|
|
86
|
+
});
|
|
87
|
+
return wrap(`规则触发配置:\n\n${lines.join('\n')}`);
|
|
88
|
+
}
|
|
89
|
+
case 'save_meta': {
|
|
90
|
+
if (!decoded.meta)
|
|
91
|
+
return wrap('❌ save_meta 需要 meta');
|
|
92
|
+
const existing = await client().getRulesMeta();
|
|
93
|
+
const merged = { ...existing, ...decoded.meta };
|
|
94
|
+
const success = await client().saveRulesMeta(merged);
|
|
95
|
+
if (!success)
|
|
96
|
+
return wrap('❌ 保存失败');
|
|
97
|
+
return wrap(`✅ 触发配置已保存 (更新${Object.keys(decoded.meta).length}个类型, 共${Object.keys(merged).length}个类型)`);
|
|
98
|
+
}
|
|
99
|
+
default:
|
|
100
|
+
return wrap(`❌ 未知 action: ${decoded.action}`);
|
|
101
|
+
}
|
|
124
102
|
}));
|
|
125
103
|
}
|
package/dist/tools/tasks.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* task_create, task_get, task_update, task_archive
|
|
2
|
+
* 📝 kg_task (4→1)
|
|
3
|
+
* 合并: task_create, task_get, task_update, task_archive
|
|
4
4
|
*/
|
|
5
5
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
6
|
export declare function registerTaskTools(server: McpServer, projectId: string, user: string): void;
|