@prmichaelsen/remember-mcp 0.2.7 → 1.0.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,171 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.1] - 2026-02-12
9
+
10
+ ### 🐛 Fixed
11
+
12
+ - **Empty Or/And Operator Bug**: Fixed "no children for operator Or" error
13
+ - Added validation to filter out undefined/null values before combining filters
14
+ - `combineFiltersWithOr` now validates operands array is not empty
15
+ - `combineFiltersWithAnd` now validates operands array is not empty
16
+ - `buildCombinedSearchFilters` filters out invalid filters before OR combination
17
+ - Prevents creation of operators with empty children arrays
18
+
19
+ ### ✨ Added
20
+
21
+ - **Edge Case Tests**: Added 3 new test cases for undefined/null filter handling
22
+ - Test for empty Or operator prevention
23
+ - Test for empty And operator prevention
24
+ - Test for mixed valid and undefined filters
25
+
26
+ ### 📊 Test Results
27
+
28
+ - **57 tests passing** (up from 54)
29
+ - **4 test suites passing** (all green)
30
+ - **Code coverage: 90.56%** on weaviate-filters.ts (up from 83.67%)
31
+
32
+ ---
33
+
34
+ ## [1.0.0] - 2026-02-12
35
+
36
+ ### 🚨 BREAKING CHANGES
37
+
38
+ - **`createServer` is now async**: The factory function now returns `Promise<Server>` instead of `Server`. Consumers must use `await` when calling `createServer()`.
39
+
40
+ ```typescript
41
+ // Before (0.2.x)
42
+ const server = createServer(token, userId);
43
+
44
+ // After (1.0.0+)
45
+ const server = await createServer(token, userId);
46
+ ```
47
+
48
+ **Impact**: Only affects direct factory users. The mcp-auth wrapper handles async factories automatically, so no changes needed for mcp-auth users.
49
+
50
+ ### ✨ Added
51
+
52
+ - **Weaviate v3 Filter API**: Implemented proper Weaviate v3 filter builders using fluent API
53
+ - Created `src/utils/weaviate-filters.ts` with filter builder utilities
54
+ - Supports AND/OR logic for complex filter combinations
55
+ - Comprehensive filter support: type, weight, trust, date range, tags
56
+
57
+ - **Combined Memory + Relationship Search**: `remember_search_memory` now searches BOTH memories and relationships by default
58
+ - Uses OR logic to search both doc types
59
+ - Results separated into `memories` and `relationships` arrays
60
+ - Backward compatible: set `include_relationships: false` to search only memories
61
+ - Relationship observations are now searchable
62
+
63
+ - **Comprehensive Unit Tests**: Added 29 test cases for filter builders
64
+ - Tests for `buildMemoryOnlyFilters`, `buildRelationshipOnlyFilters`, `buildCombinedSearchFilters`
65
+ - Tests for edge cases and complex filter scenarios
66
+ - 83.67% code coverage on weaviate-filters.ts
67
+
68
+ - **Jest Type Support**: Added "jest" to tsconfig types array for proper TypeScript support in test files
69
+
70
+ ### 🐛 Fixed
71
+
72
+ - **gRPC Filter Error**: Fixed "paths needs to have an uneven number of components" error
73
+ - Replaced old Weaviate v2 filter format (path/operator/valueText) with v3 fluent API
74
+ - Affected tools: `remember_search_memory`, `remember_query_memory`
75
+
76
+ - **Database Initialization**: Changed from fire-and-forget to await pattern
77
+ - Server now waits for database initialization before accepting requests
78
+ - Prevents server from starting in broken state
79
+ - Errors propagate clearly to caller
80
+
81
+ - **Test Failures**: Fixed server-factory tests to handle async createServer
82
+ - Updated all tests to use async/await
83
+ - Changed synchronous expect() calls to async expect().resolves/rejects
84
+
85
+ ### 📊 Test Results
86
+
87
+ - **54 tests passing** (up from 25)
88
+ - **4 test suites passing** (all green)
89
+ - **Overall coverage: 26.54%** (up from 22.53%)
90
+ - **1 skipped** (integration test requiring live Weaviate)
91
+
92
+ ### 📝 Documentation
93
+
94
+ - Updated `agent/progress.yaml` with Task 20 completion
95
+ - Updated `agent/tasks/task-20-fix-weaviate-v3-filters.md` with implementation details
96
+ - All changes documented in agent directory
97
+
98
+ ---
99
+
100
+ ## [0.2.8] - 2026-02-11
101
+
102
+ ### ✨ Added
103
+
104
+ - Complete memory CRUD operations (create, read, update, delete)
105
+ - Complete relationship CRUD operations (create, read, update, delete)
106
+ - User preferences system with 6 categories
107
+ - 12 MCP tools fully implemented
108
+ - Hybrid search (semantic + keyword)
109
+ - Vector similarity search
110
+ - RAG-optimized queries
111
+ - 45 content types
112
+
113
+ ### 📊 Milestones Completed
114
+
115
+ - M1: Project Foundation (100%)
116
+ - M2: Core Memory System (100%)
117
+ - M3: Relationships & Graph (100%)
118
+ - M4: User Preferences (100%)
119
+
120
+ ---
121
+
122
+ ## [0.1.0] - 2026-02-11
123
+
124
+ ### ✨ Initial Release
125
+
126
+ - Project structure and configuration
127
+ - Weaviate client with multi-tenant support
128
+ - Firestore integration
129
+ - Basic MCP server with stdio transport
130
+ - Server factory for mcp-auth compatibility
131
+ - 25 unit tests
132
+ - Dual build: standalone server + library factory
133
+
134
+ ---
135
+
136
+ ## Migration Guides
137
+
138
+ ### Migrating from 0.2.x to 1.0.0
139
+
140
+ **If you're using the factory export:**
141
+
142
+ ```typescript
143
+ // OLD (0.2.x)
144
+ import { createServer } from '@prmichaelsen/remember-mcp/factory';
145
+ const server = createServer(accessToken, userId);
146
+
147
+ // NEW (1.0.0+)
148
+ import { createServer } from '@prmichaelsen/remember-mcp/factory';
149
+ const server = await createServer(accessToken, userId);
150
+ ```
151
+
152
+ **If you're using mcp-auth wrapper:**
153
+ - No changes needed! mcp-auth handles async factories automatically.
154
+
155
+ **If you're using standalone server:**
156
+ - No changes needed! You don't call createServer directly.
157
+
158
+ **If you're using Claude Desktop:**
159
+ - No changes needed! You use the built server via npx.
160
+
161
+ ---
162
+
163
+ ## Versioning Policy
164
+
165
+ This project follows [Semantic Versioning](https://semver.org/):
166
+
167
+ - **Major** (X.0.0): Breaking changes
168
+ - **Minor** (0.X.0): New features, backward compatible
169
+ - **Patch** (0.0.X): Bug fixes, backward compatible
170
+
171
+ **Note**: Version 1.0.0 indicates the first stable release with a breaking change from 0.2.x. Future breaking changes will increment the major version (2.0.0, 3.0.0, etc.).
@@ -0,0 +1,82 @@
1
+ # Task: Fix Weaviate "Or" Operator Query Bug
2
+
3
+ **Project**: remember-mcp
4
+ **Estimated Time**: 1-2 hours
5
+ **Priority**: High
6
+ **Status**: Not Started
7
+
8
+ ---
9
+
10
+ ## Problem
11
+
12
+ Weaviate query failing with error:
13
+ ```
14
+ no children for operator "Or"
15
+ ```
16
+
17
+ This occurs when searching memories, indicating the query builder is creating an "Or" operator with an empty children array, which is invalid in Weaviate.
18
+
19
+ ## Root Cause
20
+
21
+ The Weaviate query construction in remember-mcp is building a filter with an "Or" operator but not adding any child conditions to it. This happens when:
22
+ - Building complex filters with multiple conditions
23
+ - Conditional logic results in empty filter arrays
24
+ - Or operator is created before checking if there are conditions to add
25
+
26
+ ## Investigation Steps
27
+
28
+ 1. **Find where "Or" operator is used**
29
+ - Search for `.or(` or `Or(` in remember-mcp codebase
30
+ - Check search-memory.ts, query-memory.ts, find-similar.ts
31
+
32
+ 2. **Identify the query construction logic**
33
+ - Look for filter building code
34
+ - Find where Or operator is created
35
+ - Check if children array is validated before creating operator
36
+
37
+ 3. **Reproduce locally**
38
+ - Test search with various parameters
39
+ - Identify which search parameters trigger the bug
40
+ - Confirm the empty Or operator
41
+
42
+ ## Expected Fix
43
+
44
+ Add validation before creating Or operator:
45
+ ```typescript
46
+ // Before (buggy):
47
+ const orFilter = weaviate.filter.or(...conditions);
48
+
49
+ // After (fixed):
50
+ if (conditions.length === 0) {
51
+ // Skip Or operator if no conditions
52
+ return baseQuery;
53
+ } else if (conditions.length === 1) {
54
+ // Use single condition directly
55
+ return baseQuery.withWhere(conditions[0]);
56
+ } else {
57
+ // Use Or operator only when multiple conditions
58
+ return baseQuery.withWhere(weaviate.filter.or(...conditions));
59
+ }
60
+ ```
61
+
62
+ ## Files to Check
63
+
64
+ - `src/tools/search-memory.ts` - Main search implementation
65
+ - `src/tools/query-memory.ts` - Query tool
66
+ - `src/tools/find-similar.ts` - Similarity search
67
+ - `src/weaviate/client.ts` - Weaviate client wrapper
68
+ - Any file that builds Weaviate filters
69
+
70
+ ## Verification
71
+
72
+ - [ ] Or operator only created when conditions.length > 1
73
+ - [ ] Empty conditions array handled gracefully
74
+ - [ ] Single condition doesn't use Or operator
75
+ - [ ] All search tools work without errors
76
+ - [ ] Tests pass
77
+
78
+ ---
79
+
80
+ **Impact**: High - blocks all search functionality
81
+ **Complexity**: Medium - requires understanding Weaviate query API
82
+ **Location**: remember-mcp project (not remember-mcp-server)
@@ -38,5 +38,5 @@ export interface ServerOptions {
38
38
  * });
39
39
  * ```
40
40
  */
41
- export declare function createServer(accessToken: string, userId: string, options?: ServerOptions): Server;
41
+ export declare function createServer(accessToken: string, userId: string, options?: ServerOptions): Promise<Server>;
42
42
  //# sourceMappingURL=server-factory.d.ts.map
@@ -1411,14 +1411,14 @@ async function handleCreateMemory(args, userId, context) {
1411
1411
  function buildCombinedSearchFilters(collection, filters) {
1412
1412
  const memoryFilters = buildDocTypeFilters(collection, "memory", filters);
1413
1413
  const relationshipFilters = buildDocTypeFilters(collection, "relationship", filters);
1414
- if (memoryFilters && relationshipFilters) {
1415
- return combineFiltersWithOr([memoryFilters, relationshipFilters]);
1416
- } else if (memoryFilters) {
1417
- return memoryFilters;
1418
- } else if (relationshipFilters) {
1419
- return relationshipFilters;
1420
- }
1421
- return void 0;
1414
+ const validFilters = [memoryFilters, relationshipFilters].filter((f) => f !== void 0 && f !== null);
1415
+ if (validFilters.length === 0) {
1416
+ return void 0;
1417
+ } else if (validFilters.length === 1) {
1418
+ return validFilters[0];
1419
+ } else {
1420
+ return combineFiltersWithOr(validFilters);
1421
+ }
1422
1422
  }
1423
1423
  function buildDocTypeFilters(collection, docType, filters) {
1424
1424
  const filterList = [];
@@ -1483,27 +1483,29 @@ function buildMemoryOnlyFilters(collection, filters) {
1483
1483
  return buildDocTypeFilters(collection, "memory", filters);
1484
1484
  }
1485
1485
  function combineFiltersWithAnd(filters) {
1486
- if (filters.length === 0) {
1486
+ const validFilters = filters.filter((f) => f !== void 0 && f !== null);
1487
+ if (validFilters.length === 0) {
1487
1488
  return void 0;
1488
1489
  }
1489
- if (filters.length === 1) {
1490
- return filters[0];
1490
+ if (validFilters.length === 1) {
1491
+ return validFilters[0];
1491
1492
  }
1492
1493
  return {
1493
1494
  operator: "And",
1494
- operands: filters
1495
+ operands: validFilters
1495
1496
  };
1496
1497
  }
1497
1498
  function combineFiltersWithOr(filters) {
1498
- if (filters.length === 0) {
1499
+ const validFilters = filters.filter((f) => f !== void 0 && f !== null);
1500
+ if (validFilters.length === 0) {
1499
1501
  return void 0;
1500
1502
  }
1501
- if (filters.length === 1) {
1502
- return filters[0];
1503
+ if (validFilters.length === 1) {
1504
+ return validFilters[0];
1503
1505
  }
1504
1506
  return {
1505
1507
  operator: "Or",
1506
- operands: filters
1508
+ operands: validFilters
1507
1509
  };
1508
1510
  }
1509
1511
 
@@ -3190,14 +3192,12 @@ async function ensureDatabasesInitialized() {
3190
3192
  })();
3191
3193
  return initializationPromise;
3192
3194
  }
3193
- function createServer(accessToken, userId, options = {}) {
3195
+ async function createServer(accessToken, userId, options = {}) {
3194
3196
  if (!userId) {
3195
3197
  throw new Error("userId is required");
3196
3198
  }
3197
3199
  logger.debug("Creating server instance", { userId });
3198
- ensureDatabasesInitialized().catch((error) => {
3199
- logger.error("Failed to initialize databases:", error);
3200
- });
3200
+ await ensureDatabasesInitialized();
3201
3201
  const server = new Server(
3202
3202
  {
3203
3203
  name: options.name || "remember-mcp",
package/dist/server.js CHANGED
@@ -1340,14 +1340,14 @@ async function handleCreateMemory(args, userId, context) {
1340
1340
  function buildCombinedSearchFilters(collection, filters) {
1341
1341
  const memoryFilters = buildDocTypeFilters(collection, "memory", filters);
1342
1342
  const relationshipFilters = buildDocTypeFilters(collection, "relationship", filters);
1343
- if (memoryFilters && relationshipFilters) {
1344
- return combineFiltersWithOr([memoryFilters, relationshipFilters]);
1345
- } else if (memoryFilters) {
1346
- return memoryFilters;
1347
- } else if (relationshipFilters) {
1348
- return relationshipFilters;
1349
- }
1350
- return void 0;
1343
+ const validFilters = [memoryFilters, relationshipFilters].filter((f) => f !== void 0 && f !== null);
1344
+ if (validFilters.length === 0) {
1345
+ return void 0;
1346
+ } else if (validFilters.length === 1) {
1347
+ return validFilters[0];
1348
+ } else {
1349
+ return combineFiltersWithOr(validFilters);
1350
+ }
1351
1351
  }
1352
1352
  function buildDocTypeFilters(collection, docType, filters) {
1353
1353
  const filterList = [];
@@ -1412,27 +1412,29 @@ function buildMemoryOnlyFilters(collection, filters) {
1412
1412
  return buildDocTypeFilters(collection, "memory", filters);
1413
1413
  }
1414
1414
  function combineFiltersWithAnd(filters) {
1415
- if (filters.length === 0) {
1415
+ const validFilters = filters.filter((f) => f !== void 0 && f !== null);
1416
+ if (validFilters.length === 0) {
1416
1417
  return void 0;
1417
1418
  }
1418
- if (filters.length === 1) {
1419
- return filters[0];
1419
+ if (validFilters.length === 1) {
1420
+ return validFilters[0];
1420
1421
  }
1421
1422
  return {
1422
1423
  operator: "And",
1423
- operands: filters
1424
+ operands: validFilters
1424
1425
  };
1425
1426
  }
1426
1427
  function combineFiltersWithOr(filters) {
1427
- if (filters.length === 0) {
1428
+ const validFilters = filters.filter((f) => f !== void 0 && f !== null);
1429
+ if (validFilters.length === 0) {
1428
1430
  return void 0;
1429
1431
  }
1430
- if (filters.length === 1) {
1431
- return filters[0];
1432
+ if (validFilters.length === 1) {
1433
+ return validFilters[0];
1432
1434
  }
1433
1435
  return {
1434
1436
  operator: "Or",
1435
- operands: filters
1437
+ operands: validFilters
1436
1438
  };
1437
1439
  }
1438
1440
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/remember-mcp",
3
- "version": "0.2.7",
3
+ "version": "1.0.1",
4
4
  "description": "Multi-tenant memory system MCP server with vector search and relationships",
5
5
  "main": "dist/server.js",
6
6
  "type": "module",
@@ -3,24 +3,24 @@ import { createServer } from './server-factory.js';
3
3
 
4
4
  describe('Server Factory', () => {
5
5
  describe('Parameter Validation', () => {
6
- it('should create server instance with valid parameters', () => {
7
- const server = createServer('test-token', 'user123');
6
+ it('should create server instance with valid parameters', async () => {
7
+ const server = await createServer('test-token', 'user123');
8
8
  expect(server).toBeDefined();
9
9
  expect(server).toHaveProperty('setRequestHandler');
10
10
  });
11
11
 
12
- it('should allow empty accessToken (not used by remember-mcp)', () => {
12
+ it('should allow empty accessToken (not used by remember-mcp)', async () => {
13
13
  // accessToken is not used by remember-mcp (self-managed data)
14
14
  // Should not throw even with empty string
15
- expect(() => createServer('', 'user123')).not.toThrow();
15
+ await expect(createServer('', 'user123')).resolves.toBeDefined();
16
16
  });
17
17
 
18
- it('should require userId', () => {
19
- expect(() => createServer('token', '')).toThrow('userId is required');
18
+ it('should require userId', async () => {
19
+ await expect(createServer('token', '')).rejects.toThrow('userId is required');
20
20
  });
21
21
 
22
- it('should accept custom options', () => {
23
- const server = createServer('token', 'user123', {
22
+ it('should accept custom options', async () => {
23
+ const server = await createServer('token', 'user123', {
24
24
  name: 'custom-name',
25
25
  version: '2.0.0',
26
26
  });
@@ -113,11 +113,11 @@ async function ensureDatabasesInitialized(): Promise<void> {
113
113
  * });
114
114
  * ```
115
115
  */
116
- export function createServer(
116
+ export async function createServer(
117
117
  accessToken: string,
118
118
  userId: string,
119
119
  options: ServerOptions = {}
120
- ): Server {
120
+ ): Promise<Server> {
121
121
  // Note: accessToken is not used by remember-mcp (self-managed data)
122
122
  // but required by mcp-auth contract. Can be any value including empty string.
123
123
 
@@ -128,11 +128,8 @@ export function createServer(
128
128
  logger.debug('Creating server instance', { userId });
129
129
 
130
130
  // Ensure databases are initialized (happens once globally)
131
- // Note: This is synchronous to match the Server return type
132
- // The actual initialization happens on first tool call
133
- ensureDatabasesInitialized().catch(error => {
134
- logger.error('Failed to initialize databases:', error);
135
- });
131
+ // Initialization must succeed or server creation fails
132
+ await ensureDatabasesInitialized();
136
133
 
137
134
  // Create MCP server
138
135
  const server = new Server(
@@ -512,4 +512,53 @@ describe('weaviate-filters', () => {
512
512
  });
513
513
  });
514
514
  });
515
+
516
+ describe('undefined/null filter handling', () => {
517
+ it('should not create Or operator with empty operands', () => {
518
+ const emptyCollection = {
519
+ filter: {
520
+ byProperty: () => ({
521
+ equal: () => undefined,
522
+ greaterThanOrEqual: () => undefined,
523
+ lessThanOrEqual: () => undefined,
524
+ containsAny: () => undefined,
525
+ }),
526
+ },
527
+ };
528
+
529
+ const result = buildCombinedSearchFilters(emptyCollection);
530
+ expect(result).toBeUndefined();
531
+ });
532
+
533
+ it('should not create And operator with empty operands', () => {
534
+ const emptyCollection = {
535
+ filter: {
536
+ byProperty: () => ({
537
+ equal: () => undefined,
538
+ }),
539
+ },
540
+ };
541
+
542
+ const result = buildMemoryOnlyFilters(emptyCollection);
543
+ expect(result).toBeUndefined();
544
+ });
545
+
546
+ it('should handle mixed valid and undefined filters in OR', () => {
547
+ const result = buildCombinedSearchFilters(mockCollection, {
548
+ types: ['note'],
549
+ });
550
+
551
+ expect(result.operator).toBe('Or');
552
+ expect(result.operands).toHaveLength(2);
553
+ expect(result.operands[0]).toBeDefined();
554
+ expect(result.operands[1]).toBeDefined();
555
+
556
+ if (result.operands[0].operands) {
557
+ expect(result.operands[0].operands.length).toBeGreaterThan(0);
558
+ }
559
+ if (result.operands[1].operands) {
560
+ expect(result.operands[1].operands.length).toBeGreaterThan(0);
561
+ }
562
+ });
563
+ });
515
564
  });
@@ -25,16 +25,17 @@ export function buildCombinedSearchFilters(
25
25
  // Build relationship-specific filters
26
26
  const relationshipFilters = buildDocTypeFilters(collection, 'relationship', filters);
27
27
 
28
+ // Filter out undefined/null values before combining
29
+ const validFilters = [memoryFilters, relationshipFilters].filter(f => f !== undefined && f !== null);
30
+
28
31
  // Combine with OR: search both memories and relationships
29
- if (memoryFilters && relationshipFilters) {
30
- return combineFiltersWithOr([memoryFilters, relationshipFilters]);
31
- } else if (memoryFilters) {
32
- return memoryFilters;
33
- } else if (relationshipFilters) {
34
- return relationshipFilters;
32
+ if (validFilters.length === 0) {
33
+ return undefined;
34
+ } else if (validFilters.length === 1) {
35
+ return validFilters[0];
36
+ } else {
37
+ return combineFiltersWithOr(validFilters);
35
38
  }
36
-
37
- return undefined;
38
39
  }
39
40
 
40
41
  /**
@@ -164,17 +165,21 @@ export function buildRelationshipOnlyFilters(
164
165
  * @returns Combined filter or undefined
165
166
  */
166
167
  function combineFiltersWithAnd(filters: any[]): any {
167
- if (filters.length === 0) {
168
+ // Filter out any undefined/null values
169
+ const validFilters = filters.filter(f => f !== undefined && f !== null);
170
+
171
+ if (validFilters.length === 0) {
168
172
  return undefined;
169
173
  }
170
- if (filters.length === 1) {
171
- return filters[0];
174
+ if (validFilters.length === 1) {
175
+ return validFilters[0];
172
176
  }
173
177
 
174
178
  // Weaviate v3 uses operator/operands structure for combining filters
179
+ // Only create And operator if we have 2+ valid filters
175
180
  return {
176
181
  operator: 'And',
177
- operands: filters
182
+ operands: validFilters
178
183
  };
179
184
  }
180
185
 
@@ -185,17 +190,21 @@ function combineFiltersWithAnd(filters: any[]): any {
185
190
  * @returns Combined filter or undefined
186
191
  */
187
192
  function combineFiltersWithOr(filters: any[]): any {
188
- if (filters.length === 0) {
193
+ // Filter out any undefined/null values
194
+ const validFilters = filters.filter(f => f !== undefined && f !== null);
195
+
196
+ if (validFilters.length === 0) {
189
197
  return undefined;
190
198
  }
191
- if (filters.length === 1) {
192
- return filters[0];
199
+ if (validFilters.length === 1) {
200
+ return validFilters[0];
193
201
  }
194
202
 
195
203
  // Weaviate v3 uses operator/operands structure for combining filters
204
+ // Only create Or operator if we have 2+ valid filters
196
205
  return {
197
206
  operator: 'Or',
198
- operands: filters
207
+ operands: validFilters
199
208
  };
200
209
  }
201
210