@ppdocs/mcp 2.1.0 → 2.4.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/README.md +476 -0
- 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 +21 -1
- package/dist/storage/httpClient.js +94 -13
- package/dist/storage/types.d.ts +44 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +102 -57
- 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/README.md
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# @ppdocs/mcp
|
|
2
|
+
|
|
3
|
+
> Knowledge Graph MCP Server for Claude - 让 Claude 拥有项目知识图谱记忆
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@ppdocs/mcp)
|
|
6
|
+
|
|
7
|
+
## 概述
|
|
8
|
+
|
|
9
|
+
ppdocs MCP 是一个 [Model Context Protocol](https://modelcontextprotocol.io/) 服务器,让 Claude 能够在对话中构建和查询项目知识图谱。
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
graph LR
|
|
13
|
+
Claude[Claude AI] -->|MCP| Server[@ppdocs/mcp]
|
|
14
|
+
Server -->|HTTP API| Backend[ppdocs 桌面应用]
|
|
15
|
+
Backend -->|文件存储| Data[(知识图谱)]
|
|
16
|
+
|
|
17
|
+
style Claude fill:#7c3aed,color:#fff
|
|
18
|
+
style Server fill:#3b82f6,color:#fff
|
|
19
|
+
style Backend fill:#10b981,color:#fff
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 核心功能
|
|
23
|
+
|
|
24
|
+
| 功能 | 描述 |
|
|
25
|
+
|------|------|
|
|
26
|
+
| 📊 **知识图谱** | 创建、更新、删除、搜索节点 |
|
|
27
|
+
| 🔗 **依赖追踪** | 自动计算节点间的依赖关系 |
|
|
28
|
+
| 📝 **任务管理** | 记录开发任务、进度日志、经验总结 |
|
|
29
|
+
| 🔍 **智能搜索** | 多关键词搜索,按相关度排序 |
|
|
30
|
+
| 🛤️ **路径分析** | 查找两节点间的依赖路径 |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 快速开始
|
|
35
|
+
|
|
36
|
+
### 1. 安装
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install -g @ppdocs/mcp
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. 配置 Claude Desktop
|
|
43
|
+
|
|
44
|
+
在 Claude Desktop 配置文件中添加:
|
|
45
|
+
|
|
46
|
+
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
47
|
+
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"ppdocs": {
|
|
53
|
+
"command": "npx",
|
|
54
|
+
"args": ["-y", "@ppdocs/mcp"],
|
|
55
|
+
"env": {
|
|
56
|
+
"PPDOCS_API_URL": "http://localhost:20001/api/项目ID/密码"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. 获取 API URL
|
|
64
|
+
|
|
65
|
+
1. 打开 ppdocs 桌面应用
|
|
66
|
+
2. 选择一个项目 → 点击设置图标
|
|
67
|
+
3. 复制 **MCP 连接地址**
|
|
68
|
+
|
|
69
|
+
```mermaid
|
|
70
|
+
sequenceDiagram
|
|
71
|
+
participant U as 用户
|
|
72
|
+
participant App as ppdocs 桌面应用
|
|
73
|
+
participant Claude as Claude Desktop
|
|
74
|
+
|
|
75
|
+
U->>App: 1. 打开项目设置
|
|
76
|
+
App->>U: 2. 显示 MCP 连接地址
|
|
77
|
+
U->>Claude: 3. 配置 claude_desktop_config.json
|
|
78
|
+
Claude->>App: 4. MCP 连接成功
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 工具列表
|
|
84
|
+
|
|
85
|
+
### 知识图谱工具
|
|
86
|
+
|
|
87
|
+
```mermaid
|
|
88
|
+
mindmap
|
|
89
|
+
root((知识图谱))
|
|
90
|
+
创建
|
|
91
|
+
kg_create_node
|
|
92
|
+
修改
|
|
93
|
+
kg_update_node
|
|
94
|
+
kg_lock_node
|
|
95
|
+
kg_delete_node
|
|
96
|
+
查询
|
|
97
|
+
kg_list_nodes
|
|
98
|
+
kg_search
|
|
99
|
+
kg_find_path
|
|
100
|
+
kg_find_orphans
|
|
101
|
+
kg_get_relations
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### `kg_create_node` - 创建节点
|
|
105
|
+
|
|
106
|
+
创建一个新的知识节点。
|
|
107
|
+
|
|
108
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
109
|
+
|------|------|------|------|
|
|
110
|
+
| `title` | string | ✅ | 节点标题 |
|
|
111
|
+
| `type` | enum | ✅ | 节点类型: `logic`(逻辑/函数), `data`(数据结构), `intro`(概念介绍) |
|
|
112
|
+
| `description` | string | ✅ | Markdown 描述 (建议使用 Mermaid 图表 + 表格) |
|
|
113
|
+
| `signature` | string | ❌ | 唯一签名,用于依赖匹配 (默认=title) |
|
|
114
|
+
| `tags` | string[] | ❌ | 分类标签 |
|
|
115
|
+
| `dependencies` | object[] | ❌ | 依赖列表: `[{name: "目标签名", description: "依赖说明"}]` |
|
|
116
|
+
|
|
117
|
+
**示例:**
|
|
118
|
+
```
|
|
119
|
+
Claude, 创建一个节点记录用户认证模块
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
#### `kg_update_node` - 更新节点
|
|
125
|
+
|
|
126
|
+
更新现有节点的内容(锁定节点不可更新)。
|
|
127
|
+
|
|
128
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
129
|
+
|------|------|------|------|
|
|
130
|
+
| `nodeId` | string | ✅ | 节点 ID |
|
|
131
|
+
| `title` | string | ❌ | 新标题 |
|
|
132
|
+
| `description` | string | ❌ | 新描述 |
|
|
133
|
+
| `status` | enum | ❌ | 状态: `incomplete`, `complete`, `fixing`, `refactoring`, `deprecated` |
|
|
134
|
+
| `tags` | string[] | ❌ | 新标签 |
|
|
135
|
+
| `dependencies` | object[] | ❌ | 新依赖列表 |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
#### `kg_delete_node` - 删除节点
|
|
140
|
+
|
|
141
|
+
删除指定节点(锁定节点和根节点不可删除)。
|
|
142
|
+
|
|
143
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
144
|
+
|------|------|------|------|
|
|
145
|
+
| `nodeId` | string | ✅ | 节点 ID |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
#### `kg_lock_node` - 锁定节点
|
|
150
|
+
|
|
151
|
+
锁定节点后只能读取,不能修改或删除。
|
|
152
|
+
|
|
153
|
+
> ⚠️ **安全限制**: AI 只能锁定节点,**不能解锁**。解锁需要用户在前端手动操作。
|
|
154
|
+
|
|
155
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
156
|
+
|------|------|------|------|
|
|
157
|
+
| `nodeId` | string | ✅ | 节点 ID |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
#### `kg_list_nodes` - 列出所有节点
|
|
162
|
+
|
|
163
|
+
获取项目中所有节点的概览。
|
|
164
|
+
|
|
165
|
+
无参数。
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
#### `kg_search` - 搜索节点
|
|
170
|
+
|
|
171
|
+
使用关键词搜索节点,按相关度排序返回。
|
|
172
|
+
|
|
173
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
174
|
+
|------|------|------|------|
|
|
175
|
+
| `keywords` | string[] | ✅ | 关键词列表 (OR 逻辑) |
|
|
176
|
+
| `limit` | number | ❌ | 返回数量上限 (默认 20) |
|
|
177
|
+
|
|
178
|
+
**示例:**
|
|
179
|
+
```
|
|
180
|
+
Claude, 搜索所有和数据库相关的节点
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
#### `kg_find_path` - 查找依赖路径
|
|
186
|
+
|
|
187
|
+
查找两个节点之间的依赖路径。
|
|
188
|
+
|
|
189
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
190
|
+
|------|------|------|------|
|
|
191
|
+
| `startId` | string | ✅ | 起点节点 ID |
|
|
192
|
+
| `endId` | string | ✅ | 终点节点 ID |
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
#### `kg_find_orphans` - 查找孤立节点
|
|
197
|
+
|
|
198
|
+
查找没有任何连线的孤立节点,用于清理。
|
|
199
|
+
|
|
200
|
+
无参数。
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
#### `kg_get_relations` - 获取节点关系
|
|
205
|
+
|
|
206
|
+
获取指定节点的上下游关系网络。
|
|
207
|
+
|
|
208
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
209
|
+
|------|------|------|------|
|
|
210
|
+
| `nodeId` | string | ✅ | 节点 ID |
|
|
211
|
+
|
|
212
|
+
**返回:**
|
|
213
|
+
- `outgoing`: 该节点依赖的其他节点
|
|
214
|
+
- `incoming`: 依赖该节点的其他节点
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### 任务管理工具
|
|
219
|
+
|
|
220
|
+
```mermaid
|
|
221
|
+
stateDiagram-v2
|
|
222
|
+
[*] --> Active: task_create
|
|
223
|
+
Active --> Active: task_add_log
|
|
224
|
+
Active --> Archived: task_complete
|
|
225
|
+
Archived --> [*]
|
|
226
|
+
|
|
227
|
+
state Active {
|
|
228
|
+
progress
|
|
229
|
+
issue
|
|
230
|
+
solution
|
|
231
|
+
reference
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### `task_create` - 创建任务
|
|
236
|
+
|
|
237
|
+
创建一个新的开发任务。
|
|
238
|
+
|
|
239
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
240
|
+
|------|------|------|------|
|
|
241
|
+
| `title` | string | ✅ | 任务标题 |
|
|
242
|
+
| `description` | string | ✅ | 任务描述 (Markdown) |
|
|
243
|
+
| `goals` | string[] | ❌ | 目标清单 |
|
|
244
|
+
| `related_nodes` | string[] | ❌ | 关联的知识节点 ID |
|
|
245
|
+
|
|
246
|
+
**示例:**
|
|
247
|
+
```
|
|
248
|
+
Claude, 创建一个任务:实现用户登录功能
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
#### `task_list` - 列出任务
|
|
254
|
+
|
|
255
|
+
列出项目中的任务。
|
|
256
|
+
|
|
257
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
258
|
+
|------|------|------|------|
|
|
259
|
+
| `status` | enum | ❌ | 筛选: `active`(进行中) 或 `archived`(已归档) |
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
#### `task_get` - 获取任务详情
|
|
264
|
+
|
|
265
|
+
获取任务的完整信息,包含所有日志记录。
|
|
266
|
+
|
|
267
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
268
|
+
|------|------|------|------|
|
|
269
|
+
| `taskId` | string | ✅ | 任务 ID |
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
#### `task_add_log` - 添加日志
|
|
274
|
+
|
|
275
|
+
为任务记录进展、问题、方案或参考资料。
|
|
276
|
+
|
|
277
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
278
|
+
|------|------|------|------|
|
|
279
|
+
| `taskId` | string | ✅ | 任务 ID |
|
|
280
|
+
| `log_type` | enum | ✅ | 日志类型: `progress`, `issue`, `solution`, `reference` |
|
|
281
|
+
| `content` | string | ✅ | 日志内容 (Markdown) |
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
#### `task_complete` - 完成任务
|
|
286
|
+
|
|
287
|
+
完成任务并归档,需要填写经验总结。
|
|
288
|
+
|
|
289
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
290
|
+
|------|------|------|------|
|
|
291
|
+
| `taskId` | string | ✅ | 任务 ID |
|
|
292
|
+
| `summary` | string | ✅ | 经验总结 (Markdown) |
|
|
293
|
+
| `difficulties` | string[] | ❌ | 遇到的困难 |
|
|
294
|
+
| `solutions` | string[] | ❌ | 解决方案 |
|
|
295
|
+
| `references` | object[] | ❌ | 参考资料: `[{title: "标题", url: "链接"}]` |
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## 数据结构
|
|
300
|
+
|
|
301
|
+
### 节点类型
|
|
302
|
+
|
|
303
|
+
```mermaid
|
|
304
|
+
classDiagram
|
|
305
|
+
class NodeData {
|
|
306
|
+
+string id
|
|
307
|
+
+string title
|
|
308
|
+
+NodeType type
|
|
309
|
+
+NodeStatus status
|
|
310
|
+
+string signature
|
|
311
|
+
+string description
|
|
312
|
+
+string[] categories
|
|
313
|
+
+Dependency[] dependencies
|
|
314
|
+
+boolean locked
|
|
315
|
+
+number x, y
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
class Dependency {
|
|
319
|
+
+string name
|
|
320
|
+
+string description
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
NodeData --> Dependency
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
| 类型 | 说明 | 适用场景 |
|
|
327
|
+
|------|------|----------|
|
|
328
|
+
| `logic` | 逻辑/函数 | 算法、处理流程、API 接口 |
|
|
329
|
+
| `data` | 数据结构 | 数据库表、配置、状态定义 |
|
|
330
|
+
| `intro` | 概念介绍 | 架构说明、设计决策、术语解释 |
|
|
331
|
+
|
|
332
|
+
### 节点状态
|
|
333
|
+
|
|
334
|
+
| 状态 | 说明 |
|
|
335
|
+
|------|------|
|
|
336
|
+
| `incomplete` | 未完成 (默认) |
|
|
337
|
+
| `complete` | 已完成 |
|
|
338
|
+
| `fixing` | 修复中 |
|
|
339
|
+
| `refactoring` | 重构中 |
|
|
340
|
+
| `deprecated` | 已废弃 |
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## 架构说明
|
|
345
|
+
|
|
346
|
+
```mermaid
|
|
347
|
+
flowchart TB
|
|
348
|
+
subgraph Claude["Claude Desktop"]
|
|
349
|
+
MCP[MCP 协议]
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
subgraph Server["@ppdocs/mcp"]
|
|
353
|
+
Tools[工具注册]
|
|
354
|
+
HTTP[HTTP Client]
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
subgraph Backend["ppdocs 桌面应用"]
|
|
358
|
+
API[REST API]
|
|
359
|
+
Store[文件存储]
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
subgraph Data["数据目录 ~/.ppdocs"]
|
|
363
|
+
Projects[projects/]
|
|
364
|
+
Tasks[tasks/]
|
|
365
|
+
Backups[backups/]
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
MCP <-->|stdio| Tools
|
|
369
|
+
Tools --> HTTP
|
|
370
|
+
HTTP <-->|HTTP| API
|
|
371
|
+
API --> Store
|
|
372
|
+
Store --> Projects
|
|
373
|
+
Store --> Tasks
|
|
374
|
+
Store --> Backups
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### 文件结构
|
|
378
|
+
|
|
379
|
+
```
|
|
380
|
+
~/.ppdocs/
|
|
381
|
+
├── projects/
|
|
382
|
+
│ └── {project-id}/
|
|
383
|
+
│ ├── meta.json # 项目元数据
|
|
384
|
+
│ └── nodes/
|
|
385
|
+
│ └── {node-id}.json
|
|
386
|
+
├── tasks/
|
|
387
|
+
│ └── {project-id}/
|
|
388
|
+
│ ├── active/ # 进行中的任务
|
|
389
|
+
│ └── archived/ # 已归档的任务
|
|
390
|
+
└── backups/
|
|
391
|
+
└── {project-id}/
|
|
392
|
+
└── {timestamp}/
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## 环境变量
|
|
398
|
+
|
|
399
|
+
| 变量 | 说明 | 示例 |
|
|
400
|
+
|------|------|------|
|
|
401
|
+
| `PPDOCS_API_URL` | API 完整地址 | `http://localhost:20001/api/myproject/abc123` |
|
|
402
|
+
| `PPDOCS_PROJECT` | 项目 ID (可选) | `myproject` |
|
|
403
|
+
| `PPDOCS_KEY` | 访问密钥 (可选) | `abc123` |
|
|
404
|
+
| `PPDOCS_USER` | 用户名 (可选) | `developer` |
|
|
405
|
+
| `PPDOCS_PORT` | API 端口 (可选) | `20001` |
|
|
406
|
+
| `PPDOCS_HOST` | API 主机 (可选) | `localhost` |
|
|
407
|
+
|
|
408
|
+
### 配置文件
|
|
409
|
+
|
|
410
|
+
也可以在项目根目录创建 `.ppdocs` 文件:
|
|
411
|
+
|
|
412
|
+
```json
|
|
413
|
+
{
|
|
414
|
+
"project": "my-project",
|
|
415
|
+
"key": "abc123",
|
|
416
|
+
"user": "developer",
|
|
417
|
+
"port": 20001,
|
|
418
|
+
"api": "localhost"
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## 常见问题
|
|
425
|
+
|
|
426
|
+
### Q: 连接失败怎么办?
|
|
427
|
+
|
|
428
|
+
1. 确保 ppdocs 桌面应用正在运行
|
|
429
|
+
2. 检查端口是否正确 (默认 20001)
|
|
430
|
+
3. 验证 API URL 格式: `http://localhost:PORT/api/项目ID/密码`
|
|
431
|
+
|
|
432
|
+
### Q: 节点无法删除?
|
|
433
|
+
|
|
434
|
+
可能的原因:
|
|
435
|
+
- 节点被锁定 → 在 ppdocs 桌面应用中手动解锁
|
|
436
|
+
- 是根节点 (isOrigin=true) → 根节点不可删除
|
|
437
|
+
|
|
438
|
+
### Q: 如何备份数据?
|
|
439
|
+
|
|
440
|
+
1. **桌面应用**: 设置 → 数据管理 → 导出全部项目
|
|
441
|
+
2. **手动备份**: 复制 `~/.ppdocs/` 目录
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## 更新日志
|
|
446
|
+
|
|
447
|
+
### v2.4.0
|
|
448
|
+
- 🛡️ 安全增强:`kg_lock_node` 只能锁定,解锁需前端手动操作
|
|
449
|
+
- ⚡ 日志系统重构:后端自动记录操作日志,无需 AI 主动调用
|
|
450
|
+
- 🐛 修复画布缩放闪烁问题
|
|
451
|
+
|
|
452
|
+
### v2.3.0
|
|
453
|
+
- 新增任务管理功能 (task_create, task_complete 等)
|
|
454
|
+
- 优化搜索算法
|
|
455
|
+
- 添加文件锁防止并发写入
|
|
456
|
+
|
|
457
|
+
### v2.2.0
|
|
458
|
+
- 从文件存储切换到 HTTP API
|
|
459
|
+
- 支持多用户协作
|
|
460
|
+
|
|
461
|
+
### v2.0.0
|
|
462
|
+
- 初始 MCP 服务器版本
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## 许可证
|
|
467
|
+
|
|
468
|
+
MIT License
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## 相关链接
|
|
473
|
+
|
|
474
|
+
- [ppdocs 桌面应用](https://github.com/ppdocs/ppdocs)
|
|
475
|
+
- [MCP 协议文档](https://modelcontextprotocol.io/)
|
|
476
|
+
- [问题反馈](https://github.com/ppdocs/ppdocs/issues)
|
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,13 @@ 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>;
|
|
@@ -14,22 +14,36 @@ export class PpdocsApiClient {
|
|
|
14
14
|
// ============ HTTP 请求工具 ============
|
|
15
15
|
async request(path, options = {}) {
|
|
16
16
|
const url = `${this.baseUrl}${path}`;
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
...options
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const timeout = setTimeout(() => controller.abort(), 10000); // 10秒超时
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(url, {
|
|
21
|
+
...options,
|
|
22
|
+
signal: controller.signal,
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
...options.headers
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
30
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
22
31
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
const json = await response.json();
|
|
33
|
+
if (!json.success) {
|
|
34
|
+
throw new Error(json.error || 'Unknown error');
|
|
35
|
+
}
|
|
36
|
+
return json.data;
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
40
|
+
throw new Error('Request timeout (10s)');
|
|
41
|
+
}
|
|
42
|
+
throw err;
|
|
27
43
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
throw new Error(json.error || 'Unknown error');
|
|
44
|
+
finally {
|
|
45
|
+
clearTimeout(timeout);
|
|
31
46
|
}
|
|
32
|
-
return json.data;
|
|
33
47
|
}
|
|
34
48
|
// ============ 节点操作 ============
|
|
35
49
|
async listNodes() {
|
|
@@ -159,6 +173,57 @@ export class PpdocsApiClient {
|
|
|
159
173
|
});
|
|
160
174
|
return updated ? newBugfix : null;
|
|
161
175
|
}
|
|
176
|
+
// ============ 任务管理 ============
|
|
177
|
+
async listTasks(status) {
|
|
178
|
+
const query = status ? `?status=${status}` : '';
|
|
179
|
+
return this.request(`/tasks${query}`);
|
|
180
|
+
}
|
|
181
|
+
async getTask(taskId) {
|
|
182
|
+
try {
|
|
183
|
+
return await this.request(`/tasks/${taskId}`);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async createTask(task, creator) {
|
|
190
|
+
const now = new Date().toISOString();
|
|
191
|
+
const payload = {
|
|
192
|
+
title: task.title,
|
|
193
|
+
creator,
|
|
194
|
+
detail: {
|
|
195
|
+
description: task.description,
|
|
196
|
+
goals: task.goals,
|
|
197
|
+
related_nodes: task.related_nodes || []
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
return this.request('/tasks', {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
body: JSON.stringify(payload)
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
async addTaskLog(taskId, logType, content) {
|
|
206
|
+
try {
|
|
207
|
+
return await this.request(`/tasks/${taskId}/logs`, {
|
|
208
|
+
method: 'POST',
|
|
209
|
+
body: JSON.stringify({ log_type: logType, content })
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async completeTask(taskId, experience) {
|
|
217
|
+
try {
|
|
218
|
+
return await this.request(`/tasks/${taskId}/complete`, {
|
|
219
|
+
method: 'POST',
|
|
220
|
+
body: JSON.stringify(experience)
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
162
227
|
}
|
|
163
228
|
// ============ 模块级 API (兼容现有 tools/index.ts) ============
|
|
164
229
|
let client = null;
|
|
@@ -205,3 +270,19 @@ export async function getRelations(_projectId, nodeId) {
|
|
|
205
270
|
export async function addBugfix(_projectId, nodeId, bugfix) {
|
|
206
271
|
return getClient().addBugfix(nodeId, bugfix);
|
|
207
272
|
}
|
|
273
|
+
// ============ 任务管理 ============
|
|
274
|
+
export async function listTasks(_projectId, status) {
|
|
275
|
+
return getClient().listTasks(status);
|
|
276
|
+
}
|
|
277
|
+
export async function getTask(_projectId, taskId) {
|
|
278
|
+
return getClient().getTask(taskId);
|
|
279
|
+
}
|
|
280
|
+
export async function createTask(_projectId, task, creator) {
|
|
281
|
+
return getClient().createTask(task, creator);
|
|
282
|
+
}
|
|
283
|
+
export async function addTaskLog(_projectId, taskId, logType, content) {
|
|
284
|
+
return getClient().addTaskLog(taskId, logType, content);
|
|
285
|
+
}
|
|
286
|
+
export async function completeTask(_projectId, taskId, experience) {
|
|
287
|
+
return getClient().completeTask(taskId, experience);
|
|
288
|
+
}
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface Edge {
|
|
|
6
6
|
auto?: boolean;
|
|
7
7
|
}
|
|
8
8
|
export type NodeType = 'logic' | 'data' | 'intro';
|
|
9
|
-
export type NodeStatus = 'incomplete' | 'complete' | 'fixing' | 'refactoring' | 'deprecated'
|
|
9
|
+
export type NodeStatus = 'incomplete' | 'complete' | 'fixing' | 'refactoring' | 'deprecated';
|
|
10
10
|
export interface DataRef {
|
|
11
11
|
type: string;
|
|
12
12
|
description: string;
|
|
@@ -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,43 +1,28 @@
|
|
|
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
4
|
// 1. 创建节点
|
|
5
|
-
server.tool('kg_create_node', '
|
|
5
|
+
server.tool('kg_create_node', '创建知识节点。type: logic=逻辑/函数, data=数据结构, intro=概念介绍', {
|
|
6
6
|
title: z.string().describe('节点标题'),
|
|
7
7
|
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('标签列表'),
|
|
8
|
+
description: z.string().describe('Markdown描述(用Mermaid流程图+表格,禁止纯文字)'),
|
|
9
|
+
signature: z.string().optional().describe('唯一签名(用于依赖匹配,默认=title)'),
|
|
10
|
+
tags: z.array(z.string()).optional().describe('分类标签'),
|
|
26
11
|
dependencies: z.array(z.object({
|
|
27
|
-
name: z.string().describe('
|
|
12
|
+
name: z.string().describe('目标节点的signature'),
|
|
28
13
|
description: z.string().describe('依赖说明')
|
|
29
|
-
})).optional().describe('依赖列表(
|
|
14
|
+
})).optional().describe('依赖列表(自动生成连线)')
|
|
30
15
|
}, async (args) => {
|
|
31
16
|
const node = await storage.createNode(projectId, {
|
|
32
17
|
title: args.title,
|
|
33
18
|
type: args.type,
|
|
34
19
|
status: 'incomplete',
|
|
35
20
|
description: args.description || '',
|
|
36
|
-
x:
|
|
37
|
-
y:
|
|
21
|
+
x: 0, // 服务端自动布局
|
|
22
|
+
y: 0,
|
|
38
23
|
locked: false,
|
|
39
24
|
signature: args.signature || args.title,
|
|
40
|
-
categories: args.tags || [],
|
|
25
|
+
categories: args.tags || [],
|
|
41
26
|
dependencies: args.dependencies || []
|
|
42
27
|
});
|
|
43
28
|
return { content: [{ type: 'text', text: JSON.stringify(node, null, 2) }] };
|
|
@@ -48,16 +33,16 @@ graph LR
|
|
|
48
33
|
return { content: [{ type: 'text', text: success ? '删除成功' : '删除失败(节点不存在/已锁定/是根节点)' }] };
|
|
49
34
|
});
|
|
50
35
|
// 3. 更新节点
|
|
51
|
-
server.tool('kg_update_node', '
|
|
36
|
+
server.tool('kg_update_node', '更新节点内容(锁定节点不可更新)', {
|
|
52
37
|
nodeId: z.string().describe('节点ID'),
|
|
53
38
|
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(),
|
|
39
|
+
signature: z.string().optional().describe('新签名'),
|
|
40
|
+
description: z.string().optional().describe('新描述(Markdown)'),
|
|
41
|
+
status: z.enum(['incomplete', 'complete', 'fixing', 'refactoring', 'deprecated']).optional().describe('状态'),
|
|
42
|
+
tags: z.array(z.string()).optional().describe('分类标签'),
|
|
58
43
|
dependencies: z.array(z.object({
|
|
59
|
-
name: z.string()
|
|
60
|
-
description: z.string()
|
|
44
|
+
name: z.string(),
|
|
45
|
+
description: z.string()
|
|
61
46
|
})).optional().describe('依赖列表')
|
|
62
47
|
}, async (args) => {
|
|
63
48
|
const { nodeId, tags, ...rest } = args;
|
|
@@ -66,18 +51,17 @@ graph LR
|
|
|
66
51
|
const node = await storage.updateNode(projectId, nodeId, updates);
|
|
67
52
|
return { content: [{ type: 'text', text: node ? JSON.stringify(node, null, 2) : '更新失败(节点不存在或已锁定)' }] };
|
|
68
53
|
});
|
|
69
|
-
// 4.
|
|
70
|
-
server.tool('kg_lock_node', '
|
|
71
|
-
nodeId: z.string().describe('节点ID')
|
|
72
|
-
locked: z.boolean().describe('true=锁定, false=解锁')
|
|
54
|
+
// 4. 锁定节点 (只能锁定,解锁需用户在前端手动操作)
|
|
55
|
+
server.tool('kg_lock_node', '锁定节点(锁定后只能读取,解锁需用户在前端手动操作)', {
|
|
56
|
+
nodeId: z.string().describe('节点ID')
|
|
73
57
|
}, async (args) => {
|
|
74
|
-
const node = await storage.lockNode(projectId, args.nodeId,
|
|
58
|
+
const node = await storage.lockNode(projectId, args.nodeId, true); // 强制锁定
|
|
75
59
|
return { content: [{ type: 'text', text: node ? JSON.stringify(node, null, 2) : '操作失败' }] };
|
|
76
60
|
});
|
|
77
61
|
// 5. 搜索节点
|
|
78
|
-
server.tool('kg_search', '
|
|
79
|
-
keywords: z.array(z.string()).describe('
|
|
80
|
-
limit: z.number().optional().describe('
|
|
62
|
+
server.tool('kg_search', '关键词搜索节点,按相关度排序返回', {
|
|
63
|
+
keywords: z.array(z.string()).describe('关键词列表(OR逻辑)'),
|
|
64
|
+
limit: z.number().optional().describe('返回数量(默认20)')
|
|
81
65
|
}, async (args) => {
|
|
82
66
|
const results = await storage.searchNodes(projectId, args.keywords, args.limit);
|
|
83
67
|
const output = results.map(r => ({
|
|
@@ -87,9 +71,9 @@ graph LR
|
|
|
87
71
|
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
88
72
|
});
|
|
89
73
|
// 6. 路径查找
|
|
90
|
-
server.tool('kg_find_path', '
|
|
91
|
-
startId: z.string().describe('
|
|
92
|
-
endId: z.string().describe('
|
|
74
|
+
server.tool('kg_find_path', '查找两节点间的依赖路径', {
|
|
75
|
+
startId: z.string().describe('起点节点ID'),
|
|
76
|
+
endId: z.string().describe('终点节点ID')
|
|
93
77
|
}, async (args) => {
|
|
94
78
|
const result = await storage.findPath(projectId, args.startId, args.endId);
|
|
95
79
|
if (!result)
|
|
@@ -102,13 +86,13 @@ graph LR
|
|
|
102
86
|
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
103
87
|
});
|
|
104
88
|
// 7. 列出所有节点
|
|
105
|
-
server.tool('kg_list_nodes', '
|
|
89
|
+
server.tool('kg_list_nodes', '列出项目全部节点概览', {}, async () => {
|
|
106
90
|
const nodes = await storage.listNodes(projectId);
|
|
107
91
|
const output = nodes.map(n => ({ id: n.id, title: n.title, type: n.type, status: n.status, locked: n.locked }));
|
|
108
92
|
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
109
93
|
});
|
|
110
94
|
// 8. 查找孤立节点
|
|
111
|
-
server.tool('kg_find_orphans', '
|
|
95
|
+
server.tool('kg_find_orphans', '查找无连线的孤立节点(用于清理)', {}, async () => {
|
|
112
96
|
const orphans = await storage.findOrphans(projectId);
|
|
113
97
|
if (orphans.length === 0) {
|
|
114
98
|
return { content: [{ type: 'text', text: '没有孤立节点' }] };
|
|
@@ -116,7 +100,7 @@ graph LR
|
|
|
116
100
|
return { content: [{ type: 'text', text: JSON.stringify(orphans, null, 2) }] };
|
|
117
101
|
});
|
|
118
102
|
// 9. 查询节点关系网
|
|
119
|
-
server.tool('kg_get_relations', '
|
|
103
|
+
server.tool('kg_get_relations', '获取节点的上下游关系(谁依赖它/它依赖谁)', {
|
|
120
104
|
nodeId: z.string().describe('节点ID')
|
|
121
105
|
}, async (args) => {
|
|
122
106
|
const relations = await storage.getRelations(projectId, args.nodeId);
|
|
@@ -132,18 +116,79 @@ graph LR
|
|
|
132
116
|
};
|
|
133
117
|
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
134
118
|
});
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
119
|
+
// ===================== 任务管理工具 =====================
|
|
120
|
+
// 10. 创建任务
|
|
121
|
+
server.tool('task_create', '创建开发任务,记录目标和关联节点', {
|
|
122
|
+
title: z.string().describe('任务标题'),
|
|
123
|
+
description: z.string().describe('任务描述(Markdown)'),
|
|
124
|
+
goals: z.array(z.string()).optional().describe('目标清单'),
|
|
125
|
+
related_nodes: z.array(z.string()).optional().describe('关联节点ID')
|
|
126
|
+
}, async (args) => {
|
|
127
|
+
const task = await storage.createTask(projectId, {
|
|
128
|
+
title: args.title,
|
|
129
|
+
description: args.description,
|
|
130
|
+
goals: args.goals || [],
|
|
131
|
+
related_nodes: args.related_nodes
|
|
132
|
+
}, _user);
|
|
133
|
+
return { content: [{ type: 'text', text: JSON.stringify(task, null, 2) }] };
|
|
134
|
+
});
|
|
135
|
+
// 11. 列出任务
|
|
136
|
+
server.tool('task_list', '列出任务(active=进行中,archived=已归档)', {
|
|
137
|
+
status: z.enum(['active', 'archived']).optional().describe('状态筛选')
|
|
138
|
+
}, async (args) => {
|
|
139
|
+
const tasks = await storage.listTasks(projectId, args.status);
|
|
140
|
+
const output = tasks.map(t => ({
|
|
141
|
+
id: t.id,
|
|
142
|
+
title: t.title,
|
|
143
|
+
status: t.status,
|
|
144
|
+
creator: t.creator,
|
|
145
|
+
created_at: t.created_at,
|
|
146
|
+
last_log: t.last_log
|
|
147
|
+
}));
|
|
148
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
149
|
+
});
|
|
150
|
+
// 12. 获取任务详情
|
|
151
|
+
server.tool('task_get', '获取任务完整信息(含全部日志)', {
|
|
152
|
+
taskId: z.string().describe('任务ID')
|
|
153
|
+
}, async (args) => {
|
|
154
|
+
const task = await storage.getTask(projectId, args.taskId);
|
|
155
|
+
if (!task) {
|
|
156
|
+
return { content: [{ type: 'text', text: '任务不存在' }] };
|
|
157
|
+
}
|
|
158
|
+
return { content: [{ type: 'text', text: JSON.stringify(task, null, 2) }] };
|
|
159
|
+
});
|
|
160
|
+
// 13. 添加任务日志
|
|
161
|
+
server.tool('task_add_log', '记录任务进展/问题/方案/参考', {
|
|
162
|
+
taskId: z.string().describe('任务ID'),
|
|
163
|
+
log_type: z.enum(['progress', 'issue', 'solution', 'reference']).describe('日志类型'),
|
|
164
|
+
content: z.string().describe('日志内容(Markdown)')
|
|
141
165
|
}, async (args) => {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
166
|
+
const task = await storage.addTaskLog(projectId, args.taskId, args.log_type, args.content);
|
|
167
|
+
if (!task) {
|
|
168
|
+
return { content: [{ type: 'text', text: '添加失败(任务不存在或已归档)' }] };
|
|
169
|
+
}
|
|
170
|
+
return { content: [{ type: 'text', text: `日志已添加,任务共有 ${task.logs.length} 条日志` }] };
|
|
171
|
+
});
|
|
172
|
+
// 14. 完成任务
|
|
173
|
+
server.tool('task_complete', '完成任务并归档,填写经验总结', {
|
|
174
|
+
taskId: z.string().describe('任务ID'),
|
|
175
|
+
summary: z.string().describe('经验总结(Markdown)'),
|
|
176
|
+
difficulties: z.array(z.string()).optional().describe('遇到的困难'),
|
|
177
|
+
solutions: z.array(z.string()).optional().describe('解决方案'),
|
|
178
|
+
references: z.array(z.object({
|
|
179
|
+
title: z.string(),
|
|
180
|
+
url: z.string().optional()
|
|
181
|
+
})).optional().describe('参考资料')
|
|
182
|
+
}, async (args) => {
|
|
183
|
+
const task = await storage.completeTask(projectId, args.taskId, {
|
|
184
|
+
summary: args.summary,
|
|
185
|
+
difficulties: args.difficulties || [],
|
|
186
|
+
solutions: args.solutions || [],
|
|
187
|
+
references: args.references || []
|
|
146
188
|
});
|
|
147
|
-
|
|
189
|
+
if (!task) {
|
|
190
|
+
return { content: [{ type: 'text', text: '完成失败(任务不存在或已归档)' }] };
|
|
191
|
+
}
|
|
192
|
+
return { content: [{ type: 'text', text: `任务已完成归档: ${task.title}` }] };
|
|
148
193
|
});
|
|
149
194
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ppdocs/mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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
|
+
}
|