@ppdocs/mcp 3.2.36 → 3.2.38

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 CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  ppdocs MCP 是一个 [Model Context Protocol](https://modelcontextprotocol.io/) 服务器,让 Claude 能够在对话中构建和查询项目知识图谱。
10
10
 
11
+ 当前 MCP 采用 `flowchart-first` 模型:节点、关系、绑定文件、任务关联和节点文档统一收敛到 `kg_flowchart(action:"...")`,不再维护独立的 `kg_doc` / `kg_search` / `kg_create_node` 一组旧接口。
12
+
11
13
  ```
12
14
  ┌─────────────┐ MCP ┌─────────────┐ HTTP ┌─────────────┐
13
15
  │ Claude AI │ ──────────── │ @ppdocs/mcp │ ────────── │ ppdocs 桌面 │
@@ -105,28 +107,55 @@ npx @ppdocs/mcp init -p <projectId> -k <key> --codex
105
107
 
106
108
  ## 工具列表
107
109
 
108
- ### 知识图谱工具
110
+ ### 核心工具
109
111
 
110
112
  | 工具 | 说明 |
111
113
  |------|------|
112
- | `kg_create_node` | 创建知识节点 |
113
- | `kg_update_node` | 更新节点内容 |
114
- | `kg_delete_node` | 删除节点 |
115
- | `kg_lock_node` | 锁定节点 (AI 只能锁定,解锁需前端手动) |
116
- | `kg_list_nodes` | 列出节点 (支持 status/edges 过滤,maxEdges=0 查孤立节点) |
117
- | `kg_search` | 关键词搜索 |
118
- | `kg_find_path` | 查找依赖路径 |
119
- | `kg_get_relations` | 获取节点关系 |
114
+ | `kg_init` | 初始化项目连接与上下文 |
115
+ | `kg_status` | 查看项目仪表盘与流程图概况 |
116
+ | `kg_flowchart` | 统一的知识图谱入口,负责查询、更新、关系分析 |
117
+ | `kg_workflow` | Markdown 文档工作流管理 |
118
+ | `kg_task` | 任务管理 |
119
+ | `kg_files` | 项目文件读写/上传下载 |
120
+ | `kg_discuss` | 讨论区 |
121
+ | `kg_meeting` | AI 协作会议 |
122
+ | `code_scan` | 代码扫描 |
123
+ | `code_smart_context` | 获取符号的智能上下文 |
124
+ | `code_full_path` | 获取两个符号之间的全路径 |
125
+
126
+ ### `kg_flowchart` actions
127
+
128
+ | action | 说明 |
129
+ |------|------|
130
+ | `list` | 列出所有流程图 |
131
+ | `get` | 获取指定流程图的节点与连线 |
132
+ | `search` | 按关键词搜索节点、节点文档与绑定资源 |
133
+ | `get_relations` | 查看某节点的入边/出边关系 |
134
+ | `find_path` | 查找两个节点之间的有向路径 |
135
+ | `get_node` | 获取节点详情、关联层级、绑定资源与文档 |
136
+ | `update_node` | 更新节点信息并追加节点文档版本 |
137
+ | `delete_node` | 删除节点 |
138
+ | `batch_add` | 批量新增节点与连线 |
139
+ | `bind` / `unbind` | 绑定或解绑文件、目录、文档、任务 |
140
+ | `orphans` | 检查孤立参考文档 |
141
+ | `health` | 查看节点健康度 |
142
+ | `create_chart` | 创建子图并绑定到父节点 |
143
+ | `delete_chart` | 删除子图 |
144
+
145
+ ### 常用示例
120
146
 
121
- ### 任务管理工具
147
+ ```bash
148
+ # 搜索相关节点
149
+ kg_flowchart(action:"search", query:"flowchart storage")
122
150
 
123
- | 工具 | 说明 |
124
- |------|------|
125
- | `task_create` | 创建开发任务 |
126
- | `task_list` | 列出任务 |
127
- | `task_get` | 获取任务详情 |
128
- | `task_add_log` | 添加进度日志 |
129
- | `task_complete` | 完成任务并归档 |
151
+ # 查看节点详情
152
+ kg_flowchart(action:"get_node", chartId:"main", nodeId:"n_storage", expand:2, includeDoc:true, includeFiles:true)
153
+
154
+ # 追加节点文档
155
+ kg_flowchart(action:"update_node", chartId:"main", nodeId:"n_storage",
156
+ docSummary:"补充存储层职责",
157
+ docContent:"## 职责\n...\n## 关键流程\n...")
158
+ ```
130
159
 
131
160
  ---
132
161
 
@@ -178,18 +207,15 @@ npx @ppdocs/mcp init -p <projectId> -k <key> --codex
178
207
 
179
208
  ## 更新日志
180
209
 
181
- ### v2.6.9
182
- - `kg_list_nodes` 支持 status/minEdges/maxEdges 过滤
183
- - 🗑️ 移除 `kg_find_orphans` (用 `kg_list_nodes(maxEdges: 0)` 替代)
210
+ ### v3.2.36
211
+ - 统一为 `flowchart-first` MCP 接口模型
212
+ - `kg_flowchart` 新增 `search` / `get_relations` / `find_path`
213
+ - 文档与模板改为使用 `kg_flowchart(action:"...")`,不再引用旧 `kg_doc` / `kg_search` 等独立接口
184
214
 
185
215
  ### v2.5.0
186
- - 新增 CLI init 命令,自动安装工作流模板
187
- - 支持 Codex 模式 (--codex)
188
- - 📦 构建时自动复制模板
189
-
190
- ### v2.4.0
191
- - 🛡️ `kg_lock_node` 只能锁定,解锁需前端手动操作
192
- - ⚡ 后端自动记录操作日志
216
+ - 新增 CLI init 命令,自动安装工作流模板
217
+ - 支持 Codex 模式 (`--codex`)
218
+ - 构建时自动复制模板
193
219
 
194
220
  ### v2.3.0
195
221
  - 新增任务管理功能
package/dist/cli.js CHANGED
@@ -392,11 +392,12 @@ function generateMcpPermissions() {
392
392
  'kg_status',
393
393
  // 知识管理
394
394
  'kg_projects',
395
- 'kg_rules',
395
+ 'kg_workflow',
396
396
  // 工作流
397
397
  'kg_task',
398
398
  'kg_files',
399
399
  'kg_discuss',
400
+ 'kg_ref',
400
401
  // 流程图
401
402
  'kg_flowchart',
402
403
  // 协作
@@ -4,19 +4,31 @@
4
4
  *
5
5
  * API URL 格式: http://localhost:20099/api/:projectId/:password/...
6
6
  */
7
- import type { Task, TaskSummary, TaskLogType, TaskExperience, FileInfo, RuleMeta } from './types.js';
7
+ import type { Task, TaskSummary, TaskLogType, TaskExperience, FileInfo, WorkflowSummary, WorkflowDoc, WorkflowSelector, Reference } from './types.js';
8
8
  export declare class PpdocsApiClient {
9
9
  private baseUrl;
10
10
  private serverUrl;
11
11
  constructor(apiUrl: string);
12
12
  private request;
13
- getRulesApi(ruleType: string): Promise<string[]>;
14
- saveRulesApi(ruleType: string, rules: string[]): Promise<boolean>;
15
- getRulesMeta(): Promise<Record<string, RuleMeta>>;
16
- saveRulesMeta(meta: Record<string, RuleMeta>): Promise<boolean>;
17
- crossGetRulesMeta(target: string): Promise<Record<string, RuleMeta>>;
18
- getGlobalRulesMeta(): Promise<Record<string, RuleMeta>>;
19
- saveGlobalRulesMeta(meta: Record<string, RuleMeta>): Promise<boolean>;
13
+ listWorkflows(scope?: 'all' | 'global' | 'project'): Promise<WorkflowSummary[]>;
14
+ getWorkflow(workflowId: string, scope?: 'all' | 'global' | 'project'): Promise<WorkflowDoc | null>;
15
+ getWorkflowBatch(selectors: WorkflowSelector[]): Promise<WorkflowDoc[]>;
16
+ saveWorkflow(workflowId: string, input: {
17
+ scope: 'global' | 'project';
18
+ title: string;
19
+ description: string;
20
+ system: string;
21
+ keywords: string[];
22
+ minHits: number;
23
+ always: boolean;
24
+ content: string;
25
+ }): Promise<WorkflowDoc>;
26
+ deleteWorkflow(workflowId: string, scope: 'global' | 'project'): Promise<boolean>;
27
+ listReferences(): Promise<Reference[]>;
28
+ getReference(refId: string): Promise<Reference | null>;
29
+ saveReference(reference: Reference): Promise<boolean>;
30
+ deleteReference(refId: string): Promise<boolean>;
31
+ readReferenceFile(path: string): Promise<string>;
20
32
  listTasks(status?: 'active' | 'archived'): Promise<TaskSummary[]>;
21
33
  getTask(taskId: string, mode?: 'smart' | 'full'): Promise<Task | null>;
22
34
  createTask(task: {
@@ -46,8 +58,6 @@ export declare class PpdocsApiClient {
46
58
  description: string;
47
59
  updatedAt: string;
48
60
  }[]>;
49
- /** 跨项目: 获取规则 */
50
- crossGetRules(target: string, ruleType: string): Promise<string[]>;
51
61
  /** 列出项目文件 */
52
62
  listFiles(dir?: string): Promise<FileInfo[]>;
53
63
  /** 读取项目文件 */
@@ -95,6 +105,10 @@ export declare class PpdocsApiClient {
95
105
  discussionReply(id: string, sender: string, content: string, msgSummary?: string, newSummary?: string): Promise<boolean>;
96
106
  /** 完成讨论 (公开路由, 标记为 completed) */
97
107
  discussionComplete(id: string): Promise<boolean>;
108
+ /** 归档关闭讨论 (需项目认证) */
109
+ discussionClose(id: string, conclusion: string): Promise<{
110
+ archived_path?: string;
111
+ }>;
98
112
  /** 删除讨论 (公开路由) */
99
113
  discussionDelete(id: string): Promise<boolean>;
100
114
  /** 列出公共文件 */
@@ -99,75 +99,64 @@ export class PpdocsApiClient {
99
99
  clearTimeout(timeout);
100
100
  }
101
101
  }
102
- // ============ 规则 API ============
103
- async getRulesApi(ruleType) {
104
- try {
105
- return await this.request(`/rules/${ruleType}`);
106
- }
107
- catch {
108
- return [];
109
- }
102
+ // ============ 工作流 API ============
103
+ async listWorkflows(scope) {
104
+ const query = scope ? `?scope=${scope}` : '';
105
+ return this.request(`/workflows${query}`);
110
106
  }
111
- async saveRulesApi(ruleType, rules) {
107
+ async getWorkflow(workflowId, scope) {
112
108
  try {
113
- await this.request(`/rules/${ruleType}`, {
114
- method: 'PUT',
115
- body: JSON.stringify(rules)
116
- });
117
- return true;
109
+ const query = scope ? `?scope=${scope}` : '';
110
+ return await this.request(`/workflows/${encodeURIComponent(workflowId)}${query}`);
118
111
  }
119
112
  catch {
120
- return false;
113
+ return null;
121
114
  }
122
115
  }
123
- async getRulesMeta() {
124
- try {
125
- return await this.request('/rules-meta');
126
- }
127
- catch {
128
- return {};
129
- }
116
+ async getWorkflowBatch(selectors) {
117
+ return this.request('/workflows/batch', {
118
+ method: 'POST',
119
+ body: JSON.stringify(selectors),
120
+ });
130
121
  }
131
- async saveRulesMeta(meta) {
132
- try {
133
- await this.request('/rules-meta', {
134
- method: 'PUT',
135
- body: JSON.stringify(meta)
136
- });
137
- return true;
138
- }
139
- catch {
140
- return false;
141
- }
122
+ async saveWorkflow(workflowId, input) {
123
+ return this.request(`/workflows/${encodeURIComponent(workflowId)}`, {
124
+ method: 'PUT',
125
+ body: JSON.stringify(input),
126
+ });
142
127
  }
143
- async crossGetRulesMeta(target) {
144
- try {
145
- return await this.request(`/cross/${encodeURIComponent(target)}/rules-meta`);
146
- }
147
- catch {
148
- return {};
149
- }
128
+ async deleteWorkflow(workflowId, scope) {
129
+ return this.request(`/workflows/${encodeURIComponent(workflowId)}?scope=${scope}`, {
130
+ method: 'DELETE',
131
+ });
150
132
  }
151
- async getGlobalRulesMeta() {
152
- try {
153
- return await this.request('/global-rules-meta');
154
- }
155
- catch {
156
- return {};
157
- }
133
+ // ============ 外部参考 API ============
134
+ async listReferences() {
135
+ return this.request('/refs');
158
136
  }
159
- async saveGlobalRulesMeta(meta) {
137
+ async getReference(refId) {
160
138
  try {
161
- await this.request('/global-rules-meta', {
162
- method: 'PUT',
163
- body: JSON.stringify(meta)
164
- });
165
- return true;
139
+ return await this.request(`/refs/${encodeURIComponent(refId)}`);
166
140
  }
167
141
  catch {
168
- return false;
142
+ return null;
169
143
  }
170
144
  }
145
+ async saveReference(reference) {
146
+ await this.request(`/refs/${encodeURIComponent(reference.id)}`, {
147
+ method: 'PUT',
148
+ body: JSON.stringify(reference),
149
+ });
150
+ return true;
151
+ }
152
+ async deleteReference(refId) {
153
+ return this.request(`/refs/${encodeURIComponent(refId)}`, {
154
+ method: 'DELETE',
155
+ });
156
+ }
157
+ async readReferenceFile(path) {
158
+ return this.request(`/refs/files/${cleanPath(path)}`);
159
+ }
171
160
  // ============ 任务管理 ============
172
161
  async listTasks(status) {
173
162
  const query = status ? `?status=${status}` : '';
@@ -285,15 +274,6 @@ export class PpdocsApiClient {
285
274
  async crossListProjects() {
286
275
  return this.request('/cross/projects');
287
276
  }
288
- /** 跨项目: 获取规则 */
289
- async crossGetRules(target, ruleType) {
290
- try {
291
- return await this.request(`/cross/${encodeURIComponent(target)}/rules/${ruleType}`);
292
- }
293
- catch {
294
- return [];
295
- }
296
- }
297
277
  // ============ 项目文件访问 ============
298
278
  /** 列出项目文件 */
299
279
  async listFiles(dir) {
@@ -466,6 +446,13 @@ export class PpdocsApiClient {
466
446
  method: 'POST',
467
447
  });
468
448
  }
449
+ /** 归档关闭讨论 (需项目认证) */
450
+ async discussionClose(id, conclusion) {
451
+ return this.request(`/discussions/${encodeURIComponent(id)}/close`, {
452
+ method: 'POST',
453
+ body: JSON.stringify({ conclusion }),
454
+ });
455
+ }
469
456
  /** 删除讨论 (公开路由) */
470
457
  async discussionDelete(id) {
471
458
  return this.publicRequest(`/api/discussions/${encodeURIComponent(id)}`, {
@@ -4,6 +4,48 @@ export interface RuleMeta {
4
4
  min_hits: number;
5
5
  always?: boolean;
6
6
  }
7
+ export interface WorkflowSummary {
8
+ id: string;
9
+ scope: 'global' | 'project';
10
+ title: string;
11
+ description: string;
12
+ system: string;
13
+ keywords: string[];
14
+ minHits: number;
15
+ always: boolean;
16
+ updatedAt: string;
17
+ }
18
+ export interface WorkflowDoc extends WorkflowSummary {
19
+ content: string;
20
+ }
21
+ export interface WorkflowSelector {
22
+ id: string;
23
+ scope?: 'global' | 'project';
24
+ }
25
+ export interface ReferenceLink {
26
+ url: string;
27
+ label: string;
28
+ }
29
+ export interface ReferenceFile {
30
+ name: string;
31
+ path: string;
32
+ }
33
+ export interface AdoptedNode {
34
+ chartId: string;
35
+ nodeId: string;
36
+ nodeLabel: string;
37
+ }
38
+ export interface Reference {
39
+ id: string;
40
+ title: string;
41
+ summary: string;
42
+ links: ReferenceLink[];
43
+ files: ReferenceFile[];
44
+ scripts: ReferenceFile[];
45
+ adoptedBy: AdoptedNode[];
46
+ createdAt: string;
47
+ updatedAt: string;
48
+ }
7
49
  export interface FileInfo {
8
50
  name: string;
9
51
  path: string;
@@ -2,7 +2,7 @@
2
2
  * 代码分析引擎工具 (3个)
3
3
  * code_scan, code_smart_context, code_full_path
4
4
  *
5
- * code_smart_context: 代码+KG文档全关联 (含影响范围)
5
+ * code_smart_context: 代码+KG文档/工作流全关联 (含影响范围)
6
6
  * code_full_path: 两点间代码链路+共享KG文档
7
7
  */
8
8
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
@@ -2,7 +2,7 @@
2
2
  * 代码分析引擎工具 (3个)
3
3
  * code_scan, code_smart_context, code_full_path
4
4
  *
5
- * code_smart_context: 代码+KG文档全关联 (含影响范围)
5
+ * code_smart_context: 代码+KG文档/工作流全关联 (含影响范围)
6
6
  * code_full_path: 两点间代码链路+共享KG文档
7
7
  */
8
8
  import { z } from 'zod';
@@ -33,7 +33,7 @@ export function registerAnalyzerTools(server, ctx) {
33
33
  ].join('\n'));
34
34
  }));
35
35
  // ===== code_smart_context: 代码+文档全关联上下文 =====
36
- server.tool('code_smart_context', '🔍 代码+文档全关联上下文 — 输入一个函数名,一次调用返回:代码依赖、关联文档、匹配规则、活跃任务、影响范围摘要。需先运行 code_scan', {
36
+ server.tool('code_smart_context', '🔍 代码+文档全关联上下文 — 输入一个函数名,一次调用返回:代码依赖、关联文档、匹配工作流、活跃任务、影响范围摘要。需先运行 code_scan', {
37
37
  projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
38
38
  symbolName: z.string().describe('要查询的符号名称(如"handleLogin", "AuthService")'),
39
39
  }, async (args) => safeTool(async () => {
@@ -75,11 +75,12 @@ export function registerAnalyzerTools(server, ctx) {
75
75
  }
76
76
  lines.push('');
77
77
  }
78
- // 匹配规则
79
- if (smartCtx.matchedRules.length > 0) {
80
- lines.push(`### 📏 匹配规则 (${smartCtx.matchedRules.length})`);
81
- for (const rule of smartCtx.matchedRules) {
82
- lines.push(` [${rule.ruleType}] ${rule.content.substring(0, 100)}`);
78
+ // 匹配工作流
79
+ if (smartCtx.matchedWorkflows.length > 0) {
80
+ lines.push(`### 📘 匹配工作流 (${smartCtx.matchedWorkflows.length})`);
81
+ for (const workflow of smartCtx.matchedWorkflows) {
82
+ const desc = workflow.description ? `${workflow.description}` : '';
83
+ lines.push(` [${workflow.scope}/${workflow.system || 'general'}] ${workflow.title}${desc}`);
83
84
  }
84
85
  lines.push('');
85
86
  }
@@ -233,13 +233,9 @@ export function registerDiscussionTools(server, ctx) {
233
233
  if (brief && !isInitiator(brief, me, myPid)) {
234
234
  return wrap(`🔒 无权限: 仅发起人 (${brief.initiator}) 可归档讨论\n你的身份: ${me}`);
235
235
  }
236
- // 先追加结案总结
237
- try {
238
- await client().discussionReply(decoded.id, me, `📋 结案总结:\n\n${decoded.conclusion}`, '结案总结');
239
- }
240
- catch { /* 忽略回复失败 */ }
241
- await client().discussionComplete(decoded.id);
242
- return wrap(`✅ 讨论已结案 (ID: ${decoded.id})\n📋 ${decoded.conclusion}`);
236
+ const result = await client().discussionClose(decoded.id, decoded.conclusion);
237
+ const archivePath = result?.archived_path ? `\n📦 已归档: ${result.archived_path}` : '';
238
+ return wrap(`✅ 讨论已结案并归档 (ID: ${decoded.id})\n📋 ${decoded.conclusion}${archivePath}`);
243
239
  }
244
240
  case 'delete': {
245
241
  if (!decoded.id)