@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.
- package/README.md +7 -3
- package/dist/tools/analyzer.d.ts +1 -4
- package/dist/tools/analyzer.js +4 -7
- package/dist/tools/flowchart.js +1 -4
- package/dist/tools/index.d.ts +8 -9
- package/dist/tools/index.js +8 -29
- package/dist/tools/init.js +1 -1
- package/dist/tools/kg_status.d.ts +1 -1
- package/dist/tools/kg_status.js +4 -6
- package/dist/tools/refs.js +38 -172
- package/dist/tools/tasks.d.ts +1 -2
- package/dist/tools/tasks.js +32 -47
- package/dist/tools/workflow.js +2 -3
- package/package.json +1 -1
- package/dist/tools/discussion.d.ts +0 -15
- package/dist/tools/discussion.js +0 -264
- package/dist/tools/doc_query.d.ts +0 -10
- package/dist/tools/doc_query.js +0 -185
- package/dist/tools/files.d.ts +0 -6
- package/dist/tools/files.js +0 -107
- package/dist/tools/meeting.d.ts +0 -7
- package/dist/tools/meeting.js +0 -97
- package/dist/tools/pitfalls.d.ts +0 -6
- package/dist/tools/pitfalls.js +0 -190
- package/dist/tools/projects.d.ts +0 -7
- package/dist/tools/projects.js +0 -19
package/dist/tools/tasks.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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', '
|
|
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('
|
|
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
|
|
63
|
+
bindMsg = `\n已绑定到节点: ${decoded.bindTo} [${chartId}]`;
|
|
68
64
|
}
|
|
69
65
|
else {
|
|
70
|
-
bindMsg = `\n
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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('
|
|
170
|
+
return wrap('update 需要 taskId');
|
|
179
171
|
if (!decoded.content)
|
|
180
|
-
return wrap('
|
|
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
|
-
|
|
187
|
-
|
|
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('
|
|
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(
|
|
192
|
+
return wrap(`任务已归档: ${task.title}`);
|
|
208
193
|
}
|
|
209
194
|
case 'delete': {
|
|
210
195
|
if (!decoded.taskId)
|
|
211
|
-
return wrap('
|
|
196
|
+
return wrap('delete 需要 taskId');
|
|
212
197
|
const ok = await client().deleteTask(decoded.taskId);
|
|
213
|
-
return wrap(ok ?
|
|
198
|
+
return wrap(ok ? `任务已删除 (ID: ${decoded.taskId})` : '删除失败(任务不存在)');
|
|
214
199
|
}
|
|
215
200
|
default:
|
|
216
|
-
return wrap(
|
|
201
|
+
return wrap(`未知 action: ${decoded.action}`);
|
|
217
202
|
}
|
|
218
203
|
}));
|
|
219
204
|
}
|
package/dist/tools/workflow.js
CHANGED
|
@@ -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', '
|
|
8
|
-
'
|
|
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,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;
|
package/dist/tools/discussion.js
DELETED
|
@@ -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;
|