@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
package/dist/server.js ADDED
@@ -0,0 +1,1690 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
10
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
11
+ }) : x)(function(x) {
12
+ if (typeof require !== "undefined")
13
+ return require.apply(this, arguments);
14
+ throw Error('Dynamic require of "' + x + '" is not supported');
15
+ });
16
+ var __commonJS = (cb, mod) => function __require2() {
17
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
28
+ // If the importer is in node compatibility mode or this is not an ESM
29
+ // file that has been converted to a CommonJS file using a Babel-
30
+ // compatible transform (i.e. "__esModule" has not been set), then set
31
+ // "default" to the CommonJS "module.exports" for node compatibility.
32
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
33
+ mod
34
+ ));
35
+
36
+ // node_modules/dotenv/package.json
37
+ var require_package = __commonJS({
38
+ "node_modules/dotenv/package.json"(exports, module) {
39
+ module.exports = {
40
+ name: "dotenv",
41
+ version: "16.6.1",
42
+ description: "Loads environment variables from .env file",
43
+ main: "lib/main.js",
44
+ types: "lib/main.d.ts",
45
+ exports: {
46
+ ".": {
47
+ types: "./lib/main.d.ts",
48
+ require: "./lib/main.js",
49
+ default: "./lib/main.js"
50
+ },
51
+ "./config": "./config.js",
52
+ "./config.js": "./config.js",
53
+ "./lib/env-options": "./lib/env-options.js",
54
+ "./lib/env-options.js": "./lib/env-options.js",
55
+ "./lib/cli-options": "./lib/cli-options.js",
56
+ "./lib/cli-options.js": "./lib/cli-options.js",
57
+ "./package.json": "./package.json"
58
+ },
59
+ scripts: {
60
+ "dts-check": "tsc --project tests/types/tsconfig.json",
61
+ lint: "standard",
62
+ pretest: "npm run lint && npm run dts-check",
63
+ test: "tap run --allow-empty-coverage --disable-coverage --timeout=60000",
64
+ "test:coverage": "tap run --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov",
65
+ prerelease: "npm test",
66
+ release: "standard-version"
67
+ },
68
+ repository: {
69
+ type: "git",
70
+ url: "git://github.com/motdotla/dotenv.git"
71
+ },
72
+ homepage: "https://github.com/motdotla/dotenv#readme",
73
+ funding: "https://dotenvx.com",
74
+ keywords: [
75
+ "dotenv",
76
+ "env",
77
+ ".env",
78
+ "environment",
79
+ "variables",
80
+ "config",
81
+ "settings"
82
+ ],
83
+ readmeFilename: "README.md",
84
+ license: "BSD-2-Clause",
85
+ devDependencies: {
86
+ "@types/node": "^18.11.3",
87
+ decache: "^4.6.2",
88
+ sinon: "^14.0.1",
89
+ standard: "^17.0.0",
90
+ "standard-version": "^9.5.0",
91
+ tap: "^19.2.0",
92
+ typescript: "^4.8.4"
93
+ },
94
+ engines: {
95
+ node: ">=12"
96
+ },
97
+ browser: {
98
+ fs: false
99
+ }
100
+ };
101
+ }
102
+ });
103
+
104
+ // node_modules/dotenv/lib/main.js
105
+ var require_main = __commonJS({
106
+ "node_modules/dotenv/lib/main.js"(exports, module) {
107
+ var fs = __require("fs");
108
+ var path = __require("path");
109
+ var os = __require("os");
110
+ var crypto = __require("crypto");
111
+ var packageJson = require_package();
112
+ var version = packageJson.version;
113
+ var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
114
+ function parse(src) {
115
+ const obj = {};
116
+ let lines = src.toString();
117
+ lines = lines.replace(/\r\n?/mg, "\n");
118
+ let match;
119
+ while ((match = LINE.exec(lines)) != null) {
120
+ const key = match[1];
121
+ let value = match[2] || "";
122
+ value = value.trim();
123
+ const maybeQuote = value[0];
124
+ value = value.replace(/^(['"`])([\s\S]*)\1$/mg, "$2");
125
+ if (maybeQuote === '"') {
126
+ value = value.replace(/\\n/g, "\n");
127
+ value = value.replace(/\\r/g, "\r");
128
+ }
129
+ obj[key] = value;
130
+ }
131
+ return obj;
132
+ }
133
+ function _parseVault(options) {
134
+ options = options || {};
135
+ const vaultPath = _vaultPath(options);
136
+ options.path = vaultPath;
137
+ const result = DotenvModule.configDotenv(options);
138
+ if (!result.parsed) {
139
+ const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
140
+ err.code = "MISSING_DATA";
141
+ throw err;
142
+ }
143
+ const keys = _dotenvKey(options).split(",");
144
+ const length = keys.length;
145
+ let decrypted;
146
+ for (let i = 0; i < length; i++) {
147
+ try {
148
+ const key = keys[i].trim();
149
+ const attrs = _instructions(result, key);
150
+ decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
151
+ break;
152
+ } catch (error) {
153
+ if (i + 1 >= length) {
154
+ throw error;
155
+ }
156
+ }
157
+ }
158
+ return DotenvModule.parse(decrypted);
159
+ }
160
+ function _warn(message) {
161
+ console.log(`[dotenv@${version}][WARN] ${message}`);
162
+ }
163
+ function _debug(message) {
164
+ console.log(`[dotenv@${version}][DEBUG] ${message}`);
165
+ }
166
+ function _log(message) {
167
+ console.log(`[dotenv@${version}] ${message}`);
168
+ }
169
+ function _dotenvKey(options) {
170
+ if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
171
+ return options.DOTENV_KEY;
172
+ }
173
+ if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
174
+ return process.env.DOTENV_KEY;
175
+ }
176
+ return "";
177
+ }
178
+ function _instructions(result, dotenvKey) {
179
+ let uri;
180
+ try {
181
+ uri = new URL(dotenvKey);
182
+ } catch (error) {
183
+ if (error.code === "ERR_INVALID_URL") {
184
+ const err = new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
185
+ err.code = "INVALID_DOTENV_KEY";
186
+ throw err;
187
+ }
188
+ throw error;
189
+ }
190
+ const key = uri.password;
191
+ if (!key) {
192
+ const err = new Error("INVALID_DOTENV_KEY: Missing key part");
193
+ err.code = "INVALID_DOTENV_KEY";
194
+ throw err;
195
+ }
196
+ const environment = uri.searchParams.get("environment");
197
+ if (!environment) {
198
+ const err = new Error("INVALID_DOTENV_KEY: Missing environment part");
199
+ err.code = "INVALID_DOTENV_KEY";
200
+ throw err;
201
+ }
202
+ const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
203
+ const ciphertext = result.parsed[environmentKey];
204
+ if (!ciphertext) {
205
+ const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
206
+ err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
207
+ throw err;
208
+ }
209
+ return { ciphertext, key };
210
+ }
211
+ function _vaultPath(options) {
212
+ let possibleVaultPath = null;
213
+ if (options && options.path && options.path.length > 0) {
214
+ if (Array.isArray(options.path)) {
215
+ for (const filepath of options.path) {
216
+ if (fs.existsSync(filepath)) {
217
+ possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
218
+ }
219
+ }
220
+ } else {
221
+ possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
222
+ }
223
+ } else {
224
+ possibleVaultPath = path.resolve(process.cwd(), ".env.vault");
225
+ }
226
+ if (fs.existsSync(possibleVaultPath)) {
227
+ return possibleVaultPath;
228
+ }
229
+ return null;
230
+ }
231
+ function _resolveHome(envPath) {
232
+ return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
233
+ }
234
+ function _configVault(options) {
235
+ const debug = Boolean(options && options.debug);
236
+ const quiet = options && "quiet" in options ? options.quiet : true;
237
+ if (debug || !quiet) {
238
+ _log("Loading env from encrypted .env.vault");
239
+ }
240
+ const parsed = DotenvModule._parseVault(options);
241
+ let processEnv = process.env;
242
+ if (options && options.processEnv != null) {
243
+ processEnv = options.processEnv;
244
+ }
245
+ DotenvModule.populate(processEnv, parsed, options);
246
+ return { parsed };
247
+ }
248
+ function configDotenv(options) {
249
+ const dotenvPath = path.resolve(process.cwd(), ".env");
250
+ let encoding = "utf8";
251
+ const debug = Boolean(options && options.debug);
252
+ const quiet = options && "quiet" in options ? options.quiet : true;
253
+ if (options && options.encoding) {
254
+ encoding = options.encoding;
255
+ } else {
256
+ if (debug) {
257
+ _debug("No encoding is specified. UTF-8 is used by default");
258
+ }
259
+ }
260
+ let optionPaths = [dotenvPath];
261
+ if (options && options.path) {
262
+ if (!Array.isArray(options.path)) {
263
+ optionPaths = [_resolveHome(options.path)];
264
+ } else {
265
+ optionPaths = [];
266
+ for (const filepath of options.path) {
267
+ optionPaths.push(_resolveHome(filepath));
268
+ }
269
+ }
270
+ }
271
+ let lastError;
272
+ const parsedAll = {};
273
+ for (const path2 of optionPaths) {
274
+ try {
275
+ const parsed = DotenvModule.parse(fs.readFileSync(path2, { encoding }));
276
+ DotenvModule.populate(parsedAll, parsed, options);
277
+ } catch (e) {
278
+ if (debug) {
279
+ _debug(`Failed to load ${path2} ${e.message}`);
280
+ }
281
+ lastError = e;
282
+ }
283
+ }
284
+ let processEnv = process.env;
285
+ if (options && options.processEnv != null) {
286
+ processEnv = options.processEnv;
287
+ }
288
+ DotenvModule.populate(processEnv, parsedAll, options);
289
+ if (debug || !quiet) {
290
+ const keysCount = Object.keys(parsedAll).length;
291
+ const shortPaths = [];
292
+ for (const filePath of optionPaths) {
293
+ try {
294
+ const relative = path.relative(process.cwd(), filePath);
295
+ shortPaths.push(relative);
296
+ } catch (e) {
297
+ if (debug) {
298
+ _debug(`Failed to load ${filePath} ${e.message}`);
299
+ }
300
+ lastError = e;
301
+ }
302
+ }
303
+ _log(`injecting env (${keysCount}) from ${shortPaths.join(",")}`);
304
+ }
305
+ if (lastError) {
306
+ return { parsed: parsedAll, error: lastError };
307
+ } else {
308
+ return { parsed: parsedAll };
309
+ }
310
+ }
311
+ function config2(options) {
312
+ if (_dotenvKey(options).length === 0) {
313
+ return DotenvModule.configDotenv(options);
314
+ }
315
+ const vaultPath = _vaultPath(options);
316
+ if (!vaultPath) {
317
+ _warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
318
+ return DotenvModule.configDotenv(options);
319
+ }
320
+ return DotenvModule._configVault(options);
321
+ }
322
+ function decrypt(encrypted, keyStr) {
323
+ const key = Buffer.from(keyStr.slice(-64), "hex");
324
+ let ciphertext = Buffer.from(encrypted, "base64");
325
+ const nonce = ciphertext.subarray(0, 12);
326
+ const authTag = ciphertext.subarray(-16);
327
+ ciphertext = ciphertext.subarray(12, -16);
328
+ try {
329
+ const aesgcm = crypto.createDecipheriv("aes-256-gcm", key, nonce);
330
+ aesgcm.setAuthTag(authTag);
331
+ return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
332
+ } catch (error) {
333
+ const isRange = error instanceof RangeError;
334
+ const invalidKeyLength = error.message === "Invalid key length";
335
+ const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
336
+ if (isRange || invalidKeyLength) {
337
+ const err = new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
338
+ err.code = "INVALID_DOTENV_KEY";
339
+ throw err;
340
+ } else if (decryptionFailed) {
341
+ const err = new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
342
+ err.code = "DECRYPTION_FAILED";
343
+ throw err;
344
+ } else {
345
+ throw error;
346
+ }
347
+ }
348
+ }
349
+ function populate(processEnv, parsed, options = {}) {
350
+ const debug = Boolean(options && options.debug);
351
+ const override = Boolean(options && options.override);
352
+ if (typeof parsed !== "object") {
353
+ const err = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
354
+ err.code = "OBJECT_REQUIRED";
355
+ throw err;
356
+ }
357
+ for (const key of Object.keys(parsed)) {
358
+ if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
359
+ if (override === true) {
360
+ processEnv[key] = parsed[key];
361
+ }
362
+ if (debug) {
363
+ if (override === true) {
364
+ _debug(`"${key}" is already defined and WAS overwritten`);
365
+ } else {
366
+ _debug(`"${key}" is already defined and was NOT overwritten`);
367
+ }
368
+ }
369
+ } else {
370
+ processEnv[key] = parsed[key];
371
+ }
372
+ }
373
+ }
374
+ var DotenvModule = {
375
+ configDotenv,
376
+ _configVault,
377
+ _parseVault,
378
+ config: config2,
379
+ decrypt,
380
+ parse,
381
+ populate
382
+ };
383
+ module.exports.configDotenv = DotenvModule.configDotenv;
384
+ module.exports._configVault = DotenvModule._configVault;
385
+ module.exports._parseVault = DotenvModule._parseVault;
386
+ module.exports.config = DotenvModule.config;
387
+ module.exports.decrypt = DotenvModule.decrypt;
388
+ module.exports.parse = DotenvModule.parse;
389
+ module.exports.populate = DotenvModule.populate;
390
+ module.exports = DotenvModule;
391
+ }
392
+ });
393
+
394
+ // src/server.ts
395
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
396
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
397
+ import {
398
+ CallToolRequestSchema,
399
+ ListToolsRequestSchema,
400
+ ErrorCode,
401
+ McpError
402
+ } from "@modelcontextprotocol/sdk/types.js";
403
+
404
+ // src/config.ts
405
+ var import_dotenv = __toESM(require_main(), 1);
406
+ import_dotenv.default.config();
407
+ var config = {
408
+ // Weaviate
409
+ weaviate: {
410
+ url: process.env.WEAVIATE_URL || "http://localhost:8080",
411
+ apiKey: process.env.WEAVIATE_API_KEY || ""
412
+ },
413
+ // OpenAI
414
+ openai: {
415
+ apiKey: process.env.OPENAI_APIKEY || ""
416
+ },
417
+ // Firebase (using firebase-admin-sdk-v8)
418
+ firebase: {
419
+ serviceAccount: process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY || "",
420
+ projectId: process.env.FIREBASE_PROJECT_ID || ""
421
+ },
422
+ // Server
423
+ server: {
424
+ port: parseInt(process.env.PORT || "3000", 10),
425
+ nodeEnv: process.env.NODE_ENV || "development",
426
+ logLevel: process.env.LOG_LEVEL || "info"
427
+ },
428
+ // MCP
429
+ mcp: {
430
+ transport: process.env.MCP_TRANSPORT || "sse"
431
+ }
432
+ };
433
+ function validateConfig() {
434
+ const required = [
435
+ { key: "WEAVIATE_URL", value: config.weaviate.url },
436
+ { key: "OPENAI_APIKEY", value: config.openai.apiKey },
437
+ { key: "FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY", value: config.firebase.serviceAccount },
438
+ { key: "FIREBASE_PROJECT_ID", value: config.firebase.projectId }
439
+ ];
440
+ const missing = required.filter((r) => !r.value);
441
+ if (missing.length > 0) {
442
+ throw new Error(
443
+ `Missing required environment variables: ${missing.map((m) => m.key).join(", ")}`
444
+ );
445
+ }
446
+ console.log("[Config] Configuration validated");
447
+ }
448
+
449
+ // src/weaviate/client.ts
450
+ import weaviate from "weaviate-client";
451
+ var client = null;
452
+ async function initWeaviateClient() {
453
+ if (client) {
454
+ return client;
455
+ }
456
+ client = await weaviate.connectToWeaviateCloud(config.weaviate.url, {
457
+ authCredentials: config.weaviate.apiKey ? new weaviate.ApiKey(config.weaviate.apiKey) : void 0,
458
+ headers: config.openai.apiKey ? { "X-OpenAI-Api-Key": config.openai.apiKey } : void 0
459
+ });
460
+ console.log("[Weaviate] Client initialized");
461
+ return client;
462
+ }
463
+ function getWeaviateClient() {
464
+ if (!client) {
465
+ throw new Error("Weaviate client not initialized. Call initWeaviateClient() first.");
466
+ }
467
+ return client;
468
+ }
469
+ async function testWeaviateConnection() {
470
+ try {
471
+ const weaviateClient = getWeaviateClient();
472
+ const isReady = await weaviateClient.isReady();
473
+ console.log("[Weaviate] Connection successful, ready:", isReady);
474
+ return isReady;
475
+ } catch (error) {
476
+ console.error("[Weaviate] Connection failed:", error);
477
+ return false;
478
+ }
479
+ }
480
+ function sanitizeUserId(userId) {
481
+ let sanitized = userId.replace(/[^a-zA-Z0-9]/g, "_");
482
+ if (/^[0-9]/.test(sanitized)) {
483
+ sanitized = "_" + sanitized;
484
+ }
485
+ return sanitized.charAt(0).toUpperCase() + sanitized.slice(1);
486
+ }
487
+
488
+ // src/firestore/init.ts
489
+ import { initializeApp } from "@prmichaelsen/firebase-admin-sdk-v8";
490
+ import {
491
+ getDocument,
492
+ setDocument,
493
+ addDocument,
494
+ updateDocument,
495
+ deleteDocument,
496
+ queryDocuments,
497
+ batchWrite,
498
+ FieldValue,
499
+ verifyIdToken
500
+ } from "@prmichaelsen/firebase-admin-sdk-v8";
501
+ var initialized = false;
502
+ function initFirestore() {
503
+ if (initialized) {
504
+ return;
505
+ }
506
+ try {
507
+ initializeApp({
508
+ serviceAccount: JSON.parse(config.firebase.serviceAccount),
509
+ projectId: config.firebase.projectId
510
+ });
511
+ initialized = true;
512
+ console.log("[Firestore] Initialized");
513
+ } catch (error) {
514
+ console.error("[Firestore] Initialization failed:", error);
515
+ throw error;
516
+ }
517
+ }
518
+ async function testFirestoreConnection() {
519
+ try {
520
+ if (!initialized) {
521
+ throw new Error("Firestore not initialized");
522
+ }
523
+ const { getDocument: getDocument2 } = await import("@prmichaelsen/firebase-admin-sdk-v8");
524
+ await getDocument2("_health_check", "test");
525
+ console.log("[Firestore] Connection successful");
526
+ return true;
527
+ } catch (error) {
528
+ console.error("[Firestore] Connection test failed:", error);
529
+ return false;
530
+ }
531
+ }
532
+
533
+ // src/utils/logger.ts
534
+ var LOG_LEVELS = {
535
+ debug: 0,
536
+ info: 1,
537
+ warn: 2,
538
+ error: 3
539
+ };
540
+ var currentLevel = LOG_LEVELS[config.server.logLevel] ?? LOG_LEVELS.info;
541
+ function shouldLog(level) {
542
+ return LOG_LEVELS[level] >= currentLevel;
543
+ }
544
+ var logger = {
545
+ debug: (message, ...args) => {
546
+ if (shouldLog("debug")) {
547
+ console.debug(`[DEBUG] ${message}`, ...args);
548
+ }
549
+ },
550
+ info: (message, ...args) => {
551
+ if (shouldLog("info")) {
552
+ console.info(`[INFO] ${message}`, ...args);
553
+ }
554
+ },
555
+ warn: (message, ...args) => {
556
+ if (shouldLog("warn")) {
557
+ console.warn(`[WARN] ${message}`, ...args);
558
+ }
559
+ },
560
+ error: (message, ...args) => {
561
+ if (shouldLog("error")) {
562
+ console.error(`[ERROR] ${message}`, ...args);
563
+ }
564
+ }
565
+ };
566
+
567
+ // src/weaviate/schema.ts
568
+ import weaviate2 from "weaviate-client";
569
+ async function createMemoryCollection(userId) {
570
+ const client2 = getWeaviateClient();
571
+ const collectionName = `Memory_${sanitizeUserId(userId)}`;
572
+ const exists = await client2.collections.exists(collectionName);
573
+ if (exists) {
574
+ console.log(`[Weaviate] Collection ${collectionName} already exists`);
575
+ return;
576
+ }
577
+ console.log(`[Weaviate] Creating collection ${collectionName}...`);
578
+ await client2.collections.create({
579
+ name: collectionName,
580
+ // Vectorizer configuration
581
+ vectorizers: weaviate2.configure.vectorizer.text2VecOpenAI({
582
+ model: "text-embedding-3-small",
583
+ // Vectorize both memory content and relationship observations
584
+ sourceProperties: ["content", "observation"]
585
+ }),
586
+ properties: [
587
+ // Discriminator
588
+ {
589
+ name: "doc_type",
590
+ dataType: "text",
591
+ description: 'Document type: "memory" or "relationship"'
592
+ },
593
+ // Core identity
594
+ {
595
+ name: "user_id",
596
+ dataType: "text",
597
+ description: "User who owns this document"
598
+ },
599
+ // Memory fields
600
+ {
601
+ name: "content",
602
+ dataType: "text",
603
+ description: "Main memory content (vectorized)"
604
+ },
605
+ {
606
+ name: "title",
607
+ dataType: "text",
608
+ description: "Optional short title"
609
+ },
610
+ {
611
+ name: "summary",
612
+ dataType: "text",
613
+ description: "Optional brief summary"
614
+ },
615
+ {
616
+ name: "type",
617
+ dataType: "text",
618
+ description: "Content type (note, event, person, etc.)"
619
+ },
620
+ // Scoring fields
621
+ {
622
+ name: "weight",
623
+ dataType: "number",
624
+ description: "Significance/priority (0-1)"
625
+ },
626
+ {
627
+ name: "trust",
628
+ dataType: "number",
629
+ description: "Access control level (0-1)"
630
+ },
631
+ {
632
+ name: "confidence",
633
+ dataType: "number",
634
+ description: "System confidence in accuracy (0-1)"
635
+ },
636
+ // Location fields (flattened for Weaviate)
637
+ {
638
+ name: "location_gps_lat",
639
+ dataType: "number",
640
+ description: "GPS latitude"
641
+ },
642
+ {
643
+ name: "location_gps_lng",
644
+ dataType: "number",
645
+ description: "GPS longitude"
646
+ },
647
+ {
648
+ name: "location_address",
649
+ dataType: "text",
650
+ description: "Formatted address"
651
+ },
652
+ {
653
+ name: "location_city",
654
+ dataType: "text",
655
+ description: "City name"
656
+ },
657
+ {
658
+ name: "location_country",
659
+ dataType: "text",
660
+ description: "Country name"
661
+ },
662
+ {
663
+ name: "location_source",
664
+ dataType: "text",
665
+ description: "Location source (gps, ip, manual, etc.)"
666
+ },
667
+ // Locale fields
668
+ {
669
+ name: "locale_language",
670
+ dataType: "text",
671
+ description: "Language code (e.g., en, es, fr)"
672
+ },
673
+ {
674
+ name: "locale_timezone",
675
+ dataType: "text",
676
+ description: "Timezone (e.g., America/Los_Angeles)"
677
+ },
678
+ // Context fields
679
+ {
680
+ name: "context_conversation_id",
681
+ dataType: "text",
682
+ description: "Conversation ID"
683
+ },
684
+ {
685
+ name: "context_summary",
686
+ dataType: "text",
687
+ description: "Brief context summary"
688
+ },
689
+ {
690
+ name: "context_timestamp",
691
+ dataType: "date",
692
+ description: "Context timestamp"
693
+ },
694
+ // Relationships
695
+ {
696
+ name: "relationships",
697
+ dataType: "text[]",
698
+ description: "Array of relationship IDs"
699
+ },
700
+ // Access tracking
701
+ {
702
+ name: "access_count",
703
+ dataType: "number",
704
+ description: "Total times accessed"
705
+ },
706
+ {
707
+ name: "last_accessed_at",
708
+ dataType: "date",
709
+ description: "Most recent access timestamp"
710
+ },
711
+ // Metadata
712
+ {
713
+ name: "tags",
714
+ dataType: "text[]",
715
+ description: "Tags for organization"
716
+ },
717
+ {
718
+ name: "references",
719
+ dataType: "text[]",
720
+ description: "Source URLs"
721
+ },
722
+ {
723
+ name: "created_at",
724
+ dataType: "date",
725
+ description: "Creation timestamp"
726
+ },
727
+ {
728
+ name: "updated_at",
729
+ dataType: "date",
730
+ description: "Last update timestamp"
731
+ },
732
+ {
733
+ name: "version",
734
+ dataType: "number",
735
+ description: "Version number"
736
+ },
737
+ // Template fields
738
+ {
739
+ name: "template_id",
740
+ dataType: "text",
741
+ description: "Template ID if using template"
742
+ },
743
+ // Relationship-specific fields
744
+ {
745
+ name: "memory_ids",
746
+ dataType: "text[]",
747
+ description: "Connected memory IDs (for relationships)"
748
+ },
749
+ {
750
+ name: "relationship_type",
751
+ dataType: "text",
752
+ description: "Relationship type (for relationships)"
753
+ },
754
+ {
755
+ name: "observation",
756
+ dataType: "text",
757
+ description: "Relationship observation (vectorized)"
758
+ },
759
+ {
760
+ name: "strength",
761
+ dataType: "number",
762
+ description: "Relationship strength (0-1)"
763
+ },
764
+ // Computed fields
765
+ {
766
+ name: "base_weight",
767
+ dataType: "number",
768
+ description: "User-specified weight"
769
+ },
770
+ {
771
+ name: "computed_weight",
772
+ dataType: "number",
773
+ description: "Calculated effective weight"
774
+ }
775
+ ]
776
+ });
777
+ console.log(`[Weaviate] Collection ${collectionName} created successfully`);
778
+ }
779
+ async function ensureMemoryCollection(userId) {
780
+ const client2 = getWeaviateClient();
781
+ const collectionName = `Memory_${sanitizeUserId(userId)}`;
782
+ const exists = await client2.collections.exists(collectionName);
783
+ if (!exists) {
784
+ await createMemoryCollection(userId);
785
+ }
786
+ }
787
+ function getMemoryCollection(userId) {
788
+ const client2 = getWeaviateClient();
789
+ const collectionName = `Memory_${sanitizeUserId(userId)}`;
790
+ return client2.collections.get(collectionName);
791
+ }
792
+
793
+ // src/constants/content-types.ts
794
+ var CONTENT_TYPES = [
795
+ // Core types
796
+ "code",
797
+ "note",
798
+ "documentation",
799
+ "reference",
800
+ // Task & Planning
801
+ "todo",
802
+ "checklist",
803
+ "project",
804
+ "goal",
805
+ "habit",
806
+ // Communication
807
+ "email",
808
+ "conversation",
809
+ "meeting",
810
+ "person",
811
+ // Unified person template (replaces both 'contact' and 'person')
812
+ // Content & Media
813
+ "article",
814
+ "webpage",
815
+ "social",
816
+ "image",
817
+ "video",
818
+ "audio",
819
+ "transcript",
820
+ "presentation",
821
+ "spreadsheet",
822
+ "pdf",
823
+ // Creative
824
+ "screenplay",
825
+ "recipe",
826
+ "idea",
827
+ "quote",
828
+ // Personal
829
+ "journal",
830
+ "memory",
831
+ "event",
832
+ // Organizational
833
+ "bookmark",
834
+ "form",
835
+ "location",
836
+ // Business
837
+ "invoice",
838
+ "contract",
839
+ // System
840
+ "system",
841
+ "action",
842
+ "audit",
843
+ "history"
844
+ ];
845
+ var CONTENT_TYPE_METADATA = {
846
+ // Core Types
847
+ code: {
848
+ name: "code",
849
+ category: "core",
850
+ description: "Source code files and programming content",
851
+ examples: ["Code snippets", "Functions", "Scripts", "Configuration files"],
852
+ common_fields: ["language", "framework", "purpose"]
853
+ },
854
+ note: {
855
+ name: "note",
856
+ category: "core",
857
+ description: "Personal notes and quick documentation",
858
+ examples: ["Quick notes", "Reminders", "Observations", "Thoughts"]
859
+ },
860
+ documentation: {
861
+ name: "documentation",
862
+ category: "core",
863
+ description: "Technical documentation and guides",
864
+ examples: ["API docs", "User guides", "Technical specs", "How-to guides"]
865
+ },
866
+ reference: {
867
+ name: "reference",
868
+ category: "core",
869
+ description: "Quick reference guides and cheat sheets",
870
+ examples: ["Command references", "Keyboard shortcuts", "API references", "Cheat sheets"]
871
+ },
872
+ // Task & Planning
873
+ todo: {
874
+ name: "todo",
875
+ category: "task",
876
+ description: "Individual tasks with due dates and priorities",
877
+ examples: ["Task items", "Action items", "Assignments"],
878
+ common_fields: ["due_date", "priority", "status", "assignee"]
879
+ },
880
+ checklist: {
881
+ name: "checklist",
882
+ category: "task",
883
+ description: "Reusable checklists and sequential steps",
884
+ examples: ["Grocery lists", "Packing lists", "Process checklists", "Preparation lists"],
885
+ common_fields: ["items", "completion_percentage"]
886
+ },
887
+ project: {
888
+ name: "project",
889
+ category: "task",
890
+ description: "Project plans and overviews",
891
+ examples: ["Project documentation", "Project plans", "Milestones"],
892
+ common_fields: ["status", "start_date", "end_date", "stakeholders"]
893
+ },
894
+ goal: {
895
+ name: "goal",
896
+ category: "task",
897
+ description: "Goals, objectives, and milestones",
898
+ examples: ["Personal goals", "Professional objectives", "KPIs"],
899
+ common_fields: ["target_date", "progress", "milestones"]
900
+ },
901
+ habit: {
902
+ name: "habit",
903
+ category: "task",
904
+ description: "Routines and habit tracking",
905
+ examples: ["Daily habits", "Routines", "Recurring activities"],
906
+ common_fields: ["frequency", "streak", "trigger"]
907
+ },
908
+ // Communication
909
+ email: {
910
+ name: "email",
911
+ category: "communication",
912
+ description: "Email messages and threads",
913
+ examples: ["Email messages", "Email threads", "Drafts"],
914
+ common_fields: ["from", "to", "subject", "date"]
915
+ },
916
+ conversation: {
917
+ name: "conversation",
918
+ category: "communication",
919
+ description: "Chat logs and conversations",
920
+ examples: ["Chat messages", "Conversation logs", "Discussions"],
921
+ common_fields: ["participants", "platform"]
922
+ },
923
+ meeting: {
924
+ name: "meeting",
925
+ category: "communication",
926
+ description: "Meeting notes and action items",
927
+ examples: ["Meeting notes", "Standup notes", "Conference calls"],
928
+ common_fields: ["attendees", "agenda", "decisions", "action_items"]
929
+ },
930
+ person: {
931
+ name: "person",
932
+ category: "communication",
933
+ description: "Track information about people - personal, professional, or both",
934
+ examples: ["Friends", "Family", "Colleagues", "Professional contacts", "Business partners"],
935
+ common_fields: ["name", "relationship", "company", "job_title", "how_we_met", "contact_info", "birthday", "interests"]
936
+ },
937
+ // Content & Media
938
+ article: {
939
+ name: "article",
940
+ category: "content",
941
+ description: "Articles and blog posts",
942
+ examples: ["Blog posts", "News articles", "Long-form content"],
943
+ common_fields: ["author", "publication", "url"]
944
+ },
945
+ webpage: {
946
+ name: "webpage",
947
+ category: "content",
948
+ description: "Saved web pages and HTML content",
949
+ examples: ["Web pages", "HTML documents", "Web content"],
950
+ common_fields: ["url", "domain", "archived_at"]
951
+ },
952
+ social: {
953
+ name: "social",
954
+ category: "content",
955
+ description: "Social media posts and updates",
956
+ examples: ["Tweets", "Posts", "Status updates"],
957
+ common_fields: ["platform", "author", "url"]
958
+ },
959
+ image: {
960
+ name: "image",
961
+ category: "media",
962
+ description: "Image files and visual content",
963
+ examples: ["Photos", "Screenshots", "Diagrams", "Illustrations"],
964
+ common_fields: ["file_path", "dimensions", "format"]
965
+ },
966
+ video: {
967
+ name: "video",
968
+ category: "media",
969
+ description: "Video files and recordings",
970
+ examples: ["Videos", "Recordings", "Tutorials"],
971
+ common_fields: ["duration", "format", "url"]
972
+ },
973
+ audio: {
974
+ name: "audio",
975
+ category: "media",
976
+ description: "Audio files and recordings",
977
+ examples: ["Voice notes", "Podcasts", "Music", "Recordings"],
978
+ common_fields: ["duration", "format"]
979
+ },
980
+ transcript: {
981
+ name: "transcript",
982
+ category: "media",
983
+ description: "Transcriptions of audio or video",
984
+ examples: ["Meeting transcripts", "Podcast transcripts", "Video captions"],
985
+ common_fields: ["source_media", "speakers"]
986
+ },
987
+ presentation: {
988
+ name: "presentation",
989
+ category: "content",
990
+ description: "Presentation slides and decks",
991
+ examples: ["Slide decks", "Pitch decks", "Presentations"],
992
+ common_fields: ["slide_count", "format"]
993
+ },
994
+ spreadsheet: {
995
+ name: "spreadsheet",
996
+ category: "content",
997
+ description: "Data tables and spreadsheet content",
998
+ examples: ["Spreadsheets", "Data tables", "CSV content"],
999
+ common_fields: ["rows", "columns", "format"]
1000
+ },
1001
+ pdf: {
1002
+ name: "pdf",
1003
+ category: "content",
1004
+ description: "PDF documents and scanned files",
1005
+ examples: ["PDF documents", "Scanned documents", "Reports"],
1006
+ common_fields: ["pages", "file_size"]
1007
+ },
1008
+ // Creative
1009
+ screenplay: {
1010
+ name: "screenplay",
1011
+ category: "creative",
1012
+ description: "Screenplay and script content",
1013
+ examples: ["Screenplays", "Scripts", "Dialogue"],
1014
+ common_fields: ["characters", "scenes"]
1015
+ },
1016
+ recipe: {
1017
+ name: "recipe",
1018
+ category: "creative",
1019
+ description: "Cooking recipes and instructions",
1020
+ examples: ["Recipes", "Cooking instructions", "Meal plans"],
1021
+ common_fields: ["ingredients", "instructions", "servings", "prep_time", "cook_time"]
1022
+ },
1023
+ idea: {
1024
+ name: "idea",
1025
+ category: "creative",
1026
+ description: "Brainstorming and concepts",
1027
+ examples: ["Ideas", "Brainstorms", "Concepts", "Inspiration"],
1028
+ common_fields: ["category", "potential_impact"]
1029
+ },
1030
+ quote: {
1031
+ name: "quote",
1032
+ category: "creative",
1033
+ description: "Memorable quotes and excerpts",
1034
+ examples: ["Quotes", "Excerpts", "Highlights", "Citations"],
1035
+ common_fields: ["author", "source"]
1036
+ },
1037
+ // Personal
1038
+ journal: {
1039
+ name: "journal",
1040
+ category: "personal",
1041
+ description: "Daily journal entries and reflections",
1042
+ examples: ["Journal entries", "Diary entries", "Reflections"],
1043
+ common_fields: ["date", "mood", "highlights"]
1044
+ },
1045
+ memory: {
1046
+ name: "memory",
1047
+ category: "personal",
1048
+ description: "Personal memories and significant moments",
1049
+ examples: ["Life events", "Significant moments", "Memories"],
1050
+ common_fields: ["date", "people_involved", "location"]
1051
+ },
1052
+ event: {
1053
+ name: "event",
1054
+ category: "personal",
1055
+ description: "Calendar events and activities",
1056
+ examples: ["Events", "Activities", "Appointments"],
1057
+ common_fields: ["date", "time", "location", "attendees"]
1058
+ },
1059
+ // Organizational
1060
+ bookmark: {
1061
+ name: "bookmark",
1062
+ category: "organizational",
1063
+ description: "Web bookmarks and resource collections",
1064
+ examples: ["Bookmarks", "Resource links", "Reading lists"],
1065
+ common_fields: ["url", "domain", "read_later"]
1066
+ },
1067
+ form: {
1068
+ name: "form",
1069
+ category: "organizational",
1070
+ description: "Forms and surveys",
1071
+ examples: ["Questionnaires", "Feedback forms", "Surveys"],
1072
+ common_fields: ["fields", "responses"]
1073
+ },
1074
+ location: {
1075
+ name: "location",
1076
+ category: "organizational",
1077
+ description: "Place information and recommendations",
1078
+ examples: ["Places", "Venues", "Destinations", "Locations"],
1079
+ common_fields: ["address", "gps", "rating"]
1080
+ },
1081
+ // Business
1082
+ invoice: {
1083
+ name: "invoice",
1084
+ category: "business",
1085
+ description: "Invoices and receipts",
1086
+ examples: ["Invoices", "Receipts", "Bills"],
1087
+ common_fields: ["amount", "date", "vendor", "items"]
1088
+ },
1089
+ contract: {
1090
+ name: "contract",
1091
+ category: "business",
1092
+ description: "Contracts and agreements",
1093
+ examples: ["Contracts", "Agreements", "Terms of service"],
1094
+ common_fields: ["parties", "effective_date", "terms"]
1095
+ },
1096
+ // System
1097
+ system: {
1098
+ name: "system",
1099
+ category: "system",
1100
+ description: "Agent instructions (reserved for internal use only)",
1101
+ examples: ["System prompts", "Agent instructions", "Configuration"]
1102
+ },
1103
+ action: {
1104
+ name: "action",
1105
+ category: "system",
1106
+ description: "Agent actions and operations",
1107
+ examples: ["Actions taken", "Operations performed", "Commands executed"],
1108
+ common_fields: ["action_type", "status", "result"]
1109
+ },
1110
+ audit: {
1111
+ name: "audit",
1112
+ category: "system",
1113
+ description: "Audit logs and compliance records",
1114
+ examples: ["Audit logs", "Access logs", "Security events"],
1115
+ common_fields: ["event_type", "actor", "target", "result"]
1116
+ },
1117
+ history: {
1118
+ name: "history",
1119
+ category: "system",
1120
+ description: "Change history and version tracking",
1121
+ examples: ["Edit history", "Version history", "Change logs"],
1122
+ common_fields: ["target_id", "change_type", "previous_value", "new_value"]
1123
+ }
1124
+ };
1125
+ function isValidContentType(type) {
1126
+ return CONTENT_TYPES.includes(type);
1127
+ }
1128
+ function getContentTypeDescription() {
1129
+ const categoryNames = {
1130
+ core: "Core Types",
1131
+ task: "Task & Planning",
1132
+ communication: "Communication",
1133
+ content: "Content & Media",
1134
+ media: "Content & Media",
1135
+ creative: "Creative",
1136
+ personal: "Personal",
1137
+ organizational: "Organizational",
1138
+ business: "Business",
1139
+ system: "System (Internal Use)"
1140
+ };
1141
+ const lines = ["Type of content:", ""];
1142
+ const categorized = /* @__PURE__ */ new Map();
1143
+ for (const type of CONTENT_TYPES) {
1144
+ const metadata = CONTENT_TYPE_METADATA[type];
1145
+ const categoryKey = metadata.category;
1146
+ if (!categorized.has(categoryKey)) {
1147
+ categorized.set(categoryKey, []);
1148
+ }
1149
+ categorized.get(categoryKey).push(type);
1150
+ }
1151
+ for (const [categoryKey, types] of categorized) {
1152
+ const categoryName = categoryNames[categoryKey] || categoryKey;
1153
+ lines.push(`${categoryName}:`);
1154
+ for (const type of types) {
1155
+ const metadata = CONTENT_TYPE_METADATA[type];
1156
+ lines.push(` - '${type}': ${metadata.description}`);
1157
+ }
1158
+ lines.push("");
1159
+ }
1160
+ return lines.join("\n").trim();
1161
+ }
1162
+ var DEFAULT_CONTENT_TYPE = "note";
1163
+
1164
+ // src/tools/create-memory.ts
1165
+ var createMemoryTool = {
1166
+ name: "remember_create_memory",
1167
+ description: `Create a new memory with optional template.
1168
+
1169
+ Memories can store any type of information: notes, events, people, recipes, etc.
1170
+ Each memory has a weight (significance 0-1) and trust level (access control 0-1).
1171
+ Location and context are automatically captured from the request.
1172
+
1173
+ Examples:
1174
+ - "Remember that I met Sarah at the conference"
1175
+ - "Save this recipe for chocolate chip cookies"
1176
+ - "Note that my tent is stored in garage bin A4"
1177
+ `,
1178
+ inputSchema: {
1179
+ type: "object",
1180
+ properties: {
1181
+ content: {
1182
+ type: "string",
1183
+ description: "Memory content (main text)"
1184
+ },
1185
+ title: {
1186
+ type: "string",
1187
+ description: "Optional short title"
1188
+ },
1189
+ type: {
1190
+ type: "string",
1191
+ description: getContentTypeDescription(),
1192
+ default: DEFAULT_CONTENT_TYPE
1193
+ },
1194
+ weight: {
1195
+ type: "number",
1196
+ description: "Significance/priority (0-1, default: 0.5)",
1197
+ minimum: 0,
1198
+ maximum: 1
1199
+ },
1200
+ trust: {
1201
+ type: "number",
1202
+ description: "Access control level (0-1, default: 0.5)",
1203
+ minimum: 0,
1204
+ maximum: 1
1205
+ },
1206
+ tags: {
1207
+ type: "array",
1208
+ items: { type: "string" },
1209
+ description: "Tags for organization"
1210
+ },
1211
+ references: {
1212
+ type: "array",
1213
+ items: { type: "string" },
1214
+ description: "Source URLs"
1215
+ },
1216
+ template_id: {
1217
+ type: "string",
1218
+ description: "Template ID to use (optional)"
1219
+ },
1220
+ skip_template_suggestion: {
1221
+ type: "boolean",
1222
+ description: "Skip automatic template suggestion",
1223
+ default: false
1224
+ }
1225
+ },
1226
+ required: ["content"]
1227
+ }
1228
+ };
1229
+ async function handleCreateMemory(args, userId, context) {
1230
+ try {
1231
+ logger.info("Creating memory", { userId, type: args.type });
1232
+ await ensureMemoryCollection(userId);
1233
+ const collection = getMemoryCollection(userId);
1234
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1235
+ const memory = {
1236
+ // Core identity
1237
+ user_id: userId,
1238
+ doc_type: "memory",
1239
+ // Content
1240
+ content: args.content,
1241
+ title: args.title,
1242
+ summary: args.title,
1243
+ // Use title as summary for now
1244
+ type: args.type && isValidContentType(args.type) ? args.type : DEFAULT_CONTENT_TYPE,
1245
+ // Scoring
1246
+ weight: args.weight ?? 0.5,
1247
+ trust: args.trust ?? 0.5,
1248
+ confidence: 1,
1249
+ // Location (from context or default)
1250
+ location: {
1251
+ gps: null,
1252
+ address: null,
1253
+ source: "unavailable",
1254
+ confidence: 0,
1255
+ is_approximate: true
1256
+ },
1257
+ // Context
1258
+ context: {
1259
+ timestamp: now,
1260
+ source: {
1261
+ type: "api",
1262
+ platform: "mcp"
1263
+ },
1264
+ summary: context?.summary || "Memory created via MCP",
1265
+ conversation_id: context?.conversation_id,
1266
+ ...context
1267
+ },
1268
+ // Relationships
1269
+ relationships: [],
1270
+ // Access tracking
1271
+ access_count: 0,
1272
+ last_accessed_at: now,
1273
+ access_frequency: 0,
1274
+ // Metadata
1275
+ created_at: now,
1276
+ updated_at: now,
1277
+ version: 1,
1278
+ tags: args.tags || [],
1279
+ references: args.references || [],
1280
+ // Template
1281
+ template_id: args.template_id,
1282
+ structured_content: args.structured_content,
1283
+ // Computed weight
1284
+ base_weight: args.weight ?? 0.5,
1285
+ computed_weight: args.weight ?? 0.5
1286
+ };
1287
+ const result = await collection.data.insert(memory);
1288
+ logger.info("Memory created successfully", { memoryId: result, userId });
1289
+ const response = {
1290
+ memory_id: result,
1291
+ created_at: now,
1292
+ message: `Memory created successfully with ID: ${result}`
1293
+ };
1294
+ return JSON.stringify(response, null, 2);
1295
+ } catch (error) {
1296
+ logger.error("Failed to create memory:", error);
1297
+ throw new Error(`Failed to create memory: ${error instanceof Error ? error.message : String(error)}`);
1298
+ }
1299
+ }
1300
+
1301
+ // src/tools/search-memory.ts
1302
+ var searchMemoryTool = {
1303
+ name: "remember_search_memory",
1304
+ description: `Search memories using hybrid semantic and keyword search.
1305
+
1306
+ Supports:
1307
+ - Semantic search (meaning-based)
1308
+ - Keyword search (exact matches)
1309
+ - Hybrid search (balanced with alpha parameter)
1310
+ - Filtering by type, tags, weight, trust, date range
1311
+ - Location-based search
1312
+
1313
+ Examples:
1314
+ - "Find memories about camping trips"
1315
+ - "Search for recipes I saved"
1316
+ - "Show me notes from last week"
1317
+ `,
1318
+ inputSchema: {
1319
+ type: "object",
1320
+ properties: {
1321
+ query: {
1322
+ type: "string",
1323
+ description: "Search query"
1324
+ },
1325
+ alpha: {
1326
+ type: "number",
1327
+ description: "Balance between semantic (1.0) and keyword (0.0) search. Default: 0.7",
1328
+ minimum: 0,
1329
+ maximum: 1,
1330
+ default: 0.7
1331
+ },
1332
+ limit: {
1333
+ type: "number",
1334
+ description: "Maximum number of results. Default: 10",
1335
+ minimum: 1,
1336
+ maximum: 100,
1337
+ default: 10
1338
+ },
1339
+ offset: {
1340
+ type: "number",
1341
+ description: "Pagination offset. Default: 0",
1342
+ minimum: 0,
1343
+ default: 0
1344
+ },
1345
+ filters: {
1346
+ type: "object",
1347
+ description: "Optional filters",
1348
+ properties: {
1349
+ types: {
1350
+ type: "array",
1351
+ items: { type: "string" },
1352
+ description: "Filter by content types"
1353
+ },
1354
+ tags: {
1355
+ type: "array",
1356
+ items: { type: "string" },
1357
+ description: "Filter by tags"
1358
+ },
1359
+ weight_min: {
1360
+ type: "number",
1361
+ description: "Minimum weight (0-1)"
1362
+ },
1363
+ trust_min: {
1364
+ type: "number",
1365
+ description: "Minimum trust level (0-1)"
1366
+ },
1367
+ date_from: {
1368
+ type: "string",
1369
+ description: "Start date (ISO 8601)"
1370
+ },
1371
+ date_to: {
1372
+ type: "string",
1373
+ description: "End date (ISO 8601)"
1374
+ }
1375
+ }
1376
+ },
1377
+ include_relationships: {
1378
+ type: "boolean",
1379
+ description: "Include relationships in results. Default: false",
1380
+ default: false
1381
+ }
1382
+ },
1383
+ required: ["query"]
1384
+ }
1385
+ };
1386
+ async function handleSearchMemory(args, userId) {
1387
+ try {
1388
+ logger.info("Searching memories", { userId, query: args.query });
1389
+ const collection = getMemoryCollection(userId);
1390
+ const alpha = args.alpha ?? 0.7;
1391
+ const limit = args.limit ?? 10;
1392
+ const offset = args.offset ?? 0;
1393
+ const whereFilters = [
1394
+ {
1395
+ path: "doc_type",
1396
+ operator: "Equal",
1397
+ valueText: "memory"
1398
+ }
1399
+ ];
1400
+ if (args.filters?.types && args.filters.types.length > 0) {
1401
+ whereFilters.push({
1402
+ path: "type",
1403
+ operator: "ContainsAny",
1404
+ valueTextArray: args.filters.types
1405
+ });
1406
+ }
1407
+ if (args.filters?.weight_min !== void 0) {
1408
+ whereFilters.push({
1409
+ path: "weight",
1410
+ operator: "GreaterThanEqual",
1411
+ valueNumber: args.filters.weight_min
1412
+ });
1413
+ }
1414
+ if (args.filters?.trust_min !== void 0) {
1415
+ whereFilters.push({
1416
+ path: "trust",
1417
+ operator: "GreaterThanEqual",
1418
+ valueNumber: args.filters.trust_min
1419
+ });
1420
+ }
1421
+ if (args.filters?.date_from) {
1422
+ whereFilters.push({
1423
+ path: "created_at",
1424
+ operator: "GreaterThanEqual",
1425
+ valueDate: new Date(args.filters.date_from)
1426
+ });
1427
+ }
1428
+ if (args.filters?.date_to) {
1429
+ whereFilters.push({
1430
+ path: "created_at",
1431
+ operator: "LessThanEqual",
1432
+ valueDate: new Date(args.filters.date_to)
1433
+ });
1434
+ }
1435
+ const searchOptions = {
1436
+ alpha,
1437
+ limit: limit + offset
1438
+ // Get extra for offset
1439
+ };
1440
+ if (whereFilters.length > 0) {
1441
+ searchOptions.filters = whereFilters.length > 1 ? {
1442
+ operator: "And",
1443
+ operands: whereFilters
1444
+ } : whereFilters[0];
1445
+ }
1446
+ const results = await collection.query.hybrid(args.query, searchOptions);
1447
+ const paginatedResults = results.objects.slice(offset);
1448
+ const memories = paginatedResults.map((obj) => ({
1449
+ id: obj.uuid,
1450
+ ...obj.properties
1451
+ }));
1452
+ const searchResult = {
1453
+ memories,
1454
+ total: memories.length,
1455
+ offset,
1456
+ limit
1457
+ };
1458
+ if (args.include_relationships) {
1459
+ searchResult.relationships = [];
1460
+ }
1461
+ logger.info("Search completed", {
1462
+ userId,
1463
+ query: args.query,
1464
+ results: memories.length
1465
+ });
1466
+ return JSON.stringify(searchResult, null, 2);
1467
+ } catch (error) {
1468
+ logger.error("Failed to search memories:", error);
1469
+ throw new Error(`Failed to search memories: ${error instanceof Error ? error.message : String(error)}`);
1470
+ }
1471
+ }
1472
+
1473
+ // src/tools/delete-memory.ts
1474
+ var deleteMemoryTool = {
1475
+ name: "remember_delete_memory",
1476
+ description: `Delete a memory from your collection.
1477
+
1478
+ Optionally delete connected relationships as well.
1479
+ This action cannot be undone.
1480
+
1481
+ Examples:
1482
+ - "Delete that old camping note"
1483
+ - "Remove the recipe I saved yesterday"
1484
+ `,
1485
+ inputSchema: {
1486
+ type: "object",
1487
+ properties: {
1488
+ memory_id: {
1489
+ type: "string",
1490
+ description: "ID of the memory to delete"
1491
+ },
1492
+ delete_relationships: {
1493
+ type: "boolean",
1494
+ description: "Also delete connected relationships. Default: false",
1495
+ default: false
1496
+ }
1497
+ },
1498
+ required: ["memory_id"]
1499
+ }
1500
+ };
1501
+ async function handleDeleteMemory(args, userId) {
1502
+ try {
1503
+ logger.info("Deleting memory", { userId, memoryId: args.memory_id });
1504
+ const collection = getMemoryCollection(userId);
1505
+ const memory = await collection.query.fetchObjectById(args.memory_id, {
1506
+ returnProperties: ["user_id", "doc_type", "relationships"]
1507
+ });
1508
+ if (!memory) {
1509
+ throw new Error(`Memory not found: ${args.memory_id}`);
1510
+ }
1511
+ if (memory.properties.user_id !== userId) {
1512
+ throw new Error("Unauthorized: Cannot delete another user's memory");
1513
+ }
1514
+ if (memory.properties.doc_type !== "memory") {
1515
+ throw new Error("Cannot delete relationships using this tool. Use remember_delete_relationship instead.");
1516
+ }
1517
+ let relationshipsDeleted = 0;
1518
+ if (args.delete_relationships && memory.properties.relationships) {
1519
+ const relationshipIds = memory.properties.relationships;
1520
+ for (const relId of relationshipIds) {
1521
+ try {
1522
+ await collection.data.deleteById(relId);
1523
+ relationshipsDeleted++;
1524
+ } catch (error) {
1525
+ logger.warn(`Failed to delete relationship ${relId}:`, error);
1526
+ }
1527
+ }
1528
+ }
1529
+ await collection.data.deleteById(args.memory_id);
1530
+ logger.info("Memory deleted successfully", {
1531
+ userId,
1532
+ memoryId: args.memory_id,
1533
+ relationshipsDeleted
1534
+ });
1535
+ const result = {
1536
+ memory_id: args.memory_id,
1537
+ deleted: true,
1538
+ relationships_deleted: relationshipsDeleted,
1539
+ message: `Memory deleted successfully${relationshipsDeleted > 0 ? ` (${relationshipsDeleted} relationships also deleted)` : ""}`
1540
+ };
1541
+ return JSON.stringify(result, null, 2);
1542
+ } catch (error) {
1543
+ logger.error("Failed to delete memory:", error);
1544
+ throw new Error(`Failed to delete memory: ${error instanceof Error ? error.message : String(error)}`);
1545
+ }
1546
+ }
1547
+
1548
+ // src/server.ts
1549
+ async function initServer() {
1550
+ logger.info("Initializing remember-mcp server...");
1551
+ validateConfig();
1552
+ logger.info("Connecting to databases...");
1553
+ await initWeaviateClient();
1554
+ initFirestore();
1555
+ const weaviateOk = await testWeaviateConnection();
1556
+ const firestoreOk = await testFirestoreConnection();
1557
+ if (!weaviateOk || !firestoreOk) {
1558
+ throw new Error("Database connection failed");
1559
+ }
1560
+ logger.info("Database connections established");
1561
+ const server = new Server(
1562
+ {
1563
+ name: "remember-mcp",
1564
+ version: "0.1.0"
1565
+ },
1566
+ {
1567
+ capabilities: {
1568
+ tools: {}
1569
+ }
1570
+ }
1571
+ );
1572
+ registerHandlers(server);
1573
+ logger.info("Server initialized successfully");
1574
+ return server;
1575
+ }
1576
+ function registerHandlers(server) {
1577
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
1578
+ return {
1579
+ tools: [
1580
+ {
1581
+ name: "health_check",
1582
+ description: "Check server health and database connections",
1583
+ inputSchema: {
1584
+ type: "object",
1585
+ properties: {}
1586
+ }
1587
+ },
1588
+ createMemoryTool,
1589
+ searchMemoryTool,
1590
+ deleteMemoryTool
1591
+ ]
1592
+ };
1593
+ });
1594
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1595
+ const { name, arguments: args } = request.params;
1596
+ try {
1597
+ let result;
1598
+ const userId = args.user_id || "default_user";
1599
+ switch (name) {
1600
+ case "health_check":
1601
+ result = await handleHealthCheck();
1602
+ break;
1603
+ case "remember_create_memory":
1604
+ result = await handleCreateMemory(args, userId);
1605
+ break;
1606
+ case "remember_search_memory":
1607
+ result = await handleSearchMemory(args, userId);
1608
+ break;
1609
+ case "remember_delete_memory":
1610
+ result = await handleDeleteMemory(args, userId);
1611
+ break;
1612
+ default:
1613
+ throw new McpError(
1614
+ ErrorCode.MethodNotFound,
1615
+ `Unknown tool: ${name}`
1616
+ );
1617
+ }
1618
+ return {
1619
+ content: [
1620
+ {
1621
+ type: "text",
1622
+ text: result
1623
+ }
1624
+ ]
1625
+ };
1626
+ } catch (error) {
1627
+ if (error instanceof McpError) {
1628
+ throw error;
1629
+ }
1630
+ logger.error(`Tool execution failed for ${name}:`, error);
1631
+ throw new McpError(
1632
+ ErrorCode.InternalError,
1633
+ `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`
1634
+ );
1635
+ }
1636
+ });
1637
+ }
1638
+ async function handleHealthCheck() {
1639
+ const health = {
1640
+ status: "healthy",
1641
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1642
+ server: {
1643
+ name: "remember-mcp",
1644
+ version: "0.1.0"
1645
+ },
1646
+ databases: {
1647
+ weaviate: {
1648
+ connected: false,
1649
+ url: config.weaviate.url
1650
+ },
1651
+ firestore: {
1652
+ connected: false,
1653
+ projectId: config.firebase.projectId
1654
+ }
1655
+ }
1656
+ };
1657
+ try {
1658
+ const weaviateClient = getWeaviateClient();
1659
+ health.databases.weaviate.connected = await weaviateClient.isReady();
1660
+ } catch (error) {
1661
+ logger.error("Weaviate health check failed:", error);
1662
+ health.databases.weaviate.connected = false;
1663
+ }
1664
+ try {
1665
+ health.databases.firestore.connected = await testFirestoreConnection();
1666
+ } catch (error) {
1667
+ logger.error("Firestore health check failed:", error);
1668
+ health.databases.firestore.connected = false;
1669
+ }
1670
+ const allHealthy = health.databases.weaviate.connected && health.databases.firestore.connected;
1671
+ health.status = allHealthy ? "healthy" : "degraded";
1672
+ return JSON.stringify(health, null, 2);
1673
+ }
1674
+ async function main() {
1675
+ try {
1676
+ logger.info("Starting remember-mcp server...");
1677
+ const server = await initServer();
1678
+ const transport = new StdioServerTransport();
1679
+ await server.connect(transport);
1680
+ logger.info("Server running on stdio transport");
1681
+ } catch (error) {
1682
+ logger.error("Server startup failed:", error);
1683
+ process.exit(1);
1684
+ }
1685
+ }
1686
+ main().catch((error) => {
1687
+ logger.error("Fatal error:", error);
1688
+ process.exit(1);
1689
+ });
1690
+ //# sourceMappingURL=server.js.map