@ppdocs/mcp 2.6.21 → 2.6.23

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 CHANGED
@@ -6,13 +6,9 @@ import * as fs from 'fs';
6
6
  import * as path from 'path';
7
7
  import { fileURLToPath } from 'url';
8
8
  import { execSync } from 'child_process';
9
+ import { generateUser } from './config.js';
9
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
11
  const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
11
- /** 生成随机用户名 (8位字母数字) */
12
- function generateUser() {
13
- const chars = 'abcdefghjkmnpqrstuvwxyz23456789';
14
- return Array.from({ length: 8 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
15
- }
16
12
  function parseArgs(args) {
17
13
  const opts = { port: 20001, api: 'localhost', codex: false };
18
14
  for (let i = 0; i < args.length; i++) {
@@ -151,12 +147,26 @@ function autoRegisterMcp(apiUrl, user) {
151
147
  detected.push('Claude');
152
148
  try {
153
149
  console.log(`✅ Detected Claude CLI, registering MCP...`);
154
- execSilent(`claude mcp remove ${serverName}`);
155
- const cmd = `claude mcp add ${serverName} -- npx -y @ppdocs/mcp@latest`;
156
- execSync(cmd, { stdio: 'inherit' });
150
+ // 先检查是否已存在
151
+ const checkResult = execSync(`claude mcp list`, { encoding: 'utf-8' });
152
+ if (checkResult.includes(serverName)) {
153
+ console.log(`✅ Claude MCP already configured`);
154
+ }
155
+ else {
156
+ const cmd = `claude mcp add ${serverName} -- npx -y @ppdocs/mcp@latest`;
157
+ execSync(cmd, { stdio: 'inherit' });
158
+ }
157
159
  }
158
160
  catch (e) {
159
- console.log(`⚠️ Claude MCP registration failed: ${e}`);
161
+ // 如果 list 失败,尝试添加
162
+ try {
163
+ execSilent(`claude mcp remove ${serverName}`);
164
+ const cmd = `claude mcp add ${serverName} -- npx -y @ppdocs/mcp@latest`;
165
+ execSync(cmd, { stdio: 'inherit' });
166
+ }
167
+ catch {
168
+ console.log(`⚠️ Claude MCP registration failed`);
169
+ }
160
170
  }
161
171
  }
162
172
  // 检测 Codex CLI (OpenAI)
@@ -246,6 +256,33 @@ function generateHooksConfig() {
246
256
  }
247
257
  };
248
258
  }
259
+ /** 生成 MCP 权限配置 (允许全部 ppdocs-kg 方法) */
260
+ function generateMcpPermissions() {
261
+ const mcpMethods = [
262
+ // 知识图谱操作
263
+ 'kg_create_node',
264
+ 'kg_delete_node',
265
+ 'kg_update_node',
266
+ 'kg_update_root',
267
+ 'kg_get_rules',
268
+ 'kg_save_rules',
269
+ 'kg_lock_node',
270
+ 'kg_search',
271
+ 'kg_find_path',
272
+ 'kg_list_nodes',
273
+ 'kg_get_relations',
274
+ 'kg_read_node',
275
+ 'kg_get_tree',
276
+ 'kg_find_by_file',
277
+ // 任务管理
278
+ 'task_create',
279
+ 'task_list',
280
+ 'task_get',
281
+ 'task_add_log',
282
+ 'task_complete',
283
+ ];
284
+ return mcpMethods.map(m => `mcp__ppdocs-kg__${m}`);
285
+ }
249
286
  /** 安装 Claude Code 模板 */
250
287
  function installClaudeTemplates(cwd) {
251
288
  const claudeDir = path.join(cwd, '.claude');
@@ -273,13 +310,24 @@ function installClaudeTemplates(cwd) {
273
310
  catch { /* ignore */ }
274
311
  }
275
312
  const hooksConfig = generateHooksConfig();
313
+ // 合并 MCP 权限到 permissions.allow
314
+ const existingPermissions = existingSettings.permissions || {};
315
+ const existingAllow = existingPermissions.allow || [];
316
+ const mcpPermissions = generateMcpPermissions();
317
+ // 去重合并
318
+ const mergedAllow = [...new Set([...existingAllow, ...mcpPermissions])];
276
319
  const mergedSettings = {
277
320
  ...existingSettings,
278
- ...hooksConfig
321
+ ...hooksConfig,
322
+ permissions: {
323
+ ...existingPermissions,
324
+ allow: mergedAllow,
325
+ }
279
326
  };
280
327
  fs.mkdirSync(claudeDir, { recursive: true });
281
328
  fs.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2));
282
329
  console.log(`✅ Configured .claude/settings.json hooks (${process.platform})`);
330
+ console.log(`✅ Added ${mcpPermissions.length} MCP method permissions`);
283
331
  }
284
332
  /** 安装 Codex 模板 (生成 AGENTS.md) */
285
333
  function installCodexTemplates(cwd) {
package/dist/config.d.ts CHANGED
@@ -7,6 +7,8 @@ export interface PpdocsConfig {
7
7
  projectId: string;
8
8
  user: string;
9
9
  }
10
+ /** 生成随机用户名 (8位字母数字) */
11
+ export declare function generateUser(): string;
10
12
  /**
11
13
  * 加载配置 (优先级: 环境变量 > .ppdocs 文件)
12
14
  */
package/dist/config.js CHANGED
@@ -5,7 +5,7 @@
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
7
  /** 生成随机用户名 (8位字母数字) */
8
- function generateUser() {
8
+ export function generateUser() {
9
9
  const chars = 'abcdefghjkmnpqrstuvwxyz23456789';
10
10
  return Array.from({ length: 8 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
11
11
  }
package/dist/index.js CHANGED
@@ -6,12 +6,19 @@
6
6
  * 1. CLI 初始化: npx @ppdocs/mcp init -p <projectId> -k <key>
7
7
  * 2. MCP 服务: 自动读取 .ppdocs 配置或 PPDOCS_API_URL 环境变量
8
8
  */
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { fileURLToPath } from 'url';
9
12
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
13
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
14
  import { registerTools } from './tools/index.js';
12
15
  import { initClient } from './storage/httpClient.js';
13
16
  import { runCli } from './cli.js';
14
17
  import { loadConfig } from './config.js';
18
+ // 从 package.json 读取版本号
19
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
21
+ const VERSION = pkg.version;
15
22
  // 检查是否为 CLI 命令
16
23
  const args = process.argv.slice(2);
17
24
  if (args.length > 0 && runCli(args)) {
@@ -21,11 +28,11 @@ if (args.length > 0 && runCli(args)) {
21
28
  async function main() {
22
29
  const config = loadConfig();
23
30
  initClient(config.apiUrl);
24
- const server = new McpServer({ name: `ppdocs [${config.projectId}]`, version: '2.3.0' }, { capabilities: { tools: {} } });
31
+ const server = new McpServer({ name: `ppdocs [${config.projectId}]`, version: VERSION }, { capabilities: { tools: {} } });
25
32
  registerTools(server, config.projectId, config.user);
26
33
  const transport = new StdioServerTransport();
27
34
  await server.connect(transport);
28
- console.error(`ppdocs MCP v2.3 | project: ${config.projectId} | user: ${config.user}`);
35
+ console.error(`ppdocs MCP v${VERSION} | project: ${config.projectId} | user: ${config.user}`);
29
36
  }
30
37
  main().catch((err) => {
31
38
  console.error('Fatal error:', err);
@@ -42,6 +42,7 @@ export interface NodeData {
42
42
  categories: string[];
43
43
  path: string;
44
44
  description: string;
45
+ summary?: string;
45
46
  dataInput?: DataRef;
46
47
  dataOutput?: DataRef;
47
48
  dependencies: Dependency[];
@@ -1,20 +1,20 @@
1
1
  import { z } from 'zod';
2
2
  import * as storage from '../storage/httpClient.js';
3
- import { decodeUnicodeEscapes, decodeObjectStrings, wrapResult, getRules, RULE_TYPE_LABELS } from '../utils.js';
3
+ import { decodeUnicodeEscapes, decodeObjectStrings, getRules, RULE_TYPE_LABELS } from '../utils.js';
4
4
  // 辅助函数: 包装返回结果
5
- async function wrap(projectId, text) {
6
- const wrapped = await wrapResult(projectId, text);
7
- return { content: [{ type: 'text', text: wrapped }] };
5
+ function wrap(_projectId, text) {
6
+ return { content: [{ type: 'text', text }] };
8
7
  }
9
8
  export function registerTools(server, projectId, _user) {
10
9
  // 1. 创建节点
11
- server.tool('kg_create_node', '创建知识节点。type: logic=逻辑/函数, data=数据结构, intro=概念介绍', {
10
+ server.tool('kg_create_node', '创建知识节点。type: logic=逻辑/函数, data=数据结构, intro=概念介绍。⚠️ tags至少提供3个分类标签', {
12
11
  title: z.string().describe('节点标题'),
13
- type: z.enum(['logic', 'data', 'intro']).describe('节点类型'),
12
+ type: z.enum(['logic', 'data', 'intro']).describe('节点类型(logic/data/intro)'),
14
13
  description: z.string().describe('Markdown描述(用Mermaid流程图+表格,禁止纯文字)'),
15
14
  path: z.string().min(1).describe('目录路径(必填,如"/前端/组件")'),
16
- tags: z.array(z.string()).min(3).describe('分类标签(至少3个)'),
15
+ tags: z.array(z.string()).min(3).describe('⚠️ 分类标签(必填,至少3个)'),
17
16
  signature: z.string().optional().describe('唯一签名(用于依赖匹配,默认=title)'),
17
+ summary: z.string().optional().describe('一句话简介(显示在标题下方)'),
18
18
  dependencies: z.array(z.object({
19
19
  name: z.string().describe('目标节点的signature'),
20
20
  description: z.string().describe('依赖说明')
@@ -27,6 +27,7 @@ export function registerTools(server, projectId, _user) {
27
27
  type: decoded.type,
28
28
  status: 'incomplete',
29
29
  description: decoded.description || '',
30
+ summary: decoded.summary || '',
30
31
  // x, y 不传递,由 httpClient 智能计算位置
31
32
  locked: false,
32
33
  signature: decoded.signature || decoded.title,
@@ -43,16 +44,17 @@ export function registerTools(server, projectId, _user) {
43
44
  return wrap(projectId, success ? '删除成功' : '删除失败(节点不存在/已锁定/是根节点)');
44
45
  });
45
46
  // 3. 更新节点
46
- server.tool('kg_update_node', '更新节点内容(锁定节点不可更新)', {
47
+ server.tool('kg_update_node', '更新节点内容(锁定节点不可更新)。⚠️ 更新tags时至少提供3个分类标签', {
47
48
  nodeId: z.string().describe('节点ID'),
48
49
  title: z.string().optional().describe('新标题'),
49
50
  signature: z.string().optional().describe('新签名'),
50
51
  description: z.string().optional().describe('新描述(Markdown)'),
52
+ summary: z.string().optional().describe('一句话简介(显示在标题下方)'),
51
53
  path: z.string().optional().describe('目录路径(如"/前端/组件")'),
52
54
  x: z.number().optional().describe('X坐标'),
53
55
  y: z.number().optional().describe('Y坐标'),
54
56
  status: z.enum(['incomplete', 'complete', 'fixing', 'refactoring', 'deprecated']).optional().describe('状态'),
55
- tags: z.array(z.string()).min(3).optional().describe('分类标签(至少3个)'),
57
+ tags: z.array(z.string()).min(3).optional().describe('⚠️ 分类标签(更新时至少3个)'),
56
58
  dependencies: z.array(z.object({
57
59
  name: z.string(),
58
60
  description: z.string()
@@ -99,7 +101,7 @@ export function registerTools(server, projectId, _user) {
99
101
  }, async (args) => {
100
102
  const decoded = decodeObjectStrings(args);
101
103
  if (decoded.title === undefined && decoded.description === undefined) {
102
- return wrap(projectId, '❌ ���至少提供 title 或 description');
104
+ return wrap(projectId, '❌ 请至少提供 title 或 description');
103
105
  }
104
106
  const node = await storage.updateRoot(projectId, {
105
107
  title: decoded.title,
@@ -146,10 +148,15 @@ export function registerTools(server, projectId, _user) {
146
148
  }, async (args) => {
147
149
  const results = await storage.searchNodes(projectId, args.keywords, args.limit || 10);
148
150
  const output = results.map(r => ({
149
- id: r.node.id, title: r.node.title, type: r.node.type,
150
- status: r.node.status, hitRate: `${Math.round(r.score)}%`
151
+ id: r.node.id,
152
+ title: r.node.title,
153
+ tags: r.node.categories || [],
154
+ status: r.node.status,
155
+ summary: r.node.summary || '',
156
+ hitRate: `${Math.round(r.score)}%`
151
157
  }));
152
- return wrap(projectId, JSON.stringify(output, null, 2));
158
+ const json = JSON.stringify(output, null, 2);
159
+ return wrap(projectId, `${json}\n\n💡 需要详细内容请使用 kg_read_node(nodeId) 获取节点详情`);
153
160
  });
154
161
  // 6. 路径查找
155
162
  server.tool('kg_find_path', '查找两节点间的依赖路径', {
@@ -176,7 +183,7 @@ export function registerTools(server, projectId, _user) {
176
183
  ? { status: args.status, minEdges: args.minEdges, maxEdges: args.maxEdges }
177
184
  : undefined;
178
185
  const nodes = await storage.listNodes(projectId, filter);
179
- const output = nodes.map(n => ({ id: n.id, title: n.title, type: n.type, status: n.status, locked: n.locked }));
186
+ const output = nodes.map(n => ({ id: n.id, title: n.title, type: n.type, status: n.status, locked: n.locked, summary: n.summary || '' }));
180
187
  return wrap(projectId, JSON.stringify(output, null, 2));
181
188
  });
182
189
  // 8. 查询节点关系网 (支持多层)
@@ -216,8 +223,9 @@ export function registerTools(server, projectId, _user) {
216
223
  return wrap(projectId, JSON.stringify({ outgoing, incoming }, null, 2));
217
224
  });
218
225
  // 9. 读取单个节点详情
219
- server.tool('kg_read_node', '读取单个节点的完整内容(描述、关联文件、依赖、历史记录)', {
220
- nodeId: z.string().describe('节点ID')
226
+ server.tool('kg_read_node', '读取单个节点的完整内容(描述、关联文件、依赖、历史记录),可选包含上下游关系', {
227
+ nodeId: z.string().describe('节点ID'),
228
+ depth: z.number().min(0).max(3).optional().describe('关系查询层数(0=不含关系,1-3=含上下游关系,默认0)')
221
229
  }, async (args) => {
222
230
  const node = await storage.getNode(projectId, args.nodeId);
223
231
  if (!node) {
@@ -227,6 +235,9 @@ export function registerTools(server, projectId, _user) {
227
235
  const lines = [];
228
236
  // 基础信息
229
237
  lines.push(`## ${node.title}\n`);
238
+ if (node.summary) {
239
+ lines.push(`> ${node.summary}\n`);
240
+ }
230
241
  lines.push('**基础信息**');
231
242
  lines.push(`| 字段 | 值 |`);
232
243
  lines.push(`|:---|:---|`);
@@ -272,6 +283,44 @@ export function registerTools(server, projectId, _user) {
272
283
  node.bugfixes.forEach(b => lines.push(`| ${b.id} | ${b.date} | ${b.issue} | ${b.solution} |`));
273
284
  lines.push('');
274
285
  }
286
+ // 上下游关系 (depth > 0 时获取)
287
+ const depth = args.depth || 0;
288
+ if (depth > 0) {
289
+ const visited = new Set();
290
+ const outgoing = [];
291
+ const incoming = [];
292
+ async function fetchRelations(nodeId, currentDepth, direction) {
293
+ if (currentDepth > depth || visited.has(`${nodeId}-${direction}`))
294
+ return;
295
+ visited.add(`${nodeId}-${direction}`);
296
+ const relations = await storage.getRelations(projectId, nodeId);
297
+ for (const r of relations) {
298
+ if (r.direction === 'outgoing' && (direction === 'outgoing' || direction === 'both')) {
299
+ if (!outgoing.some(o => o.id === r.nodeId)) {
300
+ outgoing.push({ id: r.nodeId, title: r.title, edge: r.edgeType });
301
+ await fetchRelations(r.nodeId, currentDepth + 1, 'outgoing');
302
+ }
303
+ }
304
+ if (r.direction === 'incoming' && (direction === 'incoming' || direction === 'both')) {
305
+ if (!incoming.some(i => i.id === r.nodeId)) {
306
+ incoming.push({ id: r.nodeId, title: r.title, edge: r.edgeType });
307
+ await fetchRelations(r.nodeId, currentDepth + 1, 'incoming');
308
+ }
309
+ }
310
+ }
311
+ }
312
+ await fetchRelations(args.nodeId, 1, 'both');
313
+ if (outgoing.length > 0 || incoming.length > 0) {
314
+ lines.push('**上下游关系**');
315
+ if (outgoing.length > 0) {
316
+ lines.push(`- 依赖 (${outgoing.length}): ${outgoing.map(o => o.title).join(', ')}`);
317
+ }
318
+ if (incoming.length > 0) {
319
+ lines.push(`- 被依赖 (${incoming.length}): ${incoming.map(i => i.title).join(', ')}`);
320
+ }
321
+ lines.push('');
322
+ }
323
+ }
275
324
  return wrap(projectId, lines.join('\n'));
276
325
  });
277
326
  // 10. 获取知识库树状图
package/dist/utils.d.ts CHANGED
@@ -7,18 +7,6 @@ export declare const RULE_TYPE_LABELS: Record<RuleType, string>;
7
7
  * 获取指定类型的规则 (从独立文件读取)
8
8
  */
9
9
  export declare function getRules(projectId: string, ruleType?: RuleType): Promise<string>;
10
- /**
11
- * 获取根节点的用户风格 (兼容旧接口)
12
- */
13
- export declare function getRootStyle(projectId: string): Promise<string>;
14
- /**
15
- * 清除规则缓存 (保留接口兼容性,实际无需缓存)
16
- */
17
- export declare function clearStyleCache(): void;
18
- /**
19
- * 包装工具返回结果 (不再自动注入规则,使用 kg_get_rules 按需获取)
20
- */
21
- export declare function wrapResult(_projectId: string, result: string): Promise<string>;
22
10
  /**
23
11
  * 解码 Unicode 转义序列
24
12
  * 将 \uXXXX 格式的转义序列转换为实际字符
package/dist/utils.js CHANGED
@@ -39,24 +39,6 @@ export async function getRules(projectId, ruleType) {
39
39
  }
40
40
  return allRules.join('\n\n---\n\n');
41
41
  }
42
- /**
43
- * 获取根节点的用户风格 (兼容旧接口)
44
- */
45
- export async function getRootStyle(projectId) {
46
- return getRules(projectId, 'userStyles');
47
- }
48
- /**
49
- * 清除规则缓存 (保留接口兼容性,实际无需缓存)
50
- */
51
- export function clearStyleCache() {
52
- // 独立文件存储无需缓存
53
- }
54
- /**
55
- * 包装工具返回结果 (不再自动注入规则,使用 kg_get_rules 按需获取)
56
- */
57
- export async function wrapResult(_projectId, result) {
58
- return result;
59
- }
60
42
  /**
61
43
  * 解码 Unicode 转义序列
62
44
  * 将 \uXXXX 格式的转义序列转换为实际字符
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "2.6.21",
3
+ "version": "2.6.23",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",