npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bli-cockpit/memory-mcp

v0.1.35

Published

BLI Memory — an MCP server for the memory layer BLI owns (save, search, update, forget).

Readme

@bli-cockpit/memory-mcp — BLI Memory

An MCP server for BLI Memory, the memory layer BLI owns: one table in our own Postgres, four operations, no vendor in the path.

Prompt hooks also record content-free outcome, duration and actual vector-cache observations for cockpit ops --memory. These are hook-body timings, not host acceptance or retrieval-quality scores. Explicit experiments use bli-memory-mcp hook prompt --probe; stderr confirms probe stats_excluded and neither ordinary-use counter records the probe. See the repository's docs/runbooks/memory-hook-measurement.md for coverage and timing boundaries.

Four tools:

| Tool | What it does | Annotations | |---|---|---| | search_memory | Search one space by meaning, by keyword, and by recency at once. | read-only, idempotent | | save_memory | Remember something durable. customId upserts. extract: true distils atomic facts first. | additive | | update_memory | Replace what a memory says, keeping the old version as history. | additive | | forget_memory | Tombstone a memory by id, by exact text, or by the closest match ≥ 0.85. | destructive |

search_memory and save_memory keep the names and argument names the hosted Supermemory MCP used, so an agent, a skill or a habit pointed at those words keeps working. update_memory is new — the vendor had no update tool at all, and correction there meant forget-then-save, which loses the link between the two.

Plus three Claude Code hooks, on the same bin — see below.

Install

Nobody installs this by hand. It ships as a dependency of @bli-cockpit/cli, and cockpit memory install (which cockpit doctor and the daily sync tick both run) registers it with both hosts. A standalone install is for development:

npm i -g @bli-cockpit/memory-mcp

The server prints the exact block each host wants, so nothing has to be retyped:

bli-memory-mcp --print-config           # both, with headings
bli-memory-mcp --print-config=claude    # the install contract (see below)
bli-memory-mcp --print-config=codex     # ~/.codex/config.toml

--print-config --claude prints the install contract, bli-memory-install-config.v1 — the MCP server entry and the three hooks with their timeouts, because this bin is the only thing that knows which hook subcommands it implements:

{
  "schema": "bli-memory-install-config.v1",
  "server_id": "bli-memory",
  "mcp_server": { "command": "bli-memory-mcp", "args": [], "env": {} },
  "hooks": [
    { "event": "SessionStart",     "command": "bli-memory-mcp hook session-start", "timeout_seconds": 30 },
    { "event": "UserPromptSubmit", "command": "bli-memory-mcp hook prompt",        "timeout_seconds": 5 },
    { "event": "Stop",             "command": "bli-memory-mcp hook stop",          "timeout_seconds": 30 }
  ],
  "permissions_allow": ["mcp__bli-memory__search_memory"]
}

command is the bare bin name; cockpit memory install replaces it with the absolute path it resolved on that machine, in every one of those strings. It is never npx -y @bli-cockpit/memory-mcp, which would re-resolve the package from the registry on every agent launch.

By hand: mcp_server goes under mcpServers.bli-memory in ~/.claude.json, each hook under hooks.<event> in ~/.claude/settings.json, and the bin path has to be absolute or on your PATH.

The three hooks

bli-memory-mcp hook session-start   # SessionStart
bli-memory-mcp hook prompt          # UserPromptSubmit
bli-memory-mcp hook stop            # Stop

Each reads Claude Code's JSON payload on stdin and:

| Hook | Reads | Calls | Prints | |---|---|---|---| | session-start | cwd | POST /api/memory/v4/profile | <bli-memory-context> with User Profile + Recent Context, per line | | prompt | prompt | POST /api/memory/search (top 5, ≥ 0.55 similarity) — unless the prompt is trivial, see below | <bli-memory-recall>, per line | | stop | transcript_path | POST /api/memory/save in extract mode — the door answers 202 queued and distils after the response (BLI-3730) | nothing, ever |

Four rules they all keep, because a hook runs on every turn of every session on every machine:

  • Never block. Each has a deadline below the timeout the installer registers — 8 s / 2 s / 15 s against 30 / 5 / 30 — including a hard deadline on the stdin read. On any deadline it prints nothing and gives the host its process back. The prompt hook's budget is sized to the PERSON waiting with a prompt already typed, not to the host's 5 s ceiling (BLI-3730): nobody notices a recall that gives up under its timeout, everybody notices a turn that stalls before the model starts.
  • Always exit 0. Claude Code reads exit 2 as "block this prompt" and any other non-zero as an error it shows the person. An unpaired machine, an outage, a malformed payload: all quiet in the transcript.
  • Print nothing, or one whole block. No "no memories found" line — the model would reason about it, and an empty record and an unreachable store would look the same to it. Zero hits is data and prints nothing.
  • Do not search for a prompt with nothing in it (BLI-3881, hooks/trivial-prompt.ts). "ok", "continue", "y", "thanks", anything under twelve characters, and any /slash command are answered with skipped {"reason":"trivial_prompt"} — exit 0, nothing printed, no door call. Those turns carry no subject to recall against, so the embed is of a stopword and the floor throws the neighbours away at the far end, after the person has already waited. The rule is a CLOSED LIST plus a length, decided from the text alone, with one override: a short prompt naming an identifier (BLI-3580, search.ts, v0.2.83) is always searched, because that is the single best query the lexical channel receives. Each skip is counted as skipped_trivial in the hook stats file, so a hook that stops searching can never be mistaken for a hook that broke.
  • One stderr receipt per run, on every branch, one of three shapes (BLI-3664, hooks/log-line.ts): [bli-memory] prompt ok {"hits":0,"elapsed_ms":140,"deadline_ms":2000}, [bli-memory] prompt skipped {"reason":"trivial_prompt"}, or [bli-memory] prompt failed {"reason":"dashboard_unreachable","elapsed_ms":1502,"deadline_ms":2000}. reason on skipped/failed is always one of a closed set — the four network reasons timeout, http_<status>, dashboard_unreachable, bad_response (Tower answered with a body that was not readable JSON); the six StdinFailure reasons no_stdin, stdin_empty, stdin_timeout, stdin_too_large, stdin_unreadable, stdin_malformed — each its own literal, never folded into bad_response, because a malformed payload from the host never touched the network (BLI-3700); unpaired, no_prompt, trivial_prompt (BLI-3881, below), plus three Stop-only local skips (reentrant, no_transcript, redaction_failed) — never a stack trace, never Tower's own error text, never a prompt, a memory, a path or a token. A reason toLogReason has not been taught becomes unknown_internal:<name>, never a silent bad_response. A failed stop also gets a one-line systemMessage (the same JSON-on-stdout shape the Supermemory plugin's own banner uses), because Claude Code does not surface a hook's stderr anywhere a person reads it and a failed save means the turn was not kept. A failed RECALL is silent (BLI-3730): it costs the person nothing and a banner on every prompt during an outage is noise they cannot act on.

hook stop sends the last user/assistant exchange (at most 12 KB, tail-read so a 40 MB transcript costs nothing) through the collector's own redactor — redactSecretLikeContent from @bli-cockpit/telemetry-core, the same function that masks every uploaded transcript — and lets the librarian's extract/reconcile decide what is worth keeping. A re-entrant Stop (stop_hook_active: true) saves nothing.

Since BLI-3730 the door queues that work: it writes one durable memory_extract_jobs row, answers 202, and distils after the response, so the hook reports ok {"chars":…,"masked":…,"queued":true} and never claims a save it cannot see. saved is absent on that branch rather than 0 — "accepted, pending" and "nothing was worth keeping" must never read as the same line. The outcome lands on the job row and in Tower's own [bli-memory] extract job done {added, updated, unchanged, conflicted, extract_ms, reconcile_ms}.

The resident daemon (BLI-3884)

bli-memory-mcp daemon      # one per OS user; the prompt hook starts it for you

A prompt hook is a fresh process that opens a fresh TLS connection and proves a device token before it asks anything: 107 ms of boot, 131-709 ms of connection setup and 65-165 ms of gate, none of it the question. The daemon holds all three warm and answers prompt and session-start by running the same code the hook runs — there is no second implementation of recall.

| | | |---|---| | endpoint | ~/.local/state/bli-cockpit/memory-daemon.sock (0600), or \\.\pipe\bli-memory-<user> on Windows | | protocol | one line of JSON in, one out: {event, payload, cwd}{stdout, outcome}; ping asks if it is alive | | the token | never on the wire — the daemon reads this machine's session file itself | | the fallback | any failure at all → the hook takes the direct path, unchanged, and says why on stderr | | the connect budget | 50 ms, inside the hook's own unchanged HOOK_BUDGETS deadline | | who starts it | the prompt hook, detached, at most once an hour per machine — no launchd job, no scheduled task | | when it stops | 2 hours idle, or when a second instance finds it already running (that one exits 0, daemon_already_running) | | an upgrade | every request carries the caller's version; a daemon that does not match stands down and the hook starts a current one | | off switch | BLI_MEMORY_DAEMON=0 |

Measured on an Apple Silicon Mac against production, 20 real prompts each way (2026-09-06): p50 934 ms direct → 734 ms via the daemon. The remainder is ~92 ms of Node boot the host imposes plus the door's own 468-599 ms; the ticket's 400 ms target is a server-side question, not a daemon one.

Every hook run records which route answered — via: daemon or via: direct on the stderr receipt, in the hourly counts file, on the heartbeat, and as one line per machine on cockpit ops --memory. cockpit doctor carries a memory-daemon row that is present-or-absent-and-named and never red, and cockpit memory status says whether the socket answers.

Auth

None to set up. The server reads the collector's existing paired session at ~/.config/bli-cockpit/session.json — the one cockpit login wrote — and sends that device token to Tower's memory doors. If the machine is not paired it says so, in one sentence, on stderr, and exits 1.

Optional environment overrides:

| Variable | Effect | |---|---| | BLI_MEMORY_DEVICE_TOKEN | Use this token instead of the session file (CI, containers). | | BLI_MEMORY_DASHBOARD_URL | Point at another Tower deployment. | | BLI_MEMORY_CONTAINER_TAG | Force the memory space, instead of deriving it. | | BLI_MEMORY_ISOLATE_WORKTREES=1 | Give each worktree its own space rather than sharing the repo's. |

The space a memory lands in

The container tag is computed exactly the way the old vendor plugin computed it — repo_<name>__<sha256(normalised git remote)[0:16]> — because that is what makes the switch invisible: the memories a repo had yesterday are the memories it has today. The remote is the identity, so every worktree of one repo shares a space. A folder that is not a git repository still gets its own space (dir_<name>__<sha16>), because a folder is a legitimate workspace.

One repository can still have TWO tags on the shelf — the vendor's, kept verbatim when its memories were imported, and the one derived above. Tower reconciles them server-side (agent_memory_container_aliases, BLI-3620): search_memory is answered from every tag that names the same space, and save_memory lands in the canonical one. That is why a saved memory can report a space you did not pass — the tool names where the row actually went, not what it asked for.

The stdio contract

Search text separates remembered facts from Tower documents, messages and issues. Tower matches include their source, ID, full supporting text and an openable link when supplied. Cosine similarity is labelled only when a memory row supplies it; a Tower fused score or missing value is never shown as zero percent similarity. structuredContent retains the original rows and scope.

stdout carries the JSON-RPC stream and nothing else. Every operational line — the startup banner, an auth failure, a warning — goes to stderr. The one exception is --print-config, which prints a config block to stdout and exits without opening a transport.

Two things worth knowing

Zero matches is data; a transport failure is not. A search that found nothing returns text saying the record is silent. A search that could not run returns isError and names why. An agent that cannot tell those apart will confidently report the first when the second happened.

The old plugin's auto-approve regex does not match this server. The vendor's Claude Code plugin ships a PreToolUse hook matching ^mcp__(?:plugin_supermemory_|claude_ai_)?supermemory__(.+)$ and auto-allowing its read-only tools. Our server is named bli-memory, so tool ids read mcp__bli-memory__search_memory and that hook does not fire — reads will prompt for permission until BLI ships its own plugin (with its own approve hook) through bli-agents-marketplace. Renaming this server to contain "supermemory" to inherit somebody else's auto-approve would be a worse trade than one extra prompt.

Development

npm run build --workspace=@bli-cockpit/memory-mcp
npm test  --workspace=@bli-cockpit/memory-mcp

Publish: npm publish --workspace=@bli-cockpit/memory-mcp --access public --tag latest.

Architecture, the trap list and the trial plan: docs/architecture/agent-memory.md.

Live A/B: agent experience

Every BLI Memory prompt and session-start recall line and recall banner carries [bli]; Supermemory uses . Copy the observed marker into the experience line. --store both prints [bli]+◪; it is a joint opinion, not a win credited to each store. Hook budgets are unchanged.

Run cockpit memory log win --store bli "The dated decision answered the question directly" (or MCP memory_experience with store, verdict, reason). Verdicts are win, loss, noise; stores are bli, supermemory, both. Reasons must be one line, at most 500 characters: opinions about tools only, never prompts, memory bodies or secrets. --reason-stdin accepts the reason on stdin; --json returns the entry ID, local receipt and shipped status.

The append-only ~/.codex/AGENT-EXPERIENCE.md format is - YYYY-MM-DD HH:MM ICT · <agent> · <project> · <store> <marker> · <verdict>: <reason>. Each receipt has a UUID sidecar in ~/.codex/agent-experience-outbox/ with shipped: false until Tower acknowledges it. A failed request leaves the receipt queued. Sync ships at most 20 entries per tick, stops on failure and never fails collection. A lost response is retried under the same UUID.

POST /api/memory/experience accepts {id?, store, verdict, reason, created_at?} with a signed-in cookie or paired device token. Person and device are derived from authentication. It returns 201 for insertion, 200 for an identical retry, 400 for invalid input, 409 for conflicting UUID content and 502 for a failed insert. The timestamp preserves when an offline opinion was recorded. Authenticated people can insert their own and read all opinions; only the service role can update or delete. RLS is enabled and forced.

cockpit ops --memory and GET /api/ops/memory-usage report EXPERIENCE: 7/30-day rolling totals by store and person and the last five reasons per store verbatim within the 30-day window. MCP ops_status(memory: true) and JARVIS readOps expose the same aggregation. The reader pages in timestamp/UUID order, counts requested, returned and unique rows, and names capped or failed reads. Multiple requests are not a database snapshot. This is self-reported experience; counts do not themselves establish quality, unbiased sampling or cost.

Edward's decision rule: "if BLI Memory wins the experience log for two weeks and stays ten times cheaper, kill the subscription"