@ppdocs/mcp 3.1.8 → 3.1.9

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.
@@ -0,0 +1,52 @@
1
+ /**
2
+ * 项目管理工具 (3个)
3
+ * kg_list_projects, kg_create_project, kg_update_root
4
+ */
5
+ import { z } from 'zod';
6
+ import { getClient } from '../storage/httpClient.js';
7
+ import { decodeObjectStrings } from '../utils.js';
8
+ import { wrap, safeTool } from './shared.js';
9
+ export function registerProjectTools(server, projectId) {
10
+ const client = () => getClient();
11
+ // 列出所有可访问的项目
12
+ server.tool('kg_list_projects', '列出所有可访问的项目(返回name和id)。跨项目操作的第一步:先调用此工具获取项目ID,再作为其他工具的targetProject参数', {}, async () => safeTool(async () => {
13
+ const projects = await client().crossListProjects();
14
+ if (projects.length === 0)
15
+ return wrap('暂无可访问的项目');
16
+ const lines = projects.map(p => {
17
+ const isCurrent = p.id === projectId ? ' ★当前' : '';
18
+ return `- ${p.name} (${p.id})${isCurrent}`;
19
+ });
20
+ return wrap(`可访问的项目 (${projects.length} 个):\n\n${lines.join('\n')}`);
21
+ }));
22
+ // 创建项目
23
+ server.tool('kg_create_project', '创建新项目。返回项目ID和密码(密码用于MCP连接)', {
24
+ id: z.string().describe('项目ID(英文/数字/中文,如"my-app")'),
25
+ name: z.string().describe('项目名称'),
26
+ description: z.string().optional().describe('项目简介'),
27
+ projectPath: z.string().optional().describe('项目源码路径(本地绝对路径)'),
28
+ }, async (args) => safeTool(async () => {
29
+ const result = await client().createProject(args.id, args.name, args.description, args.projectPath || process.cwd());
30
+ 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}`);
31
+ }));
32
+ // 更新根文档 (项目介绍)
33
+ server.tool('kg_update_root', '更新项目介绍(根文档)', {
34
+ title: z.string().optional().describe('项目标题'),
35
+ description: z.string().optional().describe('项目介绍(Markdown)')
36
+ }, async (args) => safeTool(async () => {
37
+ const decoded = decodeObjectStrings(args);
38
+ if (decoded.title === undefined && decoded.description === undefined) {
39
+ return wrap('❌ 请至少提供 title 或 description');
40
+ }
41
+ const existing = await client().getDoc('/');
42
+ if (!existing)
43
+ return wrap('更新失败(根文档不存在)');
44
+ const updates = {};
45
+ if (decoded.title !== undefined)
46
+ updates.summary = decoded.title;
47
+ if (decoded.description !== undefined)
48
+ updates.content = decoded.description;
49
+ const doc = await client().updateDoc('/', updates);
50
+ return wrap(doc ? '✅ 项目介绍已更新' : '更新失败');
51
+ }));
52
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 规则管理工具 (6个)
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
+ */
6
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ export declare function registerRuleTools(server: McpServer, projectId: string): void;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * 规则管理工具 (6个)
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
+ */
6
+ import { z } from 'zod';
7
+ import { getClient } from '../storage/httpClient.js';
8
+ import { decodeObjectStrings, getRules, RULE_TYPE_LABELS } from '../utils.js';
9
+ import { wrap, safeTool, crossPrefix } from './shared.js';
10
+ export function registerRuleTools(server, projectId) {
11
+ const client = () => getClient();
12
+ // 获取项目规则
13
+ server.tool('kg_get_rules', '获取项目规则(可指定类型或获取全部)。支持跨项目只读访问(需先用kg_list_projects获取项目ID)', {
14
+ ruleType: z.string().optional().describe('规则类型(如 userStyles, codeStyle, reviewRules, testRules, unitTests, 或自定义类型)。不传则返回全部'),
15
+ targetProject: z.string().optional().describe('目标项目ID或名称(不填=当前项目)。ID可从kg_list_projects获取')
16
+ }, async (args) => safeTool(async () => {
17
+ if (args.targetProject) {
18
+ const crossMeta = await client().crossGetRulesMeta(args.targetProject);
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', '保存规则触发配置(自动合并:已有类型更新,新类型创建,其他类型不受影响)', {
80
+ meta: z.record(z.string(), z.object({
81
+ label: z.string().describe('规则显示名称'),
82
+ keywords: z.array(z.string()).default([]).describe('触发关键词列表'),
83
+ min_hits: z.number().default(1).describe('最低触发关键词数'),
84
+ always: z.boolean().default(false).describe('是否始终触发(如 userStyles)')
85
+ })).describe('规则类型 → 触发配置的映射')
86
+ }, async (args) => safeTool(async () => {
87
+ const decoded = decodeObjectStrings(args.meta);
88
+ // mergeRulesMeta 逻辑内联
89
+ const existing = await client().getRulesMeta();
90
+ const merged = { ...existing, ...decoded };
91
+ const success = await client().saveRulesMeta(merged);
92
+ if (!success)
93
+ return wrap('❌ 保存失败');
94
+ return wrap(`✅ 触发配置已保存 (更新${Object.keys(decoded).length}个类型, 共${Object.keys(merged).length}个类型)`);
95
+ }));
96
+ // 获取全局默认规则触发配置
97
+ server.tool('kg_get_global_rules_meta', '获取全局默认规则触发配置(新项目创建时继承此配置)', {}, async () => safeTool(async () => {
98
+ const meta = await client().getGlobalRulesMeta();
99
+ if (Object.keys(meta).length === 0)
100
+ return wrap('暂无全局默认规则配置');
101
+ const lines = Object.entries(meta).map(([type, m]) => {
102
+ const kw = m.keywords.length > 0 ? m.keywords.join(', ') : '(无)';
103
+ const trigger = m.always ? '始终触发' : `关键词≥${m.min_hits}: ${kw}`;
104
+ return `- **${m.label}** (${type}): ${trigger}`;
105
+ });
106
+ return wrap(`全局默认规则触发配置:\n\n${lines.join('\n')}`);
107
+ }));
108
+ // 保存全局默认规则触发配置 (自动合并)
109
+ server.tool('kg_save_global_rules_meta', '保存全局默认触发配置(自动合并:已有类型更新,新类型创建,其他类型不受影响)', {
110
+ meta: z.record(z.string(), z.object({
111
+ label: z.string().describe('规则显示名称'),
112
+ keywords: z.array(z.string()).default([]).describe('触发关键词列表'),
113
+ min_hits: z.number().default(1).describe('最低触发关键词数'),
114
+ always: z.boolean().default(false).describe('是否始终触发(如 userStyles)')
115
+ })).describe('规则类型 → 触发配置的映射')
116
+ }, async (args) => safeTool(async () => {
117
+ const decoded = decodeObjectStrings(args.meta);
118
+ const existing = await client().getGlobalRulesMeta();
119
+ const merged = { ...existing, ...decoded };
120
+ const success = await client().saveGlobalRulesMeta(merged);
121
+ if (!success)
122
+ return wrap('❌ 保存失败');
123
+ return wrap(`✅ 全局触发配置已保存 (更新${Object.keys(decoded).length}个类型, 共${Object.keys(merged).length}个类型)`);
124
+ }));
125
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * MCP 工具共享模块
3
+ * 合并原 helpers.ts + 新增 safeTool / withCross 模式
4
+ */
5
+ import type { DocData } from '../storage/types.js';
6
+ export declare function wrap(text: string): {
7
+ content: {
8
+ type: "text";
9
+ text: string;
10
+ }[];
11
+ };
12
+ export declare function safeTool(fn: () => Promise<ReturnType<typeof wrap>>): Promise<ReturnType<typeof wrap>>;
13
+ export declare function crossPrefix(target: string): string;
14
+ export declare function formatDocMarkdown(doc: DocData, path: string): string[];
15
+ interface TreeNode {
16
+ path: string;
17
+ name: string;
18
+ summary?: string;
19
+ isDir: boolean;
20
+ children?: TreeNode[];
21
+ }
22
+ export declare function formatTreeText(tree: TreeNode[]): string;
23
+ export declare function countTreeDocs(tree: TreeNode[]): number;
24
+ export declare function formatFileSize(bytes: number): string;
25
+ export {};
@@ -0,0 +1,88 @@
1
+ /**
2
+ * MCP 工具共享模块
3
+ * 合并原 helpers.ts + 新增 safeTool / withCross 模式
4
+ */
5
+ // ==================== MCP 返回包装 ====================
6
+ export function wrap(text) {
7
+ return { content: [{ type: 'text', text }] };
8
+ }
9
+ // ==================== 安全工具包装 ====================
10
+ export async function safeTool(fn) {
11
+ try {
12
+ return await fn();
13
+ }
14
+ catch (e) {
15
+ return wrap(`❌ ${String(e)}`);
16
+ }
17
+ }
18
+ // ==================== 跨项目高阶函数 ====================
19
+ export function crossPrefix(target) {
20
+ return `[跨项目: ${target}]\n\n`;
21
+ }
22
+ // ==================== 文档格式化 ====================
23
+ export function formatDocMarkdown(doc, path) {
24
+ const lines = [];
25
+ const name = path.split('/').pop() || path;
26
+ lines.push(`## ${name}\n`);
27
+ if (doc.summary)
28
+ lines.push(`> ${doc.summary}\n`);
29
+ if (doc.content) {
30
+ lines.push('**内容**');
31
+ lines.push(doc.content);
32
+ lines.push('');
33
+ }
34
+ if (doc.versions && doc.versions.length > 0) {
35
+ lines.push('**更新历史**');
36
+ lines.push('| 版本 | 日期 | 变更 |');
37
+ lines.push('|:---|:---|:---|');
38
+ doc.versions.forEach((v) => lines.push(`| ${v.version} | ${v.date} | ${v.changes} |`));
39
+ lines.push('');
40
+ }
41
+ if (doc.bugfixes && doc.bugfixes.length > 0) {
42
+ lines.push('**修复历史**');
43
+ lines.push('| 日期 | 问题 | 方案 |');
44
+ lines.push('|:---|:---|:---|');
45
+ doc.bugfixes.forEach((b) => lines.push(`| ${b.date} | ${b.issue} | ${b.solution} |`));
46
+ lines.push('');
47
+ }
48
+ return lines;
49
+ }
50
+ export function formatTreeText(tree) {
51
+ function format(nodes, prefix = '') {
52
+ const lines = [];
53
+ nodes.forEach((node, index) => {
54
+ const isLast = index === nodes.length - 1;
55
+ const connector = isLast ? '└── ' : '├── ';
56
+ const childPrefix = isLast ? ' ' : '│ ';
57
+ const icon = node.isDir ? '📁' : '📄';
58
+ const summary = node.summary ? ` # ${node.summary}` : '';
59
+ lines.push(`${prefix}${connector}${icon} ${node.name}${summary}`);
60
+ if (node.children && node.children.length > 0) {
61
+ lines.push(...format(node.children, prefix + childPrefix));
62
+ }
63
+ });
64
+ return lines;
65
+ }
66
+ return format(tree).join('\n');
67
+ }
68
+ export function countTreeDocs(tree) {
69
+ let count = 0;
70
+ function traverse(nodes) {
71
+ for (const node of nodes) {
72
+ if (!node.isDir)
73
+ count++;
74
+ if (node.children)
75
+ traverse(node.children);
76
+ }
77
+ }
78
+ traverse(tree);
79
+ return count;
80
+ }
81
+ // ==================== 文件大小格式化 ====================
82
+ export function formatFileSize(bytes) {
83
+ if (bytes < 1024)
84
+ return `${bytes} B`;
85
+ if (bytes < 1024 * 1024)
86
+ return `${(bytes / 1024).toFixed(1)} KB`;
87
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
88
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 任务管理工具 (4个)
3
+ * task_create, task_get, task_update, task_archive
4
+ */
5
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ export declare function registerTaskTools(server: McpServer, projectId: string, user: string): void;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 任务管理工具 (4个)
3
+ * task_create, task_get, task_update, task_archive
4
+ */
5
+ import { z } from 'zod';
6
+ import { getClient } from '../storage/httpClient.js';
7
+ import { decodeObjectStrings } from '../utils.js';
8
+ import { wrap, safeTool } from './shared.js';
9
+ export function registerTaskTools(server, projectId, user) {
10
+ const client = () => getClient();
11
+ // 创建任务
12
+ server.tool('task_create', '创建开发任务', {
13
+ title: z.string().describe('任务标题'),
14
+ description: z.string().describe('任务描述(Markdown)'),
15
+ goals: z.array(z.string()).optional().describe('目标清单')
16
+ }, async (args) => safeTool(async () => {
17
+ const decoded = decodeObjectStrings(args);
18
+ const task = await client().createTask({
19
+ title: decoded.title,
20
+ description: decoded.description,
21
+ goals: decoded.goals || []
22
+ }, user);
23
+ return wrap(JSON.stringify(task, null, 2));
24
+ }));
25
+ // 读取任务
26
+ server.tool('task_get', '读取任务详情(按名字搜索,支持当前任务和历史任务)', {
27
+ title: z.string().describe('任务名称(模糊匹配)'),
28
+ status: z.enum(['active', 'archived', 'all']).optional().describe('状态筛选: active=进行中, archived=已归档, all=全部(默认)')
29
+ }, async (args) => safeTool(async () => {
30
+ const status = args.status || 'all';
31
+ const searchTitle = args.title.toLowerCase();
32
+ let tasks;
33
+ if (status === 'all') {
34
+ const [active, archived] = await Promise.all([
35
+ client().listTasks('active'),
36
+ client().listTasks('archived')
37
+ ]);
38
+ tasks = [...active, ...archived];
39
+ }
40
+ else {
41
+ tasks = await client().listTasks(status);
42
+ }
43
+ const matched = tasks.filter(t => t.title.toLowerCase().includes(searchTitle));
44
+ if (matched.length === 0)
45
+ return wrap(`未找到匹配 "${args.title}" 的任务`);
46
+ if (matched.length === 1) {
47
+ const task = await client().getTask(matched[0].id);
48
+ return wrap(task ? JSON.stringify(task, null, 2) : '任务详情获取失败');
49
+ }
50
+ const list = matched.map(t => ({
51
+ id: t.id, title: t.title, status: t.status,
52
+ creator: t.creator, created_at: t.created_at
53
+ }));
54
+ return wrap(`找到 ${matched.length} 个匹配任务:\n${JSON.stringify(list, null, 2)}\n\n请提供更精确的任务名称`);
55
+ }));
56
+ // 更新任务
57
+ server.tool('task_update', '更新任务(添加进展日志)', {
58
+ taskId: z.string().describe('任务ID'),
59
+ log_type: z.enum(['progress', 'issue', 'solution', 'reference']).describe('日志类型: progress=进展, issue=问题, solution=方案, reference=参考'),
60
+ content: z.string().describe('日志内容(Markdown)')
61
+ }, async (args) => safeTool(async () => {
62
+ const decoded = decodeObjectStrings(args);
63
+ const task = await client().addTaskLog(args.taskId, decoded.log_type, decoded.content);
64
+ if (!task)
65
+ return wrap('更新失败(任务不存在或已归档)');
66
+ return wrap(`✅ 日志已添加,任务共有 ${task.logs.length} 条日志`);
67
+ }));
68
+ // 归档任务
69
+ server.tool('task_archive', '归档任务(完成并填写经验总结)', {
70
+ taskId: z.string().describe('任务ID'),
71
+ summary: z.string().describe('经验总结(Markdown)'),
72
+ difficulties: z.array(z.string()).optional().describe('遇到的困难'),
73
+ solutions: z.array(z.string()).optional().describe('解决方案')
74
+ }, async (args) => safeTool(async () => {
75
+ const decoded = decodeObjectStrings(args);
76
+ const task = await client().completeTask(args.taskId, {
77
+ summary: decoded.summary,
78
+ difficulties: decoded.difficulties || [],
79
+ solutions: decoded.solutions || [],
80
+ references: []
81
+ });
82
+ if (!task)
83
+ return wrap('归档失败(任务不存在或已归档)');
84
+ return wrap(`✅ 任务已归档: ${task.title}`);
85
+ }));
86
+ }
package/dist/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * MCP Server 工具函数
3
3
  */
4
- import * as storage from './storage/httpClient.js';
4
+ import { getClient } from './storage/httpClient.js';
5
5
  // 默认规则类型标签 (fallback)
6
6
  const DEFAULT_LABELS = {
7
7
  userStyles: '用户沟通规则',
@@ -24,9 +24,10 @@ function formatRulesList(rules) {
24
24
  * 获取指定类型的规则 (动态: 从 meta 获取标签)
25
25
  */
26
26
  export async function getRules(projectId, ruleType) {
27
- const meta = await storage.getRulesMeta();
27
+ const client = getClient();
28
+ const meta = await client.getRulesMeta();
28
29
  if (ruleType) {
29
- const rules = await storage.getRules(projectId, ruleType);
30
+ const rules = await client.getRulesApi(ruleType);
30
31
  if (!rules || rules.length === 0)
31
32
  return '';
32
33
  const label = meta[ruleType]?.label || DEFAULT_LABELS[ruleType] || ruleType;
@@ -36,7 +37,7 @@ export async function getRules(projectId, ruleType) {
36
37
  const types = Object.keys(meta).length > 0 ? Object.keys(meta) : Object.keys(DEFAULT_LABELS);
37
38
  const allRules = [];
38
39
  for (const type of types) {
39
- const rules = await storage.getRules(projectId, type);
40
+ const rules = await client.getRulesApi(type);
40
41
  if (rules && rules.length > 0) {
41
42
  const label = meta[type]?.label || DEFAULT_LABELS[type] || type;
42
43
  allRules.push(`[${label}]\n${formatRulesList(rules)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "3.1.8",
3
+ "version": "3.1.9",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",