@prmichaelsen/remember-mcp 0.1.0 → 0.2.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/agent/progress.yaml +96 -36
- package/dist/server-factory.d.ts +2 -0
- package/dist/server-factory.js +1157 -51
- package/dist/server.js +1075 -1
- package/dist/tools/create-relationship.d.ts +79 -0
- package/dist/tools/delete-relationship.d.ts +41 -0
- package/dist/tools/find-similar.d.ts +77 -0
- package/dist/tools/query-memory.d.ts +115 -0
- package/dist/tools/search-relationship.d.ts +86 -0
- package/dist/tools/update-memory.d.ts +92 -0
- package/dist/tools/update-relationship.d.ts +74 -0
- package/package.json +1 -1
- package/src/server-factory.ts +98 -5
- package/src/server.ts +46 -0
- package/src/tools/create-relationship.ts +242 -0
- package/src/tools/delete-relationship.ts +160 -0
- package/src/tools/find-similar.ts +199 -0
- package/src/tools/query-memory.ts +284 -0
- package/src/tools/search-relationship.ts +228 -0
- package/src/tools/update-memory.ts +230 -0
- package/src/tools/update-relationship.ts +189 -0
package/dist/server-factory.js
CHANGED
|
@@ -630,6 +630,10 @@ var logger = {
|
|
|
630
630
|
}
|
|
631
631
|
};
|
|
632
632
|
|
|
633
|
+
// src/server-factory.ts
|
|
634
|
+
init_client();
|
|
635
|
+
init_init();
|
|
636
|
+
|
|
633
637
|
// src/weaviate/schema.ts
|
|
634
638
|
init_client();
|
|
635
639
|
import weaviate2 from "weaviate-client";
|
|
@@ -1612,63 +1616,1165 @@ async function handleDeleteMemory(args, userId) {
|
|
|
1612
1616
|
}
|
|
1613
1617
|
}
|
|
1614
1618
|
|
|
1615
|
-
// src/
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
+
// src/tools/update-memory.ts
|
|
1620
|
+
var updateMemoryTool = {
|
|
1621
|
+
name: "remember_update_memory",
|
|
1622
|
+
description: `Update an existing memory with partial updates.
|
|
1623
|
+
|
|
1624
|
+
Supports updating any field except id, user_id, doc_type, created_at.
|
|
1625
|
+
Version number is automatically incremented and updated_at is set.
|
|
1626
|
+
Only provided fields are updated (partial updates supported).
|
|
1627
|
+
|
|
1628
|
+
Examples:
|
|
1629
|
+
- "Update that camping note to add more details"
|
|
1630
|
+
- "Change the weight of my recipe memory"
|
|
1631
|
+
- "Add tags to the meeting note from yesterday"
|
|
1632
|
+
`,
|
|
1633
|
+
inputSchema: {
|
|
1634
|
+
type: "object",
|
|
1635
|
+
properties: {
|
|
1636
|
+
memory_id: {
|
|
1637
|
+
type: "string",
|
|
1638
|
+
description: "ID of the memory to update"
|
|
1639
|
+
},
|
|
1640
|
+
content: {
|
|
1641
|
+
type: "string",
|
|
1642
|
+
description: "Updated memory content"
|
|
1643
|
+
},
|
|
1644
|
+
title: {
|
|
1645
|
+
type: "string",
|
|
1646
|
+
description: "Updated title"
|
|
1647
|
+
},
|
|
1648
|
+
type: {
|
|
1649
|
+
type: "string",
|
|
1650
|
+
description: "Updated content type"
|
|
1651
|
+
},
|
|
1652
|
+
weight: {
|
|
1653
|
+
type: "number",
|
|
1654
|
+
description: "Updated significance/priority (0-1)",
|
|
1655
|
+
minimum: 0,
|
|
1656
|
+
maximum: 1
|
|
1657
|
+
},
|
|
1658
|
+
trust: {
|
|
1659
|
+
type: "number",
|
|
1660
|
+
description: "Updated access control level (0-1)",
|
|
1661
|
+
minimum: 0,
|
|
1662
|
+
maximum: 1
|
|
1663
|
+
},
|
|
1664
|
+
tags: {
|
|
1665
|
+
type: "array",
|
|
1666
|
+
items: { type: "string" },
|
|
1667
|
+
description: "Updated tags (replaces existing tags)"
|
|
1668
|
+
},
|
|
1669
|
+
references: {
|
|
1670
|
+
type: "array",
|
|
1671
|
+
items: { type: "string" },
|
|
1672
|
+
description: "Updated source URLs (replaces existing references)"
|
|
1673
|
+
},
|
|
1674
|
+
structured_content: {
|
|
1675
|
+
type: "object",
|
|
1676
|
+
description: "Updated structured content"
|
|
1677
|
+
}
|
|
1678
|
+
},
|
|
1679
|
+
required: ["memory_id"]
|
|
1619
1680
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1681
|
+
};
|
|
1682
|
+
async function handleUpdateMemory(args, userId) {
|
|
1683
|
+
try {
|
|
1684
|
+
logger.info("Updating memory", { userId, memoryId: args.memory_id });
|
|
1685
|
+
const collection = getMemoryCollection(userId);
|
|
1686
|
+
const existingMemory = await collection.query.fetchObjectById(args.memory_id, {
|
|
1687
|
+
returnProperties: ["user_id", "doc_type", "version", "type", "weight", "base_weight"]
|
|
1688
|
+
});
|
|
1689
|
+
if (!existingMemory) {
|
|
1690
|
+
throw new Error(`Memory not found: ${args.memory_id}`);
|
|
1691
|
+
}
|
|
1692
|
+
if (existingMemory.properties.user_id !== userId) {
|
|
1693
|
+
throw new Error("Unauthorized: Cannot update another user's memory");
|
|
1694
|
+
}
|
|
1695
|
+
if (existingMemory.properties.doc_type !== "memory") {
|
|
1696
|
+
throw new Error("Cannot update relationships using this tool. Use remember_update_relationship instead.");
|
|
1697
|
+
}
|
|
1698
|
+
const updates = {};
|
|
1699
|
+
const updatedFields = [];
|
|
1700
|
+
if (args.content !== void 0) {
|
|
1701
|
+
updates.content = args.content;
|
|
1702
|
+
updatedFields.push("content");
|
|
1703
|
+
}
|
|
1704
|
+
if (args.title !== void 0) {
|
|
1705
|
+
updates.title = args.title;
|
|
1706
|
+
updates.summary = args.title;
|
|
1707
|
+
updatedFields.push("title");
|
|
1708
|
+
}
|
|
1709
|
+
if (args.type !== void 0) {
|
|
1710
|
+
if (!isValidContentType(args.type)) {
|
|
1711
|
+
throw new Error(`Invalid content type: ${args.type}`);
|
|
1712
|
+
}
|
|
1713
|
+
updates.type = args.type;
|
|
1714
|
+
updatedFields.push("type");
|
|
1715
|
+
}
|
|
1716
|
+
if (args.weight !== void 0) {
|
|
1717
|
+
if (args.weight < 0 || args.weight > 1) {
|
|
1718
|
+
throw new Error("Weight must be between 0 and 1");
|
|
1719
|
+
}
|
|
1720
|
+
updates.weight = args.weight;
|
|
1721
|
+
updates.base_weight = args.weight;
|
|
1722
|
+
updates.computed_weight = args.weight;
|
|
1723
|
+
updatedFields.push("weight");
|
|
1724
|
+
}
|
|
1725
|
+
if (args.trust !== void 0) {
|
|
1726
|
+
if (args.trust < 0 || args.trust > 1) {
|
|
1727
|
+
throw new Error("Trust must be between 0 and 1");
|
|
1728
|
+
}
|
|
1729
|
+
updates.trust = args.trust;
|
|
1730
|
+
updatedFields.push("trust");
|
|
1731
|
+
}
|
|
1732
|
+
if (args.tags !== void 0) {
|
|
1733
|
+
updates.tags = args.tags;
|
|
1734
|
+
updatedFields.push("tags");
|
|
1735
|
+
}
|
|
1736
|
+
if (args.references !== void 0) {
|
|
1737
|
+
updates.references = args.references;
|
|
1738
|
+
updatedFields.push("references");
|
|
1739
|
+
}
|
|
1740
|
+
if (args.structured_content !== void 0) {
|
|
1741
|
+
updates.structured_content = args.structured_content;
|
|
1742
|
+
updatedFields.push("structured_content");
|
|
1743
|
+
}
|
|
1744
|
+
if (updatedFields.length === 0) {
|
|
1745
|
+
throw new Error("No fields provided for update. At least one field must be specified.");
|
|
1746
|
+
}
|
|
1747
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1748
|
+
updates.updated_at = now;
|
|
1749
|
+
updates.version = existingMemory.properties.version + 1;
|
|
1750
|
+
await collection.data.update({
|
|
1751
|
+
id: args.memory_id,
|
|
1752
|
+
properties: updates
|
|
1753
|
+
});
|
|
1754
|
+
logger.info("Memory updated successfully", {
|
|
1755
|
+
userId,
|
|
1756
|
+
memoryId: args.memory_id,
|
|
1757
|
+
version: updates.version,
|
|
1758
|
+
updatedFields
|
|
1759
|
+
});
|
|
1760
|
+
const result = {
|
|
1761
|
+
memory_id: args.memory_id,
|
|
1762
|
+
updated_at: now,
|
|
1763
|
+
version: updates.version,
|
|
1764
|
+
updated_fields: updatedFields,
|
|
1765
|
+
message: `Memory updated successfully. Updated fields: ${updatedFields.join(", ")}`
|
|
1766
|
+
};
|
|
1767
|
+
return JSON.stringify(result, null, 2);
|
|
1768
|
+
} catch (error) {
|
|
1769
|
+
logger.error("Failed to update memory:", error);
|
|
1770
|
+
throw new Error(`Failed to update memory: ${error instanceof Error ? error.message : String(error)}`);
|
|
1622
1771
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
// src/tools/find-similar.ts
|
|
1775
|
+
var findSimilarTool = {
|
|
1776
|
+
name: "remember_find_similar",
|
|
1777
|
+
description: `Find memories similar to a given memory or text using vector similarity.
|
|
1778
|
+
|
|
1779
|
+
Uses pure semantic similarity (vector distance) to find related memories.
|
|
1780
|
+
This is different from hybrid search - it finds memories with similar meaning,
|
|
1781
|
+
even if they don't share keywords.
|
|
1782
|
+
|
|
1783
|
+
Examples:
|
|
1784
|
+
- "Find memories similar to this camping trip note"
|
|
1785
|
+
- "Show me memories related to this recipe"
|
|
1786
|
+
- "What other memories are like this meeting note?"
|
|
1787
|
+
`,
|
|
1788
|
+
inputSchema: {
|
|
1789
|
+
type: "object",
|
|
1790
|
+
properties: {
|
|
1791
|
+
memory_id: {
|
|
1792
|
+
type: "string",
|
|
1793
|
+
description: "ID of memory to find similar memories for (provide either memory_id or text)"
|
|
1794
|
+
},
|
|
1795
|
+
text: {
|
|
1796
|
+
type: "string",
|
|
1797
|
+
description: "Text to find similar memories for (provide either memory_id or text)"
|
|
1798
|
+
},
|
|
1799
|
+
limit: {
|
|
1800
|
+
type: "number",
|
|
1801
|
+
description: "Maximum number of results. Default: 10",
|
|
1802
|
+
minimum: 1,
|
|
1803
|
+
maximum: 100,
|
|
1804
|
+
default: 10
|
|
1805
|
+
},
|
|
1806
|
+
min_similarity: {
|
|
1807
|
+
type: "number",
|
|
1808
|
+
description: "Minimum similarity score (0-1). Default: 0.7",
|
|
1809
|
+
minimum: 0,
|
|
1810
|
+
maximum: 1,
|
|
1811
|
+
default: 0.7
|
|
1812
|
+
},
|
|
1813
|
+
include_relationships: {
|
|
1814
|
+
type: "boolean",
|
|
1815
|
+
description: "Include relationships in results. Default: false",
|
|
1816
|
+
default: false
|
|
1632
1817
|
}
|
|
1633
1818
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
async function handleFindSimilar(args, userId) {
|
|
1822
|
+
try {
|
|
1823
|
+
logger.info("Finding similar memories", { userId, memoryId: args.memory_id, hasText: !!args.text });
|
|
1824
|
+
if (!args.memory_id && !args.text) {
|
|
1825
|
+
throw new Error("Either memory_id or text must be provided");
|
|
1826
|
+
}
|
|
1827
|
+
if (args.memory_id && args.text) {
|
|
1828
|
+
throw new Error("Provide either memory_id or text, not both");
|
|
1829
|
+
}
|
|
1830
|
+
const collection = getMemoryCollection(userId);
|
|
1831
|
+
const limit = args.limit ?? 10;
|
|
1832
|
+
const minSimilarity = args.min_similarity ?? 0.7;
|
|
1833
|
+
let results;
|
|
1834
|
+
if (args.memory_id) {
|
|
1835
|
+
const memory = await collection.query.fetchObjectById(args.memory_id, {
|
|
1836
|
+
returnProperties: ["user_id", "doc_type", "content"]
|
|
1837
|
+
});
|
|
1838
|
+
if (!memory) {
|
|
1839
|
+
throw new Error(`Memory not found: ${args.memory_id}`);
|
|
1840
|
+
}
|
|
1841
|
+
if (memory.properties.user_id !== userId) {
|
|
1842
|
+
throw new Error("Unauthorized: Cannot access another user's memory");
|
|
1843
|
+
}
|
|
1844
|
+
if (memory.properties.doc_type !== "memory") {
|
|
1845
|
+
throw new Error("Can only find similar memories for memory documents, not relationships");
|
|
1846
|
+
}
|
|
1847
|
+
results = await collection.query.nearObject(args.memory_id, {
|
|
1848
|
+
limit: limit + 1,
|
|
1849
|
+
// +1 to exclude the source memory itself
|
|
1850
|
+
distance: 1 - minSimilarity,
|
|
1851
|
+
// Convert similarity to distance
|
|
1852
|
+
returnMetadata: ["distance"]
|
|
1853
|
+
});
|
|
1854
|
+
results.objects = results.objects.filter((obj) => obj.uuid !== args.memory_id);
|
|
1855
|
+
} else {
|
|
1856
|
+
results = await collection.query.nearText(args.text, {
|
|
1857
|
+
limit,
|
|
1858
|
+
distance: 1 - minSimilarity,
|
|
1859
|
+
returnMetadata: ["distance"]
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
if (!args.include_relationships) {
|
|
1863
|
+
results.objects = results.objects.filter(
|
|
1864
|
+
(obj) => obj.properties.doc_type === "memory"
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
const similarMemories = results.objects.map((obj) => {
|
|
1868
|
+
const similarity = 1 - (obj.metadata?.distance ?? 0);
|
|
1869
|
+
return {
|
|
1870
|
+
id: obj.uuid,
|
|
1871
|
+
...obj.properties,
|
|
1872
|
+
similarity: Math.max(0, Math.min(1, similarity))
|
|
1873
|
+
// Clamp to [0, 1]
|
|
1874
|
+
};
|
|
1875
|
+
});
|
|
1876
|
+
similarMemories.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
|
|
1877
|
+
const limitedResults = similarMemories.slice(0, limit);
|
|
1878
|
+
logger.info("Similar memories found", {
|
|
1879
|
+
userId,
|
|
1880
|
+
query: args.memory_id || args.text,
|
|
1881
|
+
results: limitedResults.length
|
|
1882
|
+
});
|
|
1883
|
+
const result = {
|
|
1884
|
+
query: {
|
|
1885
|
+
memory_id: args.memory_id,
|
|
1886
|
+
text: args.text
|
|
1887
|
+
},
|
|
1888
|
+
similar_memories: limitedResults,
|
|
1889
|
+
total: limitedResults.length,
|
|
1890
|
+
min_similarity: minSimilarity
|
|
1891
|
+
};
|
|
1892
|
+
return JSON.stringify(result, null, 2);
|
|
1893
|
+
} catch (error) {
|
|
1894
|
+
logger.error("Failed to find similar memories:", error);
|
|
1895
|
+
throw new Error(`Failed to find similar memories: ${error instanceof Error ? error.message : String(error)}`);
|
|
1896
|
+
}
|
|
1637
1897
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1898
|
+
|
|
1899
|
+
// src/tools/query-memory.ts
|
|
1900
|
+
var queryMemoryTool = {
|
|
1901
|
+
name: "remember_query_memory",
|
|
1902
|
+
description: `Query memories using natural language for RAG (Retrieval-Augmented Generation).
|
|
1903
|
+
|
|
1904
|
+
This tool is optimized for LLM context retrieval. It returns relevant memories
|
|
1905
|
+
with their full content and context, formatted for easy consumption by LLMs.
|
|
1906
|
+
|
|
1907
|
+
Use this when you need to:
|
|
1908
|
+
- Answer questions based on stored memories
|
|
1909
|
+
- Provide context for conversations
|
|
1910
|
+
- Retrieve information for decision-making
|
|
1911
|
+
- Build responses using past knowledge
|
|
1912
|
+
|
|
1913
|
+
Examples:
|
|
1914
|
+
- "What do I know about camping?"
|
|
1915
|
+
- "Tell me about the recipes I've saved"
|
|
1916
|
+
- "What meetings did I have last week?"
|
|
1917
|
+
- "What are my project goals?"
|
|
1918
|
+
`,
|
|
1919
|
+
inputSchema: {
|
|
1920
|
+
type: "object",
|
|
1921
|
+
properties: {
|
|
1922
|
+
query: {
|
|
1923
|
+
type: "string",
|
|
1924
|
+
description: "Natural language query"
|
|
1925
|
+
},
|
|
1926
|
+
limit: {
|
|
1927
|
+
type: "number",
|
|
1928
|
+
description: "Maximum number of memories to retrieve. Default: 5",
|
|
1929
|
+
minimum: 1,
|
|
1930
|
+
maximum: 50,
|
|
1931
|
+
default: 5
|
|
1932
|
+
},
|
|
1933
|
+
min_relevance: {
|
|
1934
|
+
type: "number",
|
|
1935
|
+
description: "Minimum relevance score (0-1). Default: 0.6",
|
|
1936
|
+
minimum: 0,
|
|
1937
|
+
maximum: 1,
|
|
1938
|
+
default: 0.6
|
|
1939
|
+
},
|
|
1940
|
+
filters: {
|
|
1941
|
+
type: "object",
|
|
1942
|
+
description: "Optional filters to narrow results",
|
|
1943
|
+
properties: {
|
|
1944
|
+
types: {
|
|
1945
|
+
type: "array",
|
|
1946
|
+
items: { type: "string" },
|
|
1947
|
+
description: "Filter by content types"
|
|
1948
|
+
},
|
|
1949
|
+
tags: {
|
|
1950
|
+
type: "array",
|
|
1951
|
+
items: { type: "string" },
|
|
1952
|
+
description: "Filter by tags"
|
|
1953
|
+
},
|
|
1954
|
+
weight_min: {
|
|
1955
|
+
type: "number",
|
|
1956
|
+
description: "Minimum weight (0-1)"
|
|
1957
|
+
},
|
|
1958
|
+
trust_min: {
|
|
1959
|
+
type: "number",
|
|
1960
|
+
description: "Minimum trust level (0-1)"
|
|
1961
|
+
},
|
|
1962
|
+
date_from: {
|
|
1963
|
+
type: "string",
|
|
1964
|
+
description: "Start date (ISO 8601)"
|
|
1965
|
+
},
|
|
1966
|
+
date_to: {
|
|
1967
|
+
type: "string",
|
|
1968
|
+
description: "End date (ISO 8601)"
|
|
1648
1969
|
}
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1970
|
+
}
|
|
1971
|
+
},
|
|
1972
|
+
include_context: {
|
|
1973
|
+
type: "boolean",
|
|
1974
|
+
description: "Include full context metadata. Default: true",
|
|
1975
|
+
default: true
|
|
1976
|
+
},
|
|
1977
|
+
format: {
|
|
1978
|
+
type: "string",
|
|
1979
|
+
description: 'Output format: "detailed" (full objects) or "compact" (text summary). Default: detailed',
|
|
1980
|
+
enum: ["detailed", "compact"],
|
|
1981
|
+
default: "detailed"
|
|
1982
|
+
}
|
|
1983
|
+
},
|
|
1984
|
+
required: ["query"]
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
async function handleQueryMemory(args, userId) {
|
|
1988
|
+
try {
|
|
1989
|
+
logger.info("Querying memories", { userId, query: args.query });
|
|
1990
|
+
const collection = getMemoryCollection(userId);
|
|
1991
|
+
const limit = args.limit ?? 5;
|
|
1992
|
+
const minRelevance = args.min_relevance ?? 0.6;
|
|
1993
|
+
const includeContext = args.include_context ?? true;
|
|
1994
|
+
const format = args.format ?? "detailed";
|
|
1995
|
+
const whereFilters = [
|
|
1996
|
+
{
|
|
1997
|
+
path: "doc_type",
|
|
1998
|
+
operator: "Equal",
|
|
1999
|
+
valueText: "memory"
|
|
2000
|
+
}
|
|
2001
|
+
];
|
|
2002
|
+
if (args.filters?.types && args.filters.types.length > 0) {
|
|
2003
|
+
whereFilters.push({
|
|
2004
|
+
path: "type",
|
|
2005
|
+
operator: "ContainsAny",
|
|
2006
|
+
valueTextArray: args.filters.types
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
if (args.filters?.weight_min !== void 0) {
|
|
2010
|
+
whereFilters.push({
|
|
2011
|
+
path: "weight",
|
|
2012
|
+
operator: "GreaterThanEqual",
|
|
2013
|
+
valueNumber: args.filters.weight_min
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
if (args.filters?.trust_min !== void 0) {
|
|
2017
|
+
whereFilters.push({
|
|
2018
|
+
path: "trust",
|
|
2019
|
+
operator: "GreaterThanEqual",
|
|
2020
|
+
valueNumber: args.filters.trust_min
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
if (args.filters?.date_from) {
|
|
2024
|
+
whereFilters.push({
|
|
2025
|
+
path: "created_at",
|
|
2026
|
+
operator: "GreaterThanEqual",
|
|
2027
|
+
valueDate: new Date(args.filters.date_from)
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
if (args.filters?.date_to) {
|
|
2031
|
+
whereFilters.push({
|
|
2032
|
+
path: "created_at",
|
|
2033
|
+
operator: "LessThanEqual",
|
|
2034
|
+
valueDate: new Date(args.filters.date_to)
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
const searchOptions = {
|
|
2038
|
+
limit,
|
|
2039
|
+
distance: 1 - minRelevance,
|
|
2040
|
+
// Convert relevance to distance
|
|
2041
|
+
returnMetadata: ["distance"]
|
|
2042
|
+
};
|
|
2043
|
+
if (whereFilters.length > 0) {
|
|
2044
|
+
searchOptions.filters = whereFilters.length > 1 ? {
|
|
2045
|
+
operator: "And",
|
|
2046
|
+
operands: whereFilters
|
|
2047
|
+
} : whereFilters[0];
|
|
2048
|
+
}
|
|
2049
|
+
const results = await collection.query.nearText(args.query, searchOptions);
|
|
2050
|
+
const relevantMemories = results.objects.map((obj) => {
|
|
2051
|
+
const relevance = 1 - (obj.metadata?.distance ?? 0);
|
|
2052
|
+
const memory = {
|
|
2053
|
+
id: obj.uuid,
|
|
2054
|
+
...obj.properties,
|
|
2055
|
+
relevance: Math.max(0, Math.min(1, relevance))
|
|
2056
|
+
// Clamp to [0, 1]
|
|
2057
|
+
};
|
|
2058
|
+
if (!includeContext) {
|
|
2059
|
+
delete memory.context;
|
|
2060
|
+
delete memory.location;
|
|
2061
|
+
}
|
|
2062
|
+
return memory;
|
|
2063
|
+
});
|
|
2064
|
+
relevantMemories.sort((a, b) => (b.relevance ?? 0) - (a.relevance ?? 0));
|
|
2065
|
+
logger.info("Query completed", {
|
|
2066
|
+
userId,
|
|
2067
|
+
query: args.query,
|
|
2068
|
+
results: relevantMemories.length
|
|
2069
|
+
});
|
|
2070
|
+
let formattedMemories;
|
|
2071
|
+
let contextSummary;
|
|
2072
|
+
if (format === "compact") {
|
|
2073
|
+
const summaryParts = relevantMemories.map((mem, idx) => {
|
|
2074
|
+
const title = mem.title ? `"${mem.title}"` : `Memory ${idx + 1}`;
|
|
2075
|
+
const type = mem.type ? ` [${mem.type}]` : "";
|
|
2076
|
+
const relevancePercent = Math.round((mem.relevance ?? 0) * 100);
|
|
2077
|
+
const content = mem.content || "(no content)";
|
|
2078
|
+
const tags = mem.tags && mem.tags.length > 0 ? `
|
|
2079
|
+
Tags: ${mem.tags.join(", ")}` : "";
|
|
2080
|
+
return `${idx + 1}. ${title}${type} (${relevancePercent}% relevant)
|
|
2081
|
+
${content}${tags}`;
|
|
2082
|
+
});
|
|
2083
|
+
formattedMemories = summaryParts.join("\n\n---\n\n");
|
|
2084
|
+
contextSummary = `Found ${relevantMemories.length} relevant memories for query: "${args.query}"`;
|
|
2085
|
+
} else {
|
|
2086
|
+
formattedMemories = relevantMemories;
|
|
2087
|
+
contextSummary = `Retrieved ${relevantMemories.length} memories with relevance >= ${Math.round(minRelevance * 100)}%`;
|
|
2088
|
+
}
|
|
2089
|
+
const result = {
|
|
2090
|
+
query: args.query,
|
|
2091
|
+
memories: formattedMemories,
|
|
2092
|
+
total: relevantMemories.length,
|
|
2093
|
+
min_relevance: minRelevance,
|
|
2094
|
+
context_summary: contextSummary
|
|
2095
|
+
};
|
|
2096
|
+
return JSON.stringify(result, null, 2);
|
|
2097
|
+
} catch (error) {
|
|
2098
|
+
logger.error("Failed to query memories:", error);
|
|
2099
|
+
throw new Error(`Failed to query memories: ${error instanceof Error ? error.message : String(error)}`);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
// src/tools/create-relationship.ts
|
|
2104
|
+
var createRelationshipTool = {
|
|
2105
|
+
name: "remember_create_relationship",
|
|
2106
|
+
description: `Create a relationship connecting 2 or more memories.
|
|
2107
|
+
|
|
2108
|
+
Relationships describe how memories are connected with free-form types.
|
|
2109
|
+
Each relationship has an observation (description), strength, and confidence.
|
|
2110
|
+
Bidirectional: connected memories are automatically updated with the relationship ID.
|
|
2111
|
+
|
|
2112
|
+
Examples:
|
|
2113
|
+
- "The Yosemite trip inspired my Sequoia planning"
|
|
2114
|
+
- "My tent purchase was caused by the camping trip"
|
|
2115
|
+
- "This recipe contradicts my previous cooking notes"
|
|
2116
|
+
`,
|
|
2117
|
+
inputSchema: {
|
|
2118
|
+
type: "object",
|
|
2119
|
+
properties: {
|
|
2120
|
+
memory_ids: {
|
|
2121
|
+
type: "array",
|
|
2122
|
+
items: { type: "string" },
|
|
2123
|
+
description: "Array of 2 or more memory IDs to connect",
|
|
2124
|
+
minItems: 2
|
|
2125
|
+
},
|
|
2126
|
+
relationship_type: {
|
|
2127
|
+
type: "string",
|
|
2128
|
+
description: 'Type of relationship (free-form): "inspired_by", "contradicts", "caused_by", "related_to", etc.'
|
|
2129
|
+
},
|
|
2130
|
+
observation: {
|
|
2131
|
+
type: "string",
|
|
2132
|
+
description: "Description of the connection (will be vectorized for semantic search)"
|
|
2133
|
+
},
|
|
2134
|
+
strength: {
|
|
2135
|
+
type: "number",
|
|
2136
|
+
description: "Strength of the relationship (0-1, default: 0.5)",
|
|
2137
|
+
minimum: 0,
|
|
2138
|
+
maximum: 1
|
|
2139
|
+
},
|
|
2140
|
+
confidence: {
|
|
2141
|
+
type: "number",
|
|
2142
|
+
description: "Confidence in this relationship (0-1, default: 0.8)",
|
|
2143
|
+
minimum: 0,
|
|
2144
|
+
maximum: 1
|
|
2145
|
+
},
|
|
2146
|
+
tags: {
|
|
2147
|
+
type: "array",
|
|
2148
|
+
items: { type: "string" },
|
|
2149
|
+
description: "Tags for organization"
|
|
2150
|
+
}
|
|
2151
|
+
},
|
|
2152
|
+
required: ["memory_ids", "relationship_type", "observation"]
|
|
2153
|
+
}
|
|
2154
|
+
};
|
|
2155
|
+
async function handleCreateRelationship(args, userId, context) {
|
|
2156
|
+
try {
|
|
2157
|
+
logger.info("Creating relationship", {
|
|
2158
|
+
userId,
|
|
2159
|
+
type: args.relationship_type,
|
|
2160
|
+
memoryCount: args.memory_ids.length
|
|
2161
|
+
});
|
|
2162
|
+
if (args.memory_ids.length < 2) {
|
|
2163
|
+
throw new Error("At least 2 memory IDs are required to create a relationship");
|
|
2164
|
+
}
|
|
2165
|
+
await ensureMemoryCollection(userId);
|
|
2166
|
+
const collection = getMemoryCollection(userId);
|
|
2167
|
+
const memoryChecks = await Promise.all(
|
|
2168
|
+
args.memory_ids.map(async (memoryId) => {
|
|
2169
|
+
try {
|
|
2170
|
+
const memory = await collection.query.fetchObjectById(memoryId, {
|
|
2171
|
+
returnProperties: ["user_id", "doc_type", "relationships"]
|
|
2172
|
+
});
|
|
2173
|
+
if (!memory) {
|
|
2174
|
+
return { memoryId, error: "Memory not found" };
|
|
2175
|
+
}
|
|
2176
|
+
if (memory.properties.user_id !== userId) {
|
|
2177
|
+
return { memoryId, error: "Unauthorized: Memory belongs to another user" };
|
|
2178
|
+
}
|
|
2179
|
+
if (memory.properties.doc_type !== "memory") {
|
|
2180
|
+
return { memoryId, error: "Cannot create relationship with non-memory document" };
|
|
2181
|
+
}
|
|
2182
|
+
return {
|
|
2183
|
+
memoryId,
|
|
2184
|
+
memory,
|
|
2185
|
+
relationships: memory.properties.relationships || []
|
|
2186
|
+
};
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
return { memoryId, error: `Failed to fetch memory: ${error}` };
|
|
2189
|
+
}
|
|
2190
|
+
})
|
|
2191
|
+
);
|
|
2192
|
+
const errors = memoryChecks.filter((check) => check.error);
|
|
2193
|
+
if (errors.length > 0) {
|
|
2194
|
+
const errorMessages = errors.map((e) => `${e.memoryId}: ${e.error}`).join("; ");
|
|
2195
|
+
throw new Error(`Memory validation failed: ${errorMessages}`);
|
|
2196
|
+
}
|
|
2197
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2198
|
+
const relationship = {
|
|
2199
|
+
// Core identity
|
|
2200
|
+
user_id: userId,
|
|
2201
|
+
doc_type: "relationship",
|
|
2202
|
+
// Connection
|
|
2203
|
+
memory_ids: args.memory_ids,
|
|
2204
|
+
relationship_type: args.relationship_type,
|
|
2205
|
+
// Observation
|
|
2206
|
+
observation: args.observation,
|
|
2207
|
+
strength: args.strength ?? 0.5,
|
|
2208
|
+
confidence: args.confidence ?? 0.8,
|
|
2209
|
+
// Context
|
|
2210
|
+
context: {
|
|
2211
|
+
timestamp: now,
|
|
2212
|
+
source: {
|
|
2213
|
+
type: "api",
|
|
2214
|
+
platform: "mcp"
|
|
2215
|
+
},
|
|
2216
|
+
summary: context?.summary || "Relationship created via MCP",
|
|
2217
|
+
conversation_id: context?.conversation_id,
|
|
2218
|
+
...context
|
|
2219
|
+
},
|
|
2220
|
+
// Metadata
|
|
2221
|
+
created_at: now,
|
|
2222
|
+
updated_at: now,
|
|
2223
|
+
version: 1,
|
|
2224
|
+
tags: args.tags || []
|
|
2225
|
+
};
|
|
2226
|
+
const relationshipId = await collection.data.insert(relationship);
|
|
2227
|
+
logger.info("Relationship created, updating connected memories", {
|
|
2228
|
+
relationshipId,
|
|
2229
|
+
userId
|
|
2230
|
+
});
|
|
2231
|
+
const updatePromises = memoryChecks.filter((check) => !check.error && check.memory).map(async (check) => {
|
|
2232
|
+
try {
|
|
2233
|
+
const existingRelationships = check.relationships || [];
|
|
2234
|
+
const updatedRelationships = [...existingRelationships, relationshipId];
|
|
2235
|
+
await collection.data.update({
|
|
2236
|
+
id: check.memoryId,
|
|
2237
|
+
properties: {
|
|
2238
|
+
relationships: updatedRelationships,
|
|
2239
|
+
updated_at: now
|
|
2240
|
+
}
|
|
2241
|
+
});
|
|
2242
|
+
return { memoryId: check.memoryId, success: true };
|
|
2243
|
+
} catch (error) {
|
|
2244
|
+
logger.warn(`Failed to update memory ${check.memoryId} with relationship:`, error);
|
|
2245
|
+
return { memoryId: check.memoryId, success: false, error };
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
const updateResults = await Promise.all(updatePromises);
|
|
2249
|
+
const failedUpdates = updateResults.filter((r) => !r.success);
|
|
2250
|
+
if (failedUpdates.length > 0) {
|
|
2251
|
+
logger.warn("Some memory updates failed", { failedUpdates });
|
|
2252
|
+
}
|
|
2253
|
+
logger.info("Relationship created successfully", {
|
|
2254
|
+
relationshipId,
|
|
2255
|
+
userId,
|
|
2256
|
+
updatedMemories: updateResults.filter((r) => r.success).length
|
|
2257
|
+
});
|
|
2258
|
+
const response = {
|
|
2259
|
+
relationship_id: relationshipId,
|
|
2260
|
+
memory_ids: args.memory_ids,
|
|
2261
|
+
relationship_type: args.relationship_type,
|
|
2262
|
+
created_at: now,
|
|
2263
|
+
message: `Relationship created successfully with ID: ${relationshipId}. Connected ${args.memory_ids.length} memories.`
|
|
2264
|
+
};
|
|
2265
|
+
return JSON.stringify(response, null, 2);
|
|
2266
|
+
} catch (error) {
|
|
2267
|
+
logger.error("Failed to create relationship:", error);
|
|
2268
|
+
throw new Error(`Failed to create relationship: ${error instanceof Error ? error.message : String(error)}`);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
// src/tools/update-relationship.ts
|
|
2273
|
+
var updateRelationshipTool = {
|
|
2274
|
+
name: "remember_update_relationship",
|
|
2275
|
+
description: `Update an existing relationship with partial updates.
|
|
2276
|
+
|
|
2277
|
+
Supports updating relationship_type, observation, strength, confidence, and tags.
|
|
2278
|
+
Version number is automatically incremented and updated_at is set.
|
|
2279
|
+
Only provided fields are updated (partial updates supported).
|
|
2280
|
+
|
|
2281
|
+
Examples:
|
|
2282
|
+
- "Update that relationship to increase the strength"
|
|
2283
|
+
- "Change the observation text for the camping relationship"
|
|
2284
|
+
- "Add tags to the inspiration relationship"
|
|
2285
|
+
`,
|
|
2286
|
+
inputSchema: {
|
|
2287
|
+
type: "object",
|
|
2288
|
+
properties: {
|
|
2289
|
+
relationship_id: {
|
|
2290
|
+
type: "string",
|
|
2291
|
+
description: "ID of the relationship to update"
|
|
2292
|
+
},
|
|
2293
|
+
relationship_type: {
|
|
2294
|
+
type: "string",
|
|
2295
|
+
description: "Updated relationship type"
|
|
2296
|
+
},
|
|
2297
|
+
observation: {
|
|
2298
|
+
type: "string",
|
|
2299
|
+
description: "Updated observation/description"
|
|
2300
|
+
},
|
|
2301
|
+
strength: {
|
|
2302
|
+
type: "number",
|
|
2303
|
+
description: "Updated strength (0-1)",
|
|
2304
|
+
minimum: 0,
|
|
2305
|
+
maximum: 1
|
|
2306
|
+
},
|
|
2307
|
+
confidence: {
|
|
2308
|
+
type: "number",
|
|
2309
|
+
description: "Updated confidence (0-1)",
|
|
2310
|
+
minimum: 0,
|
|
2311
|
+
maximum: 1
|
|
2312
|
+
},
|
|
2313
|
+
tags: {
|
|
2314
|
+
type: "array",
|
|
2315
|
+
items: { type: "string" },
|
|
2316
|
+
description: "Updated tags (replaces existing tags)"
|
|
2317
|
+
}
|
|
2318
|
+
},
|
|
2319
|
+
required: ["relationship_id"]
|
|
2320
|
+
}
|
|
2321
|
+
};
|
|
2322
|
+
async function handleUpdateRelationship(args, userId) {
|
|
2323
|
+
try {
|
|
2324
|
+
logger.info("Updating relationship", { userId, relationshipId: args.relationship_id });
|
|
2325
|
+
const collection = getMemoryCollection(userId);
|
|
2326
|
+
const existingRelationship = await collection.query.fetchObjectById(args.relationship_id, {
|
|
2327
|
+
returnProperties: ["user_id", "doc_type", "version", "relationship_type", "strength", "confidence"]
|
|
2328
|
+
});
|
|
2329
|
+
if (!existingRelationship) {
|
|
2330
|
+
throw new Error(`Relationship not found: ${args.relationship_id}`);
|
|
2331
|
+
}
|
|
2332
|
+
if (existingRelationship.properties.user_id !== userId) {
|
|
2333
|
+
throw new Error("Unauthorized: Cannot update another user's relationship");
|
|
2334
|
+
}
|
|
2335
|
+
if (existingRelationship.properties.doc_type !== "relationship") {
|
|
2336
|
+
throw new Error("Cannot update memories using this tool. Use remember_update_memory instead.");
|
|
2337
|
+
}
|
|
2338
|
+
const updates = {};
|
|
2339
|
+
const updatedFields = [];
|
|
2340
|
+
if (args.relationship_type !== void 0) {
|
|
2341
|
+
updates.relationship_type = args.relationship_type;
|
|
2342
|
+
updatedFields.push("relationship_type");
|
|
2343
|
+
}
|
|
2344
|
+
if (args.observation !== void 0) {
|
|
2345
|
+
updates.observation = args.observation;
|
|
2346
|
+
updatedFields.push("observation");
|
|
2347
|
+
}
|
|
2348
|
+
if (args.strength !== void 0) {
|
|
2349
|
+
if (args.strength < 0 || args.strength > 1) {
|
|
2350
|
+
throw new Error("Strength must be between 0 and 1");
|
|
2351
|
+
}
|
|
2352
|
+
updates.strength = args.strength;
|
|
2353
|
+
updatedFields.push("strength");
|
|
2354
|
+
}
|
|
2355
|
+
if (args.confidence !== void 0) {
|
|
2356
|
+
if (args.confidence < 0 || args.confidence > 1) {
|
|
2357
|
+
throw new Error("Confidence must be between 0 and 1");
|
|
2358
|
+
}
|
|
2359
|
+
updates.confidence = args.confidence;
|
|
2360
|
+
updatedFields.push("confidence");
|
|
2361
|
+
}
|
|
2362
|
+
if (args.tags !== void 0) {
|
|
2363
|
+
updates.tags = args.tags;
|
|
2364
|
+
updatedFields.push("tags");
|
|
2365
|
+
}
|
|
2366
|
+
if (updatedFields.length === 0) {
|
|
2367
|
+
throw new Error("No fields provided for update. At least one field must be specified.");
|
|
2368
|
+
}
|
|
2369
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2370
|
+
updates.updated_at = now;
|
|
2371
|
+
updates.version = existingRelationship.properties.version + 1;
|
|
2372
|
+
await collection.data.update({
|
|
2373
|
+
id: args.relationship_id,
|
|
2374
|
+
properties: updates
|
|
2375
|
+
});
|
|
2376
|
+
logger.info("Relationship updated successfully", {
|
|
2377
|
+
userId,
|
|
2378
|
+
relationshipId: args.relationship_id,
|
|
2379
|
+
version: updates.version,
|
|
2380
|
+
updatedFields
|
|
2381
|
+
});
|
|
2382
|
+
const result = {
|
|
2383
|
+
relationship_id: args.relationship_id,
|
|
2384
|
+
updated_at: now,
|
|
2385
|
+
version: updates.version,
|
|
2386
|
+
updated_fields: updatedFields,
|
|
2387
|
+
message: `Relationship updated successfully. Updated fields: ${updatedFields.join(", ")}`
|
|
2388
|
+
};
|
|
2389
|
+
return JSON.stringify(result, null, 2);
|
|
2390
|
+
} catch (error) {
|
|
2391
|
+
logger.error("Failed to update relationship:", error);
|
|
2392
|
+
throw new Error(`Failed to update relationship: ${error instanceof Error ? error.message : String(error)}`);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
// src/tools/search-relationship.ts
|
|
2397
|
+
var searchRelationshipTool = {
|
|
2398
|
+
name: "remember_search_relationship",
|
|
2399
|
+
description: `Search relationships by observation text or relationship type.
|
|
2400
|
+
|
|
2401
|
+
Uses semantic search on relationship observations to find connections.
|
|
2402
|
+
Can filter by relationship type, strength, and tags.
|
|
2403
|
+
Returns relationships with their connected memory IDs.
|
|
2404
|
+
|
|
2405
|
+
Examples:
|
|
2406
|
+
- "Find relationships about inspiration"
|
|
2407
|
+
- "Search for contradicting relationships"
|
|
2408
|
+
- "Show me all 'caused_by' relationships"
|
|
2409
|
+
`,
|
|
2410
|
+
inputSchema: {
|
|
2411
|
+
type: "object",
|
|
2412
|
+
properties: {
|
|
2413
|
+
query: {
|
|
2414
|
+
type: "string",
|
|
2415
|
+
description: "Search query (semantic search on observation field)"
|
|
2416
|
+
},
|
|
2417
|
+
relationship_types: {
|
|
2418
|
+
type: "array",
|
|
2419
|
+
items: { type: "string" },
|
|
2420
|
+
description: 'Filter by relationship types (e.g., ["inspired_by", "caused_by"])'
|
|
2421
|
+
},
|
|
2422
|
+
strength_min: {
|
|
2423
|
+
type: "number",
|
|
2424
|
+
description: "Minimum strength (0-1)",
|
|
2425
|
+
minimum: 0,
|
|
2426
|
+
maximum: 1
|
|
2427
|
+
},
|
|
2428
|
+
confidence_min: {
|
|
2429
|
+
type: "number",
|
|
2430
|
+
description: "Minimum confidence (0-1)",
|
|
2431
|
+
minimum: 0,
|
|
2432
|
+
maximum: 1
|
|
2433
|
+
},
|
|
2434
|
+
tags: {
|
|
2435
|
+
type: "array",
|
|
2436
|
+
items: { type: "string" },
|
|
2437
|
+
description: "Filter by tags"
|
|
2438
|
+
},
|
|
2439
|
+
limit: {
|
|
2440
|
+
type: "number",
|
|
2441
|
+
description: "Maximum number of results (default: 10)",
|
|
2442
|
+
minimum: 1,
|
|
2443
|
+
maximum: 100
|
|
2444
|
+
},
|
|
2445
|
+
offset: {
|
|
2446
|
+
type: "number",
|
|
2447
|
+
description: "Offset for pagination (default: 0)",
|
|
2448
|
+
minimum: 0
|
|
2449
|
+
}
|
|
2450
|
+
},
|
|
2451
|
+
required: ["query"]
|
|
2452
|
+
}
|
|
2453
|
+
};
|
|
2454
|
+
async function handleSearchRelationship(args, userId) {
|
|
2455
|
+
try {
|
|
2456
|
+
logger.info("Searching relationships", {
|
|
2457
|
+
userId,
|
|
2458
|
+
query: args.query,
|
|
2459
|
+
types: args.relationship_types
|
|
2460
|
+
});
|
|
2461
|
+
const collection = getMemoryCollection(userId);
|
|
2462
|
+
const limit = args.limit ?? 10;
|
|
2463
|
+
const offset = args.offset ?? 0;
|
|
2464
|
+
const whereFilters = [
|
|
2465
|
+
{
|
|
2466
|
+
path: "doc_type",
|
|
2467
|
+
operator: "Equal",
|
|
2468
|
+
valueText: "relationship"
|
|
2469
|
+
}
|
|
2470
|
+
];
|
|
2471
|
+
if (args.relationship_types && args.relationship_types.length > 0) {
|
|
2472
|
+
if (args.relationship_types.length === 1) {
|
|
2473
|
+
whereFilters.push({
|
|
2474
|
+
path: "relationship_type",
|
|
2475
|
+
operator: "Equal",
|
|
2476
|
+
valueText: args.relationship_types[0]
|
|
2477
|
+
});
|
|
2478
|
+
} else {
|
|
2479
|
+
whereFilters.push({
|
|
2480
|
+
operator: "Or",
|
|
2481
|
+
operands: args.relationship_types.map((type) => ({
|
|
2482
|
+
path: "relationship_type",
|
|
2483
|
+
operator: "Equal",
|
|
2484
|
+
valueText: type
|
|
2485
|
+
}))
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
if (args.strength_min !== void 0) {
|
|
2490
|
+
whereFilters.push({
|
|
2491
|
+
path: "strength",
|
|
2492
|
+
operator: "GreaterThanEqual",
|
|
2493
|
+
valueNumber: args.strength_min
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
if (args.confidence_min !== void 0) {
|
|
2497
|
+
whereFilters.push({
|
|
2498
|
+
path: "confidence",
|
|
2499
|
+
operator: "GreaterThanEqual",
|
|
2500
|
+
valueNumber: args.confidence_min
|
|
2501
|
+
});
|
|
2502
|
+
}
|
|
2503
|
+
if (args.tags && args.tags.length > 0) {
|
|
2504
|
+
whereFilters.push({
|
|
2505
|
+
path: "tags",
|
|
2506
|
+
operator: "ContainsAny",
|
|
2507
|
+
valueTextArray: args.tags
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
const searchOptions = {
|
|
2511
|
+
alpha: 1,
|
|
2512
|
+
// Pure semantic search for relationships
|
|
2513
|
+
limit: limit + offset
|
|
2514
|
+
// Get extra for offset
|
|
2515
|
+
};
|
|
2516
|
+
if (whereFilters.length > 0) {
|
|
2517
|
+
searchOptions.filters = whereFilters.length > 1 ? {
|
|
2518
|
+
operator: "And",
|
|
2519
|
+
operands: whereFilters
|
|
2520
|
+
} : whereFilters[0];
|
|
2521
|
+
}
|
|
2522
|
+
const results = await collection.query.hybrid(args.query, searchOptions);
|
|
2523
|
+
const paginatedResults = results.objects.slice(offset, offset + limit);
|
|
2524
|
+
const relationships = paginatedResults.map((obj) => ({
|
|
2525
|
+
id: obj.uuid,
|
|
2526
|
+
user_id: obj.properties.user_id,
|
|
2527
|
+
doc_type: "relationship",
|
|
2528
|
+
memory_ids: obj.properties.memory_ids || [],
|
|
2529
|
+
relationship_type: obj.properties.relationship_type,
|
|
2530
|
+
observation: obj.properties.observation,
|
|
2531
|
+
strength: obj.properties.strength,
|
|
2532
|
+
confidence: obj.properties.confidence,
|
|
2533
|
+
context: obj.properties.context || {
|
|
2534
|
+
timestamp: obj.properties.created_at,
|
|
2535
|
+
source: { type: "api", platform: "mcp" }
|
|
2536
|
+
},
|
|
2537
|
+
created_at: obj.properties.created_at,
|
|
2538
|
+
updated_at: obj.properties.updated_at,
|
|
2539
|
+
version: obj.properties.version,
|
|
2540
|
+
tags: obj.properties.tags || []
|
|
2541
|
+
}));
|
|
2542
|
+
logger.info("Relationship search completed", {
|
|
2543
|
+
userId,
|
|
2544
|
+
found: relationships.length,
|
|
2545
|
+
total: results.objects.length
|
|
2546
|
+
});
|
|
2547
|
+
const result = {
|
|
2548
|
+
relationships,
|
|
2549
|
+
total: results.objects.length,
|
|
2550
|
+
offset,
|
|
2551
|
+
limit,
|
|
2552
|
+
message: `Found ${relationships.length} relationship(s) matching query "${args.query}"`
|
|
2553
|
+
};
|
|
2554
|
+
return JSON.stringify(result, null, 2);
|
|
2555
|
+
} catch (error) {
|
|
2556
|
+
logger.error("Failed to search relationships:", error);
|
|
2557
|
+
throw new Error(`Failed to search relationships: ${error instanceof Error ? error.message : String(error)}`);
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
// src/tools/delete-relationship.ts
|
|
2562
|
+
var deleteRelationshipTool = {
|
|
2563
|
+
name: "remember_delete_relationship",
|
|
2564
|
+
description: `Delete a relationship from your collection.
|
|
2565
|
+
|
|
2566
|
+
Automatically removes the relationship reference from all connected memories.
|
|
2567
|
+
This action cannot be undone.
|
|
2568
|
+
|
|
2569
|
+
Examples:
|
|
2570
|
+
- "Delete that inspiration relationship"
|
|
2571
|
+
- "Remove the connection between those memories"
|
|
2572
|
+
`,
|
|
2573
|
+
inputSchema: {
|
|
2574
|
+
type: "object",
|
|
2575
|
+
properties: {
|
|
2576
|
+
relationship_id: {
|
|
2577
|
+
type: "string",
|
|
2578
|
+
description: "ID of the relationship to delete"
|
|
2579
|
+
}
|
|
2580
|
+
},
|
|
2581
|
+
required: ["relationship_id"]
|
|
2582
|
+
}
|
|
2583
|
+
};
|
|
2584
|
+
async function handleDeleteRelationship(args, userId) {
|
|
2585
|
+
try {
|
|
2586
|
+
logger.info("Deleting relationship", { userId, relationshipId: args.relationship_id });
|
|
2587
|
+
const collection = getMemoryCollection(userId);
|
|
2588
|
+
const relationship = await collection.query.fetchObjectById(args.relationship_id, {
|
|
2589
|
+
returnProperties: ["user_id", "doc_type", "memory_ids", "relationship_type"]
|
|
2590
|
+
});
|
|
2591
|
+
if (!relationship) {
|
|
2592
|
+
throw new Error(`Relationship not found: ${args.relationship_id}`);
|
|
2593
|
+
}
|
|
2594
|
+
if (relationship.properties.user_id !== userId) {
|
|
2595
|
+
throw new Error("Unauthorized: Cannot delete another user's relationship");
|
|
2596
|
+
}
|
|
2597
|
+
if (relationship.properties.doc_type !== "relationship") {
|
|
2598
|
+
throw new Error("Cannot delete memories using this tool. Use remember_delete_memory instead.");
|
|
2599
|
+
}
|
|
2600
|
+
const memoryIds = relationship.properties.memory_ids || [];
|
|
2601
|
+
let memoriesUpdated = 0;
|
|
2602
|
+
if (memoryIds.length > 0) {
|
|
2603
|
+
logger.info("Cleaning up relationship references from connected memories", {
|
|
2604
|
+
relationshipId: args.relationship_id,
|
|
2605
|
+
memoryCount: memoryIds.length
|
|
2606
|
+
});
|
|
2607
|
+
const updatePromises = memoryIds.map(async (memoryId) => {
|
|
2608
|
+
try {
|
|
2609
|
+
const memory = await collection.query.fetchObjectById(memoryId, {
|
|
2610
|
+
returnProperties: ["relationships", "doc_type"]
|
|
2611
|
+
});
|
|
2612
|
+
if (!memory || memory.properties.doc_type !== "memory") {
|
|
2613
|
+
logger.warn(`Memory ${memoryId} not found or not a memory, skipping cleanup`);
|
|
2614
|
+
return { memoryId, success: false };
|
|
2615
|
+
}
|
|
2616
|
+
const currentRelationships = memory.properties.relationships || [];
|
|
2617
|
+
const updatedRelationships = currentRelationships.filter(
|
|
2618
|
+
(relId) => relId !== args.relationship_id
|
|
2619
|
+
);
|
|
2620
|
+
if (updatedRelationships.length !== currentRelationships.length) {
|
|
2621
|
+
await collection.data.update({
|
|
2622
|
+
id: memoryId,
|
|
2623
|
+
properties: {
|
|
2624
|
+
relationships: updatedRelationships,
|
|
2625
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2626
|
+
}
|
|
2627
|
+
});
|
|
2628
|
+
return { memoryId, success: true };
|
|
2629
|
+
}
|
|
2630
|
+
return { memoryId, success: false };
|
|
2631
|
+
} catch (error) {
|
|
2632
|
+
logger.warn(`Failed to update memory ${memoryId}:`, error);
|
|
2633
|
+
return { memoryId, success: false, error };
|
|
2634
|
+
}
|
|
2635
|
+
});
|
|
2636
|
+
const updateResults = await Promise.all(updatePromises);
|
|
2637
|
+
memoriesUpdated = updateResults.filter((r) => r.success).length;
|
|
2638
|
+
logger.info("Memory cleanup completed", {
|
|
2639
|
+
relationshipId: args.relationship_id,
|
|
2640
|
+
memoriesUpdated,
|
|
2641
|
+
totalMemories: memoryIds.length
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
await collection.data.deleteById(args.relationship_id);
|
|
2645
|
+
logger.info("Relationship deleted successfully", {
|
|
2646
|
+
userId,
|
|
2647
|
+
relationshipId: args.relationship_id,
|
|
2648
|
+
memoriesUpdated
|
|
2649
|
+
});
|
|
2650
|
+
const result = {
|
|
2651
|
+
relationship_id: args.relationship_id,
|
|
2652
|
+
deleted: true,
|
|
2653
|
+
memories_updated: memoriesUpdated,
|
|
2654
|
+
message: `Relationship deleted successfully${memoriesUpdated > 0 ? ` (${memoriesUpdated} memories updated)` : ""}`
|
|
2655
|
+
};
|
|
2656
|
+
return JSON.stringify(result, null, 2);
|
|
2657
|
+
} catch (error) {
|
|
2658
|
+
logger.error("Failed to delete relationship:", error);
|
|
2659
|
+
throw new Error(`Failed to delete relationship: ${error instanceof Error ? error.message : String(error)}`);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
// src/server-factory.ts
|
|
2664
|
+
var databasesInitialized = false;
|
|
2665
|
+
var initializationPromise = null;
|
|
2666
|
+
async function ensureDatabasesInitialized() {
|
|
2667
|
+
if (databasesInitialized) {
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
if (initializationPromise) {
|
|
2671
|
+
return initializationPromise;
|
|
2672
|
+
}
|
|
2673
|
+
initializationPromise = (async () => {
|
|
2674
|
+
try {
|
|
2675
|
+
logger.info("Initializing databases...");
|
|
2676
|
+
await initWeaviateClient();
|
|
2677
|
+
initFirestore();
|
|
2678
|
+
databasesInitialized = true;
|
|
2679
|
+
logger.info("Databases initialized successfully");
|
|
2680
|
+
} catch (error) {
|
|
2681
|
+
logger.error("Database initialization failed:", error);
|
|
2682
|
+
throw error;
|
|
2683
|
+
} finally {
|
|
2684
|
+
initializationPromise = null;
|
|
2685
|
+
}
|
|
2686
|
+
})();
|
|
2687
|
+
return initializationPromise;
|
|
2688
|
+
}
|
|
2689
|
+
function createServer(accessToken, userId, options = {}) {
|
|
2690
|
+
if (!accessToken) {
|
|
2691
|
+
throw new Error("accessToken is required");
|
|
2692
|
+
}
|
|
2693
|
+
if (!userId) {
|
|
2694
|
+
throw new Error("userId is required");
|
|
2695
|
+
}
|
|
2696
|
+
logger.debug("Creating server instance", { userId });
|
|
2697
|
+
ensureDatabasesInitialized().catch((error) => {
|
|
2698
|
+
logger.error("Failed to initialize databases:", error);
|
|
2699
|
+
});
|
|
2700
|
+
const server = new Server(
|
|
2701
|
+
{
|
|
2702
|
+
name: options.name || "remember-mcp",
|
|
2703
|
+
version: options.version || "0.2.0"
|
|
2704
|
+
},
|
|
2705
|
+
{
|
|
2706
|
+
capabilities: {
|
|
2707
|
+
tools: {}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
);
|
|
2711
|
+
registerHandlers(server, userId, accessToken);
|
|
2712
|
+
return server;
|
|
2713
|
+
}
|
|
2714
|
+
function registerHandlers(server, userId, accessToken) {
|
|
2715
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2716
|
+
return {
|
|
2717
|
+
tools: [
|
|
2718
|
+
{
|
|
2719
|
+
name: "health_check",
|
|
2720
|
+
description: "Check server health and database connections",
|
|
2721
|
+
inputSchema: {
|
|
2722
|
+
type: "object",
|
|
2723
|
+
properties: {}
|
|
2724
|
+
}
|
|
2725
|
+
},
|
|
2726
|
+
// Memory tools
|
|
2727
|
+
createMemoryTool,
|
|
2728
|
+
searchMemoryTool,
|
|
2729
|
+
deleteMemoryTool,
|
|
2730
|
+
updateMemoryTool,
|
|
2731
|
+
findSimilarTool,
|
|
2732
|
+
queryMemoryTool,
|
|
2733
|
+
// Relationship tools
|
|
2734
|
+
createRelationshipTool,
|
|
2735
|
+
updateRelationshipTool,
|
|
2736
|
+
searchRelationshipTool,
|
|
2737
|
+
deleteRelationshipTool
|
|
2738
|
+
]
|
|
2739
|
+
};
|
|
2740
|
+
});
|
|
2741
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2742
|
+
const { name, arguments: args } = request.params;
|
|
2743
|
+
try {
|
|
2744
|
+
let result;
|
|
2745
|
+
switch (name) {
|
|
2746
|
+
case "health_check":
|
|
2747
|
+
result = await handleHealthCheck(userId);
|
|
2748
|
+
break;
|
|
2749
|
+
case "remember_create_memory":
|
|
2750
|
+
result = await handleCreateMemory(args, userId);
|
|
2751
|
+
break;
|
|
2752
|
+
case "remember_search_memory":
|
|
2753
|
+
result = await handleSearchMemory(args, userId);
|
|
2754
|
+
break;
|
|
2755
|
+
case "remember_delete_memory":
|
|
2756
|
+
result = await handleDeleteMemory(args, userId);
|
|
2757
|
+
break;
|
|
2758
|
+
case "remember_update_memory":
|
|
2759
|
+
result = await handleUpdateMemory(args, userId);
|
|
2760
|
+
break;
|
|
2761
|
+
case "remember_find_similar":
|
|
2762
|
+
result = await handleFindSimilar(args, userId);
|
|
2763
|
+
break;
|
|
2764
|
+
case "remember_query_memory":
|
|
2765
|
+
result = await handleQueryMemory(args, userId);
|
|
2766
|
+
break;
|
|
2767
|
+
case "remember_create_relationship":
|
|
2768
|
+
result = await handleCreateRelationship(args, userId);
|
|
2769
|
+
break;
|
|
2770
|
+
case "remember_update_relationship":
|
|
2771
|
+
result = await handleUpdateRelationship(args, userId);
|
|
2772
|
+
break;
|
|
2773
|
+
case "remember_search_relationship":
|
|
2774
|
+
result = await handleSearchRelationship(args, userId);
|
|
2775
|
+
break;
|
|
2776
|
+
case "remember_delete_relationship":
|
|
2777
|
+
result = await handleDeleteRelationship(args, userId);
|
|
1672
2778
|
break;
|
|
1673
2779
|
default:
|
|
1674
2780
|
throw new McpError(
|