@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/db/index.d.ts
CHANGED
package/dist/db/index.js
CHANGED
|
@@ -7,6 +7,10 @@ const TARGET_SCHEMA_VERSION = 2;
|
|
|
7
7
|
const FTS5_CHECK_SQL = 'CREATE VIRTUAL TABLE IF NOT EXISTS __fts5_check USING fts5(x); DROP TABLE __fts5_check;';
|
|
8
8
|
const FTS5_REQUIRED_MESSAGE = 'SQLite FTS5 extension is not available. memory-mcp requires a SQLite build with FTS5 support.';
|
|
9
9
|
const DEFENSIVE_PRAGMA_SQL = 'PRAGMA defensive = ON';
|
|
10
|
+
const RELATIONSHIP_INDEX_SQL = [
|
|
11
|
+
'CREATE INDEX IF NOT EXISTS idx_relationships_from ON relationships(from_hash)',
|
|
12
|
+
'CREATE INDEX IF NOT EXISTS idx_relationships_to ON relationships(to_hash)',
|
|
13
|
+
];
|
|
10
14
|
const RELATIONSHIPS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS relationships (
|
|
11
15
|
from_hash TEXT NOT NULL REFERENCES memories(hash) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
12
16
|
to_hash TEXT NOT NULL REFERENCES memories(hash) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
@@ -57,19 +61,16 @@ const SCHEMA_SQL = `
|
|
|
57
61
|
CREATE INDEX IF NOT EXISTS idx_memories_created
|
|
58
62
|
ON memories(created_at DESC);
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
ON relationships(from_hash);
|
|
62
|
-
|
|
63
|
-
CREATE INDEX IF NOT EXISTS idx_relationships_to
|
|
64
|
-
ON relationships(to_hash);
|
|
64
|
+
${RELATIONSHIP_INDEX_SQL.join(';\n\n ')};
|
|
65
65
|
`;
|
|
66
66
|
const RELATIONSHIP_EDGE_COLUMNS = new Set(['from_hash', 'to_hash']);
|
|
67
67
|
function isMemoryRelationshipForeignKey(row) {
|
|
68
68
|
return row.table === 'memories' && RELATIONSHIP_EDGE_COLUMNS.has(row.from);
|
|
69
69
|
}
|
|
70
70
|
function ensureRelationshipIndexes(db) {
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
for (const statement of RELATIONSHIP_INDEX_SQL) {
|
|
72
|
+
db.exec(statement);
|
|
73
|
+
}
|
|
73
74
|
}
|
|
74
75
|
function runImmediateTransaction(db, action) {
|
|
75
76
|
db.exec('BEGIN IMMEDIATE');
|
package/dist/db/typed.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/lib/errors.d.ts
CHANGED
|
@@ -8,4 +8,7 @@ export declare const E_UNKNOWN = "E_UNKNOWN";
|
|
|
8
8
|
export declare function getErrorMessage(err: unknown): string;
|
|
9
9
|
export declare function isMcpError(err: unknown): err is McpError;
|
|
10
10
|
export declare function rethrowMcpError(err: unknown): void;
|
|
11
|
-
|
|
11
|
+
export declare class CancelledError extends Error {
|
|
12
|
+
constructor();
|
|
13
|
+
}
|
|
14
|
+
export declare function throwIfAborted(signal?: AbortSignal): void;
|
package/dist/lib/errors.js
CHANGED
|
@@ -20,3 +20,14 @@ export function rethrowMcpError(err) {
|
|
|
20
20
|
if (isMcpError(err))
|
|
21
21
|
throw err;
|
|
22
22
|
}
|
|
23
|
+
export class CancelledError extends Error {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(E_CANCELLED);
|
|
26
|
+
this.name = 'CancelledError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function throwIfAborted(signal) {
|
|
30
|
+
if (signal?.aborted) {
|
|
31
|
+
throw new CancelledError();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TypedDb } from '../db/typed.js';
|
|
2
|
+
import type { MemoryRow, RelationshipEdge } from './types.js';
|
|
3
|
+
export type ProgressNotifier = (hop: number, total: number) => void;
|
|
4
|
+
export declare const MAX_EDGE_ROWS: number;
|
|
5
|
+
export declare const MAX_VISITED_NODES: number;
|
|
6
|
+
export interface TraverseGraphResult {
|
|
7
|
+
edges: RelationshipEdge[];
|
|
8
|
+
visited: Set<string>;
|
|
9
|
+
depthReached: number;
|
|
10
|
+
aborted: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function traverseGraph(db: TypedDb, seeds: MemoryRow[], depth: number, signal?: AbortSignal, onHop?: ProgressNotifier): Promise<TraverseGraphResult>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { throwIfAborted } from './errors.js';
|
|
3
|
+
function yieldToEventLoop() {
|
|
4
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
5
|
+
}
|
|
6
|
+
function parseEnvInt(name, fallback, min, max) {
|
|
7
|
+
const raw = process.env[name];
|
|
8
|
+
if (raw == null)
|
|
9
|
+
return fallback;
|
|
10
|
+
const parsed = parseInt(raw, 10);
|
|
11
|
+
if (Number.isNaN(parsed))
|
|
12
|
+
return fallback;
|
|
13
|
+
return Math.max(min, Math.min(max, parsed));
|
|
14
|
+
}
|
|
15
|
+
const MAX_FRONTIER_SIZE = parseEnvInt('RECALL_MAX_FRONTIER_SIZE', 1000, 100, 50000);
|
|
16
|
+
export const MAX_EDGE_ROWS = parseEnvInt('RECALL_MAX_EDGE_ROWS', 5000, 100, 50000);
|
|
17
|
+
export const MAX_VISITED_NODES = parseEnvInt('RECALL_MAX_VISITED_NODES', 5000, 100, 50000);
|
|
18
|
+
const EDGE_QUERY_SQL = `SELECT from_hash, to_hash, relation_type FROM relationships
|
|
19
|
+
WHERE from_hash IN (SELECT value FROM json_each(?))
|
|
20
|
+
OR to_hash IN (SELECT value FROM json_each(?))
|
|
21
|
+
LIMIT ?`;
|
|
22
|
+
function initializeTraversalState(seeds) {
|
|
23
|
+
const visited = new Set();
|
|
24
|
+
const frontier = [];
|
|
25
|
+
for (const seed of seeds) {
|
|
26
|
+
visited.add(seed.hash);
|
|
27
|
+
frontier.push(seed.hash);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
visited,
|
|
31
|
+
frontier,
|
|
32
|
+
edges: [],
|
|
33
|
+
seenEdges: new Set(),
|
|
34
|
+
depthReached: 0,
|
|
35
|
+
aborted: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function capFrontier(state) {
|
|
39
|
+
if (state.frontier.length <= MAX_FRONTIER_SIZE) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
state.frontier.length = MAX_FRONTIER_SIZE;
|
|
43
|
+
state.aborted = true;
|
|
44
|
+
}
|
|
45
|
+
function getRemainingBudget(state) {
|
|
46
|
+
return {
|
|
47
|
+
edges: MAX_EDGE_ROWS - state.edges.length,
|
|
48
|
+
nodes: MAX_VISITED_NODES - state.visited.size,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function hasExhaustedBudget(budget) {
|
|
52
|
+
return budget.edges <= 0 || budget.nodes <= 0;
|
|
53
|
+
}
|
|
54
|
+
function loadEdgeRows(edgeStmt, frontier, edgeLimit) {
|
|
55
|
+
const frontierJson = JSON.stringify(frontier);
|
|
56
|
+
return edgeStmt.all(frontierJson, frontierJson, edgeLimit + 1);
|
|
57
|
+
}
|
|
58
|
+
function toRowsToProcessCount(edgeRowsLength, remainingEdgeBudget) {
|
|
59
|
+
return edgeRowsLength > remainingEdgeBudget
|
|
60
|
+
? remainingEdgeBudget
|
|
61
|
+
: edgeRowsLength;
|
|
62
|
+
}
|
|
63
|
+
function toEdgeKey(edge) {
|
|
64
|
+
return `${edge.from_hash}|${edge.to_hash}|${edge.relation_type}`;
|
|
65
|
+
}
|
|
66
|
+
function appendEdgeIfNew(state, edge) {
|
|
67
|
+
const edgeKey = toEdgeKey(edge);
|
|
68
|
+
if (state.seenEdges.has(edgeKey)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
state.seenEdges.add(edgeKey);
|
|
72
|
+
state.edges.push({
|
|
73
|
+
from_hash: edge.from_hash,
|
|
74
|
+
to_hash: edge.to_hash,
|
|
75
|
+
relation_type: edge.relation_type,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function createVisitedQueue(state, nextHashes) {
|
|
79
|
+
return (hash) => {
|
|
80
|
+
if (state.visited.has(hash)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (state.visited.size >= MAX_VISITED_NODES) {
|
|
84
|
+
state.aborted = true;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
state.visited.add(hash);
|
|
88
|
+
if (nextHashes.length < MAX_FRONTIER_SIZE) {
|
|
89
|
+
nextHashes.push(hash);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
state.aborted = true;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function shouldStopEdgeProcessing(state) {
|
|
96
|
+
return (state.aborted &&
|
|
97
|
+
(state.edges.length >= MAX_EDGE_ROWS ||
|
|
98
|
+
state.visited.size >= MAX_VISITED_NODES));
|
|
99
|
+
}
|
|
100
|
+
function processEdgeRows(state, edgeRows, rowsToProcess) {
|
|
101
|
+
const nextHashes = [];
|
|
102
|
+
const queueVisitedHash = createVisitedQueue(state, nextHashes);
|
|
103
|
+
for (let i = 0; i < rowsToProcess; i += 1) {
|
|
104
|
+
const edge = edgeRows[i];
|
|
105
|
+
if (!edge) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
appendEdgeIfNew(state, edge);
|
|
109
|
+
queueVisitedHash(edge.from_hash);
|
|
110
|
+
queueVisitedHash(edge.to_hash);
|
|
111
|
+
if (shouldStopEdgeProcessing(state)) {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
state.frontier.length = 0;
|
|
116
|
+
state.frontier.push(...nextHashes);
|
|
117
|
+
}
|
|
118
|
+
export async function traverseGraph(db, seeds, depth, signal, onHop) {
|
|
119
|
+
const state = initializeTraversalState(seeds);
|
|
120
|
+
const edgeStmt = db.prepareOnce(EDGE_QUERY_SQL);
|
|
121
|
+
for (let hop = 0; hop < depth && state.frontier.length > 0; hop += 1) {
|
|
122
|
+
await yieldToEventLoop();
|
|
123
|
+
throwIfAborted(signal);
|
|
124
|
+
state.depthReached = hop + 1;
|
|
125
|
+
onHop?.(hop, depth);
|
|
126
|
+
capFrontier(state);
|
|
127
|
+
const budget = getRemainingBudget(state);
|
|
128
|
+
if (hasExhaustedBudget(budget)) {
|
|
129
|
+
state.aborted = true;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
const edgeRows = loadEdgeRows(edgeStmt, state.frontier, budget.edges);
|
|
133
|
+
const rowsToProcess = toRowsToProcessCount(edgeRows.length, budget.edges);
|
|
134
|
+
if (edgeRows.length > budget.edges) {
|
|
135
|
+
state.aborted = true;
|
|
136
|
+
}
|
|
137
|
+
processEdgeRows(state, edgeRows, rowsToProcess);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
edges: state.edges,
|
|
141
|
+
visited: state.visited,
|
|
142
|
+
depthReached: state.depthReached,
|
|
143
|
+
aborted: state.aborted,
|
|
144
|
+
};
|
|
145
|
+
}
|
package/dist/lib/hash.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
2
|
export type JsonSchemaObject = Record<string, unknown>;
|
|
3
3
|
export declare function extractJsonSchema(schema: z.ZodType): JsonSchemaObject;
|
|
4
|
-
|
|
4
|
+
export interface SchemaMeta {
|
|
5
|
+
properties: Record<string, JsonSchemaObject>;
|
|
6
|
+
requiredFields: Set<string>;
|
|
7
|
+
}
|
|
8
|
+
export declare function getSchemaMeta(schema: z.ZodType): SchemaMeta;
|
package/dist/lib/json-schema.js
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
|
+
const JsonSchemaPayloadSchema = z
|
|
3
|
+
.object({
|
|
4
|
+
properties: z
|
|
5
|
+
.record(z.string(), z.record(z.string(), z.unknown()))
|
|
6
|
+
.optional(),
|
|
7
|
+
required: z.array(z.string()).optional(),
|
|
8
|
+
})
|
|
9
|
+
.catchall(z.unknown());
|
|
2
10
|
export function extractJsonSchema(schema) {
|
|
3
11
|
try {
|
|
4
|
-
|
|
12
|
+
const raw = z.toJSONSchema(schema);
|
|
13
|
+
return raw;
|
|
5
14
|
}
|
|
6
15
|
catch {
|
|
7
16
|
return {};
|
|
8
17
|
}
|
|
9
18
|
}
|
|
19
|
+
export function getSchemaMeta(schema) {
|
|
20
|
+
const jsonSchema = extractJsonSchema(schema);
|
|
21
|
+
const parsed = JsonSchemaPayloadSchema.safeParse(jsonSchema);
|
|
22
|
+
const data = parsed.success ? parsed.data : {};
|
|
23
|
+
return {
|
|
24
|
+
properties: data.properties ?? {},
|
|
25
|
+
requiredFields: new Set(data.required ?? []),
|
|
26
|
+
};
|
|
27
|
+
}
|
package/dist/lib/mcp-utils.d.ts
CHANGED
|
@@ -3,4 +3,3 @@ type LogLevel = 'debug' | 'info' | 'notice' | 'warning' | 'error';
|
|
|
3
3
|
export declare function logToolEvent(server: McpServer, logger: string, data: unknown, level?: LogLevel): Promise<void>;
|
|
4
4
|
export declare function notifyMemoryResourceUpdated(server: McpServer, hash: string): Promise<void>;
|
|
5
5
|
export {};
|
|
6
|
-
//# sourceMappingURL=mcp-utils.d.ts.map
|
package/dist/lib/pagination.d.ts
CHANGED
|
@@ -2,7 +2,4 @@ export interface PageSlice<T> {
|
|
|
2
2
|
page: T[];
|
|
3
3
|
hasMore: boolean;
|
|
4
4
|
}
|
|
5
|
-
export declare function encodeCursor(offset: number): string;
|
|
6
|
-
export declare function decodeCursor(cursor: string): number;
|
|
7
5
|
export declare function splitPage<T>(rows: readonly T[], limit: number): PageSlice<T>;
|
|
8
|
-
//# sourceMappingURL=pagination.d.ts.map
|
package/dist/lib/pagination.js
CHANGED
|
@@ -1,46 +1,3 @@
|
|
|
1
|
-
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
-
import { E_INVALID_CURSOR } from './errors.js';
|
|
3
|
-
const CURSOR_ENCODING = 'base64url';
|
|
4
|
-
const INVALID_CURSOR_STRUCTURE_MESSAGE = 'Invalid cursor structure';
|
|
5
|
-
function isRecord(value) {
|
|
6
|
-
return typeof value === 'object' && value !== null;
|
|
7
|
-
}
|
|
8
|
-
function isNonNegativeInteger(value) {
|
|
9
|
-
return (typeof value === 'number' &&
|
|
10
|
-
Number.isInteger(value) &&
|
|
11
|
-
Number.isFinite(value) &&
|
|
12
|
-
value >= 0);
|
|
13
|
-
}
|
|
14
|
-
function isCursorPayload(value) {
|
|
15
|
-
if (!isRecord(value)) {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
const { offset } = value;
|
|
19
|
-
return isNonNegativeInteger(offset);
|
|
20
|
-
}
|
|
21
|
-
function parseCursorPayload(cursor) {
|
|
22
|
-
const json = Buffer.from(cursor, CURSOR_ENCODING).toString();
|
|
23
|
-
const parsed = JSON.parse(json);
|
|
24
|
-
if (!isCursorPayload(parsed)) {
|
|
25
|
-
throw new Error(INVALID_CURSOR_STRUCTURE_MESSAGE);
|
|
26
|
-
}
|
|
27
|
-
return parsed;
|
|
28
|
-
}
|
|
29
|
-
function invalidCursor() {
|
|
30
|
-
return new McpError(ErrorCode.InvalidParams, `${E_INVALID_CURSOR}: malformed cursor`);
|
|
31
|
-
}
|
|
32
|
-
export function encodeCursor(offset) {
|
|
33
|
-
const payload = { offset };
|
|
34
|
-
return Buffer.from(JSON.stringify(payload)).toString(CURSOR_ENCODING);
|
|
35
|
-
}
|
|
36
|
-
export function decodeCursor(cursor) {
|
|
37
|
-
try {
|
|
38
|
-
return parseCursorPayload(cursor).offset;
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
throw invalidCursor();
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
1
|
export function splitPage(rows, limit) {
|
|
45
2
|
if (rows.length > limit) {
|
|
46
3
|
return { page: rows.slice(0, limit), hasMore: true };
|
|
@@ -10,4 +10,3 @@ export type DecodedSearchCursor = {
|
|
|
10
10
|
export declare function buildSearchCursorScope(query: string, filters: MemoryFilters): string;
|
|
11
11
|
export declare function encodeSearchCursor(scope: string, rank: number, hash: string): string;
|
|
12
12
|
export declare function decodeSearchCursor(cursor: string, expectedScope: string): DecodedSearchCursor;
|
|
13
|
-
//# sourceMappingURL=search-cursor.d.ts.map
|
package/dist/lib/search.d.ts
CHANGED
package/dist/lib/search.js
CHANGED
|
@@ -12,6 +12,9 @@ export function sanitizeFtsQuery(query) {
|
|
|
12
12
|
}
|
|
13
13
|
return tokens.map((token) => `"${token}"`).join(' ');
|
|
14
14
|
}
|
|
15
|
+
function isOffsetCursor(cursor) {
|
|
16
|
+
return cursor == null || cursor.mode === 'offset';
|
|
17
|
+
}
|
|
15
18
|
const FILTER_RULES = [
|
|
16
19
|
{ key: 'min_importance', clause: 'm.importance >= ?' },
|
|
17
20
|
{ key: 'max_importance', clause: 'm.importance <= ?' },
|
|
@@ -41,7 +44,7 @@ function buildBaseSearchWhere(whereExtra) {
|
|
|
41
44
|
}
|
|
42
45
|
function buildRankedSearchSql(whereExtra, cursor) {
|
|
43
46
|
const whereSql = buildBaseSearchWhere(whereExtra);
|
|
44
|
-
if (
|
|
47
|
+
if (isOffsetCursor(cursor)) {
|
|
45
48
|
return `${whereSql}
|
|
46
49
|
ORDER BY memories_fts.rank, m.hash
|
|
47
50
|
LIMIT ? OFFSET ?`;
|
|
@@ -54,13 +57,22 @@ function buildRankedSearchSql(whereExtra, cursor) {
|
|
|
54
57
|
ORDER BY memories_fts.rank, m.hash
|
|
55
58
|
LIMIT ?`;
|
|
56
59
|
}
|
|
60
|
+
function buildBaseSearchParams(ftsQuery, filterParams) {
|
|
61
|
+
return [ftsQuery, ...filterParams];
|
|
62
|
+
}
|
|
63
|
+
function buildOffsetParams(baseParams, limit, offset) {
|
|
64
|
+
return [...baseParams, limit + 1, offset];
|
|
65
|
+
}
|
|
66
|
+
function buildKeysetParams(baseParams, limit, cursor) {
|
|
67
|
+
return [...baseParams, cursor.rank, cursor.rank, cursor.hash, limit + 1];
|
|
68
|
+
}
|
|
57
69
|
function buildRankedSearchParams(ftsQuery, filterParams, limit, cursor) {
|
|
58
|
-
const baseParams =
|
|
59
|
-
if (
|
|
70
|
+
const baseParams = buildBaseSearchParams(ftsQuery, filterParams);
|
|
71
|
+
if (isOffsetCursor(cursor)) {
|
|
60
72
|
const offset = cursor?.offset ?? 0;
|
|
61
|
-
return
|
|
73
|
+
return buildOffsetParams(baseParams, limit, offset);
|
|
62
74
|
}
|
|
63
|
-
return
|
|
75
|
+
return buildKeysetParams(baseParams, limit, cursor);
|
|
64
76
|
}
|
|
65
77
|
function buildSearchPlan(query, limit, cursor, filters) {
|
|
66
78
|
const filter = buildFilterClauses(filters);
|
|
@@ -74,13 +86,14 @@ export function loadRankedSearchRows(db, query, limit, cursor, filters) {
|
|
|
74
86
|
return db.prepareOnce(plan.sql).all(...plan.params);
|
|
75
87
|
}
|
|
76
88
|
export function toMemoryFilters(params) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
? { max_importance: params.max_importance }
|
|
83
|
-
: {}),
|
|
84
|
-
...(params.memory_type != null ? { memory_type: params.memory_type } : {}),
|
|
89
|
+
const filters = {};
|
|
90
|
+
const addFilter = (key, value) => {
|
|
91
|
+
if (value != null) {
|
|
92
|
+
filters[key] = value;
|
|
93
|
+
}
|
|
85
94
|
};
|
|
95
|
+
addFilter('min_importance', params.min_importance);
|
|
96
|
+
addFilter('max_importance', params.max_importance);
|
|
97
|
+
addFilter('memory_type', params.memory_type);
|
|
98
|
+
return filters;
|
|
86
99
|
}
|
package/dist/lib/sql.d.ts
CHANGED
|
@@ -14,4 +14,3 @@ export declare const MEMORY_AGGREGATE_SQL = "SELECT COUNT(*) AS total,\n
|
|
|
14
14
|
export declare const RELATIONSHIP_COUNT_SQL = "SELECT COUNT(*) AS total FROM relationships";
|
|
15
15
|
/** Per-type memory breakdown. */
|
|
16
16
|
export declare const TYPE_COUNTS_SQL = "SELECT memory_type, COUNT(*) AS count FROM memories GROUP BY memory_type ORDER BY count DESC";
|
|
17
|
-
//# sourceMappingURL=sql.d.ts.map
|
|
@@ -26,115 +26,120 @@ function createToolContract(definition) {
|
|
|
26
26
|
},
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
function combineAnnotations(...annotations) {
|
|
30
|
+
const merged = {};
|
|
31
|
+
for (const annotation of annotations) {
|
|
32
|
+
Object.assign(merged, annotation);
|
|
33
|
+
}
|
|
34
|
+
return merged;
|
|
35
|
+
}
|
|
36
|
+
const TOOL_DEFINITIONS = [
|
|
37
|
+
{
|
|
31
38
|
name: 'store_memory',
|
|
32
39
|
title: 'Store Memory',
|
|
33
40
|
description: 'Store single memory. Returns hash. Idempotent (created: false if exists). Prefer store_memories.',
|
|
34
41
|
inputSchema: StoreMemoryInputSchema,
|
|
35
42
|
outputSchema: StoreResultSchema,
|
|
36
43
|
annotations: IDEMPOTENT_ANNOTATIONS,
|
|
37
|
-
}
|
|
38
|
-
|
|
44
|
+
},
|
|
45
|
+
{
|
|
39
46
|
name: 'store_memories',
|
|
40
47
|
title: 'Store Memories (Batch)',
|
|
41
48
|
description: 'Store 1-50 memories atomically. Idempotent. Rolls back on error.',
|
|
42
49
|
inputSchema: StoreMemoriesInputSchema,
|
|
43
50
|
outputSchema: BatchResultSchema,
|
|
44
51
|
annotations: IDEMPOTENT_ANNOTATIONS,
|
|
45
|
-
}
|
|
46
|
-
|
|
52
|
+
},
|
|
53
|
+
{
|
|
47
54
|
name: 'get_memory',
|
|
48
55
|
title: 'Get Memory',
|
|
49
56
|
description: 'Retrieve memory by SHA-256 hash. Returns E_NOT_FOUND if missing.',
|
|
50
57
|
inputSchema: GetMemoryInputSchema,
|
|
51
58
|
outputSchema: MemoryResultSchema,
|
|
52
59
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
53
|
-
}
|
|
54
|
-
|
|
60
|
+
},
|
|
61
|
+
{
|
|
55
62
|
name: 'search_memories',
|
|
56
63
|
title: 'Search Memories',
|
|
57
64
|
description: 'Full-text search (content+tags). Ranked, paginated. Alphanumeric/underscore only. Implicit AND.',
|
|
58
65
|
inputSchema: SearchMemoriesInputSchema,
|
|
59
66
|
outputSchema: SearchResultSchema,
|
|
60
67
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
61
|
-
}
|
|
62
|
-
|
|
68
|
+
},
|
|
69
|
+
{
|
|
63
70
|
name: 'retrieve_context',
|
|
64
71
|
title: 'Retrieve Context',
|
|
65
|
-
description: 'FTS search within token budget. Sorts by relevance/importance/recency. Returns truncated: true if limit hit.',
|
|
72
|
+
description: 'FTS search within token budget. Sorts by relevance/importance/recency. Supports importance and type filters. Returns truncated: true if limit hit.',
|
|
66
73
|
inputSchema: RetrieveContextInputSchema,
|
|
67
74
|
outputSchema: RetrieveContextResultSchema,
|
|
68
75
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
69
|
-
}
|
|
70
|
-
|
|
76
|
+
},
|
|
77
|
+
{
|
|
71
78
|
name: 'recall',
|
|
72
79
|
title: 'Recall (BFS Graph Traversal)',
|
|
73
80
|
description: 'FTS search + BFS traversal (depth hops). Returns memories+edges. Emits progress. Aborts on limit.',
|
|
74
81
|
inputSchema: RecallInputSchema,
|
|
75
82
|
outputSchema: RecallResultSchema,
|
|
76
83
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
77
|
-
}
|
|
78
|
-
|
|
84
|
+
},
|
|
85
|
+
{
|
|
79
86
|
name: 'update_memory',
|
|
80
87
|
title: 'Update Memory',
|
|
81
|
-
description: 'Update content/tags. Returns old+new hash. Cascade updates relationships.',
|
|
88
|
+
description: 'Update content and/or tags (at least one required). Returns old+new hash. Cascade updates relationships.',
|
|
82
89
|
inputSchema: UpdateMemoryInputSchema,
|
|
83
90
|
outputSchema: UpdateResultSchema,
|
|
84
91
|
annotations: DESTRUCTIVE_ANNOTATIONS,
|
|
85
|
-
}
|
|
86
|
-
|
|
92
|
+
},
|
|
93
|
+
{
|
|
87
94
|
name: 'delete_memory',
|
|
88
95
|
title: 'Delete Memory',
|
|
89
96
|
description: 'Delete memory by hash. Cascade deletes relationships. Idempotent.',
|
|
90
97
|
inputSchema: DeleteMemoryInputSchema,
|
|
91
98
|
outputSchema: DeleteResultSchema,
|
|
92
|
-
annotations:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
},
|
|
96
|
-
}),
|
|
97
|
-
createToolContract({
|
|
99
|
+
annotations: combineAnnotations(DESTRUCTIVE_ANNOTATIONS, IDEMPOTENT_ANNOTATIONS),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
98
102
|
name: 'delete_memories',
|
|
99
103
|
title: 'Delete Memories (Batch)',
|
|
100
104
|
description: 'Delete 1-50 memories atomically. Cascade deletes. Rolls back on error.',
|
|
101
105
|
inputSchema: DeleteMemoriesInputSchema,
|
|
102
106
|
outputSchema: BatchResultSchema,
|
|
103
107
|
annotations: DESTRUCTIVE_ANNOTATIONS,
|
|
104
|
-
}
|
|
105
|
-
|
|
108
|
+
},
|
|
109
|
+
{
|
|
106
110
|
name: 'create_relationship',
|
|
107
111
|
title: 'Create Relationship',
|
|
108
112
|
description: 'Create directed edge. Idempotent. Errors if endpoints missing.',
|
|
109
113
|
inputSchema: CreateRelationshipInputSchema,
|
|
110
114
|
outputSchema: CreateRelationshipResultSchema,
|
|
111
115
|
annotations: IDEMPOTENT_ANNOTATIONS,
|
|
112
|
-
}
|
|
113
|
-
|
|
116
|
+
},
|
|
117
|
+
{
|
|
114
118
|
name: 'delete_relationship',
|
|
115
119
|
title: 'Delete Relationship',
|
|
116
|
-
description: 'Delete edge. Exact match required.
|
|
120
|
+
description: 'Delete edge. Exact match required. Idempotent (deleted: false if missing).',
|
|
117
121
|
inputSchema: DeleteRelationshipInputSchema,
|
|
118
122
|
outputSchema: DeleteRelationshipResultSchema,
|
|
119
|
-
annotations: DESTRUCTIVE_ANNOTATIONS,
|
|
120
|
-
}
|
|
121
|
-
|
|
123
|
+
annotations: combineAnnotations(DESTRUCTIVE_ANNOTATIONS, IDEMPOTENT_ANNOTATIONS),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
122
126
|
name: 'get_relationships',
|
|
123
127
|
title: 'Get Relationships',
|
|
124
128
|
description: 'Get relationships for memory. Filter direction. Inlines related memory.',
|
|
125
129
|
inputSchema: GetRelationshipsInputSchema,
|
|
126
130
|
outputSchema: RelationshipResultSchema,
|
|
127
131
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
128
|
-
}
|
|
129
|
-
|
|
132
|
+
},
|
|
133
|
+
{
|
|
130
134
|
name: 'memory_stats',
|
|
131
135
|
title: 'Memory Stats',
|
|
132
136
|
description: 'Get global stats: counts, timestamps, importance.',
|
|
133
137
|
inputSchema: MemoryStatsInputSchema,
|
|
134
138
|
outputSchema: StatsResultSchema,
|
|
135
139
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
136
|
-
}
|
|
140
|
+
},
|
|
137
141
|
];
|
|
142
|
+
export const TOOL_CONTRACTS = TOOL_DEFINITIONS.map(createToolContract);
|
|
138
143
|
const TOOL_CONTRACTS_BY_NAME = new Map(TOOL_CONTRACTS.map((contract) => [contract.name, contract]));
|
|
139
144
|
export function getToolContracts() {
|
|
140
145
|
return TOOL_CONTRACTS;
|
|
@@ -9,5 +9,5 @@ interface BatchItemLike {
|
|
|
9
9
|
}
|
|
10
10
|
export declare function executeToolSafely(work: () => Promise<CallToolResult> | CallToolResult): Promise<CallToolResult>;
|
|
11
11
|
export declare function summarizeBatch<T extends BatchItemLike>(items: readonly T[], isMatched: (item: T) => boolean): BatchSummary;
|
|
12
|
+
export declare function executeLongRunningToolSafely(work: () => Promise<CallToolResult> | CallToolResult, onFinally?: () => Promise<void>): Promise<CallToolResult>;
|
|
12
13
|
export {};
|
|
13
|
-
//# sourceMappingURL=tool-execution.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { CancelledError, E_CANCELLED, E_UNKNOWN, getErrorMessage, rethrowMcpError, } from './errors.js';
|
|
2
3
|
import { createErrorResponse } from './tool-response.js';
|
|
3
4
|
export async function executeToolSafely(work) {
|
|
4
5
|
try {
|
|
@@ -26,3 +27,25 @@ export function summarizeBatch(items, isMatched) {
|
|
|
26
27
|
matched,
|
|
27
28
|
};
|
|
28
29
|
}
|
|
30
|
+
export async function executeLongRunningToolSafely(work, onFinally) {
|
|
31
|
+
let result;
|
|
32
|
+
let thrownError;
|
|
33
|
+
try {
|
|
34
|
+
result = await work();
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err instanceof CancelledError) {
|
|
38
|
+
result = createErrorResponse(E_CANCELLED, 'Request cancelled');
|
|
39
|
+
}
|
|
40
|
+
else if (err instanceof McpError) {
|
|
41
|
+
thrownError = err;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
result = createErrorResponse(E_UNKNOWN, getErrorMessage(err));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (onFinally) {
|
|
48
|
+
await onFinally();
|
|
49
|
+
}
|
|
50
|
+
return result ?? createErrorResponse(E_UNKNOWN, getErrorMessage(thrownError));
|
|
51
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
2
|
export declare function createToolResponse(payload: Record<string, unknown>): CallToolResult;
|
|
3
3
|
export declare function createErrorResponse(code: string, message: string): CallToolResult;
|
|
4
|
-
//# sourceMappingURL=tool-response.d.ts.map
|
package/dist/lib/types.d.ts
CHANGED
package/dist/prompts/index.d.ts
CHANGED