@ppdocs/mcp 2.1.0 → 2.3.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/cli.js +15 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +8 -1
- package/dist/index.js +3 -3
- package/dist/storage/httpClient.d.ts +28 -1
- package/dist/storage/httpClient.js +79 -0
- package/dist/storage/types.d.ts +43 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +113 -52
- package/package.json +2 -2
- package/templates/AGENT.md +30 -0
- package/templates/README.md +57 -0
- package/templates/claude-hooks.json +12 -0
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
+
/** 生成随机用户名 (8位字母数字) */
|
|
8
|
+
function generateUser() {
|
|
9
|
+
const chars = 'abcdefghjkmnpqrstuvwxyz23456789';
|
|
10
|
+
return Array.from({ length: 8 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
|
|
11
|
+
}
|
|
7
12
|
function parseArgs(args) {
|
|
8
13
|
const opts = { port: 20001, api: 'localhost' };
|
|
9
14
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -14,6 +19,9 @@ function parseArgs(args) {
|
|
|
14
19
|
else if (arg === '-k' || arg === '--key') {
|
|
15
20
|
opts.key = args[++i];
|
|
16
21
|
}
|
|
22
|
+
else if (arg === '-u' || arg === '--user') {
|
|
23
|
+
opts.user = args[++i];
|
|
24
|
+
}
|
|
17
25
|
else if (arg === '--port') {
|
|
18
26
|
opts.port = parseInt(args[++i], 10);
|
|
19
27
|
}
|
|
@@ -23,6 +31,9 @@ function parseArgs(args) {
|
|
|
23
31
|
}
|
|
24
32
|
if (!opts.project || !opts.key)
|
|
25
33
|
return null;
|
|
34
|
+
// 如果没设置 user,自动生成
|
|
35
|
+
if (!opts.user)
|
|
36
|
+
opts.user = generateUser();
|
|
26
37
|
return opts;
|
|
27
38
|
}
|
|
28
39
|
function showHelp() {
|
|
@@ -38,11 +49,13 @@ Usage:
|
|
|
38
49
|
Options:
|
|
39
50
|
-p, --project Project ID (required)
|
|
40
51
|
-k, --key API key (required)
|
|
52
|
+
-u, --user User name for logs (optional, auto-generated if not set)
|
|
41
53
|
--port API port (default: 20001)
|
|
42
54
|
--api API host (default: localhost)
|
|
43
55
|
|
|
44
56
|
Example:
|
|
45
57
|
npx @ppdocs/mcp init -p myproject -k abc123xyz
|
|
58
|
+
npx @ppdocs/mcp init -p myproject -k abc123xyz -u alice
|
|
46
59
|
`);
|
|
47
60
|
}
|
|
48
61
|
export function runCli(args) {
|
|
@@ -70,6 +83,7 @@ function initProject(opts) {
|
|
|
70
83
|
api: `http://${opts.api}:${opts.port}`,
|
|
71
84
|
projectId: opts.project,
|
|
72
85
|
key: opts.key,
|
|
86
|
+
user: opts.user,
|
|
73
87
|
};
|
|
74
88
|
const ppdocsPath = path.join(cwd, '.ppdocs');
|
|
75
89
|
fs.writeFileSync(ppdocsPath, JSON.stringify(ppdocsConfig, null, 2));
|
|
@@ -96,6 +110,7 @@ function initProject(opts) {
|
|
|
96
110
|
console.log(`✅ Created ${mcpPath}`);
|
|
97
111
|
console.log(`
|
|
98
112
|
🎉 Done! ppdocs MCP configured for project: ${opts.project}
|
|
113
|
+
User: ${opts.user}
|
|
99
114
|
|
|
100
115
|
Restart Claude Code to use ppdocs knowledge graph.
|
|
101
116
|
`);
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
+
/** 生成随机用户名 (8位字母数字) */
|
|
8
|
+
function generateUser() {
|
|
9
|
+
const chars = 'abcdefghjkmnpqrstuvwxyz23456789';
|
|
10
|
+
return Array.from({ length: 8 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
|
|
11
|
+
}
|
|
7
12
|
/**
|
|
8
13
|
* 从 .ppdocs 文件读取配置
|
|
9
14
|
*/
|
|
@@ -22,6 +27,7 @@ function readPpdocsFile() {
|
|
|
22
27
|
return {
|
|
23
28
|
apiUrl: `${config.api}/api/${config.projectId}/${config.key}`,
|
|
24
29
|
projectId: config.projectId,
|
|
30
|
+
user: config.user || generateUser(),
|
|
25
31
|
};
|
|
26
32
|
}
|
|
27
33
|
catch {
|
|
@@ -38,7 +44,8 @@ function readEnvConfig() {
|
|
|
38
44
|
// URL 格式: http://localhost:20001/api/{projectId}/{password}
|
|
39
45
|
const match = apiUrl.match(/\/api\/([^/]+)\/[^/]+\/?$/);
|
|
40
46
|
const projectId = match?.[1] || 'unknown';
|
|
41
|
-
|
|
47
|
+
const user = process.env.PPDOCS_USER || generateUser();
|
|
48
|
+
return { apiUrl, projectId, user };
|
|
42
49
|
}
|
|
43
50
|
/**
|
|
44
51
|
* 加载配置 (优先级: 环境变量 > .ppdocs 文件)
|
package/dist/index.js
CHANGED
|
@@ -21,11 +21,11 @@ if (args.length > 0 && runCli(args)) {
|
|
|
21
21
|
async function main() {
|
|
22
22
|
const config = loadConfig();
|
|
23
23
|
initClient(config.apiUrl);
|
|
24
|
-
const server = new McpServer({ name: `ppdocs [${config.projectId}]`, version: '2.
|
|
25
|
-
registerTools(server, config.projectId);
|
|
24
|
+
const server = new McpServer({ name: `ppdocs [${config.projectId}]`, version: '2.3.0' }, { capabilities: { tools: {} } });
|
|
25
|
+
registerTools(server, config.projectId, config.user);
|
|
26
26
|
const transport = new StdioServerTransport();
|
|
27
27
|
await server.connect(transport);
|
|
28
|
-
console.error(`ppdocs MCP v2.
|
|
28
|
+
console.error(`ppdocs MCP v2.3 | project: ${config.projectId} | user: ${config.user}`);
|
|
29
29
|
}
|
|
30
30
|
main().catch((err) => {
|
|
31
31
|
console.error('Fatal error:', err);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* API URL 格式: http://localhost:20001/api/:projectId/:password/...
|
|
6
6
|
*/
|
|
7
|
-
import type { NodeData, SearchResult, PathResult, BugfixRecord } from './types.js';
|
|
7
|
+
import type { NodeData, SearchResult, PathResult, BugfixRecord, Task, TaskSummary, TaskLogType, TaskExperience } from './types.js';
|
|
8
8
|
export declare class PpdocsApiClient {
|
|
9
9
|
private baseUrl;
|
|
10
10
|
constructor(apiUrl: string);
|
|
@@ -35,6 +35,16 @@ export declare class PpdocsApiClient {
|
|
|
35
35
|
solution: string;
|
|
36
36
|
impact?: string;
|
|
37
37
|
}): Promise<BugfixRecord | null>;
|
|
38
|
+
listTasks(status?: 'active' | 'archived'): Promise<TaskSummary[]>;
|
|
39
|
+
getTask(taskId: string): Promise<Task | null>;
|
|
40
|
+
createTask(task: {
|
|
41
|
+
title: string;
|
|
42
|
+
description: string;
|
|
43
|
+
goals: string[];
|
|
44
|
+
related_nodes?: string[];
|
|
45
|
+
}, creator: string): Promise<Task>;
|
|
46
|
+
addTaskLog(taskId: string, logType: TaskLogType, content: string): Promise<Task | null>;
|
|
47
|
+
completeTask(taskId: string, experience: TaskExperience): Promise<Task | null>;
|
|
38
48
|
}
|
|
39
49
|
export declare function initClient(apiUrl: string): void;
|
|
40
50
|
export declare function listNodes(_projectId: string): Promise<NodeData[]>;
|
|
@@ -63,3 +73,20 @@ export declare function addBugfix(_projectId: string, nodeId: string, bugfix: {
|
|
|
63
73
|
solution: string;
|
|
64
74
|
impact?: string;
|
|
65
75
|
}): Promise<BugfixRecord | null>;
|
|
76
|
+
export declare function listTasks(_projectId: string, status?: 'active' | 'archived'): Promise<TaskSummary[]>;
|
|
77
|
+
export declare function getTask(_projectId: string, taskId: string): Promise<Task | null>;
|
|
78
|
+
export declare function createTask(_projectId: string, task: {
|
|
79
|
+
title: string;
|
|
80
|
+
description: string;
|
|
81
|
+
goals: string[];
|
|
82
|
+
related_nodes?: string[];
|
|
83
|
+
}, creator: string): Promise<Task>;
|
|
84
|
+
export declare function addTaskLog(_projectId: string, taskId: string, logType: TaskLogType, content: string): Promise<Task | null>;
|
|
85
|
+
export declare function completeTask(_projectId: string, taskId: string, experience: TaskExperience): Promise<Task | null>;
|
|
86
|
+
export interface LogEntry {
|
|
87
|
+
user: string;
|
|
88
|
+
action: string;
|
|
89
|
+
target: string;
|
|
90
|
+
node_id?: string;
|
|
91
|
+
}
|
|
92
|
+
export declare function appendLog(_projectId: string, entry: LogEntry): Promise<void>;
|
|
@@ -159,6 +159,57 @@ export class PpdocsApiClient {
|
|
|
159
159
|
});
|
|
160
160
|
return updated ? newBugfix : null;
|
|
161
161
|
}
|
|
162
|
+
// ============ 任务管理 ============
|
|
163
|
+
async listTasks(status) {
|
|
164
|
+
const query = status ? `?status=${status}` : '';
|
|
165
|
+
return this.request(`/tasks${query}`);
|
|
166
|
+
}
|
|
167
|
+
async getTask(taskId) {
|
|
168
|
+
try {
|
|
169
|
+
return await this.request(`/tasks/${taskId}`);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async createTask(task, creator) {
|
|
176
|
+
const now = new Date().toISOString();
|
|
177
|
+
const payload = {
|
|
178
|
+
title: task.title,
|
|
179
|
+
creator,
|
|
180
|
+
detail: {
|
|
181
|
+
description: task.description,
|
|
182
|
+
goals: task.goals,
|
|
183
|
+
related_nodes: task.related_nodes || []
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
return this.request('/tasks', {
|
|
187
|
+
method: 'POST',
|
|
188
|
+
body: JSON.stringify(payload)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async addTaskLog(taskId, logType, content) {
|
|
192
|
+
try {
|
|
193
|
+
return await this.request(`/tasks/${taskId}/logs`, {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
body: JSON.stringify({ log_type: logType, content })
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async completeTask(taskId, experience) {
|
|
203
|
+
try {
|
|
204
|
+
return await this.request(`/tasks/${taskId}/complete`, {
|
|
205
|
+
method: 'POST',
|
|
206
|
+
body: JSON.stringify(experience)
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
162
213
|
}
|
|
163
214
|
// ============ 模块级 API (兼容现有 tools/index.ts) ============
|
|
164
215
|
let client = null;
|
|
@@ -205,3 +256,31 @@ export async function getRelations(_projectId, nodeId) {
|
|
|
205
256
|
export async function addBugfix(_projectId, nodeId, bugfix) {
|
|
206
257
|
return getClient().addBugfix(nodeId, bugfix);
|
|
207
258
|
}
|
|
259
|
+
// ============ 任务管理 ============
|
|
260
|
+
export async function listTasks(_projectId, status) {
|
|
261
|
+
return getClient().listTasks(status);
|
|
262
|
+
}
|
|
263
|
+
export async function getTask(_projectId, taskId) {
|
|
264
|
+
return getClient().getTask(taskId);
|
|
265
|
+
}
|
|
266
|
+
export async function createTask(_projectId, task, creator) {
|
|
267
|
+
return getClient().createTask(task, creator);
|
|
268
|
+
}
|
|
269
|
+
export async function addTaskLog(_projectId, taskId, logType, content) {
|
|
270
|
+
return getClient().addTaskLog(taskId, logType, content);
|
|
271
|
+
}
|
|
272
|
+
export async function completeTask(_projectId, taskId, experience) {
|
|
273
|
+
return getClient().completeTask(taskId, experience);
|
|
274
|
+
}
|
|
275
|
+
export async function appendLog(_projectId, entry) {
|
|
276
|
+
try {
|
|
277
|
+
await getClient()['request']('/logs', {
|
|
278
|
+
method: 'POST',
|
|
279
|
+
body: JSON.stringify(entry)
|
|
280
|
+
});
|
|
281
|
+
console.error(`[LOG] ${entry.action}: ${entry.target}`);
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
console.error(`[LOG ERROR] Failed to append log:`, err);
|
|
285
|
+
}
|
|
286
|
+
}
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -71,3 +71,46 @@ export interface PathResult {
|
|
|
71
71
|
path: NodeData[];
|
|
72
72
|
edges: Edge[];
|
|
73
73
|
}
|
|
74
|
+
export type TaskLogType = 'progress' | 'issue' | 'solution' | 'reference';
|
|
75
|
+
export interface TaskLog {
|
|
76
|
+
time: string;
|
|
77
|
+
log_type: TaskLogType;
|
|
78
|
+
content: string;
|
|
79
|
+
}
|
|
80
|
+
export interface TaskReference {
|
|
81
|
+
title: string;
|
|
82
|
+
url?: string;
|
|
83
|
+
}
|
|
84
|
+
export interface TaskExperience {
|
|
85
|
+
summary: string;
|
|
86
|
+
difficulties: string[];
|
|
87
|
+
solutions: string[];
|
|
88
|
+
references: TaskReference[];
|
|
89
|
+
}
|
|
90
|
+
export interface TaskDetail {
|
|
91
|
+
description: string;
|
|
92
|
+
goals: string[];
|
|
93
|
+
related_nodes: string[];
|
|
94
|
+
}
|
|
95
|
+
export interface Task {
|
|
96
|
+
id: string;
|
|
97
|
+
title: string;
|
|
98
|
+
status: 'active' | 'archived';
|
|
99
|
+
creator: string;
|
|
100
|
+
project_id: string;
|
|
101
|
+
created_at: string;
|
|
102
|
+
updated_at: string;
|
|
103
|
+
completed_at?: string;
|
|
104
|
+
detail: TaskDetail;
|
|
105
|
+
logs: TaskLog[];
|
|
106
|
+
experience?: TaskExperience;
|
|
107
|
+
}
|
|
108
|
+
export interface TaskSummary {
|
|
109
|
+
id: string;
|
|
110
|
+
title: string;
|
|
111
|
+
status: string;
|
|
112
|
+
creator: string;
|
|
113
|
+
created_at: string;
|
|
114
|
+
updated_at: string;
|
|
115
|
+
last_log?: string;
|
|
116
|
+
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
export declare function registerTools(server: McpServer, projectId: string): void;
|
|
2
|
+
export declare function registerTools(server: McpServer, projectId: string, user: string): void;
|
package/dist/tools/index.js
CHANGED
|
@@ -1,69 +1,64 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import * as storage from '../storage/httpClient.js';
|
|
3
|
-
export function registerTools(server, projectId) {
|
|
3
|
+
export function registerTools(server, projectId, user) {
|
|
4
|
+
// 日志辅助函数
|
|
5
|
+
const log = (action, target, nodeId) => {
|
|
6
|
+
storage.appendLog(projectId, { user, action, target, node_id: nodeId });
|
|
7
|
+
};
|
|
4
8
|
// 1. 创建节点
|
|
5
|
-
server.tool('kg_create_node', '
|
|
9
|
+
server.tool('kg_create_node', '创建知识节点。type: logic=逻辑/函数, data=数据结构, intro=概念介绍', {
|
|
6
10
|
title: z.string().describe('节点标题'),
|
|
7
11
|
type: z.enum(['logic', 'data', 'intro']).describe('节点类型'),
|
|
8
|
-
description: z.string().describe(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
\`\`\`mermaid
|
|
12
|
-
graph LR
|
|
13
|
-
A[输入] --> B{判断} --> C[输出]
|
|
14
|
-
\`\`\`
|
|
15
|
-
|
|
16
|
-
2. **接口用表格**:
|
|
17
|
-
| 参数 | 类型 | 说明 |
|
|
18
|
-
|-----|------|-----|
|
|
19
|
-
| id | string | 唯一标识 |
|
|
20
|
-
|
|
21
|
-
3. **禁止纯文字堆砌**`),
|
|
22
|
-
signature: z.string().optional().describe('方法签名(用于自动关联,默认=title)'),
|
|
23
|
-
x: z.number().optional().describe('X坐标'),
|
|
24
|
-
y: z.number().optional().describe('Y坐标'),
|
|
25
|
-
tags: z.array(z.string()).optional().describe('标签列表'),
|
|
12
|
+
description: z.string().describe('Markdown描述(用Mermaid流程图+表格,禁止纯文字)'),
|
|
13
|
+
signature: z.string().optional().describe('唯一签名(用于依赖匹配,默认=title)'),
|
|
14
|
+
tags: z.array(z.string()).optional().describe('分类标签'),
|
|
26
15
|
dependencies: z.array(z.object({
|
|
27
|
-
name: z.string().describe('
|
|
16
|
+
name: z.string().describe('目标节点的signature'),
|
|
28
17
|
description: z.string().describe('依赖说明')
|
|
29
|
-
})).optional().describe('依赖列表(
|
|
18
|
+
})).optional().describe('依赖列表(自动生成连线)')
|
|
30
19
|
}, async (args) => {
|
|
31
20
|
const node = await storage.createNode(projectId, {
|
|
32
21
|
title: args.title,
|
|
33
22
|
type: args.type,
|
|
34
23
|
status: 'incomplete',
|
|
35
24
|
description: args.description || '',
|
|
36
|
-
x:
|
|
37
|
-
y:
|
|
25
|
+
x: 0, // 服务端自动布局
|
|
26
|
+
y: 0,
|
|
38
27
|
locked: false,
|
|
39
28
|
signature: args.signature || args.title,
|
|
40
|
-
categories: args.tags || [],
|
|
29
|
+
categories: args.tags || [],
|
|
41
30
|
dependencies: args.dependencies || []
|
|
42
31
|
});
|
|
32
|
+
log('create_node', args.title, node.id);
|
|
43
33
|
return { content: [{ type: 'text', text: JSON.stringify(node, null, 2) }] };
|
|
44
34
|
});
|
|
45
35
|
// 2. 删除节点
|
|
46
36
|
server.tool('kg_delete_node', '删除节点(锁定节点和根节点不可删除)', { nodeId: z.string().describe('节点ID') }, async (args) => {
|
|
37
|
+
const node = await storage.getNode(projectId, args.nodeId);
|
|
47
38
|
const success = await storage.deleteNode(projectId, args.nodeId);
|
|
39
|
+
if (success)
|
|
40
|
+
log('delete_node', node?.title || args.nodeId, args.nodeId);
|
|
48
41
|
return { content: [{ type: 'text', text: success ? '删除成功' : '删除失败(节点不存在/已锁定/是根节点)' }] };
|
|
49
42
|
});
|
|
50
43
|
// 3. 更新节点
|
|
51
|
-
server.tool('kg_update_node', '
|
|
44
|
+
server.tool('kg_update_node', '更新节点内容(锁定节点不可更新)', {
|
|
52
45
|
nodeId: z.string().describe('节点ID'),
|
|
53
46
|
title: z.string().optional().describe('新标题'),
|
|
54
|
-
signature: z.string().optional().describe('
|
|
55
|
-
description: z.string().optional().describe('新描述(Markdown
|
|
56
|
-
status: z.enum(['incomplete', 'complete', 'fixing', 'refactoring', 'deprecated']).optional(),
|
|
57
|
-
tags: z.array(z.string()).optional(),
|
|
47
|
+
signature: z.string().optional().describe('新签名'),
|
|
48
|
+
description: z.string().optional().describe('新描述(Markdown)'),
|
|
49
|
+
status: z.enum(['incomplete', 'complete', 'fixing', 'refactoring', 'deprecated']).optional().describe('状态'),
|
|
50
|
+
tags: z.array(z.string()).optional().describe('分类标签'),
|
|
58
51
|
dependencies: z.array(z.object({
|
|
59
|
-
name: z.string()
|
|
60
|
-
description: z.string()
|
|
52
|
+
name: z.string(),
|
|
53
|
+
description: z.string()
|
|
61
54
|
})).optional().describe('依赖列表')
|
|
62
55
|
}, async (args) => {
|
|
63
56
|
const { nodeId, tags, ...rest } = args;
|
|
64
57
|
// API 参数 tags 转换为内部字段 categories
|
|
65
58
|
const updates = tags !== undefined ? { ...rest, categories: tags } : rest;
|
|
66
59
|
const node = await storage.updateNode(projectId, nodeId, updates);
|
|
60
|
+
if (node)
|
|
61
|
+
log('update_node', node.title, nodeId);
|
|
67
62
|
return { content: [{ type: 'text', text: node ? JSON.stringify(node, null, 2) : '更新失败(节点不存在或已锁定)' }] };
|
|
68
63
|
});
|
|
69
64
|
// 4. 锁定/解锁节点
|
|
@@ -72,12 +67,14 @@ graph LR
|
|
|
72
67
|
locked: z.boolean().describe('true=锁定, false=解锁')
|
|
73
68
|
}, async (args) => {
|
|
74
69
|
const node = await storage.lockNode(projectId, args.nodeId, args.locked);
|
|
70
|
+
if (node)
|
|
71
|
+
log(args.locked ? 'lock_node' : 'unlock_node', node.title, args.nodeId);
|
|
75
72
|
return { content: [{ type: 'text', text: node ? JSON.stringify(node, null, 2) : '操作失败' }] };
|
|
76
73
|
});
|
|
77
74
|
// 5. 搜索节点
|
|
78
|
-
server.tool('kg_search', '
|
|
79
|
-
keywords: z.array(z.string()).describe('
|
|
80
|
-
limit: z.number().optional().describe('
|
|
75
|
+
server.tool('kg_search', '关键词搜索节点,按相关度排序返回', {
|
|
76
|
+
keywords: z.array(z.string()).describe('关键词列表(OR逻辑)'),
|
|
77
|
+
limit: z.number().optional().describe('返回数量(默认20)')
|
|
81
78
|
}, async (args) => {
|
|
82
79
|
const results = await storage.searchNodes(projectId, args.keywords, args.limit);
|
|
83
80
|
const output = results.map(r => ({
|
|
@@ -87,9 +84,9 @@ graph LR
|
|
|
87
84
|
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
88
85
|
});
|
|
89
86
|
// 6. 路径查找
|
|
90
|
-
server.tool('kg_find_path', '
|
|
91
|
-
startId: z.string().describe('
|
|
92
|
-
endId: z.string().describe('
|
|
87
|
+
server.tool('kg_find_path', '查找两节点间的依赖路径', {
|
|
88
|
+
startId: z.string().describe('起点节点ID'),
|
|
89
|
+
endId: z.string().describe('终点节点ID')
|
|
93
90
|
}, async (args) => {
|
|
94
91
|
const result = await storage.findPath(projectId, args.startId, args.endId);
|
|
95
92
|
if (!result)
|
|
@@ -102,13 +99,13 @@ graph LR
|
|
|
102
99
|
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
103
100
|
});
|
|
104
101
|
// 7. 列出所有节点
|
|
105
|
-
server.tool('kg_list_nodes', '
|
|
102
|
+
server.tool('kg_list_nodes', '列出项目全部节点概览', {}, async () => {
|
|
106
103
|
const nodes = await storage.listNodes(projectId);
|
|
107
104
|
const output = nodes.map(n => ({ id: n.id, title: n.title, type: n.type, status: n.status, locked: n.locked }));
|
|
108
105
|
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
109
106
|
});
|
|
110
107
|
// 8. 查找孤立节点
|
|
111
|
-
server.tool('kg_find_orphans', '
|
|
108
|
+
server.tool('kg_find_orphans', '查找无连线的孤立节点(用于清理)', {}, async () => {
|
|
112
109
|
const orphans = await storage.findOrphans(projectId);
|
|
113
110
|
if (orphans.length === 0) {
|
|
114
111
|
return { content: [{ type: 'text', text: '没有孤立节点' }] };
|
|
@@ -116,7 +113,7 @@ graph LR
|
|
|
116
113
|
return { content: [{ type: 'text', text: JSON.stringify(orphans, null, 2) }] };
|
|
117
114
|
});
|
|
118
115
|
// 9. 查询节点关系网
|
|
119
|
-
server.tool('kg_get_relations', '
|
|
116
|
+
server.tool('kg_get_relations', '获取节点的上下游关系(谁依赖它/它依赖谁)', {
|
|
120
117
|
nodeId: z.string().describe('节点ID')
|
|
121
118
|
}, async (args) => {
|
|
122
119
|
const relations = await storage.getRelations(projectId, args.nodeId);
|
|
@@ -132,18 +129,82 @@ graph LR
|
|
|
132
129
|
};
|
|
133
130
|
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
134
131
|
});
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
// ===================== 任务管理工具 =====================
|
|
133
|
+
// 10. 创建任务
|
|
134
|
+
server.tool('task_create', '创建开发任务,记录目标和关联节点', {
|
|
135
|
+
title: z.string().describe('任务标题'),
|
|
136
|
+
description: z.string().describe('任务描述(Markdown)'),
|
|
137
|
+
goals: z.array(z.string()).optional().describe('目标清单'),
|
|
138
|
+
related_nodes: z.array(z.string()).optional().describe('关联节点ID')
|
|
139
|
+
}, async (args) => {
|
|
140
|
+
const task = await storage.createTask(projectId, {
|
|
141
|
+
title: args.title,
|
|
142
|
+
description: args.description,
|
|
143
|
+
goals: args.goals || [],
|
|
144
|
+
related_nodes: args.related_nodes
|
|
145
|
+
}, user);
|
|
146
|
+
log('create_task', args.title, task.id);
|
|
147
|
+
return { content: [{ type: 'text', text: JSON.stringify(task, null, 2) }] };
|
|
148
|
+
});
|
|
149
|
+
// 11. 列出任务
|
|
150
|
+
server.tool('task_list', '列出任务(active=进行中,archived=已归档)', {
|
|
151
|
+
status: z.enum(['active', 'archived']).optional().describe('状态筛选')
|
|
152
|
+
}, async (args) => {
|
|
153
|
+
const tasks = await storage.listTasks(projectId, args.status);
|
|
154
|
+
const output = tasks.map(t => ({
|
|
155
|
+
id: t.id,
|
|
156
|
+
title: t.title,
|
|
157
|
+
status: t.status,
|
|
158
|
+
creator: t.creator,
|
|
159
|
+
created_at: t.created_at,
|
|
160
|
+
last_log: t.last_log
|
|
161
|
+
}));
|
|
162
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
163
|
+
});
|
|
164
|
+
// 12. 获取任务详情
|
|
165
|
+
server.tool('task_get', '获取任务完整信息(含全部日志)', {
|
|
166
|
+
taskId: z.string().describe('任务ID')
|
|
167
|
+
}, async (args) => {
|
|
168
|
+
const task = await storage.getTask(projectId, args.taskId);
|
|
169
|
+
if (!task) {
|
|
170
|
+
return { content: [{ type: 'text', text: '任务不存在' }] };
|
|
171
|
+
}
|
|
172
|
+
return { content: [{ type: 'text', text: JSON.stringify(task, null, 2) }] };
|
|
173
|
+
});
|
|
174
|
+
// 13. 添加任务日志
|
|
175
|
+
server.tool('task_add_log', '记录任务进展/问题/方案/参考', {
|
|
176
|
+
taskId: z.string().describe('任务ID'),
|
|
177
|
+
log_type: z.enum(['progress', 'issue', 'solution', 'reference']).describe('日志类型'),
|
|
178
|
+
content: z.string().describe('日志内容(Markdown)')
|
|
141
179
|
}, async (args) => {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
180
|
+
const task = await storage.addTaskLog(projectId, args.taskId, args.log_type, args.content);
|
|
181
|
+
if (!task) {
|
|
182
|
+
return { content: [{ type: 'text', text: '添加失败(任务不存在或已归档)' }] };
|
|
183
|
+
}
|
|
184
|
+
log('add_task_log', `${args.log_type}: ${args.content.slice(0, 50)}...`, args.taskId);
|
|
185
|
+
return { content: [{ type: 'text', text: `日志已添加,任务共有 ${task.logs.length} 条日志` }] };
|
|
186
|
+
});
|
|
187
|
+
// 14. 完成任务
|
|
188
|
+
server.tool('task_complete', '完成任务并归档,填写经验总结', {
|
|
189
|
+
taskId: z.string().describe('任务ID'),
|
|
190
|
+
summary: z.string().describe('经验总结(Markdown)'),
|
|
191
|
+
difficulties: z.array(z.string()).optional().describe('遇到的困难'),
|
|
192
|
+
solutions: z.array(z.string()).optional().describe('解决方案'),
|
|
193
|
+
references: z.array(z.object({
|
|
194
|
+
title: z.string(),
|
|
195
|
+
url: z.string().optional()
|
|
196
|
+
})).optional().describe('参考资料')
|
|
197
|
+
}, async (args) => {
|
|
198
|
+
const task = await storage.completeTask(projectId, args.taskId, {
|
|
199
|
+
summary: args.summary,
|
|
200
|
+
difficulties: args.difficulties || [],
|
|
201
|
+
solutions: args.solutions || [],
|
|
202
|
+
references: args.references || []
|
|
146
203
|
});
|
|
147
|
-
|
|
204
|
+
if (!task) {
|
|
205
|
+
return { content: [{ type: 'text', text: '完成失败(任务不存在或已归档)' }] };
|
|
206
|
+
}
|
|
207
|
+
log('complete_task', task.title, args.taskId);
|
|
208
|
+
return { content: [{ type: 'text', text: `任务已完成归档: ${task.title}` }] };
|
|
148
209
|
});
|
|
149
210
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ppdocs/mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "ppdocs MCP Server - Knowledge Graph for Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"url": "https://github.com/ppdocs/ppdocs"
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
|
-
"files": ["dist", "README.md"],
|
|
20
|
+
"files": ["dist", "templates", "README.md"],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
23
23
|
"proper-lockfile": "^4.1.2",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# AGENT.md - ppdocs Knowledge Graph Agent
|
|
2
|
+
|
|
3
|
+
> TODO: 完善此提示词内容
|
|
4
|
+
|
|
5
|
+
## Role
|
|
6
|
+
|
|
7
|
+
你是一个项目知识管理助手,负责与 ppdocs 知识图谱交互。
|
|
8
|
+
|
|
9
|
+
## 核心原则
|
|
10
|
+
|
|
11
|
+
1. **知识优先**: 修改代码前先查询知识库
|
|
12
|
+
2. **同步更新**: 代码变更后更新对应知识节点
|
|
13
|
+
3. **结构化记录**: 使用 Mermaid 流程图 + 表格
|
|
14
|
+
|
|
15
|
+
## 可用工具
|
|
16
|
+
|
|
17
|
+
- `kg_search` - 搜索知识节点
|
|
18
|
+
- `kg_create_node` - 创建新节点
|
|
19
|
+
- `kg_update_node` - 更新节点
|
|
20
|
+
- `kg_get_relations` - 查询节点关系
|
|
21
|
+
|
|
22
|
+
## 工作流程
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
查询知识库 → 分析现状 → 执行任务 → 更新知识库
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
*此文件由 `npx @ppdocs/mcp init --agent` 生成*
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# MCP Templates
|
|
2
|
+
|
|
3
|
+
此目录包含 `npx @ppdocs/mcp init` 安装时使用的模板文件。
|
|
4
|
+
|
|
5
|
+
## 文件说明
|
|
6
|
+
|
|
7
|
+
| 文件 | 用途 | 安装参数 | 目标路径 |
|
|
8
|
+
|-----|------|---------|---------|
|
|
9
|
+
| `AGENT.md` | Codex/通用 Agent 提示词 | `--agent` | `./AGENT.md` |
|
|
10
|
+
| `claude-hooks.json` | Claude Code hooks 配置 | `--hooks` | `.claude/settings.local.json` |
|
|
11
|
+
|
|
12
|
+
## 安装命令
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# 基础安装 (只生成 .ppdocs + .mcp.json)
|
|
16
|
+
npx @ppdocs/mcp init -p <projectId> -k <key>
|
|
17
|
+
|
|
18
|
+
# 安装 + Claude hooks
|
|
19
|
+
npx @ppdocs/mcp init -p <projectId> -k <key> --hooks
|
|
20
|
+
|
|
21
|
+
# 安装 + AGENT.md
|
|
22
|
+
npx @ppdocs/mcp init -p <projectId> -k <key> --agent
|
|
23
|
+
|
|
24
|
+
# 完整安装 (全部文件)
|
|
25
|
+
npx @ppdocs/mcp init -p <projectId> -k <key> --all
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 配置合并逻辑
|
|
29
|
+
|
|
30
|
+
### Claude Hooks (`--hooks`)
|
|
31
|
+
|
|
32
|
+
安装时会**合并**到 `.claude/settings.local.json`:
|
|
33
|
+
- 如果文件不存在:创建新文件
|
|
34
|
+
- 如果文件存在:保留用户配置,只追加/更新 hooks 部分
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"hooks": {
|
|
39
|
+
"UserPromptSubmit": [
|
|
40
|
+
{
|
|
41
|
+
"matcher": "",
|
|
42
|
+
"command": "cat path/to/prompt.md"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### AGENT.md (`--agent`)
|
|
50
|
+
|
|
51
|
+
直接复制到项目根目录,如已存在会提示是否覆盖。
|
|
52
|
+
|
|
53
|
+
## TODO
|
|
54
|
+
|
|
55
|
+
- [ ] 完善 AGENT.md 提示词内容
|
|
56
|
+
- [ ] 完善 claude-hooks.json 工作流配置
|
|
57
|
+
- [ ] 更新 CLI 支持 --hooks/--agent/--all 参数
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/anthropics/claude-code/main/schema/settings.json",
|
|
3
|
+
"_comment": "TODO: 完善 hooks 配置",
|
|
4
|
+
"hooks": {
|
|
5
|
+
"UserPromptSubmit": [
|
|
6
|
+
{
|
|
7
|
+
"matcher": "",
|
|
8
|
+
"command": "echo '## ppdocs Knowledge Graph\\n请结合知识库进行分析和开发'"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
}
|