@vheins/local-memory-mcp 0.18.13 → 0.19.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.
@@ -1,8 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- CAPABILITIES,
4
- LOG_LEVEL_VALUES,
5
- MCP_PROTOCOL_VERSION,
3
+ AgentContextSchema,
4
+ CreateEntitySchema,
5
+ CreateRelationSchema,
6
+ DecisionLogSchema,
7
+ DeleteEntitySchema,
8
+ DeleteObservationSchema,
9
+ DeleteRelationSchema,
6
10
  MemoryAcknowledgeSchema,
7
11
  MemoryDeleteSchema,
8
12
  MemoryDetailSchema,
@@ -14,6 +18,7 @@ import {
14
18
  MemoryUpdateSchema,
15
19
  RealVectorStore,
16
20
  SQLiteStore,
21
+ SessionSummarizeSchema,
17
22
  StandardDeleteSchema,
18
23
  StandardDetailSchema,
19
24
  StandardSearchSchema,
@@ -35,14 +40,9 @@ import {
35
40
  createFileSink,
36
41
  createMcpResponse,
37
42
  createSessionContext,
38
- decodeCursor,
39
- encodeCursor,
40
- extractRootsFromResult,
41
43
  findContainingRoot,
42
44
  getFilesystemRoots,
43
- getLogLevel,
44
45
  getPrimaryTextContent,
45
- getPrompt,
46
46
  handleClaimList,
47
47
  handleClaimRelease,
48
48
  handleHandoffCreate,
@@ -52,146 +52,73 @@ import {
52
52
  inferOwnerFromSession,
53
53
  inferRepoFromSession,
54
54
  isPathWithinRoots,
55
- listPrompts,
56
- listResourceTemplates,
57
- listResources,
55
+ listPromptFiles,
56
+ loadPromptFromMarkdown,
57
+ loadServerInstructions,
58
58
  logger,
59
59
  normalizeRepo,
60
60
  parseRepoInput,
61
- readResource,
62
- setLogLevel,
63
- toContextSlug,
64
- updateSessionFromInitialize,
65
- updateSessionRoots
66
- } from "../chunk-BK2QIQXS.js";
61
+ rankCompletionValues,
62
+ toContextSlug
63
+ } from "../chunk-AKFMCVQ4.js";
67
64
 
68
65
  // src/mcp/server.ts
69
- import readline from "readline";
66
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
70
67
 
71
- // src/mcp/router.ts
72
- import path2 from "path";
68
+ // src/mcp/mcp-server.ts
69
+ import { McpServer as McpServer2 } from "@modelcontextprotocol/server";
73
70
 
74
- // src/mcp/completion.ts
75
- import fs from "fs";
71
+ // src/mcp/capabilities.ts
76
72
  import path from "path";
77
- var MAX_COMPLETION_VALUES = 100;
78
- var MAX_FILE_SCAN_RESULTS = 300;
79
- async function complete(params, db2, session2) {
80
- const refType = params?.ref?.type;
81
- const argumentName = typeof params?.argument?.name === "string" ? params.argument.name : "";
82
- const argumentValue = typeof params?.argument?.value === "string" ? params.argument.value : "";
83
- const contextArguments = params?.context?.arguments ?? {};
84
- if (!refType || !argumentName) {
85
- throw invalidCompletionParams("completion/complete requires ref.type and argument.name");
86
- }
87
- const dataSources = {
88
- repos: getSuggestedRepos(db2, session2),
89
- tags: getSuggestedTags(db2),
90
- filePaths: getSuggestedFilePaths(session2),
91
- tasks: getSuggestedTasks(db2, session2, contextArguments)
92
- };
93
- if (refType === "ref/prompt") {
94
- const promptName = typeof params?.ref?.name === "string" ? params.ref.name : "";
95
- if (!promptName) {
96
- throw invalidCompletionParams("Prompt completion requires ref.name");
97
- }
98
- return {
99
- completion: buildCompletionResult(
100
- await completePromptArgument(promptName, argumentName, argumentValue, contextArguments, dataSources)
101
- )
102
- };
103
- }
104
- if (refType === "ref/resource") {
105
- const resourceUri = typeof params?.ref?.uri === "string" ? params.ref.uri : "";
106
- if (!resourceUri) {
107
- throw invalidCompletionParams("Resource completion requires ref.uri");
108
- }
109
- return {
110
- completion: buildCompletionResult(
111
- completeResourceArgument(resourceUri, argumentName, argumentValue, contextArguments, dataSources)
112
- )
113
- };
114
- }
115
- throw invalidCompletionParams(`Unsupported completion ref type: ${refType}`);
116
- }
117
- function buildCompletionResult(values) {
118
- const capped = values.slice(0, MAX_COMPLETION_VALUES);
119
- return {
120
- values: capped,
121
- total: values.length,
122
- hasMore: values.length > capped.length
123
- };
124
- }
125
- function getSuggestedRepos(db2, session2) {
126
- const values = /* @__PURE__ */ new Set();
127
- const inferredRepo = inferRepoFromSession(session2);
128
- if (inferredRepo) values.add(inferredRepo);
129
- for (const rootPath of getFilesystemRoots(session2)) {
130
- values.add(path.basename(rootPath));
131
- }
132
- for (const repo of db2.system.listRepos()) {
133
- values.add(repo);
134
- }
135
- return [...values].sort((a, b) => a.localeCompare(b));
136
- }
137
- function getSuggestedTags(db2) {
138
- const values = /* @__PURE__ */ new Set();
139
- const memories = db2.memories.getRecentMemories("", "", 1e3);
140
- for (const memory of memories) {
141
- for (const tag of memory.tags || []) {
142
- if (typeof tag === "string" && tag.trim()) {
143
- values.add(tag.trim());
73
+ import { fileURLToPath } from "url";
74
+ var __dirname = path.dirname(fileURLToPath(import.meta.url));
75
+ var pkgVersion = "0.1.0";
76
+ if ("0.19.1") {
77
+ pkgVersion = "0.19.1";
78
+ } else {
79
+ let searchDir = __dirname;
80
+ for (let i = 0; i < 5; i++) {
81
+ const candidate = path.join(searchDir, "package.json");
82
+ try {
83
+ if (fs.existsSync(candidate)) {
84
+ const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
85
+ if (pkg.name === "@vheins/local-memory-mcp" && pkg.version) {
86
+ pkgVersion = pkg.version;
87
+ break;
88
+ }
144
89
  }
90
+ } catch {
145
91
  }
92
+ searchDir = path.dirname(searchDir);
146
93
  }
147
- return [...values].sort((a, b) => a.localeCompare(b));
148
- }
149
- function getSuggestedTasks(db2, session2, contextArguments) {
150
- const repo = typeof contextArguments.repo === "string" && contextArguments.repo.trim() ? contextArguments.repo.trim() : inferRepoFromSession(session2);
151
- if (!repo) return [];
152
- return db2.tasks.getTasksByRepo("", repo, void 0, 100).map((task) => ({
153
- id: task.id,
154
- task_code: task.task_code,
155
- title: task.title
156
- }));
157
94
  }
158
- function getSuggestedFilePaths(session2) {
159
- const roots = getFilesystemRoots(session2);
160
- const results = [];
161
- for (const rootPath of roots) {
162
- collectFiles(rootPath, rootPath, results);
163
- if (results.length >= MAX_FILE_SCAN_RESULTS) break;
164
- }
165
- return results.sort((a, b) => a.localeCompare(b));
166
- }
167
- function collectFiles(rootPath, currentPath, results) {
168
- if (results.length >= MAX_FILE_SCAN_RESULTS) return;
169
- let entries;
170
- try {
171
- entries = fs.readdirSync(currentPath, { withFileTypes: true });
172
- } catch {
173
- return;
174
- }
175
- for (const entry of entries) {
176
- if (results.length >= MAX_FILE_SCAN_RESULTS) return;
177
- if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") continue;
178
- const fullPath = path.join(currentPath, entry.name);
179
- if (entry.isDirectory()) {
180
- collectFiles(rootPath, fullPath, results);
181
- continue;
95
+ var CAPABILITIES = {
96
+ serverInfo: {
97
+ name: "local-memory-mcp",
98
+ version: pkgVersion
99
+ },
100
+ capabilities: {
101
+ completions: {},
102
+ logging: {},
103
+ resources: {
104
+ subscribe: true,
105
+ listChanged: true
106
+ },
107
+ tools: {
108
+ listChanged: false
109
+ },
110
+ prompts: {
111
+ listChanged: true
182
112
  }
183
- if (!entry.isFile()) continue;
184
- results.push(path.relative(rootPath, fullPath) || entry.name);
185
113
  }
186
- }
187
- function invalidCompletionParams(message) {
188
- const error = new Error(message);
189
- error.code = -32602;
190
- return error;
191
- }
114
+ };
115
+
116
+ // src/mcp/tools/index.ts
117
+ import path2 from "path";
118
+ import { z } from "zod";
192
119
 
193
120
  // src/mcp/tools/memory.store.ts
194
- import { randomUUID } from "crypto";
121
+ import { randomUUID as randomUUID2 } from "crypto";
195
122
 
196
123
  // src/mcp/utils/code-generator.ts
197
124
  var ENTITY_CONFIG = {
@@ -224,6 +151,275 @@ function generateNextCode(owner, repo, entityType, storage, batchCodes) {
224
151
  return `${config.prefix}-${String(nextSeq).padStart(3, "0")}`;
225
152
  }
226
153
 
154
+ // src/mcp/tools/kg-archivist.ts
155
+ import { randomUUID } from "crypto";
156
+ import nlp from "compromise";
157
+ var MAX_CONTENT_LENGTH = 5e3;
158
+ var PRONOUNS = /* @__PURE__ */ new Set([
159
+ "i",
160
+ "me",
161
+ "my",
162
+ "myself",
163
+ "we",
164
+ "us",
165
+ "our",
166
+ "ours",
167
+ "ourselves",
168
+ "you",
169
+ "your",
170
+ "yours",
171
+ "yourself",
172
+ "yourselves",
173
+ "he",
174
+ "him",
175
+ "his",
176
+ "himself",
177
+ "she",
178
+ "her",
179
+ "hers",
180
+ "herself",
181
+ "it",
182
+ "its",
183
+ "itself",
184
+ "they",
185
+ "them",
186
+ "their",
187
+ "theirs",
188
+ "themselves",
189
+ "this",
190
+ "that",
191
+ "these",
192
+ "those",
193
+ "someone",
194
+ "somebody",
195
+ "something",
196
+ "anyone",
197
+ "anybody",
198
+ "anything",
199
+ "everyone",
200
+ "everybody",
201
+ "everything",
202
+ "nobody",
203
+ "nothing"
204
+ ]);
205
+ var STOPWORDS = /* @__PURE__ */ new Set([
206
+ "a",
207
+ "an",
208
+ "the",
209
+ "and",
210
+ "but",
211
+ "or",
212
+ "if",
213
+ "because",
214
+ "as",
215
+ "until",
216
+ "while",
217
+ "of",
218
+ "at",
219
+ "by",
220
+ "for",
221
+ "with",
222
+ "about",
223
+ "against",
224
+ "between",
225
+ "into",
226
+ "through",
227
+ "during",
228
+ "before",
229
+ "after",
230
+ "above",
231
+ "below",
232
+ "to",
233
+ "from",
234
+ "up",
235
+ "down",
236
+ "in",
237
+ "out",
238
+ "on",
239
+ "off",
240
+ "over",
241
+ "under",
242
+ "again",
243
+ "further",
244
+ "then",
245
+ "once",
246
+ "here",
247
+ "there",
248
+ "when",
249
+ "where",
250
+ "why",
251
+ "how",
252
+ "all",
253
+ "each",
254
+ "every",
255
+ "both",
256
+ "few",
257
+ "more",
258
+ "most",
259
+ "other",
260
+ "some",
261
+ "such",
262
+ "no",
263
+ "nor",
264
+ "not",
265
+ "only",
266
+ "own",
267
+ "same",
268
+ "so",
269
+ "than",
270
+ "too",
271
+ "very",
272
+ "just",
273
+ "also",
274
+ "any",
275
+ "thing",
276
+ "things",
277
+ "way",
278
+ "ways",
279
+ "person",
280
+ "people",
281
+ "man",
282
+ "woman",
283
+ "child",
284
+ "time",
285
+ "year",
286
+ "day",
287
+ "number",
288
+ "world",
289
+ "life",
290
+ "hand",
291
+ "part",
292
+ "place",
293
+ "case",
294
+ "week",
295
+ "company",
296
+ "system",
297
+ "program",
298
+ "work",
299
+ "group",
300
+ "problem",
301
+ "fact",
302
+ "example",
303
+ "member",
304
+ "car",
305
+ "city",
306
+ "state",
307
+ "country",
308
+ "area",
309
+ "water",
310
+ "air",
311
+ "money",
312
+ "data",
313
+ "information",
314
+ "software",
315
+ "code",
316
+ "file",
317
+ "server",
318
+ "database",
319
+ "application",
320
+ "user",
321
+ "users",
322
+ "project",
323
+ "task",
324
+ "memory",
325
+ "value",
326
+ "name",
327
+ "type",
328
+ "list",
329
+ "set",
330
+ "number",
331
+ "id",
332
+ "key",
333
+ "text",
334
+ "content",
335
+ "title",
336
+ "description",
337
+ "status",
338
+ "time"
339
+ ]);
340
+ var DETERMINERS = /^(a|an|the)\s+/i;
341
+ var TRAILING_PUNCTUATION = /[.,!?;:()"'[\]]+$/g;
342
+ function cleanText(raw) {
343
+ return raw.replace(TRAILING_PUNCTUATION, "").trim();
344
+ }
345
+ function stripLeadingDeterminer(phrase) {
346
+ return phrase.replace(DETERMINERS, "").trim();
347
+ }
348
+ function isExcludedNoun(candidate) {
349
+ const lower = candidate.toLowerCase();
350
+ if (lower.length < 2) return true;
351
+ if (PRONOUNS.has(lower)) return true;
352
+ if (STOPWORDS.has(lower)) return true;
353
+ if (/^\d+$/.test(candidate)) return true;
354
+ return false;
355
+ }
356
+ function extractEntities(content) {
357
+ if (!content || content.trim().length === 0) return [];
358
+ const text = content.length > MAX_CONTENT_LENGTH ? content.slice(0, MAX_CONTENT_LENGTH) : content;
359
+ const doc = nlp(text);
360
+ const seen = /* @__PURE__ */ new Set();
361
+ const entities = [];
362
+ function add(name, type) {
363
+ const trimmed = name.trim();
364
+ if (!trimmed || trimmed.length < 2) return;
365
+ const key = trimmed.toLowerCase();
366
+ if (seen.has(key)) return;
367
+ seen.add(key);
368
+ entities.push({ name: trimmed, type });
369
+ }
370
+ for (const match of doc.people().json()) {
371
+ add(cleanText(match.text), "person");
372
+ }
373
+ for (const match of doc.places().json()) {
374
+ add(cleanText(match.text), "place");
375
+ }
376
+ for (const match of doc.organizations().json()) {
377
+ add(cleanText(match.text), "organization");
378
+ }
379
+ for (const match of doc.nouns().json()) {
380
+ const raw = cleanText(match.text);
381
+ const candidate = stripLeadingDeterminer(raw);
382
+ if (!isExcludedNoun(candidate)) {
383
+ add(candidate, "concept");
384
+ }
385
+ }
386
+ return entities;
387
+ }
388
+ function saveExtractions(content, title, owner, repo, db2) {
389
+ if (!content || content.trim().length === 0) return;
390
+ let entities;
391
+ try {
392
+ entities = extractEntities(content);
393
+ } catch (err) {
394
+ logger.warn("[KG-Archivist] Entity extraction failed, skipping", {
395
+ error: String(err)
396
+ });
397
+ return;
398
+ }
399
+ if (entities.length === 0) return;
400
+ const now = (/* @__PURE__ */ new Date()).toISOString();
401
+ const observationText = `Mentioned in memory: ${title}`;
402
+ const insertEntity = db2.db.prepare(
403
+ `INSERT OR IGNORE INTO entities (name, type, description, repo, owner, created_at, updated_at)
404
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
405
+ );
406
+ const insertObservation = db2.db.prepare(
407
+ `INSERT INTO observations (id, entity_name, observation, repo, owner, created_at)
408
+ VALUES (?, ?, ?, ?, ?, ?)`
409
+ );
410
+ for (const entity of entities) {
411
+ try {
412
+ insertEntity.run(entity.name, entity.type, null, repo, owner ?? "", now, now);
413
+ insertObservation.run(randomUUID(), entity.name, observationText, repo, owner ?? "", now);
414
+ } catch (err) {
415
+ logger.warn("[KG-Archivist] Failed to save extraction for entity", {
416
+ error: String(err),
417
+ entity: entity.name
418
+ });
419
+ }
420
+ }
421
+ }
422
+
227
423
  // src/mcp/tools/memory.store.ts
228
424
  var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
229
425
  function hasMetadataLikeTitle(title) {
@@ -280,7 +476,7 @@ async function storeSingleMemory(params, db2, vectors2) {
280
476
  tags.push(params.scope.language.toLowerCase());
281
477
  }
282
478
  const entry = {
283
- id: randomUUID(),
479
+ id: randomUUID2(),
284
480
  code: params.code || generateNextCode(params.scope.owner ?? "", params.scope.repo, "memory", db2),
285
481
  type: params.type,
286
482
  title: params.title,
@@ -309,6 +505,13 @@ async function storeSingleMemory(params, db2, vectors2) {
309
505
  } catch (error) {
310
506
  logger.warn("Failed to generate vector embedding", { error: String(error) });
311
507
  }
508
+ try {
509
+ saveExtractions(entry.content, entry.title, entry.scope.owner, entry.scope.repo, db2);
510
+ } catch (error) {
511
+ logger.warn("[KG-Archivist] NLP extraction failed, memory stored without KG enrichment", {
512
+ error: String(error)
513
+ });
514
+ }
312
515
  return createMcpResponse(
313
516
  {
314
517
  success: true,
@@ -377,7 +580,7 @@ async function handleMemoryStore(params, db2, vectors2) {
377
580
  const code = mem.code || generateNextCode(mem.scope.owner ?? "", mem.scope.repo, "memory", db2, batchCodes);
378
581
  batchCodes.add(code);
379
582
  entries.push({
380
- id: randomUUID(),
583
+ id: randomUUID2(),
381
584
  code,
382
585
  type: mem.type,
383
586
  title: mem.title,
@@ -409,6 +612,13 @@ async function handleMemoryStore(params, db2, vectors2) {
409
612
  } catch (error) {
410
613
  logger.warn("Failed to generate vector embedding", { error: String(error) });
411
614
  }
615
+ try {
616
+ saveExtractions(entry.content, entry.title, entry.scope.owner, entry.scope.repo, db2);
617
+ } catch (error) {
618
+ logger.warn("[KG-Archivist] NLP extraction failed, memory stored without KG enrichment", {
619
+ error: String(error)
620
+ });
621
+ }
412
622
  }
413
623
  const codesStr = storedCodes.length > 0 ? `: ${storedCodes.join(", ")}` : "";
414
624
  return createMcpResponse(
@@ -543,6 +753,134 @@ function expandQuery(query, prompt) {
543
753
  return Array.from(expansions).join(" ");
544
754
  }
545
755
 
756
+ // src/mcp/tools/time-tunnel.ts
757
+ function startOfDay(d) {
758
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
759
+ }
760
+ function endOfDay(d) {
761
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);
762
+ }
763
+ function currentWeekMonday(now) {
764
+ const day = now.getDay();
765
+ const offset = day === 0 ? -6 : 1 - day;
766
+ return startOfDay(new Date(now.getFullYear(), now.getMonth(), now.getDate() + offset));
767
+ }
768
+ var PATTERNS = [
769
+ // Order matters: more specific phrases first to avoid partial matches.
770
+ // "last month" — full previous calendar month
771
+ {
772
+ regex: /\blast month\b/i,
773
+ handler: (_match, now) => {
774
+ const startOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
775
+ const endOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
776
+ return {
777
+ since: startOfLastMonth.toISOString(),
778
+ until: endOfLastMonth.toISOString()
779
+ };
780
+ }
781
+ },
782
+ // "last week" — full previous calendar week (Mon–Sun)
783
+ {
784
+ regex: /\blast week\b/i,
785
+ handler: (_match, now) => {
786
+ const thisMonday = currentWeekMonday(now);
787
+ const startOfLastWeek = new Date(thisMonday.getTime() - 7 * 24 * 60 * 60 * 1e3);
788
+ const _endOfLastWeek = new Date(thisMonday.getTime() - 1);
789
+ const endOfSunday = new Date(
790
+ startOfLastWeek.getFullYear(),
791
+ startOfLastWeek.getMonth(),
792
+ startOfLastWeek.getDate() + 6,
793
+ 23,
794
+ 59,
795
+ 59,
796
+ 999
797
+ );
798
+ return {
799
+ since: startOfLastWeek.toISOString(),
800
+ until: endOfSunday.toISOString()
801
+ };
802
+ }
803
+ },
804
+ // "this week" — current week from Monday to today (no until bound)
805
+ {
806
+ regex: /\bthis week\b/i,
807
+ handler: (_match, now) => {
808
+ const monday = currentWeekMonday(now);
809
+ return {
810
+ since: monday.toISOString()
811
+ };
812
+ }
813
+ },
814
+ // "yesterday"
815
+ {
816
+ regex: /\byesterday\b/i,
817
+ handler: (_match, now) => {
818
+ const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
819
+ return {
820
+ since: startOfDay(yesterday).toISOString(),
821
+ until: endOfDay(yesterday).toISOString()
822
+ };
823
+ }
824
+ },
825
+ // "today"
826
+ {
827
+ regex: /\btoday\b/i,
828
+ handler: (_match, now) => {
829
+ return {
830
+ since: startOfDay(now).toISOString(),
831
+ until: endOfDay(now).toISOString()
832
+ };
833
+ }
834
+ },
835
+ // "last N weeks" / "past N weeks"
836
+ {
837
+ regex: /\b(?:last|past)\s+(\d+)\s+weeks?\b/i,
838
+ handler: (match, now) => {
839
+ const n = parseInt(match[1], 10);
840
+ const since = new Date(now.getFullYear(), now.getMonth(), now.getDate() - n * 7);
841
+ return {
842
+ since: startOfDay(since).toISOString()
843
+ };
844
+ }
845
+ },
846
+ // "last N days" / "past N days"
847
+ {
848
+ regex: /\b(?:last|past)\s+(\d+)\s+days?\b/i,
849
+ handler: (match, now) => {
850
+ const n = parseInt(match[1], 10);
851
+ const since = new Date(now.getFullYear(), now.getMonth(), now.getDate() - n);
852
+ return {
853
+ since: startOfDay(since).toISOString()
854
+ };
855
+ }
856
+ },
857
+ // "last_hour" / "past_hour"
858
+ {
859
+ regex: /\b(?:last|past)_hour\b/i,
860
+ handler: (_match, now) => {
861
+ const since = new Date(now.getTime() - 60 * 60 * 1e3);
862
+ return {
863
+ since: since.toISOString()
864
+ };
865
+ }
866
+ }
867
+ ];
868
+ function parseRelativeDate(query) {
869
+ if (!query || query.trim().length === 0) {
870
+ return null;
871
+ }
872
+ const now = /* @__PURE__ */ new Date();
873
+ for (const { regex, handler } of PATTERNS) {
874
+ const match = query.match(regex);
875
+ if (match) {
876
+ const cleanedQuery = query.replace(regex, "").trim().replace(/\s+/g, " ");
877
+ const { since, until } = handler(match, now);
878
+ return { cleanedQuery, since, until };
879
+ }
880
+ }
881
+ return null;
882
+ }
883
+
546
884
  // src/mcp/tools/memory.search.ts
547
885
  var HYBRID_WEIGHTS_VECTOR = {
548
886
  similarity: 0.4,
@@ -551,7 +889,9 @@ var HYBRID_WEIGHTS_VECTOR = {
551
889
  };
552
890
  async function handleMemorySearch(params, db2, vectors2) {
553
891
  const validated = MemorySearchSchema.parse(params);
554
- const searchQuery = expandQuery(validated.query, validated.prompt);
892
+ const timeTunnel = parseRelativeDate(validated.query);
893
+ const effectiveQuery = timeTunnel ? timeTunnel.cleanedQuery : validated.query;
894
+ const searchQuery = expandQuery(effectiveQuery, validated.prompt);
555
895
  const fetchLimit = (validated.offset + validated.limit) * 3;
556
896
  const similarityResults = db2.memoryVectors.searchBySimilarity(
557
897
  searchQuery,
@@ -622,6 +962,9 @@ async function handleMemorySearch(params, db2, vectors2) {
622
962
  const threshold = scoredMemories.length <= 5 ? 0.1 : 0.4;
623
963
  let allMatches = scoredMemories.filter((sm) => sm.finalScore >= threshold).map((sm) => sm.memory);
624
964
  if (allMatches.length === 0 && scoredMemories.length > 0) allMatches = [scoredMemories[0].memory];
965
+ if (timeTunnel) {
966
+ allMatches = applyTimeFilter(allMatches, timeTunnel);
967
+ }
625
968
  const total = allMatches.length;
626
969
  const paginatedResults = allMatches.slice(validated.offset, validated.offset + validated.limit);
627
970
  db2.memories.incrementHitCounts(paginatedResults.map((m) => m.id));
@@ -678,6 +1021,56 @@ async function handleMemorySearch(params, db2, vectors2) {
678
1021
  function capitalize(str) {
679
1022
  return str.charAt(0).toUpperCase() + str.slice(1);
680
1023
  }
1024
+ function applyTimeFilter(memories, tunnel) {
1025
+ const sinceMs = tunnel.since ? new Date(tunnel.since).getTime() : 0;
1026
+ const untilMs = tunnel.until ? new Date(tunnel.until).getTime() : Infinity;
1027
+ return memories.filter((m) => {
1028
+ const createdAtMs = new Date(m.created_at).getTime();
1029
+ if (sinceMs > 0 && createdAtMs < sinceMs) return false;
1030
+ if (untilMs < Infinity && createdAtMs >= untilMs) return false;
1031
+ return true;
1032
+ });
1033
+ }
1034
+
1035
+ // src/mcp/tools/memory.acknowledge.ts
1036
+ async function handleMemoryAcknowledge(params, db2) {
1037
+ const validated = MemoryAcknowledgeSchema.parse(params);
1038
+ let memoryId = validated.memory_id;
1039
+ if (!memoryId && validated.code) {
1040
+ const byCode = db2.memories.getByCode(validated.code, validated.owner, validated.repo);
1041
+ if (!byCode) throw new Error(`Memory not found: ${validated.code}`);
1042
+ memoryId = byCode.id;
1043
+ } else if (!memoryId) {
1044
+ throw new Error("Either memory_id or code must be provided");
1045
+ }
1046
+ const memory = db2.memories.getById(memoryId);
1047
+ if (!memory) {
1048
+ throw new Error(`Memory with ID ${memoryId} not found.`);
1049
+ }
1050
+ if (validated.status === "used") {
1051
+ db2.memories.incrementRecallCount(memory.id);
1052
+ logger.info("[Tool] memory.acknowledge - used", { id: memory.id, context: validated.application_context });
1053
+ } else if (validated.status === "contradictory") {
1054
+ logger.warn("[Tool] memory.acknowledge - contradiction reported", {
1055
+ id: memory.id,
1056
+ context: validated.application_context
1057
+ });
1058
+ } else {
1059
+ logger.info("[Tool] memory.acknowledge - irrelevant", { id: memory.id, context: validated.application_context });
1060
+ }
1061
+ return createMcpResponse(
1062
+ {
1063
+ success: true,
1064
+ id: memory.id,
1065
+ status: validated.status
1066
+ },
1067
+ `Acknowledged memory [${memory.code}] as "${validated.status}".`,
1068
+ {
1069
+ structuredContentPathHint: "status",
1070
+ includeSerializedStructuredContent: validated.structured
1071
+ }
1072
+ );
1073
+ }
681
1074
 
682
1075
  // src/mcp/tools/memory.summarize.ts
683
1076
  async function handleMemorySummarize(params, db2) {
@@ -895,11 +1288,11 @@ async function handleTaskList(args, storage) {
895
1288
  // src/mcp/tools/memory.synthesize.ts
896
1289
  async function handleMemorySynthesize(params, db2, vectors2, options = {}) {
897
1290
  const validated = MemorySynthesizeSchema.parse(params);
898
- const session2 = options.session;
899
- if (!options.sampleMessage || !session2?.supportsSampling) {
1291
+ const session = options.session;
1292
+ if (!options.sampleMessage || !session?.supportsSampling) {
900
1293
  throw new Error("Client does not advertise MCP sampling support");
901
1294
  }
902
- const repo = await resolveRepository(validated.repo, session2, options.elicit);
1295
+ const repo = await resolveRepository(validated.repo, session, options.elicit);
903
1296
  if (!repo) {
904
1297
  throw new Error("repo is required when repo cannot be inferred from active MCP roots");
905
1298
  }
@@ -940,7 +1333,7 @@ ${contextBlock || "No additional context provided."}`
940
1333
  }
941
1334
  }
942
1335
  ];
943
- const toolDefinitions = buildSamplingTools(session2, validated.use_tools);
1336
+ const toolDefinitions = buildSamplingTools(session, validated.use_tools);
944
1337
  let lastResponse = null;
945
1338
  let totalToolCalls = 0;
946
1339
  let iterations = 0;
@@ -1011,11 +1404,11 @@ ${contextBlock || "No additional context provided."}`
1011
1404
  }
1012
1405
  );
1013
1406
  }
1014
- async function resolveRepository(repo, session2, elicit) {
1407
+ async function resolveRepository(repo, session, elicit) {
1015
1408
  if (repo) return normalizeRepo(repo);
1016
- const inferredRepo = inferRepoFromSession(session2);
1409
+ const inferredRepo = inferRepoFromSession(session);
1017
1410
  if (inferredRepo) return normalizeRepo(inferredRepo);
1018
- if (!session2?.supportsElicitationForm || !elicit) {
1411
+ if (!session?.supportsElicitationForm || !elicit) {
1019
1412
  return void 0;
1020
1413
  }
1021
1414
  const elicited = extractAcceptedElicitationContent(
@@ -1038,8 +1431,8 @@ async function resolveRepository(repo, session2, elicit) {
1038
1431
  );
1039
1432
  return typeof elicited.repo === "string" && elicited.repo.trim() ? normalizeRepo(elicited.repo.trim()) : void 0;
1040
1433
  }
1041
- function buildSamplingTools(session2, useTools) {
1042
- if (!useTools || !session2?.supportsSamplingTools) {
1434
+ function buildSamplingTools(session, useTools) {
1435
+ if (!useTools || !session?.supportsSamplingTools) {
1043
1436
  return [];
1044
1437
  }
1045
1438
  return [
@@ -1203,58 +1596,18 @@ async function handleMemoryDelete(params, db2, vectors2, onProgress) {
1203
1596
  );
1204
1597
  }
1205
1598
 
1206
- // src/mcp/tools/memory.acknowledge.ts
1207
- async function handleMemoryAcknowledge(params, db2) {
1208
- const validated = MemoryAcknowledgeSchema.parse(params);
1209
- let memoryId = validated.memory_id;
1210
- if (!memoryId && validated.code) {
1211
- const byCode = db2.memories.getByCode(validated.code, validated.owner, validated.repo);
1212
- if (!byCode) throw new Error(`Memory not found: ${validated.code}`);
1213
- memoryId = byCode.id;
1214
- } else if (!memoryId) {
1215
- throw new Error("Either memory_id or code must be provided");
1599
+ // src/mcp/tools/memory.detail.ts
1600
+ async function handleMemoryDetail(args, storage) {
1601
+ const validated = MemoryDetailSchema.parse(args);
1602
+ const { id, code, owner, repo } = validated;
1603
+ let memory;
1604
+ if (id) {
1605
+ memory = storage.memories.getById(id);
1606
+ } else if (code) {
1607
+ memory = storage.memories.getByCode(code, owner, repo);
1216
1608
  }
1217
- const memory = db2.memories.getById(memoryId);
1218
1609
  if (!memory) {
1219
- throw new Error(`Memory with ID ${memoryId} not found.`);
1220
- }
1221
- if (validated.status === "used") {
1222
- db2.memories.incrementRecallCount(memory.id);
1223
- logger.info("[Tool] memory.acknowledge - used", { id: memory.id, context: validated.application_context });
1224
- } else if (validated.status === "contradictory") {
1225
- logger.warn("[Tool] memory.acknowledge - contradiction reported", {
1226
- id: memory.id,
1227
- context: validated.application_context
1228
- });
1229
- } else {
1230
- logger.info("[Tool] memory.acknowledge - irrelevant", { id: memory.id, context: validated.application_context });
1231
- }
1232
- return createMcpResponse(
1233
- {
1234
- success: true,
1235
- id: memory.id,
1236
- status: validated.status
1237
- },
1238
- `Acknowledged memory [${memory.code}] as "${validated.status}".`,
1239
- {
1240
- structuredContentPathHint: "status",
1241
- includeSerializedStructuredContent: validated.structured
1242
- }
1243
- );
1244
- }
1245
-
1246
- // src/mcp/tools/memory.detail.ts
1247
- async function handleMemoryDetail(args, storage) {
1248
- const validated = MemoryDetailSchema.parse(args);
1249
- const { id, code, owner, repo } = validated;
1250
- let memory;
1251
- if (id) {
1252
- memory = storage.memories.getById(id);
1253
- } else if (code) {
1254
- memory = storage.memories.getByCode(code, owner, repo);
1255
- }
1256
- if (!memory) {
1257
- throw new Error(`Memory not found: ${id || code}`);
1610
+ throw new Error(`Memory not found: ${id || code}`);
1258
1611
  }
1259
1612
  storage.memories.incrementHitCount(memory.id);
1260
1613
  const lines = [
@@ -1278,7 +1631,7 @@ async function handleMemoryDetail(args, storage) {
1278
1631
  }
1279
1632
 
1280
1633
  // src/mcp/tools/standard.store.ts
1281
- import { randomUUID as randomUUID2 } from "crypto";
1634
+ import { randomUUID as randomUUID3 } from "crypto";
1282
1635
  var UUID_REGEX3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1283
1636
  function resolveStandardParentId(value, db2, owner, repo) {
1284
1637
  if (!value) return null;
@@ -1321,7 +1674,7 @@ async function storeSingleStandard(params, db2, vectors2) {
1321
1674
  }
1322
1675
  const now = (/* @__PURE__ */ new Date()).toISOString();
1323
1676
  const entry = {
1324
- id: randomUUID2(),
1677
+ id: randomUUID3(),
1325
1678
  code: generateNextCode(params.owner, params.repo || "__global__", "standard", db2),
1326
1679
  title: params.name,
1327
1680
  content: params.content,
@@ -1404,7 +1757,7 @@ async function handleStandardStore(params, db2, vectors2) {
1404
1757
  const code = generateNextCode(validated.owner ?? "", standardRepo, "standard", db2, batchCodes);
1405
1758
  batchCodes.add(code);
1406
1759
  entries.push({
1407
- id: randomUUID2(),
1760
+ id: randomUUID3(),
1408
1761
  code,
1409
1762
  title: std.name,
1410
1763
  content: std.content,
@@ -1845,7 +2198,7 @@ async function handleStandardDelete(params, db2, vectors2) {
1845
2198
  }
1846
2199
 
1847
2200
  // src/mcp/tools/task.create.ts
1848
- import { randomUUID as randomUUID3 } from "crypto";
2201
+ import { randomUUID as randomUUID4 } from "crypto";
1849
2202
 
1850
2203
  // src/mcp/tools/task.helpers.ts
1851
2204
  var UUID_REGEX5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -1964,7 +2317,7 @@ async function handleTaskCreate(args, storage) {
1964
2317
  let pendingInRequestCount = 0;
1965
2318
  const localCodeMap = /* @__PURE__ */ new Map();
1966
2319
  for (const taskData of bulkTasks) {
1967
- localCodeMap.set(taskData.task_code, randomUUID3());
2320
+ localCodeMap.set(taskData.task_code, randomUUID4());
1968
2321
  }
1969
2322
  for (const taskData of bulkTasks) {
1970
2323
  const code = taskData.task_code;
@@ -2067,7 +2420,7 @@ async function handleTaskCreate(args, storage) {
2067
2420
  );
2068
2421
  }
2069
2422
  }
2070
- const taskId = randomUUID3();
2423
+ const taskId = randomUUID4();
2071
2424
  const now = (/* @__PURE__ */ new Date()).toISOString();
2072
2425
  const statusTimestamps = deriveTaskStatusTimestamps(status || "backlog", now);
2073
2426
  const finalTags = [...singleTask.tags || []];
@@ -2215,7 +2568,7 @@ function addRequiredStringField(properties, required, task, field, schema) {
2215
2568
  }
2216
2569
 
2217
2570
  // src/mcp/tools/task.update.ts
2218
- import { randomUUID as randomUUID4 } from "crypto";
2571
+ import { randomUUID as randomUUID5 } from "crypto";
2219
2572
  async function handleTaskUpdate(args, storage, vectors2) {
2220
2573
  const updateData = TaskUpdateSchema.parse(args);
2221
2574
  const { owner, repo, id, ids, comment, force, ...updates } = updateData;
@@ -2301,7 +2654,7 @@ async function handleTaskUpdate(args, storage, vectors2) {
2301
2654
  storage.tasks.updateTask(targetId, finalUpdates);
2302
2655
  if (comment !== void 0 || isStatusChanging) {
2303
2656
  storage.taskComments.insertTaskComment({
2304
- id: randomUUID4(),
2657
+ id: randomUUID5(),
2305
2658
  task_id: targetId,
2306
2659
  owner,
2307
2660
  repo,
@@ -2565,284 +2918,565 @@ async function handleTaskSearch(args, storage) {
2565
2918
  });
2566
2919
  }
2567
2920
 
2568
- // src/mcp/router.ts
2569
- function createRouter(db2, vectors2, options) {
2570
- const getSessionContext = options?.getSessionContext;
2571
- async function handleMethod2(method, params, signal, onProgress) {
2572
- const t0 = Date.now();
2573
- try {
2574
- const result = await _dispatch(method, params, signal, onProgress);
2575
- logger.debug(`[Router] ${method}`, { ms: Date.now() - t0 });
2576
- return result;
2577
- } catch (err) {
2578
- logger.error(`[Router] ${method} failed`, { ms: Date.now() - t0, error: String(err) });
2579
- throw err;
2580
- }
2581
- }
2582
- async function _dispatch(method, params, signal, onProgress) {
2583
- switch (method) {
2584
- // ---- tools ----
2585
- case "tools/list":
2586
- return listTools(getSessionContext?.(), params);
2587
- case "tools/call":
2588
- return await handleToolCall(
2589
- params,
2590
- params?.signal,
2591
- onProgress
2592
- );
2593
- // ---- resources ----
2594
- case "resources/list":
2595
- return listResources(getSessionContext?.(), params);
2596
- case "resources/templates/list":
2597
- return listResourceTemplates(params);
2598
- case "resources/read": {
2599
- const result = readResource(params?.uri, db2, getSessionContext?.());
2600
- if (result && Array.isArray(result.contents) && !result.content) {
2601
- result.content = result.contents;
2602
- }
2603
- return result;
2604
- }
2605
- // ---- prompts ----
2606
- case "prompts/list":
2607
- return listPrompts(db2, getSessionContext?.(), params);
2608
- case "logging/setLevel": {
2609
- const requestedLevel = typeof params?.level === "string" ? params.level : "";
2610
- const previousLevel = getLogLevel();
2611
- const level = setLogLevel(requestedLevel);
2612
- return {
2613
- level,
2614
- supportedLevels: LOG_LEVEL_VALUES,
2615
- previousLevel
2616
- };
2617
- }
2618
- case "prompts/get": {
2619
- return getPrompt(
2620
- params?.name,
2621
- params?.arguments || {},
2622
- db2,
2623
- getSessionContext?.()
2624
- );
2625
- }
2626
- case "completion/complete":
2627
- return complete(params, db2, getSessionContext?.());
2628
- default:
2629
- throw new Error(`Unsupported method: ${method}`);
2630
- }
2631
- }
2632
- const WRITE_TOOLS = /* @__PURE__ */ new Set([
2633
- "memory-store",
2634
- "memory-acknowledge",
2635
- "memory-update",
2636
- "memory-delete",
2637
- "memory-bulk-delete",
2638
- "memory-summarize",
2639
- "handoff-create",
2640
- "handoff-update",
2641
- "standard-store",
2642
- "standard-update",
2643
- "standard-delete",
2644
- "task-create",
2645
- "task-create-interactive",
2646
- "task-claim",
2647
- "claim-release",
2648
- "task-update",
2649
- "task-delete"
2650
- ]);
2651
- async function handleToolCall(params, signal, onProgress) {
2652
- const { name } = params || {};
2653
- const args = normalizeToolArguments(params?.arguments, getSessionContext?.());
2654
- const toolName = String(name).replace(/\./g, "-");
2655
- let result;
2656
- const repo = args?.repo || args?.scope?.repo || "unknown";
2657
- const isWrite = WRITE_TOOLS.has(toolName);
2658
- logger.info(`[Tool] ${toolName}`, { repo, write: isWrite });
2659
- const executeToolLogic = async () => {
2660
- switch (toolName) {
2661
- case "memory-store":
2662
- return await handleMemoryStore(args, db2, vectors2);
2663
- case "memory-acknowledge":
2664
- return await handleMemoryAcknowledge(args, db2);
2665
- case "memory-update":
2666
- return await handleMemoryUpdate(args, db2, vectors2);
2667
- case "memory-recap":
2668
- return await handleMemoryRecap(args, db2);
2669
- case "memory-search":
2670
- return await handleMemorySearch(args, db2, vectors2);
2671
- case "memory-summarize":
2672
- return await handleMemorySummarize(args, db2);
2673
- case "memory-synthesize":
2674
- return await handleMemorySynthesize(args, db2, vectors2, {
2675
- session: getSessionContext?.(),
2676
- sampleMessage: options?.sampleMessage,
2677
- elicit: options?.elicit
2678
- });
2679
- case "memory-delete":
2680
- case "memory-bulk-delete":
2681
- return await handleMemoryDelete(args, db2, vectors2, onProgress);
2682
- case "memory-detail":
2683
- return await handleMemoryDetail(args, db2);
2684
- case "handoff-create":
2685
- return await handleHandoffCreate(args, db2);
2686
- case "handoff-list":
2687
- return await handleHandoffList(args, db2);
2688
- case "handoff-update":
2689
- return await handleHandoffUpdate(args, db2);
2690
- case "task-claim":
2691
- return await handleTaskClaim(args, db2);
2692
- case "claim-list":
2693
- return await handleClaimList(args, db2);
2694
- case "claim-release":
2695
- return await handleClaimRelease(args, db2);
2696
- case "standard-store":
2697
- return await handleStandardStore(args, db2, vectors2);
2698
- case "standard-update":
2699
- return await handleStandardUpdate(args, db2, vectors2);
2700
- case "standard-detail":
2701
- return await handleStandardDetail(args, db2);
2702
- case "standard-delete":
2703
- return await handleStandardDelete(args, db2, vectors2);
2704
- case "standard-search":
2705
- return await handleStandardSearch(args, db2, vectors2);
2706
- case "task-create":
2707
- return await handleTaskCreate(args, db2);
2708
- case "task-create-interactive":
2709
- return await handleTaskCreateInteractive(args, db2, {
2710
- session: getSessionContext?.(),
2711
- elicit: options?.elicit
2712
- });
2713
- case "task-update":
2714
- return await handleTaskUpdate(args, db2, vectors2);
2715
- case "task-delete":
2716
- return await handleTaskDelete(args, db2);
2717
- case "task-list":
2718
- return await handleTaskList(args, db2);
2719
- case "task-search":
2720
- return await handleTaskSearch(args, db2);
2721
- case "task-detail":
2722
- return await handleTaskGet(args, db2);
2723
- default:
2724
- throw new Error(`Unknown tool: ${name}`);
2725
- }
2726
- };
2727
- if (isWrite) {
2728
- result = await db2.withWrite(executeToolLogic);
2729
- } else {
2730
- result = await executeToolLogic();
2921
+ // src/mcp/tools/agent-context.ts
2922
+ var ACTIVE_TASK_STATUSES = ["in_progress", "pending", "backlog", "blocked"];
2923
+ async function handleAgentContext(args, db2, _vectors) {
2924
+ const validated = AgentContextSchema.parse(args);
2925
+ const { owner, repo, objective, type_filter, limit, structured: isStructuredRequest } = validated;
2926
+ let memories;
2927
+ let decisionMemories = [];
2928
+ const shouldFetchDecisions = !type_filter || type_filter === "decision";
2929
+ if (objective) {
2930
+ memories = db2.memories.searchByRepo(owner, repo, objective, type_filter, limit);
2931
+ if (shouldFetchDecisions) {
2932
+ decisionMemories = db2.memories.searchByRepo(owner, repo, "", "decision", limit);
2731
2933
  }
2732
- logger.info(`[Tool] ${toolName} result`, { repo, result });
2733
- try {
2734
- const actionType = toolName.split("-")[1] || toolName;
2735
- const res = result;
2736
- const sc = res?.structuredData;
2737
- const logOptions = {
2738
- query: args?.query || args?.title || args?.task_code || (toolName === "memory-recap" ? `Offset: ${args?.offset || 0}` : void 0),
2739
- response: result,
2740
- memoryId: args?.id || args?.memory_id || sc?.id,
2741
- taskId: args?.id || args?.task_id || sc?.id,
2742
- resultCount: Array.isArray(sc?.results) ? sc.results.length : sc?.count || 0
2743
- };
2744
- if (isWrite) {
2745
- db2.actions.logAction(actionType, "", repo, logOptions);
2746
- } else {
2747
- await db2.withWrite(() => db2.actions.logAction(actionType, "", repo, logOptions));
2748
- }
2749
- } catch (e) {
2750
- logger.error("Failed to log action", { toolName, error: String(e) });
2934
+ } else {
2935
+ const excludeTypes = type_filter ? [] : ["decision"];
2936
+ memories = db2.memories.getRecentMemories(owner, repo, limit, 0, false, excludeTypes);
2937
+ if (shouldFetchDecisions) {
2938
+ decisionMemories = db2.memories.searchByRepo(owner, repo, "", "decision", limit);
2939
+ }
2940
+ }
2941
+ const activeTasks = db2.tasks.getTasksByMultipleStatuses(owner, repo, ACTIVE_TASK_STATUSES, 10, 0);
2942
+ const memoryIds = new Set(memories.map((m) => m.id));
2943
+ const uniqueDecisions = decisionMemories.filter((d) => !memoryIds.has(d.id));
2944
+ const sections = [];
2945
+ sections.push(`--- Active Context for ${repo} ---`);
2946
+ sections.push("");
2947
+ sections.push("== Relevant Memories ==");
2948
+ if (memories.length === 0) {
2949
+ sections.push("(No relevant memories found)");
2950
+ } else {
2951
+ for (const m of memories) {
2952
+ const code = m.code || "-";
2953
+ const snippet = m.content.length > 120 ? m.content.slice(0, 120) + "..." : m.content;
2954
+ sections.push(`- [${code}] (${m.type}, importance: ${m.importance}) ${m.title}`);
2955
+ sections.push(` ${snippet}`);
2751
2956
  }
2752
- const affectedResources = collectAffectedResourceUris(toolName, args, result);
2753
- if (affectedResources.length > 0) {
2754
- options?.onResourcesMutated?.(affectedResources);
2957
+ }
2958
+ sections.push("");
2959
+ sections.push("== Active Tasks ==");
2960
+ if (activeTasks.length === 0) {
2961
+ sections.push("(No active tasks)");
2962
+ } else {
2963
+ for (const t of activeTasks) {
2964
+ sections.push(`- ${t.task_code} | ${t.status} | priority: ${t.priority} | ${t.title}`);
2755
2965
  }
2756
- return result;
2757
2966
  }
2758
- return handleMethod2;
2759
- }
2760
- function listTools(session2, params) {
2761
- const tools = getAvailableToolDefinitions(session2);
2762
- const limit = normalizePageLimit(params?.limit, tools.length || 1);
2763
- const start = decodeCursor(params?.cursor);
2764
- const compliantTools = tools.map((tool) => {
2765
- const { name, description, inputSchema } = tool;
2766
- return { name, description, inputSchema };
2967
+ sections.push("");
2968
+ sections.push("== Recent Decisions ==");
2969
+ if (uniqueDecisions.length === 0) {
2970
+ sections.push("(No recent decision memories)");
2971
+ } else {
2972
+ for (const d of uniqueDecisions) {
2973
+ const code = d.code || "-";
2974
+ const snippet = d.content.length > 150 ? d.content.slice(0, 150) + "..." : d.content;
2975
+ sections.push(`- [${code}] (importance: ${d.importance}) ${d.title}`);
2976
+ sections.push(` ${snippet}`);
2977
+ }
2978
+ }
2979
+ sections.push("");
2980
+ sections.push("Use memory-detail with a memory code for full content.");
2981
+ const contentSummary = sections.join("\n").trim();
2982
+ const structuredData = {
2983
+ schema: "agent-context",
2984
+ repo,
2985
+ objective: objective || null,
2986
+ memories: memories.map((m) => ({
2987
+ id: m.id,
2988
+ code: m.code || null,
2989
+ title: m.title,
2990
+ type: m.type,
2991
+ importance: m.importance
2992
+ })),
2993
+ decisions: uniqueDecisions.map((d) => ({
2994
+ id: d.id,
2995
+ code: d.code || null,
2996
+ title: d.title,
2997
+ importance: d.importance
2998
+ })),
2999
+ tasks: activeTasks.map((t) => ({
3000
+ task_code: t.task_code,
3001
+ title: t.title,
3002
+ status: t.status,
3003
+ priority: t.priority
3004
+ }))
3005
+ };
3006
+ logger.info("[Tool] agent-context", {
3007
+ repo,
3008
+ memories: memories.length,
3009
+ decisions: uniqueDecisions.length,
3010
+ tasks: activeTasks.length
2767
3011
  });
2768
- const page = compliantTools.slice(start, start + limit);
2769
- const nextCursor = start + limit < tools.length ? encodeCursor(start + limit) : void 0;
2770
- return {
2771
- tools: page,
2772
- nextCursor
3012
+ return createMcpResponse(structuredData, contentSummary, {
3013
+ contentSummary,
3014
+ includeSerializedStructuredContent: isStructuredRequest
3015
+ });
3016
+ }
3017
+
3018
+ // src/mcp/tools/decision-log.ts
3019
+ async function handleDecisionLog(args, db2, vectors2) {
3020
+ const validated = DecisionLogSchema.parse(args);
3021
+ const owner = validated.owner ?? "";
3022
+ const repo = validated.repo ?? "";
3023
+ if (!owner || !repo) {
3024
+ return createMcpResponse(
3025
+ {
3026
+ success: false,
3027
+ error: "MISSING_SCOPE",
3028
+ message: "Both owner and repo are required. They can be provided explicitly or auto-detected from the session context."
3029
+ },
3030
+ "Failed to log decision: owner and repo could not be determined."
3031
+ );
3032
+ }
3033
+ const alternativesStr = validated.alternatives && validated.alternatives.length > 0 ? validated.alternatives.join(", ") : "None";
3034
+ const formattedContent = [
3035
+ `Decision: ${validated.summary}`,
3036
+ `Context: ${validated.context}`,
3037
+ `Rationale: ${validated.rationale}`,
3038
+ `Alternatives considered: ${alternativesStr}`
3039
+ ].join("\n");
3040
+ const tags = ["decision", ...validated.tags ?? []];
3041
+ const memoryStoreParams = {
3042
+ type: "decision",
3043
+ title: validated.summary,
3044
+ content: formattedContent,
3045
+ importance: 4,
3046
+ agent: "agent",
3047
+ role: "unknown",
3048
+ model: "unknown",
3049
+ scope: {
3050
+ owner,
3051
+ repo
3052
+ },
3053
+ tags,
3054
+ structured: validated.structured
2773
3055
  };
3056
+ return handleMemoryStore(memoryStoreParams, db2, vectors2);
2774
3057
  }
2775
- function getAvailableToolDefinitions(session2) {
2776
- return TOOL_DEFINITIONS.filter((tool) => {
2777
- if (tool.name === "memory-synthesize" && !session2?.supportsSampling) {
2778
- return false;
3058
+
3059
+ // src/mcp/tools/session-summarize.ts
3060
+ async function handleSessionSummarize(args, db2, vectors2) {
3061
+ const validated = SessionSummarizeSchema.parse(args);
3062
+ const owner = validated.owner ?? "";
3063
+ const repo = validated.repo ?? "";
3064
+ if (!owner || !repo) {
3065
+ return createMcpResponse(
3066
+ {
3067
+ success: false,
3068
+ error: "MISSING_SCOPE",
3069
+ message: "Both owner and repo are required. They can be provided explicitly or auto-detected from the session context."
3070
+ },
3071
+ "Failed to log session summary: owner and repo could not be determined."
3072
+ );
3073
+ }
3074
+ const decisionsStr = validated.key_decisions && validated.key_decisions.length > 0 ? validated.key_decisions.map((d) => `- ${d}`).join("\n") : "None";
3075
+ const nextStepsStr = validated.next_steps && validated.next_steps.length > 0 ? validated.next_steps.map((s) => `- ${s}`).join("\n") : "None";
3076
+ const formattedContent = [
3077
+ `Session Summary: ${validated.summary}`,
3078
+ ``,
3079
+ `Key Decisions:`,
3080
+ decisionsStr,
3081
+ ``,
3082
+ `Next Steps:`,
3083
+ nextStepsStr
3084
+ ].join("\n");
3085
+ const tags = ["session-summary", ...validated.tags ?? []];
3086
+ const memoryStoreParams = {
3087
+ type: "task_archive",
3088
+ title: validated.summary.length > 100 ? validated.summary.slice(0, 97) + "..." : validated.summary,
3089
+ content: formattedContent,
3090
+ importance: 3,
3091
+ agent: "agent",
3092
+ role: "unknown",
3093
+ model: "unknown",
3094
+ scope: {
3095
+ owner,
3096
+ repo
3097
+ },
3098
+ tags,
3099
+ structured: validated.structured
3100
+ };
3101
+ return handleMemoryStore(memoryStoreParams, db2, vectors2);
3102
+ }
3103
+
3104
+ // src/mcp/tools/kg.crud.ts
3105
+ async function handleCreateEntity(params, db2, _vectors) {
3106
+ const validated = CreateEntitySchema.parse(params);
3107
+ const { name, type, description, owner, repo, structured } = validated;
3108
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3109
+ try {
3110
+ const txn = db2.db.transaction(() => {
3111
+ db2.db.prepare(
3112
+ `INSERT INTO entities (name, type, description, repo, owner, created_at, updated_at)
3113
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
3114
+ ).run(name, type, description ?? null, repo ?? "", owner ?? "", now, now);
3115
+ });
3116
+ txn();
3117
+ } catch (error) {
3118
+ const err = error;
3119
+ if (err.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
3120
+ const response = createMcpResponse(
3121
+ {
3122
+ success: false,
3123
+ error: "ENTITY_ALREADY_EXISTS",
3124
+ message: `Entity '${name}' already exists in this repo.`
3125
+ },
3126
+ `Entity '${name}' already exists.`,
3127
+ { includeSerializedStructuredContent: structured }
3128
+ );
3129
+ response.isError = true;
3130
+ return response;
2779
3131
  }
2780
- if (tool.name === "task-create-interactive" && !session2?.supportsElicitationForm) {
2781
- return false;
3132
+ logger.error("[Tool] create_entity failed", { error: String(error) });
3133
+ throw error;
3134
+ }
3135
+ logger.info("[Tool] create_entity", { repo: repo ?? "", entity: name });
3136
+ return createMcpResponse(
3137
+ {
3138
+ success: true,
3139
+ entity: { name, type, description, repo: repo ?? "", owner: owner ?? "" }
3140
+ },
3141
+ `Created entity '${name}' (${type}) in repo "${repo ?? ""}".`,
3142
+ {
3143
+ structuredContentPathHint: "entity",
3144
+ includeSerializedStructuredContent: structured
2782
3145
  }
2783
- return true;
2784
- });
3146
+ );
2785
3147
  }
2786
- function collectAffectedResourceUris(toolName, args, result) {
2787
- const res = result;
2788
- const repo = args?.repo || args?.scope?.repo || res?.data?.repo;
2789
- const uris = /* @__PURE__ */ new Set();
2790
- const touchesMemory = toolName.startsWith("memory-") || toolName === "task-update" || toolName === "task-delete";
2791
- const touchesTasks = toolName.startsWith("task-");
2792
- if (touchesMemory && repo) {
2793
- uris.add(`repository://${encodeURIComponent(repo)}/memories`);
3148
+ async function handleDeleteEntity(params, db2, _vectors) {
3149
+ const validated = DeleteEntitySchema.parse(params);
3150
+ const { name, owner: _owner, repo, structured } = validated;
3151
+ let changes = 0;
3152
+ try {
3153
+ const txn = db2.db.transaction(() => {
3154
+ const result = db2.db.prepare(`DELETE FROM entities WHERE name = ?`).run(name);
3155
+ changes = result.changes;
3156
+ });
3157
+ txn();
3158
+ } catch (error) {
3159
+ const err = error;
3160
+ logger.error("[Tool] delete_entity failed", { error: String(error) });
3161
+ if (err.code === "SQLITE_CONSTRAINT_FOREIGNKEY" || err.code === "SQLITE_CONSTRAINT") {
3162
+ const response2 = createMcpResponse(
3163
+ {
3164
+ success: false,
3165
+ error: "ENTITY_IN_USE",
3166
+ message: `Entity '${name}' is referenced by existing relations and cannot be deleted.`
3167
+ },
3168
+ `Entity '${name}' is in use and cannot be deleted.`,
3169
+ { includeSerializedStructuredContent: structured }
3170
+ );
3171
+ response2.isError = true;
3172
+ return response2;
3173
+ }
3174
+ const response = createMcpResponse(
3175
+ {
3176
+ success: false,
3177
+ error: "DELETE_FAILED",
3178
+ message: `Failed to delete entity '${name}'.`
3179
+ },
3180
+ `Failed to delete entity '${name}'.`,
3181
+ { includeSerializedStructuredContent: structured }
3182
+ );
3183
+ response.isError = true;
3184
+ return response;
2794
3185
  }
2795
- if (touchesTasks && repo) {
2796
- uris.add(`repository://${encodeURIComponent(repo)}/tasks`);
3186
+ logger.info("[Tool] delete_entity", { repo: repo ?? "", entity: name, changes });
3187
+ if (changes === 0) {
3188
+ return createMcpResponse(
3189
+ {
3190
+ success: false,
3191
+ error: "ENTITY_NOT_FOUND",
3192
+ message: `Entity '${name}' not found.`
3193
+ },
3194
+ `Entity '${name}' not found.`,
3195
+ { includeSerializedStructuredContent: structured }
3196
+ );
2797
3197
  }
2798
- if (repo) {
2799
- uris.add("repository://index");
3198
+ return createMcpResponse(
3199
+ {
3200
+ success: true,
3201
+ deletedCount: changes
3202
+ },
3203
+ `Deleted entity '${name}' and its cascade (relations, observations).`,
3204
+ {
3205
+ structuredContentPathHint: "deletedCount",
3206
+ includeSerializedStructuredContent: structured
3207
+ }
3208
+ );
3209
+ }
3210
+ async function handleCreateRelation(params, db2, _vectors) {
3211
+ const validated = CreateRelationSchema.parse(params);
3212
+ const { from_entity, to_entity, relation_type, owner, repo, structured } = validated;
3213
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3214
+ try {
3215
+ const txn = db2.db.transaction(() => {
3216
+ const fromExists = db2.db.prepare(`SELECT 1 FROM entities WHERE name = ?`).get(from_entity);
3217
+ if (!fromExists) {
3218
+ throw new Error("FROM_ENTITY_NOT_FOUND");
3219
+ }
3220
+ const toExists = db2.db.prepare(`SELECT 1 FROM entities WHERE name = ?`).get(to_entity);
3221
+ if (!toExists) {
3222
+ throw new Error("TO_ENTITY_NOT_FOUND");
3223
+ }
3224
+ db2.db.prepare(
3225
+ `INSERT INTO relations (from_entity, to_entity, relation_type, repo, owner, created_at)
3226
+ VALUES (?, ?, ?, ?, ?, ?)`
3227
+ ).run(from_entity, to_entity, relation_type, repo ?? "", owner ?? "", now);
3228
+ });
3229
+ txn();
3230
+ } catch (error) {
3231
+ const err = error;
3232
+ if (err.message === "FROM_ENTITY_NOT_FOUND") {
3233
+ const response = createMcpResponse(
3234
+ {
3235
+ success: false,
3236
+ error: "FROM_ENTITY_NOT_FOUND",
3237
+ message: `Source entity '${from_entity}' not found. Create it first.`
3238
+ },
3239
+ `Source entity '${from_entity}' not found.`,
3240
+ { includeSerializedStructuredContent: structured }
3241
+ );
3242
+ response.isError = true;
3243
+ return response;
3244
+ }
3245
+ if (err.message === "TO_ENTITY_NOT_FOUND") {
3246
+ const response = createMcpResponse(
3247
+ {
3248
+ success: false,
3249
+ error: "TO_ENTITY_NOT_FOUND",
3250
+ message: `Target entity '${to_entity}' not found. Create it first.`
3251
+ },
3252
+ `Target entity '${to_entity}' not found.`,
3253
+ { includeSerializedStructuredContent: structured }
3254
+ );
3255
+ response.isError = true;
3256
+ return response;
3257
+ }
3258
+ if (err.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
3259
+ const response = createMcpResponse(
3260
+ {
3261
+ success: false,
3262
+ error: "RELATION_ALREADY_EXISTS",
3263
+ message: `Relation '${from_entity} \u2192[${relation_type}]\u2192 ${to_entity}' already exists.`
3264
+ },
3265
+ `Relation already exists.`,
3266
+ { includeSerializedStructuredContent: structured }
3267
+ );
3268
+ response.isError = true;
3269
+ return response;
3270
+ }
3271
+ if (err.code === "SQLITE_CONSTRAINT_FOREIGNKEY" || err.code === "SQLITE_CONSTRAINT") {
3272
+ const response = createMcpResponse(
3273
+ {
3274
+ success: false,
3275
+ error: "ENTITY_NOT_FOUND",
3276
+ message: `Entity not found \u2014 cannot create relation.`
3277
+ },
3278
+ `Entity not found \u2014 cannot create relation.`,
3279
+ { includeSerializedStructuredContent: structured }
3280
+ );
3281
+ response.isError = true;
3282
+ return response;
3283
+ }
3284
+ logger.error("[Tool] create_relation failed", { error: String(error) });
3285
+ throw error;
3286
+ }
3287
+ logger.info("[Tool] create_relation", {
3288
+ repo: repo ?? "",
3289
+ from: from_entity,
3290
+ to: to_entity,
3291
+ type: relation_type
3292
+ });
3293
+ return createMcpResponse(
3294
+ {
3295
+ success: true,
3296
+ relation: { from_entity, to_entity, relation_type }
3297
+ },
3298
+ `Created relation '${from_entity} \u2014[${relation_type}]\u2192 ${to_entity}' in repo "${repo ?? ""}".`,
3299
+ {
3300
+ structuredContentPathHint: "relation",
3301
+ includeSerializedStructuredContent: structured
3302
+ }
3303
+ );
3304
+ }
3305
+ async function handleDeleteRelation(params, db2, _vectors) {
3306
+ const validated = DeleteRelationSchema.parse(params);
3307
+ const { from_entity, to_entity, relation_type, owner: _owner, repo, structured } = validated;
3308
+ let changes = 0;
3309
+ try {
3310
+ const txn = db2.db.transaction(() => {
3311
+ const result = db2.db.prepare(`DELETE FROM relations WHERE from_entity = ? AND to_entity = ? AND relation_type = ?`).run(from_entity, to_entity, relation_type);
3312
+ changes = result.changes;
3313
+ });
3314
+ txn();
3315
+ } catch (error) {
3316
+ logger.error("[Tool] delete_relation failed", { error: String(error) });
3317
+ const response = createMcpResponse(
3318
+ {
3319
+ success: false,
3320
+ error: "DELETE_FAILED",
3321
+ message: `Failed to delete relation '${from_entity} \u2192[${relation_type}]\u2192 ${to_entity}'.`
3322
+ },
3323
+ `Failed to delete relation.`,
3324
+ { includeSerializedStructuredContent: structured }
3325
+ );
3326
+ response.isError = true;
3327
+ return response;
3328
+ }
3329
+ logger.info("[Tool] delete_relation", {
3330
+ repo: repo ?? "",
3331
+ from: from_entity,
3332
+ to: to_entity,
3333
+ type: relation_type,
3334
+ changes
3335
+ });
3336
+ if (changes === 0) {
3337
+ return createMcpResponse(
3338
+ {
3339
+ success: false,
3340
+ error: "RELATION_NOT_FOUND",
3341
+ message: `Relation '${from_entity} \u2192[${relation_type}]\u2192 ${to_entity}' not found.`
3342
+ },
3343
+ `Relation not found.`,
3344
+ { includeSerializedStructuredContent: structured }
3345
+ );
2800
3346
  }
2801
- const memoryId = args?.id || args?.memory_id || res?.data?.id;
2802
- if (typeof memoryId === "string" && /^[0-9a-f-]{36}$/i.test(memoryId) && toolName.startsWith("memory-")) {
2803
- uris.add(`memory://${memoryId}`);
3347
+ return createMcpResponse(
3348
+ {
3349
+ success: true,
3350
+ deletedCount: changes
3351
+ },
3352
+ `Deleted relation '${from_entity} \u2014[${relation_type}]\u2192 ${to_entity}'.`,
3353
+ {
3354
+ structuredContentPathHint: "deletedCount",
3355
+ includeSerializedStructuredContent: structured
3356
+ }
3357
+ );
3358
+ }
3359
+ async function handleDeleteObservation(params, db2, _vectors) {
3360
+ const validated = DeleteObservationSchema.parse(params);
3361
+ const { id, owner: _owner, repo, structured } = validated;
3362
+ let changes = 0;
3363
+ try {
3364
+ const txn = db2.db.transaction(() => {
3365
+ const result = db2.db.prepare(`DELETE FROM observations WHERE id = ?`).run(id);
3366
+ changes = result.changes;
3367
+ });
3368
+ txn();
3369
+ } catch (error) {
3370
+ logger.error("[Tool] delete_observation failed", { error: String(error) });
3371
+ const response = createMcpResponse(
3372
+ {
3373
+ success: false,
3374
+ error: "DELETE_FAILED",
3375
+ message: `Failed to delete observation '${id}'.`
3376
+ },
3377
+ `Failed to delete observation '${id}'.`,
3378
+ { includeSerializedStructuredContent: structured }
3379
+ );
3380
+ response.isError = true;
3381
+ return response;
2804
3382
  }
2805
- const taskId = args?.id || args?.task_id || res?.structuredData?.id;
2806
- if (typeof taskId === "string" && /^[0-9a-f-]{36}$/i.test(taskId) && toolName.startsWith("task-")) {
2807
- uris.add(`task://${taskId}`);
3383
+ logger.info("[Tool] delete_observation", { repo: repo ?? "", id, changes });
3384
+ if (changes === 0) {
3385
+ return createMcpResponse(
3386
+ {
3387
+ success: false,
3388
+ error: "OBSERVATION_NOT_FOUND",
3389
+ message: `Observation '${id}' not found.`
3390
+ },
3391
+ `Observation '${id}' not found.`,
3392
+ { includeSerializedStructuredContent: structured }
3393
+ );
2808
3394
  }
2809
- return [...uris];
3395
+ return createMcpResponse(
3396
+ {
3397
+ success: true,
3398
+ deletedCount: changes
3399
+ },
3400
+ `Deleted observation '${id}'.`,
3401
+ {
3402
+ structuredContentPathHint: "deletedCount",
3403
+ includeSerializedStructuredContent: structured
3404
+ }
3405
+ );
2810
3406
  }
2811
- function normalizeToolArguments(args, session2) {
2812
- if (!args || typeof args !== "object") {
2813
- return args;
3407
+
3408
+ // src/mcp/tools/index.ts
3409
+ var WRITE_TOOLS = /* @__PURE__ */ new Set([
3410
+ "memory-store",
3411
+ "memory-acknowledge",
3412
+ "memory-update",
3413
+ "memory-delete",
3414
+ "memory-bulk-delete",
3415
+ "memory-summarize",
3416
+ "handoff-create",
3417
+ "handoff-update",
3418
+ "standard-store",
3419
+ "standard-update",
3420
+ "standard-delete",
3421
+ "task-create",
3422
+ "task-create-interactive",
3423
+ "task-claim",
3424
+ "claim-release",
3425
+ "task-update",
3426
+ "task-delete",
3427
+ "decision-log",
3428
+ "session-summarize",
3429
+ // Upstream alias tools (write)
3430
+ "remember_fact",
3431
+ "remember_facts",
3432
+ "forget",
3433
+ // Knowledge graph tools (write)
3434
+ "create_entity",
3435
+ "delete_entity",
3436
+ "create_relation",
3437
+ "delete_relation",
3438
+ "delete_observation"
3439
+ ]);
3440
+ function validateRootBoundPath(value, field, session) {
3441
+ if (typeof value !== "string" || !path2.isAbsolute(value)) {
3442
+ return;
2814
3443
  }
3444
+ if (!isPathWithinRoots(value, session)) {
3445
+ throw new Error(`${field} must stay within the active MCP roots`);
3446
+ }
3447
+ }
3448
+ function normalizeToolArgs(args, session) {
2815
3449
  const anyArgs = args;
2816
3450
  const nextArgs = {
2817
3451
  ...anyArgs,
2818
3452
  scope: anyArgs.scope ? { ...anyArgs.scope } : void 0
2819
3453
  };
2820
- validateRootBoundPath(nextArgs.current_file_path, "current_file_path", session2);
2821
- validateRootBoundPath(nextArgs.doc_path, "doc_path", session2);
3454
+ validateRootBoundPath(nextArgs.current_file_path, "current_file_path", session);
3455
+ validateRootBoundPath(nextArgs.doc_path, "doc_path", session);
2822
3456
  if (!nextArgs.repo) {
2823
- nextArgs.repo = inferRepoFromSession(session2);
3457
+ nextArgs.repo = inferRepoFromSession(session);
2824
3458
  }
2825
3459
  const scope = nextArgs.scope;
2826
3460
  if (scope && !scope.repo) {
2827
- scope.repo = nextArgs.repo ?? inferRepoFromSession(session2);
3461
+ scope.repo = nextArgs.repo ?? inferRepoFromSession(session);
2828
3462
  }
2829
3463
  if (!nextArgs.owner) {
2830
3464
  const repoVal2 = nextArgs.repo || "";
2831
3465
  const parsed = parseRepoInput(repoVal2, void 0);
2832
- nextArgs.owner = parsed.owner || inferOwnerFromSession(session2) || "";
3466
+ nextArgs.owner = parsed.owner || inferOwnerFromSession(session) || "";
2833
3467
  if (nextArgs.owner && !repoVal2.includes("/")) {
2834
3468
  logger.warn(
2835
- `[router] owner inferred from session (${nextArgs.owner}) \u2014 may be incorrect. Agents should pass explicit owner/repo.`
3469
+ `[tools] owner inferred from session (${nextArgs.owner}) \u2014 may be incorrect. Agents should pass explicit owner/repo.`
2836
3470
  );
2837
3471
  }
2838
3472
  }
2839
3473
  if (scope && !scope.owner) {
2840
3474
  const repoVal2 = scope.repo || nextArgs.repo || "";
2841
3475
  const parsed = parseRepoInput(repoVal2, void 0);
2842
- scope.owner = parsed.owner || nextArgs.owner || inferOwnerFromSession(session2) || "";
3476
+ scope.owner = parsed.owner || nextArgs.owner || inferOwnerFromSession(session) || "";
2843
3477
  }
2844
- const ownerVal = nextArgs.owner || inferOwnerFromSession(session2) || "";
2845
- const repoVal = nextArgs.repo || inferRepoFromSession(session2) || "";
3478
+ const ownerVal = nextArgs.owner || inferOwnerFromSession(session) || "";
3479
+ const repoVal = nextArgs.repo || inferRepoFromSession(session) || "";
2846
3480
  const memories = nextArgs.memories;
2847
3481
  if (memories) {
2848
3482
  for (const mem of memories) {
@@ -2855,7 +3489,7 @@ function normalizeToolArguments(args, session2) {
2855
3489
  }
2856
3490
  }
2857
3491
  if (typeof nextArgs.current_file_path === "string" && scope) {
2858
- const containingRoot = path2.isAbsolute(nextArgs.current_file_path) ? findContainingRoot(nextArgs.current_file_path, session2) : null;
3492
+ const containingRoot = path2.isAbsolute(nextArgs.current_file_path) ? findContainingRoot(nextArgs.current_file_path, session) : null;
2859
3493
  if (containingRoot) {
2860
3494
  const relativePath = path2.relative(containingRoot, path2.resolve(nextArgs.current_file_path));
2861
3495
  const relativeFolder = path2.dirname(relativePath);
@@ -2866,24 +3500,835 @@ function normalizeToolArguments(args, session2) {
2866
3500
  }
2867
3501
  return nextArgs;
2868
3502
  }
2869
- function validateRootBoundPath(value, field, session2) {
2870
- if (typeof value !== "string" || !path2.isAbsolute(value)) {
2871
- return;
2872
- }
2873
- if (!isPathWithinRoots(value, session2)) {
2874
- throw new Error(`${field} must stay within the active MCP roots`);
2875
- }
2876
- }
2877
- function normalizePageLimit(value, fallback) {
2878
- if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
2879
- return Math.max(1, fallback);
2880
- }
2881
- return Math.min(value, 100);
2882
- }
2883
-
3503
+ function collectAffectedResourceUris(toolName, args, result) {
3504
+ const res = result;
3505
+ const repo = args?.repo || args?.scope?.repo || res?.data?.repo;
3506
+ const uris = /* @__PURE__ */ new Set();
3507
+ const touchesMemory = toolName.startsWith("memory-") || toolName === "task-update" || toolName === "task-delete";
3508
+ const touchesTasks = toolName.startsWith("task-");
3509
+ if (touchesMemory && repo) {
3510
+ uris.add(`repository://${encodeURIComponent(repo)}/memories`);
3511
+ }
3512
+ if (touchesTasks && repo) {
3513
+ uris.add(`repository://${encodeURIComponent(repo)}/tasks`);
3514
+ }
3515
+ if (repo) {
3516
+ uris.add("repository://index");
3517
+ }
3518
+ const memoryId = args?.id || args?.memory_id || res?.data?.id;
3519
+ if (typeof memoryId === "string" && /^[0-9a-f-]{36}$/i.test(memoryId) && toolName.startsWith("memory-")) {
3520
+ uris.add(`memory://${memoryId}`);
3521
+ }
3522
+ const taskId = args?.id || args?.task_id || res?.structuredData?.id;
3523
+ if (typeof taskId === "string" && /^[0-9a-f-]{36}$/i.test(taskId) && toolName.startsWith("task-")) {
3524
+ uris.add(`task://${taskId}`);
3525
+ }
3526
+ return [...uris];
3527
+ }
3528
+ function logToolAction(toolName, args, result, db2, isWrite) {
3529
+ try {
3530
+ const actionType = toolName.split("-")[1] || toolName;
3531
+ const res = result;
3532
+ const sc = res?.structuredData;
3533
+ const repo = args?.repo || args?.scope?.repo || "unknown";
3534
+ const logOptions = {
3535
+ query: args?.query || args?.title || args?.task_code || (toolName === "memory-recap" ? `Offset: ${args?.offset || 0}` : void 0),
3536
+ response: res,
3537
+ memoryId: args?.id || args?.memory_id || sc?.id,
3538
+ taskId: args?.id || args?.task_id || sc?.id,
3539
+ resultCount: Array.isArray(sc?.results) ? sc.results.length : sc?.count || 0
3540
+ };
3541
+ if (isWrite) {
3542
+ db2.actions.logAction(actionType, "", repo, logOptions);
3543
+ } else {
3544
+ void db2.withWrite(() => {
3545
+ db2.actions.logAction(actionType, "", repo, logOptions);
3546
+ });
3547
+ }
3548
+ } catch (e) {
3549
+ logger.error("Failed to log action", { toolName, error: String(e) });
3550
+ }
3551
+ }
3552
+ function makePublicSchema() {
3553
+ return z.object({}).catchall(z.unknown());
3554
+ }
3555
+ function toCallToolResult(response) {
3556
+ const content = Array.isArray(response.content) ? response.content.map((item) => {
3557
+ if (item.type === "image") {
3558
+ return { type: "image", data: item.data, mimeType: item.mimeType };
3559
+ }
3560
+ if (item.type === "resource") {
3561
+ return {
3562
+ type: "text",
3563
+ text: item.resource.text ?? JSON.stringify(item.resource)
3564
+ };
3565
+ }
3566
+ return { type: "text", text: item.text ?? "" };
3567
+ }) : [];
3568
+ return {
3569
+ content,
3570
+ isError: response.isError ?? false,
3571
+ ...response.structuredContent !== void 0 ? { structuredContent: response.structuredContent } : {}
3572
+ };
3573
+ }
3574
+ function buildExecutors(session, options) {
3575
+ const sampleMessage = options?.sampleMessage;
3576
+ const elicit = options?.elicit;
3577
+ return {
3578
+ "memory-store": (args, db2, vectors2, _extra) => handleMemoryStore(args, db2, vectors2),
3579
+ "memory-acknowledge": (args, db2, _vectors, _extra) => handleMemoryAcknowledge(args, db2),
3580
+ "memory-update": (args, db2, vectors2, _extra) => handleMemoryUpdate(args, db2, vectors2),
3581
+ "memory-recap": (args, db2, _vectors, _extra) => handleMemoryRecap(args, db2),
3582
+ "memory-search": (args, db2, vectors2, _extra) => handleMemorySearch(args, db2, vectors2),
3583
+ "memory-summarize": (args, db2, _vectors, _extra) => handleMemorySummarize(args, db2),
3584
+ "memory-synthesize": (args, db2, vectors2, _extra) => handleMemorySynthesize(args, db2, vectors2, {
3585
+ session,
3586
+ sampleMessage,
3587
+ elicit
3588
+ }),
3589
+ "memory-delete": (args, db2, vectors2, extra) => handleMemoryDelete(args, db2, vectors2, extra?.onProgress),
3590
+ "memory-detail": (args, db2, _vectors, _extra) => handleMemoryDetail(args, db2),
3591
+ "handoff-create": (args, db2, _vectors, _extra) => handleHandoffCreate(args, db2),
3592
+ "handoff-list": (args, db2, _vectors, _extra) => handleHandoffList(args, db2),
3593
+ "handoff-update": (args, db2, _vectors, _extra) => handleHandoffUpdate(args, db2),
3594
+ "task-claim": (args, db2, _vectors, _extra) => handleTaskClaim(args, db2),
3595
+ "claim-list": (args, db2, _vectors, _extra) => handleClaimList(args, db2),
3596
+ "claim-release": (args, db2, _vectors, _extra) => handleClaimRelease(args, db2),
3597
+ "standard-store": (args, db2, vectors2, _extra) => handleStandardStore(args, db2, vectors2),
3598
+ "standard-update": (args, db2, vectors2, _extra) => handleStandardUpdate(args, db2, vectors2),
3599
+ "standard-detail": (args, db2, _vectors, _extra) => handleStandardDetail(args, db2),
3600
+ "standard-delete": (args, db2, vectors2, _extra) => handleStandardDelete(args, db2, vectors2),
3601
+ "standard-search": (args, db2, vectors2, _extra) => handleStandardSearch(args, db2, vectors2),
3602
+ "task-create": (args, db2, _vectors, _extra) => handleTaskCreate(args, db2),
3603
+ "task-create-interactive": (args, db2, _vectors, _extra) => handleTaskCreateInteractive(args, db2, { session, elicit }),
3604
+ "task-update": (args, db2, vectors2, _extra) => handleTaskUpdate(args, db2, vectors2),
3605
+ "task-delete": (args, db2, _vectors, _extra) => handleTaskDelete(args, db2),
3606
+ "task-list": (args, db2, _vectors, _extra) => handleTaskList(args, db2),
3607
+ "task-search": (args, db2, _vectors, _extra) => handleTaskSearch(args, db2),
3608
+ "task-detail": (args, db2, _vectors, _extra) => handleTaskGet(args, db2),
3609
+ "agent-context": (args, db2, vectors2, _extra) => handleAgentContext(args, db2, vectors2),
3610
+ "decision-log": (args, db2, vectors2, _extra) => handleDecisionLog(args, db2, vectors2),
3611
+ "session-summarize": (args, db2, vectors2, _extra) => handleSessionSummarize(args, db2, vectors2),
3612
+ // Upstream alias tools
3613
+ remember_fact: (args, db2, vectors2, _extra) => handleMemoryStore(args, db2, vectors2),
3614
+ remember_facts: (args, db2, vectors2, _extra) => handleMemoryStore(args, db2, vectors2),
3615
+ recall: (args, db2, vectors2, _extra) => handleMemorySearch(args, db2, vectors2),
3616
+ forget: (args, db2, vectors2, _extra) => handleMemoryDelete(args, db2, vectors2),
3617
+ // Knowledge graph tools
3618
+ create_entity: (args, db2, _vectors, _extra) => handleCreateEntity(args, db2, _vectors),
3619
+ delete_entity: (args, db2, _vectors, _extra) => handleDeleteEntity(args, db2, _vectors),
3620
+ create_relation: (args, db2, _vectors, _extra) => handleCreateRelation(args, db2, _vectors),
3621
+ delete_relation: (args, db2, _vectors, _extra) => handleDeleteRelation(args, db2, _vectors),
3622
+ delete_observation: (args, db2, _vectors, _extra) => handleDeleteObservation(args, db2, _vectors)
3623
+ };
3624
+ }
3625
+ function registerAllTools(server, store, vectors2, session, options) {
3626
+ const executors = buildExecutors(session, options);
3627
+ const definitions = TOOL_DEFINITIONS.filter((def) => {
3628
+ if (def.name === "memory-synthesize" && !session.supportsSampling) {
3629
+ return false;
3630
+ }
3631
+ if (def.name === "task-create-interactive" && !session.supportsElicitationForm) {
3632
+ return false;
3633
+ }
3634
+ return true;
3635
+ });
3636
+ const publicSchema = makePublicSchema();
3637
+ for (const def of definitions) {
3638
+ const toolName = def.name;
3639
+ const executor = executors[toolName];
3640
+ if (!executor) {
3641
+ logger.warn(`[registerAllTools] No executor for tool: ${toolName} \u2014 skipping`);
3642
+ continue;
3643
+ }
3644
+ const isWrite = WRITE_TOOLS.has(toolName);
3645
+ server.registerTool(
3646
+ toolName,
3647
+ {
3648
+ description: def.description ?? "",
3649
+ inputSchema: publicSchema
3650
+ },
3651
+ async (args, extra) => {
3652
+ const rawArgs = args ?? {};
3653
+ const normalizedArgs = normalizeToolArgs(rawArgs, session);
3654
+ logger.info(`[Tool] ${toolName}`, {
3655
+ repo: normalizedArgs?.repo || "unknown",
3656
+ write: isWrite
3657
+ });
3658
+ const progressToken = extra?.mcpReq?._meta?.progressToken;
3659
+ const executorExtra = {
3660
+ onProgress: progressToken !== void 0 ? (progress, total) => {
3661
+ void extra.mcpReq.notify({
3662
+ method: "notifications/progress",
3663
+ params: { progressToken, progress, total }
3664
+ }).catch(() => {
3665
+ });
3666
+ } : void 0,
3667
+ signal: extra?.mcpReq?.signal
3668
+ };
3669
+ const executeFn = () => executor(normalizedArgs, store, vectors2, executorExtra);
3670
+ let result;
3671
+ try {
3672
+ if (isWrite) {
3673
+ result = await store.withWrite(executeFn);
3674
+ } else {
3675
+ result = await executeFn();
3676
+ }
3677
+ } catch (err) {
3678
+ logger.error(`[Tool] ${toolName} failed`, { error: String(err) });
3679
+ return {
3680
+ content: [{ type: "text", text: `Error: ${err.message}` }],
3681
+ isError: true
3682
+ };
3683
+ }
3684
+ logger.info(`[Tool] ${toolName} result`, {
3685
+ repo: normalizedArgs?.repo || "unknown"
3686
+ });
3687
+ logToolAction(toolName, normalizedArgs, result, store, isWrite);
3688
+ const affectedUris = collectAffectedResourceUris(toolName, normalizedArgs, result);
3689
+ if (affectedUris.length > 0) {
3690
+ options?.onResourcesMutated?.(affectedUris);
3691
+ }
3692
+ return toCallToolResult(result);
3693
+ }
3694
+ );
3695
+ logger.debug(`[registerAllTools] Registered tool: ${toolName}`);
3696
+ }
3697
+ }
3698
+
3699
+ // src/mcp/resources/sdk-index.ts
3700
+ import { ResourceTemplate } from "@modelcontextprotocol/server";
3701
+ function registerAllResources(server, store, _vectors, session) {
3702
+ const db2 = store;
3703
+ function getRepos() {
3704
+ const values = /* @__PURE__ */ new Set();
3705
+ for (const repo of db2.system.listRepos()) {
3706
+ values.add(repo);
3707
+ }
3708
+ if (session.roots.length > 0) {
3709
+ for (const root of session.roots) {
3710
+ const name = root.name || root.uri.split("/").filter(Boolean).pop() || "";
3711
+ if (name) values.add(name);
3712
+ }
3713
+ }
3714
+ return [...values].sort((a, b) => a.localeCompare(b));
3715
+ }
3716
+ function getTags() {
3717
+ const values = /* @__PURE__ */ new Set();
3718
+ const memories = db2.memories.getRecentMemories("", "", 1e3);
3719
+ for (const memory of memories) {
3720
+ for (const tag of memory.tags || []) {
3721
+ if (typeof tag === "string" && tag.trim()) {
3722
+ values.add(tag.trim());
3723
+ }
3724
+ }
3725
+ }
3726
+ return [...values].sort((a, b) => a.localeCompare(b));
3727
+ }
3728
+ function _deriveLastModified(values) {
3729
+ const normalized = values.filter((v) => typeof v === "string" && v.length > 0);
3730
+ return normalized.sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
3731
+ }
3732
+ server.registerResource(
3733
+ "repository-index",
3734
+ "repository://index",
3735
+ {
3736
+ title: "Repository Index",
3737
+ description: "List of all known repositories with memory/task counts and last activity",
3738
+ mimeType: "application/json"
3739
+ },
3740
+ async (uri, _extra) => {
3741
+ const repos2 = db2.system.listRepoNavigation();
3742
+ const payload = JSON.stringify(repos2, null, 2);
3743
+ return {
3744
+ contents: [
3745
+ {
3746
+ uri: uri.toString(),
3747
+ mimeType: "application/json",
3748
+ text: payload
3749
+ }
3750
+ ]
3751
+ };
3752
+ }
3753
+ );
3754
+ server.registerResource(
3755
+ "session-roots",
3756
+ "session://roots",
3757
+ {
3758
+ title: "Session Roots",
3759
+ description: session?.roots?.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
3760
+ mimeType: "application/json"
3761
+ },
3762
+ async (uri, _extra) => {
3763
+ const payload = JSON.stringify({ roots: session?.roots ?? [] }, null, 2);
3764
+ return {
3765
+ contents: [
3766
+ {
3767
+ uri: uri.toString(),
3768
+ mimeType: "application/json",
3769
+ text: payload
3770
+ }
3771
+ ]
3772
+ };
3773
+ }
3774
+ );
3775
+ const repos = getRepos();
3776
+ const tags = getTags();
3777
+ server.registerResource(
3778
+ "repository-memories",
3779
+ new ResourceTemplate("repository://{name}/memories{?search,type,tag,limit,offset}", {
3780
+ list: void 0,
3781
+ complete: {
3782
+ name: async (value) => rankCompletionValues(repos, value),
3783
+ tag: async (value) => rankCompletionValues(tags, value)
3784
+ }
3785
+ }),
3786
+ {
3787
+ title: "Repository Memories",
3788
+ description: "All active memory entries for a specific repository, optionally filtered by search, type, or tag",
3789
+ mimeType: "application/json"
3790
+ },
3791
+ async (uri, variables, _extra) => {
3792
+ const name = variables.name;
3793
+ const search = variables.search || "";
3794
+ const type = variables.type;
3795
+ const tag = variables.tag;
3796
+ const result = db2.memories.listMemoriesForDashboard({
3797
+ repo: name,
3798
+ type: type || void 0,
3799
+ tag: tag || void 0,
3800
+ search: search || void 0,
3801
+ limit: 50
3802
+ });
3803
+ const entries = result.items;
3804
+ const payload = JSON.stringify(entries, null, 2);
3805
+ return {
3806
+ contents: [
3807
+ {
3808
+ uri: uri.toString(),
3809
+ mimeType: "application/json",
3810
+ text: payload
3811
+ }
3812
+ ]
3813
+ };
3814
+ }
3815
+ );
3816
+ server.registerResource(
3817
+ "memory-detail",
3818
+ new ResourceTemplate("memory://{id}", { list: void 0 }),
3819
+ {
3820
+ title: "Memory Detail",
3821
+ description: "Full content and statistics for a specific memory UUID",
3822
+ mimeType: "application/json"
3823
+ },
3824
+ async (uri, variables, _extra) => {
3825
+ const id = variables.id;
3826
+ const entry = db2.memories.getByIdWithStats(id);
3827
+ if (!entry) {
3828
+ throw new Error(`Memory with ID ${id} not found.`);
3829
+ }
3830
+ const payload = JSON.stringify(entry, null, 2);
3831
+ return {
3832
+ contents: [
3833
+ {
3834
+ uri: uri.toString(),
3835
+ mimeType: "application/json",
3836
+ text: payload
3837
+ }
3838
+ ]
3839
+ };
3840
+ }
3841
+ );
3842
+ server.registerResource(
3843
+ "repository-tasks",
3844
+ new ResourceTemplate("repository://{name}/tasks{?status,priority,limit,offset}", {
3845
+ list: void 0,
3846
+ complete: {
3847
+ name: async (value) => rankCompletionValues(repos, value)
3848
+ }
3849
+ }),
3850
+ {
3851
+ title: "Repository Tasks",
3852
+ description: "All active tasks for a specific repository, optionally filtered by status or priority",
3853
+ mimeType: "application/json"
3854
+ },
3855
+ async (uri, variables, _extra) => {
3856
+ const name = variables.name;
3857
+ const status = variables.status;
3858
+ const priority = variables.priority;
3859
+ const owner = parseRepoInput(name).owner;
3860
+ let tasks;
3861
+ if (status && status !== "all") {
3862
+ const statuses = status.split(",").map((s) => s.trim());
3863
+ tasks = db2.tasks.getTasksByMultipleStatuses(owner, name, statuses);
3864
+ } else {
3865
+ tasks = db2.tasks.getTasksByMultipleStatuses(owner, name, ["backlog", "pending", "in_progress", "blocked"]);
3866
+ }
3867
+ if (priority) {
3868
+ const p = Number(priority);
3869
+ if (!isNaN(p)) {
3870
+ tasks = tasks.filter((t) => t.priority === p);
3871
+ }
3872
+ }
3873
+ const payload = JSON.stringify(tasks, null, 2);
3874
+ return {
3875
+ contents: [
3876
+ {
3877
+ uri: uri.toString(),
3878
+ mimeType: "application/json",
3879
+ text: payload
3880
+ }
3881
+ ]
3882
+ };
3883
+ }
3884
+ );
3885
+ server.registerResource(
3886
+ "task-detail",
3887
+ new ResourceTemplate("task://{id}", { list: void 0 }),
3888
+ {
3889
+ title: "Task Detail",
3890
+ description: "Full content and comments for a specific task UUID",
3891
+ mimeType: "application/json"
3892
+ },
3893
+ async (uri, variables, _extra) => {
3894
+ const id = variables.id;
3895
+ const task = db2.tasks.getTaskById(id);
3896
+ if (!task) {
3897
+ throw new Error(`Task with ID ${id} not found.`);
3898
+ }
3899
+ const payload = JSON.stringify(task, null, 2);
3900
+ return {
3901
+ contents: [
3902
+ {
3903
+ uri: uri.toString(),
3904
+ mimeType: "application/json",
3905
+ text: payload
3906
+ }
3907
+ ]
3908
+ };
3909
+ }
3910
+ );
3911
+ server.registerResource(
3912
+ "repository-summary",
3913
+ new ResourceTemplate("repository://{name}/summary", {
3914
+ list: void 0,
3915
+ complete: {
3916
+ name: async (value) => rankCompletionValues(repos, value)
3917
+ }
3918
+ }),
3919
+ {
3920
+ title: "Repository Summary",
3921
+ description: "High-level architectural summary for a repository",
3922
+ mimeType: "text/plain"
3923
+ },
3924
+ async (uri, variables, _extra) => {
3925
+ const name = variables.name;
3926
+ const summary = db2.summaries.getSummary("", name);
3927
+ const text = summary?.summary || `No summary available for repository: ${name}`;
3928
+ return {
3929
+ contents: [
3930
+ {
3931
+ uri: uri.toString(),
3932
+ mimeType: "text/plain",
3933
+ text
3934
+ }
3935
+ ]
3936
+ };
3937
+ }
3938
+ );
3939
+ server.registerResource(
3940
+ "repository-actions",
3941
+ new ResourceTemplate("repository://{name}/actions{?limit,offset}", {
3942
+ list: void 0,
3943
+ complete: {
3944
+ name: async (value) => rankCompletionValues(repos, value)
3945
+ }
3946
+ }),
3947
+ {
3948
+ title: "Repository Actions",
3949
+ description: "Audit log of agent tool actions scoped to a repository",
3950
+ mimeType: "application/json"
3951
+ },
3952
+ async (uri, variables, _extra) => {
3953
+ const name = variables.name;
3954
+ const actions = db2.actions.getRecentActions("", name, 100);
3955
+ const payload = JSON.stringify(actions, null, 2);
3956
+ return {
3957
+ contents: [
3958
+ {
3959
+ uri: uri.toString(),
3960
+ mimeType: "application/json",
3961
+ text: payload
3962
+ }
3963
+ ]
3964
+ };
3965
+ }
3966
+ );
3967
+ server.registerResource(
3968
+ "action-detail",
3969
+ new ResourceTemplate("action://{id}", { list: void 0 }),
3970
+ {
3971
+ title: "Action Detail",
3972
+ description: "Full details of a specific audit log entry by integer ID",
3973
+ mimeType: "application/json"
3974
+ },
3975
+ async (uri, variables, _extra) => {
3976
+ const idStr = variables.id;
3977
+ const id = Number(idStr);
3978
+ const action = db2.actions.getActionById(id);
3979
+ if (!action) {
3980
+ throw new Error(`Action with ID ${id} not found.`);
3981
+ }
3982
+ const payload = JSON.stringify(action, null, 2);
3983
+ return {
3984
+ contents: [
3985
+ {
3986
+ uri: uri.toString(),
3987
+ mimeType: "application/json",
3988
+ text: payload
3989
+ }
3990
+ ]
3991
+ };
3992
+ }
3993
+ );
3994
+ }
3995
+
3996
+ // src/mcp/prompts/sdk-index.ts
3997
+ function registerAllPrompts(server, store, _vectors, session) {
3998
+ const _db = store;
3999
+ const promptNames = listPromptFiles();
4000
+ for (const name of promptNames) {
4001
+ let loaded;
4002
+ try {
4003
+ loaded = loadPromptFromMarkdown(name);
4004
+ } catch (e) {
4005
+ logger.warn(`[prompts] Failed to load prompt ${name}: ${e}`);
4006
+ continue;
4007
+ }
4008
+ server.registerPrompt(
4009
+ loaded.name,
4010
+ {
4011
+ title: loaded.name,
4012
+ description: loaded.description
4013
+ },
4014
+ async (_args, _extra) => {
4015
+ const inferredRepo = inferRepoFromSession(session);
4016
+ const inferredOwner = inferOwnerFromSession(session);
4017
+ let text = loaded.content;
4018
+ text = text.replace(/\{\{current_repo\}\}/g, inferredRepo || "unknown-repo");
4019
+ text = text.replace(/\{\{current_owner\}\}/g, inferredOwner || "unknown-owner");
4020
+ return {
4021
+ description: loaded.description,
4022
+ messages: [
4023
+ {
4024
+ role: "user",
4025
+ content: {
4026
+ type: "text",
4027
+ text
4028
+ }
4029
+ }
4030
+ ],
4031
+ ...loaded.agent ? { _meta: { agent: loaded.agent } } : {}
4032
+ };
4033
+ }
4034
+ );
4035
+ logger.debug(`[prompts] Registered prompt: ${loaded.name}`);
4036
+ }
4037
+ }
4038
+
4039
+ // src/mcp/completion.ts
4040
+ import fs from "fs";
4041
+ import path3 from "path";
4042
+ var MAX_COMPLETION_VALUES = 100;
4043
+ var MAX_FILE_SCAN_RESULTS = 300;
4044
+ async function complete(params, db2, session) {
4045
+ const refType = params?.ref?.type;
4046
+ const argumentName = typeof params?.argument?.name === "string" ? params.argument.name : "";
4047
+ const argumentValue = typeof params?.argument?.value === "string" ? params.argument.value : "";
4048
+ const contextArguments = params?.context?.arguments ?? {};
4049
+ if (!refType || !argumentName) {
4050
+ throw invalidCompletionParams("completion/complete requires ref.type and argument.name");
4051
+ }
4052
+ const dataSources = {
4053
+ repos: getSuggestedRepos(db2, session),
4054
+ tags: getSuggestedTags(db2),
4055
+ filePaths: getSuggestedFilePaths(session),
4056
+ tasks: getSuggestedTasks(db2, session, contextArguments)
4057
+ };
4058
+ if (refType === "ref/prompt") {
4059
+ const promptName = typeof params?.ref?.name === "string" ? params.ref.name : "";
4060
+ if (!promptName) {
4061
+ throw invalidCompletionParams("Prompt completion requires ref.name");
4062
+ }
4063
+ return {
4064
+ completion: buildCompletionResult(
4065
+ await completePromptArgument(promptName, argumentName, argumentValue, contextArguments, dataSources)
4066
+ )
4067
+ };
4068
+ }
4069
+ if (refType === "ref/resource") {
4070
+ const resourceUri = typeof params?.ref?.uri === "string" ? params.ref.uri : "";
4071
+ if (!resourceUri) {
4072
+ throw invalidCompletionParams("Resource completion requires ref.uri");
4073
+ }
4074
+ return {
4075
+ completion: buildCompletionResult(
4076
+ completeResourceArgument(resourceUri, argumentName, argumentValue, contextArguments, dataSources)
4077
+ )
4078
+ };
4079
+ }
4080
+ throw invalidCompletionParams(`Unsupported completion ref type: ${refType}`);
4081
+ }
4082
+ function buildCompletionResult(values) {
4083
+ const capped = values.slice(0, MAX_COMPLETION_VALUES);
4084
+ return {
4085
+ values: capped,
4086
+ total: values.length,
4087
+ hasMore: values.length > capped.length
4088
+ };
4089
+ }
4090
+ function getSuggestedRepos(db2, session) {
4091
+ const values = /* @__PURE__ */ new Set();
4092
+ const inferredRepo = inferRepoFromSession(session);
4093
+ if (inferredRepo) values.add(inferredRepo);
4094
+ for (const rootPath of getFilesystemRoots(session)) {
4095
+ values.add(path3.basename(rootPath));
4096
+ }
4097
+ for (const repo of db2.system.listRepos()) {
4098
+ values.add(repo);
4099
+ }
4100
+ return [...values].sort((a, b) => a.localeCompare(b));
4101
+ }
4102
+ function getSuggestedTags(db2) {
4103
+ const values = /* @__PURE__ */ new Set();
4104
+ const memories = db2.memories.getRecentMemories("", "", 1e3);
4105
+ for (const memory of memories) {
4106
+ for (const tag of memory.tags || []) {
4107
+ if (typeof tag === "string" && tag.trim()) {
4108
+ values.add(tag.trim());
4109
+ }
4110
+ }
4111
+ }
4112
+ return [...values].sort((a, b) => a.localeCompare(b));
4113
+ }
4114
+ function getSuggestedTasks(db2, session, contextArguments) {
4115
+ const repo = typeof contextArguments.repo === "string" && contextArguments.repo.trim() ? contextArguments.repo.trim() : inferRepoFromSession(session);
4116
+ if (!repo) return [];
4117
+ return db2.tasks.getTasksByRepo("", repo, void 0, 100).map((task) => ({
4118
+ id: task.id,
4119
+ task_code: task.task_code,
4120
+ title: task.title
4121
+ }));
4122
+ }
4123
+ function getSuggestedFilePaths(session) {
4124
+ const roots = getFilesystemRoots(session);
4125
+ const results = [];
4126
+ for (const rootPath of roots) {
4127
+ collectFiles(rootPath, rootPath, results);
4128
+ if (results.length >= MAX_FILE_SCAN_RESULTS) break;
4129
+ }
4130
+ return results.sort((a, b) => a.localeCompare(b));
4131
+ }
4132
+ function collectFiles(rootPath, currentPath, results) {
4133
+ if (results.length >= MAX_FILE_SCAN_RESULTS) return;
4134
+ let entries;
4135
+ try {
4136
+ entries = fs.readdirSync(currentPath, { withFileTypes: true });
4137
+ } catch {
4138
+ return;
4139
+ }
4140
+ for (const entry of entries) {
4141
+ if (results.length >= MAX_FILE_SCAN_RESULTS) return;
4142
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") continue;
4143
+ const fullPath = path3.join(currentPath, entry.name);
4144
+ if (entry.isDirectory()) {
4145
+ collectFiles(rootPath, fullPath, results);
4146
+ continue;
4147
+ }
4148
+ if (!entry.isFile()) continue;
4149
+ results.push(path3.relative(rootPath, fullPath) || entry.name);
4150
+ }
4151
+ }
4152
+ function invalidCompletionParams(message) {
4153
+ const error = new Error(message);
4154
+ error.code = -32602;
4155
+ return error;
4156
+ }
4157
+
4158
+ // src/mcp/mcp-server.ts
4159
+ function createMcpServer(store, vectors2, session) {
4160
+ const instructions = loadServerInstructions();
4161
+ const ctx = session ?? createSessionContext();
4162
+ const server = new McpServer2(
4163
+ {
4164
+ name: "local-memory-mcp",
4165
+ version: CAPABILITIES.serverInfo.version
4166
+ },
4167
+ {
4168
+ instructions,
4169
+ capabilities: CAPABILITIES.capabilities
4170
+ }
4171
+ );
4172
+ registerAllTools(server, store, vectors2, ctx);
4173
+ registerAllResources(server, store, vectors2, ctx);
4174
+ registerAllPrompts(server, store, vectors2, ctx);
4175
+ const _removeLogSink = addLogSink((payload) => {
4176
+ void server.sendLoggingMessage({
4177
+ level: payload.level,
4178
+ data: payload.data,
4179
+ logger: payload.logger
4180
+ }).catch(() => {
4181
+ });
4182
+ });
4183
+ server.server.setRequestHandler("completion/complete", async (request, _serverCtx) => {
4184
+ const params = request.params;
4185
+ const completionResult = await complete(
4186
+ {
4187
+ ref: {
4188
+ type: params.ref.type,
4189
+ name: params.ref.name,
4190
+ uri: params.ref.uri
4191
+ },
4192
+ argument: {
4193
+ name: params.argument.name,
4194
+ value: params.argument.value
4195
+ },
4196
+ context: {
4197
+ arguments: params.context?.arguments
4198
+ }
4199
+ },
4200
+ store,
4201
+ ctx
4202
+ );
4203
+ return {
4204
+ completion: completionResult.completion
4205
+ };
4206
+ });
4207
+ return server;
4208
+ }
4209
+
4210
+ // src/mcp/services/soul-maintenance.ts
4211
+ function applyDecay(db2, options) {
4212
+ const { immunizedTags = [], decayAfterDays = 7, decayRate = 0.5, archiveThreshold = 1 } = options ?? {};
4213
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4214
+ const cutoff = new Date(Date.now() - decayAfterDays * 24 * 60 * 60 * 1e3).toISOString();
4215
+ const rows = db2.prepare(
4216
+ `SELECT id, importance, tags FROM memories
4217
+ WHERE status = 'active'
4218
+ AND (last_used_at IS NULL OR last_used_at < ?)`
4219
+ ).all(cutoff);
4220
+ if (rows.length === 0) {
4221
+ logger.debug("[SoulMaintenance] No memories eligible for decay");
4222
+ return { decayed: 0, archived: 0, immunizedSkipped: 0 };
4223
+ }
4224
+ let decayed = 0;
4225
+ let archivedCount = 0;
4226
+ let immunizedSkipped = 0;
4227
+ const toDecay = [];
4228
+ const toArchive = [];
4229
+ for (const row of rows) {
4230
+ let tags = [];
4231
+ if (row.tags) {
4232
+ try {
4233
+ tags = JSON.parse(row.tags);
4234
+ } catch {
4235
+ tags = [];
4236
+ }
4237
+ }
4238
+ if (immunizedTags.length > 0 && tags.some((t) => immunizedTags.includes(t))) {
4239
+ immunizedSkipped++;
4240
+ continue;
4241
+ }
4242
+ const newImportance = Math.max(1, Math.floor(row.importance - decayRate));
4243
+ toDecay.push({ id: row.id, newImportance });
4244
+ if (newImportance < archiveThreshold) {
4245
+ toArchive.push(row.id);
4246
+ }
4247
+ }
4248
+ if (toDecay.length > 0) {
4249
+ const updateStmt = db2.prepare("UPDATE memories SET importance = ?, updated_at = ? WHERE id = ?");
4250
+ for (const item of toDecay) {
4251
+ updateStmt.run(item.newImportance, now, item.id);
4252
+ }
4253
+ decayed = toDecay.length;
4254
+ }
4255
+ if (toArchive.length > 0) {
4256
+ const placeholders = toArchive.map(() => "?").join(",");
4257
+ const archiveResult = db2.prepare(`UPDATE memories SET status = 'archived', updated_at = ? WHERE id IN (${placeholders})`).run(now, ...toArchive);
4258
+ archivedCount = archiveResult.changes;
4259
+ }
4260
+ if (decayed > 0 || archivedCount > 0 || immunizedSkipped > 0) {
4261
+ logger.info("[SoulMaintenance] Decay cycle complete", {
4262
+ decayed,
4263
+ archived: archivedCount,
4264
+ immunizedSkipped
4265
+ });
4266
+ }
4267
+ return { decayed, archived: archivedCount, immunizedSkipped };
4268
+ }
4269
+
4270
+ // src/mcp/services/maintenance-job.ts
4271
+ var MAINTENANCE_OWNER = "__soul__";
4272
+ var MAINTENANCE_REPO = "__maintenance__";
4273
+ var MAINTENANCE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
4274
+ function wasMaintenanceRunRecent(db2) {
4275
+ try {
4276
+ const row = db2.db.prepare("SELECT updated_at FROM memory_summary WHERE owner = ? AND repo = ?").get(MAINTENANCE_OWNER, MAINTENANCE_REPO);
4277
+ if (!row?.updated_at) return false;
4278
+ const lastRun = new Date(row.updated_at).getTime();
4279
+ return Date.now() - lastRun < MAINTENANCE_INTERVAL_MS;
4280
+ } catch (err) {
4281
+ logger.warn("[MaintenanceJob] Failed to check last maintenance time", { error: String(err) });
4282
+ return false;
4283
+ }
4284
+ }
4285
+ function recordMaintenanceRun(db2) {
4286
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4287
+ try {
4288
+ db2.db.prepare(
4289
+ `INSERT INTO memory_summary (owner, repo, summary, updated_at)
4290
+ VALUES (?, ?, ?, ?)
4291
+ ON CONFLICT(owner, repo) DO UPDATE SET summary = excluded.summary, updated_at = excluded.updated_at`
4292
+ ).run(MAINTENANCE_OWNER, MAINTENANCE_REPO, now, now);
4293
+ } catch (err) {
4294
+ logger.warn("[MaintenanceJob] Failed to record maintenance run time", { error: String(err) });
4295
+ }
4296
+ }
4297
+ async function runStartupMaintenance(db2, decayOptions) {
4298
+ if (wasMaintenanceRunRecent(db2)) {
4299
+ logger.info("[MaintenanceJob] Skipping \u2014 maintenance already ran within the last 24 hours");
4300
+ return {
4301
+ decay: { decayed: 0, archived: 0, immunizedSkipped: 0 },
4302
+ expiredArchived: 0,
4303
+ lowScoreArchived: 0,
4304
+ skipped: true
4305
+ };
4306
+ }
4307
+ logger.info("[MaintenanceJob] Starting startup maintenance sweep");
4308
+ const decay = applyDecay(db2.db, decayOptions);
4309
+ const expiredArchived = db2.memoryArchives.archiveExpiredMemories(true);
4310
+ const lowScoreArchived = db2.memoryArchives.archiveLowScoreMemories(true);
4311
+ recordMaintenanceRun(db2);
4312
+ const totalArchived = (expiredArchived || 0) + (lowScoreArchived || 0) + (decay.archived || 0);
4313
+ logger.info("[MaintenanceJob] Startup maintenance complete", {
4314
+ decayed: decay.decayed,
4315
+ immunizedSkipped: decay.immunizedSkipped,
4316
+ expiredArchived,
4317
+ lowScoreArchived,
4318
+ decayArchived: decay.archived,
4319
+ totalArchived
4320
+ });
4321
+ return {
4322
+ decay,
4323
+ expiredArchived: expiredArchived || 0,
4324
+ lowScoreArchived: lowScoreArchived || 0,
4325
+ skipped: false
4326
+ };
4327
+ }
4328
+
2884
4329
  // src/mcp/server.ts
2885
4330
  import fs2 from "fs";
2886
- import path3 from "path";
4331
+ import path4 from "path";
2887
4332
  process.env.MCP_SERVER = "true";
2888
4333
  if (process.argv.includes("doctor")) {
2889
4334
  process.stderr.write("\n\u{1F3E5} MCP Local Memory - System Diagnosis\n\n");
@@ -2915,19 +4360,19 @@ if (process.argv.includes("doctor")) {
2915
4360
  }
2916
4361
  var db = await SQLiteStore.create();
2917
4362
  var vectors = new RealVectorStore(db);
2918
- addLogSink(createFileSink(path3.dirname(db.getDbPath())));
4363
+ addLogSink(createFileSink(path4.dirname(db.getDbPath())));
2919
4364
  logger.info("[Server] startup", { pid: process.pid, version: CAPABILITIES.serverInfo.version, db: db.getDbPath() });
2920
4365
  vectors.initialize().catch((err) => {
2921
4366
  logger.warn("[Server] Initial vector model loading failed. Will retry on first use.", { error: String(err) });
2922
4367
  });
2923
- var expiredArchived = db.memoryArchives.archiveExpiredMemories();
2924
- var lowScoreArchived = db.memoryArchives.archiveLowScoreMemories();
2925
- var totalArchived = (expiredArchived || 0) + (lowScoreArchived || 0);
2926
- if (totalArchived > 0) {
2927
- logger.info(
2928
- `[Server] Archived ${totalArchived} memories (expired: ${expiredArchived}, low-score: ${lowScoreArchived}) on startup.`
2929
- );
2930
- }
4368
+ runStartupMaintenance(db).then((result) => {
4369
+ if (!result.skipped) {
4370
+ logger.info("[Server] Startup maintenance complete", {
4371
+ decayed: result.decay.decayed,
4372
+ archived: result.expiredArchived + result.lowScoreArchived + result.decay.archived
4373
+ });
4374
+ }
4375
+ });
2931
4376
  process.stdout.on("error", (err) => {
2932
4377
  if (err.code === "EPIPE") return;
2933
4378
  logger.error("stdout error", { error: String(err) });
@@ -2936,265 +4381,12 @@ process.stderr.on("error", (err) => {
2936
4381
  if (err.code === "EPIPE") return;
2937
4382
  logger.error("stderr error", { error: String(err) });
2938
4383
  });
2939
- var session = createSessionContext();
2940
- var resourceSubscriptions = /* @__PURE__ */ new Set();
2941
- var logNotificationsEnabled = false;
2942
- var handleMethod = createRouter(db, vectors, {
2943
- getSessionContext: () => session,
2944
- sampleMessage: (params) => requestClient("sampling/createMessage", params),
2945
- elicit: (params) => requestClient("elicitation/create", params),
2946
- onResourcesMutated: (uris) => notifyUpdatedResources(uris)
2947
- });
2948
- addLogSink((payload) => {
2949
- if (!logNotificationsEnabled) {
2950
- return;
2951
- }
2952
- reply({
2953
- jsonrpc: "2.0",
2954
- method: "notifications/message",
2955
- params: payload
2956
- });
2957
- });
2958
- process.on("SIGINT", () => {
2959
- logger.info("[Server] shutdown", { signal: "SIGINT", pid: process.pid });
2960
- for (const pending of pendingClientRequests.values()) {
2961
- pending.reject(new Error("Server stopped"));
2962
- }
2963
- pendingClientRequests.clear();
4384
+ var shutdown = async (signal) => {
4385
+ logger.info("[Server] shutdown", { signal, pid: process.pid });
4386
+ await handle?.close();
2964
4387
  db.close();
2965
4388
  process.exit(0);
2966
- });
2967
- process.on("SIGTERM", () => {
2968
- logger.info("[Server] shutdown", { signal: "SIGTERM", pid: process.pid });
2969
- for (const pending of pendingClientRequests.values()) {
2970
- pending.reject(new Error("Server stopped"));
2971
- }
2972
- pendingClientRequests.clear();
2973
- db.close();
2974
- process.exit(0);
2975
- });
2976
- var rl = readline.createInterface({
2977
- input: process.stdin,
2978
- output: process.stdout,
2979
- terminal: false
2980
- });
2981
- function reply(payload) {
2982
- try {
2983
- process.stdout.write(JSON.stringify(payload) + "\n");
2984
- } catch (err) {
2985
- logger.error("Reply error", { error: String(err) });
2986
- }
2987
- }
2988
- var isInitialized = false;
2989
- var activeRequests = /* @__PURE__ */ new Map();
2990
- var pendingClientRequests = /* @__PURE__ */ new Map();
2991
- var outgoingRequestId = 0;
2992
- function requestClient(method, params = {}) {
2993
- const id = `server:${++outgoingRequestId}`;
2994
- reply({
2995
- jsonrpc: "2.0",
2996
- id,
2997
- method,
2998
- params
2999
- });
3000
- return new Promise((resolve, reject) => {
3001
- pendingClientRequests.set(id, { method, resolve, reject });
3002
- });
3003
- }
3004
- async function refreshRoots(trigger) {
3005
- if (!session.supportsRoots) return;
3006
- try {
3007
- const result = await requestClient("roots/list");
3008
- const changed = updateSessionRoots(session, extractRootsFromResult(result));
3009
- logger.info("[Server] Refreshed client roots", {
3010
- trigger,
3011
- count: session.roots.length,
3012
- changed
3013
- });
3014
- if (changed) {
3015
- reply({
3016
- jsonrpc: "2.0",
3017
- method: "notifications/resources/list_changed"
3018
- });
3019
- reply({
3020
- jsonrpc: "2.0",
3021
- method: "notifications/prompts/list_changed"
3022
- });
3023
- }
3024
- } catch (error) {
3025
- logger.warn("[Server] Failed to refresh client roots", {
3026
- trigger,
3027
- error: String(error)
3028
- });
3029
- }
3030
- }
3031
- function notifyUpdatedResources(uris) {
3032
- const seen = /* @__PURE__ */ new Set();
3033
- for (const uri of uris) {
3034
- if (seen.has(uri)) continue;
3035
- seen.add(uri);
3036
- if (!resourceSubscriptions.has(uri)) {
3037
- continue;
3038
- }
3039
- reply({
3040
- jsonrpc: "2.0",
3041
- method: "notifications/resources/updated",
3042
- params: { uri }
3043
- });
3044
- }
3045
- }
3046
- rl.on("line", async (line) => {
3047
- if (!line.trim()) return;
3048
- let msg;
3049
- try {
3050
- msg = JSON.parse(line);
3051
- } catch {
3052
- return;
3053
- }
3054
- const { id, method, params } = msg;
3055
- const isNotification = id === void 0 || id === null;
3056
- if ((method === void 0 || method === null) && id !== void 0 && pendingClientRequests.has(id)) {
3057
- const pending = pendingClientRequests.get(id);
3058
- pendingClientRequests.delete(id);
3059
- if (msg.error) {
3060
- pending.reject(new Error(msg.error.message || `Client request failed: ${pending.method}`));
3061
- } else {
3062
- pending.resolve(msg.result);
3063
- }
3064
- return;
3065
- }
3066
- if (method === "initialize" && !isNotification) {
3067
- updateSessionFromInitialize(session, params);
3068
- reply({
3069
- jsonrpc: "2.0",
3070
- id,
3071
- result: {
3072
- protocolVersion: MCP_PROTOCOL_VERSION,
3073
- serverInfo: CAPABILITIES.serverInfo,
3074
- capabilities: CAPABILITIES.capabilities
3075
- }
3076
- });
3077
- return;
3078
- }
3079
- if (isNotification) {
3080
- if (method === "notifications/initialized") {
3081
- isInitialized = true;
3082
- logNotificationsEnabled = true;
3083
- logger.debug("[Server] Client initialized");
3084
- void refreshRoots("initialized");
3085
- } else if (method === "notifications/roots/list_changed") {
3086
- logger.debug("[Server] Client roots changed");
3087
- void refreshRoots("roots/list_changed");
3088
- } else if (method === "notifications/cancelled") {
3089
- const requestId = params?.requestId;
3090
- if (requestId !== void 0 && activeRequests.has(requestId)) {
3091
- activeRequests.get(requestId).abort();
3092
- activeRequests.delete(requestId);
3093
- logger.debug(`[Server] Request ${requestId} cancelled`);
3094
- } else {
3095
- logger.debug(`[Server] Cancelled notification for unknown or completed request ${requestId}`);
3096
- }
3097
- }
3098
- return;
3099
- }
3100
- if (method === "ping") {
3101
- reply({
3102
- jsonrpc: "2.0",
3103
- id,
3104
- result: {}
3105
- });
3106
- return;
3107
- }
3108
- if (method === "resources/subscribe") {
3109
- const uri = typeof params?.uri === "string" ? params.uri : "";
3110
- if (!uri) {
3111
- reply({
3112
- jsonrpc: "2.0",
3113
- id,
3114
- error: {
3115
- code: -32602,
3116
- message: "resources/subscribe requires a uri"
3117
- }
3118
- });
3119
- return;
3120
- }
3121
- resourceSubscriptions.add(uri);
3122
- reply({
3123
- jsonrpc: "2.0",
3124
- id,
3125
- result: {}
3126
- });
3127
- return;
3128
- }
3129
- if (method === "resources/unsubscribe") {
3130
- const uri = typeof params?.uri === "string" ? params.uri : "";
3131
- if (!uri) {
3132
- reply({
3133
- jsonrpc: "2.0",
3134
- id,
3135
- error: {
3136
- code: -32602,
3137
- message: "resources/unsubscribe requires a uri"
3138
- }
3139
- });
3140
- return;
3141
- }
3142
- resourceSubscriptions.delete(uri);
3143
- reply({
3144
- jsonrpc: "2.0",
3145
- id,
3146
- result: {}
3147
- });
3148
- return;
3149
- }
3150
- if (!isInitialized) {
3151
- reply({
3152
- jsonrpc: "2.0",
3153
- id,
3154
- error: {
3155
- code: -32002,
3156
- message: "Server is not fully initialized yet. Please send notifications/initialized."
3157
- }
3158
- });
3159
- return;
3160
- }
3161
- const abortController = new AbortController();
3162
- activeRequests.set(id, abortController);
3163
- const progressToken = params?._meta?.progressToken;
3164
- const onProgress = progressToken !== void 0 ? (progress, total) => {
3165
- reply({
3166
- jsonrpc: "2.0",
3167
- method: "notifications/progress",
3168
- params: {
3169
- progressToken,
3170
- progress,
3171
- total
3172
- }
3173
- });
3174
- } : void 0;
3175
- try {
3176
- const result = await handleMethod(method, params, abortController.signal, onProgress);
3177
- if (!abortController.signal.aborted) {
3178
- reply({
3179
- jsonrpc: "2.0",
3180
- id,
3181
- result
3182
- });
3183
- }
3184
- } catch (err) {
3185
- if (!abortController.signal.aborted) {
3186
- const error = err;
3187
- logger.error("Method handler error", { method, id, message: error.message });
3188
- reply({
3189
- jsonrpc: "2.0",
3190
- id,
3191
- error: {
3192
- code: typeof error?.code === "number" ? error.code : -32603,
3193
- message: error.message || "Internal error"
3194
- }
3195
- });
3196
- }
3197
- } finally {
3198
- activeRequests.delete(id);
3199
- }
3200
- });
4389
+ };
4390
+ process.on("SIGINT", () => void shutdown("SIGINT"));
4391
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
4392
+ var handle = serveStdio(() => createMcpServer(db, vectors));