@ppdocs/mcp 2.6.22 → 2.6.24
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 +1 -5
- package/dist/config.d.ts +2 -0
- package/dist/config.js +1 -1
- package/dist/index.js +9 -2
- package/dist/storage/types.d.ts +1 -0
- package/dist/tools/index.js +79 -18
- package/dist/utils.d.ts +0 -12
- package/dist/utils.js +0 -18
- package/package.json +1 -1
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++) {
|
package/dist/config.d.ts
CHANGED
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:
|
|
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
|
|
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);
|
package/dist/storage/types.d.ts
CHANGED
package/dist/tools/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import * as storage from '../storage/httpClient.js';
|
|
3
|
-
import { decodeUnicodeEscapes, decodeObjectStrings,
|
|
3
|
+
import { decodeUnicodeEscapes, decodeObjectStrings, getRules, RULE_TYPE_LABELS } from '../utils.js';
|
|
4
4
|
// 辅助函数: 包装返回结果
|
|
5
|
-
|
|
6
|
-
|
|
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('分类标签(
|
|
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('分类标签(
|
|
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, '❌
|
|
104
|
+
return wrap(projectId, '❌ 请至少提供 title 或 description');
|
|
103
105
|
}
|
|
104
106
|
const node = await storage.updateRoot(projectId, {
|
|
105
107
|
title: decoded.title,
|
|
@@ -140,16 +142,33 @@ export function registerTools(server, projectId, _user) {
|
|
|
140
142
|
return wrap(projectId, node ? JSON.stringify(node, null, 2) : '操作失败');
|
|
141
143
|
});
|
|
142
144
|
// 5. 搜索节点
|
|
143
|
-
server.tool('kg_search', '
|
|
144
|
-
keywords: z.array(z.string()).describe('关键词列表(OR逻辑)'),
|
|
145
|
+
server.tool('kg_search', '关键词搜索节点,按命中率排序返回。空白关键词[""]返回全部节点', {
|
|
146
|
+
keywords: z.array(z.string()).describe('关键词列表(OR逻辑),空白[""]返回全部'),
|
|
145
147
|
limit: z.number().optional().describe('返回数量(默认10)')
|
|
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,
|
|
150
|
-
|
|
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
|
-
|
|
158
|
+
const json = JSON.stringify(output, null, 2);
|
|
159
|
+
return wrap(projectId, `${json}\n\n💡 需要详细内容请使用 kg_read_node(nodeId) 获取节点详情`);
|
|
160
|
+
});
|
|
161
|
+
// 5.5 列出所有标签
|
|
162
|
+
server.tool('kg_list_tags', '获取所有节点使用过的标签列表(去重)', {}, async () => {
|
|
163
|
+
const nodes = await storage.listNodes(projectId);
|
|
164
|
+
const tagSet = new Set();
|
|
165
|
+
for (const node of nodes) {
|
|
166
|
+
if (node.categories && Array.isArray(node.categories)) {
|
|
167
|
+
node.categories.forEach(cat => tagSet.add(cat));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const tags = Array.from(tagSet).sort();
|
|
171
|
+
return wrap(projectId, JSON.stringify({ total: tags.length, tags }, null, 2));
|
|
153
172
|
});
|
|
154
173
|
// 6. 路径查找
|
|
155
174
|
server.tool('kg_find_path', '查找两节点间的依赖路径', {
|
|
@@ -176,7 +195,7 @@ export function registerTools(server, projectId, _user) {
|
|
|
176
195
|
? { status: args.status, minEdges: args.minEdges, maxEdges: args.maxEdges }
|
|
177
196
|
: undefined;
|
|
178
197
|
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 }));
|
|
198
|
+
const output = nodes.map(n => ({ id: n.id, title: n.title, type: n.type, status: n.status, locked: n.locked, summary: n.summary || '' }));
|
|
180
199
|
return wrap(projectId, JSON.stringify(output, null, 2));
|
|
181
200
|
});
|
|
182
201
|
// 8. 查询节点关系网 (支持多层)
|
|
@@ -216,8 +235,9 @@ export function registerTools(server, projectId, _user) {
|
|
|
216
235
|
return wrap(projectId, JSON.stringify({ outgoing, incoming }, null, 2));
|
|
217
236
|
});
|
|
218
237
|
// 9. 读取单个节点详情
|
|
219
|
-
server.tool('kg_read_node', '读取单个节点的完整内容(描述、关联文件、依赖、历史记录)', {
|
|
220
|
-
nodeId: z.string().describe('节点ID')
|
|
238
|
+
server.tool('kg_read_node', '读取单个节点的完整内容(描述、关联文件、依赖、历史记录),可选包含上下游关系', {
|
|
239
|
+
nodeId: z.string().describe('节点ID'),
|
|
240
|
+
depth: z.number().min(0).max(3).optional().describe('关系查询层数(0=不含关系,1-3=含上下游关系,默认0)')
|
|
221
241
|
}, async (args) => {
|
|
222
242
|
const node = await storage.getNode(projectId, args.nodeId);
|
|
223
243
|
if (!node) {
|
|
@@ -227,6 +247,9 @@ export function registerTools(server, projectId, _user) {
|
|
|
227
247
|
const lines = [];
|
|
228
248
|
// 基础信息
|
|
229
249
|
lines.push(`## ${node.title}\n`);
|
|
250
|
+
if (node.summary) {
|
|
251
|
+
lines.push(`> ${node.summary}\n`);
|
|
252
|
+
}
|
|
230
253
|
lines.push('**基础信息**');
|
|
231
254
|
lines.push(`| 字段 | 值 |`);
|
|
232
255
|
lines.push(`|:---|:---|`);
|
|
@@ -272,6 +295,44 @@ export function registerTools(server, projectId, _user) {
|
|
|
272
295
|
node.bugfixes.forEach(b => lines.push(`| ${b.id} | ${b.date} | ${b.issue} | ${b.solution} |`));
|
|
273
296
|
lines.push('');
|
|
274
297
|
}
|
|
298
|
+
// 上下游关系 (depth > 0 时获取)
|
|
299
|
+
const depth = args.depth || 0;
|
|
300
|
+
if (depth > 0) {
|
|
301
|
+
const visited = new Set();
|
|
302
|
+
const outgoing = [];
|
|
303
|
+
const incoming = [];
|
|
304
|
+
async function fetchRelations(nodeId, currentDepth, direction) {
|
|
305
|
+
if (currentDepth > depth || visited.has(`${nodeId}-${direction}`))
|
|
306
|
+
return;
|
|
307
|
+
visited.add(`${nodeId}-${direction}`);
|
|
308
|
+
const relations = await storage.getRelations(projectId, nodeId);
|
|
309
|
+
for (const r of relations) {
|
|
310
|
+
if (r.direction === 'outgoing' && (direction === 'outgoing' || direction === 'both')) {
|
|
311
|
+
if (!outgoing.some(o => o.id === r.nodeId)) {
|
|
312
|
+
outgoing.push({ id: r.nodeId, title: r.title, edge: r.edgeType });
|
|
313
|
+
await fetchRelations(r.nodeId, currentDepth + 1, 'outgoing');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (r.direction === 'incoming' && (direction === 'incoming' || direction === 'both')) {
|
|
317
|
+
if (!incoming.some(i => i.id === r.nodeId)) {
|
|
318
|
+
incoming.push({ id: r.nodeId, title: r.title, edge: r.edgeType });
|
|
319
|
+
await fetchRelations(r.nodeId, currentDepth + 1, 'incoming');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
await fetchRelations(args.nodeId, 1, 'both');
|
|
325
|
+
if (outgoing.length > 0 || incoming.length > 0) {
|
|
326
|
+
lines.push('**上下游关系**');
|
|
327
|
+
if (outgoing.length > 0) {
|
|
328
|
+
lines.push(`- 依赖 (${outgoing.length}): ${outgoing.map(o => o.title).join(', ')}`);
|
|
329
|
+
}
|
|
330
|
+
if (incoming.length > 0) {
|
|
331
|
+
lines.push(`- 被依赖 (${incoming.length}): ${incoming.map(i => i.title).join(', ')}`);
|
|
332
|
+
}
|
|
333
|
+
lines.push('');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
275
336
|
return wrap(projectId, lines.join('\n'));
|
|
276
337
|
});
|
|
277
338
|
// 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 格式的转义序列转换为实际字符
|