@prmichaelsen/remember-mcp 0.2.5 → 0.2.7
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/agent/progress.yaml +141 -30
- package/agent/tasks/task-20-fix-weaviate-v3-filters.md +450 -0
- package/dist/server-factory.js +582 -113
- package/dist/server.js +579 -113
- package/dist/services/preferences-database.service.d.ts +22 -0
- package/dist/tools/get-preferences.d.ts +41 -0
- package/dist/tools/search-memory.d.ts +1 -1
- package/dist/tools/set-preference.d.ts +185 -0
- package/dist/types/preferences.d.ts +284 -0
- package/dist/utils/weaviate-filters.d.ts +37 -0
- package/dist/utils/weaviate-filters.spec.d.ts +5 -0
- package/package.json +1 -1
- package/src/server-factory.ts +15 -0
- package/src/server.ts +15 -0
- package/src/services/preferences-database.service.ts +120 -0
- package/src/tools/create-memory.ts +1 -0
- package/src/tools/get-preferences.ts +111 -0
- package/src/tools/query-memory.ts +5 -57
- package/src/tools/search-memory.ts +52 -83
- package/src/tools/set-preference.ts +145 -0
- package/src/types/preferences.ts +280 -0
- package/src/utils/weaviate-filters.spec.ts +515 -0
- package/src/utils/weaviate-filters.ts +207 -0
- package/tsconfig.json +1 -1
package/dist/server.js
CHANGED
|
@@ -1336,22 +1336,125 @@ async function handleCreateMemory(args, userId, context) {
|
|
|
1336
1336
|
}
|
|
1337
1337
|
}
|
|
1338
1338
|
|
|
1339
|
+
// src/utils/weaviate-filters.ts
|
|
1340
|
+
function buildCombinedSearchFilters(collection, filters) {
|
|
1341
|
+
const memoryFilters = buildDocTypeFilters(collection, "memory", filters);
|
|
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;
|
|
1351
|
+
}
|
|
1352
|
+
function buildDocTypeFilters(collection, docType, filters) {
|
|
1353
|
+
const filterList = [];
|
|
1354
|
+
filterList.push(
|
|
1355
|
+
collection.filter.byProperty("doc_type").equal(docType)
|
|
1356
|
+
);
|
|
1357
|
+
if (docType === "memory" && filters?.types && filters.types.length > 0) {
|
|
1358
|
+
if (filters.types.length === 1) {
|
|
1359
|
+
filterList.push(
|
|
1360
|
+
collection.filter.byProperty("type").equal(filters.types[0])
|
|
1361
|
+
);
|
|
1362
|
+
} else {
|
|
1363
|
+
filterList.push(
|
|
1364
|
+
collection.filter.byProperty("type").containsAny(filters.types)
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
if (filters?.weight_min !== void 0) {
|
|
1369
|
+
filterList.push(
|
|
1370
|
+
collection.filter.byProperty("weight").greaterThanOrEqual(filters.weight_min)
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
if (filters?.weight_max !== void 0) {
|
|
1374
|
+
filterList.push(
|
|
1375
|
+
collection.filter.byProperty("weight").lessThanOrEqual(filters.weight_max)
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
if (filters?.trust_min !== void 0) {
|
|
1379
|
+
filterList.push(
|
|
1380
|
+
collection.filter.byProperty("trust").greaterThanOrEqual(filters.trust_min)
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
if (filters?.trust_max !== void 0) {
|
|
1384
|
+
filterList.push(
|
|
1385
|
+
collection.filter.byProperty("trust").lessThanOrEqual(filters.trust_max)
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
if (filters?.date_from) {
|
|
1389
|
+
filterList.push(
|
|
1390
|
+
collection.filter.byProperty("created_at").greaterThanOrEqual(new Date(filters.date_from))
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
if (filters?.date_to) {
|
|
1394
|
+
filterList.push(
|
|
1395
|
+
collection.filter.byProperty("created_at").lessThanOrEqual(new Date(filters.date_to))
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
if (filters?.tags && filters.tags.length > 0) {
|
|
1399
|
+
if (filters.tags.length === 1) {
|
|
1400
|
+
filterList.push(
|
|
1401
|
+
collection.filter.byProperty("tags").containsAny([filters.tags[0]])
|
|
1402
|
+
);
|
|
1403
|
+
} else {
|
|
1404
|
+
filterList.push(
|
|
1405
|
+
collection.filter.byProperty("tags").containsAny(filters.tags)
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
return combineFiltersWithAnd(filterList);
|
|
1410
|
+
}
|
|
1411
|
+
function buildMemoryOnlyFilters(collection, filters) {
|
|
1412
|
+
return buildDocTypeFilters(collection, "memory", filters);
|
|
1413
|
+
}
|
|
1414
|
+
function combineFiltersWithAnd(filters) {
|
|
1415
|
+
if (filters.length === 0) {
|
|
1416
|
+
return void 0;
|
|
1417
|
+
}
|
|
1418
|
+
if (filters.length === 1) {
|
|
1419
|
+
return filters[0];
|
|
1420
|
+
}
|
|
1421
|
+
return {
|
|
1422
|
+
operator: "And",
|
|
1423
|
+
operands: filters
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
function combineFiltersWithOr(filters) {
|
|
1427
|
+
if (filters.length === 0) {
|
|
1428
|
+
return void 0;
|
|
1429
|
+
}
|
|
1430
|
+
if (filters.length === 1) {
|
|
1431
|
+
return filters[0];
|
|
1432
|
+
}
|
|
1433
|
+
return {
|
|
1434
|
+
operator: "Or",
|
|
1435
|
+
operands: filters
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1339
1439
|
// src/tools/search-memory.ts
|
|
1340
1440
|
var searchMemoryTool = {
|
|
1341
1441
|
name: "remember_search_memory",
|
|
1342
|
-
description: `Search memories using hybrid semantic and keyword search.
|
|
1442
|
+
description: `Search memories AND relationships using hybrid semantic and keyword search.
|
|
1443
|
+
|
|
1444
|
+
By default, searches BOTH memories and relationships to provide comprehensive results.
|
|
1445
|
+
Relationships contain valuable context in their observations.
|
|
1343
1446
|
|
|
1344
1447
|
Supports:
|
|
1345
|
-
- Semantic search (meaning-based)
|
|
1448
|
+
- Semantic search (meaning-based) across memory content and relationship observations
|
|
1346
1449
|
- Keyword search (exact matches)
|
|
1347
1450
|
- Hybrid search (balanced with alpha parameter)
|
|
1348
1451
|
- Filtering by type, tags, weight, trust, date range
|
|
1349
|
-
-
|
|
1452
|
+
- Returns both memories and relationships in separate arrays
|
|
1350
1453
|
|
|
1351
1454
|
Examples:
|
|
1352
|
-
- "Find memories about camping trips"
|
|
1353
|
-
- "Search for recipes I saved"
|
|
1354
|
-
- "Show me notes from last week"
|
|
1455
|
+
- "Find memories about camping trips" \u2192 returns memories + relationships about camping
|
|
1456
|
+
- "Search for recipes I saved" \u2192 returns recipe memories + related relationships
|
|
1457
|
+
- "Show me notes from last week" \u2192 returns notes + any relationships created that week
|
|
1355
1458
|
`,
|
|
1356
1459
|
inputSchema: {
|
|
1357
1460
|
type: "object",
|
|
@@ -1414,8 +1517,8 @@ var searchMemoryTool = {
|
|
|
1414
1517
|
},
|
|
1415
1518
|
include_relationships: {
|
|
1416
1519
|
type: "boolean",
|
|
1417
|
-
description: "Include relationships in results. Default:
|
|
1418
|
-
default:
|
|
1520
|
+
description: "Include relationships in results. Default: true (searches both memories and relationships)",
|
|
1521
|
+
default: true
|
|
1419
1522
|
}
|
|
1420
1523
|
},
|
|
1421
1524
|
required: ["query"]
|
|
@@ -1423,83 +1526,53 @@ var searchMemoryTool = {
|
|
|
1423
1526
|
};
|
|
1424
1527
|
async function handleSearchMemory(args, userId) {
|
|
1425
1528
|
try {
|
|
1426
|
-
|
|
1529
|
+
const includeRelationships = args.include_relationships !== false;
|
|
1530
|
+
logger.info("Searching memories and relationships", {
|
|
1531
|
+
userId,
|
|
1532
|
+
query: args.query,
|
|
1533
|
+
includeRelationships
|
|
1534
|
+
});
|
|
1427
1535
|
const collection = getMemoryCollection(userId);
|
|
1428
1536
|
const alpha = args.alpha ?? 0.7;
|
|
1429
1537
|
const limit = args.limit ?? 10;
|
|
1430
1538
|
const offset = args.offset ?? 0;
|
|
1431
|
-
const
|
|
1432
|
-
{
|
|
1433
|
-
path: "doc_type",
|
|
1434
|
-
operator: "Equal",
|
|
1435
|
-
valueText: "memory"
|
|
1436
|
-
}
|
|
1437
|
-
];
|
|
1438
|
-
if (args.filters?.types && args.filters.types.length > 0) {
|
|
1439
|
-
whereFilters.push({
|
|
1440
|
-
path: "type",
|
|
1441
|
-
operator: "ContainsAny",
|
|
1442
|
-
valueTextArray: args.filters.types
|
|
1443
|
-
});
|
|
1444
|
-
}
|
|
1445
|
-
if (args.filters?.weight_min !== void 0) {
|
|
1446
|
-
whereFilters.push({
|
|
1447
|
-
path: "weight",
|
|
1448
|
-
operator: "GreaterThanEqual",
|
|
1449
|
-
valueNumber: args.filters.weight_min
|
|
1450
|
-
});
|
|
1451
|
-
}
|
|
1452
|
-
if (args.filters?.trust_min !== void 0) {
|
|
1453
|
-
whereFilters.push({
|
|
1454
|
-
path: "trust",
|
|
1455
|
-
operator: "GreaterThanEqual",
|
|
1456
|
-
valueNumber: args.filters.trust_min
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1459
|
-
if (args.filters?.date_from) {
|
|
1460
|
-
whereFilters.push({
|
|
1461
|
-
path: "created_at",
|
|
1462
|
-
operator: "GreaterThanEqual",
|
|
1463
|
-
valueDate: new Date(args.filters.date_from)
|
|
1464
|
-
});
|
|
1465
|
-
}
|
|
1466
|
-
if (args.filters?.date_to) {
|
|
1467
|
-
whereFilters.push({
|
|
1468
|
-
path: "created_at",
|
|
1469
|
-
operator: "LessThanEqual",
|
|
1470
|
-
valueDate: new Date(args.filters.date_to)
|
|
1471
|
-
});
|
|
1472
|
-
}
|
|
1539
|
+
const filters = includeRelationships ? buildCombinedSearchFilters(collection, args.filters) : buildMemoryOnlyFilters(collection, args.filters);
|
|
1473
1540
|
const searchOptions = {
|
|
1474
1541
|
alpha,
|
|
1475
1542
|
limit: limit + offset
|
|
1476
1543
|
// Get extra for offset
|
|
1477
1544
|
};
|
|
1478
|
-
if (
|
|
1479
|
-
searchOptions.filters =
|
|
1480
|
-
operator: "And",
|
|
1481
|
-
operands: whereFilters
|
|
1482
|
-
} : whereFilters[0];
|
|
1545
|
+
if (filters) {
|
|
1546
|
+
searchOptions.filters = filters;
|
|
1483
1547
|
}
|
|
1484
1548
|
const results = await collection.query.hybrid(args.query, searchOptions);
|
|
1485
1549
|
const paginatedResults = results.objects.slice(offset);
|
|
1486
|
-
const memories =
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1550
|
+
const memories = [];
|
|
1551
|
+
const relationships = [];
|
|
1552
|
+
for (const obj of paginatedResults) {
|
|
1553
|
+
const doc = {
|
|
1554
|
+
id: obj.uuid,
|
|
1555
|
+
...obj.properties
|
|
1556
|
+
};
|
|
1557
|
+
if (doc.doc_type === "memory") {
|
|
1558
|
+
memories.push(doc);
|
|
1559
|
+
} else if (doc.doc_type === "relationship") {
|
|
1560
|
+
relationships.push(doc);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1490
1563
|
const searchResult = {
|
|
1491
1564
|
memories,
|
|
1492
|
-
|
|
1565
|
+
relationships: includeRelationships ? relationships : void 0,
|
|
1566
|
+
total: memories.length + relationships.length,
|
|
1493
1567
|
offset,
|
|
1494
1568
|
limit
|
|
1495
1569
|
};
|
|
1496
|
-
if (args.include_relationships) {
|
|
1497
|
-
searchResult.relationships = [];
|
|
1498
|
-
}
|
|
1499
1570
|
logger.info("Search completed", {
|
|
1500
1571
|
userId,
|
|
1501
1572
|
query: args.query,
|
|
1502
|
-
|
|
1573
|
+
memoriesFound: memories.length,
|
|
1574
|
+
relationshipsFound: relationships.length,
|
|
1575
|
+
total: searchResult.total
|
|
1503
1576
|
});
|
|
1504
1577
|
return JSON.stringify(searchResult, null, 2);
|
|
1505
1578
|
} catch (error) {
|
|
@@ -1959,59 +2032,15 @@ async function handleQueryMemory(args, userId) {
|
|
|
1959
2032
|
const minRelevance = args.min_relevance ?? 0.6;
|
|
1960
2033
|
const includeContext = args.include_context ?? true;
|
|
1961
2034
|
const format = args.format ?? "detailed";
|
|
1962
|
-
const
|
|
1963
|
-
{
|
|
1964
|
-
path: "doc_type",
|
|
1965
|
-
operator: "Equal",
|
|
1966
|
-
valueText: "memory"
|
|
1967
|
-
}
|
|
1968
|
-
];
|
|
1969
|
-
if (args.filters?.types && args.filters.types.length > 0) {
|
|
1970
|
-
whereFilters.push({
|
|
1971
|
-
path: "type",
|
|
1972
|
-
operator: "ContainsAny",
|
|
1973
|
-
valueTextArray: args.filters.types
|
|
1974
|
-
});
|
|
1975
|
-
}
|
|
1976
|
-
if (args.filters?.weight_min !== void 0) {
|
|
1977
|
-
whereFilters.push({
|
|
1978
|
-
path: "weight",
|
|
1979
|
-
operator: "GreaterThanEqual",
|
|
1980
|
-
valueNumber: args.filters.weight_min
|
|
1981
|
-
});
|
|
1982
|
-
}
|
|
1983
|
-
if (args.filters?.trust_min !== void 0) {
|
|
1984
|
-
whereFilters.push({
|
|
1985
|
-
path: "trust",
|
|
1986
|
-
operator: "GreaterThanEqual",
|
|
1987
|
-
valueNumber: args.filters.trust_min
|
|
1988
|
-
});
|
|
1989
|
-
}
|
|
1990
|
-
if (args.filters?.date_from) {
|
|
1991
|
-
whereFilters.push({
|
|
1992
|
-
path: "created_at",
|
|
1993
|
-
operator: "GreaterThanEqual",
|
|
1994
|
-
valueDate: new Date(args.filters.date_from)
|
|
1995
|
-
});
|
|
1996
|
-
}
|
|
1997
|
-
if (args.filters?.date_to) {
|
|
1998
|
-
whereFilters.push({
|
|
1999
|
-
path: "created_at",
|
|
2000
|
-
operator: "LessThanEqual",
|
|
2001
|
-
valueDate: new Date(args.filters.date_to)
|
|
2002
|
-
});
|
|
2003
|
-
}
|
|
2035
|
+
const filters = buildCombinedSearchFilters(collection, args.filters);
|
|
2004
2036
|
const searchOptions = {
|
|
2005
2037
|
limit,
|
|
2006
2038
|
distance: 1 - minRelevance,
|
|
2007
2039
|
// Convert relevance to distance
|
|
2008
2040
|
returnMetadata: ["distance"]
|
|
2009
2041
|
};
|
|
2010
|
-
if (
|
|
2011
|
-
searchOptions.filters =
|
|
2012
|
-
operator: "And",
|
|
2013
|
-
operands: whereFilters
|
|
2014
|
-
} : whereFilters[0];
|
|
2042
|
+
if (filters) {
|
|
2043
|
+
searchOptions.filters = filters;
|
|
2015
2044
|
}
|
|
2016
2045
|
const results = await collection.query.nearText(args.query, searchOptions);
|
|
2017
2046
|
const relevantMemories = results.objects.map((obj) => {
|
|
@@ -2627,6 +2656,434 @@ async function handleDeleteRelationship(args, userId) {
|
|
|
2627
2656
|
}
|
|
2628
2657
|
}
|
|
2629
2658
|
|
|
2659
|
+
// src/firestore/paths.ts
|
|
2660
|
+
var APP_NAME = "remember-mcp";
|
|
2661
|
+
function getBasePrefix() {
|
|
2662
|
+
const environment = process.env.ENVIRONMENT;
|
|
2663
|
+
if (environment && environment !== "production" && environment !== "prod") {
|
|
2664
|
+
return `${environment}.${APP_NAME}`;
|
|
2665
|
+
}
|
|
2666
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
2667
|
+
if (isDevelopment) {
|
|
2668
|
+
const customPrefix = process.env.DB_PREFIX;
|
|
2669
|
+
if (customPrefix) {
|
|
2670
|
+
return customPrefix;
|
|
2671
|
+
}
|
|
2672
|
+
return `e0.${APP_NAME}`;
|
|
2673
|
+
}
|
|
2674
|
+
return APP_NAME;
|
|
2675
|
+
}
|
|
2676
|
+
var BASE = getBasePrefix();
|
|
2677
|
+
function getUserPreferencesPath(userId) {
|
|
2678
|
+
return `${BASE}.users/${userId}/preferences`;
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
// src/types/preferences.ts
|
|
2682
|
+
var DEFAULT_PREFERENCES = {
|
|
2683
|
+
templates: {
|
|
2684
|
+
auto_suggest: true,
|
|
2685
|
+
suggestion_threshold: 0.6,
|
|
2686
|
+
max_suggestions: 3,
|
|
2687
|
+
show_preview: true,
|
|
2688
|
+
remember_choice: true,
|
|
2689
|
+
suppressed_categories: [],
|
|
2690
|
+
suppressed_templates: [],
|
|
2691
|
+
always_suggest: []
|
|
2692
|
+
},
|
|
2693
|
+
search: {
|
|
2694
|
+
default_limit: 10,
|
|
2695
|
+
include_low_trust: false,
|
|
2696
|
+
weight_by_access: true,
|
|
2697
|
+
weight_by_recency: true,
|
|
2698
|
+
default_alpha: 0.7
|
|
2699
|
+
},
|
|
2700
|
+
location: {
|
|
2701
|
+
auto_capture: true,
|
|
2702
|
+
precision: "approximate",
|
|
2703
|
+
share_with_memories: true
|
|
2704
|
+
},
|
|
2705
|
+
privacy: {
|
|
2706
|
+
default_trust_level: 0.5,
|
|
2707
|
+
allow_cross_user_access: false,
|
|
2708
|
+
auto_approve_requests: false,
|
|
2709
|
+
audit_logging: true
|
|
2710
|
+
},
|
|
2711
|
+
notifications: {
|
|
2712
|
+
trust_violations: true,
|
|
2713
|
+
access_requests: true,
|
|
2714
|
+
memory_reminders: false,
|
|
2715
|
+
relationship_suggestions: true
|
|
2716
|
+
},
|
|
2717
|
+
display: {
|
|
2718
|
+
date_format: "MM/DD/YYYY",
|
|
2719
|
+
time_format: "12h",
|
|
2720
|
+
timezone: "America/Los_Angeles",
|
|
2721
|
+
language: "en"
|
|
2722
|
+
}
|
|
2723
|
+
};
|
|
2724
|
+
var PREFERENCE_CATEGORIES = [
|
|
2725
|
+
"templates",
|
|
2726
|
+
"search",
|
|
2727
|
+
"location",
|
|
2728
|
+
"privacy",
|
|
2729
|
+
"notifications",
|
|
2730
|
+
"display"
|
|
2731
|
+
];
|
|
2732
|
+
var PREFERENCE_DESCRIPTIONS = {
|
|
2733
|
+
templates: {
|
|
2734
|
+
auto_suggest: "Automatically suggest templates when creating memories (default: true)",
|
|
2735
|
+
suggestion_threshold: "Minimum confidence to show template suggestion, 0-1 (default: 0.6)",
|
|
2736
|
+
max_suggestions: "Maximum number of templates to suggest, 1-5 (default: 3)",
|
|
2737
|
+
show_preview: "Show template preview in suggestion (default: true)",
|
|
2738
|
+
remember_choice: `Remember "don't suggest for this type" choices (default: true)`,
|
|
2739
|
+
suppressed_categories: "Categories to never suggest templates for (default: [])",
|
|
2740
|
+
suppressed_templates: "Specific templates to never suggest (default: [])",
|
|
2741
|
+
always_suggest: "Templates to always suggest regardless of confidence (default: [])"
|
|
2742
|
+
},
|
|
2743
|
+
search: {
|
|
2744
|
+
default_limit: "Default number of search results to return, 1-100 (default: 10)",
|
|
2745
|
+
include_low_trust: "Include low-trust memories in search results (default: false)",
|
|
2746
|
+
weight_by_access: "Use access count in search ranking (default: true)",
|
|
2747
|
+
weight_by_recency: "Use recency in search ranking (default: true)",
|
|
2748
|
+
default_alpha: "Default hybrid search alpha (0=keyword, 1=semantic, default: 0.7)"
|
|
2749
|
+
},
|
|
2750
|
+
location: {
|
|
2751
|
+
auto_capture: "Automatically capture location for memories (default: true)",
|
|
2752
|
+
precision: "Location precision level: exact, approximate, city, none (default: approximate)",
|
|
2753
|
+
share_with_memories: "Include location data in memories (default: true)"
|
|
2754
|
+
},
|
|
2755
|
+
privacy: {
|
|
2756
|
+
default_trust_level: "Default trust level for new memories, 0-1 (default: 0.5)",
|
|
2757
|
+
allow_cross_user_access: "Allow other users to request access to memories (default: false)",
|
|
2758
|
+
auto_approve_requests: "Automatically approve access requests (default: false)",
|
|
2759
|
+
audit_logging: "Enable audit logging for preference changes (default: true)"
|
|
2760
|
+
},
|
|
2761
|
+
notifications: {
|
|
2762
|
+
trust_violations: "Notify on trust violations (default: true)",
|
|
2763
|
+
access_requests: "Notify on access requests from other users (default: true)",
|
|
2764
|
+
memory_reminders: "Send reminders about important memories (default: false)",
|
|
2765
|
+
relationship_suggestions: "Suggest new relationships between memories (default: true)"
|
|
2766
|
+
},
|
|
2767
|
+
display: {
|
|
2768
|
+
date_format: "Date format string (default: MM/DD/YYYY)",
|
|
2769
|
+
time_format: "Time format: 12h or 24h (default: 12h)",
|
|
2770
|
+
timezone: "Timezone identifier (default: America/Los_Angeles)",
|
|
2771
|
+
language: "Language code (default: en)"
|
|
2772
|
+
}
|
|
2773
|
+
};
|
|
2774
|
+
function getPreferenceDescription() {
|
|
2775
|
+
const categories = Object.entries(PREFERENCE_DESCRIPTIONS).map(([category, fields]) => {
|
|
2776
|
+
const fieldList = Object.entries(fields).map(([field, desc]) => ` - ${field}: ${desc}`).join("\n");
|
|
2777
|
+
return `${category}:
|
|
2778
|
+
${fieldList}`;
|
|
2779
|
+
}).join("\n\n");
|
|
2780
|
+
return `User preferences control system behavior. Available preference categories and fields:
|
|
2781
|
+
|
|
2782
|
+
${categories}`;
|
|
2783
|
+
}
|
|
2784
|
+
function getPreferencesSchema() {
|
|
2785
|
+
return {
|
|
2786
|
+
type: "object",
|
|
2787
|
+
properties: {
|
|
2788
|
+
templates: {
|
|
2789
|
+
type: "object",
|
|
2790
|
+
description: "Template suggestion preferences",
|
|
2791
|
+
properties: {
|
|
2792
|
+
auto_suggest: { type: "boolean" },
|
|
2793
|
+
suggestion_threshold: { type: "number", minimum: 0, maximum: 1 },
|
|
2794
|
+
max_suggestions: { type: "number", minimum: 1, maximum: 5 },
|
|
2795
|
+
show_preview: { type: "boolean" },
|
|
2796
|
+
remember_choice: { type: "boolean" },
|
|
2797
|
+
suppressed_categories: { type: "array", items: { type: "string" } },
|
|
2798
|
+
suppressed_templates: { type: "array", items: { type: "string" } },
|
|
2799
|
+
always_suggest: { type: "array", items: { type: "string" } }
|
|
2800
|
+
}
|
|
2801
|
+
},
|
|
2802
|
+
search: {
|
|
2803
|
+
type: "object",
|
|
2804
|
+
description: "Search behavior preferences",
|
|
2805
|
+
properties: {
|
|
2806
|
+
default_limit: { type: "number", minimum: 1, maximum: 100 },
|
|
2807
|
+
include_low_trust: { type: "boolean" },
|
|
2808
|
+
weight_by_access: { type: "boolean" },
|
|
2809
|
+
weight_by_recency: { type: "boolean" },
|
|
2810
|
+
default_alpha: { type: "number", minimum: 0, maximum: 1 }
|
|
2811
|
+
}
|
|
2812
|
+
},
|
|
2813
|
+
location: {
|
|
2814
|
+
type: "object",
|
|
2815
|
+
description: "Location capture preferences",
|
|
2816
|
+
properties: {
|
|
2817
|
+
auto_capture: { type: "boolean" },
|
|
2818
|
+
precision: { type: "string", enum: ["exact", "approximate", "city", "none"] },
|
|
2819
|
+
share_with_memories: { type: "boolean" }
|
|
2820
|
+
}
|
|
2821
|
+
},
|
|
2822
|
+
privacy: {
|
|
2823
|
+
type: "object",
|
|
2824
|
+
description: "Privacy and trust preferences",
|
|
2825
|
+
properties: {
|
|
2826
|
+
default_trust_level: { type: "number", minimum: 0, maximum: 1 },
|
|
2827
|
+
allow_cross_user_access: { type: "boolean" },
|
|
2828
|
+
auto_approve_requests: { type: "boolean" },
|
|
2829
|
+
audit_logging: { type: "boolean" }
|
|
2830
|
+
}
|
|
2831
|
+
},
|
|
2832
|
+
notifications: {
|
|
2833
|
+
type: "object",
|
|
2834
|
+
description: "Notification preferences",
|
|
2835
|
+
properties: {
|
|
2836
|
+
trust_violations: { type: "boolean" },
|
|
2837
|
+
access_requests: { type: "boolean" },
|
|
2838
|
+
memory_reminders: { type: "boolean" },
|
|
2839
|
+
relationship_suggestions: { type: "boolean" }
|
|
2840
|
+
}
|
|
2841
|
+
},
|
|
2842
|
+
display: {
|
|
2843
|
+
type: "object",
|
|
2844
|
+
description: "Display format preferences",
|
|
2845
|
+
properties: {
|
|
2846
|
+
date_format: { type: "string" },
|
|
2847
|
+
time_format: { type: "string", enum: ["12h", "24h"] },
|
|
2848
|
+
timezone: { type: "string" },
|
|
2849
|
+
language: { type: "string" }
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
// src/services/preferences-database.service.ts
|
|
2857
|
+
var PreferencesDatabaseService = class {
|
|
2858
|
+
/**
|
|
2859
|
+
* Get user preferences
|
|
2860
|
+
* Returns defaults if preferences don't exist
|
|
2861
|
+
*/
|
|
2862
|
+
static async getPreferences(userId) {
|
|
2863
|
+
try {
|
|
2864
|
+
const pathParts = getUserPreferencesPath(userId).split("/");
|
|
2865
|
+
const docId = pathParts.pop();
|
|
2866
|
+
const collectionPath = pathParts.join("/");
|
|
2867
|
+
const doc = await getDocument(collectionPath, docId);
|
|
2868
|
+
if (!doc) {
|
|
2869
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2870
|
+
return {
|
|
2871
|
+
user_id: userId,
|
|
2872
|
+
...DEFAULT_PREFERENCES,
|
|
2873
|
+
created_at: now,
|
|
2874
|
+
updated_at: now
|
|
2875
|
+
};
|
|
2876
|
+
}
|
|
2877
|
+
return doc;
|
|
2878
|
+
} catch (error) {
|
|
2879
|
+
logger.error("Failed to get preferences:", error);
|
|
2880
|
+
throw new Error(`Failed to get preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
/**
|
|
2884
|
+
* Update user preferences (partial update with merge)
|
|
2885
|
+
* Creates with defaults if preferences don't exist
|
|
2886
|
+
*/
|
|
2887
|
+
static async updatePreferences(userId, updates) {
|
|
2888
|
+
try {
|
|
2889
|
+
const pathParts = getUserPreferencesPath(userId).split("/");
|
|
2890
|
+
const docId = pathParts.pop();
|
|
2891
|
+
const collectionPath = pathParts.join("/");
|
|
2892
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2893
|
+
const doc = await getDocument(collectionPath, docId);
|
|
2894
|
+
if (!doc) {
|
|
2895
|
+
const newPrefs = {
|
|
2896
|
+
user_id: userId,
|
|
2897
|
+
...DEFAULT_PREFERENCES,
|
|
2898
|
+
...updates,
|
|
2899
|
+
created_at: now,
|
|
2900
|
+
updated_at: now
|
|
2901
|
+
};
|
|
2902
|
+
await setDocument(collectionPath, docId, newPrefs);
|
|
2903
|
+
logger.info("Preferences created with defaults", { userId });
|
|
2904
|
+
return newPrefs;
|
|
2905
|
+
}
|
|
2906
|
+
const updateData = {
|
|
2907
|
+
...updates,
|
|
2908
|
+
updated_at: now
|
|
2909
|
+
};
|
|
2910
|
+
await setDocument(collectionPath, docId, updateData, { merge: true });
|
|
2911
|
+
logger.info("Preferences updated", { userId });
|
|
2912
|
+
const updatedDoc = await getDocument(collectionPath, docId);
|
|
2913
|
+
return updatedDoc;
|
|
2914
|
+
} catch (error) {
|
|
2915
|
+
logger.error("Failed to update preferences:", error);
|
|
2916
|
+
throw new Error(`Failed to update preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
/**
|
|
2920
|
+
* Create preferences with defaults
|
|
2921
|
+
*/
|
|
2922
|
+
static async createPreferences(userId) {
|
|
2923
|
+
try {
|
|
2924
|
+
const pathParts = getUserPreferencesPath(userId).split("/");
|
|
2925
|
+
const docId = pathParts.pop();
|
|
2926
|
+
const collectionPath = pathParts.join("/");
|
|
2927
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2928
|
+
const preferences = {
|
|
2929
|
+
user_id: userId,
|
|
2930
|
+
...DEFAULT_PREFERENCES,
|
|
2931
|
+
created_at: now,
|
|
2932
|
+
updated_at: now
|
|
2933
|
+
};
|
|
2934
|
+
await setDocument(collectionPath, docId, preferences);
|
|
2935
|
+
logger.info("Preferences created", { userId });
|
|
2936
|
+
return preferences;
|
|
2937
|
+
} catch (error) {
|
|
2938
|
+
logger.error("Failed to create preferences:", error);
|
|
2939
|
+
throw new Error(`Failed to create preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
};
|
|
2943
|
+
|
|
2944
|
+
// src/tools/set-preference.ts
|
|
2945
|
+
var setPreferenceTool = {
|
|
2946
|
+
name: "remember_set_preference",
|
|
2947
|
+
description: `Update user preferences for system behavior through natural conversation.
|
|
2948
|
+
|
|
2949
|
+
This tool allows bulk updates to user preferences. Provide a partial preferences object
|
|
2950
|
+
with only the fields you want to update. All updates are merged with existing preferences.
|
|
2951
|
+
|
|
2952
|
+
${getPreferenceDescription()}
|
|
2953
|
+
|
|
2954
|
+
Common examples:
|
|
2955
|
+
- Disable template suggestions: { templates: { auto_suggest: false } }
|
|
2956
|
+
- Change search defaults: { search: { default_limit: 20, default_alpha: 0.8 } }
|
|
2957
|
+
- Update privacy: { privacy: { default_trust_level: 0.8 } }
|
|
2958
|
+
- Suppress categories: { templates: { suppressed_categories: ["work", "personal"] } }
|
|
2959
|
+
`,
|
|
2960
|
+
inputSchema: {
|
|
2961
|
+
type: "object",
|
|
2962
|
+
properties: {
|
|
2963
|
+
preferences: {
|
|
2964
|
+
...getPreferencesSchema(),
|
|
2965
|
+
description: "Partial preferences object with fields to update"
|
|
2966
|
+
}
|
|
2967
|
+
},
|
|
2968
|
+
required: ["preferences"]
|
|
2969
|
+
}
|
|
2970
|
+
};
|
|
2971
|
+
function formatPreferenceChangeMessage(updates) {
|
|
2972
|
+
const changes = [];
|
|
2973
|
+
if (updates.templates) {
|
|
2974
|
+
if (updates.templates.auto_suggest !== void 0) {
|
|
2975
|
+
changes.push(
|
|
2976
|
+
updates.templates.auto_suggest ? "Template suggestions enabled" : "Template suggestions disabled"
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
if (updates.templates.suppressed_categories) {
|
|
2980
|
+
changes.push(`Suppressed categories: ${updates.templates.suppressed_categories.join(", ")}`);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
if (updates.search) {
|
|
2984
|
+
if (updates.search.default_limit !== void 0) {
|
|
2985
|
+
changes.push(`Search limit set to ${updates.search.default_limit}`);
|
|
2986
|
+
}
|
|
2987
|
+
if (updates.search.default_alpha !== void 0) {
|
|
2988
|
+
changes.push(`Search alpha set to ${updates.search.default_alpha}`);
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
if (updates.privacy) {
|
|
2992
|
+
if (updates.privacy.default_trust_level !== void 0) {
|
|
2993
|
+
changes.push(`Default trust level set to ${updates.privacy.default_trust_level}`);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
if (updates.location) {
|
|
2997
|
+
if (updates.location.auto_capture !== void 0) {
|
|
2998
|
+
changes.push(
|
|
2999
|
+
updates.location.auto_capture ? "Location auto-capture enabled" : "Location auto-capture disabled"
|
|
3000
|
+
);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
if (changes.length === 0) {
|
|
3004
|
+
return "Preferences updated successfully";
|
|
3005
|
+
}
|
|
3006
|
+
return `Preferences updated: ${changes.join(", ")}`;
|
|
3007
|
+
}
|
|
3008
|
+
async function handleSetPreference(args, userId) {
|
|
3009
|
+
try {
|
|
3010
|
+
const { preferences } = args;
|
|
3011
|
+
logger.info("Setting preferences", { userId, updates: Object.keys(preferences) });
|
|
3012
|
+
const updatedPreferences = await PreferencesDatabaseService.updatePreferences(
|
|
3013
|
+
userId,
|
|
3014
|
+
preferences
|
|
3015
|
+
);
|
|
3016
|
+
const message = formatPreferenceChangeMessage(preferences);
|
|
3017
|
+
const result = {
|
|
3018
|
+
success: true,
|
|
3019
|
+
updated_preferences: updatedPreferences,
|
|
3020
|
+
message
|
|
3021
|
+
};
|
|
3022
|
+
logger.info("Preferences set successfully", { userId });
|
|
3023
|
+
return JSON.stringify(result, null, 2);
|
|
3024
|
+
} catch (error) {
|
|
3025
|
+
logger.error("Failed to set preferences:", error);
|
|
3026
|
+
throw new Error(`Failed to set preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
// src/tools/get-preferences.ts
|
|
3031
|
+
var getPreferencesTool = {
|
|
3032
|
+
name: "remember_get_preferences",
|
|
3033
|
+
description: `Get current user preferences.
|
|
3034
|
+
|
|
3035
|
+
Use this to understand user's current settings before suggesting changes
|
|
3036
|
+
or to explain why system is behaving a certain way.
|
|
3037
|
+
|
|
3038
|
+
Returns the complete preferences object or filtered by category.
|
|
3039
|
+
If preferences don't exist, returns defaults.
|
|
3040
|
+
|
|
3041
|
+
${getPreferenceDescription()}
|
|
3042
|
+
`,
|
|
3043
|
+
inputSchema: {
|
|
3044
|
+
type: "object",
|
|
3045
|
+
properties: {
|
|
3046
|
+
category: {
|
|
3047
|
+
type: "string",
|
|
3048
|
+
enum: ["templates", "search", "location", "privacy", "notifications", "display"],
|
|
3049
|
+
description: "Optional category to filter preferences"
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
async function handleGetPreferences(args, userId) {
|
|
3055
|
+
try {
|
|
3056
|
+
const { category } = args;
|
|
3057
|
+
logger.info("Getting preferences", { userId, category });
|
|
3058
|
+
const preferences = await PreferencesDatabaseService.getPreferences(userId);
|
|
3059
|
+
const isDefault = !preferences.created_at || preferences.created_at === preferences.updated_at;
|
|
3060
|
+
let result;
|
|
3061
|
+
let message;
|
|
3062
|
+
if (category) {
|
|
3063
|
+
if (!PREFERENCE_CATEGORIES.includes(category)) {
|
|
3064
|
+
throw new Error(`Invalid category: ${category}. Valid categories: ${PREFERENCE_CATEGORIES.join(", ")}`);
|
|
3065
|
+
}
|
|
3066
|
+
result = {
|
|
3067
|
+
[category]: preferences[category]
|
|
3068
|
+
};
|
|
3069
|
+
message = isDefault ? `Showing default ${category} preferences (user has not customized preferences yet).` : `Showing current ${category} preferences.`;
|
|
3070
|
+
} else {
|
|
3071
|
+
result = preferences;
|
|
3072
|
+
message = isDefault ? "Showing default preferences (user has not customized preferences yet)." : "Showing current user preferences.";
|
|
3073
|
+
}
|
|
3074
|
+
const response = {
|
|
3075
|
+
preferences: result,
|
|
3076
|
+
is_default: isDefault,
|
|
3077
|
+
message
|
|
3078
|
+
};
|
|
3079
|
+
logger.info("Preferences retrieved successfully", { userId, category, isDefault });
|
|
3080
|
+
return JSON.stringify(response, null, 2);
|
|
3081
|
+
} catch (error) {
|
|
3082
|
+
logger.error("Failed to get preferences:", error);
|
|
3083
|
+
throw new Error(`Failed to get preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
|
|
2630
3087
|
// src/server.ts
|
|
2631
3088
|
async function initServer() {
|
|
2632
3089
|
logger.info("Initializing remember-mcp server...");
|
|
@@ -2670,7 +3127,10 @@ function registerHandlers(server) {
|
|
|
2670
3127
|
createRelationshipTool,
|
|
2671
3128
|
updateRelationshipTool,
|
|
2672
3129
|
searchRelationshipTool,
|
|
2673
|
-
deleteRelationshipTool
|
|
3130
|
+
deleteRelationshipTool,
|
|
3131
|
+
// Preference tools
|
|
3132
|
+
setPreferenceTool,
|
|
3133
|
+
getPreferencesTool
|
|
2674
3134
|
]
|
|
2675
3135
|
};
|
|
2676
3136
|
});
|
|
@@ -2710,6 +3170,12 @@ function registerHandlers(server) {
|
|
|
2710
3170
|
case "remember_delete_relationship":
|
|
2711
3171
|
result = await handleDeleteRelationship(args, userId);
|
|
2712
3172
|
break;
|
|
3173
|
+
case "remember_set_preference":
|
|
3174
|
+
result = await handleSetPreference(args, userId);
|
|
3175
|
+
break;
|
|
3176
|
+
case "remember_get_preferences":
|
|
3177
|
+
result = await handleGetPreferences(args, userId);
|
|
3178
|
+
break;
|
|
2713
3179
|
default:
|
|
2714
3180
|
throw new McpError(
|
|
2715
3181
|
ErrorCode.MethodNotFound,
|