@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,3 +1,2 @@
1
1
  import type { TypedDb } from '../db/typed.js';
2
2
  export declare function createHashCompletionCallback(db: TypedDb): (value: string) => string[];
3
- //# sourceMappingURL=index.d.ts.map
@@ -2,4 +2,3 @@ import { DatabaseSync } from 'node:sqlite';
2
2
  import { type TypedDb } from './typed.js';
3
3
  export declare function initDatabase(path: string): DatabaseSync;
4
4
  export declare function initTypedDatabase(path: string): TypedDb;
5
- //# sourceMappingURL=index.d.ts.map
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
- CREATE INDEX IF NOT EXISTS idx_relationships_from
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
- db.exec('CREATE INDEX IF NOT EXISTS idx_relationships_from ON relationships(from_hash)');
72
- db.exec('CREATE INDEX IF NOT EXISTS idx_relationships_to ON relationships(to_hash)');
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');
@@ -18,4 +18,3 @@ export declare class TypedDb {
18
18
  }
19
19
  export declare function createTypedDb(db: DatabaseSync): TypedDb;
20
20
  export {};
21
- //# sourceMappingURL=typed.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  #!/usr/bin/env node
2
2
  export {};
3
- //# sourceMappingURL=index.d.ts.map
@@ -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
- //# sourceMappingURL=errors.d.ts.map
11
+ export declare class CancelledError extends Error {
12
+ constructor();
13
+ }
14
+ export declare function throwIfAborted(signal?: AbortSignal): void;
@@ -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
+ }
@@ -1,2 +1 @@
1
1
  export declare function computeMemoryHash(content: string, tags: readonly string[]): string;
2
- //# sourceMappingURL=hash.d.ts.map
@@ -1,2 +1 @@
1
1
  export declare function loadInstructions(): string;
2
- //# sourceMappingURL=instructions.d.ts.map
@@ -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
- //# sourceMappingURL=json-schema.d.ts.map
4
+ export interface SchemaMeta {
5
+ properties: Record<string, JsonSchemaObject>;
6
+ requiredFields: Set<string>;
7
+ }
8
+ export declare function getSchemaMeta(schema: z.ZodType): SchemaMeta;
@@ -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
- return z.toJSONSchema(schema);
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
+ }
@@ -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
@@ -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
@@ -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
@@ -27,4 +27,3 @@ export declare function toMemoryFilters(params: {
27
27
  max_importance?: number | undefined;
28
28
  memory_type?: string | undefined;
29
29
  }): MemoryFilters;
30
- //# sourceMappingURL=search.d.ts.map
@@ -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 (!cursor || cursor.mode === 'offset') {
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 = [ftsQuery, ...filterParams];
59
- if (!cursor || cursor.mode === 'offset') {
70
+ const baseParams = buildBaseSearchParams(ftsQuery, filterParams);
71
+ if (isOffsetCursor(cursor)) {
60
72
  const offset = cursor?.offset ?? 0;
61
- return [...baseParams, limit + 1, offset];
73
+ return buildOffsetParams(baseParams, limit, offset);
62
74
  }
63
- return [...baseParams, cursor.rank, cursor.rank, cursor.hash, limit + 1];
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
- return {
78
- ...(params.min_importance != null
79
- ? { min_importance: params.min_importance }
80
- : {}),
81
- ...(params.max_importance != null
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
@@ -15,4 +15,3 @@ export interface ToolContract {
15
15
  export declare const TOOL_CONTRACTS: ToolContract[];
16
16
  export declare function getToolContracts(): ToolContract[];
17
17
  export declare function getToolContract(name: string): ToolContract;
18
- //# sourceMappingURL=tool-contracts.d.ts.map
@@ -26,115 +26,120 @@ function createToolContract(definition) {
26
26
  },
27
27
  };
28
28
  }
29
- export const TOOL_CONTRACTS = [
30
- createToolContract({
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
- createToolContract({
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
- createToolContract({
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
- createToolContract({
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
- createToolContract({
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
- createToolContract({
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
- createToolContract({
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
- createToolContract({
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
- ...DESTRUCTIVE_ANNOTATIONS,
94
- ...IDEMPOTENT_ANNOTATIONS,
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
- createToolContract({
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
- createToolContract({
116
+ },
117
+ {
114
118
  name: 'delete_relationship',
115
119
  title: 'Delete Relationship',
116
- description: 'Delete edge. Exact match required. Errors if missing.',
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
- createToolContract({
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
- createToolContract({
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 { E_UNKNOWN, getErrorMessage, rethrowMcpError } from './errors.js';
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
@@ -72,4 +72,3 @@ export interface HashRow {
72
72
  }
73
73
  export declare function parseTags(tagsJson: string): string[];
74
74
  export declare function parseMemoryRow(row: MemoryRow): Memory;
75
- //# sourceMappingURL=types.d.ts.map
@@ -1,3 +1,2 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  export declare function registerAllPrompts(server: McpServer): void;
3
- //# sourceMappingURL=index.d.ts.map