Skip to content

Commit a443a79

Browse files
BrainSlugs83Copilot
andcommitted
feat: deferred server deps — proxy installs in seconds, not minutes
Move server-only dependencies (@huggingface/transformers, better-sqlite3, sqlite-vec) out of package.json. The proxy now installs ~8 MB via npx instead of 432 MB, completing the MCP handshake in < 3 seconds. Server deps are installed to __dirname/.server/ on first use (one-time, background). Subsequent launches find deps already cached. Version tracking via .server/.version ensures upgrades re-install as needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7ebe08c commit a443a79

File tree

5 files changed

+109
-14
lines changed

5 files changed

+109
-14
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules/
2+
.server/
23
*.db
34
*.pid

index.js

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { join, dirname } from "path";
66
import { homedir } from "os";
77
import { fileURLToPath } from "url";
88
import { spawn } from "child_process";
9-
import { existsSync, readFileSync } from "fs";
9+
import { existsSync, readFileSync, mkdirSync, writeFileSync, copyFileSync } from "fs";
1010
import { request } from "http";
1111
import { userInfo } from "os";
1212

@@ -20,6 +20,94 @@ const PORT = parseInt(process.env.VECTOR_MEMORY_PORT || String(userPort(EXPECTED
2020
const SERVER_URL = `http://127.0.0.1:${PORT}`;
2121
const PID_FILE = join(COPILOT_DIR, "vector-memory.pid");
2222

23+
// --- Server dependency management ---
24+
// In npx mode, server-only deps (onnxruntime, better-sqlite3, etc.) are NOT in
25+
// node_modules — they'd bloat the install by 400+ MB and block the MCP handshake.
26+
// We install them to __dirname/.server/ on first use instead.
27+
28+
const SERVER_DEPS_DIR = join(__dirname, ".server");
29+
const SERVER_DEPS_JSON = join(__dirname, "server-deps.json");
30+
const SERVER_FILES = ["vector-memory-server.js", "embed-worker.js", "lib.js"];
31+
32+
/** Check if server deps are available (either in node_modules or .server/) */
33+
function serverDepsInstalled() {
34+
if (existsSync(join(__dirname, "node_modules", "better-sqlite3"))) return true;
35+
try {
36+
return readFileSync(join(SERVER_DEPS_DIR, ".version"), "utf-8").trim() === PKG.version;
37+
} catch { return false; }
38+
}
39+
40+
/** Return the directory the server should run from */
41+
function getServerDir() {
42+
if (existsSync(join(__dirname, "node_modules", "better-sqlite3"))) return __dirname;
43+
return SERVER_DEPS_DIR;
44+
}
45+
46+
let _installPromise = null;
47+
48+
/** Install server deps to .server/ (singleton — only one install runs at a time) */
49+
function installServerDeps() {
50+
if (_installPromise) return _installPromise;
51+
52+
_installPromise = new Promise((resolve) => {
53+
try {
54+
mkdirSync(SERVER_DEPS_DIR, { recursive: true });
55+
56+
const deps = JSON.parse(readFileSync(SERVER_DEPS_JSON, "utf-8"));
57+
const pkg = {
58+
name: "vector-memory-server-deps",
59+
private: true,
60+
type: "module",
61+
version: PKG.version,
62+
dependencies: deps,
63+
};
64+
writeFileSync(join(SERVER_DEPS_DIR, "package.json"), JSON.stringify(pkg, null, 2));
65+
66+
for (const file of SERVER_FILES) {
67+
copyFileSync(join(__dirname, file), join(SERVER_DEPS_DIR, file));
68+
}
69+
70+
process.stderr.write("[vector-memory] Installing server dependencies (one-time setup)...\n");
71+
const child = spawn("npm", ["install", "--production", "--no-audit", "--no-fund"], {
72+
cwd: SERVER_DEPS_DIR,
73+
stdio: "pipe",
74+
shell: true,
75+
windowsHide: true,
76+
});
77+
78+
child.on("close", (code) => {
79+
if (code === 0) {
80+
writeFileSync(join(SERVER_DEPS_DIR, ".version"), PKG.version);
81+
process.stderr.write("[vector-memory] Server dependencies installed.\n");
82+
} else {
83+
process.stderr.write(`[vector-memory] npm install failed (exit ${code}). Will retry.\n`);
84+
}
85+
_installPromise = null;
86+
resolve(code === 0);
87+
});
88+
89+
child.on("error", (err) => {
90+
process.stderr.write(`[vector-memory] npm install error: ${err.message}\n`);
91+
_installPromise = null;
92+
resolve(false);
93+
});
94+
} catch (err) {
95+
process.stderr.write(`[vector-memory] Setup error: ${err.message}\n`);
96+
_installPromise = null;
97+
resolve(false);
98+
}
99+
});
100+
101+
return _installPromise;
102+
}
103+
104+
/** Ensure server deps are available; installs them if missing or outdated */
105+
async function ensureServerDeps() {
106+
if (serverDepsInstalled()) return true;
107+
if (!existsSync(SERVER_DEPS_JSON)) return true; // dev mode — deps in node_modules
108+
return await installServerDeps();
109+
}
110+
23111
// --- Check if server is running ---
24112

25113
function ping() {
@@ -95,8 +183,13 @@ async function ensureServer() {
95183
}
96184
}
97185

98-
// Launch server detached
99-
const serverPath = join(__dirname, "vector-memory-server.js");
186+
// Ensure server deps are installed (no-op in dev mode; installs in npx mode)
187+
const depsReady = await ensureServerDeps();
188+
if (!depsReady) return; // logged in ensureServerDeps; will retry on next call
189+
190+
// Launch server detached from the correct directory
191+
const serverDir = getServerDir();
192+
const serverPath = join(serverDir, "vector-memory-server.js");
100193
const child = spawn(process.execPath, [serverPath], {
101194
detached: true,
102195
stdio: "ignore",
@@ -150,8 +243,8 @@ function callServer(path, body) {
150243
// Auto-relaunch wrapper: if the server isn't reachable, launch it and wait patiently.
151244
// On first run the model download can take several minutes — we wait up to 5 min.
152245
const WARMUP_MSG =
153-
"⏳ Vector memory server is still starting up (first launch downloads a ~34 MB ML model " +
154-
"and compiles native modules — this is a one-time cost). Try again in a minute or two.";
246+
"⏳ Vector memory server is still starting up (first launch installs server dependencies " +
247+
"and downloads a ~34 MB ML model — this is a one-time cost). Try again in a minute or two.";
155248

156249
async function callServerWithRetry(path, body) {
157250
try {
@@ -217,7 +310,6 @@ const server = new McpServer(
217310
"### Architecture (for troubleshooting):",
218311
"- Singleton HTTP server (one ONNX model in memory shared across all copilot instances)",
219312
"- Thin STDIO proxy per copilot instance auto-launches the server if needed",
220-
"- Server idles down after 5 min of inactivity; proxy restarts it on next use",
221313
].join("\n"),
222314
},
223315
);

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ghcp-cli-vector-memory-mcp",
3-
"version": "1.4.0",
3+
"version": "1.5.0",
44
"description": "MCP server that gives GitHub Copilot CLI persistent long-term memory via local semantic vector search. Install: npx -y ghcp-cli-vector-memory-mcp",
55
"main": "index.js",
66
"bin": {
@@ -11,6 +11,7 @@
1111
"vector-memory-server.js",
1212
"embed-worker.js",
1313
"lib.js",
14+
"server-deps.json",
1415
"LICENSE",
1516
"README.md"
1617
],
@@ -38,14 +39,10 @@
3839
"scripts": {
3940
"lint": "eslint index.js vector-memory-server.js embed-worker.js lib.js",
4041
"test": "node --test test.js",
41-
"check": "npm run lint && npm run test",
42-
"postinstall": "node --input-type=module -e \"import('@huggingface/transformers').then(m=>m.pipeline('feature-extraction','Xenova/gte-small',{dtype:'q8'})).then(()=>console.log('✓ Embedding model ready')).catch(e=>console.error('⚠ Model download failed (will retry on first run):',e.message))\""
42+
"check": "npm run lint && npm run test"
4343
},
4444
"dependencies": {
45-
"@huggingface/transformers": "^3.0.0",
4645
"@modelcontextprotocol/sdk": "^1.0.0",
47-
"better-sqlite3": "^12.1.0",
48-
"sqlite-vec": "^0.1.0",
4946
"zod": "^4.0.0"
5047
},
5148
"devDependencies": {

server-deps.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"@huggingface/transformers": "^3.0.0",
3+
"better-sqlite3": "^12.1.0",
4+
"sqlite-vec": "^0.1.0"
5+
}

0 commit comments

Comments
 (0)