@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 +171 -0
- package/agent/tasks/task-remember-mcp-fix-or-operator.md +82 -0
- package/dist/server-factory.d.ts +1 -1
- package/dist/server-factory.js +20 -20
- package/dist/server.js +18 -16
- package/package.json +1 -1
- package/src/server-factory.spec.ts +8 -8
- package/src/server-factory.ts +4 -7
- package/src/utils/weaviate-filters.spec.ts +49 -0
- package/src/utils/weaviate-filters.ts +25 -16
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)
|
package/dist/server-factory.d.ts
CHANGED
|
@@ -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
|
package/dist/server-factory.js
CHANGED
|
@@ -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
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
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
|
-
|
|
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 (
|
|
1490
|
-
return
|
|
1490
|
+
if (validFilters.length === 1) {
|
|
1491
|
+
return validFilters[0];
|
|
1491
1492
|
}
|
|
1492
1493
|
return {
|
|
1493
1494
|
operator: "And",
|
|
1494
|
-
operands:
|
|
1495
|
+
operands: validFilters
|
|
1495
1496
|
};
|
|
1496
1497
|
}
|
|
1497
1498
|
function combineFiltersWithOr(filters) {
|
|
1498
|
-
|
|
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 (
|
|
1502
|
-
return
|
|
1503
|
+
if (validFilters.length === 1) {
|
|
1504
|
+
return validFilters[0];
|
|
1503
1505
|
}
|
|
1504
1506
|
return {
|
|
1505
1507
|
operator: "Or",
|
|
1506
|
-
operands:
|
|
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()
|
|
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
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
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
|
-
|
|
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 (
|
|
1419
|
-
return
|
|
1419
|
+
if (validFilters.length === 1) {
|
|
1420
|
+
return validFilters[0];
|
|
1420
1421
|
}
|
|
1421
1422
|
return {
|
|
1422
1423
|
operator: "And",
|
|
1423
|
-
operands:
|
|
1424
|
+
operands: validFilters
|
|
1424
1425
|
};
|
|
1425
1426
|
}
|
|
1426
1427
|
function combineFiltersWithOr(filters) {
|
|
1427
|
-
|
|
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 (
|
|
1431
|
-
return
|
|
1432
|
+
if (validFilters.length === 1) {
|
|
1433
|
+
return validFilters[0];
|
|
1432
1434
|
}
|
|
1433
1435
|
return {
|
|
1434
1436
|
operator: "Or",
|
|
1435
|
-
operands:
|
|
1437
|
+
operands: validFilters
|
|
1436
1438
|
};
|
|
1437
1439
|
}
|
|
1438
1440
|
|
package/package.json
CHANGED
|
@@ -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(
|
|
15
|
+
await expect(createServer('', 'user123')).resolves.toBeDefined();
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
it('should require userId', () => {
|
|
19
|
-
expect(
|
|
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
|
});
|
package/src/server-factory.ts
CHANGED
|
@@ -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
|
-
//
|
|
132
|
-
|
|
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 (
|
|
30
|
-
return
|
|
31
|
-
} else if (
|
|
32
|
-
return
|
|
33
|
-
} else
|
|
34
|
-
return
|
|
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
|
-
|
|
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 (
|
|
171
|
-
return
|
|
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:
|
|
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
|
-
|
|
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 (
|
|
192
|
-
return
|
|
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:
|
|
207
|
+
operands: validFilters
|
|
199
208
|
};
|
|
200
209
|
}
|
|
201
210
|
|