@ppdocs/mcp 3.5.0 → 3.7.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/config.d.ts +1 -0
- package/dist/config.js +8 -2
- package/dist/index.js +2 -1
- package/dist/tools/discussion.js +3 -1
- package/dist/tools/doc_query.js +30 -21
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +2 -2
- package/dist/tools/init.js +1 -0
- package/dist/tools/kg_status.js +5 -1
- package/dist/tools/shared.d.ts +2 -1
- package/dist/tools/shared.js +2 -2
- package/dist/tools/tasks.js +26 -5
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -16,7 +16,13 @@ function readEnvConfig() {
|
|
|
16
16
|
if (!apiUrl)
|
|
17
17
|
return null;
|
|
18
18
|
const match = apiUrl.match(/\/api\/([^/]+)\/[^/]+\/?$/);
|
|
19
|
-
return {
|
|
19
|
+
return {
|
|
20
|
+
apiUrl,
|
|
21
|
+
projectId: match?.[1] || 'unknown',
|
|
22
|
+
user: process.env.PPDOCS_USER || generateUser(),
|
|
23
|
+
agentId: process.env.PPDOCS_AGENT_ID || 'default',
|
|
24
|
+
source: 'env',
|
|
25
|
+
};
|
|
20
26
|
}
|
|
21
27
|
function readPpdocsFile() {
|
|
22
28
|
const configPath = path.join(process.cwd(), '.ppdocs');
|
|
@@ -26,7 +32,7 @@ function readPpdocsFile() {
|
|
|
26
32
|
const c = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
27
33
|
if (!c.api || !c.projectId || !c.key)
|
|
28
34
|
return null;
|
|
29
|
-
return { apiUrl: `${c.api}/api/${c.projectId}/${c.key}`, projectId: c.projectId, user: c.user || generateUser(), source: 'file' };
|
|
35
|
+
return { apiUrl: `${c.api}/api/${c.projectId}/${c.key}`, projectId: c.projectId, user: c.user || generateUser(), agentId: c.agentId || process.env.PPDOCS_AGENT_ID || 'default', source: 'file' };
|
|
30
36
|
}
|
|
31
37
|
catch {
|
|
32
38
|
return null;
|
package/dist/index.js
CHANGED
|
@@ -39,8 +39,9 @@ async function main() {
|
|
|
39
39
|
}
|
|
40
40
|
const projectId = config?.projectId || 'pending';
|
|
41
41
|
const user = config?.user || 'agent';
|
|
42
|
+
const agentId = config?.agentId || process.env.PPDOCS_AGENT_ID || 'default';
|
|
42
43
|
const server = new McpServer({ name: `ppdocs [${projectId}]`, version: VERSION }, { capabilities: { tools: {} } });
|
|
43
|
-
registerTools(server, projectId, user, (newProjectId, newApiUrl) => {
|
|
44
|
+
registerTools(server, projectId, user, agentId, (newProjectId, newApiUrl) => {
|
|
44
45
|
console.error(`[kg_init] 项目已切换: ${newProjectId}`);
|
|
45
46
|
});
|
|
46
47
|
const transport = new StdioServerTransport();
|
package/dist/tools/discussion.js
CHANGED
|
@@ -15,7 +15,9 @@ import { getClient } from '../storage/httpClient.js';
|
|
|
15
15
|
import { decodeObjectStrings } from '../utils.js';
|
|
16
16
|
import { wrap, safeTool } from './shared.js';
|
|
17
17
|
function sender(ctx) {
|
|
18
|
-
return
|
|
18
|
+
return ctx.agentId && ctx.agentId !== 'default'
|
|
19
|
+
? `${ctx.projectId}:${ctx.user}:${ctx.agentId}`
|
|
20
|
+
: `${ctx.projectId}:${ctx.user}`;
|
|
19
21
|
}
|
|
20
22
|
/** 提取 sender 中的 projectId */
|
|
21
23
|
function senderProjectId(senderStr) {
|
package/dist/tools/doc_query.js
CHANGED
|
@@ -92,17 +92,13 @@ export function registerDocQueryTools(server, _ctx) {
|
|
|
92
92
|
.slice(0, decoded.limit ?? 10);
|
|
93
93
|
if (matches.length === 0)
|
|
94
94
|
return wrap(`No docs matched: ${rawQuery}`);
|
|
95
|
-
const
|
|
96
|
-
for (const { chart, node } of matches) {
|
|
95
|
+
const rows = matches.map(({ chart, node }) => {
|
|
97
96
|
const docCount = node.docEntries?.length ?? 0;
|
|
98
97
|
const latest = docCount > 0 ? node.docEntries[docCount - 1] : null;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
lines.push(` desc: ${node.description.slice(0, 80)}${node.description.length > 80 ? '...' : ''}`);
|
|
104
|
-
}
|
|
105
|
-
return wrap(lines.join('\n'));
|
|
98
|
+
const summary = latest ? latest.summary.substring(0, 50) : (node.description?.substring(0, 50) || '');
|
|
99
|
+
return `| ${node.label} | ${chart.id}/${node.id} | ${docCount} | ${latest ? shortDate(latest.date).slice(5) : '-'} | ${summary} |`;
|
|
100
|
+
}).join('\n');
|
|
101
|
+
return wrap(`Doc search: "${rawQuery}" (${matches.length} results)\n\n| 节点 | 图谱 | 文档数 | 日期 | 摘要 |\n|:-----|:-----|:-------|:-----|:-----|\n${rows}`);
|
|
106
102
|
}
|
|
107
103
|
case 'list': {
|
|
108
104
|
const charts = decoded.chartId
|
|
@@ -113,30 +109,43 @@ export function registerDocQueryTools(server, _ctx) {
|
|
|
113
109
|
.map((n) => ({ chart, node: n })));
|
|
114
110
|
if (nodesWithDocs.length === 0)
|
|
115
111
|
return wrap('No nodes with documentation found.');
|
|
116
|
-
const
|
|
117
|
-
for (const { chart, node } of nodesWithDocs) {
|
|
112
|
+
const rows = nodesWithDocs.map(({ chart, node }) => {
|
|
118
113
|
const docCount = node.docEntries?.length ?? 0;
|
|
119
114
|
const latest = docCount > 0 ? node.docEntries[docCount - 1] : null;
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
return wrap(
|
|
115
|
+
const summary = latest ? latest.summary.substring(0, 50) : '';
|
|
116
|
+
return `| ${node.label} | ${chart.id}/${node.id} | ${docCount} | ${latest ? shortDate(latest.date).slice(5) : '-'} | ${summary} |`;
|
|
117
|
+
}).join('\n');
|
|
118
|
+
return wrap(`Documented nodes: ${nodesWithDocs.length}\n\n| 节点 | 图谱 | 文档数 | 日期 | 摘要 |\n|:-----|:-----|:-------|:-----|:-----|\n${rows}`);
|
|
124
119
|
}
|
|
125
120
|
case 'read': {
|
|
126
121
|
if (!decoded.nodeId)
|
|
127
122
|
return wrap('read requires nodeId.');
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
let chart = null;
|
|
124
|
+
let foundChartId = decoded.chartId || '';
|
|
125
|
+
if (foundChartId) {
|
|
126
|
+
chart = (await getClient().getFlowchart(foundChartId));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// 不指定 chartId 时自动搜全部图
|
|
130
|
+
const all = await loadAllCharts();
|
|
131
|
+
for (const c of all) {
|
|
132
|
+
if (c.nodes.some(n => n.id === decoded.nodeId)) {
|
|
133
|
+
chart = c;
|
|
134
|
+
foundChartId = c.id;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
130
139
|
if (!chart)
|
|
131
|
-
return wrap(`
|
|
140
|
+
return wrap(`Node not found: ${decoded.nodeId}${decoded.chartId ? ` in ${decoded.chartId}` : ' (searched all charts)'}`);
|
|
132
141
|
const node = chart.nodes.find((n) => n.id === decoded.nodeId);
|
|
133
142
|
if (!node)
|
|
134
|
-
return wrap(`Node not found: ${decoded.nodeId} in ${
|
|
143
|
+
return wrap(`Node not found: ${decoded.nodeId} in ${foundChartId}`);
|
|
135
144
|
const entries = node.docEntries ?? [];
|
|
136
145
|
// history=true → 列出历史记录(标题+日期)
|
|
137
146
|
if (decoded.history) {
|
|
138
147
|
if (entries.length === 0)
|
|
139
|
-
return wrap(`No history for ${node.label} [${
|
|
148
|
+
return wrap(`No history for ${node.label} [${foundChartId}/${decoded.nodeId}]`);
|
|
140
149
|
const lines = [`## ${node.label} — history (${entries.length})`, ''];
|
|
141
150
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
142
151
|
const e = entries[i];
|
|
@@ -162,7 +171,7 @@ export function registerDocQueryTools(server, _ctx) {
|
|
|
162
171
|
return wrap(lines.join('\n'));
|
|
163
172
|
}
|
|
164
173
|
// 默认: 返回当前文档 (description + 最新 docEntry)
|
|
165
|
-
const lines = [`## ${node.label} [${
|
|
174
|
+
const lines = [`## ${node.label} [${foundChartId}/${node.id}]`, ''];
|
|
166
175
|
if (node.description) {
|
|
167
176
|
lines.push(node.description, '');
|
|
168
177
|
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -12,4 +12,4 @@
|
|
|
12
12
|
* 🏛️ 协作: kg_meeting (1个)
|
|
13
13
|
*/
|
|
14
14
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
15
|
-
export declare function registerTools(server: McpServer, projectId: string, user: string, onProjectChange?: (newProjectId: string, newApiUrl: string) => void): void;
|
|
15
|
+
export declare function registerTools(server: McpServer, projectId: string, user: string, agentId: string, onProjectChange?: (newProjectId: string, newApiUrl: string) => void): void;
|
package/dist/tools/index.js
CHANGED
|
@@ -24,8 +24,8 @@ import { registerAnalyzerTools } from './analyzer.js';
|
|
|
24
24
|
import { registerMeetingTools } from './meeting.js';
|
|
25
25
|
import { registerFlowchartTools } from './flowchart.js';
|
|
26
26
|
import { registerDocQueryTools } from './doc_query.js';
|
|
27
|
-
export function registerTools(server, projectId, user, onProjectChange) {
|
|
28
|
-
const ctx = createContext(projectId, user);
|
|
27
|
+
export function registerTools(server, projectId, user, agentId, onProjectChange) {
|
|
28
|
+
const ctx = createContext(projectId, user, agentId);
|
|
29
29
|
// 🔗 初始化
|
|
30
30
|
registerInitTool(server, ctx, onProjectChange || (() => { }));
|
|
31
31
|
// 📊 导航
|
package/dist/tools/init.js
CHANGED
|
@@ -60,6 +60,7 @@ export function registerInitTool(server, ctx, onProjectChange) {
|
|
|
60
60
|
// 4. 更新共享上下文 — 所有工具立刻获得新 projectId
|
|
61
61
|
ctx.projectId = config.projectId;
|
|
62
62
|
ctx.user = config.user;
|
|
63
|
+
ctx.agentId = process.env.PPDOCS_AGENT_ID || 'default';
|
|
63
64
|
// 5. 通知主进程
|
|
64
65
|
onProjectChange(config.projectId, config.apiUrl);
|
|
65
66
|
return wrap(`✅ 项目上下文已初始化\n\n` +
|
package/dist/tools/kg_status.js
CHANGED
|
@@ -6,16 +6,20 @@ import { wrap, safeTool } from './shared.js';
|
|
|
6
6
|
export function registerStatusTool(server, ctx) {
|
|
7
7
|
const client = () => getClient();
|
|
8
8
|
server.tool('kg_status', '📊 项目速览仪表盘 — 一键了解项目健康。返回: 流程图节点数、活跃任务数、最近变更。每次对话开始建议首先调用', {}, async () => safeTool(async () => {
|
|
9
|
-
const [charts, activeTasks] = await Promise.all([
|
|
9
|
+
const [charts, activeTasks, discussions] = await Promise.all([
|
|
10
10
|
client().listFlowcharts(),
|
|
11
11
|
client().listTasks('active'),
|
|
12
|
+
client().discussionList().catch(() => []),
|
|
12
13
|
]);
|
|
13
14
|
const nodeCount = charts.reduce((sum, c) => sum + (c.nodeCount || 0), 0);
|
|
15
|
+
const activeDiscussions = discussions.filter((d) => d.status === 'active');
|
|
14
16
|
const lines = [
|
|
15
17
|
`📊 项目速览 [${ctx.projectId}]`,
|
|
18
|
+
`🆔 身份: ${ctx.user}${ctx.agentId !== 'default' ? ':' + ctx.agentId : ''}`,
|
|
16
19
|
``,
|
|
17
20
|
`🔀 流程图: ${charts.length} 张 | 📦 节点: ${nodeCount} 个`,
|
|
18
21
|
`📝 活跃任务: ${activeTasks.length} 个`,
|
|
22
|
+
`📨 活跃讨论: ${activeDiscussions.length} 个`,
|
|
19
23
|
];
|
|
20
24
|
if (activeTasks.length > 0) {
|
|
21
25
|
lines.push(``, `🔧 进行中的任务:`);
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
export interface McpContext {
|
|
7
7
|
projectId: string;
|
|
8
8
|
user: string;
|
|
9
|
+
agentId: string;
|
|
9
10
|
}
|
|
10
11
|
/** 创建可变上下文对象 (工具闭包捕获此对象的引用,kg_init 更新其属性) */
|
|
11
|
-
export declare function createContext(projectId: string, user: string): McpContext;
|
|
12
|
+
export declare function createContext(projectId: string, user: string, agentId?: string): McpContext;
|
|
12
13
|
export declare function wrap(text: string): {
|
|
13
14
|
content: {
|
|
14
15
|
type: "text";
|
package/dist/tools/shared.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* 合并原 helpers.ts + 新增 safeTool / withCross 模式
|
|
4
4
|
*/
|
|
5
5
|
/** 创建可变上下文对象 (工具闭包捕获此对象的引用,kg_init 更新其属性) */
|
|
6
|
-
export function createContext(projectId, user) {
|
|
7
|
-
return { projectId, user };
|
|
6
|
+
export function createContext(projectId, user, agentId = 'default') {
|
|
7
|
+
return { projectId, user, agentId };
|
|
8
8
|
}
|
|
9
9
|
// ==================== MCP 返回包装 ====================
|
|
10
10
|
export function wrap(text) {
|
package/dist/tools/tasks.js
CHANGED
|
@@ -97,8 +97,13 @@ export function registerTaskTools(server, ctx) {
|
|
|
97
97
|
}
|
|
98
98
|
if (tasks.length === 0)
|
|
99
99
|
return wrap(`📋 ${status === 'archived' ? '归档' : '活跃'}任务为空`);
|
|
100
|
-
const
|
|
101
|
-
|
|
100
|
+
const rows = tasks.map(t => {
|
|
101
|
+
const icon = t.status === 'active' ? '🟢' : '⚪';
|
|
102
|
+
const date = t.updated_at?.split('T')[0]?.slice(5) || '';
|
|
103
|
+
const log = (t.last_log || '').substring(0, 40);
|
|
104
|
+
return `| ${icon} | ${t.title} | ${t.id} | ${date} | ${log} |`;
|
|
105
|
+
}).join('\n');
|
|
106
|
+
return wrap(`📋 共 ${tasks.length} 个任务\n\n| 状态 | 标题 | ID | 更新 | 最近日志 |\n|:-----|:-----|:---|:-----|:---------|\n${rows}`);
|
|
102
107
|
}
|
|
103
108
|
// 有 taskId = 获取详情 (结构化摘要)
|
|
104
109
|
if (decoded.taskId) {
|
|
@@ -145,7 +150,24 @@ export function registerTaskTools(server, ctx) {
|
|
|
145
150
|
const task = await client().getTask(matched[0].id, 'smart');
|
|
146
151
|
if (!task)
|
|
147
152
|
return wrap('任务详情获取失败');
|
|
148
|
-
|
|
153
|
+
const lines = [
|
|
154
|
+
`📝 ${task.title}`,
|
|
155
|
+
`ID: ${task.id} | 状态: ${task.status}`,
|
|
156
|
+
`创建: ${task.created_at?.split('T')[0] || ''}`,
|
|
157
|
+
];
|
|
158
|
+
if (task.detail?.description)
|
|
159
|
+
lines.push(`\n📋 ${task.detail.description}`);
|
|
160
|
+
if (task.detail?.goals?.length) {
|
|
161
|
+
lines.push(`\n🎯 验收标准:`);
|
|
162
|
+
task.detail.goals.forEach((g, i) => lines.push(` ${i + 1}. ☐ ${g}`));
|
|
163
|
+
}
|
|
164
|
+
if (task.logs?.length) {
|
|
165
|
+
lines.push(`\n📊 进度日志 (${task.logs.length}条, 最近3条):`);
|
|
166
|
+
for (const log of task.logs.slice(-3)) {
|
|
167
|
+
lines.push(` [${log.log_type}] ${log.time?.split('T')[0] || ''}: ${log.content?.substring(0, 120)}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return wrap(lines.join('\n'));
|
|
149
171
|
}
|
|
150
172
|
const list = matched.map(t => `• [${t.status}] ${t.title}\n ID: ${t.id}`).join('\n');
|
|
151
173
|
return wrap(`找到 ${matched.length} 个匹配任务:\n\n${list}\n\n请使用 taskId 获取详情`);
|
|
@@ -166,8 +188,7 @@ export function registerTaskTools(server, ctx) {
|
|
|
166
188
|
: ' (无验收标准)';
|
|
167
189
|
return wrap(`✅ 进度已记录 [${lastLog?.log_type || 'progress'}]\n` +
|
|
168
190
|
`📋 ${task.title} | 日志: ${task.logs.length}条\n` +
|
|
169
|
-
`\n🎯 验收标准:\n${goalsDisplay}
|
|
170
|
-
`\n⚠️ 提示: 每完成一个步骤请立即追加进度,确保时间线完整`);
|
|
191
|
+
`\n🎯 验收标准:\n${goalsDisplay}`);
|
|
171
192
|
}
|
|
172
193
|
case 'archive': {
|
|
173
194
|
if (!decoded.taskId)
|