ex-brain 0.1.1 → 0.2.1
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 +48 -0
- package/package.json +2 -1
- package/src/ai/compiler.ts +18 -53
- package/src/ai/entity-link.ts +31 -62
- package/src/ai/llm-client.ts +291 -0
- package/src/ai/timeline-extractor.ts +29 -62
- package/src/commands/index.ts +612 -86
- package/src/db/client.ts +121 -15
- package/src/db/errors.ts +178 -0
- package/src/db/schema.ts +1 -0
- package/src/mcp/server.ts +400 -237
- package/src/repositories/brain-repo.ts +576 -358
- package/src/settings.ts +23 -2
- package/src/types/index.ts +1 -0
- package/src/utils/cli-output.ts +569 -0
- package/src/utils/query-sanitizer.ts +63 -0
package/src/mcp/server.ts
CHANGED
|
@@ -5,6 +5,124 @@ import { BrainDb } from "../db/client";
|
|
|
5
5
|
import { BrainRepository } from "../repositories/brain-repo";
|
|
6
6
|
import { loadSettings } from "../settings";
|
|
7
7
|
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Error Handling Utilities
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
interface ToolError {
|
|
13
|
+
tool: string;
|
|
14
|
+
error: string;
|
|
15
|
+
message: string;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
recoverable: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatError(toolName: string, error: unknown): ToolError {
|
|
21
|
+
const err = error as Error;
|
|
22
|
+
const errorType = err?.name ?? "UnknownError";
|
|
23
|
+
const errorMessage = err?.message ?? String(error);
|
|
24
|
+
|
|
25
|
+
// 判断是否可恢复
|
|
26
|
+
const recoverablePatterns = [
|
|
27
|
+
"ECONNREFUSED",
|
|
28
|
+
"timeout",
|
|
29
|
+
"ETIMEDOUT",
|
|
30
|
+
"rate limit",
|
|
31
|
+
"429",
|
|
32
|
+
"503",
|
|
33
|
+
"502",
|
|
34
|
+
"timeout",
|
|
35
|
+
];
|
|
36
|
+
const isRecoverable = recoverablePatterns.some(p =>
|
|
37
|
+
errorMessage.toLowerCase().includes(p.toLowerCase())
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
tool: toolName,
|
|
42
|
+
error: errorType,
|
|
43
|
+
message: errorMessage,
|
|
44
|
+
timestamp: new Date().toISOString(),
|
|
45
|
+
recoverable: isRecoverable,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function logError(toolName: string, error: unknown, params?: Record<string, unknown>): void {
|
|
50
|
+
const errInfo = formatError(toolName, error);
|
|
51
|
+
console.error(`[MCP Error] Tool: ${toolName}`);
|
|
52
|
+
console.error(` Type: ${errInfo.error}`);
|
|
53
|
+
console.error(` Message: ${errInfo.message}`);
|
|
54
|
+
console.error(` Recoverable: ${errInfo.recoverable}`);
|
|
55
|
+
if (params) {
|
|
56
|
+
console.error(` Params: ${JSON.stringify(params)}`);
|
|
57
|
+
}
|
|
58
|
+
console.error(` Timestamp: ${errInfo.timestamp}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 包装工具 handler,添加错误处理
|
|
63
|
+
* 确保工具错误不会导致 MCP Server 崩溃,返回友好的 JSON 错误信息
|
|
64
|
+
*/
|
|
65
|
+
function withErrorHandling<T extends Record<string, unknown>>(
|
|
66
|
+
toolName: string,
|
|
67
|
+
handler: (params: T) => Promise<{ content: Array<{ type: string; text: string }> }>
|
|
68
|
+
) {
|
|
69
|
+
return async (params: T): Promise<{ content: Array<{ type: string; text: string }> }> => {
|
|
70
|
+
try {
|
|
71
|
+
return await handler(params);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
logError(toolName, error, params);
|
|
74
|
+
const errInfo = formatError(toolName, error);
|
|
75
|
+
return {
|
|
76
|
+
content: [
|
|
77
|
+
{
|
|
78
|
+
type: "text" as const,
|
|
79
|
+
text: JSON.stringify({
|
|
80
|
+
ok: false,
|
|
81
|
+
error: errInfo.error,
|
|
82
|
+
message: errInfo.message,
|
|
83
|
+
recoverable: errInfo.recoverable,
|
|
84
|
+
hint: errInfo.recoverable
|
|
85
|
+
? "This is a temporary error. Please try again later."
|
|
86
|
+
: "Please check the input parameters or system configuration.",
|
|
87
|
+
tool: toolName,
|
|
88
|
+
}, null, 2),
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 资源错误处理包装
|
|
97
|
+
function withResourceErrorHandling<T extends Record<string, string>>(
|
|
98
|
+
resourceName: string,
|
|
99
|
+
handler: (uri: URL, vars: T) => Promise<{ contents: Array<{ uri: string; mimeType: string; text: string }> }>
|
|
100
|
+
) {
|
|
101
|
+
return async (uri: URL, vars: T): Promise<{ contents: Array<{ uri: string; mimeType: string; text: string }> }> => {
|
|
102
|
+
try {
|
|
103
|
+
return await handler(uri, vars);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logError(resourceName, error, vars as unknown as Record<string, unknown>);
|
|
106
|
+
const errInfo = formatError(resourceName, error);
|
|
107
|
+
return {
|
|
108
|
+
contents: [
|
|
109
|
+
{
|
|
110
|
+
uri: uri.href,
|
|
111
|
+
mimeType: "application/json",
|
|
112
|
+
text: JSON.stringify({
|
|
113
|
+
ok: false,
|
|
114
|
+
error: errInfo.error,
|
|
115
|
+
message: errInfo.message,
|
|
116
|
+
recoverable: errInfo.recoverable,
|
|
117
|
+
resource: resourceName,
|
|
118
|
+
}, null, 2),
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
8
126
|
export const TOOL_MANIFEST = [
|
|
9
127
|
"brain_search",
|
|
10
128
|
"brain_query",
|
|
@@ -38,6 +156,20 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
38
156
|
// Search & Query Tools
|
|
39
157
|
// ---------------------------------------------------------------------------
|
|
40
158
|
|
|
159
|
+
// Tool handler functions (wrapped with error handling below)
|
|
160
|
+
const brainSearchHandler = async ({ query, type, limit }: { query: string; type?: string; limit?: number }) => ({
|
|
161
|
+
content: [
|
|
162
|
+
{
|
|
163
|
+
type: "text",
|
|
164
|
+
text: JSON.stringify(
|
|
165
|
+
await repo.search(query, limit ?? 10, type),
|
|
166
|
+
null,
|
|
167
|
+
2,
|
|
168
|
+
),
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
|
|
41
173
|
server.registerTool(
|
|
42
174
|
"brain_search",
|
|
43
175
|
{
|
|
@@ -48,20 +180,18 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
48
180
|
limit: z.number().int().positive().max(50).optional(),
|
|
49
181
|
}),
|
|
50
182
|
},
|
|
51
|
-
|
|
52
|
-
content: [
|
|
53
|
-
{
|
|
54
|
-
type: "text",
|
|
55
|
-
text: JSON.stringify(
|
|
56
|
-
await repo.search(query, limit ?? 10, type),
|
|
57
|
-
null,
|
|
58
|
-
2,
|
|
59
|
-
),
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
}),
|
|
183
|
+
withErrorHandling("brain_search", brainSearchHandler),
|
|
63
184
|
);
|
|
64
185
|
|
|
186
|
+
const brainQueryHandler = async ({ question, limit }: { question: string; limit?: number }) => ({
|
|
187
|
+
content: [
|
|
188
|
+
{
|
|
189
|
+
type: "text",
|
|
190
|
+
text: JSON.stringify(await repo.query(question, limit ?? 10), null, 2),
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
|
|
65
195
|
server.registerTool(
|
|
66
196
|
"brain_query",
|
|
67
197
|
{
|
|
@@ -71,33 +201,39 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
71
201
|
limit: z.number().int().positive().max(50).optional(),
|
|
72
202
|
}),
|
|
73
203
|
},
|
|
74
|
-
|
|
75
|
-
content: [
|
|
76
|
-
{
|
|
77
|
-
type: "text",
|
|
78
|
-
text: JSON.stringify(await repo.query(question, limit ?? 10), null, 2),
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
}),
|
|
204
|
+
withErrorHandling("brain_query", brainQueryHandler),
|
|
82
205
|
);
|
|
83
206
|
|
|
84
207
|
// ---------------------------------------------------------------------------
|
|
85
208
|
// Page CRUD Tools
|
|
86
209
|
// ---------------------------------------------------------------------------
|
|
87
210
|
|
|
211
|
+
const brainGetHandler = async ({ slug }: { slug: string }) => ({
|
|
212
|
+
content: [
|
|
213
|
+
{ type: "text", text: JSON.stringify(await repo.getPage(slug), null, 2) },
|
|
214
|
+
],
|
|
215
|
+
});
|
|
216
|
+
|
|
88
217
|
server.registerTool(
|
|
89
218
|
"brain_get",
|
|
90
219
|
{
|
|
91
220
|
description: "Read a page and return its full content",
|
|
92
221
|
inputSchema: z.object({ slug: z.string() }),
|
|
93
222
|
},
|
|
94
|
-
|
|
95
|
-
content: [
|
|
96
|
-
{ type: "text", text: JSON.stringify(await repo.getPage(slug), null, 2) },
|
|
97
|
-
],
|
|
98
|
-
}),
|
|
223
|
+
withErrorHandling("brain_get", brainGetHandler),
|
|
99
224
|
);
|
|
100
225
|
|
|
226
|
+
const brainPutHandler = async ({ slug, content, type, title }: { slug: string; content: string; type?: string; title?: string }) => {
|
|
227
|
+
const page = await repo.putPage({
|
|
228
|
+
slug,
|
|
229
|
+
type: type ?? "note",
|
|
230
|
+
title: title ?? slug,
|
|
231
|
+
compiledTruth: content,
|
|
232
|
+
timeline: "",
|
|
233
|
+
});
|
|
234
|
+
return { content: [{ type: "text", text: JSON.stringify(page, null, 2) }] };
|
|
235
|
+
};
|
|
236
|
+
|
|
101
237
|
server.registerTool(
|
|
102
238
|
"brain_put",
|
|
103
239
|
{
|
|
@@ -109,30 +245,37 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
109
245
|
title: z.string().optional(),
|
|
110
246
|
}),
|
|
111
247
|
},
|
|
112
|
-
|
|
113
|
-
const page = await repo.putPage({
|
|
114
|
-
slug,
|
|
115
|
-
type: type ?? "note",
|
|
116
|
-
title: title ?? slug,
|
|
117
|
-
compiledTruth: content,
|
|
118
|
-
timeline: "",
|
|
119
|
-
});
|
|
120
|
-
return { content: [{ type: "text", text: JSON.stringify(page, null, 2) }] };
|
|
121
|
-
},
|
|
248
|
+
withErrorHandling("brain_put", brainPutHandler),
|
|
122
249
|
);
|
|
123
250
|
|
|
251
|
+
const brainDeleteHandler = async ({ slug }: { slug: string }) => {
|
|
252
|
+
await repo.deletePage(slug);
|
|
253
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: true, action: "delete", slug }) }] };
|
|
254
|
+
};
|
|
255
|
+
|
|
124
256
|
server.registerTool(
|
|
125
257
|
"brain_delete",
|
|
126
258
|
{
|
|
127
259
|
description: "Delete a page and all its related data (links, tags, timeline, raw)",
|
|
128
260
|
inputSchema: z.object({ slug: z.string() }),
|
|
129
261
|
},
|
|
130
|
-
|
|
131
|
-
await repo.deletePage(slug);
|
|
132
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: true, action: "delete", slug }) }] };
|
|
133
|
-
},
|
|
262
|
+
withErrorHandling("brain_delete", brainDeleteHandler),
|
|
134
263
|
);
|
|
135
264
|
|
|
265
|
+
const brainIngestHandler = async ({ content, source_type, source_ref }: { content: string; source_type: string; source_ref: string }) => {
|
|
266
|
+
const safeRef = source_ref.replace(/[^a-zA-Z0-9/_-]+/g, "_").slice(0, 200);
|
|
267
|
+
const slug = `ingest/${safeRef || "untitled"}`;
|
|
268
|
+
const page = await repo.putPage({
|
|
269
|
+
slug,
|
|
270
|
+
type: source_type,
|
|
271
|
+
title: source_ref,
|
|
272
|
+
compiledTruth: content,
|
|
273
|
+
timeline: "",
|
|
274
|
+
frontmatter: { source_type, source_ref },
|
|
275
|
+
});
|
|
276
|
+
return { content: [{ type: "text", text: JSON.stringify(page, null, 2) }] };
|
|
277
|
+
};
|
|
278
|
+
|
|
136
279
|
server.registerTool(
|
|
137
280
|
"brain_ingest",
|
|
138
281
|
{
|
|
@@ -143,25 +286,18 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
143
286
|
source_ref: z.string(),
|
|
144
287
|
}),
|
|
145
288
|
},
|
|
146
|
-
|
|
147
|
-
const safeRef = source_ref.replace(/[^a-zA-Z0-9/_-]+/g, "_").slice(0, 200);
|
|
148
|
-
const slug = `ingest/${safeRef || "untitled"}`;
|
|
149
|
-
const page = await repo.putPage({
|
|
150
|
-
slug,
|
|
151
|
-
type: source_type,
|
|
152
|
-
title: source_ref,
|
|
153
|
-
compiledTruth: content,
|
|
154
|
-
timeline: "",
|
|
155
|
-
frontmatter: { source_type, source_ref },
|
|
156
|
-
});
|
|
157
|
-
return { content: [{ type: "text", text: JSON.stringify(page, null, 2) }] };
|
|
158
|
-
},
|
|
289
|
+
withErrorHandling("brain_ingest", brainIngestHandler),
|
|
159
290
|
);
|
|
160
291
|
|
|
161
292
|
// ---------------------------------------------------------------------------
|
|
162
293
|
// Link Tools
|
|
163
294
|
// ---------------------------------------------------------------------------
|
|
164
295
|
|
|
296
|
+
const brainLinkHandler = async ({ from, to, context }: { from: string; to: string; context?: string }) => {
|
|
297
|
+
await repo.link(from, to, context ?? "");
|
|
298
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: true }) }] };
|
|
299
|
+
};
|
|
300
|
+
|
|
165
301
|
server.registerTool(
|
|
166
302
|
"brain_link",
|
|
167
303
|
{
|
|
@@ -172,29 +308,37 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
172
308
|
context: z.string().optional(),
|
|
173
309
|
}),
|
|
174
310
|
},
|
|
175
|
-
|
|
176
|
-
await repo.link(from, to, context ?? "");
|
|
177
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: true }) }] };
|
|
178
|
-
},
|
|
311
|
+
withErrorHandling("brain_link", brainLinkHandler),
|
|
179
312
|
);
|
|
180
313
|
|
|
314
|
+
const brainBacklinksHandler = async ({ slug }: { slug: string }) => ({
|
|
315
|
+
content: [
|
|
316
|
+
{ type: "text", text: JSON.stringify(await repo.backlinks(slug), null, 2) },
|
|
317
|
+
],
|
|
318
|
+
});
|
|
319
|
+
|
|
181
320
|
server.registerTool(
|
|
182
321
|
"brain_backlinks",
|
|
183
322
|
{
|
|
184
323
|
description: "List pages that link to this page",
|
|
185
324
|
inputSchema: z.object({ slug: z.string() }),
|
|
186
325
|
},
|
|
187
|
-
|
|
188
|
-
content: [
|
|
189
|
-
{ type: "text", text: JSON.stringify(await repo.backlinks(slug), null, 2) },
|
|
190
|
-
],
|
|
191
|
-
}),
|
|
326
|
+
withErrorHandling("brain_backlinks", brainBacklinksHandler),
|
|
192
327
|
);
|
|
193
328
|
|
|
194
329
|
// ---------------------------------------------------------------------------
|
|
195
330
|
// Timeline Tools (Enhanced)
|
|
196
331
|
// ---------------------------------------------------------------------------
|
|
197
332
|
|
|
333
|
+
const brainTimelineHandler = async ({ slug, limit }: { slug: string; limit?: number }) => ({
|
|
334
|
+
content: [
|
|
335
|
+
{
|
|
336
|
+
type: "text",
|
|
337
|
+
text: JSON.stringify(await repo.timeline(slug, limit ?? 50), null, 2),
|
|
338
|
+
},
|
|
339
|
+
],
|
|
340
|
+
});
|
|
341
|
+
|
|
198
342
|
server.registerTool(
|
|
199
343
|
"brain_timeline",
|
|
200
344
|
{
|
|
@@ -204,16 +348,20 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
204
348
|
limit: z.number().int().positive().max(200).optional(),
|
|
205
349
|
}),
|
|
206
350
|
},
|
|
207
|
-
|
|
208
|
-
content: [
|
|
209
|
-
{
|
|
210
|
-
type: "text",
|
|
211
|
-
text: JSON.stringify(await repo.timeline(slug, limit ?? 50), null, 2),
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
}),
|
|
351
|
+
withErrorHandling("brain_timeline", brainTimelineHandler),
|
|
215
352
|
);
|
|
216
353
|
|
|
354
|
+
const brainTimelineAddHandler = async ({ slug, date, summary, source, detail }: { slug: string; date: string; summary: string; source?: string; detail?: string }) => {
|
|
355
|
+
await repo.timelineAdd({
|
|
356
|
+
pageSlug: slug,
|
|
357
|
+
date,
|
|
358
|
+
summary,
|
|
359
|
+
source: source ?? "manual",
|
|
360
|
+
detail: detail ?? "",
|
|
361
|
+
});
|
|
362
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: true }) }] };
|
|
363
|
+
};
|
|
364
|
+
|
|
217
365
|
server.registerTool(
|
|
218
366
|
"brain_timeline_add",
|
|
219
367
|
{
|
|
@@ -226,18 +374,18 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
226
374
|
detail: z.string().optional().describe("Optional markdown detail"),
|
|
227
375
|
}),
|
|
228
376
|
},
|
|
229
|
-
|
|
230
|
-
await repo.timelineAdd({
|
|
231
|
-
pageSlug: slug,
|
|
232
|
-
date,
|
|
233
|
-
summary,
|
|
234
|
-
source: source ?? "manual",
|
|
235
|
-
detail: detail ?? "",
|
|
236
|
-
});
|
|
237
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: true }) }] };
|
|
238
|
-
},
|
|
377
|
+
withErrorHandling("brain_timeline_add", brainTimelineAddHandler),
|
|
239
378
|
);
|
|
240
379
|
|
|
380
|
+
const brainTimelineListHandler = async ({ limit }: { limit?: number }) => ({
|
|
381
|
+
content: [
|
|
382
|
+
{
|
|
383
|
+
type: "text",
|
|
384
|
+
text: JSON.stringify(await repo.timelineGlobal(limit ?? 100), null, 2),
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
});
|
|
388
|
+
|
|
241
389
|
server.registerTool(
|
|
242
390
|
"brain_timeline_list",
|
|
243
391
|
{
|
|
@@ -246,16 +394,14 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
246
394
|
limit: z.number().int().positive().max(200).optional(),
|
|
247
395
|
}),
|
|
248
396
|
},
|
|
249
|
-
|
|
250
|
-
content: [
|
|
251
|
-
{
|
|
252
|
-
type: "text",
|
|
253
|
-
text: JSON.stringify(await repo.timelineGlobal(limit ?? 100), null, 2),
|
|
254
|
-
},
|
|
255
|
-
],
|
|
256
|
-
}),
|
|
397
|
+
withErrorHandling("brain_timeline_list", brainTimelineListHandler),
|
|
257
398
|
);
|
|
258
399
|
|
|
400
|
+
const brainTimelineDeleteHandler = async ({ id }: { id: number }) => {
|
|
401
|
+
await repo.timelineDelete(id);
|
|
402
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: true, action: "timeline-delete", id }) }] };
|
|
403
|
+
};
|
|
404
|
+
|
|
259
405
|
server.registerTool(
|
|
260
406
|
"brain_timeline_delete",
|
|
261
407
|
{
|
|
@@ -264,12 +410,32 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
264
410
|
id: z.number().int().positive().describe("Timeline entry ID to delete"),
|
|
265
411
|
}),
|
|
266
412
|
},
|
|
267
|
-
|
|
268
|
-
await repo.timelineDelete(id);
|
|
269
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: true, action: "timeline-delete", id }) }] };
|
|
270
|
-
},
|
|
413
|
+
withErrorHandling("brain_timeline_delete", brainTimelineDeleteHandler),
|
|
271
414
|
);
|
|
272
415
|
|
|
416
|
+
const brainTimelineExtractHandler = async ({ slug, content, source, default_date }: { slug: string; content: string; source?: string; default_date?: string }) => {
|
|
417
|
+
const result = await repo.extractAndAddTimeline(
|
|
418
|
+
slug,
|
|
419
|
+
content,
|
|
420
|
+
source ?? "extracted",
|
|
421
|
+
default_date ?? new Date().toISOString().slice(0, 10),
|
|
422
|
+
settings.llm,
|
|
423
|
+
);
|
|
424
|
+
return {
|
|
425
|
+
content: [
|
|
426
|
+
{
|
|
427
|
+
type: "text",
|
|
428
|
+
text: JSON.stringify({
|
|
429
|
+
ok: true,
|
|
430
|
+
entriesAdded: result.entries.length,
|
|
431
|
+
entries: result.entries,
|
|
432
|
+
confidence: result.confidence,
|
|
433
|
+
}, null, 2),
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
};
|
|
437
|
+
};
|
|
438
|
+
|
|
273
439
|
server.registerTool(
|
|
274
440
|
"brain_timeline_extract",
|
|
275
441
|
{
|
|
@@ -281,34 +447,40 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
281
447
|
default_date: z.string().optional().describe("Default date (YYYY-MM-DD) for entries without explicit dates"),
|
|
282
448
|
}),
|
|
283
449
|
},
|
|
284
|
-
|
|
285
|
-
const result = await repo.extractAndAddTimeline(
|
|
286
|
-
slug,
|
|
287
|
-
content,
|
|
288
|
-
source ?? "extracted",
|
|
289
|
-
default_date ?? new Date().toISOString().slice(0, 10),
|
|
290
|
-
settings.llm,
|
|
291
|
-
);
|
|
292
|
-
return {
|
|
293
|
-
content: [
|
|
294
|
-
{
|
|
295
|
-
type: "text",
|
|
296
|
-
text: JSON.stringify({
|
|
297
|
-
ok: true,
|
|
298
|
-
entriesAdded: result.entries.length,
|
|
299
|
-
entries: result.entries,
|
|
300
|
-
confidence: result.confidence,
|
|
301
|
-
}, null, 2),
|
|
302
|
-
},
|
|
303
|
-
],
|
|
304
|
-
};
|
|
305
|
-
},
|
|
450
|
+
withErrorHandling("brain_timeline_extract", brainTimelineExtractHandler),
|
|
306
451
|
);
|
|
307
452
|
|
|
308
453
|
// ---------------------------------------------------------------------------
|
|
309
454
|
// Smart Compilation Tools (Core Brain Function)
|
|
310
455
|
// ---------------------------------------------------------------------------
|
|
311
456
|
|
|
457
|
+
const brainCompileHandler = async ({ slug, new_info, source, date }: { slug: string; new_info: string; source?: string; date?: string }) => {
|
|
458
|
+
const result = await repo.compilePage(
|
|
459
|
+
slug,
|
|
460
|
+
new_info,
|
|
461
|
+
source ?? "user",
|
|
462
|
+
date ?? new Date().toISOString().slice(0, 10),
|
|
463
|
+
settings.llm,
|
|
464
|
+
);
|
|
465
|
+
return {
|
|
466
|
+
content: [
|
|
467
|
+
{
|
|
468
|
+
type: "text",
|
|
469
|
+
text: JSON.stringify({
|
|
470
|
+
ok: true,
|
|
471
|
+
slug,
|
|
472
|
+
changed: result.changed,
|
|
473
|
+
changeType: result.changeType,
|
|
474
|
+
changeSummary: result.changeSummary,
|
|
475
|
+
timelineEntriesAdded: result.timelineEntries.length,
|
|
476
|
+
confidence: result.confidence,
|
|
477
|
+
compiledTruthPreview: result.compiledTruth.slice(0, 500),
|
|
478
|
+
}, null, 2),
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
};
|
|
482
|
+
};
|
|
483
|
+
|
|
312
484
|
server.registerTool(
|
|
313
485
|
"brain_compile",
|
|
314
486
|
{
|
|
@@ -320,34 +492,41 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
320
492
|
date: z.string().optional().describe("Date of information (YYYY-MM-DD)"),
|
|
321
493
|
}),
|
|
322
494
|
},
|
|
323
|
-
|
|
324
|
-
const result = await repo.compilePage(
|
|
325
|
-
slug,
|
|
326
|
-
new_info,
|
|
327
|
-
source ?? "user",
|
|
328
|
-
date ?? new Date().toISOString().slice(0, 10),
|
|
329
|
-
settings.llm,
|
|
330
|
-
);
|
|
331
|
-
return {
|
|
332
|
-
content: [
|
|
333
|
-
{
|
|
334
|
-
type: "text",
|
|
335
|
-
text: JSON.stringify({
|
|
336
|
-
ok: true,
|
|
337
|
-
slug,
|
|
338
|
-
changed: result.changed,
|
|
339
|
-
changeType: result.changeType,
|
|
340
|
-
changeSummary: result.changeSummary,
|
|
341
|
-
timelineEntriesAdded: result.timelineEntries.length,
|
|
342
|
-
confidence: result.confidence,
|
|
343
|
-
compiledTruthPreview: result.compiledTruth.slice(0, 500),
|
|
344
|
-
}, null, 2),
|
|
345
|
-
},
|
|
346
|
-
],
|
|
347
|
-
};
|
|
348
|
-
},
|
|
495
|
+
withErrorHandling("brain_compile", brainCompileHandler),
|
|
349
496
|
);
|
|
350
497
|
|
|
498
|
+
const brainSmartIngestHandler = async ({ slug, content, source, type }: { slug: string; content: string; source?: string; type?: string }) => {
|
|
499
|
+
const result = await repo.ingestContent(
|
|
500
|
+
slug,
|
|
501
|
+
content,
|
|
502
|
+
source ?? "ingest",
|
|
503
|
+
type ?? "note",
|
|
504
|
+
settings.llm,
|
|
505
|
+
);
|
|
506
|
+
return {
|
|
507
|
+
content: [
|
|
508
|
+
{
|
|
509
|
+
type: "text",
|
|
510
|
+
text: JSON.stringify({
|
|
511
|
+
ok: true,
|
|
512
|
+
slug: result.page.slug,
|
|
513
|
+
compileResult: {
|
|
514
|
+
changed: result.compileResult.changed,
|
|
515
|
+
changeType: result.compileResult.changeType,
|
|
516
|
+
changeSummary: result.compileResult.changeSummary,
|
|
517
|
+
confidence: result.compileResult.confidence,
|
|
518
|
+
},
|
|
519
|
+
timelineResult: {
|
|
520
|
+
entriesAdded: result.timelineResult.entries.length,
|
|
521
|
+
confidence: result.timelineResult.confidence,
|
|
522
|
+
},
|
|
523
|
+
updatedAt: result.page.updatedAt,
|
|
524
|
+
}, null, 2),
|
|
525
|
+
},
|
|
526
|
+
],
|
|
527
|
+
};
|
|
528
|
+
};
|
|
529
|
+
|
|
351
530
|
server.registerTool(
|
|
352
531
|
"brain_smart_ingest",
|
|
353
532
|
{
|
|
@@ -359,56 +538,37 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
359
538
|
type: z.string().optional().describe("Page type (person, company, project, note, etc.)"),
|
|
360
539
|
}),
|
|
361
540
|
},
|
|
362
|
-
|
|
363
|
-
const result = await repo.ingestContent(
|
|
364
|
-
slug,
|
|
365
|
-
content,
|
|
366
|
-
source ?? "ingest",
|
|
367
|
-
type ?? "note",
|
|
368
|
-
settings.llm,
|
|
369
|
-
);
|
|
370
|
-
return {
|
|
371
|
-
content: [
|
|
372
|
-
{
|
|
373
|
-
type: "text",
|
|
374
|
-
text: JSON.stringify({
|
|
375
|
-
ok: true,
|
|
376
|
-
slug: result.page.slug,
|
|
377
|
-
compileResult: {
|
|
378
|
-
changed: result.compileResult.changed,
|
|
379
|
-
changeType: result.compileResult.changeType,
|
|
380
|
-
changeSummary: result.compileResult.changeSummary,
|
|
381
|
-
confidence: result.compileResult.confidence,
|
|
382
|
-
},
|
|
383
|
-
timelineResult: {
|
|
384
|
-
entriesAdded: result.timelineResult.entries.length,
|
|
385
|
-
confidence: result.timelineResult.confidence,
|
|
386
|
-
},
|
|
387
|
-
updatedAt: result.page.updatedAt,
|
|
388
|
-
}, null, 2),
|
|
389
|
-
},
|
|
390
|
-
],
|
|
391
|
-
};
|
|
392
|
-
},
|
|
541
|
+
withErrorHandling("brain_smart_ingest", brainSmartIngestHandler),
|
|
393
542
|
);
|
|
394
543
|
|
|
395
544
|
// ---------------------------------------------------------------------------
|
|
396
545
|
// Tag Tools
|
|
397
546
|
// ---------------------------------------------------------------------------
|
|
398
547
|
|
|
548
|
+
const brainTagsHandler = async ({ slug }: { slug: string }) => ({
|
|
549
|
+
content: [
|
|
550
|
+
{ type: "text", text: JSON.stringify(await repo.tags(slug), null, 2) },
|
|
551
|
+
],
|
|
552
|
+
});
|
|
553
|
+
|
|
399
554
|
server.registerTool(
|
|
400
555
|
"brain_tags",
|
|
401
556
|
{
|
|
402
557
|
description: "List tags on a page",
|
|
403
558
|
inputSchema: z.object({ slug: z.string() }),
|
|
404
559
|
},
|
|
405
|
-
|
|
406
|
-
content: [
|
|
407
|
-
{ type: "text", text: JSON.stringify(await repo.tags(slug), null, 2) },
|
|
408
|
-
],
|
|
409
|
-
}),
|
|
560
|
+
withErrorHandling("brain_tags", brainTagsHandler),
|
|
410
561
|
);
|
|
411
562
|
|
|
563
|
+
const brainTagHandler = async ({ slug, tag, remove }: { slug: string; tag: string; remove?: boolean }) => {
|
|
564
|
+
if (remove) {
|
|
565
|
+
await repo.untag(slug, tag);
|
|
566
|
+
} else {
|
|
567
|
+
await repo.tag(slug, tag);
|
|
568
|
+
}
|
|
569
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: true }) }] };
|
|
570
|
+
};
|
|
571
|
+
|
|
412
572
|
server.registerTool(
|
|
413
573
|
"brain_tag",
|
|
414
574
|
{
|
|
@@ -419,20 +579,26 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
419
579
|
remove: z.boolean().optional(),
|
|
420
580
|
}),
|
|
421
581
|
},
|
|
422
|
-
|
|
423
|
-
if (remove) {
|
|
424
|
-
await repo.untag(slug, tag);
|
|
425
|
-
} else {
|
|
426
|
-
await repo.tag(slug, tag);
|
|
427
|
-
}
|
|
428
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: true }) }] };
|
|
429
|
-
},
|
|
582
|
+
withErrorHandling("brain_tag", brainTagHandler),
|
|
430
583
|
);
|
|
431
584
|
|
|
432
585
|
// ---------------------------------------------------------------------------
|
|
433
586
|
// Query & List Tools
|
|
434
587
|
// ---------------------------------------------------------------------------
|
|
435
588
|
|
|
589
|
+
const brainListHandler = async ({ type, tag, limit }: { type?: string; tag?: string; limit?: number }) => ({
|
|
590
|
+
content: [
|
|
591
|
+
{
|
|
592
|
+
type: "text",
|
|
593
|
+
text: JSON.stringify(
|
|
594
|
+
await repo.listPages({ type, tag, limit: limit ?? 50 }),
|
|
595
|
+
null,
|
|
596
|
+
2,
|
|
597
|
+
),
|
|
598
|
+
},
|
|
599
|
+
],
|
|
600
|
+
});
|
|
601
|
+
|
|
436
602
|
server.registerTool(
|
|
437
603
|
"brain_list",
|
|
438
604
|
{
|
|
@@ -443,28 +609,34 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
443
609
|
limit: z.number().int().positive().optional(),
|
|
444
610
|
}),
|
|
445
611
|
},
|
|
446
|
-
|
|
447
|
-
content: [
|
|
448
|
-
{
|
|
449
|
-
type: "text",
|
|
450
|
-
text: JSON.stringify(
|
|
451
|
-
await repo.listPages({ type, tag, limit: limit ?? 50 }),
|
|
452
|
-
null,
|
|
453
|
-
2,
|
|
454
|
-
),
|
|
455
|
-
},
|
|
456
|
-
],
|
|
457
|
-
}),
|
|
612
|
+
withErrorHandling("brain_list", brainListHandler),
|
|
458
613
|
);
|
|
459
614
|
|
|
615
|
+
const brainStatsHandler = async () => ({
|
|
616
|
+
content: [{ type: "text", text: JSON.stringify(await repo.stats(), null, 2) }],
|
|
617
|
+
});
|
|
618
|
+
|
|
460
619
|
server.registerTool(
|
|
461
620
|
"brain_stats",
|
|
462
621
|
{ description: "Show knowledge base statistics", inputSchema: z.object({}) },
|
|
463
|
-
|
|
464
|
-
content: [{ type: "text", text: JSON.stringify(await repo.stats(), null, 2) }],
|
|
465
|
-
}),
|
|
622
|
+
withErrorHandling("brain_stats", brainStatsHandler),
|
|
466
623
|
);
|
|
467
624
|
|
|
625
|
+
const brainRawHandler = async ({ slug, source, data }: { slug: string; source?: string; data?: unknown }) => {
|
|
626
|
+
if (data !== undefined) {
|
|
627
|
+
await repo.writeRaw(slug, source ?? "manual", data);
|
|
628
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: true }) }] };
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
content: [
|
|
632
|
+
{
|
|
633
|
+
type: "text",
|
|
634
|
+
text: JSON.stringify(await repo.readRaw(slug, source), null, 2),
|
|
635
|
+
},
|
|
636
|
+
],
|
|
637
|
+
};
|
|
638
|
+
};
|
|
639
|
+
|
|
468
640
|
server.registerTool(
|
|
469
641
|
"brain_raw",
|
|
470
642
|
{
|
|
@@ -475,44 +647,47 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
475
647
|
data: z.unknown().optional(),
|
|
476
648
|
}),
|
|
477
649
|
},
|
|
478
|
-
|
|
479
|
-
if (data !== undefined) {
|
|
480
|
-
await repo.writeRaw(slug, source ?? "manual", data);
|
|
481
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: true }) }] };
|
|
482
|
-
}
|
|
483
|
-
return {
|
|
484
|
-
content: [
|
|
485
|
-
{
|
|
486
|
-
type: "text",
|
|
487
|
-
text: JSON.stringify(await repo.readRaw(slug, source), null, 2),
|
|
488
|
-
},
|
|
489
|
-
],
|
|
490
|
-
};
|
|
491
|
-
},
|
|
650
|
+
withErrorHandling("brain_raw", brainRawHandler),
|
|
492
651
|
);
|
|
493
652
|
|
|
494
653
|
// ---------------------------------------------------------------------------
|
|
495
654
|
// Resources
|
|
496
655
|
// ---------------------------------------------------------------------------
|
|
497
656
|
|
|
657
|
+
const brainIndexHandler = async () => {
|
|
658
|
+
const slugs = await repo.allSlugs();
|
|
659
|
+
return {
|
|
660
|
+
contents: [
|
|
661
|
+
{
|
|
662
|
+
uri: "brain://index",
|
|
663
|
+
mimeType: "text/plain",
|
|
664
|
+
text: slugs.join("\n"),
|
|
665
|
+
},
|
|
666
|
+
],
|
|
667
|
+
};
|
|
668
|
+
};
|
|
669
|
+
|
|
498
670
|
server.registerResource(
|
|
499
671
|
"brain-index",
|
|
500
672
|
"brain://index",
|
|
501
673
|
{ title: "Brain Index", description: "All page slugs grouped in plain list." },
|
|
502
|
-
|
|
503
|
-
const slugs = await repo.allSlugs();
|
|
504
|
-
return {
|
|
505
|
-
contents: [
|
|
506
|
-
{
|
|
507
|
-
uri: "brain://index",
|
|
508
|
-
mimeType: "text/plain",
|
|
509
|
-
text: slugs.join("\n"),
|
|
510
|
-
},
|
|
511
|
-
],
|
|
512
|
-
};
|
|
513
|
-
},
|
|
674
|
+
withResourceErrorHandling("brain-index", brainIndexHandler),
|
|
514
675
|
);
|
|
515
676
|
|
|
677
|
+
const brainPageHandler = async (uri: URL, vars: { slug?: string }) => {
|
|
678
|
+
const slug = String(vars.slug ?? "");
|
|
679
|
+
const page = await repo.getPage(slug);
|
|
680
|
+
return {
|
|
681
|
+
contents: [
|
|
682
|
+
{
|
|
683
|
+
uri: uri.href,
|
|
684
|
+
mimeType: "application/json",
|
|
685
|
+
text: JSON.stringify(page, null, 2),
|
|
686
|
+
},
|
|
687
|
+
],
|
|
688
|
+
};
|
|
689
|
+
};
|
|
690
|
+
|
|
516
691
|
const pageTemplate = new ResourceTemplate("brain://pages/{slug}", {
|
|
517
692
|
list: undefined,
|
|
518
693
|
});
|
|
@@ -520,19 +695,7 @@ export async function startMcpServer(dbPath: string): Promise<void> {
|
|
|
520
695
|
"brain-page",
|
|
521
696
|
pageTemplate,
|
|
522
697
|
{ title: "Brain Page", description: "Single page JSON resource." },
|
|
523
|
-
|
|
524
|
-
const slug = String(vars.slug ?? "");
|
|
525
|
-
const page = await repo.getPage(slug);
|
|
526
|
-
return {
|
|
527
|
-
contents: [
|
|
528
|
-
{
|
|
529
|
-
uri: uri.href,
|
|
530
|
-
mimeType: "application/json",
|
|
531
|
-
text: JSON.stringify(page, null, 2),
|
|
532
|
-
},
|
|
533
|
-
],
|
|
534
|
-
};
|
|
535
|
-
},
|
|
698
|
+
withResourceErrorHandling("brain-page", brainPageHandler),
|
|
536
699
|
);
|
|
537
700
|
|
|
538
701
|
const transport = new StdioServerTransport();
|