@prmichaelsen/remember-mcp 2.3.4 → 2.5.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.
Files changed (34) hide show
  1. package/AGENT.md +2 -2
  2. package/CHANGELOG.md +63 -0
  3. package/README.md +50 -5
  4. package/agent/commands/git.commit.md +511 -0
  5. package/agent/commands/git.init.md +513 -0
  6. package/agent/milestones/milestone-11-unified-public-collection.md +205 -0
  7. package/agent/scripts/install.sh +31 -16
  8. package/agent/scripts/update.sh +32 -17
  9. package/agent/tasks/task-45-fix-publish-false-success-bug.md +114 -181
  10. package/agent/tasks/task-46-update-spacememory-types.md +94 -0
  11. package/agent/tasks/task-47-update-space-schema.md +102 -0
  12. package/agent/tasks/task-48-create-memory-public-collection.md +96 -0
  13. package/agent/tasks/task-49-update-remember-publish.md +153 -0
  14. package/agent/tasks/task-50-update-remember-confirm.md +111 -0
  15. package/agent/tasks/task-51-update-remember-search-space.md +154 -0
  16. package/agent/tasks/task-52-update-remember-query-space.md +142 -0
  17. package/agent/tasks/task-53-add-multispace-tests.md +193 -0
  18. package/agent/tasks/task-54-update-documentation-multispace.md +191 -0
  19. package/dist/server-factory.js +203 -107
  20. package/dist/server.js +203 -107
  21. package/dist/tools/publish.d.ts +1 -1
  22. package/dist/tools/query-space.d.ts +1 -1
  23. package/dist/tools/search-space.d.ts +1 -1
  24. package/dist/types/space-memory.d.ts +5 -3
  25. package/dist/weaviate/space-schema.d.ts +16 -0
  26. package/package.json +1 -1
  27. package/src/services/confirmation-token.service.ts +74 -30
  28. package/src/tools/confirm.ts +15 -16
  29. package/src/tools/publish.ts +42 -20
  30. package/src/tools/query-space.ts +38 -24
  31. package/src/tools/search-space.ts +51 -28
  32. package/src/types/space-memory.ts +5 -3
  33. package/src/weaviate/space-schema.spec.ts +55 -0
  34. package/src/weaviate/space-schema.ts +45 -6
@@ -50,40 +50,84 @@ export class ConfirmationTokenService {
50
50
  payload: any,
51
51
  targetCollection?: string
52
52
  ): Promise<{ requestId: string; token: string }> {
53
- const token = randomUUID();
53
+ try {
54
+ const token = randomUUID();
54
55
 
55
- const now = new Date();
56
- const expiresAt = new Date(now.getTime() + this.EXPIRY_MINUTES * 60 * 1000);
56
+ const now = new Date();
57
+ const expiresAt = new Date(now.getTime() + this.EXPIRY_MINUTES * 60 * 1000);
57
58
 
58
- const request: ConfirmationRequest = {
59
- user_id: userId,
60
- token,
61
- action,
62
- target_collection: targetCollection,
63
- payload,
64
- created_at: now.toISOString(),
65
- expires_at: expiresAt.toISOString(),
66
- status: 'pending',
67
- };
59
+ const request: ConfirmationRequest = {
60
+ user_id: userId,
61
+ token,
62
+ action,
63
+ target_collection: targetCollection,
64
+ payload,
65
+ created_at: now.toISOString(),
66
+ expires_at: expiresAt.toISOString(),
67
+ status: 'pending',
68
+ };
68
69
 
69
- // Add document to Firestore (auto-generates ID)
70
- const collectionPath = `users/${userId}/requests`;
71
- console.log('[ConfirmationTokenService] Creating request:', {
72
- userId,
73
- action,
74
- targetCollection,
75
- collectionPath,
76
- });
77
-
78
- const docRef = await addDocument(collectionPath, request);
79
-
80
- console.log('[ConfirmationTokenService] Request created:', {
81
- requestId: docRef.id,
82
- token,
83
- expiresAt: request.expires_at,
84
- });
70
+ // Add document to Firestore (auto-generates ID)
71
+ const collectionPath = `users/${userId}/requests`;
72
+ console.log('[ConfirmationTokenService] Creating request:', {
73
+ userId,
74
+ action,
75
+ targetCollection,
76
+ collectionPath,
77
+ requestData: {
78
+ token,
79
+ action,
80
+ payloadKeys: Object.keys(payload),
81
+ },
82
+ });
83
+
84
+ console.log('[ConfirmationTokenService] Calling addDocument...');
85
+ const docRef = await addDocument(collectionPath, request);
86
+ console.log('[ConfirmationTokenService] addDocument returned:', {
87
+ hasDocRef: !!docRef,
88
+ hasId: !!docRef?.id,
89
+ docRefId: docRef?.id,
90
+ });
91
+
92
+ // Validate docRef
93
+ if (!docRef) {
94
+ const error = new Error('Firestore addDocument returned null/undefined');
95
+ console.error('[ConfirmationTokenService] CRITICAL: addDocument returned null', {
96
+ userId,
97
+ collectionPath,
98
+ });
99
+ throw error;
100
+ }
101
+
102
+ if (!docRef.id) {
103
+ const error = new Error('Firestore addDocument returned docRef without ID');
104
+ console.error('[ConfirmationTokenService] CRITICAL: docRef has no ID', {
105
+ userId,
106
+ collectionPath,
107
+ docRef,
108
+ });
109
+ throw error;
110
+ }
111
+
112
+ console.log('[ConfirmationTokenService] Request created successfully:', {
113
+ requestId: docRef.id,
114
+ token,
115
+ expiresAt: request.expires_at,
116
+ });
85
117
 
86
- return { requestId: docRef.id, token };
118
+ return { requestId: docRef.id, token };
119
+ } catch (error) {
120
+ console.error('[ConfirmationTokenService] FAILED to create request:', {
121
+ error: error instanceof Error ? error.message : String(error),
122
+ stack: error instanceof Error ? error.stack : undefined,
123
+ userId,
124
+ action,
125
+ collectionPath: `users/${userId}/requests`,
126
+ });
127
+
128
+ // Re-throw so caller (tool handler) can catch and return error response
129
+ throw error;
130
+ }
87
131
  }
88
132
 
89
133
  /**
@@ -8,7 +8,7 @@
8
8
  import type { Tool } from '@modelcontextprotocol/sdk/types.js';
9
9
  import { confirmationTokenService, type ConfirmationRequest } from '../services/confirmation-token.service.js';
10
10
  import { getWeaviateClient, getMemoryCollectionName } from '../weaviate/client.js';
11
- import { ensureSpaceCollection } from '../weaviate/space-schema.js';
11
+ import { ensurePublicCollection } from '../weaviate/space-schema.js';
12
12
  import { handleToolError } from '../utils/error-handler.js';
13
13
 
14
14
  /**
@@ -102,7 +102,8 @@ async function executePublishMemory(
102
102
  console.log('[executePublishMemory] Starting execution:', {
103
103
  userId,
104
104
  memoryId: request.payload.memory_id,
105
- targetSpace: request.target_collection,
105
+ spaces: request.payload.spaces,
106
+ spaceCount: request.payload.spaces?.length || 0,
106
107
  });
107
108
 
108
109
  // Fetch the memory NOW (during confirmation, not from stored payload)
@@ -149,15 +150,12 @@ async function executePublishMemory(
149
150
  );
150
151
  }
151
152
 
152
- console.log('[executePublishMemory] Ensuring space collection:', request.target_collection || 'the_void');
153
+ console.log('[executePublishMemory] Ensuring public collection');
153
154
 
154
- // Get target collection
155
- const targetCollection = await ensureSpaceCollection(
156
- weaviateClient,
157
- request.target_collection || 'the_void'
158
- );
155
+ // Get unified public collection
156
+ const publicCollection = await ensurePublicCollection(weaviateClient);
159
157
 
160
- console.log('[executePublishMemory] Space collection ready');
158
+ console.log('[executePublishMemory] Public collection ready');
161
159
 
162
160
  // Create published memory (copy with modifications)
163
161
  const originalTags = Array.isArray(originalMemory.properties.tags)
@@ -171,7 +169,7 @@ async function executePublishMemory(
171
169
  const publishedMemory = {
172
170
  ...originalMemory.properties,
173
171
  // Add space-specific fields
174
- space_id: request.target_collection || 'the_void',
172
+ spaces: request.payload.spaces || ['the_void'], // ✅ Array of spaces!
175
173
  author_id: userId, // Track original author
176
174
  published_at: new Date().toISOString(),
177
175
  discovery_count: 0,
@@ -185,27 +183,28 @@ async function executePublishMemory(
185
183
  version: 1,
186
184
  };
187
185
 
188
- console.log('[executePublishMemory] Inserting into space collection:', {
189
- spaceId: request.target_collection || 'the_void',
186
+ console.log('[executePublishMemory] Inserting into Memory_public:', {
187
+ spaces: request.payload.spaces,
188
+ spaceCount: request.payload.spaces?.length || 0,
190
189
  memoryId: request.payload.memory_id,
191
190
  hasUserId: !!(publishedMemory as any).user_id,
192
191
  hasAuthorId: !!publishedMemory.author_id,
193
- hasSpaceId: !!publishedMemory.space_id,
194
192
  });
195
193
 
196
- // Insert directly - publishedMemory is already the properties object
197
- const result = await targetCollection.data.insert(publishedMemory as any);
194
+ // Insert directly into unified public collection
195
+ const result = await publicCollection.data.insert(publishedMemory as any);
198
196
 
199
197
  console.log('[executePublishMemory] Insert result:', {
200
198
  success: !!result,
201
199
  spaceMemoryId: result,
202
200
  });
203
201
 
204
- // Return minimal response - agent already knows original memory
202
+ // Return minimal response with spaces array
205
203
  return JSON.stringify(
206
204
  {
207
205
  success: true,
208
206
  space_memory_id: result,
207
+ spaces: request.payload.spaces || ['the_void'],
209
208
  },
210
209
  null,
211
210
  2
@@ -17,7 +17,7 @@ import { SUPPORTED_SPACES } from '../types/space-memory.js';
17
17
  */
18
18
  export const publishTool: Tool = {
19
19
  name: 'remember_publish',
20
- description: 'Publish a memory to a shared space (like "The Void"). The memory will be COPIED (not moved) from your personal collection. Generates a confirmation token. Use remember_confirm to execute.',
20
+ description: 'Publish a memory to one or more shared spaces (like "The Void"). The memory will be COPIED (not moved) from your personal collection. Generates a confirmation token. Use remember_confirm to execute.',
21
21
  inputSchema: {
22
22
  type: 'object',
23
23
  properties: {
@@ -25,11 +25,15 @@ export const publishTool: Tool = {
25
25
  type: 'string',
26
26
  description: 'ID of the memory from your personal collection to publish',
27
27
  },
28
- target: {
29
- type: 'string',
30
- description: 'Target space to publish to (snake_case ID)',
31
- enum: SUPPORTED_SPACES,
32
- default: 'the_void',
28
+ spaces: {
29
+ type: 'array',
30
+ items: {
31
+ type: 'string',
32
+ enum: SUPPORTED_SPACES,
33
+ },
34
+ description: 'Spaces to publish to (e.g., ["the_void", "dogs"]). Can publish to multiple spaces at once.',
35
+ minItems: 1,
36
+ default: ['the_void'],
33
37
  },
34
38
  additional_tags: {
35
39
  type: 'array',
@@ -38,13 +42,13 @@ export const publishTool: Tool = {
38
42
  default: [],
39
43
  },
40
44
  },
41
- required: ['memory_id', 'target'],
45
+ required: ['memory_id', 'spaces'],
42
46
  },
43
47
  };
44
48
 
45
49
  interface PublishArgs {
46
50
  memory_id: string;
47
- target: string;
51
+ spaces: string[];
48
52
  additional_tags?: string[];
49
53
  }
50
54
 
@@ -59,20 +63,23 @@ export async function handlePublish(
59
63
  console.log('[remember_publish] Starting publish request:', {
60
64
  userId,
61
65
  memoryId: args.memory_id,
62
- target: args.target,
66
+ spaces: args.spaces,
67
+ spaceCount: args.spaces.length,
63
68
  additionalTags: args.additional_tags?.length || 0,
64
69
  });
65
70
 
66
- // Validate space ID
67
- if (!isValidSpaceId(args.target)) {
68
- console.log('[remember_publish] Invalid space ID:', args.target);
71
+ // Validate all space IDs
72
+ const invalidSpaces = args.spaces.filter(s => !isValidSpaceId(s));
73
+ if (invalidSpaces.length > 0) {
74
+ console.log('[remember_publish] Invalid space IDs:', invalidSpaces);
69
75
  return JSON.stringify(
70
76
  {
71
77
  success: false,
72
- error: 'Invalid space ID',
73
- message: `Space "${args.target}" is not supported. Supported spaces: ${SUPPORTED_SPACES.join(', ')}`,
78
+ error: 'Invalid space IDs',
79
+ message: `Invalid spaces: ${invalidSpaces.join(', ')}. Supported spaces: ${SUPPORTED_SPACES.join(', ')}`,
74
80
  context: {
75
- provided_space: args.target,
81
+ invalid_spaces: invalidSpaces,
82
+ provided_spaces: args.spaces,
76
83
  supported_spaces: SUPPORTED_SPACES,
77
84
  },
78
85
  },
@@ -80,6 +87,20 @@ export async function handlePublish(
80
87
  2
81
88
  );
82
89
  }
90
+
91
+ // Validate not empty
92
+ if (args.spaces.length === 0) {
93
+ console.log('[remember_publish] Empty spaces array');
94
+ return JSON.stringify(
95
+ {
96
+ success: false,
97
+ error: 'Empty spaces array',
98
+ message: 'Must specify at least one space to publish to',
99
+ },
100
+ null,
101
+ 2
102
+ );
103
+ }
83
104
 
84
105
  // Verify memory exists and user owns it
85
106
  const weaviateClient = getWeaviateClient();
@@ -147,20 +168,21 @@ export async function handlePublish(
147
168
  );
148
169
  }
149
170
 
150
- // Create payload with only memory_id (content fetched during confirmation)
171
+ // Create payload with memory_id and spaces array
151
172
  const payload = {
152
173
  memory_id: args.memory_id,
174
+ spaces: args.spaces,
153
175
  additional_tags: args.additional_tags || [],
154
176
  };
155
177
 
156
178
  console.log('[remember_publish] Generating confirmation token');
157
179
 
158
180
  // Generate confirmation token
159
- const { requestId, token } = await confirmationTokenService.createRequest(
181
+ const { requestId, token} = await confirmationTokenService.createRequest(
160
182
  userId,
161
183
  'publish_memory',
162
184
  payload,
163
- args.target
185
+ undefined // No single target_collection anymore
164
186
  );
165
187
 
166
188
  console.log('[remember_publish] Token generated:', {
@@ -179,12 +201,12 @@ export async function handlePublish(
179
201
  2
180
202
  );
181
203
  } catch (error) {
182
- handleToolError(error, {
204
+ return handleToolError(error, {
183
205
  toolName: 'remember_publish',
184
206
  userId,
185
207
  operation: 'publish memory',
186
208
  memory_id: args.memory_id,
187
- target: args.target,
209
+ spaces: args.spaces,
188
210
  });
189
211
  }
190
212
  }
@@ -8,7 +8,7 @@
8
8
  import type { Tool } from '@modelcontextprotocol/sdk/types.js';
9
9
  import { Filters } from 'weaviate-client';
10
10
  import { getWeaviateClient } from '../weaviate/client.js';
11
- import { ensureSpaceCollection, isValidSpaceId } from '../weaviate/space-schema.js';
11
+ import { ensurePublicCollection, isValidSpaceId } from '../weaviate/space-schema.js';
12
12
  import { SUPPORTED_SPACES } from '../types/space-memory.js';
13
13
  import { handleToolError } from '../utils/error-handler.js';
14
14
 
@@ -66,13 +66,13 @@ export const querySpaceTool: Tool = {
66
66
  description: 'Output format: detailed (full objects) or compact (text summary)',
67
67
  },
68
68
  },
69
- required: ['question', 'space'],
69
+ required: ['question', 'spaces'],
70
70
  },
71
71
  };
72
72
 
73
73
  interface QuerySpaceArgs {
74
74
  question: string;
75
- space: string;
75
+ spaces: string[];
76
76
  content_type?: string;
77
77
  tags?: string[];
78
78
  min_weight?: number;
@@ -90,13 +90,27 @@ export async function handleQuerySpace(
90
90
  userId: string // May be used for private spaces in future
91
91
  ): Promise<string> {
92
92
  try {
93
- // Validate space ID
94
- if (!isValidSpaceId(args.space)) {
93
+ // Validate all space IDs
94
+ const invalidSpaces = args.spaces.filter(s => !isValidSpaceId(s));
95
+ if (invalidSpaces.length > 0) {
95
96
  return JSON.stringify(
96
97
  {
97
98
  success: false,
98
- error: 'Invalid space ID',
99
- message: `Space "${args.space}" is not supported. Supported spaces: ${SUPPORTED_SPACES.join(', ')}`,
99
+ error: 'Invalid space IDs',
100
+ message: `Invalid spaces: ${invalidSpaces.join(', ')}. Supported spaces: ${SUPPORTED_SPACES.join(', ')}`,
101
+ },
102
+ null,
103
+ 2
104
+ );
105
+ }
106
+
107
+ // Validate not empty
108
+ if (args.spaces.length === 0) {
109
+ return JSON.stringify(
110
+ {
111
+ success: false,
112
+ error: 'Empty spaces array',
113
+ message: 'Must specify at least one space to query',
100
114
  },
101
115
  null,
102
116
  2
@@ -104,47 +118,47 @@ export async function handleQuerySpace(
104
118
  }
105
119
 
106
120
  const weaviateClient = getWeaviateClient();
107
- const spaceCollection = await ensureSpaceCollection(weaviateClient, args.space);
121
+ const publicCollection = await ensurePublicCollection(weaviateClient);
108
122
 
109
123
  // Build filters
110
124
  const filterList: any[] = [];
111
125
 
112
- // Filter by space_id
113
- filterList.push(spaceCollection.filter.byProperty('space_id').equal(args.space));
126
+ // Filter by spaces array (memory must be in at least one requested space)
127
+ filterList.push(publicCollection.filter.byProperty('spaces').containsAny(args.spaces));
114
128
 
115
129
  // Filter by doc_type (space_memory)
116
- filterList.push(spaceCollection.filter.byProperty('doc_type').equal('space_memory'));
130
+ filterList.push(publicCollection.filter.byProperty('doc_type').equal('space_memory'));
117
131
 
118
132
  // Apply content type filter
119
133
  if (args.content_type) {
120
- filterList.push(spaceCollection.filter.byProperty('type').equal(args.content_type));
134
+ filterList.push(publicCollection.filter.byProperty('type').equal(args.content_type));
121
135
  }
122
136
 
123
137
  // Apply tags filter
124
138
  if (args.tags && args.tags.length > 0) {
125
139
  args.tags.forEach(tag => {
126
- filterList.push(spaceCollection.filter.byProperty('tags').containsAny([tag]));
140
+ filterList.push(publicCollection.filter.byProperty('tags').containsAny([tag]));
127
141
  });
128
142
  }
129
143
 
130
144
  // Apply weight filter
131
145
  if (args.min_weight !== undefined) {
132
- filterList.push(spaceCollection.filter.byProperty('weight').greaterOrEqual(args.min_weight));
146
+ filterList.push(publicCollection.filter.byProperty('weight').greaterOrEqual(args.min_weight));
133
147
  }
134
148
 
135
149
  // Apply date filters
136
150
  if (args.date_from) {
137
- filterList.push(spaceCollection.filter.byProperty('created_at').greaterOrEqual(new Date(args.date_from)));
151
+ filterList.push(publicCollection.filter.byProperty('created_at').greaterOrEqual(new Date(args.date_from)));
138
152
  }
139
153
 
140
154
  if (args.date_to) {
141
- filterList.push(spaceCollection.filter.byProperty('created_at').lessOrEqual(new Date(args.date_to)));
155
+ filterList.push(publicCollection.filter.byProperty('created_at').lessOrEqual(new Date(args.date_to)));
142
156
  }
143
157
 
144
158
  const whereFilter = filterList.length > 0 ? Filters.and(...filterList) : undefined;
145
159
 
146
160
  // Execute semantic search using nearText
147
- const searchResults = await spaceCollection.query.nearText(args.question, {
161
+ const searchResults = await publicCollection.query.nearText(args.question, {
148
162
  limit: args.limit || 10,
149
163
  ...(whereFilter && { where: whereFilter }),
150
164
  });
@@ -154,14 +168,14 @@ export async function handleQuerySpace(
154
168
 
155
169
  if (format === 'compact') {
156
170
  // Compact format: text summary for LLM context
157
- const summaries = searchResults.objects.map((obj, idx) => {
171
+ const summaries = searchResults.objects.map((obj: any, idx: number) => {
158
172
  const props = obj.properties;
159
173
  return `${idx + 1}. ${props.title || props.content?.substring(0, 100) || 'Untitled'}`;
160
174
  });
161
175
 
162
176
  const result = {
163
177
  question: args.question,
164
- space: args.space,
178
+ spaces_queried: args.spaces,
165
179
  format: 'compact',
166
180
  summary: summaries.join('\n'),
167
181
  count: searchResults.objects.length,
@@ -170,7 +184,7 @@ export async function handleQuerySpace(
170
184
  return JSON.stringify(result, null, 2);
171
185
  } else {
172
186
  // Detailed format: full objects
173
- const memories = searchResults.objects.map((obj) => ({
187
+ const memories = searchResults.objects.map((obj: any) => ({
174
188
  id: obj.uuid,
175
189
  ...obj.properties,
176
190
  _distance: obj.metadata?.distance,
@@ -178,7 +192,7 @@ export async function handleQuerySpace(
178
192
 
179
193
  const result = {
180
194
  question: args.question,
181
- space: args.space,
195
+ spaces_queried: args.spaces,
182
196
  format: 'detailed',
183
197
  memories,
184
198
  total: memories.length,
@@ -187,10 +201,10 @@ export async function handleQuerySpace(
187
201
  return JSON.stringify(result, null, 2);
188
202
  }
189
203
  } catch (error) {
190
- handleToolError(error, {
204
+ return handleToolError(error, {
191
205
  toolName: 'remember_query_space',
192
- operation: 'query space',
193
- space: args.space,
206
+ operation: 'query spaces',
207
+ spaces: args.spaces,
194
208
  question: args.question,
195
209
  });
196
210
  }
@@ -8,7 +8,7 @@
8
8
  import type { Tool } from '@modelcontextprotocol/sdk/types.js';
9
9
  import { Filters } from 'weaviate-client';
10
10
  import { getWeaviateClient } from '../weaviate/client.js';
11
- import { ensureSpaceCollection, isValidSpaceId } from '../weaviate/space-schema.js';
11
+ import { ensurePublicCollection, isValidSpaceId } from '../weaviate/space-schema.js';
12
12
  import { SUPPORTED_SPACES } from '../types/space-memory.js';
13
13
  import { handleToolError } from '../utils/error-handler.js';
14
14
  import type { SearchFilters } from '../types/memory.js';
@@ -18,7 +18,7 @@ import type { SearchFilters } from '../types/memory.js';
18
18
  */
19
19
  export const searchSpaceTool: Tool = {
20
20
  name: 'remember_search_space',
21
- description: 'Search shared spaces to discover thoughts, ideas, and memories. Works like remember_search_memory but searches shared spaces instead of personal memories.',
21
+ description: 'Search one or more shared spaces to discover thoughts, ideas, and memories. Works like remember_search_memory but searches shared spaces instead of personal memories. Can search multiple spaces in a single query.',
22
22
  inputSchema: {
23
23
  type: 'object',
24
24
  properties: {
@@ -26,11 +26,15 @@ export const searchSpaceTool: Tool = {
26
26
  type: 'string',
27
27
  description: 'Search query (semantic + keyword hybrid)',
28
28
  },
29
- space: {
30
- type: 'string',
31
- description: 'Which space to search',
32
- enum: SUPPORTED_SPACES,
33
- default: 'the_void',
29
+ spaces: {
30
+ type: 'array',
31
+ items: {
32
+ type: 'string',
33
+ enum: SUPPORTED_SPACES,
34
+ },
35
+ description: 'Spaces to search (e.g., ["the_void", "dogs"]). Can search multiple spaces at once.',
36
+ minItems: 1,
37
+ default: ['the_void'],
34
38
  },
35
39
  content_type: {
36
40
  type: 'string',
@@ -72,13 +76,13 @@ export const searchSpaceTool: Tool = {
72
76
  description: 'Offset for pagination',
73
77
  },
74
78
  },
75
- required: ['query', 'space'],
79
+ required: ['query', 'spaces'],
76
80
  },
77
81
  };
78
82
 
79
83
  interface SearchSpaceArgs {
80
84
  query: string;
81
- space: string;
85
+ spaces: string[];
82
86
  content_type?: string;
83
87
  tags?: string[];
84
88
  min_weight?: number;
@@ -97,13 +101,32 @@ export async function handleSearchSpace(
97
101
  userId: string // May be used for private spaces in future
98
102
  ): Promise<string> {
99
103
  try {
100
- // Validate space ID
101
- if (!isValidSpaceId(args.space)) {
104
+ // Validate all space IDs
105
+ const invalidSpaces = args.spaces.filter(s => !isValidSpaceId(s));
106
+ if (invalidSpaces.length > 0) {
107
+ return JSON.stringify(
108
+ {
109
+ success: false,
110
+ error: 'Invalid space IDs',
111
+ message: `Invalid spaces: ${invalidSpaces.join(', ')}. Supported spaces: ${SUPPORTED_SPACES.join(', ')}`,
112
+ context: {
113
+ invalid_spaces: invalidSpaces,
114
+ provided_spaces: args.spaces,
115
+ supported_spaces: SUPPORTED_SPACES,
116
+ },
117
+ },
118
+ null,
119
+ 2
120
+ );
121
+ }
122
+
123
+ // Validate not empty
124
+ if (args.spaces.length === 0) {
102
125
  return JSON.stringify(
103
126
  {
104
127
  success: false,
105
- error: 'Invalid space ID',
106
- message: `Space "${args.space}" is not supported. Supported spaces: ${SUPPORTED_SPACES.join(', ')}`,
128
+ error: 'Empty spaces array',
129
+ message: 'Must specify at least one space to search',
107
130
  },
108
131
  null,
109
132
  2
@@ -111,51 +134,51 @@ export async function handleSearchSpace(
111
134
  }
112
135
 
113
136
  const weaviateClient = getWeaviateClient();
114
- const spaceCollection = await ensureSpaceCollection(weaviateClient, args.space);
137
+ const publicCollection = await ensurePublicCollection(weaviateClient);
115
138
 
116
139
  // Build filters for space search
117
140
  const filterList: any[] = [];
118
141
 
119
- // Filter by space_id
120
- filterList.push(spaceCollection.filter.byProperty('space_id').equal(args.space));
142
+ // Filter by spaces array (memory must be in at least one requested space)
143
+ filterList.push(publicCollection.filter.byProperty('spaces').containsAny(args.spaces));
121
144
 
122
145
  // Filter by doc_type (space_memory)
123
- filterList.push(spaceCollection.filter.byProperty('doc_type').equal('space_memory'));
146
+ filterList.push(publicCollection.filter.byProperty('doc_type').equal('space_memory'));
124
147
 
125
148
  // Apply content type filter
126
149
  if (args.content_type) {
127
- filterList.push(spaceCollection.filter.byProperty('type').equal(args.content_type));
150
+ filterList.push(publicCollection.filter.byProperty('type').equal(args.content_type));
128
151
  }
129
152
 
130
153
  // Apply tags filter
131
154
  if (args.tags && args.tags.length > 0) {
132
155
  args.tags.forEach(tag => {
133
- filterList.push(spaceCollection.filter.byProperty('tags').containsAny([tag]));
156
+ filterList.push(publicCollection.filter.byProperty('tags').containsAny([tag]));
134
157
  });
135
158
  }
136
159
 
137
160
  // Apply weight filters
138
161
  if (args.min_weight !== undefined) {
139
- filterList.push(spaceCollection.filter.byProperty('weight').greaterOrEqual(args.min_weight));
162
+ filterList.push(publicCollection.filter.byProperty('weight').greaterOrEqual(args.min_weight));
140
163
  }
141
164
 
142
165
  if (args.max_weight !== undefined) {
143
- filterList.push(spaceCollection.filter.byProperty('weight').lessOrEqual(args.max_weight));
166
+ filterList.push(publicCollection.filter.byProperty('weight').lessOrEqual(args.max_weight));
144
167
  }
145
168
 
146
169
  // Apply date filters (convert ISO strings to Date objects)
147
170
  if (args.date_from) {
148
- filterList.push(spaceCollection.filter.byProperty('created_at').greaterOrEqual(new Date(args.date_from)));
171
+ filterList.push(publicCollection.filter.byProperty('created_at').greaterOrEqual(new Date(args.date_from)));
149
172
  }
150
173
 
151
174
  if (args.date_to) {
152
- filterList.push(spaceCollection.filter.byProperty('created_at').lessOrEqual(new Date(args.date_to)));
175
+ filterList.push(publicCollection.filter.byProperty('created_at').lessOrEqual(new Date(args.date_to)));
153
176
  }
154
177
 
155
178
  const whereFilter = filterList.length > 0 ? Filters.and(...filterList) : undefined;
156
179
 
157
180
  // Execute hybrid search
158
- const searchResults = await spaceCollection.query.hybrid(args.query, {
181
+ const searchResults = await publicCollection.query.hybrid(args.query, {
159
182
  limit: args.limit || 10,
160
183
  offset: args.offset || 0,
161
184
  ...(whereFilter && { where: whereFilter }),
@@ -169,7 +192,7 @@ export async function handleSearchSpace(
169
192
  }));
170
193
 
171
194
  const result = {
172
- space: args.space,
195
+ spaces_searched: args.spaces,
173
196
  query: args.query,
174
197
  memories,
175
198
  total: memories.length,
@@ -179,10 +202,10 @@ export async function handleSearchSpace(
179
202
 
180
203
  return JSON.stringify(result, null, 2);
181
204
  } catch (error) {
182
- handleToolError(error, {
205
+ return handleToolError(error, {
183
206
  toolName: 'remember_search_space',
184
- operation: 'search space',
185
- space: args.space,
207
+ operation: 'search spaces',
208
+ spaces: args.spaces,
186
209
  query: args.query,
187
210
  });
188
211
  }