@prmichaelsen/remember-mcp 0.1.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.
Files changed (95) hide show
  1. package/.env.example +65 -0
  2. package/AGENT.md +840 -0
  3. package/README.md +72 -0
  4. package/agent/design/.gitkeep +0 -0
  5. package/agent/design/access-control-result-pattern.md +458 -0
  6. package/agent/design/action-audit-memory-types.md +637 -0
  7. package/agent/design/common-template-fields.md +282 -0
  8. package/agent/design/complete-tool-set.md +407 -0
  9. package/agent/design/content-types-expansion.md +521 -0
  10. package/agent/design/cross-database-id-strategy.md +358 -0
  11. package/agent/design/default-template-library.md +423 -0
  12. package/agent/design/firestore-wrapper-analysis.md +606 -0
  13. package/agent/design/llm-provider-abstraction.md +691 -0
  14. package/agent/design/location-handling-architecture.md +523 -0
  15. package/agent/design/memory-templates-design.md +364 -0
  16. package/agent/design/permissions-storage-architecture.md +680 -0
  17. package/agent/design/relationship-storage-strategy.md +361 -0
  18. package/agent/design/remember-mcp-implementation-tasks.md +417 -0
  19. package/agent/design/remember-mcp-progress.yaml +141 -0
  20. package/agent/design/requirements-enhancements.md +468 -0
  21. package/agent/design/requirements.md +56 -0
  22. package/agent/design/template-storage-strategy.md +412 -0
  23. package/agent/design/template-suggestion-system.md +853 -0
  24. package/agent/design/trust-escalation-prevention.md +343 -0
  25. package/agent/design/trust-system-implementation.md +592 -0
  26. package/agent/design/user-preferences.md +683 -0
  27. package/agent/design/weaviate-collection-strategy.md +461 -0
  28. package/agent/milestones/.gitkeep +0 -0
  29. package/agent/milestones/milestone-1-project-foundation.md +121 -0
  30. package/agent/milestones/milestone-2-core-memory-system.md +150 -0
  31. package/agent/milestones/milestone-3-relationships-graph.md +116 -0
  32. package/agent/milestones/milestone-4-user-preferences.md +103 -0
  33. package/agent/milestones/milestone-5-template-system.md +126 -0
  34. package/agent/milestones/milestone-6-auth-multi-tenancy.md +124 -0
  35. package/agent/milestones/milestone-7-trust-permissions.md +133 -0
  36. package/agent/milestones/milestone-8-testing-quality.md +137 -0
  37. package/agent/milestones/milestone-9-deployment-documentation.md +147 -0
  38. package/agent/patterns/.gitkeep +0 -0
  39. package/agent/patterns/bootstrap.md +1271 -0
  40. package/agent/patterns/firebase-admin-sdk-v8-usage.md +950 -0
  41. package/agent/patterns/firestore-users-pattern-best-practices.md +347 -0
  42. package/agent/patterns/library-services.md +454 -0
  43. package/agent/patterns/testing-colocated.md +316 -0
  44. package/agent/progress.yaml +395 -0
  45. package/agent/tasks/.gitkeep +0 -0
  46. package/agent/tasks/task-1-initialize-project-structure.md +266 -0
  47. package/agent/tasks/task-2-install-dependencies.md +199 -0
  48. package/agent/tasks/task-3-setup-weaviate-client.md +330 -0
  49. package/agent/tasks/task-4-setup-firestore-client.md +362 -0
  50. package/agent/tasks/task-5-create-basic-mcp-server.md +114 -0
  51. package/agent/tasks/task-6-create-integration-tests.md +195 -0
  52. package/agent/tasks/task-7-finalize-milestone-1.md +363 -0
  53. package/agent/tasks/task-8-setup-utility-scripts.md +382 -0
  54. package/agent/tasks/task-9-create-server-factory.md +404 -0
  55. package/dist/config.d.ts +26 -0
  56. package/dist/constants/content-types.d.ts +60 -0
  57. package/dist/firestore/init.d.ts +14 -0
  58. package/dist/firestore/paths.d.ts +53 -0
  59. package/dist/firestore/paths.spec.d.ts +2 -0
  60. package/dist/server-factory.d.ts +40 -0
  61. package/dist/server-factory.js +1741 -0
  62. package/dist/server-factory.spec.d.ts +2 -0
  63. package/dist/server.d.ts +3 -0
  64. package/dist/server.js +1690 -0
  65. package/dist/tools/create-memory.d.ts +94 -0
  66. package/dist/tools/delete-memory.d.ts +47 -0
  67. package/dist/tools/search-memory.d.ts +88 -0
  68. package/dist/types/memory.d.ts +183 -0
  69. package/dist/utils/logger.d.ts +7 -0
  70. package/dist/weaviate/client.d.ts +39 -0
  71. package/dist/weaviate/client.spec.d.ts +2 -0
  72. package/dist/weaviate/schema.d.ts +29 -0
  73. package/esbuild.build.js +60 -0
  74. package/esbuild.watch.js +25 -0
  75. package/jest.config.js +31 -0
  76. package/jest.e2e.config.js +17 -0
  77. package/package.json +68 -0
  78. package/src/.gitkeep +0 -0
  79. package/src/config.ts +56 -0
  80. package/src/constants/content-types.ts +454 -0
  81. package/src/firestore/init.ts +68 -0
  82. package/src/firestore/paths.spec.ts +75 -0
  83. package/src/firestore/paths.ts +124 -0
  84. package/src/server-factory.spec.ts +60 -0
  85. package/src/server-factory.ts +215 -0
  86. package/src/server.ts +243 -0
  87. package/src/tools/create-memory.ts +198 -0
  88. package/src/tools/delete-memory.ts +126 -0
  89. package/src/tools/search-memory.ts +216 -0
  90. package/src/types/memory.ts +276 -0
  91. package/src/utils/logger.ts +42 -0
  92. package/src/weaviate/client.spec.ts +58 -0
  93. package/src/weaviate/client.ts +114 -0
  94. package/src/weaviate/schema.ts +288 -0
  95. package/tsconfig.json +26 -0
@@ -0,0 +1,950 @@
1
+ # Firebase Admin SDK v8 Usage Pattern
2
+
3
+ **Library**: @prmichaelsen/firebase-admin-sdk-v8
4
+ **Version**: 2.2.0+
5
+ **Created**: 2026-02-11
6
+ **Status**: Reference Pattern
7
+
8
+ ---
9
+
10
+ ## Overview
11
+
12
+ The `@prmichaelsen/firebase-admin-sdk-v8` library provides Firebase Admin SDK functionality for Cloudflare Workers and edge runtimes using REST APIs instead of Node.js Admin SDK. This makes it compatible with environments that don't support Node.js.
13
+
14
+ **Key Features**:
15
+ - ✅ Zero dependencies (uses Web APIs: crypto.subtle, fetch)
16
+ - ✅ JWT token generation for service account authentication
17
+ - ✅ ID token verification (supports Firebase v9 and v10 formats)
18
+ - ✅ Firestore REST API with full CRUD operations
19
+ - ✅ Advanced queries (where, orderBy, limit, pagination)
20
+ - ✅ Field value operations (increment, arrayUnion, serverTimestamp, delete)
21
+ - ✅ Batch operations (atomic multi-document writes)
22
+ - ✅ TypeScript support with full type definitions
23
+
24
+ **Limitations**:
25
+ - ❌ No realtime listeners (REST API is stateless)
26
+ - ❌ No custom token creation yet
27
+ - ❌ No user management yet
28
+ - ❌ No transactions yet
29
+
30
+ ---
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ npm install @prmichaelsen/firebase-admin-sdk-v8
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Initialization
41
+
42
+ ### Option 1: Explicit Initialization (Recommended for remember-mcp)
43
+
44
+ ```typescript
45
+ import { initializeApp } from '@prmichaelsen/firebase-admin-sdk-v8';
46
+
47
+ // Initialize once at startup
48
+ initializeApp({
49
+ serviceAccount: JSON.parse(process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY!),
50
+ projectId: process.env.FIREBASE_PROJECT_ID
51
+ });
52
+ ```
53
+
54
+ ### Option 2: Auto-Detection from process.env
55
+
56
+ If you don't call `initializeApp()`, the SDK automatically uses:
57
+ - `process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY` (JSON string)
58
+ - `process.env.FIREBASE_PROJECT_ID`
59
+
60
+ ### Environment Variables
61
+
62
+ ```env
63
+ FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY='{"type":"service_account","project_id":"...","private_key":"...","client_email":"..."}'
64
+ FIREBASE_PROJECT_ID=your-project-id
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Authentication
70
+
71
+ ### Verify ID Tokens
72
+
73
+ ```typescript
74
+ import { verifyIdToken, getUserFromToken } from '@prmichaelsen/firebase-admin-sdk-v8';
75
+
76
+ // Verify token and get decoded claims
77
+ const decodedToken = await verifyIdToken(idToken);
78
+ console.log('User ID:', decodedToken.uid);
79
+ console.log('Email:', decodedToken.email);
80
+
81
+ // Or get user object directly
82
+ const user = await getUserFromToken(idToken);
83
+ console.log('User:', user.email, user.displayName);
84
+ ```
85
+
86
+ **Token Structure**:
87
+ ```typescript
88
+ interface DecodedIdToken {
89
+ uid: string; // User ID
90
+ email?: string;
91
+ email_verified?: boolean;
92
+ name?: string;
93
+ picture?: string;
94
+ iss: string; // Issuer
95
+ aud: string; // Audience (project ID)
96
+ auth_time: number; // Authentication time
97
+ iat: number; // Issued at
98
+ exp: number; // Expires at
99
+ sub: string; // Subject (same as uid)
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Firestore Operations
106
+
107
+ ### Basic CRUD
108
+
109
+ #### Get Document
110
+
111
+ ```typescript
112
+ import { getDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
113
+
114
+ // Get a document
115
+ const user = await getDocument('users', 'user123');
116
+
117
+ // Get from subcollection
118
+ const message = await getDocument('users/user123/messages', 'msg456');
119
+
120
+ // Returns null if document doesn't exist
121
+ if (!user) {
122
+ console.log('User not found');
123
+ }
124
+ ```
125
+
126
+ #### Set Document (Create or Overwrite)
127
+
128
+ ```typescript
129
+ import { setDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
130
+
131
+ // Create or overwrite document
132
+ await setDocument('users', 'user123', {
133
+ name: 'John Doe',
134
+ email: 'john@example.com',
135
+ createdAt: new Date().toISOString()
136
+ });
137
+
138
+ // Merge with existing data (don't overwrite)
139
+ await setDocument('users', 'user123', {
140
+ lastLogin: new Date().toISOString()
141
+ }, { merge: true });
142
+ ```
143
+
144
+ #### Add Document (Auto-Generated ID)
145
+
146
+ ```typescript
147
+ import { addDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
148
+
149
+ // Add document with auto-generated ID
150
+ const docRef = await addDocument('users/user123/messages', {
151
+ content: 'Hello world',
152
+ timestamp: new Date().toISOString()
153
+ });
154
+
155
+ console.log('Created document with ID:', docRef.id);
156
+
157
+ // Add with custom ID
158
+ const docRef2 = await addDocument('users/user123/messages', {
159
+ content: 'Custom ID message'
160
+ }, 'custom-message-id');
161
+ ```
162
+
163
+ #### Update Document
164
+
165
+ ```typescript
166
+ import { updateDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
167
+
168
+ // Update specific fields
169
+ await updateDocument('users', 'user123', {
170
+ name: 'Jane Doe',
171
+ updatedAt: new Date().toISOString()
172
+ });
173
+
174
+ // Note: updateDocument fails if document doesn't exist
175
+ // Use setDocument with merge: true if you want upsert behavior
176
+ ```
177
+
178
+ #### Delete Document
179
+
180
+ ```typescript
181
+ import { deleteDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
182
+
183
+ // Delete a document
184
+ await deleteDocument('users', 'user123');
185
+
186
+ // Soft delete (recommended)
187
+ await setDocument('users', 'user123', {
188
+ deleted: true,
189
+ deletedAt: new Date().toISOString()
190
+ }, { merge: true });
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Field Value Operations
196
+
197
+ ### Special Field Values
198
+
199
+ ```typescript
200
+ import { FieldValue, setDocument, updateDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
201
+
202
+ // Server timestamp
203
+ await setDocument('users', 'user123', {
204
+ createdAt: FieldValue.serverTimestamp(),
205
+ updatedAt: FieldValue.serverTimestamp()
206
+ });
207
+
208
+ // Increment/Decrement
209
+ await updateDocument('users', 'user123', {
210
+ loginCount: FieldValue.increment(1),
211
+ credits: FieldValue.increment(-10) // Decrement
212
+ });
213
+
214
+ // Array operations
215
+ await updateDocument('users', 'user123', {
216
+ tags: FieldValue.arrayUnion('premium', 'verified'),
217
+ blockedUsers: FieldValue.arrayRemove('user456')
218
+ });
219
+
220
+ // Delete field
221
+ await updateDocument('users', 'user123', {
222
+ temporaryField: FieldValue.delete()
223
+ });
224
+ ```
225
+
226
+ ---
227
+
228
+ ## Queries
229
+
230
+ ### Basic Query
231
+
232
+ ```typescript
233
+ import { queryDocuments } from '@prmichaelsen/firebase-admin-sdk-v8';
234
+ import type { QueryOptions } from '@prmichaelsen/firebase-admin-sdk-v8';
235
+
236
+ const options: QueryOptions = {
237
+ where: [
238
+ { field: 'active', op: '==', value: true }
239
+ ],
240
+ orderBy: [{ field: 'createdAt', direction: 'DESCENDING' }],
241
+ limit: 10
242
+ };
243
+
244
+ const results = await queryDocuments('users', options);
245
+
246
+ // Results format
247
+ results.forEach(doc => {
248
+ console.log('ID:', doc.id);
249
+ console.log('Data:', doc.data);
250
+ });
251
+ ```
252
+
253
+ ### Query Operators
254
+
255
+ ```typescript
256
+ // Comparison operators
257
+ { field: 'age', op: '==', value: 25 }
258
+ { field: 'age', op: '!=', value: 25 }
259
+ { field: 'age', op: '<', value: 25 }
260
+ { field: 'age', op: '<=', value: 25 }
261
+ { field: 'age', op: '>', value: 25 }
262
+ { field: 'age', op: '>=', value: 25 }
263
+
264
+ // Array operators
265
+ { field: 'tags', op: 'array-contains', value: 'premium' }
266
+ { field: 'tags', op: 'array-contains-any', value: ['premium', 'verified'] }
267
+ { field: 'roles', op: 'in', value: ['admin', 'moderator'] }
268
+ { field: 'status', op: 'not-in', value: ['banned', 'suspended'] }
269
+ ```
270
+
271
+ ### Complex Queries
272
+
273
+ ```typescript
274
+ // Multiple conditions (AND)
275
+ const activeAdults = await queryDocuments('users', {
276
+ where: [
277
+ { field: 'active', op: '==', value: true },
278
+ { field: 'age', op: '>=', value: 18 },
279
+ { field: 'verified', op: '==', value: true }
280
+ ],
281
+ orderBy: [{ field: 'createdAt', direction: 'DESCENDING' }],
282
+ limit: 50
283
+ });
284
+
285
+ // Pagination with cursor
286
+ const firstPage = await queryDocuments('users', {
287
+ orderBy: [{ field: 'createdAt', direction: 'DESCENDING' }],
288
+ limit: 10
289
+ });
290
+
291
+ // Get next page
292
+ const secondPage = await queryDocuments('users', {
293
+ orderBy: [{ field: 'createdAt', direction: 'DESCENDING' }],
294
+ limit: 10,
295
+ startAfter: [firstPage[firstPage.length - 1].data.createdAt]
296
+ });
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Batch Operations
302
+
303
+ ### Atomic Writes
304
+
305
+ ```typescript
306
+ import { batchWrite } from '@prmichaelsen/firebase-admin-sdk-v8';
307
+
308
+ await batchWrite([
309
+ {
310
+ type: 'set',
311
+ collection: 'users',
312
+ documentId: 'user123',
313
+ data: { name: 'John' }
314
+ },
315
+ {
316
+ type: 'update',
317
+ collection: 'users',
318
+ documentId: 'user456',
319
+ data: { loginCount: FieldValue.increment(1) }
320
+ },
321
+ {
322
+ type: 'delete',
323
+ collection: 'users',
324
+ documentId: 'user789'
325
+ }
326
+ ]);
327
+
328
+ // All operations succeed or all fail (atomic)
329
+ ```
330
+
331
+ ---
332
+
333
+ ## Collection Path Patterns
334
+
335
+ ### User-Scoped Collections
336
+
337
+ Following agentbase.me pattern:
338
+
339
+ ```typescript
340
+ // Helper functions for collection paths
341
+ export function getUserCollection(userId: string): string {
342
+ return `users/${userId}`;
343
+ }
344
+
345
+ export function getUserSubcollection(userId: string, subcollection: string): string {
346
+ return `users/${userId}/${subcollection}`;
347
+ }
348
+
349
+ // Usage
350
+ const conversationsPath = getUserSubcollection(userId, 'conversations');
351
+ const messagesPath = `users/${userId}/conversations/${conversationId}/messages`;
352
+ ```
353
+
354
+ ### remember-mcp Collection Patterns
355
+
356
+ Based on design documents:
357
+
358
+ ```typescript
359
+ // User preferences
360
+ export function getUserPreferencesPath(userId: string): string {
361
+ return `user_preferences/${userId}`;
362
+ }
363
+
364
+ // User permissions (who can access user's memories)
365
+ export function getUserPermissionsPath(userId: string): string {
366
+ return `user_permissions/${userId}/allowed_accessors`;
367
+ }
368
+
369
+ // Trust history
370
+ export function getTrustHistoryPath(userId: string): string {
371
+ return `trust_history/${userId}/history`;
372
+ }
373
+
374
+ // Templates
375
+ export function getDefaultTemplatesPath(): string {
376
+ return 'templates/default';
377
+ }
378
+
379
+ export function getUserTemplatesPath(userId: string): string {
380
+ return `users/${userId}/templates`;
381
+ }
382
+ ```
383
+
384
+ ---
385
+
386
+ ## Service Class Pattern
387
+
388
+ ### Recommended Pattern (from agentbase.me)
389
+
390
+ ```typescript
391
+ import { getDocument, setDocument, queryDocuments, addDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
392
+ import type { QueryOptions } from '@prmichaelsen/firebase-admin-sdk-v8';
393
+
394
+ export class UserPreferencesService {
395
+ /**
396
+ * Get user preferences
397
+ */
398
+ static async getPreferences(userId: string): Promise<UserPreferences | null> {
399
+ const doc = await getDocument('user_preferences', userId);
400
+
401
+ if (!doc) {
402
+ // Return defaults if not found
403
+ return DEFAULT_PREFERENCES;
404
+ }
405
+
406
+ return doc as UserPreferences;
407
+ }
408
+
409
+ /**
410
+ * Update user preferences
411
+ */
412
+ static async updatePreferences(
413
+ userId: string,
414
+ updates: Partial<UserPreferences>
415
+ ): Promise<void> {
416
+ await setDocument('user_preferences', userId, {
417
+ ...updates,
418
+ updated_at: new Date().toISOString()
419
+ }, { merge: true });
420
+ }
421
+
422
+ /**
423
+ * Create user preferences with defaults
424
+ */
425
+ static async createPreferences(userId: string): Promise<UserPreferences> {
426
+ const preferences: UserPreferences = {
427
+ ...DEFAULT_PREFERENCES,
428
+ user_id: userId,
429
+ created_at: new Date().toISOString(),
430
+ updated_at: new Date().toISOString()
431
+ };
432
+
433
+ await setDocument('user_preferences', userId, preferences);
434
+ return preferences;
435
+ }
436
+ }
437
+ ```
438
+
439
+ ---
440
+
441
+ ## Multi-Tenant Patterns
442
+
443
+ ### Per-User Collections
444
+
445
+ ```typescript
446
+ // Pattern 1: User as document ID
447
+ // Collection: user_preferences/{userId}
448
+ await setDocument('user_preferences', userId, preferences);
449
+
450
+ // Pattern 2: User as subcollection
451
+ // Collection: users/{userId}/preferences
452
+ await setDocument(`users/${userId}/preferences`, 'settings', preferences);
453
+
454
+ // Pattern 3: User-scoped subcollections
455
+ // Collection: users/{userId}/conversations/{conversationId}
456
+ const conversationPath = `users/${userId}/conversations`;
457
+ await addDocument(conversationPath, conversation);
458
+ ```
459
+
460
+ ### Query User-Scoped Data
461
+
462
+ ```typescript
463
+ // Get all conversations for a user
464
+ const conversations = await queryDocuments(`users/${userId}/conversations`, {
465
+ orderBy: [{ field: 'updated_at', direction: 'DESCENDING' }],
466
+ limit: 50
467
+ });
468
+
469
+ // Get user's memories with filters
470
+ const memories = await queryDocuments(`users/${userId}/memories`, {
471
+ where: [
472
+ { field: 'type', op: '==', value: 'note' },
473
+ { field: 'weight', op: '>=', value: 0.5 }
474
+ ],
475
+ orderBy: [{ field: 'created_at', direction: 'DESCENDING' }],
476
+ limit: 20
477
+ });
478
+ ```
479
+
480
+ ---
481
+
482
+ ## Common Patterns from agentbase.me
483
+
484
+ ### 1. Service Class Pattern
485
+
486
+ ```typescript
487
+ export class ConversationDatabaseService {
488
+ static async createConversation(userId: string, title: string): Promise<Conversation> {
489
+ const now = new Date().toISOString();
490
+
491
+ const conversation = {
492
+ title,
493
+ created_at: now,
494
+ updated_at: now
495
+ };
496
+
497
+ const conversationsPath = getUserConversations(userId);
498
+ const docRef = await addDocument(conversationsPath, conversation);
499
+
500
+ return {
501
+ id: docRef.id,
502
+ user_id: userId,
503
+ ...conversation
504
+ };
505
+ }
506
+
507
+ static async getConversation(userId: string, conversationId: string): Promise<Conversation | null> {
508
+ const conversationsPath = getUserConversations(userId);
509
+ return await getDocument(conversationsPath, conversationId);
510
+ }
511
+
512
+ static async getUserConversations(userId: string, limit = 50): Promise<Conversation[]> {
513
+ const conversationsPath = getUserConversations(userId);
514
+ const results = await queryDocuments(conversationsPath, {
515
+ orderBy: [{ field: 'updated_at', direction: 'DESCENDING' }],
516
+ limit
517
+ });
518
+
519
+ return results.map(doc => ({ id: doc.id, ...doc.data }));
520
+ }
521
+ }
522
+ ```
523
+
524
+ ### 2. Collection Path Helpers
525
+
526
+ ```typescript
527
+ // From agentbase.me/src/constant/collections.ts
528
+ const BASE = process.env.NODE_ENV === 'development' ? 'e0.agentbase' : 'agentbase';
529
+
530
+ export function getUserConversations(userId: string): string {
531
+ return `${BASE}.users/${userId}/conversations`;
532
+ }
533
+
534
+ export function getUserConversationMessages(userId: string, conversationId: string): string {
535
+ return `${BASE}.users/${userId}/conversations/${conversationId}/messages`;
536
+ }
537
+
538
+ export function getUserCredentialsCollection(userId: string): string {
539
+ return `${BASE}.users/${userId}/credentials`;
540
+ }
541
+ ```
542
+
543
+ ### 3. Soft Delete Pattern
544
+
545
+ ```typescript
546
+ // Instead of deleting, mark as deleted
547
+ await setDocument('users', userId, {
548
+ deleted: true,
549
+ deleted_at: new Date().toISOString()
550
+ }, { merge: true });
551
+
552
+ // Query non-deleted documents
553
+ const activeUsers = await queryDocuments('users', {
554
+ where: [
555
+ { field: 'deleted', op: '!=', value: true }
556
+ ]
557
+ });
558
+ ```
559
+
560
+ ### 4. Timestamp Pattern
561
+
562
+ ```typescript
563
+ // Use ISO strings for timestamps (not Firestore Timestamp objects)
564
+ const now = new Date().toISOString();
565
+
566
+ await setDocument('users', userId, {
567
+ created_at: now,
568
+ updated_at: now
569
+ });
570
+
571
+ // Or use FieldValue.serverTimestamp() for server-side timestamp
572
+ await setDocument('users', userId, {
573
+ created_at: FieldValue.serverTimestamp(),
574
+ updated_at: FieldValue.serverTimestamp()
575
+ });
576
+ ```
577
+
578
+ ---
579
+
580
+ ## remember-mcp Specific Patterns
581
+
582
+ ### User Preferences
583
+
584
+ ```typescript
585
+ import { getDocument, setDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
586
+
587
+ export async function getUserPreferences(userId: string): Promise<UserPreferences> {
588
+ const doc = await getDocument('user_preferences', userId);
589
+
590
+ if (!doc) {
591
+ // Create with defaults
592
+ const defaults = DEFAULT_PREFERENCES;
593
+ await setDocument('user_preferences', userId, defaults);
594
+ return defaults;
595
+ }
596
+
597
+ return doc as UserPreferences;
598
+ }
599
+
600
+ export async function updateUserPreferences(
601
+ userId: string,
602
+ updates: Partial<UserPreferences>
603
+ ): Promise<void> {
604
+ await setDocument('user_preferences', userId, {
605
+ ...updates,
606
+ updated_at: new Date().toISOString()
607
+ }, { merge: true });
608
+ }
609
+ ```
610
+
611
+ ### User Permissions (Trust System)
612
+
613
+ ```typescript
614
+ import { getDocument, setDocument, queryDocuments } from '@prmichaelsen/firebase-admin-sdk-v8';
615
+
616
+ export async function grantPermission(
617
+ ownerUserId: string,
618
+ accessorUserId: string,
619
+ trustLevel: number,
620
+ options: PermissionOptions
621
+ ): Promise<void> {
622
+ const permission: UserPermission = {
623
+ owner_user_id: ownerUserId,
624
+ accessor_user_id: accessorUserId,
625
+ can_access: true,
626
+ trust_level: trustLevel,
627
+ granted_at: new Date().toISOString(),
628
+ ...options
629
+ };
630
+
631
+ const permissionsPath = `user_permissions/${ownerUserId}/allowed_accessors`;
632
+ await setDocument(permissionsPath, accessorUserId, permission);
633
+ }
634
+
635
+ export async function checkPermission(
636
+ ownerUserId: string,
637
+ accessorUserId: string
638
+ ): Promise<UserPermission | null> {
639
+ const permissionsPath = `user_permissions/${ownerUserId}/allowed_accessors`;
640
+ return await getDocument(permissionsPath, accessorUserId);
641
+ }
642
+
643
+ export async function listAccessors(ownerUserId: string): Promise<UserPermission[]> {
644
+ const permissionsPath = `user_permissions/${ownerUserId}/allowed_accessors`;
645
+ const results = await queryDocuments(permissionsPath, {
646
+ where: [{ field: 'revoked', op: '!=', value: true }],
647
+ orderBy: [{ field: 'trust_level', direction: 'DESCENDING' }]
648
+ });
649
+
650
+ return results.map(doc => ({ id: doc.id, ...doc.data }));
651
+ }
652
+ ```
653
+
654
+ ### Templates
655
+
656
+ ```typescript
657
+ export async function getDefaultTemplates(): Promise<Template[]> {
658
+ const results = await queryDocuments('templates/default', {
659
+ where: [{ field: 'is_default', op: '==', value: true }],
660
+ orderBy: [{ field: 'usage_count', direction: 'DESCENDING' }]
661
+ });
662
+
663
+ return results.map(doc => ({ id: doc.id, ...doc.data }));
664
+ }
665
+
666
+ export async function getUserTemplates(userId: string): Promise<Template[]> {
667
+ const templatesPath = `users/${userId}/templates`;
668
+ const results = await queryDocuments(templatesPath, {
669
+ orderBy: [{ field: 'created_at', direction: 'DESCENDING' }]
670
+ });
671
+
672
+ return results.map(doc => ({ id: doc.id, ...doc.data }));
673
+ }
674
+ ```
675
+
676
+ ---
677
+
678
+ ## Best Practices
679
+
680
+ ### 1. Use Service Classes
681
+
682
+ Organize Firestore operations into service classes:
683
+ - `UserPreferencesService`
684
+ - `PermissionsService`
685
+ - `TemplateService`
686
+ - `TrustHistoryService`
687
+
688
+ ### 2. Use Collection Path Helpers
689
+
690
+ Create helper functions for collection paths:
691
+ ```typescript
692
+ export function getUserPreferencesPath(userId: string): string {
693
+ return `user_preferences/${userId}`;
694
+ }
695
+ ```
696
+
697
+ ### 3. Use Merge for Updates
698
+
699
+ Prefer `setDocument` with `merge: true` over `updateDocument`:
700
+ ```typescript
701
+ // ✅ Good: Creates if doesn't exist
702
+ await setDocument('users', userId, { name: 'John' }, { merge: true });
703
+
704
+ // ❌ Risky: Fails if document doesn't exist
705
+ await updateDocument('users', userId, { name: 'John' });
706
+ ```
707
+
708
+ ### 4. Use ISO Timestamps
709
+
710
+ Use ISO string timestamps for consistency:
711
+ ```typescript
712
+ const now = new Date().toISOString();
713
+ await setDocument('users', userId, {
714
+ created_at: now,
715
+ updated_at: now
716
+ });
717
+ ```
718
+
719
+ ### 5. Handle Null Returns
720
+
721
+ Always check for null when getting documents:
722
+ ```typescript
723
+ const doc = await getDocument('users', userId);
724
+ if (!doc) {
725
+ // Handle missing document
726
+ return DEFAULT_VALUE;
727
+ }
728
+ ```
729
+
730
+ ### 6. Use Soft Deletes
731
+
732
+ Mark documents as deleted instead of deleting:
733
+ ```typescript
734
+ await setDocument('users', userId, {
735
+ deleted: true,
736
+ deleted_at: new Date().toISOString()
737
+ }, { merge: true });
738
+ ```
739
+
740
+ ---
741
+
742
+ ## Error Handling
743
+
744
+ ```typescript
745
+ try {
746
+ const doc = await getDocument('users', userId);
747
+ } catch (error) {
748
+ if (error instanceof Error) {
749
+ console.error('Firestore error:', error.message);
750
+ }
751
+ throw new Error('Failed to get user document');
752
+ }
753
+ ```
754
+
755
+ ---
756
+
757
+ ## Testing
758
+
759
+ ### Unit Tests (Mock)
760
+
761
+ ```typescript
762
+ import { jest } from '@jest/globals';
763
+
764
+ // Mock the firebase-admin-sdk-v8 module
765
+ jest.mock('@prmichaelsen/firebase-admin-sdk-v8', () => ({
766
+ getDocument: jest.fn(),
767
+ setDocument: jest.fn(),
768
+ queryDocuments: jest.fn()
769
+ }));
770
+
771
+ // In tests
772
+ import { getDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
773
+ (getDocument as jest.Mock).mockResolvedValue({ name: 'John' });
774
+ ```
775
+
776
+ ### Integration Tests (Real Firestore)
777
+
778
+ ```typescript
779
+ import { initializeApp, getDocument, setDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
780
+
781
+ beforeAll(() => {
782
+ initializeApp({
783
+ serviceAccount: JSON.parse(process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY!),
784
+ projectId: process.env.FIREBASE_PROJECT_ID
785
+ });
786
+ });
787
+
788
+ it('should create and retrieve document', async () => {
789
+ const testId = `test_${Date.now()}`;
790
+
791
+ await setDocument('test_collection', testId, { value: 'test' });
792
+ const doc = await getDocument('test_collection', testId);
793
+
794
+ expect(doc).toEqual({ value: 'test' });
795
+
796
+ // Cleanup
797
+ await deleteDocument('test_collection', testId);
798
+ });
799
+ ```
800
+
801
+ ---
802
+
803
+ ## Migration from firebase-admin
804
+
805
+ ### Old (firebase-admin)
806
+
807
+ ```typescript
808
+ import admin from 'firebase-admin';
809
+
810
+ admin.initializeApp({
811
+ credential: admin.credential.cert(serviceAccount)
812
+ });
813
+
814
+ const db = admin.firestore();
815
+
816
+ // Get document
817
+ const doc = await db.collection('users').doc('user123').get();
818
+ const data = doc.data();
819
+
820
+ // Set document
821
+ await db.collection('users').doc('user123').set({ name: 'John' });
822
+
823
+ // Query
824
+ const snapshot = await db.collection('users')
825
+ .where('active', '==', true)
826
+ .orderBy('createdAt', 'desc')
827
+ .limit(10)
828
+ .get();
829
+
830
+ const users = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
831
+ ```
832
+
833
+ ### New (firebase-admin-sdk-v8)
834
+
835
+ ```typescript
836
+ import { initializeApp, getDocument, setDocument, queryDocuments } from '@prmichaelsen/firebase-admin-sdk-v8';
837
+
838
+ initializeApp({
839
+ serviceAccount: JSON.parse(process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY!),
840
+ projectId: process.env.FIREBASE_PROJECT_ID
841
+ });
842
+
843
+ // Get document
844
+ const data = await getDocument('users', 'user123');
845
+
846
+ // Set document
847
+ await setDocument('users', 'user123', { name: 'John' });
848
+
849
+ // Query
850
+ const users = await queryDocuments('users', {
851
+ where: [{ field: 'active', op: '==', value: true }],
852
+ orderBy: [{ field: 'createdAt', direction: 'DESCENDING' }],
853
+ limit: 10
854
+ });
855
+ ```
856
+
857
+ ---
858
+
859
+ ## Key Differences from firebase-admin
860
+
861
+ ### Advantages
862
+ - ✅ Works in Cloudflare Workers and edge runtimes
863
+ - ✅ Zero dependencies (uses Web APIs)
864
+ - ✅ Simpler API (flat functions vs nested objects)
865
+ - ✅ REST-based (no gRPC dependencies)
866
+
867
+ ### Limitations
868
+ - ❌ No realtime listeners (use polling or client SDK)
869
+ - ❌ No transactions yet
870
+ - ❌ No custom token creation yet
871
+ - ❌ Slightly higher latency (REST vs gRPC)
872
+
873
+ ---
874
+
875
+ ## remember-mcp Integration
876
+
877
+ ### Update package.json
878
+
879
+ ```json
880
+ {
881
+ "dependencies": {
882
+ "@prmichaelsen/firebase-admin-sdk-v8": "^2.2.0"
883
+ }
884
+ }
885
+ ```
886
+
887
+ ### Remove firebase-admin
888
+
889
+ Since we're using firebase-admin-sdk-v8, we should remove the standard firebase-admin:
890
+
891
+ ```bash
892
+ npm uninstall firebase-admin
893
+ npm install @prmichaelsen/firebase-admin-sdk-v8
894
+ ```
895
+
896
+ ### Update config.ts
897
+
898
+ ```typescript
899
+ export const config = {
900
+ firebase: {
901
+ serviceAccount: process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY || '',
902
+ projectId: process.env.FIREBASE_PROJECT_ID || '',
903
+ }
904
+ };
905
+ ```
906
+
907
+ ### Update .env.example
908
+
909
+ ```env
910
+ # Firebase Configuration (using firebase-admin-sdk-v8)
911
+ FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY='{"type":"service_account",...}'
912
+ FIREBASE_PROJECT_ID=remember-mcp-dev
913
+ ```
914
+
915
+ ---
916
+
917
+ ## Reference Examples
918
+
919
+ ### From agentbase.me
920
+
921
+ - **Auth**: [`src/lib/auth/session.ts`](/home/prmichaelsen/agentbase.me/src/lib/auth/session.ts)
922
+ - **Conversations**: [`src/services/conversation-database.service.ts`](/home/prmichaelsen/agentbase.me/src/services/conversation-database.service.ts)
923
+ - **Credentials**: [`src/services/credentials-database.service.ts`](/home/prmichaelsen/agentbase.me/src/services/credentials-database.service.ts)
924
+ - **Collection Paths**: [`src/constant/collections.ts`](/home/prmichaelsen/agentbase.me/src/constant/collections.ts)
925
+
926
+ ---
927
+
928
+ ## Summary
929
+
930
+ The `@prmichaelsen/firebase-admin-sdk-v8` library provides a REST-based Firebase Admin SDK that:
931
+
932
+ 1. **Works in edge runtimes** (Cloudflare Workers, Vercel Edge)
933
+ 2. **Simple API** with flat functions (getDocument, setDocument, etc.)
934
+ 3. **Zero dependencies** (uses Web APIs)
935
+ 4. **Full Firestore CRUD** with advanced queries
936
+ 5. **Field value operations** (increment, arrayUnion, serverTimestamp)
937
+ 6. **Batch operations** for atomic writes
938
+
939
+ **For remember-mcp**, we should:
940
+ - ✅ Use this library instead of firebase-admin
941
+ - ✅ Follow the service class pattern from agentbase.me
942
+ - ✅ Use collection path helpers for multi-tenancy
943
+ - ✅ Use ISO timestamps and soft deletes
944
+ - ✅ Handle null returns and errors properly
945
+
946
+ ---
947
+
948
+ **Status**: Reference Pattern
949
+ **Library Version**: 2.2.0+
950
+ **Recommended For**: remember-mcp Firestore integration