@melandlabs/memory-store 0.1.1 → 0.1.3

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.
@@ -5,7 +5,8 @@ import { z } from 'zod';
5
5
  import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
6
6
  import { ChromaVectorStore } from '@melandlabs/rag';
7
7
  import { mkdirSync } from 'fs';
8
- import { dirname } from 'path';
8
+ import { homedir } from 'os';
9
+ import { join, dirname } from 'path';
9
10
  import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
10
11
 
11
12
  // src/contracts.ts
@@ -150,16 +151,14 @@ function hasPostgresFactory() {
150
151
  return factory !== null;
151
152
  }
152
153
  var manager = null;
153
- function isSQLiteRawMessageStorageAvailable(env) {
154
- return resolveEnv(env).isTauriMode();
154
+ var DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
155
+ function resolveSQLiteRawMessageDbPath() {
156
+ const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
157
+ return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
155
158
  }
156
- async function getSQLiteRawMessageManager(env) {
157
- const e = resolveEnv(env);
158
- if (!e.isTauriMode()) {
159
- throw new Error("SQLite raw message storage is only available in Tauri mode.");
160
- }
159
+ async function getSQLiteRawMessageManager(_env) {
161
160
  if (!manager) {
162
- const dbPath = e.getTauriDbPath?.() ?? process.env.TAURI_DB_PATH ?? "";
161
+ const dbPath = resolveSQLiteRawMessageDbPath();
163
162
  mkdirSync(dirname(dbPath), { recursive: true });
164
163
  manager = new SQLiteRawMessageManager({
165
164
  dbPath,
@@ -176,49 +175,39 @@ async function closeSQLiteRawMessageManager() {
176
175
  await manager.close();
177
176
  manager = null;
178
177
  }
179
- function resolveEnv(env) {
180
- if (env) return env;
181
- return {
182
- isTauriMode: () => process.env.IS_TAURI === "true" || typeof process.env.TAURI_MODE === "string",
183
- getTauriDbPath: () => process.env.TAURI_DB_PATH ?? "",
184
- getTauriDataDir: () => process.env.TAURI_DATA_DIR ?? ""
185
- };
186
- }
187
178
 
188
179
  // src/storage/raw-message-store.ts
189
- function resolveEnv2(env) {
190
- if (env) return env;
191
- return {
192
- isTauriMode: () => process.env.IS_TAURI === "true" || typeof process.env.TAURI_MODE === "string",
193
- getTauriDbPath: () => process.env.TAURI_DB_PATH ?? "",
194
- getTauriDataDir: () => process.env.TAURI_DATA_DIR ?? ""
195
- };
180
+ function resolveBackend(env) {
181
+ const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
182
+ if (explicit === "postgres") return "postgres";
183
+ return "sqlite";
184
+ }
185
+ function resolveEnv(_env) {
186
+ return {};
196
187
  }
197
188
  function createRawMessageStore(options = {}) {
198
- const env = resolveEnv2(options.env);
189
+ resolveEnv(options.env);
199
190
  return {
200
191
  getBackend() {
201
- return isSQLiteRawMessageStorageAvailable(env) ? "sqlite" : "postgres";
192
+ return resolveBackend();
202
193
  },
203
194
  isAvailable() {
204
- return isSQLiteRawMessageStorageAvailable(env) || hasPostgresFactory();
195
+ return resolveBackend() === "sqlite" || hasPostgresFactory();
205
196
  },
206
197
  async getManager() {
207
- if (isSQLiteRawMessageStorageAvailable(env)) {
208
- return await getSQLiteRawMessageManager(env);
209
- }
210
- const pg = await resolvePostgresFactory();
211
- if (!pg) {
212
- throw new Error(
213
- "No raw-message backend available. SQLite is disabled (not Tauri) and no Postgres factory is registered."
214
- );
198
+ if (resolveBackend() === "postgres") {
199
+ const pg = await resolvePostgresFactory();
200
+ if (!pg) {
201
+ throw new Error(
202
+ "OPENCONTEXT_MEMORY_STORE_BACKEND=postgres but no Postgres factory is registered. Call registerPostgresFactory() at startup, or unset the env var to use the default SQLite backend."
203
+ );
204
+ }
205
+ return pg;
215
206
  }
216
- return pg;
207
+ return await getSQLiteRawMessageManager();
217
208
  },
218
209
  async close() {
219
- if (isSQLiteRawMessageStorageAvailable(env)) {
220
- await closeSQLiteRawMessageManager();
221
- }
210
+ await closeSQLiteRawMessageManager();
222
211
  }
223
212
  };
224
213
  }
@@ -476,6 +465,34 @@ function createUnifiedSearch(deps = {}) {
476
465
  // src/mcp.ts
477
466
  var DEFAULT_NAME = "@melandlabs/memory-store";
478
467
  var DEFAULT_VERSION = "0.1.0";
468
+ async function embedMissingMessages(messages, deps) {
469
+ if (typeof deps.embedQuery !== "function") {
470
+ return messages;
471
+ }
472
+ const out = [];
473
+ for (const message of messages) {
474
+ if (Array.isArray(message.embedding) && message.embedding.length > 0) {
475
+ out.push(message);
476
+ continue;
477
+ }
478
+ if (typeof message.content !== "string" || message.content.length === 0) {
479
+ out.push(message);
480
+ continue;
481
+ }
482
+ const vector = await deps.embedQuery({
483
+ userId: message.userId,
484
+ query: message.content
485
+ });
486
+ out.push({
487
+ ...message,
488
+ embedding: vector,
489
+ embeddingModel: message.embeddingModel ?? "server",
490
+ embeddingDimensions: vector.length,
491
+ embeddingUpdatedAt: Date.now()
492
+ });
493
+ }
494
+ return out;
495
+ }
479
496
  async function startMcpServer(options = {}) {
480
497
  const server = new McpServer({
481
498
  name: options.name ?? DEFAULT_NAME,
@@ -501,6 +518,9 @@ async function startMcpServer(options = {}) {
501
518
  };
502
519
  const writeSchema = {
503
520
  userId: z.string(),
521
+ embedOnInsert: z.boolean().optional().describe(
522
+ "When true AND the host wired `unified.embedQuery`, fill in any missing `embedding` server-side via that embedder. Required when the active memory backend is sqlite-vec and the caller doesn't pre-embed."
523
+ ),
504
524
  message: z.object({
505
525
  id: z.string().optional(),
506
526
  messageId: z.string().optional(),
@@ -511,6 +531,9 @@ async function startMcpServer(options = {}) {
511
531
  channel: z.string().optional(),
512
532
  person: z.string().optional(),
513
533
  timestamp: z.number().optional(),
534
+ embedding: z.array(z.number()).optional(),
535
+ embeddingModel: z.string().optional(),
536
+ embeddingDimensions: z.number().optional(),
514
537
  metadata: z.record(z.string(), z.unknown()).optional()
515
538
  }).passthrough()
516
539
  };
@@ -543,17 +566,32 @@ async function startMcpServer(options = {}) {
543
566
  async (args) => {
544
567
  const a = args;
545
568
  const manager2 = await rawStore.getManager();
546
- const result = await manager2.upsertRawMessages?.({
547
- userId: a.userId,
548
- messages: [a.message]
549
- });
569
+ const incoming = [a.message].map((m) => ({ ...m, userId: m.userId ?? a.userId }));
570
+ const messages = a.embedOnInsert === true ? await embedMissingMessages(incoming, options.unified ?? {}) : incoming;
571
+ let result;
572
+ if (typeof manager2.upsertRawMessages === "function") {
573
+ result = await manager2.upsertRawMessages({
574
+ userId: a.userId,
575
+ messages
576
+ });
577
+ } else if (typeof manager2.storeMessages === "function") {
578
+ const ids = await manager2.storeMessages(messages);
579
+ result = { inserted: ids.length, ids };
580
+ } else {
581
+ throw new Error("active raw-message manager exposes neither upsertRawMessages nor storeMessages");
582
+ }
550
583
  try {
551
- await upsertRawMessagesToChroma([a.message]);
584
+ await upsertRawMessagesToChroma(messages);
552
585
  } catch (error) {
553
586
  console.warn("[memory-store/mcp] chroma upsert failed:", error);
554
587
  }
555
588
  return {
556
- content: [{ type: "text", text: JSON.stringify({ ok: true, result }) }]
589
+ content: [
590
+ {
591
+ type: "text",
592
+ text: JSON.stringify({ ok: true, count: messages.length, result })
593
+ }
594
+ ]
557
595
  };
558
596
  }
559
597
  );
@@ -583,9 +621,348 @@ async function startMcpServer(options = {}) {
583
621
  return server;
584
622
  }
585
623
 
624
+ // src/storage/sqlite-vector-index.ts
625
+ function getMemoryVectorStoreBackend() {
626
+ const backend = (process.env.INSIGHT_VECTOR_STORE_BACKEND || process.env.MEMORY_VECTOR_STORE_BACKEND || process.env.VECTOR_STORE_BACKEND || "sqlite-vec").toLowerCase();
627
+ return backend === "chroma" ? "chroma" : "sqlite-vec";
628
+ }
629
+ function isInsightSQLiteVecEnabled(_env) {
630
+ return getMemoryVectorStoreBackend() === "sqlite-vec";
631
+ }
632
+ async function getInsightSQLiteVecStore(_env) {
633
+ const dbPath = resolveSQLiteRawMessageDbPath();
634
+ const { getSQLiteVecStore } = await import('@melandlabs/rag/sqlite-vec-store');
635
+ return await getSQLiteVecStore(dbPath, void 0, {
636
+ collectionName: process.env.SQLITE_VEC_INSIGHTS_COLLECTION || "opencontext_insights"
637
+ });
638
+ }
639
+ async function searchInsightsWithSQLiteVec(input, env) {
640
+ const store = await getInsightSQLiteVecStore();
641
+ const overfetchLimit = Math.max(input.limit * 8, input.limit);
642
+ const results = await store.similaritySearchWithOptions(input.queryEmbedding, {
643
+ limit: overfetchLimit,
644
+ filter: { userId: input.userId }
645
+ });
646
+ return results.filter((result) => result.score >= input.threshold).filter((result) => {
647
+ const metadata = result.metadata ?? {};
648
+ if (input.botIds?.length && !input.botIds.includes(String(metadata.botId ?? ""))) {
649
+ return false;
650
+ }
651
+ return input.includeArchived || metadata.archived !== true;
652
+ }).slice(0, input.limit);
653
+ }
654
+
655
+ // src/server/cli-shared.ts
656
+ async function loadAiRag() {
657
+ try {
658
+ const [local, chroma] = await Promise.all([
659
+ import('@melandlabs/ai-rag/local-transformers-embedding-provider'),
660
+ import('@melandlabs/ai-rag/chroma-store')
661
+ ]);
662
+ return {
663
+ LocalTransformersEmbeddingProvider: local.LocalTransformersEmbeddingProvider,
664
+ ChromaVectorStore: chroma.ChromaVectorStore
665
+ };
666
+ } catch (error) {
667
+ throw new Error(
668
+ `--embedding-provider local / --*-backend=chroma require @melandlabs/ai-rag to be installed. Run \`pnpm add @melandlabs/ai-rag\` and try again. (${error.message})`
669
+ );
670
+ }
671
+ }
672
+ function parseUnifiedArgs(argv) {
673
+ const env = process.env;
674
+ const args = {
675
+ embeddingProvider: env.EMBEDDING_PROVIDER ?? "none",
676
+ embeddingModel: env.EMBEDDING_MODEL,
677
+ chromaUrl: env.CHROMA_URL,
678
+ memoryBackend: env.MEMORY_BACKEND ?? "none",
679
+ insightsBackend: env.INSIGHTS_BACKEND ?? "none",
680
+ insightsCollection: env.INSIGHTS_COLLECTION ?? "opencontext_insights",
681
+ knowledgeBackend: env.KNOWLEDGE_BACKEND ?? "none",
682
+ knowledgeCollection: env.KNOWLEDGE_COLLECTION ?? "opencontext_knowledge"
683
+ };
684
+ for (let i = 0; i < argv.length; i += 1) {
685
+ const arg = argv[i];
686
+ const next = argv[i + 1];
687
+ const takeValue = () => {
688
+ if (next === void 0) throw new Error(`[memory-store/http] ${arg} requires a value`);
689
+ i += 1;
690
+ return next;
691
+ };
692
+ switch (arg) {
693
+ case "--embedding-provider":
694
+ args.embeddingProvider = takeValue();
695
+ break;
696
+ case "--embedding-model":
697
+ args.embeddingModel = takeValue();
698
+ break;
699
+ case "--chroma-url":
700
+ args.chromaUrl = takeValue();
701
+ break;
702
+ case "--memory-backend":
703
+ args.memoryBackend = takeValue();
704
+ break;
705
+ case "--insights-backend":
706
+ args.insightsBackend = takeValue();
707
+ break;
708
+ case "--insights-collection":
709
+ args.insightsCollection = takeValue();
710
+ break;
711
+ case "--knowledge-backend":
712
+ args.knowledgeBackend = takeValue();
713
+ break;
714
+ case "--knowledge-collection":
715
+ args.knowledgeCollection = takeValue();
716
+ break;
717
+ }
718
+ }
719
+ for (const [name, value] of [
720
+ ["--embedding-provider", args.embeddingProvider],
721
+ ["--memory-backend", args.memoryBackend],
722
+ ["--insights-backend", args.insightsBackend],
723
+ ["--knowledge-backend", args.knowledgeBackend]
724
+ ]) {
725
+ const allowed = ["local", "openrouter", "none", "chroma", "sqlite-vec"];
726
+ if (!allowed.includes(value)) {
727
+ throw new Error(`[memory-store/cli] ${name} must be one of: ${allowed.join(", ")} (got "${value}")`);
728
+ }
729
+ }
730
+ return args;
731
+ }
732
+ async function buildUnified(args) {
733
+ const unified = {};
734
+ const log = (msg) => console.warn(`[memory-store/cli] ${msg}`);
735
+ const wantsAiRag = args.embeddingProvider === "local" || args.memoryBackend === "chroma" || args.insightsBackend === "chroma" || args.knowledgeBackend === "chroma";
736
+ const aiRag = wantsAiRag ? await loadAiRag() : null;
737
+ if (args.embeddingProvider === "local") {
738
+ const provider = new aiRag.LocalTransformersEmbeddingProvider({ modelName: args.embeddingModel });
739
+ unified.embedQuery = async ({ query }) => provider.embedQuery(query);
740
+ log(
741
+ `embedQuery wired via LocalTransformersEmbeddingProvider (model=${args.embeddingModel ?? "Xenova/all-MiniLM-L6-v2"})`
742
+ );
743
+ } else if (args.embeddingProvider === "openrouter") {
744
+ const apiKey = process.env.OPENROUTER_API_KEY;
745
+ if (!apiKey) {
746
+ throw new Error("--embedding-provider openrouter requires OPENROUTER_API_KEY in the environment");
747
+ }
748
+ const model = args.embeddingModel ?? "text-embedding-3-small";
749
+ const baseURL = "https://openrouter.ai/api/v1";
750
+ unified.embedQuery = async ({ query }) => {
751
+ const res = await fetch(`${baseURL}/embeddings`, {
752
+ method: "POST",
753
+ headers: {
754
+ "content-type": "application/json",
755
+ authorization: `Bearer ${apiKey}`,
756
+ "HTTP-Referer": process.env.NEXT_PUBLIC_APP_URL ?? "https://opencontext.ai",
757
+ "X-Title": "opencontext AI"
758
+ },
759
+ body: JSON.stringify({ model, input: query })
760
+ });
761
+ if (!res.ok) {
762
+ throw new Error(`openrouter embeddings ${res.status}: ${await res.text()}`);
763
+ }
764
+ const body = await res.json();
765
+ const embedding = body.data?.[0]?.embedding;
766
+ if (!embedding) throw new Error("openrouter embeddings response missing data[0].embedding");
767
+ return embedding;
768
+ };
769
+ log(`embedQuery wired via openrouter (model=${model})`);
770
+ }
771
+ if (args.memoryBackend === "sqlite-vec") {
772
+ const store = createRawMessageStore({ env: void 0 });
773
+ const manager2 = await store.getManager();
774
+ if (typeof manager2.searchMessagesSemantically !== "function") {
775
+ throw new Error(
776
+ "the active raw-message manager does not implement searchMessagesSemantically; sqlite-vec ANN for the memory source is unavailable in this build"
777
+ );
778
+ }
779
+ unified.searchRawMessagesAnn = async ({ userId, queryEmbedding, limit, threshold, botId }) => {
780
+ const rows = await manager2.searchMessagesSemantically({
781
+ userId,
782
+ queryEmbedding,
783
+ limit,
784
+ threshold,
785
+ botId
786
+ });
787
+ return rows.map((r) => ({
788
+ id: r.id,
789
+ content: r.content,
790
+ similarity: r.similarity,
791
+ metadata: r.metadata ?? {}
792
+ }));
793
+ };
794
+ log("memory backend wired via sqlite-vec (uses the manager's searchMessagesSemantically)");
795
+ } else if (args.memoryBackend === "chroma") {
796
+ if (!args.chromaUrl)
797
+ throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
798
+ const store = new aiRag.ChromaVectorStore({
799
+ url: args.chromaUrl,
800
+ collectionName: "opencontext_raw_messages"
801
+ });
802
+ unified.searchRawMessagesAnn = async ({ queryEmbedding, limit, threshold }) => {
803
+ const results = await store.similaritySearchWithOptions(queryEmbedding, { limit: limit + 1 });
804
+ return results.filter((r) => r.score >= threshold).map((r) => ({ id: r.id, content: r.content, similarity: r.score, metadata: r.metadata ?? {} }));
805
+ };
806
+ log("memory backend wired via chroma (collection=opencontext_raw_messages)");
807
+ }
808
+ if (args.insightsBackend === "sqlite-vec" && isInsightSQLiteVecEnabled()) {
809
+ if (!unified.embedQuery) {
810
+ throw new Error("--insights-backend=sqlite-vec requires --embedding-provider local|openrouter");
811
+ }
812
+ const embedQuery = unified.embedQuery;
813
+ unified.searchInsights = async ({ userId, query, limit, threshold, botIds, includeArchived }) => {
814
+ const emb = await embedQuery({ userId, query });
815
+ const rows = await searchInsightsWithSQLiteVec({
816
+ userId,
817
+ queryEmbedding: emb,
818
+ limit,
819
+ threshold,
820
+ botIds,
821
+ includeArchived
822
+ });
823
+ return rows.map((r) => ({
824
+ id: r.id,
825
+ content: r.content,
826
+ similarity: r.score,
827
+ metadata: r.metadata ?? {}
828
+ }));
829
+ };
830
+ log("insights backend wired via sqlite-vec");
831
+ } else if (args.insightsBackend === "chroma") {
832
+ if (!args.chromaUrl)
833
+ throw new Error("--insights-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
834
+ if (!unified.embedQuery)
835
+ throw new Error("--insights-backend=chroma requires --embedding-provider local|openrouter");
836
+ const store = new aiRag.ChromaVectorStore({
837
+ url: args.chromaUrl,
838
+ collectionName: args.insightsCollection
839
+ });
840
+ const embedQuery = unified.embedQuery;
841
+ unified.searchInsights = async ({ query, limit, threshold }) => {
842
+ const emb = await embedQuery({ userId: "", query });
843
+ const results = await store.similaritySearchWithOptions(emb, { limit: limit + 1 });
844
+ return results.filter((r) => r.score >= threshold).map((r) => ({ id: r.id, content: r.content, similarity: r.score, metadata: r.metadata ?? {} }));
845
+ };
846
+ log(`insights backend wired via chroma (collection=${args.insightsCollection})`);
847
+ }
848
+ if (args.knowledgeBackend === "chroma") {
849
+ if (!args.chromaUrl)
850
+ throw new Error("--knowledge-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
851
+ if (!unified.embedQuery)
852
+ throw new Error("--knowledge-backend=chroma requires --embedding-provider local|openrouter");
853
+ const store = new aiRag.ChromaVectorStore({
854
+ url: args.chromaUrl,
855
+ collectionName: args.knowledgeCollection
856
+ });
857
+ const embedQuery = unified.embedQuery;
858
+ unified.searchKnowledge = async ({ query, options }) => {
859
+ const emb = await embedQuery({ userId: "", query });
860
+ const results = await store.similaritySearchWithOptions(emb, { limit: options.limit + 1 });
861
+ return results.filter((r) => r.score >= options.threshold).map((r, i) => ({
862
+ chunkId: r.id,
863
+ documentId: r.documentId || String(r.metadata?.documentId ?? r.id),
864
+ documentName: String(r.metadata?.documentName ?? ""),
865
+ content: r.content,
866
+ similarity: r.score,
867
+ chunkIndex: i
868
+ }));
869
+ };
870
+ log(`knowledge backend wired via chroma (collection=${args.knowledgeCollection})`);
871
+ }
872
+ return unified;
873
+ }
874
+ function printUnifiedHelp() {
875
+ console.log(`Embedding (wires unified.embedQuery):
876
+ --embedding-provider <name> local | openrouter | none
877
+ (env: EMBEDDING_PROVIDER, default: none)
878
+ --embedding-model <name> Model name
879
+ (env: EMBEDDING_MODEL; local \u2192 Xenova/all-MiniLM-L6-v2,
880
+ openrouter \u2192 text-embedding-3-small)
881
+ Note: "local" dynamically imports @melandlabs/ai-rag (a peer install).
882
+
883
+ Cross-source search (wires unified.searchKnowledge / searchInsights / searchRawMessagesAnn):
884
+ --chroma-url <url> Chroma server URL
885
+ (env: CHROMA_URL; required when any *-backend=chroma)
886
+ --memory-backend <name> sqlite-vec | chroma | none
887
+ (env: MEMORY_BACKEND, default: none)
888
+ --insights-backend <name> sqlite-vec | chroma | none
889
+ (env: INSIGHTS_BACKEND, default: none)
890
+ --insights-collection <name> Chroma collection (default: opencontext_insights)
891
+ --knowledge-backend <name> chroma | none
892
+ (env: KNOWLEDGE_BACKEND, default: none)
893
+ --knowledge-collection <name> Chroma collection (default: opencontext_knowledge)
894
+ Note: the chroma backends dynamically import @melandlabs/ai-rag.`);
895
+ }
896
+
586
897
  // src/server/cli-mcp.ts
898
+ function parseArgs(argv) {
899
+ const env = process.env;
900
+ const unified = parseUnifiedArgs(argv);
901
+ const args = {
902
+ ...unified,
903
+ name: env.MEMORY_MCP_NAME,
904
+ version: env.MEMORY_MCP_VERSION
905
+ };
906
+ for (let i = 0; i < argv.length; i += 1) {
907
+ const arg = argv[i];
908
+ const next = argv[i + 1];
909
+ const takeValue = () => {
910
+ if (next === void 0) throw new Error(`[memory-store/mcp] ${arg} requires a value`);
911
+ i += 1;
912
+ return next;
913
+ };
914
+ switch (arg) {
915
+ case "--name":
916
+ args.name = takeValue();
917
+ break;
918
+ case "--version":
919
+ args.version = takeValue();
920
+ break;
921
+ case "--help":
922
+ case "-h":
923
+ printHelp();
924
+ process.exit(0);
925
+ }
926
+ }
927
+ return args;
928
+ }
929
+ function printHelp() {
930
+ console.log(`opencontext-memory-mcp \u2014 standalone memory-store MCP daemon.
931
+
932
+ Usage:
933
+ opencontext-memory-mcp [options]
934
+
935
+ Server identity (advertised to MCP clients):
936
+ --name <name> Server name (env: MEMORY_MCP_NAME)
937
+ --version <version> Server version (env: MEMORY_MCP_VERSION)
938
+ `);
939
+ printUnifiedHelp();
940
+ console.log(`
941
+ Examples:
942
+ # Minimal \u2014 same as before, all three *not_configured warnings remain
943
+ opencontext-memory-mcp
944
+
945
+ # Local ONNX embedder + sqlite-vec ANN for the memory source
946
+ opencontext-memory-mcp \\
947
+ --embedding-provider local \\
948
+ --memory-backend sqlite-vec
949
+
950
+ # Wire everything via a running Chroma server
951
+ opencontext-memory-mcp \\
952
+ --embedding-provider openrouter \\
953
+ --chroma-url http://127.0.0.1:8000 \\
954
+ --memory-backend chroma \\
955
+ --insights-backend chroma \\
956
+ --knowledge-backend chroma`);
957
+ }
587
958
  async function main() {
588
- const server = await startMcpServer();
959
+ const args = parseArgs(process.argv.slice(2));
960
+ const unified = await buildUnified(args);
961
+ const server = await startMcpServer({
962
+ unified,
963
+ name: args.name,
964
+ version: args.version
965
+ });
589
966
  console.error("[memory-store/mcp] listening on stdio");
590
967
  const shutdown = async (signal) => {
591
968
  console.error(`[memory-store/mcp] ${signal} received, shutting down\u2026`);