@seanhogg/builderforce-memory-mcp 2026.6.30 → 2026.6.34
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 +22 -2
- package/dist/backends/memory-store.d.ts +1 -1
- package/dist/bin/install.js +22 -0
- package/dist/bin/install.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/install/claude-hooks.d.ts +49 -0
- package/dist/install/claude-hooks.d.ts.map +1 -0
- package/dist/install/claude-hooks.js +255 -0
- package/dist/install/claude-hooks.js.map +1 -0
- package/dist/transports/auth.d.ts +36 -0
- package/dist/transports/auth.d.ts.map +1 -0
- package/dist/transports/auth.js +78 -0
- package/dist/transports/auth.js.map +1 -0
- package/dist/transports/http.d.ts +41 -7
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +79 -12
- package/dist/transports/http.js.map +1 -1
- package/package.json +3 -3
- package/src/backends/memory-store.ts +1 -1
- package/src/bin/install.ts +22 -0
- package/src/index.ts +5 -0
- package/src/install/claude-hooks.ts +267 -0
- package/src/transports/auth.ts +78 -0
- package/src/transports/http.ts +88 -15
package/README.md
CHANGED
|
@@ -7,7 +7,9 @@ tool core over a pluggable `MemoryBackend`, three transports:
|
|
|
7
7
|
|---|---|---|---|
|
|
8
8
|
| **In-process (Claude Agent SDK)** | `createMemoryMcpServer(backend)` | the returned `type:"sdk"` object | the consuming product is TS and runs `@anthropic-ai/claude-agent-sdk` in-process — lowest latency |
|
|
9
9
|
| **stdio** | `runStdio(backend)` / `npx @seanhogg/builderforce-memory-mcp` | `{ type:"stdio", command, args }` | any language / separate process; decouples the SSM+IndexedDB deps from the consumer |
|
|
10
|
-
| **HTTP (Streamable)** | `createMemoryHttpHandler(backend, { authToken })` | `{ type:"http", url, headers }` | multi-tenant / networked (builderforce.ai hosting, remote
|
|
10
|
+
| **HTTP (Streamable)** | `createMemoryHttpHandler(backend, { authToken })` | `{ type:"http", url, headers }` | multi-tenant / networked (builderforce.ai hosting, remote agents) |
|
|
11
|
+
|
|
12
|
+
> **Technical report & peer review.** This transport is reviewed in the Evermind technical report's adversarial [`PEER-REVIEW.md`](../../publication/evermind/PEER-REVIEW.md). Note before exposing the HTTP handler publicly: the `authToken` check is a **non-constant-time** string compare (timing channel) and the handler shares **one `backend` with no per-tenant isolation** — a single token reads every tenant's facts. Despite the "multi-tenant" note above, real isolation (token→namespace mapping, constant-time compare, rate limiting) is **not yet implemented**. Tracked as `EVM-3` in the Builderforce.ai Consolidated Gap Register.
|
|
11
13
|
|
|
12
14
|
## Why this saves tokens
|
|
13
15
|
|
|
@@ -67,7 +69,7 @@ for await (const msg of query({
|
|
|
67
69
|
|
|
68
70
|
The local backend defaults to **lexical (Jaccard)** recall. For SSM-embedding
|
|
69
71
|
cosine recall, pass an SSM runtime — e.g. reuse the agent-runtime's already-loaded
|
|
70
|
-
|
|
72
|
+
Evermind runtime instead of standing up a second model:
|
|
71
73
|
|
|
72
74
|
```ts
|
|
73
75
|
const backend = await createLocalMemoryStoreBackend({ runtime: ssmMemoryService.runtime });
|
|
@@ -95,6 +97,24 @@ write tools; `--local=<dist/bin/stdio.js>` runs a checkout for development.
|
|
|
95
97
|
The same launch spec is exported for programmatic use:
|
|
96
98
|
`buildServerSpec()` / `installMemoryServer()` (see `src/install/`).
|
|
97
99
|
|
|
100
|
+
### Claude Code: the self-driving "memory combo"
|
|
101
|
+
|
|
102
|
+
Registering the server gives Claude Code the memory *tools*. For Claude Code the
|
|
103
|
+
installer ALSO wires hooks so memory is **self-driving instead of advisory** (skip
|
|
104
|
+
with `--no-hooks`):
|
|
105
|
+
|
|
106
|
+
| Hook | Behaviour |
|
|
107
|
+
|------|-----------|
|
|
108
|
+
| `SessionStart` | Inject a digest of the top durable memories. |
|
|
109
|
+
| `PreCompact` | Flush durable learnings before the window is summarised. |
|
|
110
|
+
| `UserPromptSubmit` | **Contextual recall** — score memories against the prompt and inject the relevant ones, so a stored fact is retrieved at the *decision point*, not only at session start. |
|
|
111
|
+
| `Stop` | **Autonomous capture** — if the user's last message was a durable instruction/correction and it wasn't consolidated this turn, BLOCK the stop with a directive to `memory_remember`. Capture flows through the MCP write path (never racing the snapshot); a `stop_hook_active` guard means at most one nudge per turn. |
|
|
112
|
+
|
|
113
|
+
All four point at one generated, dependency-free hook script
|
|
114
|
+
(`~/.claude/builderforce-memory/bfmem-hook.mjs`, four modes) plus a companion
|
|
115
|
+
skill. Idempotent + `.bak`-safe. Source of truth: `src/install/claude-hooks.ts`
|
|
116
|
+
(`installClaudeCombo` / `bfmemHookSource`).
|
|
117
|
+
|
|
98
118
|
## Quick start — stdio (any process / language)
|
|
99
119
|
|
|
100
120
|
```bash
|
|
@@ -47,7 +47,7 @@ export interface LocalBackendOptions {
|
|
|
47
47
|
/**
|
|
48
48
|
* Optional SSMRuntime for embedding-based recall. Omit for lexical (Jaccard)
|
|
49
49
|
* recall. In the agent-runtime, pass `ssmMemoryService.runtime` here to reuse
|
|
50
|
-
* the already-loaded
|
|
50
|
+
* the already-loaded Evermind runtime instead of standing up a second model.
|
|
51
51
|
*/
|
|
52
52
|
runtime?: unknown;
|
|
53
53
|
/**
|
package/dist/bin/install.js
CHANGED
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import os from "node:os";
|
|
19
19
|
import path from "node:path";
|
|
20
|
+
import nodeFs from "node:fs";
|
|
20
21
|
import { HOSTS } from "../install/hosts.js";
|
|
21
22
|
import { installMemoryServer } from "../install/install.js";
|
|
23
|
+
import { installClaudeCombo } from "../install/claude-hooks.js";
|
|
22
24
|
function argValue(name) {
|
|
23
25
|
const hit = process.argv.find((a) => a.startsWith(`--${name}=`));
|
|
24
26
|
return hit ? hit.slice(name.length + 3) : undefined;
|
|
@@ -73,6 +75,26 @@ for (const r of results) {
|
|
|
73
75
|
if (r.status === "installed" || r.status === "updated")
|
|
74
76
|
wrote += 1;
|
|
75
77
|
}
|
|
78
|
+
// ── Claude Code memory combo (hooks + companion skill) ───────────────────────
|
|
79
|
+
// Registering the server above gives Claude Code the TOOLS; this adds the
|
|
80
|
+
// self-driving behaviour (SessionStart digest, contextual recall, autonomous Stop
|
|
81
|
+
// capture). Only for Claude Code, only when present, and skippable via --no-hooks.
|
|
82
|
+
const claudeResult = results.find((r) => r.host === "claude-code");
|
|
83
|
+
const claudePresent = claudeResult && claudeResult.status !== "unsupported" && claudeResult.status !== "error";
|
|
84
|
+
if (claudePresent && !hasFlag("no-hooks")) {
|
|
85
|
+
try {
|
|
86
|
+
const combo = installClaudeCombo({
|
|
87
|
+
fs: nodeFs,
|
|
88
|
+
claudeDir: path.join(os.homedir(), ".claude"),
|
|
89
|
+
memoryFile,
|
|
90
|
+
});
|
|
91
|
+
const what = combo.addedHooks.length ? `added ${combo.addedHooks.join(", ")}` : "already current";
|
|
92
|
+
process.stdout.write(`✓ Claude Code memory combo: hooks ${what}; recall + autonomous capture wired\n`);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
process.stderr.write(`✗ Claude Code hooks: ${String(err.message ?? err)}\n`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
76
98
|
if (results.length === 0) {
|
|
77
99
|
process.stdout.write("No MCP hosts detected. Pass --host=all to write configs anyway, or --host=cursor (etc.).\n");
|
|
78
100
|
}
|
package/dist/bin/install.js.map
CHANGED
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/bin/install.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,10 +18,13 @@ 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 { hashToken, timingSafeEqualStr, buildTenantIndex, bearerToken, RateLimiter } from "./transports/auth.js";
|
|
21
22
|
export { buildServerSpec, MCP_PACKAGE, MCP_BIN, RUNTIME_PEERS } from "./install/server-spec.js";
|
|
22
23
|
export type { StdioServerSpec, ServerSpecOptions } from "./install/server-spec.js";
|
|
23
24
|
export { installMemoryServer } from "./install/install.js";
|
|
24
25
|
export type { InstallOptions, InstallResult, InstallStatus, HostSelector, FsLike } from "./install/install.js";
|
|
25
26
|
export { HOSTS, SERVER_KEY, findHost } from "./install/hosts.js";
|
|
26
27
|
export type { HostAdapter, HostEnv, ConfigFormat } from "./install/hosts.js";
|
|
28
|
+
export { installClaudeCombo, bfmemHookSource, companionSkillMd, claudeComboPaths } from "./install/claude-hooks.js";
|
|
29
|
+
export type { ClaudeComboPaths } from "./install/claude-hooks.js";
|
|
27
30
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;AAC/D,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGjH,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;AAG7E,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACpH,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -15,8 +15,12 @@ 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
|
+
export { hashToken, timingSafeEqualStr, buildTenantIndex, bearerToken, RateLimiter } from "./transports/auth.js";
|
|
18
19
|
// ── Multi-host installer (wire the stdio server into any MCP-capable agent) ───
|
|
19
20
|
export { buildServerSpec, MCP_PACKAGE, MCP_BIN, RUNTIME_PEERS } from "./install/server-spec.js";
|
|
20
21
|
export { installMemoryServer } from "./install/install.js";
|
|
21
22
|
export { HOSTS, SERVER_KEY, findHost } from "./install/hosts.js";
|
|
23
|
+
// Claude Code "memory combo" — hooks (SessionStart/PreCompact/UserPromptSubmit/Stop)
|
|
24
|
+
// + companion skill that make memory self-driving (contextual recall + autonomous capture).
|
|
25
|
+
export { installClaudeCombo, bfmemHookSource, companionSkillMd, claudeComboPaths } from "./install/claude-hooks.js";
|
|
22
26
|
//# 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;AAE/D,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjH,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;AAEjE,qFAAqF;AACrF,4FAA4F;AAC5F,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code "memory combo" — the hooks + companion skill half of the install.
|
|
3
|
+
*
|
|
4
|
+
* Registering the MCP server (install.ts) gives Claude Code the memory TOOLS.
|
|
5
|
+
* This module adds the behaviour that makes memory SELF-DRIVING rather than
|
|
6
|
+
* advisory, so the agent doesn't have to be reminded to use it:
|
|
7
|
+
*
|
|
8
|
+
* SessionStart → inject a digest of the top durable memories.
|
|
9
|
+
* PreCompact → flush durable learnings before the window is summarised.
|
|
10
|
+
* UserPromptSubmit → CONTEXTUAL recall: score memories against the prompt and
|
|
11
|
+
* inject the relevant ones (so a stored fact is retrieved at
|
|
12
|
+
* the decision point, not just at session start).
|
|
13
|
+
* Stop → AUTONOMOUS capture: detect a durable instruction/correction
|
|
14
|
+
* in the user's last message and, if it wasn't consolidated
|
|
15
|
+
* this turn, BLOCK the stop with a directive to memory_remember
|
|
16
|
+
* (capture flows through the MCP write path — never racing the
|
|
17
|
+
* server's snapshot).
|
|
18
|
+
*
|
|
19
|
+
* All four point at one generated, dependency-free hook script (DRY). The combo
|
|
20
|
+
* is idempotent and backup-safe, matching install.ts.
|
|
21
|
+
*/
|
|
22
|
+
import type { FsLike } from "./install.js";
|
|
23
|
+
/** The single hook script, four modes. `memoryFile` is the JSON snapshot path. */
|
|
24
|
+
export declare function bfmemHookSource(memoryFile: string): string;
|
|
25
|
+
/** The companion skill doc that teaches the recall/remember discipline. */
|
|
26
|
+
export declare function companionSkillMd(): string;
|
|
27
|
+
/** Files/paths the combo manages, derived from the Claude config dir (~/.claude). */
|
|
28
|
+
export interface ClaudeComboPaths {
|
|
29
|
+
claudeDir: string;
|
|
30
|
+
settingsFile: string;
|
|
31
|
+
hookScript: string;
|
|
32
|
+
companionSkill: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function claudeComboPaths(claudeDir: string): ClaudeComboPaths;
|
|
35
|
+
/**
|
|
36
|
+
* Write the hook script + companion skill and register all four hooks in
|
|
37
|
+
* settings.json. Idempotent (skips a hook command already present) and
|
|
38
|
+
* backup-safe (.bak before overwriting settings). Returns the events it added.
|
|
39
|
+
*/
|
|
40
|
+
export declare function installClaudeCombo(opts: {
|
|
41
|
+
fs: FsLike;
|
|
42
|
+
claudeDir: string;
|
|
43
|
+
memoryFile: string;
|
|
44
|
+
}): {
|
|
45
|
+
hookScript: string;
|
|
46
|
+
addedHooks: string[];
|
|
47
|
+
settingsFile: string;
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=claude-hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-hooks.d.ts","sourceRoot":"","sources":["../../src/install/claude-hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA6I1D;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,IAAI,MAAM,CA0BzC;AAED,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAOpE;AASD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACtB,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAmCrE"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code "memory combo" — the hooks + companion skill half of the install.
|
|
3
|
+
*
|
|
4
|
+
* Registering the MCP server (install.ts) gives Claude Code the memory TOOLS.
|
|
5
|
+
* This module adds the behaviour that makes memory SELF-DRIVING rather than
|
|
6
|
+
* advisory, so the agent doesn't have to be reminded to use it:
|
|
7
|
+
*
|
|
8
|
+
* SessionStart → inject a digest of the top durable memories.
|
|
9
|
+
* PreCompact → flush durable learnings before the window is summarised.
|
|
10
|
+
* UserPromptSubmit → CONTEXTUAL recall: score memories against the prompt and
|
|
11
|
+
* inject the relevant ones (so a stored fact is retrieved at
|
|
12
|
+
* the decision point, not just at session start).
|
|
13
|
+
* Stop → AUTONOMOUS capture: detect a durable instruction/correction
|
|
14
|
+
* in the user's last message and, if it wasn't consolidated
|
|
15
|
+
* this turn, BLOCK the stop with a directive to memory_remember
|
|
16
|
+
* (capture flows through the MCP write path — never racing the
|
|
17
|
+
* server's snapshot).
|
|
18
|
+
*
|
|
19
|
+
* All four point at one generated, dependency-free hook script (DRY). The combo
|
|
20
|
+
* is idempotent and backup-safe, matching install.ts.
|
|
21
|
+
*/
|
|
22
|
+
import nodePath from "node:path";
|
|
23
|
+
/** The single hook script, four modes. `memoryFile` is the JSON snapshot path. */
|
|
24
|
+
export function bfmemHookSource(memoryFile) {
|
|
25
|
+
return `#!/usr/bin/env node
|
|
26
|
+
/**
|
|
27
|
+
* builderforce-memory hook — generated by builderforce-memory-install. Dependency-free:
|
|
28
|
+
* reads only the JSON snapshot, never stands up the MCP server.
|
|
29
|
+
* --session SessionStart: inject a small digest of durable memories.
|
|
30
|
+
* --precompact PreCompact: remind to flush learnings before summarisation.
|
|
31
|
+
* --recall UserPromptSubmit: contextual recall — inject memories relevant to the prompt.
|
|
32
|
+
* --capture Stop: autonomous consolidation — block the stop until a detected
|
|
33
|
+
* durable correction is persisted via memory_remember.
|
|
34
|
+
*/
|
|
35
|
+
import fs from "node:fs";
|
|
36
|
+
|
|
37
|
+
const FILE = ${JSON.stringify(memoryFile)};
|
|
38
|
+
const mode = process.argv[2] || "--session";
|
|
39
|
+
|
|
40
|
+
function readStdin() {
|
|
41
|
+
try { return fs.readFileSync(0, "utf8"); } catch { return ""; }
|
|
42
|
+
}
|
|
43
|
+
function loadEntries() {
|
|
44
|
+
try { const p = JSON.parse(fs.readFileSync(FILE, "utf8")); return Array.isArray(p) ? p : []; }
|
|
45
|
+
catch { return []; }
|
|
46
|
+
}
|
|
47
|
+
function tokens(s) {
|
|
48
|
+
return String(s || "").toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (mode === "--precompact") {
|
|
52
|
+
process.stdout.write(
|
|
53
|
+
"[builderforce-memory] Context is about to be compacted. Persist any durable learnings from this session — decisions, fixes, project facts, user preferences — by calling memory_remember with concise one-line facts. Recall them later with memory_recall instead of re-reading files.",
|
|
54
|
+
);
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (mode === "--recall") {
|
|
59
|
+
let prompt = "";
|
|
60
|
+
try { const j = JSON.parse(readStdin() || "{}"); prompt = typeof j.prompt === "string" ? j.prompt : ""; } catch { /* no input */ }
|
|
61
|
+
if (!prompt) process.exit(0);
|
|
62
|
+
const entries = loadEntries();
|
|
63
|
+
if (entries.length === 0) process.exit(0);
|
|
64
|
+
const q = new Set(tokens(prompt));
|
|
65
|
+
if (q.size === 0) process.exit(0);
|
|
66
|
+
const scored = entries
|
|
67
|
+
.map((e) => {
|
|
68
|
+
const hay = new Set(tokens([e.key, e.content, (e.tags || []).join(" ")].join(" ")));
|
|
69
|
+
let score = 0;
|
|
70
|
+
for (const t of q) if (hay.has(t)) score++;
|
|
71
|
+
return { e, score };
|
|
72
|
+
})
|
|
73
|
+
.filter((x) => x.score >= 2)
|
|
74
|
+
.sort((a, b) => b.score - a.score)
|
|
75
|
+
.slice(0, 3);
|
|
76
|
+
if (scored.length === 0) process.exit(0);
|
|
77
|
+
const lines = scored.map((x) => "• " + x.e.key + ": " + String(x.e.content || "").slice(0, 240)).join("\\n");
|
|
78
|
+
process.stdout.write(
|
|
79
|
+
"[builderforce-memory] Relevant stored memories for this request — use these instead of re-deriving or re-reading:\\n" + lines,
|
|
80
|
+
);
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (mode === "--capture") {
|
|
85
|
+
let input = {};
|
|
86
|
+
try { input = JSON.parse(readStdin() || "{}"); } catch { /* no input */ }
|
|
87
|
+
if (input.stop_hook_active) process.exit(0);
|
|
88
|
+
const tpath = input.transcript_path;
|
|
89
|
+
if (!tpath || typeof tpath !== "string") process.exit(0);
|
|
90
|
+
|
|
91
|
+
let raw = "";
|
|
92
|
+
try {
|
|
93
|
+
const stat = fs.statSync(tpath);
|
|
94
|
+
const want = Math.min(stat.size, 400000);
|
|
95
|
+
const fd = fs.openSync(tpath, "r");
|
|
96
|
+
const buf = Buffer.alloc(want);
|
|
97
|
+
fs.readSync(fd, buf, 0, want, stat.size - want);
|
|
98
|
+
fs.closeSync(fd);
|
|
99
|
+
raw = buf.toString("utf8");
|
|
100
|
+
} catch { process.exit(0); }
|
|
101
|
+
|
|
102
|
+
const msgs = [];
|
|
103
|
+
for (const ln of raw.split("\\n")) { if (ln.trim()) { try { msgs.push(JSON.parse(ln)); } catch { /* partial */ } } }
|
|
104
|
+
|
|
105
|
+
let lastUserText = "";
|
|
106
|
+
let rememberedThisTurn = false;
|
|
107
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
108
|
+
const m = msgs[i] || {};
|
|
109
|
+
const inner = m.message || m;
|
|
110
|
+
const role = inner.role;
|
|
111
|
+
const content = inner.content;
|
|
112
|
+
if (role === "assistant" && !lastUserText) {
|
|
113
|
+
if (Array.isArray(content)) for (const c of content) {
|
|
114
|
+
if (c && c.type === "tool_use" && typeof c.name === "string" && c.name.includes("memory_remember")) rememberedThisTurn = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (role === "user" && !lastUserText) {
|
|
118
|
+
let txt = "";
|
|
119
|
+
if (typeof content === "string") txt = content;
|
|
120
|
+
else if (Array.isArray(content)) for (const c of content) {
|
|
121
|
+
if (typeof c === "string") txt += c + " ";
|
|
122
|
+
else if (c && c.type === "text" && typeof c.text === "string") txt += c.text + " ";
|
|
123
|
+
}
|
|
124
|
+
if (txt.trim()) lastUserText = txt;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!lastUserText || rememberedThisTurn) process.exit(0);
|
|
128
|
+
|
|
129
|
+
const lower = lastUserText.toLowerCase();
|
|
130
|
+
const SIGNALS = [
|
|
131
|
+
"you have to", "you should", "you need to", "you must", "from now on", "going forward",
|
|
132
|
+
"always ", "never ", "make sure", "remember to", "don't forget", "do not forget",
|
|
133
|
+
"i keep telling", "i already told", "shouldn't have to", "keep having to remind",
|
|
134
|
+
"you forgot", "you keep", "that's wrong", "you're wrong", "this is wrong", "stop ",
|
|
135
|
+
];
|
|
136
|
+
const hit = SIGNALS.find((s) => lower.includes(s));
|
|
137
|
+
if (!hit) process.exit(0);
|
|
138
|
+
|
|
139
|
+
const snippet = lastUserText.trim().slice(0, 240);
|
|
140
|
+
const reason =
|
|
141
|
+
"[builderforce-memory] A durable instruction/correction was detected in the user's last message and you did NOT consolidate it this turn. " +
|
|
142
|
+
"Before stopping, call memory_remember with a concise, stable-keyed fact capturing it (reuse the same key to replace a superseded one, or memory_forget it). " +
|
|
143
|
+
"User said: '" + snippet + "'. If it is genuinely not durable (a one-off, or already stored), briefly say why and then stop.";
|
|
144
|
+
process.stdout.write(JSON.stringify({ decision: "block", reason }));
|
|
145
|
+
process.exit(0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const entries = loadEntries();
|
|
149
|
+
if (entries.length === 0) {
|
|
150
|
+
process.stdout.write(
|
|
151
|
+
"[builderforce-memory] Active (memory empty). Store durable facts with memory_remember and retrieve them with memory_recall instead of re-reading files or chat history — that is how token usage stays low.",
|
|
152
|
+
);
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
const top = entries
|
|
156
|
+
.map((e) => ({ key: e.key, content: String(e.content ?? ""), importance: typeof e.importance === "number" ? e.importance : 0.5 }))
|
|
157
|
+
.sort((a, b) => b.importance - a.importance)
|
|
158
|
+
.slice(0, 8)
|
|
159
|
+
.map((e) => "• " + e.key + ": " + e.content.slice(0, 200))
|
|
160
|
+
.join("\\n");
|
|
161
|
+
process.stdout.write(
|
|
162
|
+
"[builderforce-memory] Active — token-saving memory loaded. Before re-reading files or history, call memory_recall. Store durable facts with memory_remember.\\nTop memories:\\n" + top,
|
|
163
|
+
);
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
/** The companion skill doc that teaches the recall/remember discipline. */
|
|
167
|
+
export function companionSkillMd() {
|
|
168
|
+
return `---
|
|
169
|
+
name: builderforce-memory
|
|
170
|
+
description: Token-saving persistent memory. Recall durable facts (decisions, fixes, project context, user preferences) instead of re-reading files or chat history, and remember new durable facts as you learn them.
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
# builderforce-memory — the conductor
|
|
174
|
+
|
|
175
|
+
Persistent memory backed by the \`builderforce-memory\` MCP server. The point is
|
|
176
|
+
**token reduction**: pull a small relevant slice from memory instead of re-reading
|
|
177
|
+
the full file/history. The install also wires hooks so this is largely automatic:
|
|
178
|
+
the SessionStart digest, contextual recall on each prompt, and an autonomous Stop
|
|
179
|
+
capture that won't let a durable correction go unrecorded.
|
|
180
|
+
|
|
181
|
+
## Tools
|
|
182
|
+
- \`memory_recall(query, [k])\` — top-K relevant memories. First move on a task.
|
|
183
|
+
- \`memory_get(key)\` — exact lookup by key.
|
|
184
|
+
- \`memory_recall_by_tag(tag)\` — everything under a tag.
|
|
185
|
+
- \`memory_remember(key, content, [tags], [importance])\` — store ONE durable fact.
|
|
186
|
+
- \`memory_forget(key)\` — drop a superseded fact.
|
|
187
|
+
|
|
188
|
+
## Discipline
|
|
189
|
+
Reuse a stable key to REPLACE a fact (write-through), don't append duplicates.
|
|
190
|
+
Store decisions, non-obvious fixes, project constraints, user preferences — one
|
|
191
|
+
tight line each. Not things git already records or one-off turn details.
|
|
192
|
+
`;
|
|
193
|
+
}
|
|
194
|
+
export function claudeComboPaths(claudeDir) {
|
|
195
|
+
return {
|
|
196
|
+
claudeDir,
|
|
197
|
+
settingsFile: nodePath.join(claudeDir, "settings.json"),
|
|
198
|
+
hookScript: nodePath.join(claudeDir, "builderforce-memory", "bfmem-hook.mjs"),
|
|
199
|
+
companionSkill: nodePath.join(claudeDir, "skills", "builderforce-memory", "SKILL.md"),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const HOOK_EVENTS = [
|
|
203
|
+
{ event: "SessionStart", mode: "--session" },
|
|
204
|
+
{ event: "PreCompact", mode: "--precompact" },
|
|
205
|
+
{ event: "UserPromptSubmit", mode: "--recall" },
|
|
206
|
+
{ event: "Stop", mode: "--capture" },
|
|
207
|
+
];
|
|
208
|
+
/**
|
|
209
|
+
* Write the hook script + companion skill and register all four hooks in
|
|
210
|
+
* settings.json. Idempotent (skips a hook command already present) and
|
|
211
|
+
* backup-safe (.bak before overwriting settings). Returns the events it added.
|
|
212
|
+
*/
|
|
213
|
+
export function installClaudeCombo(opts) {
|
|
214
|
+
const { fs, memoryFile } = opts;
|
|
215
|
+
const paths = claudeComboPaths(opts.claudeDir);
|
|
216
|
+
// 1) Hook script.
|
|
217
|
+
const hookDir = nodePath.dirname(paths.hookScript);
|
|
218
|
+
if (!fs.existsSync(hookDir))
|
|
219
|
+
fs.mkdirSync(hookDir, { recursive: true });
|
|
220
|
+
fs.writeFileSync(paths.hookScript, bfmemHookSource(memoryFile));
|
|
221
|
+
// 2) Companion skill.
|
|
222
|
+
const skillDir = nodePath.dirname(paths.companionSkill);
|
|
223
|
+
if (!fs.existsSync(skillDir))
|
|
224
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
225
|
+
fs.writeFileSync(paths.companionSkill, companionSkillMd());
|
|
226
|
+
// 3) Merge hooks into settings.json.
|
|
227
|
+
let settings = {};
|
|
228
|
+
if (fs.existsSync(paths.settingsFile)) {
|
|
229
|
+
try {
|
|
230
|
+
settings = JSON.parse(fs.readFileSync(paths.settingsFile, "utf8"));
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
throw new Error("existing settings.json is not valid JSON");
|
|
234
|
+
}
|
|
235
|
+
fs.copyFileSync(paths.settingsFile, `${paths.settingsFile}.bak`);
|
|
236
|
+
}
|
|
237
|
+
const hooks = (settings.hooks && typeof settings.hooks === "object" ? settings.hooks : {});
|
|
238
|
+
const added = [];
|
|
239
|
+
for (const { event, mode } of HOOK_EVENTS) {
|
|
240
|
+
const command = `node "${paths.hookScript}" ${mode}`;
|
|
241
|
+
const arr = (Array.isArray(hooks[event]) ? hooks[event] : (hooks[event] = []));
|
|
242
|
+
const exists = arr.some((g) => (g.hooks || []).some((h) => h.command === command));
|
|
243
|
+
if (!exists) {
|
|
244
|
+
arr.push({ hooks: [{ type: "command", command }] });
|
|
245
|
+
added.push(event);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
settings.hooks = hooks;
|
|
249
|
+
const settingsDir = nodePath.dirname(paths.settingsFile);
|
|
250
|
+
if (!fs.existsSync(settingsDir))
|
|
251
|
+
fs.mkdirSync(settingsDir, { recursive: true });
|
|
252
|
+
fs.writeFileSync(paths.settingsFile, `${JSON.stringify(settings, null, 2)}\n`);
|
|
253
|
+
return { hookScript: paths.hookScript, addedHooks: added, settingsFile: paths.settingsFile };
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=claude-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-hooks.js","sourceRoot":"","sources":["../../src/install/claude-hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,QAAQ,MAAM,WAAW,CAAC;AAGjC,kFAAkF;AAClF,MAAM,UAAU,eAAe,CAAC,UAAkB;IAC9C,OAAO;;;;;;;;;;;;eAYI,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+HxC,CAAC;AACF,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,gBAAgB;IAC5B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;CAwBV,CAAC;AACF,CAAC;AAUD,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAC9C,OAAO;QACH,SAAS;QACT,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC;QACvD,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,qBAAqB,EAAE,gBAAgB,CAAC;QAC7E,cAAc,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,qBAAqB,EAAE,UAAU,CAAC;KACxF,CAAC;AACN,CAAC;AAED,MAAM,WAAW,GAAG;IAChB,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,EAAE;IAC5C,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE;IAC7C,EAAE,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,UAAU,EAAE;IAC/C,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE;CAC9B,CAAC;AAEX;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAIlC;IACG,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAChC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAE/C,kBAAkB;IAClB,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACnD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxE,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,UAAU,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC;IAEhE,sBAAsB;IACtB,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IACxD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1E,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,cAAc,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAE3D,qCAAqC;IACrC,IAAI,QAAQ,GAA4B,EAAE,CAAC;IAC3C,IAAI,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC;YAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,YAAY,EAAE,MAAM,CAAC,CAA4B,CAAC;QAAC,CAAC;QACtG,MAAM,CAAC;YAAC,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAAC,CAAC;QACtE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,YAAY,EAAE,GAAG,KAAK,CAAC,YAAY,MAAM,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,KAAK,IAAI,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAA4B,CAAC;IACtH,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,SAAS,KAAK,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAkE,CAAC;QAChJ,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC;QACnF,IAAI,CAAC,MAAM,EAAE,CAAC;YAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAAC,CAAC;IAC5F,CAAC;IACD,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACzD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChF,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,YAAY,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAE/E,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;AACjG,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* transports/auth.ts — auth + rate-limiting primitives for the HTTP transport.
|
|
3
|
+
*
|
|
4
|
+
* The networked MCP surface is multi-tenant: one bearer token must map to ONE
|
|
5
|
+
* tenant's memory namespace, the comparison must not leak the secret through a
|
|
6
|
+
* timing side channel, and a noisy or hostile caller must not be able to hammer
|
|
7
|
+
* the server. These helpers provide exactly those three pieces, dependency-free
|
|
8
|
+
* (node:crypto only).
|
|
9
|
+
*/
|
|
10
|
+
/** SHA-256 hex digest of a token — the stable, non-reversible lookup key. */
|
|
11
|
+
export declare function hashToken(token: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Constant-time string equality. Hashes both sides to fixed-length digests
|
|
14
|
+
* first, so neither the comparison nor the input length leaks the secret.
|
|
15
|
+
*/
|
|
16
|
+
export declare function timingSafeEqualStr(a: string, b: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Build a tenant lookup keyed by token HASH (never the raw token). Resolving a
|
|
19
|
+
* presented token is a hash + map lookup — no per-character secret comparison,
|
|
20
|
+
* so there is no early-return timing channel on the token value.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildTenantIndex<B>(tenants: Record<string, B>): Map<string, B>;
|
|
23
|
+
/** Fixed-window rate limiter. Memory-bounded by pruning expired windows. */
|
|
24
|
+
export declare class RateLimiter {
|
|
25
|
+
private readonly windowMs;
|
|
26
|
+
private readonly max;
|
|
27
|
+
private readonly now;
|
|
28
|
+
private readonly _hits;
|
|
29
|
+
constructor(windowMs: number, max: number, now?: () => number);
|
|
30
|
+
/** Returns true when the call is allowed; false when the window is exhausted. */
|
|
31
|
+
check(key: string): boolean;
|
|
32
|
+
private _prune;
|
|
33
|
+
}
|
|
34
|
+
/** Extract the bearer token from an Authorization header value, or undefined. */
|
|
35
|
+
export declare function bearerToken(header: string | string[] | undefined): string | undefined;
|
|
36
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/transports/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAIhE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAM9E;AAED,4EAA4E;AAC5E,qBAAa,WAAW;IAIhB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IALxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyD;gBAG1D,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,MAAM,MAAyB;IAGzD,iFAAiF;IACjF,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAa3B,OAAO,CAAC,MAAM;CAKjB;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAKrF"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* transports/auth.ts — auth + rate-limiting primitives for the HTTP transport.
|
|
3
|
+
*
|
|
4
|
+
* The networked MCP surface is multi-tenant: one bearer token must map to ONE
|
|
5
|
+
* tenant's memory namespace, the comparison must not leak the secret through a
|
|
6
|
+
* timing side channel, and a noisy or hostile caller must not be able to hammer
|
|
7
|
+
* the server. These helpers provide exactly those three pieces, dependency-free
|
|
8
|
+
* (node:crypto only).
|
|
9
|
+
*/
|
|
10
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
11
|
+
/** SHA-256 hex digest of a token — the stable, non-reversible lookup key. */
|
|
12
|
+
export function hashToken(token) {
|
|
13
|
+
return createHash("sha256").update(token, "utf8").digest("hex");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Constant-time string equality. Hashes both sides to fixed-length digests
|
|
17
|
+
* first, so neither the comparison nor the input length leaks the secret.
|
|
18
|
+
*/
|
|
19
|
+
export function timingSafeEqualStr(a, b) {
|
|
20
|
+
const ha = createHash("sha256").update(a, "utf8").digest();
|
|
21
|
+
const hb = createHash("sha256").update(b, "utf8").digest();
|
|
22
|
+
return timingSafeEqual(ha, hb);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build a tenant lookup keyed by token HASH (never the raw token). Resolving a
|
|
26
|
+
* presented token is a hash + map lookup — no per-character secret comparison,
|
|
27
|
+
* so there is no early-return timing channel on the token value.
|
|
28
|
+
*/
|
|
29
|
+
export function buildTenantIndex(tenants) {
|
|
30
|
+
const index = new Map();
|
|
31
|
+
for (const [token, backend] of Object.entries(tenants)) {
|
|
32
|
+
if (token)
|
|
33
|
+
index.set(hashToken(token), backend);
|
|
34
|
+
}
|
|
35
|
+
return index;
|
|
36
|
+
}
|
|
37
|
+
/** Fixed-window rate limiter. Memory-bounded by pruning expired windows. */
|
|
38
|
+
export class RateLimiter {
|
|
39
|
+
windowMs;
|
|
40
|
+
max;
|
|
41
|
+
now;
|
|
42
|
+
_hits = new Map();
|
|
43
|
+
constructor(windowMs, max, now = () => Date.now()) {
|
|
44
|
+
this.windowMs = windowMs;
|
|
45
|
+
this.max = max;
|
|
46
|
+
this.now = now;
|
|
47
|
+
}
|
|
48
|
+
/** Returns true when the call is allowed; false when the window is exhausted. */
|
|
49
|
+
check(key) {
|
|
50
|
+
const t = this.now();
|
|
51
|
+
const entry = this._hits.get(key);
|
|
52
|
+
if (!entry || t >= entry.resetAt) {
|
|
53
|
+
if (this._hits.size > 10_000)
|
|
54
|
+
this._prune(t);
|
|
55
|
+
this._hits.set(key, { count: 1, resetAt: t + this.windowMs });
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (entry.count >= this.max)
|
|
59
|
+
return false;
|
|
60
|
+
entry.count++;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
_prune(t) {
|
|
64
|
+
for (const [k, v] of this._hits) {
|
|
65
|
+
if (t >= v.resetAt)
|
|
66
|
+
this._hits.delete(k);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Extract the bearer token from an Authorization header value, or undefined. */
|
|
71
|
+
export function bearerToken(header) {
|
|
72
|
+
const value = Array.isArray(header) ? header[0] : header;
|
|
73
|
+
if (!value)
|
|
74
|
+
return undefined;
|
|
75
|
+
const m = /^Bearer\s+(.+)$/i.exec(value.trim());
|
|
76
|
+
return m ? m[1].trim() : undefined;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/transports/auth.ts"],"names":[],"mappings":"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"}
|