@prmichaelsen/remember-mcp 0.2.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +145 -0
- package/agent/progress.yaml +141 -30
- package/agent/tasks/task-20-fix-weaviate-v3-filters.md +450 -0
- package/dist/server-factory.d.ts +1 -1
- package/dist/server-factory.js +584 -117
- 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.spec.ts +8 -8
- package/src/server-factory.ts +19 -7
- 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-factory.js
CHANGED
|
@@ -1407,22 +1407,125 @@ async function handleCreateMemory(args, userId, context) {
|
|
|
1407
1407
|
}
|
|
1408
1408
|
}
|
|
1409
1409
|
|
|
1410
|
+
// src/utils/weaviate-filters.ts
|
|
1411
|
+
function buildCombinedSearchFilters(collection, filters) {
|
|
1412
|
+
const memoryFilters = buildDocTypeFilters(collection, "memory", filters);
|
|
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;
|
|
1422
|
+
}
|
|
1423
|
+
function buildDocTypeFilters(collection, docType, filters) {
|
|
1424
|
+
const filterList = [];
|
|
1425
|
+
filterList.push(
|
|
1426
|
+
collection.filter.byProperty("doc_type").equal(docType)
|
|
1427
|
+
);
|
|
1428
|
+
if (docType === "memory" && filters?.types && filters.types.length > 0) {
|
|
1429
|
+
if (filters.types.length === 1) {
|
|
1430
|
+
filterList.push(
|
|
1431
|
+
collection.filter.byProperty("type").equal(filters.types[0])
|
|
1432
|
+
);
|
|
1433
|
+
} else {
|
|
1434
|
+
filterList.push(
|
|
1435
|
+
collection.filter.byProperty("type").containsAny(filters.types)
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
if (filters?.weight_min !== void 0) {
|
|
1440
|
+
filterList.push(
|
|
1441
|
+
collection.filter.byProperty("weight").greaterThanOrEqual(filters.weight_min)
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
if (filters?.weight_max !== void 0) {
|
|
1445
|
+
filterList.push(
|
|
1446
|
+
collection.filter.byProperty("weight").lessThanOrEqual(filters.weight_max)
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
if (filters?.trust_min !== void 0) {
|
|
1450
|
+
filterList.push(
|
|
1451
|
+
collection.filter.byProperty("trust").greaterThanOrEqual(filters.trust_min)
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1454
|
+
if (filters?.trust_max !== void 0) {
|
|
1455
|
+
filterList.push(
|
|
1456
|
+
collection.filter.byProperty("trust").lessThanOrEqual(filters.trust_max)
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
if (filters?.date_from) {
|
|
1460
|
+
filterList.push(
|
|
1461
|
+
collection.filter.byProperty("created_at").greaterThanOrEqual(new Date(filters.date_from))
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
if (filters?.date_to) {
|
|
1465
|
+
filterList.push(
|
|
1466
|
+
collection.filter.byProperty("created_at").lessThanOrEqual(new Date(filters.date_to))
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
if (filters?.tags && filters.tags.length > 0) {
|
|
1470
|
+
if (filters.tags.length === 1) {
|
|
1471
|
+
filterList.push(
|
|
1472
|
+
collection.filter.byProperty("tags").containsAny([filters.tags[0]])
|
|
1473
|
+
);
|
|
1474
|
+
} else {
|
|
1475
|
+
filterList.push(
|
|
1476
|
+
collection.filter.byProperty("tags").containsAny(filters.tags)
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
return combineFiltersWithAnd(filterList);
|
|
1481
|
+
}
|
|
1482
|
+
function buildMemoryOnlyFilters(collection, filters) {
|
|
1483
|
+
return buildDocTypeFilters(collection, "memory", filters);
|
|
1484
|
+
}
|
|
1485
|
+
function combineFiltersWithAnd(filters) {
|
|
1486
|
+
if (filters.length === 0) {
|
|
1487
|
+
return void 0;
|
|
1488
|
+
}
|
|
1489
|
+
if (filters.length === 1) {
|
|
1490
|
+
return filters[0];
|
|
1491
|
+
}
|
|
1492
|
+
return {
|
|
1493
|
+
operator: "And",
|
|
1494
|
+
operands: filters
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
function combineFiltersWithOr(filters) {
|
|
1498
|
+
if (filters.length === 0) {
|
|
1499
|
+
return void 0;
|
|
1500
|
+
}
|
|
1501
|
+
if (filters.length === 1) {
|
|
1502
|
+
return filters[0];
|
|
1503
|
+
}
|
|
1504
|
+
return {
|
|
1505
|
+
operator: "Or",
|
|
1506
|
+
operands: filters
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1410
1510
|
// src/tools/search-memory.ts
|
|
1411
1511
|
var searchMemoryTool = {
|
|
1412
1512
|
name: "remember_search_memory",
|
|
1413
|
-
description: `Search memories using hybrid semantic and keyword search.
|
|
1513
|
+
description: `Search memories AND relationships using hybrid semantic and keyword search.
|
|
1514
|
+
|
|
1515
|
+
By default, searches BOTH memories and relationships to provide comprehensive results.
|
|
1516
|
+
Relationships contain valuable context in their observations.
|
|
1414
1517
|
|
|
1415
1518
|
Supports:
|
|
1416
|
-
- Semantic search (meaning-based)
|
|
1519
|
+
- Semantic search (meaning-based) across memory content and relationship observations
|
|
1417
1520
|
- Keyword search (exact matches)
|
|
1418
1521
|
- Hybrid search (balanced with alpha parameter)
|
|
1419
1522
|
- Filtering by type, tags, weight, trust, date range
|
|
1420
|
-
-
|
|
1523
|
+
- Returns both memories and relationships in separate arrays
|
|
1421
1524
|
|
|
1422
1525
|
Examples:
|
|
1423
|
-
- "Find memories about camping trips"
|
|
1424
|
-
- "Search for recipes I saved"
|
|
1425
|
-
- "Show me notes from last week"
|
|
1526
|
+
- "Find memories about camping trips" \u2192 returns memories + relationships about camping
|
|
1527
|
+
- "Search for recipes I saved" \u2192 returns recipe memories + related relationships
|
|
1528
|
+
- "Show me notes from last week" \u2192 returns notes + any relationships created that week
|
|
1426
1529
|
`,
|
|
1427
1530
|
inputSchema: {
|
|
1428
1531
|
type: "object",
|
|
@@ -1485,8 +1588,8 @@ var searchMemoryTool = {
|
|
|
1485
1588
|
},
|
|
1486
1589
|
include_relationships: {
|
|
1487
1590
|
type: "boolean",
|
|
1488
|
-
description: "Include relationships in results. Default:
|
|
1489
|
-
default:
|
|
1591
|
+
description: "Include relationships in results. Default: true (searches both memories and relationships)",
|
|
1592
|
+
default: true
|
|
1490
1593
|
}
|
|
1491
1594
|
},
|
|
1492
1595
|
required: ["query"]
|
|
@@ -1494,83 +1597,53 @@ var searchMemoryTool = {
|
|
|
1494
1597
|
};
|
|
1495
1598
|
async function handleSearchMemory(args, userId) {
|
|
1496
1599
|
try {
|
|
1497
|
-
|
|
1600
|
+
const includeRelationships = args.include_relationships !== false;
|
|
1601
|
+
logger.info("Searching memories and relationships", {
|
|
1602
|
+
userId,
|
|
1603
|
+
query: args.query,
|
|
1604
|
+
includeRelationships
|
|
1605
|
+
});
|
|
1498
1606
|
const collection = getMemoryCollection(userId);
|
|
1499
1607
|
const alpha = args.alpha ?? 0.7;
|
|
1500
1608
|
const limit = args.limit ?? 10;
|
|
1501
1609
|
const offset = args.offset ?? 0;
|
|
1502
|
-
const
|
|
1503
|
-
{
|
|
1504
|
-
path: "doc_type",
|
|
1505
|
-
operator: "Equal",
|
|
1506
|
-
valueText: "memory"
|
|
1507
|
-
}
|
|
1508
|
-
];
|
|
1509
|
-
if (args.filters?.types && args.filters.types.length > 0) {
|
|
1510
|
-
whereFilters.push({
|
|
1511
|
-
path: "type",
|
|
1512
|
-
operator: "ContainsAny",
|
|
1513
|
-
valueTextArray: args.filters.types
|
|
1514
|
-
});
|
|
1515
|
-
}
|
|
1516
|
-
if (args.filters?.weight_min !== void 0) {
|
|
1517
|
-
whereFilters.push({
|
|
1518
|
-
path: "weight",
|
|
1519
|
-
operator: "GreaterThanEqual",
|
|
1520
|
-
valueNumber: args.filters.weight_min
|
|
1521
|
-
});
|
|
1522
|
-
}
|
|
1523
|
-
if (args.filters?.trust_min !== void 0) {
|
|
1524
|
-
whereFilters.push({
|
|
1525
|
-
path: "trust",
|
|
1526
|
-
operator: "GreaterThanEqual",
|
|
1527
|
-
valueNumber: args.filters.trust_min
|
|
1528
|
-
});
|
|
1529
|
-
}
|
|
1530
|
-
if (args.filters?.date_from) {
|
|
1531
|
-
whereFilters.push({
|
|
1532
|
-
path: "created_at",
|
|
1533
|
-
operator: "GreaterThanEqual",
|
|
1534
|
-
valueDate: new Date(args.filters.date_from)
|
|
1535
|
-
});
|
|
1536
|
-
}
|
|
1537
|
-
if (args.filters?.date_to) {
|
|
1538
|
-
whereFilters.push({
|
|
1539
|
-
path: "created_at",
|
|
1540
|
-
operator: "LessThanEqual",
|
|
1541
|
-
valueDate: new Date(args.filters.date_to)
|
|
1542
|
-
});
|
|
1543
|
-
}
|
|
1610
|
+
const filters = includeRelationships ? buildCombinedSearchFilters(collection, args.filters) : buildMemoryOnlyFilters(collection, args.filters);
|
|
1544
1611
|
const searchOptions = {
|
|
1545
1612
|
alpha,
|
|
1546
1613
|
limit: limit + offset
|
|
1547
1614
|
// Get extra for offset
|
|
1548
1615
|
};
|
|
1549
|
-
if (
|
|
1550
|
-
searchOptions.filters =
|
|
1551
|
-
operator: "And",
|
|
1552
|
-
operands: whereFilters
|
|
1553
|
-
} : whereFilters[0];
|
|
1616
|
+
if (filters) {
|
|
1617
|
+
searchOptions.filters = filters;
|
|
1554
1618
|
}
|
|
1555
1619
|
const results = await collection.query.hybrid(args.query, searchOptions);
|
|
1556
1620
|
const paginatedResults = results.objects.slice(offset);
|
|
1557
|
-
const memories =
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1621
|
+
const memories = [];
|
|
1622
|
+
const relationships = [];
|
|
1623
|
+
for (const obj of paginatedResults) {
|
|
1624
|
+
const doc = {
|
|
1625
|
+
id: obj.uuid,
|
|
1626
|
+
...obj.properties
|
|
1627
|
+
};
|
|
1628
|
+
if (doc.doc_type === "memory") {
|
|
1629
|
+
memories.push(doc);
|
|
1630
|
+
} else if (doc.doc_type === "relationship") {
|
|
1631
|
+
relationships.push(doc);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1561
1634
|
const searchResult = {
|
|
1562
1635
|
memories,
|
|
1563
|
-
|
|
1636
|
+
relationships: includeRelationships ? relationships : void 0,
|
|
1637
|
+
total: memories.length + relationships.length,
|
|
1564
1638
|
offset,
|
|
1565
1639
|
limit
|
|
1566
1640
|
};
|
|
1567
|
-
if (args.include_relationships) {
|
|
1568
|
-
searchResult.relationships = [];
|
|
1569
|
-
}
|
|
1570
1641
|
logger.info("Search completed", {
|
|
1571
1642
|
userId,
|
|
1572
1643
|
query: args.query,
|
|
1573
|
-
|
|
1644
|
+
memoriesFound: memories.length,
|
|
1645
|
+
relationshipsFound: relationships.length,
|
|
1646
|
+
total: searchResult.total
|
|
1574
1647
|
});
|
|
1575
1648
|
return JSON.stringify(searchResult, null, 2);
|
|
1576
1649
|
} catch (error) {
|
|
@@ -2030,59 +2103,15 @@ async function handleQueryMemory(args, userId) {
|
|
|
2030
2103
|
const minRelevance = args.min_relevance ?? 0.6;
|
|
2031
2104
|
const includeContext = args.include_context ?? true;
|
|
2032
2105
|
const format = args.format ?? "detailed";
|
|
2033
|
-
const
|
|
2034
|
-
{
|
|
2035
|
-
path: "doc_type",
|
|
2036
|
-
operator: "Equal",
|
|
2037
|
-
valueText: "memory"
|
|
2038
|
-
}
|
|
2039
|
-
];
|
|
2040
|
-
if (args.filters?.types && args.filters.types.length > 0) {
|
|
2041
|
-
whereFilters.push({
|
|
2042
|
-
path: "type",
|
|
2043
|
-
operator: "ContainsAny",
|
|
2044
|
-
valueTextArray: args.filters.types
|
|
2045
|
-
});
|
|
2046
|
-
}
|
|
2047
|
-
if (args.filters?.weight_min !== void 0) {
|
|
2048
|
-
whereFilters.push({
|
|
2049
|
-
path: "weight",
|
|
2050
|
-
operator: "GreaterThanEqual",
|
|
2051
|
-
valueNumber: args.filters.weight_min
|
|
2052
|
-
});
|
|
2053
|
-
}
|
|
2054
|
-
if (args.filters?.trust_min !== void 0) {
|
|
2055
|
-
whereFilters.push({
|
|
2056
|
-
path: "trust",
|
|
2057
|
-
operator: "GreaterThanEqual",
|
|
2058
|
-
valueNumber: args.filters.trust_min
|
|
2059
|
-
});
|
|
2060
|
-
}
|
|
2061
|
-
if (args.filters?.date_from) {
|
|
2062
|
-
whereFilters.push({
|
|
2063
|
-
path: "created_at",
|
|
2064
|
-
operator: "GreaterThanEqual",
|
|
2065
|
-
valueDate: new Date(args.filters.date_from)
|
|
2066
|
-
});
|
|
2067
|
-
}
|
|
2068
|
-
if (args.filters?.date_to) {
|
|
2069
|
-
whereFilters.push({
|
|
2070
|
-
path: "created_at",
|
|
2071
|
-
operator: "LessThanEqual",
|
|
2072
|
-
valueDate: new Date(args.filters.date_to)
|
|
2073
|
-
});
|
|
2074
|
-
}
|
|
2106
|
+
const filters = buildCombinedSearchFilters(collection, args.filters);
|
|
2075
2107
|
const searchOptions = {
|
|
2076
2108
|
limit,
|
|
2077
2109
|
distance: 1 - minRelevance,
|
|
2078
2110
|
// Convert relevance to distance
|
|
2079
2111
|
returnMetadata: ["distance"]
|
|
2080
2112
|
};
|
|
2081
|
-
if (
|
|
2082
|
-
searchOptions.filters =
|
|
2083
|
-
operator: "And",
|
|
2084
|
-
operands: whereFilters
|
|
2085
|
-
} : whereFilters[0];
|
|
2113
|
+
if (filters) {
|
|
2114
|
+
searchOptions.filters = filters;
|
|
2086
2115
|
}
|
|
2087
2116
|
const results = await collection.query.nearText(args.query, searchOptions);
|
|
2088
2117
|
const relevantMemories = results.objects.map((obj) => {
|
|
@@ -2698,6 +2727,437 @@ async function handleDeleteRelationship(args, userId) {
|
|
|
2698
2727
|
}
|
|
2699
2728
|
}
|
|
2700
2729
|
|
|
2730
|
+
// src/services/preferences-database.service.ts
|
|
2731
|
+
init_init();
|
|
2732
|
+
|
|
2733
|
+
// src/firestore/paths.ts
|
|
2734
|
+
var APP_NAME = "remember-mcp";
|
|
2735
|
+
function getBasePrefix() {
|
|
2736
|
+
const environment = process.env.ENVIRONMENT;
|
|
2737
|
+
if (environment && environment !== "production" && environment !== "prod") {
|
|
2738
|
+
return `${environment}.${APP_NAME}`;
|
|
2739
|
+
}
|
|
2740
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
2741
|
+
if (isDevelopment) {
|
|
2742
|
+
const customPrefix = process.env.DB_PREFIX;
|
|
2743
|
+
if (customPrefix) {
|
|
2744
|
+
return customPrefix;
|
|
2745
|
+
}
|
|
2746
|
+
return `e0.${APP_NAME}`;
|
|
2747
|
+
}
|
|
2748
|
+
return APP_NAME;
|
|
2749
|
+
}
|
|
2750
|
+
var BASE = getBasePrefix();
|
|
2751
|
+
function getUserPreferencesPath(userId) {
|
|
2752
|
+
return `${BASE}.users/${userId}/preferences`;
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
// src/types/preferences.ts
|
|
2756
|
+
var DEFAULT_PREFERENCES = {
|
|
2757
|
+
templates: {
|
|
2758
|
+
auto_suggest: true,
|
|
2759
|
+
suggestion_threshold: 0.6,
|
|
2760
|
+
max_suggestions: 3,
|
|
2761
|
+
show_preview: true,
|
|
2762
|
+
remember_choice: true,
|
|
2763
|
+
suppressed_categories: [],
|
|
2764
|
+
suppressed_templates: [],
|
|
2765
|
+
always_suggest: []
|
|
2766
|
+
},
|
|
2767
|
+
search: {
|
|
2768
|
+
default_limit: 10,
|
|
2769
|
+
include_low_trust: false,
|
|
2770
|
+
weight_by_access: true,
|
|
2771
|
+
weight_by_recency: true,
|
|
2772
|
+
default_alpha: 0.7
|
|
2773
|
+
},
|
|
2774
|
+
location: {
|
|
2775
|
+
auto_capture: true,
|
|
2776
|
+
precision: "approximate",
|
|
2777
|
+
share_with_memories: true
|
|
2778
|
+
},
|
|
2779
|
+
privacy: {
|
|
2780
|
+
default_trust_level: 0.5,
|
|
2781
|
+
allow_cross_user_access: false,
|
|
2782
|
+
auto_approve_requests: false,
|
|
2783
|
+
audit_logging: true
|
|
2784
|
+
},
|
|
2785
|
+
notifications: {
|
|
2786
|
+
trust_violations: true,
|
|
2787
|
+
access_requests: true,
|
|
2788
|
+
memory_reminders: false,
|
|
2789
|
+
relationship_suggestions: true
|
|
2790
|
+
},
|
|
2791
|
+
display: {
|
|
2792
|
+
date_format: "MM/DD/YYYY",
|
|
2793
|
+
time_format: "12h",
|
|
2794
|
+
timezone: "America/Los_Angeles",
|
|
2795
|
+
language: "en"
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2798
|
+
var PREFERENCE_CATEGORIES = [
|
|
2799
|
+
"templates",
|
|
2800
|
+
"search",
|
|
2801
|
+
"location",
|
|
2802
|
+
"privacy",
|
|
2803
|
+
"notifications",
|
|
2804
|
+
"display"
|
|
2805
|
+
];
|
|
2806
|
+
var PREFERENCE_DESCRIPTIONS = {
|
|
2807
|
+
templates: {
|
|
2808
|
+
auto_suggest: "Automatically suggest templates when creating memories (default: true)",
|
|
2809
|
+
suggestion_threshold: "Minimum confidence to show template suggestion, 0-1 (default: 0.6)",
|
|
2810
|
+
max_suggestions: "Maximum number of templates to suggest, 1-5 (default: 3)",
|
|
2811
|
+
show_preview: "Show template preview in suggestion (default: true)",
|
|
2812
|
+
remember_choice: `Remember "don't suggest for this type" choices (default: true)`,
|
|
2813
|
+
suppressed_categories: "Categories to never suggest templates for (default: [])",
|
|
2814
|
+
suppressed_templates: "Specific templates to never suggest (default: [])",
|
|
2815
|
+
always_suggest: "Templates to always suggest regardless of confidence (default: [])"
|
|
2816
|
+
},
|
|
2817
|
+
search: {
|
|
2818
|
+
default_limit: "Default number of search results to return, 1-100 (default: 10)",
|
|
2819
|
+
include_low_trust: "Include low-trust memories in search results (default: false)",
|
|
2820
|
+
weight_by_access: "Use access count in search ranking (default: true)",
|
|
2821
|
+
weight_by_recency: "Use recency in search ranking (default: true)",
|
|
2822
|
+
default_alpha: "Default hybrid search alpha (0=keyword, 1=semantic, default: 0.7)"
|
|
2823
|
+
},
|
|
2824
|
+
location: {
|
|
2825
|
+
auto_capture: "Automatically capture location for memories (default: true)",
|
|
2826
|
+
precision: "Location precision level: exact, approximate, city, none (default: approximate)",
|
|
2827
|
+
share_with_memories: "Include location data in memories (default: true)"
|
|
2828
|
+
},
|
|
2829
|
+
privacy: {
|
|
2830
|
+
default_trust_level: "Default trust level for new memories, 0-1 (default: 0.5)",
|
|
2831
|
+
allow_cross_user_access: "Allow other users to request access to memories (default: false)",
|
|
2832
|
+
auto_approve_requests: "Automatically approve access requests (default: false)",
|
|
2833
|
+
audit_logging: "Enable audit logging for preference changes (default: true)"
|
|
2834
|
+
},
|
|
2835
|
+
notifications: {
|
|
2836
|
+
trust_violations: "Notify on trust violations (default: true)",
|
|
2837
|
+
access_requests: "Notify on access requests from other users (default: true)",
|
|
2838
|
+
memory_reminders: "Send reminders about important memories (default: false)",
|
|
2839
|
+
relationship_suggestions: "Suggest new relationships between memories (default: true)"
|
|
2840
|
+
},
|
|
2841
|
+
display: {
|
|
2842
|
+
date_format: "Date format string (default: MM/DD/YYYY)",
|
|
2843
|
+
time_format: "Time format: 12h or 24h (default: 12h)",
|
|
2844
|
+
timezone: "Timezone identifier (default: America/Los_Angeles)",
|
|
2845
|
+
language: "Language code (default: en)"
|
|
2846
|
+
}
|
|
2847
|
+
};
|
|
2848
|
+
function getPreferenceDescription() {
|
|
2849
|
+
const categories = Object.entries(PREFERENCE_DESCRIPTIONS).map(([category, fields]) => {
|
|
2850
|
+
const fieldList = Object.entries(fields).map(([field, desc]) => ` - ${field}: ${desc}`).join("\n");
|
|
2851
|
+
return `${category}:
|
|
2852
|
+
${fieldList}`;
|
|
2853
|
+
}).join("\n\n");
|
|
2854
|
+
return `User preferences control system behavior. Available preference categories and fields:
|
|
2855
|
+
|
|
2856
|
+
${categories}`;
|
|
2857
|
+
}
|
|
2858
|
+
function getPreferencesSchema() {
|
|
2859
|
+
return {
|
|
2860
|
+
type: "object",
|
|
2861
|
+
properties: {
|
|
2862
|
+
templates: {
|
|
2863
|
+
type: "object",
|
|
2864
|
+
description: "Template suggestion preferences",
|
|
2865
|
+
properties: {
|
|
2866
|
+
auto_suggest: { type: "boolean" },
|
|
2867
|
+
suggestion_threshold: { type: "number", minimum: 0, maximum: 1 },
|
|
2868
|
+
max_suggestions: { type: "number", minimum: 1, maximum: 5 },
|
|
2869
|
+
show_preview: { type: "boolean" },
|
|
2870
|
+
remember_choice: { type: "boolean" },
|
|
2871
|
+
suppressed_categories: { type: "array", items: { type: "string" } },
|
|
2872
|
+
suppressed_templates: { type: "array", items: { type: "string" } },
|
|
2873
|
+
always_suggest: { type: "array", items: { type: "string" } }
|
|
2874
|
+
}
|
|
2875
|
+
},
|
|
2876
|
+
search: {
|
|
2877
|
+
type: "object",
|
|
2878
|
+
description: "Search behavior preferences",
|
|
2879
|
+
properties: {
|
|
2880
|
+
default_limit: { type: "number", minimum: 1, maximum: 100 },
|
|
2881
|
+
include_low_trust: { type: "boolean" },
|
|
2882
|
+
weight_by_access: { type: "boolean" },
|
|
2883
|
+
weight_by_recency: { type: "boolean" },
|
|
2884
|
+
default_alpha: { type: "number", minimum: 0, maximum: 1 }
|
|
2885
|
+
}
|
|
2886
|
+
},
|
|
2887
|
+
location: {
|
|
2888
|
+
type: "object",
|
|
2889
|
+
description: "Location capture preferences",
|
|
2890
|
+
properties: {
|
|
2891
|
+
auto_capture: { type: "boolean" },
|
|
2892
|
+
precision: { type: "string", enum: ["exact", "approximate", "city", "none"] },
|
|
2893
|
+
share_with_memories: { type: "boolean" }
|
|
2894
|
+
}
|
|
2895
|
+
},
|
|
2896
|
+
privacy: {
|
|
2897
|
+
type: "object",
|
|
2898
|
+
description: "Privacy and trust preferences",
|
|
2899
|
+
properties: {
|
|
2900
|
+
default_trust_level: { type: "number", minimum: 0, maximum: 1 },
|
|
2901
|
+
allow_cross_user_access: { type: "boolean" },
|
|
2902
|
+
auto_approve_requests: { type: "boolean" },
|
|
2903
|
+
audit_logging: { type: "boolean" }
|
|
2904
|
+
}
|
|
2905
|
+
},
|
|
2906
|
+
notifications: {
|
|
2907
|
+
type: "object",
|
|
2908
|
+
description: "Notification preferences",
|
|
2909
|
+
properties: {
|
|
2910
|
+
trust_violations: { type: "boolean" },
|
|
2911
|
+
access_requests: { type: "boolean" },
|
|
2912
|
+
memory_reminders: { type: "boolean" },
|
|
2913
|
+
relationship_suggestions: { type: "boolean" }
|
|
2914
|
+
}
|
|
2915
|
+
},
|
|
2916
|
+
display: {
|
|
2917
|
+
type: "object",
|
|
2918
|
+
description: "Display format preferences",
|
|
2919
|
+
properties: {
|
|
2920
|
+
date_format: { type: "string" },
|
|
2921
|
+
time_format: { type: "string", enum: ["12h", "24h"] },
|
|
2922
|
+
timezone: { type: "string" },
|
|
2923
|
+
language: { type: "string" }
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
// src/services/preferences-database.service.ts
|
|
2931
|
+
var PreferencesDatabaseService = class {
|
|
2932
|
+
/**
|
|
2933
|
+
* Get user preferences
|
|
2934
|
+
* Returns defaults if preferences don't exist
|
|
2935
|
+
*/
|
|
2936
|
+
static async getPreferences(userId) {
|
|
2937
|
+
try {
|
|
2938
|
+
const pathParts = getUserPreferencesPath(userId).split("/");
|
|
2939
|
+
const docId = pathParts.pop();
|
|
2940
|
+
const collectionPath = pathParts.join("/");
|
|
2941
|
+
const doc = await getDocument(collectionPath, docId);
|
|
2942
|
+
if (!doc) {
|
|
2943
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2944
|
+
return {
|
|
2945
|
+
user_id: userId,
|
|
2946
|
+
...DEFAULT_PREFERENCES,
|
|
2947
|
+
created_at: now,
|
|
2948
|
+
updated_at: now
|
|
2949
|
+
};
|
|
2950
|
+
}
|
|
2951
|
+
return doc;
|
|
2952
|
+
} catch (error) {
|
|
2953
|
+
logger.error("Failed to get preferences:", error);
|
|
2954
|
+
throw new Error(`Failed to get preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
/**
|
|
2958
|
+
* Update user preferences (partial update with merge)
|
|
2959
|
+
* Creates with defaults if preferences don't exist
|
|
2960
|
+
*/
|
|
2961
|
+
static async updatePreferences(userId, updates) {
|
|
2962
|
+
try {
|
|
2963
|
+
const pathParts = getUserPreferencesPath(userId).split("/");
|
|
2964
|
+
const docId = pathParts.pop();
|
|
2965
|
+
const collectionPath = pathParts.join("/");
|
|
2966
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2967
|
+
const doc = await getDocument(collectionPath, docId);
|
|
2968
|
+
if (!doc) {
|
|
2969
|
+
const newPrefs = {
|
|
2970
|
+
user_id: userId,
|
|
2971
|
+
...DEFAULT_PREFERENCES,
|
|
2972
|
+
...updates,
|
|
2973
|
+
created_at: now,
|
|
2974
|
+
updated_at: now
|
|
2975
|
+
};
|
|
2976
|
+
await setDocument(collectionPath, docId, newPrefs);
|
|
2977
|
+
logger.info("Preferences created with defaults", { userId });
|
|
2978
|
+
return newPrefs;
|
|
2979
|
+
}
|
|
2980
|
+
const updateData = {
|
|
2981
|
+
...updates,
|
|
2982
|
+
updated_at: now
|
|
2983
|
+
};
|
|
2984
|
+
await setDocument(collectionPath, docId, updateData, { merge: true });
|
|
2985
|
+
logger.info("Preferences updated", { userId });
|
|
2986
|
+
const updatedDoc = await getDocument(collectionPath, docId);
|
|
2987
|
+
return updatedDoc;
|
|
2988
|
+
} catch (error) {
|
|
2989
|
+
logger.error("Failed to update preferences:", error);
|
|
2990
|
+
throw new Error(`Failed to update preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
/**
|
|
2994
|
+
* Create preferences with defaults
|
|
2995
|
+
*/
|
|
2996
|
+
static async createPreferences(userId) {
|
|
2997
|
+
try {
|
|
2998
|
+
const pathParts = getUserPreferencesPath(userId).split("/");
|
|
2999
|
+
const docId = pathParts.pop();
|
|
3000
|
+
const collectionPath = pathParts.join("/");
|
|
3001
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3002
|
+
const preferences = {
|
|
3003
|
+
user_id: userId,
|
|
3004
|
+
...DEFAULT_PREFERENCES,
|
|
3005
|
+
created_at: now,
|
|
3006
|
+
updated_at: now
|
|
3007
|
+
};
|
|
3008
|
+
await setDocument(collectionPath, docId, preferences);
|
|
3009
|
+
logger.info("Preferences created", { userId });
|
|
3010
|
+
return preferences;
|
|
3011
|
+
} catch (error) {
|
|
3012
|
+
logger.error("Failed to create preferences:", error);
|
|
3013
|
+
throw new Error(`Failed to create preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
};
|
|
3017
|
+
|
|
3018
|
+
// src/tools/set-preference.ts
|
|
3019
|
+
var setPreferenceTool = {
|
|
3020
|
+
name: "remember_set_preference",
|
|
3021
|
+
description: `Update user preferences for system behavior through natural conversation.
|
|
3022
|
+
|
|
3023
|
+
This tool allows bulk updates to user preferences. Provide a partial preferences object
|
|
3024
|
+
with only the fields you want to update. All updates are merged with existing preferences.
|
|
3025
|
+
|
|
3026
|
+
${getPreferenceDescription()}
|
|
3027
|
+
|
|
3028
|
+
Common examples:
|
|
3029
|
+
- Disable template suggestions: { templates: { auto_suggest: false } }
|
|
3030
|
+
- Change search defaults: { search: { default_limit: 20, default_alpha: 0.8 } }
|
|
3031
|
+
- Update privacy: { privacy: { default_trust_level: 0.8 } }
|
|
3032
|
+
- Suppress categories: { templates: { suppressed_categories: ["work", "personal"] } }
|
|
3033
|
+
`,
|
|
3034
|
+
inputSchema: {
|
|
3035
|
+
type: "object",
|
|
3036
|
+
properties: {
|
|
3037
|
+
preferences: {
|
|
3038
|
+
...getPreferencesSchema(),
|
|
3039
|
+
description: "Partial preferences object with fields to update"
|
|
3040
|
+
}
|
|
3041
|
+
},
|
|
3042
|
+
required: ["preferences"]
|
|
3043
|
+
}
|
|
3044
|
+
};
|
|
3045
|
+
function formatPreferenceChangeMessage(updates) {
|
|
3046
|
+
const changes = [];
|
|
3047
|
+
if (updates.templates) {
|
|
3048
|
+
if (updates.templates.auto_suggest !== void 0) {
|
|
3049
|
+
changes.push(
|
|
3050
|
+
updates.templates.auto_suggest ? "Template suggestions enabled" : "Template suggestions disabled"
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
if (updates.templates.suppressed_categories) {
|
|
3054
|
+
changes.push(`Suppressed categories: ${updates.templates.suppressed_categories.join(", ")}`);
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
if (updates.search) {
|
|
3058
|
+
if (updates.search.default_limit !== void 0) {
|
|
3059
|
+
changes.push(`Search limit set to ${updates.search.default_limit}`);
|
|
3060
|
+
}
|
|
3061
|
+
if (updates.search.default_alpha !== void 0) {
|
|
3062
|
+
changes.push(`Search alpha set to ${updates.search.default_alpha}`);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
if (updates.privacy) {
|
|
3066
|
+
if (updates.privacy.default_trust_level !== void 0) {
|
|
3067
|
+
changes.push(`Default trust level set to ${updates.privacy.default_trust_level}`);
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
if (updates.location) {
|
|
3071
|
+
if (updates.location.auto_capture !== void 0) {
|
|
3072
|
+
changes.push(
|
|
3073
|
+
updates.location.auto_capture ? "Location auto-capture enabled" : "Location auto-capture disabled"
|
|
3074
|
+
);
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
if (changes.length === 0) {
|
|
3078
|
+
return "Preferences updated successfully";
|
|
3079
|
+
}
|
|
3080
|
+
return `Preferences updated: ${changes.join(", ")}`;
|
|
3081
|
+
}
|
|
3082
|
+
async function handleSetPreference(args, userId) {
|
|
3083
|
+
try {
|
|
3084
|
+
const { preferences } = args;
|
|
3085
|
+
logger.info("Setting preferences", { userId, updates: Object.keys(preferences) });
|
|
3086
|
+
const updatedPreferences = await PreferencesDatabaseService.updatePreferences(
|
|
3087
|
+
userId,
|
|
3088
|
+
preferences
|
|
3089
|
+
);
|
|
3090
|
+
const message = formatPreferenceChangeMessage(preferences);
|
|
3091
|
+
const result = {
|
|
3092
|
+
success: true,
|
|
3093
|
+
updated_preferences: updatedPreferences,
|
|
3094
|
+
message
|
|
3095
|
+
};
|
|
3096
|
+
logger.info("Preferences set successfully", { userId });
|
|
3097
|
+
return JSON.stringify(result, null, 2);
|
|
3098
|
+
} catch (error) {
|
|
3099
|
+
logger.error("Failed to set preferences:", error);
|
|
3100
|
+
throw new Error(`Failed to set preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
// src/tools/get-preferences.ts
|
|
3105
|
+
var getPreferencesTool = {
|
|
3106
|
+
name: "remember_get_preferences",
|
|
3107
|
+
description: `Get current user preferences.
|
|
3108
|
+
|
|
3109
|
+
Use this to understand user's current settings before suggesting changes
|
|
3110
|
+
or to explain why system is behaving a certain way.
|
|
3111
|
+
|
|
3112
|
+
Returns the complete preferences object or filtered by category.
|
|
3113
|
+
If preferences don't exist, returns defaults.
|
|
3114
|
+
|
|
3115
|
+
${getPreferenceDescription()}
|
|
3116
|
+
`,
|
|
3117
|
+
inputSchema: {
|
|
3118
|
+
type: "object",
|
|
3119
|
+
properties: {
|
|
3120
|
+
category: {
|
|
3121
|
+
type: "string",
|
|
3122
|
+
enum: ["templates", "search", "location", "privacy", "notifications", "display"],
|
|
3123
|
+
description: "Optional category to filter preferences"
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
};
|
|
3128
|
+
async function handleGetPreferences(args, userId) {
|
|
3129
|
+
try {
|
|
3130
|
+
const { category } = args;
|
|
3131
|
+
logger.info("Getting preferences", { userId, category });
|
|
3132
|
+
const preferences = await PreferencesDatabaseService.getPreferences(userId);
|
|
3133
|
+
const isDefault = !preferences.created_at || preferences.created_at === preferences.updated_at;
|
|
3134
|
+
let result;
|
|
3135
|
+
let message;
|
|
3136
|
+
if (category) {
|
|
3137
|
+
if (!PREFERENCE_CATEGORIES.includes(category)) {
|
|
3138
|
+
throw new Error(`Invalid category: ${category}. Valid categories: ${PREFERENCE_CATEGORIES.join(", ")}`);
|
|
3139
|
+
}
|
|
3140
|
+
result = {
|
|
3141
|
+
[category]: preferences[category]
|
|
3142
|
+
};
|
|
3143
|
+
message = isDefault ? `Showing default ${category} preferences (user has not customized preferences yet).` : `Showing current ${category} preferences.`;
|
|
3144
|
+
} else {
|
|
3145
|
+
result = preferences;
|
|
3146
|
+
message = isDefault ? "Showing default preferences (user has not customized preferences yet)." : "Showing current user preferences.";
|
|
3147
|
+
}
|
|
3148
|
+
const response = {
|
|
3149
|
+
preferences: result,
|
|
3150
|
+
is_default: isDefault,
|
|
3151
|
+
message
|
|
3152
|
+
};
|
|
3153
|
+
logger.info("Preferences retrieved successfully", { userId, category, isDefault });
|
|
3154
|
+
return JSON.stringify(response, null, 2);
|
|
3155
|
+
} catch (error) {
|
|
3156
|
+
logger.error("Failed to get preferences:", error);
|
|
3157
|
+
throw new Error(`Failed to get preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
|
|
2701
3161
|
// src/server-factory.ts
|
|
2702
3162
|
var databasesInitialized = false;
|
|
2703
3163
|
var initializationPromise = null;
|
|
@@ -2730,14 +3190,12 @@ async function ensureDatabasesInitialized() {
|
|
|
2730
3190
|
})();
|
|
2731
3191
|
return initializationPromise;
|
|
2732
3192
|
}
|
|
2733
|
-
function createServer(accessToken, userId, options = {}) {
|
|
3193
|
+
async function createServer(accessToken, userId, options = {}) {
|
|
2734
3194
|
if (!userId) {
|
|
2735
3195
|
throw new Error("userId is required");
|
|
2736
3196
|
}
|
|
2737
3197
|
logger.debug("Creating server instance", { userId });
|
|
2738
|
-
ensureDatabasesInitialized()
|
|
2739
|
-
logger.error("Failed to initialize databases:", error);
|
|
2740
|
-
});
|
|
3198
|
+
await ensureDatabasesInitialized();
|
|
2741
3199
|
const server = new Server(
|
|
2742
3200
|
{
|
|
2743
3201
|
name: options.name || "remember-mcp",
|
|
@@ -2775,7 +3233,10 @@ function registerHandlers(server, userId, accessToken) {
|
|
|
2775
3233
|
createRelationshipTool,
|
|
2776
3234
|
updateRelationshipTool,
|
|
2777
3235
|
searchRelationshipTool,
|
|
2778
|
-
deleteRelationshipTool
|
|
3236
|
+
deleteRelationshipTool,
|
|
3237
|
+
// Preference tools
|
|
3238
|
+
setPreferenceTool,
|
|
3239
|
+
getPreferencesTool
|
|
2779
3240
|
]
|
|
2780
3241
|
};
|
|
2781
3242
|
});
|
|
@@ -2817,6 +3278,12 @@ function registerHandlers(server, userId, accessToken) {
|
|
|
2817
3278
|
case "remember_delete_relationship":
|
|
2818
3279
|
result = await handleDeleteRelationship(args, userId);
|
|
2819
3280
|
break;
|
|
3281
|
+
case "remember_set_preference":
|
|
3282
|
+
result = await handleSetPreference(args, userId);
|
|
3283
|
+
break;
|
|
3284
|
+
case "remember_get_preferences":
|
|
3285
|
+
result = await handleGetPreferences(args, userId);
|
|
3286
|
+
break;
|
|
2820
3287
|
default:
|
|
2821
3288
|
throw new McpError(
|
|
2822
3289
|
ErrorCode.MethodNotFound,
|