@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.
- package/dist/completions/index.d.ts +0 -1
- package/dist/db/index.d.ts +0 -1
- package/dist/db/index.js +8 -7
- package/dist/db/typed.d.ts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/lib/errors.d.ts +4 -1
- package/dist/lib/errors.js +11 -0
- package/dist/lib/graph-traversal.d.ts +12 -0
- package/dist/lib/graph-traversal.js +145 -0
- package/dist/lib/hash.d.ts +0 -1
- package/dist/lib/instructions.d.ts +0 -1
- package/dist/lib/json-schema.d.ts +5 -1
- package/dist/lib/json-schema.js +19 -1
- package/dist/lib/mcp-utils.d.ts +0 -1
- package/dist/lib/pagination.d.ts +0 -3
- package/dist/lib/pagination.js +0 -43
- package/dist/lib/search-cursor.d.ts +0 -1
- package/dist/lib/search.d.ts +0 -1
- package/dist/lib/search.js +26 -13
- package/dist/lib/sql.d.ts +0 -1
- package/dist/lib/tool-contracts.d.ts +0 -1
- package/dist/lib/tool-contracts.js +40 -35
- package/dist/lib/tool-execution.d.ts +1 -1
- package/dist/lib/tool-execution.js +24 -1
- package/dist/lib/tool-response.d.ts +0 -1
- package/dist/lib/types.d.ts +0 -1
- package/dist/prompts/index.d.ts +0 -1
- package/dist/resources/index.d.ts +0 -1
- package/dist/resources/index.js +24 -27
- package/dist/resources/instructions.d.ts +0 -1
- package/dist/resources/server-config.d.ts +0 -1
- package/dist/resources/tool-catalog.d.ts +0 -1
- package/dist/resources/tool-catalog.js +1 -10
- package/dist/resources/tool-info.d.ts +0 -1
- package/dist/resources/tool-info.js +1 -10
- package/dist/resources/workflows.d.ts +0 -1
- package/dist/schemas/index.d.ts +0 -1
- package/dist/schemas/inputs.d.ts +8 -6
- package/dist/schemas/inputs.js +43 -30
- package/dist/schemas/outputs.d.ts +0 -1
- package/dist/server.d.ts +0 -1
- package/dist/tools/create-relationship.d.ts +0 -1
- package/dist/tools/create-relationship.js +4 -4
- package/dist/tools/delete-memories.d.ts +0 -1
- package/dist/tools/delete-memories.js +4 -3
- package/dist/tools/delete-memory.d.ts +0 -1
- package/dist/tools/delete-memory.js +6 -4
- package/dist/tools/delete-relationship.d.ts +0 -1
- package/dist/tools/delete-relationship.js +7 -17
- package/dist/tools/get-memory.d.ts +0 -1
- package/dist/tools/get-memory.js +4 -4
- package/dist/tools/get-relationships.d.ts +0 -1
- package/dist/tools/get-relationships.js +16 -12
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/memory-stats.d.ts +0 -1
- package/dist/tools/memory-stats.js +1 -3
- package/dist/tools/progress.d.ts +6 -1
- package/dist/tools/progress.js +38 -2
- package/dist/tools/recall.d.ts +0 -1
- package/dist/tools/recall.js +28 -166
- package/dist/tools/register-contract.d.ts +1 -3
- package/dist/tools/register-contract.js +4 -2
- package/dist/tools/result.d.ts +4 -1
- package/dist/tools/result.js +27 -0
- package/dist/tools/retrieve-context.d.ts +0 -1
- package/dist/tools/retrieve-context.js +42 -74
- package/dist/tools/search-memories.d.ts +0 -1
- package/dist/tools/search-memories.js +14 -11
- package/dist/tools/store-memories.d.ts +0 -1
- package/dist/tools/store-memories.js +2 -3
- package/dist/tools/store-memory.d.ts +0 -1
- package/dist/tools/store-memory.js +2 -3
- package/dist/tools/update-memory.d.ts +0 -1
- package/dist/tools/update-memory.js +7 -6
- package/package.json +1 -1
- package/dist/completions/index.d.ts.map +0 -1
- package/dist/db/index.d.ts.map +0 -1
- package/dist/db/typed.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/errors.d.ts.map +0 -1
- package/dist/lib/hash.d.ts.map +0 -1
- package/dist/lib/instructions.d.ts.map +0 -1
- package/dist/lib/json-schema.d.ts.map +0 -1
- package/dist/lib/mcp-utils.d.ts.map +0 -1
- package/dist/lib/pagination.d.ts.map +0 -1
- package/dist/lib/search-cursor.d.ts.map +0 -1
- package/dist/lib/search.d.ts.map +0 -1
- package/dist/lib/sql.d.ts.map +0 -1
- package/dist/lib/tool-contracts.d.ts.map +0 -1
- package/dist/lib/tool-execution.d.ts.map +0 -1
- package/dist/lib/tool-response.d.ts.map +0 -1
- package/dist/lib/types.d.ts.map +0 -1
- package/dist/prompts/index.d.ts.map +0 -1
- package/dist/resources/index.d.ts.map +0 -1
- package/dist/resources/instructions.d.ts.map +0 -1
- package/dist/resources/server-config.d.ts.map +0 -1
- package/dist/resources/tool-catalog.d.ts.map +0 -1
- package/dist/resources/tool-info.d.ts.map +0 -1
- package/dist/resources/workflows.d.ts.map +0 -1
- package/dist/schemas/index.d.ts.map +0 -1
- package/dist/schemas/inputs.d.ts.map +0 -1
- package/dist/schemas/outputs.d.ts.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/tools/create-relationship.d.ts.map +0 -1
- package/dist/tools/delete-memories.d.ts.map +0 -1
- package/dist/tools/delete-memory.d.ts.map +0 -1
- package/dist/tools/delete-relationship.d.ts.map +0 -1
- package/dist/tools/get-memory.d.ts.map +0 -1
- package/dist/tools/get-relationships.d.ts.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/memory-stats.d.ts.map +0 -1
- package/dist/tools/progress.d.ts.map +0 -1
- package/dist/tools/recall.d.ts.map +0 -1
- package/dist/tools/register-contract.d.ts.map +0 -1
- package/dist/tools/result.d.ts.map +0 -1
- package/dist/tools/retrieve-context.d.ts.map +0 -1
- package/dist/tools/search-memories.d.ts.map +0 -1
- package/dist/tools/store-memories.d.ts.map +0 -1
- package/dist/tools/store-memory.d.ts.map +0 -1
- package/dist/tools/update-memory.d.ts.map +0 -1
package/dist/tools/progress.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
+
}
|
package/dist/tools/recall.d.ts
CHANGED
package/dist/tools/recall.js
CHANGED
|
@@ -1,121 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
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 {
|
|
7
|
+
import { createToolResponse } from '../lib/tool-response.js';
|
|
9
8
|
import { parseMemoryRow } from '../lib/types.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
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,
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
},
|
|
10
|
+
},
|
|
11
|
+
// The handler is validated at runtime and tested via contract verification
|
|
12
|
+
handler);
|
|
11
13
|
}
|
package/dist/tools/result.d.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/dist/tools/result.js
CHANGED
|
@@ -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,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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 {
|
|
7
|
-
import {
|
|
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,
|
|
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,
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return
|
|
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
|
|
108
|
-
const rows = loadContextRows(db, params.query,
|
|
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',
|
|
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
|
-
|
|
131
|
-
let thrownError;
|
|
132
|
-
try {
|
|
122
|
+
return runWithProgressCompletion(extra, () => {
|
|
133
123
|
throwIfAborted(extra.signal);
|
|
134
|
-
const
|
|
124
|
+
const computation = computeRetrieveContextResult(db, params, limit, extra.signal, loopProgress);
|
|
125
|
+
const { completionCurrent: nextCompletionCurrent } = computation;
|
|
135
126
|
completionCurrent = nextCompletionCurrent;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
}
|
|
@@ -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 {
|
|
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',
|
|
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
|
-
|
|
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,
|
|
@@ -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 {
|
|
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',
|
|
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 = [];
|
|
@@ -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 {
|
|
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',
|
|
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();
|