@membank/mcp 0.12.2 → 0.13.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.
- package/README.md +13 -9
- package/dist/bin.mjs +2 -0
- package/dist/index.mjs +1 -753
- package/package.json +8 -4
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -6,11 +6,19 @@ MCP server for membank. Exposes memory tools to LLMs via the [Model Context Prot
|
|
|
6
6
|
|
|
7
7
|
Runs as a stdio MCP server. LLM harnesses (Claude Code, GitHub Copilot, etc.) connect to it and can call tools to query, save, update, delete, pin, and migrate memories.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Run `membank setup` to auto-configure your harness — it writes the correct command for you.
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
|
-
###
|
|
13
|
+
### Standalone binary (recommended)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @membank/mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Starts the stdio MCP server directly, without pulling in the full CLI package. This is what `membank setup` writes into harness configs.
|
|
20
|
+
|
|
21
|
+
### Programmatic
|
|
14
22
|
|
|
15
23
|
```typescript
|
|
16
24
|
import { startServer } from '@membank/mcp'
|
|
@@ -18,12 +26,6 @@ import { startServer } from '@membank/mcp'
|
|
|
18
26
|
await startServer()
|
|
19
27
|
```
|
|
20
28
|
|
|
21
|
-
Or via the CLI:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npx @membank/cli --mcp
|
|
25
|
-
```
|
|
26
|
-
|
|
27
29
|
### MCP config (manual)
|
|
28
30
|
|
|
29
31
|
If you're configuring a harness by hand, point it at:
|
|
@@ -31,10 +33,12 @@ If you're configuring a harness by hand, point it at:
|
|
|
31
33
|
```json
|
|
32
34
|
{
|
|
33
35
|
"command": "npx",
|
|
34
|
-
"args": ["
|
|
36
|
+
"args": ["-y", "@membank/mcp"]
|
|
35
37
|
}
|
|
36
38
|
```
|
|
37
39
|
|
|
40
|
+
> **Legacy:** `npx @membank/cli --mcp` still works but emits a deprecation warning. Run `membank setup upgrade` to migrate existing configs.
|
|
41
|
+
|
|
38
42
|
## Tools
|
|
39
43
|
|
|
40
44
|
### `query_memory`
|
package/dist/bin.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{readFileSync as e}from"node:fs";import{dirname as t,join as n}from"node:path";import{fileURLToPath as r}from"node:url";import{Command as i}from"commander";import{DatabaseManager as a,EmbeddingService as o,MEMORY_TYPE_VALUES as s,MIGRATIONS as c,MemoryTypeSchema as l,PIN_BUDGET_THRESHOLD as u,QueryEngine as d,SynthesisEngine as f,createMemoryRepository as p,createProjectRepository as m,createSynthesisAgentRunner as h,createSynthesisRepository as g,isSynthesisEnabled as _,listMemoryTypes as v,resolveProject as y,runScopeToProjectsMigration as b,saveMemory as x,updateMemory as S}from"@membank/core";import{StdioServerTransport as C}from"@modelcontextprotocol/sdk/server/stdio.js";import{homedir as w}from"node:os";import{Server as T}from"@modelcontextprotocol/sdk/server";import{CallToolRequestSchema as E,ErrorCode as D,ListToolsRequestSchema as O,McpError as k}from"@modelcontextprotocol/sdk/types.js";import{ZodError as A,z as j}from"zod";const M=j.object({content:j.string().min(1),type:l,tags:j.array(j.string()).optional(),global:j.boolean().optional()}),N=j.object({id:j.string().min(1),content:j.string().min(1).optional(),type:l.optional(),tags:j.array(j.string()).optional()}),P=j.object({id:j.string().min(1)}),F=j.object({query:j.string().min(1),type:l.optional(),limit:j.number().int().positive().optional(),includePinned:j.boolean().optional(),global:j.boolean().optional()}),I=j.discriminatedUnion(`mode`,[j.object({mode:j.literal(`list`)}),j.object({mode:j.literal(`run`),name:j.string().min(1)})]),L=j.object({id:j.string().min(1)}),R=j.object({id:j.string().min(1)});function z(){let t=n(w(),`.membank`,`config.json`);try{let n=e(t,`utf8`),r=JSON.parse(n);return{enabled:r.synthesis?.enabled===!0,maxTokensPerRun:r.synthesis?.maxTokensPerRun,debounceMs:r.synthesis?.debounceMs,stalenessDays:r.synthesis?.stalenessDays,inFlightTimeoutMs:r.synthesis?.inFlightTimeoutMs}}catch{return{enabled:!1}}}function B(e,t){return{queryMemory:async e=>{let n=e.global===!0?void 0:e.projectHash??(await y()).hash,r=await t.query({query:e.query,projectHash:n,limit:e.limit??20,includePinned:!0});return JSON.stringify(r)},getMemorySummary:async()=>JSON.stringify(e.stats())}}function V(e={}){let t=e.useInMemoryDb?a.openInMemory():a.open(e.dbPath),n=new o,r=m(t),i=p(t,r),s=new d(t,n,i),c=z(),l;return c.enabled&&(l=new f(g(t),c,h(B(i,s),c))),{db:t,embedding:n,repo:i,query:s,projects:r,synthEngine:l}}function H(e,t){try{return e.parse(t)}catch(e){let t=e instanceof A?e.issues[0]?.message??e.message:String(e);throw new k(D.InvalidParams,t)}}function U(e){let t=new T({name:`membank`,version:`0.1.0`},{capabilities:{tools:{}}});return t.setRequestHandler(O,()=>({tools:[{name:`list_memory_types`,description:`Returns the ordered list of memory type values supported by membank.`,inputSchema:{type:`object`,properties:{},required:[]}},{name:`save_memory`,description:`Save a new memory. Handles deduplication automatically — near-identical memories (cosine similarity >0.92, same type and project) overwrite the existing record.`,inputSchema:{type:`object`,properties:{content:{type:`string`,description:`Memory content to save`},type:{type:`string`,enum:[...s],description:`Memory type`},tags:{type:`array`,items:{type:`string`},description:`Optional tags`},global:{type:`boolean`,description:`Save as a global memory, not tied to any project`}},required:[`content`,`type`]}},{name:`update_memory`,description:`Update the content, type, and/or tags of an existing memory by id. All fields except id are optional.`,inputSchema:{type:`object`,properties:{id:{type:`string`,description:`Memory id to update`},content:{type:`string`,description:`New content for the memory`},type:{type:`string`,enum:[...s],description:`New type for the memory (reclassification)`},tags:{type:`array`,items:{type:`string`},description:`Replacement tags (optional)`}},required:[`id`]}},{name:`delete_memory`,description:`Delete a memory by id.`,inputSchema:{type:`object`,properties:{id:{type:`string`,description:`Memory id to delete`}},required:[`id`]}},{name:`query_memory`,description:`Search memories by semantic similarity. Returns results ranked by confidence score.`,inputSchema:{type:`object`,properties:{query:{type:`string`,description:`Search text`},type:{type:`string`,enum:[...s],description:`Filter by memory type`},limit:{type:`number`,description:`Maximum results to return (default 10)`},includePinned:{type:`boolean`,description:`Include pinned memories in results. Pinned memories are already injected into session context, so excluded by default to avoid duplicates.`},global:{type:`boolean`,description:`Query global memories only. When omitted or false, queries the current project scope.`}},required:[`query`]}},{name:`migrate`,description:`List or run named data migrations. Use mode "list" to see available migrations; mode "run" with a migration name to execute one.`,inputSchema:{type:`object`,properties:{mode:{type:`string`,enum:[`list`,`run`],description:`Mode: "list" to see available migrations, "run" to execute one`},name:{type:`string`,description:`Migration name (required when mode is "run")`}},required:[`mode`]}},{name:`pin_memory`,description:`Pin a memory by id. Pinned memories are always injected into the session context.`,inputSchema:{type:`object`,properties:{id:{type:`string`,description:`Memory id to pin`}},required:[`id`]}},{name:`unpin_memory`,description:`Unpin a memory by id. Removes the memory from guaranteed session injection.`,inputSchema:{type:`object`,properties:{id:{type:`string`,description:`Memory id to unpin`}},required:[`id`]}},{name:`get_memory_summary`,description:`Returns aggregate stats for session orientation: total memories, counts by type, pinned count, and review queue size.`,inputSchema:{type:`object`,properties:{},required:[]}},{name:`list_flagged_memories`,description:`List memories that have unresolved dedup review events. These were flagged automatically when a near-duplicate was saved (cosine similarity 0.75–0.92).`,inputSchema:{type:`object`,properties:{},required:[]}},{name:`resolve_review`,description:`Dismiss all unresolved review events for a memory. Use after reviewing the memory and deciding it should be kept as-is.`,inputSchema:{type:`object`,properties:{id:{type:`string`,description:`Memory id to resolve review events for`}},required:[`id`]}}]})),t.setRequestHandler(E,async t=>{if(t.params.name===`list_memory_types`)try{return{content:[{type:`text`,text:JSON.stringify(v())}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}if(t.params.name===`save_memory`){let n=H(M,t.params.arguments),r=n.global===!0?void 0:await y();try{let t=await x({content:n.content,type:n.type,tags:n.tags,projectScope:r},{repo:e.repo,embedder:e.embedding});if(e.synthEngine!==void 0){let n=t.projects.length>0?t.projects[0]?.scopeHash??`global`:`global`;e.synthEngine.markDirty(n)}return{content:[{type:`text`,text:JSON.stringify(t)}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}if(t.params.name===`update_memory`){let n=H(N,t.params.arguments);try{let t=await S(n.id,{content:n.content,type:n.type,tags:n.tags},{repo:e.repo,embedder:e.embedding});if(e.synthEngine!==void 0){let n=t.projects.length>0?t.projects[0]?.scopeHash??`global`:`global`;e.synthEngine.markDirty(n)}return{content:[{type:`text`,text:JSON.stringify(t)}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}if(t.params.name===`delete_memory`){let n=H(P,t.params.arguments);try{let t=e.repo.findById(n.id);if(t===void 0)return{content:[{type:`text`,text:`Memory not found: ${n.id}`}],isError:!0};let r=e.synthEngine===void 0?void 0:t.projects[0]?.scopeHash??`global`;return e.repo.delete(n.id),e.synthEngine!==void 0&&r!==void 0&&e.synthEngine.markDirty(r),{content:[{type:`text`,text:JSON.stringify({success:!0,id:n.id})}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}if(t.params.name===`query_memory`){let n=H(F,t.params.arguments),r=n.global===!0?void 0:(await y()).hash;try{let t=(await e.query.query({query:n.query,type:n.type,projectHash:r,limit:n.limit??10,includePinned:n.includePinned})).map(e=>({id:e.id,content:e.content,type:e.type,tags:e.tags,projects:e.projects,pinned:e.pinned,reviewEvents:e.reviewEvents,createdAt:e.createdAt,updatedAt:e.updatedAt,sourceHarness:e.sourceHarness,score:e.score}));return{content:[{type:`text`,text:JSON.stringify(t)}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}if(t.params.name===`migrate`){let n=H(I,t.params.arguments);if(n.mode===`list`)return{content:[{type:`text`,text:JSON.stringify(c)}]};if(n.name===`scope-to-projects`)try{let t=await b(e.projects);return t===null?{content:[{type:`text`,text:JSON.stringify({error:`No project found for current directory.`})}],isError:!0}:{content:[{type:`text`,text:JSON.stringify(t)}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}throw new k(D.InvalidParams,`Unknown migration: "${n.name}". Available: ${c.map(e=>e.name).join(`, `)}`)}if(t.params.name===`pin_memory`||t.params.name===`unpin_memory`){let n=H(L,t.params.arguments),r=t.params.name===`pin_memory`;try{let t=e.repo.setPin(n.id,r);if(r){let n=e.repo.getPinnedCharCount();if(n>u&&!_()){let e={...t,pinBudgetWarning:`Pinned memories now use ${n} characters (threshold: ${u}). Consider unpinning older memories or enabling synthesis to compress them.`};return{content:[{type:`text`,text:JSON.stringify(e)}]}}}return{content:[{type:`text`,text:JSON.stringify(t)}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}if(t.params.name===`get_memory_summary`)try{return{content:[{type:`text`,text:JSON.stringify(e.repo.stats())}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}if(t.params.name===`list_flagged_memories`)try{let t=e.repo.listFlagged();return{content:[{type:`text`,text:JSON.stringify(t)}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}if(t.params.name===`resolve_review`){let n=H(R,t.params.arguments);try{return e.repo.findById(n.id)===void 0?{content:[{type:`text`,text:`Memory not found: ${n.id}`}],isError:!0}:(e.repo.resolveReviewEvents(n.id),{content:[{type:`text`,text:JSON.stringify({success:!0,id:n.id})}]})}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}throw Error(`Unknown tool: ${t.params.name}`)}),t}async function W(){let e;try{e=V()}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`membank: failed to initialise core: ${t}\n`),process.exit(1)}if(e.synthEngine!==void 0)try{await e.synthEngine.init()}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`membank: synthesis engine init failed: ${t}\n`)}let t=async()=>{e.synthEngine!==void 0&&await e.synthEngine.shutdown(),process.exit(0)};process.on(`SIGTERM`,()=>{t()}),process.on(`SIGINT`,()=>{t()});let n=U(e),r=new C;await n.connect(r)}const{version:G}=JSON.parse(e(n(t(r(import.meta.url)),`../package.json`),`utf8`)),K=new i;K.name(`membank-mcp`).description(`Membank MCP stdio server — for harness integration`).version(G).action(W),await K.parseAsync(process.argv);export{};
|
package/dist/index.mjs
CHANGED
|
@@ -1,754 +1,2 @@
|
|
|
1
|
-
import { DatabaseManager, EmbeddingService, MEMORY_TYPE_VALUES, MIGRATIONS, MemoryRepository, MemoryTypeSchema, PIN_BUDGET_THRESHOLD, ProjectRepository, QueryEngine, SynthesisRepository, isSynthesisEnabled, listMemoryTypes, resolveProject, runScopeToProjectsMigration } from "@membank/core";
|
|
2
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
7
|
-
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
-
import { ZodError, z } from "zod";
|
|
9
|
-
import { createSdkMcpServer, query, tool } from "@anthropic-ai/claude-agent-sdk";
|
|
10
|
-
//#region src/schemas.ts
|
|
11
|
-
const SaveMemoryArgsSchema = z.object({
|
|
12
|
-
content: z.string().min(1),
|
|
13
|
-
type: MemoryTypeSchema,
|
|
14
|
-
tags: z.array(z.string()).optional(),
|
|
15
|
-
global: z.boolean().optional()
|
|
16
|
-
});
|
|
17
|
-
const UpdateMemoryArgsSchema = z.object({
|
|
18
|
-
id: z.string().min(1),
|
|
19
|
-
content: z.string().min(1).optional(),
|
|
20
|
-
type: MemoryTypeSchema.optional(),
|
|
21
|
-
tags: z.array(z.string()).optional()
|
|
22
|
-
});
|
|
23
|
-
const DeleteMemoryArgsSchema = z.object({ id: z.string().min(1) });
|
|
24
|
-
const QueryMemoryArgsSchema = z.object({
|
|
25
|
-
query: z.string().min(1),
|
|
26
|
-
type: MemoryTypeSchema.optional(),
|
|
27
|
-
limit: z.number().int().positive().optional(),
|
|
28
|
-
includePinned: z.boolean().optional(),
|
|
29
|
-
global: z.boolean().optional()
|
|
30
|
-
});
|
|
31
|
-
const MigrateArgsSchema = z.discriminatedUnion("mode", [z.object({ mode: z.literal("list") }), z.object({
|
|
32
|
-
mode: z.literal("run"),
|
|
33
|
-
name: z.string().min(1)
|
|
34
|
-
})]);
|
|
35
|
-
const PinMemoryArgsSchema = z.object({ id: z.string().min(1) });
|
|
36
|
-
const ResolveReviewArgsSchema = z.object({ id: z.string().min(1) });
|
|
37
|
-
//#endregion
|
|
38
|
-
//#region src/synthesis/agent-loop.ts
|
|
39
|
-
const SYNTHESIS_SYSTEM_PROMPT = "You are a memory synthesizer. Your job is to read the user's stored memories and produce a concise, well-structured summary of what's most important to remember about this user — their preferences, corrections, decisions, and key facts. Pinned memories are higher fidelity and should be weighted more heavily. Exclude transient or ephemeral details. Output plain text suitable for injection into an LLM context window. Be concise — target 200-400 words.";
|
|
40
|
-
var SynthesisAgentLoop = class {
|
|
41
|
-
#tools;
|
|
42
|
-
constructor(tools, _config) {
|
|
43
|
-
this.#tools = tools;
|
|
44
|
-
}
|
|
45
|
-
async run(scope, projectHash) {
|
|
46
|
-
const mcpServer = createSdkMcpServer({
|
|
47
|
-
name: "membank-synthesis-tools",
|
|
48
|
-
version: "1.0.0",
|
|
49
|
-
tools: [tool("query_memory", "Search memories by semantic similarity", {
|
|
50
|
-
query: z.string().describe("Search text"),
|
|
51
|
-
limit: z.number().optional().describe("Maximum results to return"),
|
|
52
|
-
global: z.boolean().optional().describe("Query global memories only when true, otherwise current project scope")
|
|
53
|
-
}, async ({ query: q, limit, global: isGlobal }) => {
|
|
54
|
-
return { content: [{
|
|
55
|
-
type: "text",
|
|
56
|
-
text: await this.#tools.queryMemory({
|
|
57
|
-
query: q,
|
|
58
|
-
limit,
|
|
59
|
-
global: isGlobal,
|
|
60
|
-
projectHash
|
|
61
|
-
})
|
|
62
|
-
}] };
|
|
63
|
-
}, { annotations: { readOnlyHint: true } }), tool("get_memory_summary", "Returns aggregate stats: total memories, counts by type, pinned count, review queue size", {}, async () => {
|
|
64
|
-
return { content: [{
|
|
65
|
-
type: "text",
|
|
66
|
-
text: await this.#tools.getMemorySummary()
|
|
67
|
-
}] };
|
|
68
|
-
}, { annotations: { readOnlyHint: true } })]
|
|
69
|
-
});
|
|
70
|
-
const prompt = `Synthesize the memories for ${scope === "global" ? "global (across all projects)" : `project scope: ${scope}`}. Use get_memory_summary first to understand the overall state, then use query_memory to retrieve relevant memories (query with broad terms like "preferences", "corrections", "decisions", "key facts"). After gathering information, produce a concise synthesis of the most important things to remember about this user. Output only the synthesis text — no preamble, no metadata.`;
|
|
71
|
-
const startTime = Date.now();
|
|
72
|
-
const env = Object.fromEntries(Object.entries(process.env).filter((e) => e[1] !== void 0));
|
|
73
|
-
const agentQuery = query({
|
|
74
|
-
prompt,
|
|
75
|
-
options: {
|
|
76
|
-
model: "claude-haiku-4-5-20251001",
|
|
77
|
-
systemPrompt: SYNTHESIS_SYSTEM_PROMPT,
|
|
78
|
-
mcpServers: { "membank-synthesis-tools": mcpServer },
|
|
79
|
-
allowedTools: ["query_memory", "get_memory_summary"],
|
|
80
|
-
permissionMode: "bypassPermissions",
|
|
81
|
-
env
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
let finalResult = "";
|
|
85
|
-
for await (const message of agentQuery) if (message.type === "result") if (message.subtype === "success") finalResult = message.result;
|
|
86
|
-
else {
|
|
87
|
-
const details = "errors" in message && Array.isArray(message.errors) ? `: ${message.errors.join("; ")}` : "";
|
|
88
|
-
throw new Error(`Synthesis agent failed: ${message.subtype}${details}`);
|
|
89
|
-
}
|
|
90
|
-
const durationMs = Date.now() - startTime;
|
|
91
|
-
process.stderr.write(`membank synthesis: scope=${scope} duration=${durationMs}ms\n`);
|
|
92
|
-
if (finalResult === "") throw new Error("Synthesis agent returned empty result");
|
|
93
|
-
return finalResult;
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
//#endregion
|
|
97
|
-
//#region src/synthesis/engine.ts
|
|
98
|
-
const DEFAULT_DEBOUNCE_MS = 45e3;
|
|
99
|
-
const DEFAULT_IN_FLIGHT_TIMEOUT_MS = 12e4;
|
|
100
|
-
const MAX_BACKOFF_MULTIPLIER = 5;
|
|
101
|
-
var SynthesisEngine = class {
|
|
102
|
-
#synthRepo;
|
|
103
|
-
#config;
|
|
104
|
-
#agentLoop;
|
|
105
|
-
#dirtyScopes = /* @__PURE__ */ new Set();
|
|
106
|
-
#failureCounts = /* @__PURE__ */ new Map();
|
|
107
|
-
#running = false;
|
|
108
|
-
#loopTimer;
|
|
109
|
-
#inFlightPromises = /* @__PURE__ */ new Map();
|
|
110
|
-
constructor(_db, synthRepo, config, agentLoop) {
|
|
111
|
-
this.#synthRepo = synthRepo;
|
|
112
|
-
this.#config = config;
|
|
113
|
-
this.#agentLoop = agentLoop;
|
|
114
|
-
}
|
|
115
|
-
async init() {
|
|
116
|
-
this.#synthRepo.clearStaleInFlight(this.#config.inFlightTimeoutMs ?? DEFAULT_IN_FLIGHT_TIMEOUT_MS);
|
|
117
|
-
this.#synthRepo.expireStale();
|
|
118
|
-
const stale = this.#synthRepo.getExpiredOrDirtyScopes();
|
|
119
|
-
for (const { scope } of stale) this.#dirtyScopes.add(scope);
|
|
120
|
-
this.#running = true;
|
|
121
|
-
await this.#debounceLoop();
|
|
122
|
-
}
|
|
123
|
-
shutdown() {
|
|
124
|
-
this.#running = false;
|
|
125
|
-
if (this.#loopTimer !== void 0) {
|
|
126
|
-
clearTimeout(this.#loopTimer);
|
|
127
|
-
this.#loopTimer = void 0;
|
|
128
|
-
}
|
|
129
|
-
const inFlight = [...this.#inFlightPromises.values()];
|
|
130
|
-
if (inFlight.length === 0) return Promise.resolve();
|
|
131
|
-
const graceMs = 5e3;
|
|
132
|
-
return Promise.race([Promise.allSettled(inFlight).then(() => void 0), new Promise((resolve) => setTimeout(resolve, graceMs))]);
|
|
133
|
-
}
|
|
134
|
-
markDirty(scope) {
|
|
135
|
-
this.#dirtyScopes.add(scope);
|
|
136
|
-
}
|
|
137
|
-
#scheduleNextCycle() {
|
|
138
|
-
if (!this.#running) return;
|
|
139
|
-
const debounceMs = this.#config.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
140
|
-
this.#loopTimer = setTimeout(() => {
|
|
141
|
-
this.#debounceLoop();
|
|
142
|
-
}, debounceMs);
|
|
143
|
-
}
|
|
144
|
-
async #debounceLoop() {
|
|
145
|
-
const scopesToProcess = [...this.#dirtyScopes];
|
|
146
|
-
for (const scope of scopesToProcess) {
|
|
147
|
-
const inFlightTimeoutMs = this.#config.inFlightTimeoutMs ?? DEFAULT_IN_FLIGHT_TIMEOUT_MS;
|
|
148
|
-
const synthesis = this.#synthRepo.getSynthesis(scope);
|
|
149
|
-
if (synthesis?.inFlightSince !== null && synthesis?.inFlightSince !== void 0) {
|
|
150
|
-
if (Date.now() - new Date(synthesis.inFlightSince).getTime() < inFlightTimeoutMs) continue;
|
|
151
|
-
this.#synthRepo.clearInFlight(scope);
|
|
152
|
-
}
|
|
153
|
-
this.#dirtyScopes.delete(scope);
|
|
154
|
-
const promise = this.#synthesizeScope(scope).finally(() => {
|
|
155
|
-
this.#inFlightPromises.delete(scope);
|
|
156
|
-
});
|
|
157
|
-
this.#inFlightPromises.set(scope, promise);
|
|
158
|
-
}
|
|
159
|
-
this.#scheduleNextCycle();
|
|
160
|
-
}
|
|
161
|
-
async #synthesizeScope(scope) {
|
|
162
|
-
this.#synthRepo.markInFlight(scope);
|
|
163
|
-
try {
|
|
164
|
-
const projectHash = scope === "global" ? void 0 : scope;
|
|
165
|
-
const content = await this.#agentLoop.run(scope, projectHash);
|
|
166
|
-
const sourceHash = this.#synthRepo.computeSourceMemoryHash(scope);
|
|
167
|
-
this.#synthRepo.saveSynthesis(scope, content, sourceHash);
|
|
168
|
-
this.#failureCounts.delete(scope);
|
|
169
|
-
} catch (err) {
|
|
170
|
-
const failures = (this.#failureCounts.get(scope) ?? 0) + 1;
|
|
171
|
-
this.#failureCounts.set(scope, failures);
|
|
172
|
-
const backoffMultiplier = Math.min(failures, MAX_BACKOFF_MULTIPLIER);
|
|
173
|
-
const backoffMs = (this.#config.debounceMs ?? DEFAULT_DEBOUNCE_MS) * backoffMultiplier;
|
|
174
|
-
process.stderr.write(`membank synthesis: error for scope=${scope} failures=${failures} backoff=${backoffMs}ms: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
175
|
-
setTimeout(() => {
|
|
176
|
-
this.#dirtyScopes.add(scope);
|
|
177
|
-
}, backoffMs);
|
|
178
|
-
this.#synthRepo.clearInFlight(scope);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
//#endregion
|
|
183
|
-
//#region src/server.ts
|
|
184
|
-
const SERVER_NAME = "membank";
|
|
185
|
-
const SERVER_VERSION = "0.1.0";
|
|
186
|
-
function loadSynthesisConfig() {
|
|
187
|
-
const configPath = join(homedir(), ".membank", "config.json");
|
|
188
|
-
try {
|
|
189
|
-
const raw = readFileSync(configPath, "utf8");
|
|
190
|
-
const parsed = JSON.parse(raw);
|
|
191
|
-
return {
|
|
192
|
-
enabled: parsed.synthesis?.enabled === true,
|
|
193
|
-
maxTokensPerRun: parsed.synthesis?.maxTokensPerRun,
|
|
194
|
-
debounceMs: parsed.synthesis?.debounceMs,
|
|
195
|
-
stalenessDays: parsed.synthesis?.stalenessDays,
|
|
196
|
-
inFlightTimeoutMs: parsed.synthesis?.inFlightTimeoutMs
|
|
197
|
-
};
|
|
198
|
-
} catch {
|
|
199
|
-
return { enabled: false };
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
function buildSynthesisTools(repo, query) {
|
|
203
|
-
return {
|
|
204
|
-
queryMemory: async (args) => {
|
|
205
|
-
const projectHash = args.global === true ? void 0 : args.projectHash ?? (await resolveProject()).hash;
|
|
206
|
-
const results = await query.query({
|
|
207
|
-
query: args.query,
|
|
208
|
-
projectHash,
|
|
209
|
-
limit: args.limit ?? 20,
|
|
210
|
-
includePinned: true
|
|
211
|
-
});
|
|
212
|
-
return JSON.stringify(results);
|
|
213
|
-
},
|
|
214
|
-
getMemorySummary: async () => JSON.stringify(repo.stats())
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
function initCore(options = {}) {
|
|
218
|
-
const db = options.useInMemoryDb ? DatabaseManager.openInMemory() : DatabaseManager.open(options.dbPath);
|
|
219
|
-
const embedding = new EmbeddingService();
|
|
220
|
-
const projects = new ProjectRepository(db);
|
|
221
|
-
const repo = new MemoryRepository(db, embedding, projects);
|
|
222
|
-
const query = new QueryEngine(db, embedding, repo);
|
|
223
|
-
const synthConfig = loadSynthesisConfig();
|
|
224
|
-
let synthEngine;
|
|
225
|
-
if (synthConfig.enabled) synthEngine = new SynthesisEngine(db, new SynthesisRepository(db), synthConfig, new SynthesisAgentLoop(buildSynthesisTools(repo, query), synthConfig));
|
|
226
|
-
return {
|
|
227
|
-
db,
|
|
228
|
-
embedding,
|
|
229
|
-
repo,
|
|
230
|
-
query,
|
|
231
|
-
projects,
|
|
232
|
-
synthEngine
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
function parseArgs(schema, raw) {
|
|
236
|
-
try {
|
|
237
|
-
return schema.parse(raw);
|
|
238
|
-
} catch (e) {
|
|
239
|
-
const msg = e instanceof ZodError ? e.issues[0]?.message ?? e.message : String(e);
|
|
240
|
-
throw new McpError(ErrorCode.InvalidParams, msg);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
function createServer(core) {
|
|
244
|
-
const server = new Server({
|
|
245
|
-
name: SERVER_NAME,
|
|
246
|
-
version: SERVER_VERSION
|
|
247
|
-
}, { capabilities: { tools: {} } });
|
|
248
|
-
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: [
|
|
249
|
-
{
|
|
250
|
-
name: "list_memory_types",
|
|
251
|
-
description: "Returns the ordered list of memory type values supported by membank.",
|
|
252
|
-
inputSchema: {
|
|
253
|
-
type: "object",
|
|
254
|
-
properties: {},
|
|
255
|
-
required: []
|
|
256
|
-
}
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
name: "save_memory",
|
|
260
|
-
description: "Save a new memory. Handles deduplication automatically — near-identical memories (cosine similarity >0.92, same type and project) overwrite the existing record.",
|
|
261
|
-
inputSchema: {
|
|
262
|
-
type: "object",
|
|
263
|
-
properties: {
|
|
264
|
-
content: {
|
|
265
|
-
type: "string",
|
|
266
|
-
description: "Memory content to save"
|
|
267
|
-
},
|
|
268
|
-
type: {
|
|
269
|
-
type: "string",
|
|
270
|
-
enum: [...MEMORY_TYPE_VALUES],
|
|
271
|
-
description: "Memory type"
|
|
272
|
-
},
|
|
273
|
-
tags: {
|
|
274
|
-
type: "array",
|
|
275
|
-
items: { type: "string" },
|
|
276
|
-
description: "Optional tags"
|
|
277
|
-
},
|
|
278
|
-
global: {
|
|
279
|
-
type: "boolean",
|
|
280
|
-
description: "Save as a global memory, not tied to any project"
|
|
281
|
-
}
|
|
282
|
-
},
|
|
283
|
-
required: ["content", "type"]
|
|
284
|
-
}
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
name: "update_memory",
|
|
288
|
-
description: "Update the content, type, and/or tags of an existing memory by id. All fields except id are optional.",
|
|
289
|
-
inputSchema: {
|
|
290
|
-
type: "object",
|
|
291
|
-
properties: {
|
|
292
|
-
id: {
|
|
293
|
-
type: "string",
|
|
294
|
-
description: "Memory id to update"
|
|
295
|
-
},
|
|
296
|
-
content: {
|
|
297
|
-
type: "string",
|
|
298
|
-
description: "New content for the memory"
|
|
299
|
-
},
|
|
300
|
-
type: {
|
|
301
|
-
type: "string",
|
|
302
|
-
enum: [...MEMORY_TYPE_VALUES],
|
|
303
|
-
description: "New type for the memory (reclassification)"
|
|
304
|
-
},
|
|
305
|
-
tags: {
|
|
306
|
-
type: "array",
|
|
307
|
-
items: { type: "string" },
|
|
308
|
-
description: "Replacement tags (optional)"
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
required: ["id"]
|
|
312
|
-
}
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
name: "delete_memory",
|
|
316
|
-
description: "Delete a memory by id.",
|
|
317
|
-
inputSchema: {
|
|
318
|
-
type: "object",
|
|
319
|
-
properties: { id: {
|
|
320
|
-
type: "string",
|
|
321
|
-
description: "Memory id to delete"
|
|
322
|
-
} },
|
|
323
|
-
required: ["id"]
|
|
324
|
-
}
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
name: "query_memory",
|
|
328
|
-
description: "Search memories by semantic similarity. Returns results ranked by confidence score.",
|
|
329
|
-
inputSchema: {
|
|
330
|
-
type: "object",
|
|
331
|
-
properties: {
|
|
332
|
-
query: {
|
|
333
|
-
type: "string",
|
|
334
|
-
description: "Search text"
|
|
335
|
-
},
|
|
336
|
-
type: {
|
|
337
|
-
type: "string",
|
|
338
|
-
enum: [...MEMORY_TYPE_VALUES],
|
|
339
|
-
description: "Filter by memory type"
|
|
340
|
-
},
|
|
341
|
-
limit: {
|
|
342
|
-
type: "number",
|
|
343
|
-
description: "Maximum results to return (default 10)"
|
|
344
|
-
},
|
|
345
|
-
includePinned: {
|
|
346
|
-
type: "boolean",
|
|
347
|
-
description: "Include pinned memories in results. Pinned memories are already injected into session context, so excluded by default to avoid duplicates."
|
|
348
|
-
},
|
|
349
|
-
global: {
|
|
350
|
-
type: "boolean",
|
|
351
|
-
description: "Query global memories only. When omitted or false, queries the current project scope."
|
|
352
|
-
}
|
|
353
|
-
},
|
|
354
|
-
required: ["query"]
|
|
355
|
-
}
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
name: "migrate",
|
|
359
|
-
description: "List or run named data migrations. Use mode \"list\" to see available migrations; mode \"run\" with a migration name to execute one.",
|
|
360
|
-
inputSchema: {
|
|
361
|
-
type: "object",
|
|
362
|
-
properties: {
|
|
363
|
-
mode: {
|
|
364
|
-
type: "string",
|
|
365
|
-
enum: ["list", "run"],
|
|
366
|
-
description: "Mode: \"list\" to see available migrations, \"run\" to execute one"
|
|
367
|
-
},
|
|
368
|
-
name: {
|
|
369
|
-
type: "string",
|
|
370
|
-
description: "Migration name (required when mode is \"run\")"
|
|
371
|
-
}
|
|
372
|
-
},
|
|
373
|
-
required: ["mode"]
|
|
374
|
-
}
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
name: "pin_memory",
|
|
378
|
-
description: "Pin a memory by id. Pinned memories are always injected into the session context.",
|
|
379
|
-
inputSchema: {
|
|
380
|
-
type: "object",
|
|
381
|
-
properties: { id: {
|
|
382
|
-
type: "string",
|
|
383
|
-
description: "Memory id to pin"
|
|
384
|
-
} },
|
|
385
|
-
required: ["id"]
|
|
386
|
-
}
|
|
387
|
-
},
|
|
388
|
-
{
|
|
389
|
-
name: "unpin_memory",
|
|
390
|
-
description: "Unpin a memory by id. Removes the memory from guaranteed session injection.",
|
|
391
|
-
inputSchema: {
|
|
392
|
-
type: "object",
|
|
393
|
-
properties: { id: {
|
|
394
|
-
type: "string",
|
|
395
|
-
description: "Memory id to unpin"
|
|
396
|
-
} },
|
|
397
|
-
required: ["id"]
|
|
398
|
-
}
|
|
399
|
-
},
|
|
400
|
-
{
|
|
401
|
-
name: "get_memory_summary",
|
|
402
|
-
description: "Returns aggregate stats for session orientation: total memories, counts by type, pinned count, and review queue size.",
|
|
403
|
-
inputSchema: {
|
|
404
|
-
type: "object",
|
|
405
|
-
properties: {},
|
|
406
|
-
required: []
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
name: "list_flagged_memories",
|
|
411
|
-
description: "List memories that have unresolved dedup review events. These were flagged automatically when a near-duplicate was saved (cosine similarity 0.75–0.92).",
|
|
412
|
-
inputSchema: {
|
|
413
|
-
type: "object",
|
|
414
|
-
properties: {},
|
|
415
|
-
required: []
|
|
416
|
-
}
|
|
417
|
-
},
|
|
418
|
-
{
|
|
419
|
-
name: "resolve_review",
|
|
420
|
-
description: "Dismiss all unresolved review events for a memory. Use after reviewing the memory and deciding it should be kept as-is.",
|
|
421
|
-
inputSchema: {
|
|
422
|
-
type: "object",
|
|
423
|
-
properties: { id: {
|
|
424
|
-
type: "string",
|
|
425
|
-
description: "Memory id to resolve review events for"
|
|
426
|
-
} },
|
|
427
|
-
required: ["id"]
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
] }));
|
|
431
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
432
|
-
if (request.params.name === "list_memory_types") try {
|
|
433
|
-
return { content: [{
|
|
434
|
-
type: "text",
|
|
435
|
-
text: JSON.stringify(listMemoryTypes())
|
|
436
|
-
}] };
|
|
437
|
-
} catch (err) {
|
|
438
|
-
return {
|
|
439
|
-
content: [{
|
|
440
|
-
type: "text",
|
|
441
|
-
text: err instanceof Error ? err.message : String(err)
|
|
442
|
-
}],
|
|
443
|
-
isError: true
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
if (request.params.name === "save_memory") {
|
|
447
|
-
const args = parseArgs(SaveMemoryArgsSchema, request.params.arguments);
|
|
448
|
-
const projectScope = args.global === true ? void 0 : await resolveProject();
|
|
449
|
-
try {
|
|
450
|
-
const memory = await core.repo.save({
|
|
451
|
-
content: args.content,
|
|
452
|
-
type: args.type,
|
|
453
|
-
tags: args.tags,
|
|
454
|
-
projectScope
|
|
455
|
-
});
|
|
456
|
-
if (core.synthEngine !== void 0) {
|
|
457
|
-
const scope = memory.projects.length > 0 ? memory.projects[0]?.scopeHash ?? "global" : "global";
|
|
458
|
-
core.synthEngine.markDirty(scope);
|
|
459
|
-
}
|
|
460
|
-
return { content: [{
|
|
461
|
-
type: "text",
|
|
462
|
-
text: JSON.stringify(memory)
|
|
463
|
-
}] };
|
|
464
|
-
} catch (err) {
|
|
465
|
-
return {
|
|
466
|
-
content: [{
|
|
467
|
-
type: "text",
|
|
468
|
-
text: err instanceof Error ? err.message : String(err)
|
|
469
|
-
}],
|
|
470
|
-
isError: true
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
if (request.params.name === "update_memory") {
|
|
475
|
-
const args = parseArgs(UpdateMemoryArgsSchema, request.params.arguments);
|
|
476
|
-
try {
|
|
477
|
-
const memory = await core.repo.update(args.id, {
|
|
478
|
-
content: args.content,
|
|
479
|
-
type: args.type,
|
|
480
|
-
tags: args.tags
|
|
481
|
-
});
|
|
482
|
-
if (core.synthEngine !== void 0) {
|
|
483
|
-
const scope = memory.projects.length > 0 ? memory.projects[0]?.scopeHash ?? "global" : "global";
|
|
484
|
-
core.synthEngine.markDirty(scope);
|
|
485
|
-
}
|
|
486
|
-
return { content: [{
|
|
487
|
-
type: "text",
|
|
488
|
-
text: JSON.stringify(memory)
|
|
489
|
-
}] };
|
|
490
|
-
} catch (err) {
|
|
491
|
-
return {
|
|
492
|
-
content: [{
|
|
493
|
-
type: "text",
|
|
494
|
-
text: err instanceof Error ? err.message : String(err)
|
|
495
|
-
}],
|
|
496
|
-
isError: true
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
if (request.params.name === "delete_memory") {
|
|
501
|
-
const args = parseArgs(DeleteMemoryArgsSchema, request.params.arguments);
|
|
502
|
-
try {
|
|
503
|
-
if (!(core.db.db.prepare(`SELECT id FROM memories WHERE id = ?`).get(args.id) !== void 0)) return {
|
|
504
|
-
content: [{
|
|
505
|
-
type: "text",
|
|
506
|
-
text: `Memory not found: ${args.id}`
|
|
507
|
-
}],
|
|
508
|
-
isError: true
|
|
509
|
-
};
|
|
510
|
-
let memoryScopeBeforeDelete;
|
|
511
|
-
if (core.synthEngine !== void 0) memoryScopeBeforeDelete = core.db.db.prepare(`SELECT p.scope_hash FROM projects p
|
|
512
|
-
JOIN memory_projects mp ON mp.project_id = p.id
|
|
513
|
-
WHERE mp.memory_id = ?`).get(args.id)?.scope_hash ?? "global";
|
|
514
|
-
await core.repo.delete(args.id);
|
|
515
|
-
if (core.synthEngine !== void 0 && memoryScopeBeforeDelete !== void 0) core.synthEngine.markDirty(memoryScopeBeforeDelete);
|
|
516
|
-
return { content: [{
|
|
517
|
-
type: "text",
|
|
518
|
-
text: JSON.stringify({
|
|
519
|
-
success: true,
|
|
520
|
-
id: args.id
|
|
521
|
-
})
|
|
522
|
-
}] };
|
|
523
|
-
} catch (err) {
|
|
524
|
-
return {
|
|
525
|
-
content: [{
|
|
526
|
-
type: "text",
|
|
527
|
-
text: err instanceof Error ? err.message : String(err)
|
|
528
|
-
}],
|
|
529
|
-
isError: true
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
if (request.params.name === "query_memory") {
|
|
534
|
-
const args = parseArgs(QueryMemoryArgsSchema, request.params.arguments);
|
|
535
|
-
const projectHash = args.global === true ? void 0 : (await resolveProject()).hash;
|
|
536
|
-
try {
|
|
537
|
-
const serialised = (await core.query.query({
|
|
538
|
-
query: args.query,
|
|
539
|
-
type: args.type,
|
|
540
|
-
projectHash,
|
|
541
|
-
limit: args.limit ?? 10,
|
|
542
|
-
includePinned: args.includePinned
|
|
543
|
-
})).map((r) => ({
|
|
544
|
-
id: r.id,
|
|
545
|
-
content: r.content,
|
|
546
|
-
type: r.type,
|
|
547
|
-
tags: r.tags,
|
|
548
|
-
projects: r.projects,
|
|
549
|
-
pinned: r.pinned,
|
|
550
|
-
reviewEvents: r.reviewEvents,
|
|
551
|
-
createdAt: r.createdAt,
|
|
552
|
-
updatedAt: r.updatedAt,
|
|
553
|
-
sourceHarness: r.sourceHarness,
|
|
554
|
-
score: r.score
|
|
555
|
-
}));
|
|
556
|
-
return { content: [{
|
|
557
|
-
type: "text",
|
|
558
|
-
text: JSON.stringify(serialised)
|
|
559
|
-
}] };
|
|
560
|
-
} catch (err) {
|
|
561
|
-
return {
|
|
562
|
-
content: [{
|
|
563
|
-
type: "text",
|
|
564
|
-
text: err instanceof Error ? err.message : String(err)
|
|
565
|
-
}],
|
|
566
|
-
isError: true
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
if (request.params.name === "migrate") {
|
|
571
|
-
const args = parseArgs(MigrateArgsSchema, request.params.arguments);
|
|
572
|
-
if (args.mode === "list") return { content: [{
|
|
573
|
-
type: "text",
|
|
574
|
-
text: JSON.stringify(MIGRATIONS)
|
|
575
|
-
}] };
|
|
576
|
-
if (args.name === "scope-to-projects") try {
|
|
577
|
-
const result = await runScopeToProjectsMigration(core.projects);
|
|
578
|
-
if (result === null) return {
|
|
579
|
-
content: [{
|
|
580
|
-
type: "text",
|
|
581
|
-
text: JSON.stringify({ error: "No project found for current directory." })
|
|
582
|
-
}],
|
|
583
|
-
isError: true
|
|
584
|
-
};
|
|
585
|
-
return { content: [{
|
|
586
|
-
type: "text",
|
|
587
|
-
text: JSON.stringify(result)
|
|
588
|
-
}] };
|
|
589
|
-
} catch (err) {
|
|
590
|
-
return {
|
|
591
|
-
content: [{
|
|
592
|
-
type: "text",
|
|
593
|
-
text: err instanceof Error ? err.message : String(err)
|
|
594
|
-
}],
|
|
595
|
-
isError: true
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
throw new McpError(ErrorCode.InvalidParams, `Unknown migration: "${args.name}". Available: ${MIGRATIONS.map((m) => m.name).join(", ")}`);
|
|
599
|
-
}
|
|
600
|
-
if (request.params.name === "pin_memory" || request.params.name === "unpin_memory") {
|
|
601
|
-
const args = parseArgs(PinMemoryArgsSchema, request.params.arguments);
|
|
602
|
-
const pinned = request.params.name === "pin_memory";
|
|
603
|
-
try {
|
|
604
|
-
const memory = core.repo.setPin(args.id, pinned);
|
|
605
|
-
if (pinned) {
|
|
606
|
-
const charCount = core.repo.getPinnedCharCount();
|
|
607
|
-
if (charCount > PIN_BUDGET_THRESHOLD && !isSynthesisEnabled()) {
|
|
608
|
-
const result = {
|
|
609
|
-
...memory,
|
|
610
|
-
pinBudgetWarning: `Pinned memories now use ${charCount} characters (threshold: ${PIN_BUDGET_THRESHOLD}). Consider unpinning older memories or enabling synthesis to compress them.`
|
|
611
|
-
};
|
|
612
|
-
return { content: [{
|
|
613
|
-
type: "text",
|
|
614
|
-
text: JSON.stringify(result)
|
|
615
|
-
}] };
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
return { content: [{
|
|
619
|
-
type: "text",
|
|
620
|
-
text: JSON.stringify(memory)
|
|
621
|
-
}] };
|
|
622
|
-
} catch (err) {
|
|
623
|
-
return {
|
|
624
|
-
content: [{
|
|
625
|
-
type: "text",
|
|
626
|
-
text: err instanceof Error ? err.message : String(err)
|
|
627
|
-
}],
|
|
628
|
-
isError: true
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
if (request.params.name === "get_memory_summary") try {
|
|
633
|
-
return { content: [{
|
|
634
|
-
type: "text",
|
|
635
|
-
text: JSON.stringify(core.repo.stats())
|
|
636
|
-
}] };
|
|
637
|
-
} catch (err) {
|
|
638
|
-
return {
|
|
639
|
-
content: [{
|
|
640
|
-
type: "text",
|
|
641
|
-
text: err instanceof Error ? err.message : String(err)
|
|
642
|
-
}],
|
|
643
|
-
isError: true
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
if (request.params.name === "list_flagged_memories") try {
|
|
647
|
-
const memories = core.repo.listFlagged();
|
|
648
|
-
return { content: [{
|
|
649
|
-
type: "text",
|
|
650
|
-
text: JSON.stringify(memories)
|
|
651
|
-
}] };
|
|
652
|
-
} catch (err) {
|
|
653
|
-
return {
|
|
654
|
-
content: [{
|
|
655
|
-
type: "text",
|
|
656
|
-
text: err instanceof Error ? err.message : String(err)
|
|
657
|
-
}],
|
|
658
|
-
isError: true
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
if (request.params.name === "resolve_review") {
|
|
662
|
-
const args = parseArgs(ResolveReviewArgsSchema, request.params.arguments);
|
|
663
|
-
try {
|
|
664
|
-
if (!(core.db.db.prepare(`SELECT id FROM memories WHERE id = ?`).get(args.id) !== void 0)) return {
|
|
665
|
-
content: [{
|
|
666
|
-
type: "text",
|
|
667
|
-
text: `Memory not found: ${args.id}`
|
|
668
|
-
}],
|
|
669
|
-
isError: true
|
|
670
|
-
};
|
|
671
|
-
core.repo.resolveReviewEvents(args.id);
|
|
672
|
-
return { content: [{
|
|
673
|
-
type: "text",
|
|
674
|
-
text: JSON.stringify({
|
|
675
|
-
success: true,
|
|
676
|
-
id: args.id
|
|
677
|
-
})
|
|
678
|
-
}] };
|
|
679
|
-
} catch (err) {
|
|
680
|
-
return {
|
|
681
|
-
content: [{
|
|
682
|
-
type: "text",
|
|
683
|
-
text: err instanceof Error ? err.message : String(err)
|
|
684
|
-
}],
|
|
685
|
-
isError: true
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
690
|
-
});
|
|
691
|
-
return server;
|
|
692
|
-
}
|
|
693
|
-
//#endregion
|
|
694
|
-
//#region src/index.ts
|
|
695
|
-
async function startServer() {
|
|
696
|
-
let core;
|
|
697
|
-
try {
|
|
698
|
-
core = initCore();
|
|
699
|
-
} catch (err) {
|
|
700
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
701
|
-
process.stderr.write(`membank: failed to initialise core: ${message}\n`);
|
|
702
|
-
process.exit(1);
|
|
703
|
-
}
|
|
704
|
-
if (core.synthEngine !== void 0) try {
|
|
705
|
-
await core.synthEngine.init();
|
|
706
|
-
} catch (err) {
|
|
707
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
708
|
-
process.stderr.write(`membank: synthesis engine init failed: ${message}\n`);
|
|
709
|
-
}
|
|
710
|
-
const shutdown = async () => {
|
|
711
|
-
if (core.synthEngine !== void 0) await core.synthEngine.shutdown();
|
|
712
|
-
process.exit(0);
|
|
713
|
-
};
|
|
714
|
-
process.on("SIGTERM", () => {
|
|
715
|
-
shutdown();
|
|
716
|
-
});
|
|
717
|
-
process.on("SIGINT", () => {
|
|
718
|
-
shutdown();
|
|
719
|
-
});
|
|
720
|
-
const server = createServer(core);
|
|
721
|
-
const transport = new StdioServerTransport();
|
|
722
|
-
await server.connect(transport);
|
|
723
|
-
}
|
|
724
|
-
async function runSynthesis(scope) {
|
|
725
|
-
if (!isSynthesisEnabled()) throw new Error("Synthesis is not enabled. Run: membank config set synthesis.enabled true");
|
|
726
|
-
const db = DatabaseManager.open();
|
|
727
|
-
const embedding = new EmbeddingService();
|
|
728
|
-
const projects = new ProjectRepository(db);
|
|
729
|
-
const repo = new MemoryRepository(db, embedding, projects);
|
|
730
|
-
const queryEngine = new QueryEngine(db, embedding, repo);
|
|
731
|
-
const synthRepo = new SynthesisRepository(db);
|
|
732
|
-
const agentLoop = new SynthesisAgentLoop(buildSynthesisTools(repo, queryEngine), { enabled: true });
|
|
733
|
-
let resolvedScope = scope;
|
|
734
|
-
if (scope !== "global" && !/^[0-9a-f]{16}$/.test(scope)) {
|
|
735
|
-
const project = projects.getByName(scope);
|
|
736
|
-
if (project !== void 0) resolvedScope = project.scopeHash;
|
|
737
|
-
}
|
|
738
|
-
const projectHash = resolvedScope === "global" ? void 0 : resolvedScope;
|
|
739
|
-
synthRepo.markInFlight(resolvedScope);
|
|
740
|
-
try {
|
|
741
|
-
const [content, sourceHash] = await Promise.all([agentLoop.run(resolvedScope, projectHash), Promise.resolve(synthRepo.computeSourceMemoryHash(resolvedScope))]);
|
|
742
|
-
synthRepo.saveSynthesis(resolvedScope, content, sourceHash);
|
|
743
|
-
return content;
|
|
744
|
-
} catch (err) {
|
|
745
|
-
synthRepo.clearInFlight(resolvedScope);
|
|
746
|
-
throw err;
|
|
747
|
-
} finally {
|
|
748
|
-
db.close();
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
//#endregion
|
|
752
|
-
export { runSynthesis, startServer };
|
|
753
|
-
|
|
1
|
+
import{DatabaseManager as e,EmbeddingService as t,MEMORY_TYPE_VALUES as n,MIGRATIONS as r,MemoryTypeSchema as i,PIN_BUDGET_THRESHOLD as a,QueryEngine as o,SynthesisEngine as s,createMemoryRepository as c,createProjectRepository as l,createSynthesisAgentRunner as u,createSynthesisRepository as d,isSynthesisEnabled as f,listMemoryTypes as p,resolveProject as m,runScopeToProjectsMigration as h,runSynthesis as g,saveMemory as _,updateMemory as v}from"@membank/core";import{StdioServerTransport as y}from"@modelcontextprotocol/sdk/server/stdio.js";import{readFileSync as b}from"node:fs";import{homedir as x}from"node:os";import{join as S}from"node:path";import{Server as C}from"@modelcontextprotocol/sdk/server";import{CallToolRequestSchema as w,ErrorCode as T,ListToolsRequestSchema as E,McpError as D}from"@modelcontextprotocol/sdk/types.js";import{ZodError as O,z as k}from"zod";const A=k.object({content:k.string().min(1),type:i,tags:k.array(k.string()).optional(),global:k.boolean().optional()}),j=k.object({id:k.string().min(1),content:k.string().min(1).optional(),type:i.optional(),tags:k.array(k.string()).optional()}),M=k.object({id:k.string().min(1)}),N=k.object({query:k.string().min(1),type:i.optional(),limit:k.number().int().positive().optional(),includePinned:k.boolean().optional(),global:k.boolean().optional()}),P=k.discriminatedUnion(`mode`,[k.object({mode:k.literal(`list`)}),k.object({mode:k.literal(`run`),name:k.string().min(1)})]),F=k.object({id:k.string().min(1)}),I=k.object({id:k.string().min(1)});function L(){let e=S(x(),`.membank`,`config.json`);try{let t=b(e,`utf8`),n=JSON.parse(t);return{enabled:n.synthesis?.enabled===!0,maxTokensPerRun:n.synthesis?.maxTokensPerRun,debounceMs:n.synthesis?.debounceMs,stalenessDays:n.synthesis?.stalenessDays,inFlightTimeoutMs:n.synthesis?.inFlightTimeoutMs}}catch{return{enabled:!1}}}function R(e,t){return{queryMemory:async e=>{let n=e.global===!0?void 0:e.projectHash??(await m()).hash,r=await t.query({query:e.query,projectHash:n,limit:e.limit??20,includePinned:!0});return JSON.stringify(r)},getMemorySummary:async()=>JSON.stringify(e.stats())}}function z(n={}){let r=n.useInMemoryDb?e.openInMemory():e.open(n.dbPath),i=new t,a=l(r),f=c(r,a),p=new o(r,i,f),m=L(),h;return m.enabled&&(h=new s(d(r),m,u(R(f,p),m))),{db:r,embedding:i,repo:f,query:p,projects:a,synthEngine:h}}function B(e,t){try{return e.parse(t)}catch(e){let t=e instanceof O?e.issues[0]?.message??e.message:String(e);throw new D(T.InvalidParams,t)}}function V(e){let t=new C({name:`membank`,version:`0.1.0`},{capabilities:{tools:{}}});return t.setRequestHandler(E,()=>({tools:[{name:`list_memory_types`,description:`Returns the ordered list of memory type values supported by membank.`,inputSchema:{type:`object`,properties:{},required:[]}},{name:`save_memory`,description:`Save a new memory. Handles deduplication automatically — near-identical memories (cosine similarity >0.92, same type and project) overwrite the existing record.`,inputSchema:{type:`object`,properties:{content:{type:`string`,description:`Memory content to save`},type:{type:`string`,enum:[...n],description:`Memory type`},tags:{type:`array`,items:{type:`string`},description:`Optional tags`},global:{type:`boolean`,description:`Save as a global memory, not tied to any project`}},required:[`content`,`type`]}},{name:`update_memory`,description:`Update the content, type, and/or tags of an existing memory by id. All fields except id are optional.`,inputSchema:{type:`object`,properties:{id:{type:`string`,description:`Memory id to update`},content:{type:`string`,description:`New content for the memory`},type:{type:`string`,enum:[...n],description:`New type for the memory (reclassification)`},tags:{type:`array`,items:{type:`string`},description:`Replacement tags (optional)`}},required:[`id`]}},{name:`delete_memory`,description:`Delete a memory by id.`,inputSchema:{type:`object`,properties:{id:{type:`string`,description:`Memory id to delete`}},required:[`id`]}},{name:`query_memory`,description:`Search memories by semantic similarity. Returns results ranked by confidence score.`,inputSchema:{type:`object`,properties:{query:{type:`string`,description:`Search text`},type:{type:`string`,enum:[...n],description:`Filter by memory type`},limit:{type:`number`,description:`Maximum results to return (default 10)`},includePinned:{type:`boolean`,description:`Include pinned memories in results. Pinned memories are already injected into session context, so excluded by default to avoid duplicates.`},global:{type:`boolean`,description:`Query global memories only. When omitted or false, queries the current project scope.`}},required:[`query`]}},{name:`migrate`,description:`List or run named data migrations. Use mode "list" to see available migrations; mode "run" with a migration name to execute one.`,inputSchema:{type:`object`,properties:{mode:{type:`string`,enum:[`list`,`run`],description:`Mode: "list" to see available migrations, "run" to execute one`},name:{type:`string`,description:`Migration name (required when mode is "run")`}},required:[`mode`]}},{name:`pin_memory`,description:`Pin a memory by id. Pinned memories are always injected into the session context.`,inputSchema:{type:`object`,properties:{id:{type:`string`,description:`Memory id to pin`}},required:[`id`]}},{name:`unpin_memory`,description:`Unpin a memory by id. Removes the memory from guaranteed session injection.`,inputSchema:{type:`object`,properties:{id:{type:`string`,description:`Memory id to unpin`}},required:[`id`]}},{name:`get_memory_summary`,description:`Returns aggregate stats for session orientation: total memories, counts by type, pinned count, and review queue size.`,inputSchema:{type:`object`,properties:{},required:[]}},{name:`list_flagged_memories`,description:`List memories that have unresolved dedup review events. These were flagged automatically when a near-duplicate was saved (cosine similarity 0.75–0.92).`,inputSchema:{type:`object`,properties:{},required:[]}},{name:`resolve_review`,description:`Dismiss all unresolved review events for a memory. Use after reviewing the memory and deciding it should be kept as-is.`,inputSchema:{type:`object`,properties:{id:{type:`string`,description:`Memory id to resolve review events for`}},required:[`id`]}}]})),t.setRequestHandler(w,async t=>{if(t.params.name===`list_memory_types`)try{return{content:[{type:`text`,text:JSON.stringify(p())}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}if(t.params.name===`save_memory`){let n=B(A,t.params.arguments),r=n.global===!0?void 0:await m();try{let t=await _({content:n.content,type:n.type,tags:n.tags,projectScope:r},{repo:e.repo,embedder:e.embedding});if(e.synthEngine!==void 0){let n=t.projects.length>0?t.projects[0]?.scopeHash??`global`:`global`;e.synthEngine.markDirty(n)}return{content:[{type:`text`,text:JSON.stringify(t)}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}if(t.params.name===`update_memory`){let n=B(j,t.params.arguments);try{let t=await v(n.id,{content:n.content,type:n.type,tags:n.tags},{repo:e.repo,embedder:e.embedding});if(e.synthEngine!==void 0){let n=t.projects.length>0?t.projects[0]?.scopeHash??`global`:`global`;e.synthEngine.markDirty(n)}return{content:[{type:`text`,text:JSON.stringify(t)}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}if(t.params.name===`delete_memory`){let n=B(M,t.params.arguments);try{let t=e.repo.findById(n.id);if(t===void 0)return{content:[{type:`text`,text:`Memory not found: ${n.id}`}],isError:!0};let r=e.synthEngine===void 0?void 0:t.projects[0]?.scopeHash??`global`;return e.repo.delete(n.id),e.synthEngine!==void 0&&r!==void 0&&e.synthEngine.markDirty(r),{content:[{type:`text`,text:JSON.stringify({success:!0,id:n.id})}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}if(t.params.name===`query_memory`){let n=B(N,t.params.arguments),r=n.global===!0?void 0:(await m()).hash;try{let t=(await e.query.query({query:n.query,type:n.type,projectHash:r,limit:n.limit??10,includePinned:n.includePinned})).map(e=>({id:e.id,content:e.content,type:e.type,tags:e.tags,projects:e.projects,pinned:e.pinned,reviewEvents:e.reviewEvents,createdAt:e.createdAt,updatedAt:e.updatedAt,sourceHarness:e.sourceHarness,score:e.score}));return{content:[{type:`text`,text:JSON.stringify(t)}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}if(t.params.name===`migrate`){let n=B(P,t.params.arguments);if(n.mode===`list`)return{content:[{type:`text`,text:JSON.stringify(r)}]};if(n.name===`scope-to-projects`)try{let t=await h(e.projects);return t===null?{content:[{type:`text`,text:JSON.stringify({error:`No project found for current directory.`})}],isError:!0}:{content:[{type:`text`,text:JSON.stringify(t)}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}throw new D(T.InvalidParams,`Unknown migration: "${n.name}". Available: ${r.map(e=>e.name).join(`, `)}`)}if(t.params.name===`pin_memory`||t.params.name===`unpin_memory`){let n=B(F,t.params.arguments),r=t.params.name===`pin_memory`;try{let t=e.repo.setPin(n.id,r);if(r){let n=e.repo.getPinnedCharCount();if(n>a&&!f()){let e={...t,pinBudgetWarning:`Pinned memories now use ${n} characters (threshold: ${a}). Consider unpinning older memories or enabling synthesis to compress them.`};return{content:[{type:`text`,text:JSON.stringify(e)}]}}}return{content:[{type:`text`,text:JSON.stringify(t)}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}if(t.params.name===`get_memory_summary`)try{return{content:[{type:`text`,text:JSON.stringify(e.repo.stats())}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}if(t.params.name===`list_flagged_memories`)try{let t=e.repo.listFlagged();return{content:[{type:`text`,text:JSON.stringify(t)}]}}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}if(t.params.name===`resolve_review`){let n=B(I,t.params.arguments);try{return e.repo.findById(n.id)===void 0?{content:[{type:`text`,text:`Memory not found: ${n.id}`}],isError:!0}:(e.repo.resolveReviewEvents(n.id),{content:[{type:`text`,text:JSON.stringify({success:!0,id:n.id})}]})}catch(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}throw Error(`Unknown tool: ${t.params.name}`)}),t}async function H(){let e;try{e=z()}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`membank: failed to initialise core: ${t}\n`),process.exit(1)}if(e.synthEngine!==void 0)try{await e.synthEngine.init()}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`membank: synthesis engine init failed: ${t}\n`)}let t=async()=>{e.synthEngine!==void 0&&await e.synthEngine.shutdown(),process.exit(0)};process.on(`SIGTERM`,()=>{t()}),process.on(`SIGINT`,()=>{t()});let n=V(e),r=new y;await n.connect(r)}async function U(n){if(!f())throw Error(`Synthesis is not enabled. Run: membank config set synthesis.enabled true`);let r=e.open(),i=new t,a=l(r),s=c(r,a),p=new o(r,i,s),m=d(r),h=u(R(s,p),{enabled:!0}),_=n;if(n!==`global`&&!/^[0-9a-f]{16}$/.test(n)){let e=a.getByName(n);e!==void 0&&(_=e.scopeHash)}try{return await g(_,{synthRepo:m,agentRunner:h})}finally{r.close()}}export{U as runSynthesis,H as startServer};
|
|
754
2
|
//# sourceMappingURL=index.mjs.map
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@membank/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/draekien-industries/membank.git",
|
|
8
8
|
"directory": "packages/mcp"
|
|
9
9
|
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"membank-mcp": "./dist/bin.mjs"
|
|
12
|
+
},
|
|
10
13
|
"exports": {
|
|
11
14
|
".": {
|
|
12
15
|
"import": "./dist/index.mjs",
|
|
@@ -14,13 +17,14 @@
|
|
|
14
17
|
}
|
|
15
18
|
},
|
|
16
19
|
"files": [
|
|
17
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"!dist/**/*.map"
|
|
18
22
|
],
|
|
19
23
|
"dependencies": {
|
|
20
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.131",
|
|
21
24
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
25
|
+
"commander": "^14.0.3",
|
|
22
26
|
"zod": "^4.4.3",
|
|
23
|
-
"@membank/core": "0.
|
|
27
|
+
"@membank/core": "0.10.0"
|
|
24
28
|
},
|
|
25
29
|
"devDependencies": {
|
|
26
30
|
"@types/node": "^25.6.0",
|
package/dist/index.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";iBAcsB,WAAA,CAAA,GAAe,OAAA;AAAA,iBAsCf,YAAA,CAAa,KAAA,WAAgB,OAAA"}
|
package/dist/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["#tools","#synthRepo","#config","#agentLoop","#dirtyScopes","#failureCounts","#running","#debounceLoop","#loopTimer","#inFlightPromises","#synthesizeScope","#scheduleNextCycle"],"sources":["../src/schemas.ts","../src/synthesis/agent-loop.ts","../src/synthesis/engine.ts","../src/server.ts","../src/index.ts"],"sourcesContent":["import { MemoryTypeSchema } from \"@membank/core\";\nimport { z } from \"zod\";\n\nexport const SaveMemoryArgsSchema = z.object({\n content: z.string().min(1),\n type: MemoryTypeSchema,\n tags: z.array(z.string()).optional(),\n global: z.boolean().optional(),\n});\n\nexport const UpdateMemoryArgsSchema = z.object({\n id: z.string().min(1),\n content: z.string().min(1).optional(),\n type: MemoryTypeSchema.optional(),\n tags: z.array(z.string()).optional(),\n});\n\nexport const DeleteMemoryArgsSchema = z.object({\n id: z.string().min(1),\n});\n\nexport const QueryMemoryArgsSchema = z.object({\n query: z.string().min(1),\n type: MemoryTypeSchema.optional(),\n limit: z.number().int().positive().optional(),\n includePinned: z.boolean().optional(),\n global: z.boolean().optional(),\n});\n\nexport const MigrateArgsSchema = z.discriminatedUnion(\"mode\", [\n z.object({ mode: z.literal(\"list\") }),\n z.object({ mode: z.literal(\"run\"), name: z.string().min(1) }),\n]);\n\nexport const PinMemoryArgsSchema = z.object({\n id: z.string().min(1),\n});\n\nexport const ResolveReviewArgsSchema = z.object({\n id: z.string().min(1),\n});\n","import { createSdkMcpServer, query, tool } from \"@anthropic-ai/claude-agent-sdk\";\nimport { z } from \"zod\";\n\nconst SYNTHESIS_SYSTEM_PROMPT =\n \"You are a memory synthesizer. Your job is to read the user's stored memories and produce a concise, well-structured summary of what's most important to remember about this user — their preferences, corrections, decisions, and key facts. Pinned memories are higher fidelity and should be weighted more heavily. Exclude transient or ephemeral details. Output plain text suitable for injection into an LLM context window. Be concise — target 200-400 words.\";\n\nexport interface SynthesisConfig {\n enabled: boolean;\n maxTokensPerRun?: number;\n debounceMs?: number;\n stalenessDays?: number;\n inFlightTimeoutMs?: number;\n}\n\nexport interface SynthesisTools {\n queryMemory: (args: {\n query: string;\n limit?: number;\n global?: boolean;\n projectHash?: string;\n }) => Promise<string>;\n getMemorySummary: () => Promise<string>;\n}\n\nexport class SynthesisAgentLoop {\n readonly #tools: SynthesisTools;\n\n constructor(tools: SynthesisTools, _config: SynthesisConfig) {\n this.#tools = tools;\n }\n\n async run(scope: string, projectHash?: string): Promise<string> {\n const queryMemoryTool = tool(\n \"query_memory\",\n \"Search memories by semantic similarity\",\n {\n query: z.string().describe(\"Search text\"),\n limit: z.number().optional().describe(\"Maximum results to return\"),\n global: z\n .boolean()\n .optional()\n .describe(\"Query global memories only when true, otherwise current project scope\"),\n },\n async ({ query: q, limit, global: isGlobal }) => {\n const result = await this.#tools.queryMemory({\n query: q,\n limit,\n global: isGlobal,\n projectHash,\n });\n return { content: [{ type: \"text\" as const, text: result }] };\n },\n { annotations: { readOnlyHint: true } }\n );\n\n const getMemorySummaryTool = tool(\n \"get_memory_summary\",\n \"Returns aggregate stats: total memories, counts by type, pinned count, review queue size\",\n {},\n async () => {\n const result = await this.#tools.getMemorySummary();\n return { content: [{ type: \"text\" as const, text: result }] };\n },\n { annotations: { readOnlyHint: true } }\n );\n\n const mcpServer = createSdkMcpServer({\n name: \"membank-synthesis-tools\",\n version: \"1.0.0\",\n tools: [queryMemoryTool, getMemorySummaryTool],\n });\n\n const isGlobal = scope === \"global\";\n const scopeDescription = isGlobal ? \"global (across all projects)\" : `project scope: ${scope}`;\n\n const prompt = `Synthesize the memories for ${scopeDescription}. Use get_memory_summary first to understand the overall state, then use query_memory to retrieve relevant memories (query with broad terms like \"preferences\", \"corrections\", \"decisions\", \"key facts\"). After gathering information, produce a concise synthesis of the most important things to remember about this user. Output only the synthesis text — no preamble, no metadata.`;\n\n const startTime = Date.now();\n\n const env = Object.fromEntries(\n Object.entries(process.env).filter((e): e is [string, string] => e[1] !== undefined)\n );\n\n const agentQuery = query({\n prompt,\n options: {\n model: \"claude-haiku-4-5-20251001\",\n systemPrompt: SYNTHESIS_SYSTEM_PROMPT,\n mcpServers: { \"membank-synthesis-tools\": mcpServer },\n allowedTools: [\"query_memory\", \"get_memory_summary\"],\n permissionMode: \"bypassPermissions\",\n env,\n },\n });\n\n let finalResult = \"\";\n\n for await (const message of agentQuery) {\n if (message.type === \"result\") {\n if (message.subtype === \"success\") {\n finalResult = message.result;\n } else {\n const details =\n \"errors\" in message && Array.isArray(message.errors)\n ? `: ${message.errors.join(\"; \")}`\n : \"\";\n throw new Error(`Synthesis agent failed: ${message.subtype}${details}`);\n }\n }\n }\n\n const durationMs = Date.now() - startTime;\n process.stderr.write(`membank synthesis: scope=${scope} duration=${durationMs}ms\\n`);\n\n if (finalResult === \"\") {\n throw new Error(\"Synthesis agent returned empty result\");\n }\n\n return finalResult;\n }\n}\n","import type { DatabaseManager, SynthesisRepository } from \"@membank/core\";\nimport type { SynthesisAgentLoop, SynthesisConfig } from \"./agent-loop.js\";\n\nconst DEFAULT_DEBOUNCE_MS = 45_000;\nconst DEFAULT_IN_FLIGHT_TIMEOUT_MS = 120_000;\nconst MAX_BACKOFF_MULTIPLIER = 5;\n\nexport class SynthesisEngine {\n readonly #synthRepo: SynthesisRepository;\n readonly #config: SynthesisConfig;\n readonly #agentLoop: SynthesisAgentLoop;\n readonly #dirtyScopes = new Set<string>();\n readonly #failureCounts = new Map<string, number>();\n #running = false;\n #loopTimer: ReturnType<typeof setTimeout> | undefined;\n #inFlightPromises = new Map<string, Promise<void>>();\n\n constructor(\n _db: DatabaseManager,\n synthRepo: SynthesisRepository,\n config: SynthesisConfig,\n agentLoop: SynthesisAgentLoop\n ) {\n this.#synthRepo = synthRepo;\n this.#config = config;\n this.#agentLoop = agentLoop;\n }\n\n async init(): Promise<void> {\n // Clear in_flight_since markers older than the timeout — they belong to a dead process.\n this.#synthRepo.clearStaleInFlight(\n this.#config.inFlightTimeoutMs ?? DEFAULT_IN_FLIGHT_TIMEOUT_MS\n );\n this.#synthRepo.expireStale();\n\n const stale = this.#synthRepo.getExpiredOrDirtyScopes();\n for (const { scope } of stale) {\n this.#dirtyScopes.add(scope);\n }\n\n this.#running = true;\n // Process any scopes discovered at startup immediately, then begin the periodic cycle\n await this.#debounceLoop();\n }\n\n shutdown(): Promise<void> {\n this.#running = false;\n\n if (this.#loopTimer !== undefined) {\n clearTimeout(this.#loopTimer);\n this.#loopTimer = undefined;\n }\n\n const inFlight = [...this.#inFlightPromises.values()];\n if (inFlight.length === 0) return Promise.resolve();\n\n const graceMs = 5_000;\n return Promise.race([\n Promise.allSettled(inFlight).then(() => undefined),\n new Promise<void>((resolve) => setTimeout(resolve, graceMs)),\n ]);\n }\n\n markDirty(scope: string): void {\n this.#dirtyScopes.add(scope);\n }\n\n #scheduleNextCycle(): void {\n if (!this.#running) return;\n const debounceMs = this.#config.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n this.#loopTimer = setTimeout(() => {\n void this.#debounceLoop();\n }, debounceMs);\n }\n\n async #debounceLoop(): Promise<void> {\n const scopesToProcess = [...this.#dirtyScopes];\n\n for (const scope of scopesToProcess) {\n const inFlightTimeoutMs = this.#config.inFlightTimeoutMs ?? DEFAULT_IN_FLIGHT_TIMEOUT_MS;\n const synthesis = this.#synthRepo.getSynthesis(scope);\n\n if (synthesis?.inFlightSince !== null && synthesis?.inFlightSince !== undefined) {\n const inFlightMs = Date.now() - new Date(synthesis.inFlightSince).getTime();\n if (inFlightMs < inFlightTimeoutMs) {\n continue;\n }\n // Stale in-flight — clear it and allow resynthesis\n this.#synthRepo.clearInFlight(scope);\n }\n\n this.#dirtyScopes.delete(scope);\n const promise = this.#synthesizeScope(scope).finally(() => {\n this.#inFlightPromises.delete(scope);\n });\n this.#inFlightPromises.set(scope, promise);\n }\n\n this.#scheduleNextCycle();\n }\n\n async #synthesizeScope(scope: string): Promise<void> {\n this.#synthRepo.markInFlight(scope);\n\n try {\n const projectHash = scope === \"global\" ? undefined : scope;\n const content = await this.#agentLoop.run(scope, projectHash);\n const sourceHash = this.#synthRepo.computeSourceMemoryHash(scope);\n this.#synthRepo.saveSynthesis(scope, content, sourceHash);\n this.#failureCounts.delete(scope);\n } catch (err) {\n const failures = (this.#failureCounts.get(scope) ?? 0) + 1;\n this.#failureCounts.set(scope, failures);\n\n // Exponential backoff: re-queue with multiplied debounce up to MAX_BACKOFF_MULTIPLIER\n const backoffMultiplier = Math.min(failures, MAX_BACKOFF_MULTIPLIER);\n const backoffMs = (this.#config.debounceMs ?? DEFAULT_DEBOUNCE_MS) * backoffMultiplier;\n\n process.stderr.write(\n `membank synthesis: error for scope=${scope} failures=${failures} backoff=${backoffMs}ms: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n\n setTimeout(() => {\n this.#dirtyScopes.add(scope);\n }, backoffMs);\n\n this.#synthRepo.clearInFlight(scope);\n }\n }\n}\n","import { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n DatabaseManager,\n EmbeddingService,\n isSynthesisEnabled,\n listMemoryTypes,\n MEMORY_TYPE_VALUES,\n MemoryRepository,\n MIGRATIONS,\n PIN_BUDGET_THRESHOLD,\n ProjectRepository,\n QueryEngine,\n resolveProject,\n runScopeToProjectsMigration,\n SynthesisRepository,\n} from \"@membank/core\";\nimport { Server } from \"@modelcontextprotocol/sdk/server\";\nimport {\n CallToolRequestSchema,\n ErrorCode,\n ListToolsRequestSchema,\n McpError,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { ZodError } from \"zod\";\nimport {\n DeleteMemoryArgsSchema,\n MigrateArgsSchema,\n PinMemoryArgsSchema,\n QueryMemoryArgsSchema,\n ResolveReviewArgsSchema,\n SaveMemoryArgsSchema,\n UpdateMemoryArgsSchema,\n} from \"./schemas.js\";\nimport type { SynthesisConfig, SynthesisTools } from \"./synthesis/index.js\";\nimport { SynthesisAgentLoop, SynthesisEngine } from \"./synthesis/index.js\";\n\nconst SERVER_NAME = \"membank\";\nconst SERVER_VERSION = \"0.1.0\";\n\nexport interface CoreServices {\n db: DatabaseManager;\n embedding: EmbeddingService;\n repo: MemoryRepository;\n query: QueryEngine;\n projects: ProjectRepository;\n synthEngine?: SynthesisEngine;\n}\n\nexport interface ServerOptions {\n dbPath?: string;\n useInMemoryDb?: boolean;\n}\n\nfunction loadSynthesisConfig(): SynthesisConfig {\n const configPath = join(homedir(), \".membank\", \"config.json\");\n try {\n const raw = readFileSync(configPath, \"utf8\");\n const parsed = JSON.parse(raw) as {\n synthesis?: Partial<SynthesisConfig>;\n };\n return {\n enabled: parsed.synthesis?.enabled === true,\n maxTokensPerRun: parsed.synthesis?.maxTokensPerRun,\n debounceMs: parsed.synthesis?.debounceMs,\n stalenessDays: parsed.synthesis?.stalenessDays,\n inFlightTimeoutMs: parsed.synthesis?.inFlightTimeoutMs,\n };\n } catch {\n return { enabled: false };\n }\n}\n\nexport function buildSynthesisTools(repo: MemoryRepository, query: QueryEngine): SynthesisTools {\n return {\n queryMemory: async (args) => {\n const projectHash =\n args.global === true ? undefined : (args.projectHash ?? (await resolveProject()).hash);\n const results = await query.query({\n query: args.query,\n projectHash,\n limit: args.limit ?? 20,\n includePinned: true,\n });\n return JSON.stringify(results);\n },\n getMemorySummary: async () => JSON.stringify(repo.stats()),\n };\n}\n\nexport function initCore(options: ServerOptions = {}): CoreServices {\n const db = options.useInMemoryDb\n ? DatabaseManager.openInMemory()\n : DatabaseManager.open(options.dbPath);\n const embedding = new EmbeddingService();\n const projects = new ProjectRepository(db);\n const repo = new MemoryRepository(db, embedding, projects);\n const query = new QueryEngine(db, embedding, repo);\n\n const synthConfig = loadSynthesisConfig();\n let synthEngine: SynthesisEngine | undefined;\n\n if (synthConfig.enabled) {\n const synthRepo = new SynthesisRepository(db);\n const agentLoop = new SynthesisAgentLoop(buildSynthesisTools(repo, query), synthConfig);\n synthEngine = new SynthesisEngine(db, synthRepo, synthConfig, agentLoop);\n }\n\n return { db, embedding, repo, query, projects, synthEngine };\n}\n\nfunction parseArgs<T>(schema: { parse: (v: unknown) => T }, raw: unknown): T {\n try {\n return schema.parse(raw);\n } catch (e) {\n const msg = e instanceof ZodError ? (e.issues[0]?.message ?? e.message) : String(e);\n throw new McpError(ErrorCode.InvalidParams, msg);\n }\n}\n\nexport function createServer(core: CoreServices): Server {\n const server = new Server(\n { name: SERVER_NAME, version: SERVER_VERSION },\n { capabilities: { tools: {} } }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, () => ({\n tools: [\n {\n name: \"list_memory_types\",\n description: \"Returns the ordered list of memory type values supported by membank.\",\n inputSchema: { type: \"object\", properties: {}, required: [] },\n },\n {\n name: \"save_memory\",\n description:\n \"Save a new memory. Handles deduplication automatically — near-identical memories (cosine similarity >0.92, same type and project) overwrite the existing record.\",\n inputSchema: {\n type: \"object\",\n properties: {\n content: { type: \"string\", description: \"Memory content to save\" },\n type: {\n type: \"string\",\n enum: [...MEMORY_TYPE_VALUES],\n description: \"Memory type\",\n },\n tags: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Optional tags\",\n },\n global: {\n type: \"boolean\",\n description: \"Save as a global memory, not tied to any project\",\n },\n },\n required: [\"content\", \"type\"],\n },\n },\n {\n name: \"update_memory\",\n description:\n \"Update the content, type, and/or tags of an existing memory by id. All fields except id are optional.\",\n inputSchema: {\n type: \"object\",\n properties: {\n id: { type: \"string\", description: \"Memory id to update\" },\n content: { type: \"string\", description: \"New content for the memory\" },\n type: {\n type: \"string\",\n enum: [...MEMORY_TYPE_VALUES],\n description: \"New type for the memory (reclassification)\",\n },\n tags: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Replacement tags (optional)\",\n },\n },\n required: [\"id\"],\n },\n },\n {\n name: \"delete_memory\",\n description: \"Delete a memory by id.\",\n inputSchema: {\n type: \"object\",\n properties: {\n id: { type: \"string\", description: \"Memory id to delete\" },\n },\n required: [\"id\"],\n },\n },\n {\n name: \"query_memory\",\n description:\n \"Search memories by semantic similarity. Returns results ranked by confidence score.\",\n inputSchema: {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"Search text\" },\n type: {\n type: \"string\",\n enum: [...MEMORY_TYPE_VALUES],\n description: \"Filter by memory type\",\n },\n limit: { type: \"number\", description: \"Maximum results to return (default 10)\" },\n includePinned: {\n type: \"boolean\",\n description:\n \"Include pinned memories in results. Pinned memories are already injected into session context, so excluded by default to avoid duplicates.\",\n },\n global: {\n type: \"boolean\",\n description:\n \"Query global memories only. When omitted or false, queries the current project scope.\",\n },\n },\n required: [\"query\"],\n },\n },\n {\n name: \"migrate\",\n description:\n 'List or run named data migrations. Use mode \"list\" to see available migrations; mode \"run\" with a migration name to execute one.',\n inputSchema: {\n type: \"object\",\n properties: {\n mode: {\n type: \"string\",\n enum: [\"list\", \"run\"],\n description: 'Mode: \"list\" to see available migrations, \"run\" to execute one',\n },\n name: {\n type: \"string\",\n description: 'Migration name (required when mode is \"run\")',\n },\n },\n required: [\"mode\"],\n },\n },\n {\n name: \"pin_memory\",\n description:\n \"Pin a memory by id. Pinned memories are always injected into the session context.\",\n inputSchema: {\n type: \"object\",\n properties: {\n id: { type: \"string\", description: \"Memory id to pin\" },\n },\n required: [\"id\"],\n },\n },\n {\n name: \"unpin_memory\",\n description: \"Unpin a memory by id. Removes the memory from guaranteed session injection.\",\n inputSchema: {\n type: \"object\",\n properties: {\n id: { type: \"string\", description: \"Memory id to unpin\" },\n },\n required: [\"id\"],\n },\n },\n {\n name: \"get_memory_summary\",\n description:\n \"Returns aggregate stats for session orientation: total memories, counts by type, pinned count, and review queue size.\",\n inputSchema: { type: \"object\", properties: {}, required: [] },\n },\n {\n name: \"list_flagged_memories\",\n description:\n \"List memories that have unresolved dedup review events. These were flagged automatically when a near-duplicate was saved (cosine similarity 0.75–0.92).\",\n inputSchema: { type: \"object\", properties: {}, required: [] },\n },\n {\n name: \"resolve_review\",\n description:\n \"Dismiss all unresolved review events for a memory. Use after reviewing the memory and deciding it should be kept as-is.\",\n inputSchema: {\n type: \"object\",\n properties: {\n id: { type: \"string\", description: \"Memory id to resolve review events for\" },\n },\n required: [\"id\"],\n },\n },\n ],\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n if (request.params.name === \"list_memory_types\") {\n try {\n return {\n content: [{ type: \"text\", text: JSON.stringify(listMemoryTypes()) }],\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: \"text\", text: message }], isError: true };\n }\n }\n\n if (request.params.name === \"save_memory\") {\n const args = parseArgs(SaveMemoryArgsSchema, request.params.arguments);\n const projectScope = args.global === true ? undefined : await resolveProject();\n\n try {\n const memory = await core.repo.save({\n content: args.content,\n type: args.type,\n tags: args.tags,\n projectScope,\n });\n\n if (core.synthEngine !== undefined) {\n const scope =\n memory.projects.length > 0 ? (memory.projects[0]?.scopeHash ?? \"global\") : \"global\";\n core.synthEngine.markDirty(scope);\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(memory) }],\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: \"text\", text: message }], isError: true };\n }\n }\n\n if (request.params.name === \"update_memory\") {\n const args = parseArgs(UpdateMemoryArgsSchema, request.params.arguments);\n\n try {\n const memory = await core.repo.update(args.id, {\n content: args.content,\n type: args.type,\n tags: args.tags,\n });\n\n if (core.synthEngine !== undefined) {\n const scope =\n memory.projects.length > 0 ? (memory.projects[0]?.scopeHash ?? \"global\") : \"global\";\n core.synthEngine.markDirty(scope);\n }\n\n return { content: [{ type: \"text\", text: JSON.stringify(memory) }] };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: \"text\", text: message }], isError: true };\n }\n }\n\n if (request.params.name === \"delete_memory\") {\n const args = parseArgs(DeleteMemoryArgsSchema, request.params.arguments);\n\n try {\n const exists =\n core.db.db\n .prepare<[string], { id: string }>(`SELECT id FROM memories WHERE id = ?`)\n .get(args.id) !== undefined;\n\n if (!exists) {\n return {\n content: [{ type: \"text\", text: `Memory not found: ${args.id}` }],\n isError: true,\n };\n }\n\n let memoryScopeBeforeDelete: string | undefined;\n if (core.synthEngine !== undefined) {\n const projectRow = core.db.db\n .prepare<[string], { scope_hash: string }>(\n `SELECT p.scope_hash FROM projects p\n JOIN memory_projects mp ON mp.project_id = p.id\n WHERE mp.memory_id = ?`\n )\n .get(args.id);\n memoryScopeBeforeDelete = projectRow?.scope_hash ?? \"global\";\n }\n\n await core.repo.delete(args.id);\n\n if (core.synthEngine !== undefined && memoryScopeBeforeDelete !== undefined) {\n core.synthEngine.markDirty(memoryScopeBeforeDelete);\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify({ success: true, id: args.id }) }],\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: \"text\", text: message }], isError: true };\n }\n }\n\n if (request.params.name === \"query_memory\") {\n const args = parseArgs(QueryMemoryArgsSchema, request.params.arguments);\n const projectHash = args.global === true ? undefined : (await resolveProject()).hash;\n\n try {\n const results = await core.query.query({\n query: args.query,\n type: args.type,\n projectHash,\n limit: args.limit ?? 10,\n includePinned: args.includePinned,\n });\n\n const serialised = results.map((r) => ({\n id: r.id,\n content: r.content,\n type: r.type,\n tags: r.tags,\n projects: r.projects,\n pinned: r.pinned,\n reviewEvents: r.reviewEvents,\n createdAt: r.createdAt,\n updatedAt: r.updatedAt,\n sourceHarness: r.sourceHarness,\n score: r.score,\n }));\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(serialised) }],\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: \"text\", text: message }], isError: true };\n }\n }\n\n if (request.params.name === \"migrate\") {\n const args = parseArgs(MigrateArgsSchema, request.params.arguments);\n\n if (args.mode === \"list\") {\n return {\n content: [{ type: \"text\", text: JSON.stringify(MIGRATIONS) }],\n };\n }\n\n if (args.name === \"scope-to-projects\") {\n try {\n const result = await runScopeToProjectsMigration(core.projects);\n if (result === null) {\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify({ error: \"No project found for current directory.\" }),\n },\n ],\n isError: true,\n };\n }\n return {\n content: [{ type: \"text\", text: JSON.stringify(result) }],\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: \"text\", text: message }], isError: true };\n }\n }\n\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown migration: \"${args.name}\". Available: ${MIGRATIONS.map((m) => m.name).join(\", \")}`\n );\n }\n\n if (request.params.name === \"pin_memory\" || request.params.name === \"unpin_memory\") {\n const args = parseArgs(PinMemoryArgsSchema, request.params.arguments);\n const pinned = request.params.name === \"pin_memory\";\n\n try {\n const memory = core.repo.setPin(args.id, pinned);\n\n if (pinned) {\n const charCount = core.repo.getPinnedCharCount();\n if (charCount > PIN_BUDGET_THRESHOLD && !isSynthesisEnabled()) {\n const result = {\n ...memory,\n pinBudgetWarning: `Pinned memories now use ${charCount} characters (threshold: ${PIN_BUDGET_THRESHOLD}). Consider unpinning older memories or enabling synthesis to compress them.`,\n };\n return { content: [{ type: \"text\", text: JSON.stringify(result) }] };\n }\n }\n\n return { content: [{ type: \"text\", text: JSON.stringify(memory) }] };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: \"text\", text: message }], isError: true };\n }\n }\n\n if (request.params.name === \"get_memory_summary\") {\n try {\n return { content: [{ type: \"text\", text: JSON.stringify(core.repo.stats()) }] };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: \"text\", text: message }], isError: true };\n }\n }\n\n if (request.params.name === \"list_flagged_memories\") {\n try {\n const memories = core.repo.listFlagged();\n return { content: [{ type: \"text\", text: JSON.stringify(memories) }] };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: \"text\", text: message }], isError: true };\n }\n }\n\n if (request.params.name === \"resolve_review\") {\n const args = parseArgs(ResolveReviewArgsSchema, request.params.arguments);\n\n try {\n const exists =\n core.db.db\n .prepare<[string], { id: string }>(`SELECT id FROM memories WHERE id = ?`)\n .get(args.id) !== undefined;\n\n if (!exists) {\n return {\n content: [{ type: \"text\", text: `Memory not found: ${args.id}` }],\n isError: true,\n };\n }\n\n core.repo.resolveReviewEvents(args.id);\n return {\n content: [{ type: \"text\", text: JSON.stringify({ success: true, id: args.id }) }],\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: \"text\", text: message }], isError: true };\n }\n }\n\n throw new Error(`Unknown tool: ${request.params.name}`);\n });\n\n return server;\n}\n","import {\n DatabaseManager,\n EmbeddingService,\n isSynthesisEnabled,\n MemoryRepository,\n ProjectRepository,\n QueryEngine,\n SynthesisRepository,\n} from \"@membank/core\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport type { CoreServices } from \"./server.js\";\nimport { buildSynthesisTools, createServer, initCore } from \"./server.js\";\nimport { SynthesisAgentLoop } from \"./synthesis/index.js\";\n\nexport async function startServer(): Promise<void> {\n let core: CoreServices;\n try {\n core = initCore();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`membank: failed to initialise core: ${message}\\n`);\n process.exit(1);\n }\n\n if (core.synthEngine !== undefined) {\n try {\n await core.synthEngine.init();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`membank: synthesis engine init failed: ${message}\\n`);\n }\n }\n\n const shutdown = async (): Promise<void> => {\n if (core.synthEngine !== undefined) {\n await core.synthEngine.shutdown();\n }\n process.exit(0);\n };\n\n process.on(\"SIGTERM\", () => {\n void shutdown();\n });\n process.on(\"SIGINT\", () => {\n void shutdown();\n });\n\n const server = createServer(core);\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nexport async function runSynthesis(scope: string): Promise<string> {\n if (!isSynthesisEnabled()) {\n throw new Error(\"Synthesis is not enabled. Run: membank config set synthesis.enabled true\");\n }\n\n const db = DatabaseManager.open();\n const embedding = new EmbeddingService();\n const projects = new ProjectRepository(db);\n const repo = new MemoryRepository(db, embedding, projects);\n const queryEngine = new QueryEngine(db, embedding, repo);\n const synthRepo = new SynthesisRepository(db);\n const agentLoop = new SynthesisAgentLoop(buildSynthesisTools(repo, queryEngine), {\n enabled: true,\n });\n\n let resolvedScope = scope;\n if (scope !== \"global\" && !/^[0-9a-f]{16}$/.test(scope)) {\n const project = projects.getByName(scope);\n if (project !== undefined) {\n resolvedScope = project.scopeHash;\n }\n }\n\n const projectHash = resolvedScope === \"global\" ? undefined : resolvedScope;\n\n synthRepo.markInFlight(resolvedScope);\n try {\n const [content, sourceHash] = await Promise.all([\n agentLoop.run(resolvedScope, projectHash),\n Promise.resolve(synthRepo.computeSourceMemoryHash(resolvedScope)),\n ]);\n synthRepo.saveSynthesis(resolvedScope, content, sourceHash);\n return content;\n } catch (err) {\n synthRepo.clearInFlight(resolvedScope);\n throw err;\n } finally {\n db.close();\n }\n}\n"],"mappings":"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"}
|