@prmichaelsen/remember-mcp 0.1.0 → 0.2.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/src/server.ts CHANGED
@@ -17,6 +17,15 @@ import { logger } from './utils/logger.js';
17
17
  import { createMemoryTool, handleCreateMemory } from './tools/create-memory.js';
18
18
  import { searchMemoryTool, handleSearchMemory } from './tools/search-memory.js';
19
19
  import { deleteMemoryTool, handleDeleteMemory } from './tools/delete-memory.js';
20
+ import { updateMemoryTool, handleUpdateMemory } from './tools/update-memory.js';
21
+ import { findSimilarTool, handleFindSimilar } from './tools/find-similar.js';
22
+ import { queryMemoryTool, handleQueryMemory } from './tools/query-memory.js';
23
+
24
+ // Import relationship tools
25
+ import { createRelationshipTool, handleCreateRelationship } from './tools/create-relationship.js';
26
+ import { updateRelationshipTool, handleUpdateRelationship } from './tools/update-relationship.js';
27
+ import { searchRelationshipTool, handleSearchRelationship } from './tools/search-relationship.js';
28
+ import { deleteRelationshipTool, handleDeleteRelationship } from './tools/delete-relationship.js';
20
29
 
21
30
  /**
22
31
  * Initialize remember-mcp server
@@ -78,9 +87,18 @@ function registerHandlers(server: Server): void {
78
87
  properties: {},
79
88
  },
80
89
  },
90
+ // Memory tools
81
91
  createMemoryTool,
82
92
  searchMemoryTool,
83
93
  deleteMemoryTool,
94
+ updateMemoryTool,
95
+ findSimilarTool,
96
+ queryMemoryTool,
97
+ // Relationship tools
98
+ createRelationshipTool,
99
+ updateRelationshipTool,
100
+ searchRelationshipTool,
101
+ deleteRelationshipTool,
84
102
  ],
85
103
  };
86
104
  });
@@ -113,6 +131,34 @@ function registerHandlers(server: Server): void {
113
131
  result = await handleDeleteMemory(args as any, userId);
114
132
  break;
115
133
 
134
+ case 'remember_update_memory':
135
+ result = await handleUpdateMemory(args as any, userId);
136
+ break;
137
+
138
+ case 'remember_find_similar':
139
+ result = await handleFindSimilar(args as any, userId);
140
+ break;
141
+
142
+ case 'remember_query_memory':
143
+ result = await handleQueryMemory(args as any, userId);
144
+ break;
145
+
146
+ case 'remember_create_relationship':
147
+ result = await handleCreateRelationship(args as any, userId);
148
+ break;
149
+
150
+ case 'remember_update_relationship':
151
+ result = await handleUpdateRelationship(args as any, userId);
152
+ break;
153
+
154
+ case 'remember_search_relationship':
155
+ result = await handleSearchRelationship(args as any, userId);
156
+ break;
157
+
158
+ case 'remember_delete_relationship':
159
+ result = await handleDeleteRelationship(args as any, userId);
160
+ break;
161
+
116
162
  default:
117
163
  throw new McpError(
118
164
  ErrorCode.MethodNotFound,
@@ -0,0 +1,242 @@
1
+ /**
2
+ * remember_create_relationship tool
3
+ * Create a relationship connecting 2...N memories
4
+ */
5
+
6
+ import type { Relationship, MemoryContext } from '../types/memory.js';
7
+ import { ensureMemoryCollection, getMemoryCollection } from '../weaviate/schema.js';
8
+ import { logger } from '../utils/logger.js';
9
+
10
+ /**
11
+ * Tool definition for remember_create_relationship
12
+ */
13
+ export const createRelationshipTool = {
14
+ name: 'remember_create_relationship',
15
+ description: `Create a relationship connecting 2 or more memories.
16
+
17
+ Relationships describe how memories are connected with free-form types.
18
+ Each relationship has an observation (description), strength, and confidence.
19
+ Bidirectional: connected memories are automatically updated with the relationship ID.
20
+
21
+ Examples:
22
+ - "The Yosemite trip inspired my Sequoia planning"
23
+ - "My tent purchase was caused by the camping trip"
24
+ - "This recipe contradicts my previous cooking notes"
25
+ `,
26
+ inputSchema: {
27
+ type: 'object',
28
+ properties: {
29
+ memory_ids: {
30
+ type: 'array',
31
+ items: { type: 'string' },
32
+ description: 'Array of 2 or more memory IDs to connect',
33
+ minItems: 2,
34
+ },
35
+ relationship_type: {
36
+ type: 'string',
37
+ description: 'Type of relationship (free-form): "inspired_by", "contradicts", "caused_by", "related_to", etc.',
38
+ },
39
+ observation: {
40
+ type: 'string',
41
+ description: 'Description of the connection (will be vectorized for semantic search)',
42
+ },
43
+ strength: {
44
+ type: 'number',
45
+ description: 'Strength of the relationship (0-1, default: 0.5)',
46
+ minimum: 0,
47
+ maximum: 1,
48
+ },
49
+ confidence: {
50
+ type: 'number',
51
+ description: 'Confidence in this relationship (0-1, default: 0.8)',
52
+ minimum: 0,
53
+ maximum: 1,
54
+ },
55
+ tags: {
56
+ type: 'array',
57
+ items: { type: 'string' },
58
+ description: 'Tags for organization',
59
+ },
60
+ },
61
+ required: ['memory_ids', 'relationship_type', 'observation'],
62
+ },
63
+ };
64
+
65
+ /**
66
+ * Create relationship arguments
67
+ */
68
+ export interface CreateRelationshipArgs {
69
+ memory_ids: string[];
70
+ relationship_type: string;
71
+ observation: string;
72
+ strength?: number;
73
+ confidence?: number;
74
+ tags?: string[];
75
+ }
76
+
77
+ /**
78
+ * Create relationship result
79
+ */
80
+ export interface CreateRelationshipResult {
81
+ relationship_id: string;
82
+ memory_ids: string[];
83
+ relationship_type: string;
84
+ created_at: string;
85
+ message: string;
86
+ }
87
+
88
+ /**
89
+ * Handle remember_create_relationship tool
90
+ */
91
+ export async function handleCreateRelationship(
92
+ args: CreateRelationshipArgs,
93
+ userId: string,
94
+ context?: Partial<MemoryContext>
95
+ ): Promise<string> {
96
+ try {
97
+ logger.info('Creating relationship', {
98
+ userId,
99
+ type: args.relationship_type,
100
+ memoryCount: args.memory_ids.length
101
+ });
102
+
103
+ // Validate memory_ids count
104
+ if (args.memory_ids.length < 2) {
105
+ throw new Error('At least 2 memory IDs are required to create a relationship');
106
+ }
107
+
108
+ // Ensure collection exists
109
+ await ensureMemoryCollection(userId);
110
+ const collection = getMemoryCollection(userId);
111
+
112
+ // Verify all memories exist and belong to user
113
+ const memoryChecks = await Promise.all(
114
+ args.memory_ids.map(async (memoryId) => {
115
+ try {
116
+ const memory = await collection.query.fetchObjectById(memoryId, {
117
+ returnProperties: ['user_id', 'doc_type', 'relationships'],
118
+ });
119
+
120
+ if (!memory) {
121
+ return { memoryId, error: 'Memory not found' };
122
+ }
123
+
124
+ if (memory.properties.user_id !== userId) {
125
+ return { memoryId, error: 'Unauthorized: Memory belongs to another user' };
126
+ }
127
+
128
+ if (memory.properties.doc_type !== 'memory') {
129
+ return { memoryId, error: 'Cannot create relationship with non-memory document' };
130
+ }
131
+
132
+ return {
133
+ memoryId,
134
+ memory,
135
+ relationships: (memory.properties.relationships as string[]) || []
136
+ };
137
+ } catch (error) {
138
+ return { memoryId, error: `Failed to fetch memory: ${error}` };
139
+ }
140
+ })
141
+ );
142
+
143
+ // Check for errors
144
+ const errors = memoryChecks.filter(check => check.error);
145
+ if (errors.length > 0) {
146
+ const errorMessages = errors.map(e => `${e.memoryId}: ${e.error}`).join('; ');
147
+ throw new Error(`Memory validation failed: ${errorMessages}`);
148
+ }
149
+
150
+ // Build relationship object
151
+ const now = new Date().toISOString();
152
+ const relationship: Omit<Relationship, 'id'> = {
153
+ // Core identity
154
+ user_id: userId,
155
+ doc_type: 'relationship',
156
+
157
+ // Connection
158
+ memory_ids: args.memory_ids,
159
+ relationship_type: args.relationship_type,
160
+
161
+ // Observation
162
+ observation: args.observation,
163
+ strength: args.strength ?? 0.5,
164
+ confidence: args.confidence ?? 0.8,
165
+
166
+ // Context
167
+ context: {
168
+ timestamp: now,
169
+ source: {
170
+ type: 'api',
171
+ platform: 'mcp',
172
+ },
173
+ summary: context?.summary || 'Relationship created via MCP',
174
+ conversation_id: context?.conversation_id,
175
+ ...context,
176
+ },
177
+
178
+ // Metadata
179
+ created_at: now,
180
+ updated_at: now,
181
+ version: 1,
182
+ tags: args.tags || [],
183
+ };
184
+
185
+ // Insert relationship into Weaviate
186
+ const relationshipId = await collection.data.insert(relationship as any);
187
+
188
+ logger.info('Relationship created, updating connected memories', {
189
+ relationshipId,
190
+ userId
191
+ });
192
+
193
+ // Update all connected memories with bidirectional reference
194
+ const updatePromises = memoryChecks
195
+ .filter(check => !check.error && check.memory)
196
+ .map(async (check) => {
197
+ try {
198
+ const existingRelationships = check.relationships || [];
199
+ const updatedRelationships = [...existingRelationships, relationshipId];
200
+
201
+ await collection.data.update({
202
+ id: check.memoryId,
203
+ properties: {
204
+ relationships: updatedRelationships,
205
+ updated_at: now,
206
+ },
207
+ });
208
+
209
+ return { memoryId: check.memoryId, success: true };
210
+ } catch (error) {
211
+ logger.warn(`Failed to update memory ${check.memoryId} with relationship:`, error);
212
+ return { memoryId: check.memoryId, success: false, error };
213
+ }
214
+ });
215
+
216
+ const updateResults = await Promise.all(updatePromises);
217
+ const failedUpdates = updateResults.filter(r => !r.success);
218
+
219
+ if (failedUpdates.length > 0) {
220
+ logger.warn('Some memory updates failed', { failedUpdates });
221
+ }
222
+
223
+ logger.info('Relationship created successfully', {
224
+ relationshipId,
225
+ userId,
226
+ updatedMemories: updateResults.filter(r => r.success).length
227
+ });
228
+
229
+ const response: CreateRelationshipResult = {
230
+ relationship_id: relationshipId,
231
+ memory_ids: args.memory_ids,
232
+ relationship_type: args.relationship_type,
233
+ created_at: now,
234
+ message: `Relationship created successfully with ID: ${relationshipId}. Connected ${args.memory_ids.length} memories.`,
235
+ };
236
+
237
+ return JSON.stringify(response, null, 2);
238
+ } catch (error) {
239
+ logger.error('Failed to create relationship:', error);
240
+ throw new Error(`Failed to create relationship: ${error instanceof Error ? error.message : String(error)}`);
241
+ }
242
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * remember_delete_relationship tool
3
+ * Delete a relationship and clean up references in connected memories
4
+ */
5
+
6
+ import { getMemoryCollection } from '../weaviate/schema.js';
7
+ import { logger } from '../utils/logger.js';
8
+
9
+ /**
10
+ * Tool definition for remember_delete_relationship
11
+ */
12
+ export const deleteRelationshipTool = {
13
+ name: 'remember_delete_relationship',
14
+ description: `Delete a relationship from your collection.
15
+
16
+ Automatically removes the relationship reference from all connected memories.
17
+ This action cannot be undone.
18
+
19
+ Examples:
20
+ - "Delete that inspiration relationship"
21
+ - "Remove the connection between those memories"
22
+ `,
23
+ inputSchema: {
24
+ type: 'object',
25
+ properties: {
26
+ relationship_id: {
27
+ type: 'string',
28
+ description: 'ID of the relationship to delete',
29
+ },
30
+ },
31
+ required: ['relationship_id'],
32
+ },
33
+ };
34
+
35
+ /**
36
+ * Delete relationship arguments
37
+ */
38
+ export interface DeleteRelationshipArgs {
39
+ relationship_id: string;
40
+ }
41
+
42
+ /**
43
+ * Delete relationship result
44
+ */
45
+ export interface DeleteRelationshipResult {
46
+ relationship_id: string;
47
+ deleted: boolean;
48
+ memories_updated: number;
49
+ message: string;
50
+ }
51
+
52
+ /**
53
+ * Handle remember_delete_relationship tool
54
+ */
55
+ export async function handleDeleteRelationship(
56
+ args: DeleteRelationshipArgs,
57
+ userId: string
58
+ ): Promise<string> {
59
+ try {
60
+ logger.info('Deleting relationship', { userId, relationshipId: args.relationship_id });
61
+
62
+ const collection = getMemoryCollection(userId);
63
+
64
+ // Get relationship to verify ownership and get connected memory IDs
65
+ const relationship = await collection.query.fetchObjectById(args.relationship_id, {
66
+ returnProperties: ['user_id', 'doc_type', 'memory_ids', 'relationship_type'],
67
+ });
68
+
69
+ if (!relationship) {
70
+ throw new Error(`Relationship not found: ${args.relationship_id}`);
71
+ }
72
+
73
+ // Verify ownership
74
+ if (relationship.properties.user_id !== userId) {
75
+ throw new Error('Unauthorized: Cannot delete another user\'s relationship');
76
+ }
77
+
78
+ // Verify it's a relationship (not a memory)
79
+ if (relationship.properties.doc_type !== 'relationship') {
80
+ throw new Error('Cannot delete memories using this tool. Use remember_delete_memory instead.');
81
+ }
82
+
83
+ const memoryIds = (relationship.properties.memory_ids as string[]) || [];
84
+ let memoriesUpdated = 0;
85
+
86
+ // Remove relationship reference from all connected memories
87
+ if (memoryIds.length > 0) {
88
+ logger.info('Cleaning up relationship references from connected memories', {
89
+ relationshipId: args.relationship_id,
90
+ memoryCount: memoryIds.length,
91
+ });
92
+
93
+ const updatePromises = memoryIds.map(async (memoryId) => {
94
+ try {
95
+ // Get current memory to read relationships array
96
+ const memory = await collection.query.fetchObjectById(memoryId, {
97
+ returnProperties: ['relationships', 'doc_type'],
98
+ });
99
+
100
+ if (!memory || memory.properties.doc_type !== 'memory') {
101
+ logger.warn(`Memory ${memoryId} not found or not a memory, skipping cleanup`);
102
+ return { memoryId, success: false };
103
+ }
104
+
105
+ const currentRelationships = (memory.properties.relationships as string[]) || [];
106
+ const updatedRelationships = currentRelationships.filter(
107
+ (relId) => relId !== args.relationship_id
108
+ );
109
+
110
+ // Only update if there was a change
111
+ if (updatedRelationships.length !== currentRelationships.length) {
112
+ await collection.data.update({
113
+ id: memoryId,
114
+ properties: {
115
+ relationships: updatedRelationships,
116
+ updated_at: new Date().toISOString(),
117
+ },
118
+ });
119
+ return { memoryId, success: true };
120
+ }
121
+
122
+ return { memoryId, success: false };
123
+ } catch (error) {
124
+ logger.warn(`Failed to update memory ${memoryId}:`, error);
125
+ return { memoryId, success: false, error };
126
+ }
127
+ });
128
+
129
+ const updateResults = await Promise.all(updatePromises);
130
+ memoriesUpdated = updateResults.filter((r) => r.success).length;
131
+
132
+ logger.info('Memory cleanup completed', {
133
+ relationshipId: args.relationship_id,
134
+ memoriesUpdated,
135
+ totalMemories: memoryIds.length,
136
+ });
137
+ }
138
+
139
+ // Delete the relationship
140
+ await collection.data.deleteById(args.relationship_id);
141
+
142
+ logger.info('Relationship deleted successfully', {
143
+ userId,
144
+ relationshipId: args.relationship_id,
145
+ memoriesUpdated,
146
+ });
147
+
148
+ const result: DeleteRelationshipResult = {
149
+ relationship_id: args.relationship_id,
150
+ deleted: true,
151
+ memories_updated: memoriesUpdated,
152
+ message: `Relationship deleted successfully${memoriesUpdated > 0 ? ` (${memoriesUpdated} memories updated)` : ''}`,
153
+ };
154
+
155
+ return JSON.stringify(result, null, 2);
156
+ } catch (error) {
157
+ logger.error('Failed to delete relationship:', error);
158
+ throw new Error(`Failed to delete relationship: ${error instanceof Error ? error.message : String(error)}`);
159
+ }
160
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * remember_find_similar tool
3
+ * Find similar memories using vector similarity search
4
+ */
5
+
6
+ import type { Memory } from '../types/memory.js';
7
+ import { getMemoryCollection } from '../weaviate/schema.js';
8
+ import { logger } from '../utils/logger.js';
9
+
10
+ /**
11
+ * Tool definition for remember_find_similar
12
+ */
13
+ export const findSimilarTool = {
14
+ name: 'remember_find_similar',
15
+ description: `Find memories similar to a given memory or text using vector similarity.
16
+
17
+ Uses pure semantic similarity (vector distance) to find related memories.
18
+ This is different from hybrid search - it finds memories with similar meaning,
19
+ even if they don't share keywords.
20
+
21
+ Examples:
22
+ - "Find memories similar to this camping trip note"
23
+ - "Show me memories related to this recipe"
24
+ - "What other memories are like this meeting note?"
25
+ `,
26
+ inputSchema: {
27
+ type: 'object',
28
+ properties: {
29
+ memory_id: {
30
+ type: 'string',
31
+ description: 'ID of memory to find similar memories for (provide either memory_id or text)',
32
+ },
33
+ text: {
34
+ type: 'string',
35
+ description: 'Text to find similar memories for (provide either memory_id or text)',
36
+ },
37
+ limit: {
38
+ type: 'number',
39
+ description: 'Maximum number of results. Default: 10',
40
+ minimum: 1,
41
+ maximum: 100,
42
+ default: 10,
43
+ },
44
+ min_similarity: {
45
+ type: 'number',
46
+ description: 'Minimum similarity score (0-1). Default: 0.7',
47
+ minimum: 0,
48
+ maximum: 1,
49
+ default: 0.7,
50
+ },
51
+ include_relationships: {
52
+ type: 'boolean',
53
+ description: 'Include relationships in results. Default: false',
54
+ default: false,
55
+ },
56
+ },
57
+ },
58
+ };
59
+
60
+ /**
61
+ * Find similar arguments
62
+ */
63
+ export interface FindSimilarArgs {
64
+ memory_id?: string;
65
+ text?: string;
66
+ limit?: number;
67
+ min_similarity?: number;
68
+ include_relationships?: boolean;
69
+ }
70
+
71
+ /**
72
+ * Similar memory result
73
+ */
74
+ export interface SimilarMemory extends Partial<Memory> {
75
+ similarity: number; // 0-1 similarity score
76
+ }
77
+
78
+ /**
79
+ * Find similar result
80
+ */
81
+ export interface FindSimilarResult {
82
+ query: {
83
+ memory_id?: string;
84
+ text?: string;
85
+ };
86
+ similar_memories: SimilarMemory[];
87
+ total: number;
88
+ min_similarity: number;
89
+ }
90
+
91
+ /**
92
+ * Handle remember_find_similar tool
93
+ */
94
+ export async function handleFindSimilar(
95
+ args: FindSimilarArgs,
96
+ userId: string
97
+ ): Promise<string> {
98
+ try {
99
+ logger.info('Finding similar memories', { userId, memoryId: args.memory_id, hasText: !!args.text });
100
+
101
+ // Validate input
102
+ if (!args.memory_id && !args.text) {
103
+ throw new Error('Either memory_id or text must be provided');
104
+ }
105
+
106
+ if (args.memory_id && args.text) {
107
+ throw new Error('Provide either memory_id or text, not both');
108
+ }
109
+
110
+ const collection = getMemoryCollection(userId);
111
+ const limit = args.limit ?? 10;
112
+ const minSimilarity = args.min_similarity ?? 0.7;
113
+
114
+ let results: any;
115
+
116
+ if (args.memory_id) {
117
+ // Find similar to existing memory
118
+ // First get the memory to verify ownership
119
+ const memory = await collection.query.fetchObjectById(args.memory_id, {
120
+ returnProperties: ['user_id', 'doc_type', 'content'],
121
+ });
122
+
123
+ if (!memory) {
124
+ throw new Error(`Memory not found: ${args.memory_id}`);
125
+ }
126
+
127
+ // Verify ownership
128
+ if (memory.properties.user_id !== userId) {
129
+ throw new Error('Unauthorized: Cannot access another user\'s memory');
130
+ }
131
+
132
+ // Verify it's a memory
133
+ if (memory.properties.doc_type !== 'memory') {
134
+ throw new Error('Can only find similar memories for memory documents, not relationships');
135
+ }
136
+
137
+ // Find similar using nearObject
138
+ results = await collection.query.nearObject(args.memory_id, {
139
+ limit: limit + 1, // +1 to exclude the source memory itself
140
+ distance: 1 - minSimilarity, // Convert similarity to distance
141
+ returnMetadata: ['distance'],
142
+ });
143
+
144
+ // Filter out the source memory
145
+ results.objects = results.objects.filter((obj: any) => obj.uuid !== args.memory_id);
146
+ } else {
147
+ // Find similar to text
148
+ results = await collection.query.nearText(args.text!, {
149
+ limit: limit,
150
+ distance: 1 - minSimilarity,
151
+ returnMetadata: ['distance'],
152
+ });
153
+ }
154
+
155
+ // Filter to only memories (not relationships) unless requested
156
+ if (!args.include_relationships) {
157
+ results.objects = results.objects.filter(
158
+ (obj: any) => obj.properties.doc_type === 'memory'
159
+ );
160
+ }
161
+
162
+ // Format results with similarity scores
163
+ const similarMemories: SimilarMemory[] = results.objects.map((obj: any) => {
164
+ const similarity = 1 - (obj.metadata?.distance ?? 0); // Convert distance back to similarity
165
+ return {
166
+ id: obj.uuid,
167
+ ...obj.properties,
168
+ similarity: Math.max(0, Math.min(1, similarity)), // Clamp to [0, 1]
169
+ };
170
+ });
171
+
172
+ // Sort by similarity (highest first)
173
+ similarMemories.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
174
+
175
+ // Limit results
176
+ const limitedResults = similarMemories.slice(0, limit);
177
+
178
+ logger.info('Similar memories found', {
179
+ userId,
180
+ query: args.memory_id || args.text,
181
+ results: limitedResults.length,
182
+ });
183
+
184
+ const result: FindSimilarResult = {
185
+ query: {
186
+ memory_id: args.memory_id,
187
+ text: args.text,
188
+ },
189
+ similar_memories: limitedResults,
190
+ total: limitedResults.length,
191
+ min_similarity: minSimilarity,
192
+ };
193
+
194
+ return JSON.stringify(result, null, 2);
195
+ } catch (error) {
196
+ logger.error('Failed to find similar memories:', error);
197
+ throw new Error(`Failed to find similar memories: ${error instanceof Error ? error.message : String(error)}`);
198
+ }
199
+ }