Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/memory/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@arvoretech/memory-mcp",
"version": "1.1.1",
"version": "1.2.0",
"description": "MCP server for team memory — persistent knowledge base with semantic search for AI-assisted development",
"main": "dist/index.js",
"type": "module",
Expand Down
5 changes: 4 additions & 1 deletion packages/memory/src/embeddings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ export class EmbeddingEngine {

async init(): Promise<void> {
try {
const { pipeline } = await import("@xenova/transformers");
const { pipeline, env } = await import("@xenova/transformers");
if (process.env.TRANSFORMERS_CACHE_DIR) {
env.cacheDir = process.env.TRANSFORMERS_CACHE_DIR;
}
this.pipeline = (await pipeline(
"feature-extraction",
this.modelName
Expand Down
9 changes: 7 additions & 2 deletions packages/memory/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,15 @@ export class MemoryMCPServer {
}

async start(): Promise<void> {
await this.store.load();
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("Memory MCP Server started successfully");
console.error("Memory MCP Server connected, loading store in background...");

this.store.load().then(() => {
console.error("Memory MCP Server store loaded successfully");
}).catch((error) => {
console.error(`Failed to load store: ${error instanceof Error ? error.message : error}`);
});
}

setupGracefulShutdown(): void {
Expand Down
20 changes: 10 additions & 10 deletions packages/memory/src/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe("MemoryStore", () => {
describe("load", () => {
it("should load an empty directory without errors", async () => {
await store.load();
const result = store.list();
const result = await store.list();
expect(result).toEqual([]);
});

Expand Down Expand Up @@ -96,7 +96,7 @@ describe("MemoryStore", () => {
);

await store.load();
const result = store.list();
const result = await store.list();

expect(result).toHaveLength(2);
expect(result.map((m) => m.title)).toContain("Use PostgreSQL");
Expand All @@ -119,7 +119,7 @@ describe("MemoryStore", () => {
);

await store.load();
const result = store.list();
const result = await store.list();

expect(result[0].title).toBe("New");
expect(result[1].title).toBe("Old");
Expand Down Expand Up @@ -224,7 +224,7 @@ describe("MemoryStore", () => {
);

await store.load();
const entry = store.get("use-postgres");
const entry = await store.get("use-postgres");

expect(entry).not.toBeNull();
expect(entry!.title).toBe("Use PostgreSQL");
Expand All @@ -233,7 +233,7 @@ describe("MemoryStore", () => {

it("should return null for non-existent id", async () => {
await store.load();
const entry = store.get("does-not-exist");
const entry = await store.get("does-not-exist");
expect(entry).toBeNull();
});
});
Expand Down Expand Up @@ -310,10 +310,10 @@ describe("MemoryStore", () => {
);

await store.load();
expect(store.get("glossary-term")).not.toBeNull();
expect(await store.get("glossary-term")).not.toBeNull();

await store.remove("glossary-term");
expect(store.get("glossary-term")).toBeNull();
expect(await store.get("glossary-term")).toBeNull();
});

it("should throw for non-existent memory", async () => {
Expand All @@ -338,7 +338,7 @@ describe("MemoryStore", () => {
);

await store.load();
const decisions = store.list({ category: "decisions" });
const decisions = await store.list({ category: "decisions" });

expect(decisions).toHaveLength(1);
expect(decisions[0].category).toBe("decisions");
Expand All @@ -359,8 +359,8 @@ describe("MemoryStore", () => {
);

await store.load();
const active = store.list({ status: "active" });
const archived = store.list({ status: "archived" });
const active = await store.list({ status: "active" });
const archived = await store.list({ status: "archived" });

expect(active).toHaveLength(1);
expect(active[0].title).toBe("Active");
Expand Down
31 changes: 20 additions & 11 deletions packages/memory/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,19 @@ export class MemoryStore {
private db: lancedb.Connection | null = null;
private table: lancedb.Table | null = null;
private loaded = false;
private loadingPromise: Promise<void> | null = null;

constructor(memoriesPath: string, embeddingModel?: string) {
this.memoriesPath = memoriesPath;
this.embeddings = new EmbeddingEngine(embeddingModel);
}

async load(): Promise<void> {
this.loadingPromise = this.doLoad();
return this.loadingPromise;
}

private async doLoad(): Promise<void> {
this.catalog = [];

await this.embeddings.init();
Expand Down Expand Up @@ -186,17 +192,20 @@ export class MemoryStore {
}
}

private ensureLoaded(): void {
if (!this.loaded) {
throw new MemoryMCPError("Store not loaded. Call load() first.", "NOT_LOADED");
private async ensureLoaded(): Promise<void> {
if (this.loaded) return;
if (this.loadingPromise) {
await this.loadingPromise;
return;
}
throw new MemoryMCPError("Store not loaded. Call load() first.", "NOT_LOADED");
}

async search(
query: string,
opts?: { category?: MemoryCategory; status?: MemoryStatus; limit?: number }
): Promise<(MemoryCatalogEntry & { score: number })[]> {
this.ensureLoaded();
await this.ensureLoaded();

const status = opts?.status || "active";
const limit = opts?.limit || 10;
Expand Down Expand Up @@ -274,12 +283,12 @@ export class MemoryStore {
.map((s) => ({ ...this.toCatalogEntry(s.entry), score: round(s.score) }));
}

list(opts?: {
async list(opts?: {
category?: MemoryCategory;
status?: MemoryStatus;
limit?: number;
}): MemoryCatalogEntry[] {
this.ensureLoaded();
}): Promise<MemoryCatalogEntry[]> {
await this.ensureLoaded();

let filtered = [...this.catalog];

Expand All @@ -294,8 +303,8 @@ export class MemoryStore {
return filtered.slice(0, limit).map((e) => this.toCatalogEntry(e));
}

get(id: string): MemoryEntry | null {
this.ensureLoaded();
async get(id: string): Promise<MemoryEntry | null> {
await this.ensureLoaded();
return this.catalog.find((m) => m.id === id) || null;
}

Expand Down Expand Up @@ -359,7 +368,7 @@ export class MemoryStore {
}

async archive(id: string): Promise<MemoryEntry> {
const entry = this.get(id);
const entry = await this.get(id);
if (!entry) {
throw new MemoryMCPError(`Memory "${id}" not found`, "NOT_FOUND");
}
Expand All @@ -380,7 +389,7 @@ export class MemoryStore {
}

async remove(id: string): Promise<void> {
const entry = this.get(id);
const entry = await this.get(id);
if (!entry) {
throw new MemoryMCPError(`Memory "${id}" not found`, "NOT_FOUND");
}
Expand Down
4 changes: 2 additions & 2 deletions packages/memory/src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class MemoryMCPTools {

async getMemory(params: GetMemoryParams): Promise<McpToolResult> {
try {
const entry = this.store.get(params.id);
const entry = await this.store.get(params.id);

if (!entry) {
return {
Expand Down Expand Up @@ -116,7 +116,7 @@ export class MemoryMCPTools {

async listMemories(params: ListMemoriesParams): Promise<McpToolResult> {
try {
const results = this.store.list({
const results = await this.store.list({
category: params.category,
status: params.status,
limit: params.limit,
Expand Down
Loading