@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/src/server-factory.ts
CHANGED
|
@@ -11,35 +11,84 @@ import {
|
|
|
11
11
|
McpError,
|
|
12
12
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
13
13
|
import { logger } from './utils/logger.js';
|
|
14
|
+
import { initWeaviateClient } from './weaviate/client.js';
|
|
15
|
+
import { initFirestore } from './firestore/init.js';
|
|
14
16
|
|
|
15
17
|
// Import memory tools
|
|
16
18
|
import { createMemoryTool, handleCreateMemory } from './tools/create-memory.js';
|
|
17
19
|
import { searchMemoryTool, handleSearchMemory } from './tools/search-memory.js';
|
|
18
20
|
import { deleteMemoryTool, handleDeleteMemory } from './tools/delete-memory.js';
|
|
21
|
+
import { updateMemoryTool, handleUpdateMemory } from './tools/update-memory.js';
|
|
22
|
+
import { findSimilarTool, handleFindSimilar } from './tools/find-similar.js';
|
|
23
|
+
import { queryMemoryTool, handleQueryMemory } from './tools/query-memory.js';
|
|
24
|
+
|
|
25
|
+
// Import relationship tools
|
|
26
|
+
import { createRelationshipTool, handleCreateRelationship } from './tools/create-relationship.js';
|
|
27
|
+
import { updateRelationshipTool, handleUpdateRelationship } from './tools/update-relationship.js';
|
|
28
|
+
import { searchRelationshipTool, handleSearchRelationship } from './tools/search-relationship.js';
|
|
29
|
+
import { deleteRelationshipTool, handleDeleteRelationship } from './tools/delete-relationship.js';
|
|
19
30
|
|
|
20
31
|
export interface ServerOptions {
|
|
21
32
|
name?: string;
|
|
22
33
|
version?: string;
|
|
23
34
|
}
|
|
24
35
|
|
|
36
|
+
// Global initialization flag to ensure databases are initialized once
|
|
37
|
+
let databasesInitialized = false;
|
|
38
|
+
let initializationPromise: Promise<void> | null = null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initialize databases (called once globally)
|
|
42
|
+
*/
|
|
43
|
+
async function ensureDatabasesInitialized(): Promise<void> {
|
|
44
|
+
if (databasesInitialized) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// If initialization is in progress, wait for it
|
|
49
|
+
if (initializationPromise) {
|
|
50
|
+
return initializationPromise;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Start initialization
|
|
54
|
+
initializationPromise = (async () => {
|
|
55
|
+
try {
|
|
56
|
+
logger.info('Initializing databases...');
|
|
57
|
+
await initWeaviateClient();
|
|
58
|
+
initFirestore();
|
|
59
|
+
databasesInitialized = true;
|
|
60
|
+
logger.info('Databases initialized successfully');
|
|
61
|
+
} catch (error) {
|
|
62
|
+
logger.error('Database initialization failed:', error);
|
|
63
|
+
throw error;
|
|
64
|
+
} finally {
|
|
65
|
+
initializationPromise = null;
|
|
66
|
+
}
|
|
67
|
+
})();
|
|
68
|
+
|
|
69
|
+
return initializationPromise;
|
|
70
|
+
}
|
|
71
|
+
|
|
25
72
|
/**
|
|
26
73
|
* Create a server instance for a specific user/tenant
|
|
27
|
-
*
|
|
74
|
+
*
|
|
28
75
|
* This factory function is compatible with mcp-auth wrapping pattern.
|
|
29
76
|
* It creates isolated server instances with no shared state.
|
|
30
|
-
*
|
|
77
|
+
*
|
|
78
|
+
* Note: Databases (Weaviate + Firestore) are initialized once globally on first call.
|
|
79
|
+
*
|
|
31
80
|
* @param accessToken - User's access token (reserved for future external APIs)
|
|
32
81
|
* @param userId - User identifier for scoping operations
|
|
33
82
|
* @param options - Optional server configuration
|
|
34
83
|
* @returns Configured MCP Server instance (not connected to transport)
|
|
35
|
-
*
|
|
84
|
+
*
|
|
36
85
|
* @example
|
|
37
86
|
* ```typescript
|
|
38
87
|
* // Direct usage
|
|
39
88
|
* const server = createServer('token', 'user123');
|
|
40
89
|
* const transport = new StdioServerTransport();
|
|
41
90
|
* await server.connect(transport);
|
|
42
|
-
*
|
|
91
|
+
*
|
|
43
92
|
* // With mcp-auth
|
|
44
93
|
* import { wrapServer } from '@prmichaelsen/mcp-auth';
|
|
45
94
|
* const wrapped = wrapServer({
|
|
@@ -66,11 +115,18 @@ export function createServer(
|
|
|
66
115
|
|
|
67
116
|
logger.debug('Creating server instance', { userId });
|
|
68
117
|
|
|
118
|
+
// Ensure databases are initialized (happens once globally)
|
|
119
|
+
// Note: This is synchronous to match the Server return type
|
|
120
|
+
// The actual initialization happens on first tool call
|
|
121
|
+
ensureDatabasesInitialized().catch(error => {
|
|
122
|
+
logger.error('Failed to initialize databases:', error);
|
|
123
|
+
});
|
|
124
|
+
|
|
69
125
|
// Create MCP server
|
|
70
126
|
const server = new Server(
|
|
71
127
|
{
|
|
72
128
|
name: options.name || 'remember-mcp',
|
|
73
|
-
version: options.version || '0.
|
|
129
|
+
version: options.version || '0.2.0',
|
|
74
130
|
},
|
|
75
131
|
{
|
|
76
132
|
capabilities: {
|
|
@@ -101,9 +157,18 @@ function registerHandlers(server: Server, userId: string, accessToken: string):
|
|
|
101
157
|
properties: {},
|
|
102
158
|
},
|
|
103
159
|
},
|
|
160
|
+
// Memory tools
|
|
104
161
|
createMemoryTool,
|
|
105
162
|
searchMemoryTool,
|
|
106
163
|
deleteMemoryTool,
|
|
164
|
+
updateMemoryTool,
|
|
165
|
+
findSimilarTool,
|
|
166
|
+
queryMemoryTool,
|
|
167
|
+
// Relationship tools
|
|
168
|
+
createRelationshipTool,
|
|
169
|
+
updateRelationshipTool,
|
|
170
|
+
searchRelationshipTool,
|
|
171
|
+
deleteRelationshipTool,
|
|
107
172
|
],
|
|
108
173
|
};
|
|
109
174
|
});
|
|
@@ -132,6 +197,34 @@ function registerHandlers(server: Server, userId: string, accessToken: string):
|
|
|
132
197
|
result = await handleDeleteMemory(args as any, userId);
|
|
133
198
|
break;
|
|
134
199
|
|
|
200
|
+
case 'remember_update_memory':
|
|
201
|
+
result = await handleUpdateMemory(args as any, userId);
|
|
202
|
+
break;
|
|
203
|
+
|
|
204
|
+
case 'remember_find_similar':
|
|
205
|
+
result = await handleFindSimilar(args as any, userId);
|
|
206
|
+
break;
|
|
207
|
+
|
|
208
|
+
case 'remember_query_memory':
|
|
209
|
+
result = await handleQueryMemory(args as any, userId);
|
|
210
|
+
break;
|
|
211
|
+
|
|
212
|
+
case 'remember_create_relationship':
|
|
213
|
+
result = await handleCreateRelationship(args as any, userId);
|
|
214
|
+
break;
|
|
215
|
+
|
|
216
|
+
case 'remember_update_relationship':
|
|
217
|
+
result = await handleUpdateRelationship(args as any, userId);
|
|
218
|
+
break;
|
|
219
|
+
|
|
220
|
+
case 'remember_search_relationship':
|
|
221
|
+
result = await handleSearchRelationship(args as any, userId);
|
|
222
|
+
break;
|
|
223
|
+
|
|
224
|
+
case 'remember_delete_relationship':
|
|
225
|
+
result = await handleDeleteRelationship(args as any, userId);
|
|
226
|
+
break;
|
|
227
|
+
|
|
135
228
|
default:
|
|
136
229
|
throw new McpError(
|
|
137
230
|
ErrorCode.MethodNotFound,
|
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
|
+
}
|