@j0hanz/memory-mcp 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/completions/index.d.ts +1 -0
  2. package/dist/completions/index.d.ts.map +1 -0
  3. package/dist/db/index.d.ts +1 -0
  4. package/dist/db/index.d.ts.map +1 -0
  5. package/dist/db/index.js +10 -8
  6. package/dist/db/typed.d.ts +1 -0
  7. package/dist/db/typed.d.ts.map +1 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/lib/errors.d.ts +1 -0
  11. package/dist/lib/errors.d.ts.map +1 -0
  12. package/dist/lib/hash.d.ts +1 -0
  13. package/dist/lib/hash.d.ts.map +1 -0
  14. package/dist/lib/instructions.d.ts +1 -0
  15. package/dist/lib/instructions.d.ts.map +1 -0
  16. package/dist/lib/json-schema.d.ts +1 -0
  17. package/dist/lib/json-schema.d.ts.map +1 -0
  18. package/dist/lib/mcp-utils.d.ts +1 -0
  19. package/dist/lib/mcp-utils.d.ts.map +1 -0
  20. package/dist/lib/pagination.d.ts +1 -0
  21. package/dist/lib/pagination.d.ts.map +1 -0
  22. package/dist/lib/search-cursor.d.ts +1 -0
  23. package/dist/lib/search-cursor.d.ts.map +1 -0
  24. package/dist/lib/search.d.ts +1 -0
  25. package/dist/lib/search.d.ts.map +1 -0
  26. package/dist/lib/search.js +31 -23
  27. package/dist/lib/sql.d.ts +1 -0
  28. package/dist/lib/sql.d.ts.map +1 -0
  29. package/dist/lib/tool-contracts.d.ts +1 -0
  30. package/dist/lib/tool-contracts.d.ts.map +1 -0
  31. package/dist/lib/tool-contracts.js +65 -93
  32. package/dist/lib/tool-execution.d.ts +13 -0
  33. package/dist/lib/tool-execution.d.ts.map +1 -0
  34. package/dist/lib/tool-execution.js +28 -0
  35. package/dist/lib/tool-response.d.ts +1 -0
  36. package/dist/lib/tool-response.d.ts.map +1 -0
  37. package/dist/lib/types.d.ts +1 -0
  38. package/dist/lib/types.d.ts.map +1 -0
  39. package/dist/prompts/index.d.ts +1 -0
  40. package/dist/prompts/index.d.ts.map +1 -0
  41. package/dist/prompts/index.js +12 -8
  42. package/dist/resources/index.d.ts +1 -0
  43. package/dist/resources/index.d.ts.map +1 -0
  44. package/dist/resources/index.js +65 -38
  45. package/dist/resources/instructions.d.ts +1 -0
  46. package/dist/resources/instructions.d.ts.map +1 -0
  47. package/dist/resources/instructions.js +44 -37
  48. package/dist/resources/server-config.d.ts +1 -0
  49. package/dist/resources/server-config.d.ts.map +1 -0
  50. package/dist/resources/server-config.js +33 -22
  51. package/dist/resources/tool-catalog.d.ts +1 -0
  52. package/dist/resources/tool-catalog.d.ts.map +1 -0
  53. package/dist/resources/tool-catalog.js +10 -5
  54. package/dist/resources/tool-info.d.ts +1 -0
  55. package/dist/resources/tool-info.d.ts.map +1 -0
  56. package/dist/resources/tool-info.js +17 -8
  57. package/dist/resources/workflows.d.ts +1 -0
  58. package/dist/resources/workflows.d.ts.map +1 -0
  59. package/dist/resources/workflows.js +69 -40
  60. package/dist/schemas/index.d.ts +1 -0
  61. package/dist/schemas/index.d.ts.map +1 -0
  62. package/dist/schemas/inputs.d.ts +1 -0
  63. package/dist/schemas/inputs.d.ts.map +1 -0
  64. package/dist/schemas/inputs.js +14 -10
  65. package/dist/schemas/outputs.d.ts +7 -6
  66. package/dist/schemas/outputs.d.ts.map +1 -0
  67. package/dist/schemas/outputs.js +7 -6
  68. package/dist/server.d.ts +1 -0
  69. package/dist/server.d.ts.map +1 -0
  70. package/dist/server.js +11 -4
  71. package/dist/tools/create-relationship.d.ts +1 -0
  72. package/dist/tools/create-relationship.d.ts.map +1 -0
  73. package/dist/tools/create-relationship.js +14 -19
  74. package/dist/tools/delete-memories.d.ts +1 -0
  75. package/dist/tools/delete-memories.d.ts.map +1 -0
  76. package/dist/tools/delete-memories.js +27 -37
  77. package/dist/tools/delete-memory.d.ts +1 -0
  78. package/dist/tools/delete-memory.d.ts.map +1 -0
  79. package/dist/tools/delete-memory.js +9 -15
  80. package/dist/tools/delete-relationship.d.ts +1 -0
  81. package/dist/tools/delete-relationship.d.ts.map +1 -0
  82. package/dist/tools/delete-relationship.js +7 -12
  83. package/dist/tools/get-memory.d.ts +1 -0
  84. package/dist/tools/get-memory.d.ts.map +1 -0
  85. package/dist/tools/get-memory.js +9 -14
  86. package/dist/tools/get-relationships.d.ts +1 -0
  87. package/dist/tools/get-relationships.d.ts.map +1 -0
  88. package/dist/tools/get-relationships.js +12 -17
  89. package/dist/tools/index.d.ts +1 -0
  90. package/dist/tools/index.d.ts.map +1 -0
  91. package/dist/tools/memory-stats.d.ts +1 -0
  92. package/dist/tools/memory-stats.d.ts.map +1 -0
  93. package/dist/tools/memory-stats.js +22 -28
  94. package/dist/tools/progress.d.ts +1 -0
  95. package/dist/tools/progress.d.ts.map +1 -0
  96. package/dist/tools/progress.js +30 -23
  97. package/dist/tools/recall.d.ts +1 -0
  98. package/dist/tools/recall.d.ts.map +1 -0
  99. package/dist/tools/recall.js +77 -48
  100. package/dist/tools/register-contract.d.ts +1 -0
  101. package/dist/tools/register-contract.d.ts.map +1 -0
  102. package/dist/tools/result.d.ts +1 -0
  103. package/dist/tools/result.d.ts.map +1 -0
  104. package/dist/tools/retrieve-context.d.ts +1 -0
  105. package/dist/tools/retrieve-context.d.ts.map +1 -0
  106. package/dist/tools/retrieve-context.js +47 -33
  107. package/dist/tools/search-memories.d.ts +1 -0
  108. package/dist/tools/search-memories.d.ts.map +1 -0
  109. package/dist/tools/search-memories.js +24 -30
  110. package/dist/tools/store-memories.d.ts +1 -0
  111. package/dist/tools/store-memories.d.ts.map +1 -0
  112. package/dist/tools/store-memories.js +32 -42
  113. package/dist/tools/store-memory.d.ts +1 -0
  114. package/dist/tools/store-memory.d.ts.map +1 -0
  115. package/dist/tools/store-memory.js +12 -18
  116. package/dist/tools/update-memory.d.ts +1 -0
  117. package/dist/tools/update-memory.d.ts.map +1 -0
  118. package/dist/tools/update-memory.js +41 -46
  119. package/package.json +2 -2
@@ -1,6 +1,10 @@
1
1
  import { E_CANCELLED } from '../lib/errors.js';
2
2
  import { getToolResultText } from './result.js';
3
3
  const DEFAULT_PROGRESS_INTERVAL_MS = 250;
4
+ const TOOL_HANDLER_PROGRESS_TOTAL = 1;
5
+ function hasProgressTransport(extra, progressToken) {
6
+ return (progressToken !== undefined && typeof extra.sendNotification === 'function');
7
+ }
4
8
  function toProgressToken(value) {
5
9
  if (typeof value === 'string' || typeof value === 'number') {
6
10
  return value;
@@ -20,6 +24,19 @@ function toNotificationParams(progressToken, progress) {
20
24
  }
21
25
  return params;
22
26
  }
27
+ function toProgressNotification(progressToken, progress) {
28
+ return {
29
+ method: 'notifications/progress',
30
+ params: toNotificationParams(progressToken, progress),
31
+ };
32
+ }
33
+ function toProgressPayload(progress, current) {
34
+ return {
35
+ current,
36
+ ...(progress.total !== undefined ? { total: progress.total } : {}),
37
+ ...(progress.message !== undefined ? { message: progress.message } : {}),
38
+ };
39
+ }
23
40
  function getResultOutcome(result) {
24
41
  if (result.isError) {
25
42
  const text = getToolResultText(result);
@@ -41,14 +58,11 @@ export async function notifyProgress(extra, progress) {
41
58
  if (progressToken === undefined) {
42
59
  return;
43
60
  }
44
- if (typeof extra.sendNotification !== 'function') {
61
+ if (!hasProgressTransport(extra, progressToken)) {
45
62
  return;
46
63
  }
47
64
  try {
48
- await extra.sendNotification({
49
- method: 'notifications/progress',
50
- params: toNotificationParams(progressToken, progress),
51
- });
65
+ await extra.sendNotification(toProgressNotification(progressToken, progress));
52
66
  }
53
67
  catch {
54
68
  // best-effort progress
@@ -75,13 +89,7 @@ export function createProgressReporter(extra, options = {}) {
75
89
  lastReportedAt = now;
76
90
  isCompleted =
77
91
  progress.total !== undefined && monotonicCurrent >= progress.total;
78
- const payload = { current: monotonicCurrent };
79
- if (progress.total !== undefined) {
80
- payload.total = progress.total;
81
- }
82
- if (progress.message !== undefined) {
83
- payload.message = progress.message;
84
- }
92
+ const payload = toProgressPayload(progress, monotonicCurrent);
85
93
  notificationChain = notificationChain.then(() => notifyProgress(extra, payload));
86
94
  });
87
95
  reporter.flush = async () => {
@@ -102,11 +110,18 @@ export function progressWithMessage(reporter, getMessage) {
102
110
  return wrapped;
103
111
  }
104
112
  export function wrapToolHandler(handler, options) {
113
+ const notifyTerminalProgress = async (extra, startMessage, outcome, completionMessage) => {
114
+ await notifyProgress(extra, {
115
+ current: TOOL_HANDLER_PROGRESS_TOTAL,
116
+ total: TOOL_HANDLER_PROGRESS_TOTAL,
117
+ message: completionMessage ?? `${startMessage} • ${outcome}`,
118
+ });
119
+ };
105
120
  return async (args, extra) => {
106
121
  const startMessage = options.progressMessage(args);
107
122
  await notifyProgress(extra, {
108
123
  current: 0,
109
- total: 1,
124
+ total: TOOL_HANDLER_PROGRESS_TOTAL,
110
125
  message: startMessage,
111
126
  });
112
127
  let result;
@@ -115,20 +130,12 @@ export function wrapToolHandler(handler, options) {
115
130
  }
116
131
  catch (error) {
117
132
  const isCancelled = error instanceof Error && error.message === E_CANCELLED;
118
- await notifyProgress(extra, {
119
- current: 1,
120
- total: 1,
121
- message: `${startMessage} • ${isCancelled ? 'cancelled' : 'failed'}`,
122
- });
133
+ await notifyTerminalProgress(extra, startMessage, isCancelled ? 'cancelled' : 'failed');
123
134
  throw error;
124
135
  }
125
136
  const completionMessage = options.completionMessage?.(args, result) ??
126
137
  defaultCompletionMessage(startMessage, result);
127
- await notifyProgress(extra, {
128
- current: 1,
129
- total: 1,
130
- message: completionMessage,
131
- });
138
+ await notifyTerminalProgress(extra, startMessage, 'completed', completionMessage);
132
139
  return result;
133
140
  };
134
141
  }
@@ -1,3 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { TypedDb } from '../db/typed.js';
3
3
  export declare function registerRecall(server: McpServer, db: TypedDb): void;
4
+ //# sourceMappingURL=recall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recall.d.ts","sourceRoot":"","sources":["../../src/tools/recall.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAUzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAoX9C,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAqFnE"}
@@ -145,9 +145,71 @@ function formatRecallCompletionMessage(query, result) {
145
145
  const aborted = 'aborted' in payload && payload.aborted === true;
146
146
  return `⊙ recall: ${query} • ${memoriesCount} memories, ${edgesCount} edges${aborted ? ' [aborted]' : ''}`;
147
147
  }
148
+ function decodeCursorForRecall(cursor, scope) {
149
+ if (!cursor) {
150
+ return undefined;
151
+ }
152
+ return decodeSearchCursor(cursor, scope);
153
+ }
154
+ function buildSeedRelevanceMap(seeds) {
155
+ const seedRelevance = new Map();
156
+ for (const seed of seeds) {
157
+ if (seed.rank != null) {
158
+ seedRelevance.set(seed.hash, -seed.rank);
159
+ }
160
+ }
161
+ return seedRelevance;
162
+ }
163
+ function toMemoriesWithRelevance(rows, seedRelevance) {
164
+ return rows.map((row) => {
165
+ const memory = parseMemoryRow(row);
166
+ const relevance = seedRelevance.get(memory.hash);
167
+ if (relevance != null) {
168
+ memory.relevance = relevance;
169
+ }
170
+ return memory;
171
+ });
172
+ }
173
+ function buildNextCursor(hasMore, pageSeeds, scope) {
174
+ if (!hasMore || pageSeeds.length === 0) {
175
+ return undefined;
176
+ }
177
+ const lastSeed = pageSeeds[pageSeeds.length - 1];
178
+ if (!lastSeed) {
179
+ return undefined;
180
+ }
181
+ const rank = lastSeed.rank ?? 0;
182
+ return encodeSearchCursor(scope, rank, lastSeed.hash);
183
+ }
184
+ function createHopReporter(extra, query, completionCurrent) {
185
+ const reporter = progressWithMessage(createProgressReporter(extra), ({ current, total }) => `⊙ recall: ${query} [hop ${current}/${Math.max((total ?? current) - 1, current)}]`);
186
+ const onHop = (hop) => {
187
+ reporter({ current: hop + 1, total: completionCurrent });
188
+ };
189
+ return { reporter, onHop };
190
+ }
191
+ async function computeRecall(db, params, scope, signal, onHop) {
192
+ const decodedCursor = decodeCursorForRecall(params.cursor, scope);
193
+ const seedRows = loadRankedSearchRows(db, params.query, params.limit, decodedCursor, toMemoryFilters(params));
194
+ const { page: pageSeeds, hasMore } = splitPage(seedRows, params.limit);
195
+ const traversal = await traverseGraph(db, pageSeeds, params.depth, signal, onHop);
196
+ const allHashes = Array.from(traversal.visited);
197
+ const seedRelevance = buildSeedRelevanceMap(pageSeeds);
198
+ const memoryRows = loadMemoriesByHashes(db, allHashes);
199
+ const nextCursor = buildNextCursor(hasMore, pageSeeds, scope);
200
+ return {
201
+ memories: toMemoriesWithRelevance(memoryRows, seedRelevance),
202
+ edges: traversal.edges,
203
+ depthReached: traversal.depthReached,
204
+ aborted: traversal.aborted,
205
+ ...(nextCursor ? { nextCursor } : {}),
206
+ seedCount: pageSeeds.length,
207
+ visitedCount: traversal.visited.size,
208
+ };
209
+ }
148
210
  export function registerRecall(server, db) {
149
211
  registerToolWithContract(server, 'recall', RecallInputSchema, RecallResultSchema, async (params, extra) => {
150
- const { depth, limit, cursor } = params;
212
+ const { depth } = params;
151
213
  const filters = toMemoryFilters(params);
152
214
  const scope = buildSearchCursorScope(params.query, filters);
153
215
  const contextLabel = `⊙ recall: ${params.query} [depth ${depth}]`;
@@ -157,62 +219,29 @@ export function registerRecall(server, db) {
157
219
  total: completionCurrent,
158
220
  message: contextLabel,
159
221
  });
160
- const hopReporter = progressWithMessage(createProgressReporter(extra), ({ current, total }) => `⊙ recall: ${params.query} [hop ${current}/${Math.max((total ?? current) - 1, current)}]`);
161
- const onHop = (hop) => {
162
- hopReporter({ current: hop + 1, total: completionCurrent });
163
- };
222
+ const { reporter: hopReporter, onHop } = createHopReporter(extra, params.query, completionCurrent);
164
223
  let result;
165
224
  let thrownError;
166
225
  try {
167
226
  throwIfAborted(extra.signal);
168
- const decodedCursor = cursor
169
- ? decodeSearchCursor(cursor, scope)
170
- : undefined;
171
- // Step 1: FTS seed search
172
- const seedRows = loadRankedSearchRows(db, params.query, limit, decodedCursor, filters);
227
+ const computation = await computeRecall(db, params, scope, extra.signal, onHop);
173
228
  throwIfAborted(extra.signal);
174
- const { page: pageSeeds, hasMore } = splitPage(seedRows, limit);
175
- // Step 2: BFS traversal up to `depth` hops
176
- const traversal = await traverseGraph(db, pageSeeds, depth, extra.signal, onHop);
177
- throwIfAborted(extra.signal);
178
- // Step 3: Load all discovered memory rows
179
- const allHashes = Array.from(traversal.visited);
180
- const seedRelevance = new Map();
181
- for (const seed of pageSeeds) {
182
- if (seed.rank != null)
183
- seedRelevance.set(seed.hash, -seed.rank);
184
- }
185
- const memoryRows = loadMemoriesByHashes(db, allHashes);
186
- const memories = memoryRows.map((row) => {
187
- const memory = parseMemoryRow(row);
188
- const rel = seedRelevance.get(memory.hash);
189
- if (rel != null) {
190
- memory.relevance = rel;
191
- }
192
- return memory;
193
- });
194
- let nextCursor;
195
- if (hasMore && pageSeeds.length > 0) {
196
- const lastSeed = pageSeeds[pageSeeds.length - 1];
197
- if (lastSeed !== undefined) {
198
- const rank = lastSeed.rank ?? 0;
199
- nextCursor = encodeSearchCursor(scope, rank, lastSeed.hash);
200
- }
201
- }
202
229
  await logToolEvent(server, 'recall', {
203
230
  depth,
204
- depth_reached: traversal.depthReached,
205
- seed_count: pageSeeds.length,
206
- visited_nodes: traversal.visited.size,
207
- edge_count: traversal.edges.length,
208
- aborted: traversal.aborted,
231
+ depth_reached: computation.depthReached,
232
+ seed_count: computation.seedCount,
233
+ visited_nodes: computation.visitedCount,
234
+ edge_count: computation.edges.length,
235
+ aborted: computation.aborted,
209
236
  });
210
237
  result = createToolResponse({
211
- memories,
212
- graph: traversal.edges,
213
- depth_reached: traversal.depthReached,
214
- ...(traversal.aborted ? { aborted: true } : {}),
215
- ...(nextCursor ? { nextCursor } : {}),
238
+ memories: computation.memories,
239
+ graph: computation.edges,
240
+ depth_reached: computation.depthReached,
241
+ ...(computation.aborted ? { aborted: true } : {}),
242
+ ...(computation.nextCursor
243
+ ? { nextCursor: computation.nextCursor }
244
+ : {}),
216
245
  });
217
246
  }
218
247
  catch (err) {
@@ -1,3 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { z } from 'zod/v4';
3
3
  export declare function registerToolWithContract(server: McpServer, toolName: string, _inputSchema: z.ZodType, _outputSchema: z.ZodType, handler: unknown): void;
4
+ //# sourceMappingURL=register-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-contract.d.ts","sourceRoot":"","sources":["../../src/tools/register-contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAIhC,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,CAAC,CAAC,OAAO,EACvB,aAAa,EAAE,CAAC,CAAC,OAAO,EACxB,OAAO,EAAE,OAAO,GACf,IAAI,CAcN"}
@@ -5,3 +5,4 @@ export declare function getToolResultPayload(result: CallToolResult): ToolResult
5
5
  export declare function getToolResultText(result: CallToolResult): string;
6
6
  /** Count items in a named array field of a tool result payload. */
7
7
  export declare function countPayloadArrayItems(payload: Record<string, unknown>, key: string): number;
8
+ //# sourceMappingURL=result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/tools/result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAMxD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAExE;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,GACrB,iBAAiB,GAAG,SAAS,CAO/B;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAGhE;AAED,mEAAmE;AACnE,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,GAAG,EAAE,MAAM,GACV,MAAM,CAGR"}
@@ -1,3 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { TypedDb } from '../db/typed.js';
3
3
  export declare function registerRetrieveContext(server: McpServer, db: TypedDb): void;
4
+ //# sourceMappingURL=retrieve-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retrieve-context.d.ts","sourceRoot":"","sources":["../../src/tools/retrieve-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AA8N9C,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CA0E5E"}
@@ -74,15 +74,54 @@ function throwIfAborted(signal) {
74
74
  }
75
75
  throw new Error(E_CANCELLED);
76
76
  }
77
+ function computeCandidateLimit(tokenBudget) {
78
+ const estimatedCandidates = Math.ceil(tokenBudget / ESTIMATED_TOKENS_PER_MEMORY);
79
+ return Math.min(Math.max(MIN_CANDIDATE_ROWS, estimatedCandidates), MAX_CANDIDATE_ROWS);
80
+ }
81
+ function selectMemoriesWithinBudget(rows, candidateCount, tokenBudget, completionCurrent, signal, onProgress) {
82
+ let estimatedTokens = 0;
83
+ let truncated = rows.length > candidateCount;
84
+ const selected = [];
85
+ let scanned = 0;
86
+ for (let i = 0; i < candidateCount; i += 1) {
87
+ throwIfAborted(signal);
88
+ const row = rows[i];
89
+ if (!row) {
90
+ break;
91
+ }
92
+ const memory = parseMemoryRow(row);
93
+ const tokens = estimateTokens(memory.content);
94
+ scanned += 1;
95
+ reportSelectionProgress(onProgress, scanned, completionCurrent, false);
96
+ if (estimatedTokens + tokens > tokenBudget) {
97
+ truncated = true;
98
+ break;
99
+ }
100
+ estimatedTokens += tokens;
101
+ selected.push(memory);
102
+ }
103
+ reportSelectionProgress(onProgress, scanned, completionCurrent, true);
104
+ return { selected, estimatedTokens, truncated };
105
+ }
106
+ function computeRetrieveContextResult(db, params, limit, signal, onProgress) {
107
+ const orderBy = ORDER_BY_MAP[params.strategy];
108
+ const rows = loadContextRows(db, params.query, orderBy, limit);
109
+ const rowCapExceeded = rows.length > limit;
110
+ const candidateCount = rowCapExceeded ? limit : rows.length;
111
+ const completionCurrent = candidateCount + 1;
112
+ return {
113
+ selection: selectMemoriesWithinBudget(rows, candidateCount, params.token_budget, completionCurrent, signal, onProgress),
114
+ completionCurrent,
115
+ };
116
+ }
77
117
  export function registerRetrieveContext(server, db) {
78
118
  registerToolWithContract(server, 'retrieve_context', RetrieveContextInputSchema, RetrieveContextResultSchema, async (params, extra) => {
79
119
  const { query, strategy } = params;
80
120
  const tokenBudget = params.token_budget;
81
121
  const contextLabel = `⊙ retrieve_context: ${query} [${strategy}]`;
82
122
  let completionCurrent = 1;
83
- // Heuristic: Load enough candidates to likely fill the budget, but cap to avoid massive queries
84
- const estimatedCandidates = Math.ceil(tokenBudget / ESTIMATED_TOKENS_PER_MEMORY);
85
- const limit = Math.min(Math.max(MIN_CANDIDATE_ROWS, estimatedCandidates), MAX_CANDIDATE_ROWS);
123
+ // Heuristic: Load enough candidates to likely fill the budget, but cap to avoid massive queries.
124
+ const limit = computeCandidateLimit(tokenBudget);
86
125
  await notifyProgress(extra, {
87
126
  current: 0,
88
127
  message: `${contextLabel} [budget ${tokenBudget}]`,
@@ -92,37 +131,12 @@ export function registerRetrieveContext(server, db) {
92
131
  let thrownError;
93
132
  try {
94
133
  throwIfAborted(extra.signal);
95
- const orderBy = ORDER_BY_MAP[strategy];
96
- const rows = loadContextRows(db, query, orderBy, limit);
97
- const rowCapExceeded = rows.length > limit;
98
- const candidateCount = rowCapExceeded ? limit : rows.length;
99
- completionCurrent = candidateCount + 1;
100
- let estimatedTokens = 0;
101
- let truncated = rowCapExceeded;
102
- const selected = [];
103
- let scanned = 0;
104
- for (let i = 0; i < candidateCount; i += 1) {
105
- throwIfAborted(extra.signal);
106
- const row = rows[i];
107
- if (row === undefined) {
108
- break;
109
- }
110
- const mem = parseMemoryRow(row);
111
- const tokens = estimateTokens(mem.content);
112
- scanned += 1;
113
- reportSelectionProgress(loopProgress, scanned, completionCurrent, false);
114
- if (estimatedTokens + tokens > tokenBudget) {
115
- truncated = true;
116
- break;
117
- }
118
- estimatedTokens += tokens;
119
- selected.push(mem);
120
- }
121
- reportSelectionProgress(loopProgress, scanned, completionCurrent, true);
134
+ const { selection, completionCurrent: nextCompletionCurrent } = computeRetrieveContextResult(db, params, limit, extra.signal, loopProgress);
135
+ completionCurrent = nextCompletionCurrent;
122
136
  result = createToolResponse({
123
- memories: selected,
124
- estimated_tokens: estimatedTokens,
125
- truncated,
137
+ memories: selection.selected,
138
+ estimated_tokens: selection.estimatedTokens,
139
+ truncated: selection.truncated,
126
140
  });
127
141
  }
128
142
  catch (err) {
@@ -1,3 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { TypedDb } from '../db/typed.js';
3
3
  export declare function registerSearchMemories(server: McpServer, db: TypedDb): void;
4
+ //# sourceMappingURL=search-memories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-memories.d.ts","sourceRoot":"","sources":["../../src/tools/search-memories.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAmB9C,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CA8C3E"}
@@ -1,44 +1,38 @@
1
- import { E_UNKNOWN, getErrorMessage, rethrowMcpError } from '../lib/errors.js';
2
1
  import { splitPage } from '../lib/pagination.js';
3
2
  import { buildSearchCursorScope, decodeSearchCursor, encodeSearchCursor, } from '../lib/search-cursor.js';
4
3
  import { loadRankedSearchRows, toMemoryFilters } from '../lib/search.js';
5
- import { createErrorResponse, createToolResponse, } from '../lib/tool-response.js';
4
+ import { executeToolSafely } from '../lib/tool-execution.js';
5
+ import { createToolResponse } from '../lib/tool-response.js';
6
6
  import { parseMemoryRow } from '../lib/types.js';
7
7
  import { SearchMemoriesInputSchema } from '../schemas/inputs.js';
8
8
  import { SearchResultSchema } from '../schemas/outputs.js';
9
9
  import { wrapToolHandler } from './progress.js';
10
10
  import { registerToolWithContract } from './register-contract.js';
11
11
  export function registerSearchMemories(server, db) {
12
- registerToolWithContract(server, 'search_memories', SearchMemoriesInputSchema, SearchResultSchema, wrapToolHandler((params) => {
13
- try {
14
- const { limit, cursor } = params;
15
- const filters = toMemoryFilters(params);
16
- const scope = buildSearchCursorScope(params.query, filters);
17
- const decodedCursor = cursor
18
- ? decodeSearchCursor(cursor, scope)
19
- : undefined;
20
- const rows = loadRankedSearchRows(db, params.query, limit, decodedCursor, filters);
21
- const { page: pageRows, hasMore } = splitPage(rows, limit);
22
- const memories = pageRows.map(parseMemoryRow);
23
- let nextCursor;
24
- if (hasMore && pageRows.length > 0) {
25
- const lastRow = pageRows[pageRows.length - 1];
26
- if (lastRow !== undefined) {
27
- const rank = lastRow.rank ?? 0;
28
- nextCursor = encodeSearchCursor(scope, rank, lastRow.hash);
29
- }
12
+ registerToolWithContract(server, 'search_memories', SearchMemoriesInputSchema, SearchResultSchema, wrapToolHandler((params) => executeToolSafely(() => {
13
+ const { limit, cursor } = params;
14
+ const filters = toMemoryFilters(params);
15
+ const scope = buildSearchCursorScope(params.query, filters);
16
+ const decodedCursor = cursor
17
+ ? decodeSearchCursor(cursor, scope)
18
+ : undefined;
19
+ const rows = loadRankedSearchRows(db, params.query, limit, decodedCursor, filters);
20
+ const { page: pageRows, hasMore } = splitPage(rows, limit);
21
+ const memories = pageRows.map(parseMemoryRow);
22
+ let nextCursor;
23
+ if (hasMore && pageRows.length > 0) {
24
+ const lastRow = pageRows[pageRows.length - 1];
25
+ if (lastRow !== undefined) {
26
+ const rank = lastRow.rank ?? 0;
27
+ nextCursor = encodeSearchCursor(scope, rank, lastRow.hash);
30
28
  }
31
- return createToolResponse({
32
- memories,
33
- total_returned: memories.length,
34
- ...(nextCursor ? { nextCursor } : {}),
35
- });
36
29
  }
37
- catch (err) {
38
- rethrowMcpError(err);
39
- return createErrorResponse(E_UNKNOWN, getErrorMessage(err));
40
- }
41
- }, {
30
+ return createToolResponse({
31
+ memories,
32
+ total_returned: memories.length,
33
+ ...(nextCursor ? { nextCursor } : {}),
34
+ });
35
+ }), {
42
36
  progressMessage: (params) => `⊙ search_memories: ${params.query} [limit ${params.limit}]`,
43
37
  }));
44
38
  }
@@ -1,3 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { TypedDb } from '../db/typed.js';
3
3
  export declare function registerStoreMemories(server: McpServer, db: TypedDb): void;
4
+ //# sourceMappingURL=store-memories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-memories.d.ts","sourceRoot":"","sources":["../../src/tools/store-memories.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAwB9C,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CA2D1E"}
@@ -1,8 +1,8 @@
1
- import { E_UNKNOWN, getErrorMessage, rethrowMcpError } from '../lib/errors.js';
2
1
  import { computeMemoryHash } from '../lib/hash.js';
3
2
  import { logToolEvent, notifyMemoryResourceUpdated } from '../lib/mcp-utils.js';
4
3
  import { INSERT_MEMORY_SQL } from '../lib/sql.js';
5
- import { createErrorResponse, createToolResponse, } from '../lib/tool-response.js';
4
+ import { executeToolSafely, summarizeBatch } from '../lib/tool-execution.js';
5
+ import { createToolResponse } from '../lib/tool-response.js';
6
6
  import { StoreMemoriesInputSchema } from '../schemas/inputs.js';
7
7
  import { BatchResultSchema } from '../schemas/outputs.js';
8
8
  import { wrapToolHandler } from './progress.js';
@@ -14,47 +14,37 @@ async function notifyCreatedResources(server, items) {
14
14
  await Promise.allSettled(notifications);
15
15
  }
16
16
  export function registerStoreMemories(server, db) {
17
- registerToolWithContract(server, 'store_memories', StoreMemoriesInputSchema, BatchResultSchema, wrapToolHandler(async (params) => {
18
- try {
19
- const now = new Date().toISOString();
20
- const results = db.transaction(() => {
21
- const items = [];
22
- const stmt = db.prepareOnce(INSERT_MEMORY_SQL);
23
- for (const item of params.items) {
24
- const { importance, memory_type: rawMemoryType } = item;
25
- const memoryType = rawMemoryType ?? 'general';
26
- const hash = computeMemoryHash(item.content, item.tags);
27
- const tagsJson = JSON.stringify(item.tags);
28
- const result = stmt.run(hash, item.content, tagsJson, memoryType, importance, now, now);
29
- items.push({
30
- hash,
31
- ok: true,
32
- created: result.changes > 0,
33
- });
34
- }
35
- return items;
36
- });
37
- let created = 0;
38
- let succeeded = 0;
39
- for (const item of results) {
40
- if (item.ok)
41
- succeeded += 1;
42
- if (item.created)
43
- created += 1;
17
+ registerToolWithContract(server, 'store_memories', StoreMemoriesInputSchema, BatchResultSchema, wrapToolHandler(async (params) => executeToolSafely(async () => {
18
+ const now = new Date().toISOString();
19
+ const results = db.transaction(() => {
20
+ const items = [];
21
+ const stmt = db.prepareOnce(INSERT_MEMORY_SQL);
22
+ for (const item of params.items) {
23
+ const { importance, memory_type: rawMemoryType } = item;
24
+ const memoryType = rawMemoryType ?? 'general';
25
+ const hash = computeMemoryHash(item.content, item.tags);
26
+ const tagsJson = JSON.stringify(item.tags);
27
+ const result = stmt.run(hash, item.content, tagsJson, memoryType, importance, now, now);
28
+ items.push({
29
+ hash,
30
+ ok: true,
31
+ created: result.changes > 0,
32
+ });
44
33
  }
45
- const failed = results.length - succeeded;
46
- await logToolEvent(server, 'store_memories', {
47
- total: results.length,
48
- created,
49
- });
50
- await notifyCreatedResources(server, results);
51
- return createToolResponse({ items: results, succeeded, failed });
52
- }
53
- catch (err) {
54
- rethrowMcpError(err);
55
- return createErrorResponse(E_UNKNOWN, getErrorMessage(err));
56
- }
57
- }, {
34
+ return items;
35
+ });
36
+ const summary = summarizeBatch(results, (item) => item.created === true);
37
+ await logToolEvent(server, 'store_memories', {
38
+ total: results.length,
39
+ created: summary.matched,
40
+ });
41
+ await notifyCreatedResources(server, results);
42
+ return createToolResponse({
43
+ items: results,
44
+ succeeded: summary.succeeded,
45
+ failed: summary.failed,
46
+ });
47
+ }), {
58
48
  progressMessage: (params) => `⊕ store_memories: ${params.items.length} items [batch]`,
59
49
  }));
60
50
  }
@@ -1,3 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { TypedDb } from '../db/typed.js';
3
3
  export declare function registerStoreMemory(server: McpServer, db: TypedDb): void;
4
+ //# sourceMappingURL=store-memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-memory.d.ts","sourceRoot":"","sources":["../../src/tools/store-memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AA2C9C,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CA0BxE"}
@@ -1,8 +1,8 @@
1
- import { E_UNKNOWN, getErrorMessage, rethrowMcpError } from '../lib/errors.js';
2
1
  import { computeMemoryHash } from '../lib/hash.js';
3
2
  import { logToolEvent, notifyMemoryResourceUpdated } from '../lib/mcp-utils.js';
4
3
  import { INSERT_MEMORY_SQL } from '../lib/sql.js';
5
- import { createErrorResponse, createToolResponse, } from '../lib/tool-response.js';
4
+ import { executeToolSafely } from '../lib/tool-execution.js';
5
+ import { createToolResponse } from '../lib/tool-response.js';
6
6
  import { StoreMemoryInputSchema } from '../schemas/inputs.js';
7
7
  import { StoreResultSchema } from '../schemas/outputs.js';
8
8
  import { wrapToolHandler } from './progress.js';
@@ -25,23 +25,17 @@ function insertMemory(db, params, hash, memoryType, now) {
25
25
  return insertResult.changes > 0;
26
26
  }
27
27
  export function registerStoreMemory(server, db) {
28
- registerToolWithContract(server, 'store_memory', StoreMemoryInputSchema, StoreResultSchema, wrapToolHandler(async (params) => {
29
- try {
30
- const memoryType = params.memory_type ?? 'general';
31
- const hash = computeMemoryHash(params.content, params.tags);
32
- const now = new Date().toISOString();
33
- const created = insertMemory(db, params, hash, memoryType, now);
34
- await logToolEvent(server, 'store', { hash, created });
35
- if (created) {
36
- await notifyMemoryResourceUpdated(server, hash);
37
- }
38
- return createToolResponse({ hash, created });
28
+ registerToolWithContract(server, 'store_memory', StoreMemoryInputSchema, StoreResultSchema, wrapToolHandler(async (params) => executeToolSafely(async () => {
29
+ const memoryType = params.memory_type ?? 'general';
30
+ const hash = computeMemoryHash(params.content, params.tags);
31
+ const now = new Date().toISOString();
32
+ const created = insertMemory(db, params, hash, memoryType, now);
33
+ await logToolEvent(server, 'store', { hash, created });
34
+ if (created) {
35
+ await notifyMemoryResourceUpdated(server, hash);
39
36
  }
40
- catch (err) {
41
- rethrowMcpError(err);
42
- return createErrorResponse(E_UNKNOWN, getErrorMessage(err));
43
- }
44
- }, {
37
+ return createToolResponse({ hash, created });
38
+ }), {
45
39
  progressMessage: (params) => `⊕ store_memory: ${params.tags.length} tags [single]`,
46
40
  }));
47
41
  }
@@ -1,3 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { TypedDb } from '../db/typed.js';
3
3
  export declare function registerUpdateMemory(server: McpServer, db: TypedDb): void;
4
+ //# sourceMappingURL=update-memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-memory.d.ts","sourceRoot":"","sources":["../../src/tools/update-memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAyC9C,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CA6EzE"}