libero-mcp 0.2.12 → 0.2.13
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/knowledge/_schema/analysis-model.schema.js +32 -0
- package/dist/knowledge/_schema/entry.schema.js +45 -0
- package/dist/knowledge/_schema/persona.schema.js +23 -0
- package/dist/knowledge/_schema/scenario-pack.schema.js +29 -0
- package/dist/knowledge/src/repo/types.js +1 -0
- package/dist/mcp/server-core.js +499 -323
- package/dist/mcp/src/coachmod/index.js +135 -0
- package/dist/mcp/src/repo/factory.js +32 -0
- package/dist/mcp/src/timeline/index.js +61 -3
- package/package.json +1 -1
package/dist/mcp/server-core.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
4
|
-
import { SqliteIdentityRepo } from "./src/repo/sqlite-identity.js";
|
|
3
|
+
import { createRepos } from "./src/repo/factory.js";
|
|
5
4
|
import { createCategoryTools } from "./src/categories/index.js";
|
|
6
5
|
import { createTimeBlockTools } from "./src/timeblock/index.js";
|
|
7
6
|
import { createStatsTools } from "./src/stats/index.js";
|
|
@@ -47,47 +46,36 @@ export function errorContent(msg) {
|
|
|
47
46
|
};
|
|
48
47
|
}
|
|
49
48
|
/**
|
|
50
|
-
* Build the
|
|
51
|
-
*
|
|
52
|
-
*
|
|
49
|
+
* Build the full tool registry (48 tools). Each entry is a data-fied version of
|
|
50
|
+
* the pre-refactor inline registration calls — handlers are byte-for-byte copies
|
|
51
|
+
* preserving declaration order for diagnostic readability.
|
|
53
52
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
const categoryRepo = new SqliteCategoryRepo(db);
|
|
61
|
-
const timeblockRepo = new SqliteTimeBlockRepo(db);
|
|
62
|
-
const statsRepo = new SqliteStatsRepo(db);
|
|
63
|
-
const timerRepo = new SqliteTimerRepo(db);
|
|
64
|
-
const profileRepo = new SqliteProfileRepo(db);
|
|
65
|
-
const reminderRepo = new SqliteReminderRepo(db);
|
|
66
|
-
const timelineRepo = new SqliteTimelineRepo(db);
|
|
67
|
-
const identityRepo = new SqliteIdentityRepo(db);
|
|
68
|
-
// ── inject tool factories ──
|
|
69
|
-
const categories = createCategoryTools(categoryRepo);
|
|
70
|
-
const tb = createTimeBlockTools(timeblockRepo, db);
|
|
71
|
-
const stats = createStatsTools(statsRepo);
|
|
72
|
-
const matrix = createMatrixTools(statsRepo); // matrix reuses StatsRepo
|
|
73
|
-
const timeline = createTimelineTools(timelineRepo);
|
|
74
|
-
const timer = createTimerTools(timerRepo, timeblockRepo, db);
|
|
75
|
-
const profile = createProfileTools(profileRepo, identityRepo);
|
|
76
|
-
const profileConfirm = createProfileConfirmTools(db);
|
|
77
|
-
const reminder = createReminderTools(reminderRepo, undefined, db);
|
|
78
|
-
const scheduler = createSchedulerTools(timeblockRepo, reminderRepo, categoryRepo);
|
|
79
|
-
const task = createTaskTools(db);
|
|
80
|
-
// instructions 必须放在第 2 参 ServerOptions(不是 serverInfo)—— SDK 只从 options 读
|
|
81
|
-
const server = new McpServer({ name: "libero-mcp", version: "0.2.3" }, { instructions: buildServerInstructions() });
|
|
53
|
+
function buildToolRegistry(ctx) {
|
|
54
|
+
const { categories, tb, stats, matrix, timeline, timer, profile, profileConfirm, reminder, scheduler, task, injector, } = ctx;
|
|
55
|
+
// identityRepo is referenced directly inside handlers below (closure scope),
|
|
56
|
+
// matching the pre-refactor behavior.
|
|
57
|
+
const identityRepo = ctx.repos.identityRepo;
|
|
58
|
+
const tools = [];
|
|
82
59
|
// ============================================================
|
|
83
60
|
// categories (2 tools)
|
|
84
61
|
// ============================================================
|
|
85
|
-
|
|
86
|
-
|
|
62
|
+
tools.push({
|
|
63
|
+
name: "categories.getAll",
|
|
64
|
+
description: "获取全部时间分类(15 类)",
|
|
65
|
+
inputSchema: {},
|
|
66
|
+
handler: async () => asContent(await categories.getAllCategories()),
|
|
67
|
+
});
|
|
68
|
+
tools.push({
|
|
69
|
+
name: "categories.getById",
|
|
70
|
+
description: "按 id 获取单个分类",
|
|
71
|
+
inputSchema: { id: z.string().min(1) },
|
|
72
|
+
handler: async ({ id }) => asContent(await categories.getCategoryById(id)),
|
|
73
|
+
});
|
|
87
74
|
// ============================================================
|
|
88
|
-
// timer (
|
|
75
|
+
// timer (9 tools — positional userId args, R4 pattern)
|
|
89
76
|
// ============================================================
|
|
90
|
-
|
|
77
|
+
tools.push({
|
|
78
|
+
name: "timer.start",
|
|
91
79
|
description: "开始一个计时会话",
|
|
92
80
|
inputSchema: {
|
|
93
81
|
user_id: z.string().optional(),
|
|
@@ -96,55 +84,90 @@ export function buildServer(db) {
|
|
|
96
84
|
categoryId: z.string().optional(),
|
|
97
85
|
task_id: z.string().optional(),
|
|
98
86
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
87
|
+
needsUserIdInjection: true,
|
|
88
|
+
handler: async (rawInput) => {
|
|
89
|
+
const r = injector(rawInput);
|
|
90
|
+
if ("error" in r)
|
|
91
|
+
return errorContent(r.error);
|
|
92
|
+
return asContent(await timer.start(r.user_id, r.event, r.categoryId, r.task_id, r.target_user_id));
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
tools.push({
|
|
96
|
+
name: "timer.pause",
|
|
97
|
+
description: "暂停当前计时",
|
|
98
|
+
inputSchema: { user_id: z.string().optional() },
|
|
99
|
+
needsUserIdInjection: true,
|
|
100
|
+
handler: async (rawInput) => {
|
|
101
|
+
const r = injector(rawInput);
|
|
102
|
+
if ("error" in r)
|
|
103
|
+
return errorContent(r.error);
|
|
104
|
+
return asContent(await timer.pause(r.user_id));
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
tools.push({
|
|
108
|
+
name: "timer.resume",
|
|
109
|
+
description: "恢复已暂停的计时",
|
|
110
|
+
inputSchema: { user_id: z.string().optional() },
|
|
111
|
+
needsUserIdInjection: true,
|
|
112
|
+
handler: async (rawInput) => {
|
|
113
|
+
const r = injector(rawInput);
|
|
114
|
+
if ("error" in r)
|
|
115
|
+
return errorContent(r.error);
|
|
116
|
+
return asContent(await timer.resume(r.user_id));
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
tools.push({
|
|
120
|
+
name: "timer.stop",
|
|
118
121
|
description: "停止计时并返回记录(传 rating 时自动落盘 timeblock)",
|
|
119
122
|
inputSchema: {
|
|
120
123
|
user_id: z.string().optional(),
|
|
121
124
|
rating: z.number().min(1).max(5).optional(),
|
|
122
125
|
rating_reason: z.string().optional(),
|
|
123
126
|
},
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
127
|
+
needsUserIdInjection: true,
|
|
128
|
+
handler: async (rawInput) => {
|
|
129
|
+
const r = injector(rawInput);
|
|
130
|
+
if ("error" in r)
|
|
131
|
+
return errorContent(r.error);
|
|
132
|
+
return asContent(await timer.stop(r.user_id, {
|
|
133
|
+
rating: r.rating,
|
|
134
|
+
rating_reason: r.rating_reason,
|
|
135
|
+
}));
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
tools.push({
|
|
139
|
+
name: "timer.status",
|
|
140
|
+
description: "查询某用户当前计时状态",
|
|
141
|
+
inputSchema: { user_id: z.string().optional() },
|
|
142
|
+
needsUserIdInjection: true,
|
|
143
|
+
handler: async (rawInput) => {
|
|
144
|
+
const r = injector(rawInput);
|
|
145
|
+
if ("error" in r)
|
|
146
|
+
return errorContent(r.error);
|
|
147
|
+
return asContent(await timer.status(r.user_id));
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
tools.push({
|
|
151
|
+
name: "timer.elapsed",
|
|
152
|
+
description: "查询当前计时已过毫秒数",
|
|
153
|
+
inputSchema: { user_id: z.string().optional() },
|
|
154
|
+
needsUserIdInjection: true,
|
|
155
|
+
handler: async (rawInput) => {
|
|
156
|
+
const r = injector(rawInput);
|
|
157
|
+
if ("error" in r)
|
|
158
|
+
return errorContent(r.error);
|
|
159
|
+
return asContent(await timer.elapsed(r.user_id));
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
tools.push({
|
|
163
|
+
name: "timer.statusAll",
|
|
164
|
+
description: "查询所有用户的当前计时状态(调试/兜底用)",
|
|
165
|
+
inputSchema: {},
|
|
166
|
+
handler: async () => asContent(await timer.statusAll()),
|
|
167
|
+
});
|
|
146
168
|
// ── S26: pomodoro v1 tools ──
|
|
147
|
-
|
|
169
|
+
tools.push({
|
|
170
|
+
name: "timer.startFocus",
|
|
148
171
|
description: "开始一个专注计时(番茄钟),带目标时长",
|
|
149
172
|
inputSchema: {
|
|
150
173
|
user_id: z.string().optional(),
|
|
@@ -153,27 +176,33 @@ export function buildServer(db) {
|
|
|
153
176
|
categoryId: z.string().optional(),
|
|
154
177
|
task_id: z.string().optional(),
|
|
155
178
|
},
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
179
|
+
needsUserIdInjection: true,
|
|
180
|
+
handler: async (rawInput) => {
|
|
181
|
+
const r = injector(rawInput);
|
|
182
|
+
if ("error" in r)
|
|
183
|
+
return errorContent(r.error);
|
|
184
|
+
return asContent(await timer.startFocus(r.user_id, r.event, r.focusMin, r.categoryId, r.task_id));
|
|
185
|
+
},
|
|
161
186
|
});
|
|
162
|
-
|
|
187
|
+
tools.push({
|
|
188
|
+
name: "timer.remaining",
|
|
163
189
|
description: "查询专注计时剩余时间(负数=超时)",
|
|
164
190
|
inputSchema: {
|
|
165
191
|
user_id: z.string().optional(),
|
|
166
192
|
},
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
193
|
+
needsUserIdInjection: true,
|
|
194
|
+
handler: async (rawInput) => {
|
|
195
|
+
const r = injector(rawInput);
|
|
196
|
+
if ("error" in r)
|
|
197
|
+
return errorContent(r.error);
|
|
198
|
+
return asContent(await timer.remaining(r.user_id));
|
|
199
|
+
},
|
|
172
200
|
});
|
|
173
201
|
// ============================================================
|
|
174
|
-
// timeblock (
|
|
202
|
+
// timeblock (6 tools — single-object args)
|
|
175
203
|
// ============================================================
|
|
176
|
-
|
|
204
|
+
tools.push({
|
|
205
|
+
name: "timeblock.create",
|
|
177
206
|
description: "创建一条时间记录",
|
|
178
207
|
inputSchema: {
|
|
179
208
|
user_id: z.string().optional(),
|
|
@@ -187,26 +216,32 @@ export function buildServer(db) {
|
|
|
187
216
|
rating_reason: z.string().optional(),
|
|
188
217
|
notes: z.string().optional(),
|
|
189
218
|
},
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
219
|
+
needsUserIdInjection: true,
|
|
220
|
+
handler: async (rawInput) => {
|
|
221
|
+
const r = injector(rawInput);
|
|
222
|
+
if ("error" in r)
|
|
223
|
+
return errorContent(r.error);
|
|
224
|
+
return asContent(await tb.create(r));
|
|
225
|
+
},
|
|
195
226
|
});
|
|
196
|
-
|
|
227
|
+
tools.push({
|
|
228
|
+
name: "timeblock.getById",
|
|
197
229
|
description: "按 id 获取单条时间记录。" +
|
|
198
230
|
"S41 多用户:默认按当前 user_id 过滤,跨用户查不到返回 null。",
|
|
199
231
|
inputSchema: {
|
|
200
232
|
id: z.string().min(1),
|
|
201
233
|
user_id: z.string().optional(),
|
|
202
234
|
},
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
235
|
+
needsUserIdInjection: true,
|
|
236
|
+
handler: async (rawInput) => {
|
|
237
|
+
const r = injector(rawInput);
|
|
238
|
+
if ("error" in r)
|
|
239
|
+
return errorContent(r.error);
|
|
240
|
+
return asContent(await tb.getById(rawInput.id, r.user_id));
|
|
241
|
+
},
|
|
208
242
|
});
|
|
209
|
-
|
|
243
|
+
tools.push({
|
|
244
|
+
name: "timeblock.update",
|
|
210
245
|
description: "更新一条时间记录(标题/时间/评分/status 等)。" +
|
|
211
246
|
"MV-COACH-3 Slice 4 用户主权状态机:" +
|
|
212
247
|
"① 开工 cron 触发到点 → status='unverified'(等用户核实,**不计入实绩**);" +
|
|
@@ -227,16 +262,25 @@ export function buildServer(db) {
|
|
|
227
262
|
notes: z.string().optional(),
|
|
228
263
|
status: z.enum(["planned", "unverified", "completed"]).optional(),
|
|
229
264
|
},
|
|
230
|
-
|
|
231
|
-
|
|
265
|
+
handler: async ({ id, ...rest }) => asContent(await tb.update(id, rest)),
|
|
266
|
+
});
|
|
267
|
+
tools.push({
|
|
268
|
+
name: "timeblock.remove",
|
|
232
269
|
description: "软删除一条时间记录",
|
|
233
270
|
inputSchema: {
|
|
234
271
|
id: z.string().min(1),
|
|
235
272
|
target_user_id: z.string().min(1).optional(),
|
|
236
273
|
},
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
274
|
+
handler: async ({ id, target_user_id }) => asContent(await tb.remove(id, { target_user_id })),
|
|
275
|
+
});
|
|
276
|
+
tools.push({
|
|
277
|
+
name: "timeblock.restore",
|
|
278
|
+
description: "恢复一条已软删除的时间记录",
|
|
279
|
+
inputSchema: { id: z.string().min(1) },
|
|
280
|
+
handler: async ({ id }) => asContent(await tb.restore(id)),
|
|
281
|
+
});
|
|
282
|
+
tools.push({
|
|
283
|
+
name: "timeblock.list",
|
|
240
284
|
description: "按条件查询时间记录列表",
|
|
241
285
|
inputSchema: {
|
|
242
286
|
user_id: z.string().optional(),
|
|
@@ -249,16 +293,19 @@ export function buildServer(db) {
|
|
|
249
293
|
limit: z.number().optional(),
|
|
250
294
|
offset: z.number().optional(),
|
|
251
295
|
},
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
296
|
+
needsUserIdInjection: true,
|
|
297
|
+
handler: async (rawInput) => {
|
|
298
|
+
const r = injector(rawInput);
|
|
299
|
+
if ("error" in r)
|
|
300
|
+
return errorContent(r.error);
|
|
301
|
+
return asContent(await tb.list(r));
|
|
302
|
+
},
|
|
257
303
|
});
|
|
258
304
|
// ============================================================
|
|
259
305
|
// stats (2 tools — single-object filters)
|
|
260
306
|
// ============================================================
|
|
261
|
-
|
|
307
|
+
tools.push({
|
|
308
|
+
name: "stats.summarize",
|
|
262
309
|
description: "按分类汇总时间统计(占比/记录数)",
|
|
263
310
|
inputSchema: {
|
|
264
311
|
user_id: z.string().optional(),
|
|
@@ -267,13 +314,16 @@ export function buildServer(db) {
|
|
|
267
314
|
to: z.string().optional(),
|
|
268
315
|
category_id: z.string().optional(),
|
|
269
316
|
},
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
317
|
+
needsUserIdInjection: true,
|
|
318
|
+
handler: async (rawInput) => {
|
|
319
|
+
const r = injector(rawInput);
|
|
320
|
+
if ("error" in r)
|
|
321
|
+
return errorContent(r.error);
|
|
322
|
+
return asContent(await stats.summarize(r));
|
|
323
|
+
},
|
|
275
324
|
});
|
|
276
|
-
|
|
325
|
+
tools.push({
|
|
326
|
+
name: "stats.dailyBreakdown",
|
|
277
327
|
description: "按天×分类拆分时间明细",
|
|
278
328
|
inputSchema: {
|
|
279
329
|
user_id: z.string().optional(),
|
|
@@ -282,32 +332,38 @@ export function buildServer(db) {
|
|
|
282
332
|
to: z.string().optional(),
|
|
283
333
|
category_id: z.string().optional(),
|
|
284
334
|
},
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
335
|
+
needsUserIdInjection: true,
|
|
336
|
+
handler: async (rawInput) => {
|
|
337
|
+
const r = injector(rawInput);
|
|
338
|
+
if ("error" in r)
|
|
339
|
+
return errorContent(r.error);
|
|
340
|
+
return asContent(await stats.dailyBreakdown(r));
|
|
341
|
+
},
|
|
290
342
|
});
|
|
291
343
|
// ============================================================
|
|
292
344
|
// matrix (1 tool — single-object filters, reuses StatsRepo)
|
|
293
345
|
// ============================================================
|
|
294
|
-
|
|
346
|
+
tools.push({
|
|
347
|
+
name: "matrix.renderMatrix",
|
|
295
348
|
description: "时间×评分四象限矩阵(能耗黑洞/保持区/亮点区/可放弃)",
|
|
296
349
|
inputSchema: {
|
|
297
350
|
user_id: z.string().optional(),
|
|
298
351
|
from: z.string().optional(),
|
|
299
352
|
to: z.string().optional(),
|
|
300
353
|
},
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
354
|
+
needsUserIdInjection: true,
|
|
355
|
+
handler: async (rawInput) => {
|
|
356
|
+
const r = injector(rawInput);
|
|
357
|
+
if ("error" in r)
|
|
358
|
+
return errorContent(r.error);
|
|
359
|
+
return asContent(await matrix.renderMatrix(r));
|
|
360
|
+
},
|
|
306
361
|
});
|
|
307
362
|
// ============================================================
|
|
308
363
|
// schedule.timeline (emoji matrix + detail table)
|
|
309
364
|
// ============================================================
|
|
310
|
-
|
|
365
|
+
tools.push({
|
|
366
|
+
name: "schedule.timeline",
|
|
311
367
|
description: "看日程/复盘的首选视图:emoji 矩阵+细项表格,含计划/计时中/未回应/实绩全状态。" +
|
|
312
368
|
"用户说「看今天/看日程/在干啥/复盘/接下来 N 小时」时调本工具,并直接 echo rendered_text(不重写、不改格式)。" +
|
|
313
369
|
"不要调 scheduler.listSchedules(那是内部排程查询,不是用户视图)。",
|
|
@@ -318,45 +374,58 @@ export function buildServer(db) {
|
|
|
318
374
|
to: z.string().optional(),
|
|
319
375
|
format: z.enum(["emoji", "ascii"]).optional(),
|
|
320
376
|
},
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
377
|
+
needsUserIdInjection: true,
|
|
378
|
+
handler: async (rawInput) => {
|
|
379
|
+
const r = injector(rawInput);
|
|
380
|
+
if ("error" in r)
|
|
381
|
+
return errorContent(r.error);
|
|
382
|
+
return asContent(await timeline.renderTimeline(r));
|
|
383
|
+
},
|
|
326
384
|
});
|
|
327
385
|
// ============================================================
|
|
328
386
|
// clock (3 tools — pure functions, no repo)
|
|
329
387
|
// ============================================================
|
|
330
|
-
|
|
388
|
+
tools.push({
|
|
389
|
+
name: "clock.now",
|
|
331
390
|
description: "返回当前 ISO 时间字符串(可选时区偏移)",
|
|
332
391
|
inputSchema: {
|
|
333
392
|
offsetMinutes: z.number().optional(),
|
|
334
393
|
},
|
|
335
|
-
|
|
336
|
-
|
|
394
|
+
handler: async ({ offsetMinutes }) => asContent(offsetMinutes !== undefined ? nowLocal(offsetMinutes) : nowLocal()),
|
|
395
|
+
});
|
|
396
|
+
tools.push({
|
|
397
|
+
name: "clock.formatTime",
|
|
337
398
|
description: "按指定格式格式化 ISO 时间",
|
|
338
399
|
inputSchema: {
|
|
339
400
|
iso: z.string().min(1),
|
|
340
401
|
format: z.string().min(1),
|
|
341
402
|
},
|
|
342
|
-
|
|
343
|
-
|
|
403
|
+
handler: async ({ iso, format }) => asContent(formatTime(iso, format)),
|
|
404
|
+
});
|
|
405
|
+
tools.push({
|
|
406
|
+
name: "clock.parseTime",
|
|
344
407
|
description: "解析 ISO 时间字符串为结构化字段",
|
|
345
408
|
inputSchema: { iso: z.string().min(1) },
|
|
346
|
-
|
|
409
|
+
handler: async ({ iso }) => asContent(parseTime(iso)),
|
|
410
|
+
});
|
|
347
411
|
// ============================================================
|
|
348
|
-
// profile (
|
|
412
|
+
// profile (8 tools — get / upsert / markInactivityNudge / claimNudge /
|
|
413
|
+
// confirm_default / bindAlias / listMyChannels / createUser)
|
|
349
414
|
// ============================================================
|
|
350
|
-
|
|
415
|
+
tools.push({
|
|
416
|
+
name: "profile.get",
|
|
351
417
|
description: "获取用户 profile(含结构化约定)",
|
|
352
418
|
inputSchema: { user_id: z.string().optional() },
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
419
|
+
needsUserIdInjection: true,
|
|
420
|
+
handler: async (rawInput) => {
|
|
421
|
+
const r = injector(rawInput);
|
|
422
|
+
if ("error" in r)
|
|
423
|
+
return errorContent(r.error);
|
|
424
|
+
return asContent(await profile.get(r.user_id));
|
|
425
|
+
},
|
|
358
426
|
});
|
|
359
|
-
|
|
427
|
+
tools.push({
|
|
428
|
+
name: "profile.upsert",
|
|
360
429
|
description: "创建或更新用户 profile(display_name / expectations / settings)。首次确认请用 profile.confirm_default。",
|
|
361
430
|
inputSchema: {
|
|
362
431
|
user_id: z.string().optional(),
|
|
@@ -367,13 +436,16 @@ export function buildServer(db) {
|
|
|
367
436
|
.passthrough()
|
|
368
437
|
.optional(),
|
|
369
438
|
},
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
439
|
+
needsUserIdInjection: true,
|
|
440
|
+
handler: async (rawInput) => {
|
|
441
|
+
const r = injector(rawInput);
|
|
442
|
+
if ("error" in r)
|
|
443
|
+
return errorContent(r.error);
|
|
444
|
+
return asContent(await profile.upsert(r));
|
|
445
|
+
},
|
|
375
446
|
});
|
|
376
|
-
|
|
447
|
+
tools.push({
|
|
448
|
+
name: "profile.markInactivityNudge",
|
|
377
449
|
description: "MV-COACH-3 slice 2(防重复失效专用):触发「5 天没记录轻触」问候后必调。" +
|
|
378
450
|
"专门写 settings.last_inactivity_nudge_at = now。" +
|
|
379
451
|
"⚠️ 不要用 profile.upsert 写这个字段——upsert 是 whole-replace 会清掉其它 settings key。" +
|
|
@@ -381,13 +453,16 @@ export function buildServer(db) {
|
|
|
381
453
|
inputSchema: {
|
|
382
454
|
user_id: z.string().optional(),
|
|
383
455
|
},
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
456
|
+
needsUserIdInjection: true,
|
|
457
|
+
handler: async (rawInput) => {
|
|
458
|
+
const r = injector(rawInput);
|
|
459
|
+
if ("error" in r)
|
|
460
|
+
return errorContent(r.error);
|
|
461
|
+
return asContent(await profile.markInactivityNudge({ user_id: r.user_id }));
|
|
462
|
+
},
|
|
389
463
|
});
|
|
390
|
-
|
|
464
|
+
tools.push({
|
|
465
|
+
name: "profile.claimNudge",
|
|
391
466
|
description: "S40 slice 2(多 cron 并发 atomic 抢占):nag fan-out 多渠道时用。" +
|
|
392
467
|
"window_minutes 内只有第一个调用赢(won=true),其它返回 won=false。" +
|
|
393
468
|
"**调用方必须看返回的 won 字段**:" +
|
|
@@ -397,28 +472,33 @@ export function buildServer(db) {
|
|
|
397
472
|
user_id: z.string().optional(),
|
|
398
473
|
window_minutes: z.number().optional(),
|
|
399
474
|
},
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
475
|
+
needsUserIdInjection: true,
|
|
476
|
+
handler: async (rawInput) => {
|
|
477
|
+
const r = injector(rawInput);
|
|
478
|
+
if ("error" in r)
|
|
479
|
+
return errorContent(r.error);
|
|
480
|
+
return asContent(await profile.claimNudge({
|
|
481
|
+
user_id: r.user_id,
|
|
482
|
+
window_minutes: typeof rawInput.window_minutes === "number"
|
|
483
|
+
? rawInput.window_minutes
|
|
484
|
+
: undefined,
|
|
485
|
+
}));
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
tools.push({
|
|
489
|
+
name: "profile.confirm_default",
|
|
412
490
|
description: "首次写入前确认默认用户:写入 user_profile,设置 display_name 与 default_confirmed_at。display_name 重名则拒绝。",
|
|
413
491
|
inputSchema: {
|
|
414
492
|
platform_user_id: z.string().min(1),
|
|
415
493
|
display_name: z.string().min(1),
|
|
416
494
|
},
|
|
417
|
-
|
|
495
|
+
handler: async (input) => asContent(await profileConfirm.confirmDefault(input)),
|
|
496
|
+
});
|
|
418
497
|
// ============================================================
|
|
419
|
-
// S40: cross-platform identity (
|
|
498
|
+
// S40: cross-platform identity (bindAlias / listMyChannels)
|
|
420
499
|
// ============================================================
|
|
421
|
-
|
|
500
|
+
tools.push({
|
|
501
|
+
name: "profile.bindAlias",
|
|
422
502
|
description: "S40 跨平台身份层:把一个平台 chat_id 绑定到已有 libero 用户身份。" +
|
|
423
503
|
"场景:用户在新设备(如飞书)首次开口,主会话问『要和已有账号(如企微)合并吗』,得到肯定后调这个。" +
|
|
424
504
|
"libero_user_id 为空 = 首次绑定,自动建一个 libero_user_id。" +
|
|
@@ -430,59 +510,64 @@ export function buildServer(db) {
|
|
|
430
510
|
chat_id: z.string().optional(),
|
|
431
511
|
display_name: z.string().optional(),
|
|
432
512
|
},
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
443
|
-
else {
|
|
444
|
-
const existing = await identityRepo.getIdentity(lid);
|
|
445
|
-
if (!existing) {
|
|
446
|
-
return errorContent(`libero_user_id not found: ${lid}`);
|
|
513
|
+
handler: async (input) => {
|
|
514
|
+
const crypto = await import("node:crypto");
|
|
515
|
+
let lid = input.libero_user_id;
|
|
516
|
+
if (!lid) {
|
|
517
|
+
lid = `u_libero_${crypto.randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
518
|
+
await identityRepo.createIdentity({
|
|
519
|
+
libero_user_id: lid,
|
|
520
|
+
display_name: input.display_name ?? input.platform_user_id.slice(0, 16),
|
|
521
|
+
});
|
|
447
522
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
523
|
+
else {
|
|
524
|
+
const existing = await identityRepo.getIdentity(lid);
|
|
525
|
+
if (!existing) {
|
|
526
|
+
return errorContent(`libero_user_id not found: ${lid}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
await identityRepo.bindAlias({
|
|
531
|
+
libero_user_id: lid,
|
|
532
|
+
platform: input.platform,
|
|
533
|
+
platform_user_id: input.platform_user_id,
|
|
534
|
+
chat_id: input.chat_id,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
catch (e) {
|
|
538
|
+
return errorContent(e.message);
|
|
539
|
+
}
|
|
540
|
+
const aliases = await identityRepo.listAliases(lid);
|
|
541
|
+
return asContent({ libero_user_id: lid, bound_aliases: aliases });
|
|
542
|
+
},
|
|
543
|
+
});
|
|
544
|
+
tools.push({
|
|
545
|
+
name: "profile.listMyChannels",
|
|
464
546
|
description: "S40 跨平台身份层:列出当前用户绑定的所有平台渠道。" +
|
|
465
547
|
"用途:nag fan-out(按渠道建独立 cron)、跨平台数据查询、身份确认。",
|
|
466
548
|
inputSchema: {
|
|
467
549
|
user_id: z.string().optional(),
|
|
468
550
|
},
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
551
|
+
needsUserIdInjection: true,
|
|
552
|
+
handler: async (rawInput) => {
|
|
553
|
+
const r = injector(rawInput);
|
|
554
|
+
if ("error" in r)
|
|
555
|
+
return errorContent(r.error);
|
|
556
|
+
const lid = await resolveUserId(identityRepo, r.user_id);
|
|
557
|
+
const aliases = await identityRepo.listAliases(lid);
|
|
558
|
+
return asContent({
|
|
559
|
+
libero_user_id: lid,
|
|
560
|
+
channels: aliases.map((a) => ({
|
|
561
|
+
platform: a.platform,
|
|
562
|
+
platform_user_id: a.platform_user_id,
|
|
563
|
+
chat_id: a.chat_id,
|
|
564
|
+
bound_at: a.bound_at,
|
|
565
|
+
})),
|
|
566
|
+
});
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
tools.push({
|
|
570
|
+
name: "profile.createUser",
|
|
486
571
|
description: "S41 多用户建档:建立新 libero 用户(父母代管模式)。" +
|
|
487
572
|
"用户体感:「帮我建一个新用户」「我要给我儿子建档」时调用。" +
|
|
488
573
|
"返回新 libero_user_id,建档后该 user_id 立即可用于所有 libero 工具调用。" +
|
|
@@ -498,26 +583,28 @@ export function buildServer(db) {
|
|
|
498
583
|
platform_user_id: z.string().optional(),
|
|
499
584
|
chat_id: z.string().optional(),
|
|
500
585
|
},
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
586
|
+
handler: async (rawInput) => {
|
|
587
|
+
try {
|
|
588
|
+
const result = await profile.createUser({
|
|
589
|
+
display_name: rawInput.display_name,
|
|
590
|
+
focus_areas: rawInput.focus_areas,
|
|
591
|
+
goals: rawInput.goals,
|
|
592
|
+
platform: rawInput.platform,
|
|
593
|
+
platform_user_id: rawInput.platform_user_id,
|
|
594
|
+
chat_id: rawInput.chat_id,
|
|
595
|
+
});
|
|
596
|
+
return asContent(result);
|
|
597
|
+
}
|
|
598
|
+
catch (e) {
|
|
599
|
+
return errorContent(e.message);
|
|
600
|
+
}
|
|
601
|
+
},
|
|
516
602
|
});
|
|
517
603
|
// ============================================================
|
|
518
604
|
// reminder (6 tools — S11)
|
|
519
605
|
// ============================================================
|
|
520
|
-
|
|
606
|
+
tools.push({
|
|
607
|
+
name: "reminder.create",
|
|
521
608
|
description: "⚠️ 仅写 DB,不主动推送(libero-mcp 无 background sender)。" +
|
|
522
609
|
"在 hermes 等有 cronjob 工具的平台上,用户要'X 时间提醒我 Y'请调宿主 cronjob(action=create, deliver=origin),不要用本工具。" +
|
|
523
610
|
"本工具仅用于:① 无 cron 能力的平台兜底;② 查询场景配合 getDue/getMissed。",
|
|
@@ -529,48 +616,62 @@ export function buildServer(db) {
|
|
|
529
616
|
channel: z.string().optional(),
|
|
530
617
|
timeblock_id: z.string().optional(),
|
|
531
618
|
},
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
619
|
+
needsUserIdInjection: true,
|
|
620
|
+
handler: async (rawInput) => {
|
|
621
|
+
const r = injector(rawInput);
|
|
622
|
+
if ("error" in r)
|
|
623
|
+
return errorContent(r.error);
|
|
624
|
+
return asContent(await reminder.create(r));
|
|
625
|
+
},
|
|
537
626
|
});
|
|
538
|
-
|
|
627
|
+
tools.push({
|
|
628
|
+
name: "reminder.getDue",
|
|
539
629
|
description: "获取到期且待发送的提醒列表",
|
|
540
630
|
inputSchema: {
|
|
541
631
|
user_id: z.string().optional(),
|
|
542
632
|
until: z.string().optional(),
|
|
543
633
|
},
|
|
544
|
-
|
|
545
|
-
|
|
634
|
+
handler: async (input) => asContent(await reminder.getDueReminders(input)),
|
|
635
|
+
});
|
|
636
|
+
tools.push({
|
|
637
|
+
name: "reminder.getNextDue",
|
|
546
638
|
description: "获取下一条待发送提醒的时间",
|
|
547
639
|
inputSchema: {
|
|
548
640
|
user_id: z.string().optional(),
|
|
549
641
|
},
|
|
550
|
-
|
|
551
|
-
|
|
642
|
+
handler: async (input) => asContent(await reminder.getNextDue(input)),
|
|
643
|
+
});
|
|
644
|
+
tools.push({
|
|
645
|
+
name: "reminder.getMissed",
|
|
552
646
|
description: "获取过期但未送达的提醒(被动补发用)",
|
|
553
647
|
inputSchema: {
|
|
554
648
|
user_id: z.string().optional(),
|
|
555
649
|
},
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
650
|
+
needsUserIdInjection: true,
|
|
651
|
+
handler: async (rawInput) => {
|
|
652
|
+
const r = injector(rawInput);
|
|
653
|
+
if ("error" in r)
|
|
654
|
+
return errorContent(r.error);
|
|
655
|
+
return asContent(await reminder.getMissedReminders({ user_id: r.user_id }));
|
|
656
|
+
},
|
|
561
657
|
});
|
|
562
|
-
|
|
658
|
+
tools.push({
|
|
659
|
+
name: "reminder.markSent",
|
|
563
660
|
description: "标记提醒为已发送(幂等)",
|
|
564
661
|
inputSchema: { id: z.string().min(1) },
|
|
565
|
-
|
|
566
|
-
|
|
662
|
+
handler: async ({ id }) => asContent(await reminder.markSent(id)),
|
|
663
|
+
});
|
|
664
|
+
tools.push({
|
|
665
|
+
name: "reminder.cancel",
|
|
567
666
|
description: "取消一条提醒(幂等)",
|
|
568
667
|
inputSchema: { id: z.string().min(1) },
|
|
569
|
-
|
|
668
|
+
handler: async ({ id }) => asContent(await reminder.cancelReminder(id)),
|
|
669
|
+
});
|
|
570
670
|
// ============================================================
|
|
571
671
|
// scheduler (3 tools — S27, reuses timeblockRepo)
|
|
572
672
|
// ============================================================
|
|
573
|
-
|
|
673
|
+
tools.push({
|
|
674
|
+
name: "scheduler.createSchedule",
|
|
574
675
|
description: "创建一个日程(planned 时间块)。" +
|
|
575
676
|
"⚠️ MV-COACH-3 Slice 4 v2 教练闭环(用户主权模型):建完 schedule 后,**必须**在同一回复里完成:" +
|
|
576
677
|
"① 调 cronjob(action=create, deliver=origin) 建开工 cron(schedule=日程开始时间),cron prompt 必须用 libero-coach skill 的'开工 cron 标准 prompt 模板'——**不允许自创结构**。" +
|
|
@@ -589,13 +690,16 @@ export function buildServer(db) {
|
|
|
589
690
|
rating_reason: z.string().optional(),
|
|
590
691
|
notes: z.string().optional(),
|
|
591
692
|
},
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
693
|
+
needsUserIdInjection: true,
|
|
694
|
+
handler: async (rawInput) => {
|
|
695
|
+
const r = injector(rawInput);
|
|
696
|
+
if ("error" in r)
|
|
697
|
+
return errorContent(r.error);
|
|
698
|
+
return asContent(await scheduler.createSchedule(r));
|
|
699
|
+
},
|
|
597
700
|
});
|
|
598
|
-
|
|
701
|
+
tools.push({
|
|
702
|
+
name: "scheduler.listSchedules",
|
|
599
703
|
description: "内部工具:查询 planned 日程块用于排程冲突检查。" +
|
|
600
704
|
"⚠️ 用户「看今天/看日程/复盘/在干啥」请调 schedule.timeline(含矩阵+表格+全状态视图,不要调本工具)。" +
|
|
601
705
|
"本工具仅用于排程编排时的内部查询。",
|
|
@@ -604,29 +708,35 @@ export function buildServer(db) {
|
|
|
604
708
|
from: z.string().optional(),
|
|
605
709
|
to: z.string().optional(),
|
|
606
710
|
},
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
711
|
+
needsUserIdInjection: true,
|
|
712
|
+
handler: async (rawInput) => {
|
|
713
|
+
const r = injector(rawInput);
|
|
714
|
+
if ("error" in r)
|
|
715
|
+
return errorContent(r.error);
|
|
716
|
+
return asContent(await scheduler.listSchedules(r));
|
|
717
|
+
},
|
|
612
718
|
});
|
|
613
|
-
|
|
719
|
+
tools.push({
|
|
720
|
+
name: "scheduler.checkConflict",
|
|
614
721
|
description: "检查时间段是否与已有日程冲突",
|
|
615
722
|
inputSchema: {
|
|
616
723
|
user_id: z.string().optional(),
|
|
617
724
|
start_time: z.string(),
|
|
618
725
|
end_time: z.string(),
|
|
619
726
|
},
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
727
|
+
needsUserIdInjection: true,
|
|
728
|
+
handler: async (rawInput) => {
|
|
729
|
+
const r = injector(rawInput);
|
|
730
|
+
if ("error" in r)
|
|
731
|
+
return errorContent(r.error);
|
|
732
|
+
return asContent(await scheduler.checkConflict(r));
|
|
733
|
+
},
|
|
625
734
|
});
|
|
626
735
|
// ============================================================
|
|
627
|
-
// task (
|
|
736
|
+
// task (6 tools — S29 P2-3 + 事一 draft/commit, takes raw db)
|
|
628
737
|
// ============================================================
|
|
629
|
-
|
|
738
|
+
tools.push({
|
|
739
|
+
name: "task.create",
|
|
630
740
|
description: "建单个独立任务(不挂任何 timeblock)。⚠️ 拆解请用 decompose.split,本工具一次只能建一个任务、不能建子任务。",
|
|
631
741
|
// S33 v3: 用 z.object().strict() 显式拒 parent_id(zod 默认 strip 不报错,
|
|
632
742
|
// 严格模式让多余键触发 safeParse 失败 → MCP 返回 isError)。
|
|
@@ -639,30 +749,38 @@ export function buildServer(db) {
|
|
|
639
749
|
est_minutes: z.number().optional(),
|
|
640
750
|
})
|
|
641
751
|
.strict(),
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
752
|
+
needsUserIdInjection: true,
|
|
753
|
+
handler: async (rawInput) => {
|
|
754
|
+
const r = injector(rawInput);
|
|
755
|
+
if ("error" in r)
|
|
756
|
+
return errorContent(r.error);
|
|
757
|
+
return asContent(await task.createTask(r));
|
|
758
|
+
},
|
|
647
759
|
});
|
|
648
|
-
|
|
760
|
+
tools.push({
|
|
761
|
+
name: "task.get",
|
|
649
762
|
description: "按 id 获取任务(含递归子树)",
|
|
650
763
|
inputSchema: { id: z.string().min(1) },
|
|
651
|
-
|
|
652
|
-
|
|
764
|
+
handler: async ({ id }) => asContent(await task.getTask(id)),
|
|
765
|
+
});
|
|
766
|
+
tools.push({
|
|
767
|
+
name: "task.list",
|
|
653
768
|
description: "列出任务(parent_id=null 仅根;给定则该父的子;省略则全部)",
|
|
654
769
|
inputSchema: {
|
|
655
770
|
user_id: z.string().optional(),
|
|
656
771
|
target_user_id: z.string().min(1).optional(),
|
|
657
772
|
parent_id: z.string().nullable().optional(),
|
|
658
773
|
},
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
774
|
+
needsUserIdInjection: true,
|
|
775
|
+
handler: async (rawInput) => {
|
|
776
|
+
const r = injector(rawInput);
|
|
777
|
+
if ("error" in r)
|
|
778
|
+
return errorContent(r.error);
|
|
779
|
+
return asContent(await task.listTasks(r));
|
|
780
|
+
},
|
|
664
781
|
});
|
|
665
|
-
|
|
782
|
+
tools.push({
|
|
783
|
+
name: "task.update",
|
|
666
784
|
description: "更新任务(title/est_minutes/status;状态机校验非法流转)",
|
|
667
785
|
inputSchema: {
|
|
668
786
|
id: z.string().min(1),
|
|
@@ -670,25 +788,31 @@ export function buildServer(db) {
|
|
|
670
788
|
est_minutes: z.number().optional(),
|
|
671
789
|
status: z.string().optional(),
|
|
672
790
|
},
|
|
673
|
-
|
|
674
|
-
|
|
791
|
+
handler: async ({ id, ...rest }) => asContent(await task.updateTask(id, rest)),
|
|
792
|
+
});
|
|
793
|
+
tools.push({
|
|
794
|
+
name: "task.linkTimeblock",
|
|
675
795
|
description: "把任务关联到一个时间块(子任务开工执行时)",
|
|
676
796
|
inputSchema: {
|
|
677
797
|
task_id: z.string().min(1),
|
|
678
798
|
block_id: z.string().min(1),
|
|
679
799
|
},
|
|
680
|
-
|
|
681
|
-
|
|
800
|
+
handler: async ({ task_id, block_id }) => asContent(await task.linkTimeblock(task_id, block_id)),
|
|
801
|
+
});
|
|
802
|
+
tools.push({
|
|
803
|
+
name: "task.findPlan",
|
|
682
804
|
description: "按事件名 exact-match 查拆解计划:返回根任务 + 第一块未完成子任务(record 开工时链 task_id,驱动闭环正反馈)",
|
|
683
805
|
inputSchema: {
|
|
684
806
|
user_id: z.string().optional(),
|
|
685
807
|
title: z.string().min(1),
|
|
686
808
|
},
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
809
|
+
needsUserIdInjection: true,
|
|
810
|
+
handler: async (rawInput) => {
|
|
811
|
+
const r = injector(rawInput);
|
|
812
|
+
if ("error" in r)
|
|
813
|
+
return errorContent(r.error);
|
|
814
|
+
return asContent(await task.findPlan(r));
|
|
815
|
+
},
|
|
692
816
|
});
|
|
693
817
|
// S33 v3: task.draftPlan / task.commitPlan 从 MCP 工具列表移除(不再暴露给 mimo)。
|
|
694
818
|
// 拆解唯一入口 = decompose.split(见下方 decompose 段)。
|
|
@@ -698,7 +822,8 @@ export function buildServer(db) {
|
|
|
698
822
|
// decompose.split = 拆解唯一入口。状态机强制 draft→confirm→commit。
|
|
699
823
|
// 错误返回 isError content(不 throw),避 hermes 熔断器(v2 实证坑)。
|
|
700
824
|
// ============================================================
|
|
701
|
-
|
|
825
|
+
tools.push({
|
|
826
|
+
name: "decompose.split",
|
|
702
827
|
description: "拆解 timeblock 为结构化子任务树(拆解唯一入口)。第一次调用出方案(不写库),第二次带 confirm_token 落地。状态机强制 draft→confirm→commit。⚠️ 拆子任务请用本工具,不要用 task.create。",
|
|
703
828
|
inputSchema: {
|
|
704
829
|
user_id: z.string().optional(),
|
|
@@ -711,24 +836,75 @@ export function buildServer(db) {
|
|
|
711
836
|
.min(1),
|
|
712
837
|
confirm_token: z.string().optional(),
|
|
713
838
|
},
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
839
|
+
needsUserIdInjection: true,
|
|
840
|
+
handler: async (rawInput) => {
|
|
841
|
+
const r = injector(rawInput);
|
|
842
|
+
if ("error" in r)
|
|
843
|
+
return errorContent(r.error);
|
|
844
|
+
const result = await task.splitDecompose(r);
|
|
845
|
+
// DecomposeErrorResult → isError content(不 throw,避 hermes 熔断器)
|
|
846
|
+
if ("ok" in result && result.ok === false) {
|
|
847
|
+
return {
|
|
848
|
+
content: [
|
|
849
|
+
{
|
|
850
|
+
type: "text",
|
|
851
|
+
text: JSON.stringify(result),
|
|
852
|
+
},
|
|
853
|
+
],
|
|
854
|
+
isError: true,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
return asContent(result);
|
|
858
|
+
},
|
|
732
859
|
});
|
|
860
|
+
return tools;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Build the libero MCP server on a given DB connection.
|
|
864
|
+
* Tools throw Error on business violations; handlers let them propagate —
|
|
865
|
+
* the SDK turns thrown errors into MCP tool errors (isError) preserving the message.
|
|
866
|
+
*/
|
|
867
|
+
export function buildServer(db) {
|
|
868
|
+
// MV-COACH-3 Slice 2: resolve default user_id once at server build
|
|
869
|
+
const defaultUser = resolveDefaultUserId({ db, env: process.env });
|
|
870
|
+
logResolvedDefaultUserId(defaultUser);
|
|
871
|
+
const injector = makeUserIdInjector(defaultUser.userId);
|
|
872
|
+
// ── construct repos via MC-0a factory (default sqlite; postgres reserved) ──
|
|
873
|
+
const repos = createRepos(db);
|
|
874
|
+
// ── inject tool factories ──
|
|
875
|
+
const categories = createCategoryTools(repos.categoryRepo);
|
|
876
|
+
const tb = createTimeBlockTools(repos.timeblockRepo, db);
|
|
877
|
+
const stats = createStatsTools(repos.statsRepo);
|
|
878
|
+
const matrix = createMatrixTools(repos.statsRepo); // matrix reuses StatsRepo
|
|
879
|
+
const timeline = createTimelineTools(repos.timelineRepo);
|
|
880
|
+
const timer = createTimerTools(repos.timerRepo, repos.timeblockRepo, db);
|
|
881
|
+
const profile = createProfileTools(repos.profileRepo, repos.identityRepo);
|
|
882
|
+
const profileConfirm = createProfileConfirmTools(db);
|
|
883
|
+
const reminder = createReminderTools(repos.reminderRepo, undefined, db);
|
|
884
|
+
const scheduler = createSchedulerTools(repos.timeblockRepo, repos.reminderRepo, repos.categoryRepo);
|
|
885
|
+
const task = createTaskTools(db);
|
|
886
|
+
// instructions 必须放在第 2 参 ServerOptions(不是 serverInfo)—— SDK 只从 options 读
|
|
887
|
+
const server = new McpServer({ name: "libero-mcp", version: "0.2.3" }, { instructions: buildServerInstructions() });
|
|
888
|
+
// ── MC-0a: build registry once, register in a single traversal ──
|
|
889
|
+
const ctx = {
|
|
890
|
+
repos,
|
|
891
|
+
categories,
|
|
892
|
+
tb,
|
|
893
|
+
stats,
|
|
894
|
+
matrix,
|
|
895
|
+
timeline,
|
|
896
|
+
timer,
|
|
897
|
+
profile,
|
|
898
|
+
profileConfirm,
|
|
899
|
+
reminder,
|
|
900
|
+
scheduler,
|
|
901
|
+
task,
|
|
902
|
+
injector,
|
|
903
|
+
db,
|
|
904
|
+
};
|
|
905
|
+
const registry = buildToolRegistry(ctx);
|
|
906
|
+
for (const tool of registry) {
|
|
907
|
+
server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, tool.handler);
|
|
908
|
+
}
|
|
733
909
|
return server;
|
|
734
910
|
}
|