@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 CHANGED
@@ -6,6 +6,7 @@ export interface PpdocsConfig {
6
6
  apiUrl: string;
7
7
  projectId: string;
8
8
  user: string;
9
+ agentId: string;
9
10
  source: 'env' | 'file';
10
11
  }
11
12
  export declare const PPDOCS_CONFIG_FILE = ".ppdocs";
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 { apiUrl, projectId: match?.[1] || 'unknown', user: process.env.PPDOCS_USER || generateUser(), source: 'env' };
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();
@@ -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 `${ctx.projectId}:${ctx.user}`;
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) {
@@ -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 lines = [`Doc search: "${rawQuery}" (${matches.length} results)`, ''];
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
- lines.push(`- **${node.label}** [${chart.id}/${node.id}] history=${docCount}`);
100
- if (latest)
101
- lines.push(` current: ${shortDate(latest.date)} — ${latest.summary}`);
102
- if (node.description)
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 lines = [`Documented nodes: ${nodesWithDocs.length}`, ''];
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 latestInfo = latest ? ` | ${shortDate(latest.date)} ${latest.summary}` : '';
121
- lines.push(`- ${node.label} [${chart.id}/${node.id}] history=${docCount}${latestInfo}`);
122
- }
123
- return wrap(lines.join('\n'));
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
- const chartId = decoded.chartId || 'main';
129
- const chart = (await getClient().getFlowchart(chartId));
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(`Flowchart not found: ${chartId}`);
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 ${chartId}`);
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} [${chartId}/${decoded.nodeId}]`);
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} [${chartId}/${node.id}]`, ''];
174
+ const lines = [`## ${node.label} [${foundChartId}/${node.id}]`, ''];
166
175
  if (node.description) {
167
176
  lines.push(node.description, '');
168
177
  }
@@ -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;
@@ -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
  // 📊 导航
@@ -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` +
@@ -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(``, `🔧 进行中的任务:`);
@@ -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";
@@ -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) {
@@ -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 list = tasks.map(t => `• [${t.status}] ${t.title}\n ID: ${t.id} | 更新: ${t.updated_at.split('T')[0]} | ${t.last_log || ''}`).join('\n');
101
- return wrap(`📋 ${tasks.length} 个任务:\n\n${list}`);
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
- return wrap(`📝 ${task.title}\nID: ${task.id} | 状态: ${task.status}`);
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}\n` +
170
- `\n⚠️ 提示: 每完成一个步骤请立即追加进度,确保时间线完整`);
191
+ `\n🎯 验收标准:\n${goalsDisplay}`);
171
192
  }
172
193
  case 'archive': {
173
194
  if (!decoded.taskId)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "3.5.0",
3
+ "version": "3.7.0",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",