@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.
- package/dist/{config-MlK9f9CM.d.ts → config-D6MzksfT.d.ts} +20 -14
- package/dist/http.d.ts +14 -2
- package/dist/http.js +80 -45
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +28 -39
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +83 -45
- package/dist/mcp.js.map +1 -1
- package/dist/search/unified-search.d.ts +1 -1
- package/dist/search/unified-search.js +28 -39
- package/dist/search/unified-search.js.map +1 -1
- package/dist/server/cli-http.js +411 -57
- package/dist/server/cli-http.js.map +1 -1
- package/dist/server/cli-mcp.js +423 -46
- package/dist/server/cli-mcp.js.map +1 -1
- package/dist/server/cli-shared.d.ts +39 -0
- package/dist/server/cli-shared.js +373 -0
- package/dist/server/cli-shared.js.map +1 -0
- package/dist/storage/postgres-raw-message-factory.d.ts +1 -1
- package/dist/storage/raw-message-store.d.ts +12 -5
- package/dist/storage/raw-message-store.js +28 -39
- package/dist/storage/raw-message-store.js.map +1 -1
- package/dist/storage/sqlite-raw-message-store.d.ts +21 -6
- package/dist/storage/sqlite-raw-message-store.js +12 -18
- package/dist/storage/sqlite-raw-message-store.js.map +1 -1
- package/dist/storage/sqlite-vector-index.d.ts +4 -3
- package/dist/storage/sqlite-vector-index.js +23 -16
- package/dist/storage/sqlite-vector-index.js.map +1 -1
- package/package.json +15 -8
package/dist/server/cli-mcp.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
154
|
-
|
|
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(
|
|
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 =
|
|
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
|
|
190
|
-
|
|
191
|
-
return
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
189
|
+
resolveEnv(options.env);
|
|
199
190
|
return {
|
|
200
191
|
getBackend() {
|
|
201
|
-
return
|
|
192
|
+
return resolveBackend();
|
|
202
193
|
},
|
|
203
194
|
isAvailable() {
|
|
204
|
-
return
|
|
195
|
+
return resolveBackend() === "sqlite" || hasPostgresFactory();
|
|
205
196
|
},
|
|
206
197
|
async getManager() {
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
207
|
+
return await getSQLiteRawMessageManager();
|
|
217
208
|
},
|
|
218
209
|
async close() {
|
|
219
|
-
|
|
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
|
|
547
|
-
|
|
548
|
-
|
|
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(
|
|
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: [
|
|
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
|
|
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`);
|