@j0hanz/memory-mcp 1.6.0 → 1.7.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 (120) hide show
  1. package/dist/completions/index.d.ts +0 -1
  2. package/dist/db/index.d.ts +0 -1
  3. package/dist/db/index.js +8 -7
  4. package/dist/db/typed.d.ts +0 -1
  5. package/dist/index.d.ts +0 -1
  6. package/dist/lib/errors.d.ts +4 -1
  7. package/dist/lib/errors.js +11 -0
  8. package/dist/lib/graph-traversal.d.ts +12 -0
  9. package/dist/lib/graph-traversal.js +145 -0
  10. package/dist/lib/hash.d.ts +0 -1
  11. package/dist/lib/instructions.d.ts +0 -1
  12. package/dist/lib/json-schema.d.ts +5 -1
  13. package/dist/lib/json-schema.js +19 -1
  14. package/dist/lib/mcp-utils.d.ts +0 -1
  15. package/dist/lib/pagination.d.ts +0 -3
  16. package/dist/lib/pagination.js +0 -43
  17. package/dist/lib/search-cursor.d.ts +0 -1
  18. package/dist/lib/search.d.ts +0 -1
  19. package/dist/lib/search.js +26 -13
  20. package/dist/lib/sql.d.ts +0 -1
  21. package/dist/lib/tool-contracts.d.ts +0 -1
  22. package/dist/lib/tool-contracts.js +40 -35
  23. package/dist/lib/tool-execution.d.ts +1 -1
  24. package/dist/lib/tool-execution.js +24 -1
  25. package/dist/lib/tool-response.d.ts +0 -1
  26. package/dist/lib/types.d.ts +0 -1
  27. package/dist/prompts/index.d.ts +0 -1
  28. package/dist/resources/index.d.ts +0 -1
  29. package/dist/resources/index.js +24 -27
  30. package/dist/resources/instructions.d.ts +0 -1
  31. package/dist/resources/server-config.d.ts +0 -1
  32. package/dist/resources/tool-catalog.d.ts +0 -1
  33. package/dist/resources/tool-catalog.js +1 -10
  34. package/dist/resources/tool-info.d.ts +0 -1
  35. package/dist/resources/tool-info.js +1 -10
  36. package/dist/resources/workflows.d.ts +0 -1
  37. package/dist/schemas/index.d.ts +0 -1
  38. package/dist/schemas/inputs.d.ts +8 -6
  39. package/dist/schemas/inputs.js +43 -30
  40. package/dist/schemas/outputs.d.ts +0 -1
  41. package/dist/server.d.ts +0 -1
  42. package/dist/tools/create-relationship.d.ts +0 -1
  43. package/dist/tools/create-relationship.js +4 -4
  44. package/dist/tools/delete-memories.d.ts +0 -1
  45. package/dist/tools/delete-memories.js +4 -3
  46. package/dist/tools/delete-memory.d.ts +0 -1
  47. package/dist/tools/delete-memory.js +6 -4
  48. package/dist/tools/delete-relationship.d.ts +0 -1
  49. package/dist/tools/delete-relationship.js +7 -17
  50. package/dist/tools/get-memory.d.ts +0 -1
  51. package/dist/tools/get-memory.js +4 -4
  52. package/dist/tools/get-relationships.d.ts +0 -1
  53. package/dist/tools/get-relationships.js +16 -12
  54. package/dist/tools/index.d.ts +0 -1
  55. package/dist/tools/memory-stats.d.ts +0 -1
  56. package/dist/tools/memory-stats.js +1 -3
  57. package/dist/tools/progress.d.ts +6 -1
  58. package/dist/tools/progress.js +38 -2
  59. package/dist/tools/recall.d.ts +0 -1
  60. package/dist/tools/recall.js +28 -166
  61. package/dist/tools/register-contract.d.ts +1 -3
  62. package/dist/tools/register-contract.js +4 -2
  63. package/dist/tools/result.d.ts +4 -1
  64. package/dist/tools/result.js +27 -0
  65. package/dist/tools/retrieve-context.d.ts +0 -1
  66. package/dist/tools/retrieve-context.js +42 -74
  67. package/dist/tools/search-memories.d.ts +0 -1
  68. package/dist/tools/search-memories.js +14 -11
  69. package/dist/tools/store-memories.d.ts +0 -1
  70. package/dist/tools/store-memories.js +2 -3
  71. package/dist/tools/store-memory.d.ts +0 -1
  72. package/dist/tools/store-memory.js +2 -3
  73. package/dist/tools/update-memory.d.ts +0 -1
  74. package/dist/tools/update-memory.js +7 -6
  75. package/package.json +1 -1
  76. package/dist/completions/index.d.ts.map +0 -1
  77. package/dist/db/index.d.ts.map +0 -1
  78. package/dist/db/typed.d.ts.map +0 -1
  79. package/dist/index.d.ts.map +0 -1
  80. package/dist/lib/errors.d.ts.map +0 -1
  81. package/dist/lib/hash.d.ts.map +0 -1
  82. package/dist/lib/instructions.d.ts.map +0 -1
  83. package/dist/lib/json-schema.d.ts.map +0 -1
  84. package/dist/lib/mcp-utils.d.ts.map +0 -1
  85. package/dist/lib/pagination.d.ts.map +0 -1
  86. package/dist/lib/search-cursor.d.ts.map +0 -1
  87. package/dist/lib/search.d.ts.map +0 -1
  88. package/dist/lib/sql.d.ts.map +0 -1
  89. package/dist/lib/tool-contracts.d.ts.map +0 -1
  90. package/dist/lib/tool-execution.d.ts.map +0 -1
  91. package/dist/lib/tool-response.d.ts.map +0 -1
  92. package/dist/lib/types.d.ts.map +0 -1
  93. package/dist/prompts/index.d.ts.map +0 -1
  94. package/dist/resources/index.d.ts.map +0 -1
  95. package/dist/resources/instructions.d.ts.map +0 -1
  96. package/dist/resources/server-config.d.ts.map +0 -1
  97. package/dist/resources/tool-catalog.d.ts.map +0 -1
  98. package/dist/resources/tool-info.d.ts.map +0 -1
  99. package/dist/resources/workflows.d.ts.map +0 -1
  100. package/dist/schemas/index.d.ts.map +0 -1
  101. package/dist/schemas/inputs.d.ts.map +0 -1
  102. package/dist/schemas/outputs.d.ts.map +0 -1
  103. package/dist/server.d.ts.map +0 -1
  104. package/dist/tools/create-relationship.d.ts.map +0 -1
  105. package/dist/tools/delete-memories.d.ts.map +0 -1
  106. package/dist/tools/delete-memory.d.ts.map +0 -1
  107. package/dist/tools/delete-relationship.d.ts.map +0 -1
  108. package/dist/tools/get-memory.d.ts.map +0 -1
  109. package/dist/tools/get-relationships.d.ts.map +0 -1
  110. package/dist/tools/index.d.ts.map +0 -1
  111. package/dist/tools/memory-stats.d.ts.map +0 -1
  112. package/dist/tools/progress.d.ts.map +0 -1
  113. package/dist/tools/recall.d.ts.map +0 -1
  114. package/dist/tools/register-contract.d.ts.map +0 -1
  115. package/dist/tools/result.d.ts.map +0 -1
  116. package/dist/tools/retrieve-context.d.ts.map +0 -1
  117. package/dist/tools/search-memories.d.ts.map +0 -1
  118. package/dist/tools/store-memories.d.ts.map +0 -1
  119. package/dist/tools/store-memory.d.ts.map +0 -1
  120. package/dist/tools/update-memory.d.ts.map +0 -1
@@ -1,5 +1,10 @@
1
- import { E_CANCELLED } from '../lib/errors.js';
1
+ import { McpError } from '@modelcontextprotocol/sdk/types.js';
2
+ import { CancelledError, E_CANCELLED, E_UNKNOWN, getErrorMessage, rethrowMcpError, } from '../lib/errors.js';
3
+ import { createErrorResponse } from '../lib/tool-response.js';
2
4
  import { getToolResultText } from './result.js';
5
+ function resolveCompletionCurrent(value) {
6
+ return typeof value === 'function' ? value() : value;
7
+ }
3
8
  const DEFAULT_PROGRESS_INTERVAL_MS = 250;
4
9
  const TOOL_HANDLER_PROGRESS_TOTAL = 1;
5
10
  function hasProgressTransport(extra, progressToken) {
@@ -129,7 +134,7 @@ export function wrapToolHandler(handler, options) {
129
134
  result = await handler(args, extra);
130
135
  }
131
136
  catch (error) {
132
- const isCancelled = error instanceof Error && error.message === E_CANCELLED;
137
+ const isCancelled = error instanceof CancelledError;
133
138
  await notifyTerminalProgress(extra, startMessage, isCancelled ? 'cancelled' : 'failed');
134
139
  throw error;
135
140
  }
@@ -139,3 +144,34 @@ export function wrapToolHandler(handler, options) {
139
144
  return result;
140
145
  };
141
146
  }
147
+ export async function runWithProgressCompletion(extra, run, options) {
148
+ let result;
149
+ let thrownError;
150
+ try {
151
+ result = await run();
152
+ }
153
+ catch (error) {
154
+ if (error instanceof CancelledError) {
155
+ result = createErrorResponse(E_CANCELLED, 'Request cancelled');
156
+ }
157
+ else if (error instanceof McpError) {
158
+ thrownError = error;
159
+ }
160
+ else {
161
+ rethrowMcpError(error);
162
+ result = createErrorResponse(E_UNKNOWN, getErrorMessage(error));
163
+ }
164
+ }
165
+ await options.reporter.flush();
166
+ const completionResult = result ?? createErrorResponse(E_UNKNOWN, getErrorMessage(thrownError));
167
+ const completionCurrent = resolveCompletionCurrent(options.completionCurrent);
168
+ await notifyProgress(extra, {
169
+ current: completionCurrent,
170
+ total: completionCurrent,
171
+ message: options.completionMessage(completionResult),
172
+ });
173
+ if (thrownError) {
174
+ throw thrownError;
175
+ }
176
+ return completionResult;
177
+ }
@@ -1,4 +1,3 @@
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
@@ -1,121 +1,16 @@
1
- import { McpError, } from '@modelcontextprotocol/sdk/types.js';
2
- import process from 'node:process';
3
- import { E_CANCELLED, E_UNKNOWN, getErrorMessage, rethrowMcpError, } from '../lib/errors.js';
1
+ import { throwIfAborted } from '../lib/errors.js';
2
+ import { traverseGraph, } from '../lib/graph-traversal.js';
4
3
  import { logToolEvent } from '../lib/mcp-utils.js';
5
4
  import { splitPage } from '../lib/pagination.js';
6
5
  import { buildSearchCursorScope, decodeSearchCursor, encodeSearchCursor, } from '../lib/search-cursor.js';
7
6
  import { loadRankedSearchRows, toMemoryFilters } from '../lib/search.js';
8
- import { createErrorResponse, createToolResponse, } from '../lib/tool-response.js';
7
+ import { createToolResponse } from '../lib/tool-response.js';
9
8
  import { parseMemoryRow } from '../lib/types.js';
10
- import { RecallInputSchema } from '../schemas/inputs.js';
11
- import { RecallResultSchema } from '../schemas/outputs.js';
12
- import { createProgressReporter, notifyProgress, progressWithMessage, } from './progress.js';
9
+ import {} from '../schemas/inputs.js';
10
+ import { createProgressReporter, notifyProgress, progressWithMessage, runWithProgressCompletion, } from './progress.js';
13
11
  import { registerToolWithContract } from './register-contract.js';
14
- import { countPayloadArrayItems, getToolResultPayload, getToolResultText, isOkStructuredToolResult, } from './result.js';
15
- function throwIfAborted(signal) {
16
- if (!signal?.aborted) {
17
- return;
18
- }
19
- throw new Error(E_CANCELLED);
20
- }
21
- function yieldToEventLoop() {
22
- return new Promise((resolve) => setImmediate(resolve));
23
- }
24
- function parseEnvInt(name, fallback, min, max) {
25
- const raw = process.env[name];
26
- if (raw == null)
27
- return fallback;
28
- const parsed = parseInt(raw, 10);
29
- if (Number.isNaN(parsed))
30
- return fallback;
31
- return Math.max(min, Math.min(max, parsed));
32
- }
33
- const MAX_FRONTIER_SIZE = parseEnvInt('RECALL_MAX_FRONTIER_SIZE', 1000, 100, 50000);
34
- const MAX_EDGE_ROWS = parseEnvInt('RECALL_MAX_EDGE_ROWS', 5000, 100, 50000);
35
- const MAX_VISITED_NODES = parseEnvInt('RECALL_MAX_VISITED_NODES', 5000, 100, 50000);
36
- const EDGE_QUERY_SQL = `SELECT from_hash, to_hash, relation_type FROM relationships
37
- WHERE from_hash IN (SELECT value FROM json_each(?))
38
- OR to_hash IN (SELECT value FROM json_each(?))
39
- LIMIT ?`;
12
+ import { countPayloadArrayItems, formatToolCompletionMessage, } from './result.js';
40
13
  const MEMORIES_BY_HASH_SQL = 'SELECT * FROM memories WHERE hash IN (SELECT value FROM json_each(?))';
41
- async function traverseGraph(db, seeds, depth, signal, onHop) {
42
- const visited = new Set();
43
- const frontier = [];
44
- for (const seed of seeds) {
45
- visited.add(seed.hash);
46
- frontier.push(seed.hash);
47
- }
48
- const edges = [];
49
- const seenEdges = new Set();
50
- let depthReached = 0;
51
- let aborted = false;
52
- const edgeStmt = db.prepareOnce(EDGE_QUERY_SQL);
53
- for (let hop = 0; hop < depth && frontier.length > 0; hop += 1) {
54
- // Yield to event loop to allow progress notifications and cancellation
55
- await yieldToEventLoop();
56
- throwIfAborted(signal);
57
- depthReached = hop + 1;
58
- onHop?.(hop, depth);
59
- if (frontier.length > MAX_FRONTIER_SIZE) {
60
- frontier.length = MAX_FRONTIER_SIZE;
61
- aborted = true;
62
- }
63
- const remainingEdgeBudget = MAX_EDGE_ROWS - edges.length;
64
- const remainingNodeBudget = MAX_VISITED_NODES - visited.size;
65
- if (remainingEdgeBudget <= 0 || remainingNodeBudget <= 0) {
66
- aborted = true;
67
- break;
68
- }
69
- const frontierJson = JSON.stringify(frontier);
70
- const edgeRows = edgeStmt.all(frontierJson, frontierJson, remainingEdgeBudget + 1);
71
- const rowsToProcess = edgeRows.length > remainingEdgeBudget
72
- ? remainingEdgeBudget
73
- : edgeRows.length;
74
- if (edgeRows.length > remainingEdgeBudget) {
75
- aborted = true;
76
- }
77
- const nextHashes = [];
78
- const queueVisitedHash = (hash) => {
79
- if (visited.has(hash)) {
80
- return;
81
- }
82
- if (visited.size >= MAX_VISITED_NODES) {
83
- aborted = true;
84
- return;
85
- }
86
- visited.add(hash);
87
- if (nextHashes.length < MAX_FRONTIER_SIZE) {
88
- nextHashes.push(hash);
89
- return;
90
- }
91
- aborted = true;
92
- };
93
- for (let i = 0; i < rowsToProcess; i += 1) {
94
- const edge = edgeRows[i];
95
- if (edge === undefined) {
96
- break;
97
- }
98
- const edgeKey = `${edge.from_hash}|${edge.to_hash}|${edge.relation_type}`;
99
- if (!seenEdges.has(edgeKey)) {
100
- seenEdges.add(edgeKey);
101
- edges.push({
102
- from_hash: edge.from_hash,
103
- to_hash: edge.to_hash,
104
- relation_type: edge.relation_type,
105
- });
106
- }
107
- queueVisitedHash(edge.from_hash);
108
- queueVisitedHash(edge.to_hash);
109
- if (aborted &&
110
- (edges.length >= MAX_EDGE_ROWS || visited.size >= MAX_VISITED_NODES)) {
111
- break;
112
- }
113
- }
114
- frontier.length = 0;
115
- frontier.push(...nextHashes);
116
- }
117
- return { edges, visited, depthReached, aborted };
118
- }
119
14
  function loadMemoriesByHashes(db, hashes) {
120
15
  if (hashes.length === 0) {
121
16
  return [];
@@ -125,25 +20,12 @@ function loadMemoriesByHashes(db, hashes) {
125
20
  .all(JSON.stringify(hashes));
126
21
  }
127
22
  function formatRecallCompletionMessage(query, result) {
128
- const failedMessage = `⊙ recall: ${query} • failed`;
129
- if (result.isError) {
130
- const text = getToolResultText(result);
131
- if (text.includes(E_CANCELLED)) {
132
- return `⊙ recall: ${query} cancelled`;
133
- }
134
- return failedMessage;
135
- }
136
- if (!isOkStructuredToolResult(result)) {
137
- return failedMessage;
138
- }
139
- const payload = getToolResultPayload(result);
140
- if (!payload) {
141
- return `⊙ recall: ${query} • completed`;
142
- }
143
- const memoriesCount = countPayloadArrayItems(payload, 'memories');
144
- const edgesCount = countPayloadArrayItems(payload, 'graph');
145
- const aborted = 'aborted' in payload && payload.aborted === true;
146
- return `⊙ recall: ${query} • ${memoriesCount} memories, ${edgesCount} edges${aborted ? ' [aborted]' : ''}`;
23
+ return formatToolCompletionMessage('recall', query, result, (payload) => {
24
+ const memoriesCount = countPayloadArrayItems(payload, 'memories');
25
+ const edgesCount = countPayloadArrayItems(payload, 'graph');
26
+ const aborted = 'aborted' in payload && payload.aborted === true;
27
+ return `${memoriesCount} memories, ${edgesCount} edges${aborted ? ' [aborted]' : ''}`;
28
+ });
147
29
  }
148
30
  function decodeCursorForRecall(cursor, scope) {
149
31
  if (!cursor) {
@@ -188,6 +70,15 @@ function createHopReporter(extra, query, completionCurrent) {
188
70
  };
189
71
  return { reporter, onHop };
190
72
  }
73
+ function toRecallResponse(computation) {
74
+ return createToolResponse({
75
+ memories: computation.memories,
76
+ graph: computation.edges,
77
+ depth_reached: computation.depthReached,
78
+ ...(computation.aborted ? { aborted: true } : {}),
79
+ ...(computation.nextCursor ? { nextCursor: computation.nextCursor } : {}),
80
+ });
81
+ }
191
82
  async function computeRecall(db, params, scope, signal, onHop) {
192
83
  const decodedCursor = decodeCursorForRecall(params.cursor, scope);
193
84
  const seedRows = loadRankedSearchRows(db, params.query, params.limit, decodedCursor, toMemoryFilters(params));
@@ -208,7 +99,7 @@ async function computeRecall(db, params, scope, signal, onHop) {
208
99
  };
209
100
  }
210
101
  export function registerRecall(server, db) {
211
- registerToolWithContract(server, 'recall', RecallInputSchema, RecallResultSchema, async (params, extra) => {
102
+ registerToolWithContract(server, 'recall', async (params, extra) => {
212
103
  const { depth } = params;
213
104
  const filters = toMemoryFilters(params);
214
105
  const scope = buildSearchCursorScope(params.query, filters);
@@ -220,9 +111,7 @@ export function registerRecall(server, db) {
220
111
  message: contextLabel,
221
112
  });
222
113
  const { reporter: hopReporter, onHop } = createHopReporter(extra, params.query, completionCurrent);
223
- let result;
224
- let thrownError;
225
- try {
114
+ return runWithProgressCompletion(extra, async () => {
226
115
  throwIfAborted(extra.signal);
227
116
  const computation = await computeRecall(db, params, scope, extra.signal, onHop);
228
117
  throwIfAborted(extra.signal);
@@ -234,38 +123,11 @@ export function registerRecall(server, db) {
234
123
  edge_count: computation.edges.length,
235
124
  aborted: computation.aborted,
236
125
  });
237
- result = createToolResponse({
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
- : {}),
245
- });
246
- }
247
- catch (err) {
248
- if (err instanceof Error && err.message === E_CANCELLED) {
249
- result = createErrorResponse(E_CANCELLED, 'Request cancelled');
250
- }
251
- else if (err instanceof McpError) {
252
- thrownError = err;
253
- }
254
- else {
255
- rethrowMcpError(err);
256
- result = createErrorResponse(E_UNKNOWN, getErrorMessage(err));
257
- }
258
- }
259
- await hopReporter.flush();
260
- const completionResult = result ?? createErrorResponse(E_UNKNOWN, getErrorMessage(thrownError));
261
- await notifyProgress(extra, {
262
- current: completionCurrent,
263
- total: completionCurrent,
264
- message: formatRecallCompletionMessage(params.query, completionResult),
126
+ return toRecallResponse(computation);
127
+ }, {
128
+ reporter: hopReporter,
129
+ completionCurrent,
130
+ completionMessage: (result) => formatRecallCompletionMessage(params.query, result),
265
131
  });
266
- if (thrownError) {
267
- throw thrownError;
268
- }
269
- return completionResult;
270
132
  });
271
133
  }
@@ -1,4 +1,2 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { z } from 'zod/v4';
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
2
+ export declare function registerToolWithContract(server: McpServer, toolName: string, handler: unknown): void;
@@ -1,5 +1,5 @@
1
1
  import { getToolContract } from '../lib/tool-contracts.js';
2
- export function registerToolWithContract(server, toolName, _inputSchema, _outputSchema, handler) {
2
+ export function registerToolWithContract(server, toolName, handler) {
3
3
  const contract = getToolContract(toolName);
4
4
  server.registerTool(contract.name, {
5
5
  title: contract.title,
@@ -7,5 +7,7 @@ export function registerToolWithContract(server, toolName, _inputSchema, _output
7
7
  inputSchema: contract.inputSchema,
8
8
  outputSchema: contract.outputSchema,
9
9
  annotations: contract.annotations,
10
- }, handler);
10
+ },
11
+ // The handler is validated at runtime and tested via contract verification
12
+ handler);
11
13
  }
@@ -5,4 +5,7 @@ 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
8
+ /** Standardize format of tool completion messages. */
9
+ export declare function formatToolCompletionMessage(toolName: string, query: string, result: CallToolResult, getSuccessMessage: (payload: ToolResultPayload) => string): string;
10
+ export declare function formatHashPreview(hash: string, length?: number): string;
11
+ export declare function formatRelationshipPreview(fromHash: string, toHash: string, length?: number): string;
@@ -1,3 +1,4 @@
1
+ import { E_CANCELLED } from '../lib/errors.js';
1
2
  function isRecord(value) {
2
3
  return typeof value === 'object' && value !== null;
3
4
  }
@@ -20,3 +21,29 @@ export function countPayloadArrayItems(payload, key) {
20
21
  const value = payload[key];
21
22
  return Array.isArray(value) ? value.length : 0;
22
23
  }
24
+ /** Standardize format of tool completion messages. */
25
+ export function formatToolCompletionMessage(toolName, query, result, getSuccessMessage) {
26
+ const failedMessage = `⊙ ${toolName}: ${query} • failed`;
27
+ if (result.isError) {
28
+ const text = getToolResultText(result);
29
+ if (text.includes(E_CANCELLED)) {
30
+ return `⊙ ${toolName}: ${query} • cancelled`;
31
+ }
32
+ return failedMessage;
33
+ }
34
+ if (!isOkStructuredToolResult(result)) {
35
+ return failedMessage;
36
+ }
37
+ const payload = getToolResultPayload(result);
38
+ if (!payload) {
39
+ return `⊙ ${toolName}: ${query} • completed`;
40
+ }
41
+ const successSuffix = getSuccessMessage(payload);
42
+ return `⊙ ${toolName}: ${query} • ${successSuffix}`;
43
+ }
44
+ export function formatHashPreview(hash, length = 12) {
45
+ return `${hash.slice(0, length)}...`;
46
+ }
47
+ export function formatRelationshipPreview(fromHash, toHash, length = 8) {
48
+ return `${formatHashPreview(fromHash, length)} -> ${formatHashPreview(toHash, length)}`;
49
+ }
@@ -1,4 +1,3 @@
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
@@ -1,13 +1,11 @@
1
- import { McpError } from '@modelcontextprotocol/sdk/types.js';
2
- import { E_CANCELLED, E_UNKNOWN, getErrorMessage, rethrowMcpError, } from '../lib/errors.js';
3
- import { sanitizeFtsQuery } from '../lib/search.js';
4
- import { createErrorResponse, createToolResponse, } from '../lib/tool-response.js';
1
+ import { throwIfAborted } from '../lib/errors.js';
2
+ import { buildAndWhereClause, buildFilterClauses, sanitizeFtsQuery, toMemoryFilters, } from '../lib/search.js';
3
+ import { createToolResponse } from '../lib/tool-response.js';
5
4
  import { parseMemoryRow } from '../lib/types.js';
6
- import { RetrieveContextInputSchema } from '../schemas/inputs.js';
7
- import { RetrieveContextResultSchema } from '../schemas/outputs.js';
8
- import { createProgressReporter, notifyProgress, progressWithMessage, } from './progress.js';
5
+ import {} from '../schemas/inputs.js';
6
+ import { createProgressReporter, notifyProgress, progressWithMessage, runWithProgressCompletion, } from './progress.js';
9
7
  import { registerToolWithContract } from './register-contract.js';
10
- import { countPayloadArrayItems, getToolResultPayload, getToolResultText, isOkStructuredToolResult, } from './result.js';
8
+ import { countPayloadArrayItems, formatToolCompletionMessage, } from './result.js';
11
9
  const MIN_CANDIDATE_ROWS = 200;
12
10
  const MAX_CANDIDATE_ROWS = 2000;
13
11
  const ESTIMATED_CHARS_PER_TOKEN = 4;
@@ -25,15 +23,18 @@ function countPayloadMemories(payload) {
25
23
  function estimateTokens(content) {
26
24
  return Math.ceil(content.length / ESTIMATED_CHARS_PER_TOKEN);
27
25
  }
28
- function loadContextRows(db, query, orderBy, limit) {
26
+ function loadContextRows(db, query, strategy, limit, filters) {
29
27
  const ftsQuery = sanitizeFtsQuery(query);
28
+ const filter = buildFilterClauses(filters);
29
+ const whereExtra = buildAndWhereClause(filter.clauses);
30
+ const orderBy = ORDER_BY_MAP[strategy];
30
31
  return db
31
32
  .prepareOnce(`SELECT m.*, memories_fts.rank AS rank FROM memories m
32
33
  JOIN memories_fts ON memories_fts.rowid = m.rowid
33
- WHERE memories_fts MATCH ?
34
+ WHERE memories_fts MATCH ?${whereExtra}
34
35
  ORDER BY ${orderBy}
35
36
  LIMIT ?`)
36
- .all(ftsQuery, limit + 1);
37
+ .all(ftsQuery, ...filter.params, limit + 1);
37
38
  }
38
39
  function reportSelectionProgress(onProgress, current, total, force = false) {
39
40
  if (!onProgress || current === 0) {
@@ -45,34 +46,17 @@ function reportSelectionProgress(onProgress, current, total, force = false) {
45
46
  onProgress({ current, total });
46
47
  }
47
48
  function formatCompletionMessage(query, result) {
48
- const failedMessage = `⊙ retrieve_context: ${query} • failed`;
49
- if (result.isError) {
50
- const text = getToolResultText(result);
51
- if (text.includes(E_CANCELLED)) {
52
- return `⊙ retrieve_context: ${query} • cancelled`;
53
- }
54
- return failedMessage;
55
- }
56
- if (!isOkStructuredToolResult(result)) {
57
- return failedMessage;
58
- }
59
- const payload = getToolResultPayload(result);
60
- if (!payload) {
61
- return `⊙ retrieve_context: ${query} • completed`;
62
- }
63
- const memoriesCount = countPayloadMemories(payload);
64
- const estimatedTokens = 'estimated_tokens' in payload &&
65
- typeof payload.estimated_tokens === 'number'
66
- ? payload.estimated_tokens
67
- : 0;
68
- const truncated = 'truncated' in payload && payload.truncated === true ? ' [truncated]' : '';
69
- return `⊙ retrieve_context: ${query} • ${memoriesCount} memories, ${estimatedTokens} tokens${truncated}`;
70
- }
71
- function throwIfAborted(signal) {
72
- if (!signal?.aborted) {
73
- return;
74
- }
75
- throw new Error(E_CANCELLED);
49
+ return formatToolCompletionMessage('retrieve_context', query, result, (payload) => {
50
+ const memoriesCount = countPayloadMemories(payload);
51
+ const estimatedTokens = 'estimated_tokens' in payload &&
52
+ typeof payload.estimated_tokens === 'number'
53
+ ? payload.estimated_tokens
54
+ : 0;
55
+ const truncated = 'truncated' in payload && payload.truncated === true
56
+ ? ' [truncated]'
57
+ : '';
58
+ return `${memoriesCount} memories, ${estimatedTokens} tokens${truncated}`;
59
+ });
76
60
  }
77
61
  function computeCandidateLimit(tokenBudget) {
78
62
  const estimatedCandidates = Math.ceil(tokenBudget / ESTIMATED_TOKENS_PER_MEMORY);
@@ -103,9 +87,17 @@ function selectMemoriesWithinBudget(rows, candidateCount, tokenBudget, completio
103
87
  reportSelectionProgress(onProgress, scanned, completionCurrent, true);
104
88
  return { selected, estimatedTokens, truncated };
105
89
  }
90
+ function toRetrieveContextResponse(computation) {
91
+ const { selection } = computation;
92
+ return createToolResponse({
93
+ memories: selection.selected,
94
+ estimated_tokens: selection.estimatedTokens,
95
+ truncated: selection.truncated,
96
+ });
97
+ }
106
98
  function computeRetrieveContextResult(db, params, limit, signal, onProgress) {
107
- const orderBy = ORDER_BY_MAP[params.strategy];
108
- const rows = loadContextRows(db, params.query, orderBy, limit);
99
+ const filters = toMemoryFilters(params);
100
+ const rows = loadContextRows(db, params.query, params.strategy, limit, filters);
109
101
  const rowCapExceeded = rows.length > limit;
110
102
  const candidateCount = rowCapExceeded ? limit : rows.length;
111
103
  const completionCurrent = candidateCount + 1;
@@ -115,7 +107,7 @@ function computeRetrieveContextResult(db, params, limit, signal, onProgress) {
115
107
  };
116
108
  }
117
109
  export function registerRetrieveContext(server, db) {
118
- registerToolWithContract(server, 'retrieve_context', RetrieveContextInputSchema, RetrieveContextResultSchema, async (params, extra) => {
110
+ registerToolWithContract(server, 'retrieve_context', async (params, extra) => {
119
111
  const { query, strategy } = params;
120
112
  const tokenBudget = params.token_budget;
121
113
  const contextLabel = `⊙ retrieve_context: ${query} [${strategy}]`;
@@ -127,40 +119,16 @@ export function registerRetrieveContext(server, db) {
127
119
  message: `${contextLabel} [budget ${tokenBudget}]`,
128
120
  });
129
121
  const loopProgress = progressWithMessage(createProgressReporter(extra), ({ current, total }) => `${contextLabel} [scan ${current}/${Math.max((total ?? current) - 1, current)}]`);
130
- let result;
131
- let thrownError;
132
- try {
122
+ return runWithProgressCompletion(extra, () => {
133
123
  throwIfAborted(extra.signal);
134
- const { selection, completionCurrent: nextCompletionCurrent } = computeRetrieveContextResult(db, params, limit, extra.signal, loopProgress);
124
+ const computation = computeRetrieveContextResult(db, params, limit, extra.signal, loopProgress);
125
+ const { completionCurrent: nextCompletionCurrent } = computation;
135
126
  completionCurrent = nextCompletionCurrent;
136
- result = createToolResponse({
137
- memories: selection.selected,
138
- estimated_tokens: selection.estimatedTokens,
139
- truncated: selection.truncated,
140
- });
141
- }
142
- catch (err) {
143
- if (err instanceof Error && err.message === E_CANCELLED) {
144
- result = createErrorResponse(E_CANCELLED, 'Request cancelled');
145
- }
146
- else if (err instanceof McpError) {
147
- thrownError = err;
148
- }
149
- else {
150
- rethrowMcpError(err);
151
- result = createErrorResponse(E_UNKNOWN, getErrorMessage(err));
152
- }
153
- }
154
- await loopProgress.flush();
155
- const completionResult = result ?? createErrorResponse(E_UNKNOWN, getErrorMessage(thrownError));
156
- await notifyProgress(extra, {
157
- current: completionCurrent,
158
- total: completionCurrent,
159
- message: formatCompletionMessage(query, completionResult),
127
+ return toRetrieveContextResponse(computation);
128
+ }, {
129
+ reporter: loopProgress,
130
+ completionCurrent: () => completionCurrent,
131
+ completionMessage: (result) => formatCompletionMessage(query, result),
160
132
  });
161
- if (thrownError) {
162
- throw thrownError;
163
- }
164
- return completionResult;
165
133
  });
166
134
  }
@@ -1,4 +1,3 @@
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
@@ -4,12 +4,22 @@ import { loadRankedSearchRows, toMemoryFilters } from '../lib/search.js';
4
4
  import { executeToolSafely } from '../lib/tool-execution.js';
5
5
  import { createToolResponse } from '../lib/tool-response.js';
6
6
  import { parseMemoryRow } from '../lib/types.js';
7
- import { SearchMemoriesInputSchema } from '../schemas/inputs.js';
8
- import { SearchResultSchema } from '../schemas/outputs.js';
7
+ import {} from '../schemas/inputs.js';
9
8
  import { wrapToolHandler } from './progress.js';
10
9
  import { registerToolWithContract } from './register-contract.js';
10
+ function buildNextCursorFromRows(scope, hasMore, pageRows) {
11
+ if (!hasMore || pageRows.length === 0) {
12
+ return undefined;
13
+ }
14
+ const lastRow = pageRows[pageRows.length - 1];
15
+ if (!lastRow) {
16
+ return undefined;
17
+ }
18
+ const rank = lastRow.rank ?? 0;
19
+ return encodeSearchCursor(scope, rank, lastRow.hash);
20
+ }
11
21
  export function registerSearchMemories(server, db) {
12
- registerToolWithContract(server, 'search_memories', SearchMemoriesInputSchema, SearchResultSchema, wrapToolHandler((params) => executeToolSafely(() => {
22
+ registerToolWithContract(server, 'search_memories', wrapToolHandler((params) => executeToolSafely(() => {
13
23
  const { limit, cursor } = params;
14
24
  const filters = toMemoryFilters(params);
15
25
  const scope = buildSearchCursorScope(params.query, filters);
@@ -19,14 +29,7 @@ export function registerSearchMemories(server, db) {
19
29
  const rows = loadRankedSearchRows(db, params.query, limit, decodedCursor, filters);
20
30
  const { page: pageRows, hasMore } = splitPage(rows, limit);
21
31
  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);
28
- }
29
- }
32
+ const nextCursor = buildNextCursorFromRows(scope, hasMore, pageRows);
30
33
  return createToolResponse({
31
34
  memories,
32
35
  total_returned: memories.length,
@@ -1,4 +1,3 @@
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
@@ -3,8 +3,7 @@ import { logToolEvent, notifyMemoryResourceUpdated } from '../lib/mcp-utils.js';
3
3
  import { INSERT_MEMORY_SQL } from '../lib/sql.js';
4
4
  import { executeToolSafely, summarizeBatch } from '../lib/tool-execution.js';
5
5
  import { createToolResponse } from '../lib/tool-response.js';
6
- import { StoreMemoriesInputSchema } from '../schemas/inputs.js';
7
- import { BatchResultSchema } from '../schemas/outputs.js';
6
+ import {} from '../schemas/inputs.js';
8
7
  import { wrapToolHandler } from './progress.js';
9
8
  import { registerToolWithContract } from './register-contract.js';
10
9
  async function notifyCreatedResources(server, items) {
@@ -14,7 +13,7 @@ async function notifyCreatedResources(server, items) {
14
13
  await Promise.allSettled(notifications);
15
14
  }
16
15
  export function registerStoreMemories(server, db) {
17
- registerToolWithContract(server, 'store_memories', StoreMemoriesInputSchema, BatchResultSchema, wrapToolHandler(async (params) => executeToolSafely(async () => {
16
+ registerToolWithContract(server, 'store_memories', wrapToolHandler(async (params) => executeToolSafely(async () => {
18
17
  const now = new Date().toISOString();
19
18
  const results = db.transaction(() => {
20
19
  const items = [];
@@ -1,4 +1,3 @@
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
@@ -3,8 +3,7 @@ import { logToolEvent, notifyMemoryResourceUpdated } from '../lib/mcp-utils.js';
3
3
  import { INSERT_MEMORY_SQL } from '../lib/sql.js';
4
4
  import { executeToolSafely } from '../lib/tool-execution.js';
5
5
  import { createToolResponse } from '../lib/tool-response.js';
6
- import { StoreMemoryInputSchema } from '../schemas/inputs.js';
7
- import { StoreResultSchema } from '../schemas/outputs.js';
6
+ import {} from '../schemas/inputs.js';
8
7
  import { wrapToolHandler } from './progress.js';
9
8
  import { registerToolWithContract } from './register-contract.js';
10
9
  function toInsertParams(params, hash, memoryType, now) {
@@ -25,7 +24,7 @@ function insertMemory(db, params, hash, memoryType, now) {
25
24
  return insertResult.changes > 0;
26
25
  }
27
26
  export function registerStoreMemory(server, db) {
28
- registerToolWithContract(server, 'store_memory', StoreMemoryInputSchema, StoreResultSchema, wrapToolHandler(async (params) => executeToolSafely(async () => {
27
+ registerToolWithContract(server, 'store_memory', wrapToolHandler(async (params) => executeToolSafely(async () => {
29
28
  const memoryType = params.memory_type ?? 'general';
30
29
  const hash = computeMemoryHash(params.content, params.tags);
31
30
  const now = new Date().toISOString();
@@ -1,4 +1,3 @@
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