chat-recall 0.5.32 → 0.6.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 +17 -0
- package/dist/cli.js +74 -14
- package/dist/cli.js.map +1 -1
- package/dist/mcp-relay.d.ts +3 -0
- package/dist/mcp-relay.d.ts.map +1 -0
- package/dist/mcp-relay.js +229 -0
- package/dist/mcp-relay.js.map +1 -0
- package/dist/mcp-socket.d.ts +98 -0
- package/dist/mcp-socket.d.ts.map +1 -0
- package/dist/mcp-socket.js +249 -0
- package/dist/mcp-socket.js.map +1 -0
- package/dist/mcp.js +856 -608
- package/dist/mcp.js.map +1 -1
- package/dist/update-notice.d.ts +23 -2
- package/dist/update-notice.d.ts.map +1 -1
- package/dist/update-notice.js +50 -5
- package/dist/update-notice.js.map +1 -1
- package/dist/watch.js +5 -5
- package/package.json +4 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-relay.d.ts","sourceRoot":"","sources":["../src/mcp-relay.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// src/mcp-relay.ts
|
|
5
|
+
import { connect } from "node:net";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { openSync } from "node:fs";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
// src/mcp-socket.ts
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { accessSync, chmodSync, constants, mkdirSync, statSync } from "node:fs";
|
|
15
|
+
import { dirname } from "node:path";
|
|
16
|
+
var MAX_PATH = 100;
|
|
17
|
+
var fits = (dir) => dir.length + 1 + 16 + 5 <= MAX_PATH;
|
|
18
|
+
function socketDirCandidates() {
|
|
19
|
+
if (process.platform === "win32") {
|
|
20
|
+
const out2 = [];
|
|
21
|
+
if (process.env.LOCALAPPDATA) out2.push(join(process.env.LOCALAPPDATA, "chat-recall", "run"));
|
|
22
|
+
if (process.env.TEMP) out2.push(join(process.env.TEMP, "chat-recall-run"));
|
|
23
|
+
out2.push(join(tmpdir(), "chat-recall-run"));
|
|
24
|
+
return out2;
|
|
25
|
+
}
|
|
26
|
+
const out = [];
|
|
27
|
+
const xdg = process.env.XDG_RUNTIME_DIR;
|
|
28
|
+
if (xdg && xdg.length > 0) {
|
|
29
|
+
const p = join(xdg, "chat-recall");
|
|
30
|
+
if (fits(p)) out.push(p);
|
|
31
|
+
}
|
|
32
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
33
|
+
const named = join(tmpdir(), `chat-recall-${uid}`);
|
|
34
|
+
if (fits(named)) out.push(named);
|
|
35
|
+
const last = `/tmp/chat-recall-${uid}`;
|
|
36
|
+
if (!out.includes(last)) out.push(last);
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
var MAX_MISSING_LEVELS = 8;
|
|
40
|
+
function isDir(path) {
|
|
41
|
+
try {
|
|
42
|
+
return statSync(path).isDirectory();
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function usableDir(dir) {
|
|
48
|
+
if (!isDir(dir)) {
|
|
49
|
+
const missing = [];
|
|
50
|
+
let cur = dir;
|
|
51
|
+
let base = false;
|
|
52
|
+
while (missing.length < MAX_MISSING_LEVELS) {
|
|
53
|
+
const parent = dirname(cur);
|
|
54
|
+
if (parent === cur) break;
|
|
55
|
+
missing.push(cur);
|
|
56
|
+
if (isDir(parent)) {
|
|
57
|
+
base = true;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
cur = parent;
|
|
61
|
+
}
|
|
62
|
+
if (!base) return false;
|
|
63
|
+
for (let i = missing.length - 1; i >= 0; i--) {
|
|
64
|
+
try {
|
|
65
|
+
mkdirSync(missing[i]);
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
if (!isDir(missing[i])) return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
accessSync(dir, constants.W_OK);
|
|
73
|
+
return true;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function privateDir(dir) {
|
|
79
|
+
if (process.platform === "win32") return true;
|
|
80
|
+
try {
|
|
81
|
+
chmodSync(dir, 448);
|
|
82
|
+
return (statSync(dir).mode & 63) === 0;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function ensureSocketDir() {
|
|
88
|
+
const candidates = socketDirCandidates();
|
|
89
|
+
for (const dir of candidates) {
|
|
90
|
+
if (!usableDir(dir)) continue;
|
|
91
|
+
if (!privateDir(dir)) continue;
|
|
92
|
+
return dir;
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`no private runtime directory for the MCP daemon (tried ${candidates.join(", ")})`);
|
|
95
|
+
}
|
|
96
|
+
function socketSlug(version) {
|
|
97
|
+
const key = [
|
|
98
|
+
process.env.CHAT_RECALL_MCP_PROFILE ?? "",
|
|
99
|
+
process.env.CHAT_RECALL_SERVER ?? "",
|
|
100
|
+
version
|
|
101
|
+
].join(" ");
|
|
102
|
+
return createHash("sha256").update(key).digest("hex").slice(0, 16);
|
|
103
|
+
}
|
|
104
|
+
function socketPath(dir, version) {
|
|
105
|
+
const slug = socketSlug(version);
|
|
106
|
+
if (process.platform === "win32") return "\\\\.\\pipe\\chat-recall-" + slug;
|
|
107
|
+
return join(dir, `${slug}.sock`);
|
|
108
|
+
}
|
|
109
|
+
function logPath(dir, version) {
|
|
110
|
+
return join(dir, `${socketSlug(version)}.log`);
|
|
111
|
+
}
|
|
112
|
+
var SOCKET_FLAG = "--socket";
|
|
113
|
+
function daemonEnabled() {
|
|
114
|
+
const v = process.env.CHAT_RECALL_NO_DAEMON;
|
|
115
|
+
if (v === void 0) return true;
|
|
116
|
+
const t = v.trim();
|
|
117
|
+
return t === "" || t === "0" || t === "false";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/mcp-relay.ts
|
|
121
|
+
var VERSION = true ? "0.6.0" : "0.0.0";
|
|
122
|
+
var SERVER_URL = new URL("./mcp.js", import.meta.url);
|
|
123
|
+
var SERVER_PATH = fileURLToPath(SERVER_URL);
|
|
124
|
+
var START_TIMEOUT_MS = 15e3;
|
|
125
|
+
var RETRY_MS = 100;
|
|
126
|
+
async function serveInProcess() {
|
|
127
|
+
const spec = SERVER_URL.href;
|
|
128
|
+
await import(spec);
|
|
129
|
+
}
|
|
130
|
+
function tryConnect(path) {
|
|
131
|
+
return new Promise((resolve) => {
|
|
132
|
+
const sock = connect(path);
|
|
133
|
+
const give_up = () => {
|
|
134
|
+
sock.destroy();
|
|
135
|
+
resolve(null);
|
|
136
|
+
};
|
|
137
|
+
sock.once("connect", () => {
|
|
138
|
+
sock.removeListener("error", give_up);
|
|
139
|
+
resolve(sock);
|
|
140
|
+
});
|
|
141
|
+
sock.once("error", give_up);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function startDaemon(dir, sock) {
|
|
145
|
+
try {
|
|
146
|
+
let err = "ignore";
|
|
147
|
+
try {
|
|
148
|
+
err = openSync(logPath(dir, VERSION), "a");
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
const child = spawn(process.execPath, [SERVER_PATH, "--daemon", SOCKET_FLAG, sock], {
|
|
152
|
+
detached: true,
|
|
153
|
+
stdio: ["ignore", "ignore", err],
|
|
154
|
+
env: process.env
|
|
155
|
+
});
|
|
156
|
+
child.unref();
|
|
157
|
+
return child;
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function relay(sock) {
|
|
163
|
+
process.stdin.pipe(sock);
|
|
164
|
+
sock.pipe(process.stdout);
|
|
165
|
+
const done = (code) => {
|
|
166
|
+
try {
|
|
167
|
+
sock.destroy();
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
process.exit(code);
|
|
171
|
+
};
|
|
172
|
+
process.stdin.on("end", () => sock.end());
|
|
173
|
+
sock.on("close", () => done(0));
|
|
174
|
+
sock.on("error", () => done(0));
|
|
175
|
+
}
|
|
176
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
177
|
+
async function main() {
|
|
178
|
+
if (!daemonEnabled()) {
|
|
179
|
+
await serveInProcess();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
let dir;
|
|
183
|
+
let path;
|
|
184
|
+
try {
|
|
185
|
+
dir = ensureSocketDir();
|
|
186
|
+
path = socketPath(dir, VERSION);
|
|
187
|
+
} catch {
|
|
188
|
+
await serveInProcess();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const existing = await tryConnect(path);
|
|
192
|
+
if (existing) {
|
|
193
|
+
relay(existing);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const child = startDaemon(dir, path);
|
|
197
|
+
if (!child) {
|
|
198
|
+
await serveInProcess();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const deadline = Date.now() + START_TIMEOUT_MS;
|
|
202
|
+
while (Date.now() < deadline) {
|
|
203
|
+
await sleep(RETRY_MS);
|
|
204
|
+
const sock = await tryConnect(path);
|
|
205
|
+
if (sock) {
|
|
206
|
+
relay(sock);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
210
|
+
const sibling = await tryConnect(path);
|
|
211
|
+
if (sibling) {
|
|
212
|
+
relay(sibling);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
console.error("[mcp] no daemon answered; serving this session in-process");
|
|
219
|
+
await serveInProcess();
|
|
220
|
+
}
|
|
221
|
+
main().catch(async (err) => {
|
|
222
|
+
console.error("[mcp] relay failed, serving in-process:", err?.message ?? err);
|
|
223
|
+
try {
|
|
224
|
+
await serveInProcess();
|
|
225
|
+
} catch (inner) {
|
|
226
|
+
console.error("[mcp] could not start the server at all:", inner);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-relay.js","sourceRoot":"","sources":["../src/mcp-relay.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every directory the socket and the daemon log may live in, best first.
|
|
3
|
+
*
|
|
4
|
+
* XDG_RUNTIME_DIR first: it is per-user, mode 0700, and cleared on logout, so a
|
|
5
|
+
* socket never outlives the login session that made it. macOS never sets it,
|
|
6
|
+
* hence the per-uid directory under TMPDIR — which is also what keeps one
|
|
7
|
+
* user's socket out of another's reach on a shared /tmp. `/tmp` itself is the
|
|
8
|
+
* last resort: a TMPDIR long enough to break the path limit still leaves it.
|
|
9
|
+
*
|
|
10
|
+
* This is a LIST, not one answer, because every entry but the last comes from
|
|
11
|
+
* the environment. A sandbox, a read-only mount or a stale variable pointing at
|
|
12
|
+
* a directory this user cannot write makes the first choice fail, and one
|
|
13
|
+
* failed candidate must cost a session nothing: the next one is tried.
|
|
14
|
+
*/
|
|
15
|
+
export declare function socketDirCandidates(): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Create `dir` if it is missing, and report whether it is a directory this
|
|
18
|
+
* process can write afterwards. Never throws: a candidate that cannot be used
|
|
19
|
+
* is a reason to try the next one, not a failure.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately NOT `mkdirSync(dir, { recursive: true })`. Node 23's recursive
|
|
22
|
+
* mkdir walks BACK to the parent whenever a component reports ENOENT and
|
|
23
|
+
* forward again when one exists — and a path whose parent exists while the
|
|
24
|
+
* child still reports ENOENT makes it oscillate between the two forever, at
|
|
25
|
+
* 100% of a core. `/proc/anything` does exactly that on Linux: `mkdir
|
|
26
|
+
* /proc/x` returns ENOENT, not EACCES. This path comes from XDG_RUNTIME_DIR or
|
|
27
|
+
* TMPDIR — the values this program does not control — so it must never be
|
|
28
|
+
* handed to that loop. (Verified on Node v23.9.0: the recursive call never
|
|
29
|
+
* returns; a single-level `mkdirSync` on the same path throws ENOENT at once.)
|
|
30
|
+
*
|
|
31
|
+
* This walks up to the deepest ancestor that already exists, bounded, then
|
|
32
|
+
* creates forward from there ONE level at a time. It never revisits a level,
|
|
33
|
+
* so it cannot spin.
|
|
34
|
+
*/
|
|
35
|
+
export declare function usableDir(dir: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Make `dir` private to this user, and report whether that took effect.
|
|
38
|
+
*
|
|
39
|
+
* This is also the OWNERSHIP proof. `usableDir` says yes to anything writable,
|
|
40
|
+
* and on a shared /tmp that includes a directory another local user created
|
|
41
|
+
* first — our socket would then sit in their directory, theirs to swap out.
|
|
42
|
+
* chmod(2) fails with EPERM for anyone but the owner, so a chmod that
|
|
43
|
+
* succeeded means we own the directory; the mode check after it catches a
|
|
44
|
+
* filesystem that accepts chmod and ignores it (FAT, some FUSE mounts). No
|
|
45
|
+
* uid comparison, no window between a stat and a chmod. Windows has no POSIX
|
|
46
|
+
* modes; the per-user LOCALAPPDATA path is the protection there.
|
|
47
|
+
*/
|
|
48
|
+
export declare function privateDir(dir: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The runtime directory for this user: the first candidate that can be used
|
|
51
|
+
* AND made private, created on the way. Throws only when NO candidate works,
|
|
52
|
+
* which the relay turns into serving the session in-process.
|
|
53
|
+
*/
|
|
54
|
+
export declare function ensureSocketDir(): string;
|
|
55
|
+
/**
|
|
56
|
+
* Close the socket file itself to other users. connect(2) needs write
|
|
57
|
+
* permission on a Unix socket, so 0600 refuses every other uid even if the
|
|
58
|
+
* directory were traversable. Named pipes on Windows are not files.
|
|
59
|
+
*/
|
|
60
|
+
export declare function restrictSocket(path: string): void;
|
|
61
|
+
/**
|
|
62
|
+
* What makes two sessions share a daemon.
|
|
63
|
+
*
|
|
64
|
+
* The profile decides WHICH TOOLS a server registers — "lean" is 27, "full" is
|
|
65
|
+
* all 61 — and that list is fixed when the server object is built. Two sessions
|
|
66
|
+
* that asked for different lists therefore cannot share one daemon; keying on it
|
|
67
|
+
* gives them one each. The server URL is in the key for the same reason: a
|
|
68
|
+
* daemon holds a connection to one backend.
|
|
69
|
+
*
|
|
70
|
+
* The version is in it because without it an upgraded package would attach to a
|
|
71
|
+
* daemon still running the old code — the handshake would succeed and every
|
|
72
|
+
* answer would come from the version the user believes they replaced.
|
|
73
|
+
*/
|
|
74
|
+
export declare function socketSlug(version: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* The address the daemon listens on, inside the runtime directory the caller
|
|
77
|
+
* resolved with `ensureSocketDir()`.
|
|
78
|
+
*
|
|
79
|
+
* Windows gets a named pipe rather than a file. Node's local-domain sockets ARE
|
|
80
|
+
* named pipes there, and a path outside the pipe namespace is not a valid one —
|
|
81
|
+
* so this is the native equivalent, not a workaround. It also needs no
|
|
82
|
+
* directory and leaves nothing behind when the daemon dies.
|
|
83
|
+
*/
|
|
84
|
+
export declare function socketPath(dir: string, version: string): string;
|
|
85
|
+
/** Where a daemon's diagnostics go. Never a session's stderr: it outlives it. */
|
|
86
|
+
export declare function logPath(dir: string, version: string): string;
|
|
87
|
+
/**
|
|
88
|
+
* The flag the relay hands the daemon it starts. The relay already resolved a
|
|
89
|
+
* directory and is waiting on that exact path, so the daemon binds there rather
|
|
90
|
+
* than resolving again — a second resolution could land somewhere else if the
|
|
91
|
+
* environment shifted between the two processes.
|
|
92
|
+
*/
|
|
93
|
+
export declare const SOCKET_FLAG = "--socket";
|
|
94
|
+
/** The path given with `--socket`, if the process was started with one. */
|
|
95
|
+
export declare function socketPathFromArgv(argv: readonly string[]): string | undefined;
|
|
96
|
+
/** True unless the user asked to keep everything inside their own session. */
|
|
97
|
+
export declare function daemonEnabled(): boolean;
|
|
98
|
+
//# sourceMappingURL=mcp-socket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-socket.d.ts","sourceRoot":"","sources":["../src/mcp-socket.ts"],"names":[],"mappings":"AAuBA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAoB9C;AAeD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAmC9C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQ/C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAQxC;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOlD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAI/D;AAED,iFAAiF;AACjF,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,aAAa,CAAC;AAEtC,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAK9E;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,IAAI,OAAO,CAKvC"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a chat-recall MCP daemon listens, and how a session reaches it.
|
|
3
|
+
*
|
|
4
|
+
* One daemon per (profile, server, version) — not one per session. Every MCP
|
|
5
|
+
* session on a machine used to be a full copy of this package: the whole bundle
|
|
6
|
+
* loaded, its own background sync loop, its own skills check. They now share
|
|
7
|
+
* one, and each session is a thin relay onto it.
|
|
8
|
+
*
|
|
9
|
+
* Imported by BOTH the relay and the server, so it may use Node builtins only.
|
|
10
|
+
* Anything heavier here lands in the relay bundle and undoes the point of it.
|
|
11
|
+
*/
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
import { tmpdir } from 'node:os';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { accessSync, chmodSync, constants, mkdirSync, statSync } from 'node:fs';
|
|
16
|
+
import { dirname } from 'node:path';
|
|
17
|
+
/** POSIX allows 108 bytes for a socket path, macOS 104. Stay well inside. */
|
|
18
|
+
const MAX_PATH = 100;
|
|
19
|
+
/** Room a candidate directory must leave for `/<16-hex slug>.sock`. */
|
|
20
|
+
const fits = (dir) => dir.length + 1 + 16 + 5 <= MAX_PATH;
|
|
21
|
+
/**
|
|
22
|
+
* Every directory the socket and the daemon log may live in, best first.
|
|
23
|
+
*
|
|
24
|
+
* XDG_RUNTIME_DIR first: it is per-user, mode 0700, and cleared on logout, so a
|
|
25
|
+
* socket never outlives the login session that made it. macOS never sets it,
|
|
26
|
+
* hence the per-uid directory under TMPDIR — which is also what keeps one
|
|
27
|
+
* user's socket out of another's reach on a shared /tmp. `/tmp` itself is the
|
|
28
|
+
* last resort: a TMPDIR long enough to break the path limit still leaves it.
|
|
29
|
+
*
|
|
30
|
+
* This is a LIST, not one answer, because every entry but the last comes from
|
|
31
|
+
* the environment. A sandbox, a read-only mount or a stale variable pointing at
|
|
32
|
+
* a directory this user cannot write makes the first choice fail, and one
|
|
33
|
+
* failed candidate must cost a session nothing: the next one is tried.
|
|
34
|
+
*/
|
|
35
|
+
export function socketDirCandidates() {
|
|
36
|
+
if (process.platform === 'win32') {
|
|
37
|
+
const out = [];
|
|
38
|
+
if (process.env.LOCALAPPDATA)
|
|
39
|
+
out.push(join(process.env.LOCALAPPDATA, 'chat-recall', 'run'));
|
|
40
|
+
if (process.env.TEMP)
|
|
41
|
+
out.push(join(process.env.TEMP, 'chat-recall-run'));
|
|
42
|
+
out.push(join(tmpdir(), 'chat-recall-run'));
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
const out = [];
|
|
46
|
+
const xdg = process.env.XDG_RUNTIME_DIR;
|
|
47
|
+
if (xdg && xdg.length > 0) {
|
|
48
|
+
const p = join(xdg, 'chat-recall');
|
|
49
|
+
if (fits(p))
|
|
50
|
+
out.push(p);
|
|
51
|
+
}
|
|
52
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : 0;
|
|
53
|
+
const named = join(tmpdir(), `chat-recall-${uid}`);
|
|
54
|
+
if (fits(named))
|
|
55
|
+
out.push(named);
|
|
56
|
+
const last = `/tmp/chat-recall-${uid}`;
|
|
57
|
+
if (!out.includes(last))
|
|
58
|
+
out.push(last);
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/** How many missing levels this will build before giving up. A runtime
|
|
62
|
+
* directory is one or two below something that already exists; deeper than
|
|
63
|
+
* this means the path is wrong, not a tree worth creating. */
|
|
64
|
+
const MAX_MISSING_LEVELS = 8;
|
|
65
|
+
function isDir(path) {
|
|
66
|
+
try {
|
|
67
|
+
return statSync(path).isDirectory();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create `dir` if it is missing, and report whether it is a directory this
|
|
75
|
+
* process can write afterwards. Never throws: a candidate that cannot be used
|
|
76
|
+
* is a reason to try the next one, not a failure.
|
|
77
|
+
*
|
|
78
|
+
* Deliberately NOT `mkdirSync(dir, { recursive: true })`. Node 23's recursive
|
|
79
|
+
* mkdir walks BACK to the parent whenever a component reports ENOENT and
|
|
80
|
+
* forward again when one exists — and a path whose parent exists while the
|
|
81
|
+
* child still reports ENOENT makes it oscillate between the two forever, at
|
|
82
|
+
* 100% of a core. `/proc/anything` does exactly that on Linux: `mkdir
|
|
83
|
+
* /proc/x` returns ENOENT, not EACCES. This path comes from XDG_RUNTIME_DIR or
|
|
84
|
+
* TMPDIR — the values this program does not control — so it must never be
|
|
85
|
+
* handed to that loop. (Verified on Node v23.9.0: the recursive call never
|
|
86
|
+
* returns; a single-level `mkdirSync` on the same path throws ENOENT at once.)
|
|
87
|
+
*
|
|
88
|
+
* This walks up to the deepest ancestor that already exists, bounded, then
|
|
89
|
+
* creates forward from there ONE level at a time. It never revisits a level,
|
|
90
|
+
* so it cannot spin.
|
|
91
|
+
*/
|
|
92
|
+
export function usableDir(dir) {
|
|
93
|
+
if (!isDir(dir)) {
|
|
94
|
+
// Collect missing components up to the first existing ancestor.
|
|
95
|
+
const missing = [];
|
|
96
|
+
let cur = dir;
|
|
97
|
+
let base = false;
|
|
98
|
+
while (missing.length < MAX_MISSING_LEVELS) {
|
|
99
|
+
const parent = dirname(cur);
|
|
100
|
+
if (parent === cur)
|
|
101
|
+
break; // reached the root
|
|
102
|
+
missing.push(cur);
|
|
103
|
+
if (isDir(parent)) {
|
|
104
|
+
base = true;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
cur = parent;
|
|
108
|
+
}
|
|
109
|
+
// No existing directory within the bound: the path is not somewhere this
|
|
110
|
+
// program should be building a tree.
|
|
111
|
+
if (!base)
|
|
112
|
+
return false;
|
|
113
|
+
for (let i = missing.length - 1; i >= 0; i--) {
|
|
114
|
+
try {
|
|
115
|
+
mkdirSync(missing[i]); // single level — throws ENOENT rather than spins
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* judged by what exists, not by what mkdir said (EEXIST is fine) */
|
|
119
|
+
}
|
|
120
|
+
if (!isDir(missing[i]))
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
accessSync(dir, constants.W_OK);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Make `dir` private to this user, and report whether that took effect.
|
|
134
|
+
*
|
|
135
|
+
* This is also the OWNERSHIP proof. `usableDir` says yes to anything writable,
|
|
136
|
+
* and on a shared /tmp that includes a directory another local user created
|
|
137
|
+
* first — our socket would then sit in their directory, theirs to swap out.
|
|
138
|
+
* chmod(2) fails with EPERM for anyone but the owner, so a chmod that
|
|
139
|
+
* succeeded means we own the directory; the mode check after it catches a
|
|
140
|
+
* filesystem that accepts chmod and ignores it (FAT, some FUSE mounts). No
|
|
141
|
+
* uid comparison, no window between a stat and a chmod. Windows has no POSIX
|
|
142
|
+
* modes; the per-user LOCALAPPDATA path is the protection there.
|
|
143
|
+
*/
|
|
144
|
+
export function privateDir(dir) {
|
|
145
|
+
if (process.platform === 'win32')
|
|
146
|
+
return true;
|
|
147
|
+
try {
|
|
148
|
+
chmodSync(dir, 0o700);
|
|
149
|
+
return (statSync(dir).mode & 0o077) === 0;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The runtime directory for this user: the first candidate that can be used
|
|
157
|
+
* AND made private, created on the way. Throws only when NO candidate works,
|
|
158
|
+
* which the relay turns into serving the session in-process.
|
|
159
|
+
*/
|
|
160
|
+
export function ensureSocketDir() {
|
|
161
|
+
const candidates = socketDirCandidates();
|
|
162
|
+
for (const dir of candidates) {
|
|
163
|
+
if (!usableDir(dir))
|
|
164
|
+
continue;
|
|
165
|
+
if (!privateDir(dir))
|
|
166
|
+
continue;
|
|
167
|
+
return dir;
|
|
168
|
+
}
|
|
169
|
+
throw new Error(`no private runtime directory for the MCP daemon (tried ${candidates.join(', ')})`);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Close the socket file itself to other users. connect(2) needs write
|
|
173
|
+
* permission on a Unix socket, so 0600 refuses every other uid even if the
|
|
174
|
+
* directory were traversable. Named pipes on Windows are not files.
|
|
175
|
+
*/
|
|
176
|
+
export function restrictSocket(path) {
|
|
177
|
+
if (process.platform === 'win32')
|
|
178
|
+
return;
|
|
179
|
+
try {
|
|
180
|
+
chmodSync(path, 0o600);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
/* the directory is already 0700; this is the second lock, not the only one */
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* What makes two sessions share a daemon.
|
|
188
|
+
*
|
|
189
|
+
* The profile decides WHICH TOOLS a server registers — "lean" is 27, "full" is
|
|
190
|
+
* all 61 — and that list is fixed when the server object is built. Two sessions
|
|
191
|
+
* that asked for different lists therefore cannot share one daemon; keying on it
|
|
192
|
+
* gives them one each. The server URL is in the key for the same reason: a
|
|
193
|
+
* daemon holds a connection to one backend.
|
|
194
|
+
*
|
|
195
|
+
* The version is in it because without it an upgraded package would attach to a
|
|
196
|
+
* daemon still running the old code — the handshake would succeed and every
|
|
197
|
+
* answer would come from the version the user believes they replaced.
|
|
198
|
+
*/
|
|
199
|
+
export function socketSlug(version) {
|
|
200
|
+
const key = [
|
|
201
|
+
process.env.CHAT_RECALL_MCP_PROFILE ?? '',
|
|
202
|
+
process.env.CHAT_RECALL_SERVER ?? '',
|
|
203
|
+
version,
|
|
204
|
+
].join(' ');
|
|
205
|
+
return createHash('sha256').update(key).digest('hex').slice(0, 16);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The address the daemon listens on, inside the runtime directory the caller
|
|
209
|
+
* resolved with `ensureSocketDir()`.
|
|
210
|
+
*
|
|
211
|
+
* Windows gets a named pipe rather than a file. Node's local-domain sockets ARE
|
|
212
|
+
* named pipes there, and a path outside the pipe namespace is not a valid one —
|
|
213
|
+
* so this is the native equivalent, not a workaround. It also needs no
|
|
214
|
+
* directory and leaves nothing behind when the daemon dies.
|
|
215
|
+
*/
|
|
216
|
+
export function socketPath(dir, version) {
|
|
217
|
+
const slug = socketSlug(version);
|
|
218
|
+
if (process.platform === 'win32')
|
|
219
|
+
return '\\\\.\\pipe\\chat-recall-' + slug;
|
|
220
|
+
return join(dir, `${slug}.sock`);
|
|
221
|
+
}
|
|
222
|
+
/** Where a daemon's diagnostics go. Never a session's stderr: it outlives it. */
|
|
223
|
+
export function logPath(dir, version) {
|
|
224
|
+
return join(dir, `${socketSlug(version)}.log`);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The flag the relay hands the daemon it starts. The relay already resolved a
|
|
228
|
+
* directory and is waiting on that exact path, so the daemon binds there rather
|
|
229
|
+
* than resolving again — a second resolution could land somewhere else if the
|
|
230
|
+
* environment shifted between the two processes.
|
|
231
|
+
*/
|
|
232
|
+
export const SOCKET_FLAG = '--socket';
|
|
233
|
+
/** The path given with `--socket`, if the process was started with one. */
|
|
234
|
+
export function socketPathFromArgv(argv) {
|
|
235
|
+
const i = argv.indexOf(SOCKET_FLAG);
|
|
236
|
+
if (i === -1)
|
|
237
|
+
return undefined;
|
|
238
|
+
const v = argv[i + 1];
|
|
239
|
+
return v && !v.startsWith('--') ? v : undefined;
|
|
240
|
+
}
|
|
241
|
+
/** True unless the user asked to keep everything inside their own session. */
|
|
242
|
+
export function daemonEnabled() {
|
|
243
|
+
const v = process.env.CHAT_RECALL_NO_DAEMON;
|
|
244
|
+
if (v === undefined)
|
|
245
|
+
return true;
|
|
246
|
+
const t = v.trim();
|
|
247
|
+
return t === '' || t === '0' || t === 'false';
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=mcp-socket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-socket.js","sourceRoot":"","sources":["../src/mcp-socket.ts"],"names":[],"mappings":"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"}
|