@ppdocs/mcp 3.12.0 → 3.13.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.
@@ -1,6 +1,5 @@
1
1
  /**
2
- * 📝 kg_task (4→1) 极简任务管理
3
- * AI 只需说人话写内容,后端负责所有格式化、分类、时间戳
2
+ * kg_task — 任务管理
4
3
  */
5
4
  import { z } from 'zod';
6
5
  import { getClient } from '../storage/httpClient.js';
@@ -8,8 +7,7 @@ import { decodeObjectStrings } from '../utils.js';
8
7
  import { wrap, safeTool } from './shared.js';
9
8
  export function registerTaskTools(server, ctx) {
10
9
  const client = () => getClient();
11
- server.tool('kg_task', '📝 任务记录 — action: create(创建,建议bindTo关联节点)|get(查询)|update(追加进度)|archive(归档)|delete(删除)。⚠️ 每完成一个步骤必须立即update!\n' +
12
- '★ 涉及逻辑变更的任务,创建后必须先执行 design-first 工作流(kg_workflow(id:"design-first")),在流程图中完成设计并验证后才能编码。', {
10
+ server.tool('kg_task', '任务记录 — action: create(创建)|get(查询)|update(追加进度)|archive(归档)|delete(删除)', {
13
11
  action: z.enum(['create', 'get', 'update', 'archive', 'delete'])
14
12
  .describe('操作类型'),
15
13
  title: z.string().optional()
@@ -27,9 +25,9 @@ export function registerTaskTools(server, ctx) {
27
25
  summary: z.string().optional()
28
26
  .describe('经验总结Markdown (archive)'),
29
27
  difficulties: z.array(z.string()).optional()
30
- .describe('遇到的困难 (archive)'),
28
+ .describe('遇到的困难 (archive, 可选)'),
31
29
  solutions: z.array(z.string()).optional()
32
- .describe('解决方案 (archive)'),
30
+ .describe('解决方案 (archive, 可选)'),
33
31
  status: z.enum(['active', 'archived', 'all']).optional()
34
32
  .describe('状态筛选 (get, 默认active)'),
35
33
  bindTo: z.string().optional()
@@ -41,12 +39,10 @@ export function registerTaskTools(server, ctx) {
41
39
  switch (decoded.action) {
42
40
  case 'create': {
43
41
  if (!decoded.title)
44
- return wrap('create 需要 title');
45
- if (!decoded.description)
46
- return wrap('❌ create 需要 description');
42
+ return wrap('create 需要 title');
47
43
  const task = await client().createTask({
48
44
  title: decoded.title,
49
- description: decoded.description,
45
+ description: decoded.description || '',
50
46
  goals: decoded.goals || []
51
47
  }, ctx.user);
52
48
  // 自动绑定到流程图节点
@@ -64,22 +60,18 @@ export function registerTaskTools(server, ctx) {
64
60
  node.boundTasks.push(task.id);
65
61
  }
66
62
  await client().saveFlowchart(chartId, chart);
67
- bindMsg = `\n🔗 已绑定到节点: ${decoded.bindTo} [${chartId}]`;
63
+ bindMsg = `\n已绑定到节点: ${decoded.bindTo} [${chartId}]`;
68
64
  }
69
65
  else {
70
- bindMsg = `\n⚠️ 节点 "${decoded.bindTo}" 不存在于流程图 "${chartId}"`;
66
+ bindMsg = `\n节点 "${decoded.bindTo}" 不存在于流程图 "${chartId}"`;
71
67
  }
72
68
  }
73
69
  }
74
70
  catch {
75
- bindMsg = `\n⚠️ 绑定失败`;
71
+ bindMsg = `\n绑定失败`;
76
72
  }
77
73
  }
78
- // 约束: 未绑定流程图 强制警告
79
- if (!decoded.bindTo) {
80
- bindMsg = `\n\n⚠️ 【未关联知识锚点】此任务未绑定到任何流程图节点!\n💡 建议: 使用 kg_flowchart(get) 查看节点列表, 然后用 kg_flowchart(bind, nodeId, tasks:["${task.id}"]) 绑定\n📌 不关联的任务会成为孤岛数据, 无法被其他AI发现`;
81
- }
82
- return wrap(`✅ 任务已创建\nID: ${task.id}\n标题: ${task.title}\n验收标准: ${task.detail.goals.length}条${bindMsg}`);
74
+ return wrap(`任务已创建\nID: ${task.id}\n标题: ${task.title}\n验收标准: ${task.detail.goals.length}条${bindMsg}`);
83
75
  }
84
76
  case 'get': {
85
77
  // 无参数 = 列出活跃任务
@@ -97,33 +89,33 @@ export function registerTaskTools(server, ctx) {
97
89
  tasks = await client().listTasks(status);
98
90
  }
99
91
  if (tasks.length === 0)
100
- return wrap(`📋 ${status === 'archived' ? '归档' : '活跃'}任务为空`);
92
+ return wrap(`${status === 'archived' ? '归档' : '活跃'}任务为空`);
101
93
  const rows = tasks.map(t => {
102
- const icon = t.status === 'active' ? '🟢' : '';
94
+ const icon = t.status === 'active' ? '' : '';
103
95
  const date = t.updated_at?.split('T')[0]?.slice(5) || '';
104
96
  const log = (t.last_log || '').substring(0, 40);
105
97
  return `| ${icon} | ${t.title} | ${t.id} | ${date} | ${log} |`;
106
98
  }).join('\n');
107
- return wrap(`📋 ${tasks.length} 个任务\n\n| 状态 | 标题 | ID | 更新 | 最近日志 |\n|:-----|:-----|:---|:-----|:---------|\n${rows}`);
99
+ return wrap(`共 ${tasks.length} 个任务\n\n| 状态 | 标题 | ID | 更新 | 最近日志 |\n|:-----|:-----|:---|:-----|:---------|\n${rows}`);
108
100
  }
109
101
  // 有 taskId = 获取详情 (结构化摘要)
110
102
  if (decoded.taskId) {
111
103
  const task = await client().getTask(decoded.taskId, 'smart');
112
104
  if (!task)
113
- return wrap('任务未找到');
105
+ return wrap('任务未找到');
114
106
  const lines = [
115
- `📝 ${task.title}`,
107
+ `${task.title}`,
116
108
  `ID: ${task.id} | 状态: ${task.status}`,
117
109
  `创建: ${task.created_at?.split('T')[0] || ''}`,
118
110
  ];
119
111
  if (task.detail?.description)
120
- lines.push(`\n📋 ${task.detail.description}`);
112
+ lines.push(`\n${task.detail.description}`);
121
113
  if (task.detail?.goals?.length) {
122
- lines.push(`\n🎯 验收标准:`);
114
+ lines.push(`\n验收标准:`);
123
115
  task.detail.goals.forEach((g, i) => lines.push(` ${i + 1}. ☐ ${g}`));
124
116
  }
125
117
  if (task.logs?.length) {
126
- lines.push(`\n📊 进度日志 (${task.logs.length}条, 最近3条):`);
118
+ lines.push(`\n进度日志 (${task.logs.length}条, 最近3条):`);
127
119
  for (const log of task.logs.slice(-3)) {
128
120
  lines.push(` [${log.log_type}] ${log.time?.split('T')[0] || ''}: ${log.content?.substring(0, 120)}`);
129
121
  }
@@ -152,18 +144,18 @@ export function registerTaskTools(server, ctx) {
152
144
  if (!task)
153
145
  return wrap('任务详情获取失败');
154
146
  const lines = [
155
- `📝 ${task.title}`,
147
+ `${task.title}`,
156
148
  `ID: ${task.id} | 状态: ${task.status}`,
157
149
  `创建: ${task.created_at?.split('T')[0] || ''}`,
158
150
  ];
159
151
  if (task.detail?.description)
160
- lines.push(`\n📋 ${task.detail.description}`);
152
+ lines.push(`\n${task.detail.description}`);
161
153
  if (task.detail?.goals?.length) {
162
- lines.push(`\n🎯 验收标准:`);
154
+ lines.push(`\n验收标准:`);
163
155
  task.detail.goals.forEach((g, i) => lines.push(` ${i + 1}. ☐ ${g}`));
164
156
  }
165
157
  if (task.logs?.length) {
166
- lines.push(`\n📊 进度日志 (${task.logs.length}条, 最近3条):`);
158
+ lines.push(`\n进度日志 (${task.logs.length}条, 最近3条):`);
167
159
  for (const log of task.logs.slice(-3)) {
168
160
  lines.push(` [${log.log_type}] ${log.time?.split('T')[0] || ''}: ${log.content?.substring(0, 120)}`);
169
161
  }
@@ -175,45 +167,38 @@ export function registerTaskTools(server, ctx) {
175
167
  }
176
168
  case 'update': {
177
169
  if (!decoded.taskId)
178
- return wrap('update 需要 taskId');
170
+ return wrap('update 需要 taskId');
179
171
  if (!decoded.content)
180
- return wrap('update 需要 content');
172
+ return wrap('update 需要 content');
181
173
  // log_type 可选 — 不传则后端自动检测关键词
182
174
  const task = await client().addTaskLog(decoded.taskId, decoded.content, decoded.log_type);
183
175
  if (!task)
184
176
  return wrap('更新失败(任务不存在或已归档)');
185
177
  const lastLog = task.logs[task.logs.length - 1];
186
- // 返回任务摘要 + 验收清单,让 AI 始终知道剩余目标
187
- const goalsDisplay = task.detail.goals.length > 0
188
- ? task.detail.goals.map((g, i) => ` ${i + 1}. ☐ ${g}`).join('\n')
189
- : ' (无验收标准)';
190
- return wrap(`✅ 进度已记录 [${lastLog?.log_type || 'progress'}]\n` +
191
- `📋 ${task.title} | 日志: ${task.logs.length}条\n` +
192
- `\n🎯 验收标准:\n${goalsDisplay}`);
178
+ return wrap(`进度已记录 [${lastLog?.log_type || 'progress'}]\n` +
179
+ `${task.title} | 日志: ${task.logs.length}条`);
193
180
  }
194
181
  case 'archive': {
195
182
  if (!decoded.taskId)
196
- return wrap('archive 需要 taskId');
197
- if (!decoded.summary)
198
- return wrap('❌ archive 需要 summary');
183
+ return wrap('archive 需要 taskId');
199
184
  const task = await client().completeTask(decoded.taskId, {
200
- summary: decoded.summary,
185
+ summary: decoded.summary || '任务已完成',
201
186
  difficulties: decoded.difficulties || [],
202
187
  solutions: decoded.solutions || [],
203
188
  references: []
204
189
  });
205
190
  if (!task)
206
191
  return wrap('归档失败(任务不存在或已归档)');
207
- return wrap(`✅ 任务已归档: ${task.title}`);
192
+ return wrap(`任务已归档: ${task.title}`);
208
193
  }
209
194
  case 'delete': {
210
195
  if (!decoded.taskId)
211
- return wrap('delete 需要 taskId');
196
+ return wrap('delete 需要 taskId');
212
197
  const ok = await client().deleteTask(decoded.taskId);
213
- return wrap(ok ? `✅ 任务已删除 (ID: ${decoded.taskId})` : '删除失败(任务不存在)');
198
+ return wrap(ok ? `任务已删除 (ID: ${decoded.taskId})` : '删除失败(任务不存在)');
214
199
  }
215
200
  default:
216
- return wrap(`❌ 未知 action: ${decoded.action}`);
201
+ return wrap(`未知 action: ${decoded.action}`);
217
202
  }
218
203
  }));
219
204
  }
@@ -4,9 +4,8 @@ import { decodeObjectStrings } from '../utils.js';
4
4
  import { safeTool, wrap } from './shared.js';
5
5
  export function registerWorkflowTools(server, _ctx) {
6
6
  const client = () => getClient();
7
- server.tool('kg_workflow', '📘 文档工作流 — AI 的标准操作手册。每个工作流是一份 Markdown 文档,定义了特定场景下 AI 应该遵循的步骤和规范。\n' +
8
- '调用方式:空参数=列出所有工作流;传 id=获取工作流正文;save/delete 需显式 action。\n' +
9
- '典型用法:拿到任务后先 kg_workflow() 看有无相关工作流,有则 kg_workflow(id:"xxx") 读取并遵循执行。', {
7
+ server.tool('kg_workflow', '文档工作流。AI 的标准操作手册,每个工作流定义特定场景下应遵循的步骤。空参数=列出全部;传id=获取正文;save/delete需显式action。' +
8
+ 'actions: list|get|save|delete', {
10
9
  action: z.enum(['list', 'get', 'save', 'delete']).optional().describe('省略时自动推断:无参数=list,有id=get'),
11
10
  id: z.string().optional().describe('工作流 ID'),
12
11
  scope: z.enum(['all', 'global', 'project']).optional().describe('范围:all(默认)|global|project'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "3.12.0",
3
+ "version": "3.13.0",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,15 +0,0 @@
1
- /**
2
- * 💬 kg_discuss (8 actions)
3
- * 合并: list, read, create, reply, close, delete, complete, history
4
- * 统一走 HTTP → Rust 后端 (单一写入者)
5
- * sender 格式: "projectId:user" (如 "p-ca3sgejg:张三")
6
- *
7
- * 权限模型:
8
- * list/history — 任何人可列出(全局可见)
9
- * create — 任何人可发起(指定参与项目ID)
10
- * read/reply — 仅讨论组内成员(initiator + participants)
11
- * complete/close/delete — 仅发起人
12
- */
13
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
14
- import { type McpContext } from './shared.js';
15
- export declare function registerDiscussionTools(server: McpServer, ctx: McpContext): void;
@@ -1,264 +0,0 @@
1
- /**
2
- * 💬 kg_discuss (8 actions)
3
- * 合并: list, read, create, reply, close, delete, complete, history
4
- * 统一走 HTTP → Rust 后端 (单一写入者)
5
- * sender 格式: "projectId:user" (如 "p-ca3sgejg:张三")
6
- *
7
- * 权限模型:
8
- * list/history — 任何人可列出(全局可见)
9
- * create — 任何人可发起(指定参与项目ID)
10
- * read/reply — 仅讨论组内成员(initiator + participants)
11
- * complete/close/delete — 仅发起人
12
- */
13
- import { z } from 'zod';
14
- import { getClient } from '../storage/httpClient.js';
15
- import { decodeObjectStrings } from '../utils.js';
16
- import { wrap, safeTool } from './shared.js';
17
- function sender(ctx) {
18
- return `${ctx.projectId}:${ctx.user}:${ctx.agentId}`;
19
- }
20
- /** 提取 sender 中的 projectId */
21
- function senderProjectId(senderStr) {
22
- return senderStr.split(':')[0] || senderStr;
23
- }
24
- /** 检查项目是否是讨论的成员 (发起方 或 参与方) */
25
- function isMember(brief, projectId) {
26
- const initiatorPid = senderProjectId(brief.initiator);
27
- if (initiatorPid === projectId)
28
- return true;
29
- return brief.participants.some(p => {
30
- // participants 可能是 "projectId" 或 "projectId:user"
31
- return senderProjectId(p) === projectId;
32
- });
33
- }
34
- /** 检查是否是讨论的发起人 */
35
- function isInitiator(brief, me, projectId) {
36
- return brief.initiator === me || senderProjectId(brief.initiator) === projectId;
37
- }
38
- /** 根据 ID 获取讨论摘要 (用于权限检查) */
39
- async function getBrief(discussionId) {
40
- try {
41
- const all = await getClient().discussionList();
42
- return all.find(d => d.id === discussionId) || null;
43
- }
44
- catch {
45
- return null;
46
- }
47
- }
48
- function relativeTime(iso) {
49
- try {
50
- const diff = Date.now() - new Date(iso).getTime();
51
- if (diff < 60000)
52
- return '刚刚';
53
- if (diff < 3600000)
54
- return `${Math.floor(diff / 60000)}分钟前`;
55
- if (diff < 86400000)
56
- return `${Math.floor(diff / 3600000)}小时前`;
57
- return `${Math.floor(diff / 86400000)}天前`;
58
- }
59
- catch {
60
- return iso;
61
- }
62
- }
63
- function formatList(items, ctx) {
64
- const lines = [
65
- `本项目: ${sender(ctx)}`,
66
- ``,
67
- `📋 活跃讨论 (${items.length}个)`,
68
- ``,
69
- `| ID | 标题 | 发起方 | 参与方 | 摘要 | 回复 | 更新 |`,
70
- `|:---|:---|:---|:---|:---|:---:|:---|`,
71
- ];
72
- for (const d of items) {
73
- const others = d.participants.filter(p => p !== d.initiator).join(', ') || '—';
74
- const memberTag = isMember(d, ctx.projectId) ? '✅' : '🔒';
75
- lines.push(`| ${d.id} | ${memberTag} ${d.title} | ${d.initiator} | ${others} | ${d.summary} | ${d.status} | ${relativeTime(d.updatedAt)} |`);
76
- }
77
- lines.push('');
78
- lines.push('✅ = 你是成员(可读写) | 🔒 = 非成员(仅可查看标题)');
79
- return lines.join('\n');
80
- }
81
- function formatDetailView(d) {
82
- const msgs = d.messages || [];
83
- const lines = [
84
- `💬 ${d.title}`,
85
- `发起: ${d.initiator} | 参与: ${d.participants.length}方 | 状态: ${d.status}`,
86
- `📌 ${d.summary}`,
87
- `📊 总消息: ${d.totalCount} | 未读: ${d.unreadCount}`,
88
- ``,
89
- ];
90
- msgs.forEach((m, i) => {
91
- const readTag = m.isRead ? '📖' : '🆕';
92
- lines.push(`--- ${readTag} 消息 ${i + 1}/${msgs.length} [${m.sender}] ${relativeTime(m.timestamp)} ---`);
93
- // 已读且无 content → 显示摘要
94
- if (m.isRead && !m.content) {
95
- lines.push(`[摘要] ${m.summary}`);
96
- }
97
- else {
98
- lines.push(m.content || m.summary || '(无内容)');
99
- }
100
- lines.push('');
101
- });
102
- return lines.join('\n');
103
- }
104
- export function registerDiscussionTools(server, ctx) {
105
- const client = () => getClient();
106
- server.tool('kg_discuss', '💬 跨项目讨论 — action 可选: 省略时无 id/ids 则列出讨论,有 id/ids 则读取详情。亦可显式指定 list|read|create|reply|complete|close|delete|history。权限: 仅成员可读写,仅发起人可删除/归档', {
107
- action: z.enum(['list', 'read', 'create', 'reply', 'complete', 'close', 'delete', 'history']).optional()
108
- .describe('操作类型(可选:省略时根据 id/ids 自动为 read,否则为 list)'),
109
- id: z.string().optional()
110
- .describe('讨论哈希ID (read/reply/complete/close/delete)'),
111
- ids: z.array(z.string()).optional()
112
- .describe('批量读取的讨论ID数组 (read)'),
113
- title: z.string().optional()
114
- .describe('讨论标题 (create)'),
115
- participants: z.array(z.string()).optional()
116
- .describe('参与项目ID数组 (create, 不含自己)'),
117
- summary: z.string().optional()
118
- .describe('消息摘要 (create/reply, 不传则自动截取content前50字)'),
119
- content: z.string().optional()
120
- .describe('消息详细内容Markdown (create/reply)'),
121
- conclusion: z.string().optional()
122
- .describe('结案总结 (close)'),
123
- newSummary: z.string().optional()
124
- .describe('更新讨论进展摘要 (reply)'),
125
- mode: z.enum(['auto', 'full']).optional()
126
- .describe('读取模式 (read, 默认auto: 已读消息仅返回摘要)'),
127
- }, async (args) => safeTool(async () => {
128
- const decoded = decodeObjectStrings(args);
129
- const me = sender(ctx);
130
- const myPid = ctx.projectId;
131
- // 智能默认:未指定 action 时,有 id/ids → read,否则 → list
132
- let action = decoded.action;
133
- if (!action) {
134
- const hasIds = Boolean(decoded.id) || (Array.isArray(decoded.ids) && decoded.ids.length > 0);
135
- action = hasIds ? 'read' : 'list';
136
- }
137
- switch (action) {
138
- // ============ 公开操作 ============
139
- case 'list': {
140
- const all = await client().discussionList();
141
- if (!Array.isArray(all))
142
- return wrap(`当前无活跃的讨论 (本项目: ${me})`);
143
- const active = all.filter(d => isMember(d, myPid));
144
- if (active.length === 0)
145
- return wrap(`当前无参与的活跃讨论 (本项目: ${myPid})`);
146
- return wrap(formatList(active, ctx));
147
- }
148
- case 'history': {
149
- const all = await client().discussionListAll(me);
150
- if (!Array.isArray(all) || all.length === 0)
151
- return wrap(`暂无参与过的讨论记录 (身份: ${me})`);
152
- const lines = [
153
- `本项目: ${me}`,
154
- ``,
155
- `📋 讨论历史 (${all.length}个)`,
156
- ``,
157
- `| ID | 标题 | 发起方 | 状态 | 消息数 | 更新 |`,
158
- `|:---|:---|:---|:---|:---:|:---|`,
159
- ];
160
- for (const d of all) {
161
- const statusIcon = d.status === 'active' ? '🟢' : '⚪';
162
- lines.push(`| ${d.id} | ${d.title} | ${d.initiator} | ${statusIcon} ${d.status} | ${d.messageCount ?? 0} | ${relativeTime(d.updatedAt)} |`);
163
- }
164
- return wrap(lines.join('\n'));
165
- }
166
- case 'create': {
167
- if (!decoded.title)
168
- return wrap('❌ create 需要 title');
169
- if (!decoded.participants)
170
- return wrap('❌ create 需要 participants');
171
- if (!decoded.content)
172
- return wrap('❌ create 需要 content');
173
- const result = await client().discussionCreate(decoded.title, me, decoded.participants, decoded.content, decoded.summary);
174
- return wrap(`✅ 讨论已发起\nID: ${result.id}\n发起方: ${me}\n参与方: ${decoded.participants.join(', ')}\n\n🔒 权限: 仅以上项目可读写此讨论,仅发起方可删除/归档`);
175
- }
176
- // ============ 成员操作 (需验证成员身份) ============
177
- case 'read': {
178
- const readIds = decoded.ids || (decoded.id ? [decoded.id] : []);
179
- if (readIds.length === 0)
180
- return wrap('❌ read 需要 id 或 ids');
181
- // 权限检查: 逐个验证成员身份
182
- const allActive = await client().discussionList();
183
- const deniedIds = [];
184
- const allowedIds = [];
185
- for (const rid of readIds) {
186
- const brief = allActive.find(d => d.id === rid);
187
- if (!brief) {
188
- allowedIds.push(rid); // 找不到就放行(可能是历史讨论)
189
- }
190
- else if (isMember(brief, myPid)) {
191
- allowedIds.push(rid);
192
- }
193
- else {
194
- deniedIds.push(rid);
195
- }
196
- }
197
- if (deniedIds.length > 0 && allowedIds.length === 0) {
198
- return wrap(`🔒 无权限: 你 (${myPid}) 不是这些讨论的成员\n拒绝: ${deniedIds.join(', ')}`);
199
- }
200
- const readMode = decoded.mode || 'auto';
201
- const discussions = await client().discussionReadByIds(allowedIds, me, readMode);
202
- if (!Array.isArray(discussions) || discussions.length === 0)
203
- return wrap('未找到对应的讨论记录');
204
- let result = discussions.map(formatDetailView).join('\n\n━━━━━━━━━━━━━━━━━━━━\n\n');
205
- if (deniedIds.length > 0) {
206
- result += `\n\n🔒 已跳过 ${deniedIds.length} 个无权限的讨论: ${deniedIds.join(', ')}`;
207
- }
208
- return wrap(result);
209
- }
210
- case 'reply': {
211
- if (!decoded.id)
212
- return wrap('❌ reply 需要 id');
213
- if (!decoded.content)
214
- return wrap('❌ reply 需要 content');
215
- // 权限检查: 仅成员可回复
216
- const brief = await getBrief(decoded.id);
217
- if (brief && !isMember(brief, myPid)) {
218
- return wrap(`🔒 无权限: 你 (${myPid}) 不是讨论 "${brief.title}" 的成员\n成员: ${brief.initiator}, ${brief.participants.join(', ')}`);
219
- }
220
- await client().discussionReply(decoded.id, me, decoded.content, decoded.summary, decoded.newSummary);
221
- return wrap(`✅ 回复成功 (ID: ${decoded.id}, 身份: ${me})`);
222
- }
223
- // ============ 发起人操作 (需验证发起人身份) ============
224
- case 'complete': {
225
- if (!decoded.id)
226
- return wrap('❌ complete 需要 id');
227
- // 权限检查: 仅发起人可标记完成
228
- const brief = await getBrief(decoded.id);
229
- if (brief && !isInitiator(brief, me, myPid)) {
230
- return wrap(`🔒 无权限: 仅发起人 (${brief.initiator}) 可标记完成\n你的身份: ${me}`);
231
- }
232
- await client().discussionComplete(decoded.id);
233
- return wrap(`✅ 讨论已标记完成 (ID: ${decoded.id})`);
234
- }
235
- case 'close': {
236
- if (!decoded.id)
237
- return wrap('❌ close 需要 id');
238
- if (!decoded.conclusion)
239
- return wrap('❌ close 需要 conclusion');
240
- // 权限检查: 仅发起人可归档
241
- const brief = await getBrief(decoded.id);
242
- if (brief && !isInitiator(brief, me, myPid)) {
243
- return wrap(`🔒 无权限: 仅发起人 (${brief.initiator}) 可归档讨论\n你的身份: ${me}`);
244
- }
245
- const result = await client().discussionClose(decoded.id, decoded.conclusion);
246
- const archivePath = result?.archived_path ? `\n📦 已归档: ${result.archived_path}` : '';
247
- return wrap(`✅ 讨论已结案并归档 (ID: ${decoded.id})\n📋 ${decoded.conclusion}${archivePath}`);
248
- }
249
- case 'delete': {
250
- if (!decoded.id)
251
- return wrap('❌ delete 需要 id');
252
- // 权限检查: 仅发起人可删除
253
- const brief = await getBrief(decoded.id);
254
- if (brief && !isInitiator(brief, me, myPid)) {
255
- return wrap(`🔒 无权限: 仅发起人 (${brief.initiator}) 可删除讨论\n你的身份: ${me}`);
256
- }
257
- await client().discussionDelete(decoded.id);
258
- return wrap(`✅ 讨论已删除 (ID: ${decoded.id})`);
259
- }
260
- default:
261
- return wrap(`❌ 未知 action: ${action}`);
262
- }
263
- }));
264
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * kg_doc — 文档查询视图
3
- * 纯查询工具,数据来源为流程图节点的 docEntries + description
4
- * 不创建独立文档,只是提供面向文档的查询入口
5
- *
6
- * read 默认返回当前文档,history=true 返回历史列表,index=N 返回指定历史条目
7
- */
8
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
- import { type McpContext } from './shared.js';
10
- export declare function registerDocQueryTools(server: McpServer, _ctx: McpContext): void;