@seanhogg/builderforce-memory-mcp 2026.6.19 → 2026.6.27

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 CHANGED
@@ -75,6 +75,26 @@ const backend = await createLocalMemoryStoreBackend({ runtime: ssmMemoryService.
75
75
 
76
76
  Recall quality then improves automatically as that model is adapted/distilled.
77
77
 
78
+ ## One-shot install into any MCP host (not just Claude)
79
+
80
+ ```bash
81
+ # Auto-detect installed agents and register the memory server into each:
82
+ npx -y -p @seanhogg/builderforce-memory-mcp builderforce-memory-install
83
+
84
+ npx -y -p @seanhogg/builderforce-memory-mcp builderforce-memory-install --host=all
85
+ npx -y -p @seanhogg/builderforce-memory-mcp builderforce-memory-install --host=cursor,windsurf
86
+ ```
87
+
88
+ Writes the stdio server entry into each host's own config, idempotently and with
89
+ `.bak` backups. Supported hosts: **Claude Code, Claude Desktop, Cursor, Windsurf,
90
+ VS Code, Cline, Gemini CLI, Codex CLI**. Every agent points at one shared store
91
+ (`~/.builderforce-memory/memory.json` by default, override with `--memory-file`),
92
+ so a fact remembered in one agent is recalled in another. `--readonly` drops the
93
+ write tools; `--local=<dist/bin/stdio.js>` runs a checkout for development.
94
+
95
+ The same launch spec is exported for programmatic use:
96
+ `buildServerSpec()` / `installMemoryServer()` (see `src/install/`).
97
+
78
98
  ## Quick start — stdio (any process / language)
79
99
 
80
100
  ```bash
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * builderforce-memory-install — wire the memory MCP server into any MCP-capable
4
+ * agent (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, Cline, Gemini
5
+ * CLI, Codex CLI), not just Claude Code.
6
+ *
7
+ * npx -y @seanhogg/builderforce-memory-mcp # via the package's other bin
8
+ * builderforce-memory-install # auto-detect installed hosts
9
+ * builderforce-memory-install --host=cursor,windsurf # explicit hosts
10
+ * builderforce-memory-install --host=all # every known host
11
+ * builderforce-memory-install --memory-file=/path/mem.json # shared store path
12
+ * builderforce-memory-install --readonly # recall-only
13
+ * builderforce-memory-install --local=/abs/dist/bin/stdio.js # dev: local bin
14
+ *
15
+ * The store defaults to ~/.builderforce-memory/memory.json so every agent on
16
+ * the machine shares one memory. Re-running is safe (idempotent + .bak backups).
17
+ */
18
+ export {};
19
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/bin/install.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * builderforce-memory-install — wire the memory MCP server into any MCP-capable
4
+ * agent (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, Cline, Gemini
5
+ * CLI, Codex CLI), not just Claude Code.
6
+ *
7
+ * npx -y @seanhogg/builderforce-memory-mcp # via the package's other bin
8
+ * builderforce-memory-install # auto-detect installed hosts
9
+ * builderforce-memory-install --host=cursor,windsurf # explicit hosts
10
+ * builderforce-memory-install --host=all # every known host
11
+ * builderforce-memory-install --memory-file=/path/mem.json # shared store path
12
+ * builderforce-memory-install --readonly # recall-only
13
+ * builderforce-memory-install --local=/abs/dist/bin/stdio.js # dev: local bin
14
+ *
15
+ * The store defaults to ~/.builderforce-memory/memory.json so every agent on
16
+ * the machine shares one memory. Re-running is safe (idempotent + .bak backups).
17
+ */
18
+ import os from "node:os";
19
+ import path from "node:path";
20
+ import { HOSTS } from "../install/hosts.js";
21
+ import { installMemoryServer } from "../install/install.js";
22
+ function argValue(name) {
23
+ const hit = process.argv.find((a) => a.startsWith(`--${name}=`));
24
+ return hit ? hit.slice(name.length + 3) : undefined;
25
+ }
26
+ const hasFlag = (name) => process.argv.includes(`--${name}`);
27
+ if (hasFlag("help") || hasFlag("h")) {
28
+ process.stdout.write([
29
+ "builderforce-memory-install — register the memory MCP server into MCP hosts.",
30
+ "",
31
+ " --host=<a,b|all> hosts to target (default: auto-detect installed)",
32
+ " --memory-file=<path> JSON snapshot path (default: ~/.builderforce-memory/memory.json)",
33
+ " --readonly recall-only (no remember/forget)",
34
+ " --local=<path> run a locally-built stdio bin via node (dev)",
35
+ "",
36
+ `Known hosts: ${HOSTS.map((h) => h.id).join(", ")}`,
37
+ "",
38
+ ].join("\n"));
39
+ process.exit(0);
40
+ }
41
+ const hostArg = argValue("host");
42
+ const hosts = hostArg === undefined || hostArg === "auto"
43
+ ? "auto"
44
+ : hostArg === "all"
45
+ ? "all"
46
+ : hostArg.split(",").map((s) => s.trim()).filter(Boolean);
47
+ const memoryFile = argValue("memory-file") ?? path.join(os.homedir(), ".builderforce-memory", "memory.json");
48
+ let results;
49
+ try {
50
+ results = installMemoryServer({
51
+ hosts,
52
+ memoryFile,
53
+ readonly: hasFlag("readonly"),
54
+ localBin: argValue("local"),
55
+ });
56
+ }
57
+ catch (err) {
58
+ process.stderr.write(`✗ ${String(err.message ?? err)}\n`);
59
+ process.exit(1);
60
+ }
61
+ const icon = {
62
+ installed: "✓",
63
+ updated: "✓",
64
+ skipped: "•",
65
+ unsupported: "–",
66
+ error: "✗",
67
+ };
68
+ let wrote = 0;
69
+ for (const r of results) {
70
+ const where = r.path ? ` ${r.path}` : "";
71
+ const why = r.detail ? ` (${r.detail})` : "";
72
+ process.stdout.write(`${icon[r.status] ?? "?"} ${r.label}: ${r.status}${why}${where}\n`);
73
+ if (r.status === "installed" || r.status === "updated")
74
+ wrote += 1;
75
+ }
76
+ if (results.length === 0) {
77
+ process.stdout.write("No MCP hosts detected. Pass --host=all to write configs anyway, or --host=cursor (etc.).\n");
78
+ }
79
+ else {
80
+ process.stdout.write(`\nDone — ${wrote} config(s) written. Store: ${memoryFile}\nRestart the agent(s) to connect. Tools: memory_recall · memory_remember · memory_get · memory_recall_by_tag · memory_forget\n`);
81
+ }
82
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/bin/install.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAqB,MAAM,uBAAuB,CAAC;AAE/E,SAAS,QAAQ,CAAC,IAAY;IAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC;IACjE,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AACD,MAAM,OAAO,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AAE9E,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;IAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB;QACI,8EAA8E;QAC9E,EAAE;QACF,yEAAyE;QACzE,yFAAyF;QACzF,yDAAyD;QACzD,qEAAqE;QACrE,EAAE;QACF,gBAAgB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACnD,EAAE;KACL,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;AACjC,MAAM,KAAK,GACP,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,MAAM;IACvC,CAAC,CAAC,MAAM;IACR,CAAC,CAAC,OAAO,KAAK,KAAK;QACjB,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAEpE,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,sBAAsB,EAAE,aAAa,CAAC,CAAC;AAE7G,IAAI,OAAO,CAAC;AACZ,IAAI,CAAC;IACD,OAAO,GAAG,mBAAmB,CAAC;QAC1B,KAAK;QACL,UAAU;QACV,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC;QAC7B,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC;KAC9B,CAAC,CAAC;AACP,CAAC;AAAC,OAAO,GAAG,EAAE,CAAC;IACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,MAAM,CAAE,GAAa,CAAC,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,IAAI,GAA2B;IACjC,SAAS,EAAE,GAAG;IACd,OAAO,EAAE,GAAG;IACZ,OAAO,EAAE,GAAG;IACZ,WAAW,EAAE,GAAG;IAChB,KAAK,EAAE,GAAG;CACb,CAAC;AAEF,IAAI,KAAK,GAAG,CAAC,CAAC;AACd,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;IACtB,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1C,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC;IACzF,IAAI,CAAC,CAAC,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS;QAAE,KAAK,IAAI,CAAC,CAAC;AACvE,CAAC;AAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;IACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,4FAA4F,CAC/F,CAAC;AACN,CAAC;KAAM,CAAC;IACJ,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,YAAY,KAAK,8BAA8B,UAAU,iIAAiI,CAC7L,CAAC;AACN,CAAC"}
package/dist/index.d.ts CHANGED
@@ -18,4 +18,10 @@ export type { McpServerOptions } from "./transports/mcp-server.js";
18
18
  export { runStdio } from "./transports/stdio.js";
19
19
  export { createMemoryHttpHandler } from "./transports/http.js";
20
20
  export type { HttpHandlerOptions } from "./transports/http.js";
21
+ export { buildServerSpec, MCP_PACKAGE, MCP_BIN, RUNTIME_PEERS } from "./install/server-spec.js";
22
+ export type { StdioServerSpec, ServerSpecOptions } from "./install/server-spec.js";
23
+ export { installMemoryServer } from "./install/install.js";
24
+ export type { InstallOptions, InstallResult, InstallStatus, HostSelector, FsLike } from "./install/install.js";
25
+ export { HOSTS, SERVER_KEY, findHost } from "./install/hosts.js";
26
+ export type { HostAdapter, HostEnv, ConfigFormat } from "./install/hosts.js";
21
27
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG5E,OAAO,EAAE,kBAAkB,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAC/F,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEhF,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG5E,OAAO,EAAE,kBAAkB,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAC/F,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEhF,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAChG,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC/G,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACjE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -15,4 +15,8 @@ export { createMemoryMcpServer } from "./transports/sdk.js";
15
15
  export { buildMcpServer } from "./transports/mcp-server.js";
16
16
  export { runStdio } from "./transports/stdio.js";
17
17
  export { createMemoryHttpHandler } from "./transports/http.js";
18
+ // ── Multi-host installer (wire the stdio server into any MCP-capable agent) ───
19
+ export { buildServerSpec, MCP_PACKAGE, MCP_BIN, RUNTIME_PEERS } from "./install/server-spec.js";
20
+ export { installMemoryServer } from "./install/install.js";
21
+ export { HOSTS, SERVER_KEY, findHost } from "./install/hosts.js";
18
22
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,0FAA0F;AAC1F,OAAO,EAAE,kBAAkB,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAG/F,iFAAiF;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9C,+EAA+E;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG5D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,0FAA0F;AAC1F,OAAO,EAAE,kBAAkB,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAG/F,iFAAiF;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9C,+EAA+E;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG5D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,iFAAiF;AACjF,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEhG,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Host registry — every MCP-capable agent we know how to wire memory into.
3
+ *
4
+ * The differences between hosts collapse to three things: WHERE the config
5
+ * file lives, which top-level key holds the server map (`mcpServers` vs VS
6
+ * Code's `servers`), and whether the entry carries an explicit `type:"stdio"`.
7
+ * Codex CLI is the one odd-one-out (TOML, not JSON). Everything else is one
8
+ * shared JSON merge — see install.ts.
9
+ */
10
+ export type ConfigFormat = "json-mcpServers" | "json-servers" | "toml";
11
+ export interface HostEnv {
12
+ homedir: string;
13
+ platform: NodeJS.Platform;
14
+ env: Record<string, string | undefined>;
15
+ }
16
+ export interface HostAdapter {
17
+ id: string;
18
+ label: string;
19
+ format: ConfigFormat;
20
+ /** Include `type:"stdio"` in the written entry (Claude Code + VS Code want it). */
21
+ includeType: boolean;
22
+ /** Absolute config-file path for this host, or null if N/A on this OS. */
23
+ configPath(e: HostEnv): string | null;
24
+ }
25
+ /** The server key written into every host's config. */
26
+ export declare const SERVER_KEY = "builderforce-memory";
27
+ export declare const HOSTS: HostAdapter[];
28
+ export declare function findHost(id: string): HostAdapter | undefined;
29
+ //# sourceMappingURL=hosts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hosts.d.ts","sourceRoot":"","sources":["../../src/install/hosts.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,cAAc,GAAG,MAAM,CAAC;AAEvE,MAAM,WAAW,OAAO;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,mFAAmF;IACnF,WAAW,EAAE,OAAO,CAAC;IACrB,0EAA0E;IAC1E,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC;CACzC;AAED,uDAAuD;AACvD,eAAO,MAAM,UAAU,wBAAwB,CAAC;AAqBhD,eAAO,MAAM,KAAK,EAAE,WAAW,EAoE9B,CAAC;AAEF,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAE5D"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Host registry — every MCP-capable agent we know how to wire memory into.
3
+ *
4
+ * The differences between hosts collapse to three things: WHERE the config
5
+ * file lives, which top-level key holds the server map (`mcpServers` vs VS
6
+ * Code's `servers`), and whether the entry carries an explicit `type:"stdio"`.
7
+ * Codex CLI is the one odd-one-out (TOML, not JSON). Everything else is one
8
+ * shared JSON merge — see install.ts.
9
+ */
10
+ import path from "node:path";
11
+ /** The server key written into every host's config. */
12
+ export const SERVER_KEY = "builderforce-memory";
13
+ /** Per-OS application-support directory for a desktop vendor (Claude, Code). */
14
+ function appSupport(e, vendor) {
15
+ if (e.platform === "win32") {
16
+ const roaming = e.env["APPDATA"] ?? path.join(e.homedir, "AppData", "Roaming");
17
+ return path.join(roaming, vendor);
18
+ }
19
+ if (e.platform === "darwin") {
20
+ return path.join(e.homedir, "Library", "Application Support", vendor);
21
+ }
22
+ const xdg = e.env["XDG_CONFIG_HOME"] ?? path.join(e.homedir, ".config");
23
+ return path.join(xdg, vendor);
24
+ }
25
+ /** VS Code stores its per-user files under `<appSupport(Code)>/User`. */
26
+ function vscodeUserDir(e) {
27
+ const base = appSupport(e, "Code");
28
+ return base ? path.join(base, "User") : null;
29
+ }
30
+ export const HOSTS = [
31
+ {
32
+ id: "claude-code",
33
+ label: "Claude Code",
34
+ format: "json-mcpServers",
35
+ includeType: true,
36
+ configPath: (e) => path.join(e.homedir, ".claude.json"),
37
+ },
38
+ {
39
+ id: "claude-desktop",
40
+ label: "Claude Desktop",
41
+ format: "json-mcpServers",
42
+ includeType: false,
43
+ configPath: (e) => {
44
+ const dir = appSupport(e, "Claude");
45
+ return dir ? path.join(dir, "claude_desktop_config.json") : null;
46
+ },
47
+ },
48
+ {
49
+ id: "cursor",
50
+ label: "Cursor",
51
+ format: "json-mcpServers",
52
+ includeType: false,
53
+ configPath: (e) => path.join(e.homedir, ".cursor", "mcp.json"),
54
+ },
55
+ {
56
+ id: "windsurf",
57
+ label: "Windsurf",
58
+ format: "json-mcpServers",
59
+ includeType: false,
60
+ configPath: (e) => path.join(e.homedir, ".codeium", "windsurf", "mcp_config.json"),
61
+ },
62
+ {
63
+ id: "vscode",
64
+ label: "VS Code",
65
+ format: "json-servers",
66
+ includeType: true,
67
+ configPath: (e) => {
68
+ const dir = vscodeUserDir(e);
69
+ return dir ? path.join(dir, "mcp.json") : null;
70
+ },
71
+ },
72
+ {
73
+ id: "cline",
74
+ label: "Cline (VS Code)",
75
+ format: "json-mcpServers",
76
+ includeType: false,
77
+ configPath: (e) => {
78
+ const dir = vscodeUserDir(e);
79
+ return dir
80
+ ? path.join(dir, "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
81
+ : null;
82
+ },
83
+ },
84
+ {
85
+ id: "gemini",
86
+ label: "Gemini CLI",
87
+ format: "json-mcpServers",
88
+ includeType: false,
89
+ configPath: (e) => path.join(e.homedir, ".gemini", "settings.json"),
90
+ },
91
+ {
92
+ id: "codex",
93
+ label: "Codex CLI",
94
+ format: "toml",
95
+ includeType: false,
96
+ configPath: (e) => path.join(e.homedir, ".codex", "config.toml"),
97
+ },
98
+ ];
99
+ export function findHost(id) {
100
+ return HOSTS.find((h) => h.id === id);
101
+ }
102
+ //# sourceMappingURL=hosts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hosts.js","sourceRoot":"","sources":["../../src/install/hosts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Host-agnostic installer: register the builderforce-memory MCP server into any
3
+ * combination of MCP-capable agents. One shared launch spec (server-spec.ts),
4
+ * one entry per host config, idempotent and backup-safe.
5
+ *
6
+ * Pure-ish: all filesystem + environment access goes through injectable seams
7
+ * so the merge logic is unit-testable without touching a real machine.
8
+ */
9
+ import { type HostEnv } from "./hosts.js";
10
+ import { type ServerSpecOptions } from "./server-spec.js";
11
+ export interface FsLike {
12
+ existsSync(p: string): boolean;
13
+ readFileSync(p: string, enc: "utf8"): string;
14
+ writeFileSync(p: string, data: string): void;
15
+ mkdirSync(p: string, opts: {
16
+ recursive: boolean;
17
+ }): void;
18
+ copyFileSync(src: string, dest: string): void;
19
+ }
20
+ export type HostSelector = string[] | "auto" | "all";
21
+ export interface InstallOptions extends ServerSpecOptions {
22
+ /**
23
+ * Which hosts to target:
24
+ * - "auto" (default) — only hosts whose config file or parent dir exists.
25
+ * - "all" — every known host (creates configs as needed).
26
+ * - string[] — explicit host ids (see HOSTS).
27
+ */
28
+ hosts?: HostSelector;
29
+ fs?: FsLike;
30
+ hostEnv?: HostEnv;
31
+ }
32
+ export type InstallStatus = "installed" | "updated" | "skipped" | "unsupported" | "error";
33
+ export interface InstallResult {
34
+ host: string;
35
+ label: string;
36
+ path: string | null;
37
+ status: InstallStatus;
38
+ detail?: string;
39
+ }
40
+ /**
41
+ * Register (or refresh) the memory MCP server across the selected hosts.
42
+ * Never throws for a single host — per-host failures are captured as
43
+ * `status:"error"` so one bad config can't abort the rest.
44
+ */
45
+ export declare function installMemoryServer(opts?: InstallOptions): InstallResult[];
46
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/install/install.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EAAuC,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAmB,KAAK,iBAAiB,EAAwB,MAAM,kBAAkB,CAAC;AAEjG,MAAM,WAAW,MAAM;IACnB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7C,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACzD,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACjD;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,KAAK,CAAC;AAErD,MAAM,WAAW,cAAe,SAAQ,iBAAiB;IACrD;;;;;OAKG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,CAAC;AAE1F,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAsGD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,cAAmB,GAAG,aAAa,EAAE,CAwB9E"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Host-agnostic installer: register the builderforce-memory MCP server into any
3
+ * combination of MCP-capable agents. One shared launch spec (server-spec.ts),
4
+ * one entry per host config, idempotent and backup-safe.
5
+ *
6
+ * Pure-ish: all filesystem + environment access goes through injectable seams
7
+ * so the merge logic is unit-testable without touching a real machine.
8
+ */
9
+ import nodeFs from "node:fs";
10
+ import nodeOs from "node:os";
11
+ import nodePath from "node:path";
12
+ import { HOSTS, SERVER_KEY } from "./hosts.js";
13
+ import { buildServerSpec } from "./server-spec.js";
14
+ function defaultHostEnv() {
15
+ return { homedir: nodeOs.homedir(), platform: process.platform, env: process.env };
16
+ }
17
+ function readJson(fs, file) {
18
+ if (!fs.existsSync(file))
19
+ return {};
20
+ try {
21
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
22
+ return parsed && typeof parsed === "object" ? parsed : {};
23
+ }
24
+ catch {
25
+ // Corrupt/partial config — surface as error rather than clobbering it.
26
+ throw new Error("existing config is not valid JSON");
27
+ }
28
+ }
29
+ function entryFor(spec, includeType) {
30
+ return {
31
+ ...(includeType ? { type: "stdio" } : {}),
32
+ command: spec.command,
33
+ args: spec.args,
34
+ ...(spec.env ? { env: spec.env } : {}),
35
+ };
36
+ }
37
+ function ensureParent(fs, file) {
38
+ const dir = nodePath.dirname(file);
39
+ if (dir && !fs.existsSync(dir))
40
+ fs.mkdirSync(dir, { recursive: true });
41
+ }
42
+ /** A host is "present" if its config file or its parent directory already exists. */
43
+ function isPresent(fs, file) {
44
+ return fs.existsSync(file) || fs.existsSync(nodePath.dirname(file));
45
+ }
46
+ function installJson(fs, host, file, spec) {
47
+ const mapKey = host.format === "json-servers" ? "servers" : "mcpServers";
48
+ const config = readJson(fs, file);
49
+ const map = (config[mapKey] && typeof config[mapKey] === "object" ? config[mapKey] : {});
50
+ const next = entryFor(spec, host.includeType);
51
+ const prev = map[SERVER_KEY];
52
+ const existed = prev !== undefined;
53
+ if (existed && JSON.stringify(prev) === JSON.stringify(next))
54
+ return "skipped";
55
+ if (fs.existsSync(file))
56
+ fs.copyFileSync(file, `${file}.bak`);
57
+ map[SERVER_KEY] = next;
58
+ config[mapKey] = map;
59
+ ensureParent(fs, file);
60
+ fs.writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
61
+ return existed ? "updated" : "installed";
62
+ }
63
+ /** TOML emit is append-only and idempotent: skip if the table already exists. */
64
+ function installToml(fs, file, spec) {
65
+ const header = `[mcp_servers.${SERVER_KEY}]`;
66
+ const existing = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
67
+ if (existing.includes(header))
68
+ return "skipped";
69
+ const q = (s) => JSON.stringify(s); // TOML basic strings are JSON-compatible here
70
+ const lines = [
71
+ header,
72
+ `command = ${q(spec.command)}`,
73
+ `args = [${spec.args.map(q).join(", ")}]`,
74
+ ];
75
+ if (spec.env) {
76
+ lines.push(`[mcp_servers.${SERVER_KEY}.env]`);
77
+ for (const [k, v] of Object.entries(spec.env))
78
+ lines.push(`${k} = ${q(v)}`);
79
+ }
80
+ const block = `${lines.join("\n")}\n`;
81
+ if (fs.existsSync(file))
82
+ fs.copyFileSync(file, `${file}.bak`);
83
+ ensureParent(fs, file);
84
+ const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
85
+ fs.writeFileSync(file, `${existing}${sep}${block}`);
86
+ return "installed";
87
+ }
88
+ function resolveHosts(selector, fs, hostEnv) {
89
+ if (Array.isArray(selector)) {
90
+ return selector.map((id) => {
91
+ const h = HOSTS.find((x) => x.id === id);
92
+ if (!h)
93
+ throw new Error(`unknown host "${id}" (known: ${HOSTS.map((x) => x.id).join(", ")})`);
94
+ return h;
95
+ });
96
+ }
97
+ if (selector === "all")
98
+ return HOSTS;
99
+ // "auto": only hosts that look installed on this machine.
100
+ return HOSTS.filter((h) => {
101
+ const p = h.configPath(hostEnv);
102
+ return p !== null && isPresent(fs, p);
103
+ });
104
+ }
105
+ /**
106
+ * Register (or refresh) the memory MCP server across the selected hosts.
107
+ * Never throws for a single host — per-host failures are captured as
108
+ * `status:"error"` so one bad config can't abort the rest.
109
+ */
110
+ export function installMemoryServer(opts = {}) {
111
+ const fs = opts.fs ?? nodeFs;
112
+ const hostEnv = opts.hostEnv ?? defaultHostEnv();
113
+ const selector = opts.hosts ?? "auto";
114
+ const spec = buildServerSpec({
115
+ memoryFile: opts.memoryFile,
116
+ readonly: opts.readonly,
117
+ platform: opts.platform ?? hostEnv.platform,
118
+ localBin: opts.localBin,
119
+ });
120
+ const hosts = resolveHosts(selector, fs, hostEnv);
121
+ return hosts.map((host) => {
122
+ const file = host.configPath(hostEnv);
123
+ if (file === null) {
124
+ return { host: host.id, label: host.label, path: null, status: "unsupported", detail: "not available on this OS" };
125
+ }
126
+ try {
127
+ const status = host.format === "toml" ? installToml(fs, file, spec) : installJson(fs, host, file, spec);
128
+ return { host: host.id, label: host.label, path: file, status };
129
+ }
130
+ catch (err) {
131
+ return { host: host.id, label: host.label, path: file, status: "error", detail: String(err.message ?? err) };
132
+ }
133
+ });
134
+ }
135
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/install/install.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,MAAM,MAAM,SAAS,CAAC;AAC7B,OAAO,MAAM,MAAM,SAAS,CAAC;AAC7B,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAkC,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAgD,MAAM,kBAAkB,CAAC;AAkCjG,SAAS,cAAc;IACnB,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;AACvF,CAAC;AAED,SAAS,QAAQ,CAAC,EAAU,EAAE,IAAY;IACtC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACpC,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QACzD,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3F,CAAC;IAAC,MAAM,CAAC;QACL,uEAAuE;QACvE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACzD,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,IAAqB,EAAE,WAAoB;IACzD,OAAO;QACH,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzC,CAAC;AACN,CAAC;AAED,SAAS,YAAY,CAAC,EAAU,EAAE,IAAY;IAC1C,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC3E,CAAC;AAED,qFAAqF;AACrF,SAAS,SAAS,CAAC,EAAU,EAAE,IAAY;IACvC,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,WAAW,CAChB,EAAU,EACV,IAAiB,EACjB,IAAY,EACZ,IAAqB;IAErB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC;IACzE,MAAM,MAAM,GAAG,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAGtF,CAAC;IACF,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAI,KAAK,SAAS,CAAC;IACnC,IAAI,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAE/E,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;IAC9D,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IACvB,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC;IACrB,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACvB,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC/D,OAAO,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;AAC7C,CAAC;AAED,iFAAiF;AACjF,SAAS,WAAW,CAAC,EAAU,EAAE,IAAY,EAAE,IAAqB;IAChE,MAAM,MAAM,GAAG,gBAAgB,UAAU,GAAG,CAAC;IAC7C,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IAEhD,MAAM,CAAC,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,8CAA8C;IAC1F,MAAM,KAAK,GAAG;QACV,MAAM;QACN,aAAa,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;QAC9B,WAAW,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;KAC5C,CAAC;IACF,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACX,KAAK,CAAC,IAAI,CAAC,gBAAgB,UAAU,OAAO,CAAC,CAAC;QAC9C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IAEtC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;IAC9D,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACvB,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACvG,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,QAAQ,GAAG,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC;IACpD,OAAO,WAAW,CAAC;AACvB,CAAC;AAED,SAAS,YAAY,CAAC,QAAsB,EAAE,EAAU,EAAE,OAAgB;IACtE,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;YACvB,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YACzC,IAAI,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,EAAE,aAAa,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC9F,OAAO,CAAC,CAAC;QACb,CAAC,CAAC,CAAC;IACP,CAAC;IACD,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IACrC,0DAA0D;IAC1D,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACtB,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO,CAAC,KAAK,IAAI,IAAI,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAuB,EAAE;IACzD,MAAM,EAAE,GAAW,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC;IACrC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,cAAc,EAAE,CAAC;IACjD,MAAM,QAAQ,GAAiB,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC;IACpD,MAAM,IAAI,GAAG,eAAe,CAAC;QACzB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ;QAC3C,QAAQ,EAAE,IAAI,CAAC,QAAQ;KAC1B,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAiB,EAAE;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAChB,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAC;QACvH,CAAC;QACD,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;YACxG,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QACpE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAE,GAAa,CAAC,OAAO,IAAI,GAAG,CAAC,EAAE,CAAC;QAC5H,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The canonical stdio launch spec for the builderforce-memory MCP server.
3
+ *
4
+ * Every host (Claude Code, Cursor, Windsurf, VS Code, Cline, Gemini/Codex CLI,
5
+ * Claude Desktop) registers the SAME server — only the surrounding config
6
+ * file/shape differs. This module is the single source of truth for HOW the
7
+ * server is launched, so the per-host adapters never re-derive the command.
8
+ */
9
+ export interface StdioServerSpec {
10
+ command: string;
11
+ args: string[];
12
+ env?: Record<string, string>;
13
+ }
14
+ export interface ServerSpecOptions {
15
+ /** Absolute path to the JSON snapshot that makes memory survive restarts. */
16
+ memoryFile?: string;
17
+ /** Register read-only (disables the remember/forget tools). */
18
+ readonly?: boolean;
19
+ /** Platform to target (command wrapping). Defaults to `process.platform`. */
20
+ platform?: NodeJS.Platform;
21
+ /**
22
+ * Run a locally-built stdio bin via `node <path>` instead of the published
23
+ * package via `npx` — for development against a checkout.
24
+ */
25
+ localBin?: string;
26
+ }
27
+ /** Published package + bin. The bin name matches the unscoped package name. */
28
+ export declare const MCP_PACKAGE = "@seanhogg/builderforce-memory-mcp";
29
+ export declare const MCP_BIN = "builderforce-memory-mcp";
30
+ /**
31
+ * Runtime peers the stdio bin loads on demand. They are OPTIONAL peers of the
32
+ * MCP package (so a custom-backend / HTTP-thin-client consumer needn't install
33
+ * them), which means `npx` will NOT auto-pull them — the stdio launch lists
34
+ * them explicitly. `-engine` arrives as `-memory`'s required peer; listed too
35
+ * for determinism.
36
+ */
37
+ export declare const RUNTIME_PEERS: string[];
38
+ /**
39
+ * Build the stdio launch spec. On Windows `npx` is a `.cmd` shim that an MCP
40
+ * launcher must invoke through `cmd`; `node` + a local path needs no shell.
41
+ */
42
+ export declare function buildServerSpec(opts?: ServerSpecOptions): StdioServerSpec;
43
+ //# sourceMappingURL=server-spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-spec.d.ts","sourceRoot":"","sources":["../../src/install/server-spec.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAC9B,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,+EAA+E;AAC/E,eAAO,MAAM,WAAW,sCAAsC,CAAC;AAC/D,eAAO,MAAM,OAAO,4BAA4B,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,UAIzB,CAAC;AASF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,iBAAsB,GAAG,eAAe,CAc7E"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The canonical stdio launch spec for the builderforce-memory MCP server.
3
+ *
4
+ * Every host (Claude Code, Cursor, Windsurf, VS Code, Cline, Gemini/Codex CLI,
5
+ * Claude Desktop) registers the SAME server — only the surrounding config
6
+ * file/shape differs. This module is the single source of truth for HOW the
7
+ * server is launched, so the per-host adapters never re-derive the command.
8
+ */
9
+ /** Published package + bin. The bin name matches the unscoped package name. */
10
+ export const MCP_PACKAGE = "@seanhogg/builderforce-memory-mcp";
11
+ export const MCP_BIN = "builderforce-memory-mcp";
12
+ /**
13
+ * Runtime peers the stdio bin loads on demand. They are OPTIONAL peers of the
14
+ * MCP package (so a custom-backend / HTTP-thin-client consumer needn't install
15
+ * them), which means `npx` will NOT auto-pull them — the stdio launch lists
16
+ * them explicitly. `-engine` arrives as `-memory`'s required peer; listed too
17
+ * for determinism.
18
+ */
19
+ export const RUNTIME_PEERS = [
20
+ "@seanhogg/builderforce-memory",
21
+ "@seanhogg/builderforce-memory-engine",
22
+ "fake-indexeddb",
23
+ ];
24
+ function specEnv(opts) {
25
+ const env = {};
26
+ if (opts.memoryFile)
27
+ env["BUILDERFORCE_MEMORY_FILE"] = opts.memoryFile;
28
+ if (opts.readonly)
29
+ env["BUILDERFORCE_MEMORY_READONLY"] = "1";
30
+ return Object.keys(env).length > 0 ? env : undefined;
31
+ }
32
+ /**
33
+ * Build the stdio launch spec. On Windows `npx` is a `.cmd` shim that an MCP
34
+ * launcher must invoke through `cmd`; `node` + a local path needs no shell.
35
+ */
36
+ export function buildServerSpec(opts = {}) {
37
+ const platform = opts.platform ?? process.platform;
38
+ const env = specEnv(opts);
39
+ if (opts.localBin) {
40
+ return { command: "node", args: [opts.localBin], ...(env ? { env } : {}) };
41
+ }
42
+ const npxArgs = ["-y", "-p", MCP_PACKAGE, ...RUNTIME_PEERS.flatMap((p) => ["-p", p]), MCP_BIN];
43
+ if (platform === "win32") {
44
+ return { command: "cmd", args: ["/c", "npx", ...npxArgs], ...(env ? { env } : {}) };
45
+ }
46
+ return { command: "npx", args: npxArgs, ...(env ? { env } : {}) };
47
+ }
48
+ //# sourceMappingURL=server-spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-spec.js","sourceRoot":"","sources":["../../src/install/server-spec.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAsBH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,WAAW,GAAG,mCAAmC,CAAC;AAC/D,MAAM,CAAC,MAAM,OAAO,GAAG,yBAAyB,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IACzB,+BAA+B;IAC/B,sCAAsC;IACtC,gBAAgB;CACnB,CAAC;AAEF,SAAS,OAAO,CAAC,IAAuB;IACpC,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,IAAI,IAAI,CAAC,UAAU;QAAE,GAAG,CAAC,0BAA0B,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC;IACvE,IAAI,IAAI,CAAC,QAAQ;QAAE,GAAG,CAAC,8BAA8B,CAAC,GAAG,GAAG,CAAC;IAC7D,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,OAA0B,EAAE;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;IACnD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAC/E,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAE/F,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACxF,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACtE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanhogg/builderforce-memory-mcp",
3
- "version": "2026.6.19",
3
+ "version": "2026.6.27",
4
4
  "description": "BuilderForce Agent Memory — MCP transport. Exposes @seanhogg/builderforce-memory to any MCP client (Claude Agent SDK in-process, stdio, or HTTP) over a pluggable MemoryBackend, with token-saving fetch-on-demand recall.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,7 +14,8 @@
14
14
  },
15
15
  "bin": {
16
16
  "builderforce-memory-mcp": "dist/bin/stdio.js",
17
- "builderforce-memory-mcp-http": "dist/bin/http.js"
17
+ "builderforce-memory-mcp-http": "dist/bin/http.js",
18
+ "builderforce-memory-install": "dist/bin/install.js"
18
19
  },
19
20
  "files": [
20
21
  "dist",
@@ -49,7 +50,7 @@
49
50
  },
50
51
  "peerDependencies": {
51
52
  "@anthropic-ai/claude-agent-sdk": ">=0.1.0",
52
- "@seanhogg/builderforce-memory": "^2026.6.19",
53
+ "@seanhogg/builderforce-memory": "^2026.6.27",
53
54
  "fake-indexeddb": "^6.0.0"
54
55
  },
55
56
  "peerDependenciesMeta": {
@@ -71,7 +72,7 @@
71
72
  "eslint": "^8.57.0",
72
73
  "fake-indexeddb": "^6.0.0",
73
74
  "typescript": "^5.0.0",
74
- "@seanhogg/builderforce-memory": "2026.6.19"
75
+ "@seanhogg/builderforce-memory": "2026.6.27"
75
76
  },
76
77
  "engines": {
77
78
  "node": ">=18.0.0"
@@ -81,6 +82,7 @@
81
82
  },
82
83
  "scripts": {
83
84
  "build": "tsc",
85
+ "test": "tsc && node --test tests/install.test.mjs",
84
86
  "lint": "eslint src/"
85
87
  }
86
88
  }
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * builderforce-memory-install — wire the memory MCP server into any MCP-capable
4
+ * agent (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, Cline, Gemini
5
+ * CLI, Codex CLI), not just Claude Code.
6
+ *
7
+ * npx -y @seanhogg/builderforce-memory-mcp # via the package's other bin
8
+ * builderforce-memory-install # auto-detect installed hosts
9
+ * builderforce-memory-install --host=cursor,windsurf # explicit hosts
10
+ * builderforce-memory-install --host=all # every known host
11
+ * builderforce-memory-install --memory-file=/path/mem.json # shared store path
12
+ * builderforce-memory-install --readonly # recall-only
13
+ * builderforce-memory-install --local=/abs/dist/bin/stdio.js # dev: local bin
14
+ *
15
+ * The store defaults to ~/.builderforce-memory/memory.json so every agent on
16
+ * the machine shares one memory. Re-running is safe (idempotent + .bak backups).
17
+ */
18
+
19
+ import os from "node:os";
20
+ import path from "node:path";
21
+ import { HOSTS } from "../install/hosts.js";
22
+ import { installMemoryServer, type HostSelector } from "../install/install.js";
23
+
24
+ function argValue(name: string): string | undefined {
25
+ const hit = process.argv.find((a) => a.startsWith(`--${name}=`));
26
+ return hit ? hit.slice(name.length + 3) : undefined;
27
+ }
28
+ const hasFlag = (name: string): boolean => process.argv.includes(`--${name}`);
29
+
30
+ if (hasFlag("help") || hasFlag("h")) {
31
+ process.stdout.write(
32
+ [
33
+ "builderforce-memory-install — register the memory MCP server into MCP hosts.",
34
+ "",
35
+ " --host=<a,b|all> hosts to target (default: auto-detect installed)",
36
+ " --memory-file=<path> JSON snapshot path (default: ~/.builderforce-memory/memory.json)",
37
+ " --readonly recall-only (no remember/forget)",
38
+ " --local=<path> run a locally-built stdio bin via node (dev)",
39
+ "",
40
+ `Known hosts: ${HOSTS.map((h) => h.id).join(", ")}`,
41
+ "",
42
+ ].join("\n"),
43
+ );
44
+ process.exit(0);
45
+ }
46
+
47
+ const hostArg = argValue("host");
48
+ const hosts: HostSelector =
49
+ hostArg === undefined || hostArg === "auto"
50
+ ? "auto"
51
+ : hostArg === "all"
52
+ ? "all"
53
+ : hostArg.split(",").map((s) => s.trim()).filter(Boolean);
54
+
55
+ const memoryFile = argValue("memory-file") ?? path.join(os.homedir(), ".builderforce-memory", "memory.json");
56
+
57
+ let results;
58
+ try {
59
+ results = installMemoryServer({
60
+ hosts,
61
+ memoryFile,
62
+ readonly: hasFlag("readonly"),
63
+ localBin: argValue("local"),
64
+ });
65
+ } catch (err) {
66
+ process.stderr.write(`✗ ${String((err as Error).message ?? err)}\n`);
67
+ process.exit(1);
68
+ }
69
+
70
+ const icon: Record<string, string> = {
71
+ installed: "✓",
72
+ updated: "✓",
73
+ skipped: "•",
74
+ unsupported: "–",
75
+ error: "✗",
76
+ };
77
+
78
+ let wrote = 0;
79
+ for (const r of results) {
80
+ const where = r.path ? ` ${r.path}` : "";
81
+ const why = r.detail ? ` (${r.detail})` : "";
82
+ process.stdout.write(`${icon[r.status] ?? "?"} ${r.label}: ${r.status}${why}${where}\n`);
83
+ if (r.status === "installed" || r.status === "updated") wrote += 1;
84
+ }
85
+
86
+ if (results.length === 0) {
87
+ process.stdout.write(
88
+ "No MCP hosts detected. Pass --host=all to write configs anyway, or --host=cursor (etc.).\n",
89
+ );
90
+ } else {
91
+ process.stdout.write(
92
+ `\nDone — ${wrote} config(s) written. Store: ${memoryFile}\nRestart the agent(s) to connect. Tools: memory_recall · memory_remember · memory_get · memory_recall_by_tag · memory_forget\n`,
93
+ );
94
+ }
package/src/index.ts CHANGED
@@ -29,3 +29,11 @@ export { runStdio } from "./transports/stdio.js";
29
29
 
30
30
  export { createMemoryHttpHandler } from "./transports/http.js";
31
31
  export type { HttpHandlerOptions } from "./transports/http.js";
32
+
33
+ // ── Multi-host installer (wire the stdio server into any MCP-capable agent) ───
34
+ export { buildServerSpec, MCP_PACKAGE, MCP_BIN, RUNTIME_PEERS } from "./install/server-spec.js";
35
+ export type { StdioServerSpec, ServerSpecOptions } from "./install/server-spec.js";
36
+ export { installMemoryServer } from "./install/install.js";
37
+ export type { InstallOptions, InstallResult, InstallStatus, HostSelector, FsLike } from "./install/install.js";
38
+ export { HOSTS, SERVER_KEY, findHost } from "./install/hosts.js";
39
+ export type { HostAdapter, HostEnv, ConfigFormat } from "./install/hosts.js";
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Host registry — every MCP-capable agent we know how to wire memory into.
3
+ *
4
+ * The differences between hosts collapse to three things: WHERE the config
5
+ * file lives, which top-level key holds the server map (`mcpServers` vs VS
6
+ * Code's `servers`), and whether the entry carries an explicit `type:"stdio"`.
7
+ * Codex CLI is the one odd-one-out (TOML, not JSON). Everything else is one
8
+ * shared JSON merge — see install.ts.
9
+ */
10
+
11
+ import path from "node:path";
12
+
13
+ export type ConfigFormat = "json-mcpServers" | "json-servers" | "toml";
14
+
15
+ export interface HostEnv {
16
+ homedir: string;
17
+ platform: NodeJS.Platform;
18
+ env: Record<string, string | undefined>;
19
+ }
20
+
21
+ export interface HostAdapter {
22
+ id: string;
23
+ label: string;
24
+ format: ConfigFormat;
25
+ /** Include `type:"stdio"` in the written entry (Claude Code + VS Code want it). */
26
+ includeType: boolean;
27
+ /** Absolute config-file path for this host, or null if N/A on this OS. */
28
+ configPath(e: HostEnv): string | null;
29
+ }
30
+
31
+ /** The server key written into every host's config. */
32
+ export const SERVER_KEY = "builderforce-memory";
33
+
34
+ /** Per-OS application-support directory for a desktop vendor (Claude, Code). */
35
+ function appSupport(e: HostEnv, vendor: string): string | null {
36
+ if (e.platform === "win32") {
37
+ const roaming = e.env["APPDATA"] ?? path.join(e.homedir, "AppData", "Roaming");
38
+ return path.join(roaming, vendor);
39
+ }
40
+ if (e.platform === "darwin") {
41
+ return path.join(e.homedir, "Library", "Application Support", vendor);
42
+ }
43
+ const xdg = e.env["XDG_CONFIG_HOME"] ?? path.join(e.homedir, ".config");
44
+ return path.join(xdg, vendor);
45
+ }
46
+
47
+ /** VS Code stores its per-user files under `<appSupport(Code)>/User`. */
48
+ function vscodeUserDir(e: HostEnv): string | null {
49
+ const base = appSupport(e, "Code");
50
+ return base ? path.join(base, "User") : null;
51
+ }
52
+
53
+ export const HOSTS: HostAdapter[] = [
54
+ {
55
+ id: "claude-code",
56
+ label: "Claude Code",
57
+ format: "json-mcpServers",
58
+ includeType: true,
59
+ configPath: (e) => path.join(e.homedir, ".claude.json"),
60
+ },
61
+ {
62
+ id: "claude-desktop",
63
+ label: "Claude Desktop",
64
+ format: "json-mcpServers",
65
+ includeType: false,
66
+ configPath: (e) => {
67
+ const dir = appSupport(e, "Claude");
68
+ return dir ? path.join(dir, "claude_desktop_config.json") : null;
69
+ },
70
+ },
71
+ {
72
+ id: "cursor",
73
+ label: "Cursor",
74
+ format: "json-mcpServers",
75
+ includeType: false,
76
+ configPath: (e) => path.join(e.homedir, ".cursor", "mcp.json"),
77
+ },
78
+ {
79
+ id: "windsurf",
80
+ label: "Windsurf",
81
+ format: "json-mcpServers",
82
+ includeType: false,
83
+ configPath: (e) => path.join(e.homedir, ".codeium", "windsurf", "mcp_config.json"),
84
+ },
85
+ {
86
+ id: "vscode",
87
+ label: "VS Code",
88
+ format: "json-servers",
89
+ includeType: true,
90
+ configPath: (e) => {
91
+ const dir = vscodeUserDir(e);
92
+ return dir ? path.join(dir, "mcp.json") : null;
93
+ },
94
+ },
95
+ {
96
+ id: "cline",
97
+ label: "Cline (VS Code)",
98
+ format: "json-mcpServers",
99
+ includeType: false,
100
+ configPath: (e) => {
101
+ const dir = vscodeUserDir(e);
102
+ return dir
103
+ ? path.join(dir, "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
104
+ : null;
105
+ },
106
+ },
107
+ {
108
+ id: "gemini",
109
+ label: "Gemini CLI",
110
+ format: "json-mcpServers",
111
+ includeType: false,
112
+ configPath: (e) => path.join(e.homedir, ".gemini", "settings.json"),
113
+ },
114
+ {
115
+ id: "codex",
116
+ label: "Codex CLI",
117
+ format: "toml",
118
+ includeType: false,
119
+ configPath: (e) => path.join(e.homedir, ".codex", "config.toml"),
120
+ },
121
+ ];
122
+
123
+ export function findHost(id: string): HostAdapter | undefined {
124
+ return HOSTS.find((h) => h.id === id);
125
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Host-agnostic installer: register the builderforce-memory MCP server into any
3
+ * combination of MCP-capable agents. One shared launch spec (server-spec.ts),
4
+ * one entry per host config, idempotent and backup-safe.
5
+ *
6
+ * Pure-ish: all filesystem + environment access goes through injectable seams
7
+ * so the merge logic is unit-testable without touching a real machine.
8
+ */
9
+
10
+ import nodeFs from "node:fs";
11
+ import nodeOs from "node:os";
12
+ import nodePath from "node:path";
13
+ import { HOSTS, SERVER_KEY, type HostAdapter, type HostEnv } from "./hosts.js";
14
+ import { buildServerSpec, type ServerSpecOptions, type StdioServerSpec } from "./server-spec.js";
15
+
16
+ export interface FsLike {
17
+ existsSync(p: string): boolean;
18
+ readFileSync(p: string, enc: "utf8"): string;
19
+ writeFileSync(p: string, data: string): void;
20
+ mkdirSync(p: string, opts: { recursive: boolean }): void;
21
+ copyFileSync(src: string, dest: string): void;
22
+ }
23
+
24
+ export type HostSelector = string[] | "auto" | "all";
25
+
26
+ export interface InstallOptions extends ServerSpecOptions {
27
+ /**
28
+ * Which hosts to target:
29
+ * - "auto" (default) — only hosts whose config file or parent dir exists.
30
+ * - "all" — every known host (creates configs as needed).
31
+ * - string[] — explicit host ids (see HOSTS).
32
+ */
33
+ hosts?: HostSelector;
34
+ fs?: FsLike;
35
+ hostEnv?: HostEnv;
36
+ }
37
+
38
+ export type InstallStatus = "installed" | "updated" | "skipped" | "unsupported" | "error";
39
+
40
+ export interface InstallResult {
41
+ host: string;
42
+ label: string;
43
+ path: string | null;
44
+ status: InstallStatus;
45
+ detail?: string;
46
+ }
47
+
48
+ function defaultHostEnv(): HostEnv {
49
+ return { homedir: nodeOs.homedir(), platform: process.platform, env: process.env };
50
+ }
51
+
52
+ function readJson(fs: FsLike, file: string): Record<string, unknown> {
53
+ if (!fs.existsSync(file)) return {};
54
+ try {
55
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
56
+ return parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : {};
57
+ } catch {
58
+ // Corrupt/partial config — surface as error rather than clobbering it.
59
+ throw new Error("existing config is not valid JSON");
60
+ }
61
+ }
62
+
63
+ function entryFor(spec: StdioServerSpec, includeType: boolean): Record<string, unknown> {
64
+ return {
65
+ ...(includeType ? { type: "stdio" } : {}),
66
+ command: spec.command,
67
+ args: spec.args,
68
+ ...(spec.env ? { env: spec.env } : {}),
69
+ };
70
+ }
71
+
72
+ function ensureParent(fs: FsLike, file: string): void {
73
+ const dir = nodePath.dirname(file);
74
+ if (dir && !fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
75
+ }
76
+
77
+ /** A host is "present" if its config file or its parent directory already exists. */
78
+ function isPresent(fs: FsLike, file: string): boolean {
79
+ return fs.existsSync(file) || fs.existsSync(nodePath.dirname(file));
80
+ }
81
+
82
+ function installJson(
83
+ fs: FsLike,
84
+ host: HostAdapter,
85
+ file: string,
86
+ spec: StdioServerSpec,
87
+ ): InstallStatus {
88
+ const mapKey = host.format === "json-servers" ? "servers" : "mcpServers";
89
+ const config = readJson(fs, file);
90
+ const map = (config[mapKey] && typeof config[mapKey] === "object" ? config[mapKey] : {}) as Record<
91
+ string,
92
+ unknown
93
+ >;
94
+ const next = entryFor(spec, host.includeType);
95
+ const prev = map[SERVER_KEY];
96
+ const existed = prev !== undefined;
97
+ if (existed && JSON.stringify(prev) === JSON.stringify(next)) return "skipped";
98
+
99
+ if (fs.existsSync(file)) fs.copyFileSync(file, `${file}.bak`);
100
+ map[SERVER_KEY] = next;
101
+ config[mapKey] = map;
102
+ ensureParent(fs, file);
103
+ fs.writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
104
+ return existed ? "updated" : "installed";
105
+ }
106
+
107
+ /** TOML emit is append-only and idempotent: skip if the table already exists. */
108
+ function installToml(fs: FsLike, file: string, spec: StdioServerSpec): InstallStatus {
109
+ const header = `[mcp_servers.${SERVER_KEY}]`;
110
+ const existing = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
111
+ if (existing.includes(header)) return "skipped";
112
+
113
+ const q = (s: string) => JSON.stringify(s); // TOML basic strings are JSON-compatible here
114
+ const lines = [
115
+ header,
116
+ `command = ${q(spec.command)}`,
117
+ `args = [${spec.args.map(q).join(", ")}]`,
118
+ ];
119
+ if (spec.env) {
120
+ lines.push(`[mcp_servers.${SERVER_KEY}.env]`);
121
+ for (const [k, v] of Object.entries(spec.env)) lines.push(`${k} = ${q(v)}`);
122
+ }
123
+ const block = `${lines.join("\n")}\n`;
124
+
125
+ if (fs.existsSync(file)) fs.copyFileSync(file, `${file}.bak`);
126
+ ensureParent(fs, file);
127
+ const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
128
+ fs.writeFileSync(file, `${existing}${sep}${block}`);
129
+ return "installed";
130
+ }
131
+
132
+ function resolveHosts(selector: HostSelector, fs: FsLike, hostEnv: HostEnv): HostAdapter[] {
133
+ if (Array.isArray(selector)) {
134
+ return selector.map((id) => {
135
+ const h = HOSTS.find((x) => x.id === id);
136
+ if (!h) throw new Error(`unknown host "${id}" (known: ${HOSTS.map((x) => x.id).join(", ")})`);
137
+ return h;
138
+ });
139
+ }
140
+ if (selector === "all") return HOSTS;
141
+ // "auto": only hosts that look installed on this machine.
142
+ return HOSTS.filter((h) => {
143
+ const p = h.configPath(hostEnv);
144
+ return p !== null && isPresent(fs, p);
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Register (or refresh) the memory MCP server across the selected hosts.
150
+ * Never throws for a single host — per-host failures are captured as
151
+ * `status:"error"` so one bad config can't abort the rest.
152
+ */
153
+ export function installMemoryServer(opts: InstallOptions = {}): InstallResult[] {
154
+ const fs: FsLike = opts.fs ?? nodeFs;
155
+ const hostEnv = opts.hostEnv ?? defaultHostEnv();
156
+ const selector: HostSelector = opts.hosts ?? "auto";
157
+ const spec = buildServerSpec({
158
+ memoryFile: opts.memoryFile,
159
+ readonly: opts.readonly,
160
+ platform: opts.platform ?? hostEnv.platform,
161
+ localBin: opts.localBin,
162
+ });
163
+
164
+ const hosts = resolveHosts(selector, fs, hostEnv);
165
+ return hosts.map((host): InstallResult => {
166
+ const file = host.configPath(hostEnv);
167
+ if (file === null) {
168
+ return { host: host.id, label: host.label, path: null, status: "unsupported", detail: "not available on this OS" };
169
+ }
170
+ try {
171
+ const status = host.format === "toml" ? installToml(fs, file, spec) : installJson(fs, host, file, spec);
172
+ return { host: host.id, label: host.label, path: file, status };
173
+ } catch (err) {
174
+ return { host: host.id, label: host.label, path: file, status: "error", detail: String((err as Error).message ?? err) };
175
+ }
176
+ });
177
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The canonical stdio launch spec for the builderforce-memory MCP server.
3
+ *
4
+ * Every host (Claude Code, Cursor, Windsurf, VS Code, Cline, Gemini/Codex CLI,
5
+ * Claude Desktop) registers the SAME server — only the surrounding config
6
+ * file/shape differs. This module is the single source of truth for HOW the
7
+ * server is launched, so the per-host adapters never re-derive the command.
8
+ */
9
+
10
+ export interface StdioServerSpec {
11
+ command: string;
12
+ args: string[];
13
+ env?: Record<string, string>;
14
+ }
15
+
16
+ export interface ServerSpecOptions {
17
+ /** Absolute path to the JSON snapshot that makes memory survive restarts. */
18
+ memoryFile?: string;
19
+ /** Register read-only (disables the remember/forget tools). */
20
+ readonly?: boolean;
21
+ /** Platform to target (command wrapping). Defaults to `process.platform`. */
22
+ platform?: NodeJS.Platform;
23
+ /**
24
+ * Run a locally-built stdio bin via `node <path>` instead of the published
25
+ * package via `npx` — for development against a checkout.
26
+ */
27
+ localBin?: string;
28
+ }
29
+
30
+ /** Published package + bin. The bin name matches the unscoped package name. */
31
+ export const MCP_PACKAGE = "@seanhogg/builderforce-memory-mcp";
32
+ export const MCP_BIN = "builderforce-memory-mcp";
33
+
34
+ /**
35
+ * Runtime peers the stdio bin loads on demand. They are OPTIONAL peers of the
36
+ * MCP package (so a custom-backend / HTTP-thin-client consumer needn't install
37
+ * them), which means `npx` will NOT auto-pull them — the stdio launch lists
38
+ * them explicitly. `-engine` arrives as `-memory`'s required peer; listed too
39
+ * for determinism.
40
+ */
41
+ export const RUNTIME_PEERS = [
42
+ "@seanhogg/builderforce-memory",
43
+ "@seanhogg/builderforce-memory-engine",
44
+ "fake-indexeddb",
45
+ ];
46
+
47
+ function specEnv(opts: ServerSpecOptions): Record<string, string> | undefined {
48
+ const env: Record<string, string> = {};
49
+ if (opts.memoryFile) env["BUILDERFORCE_MEMORY_FILE"] = opts.memoryFile;
50
+ if (opts.readonly) env["BUILDERFORCE_MEMORY_READONLY"] = "1";
51
+ return Object.keys(env).length > 0 ? env : undefined;
52
+ }
53
+
54
+ /**
55
+ * Build the stdio launch spec. On Windows `npx` is a `.cmd` shim that an MCP
56
+ * launcher must invoke through `cmd`; `node` + a local path needs no shell.
57
+ */
58
+ export function buildServerSpec(opts: ServerSpecOptions = {}): StdioServerSpec {
59
+ const platform = opts.platform ?? process.platform;
60
+ const env = specEnv(opts);
61
+
62
+ if (opts.localBin) {
63
+ return { command: "node", args: [opts.localBin], ...(env ? { env } : {}) };
64
+ }
65
+
66
+ const npxArgs = ["-y", "-p", MCP_PACKAGE, ...RUNTIME_PEERS.flatMap((p) => ["-p", p]), MCP_BIN];
67
+
68
+ if (platform === "win32") {
69
+ return { command: "cmd", args: ["/c", "npx", ...npxArgs], ...(env ? { env } : {}) };
70
+ }
71
+ return { command: "npx", args: npxArgs, ...(env ? { env } : {}) };
72
+ }