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

@python1/verified-memory-mcp

v0.4.1

Published

Local read-only stdio MCP server for checksum-verified, optionally signed memory packs

Readme

verified-memory-mcp

Free, MIT-licensed local stdio MCP server exposing verified_memory_search and verified_memory_get using the official MCP TypeScript SDK (@modelcontextprotocol/sdk 1.32.1). Published on npm as @python1/verified-memory-mcp and in the MCP Registry as io.github.python1/verified-memory.

Runtime is read-only and offline: no network transport, sockets, telemetry, embeddings, accounts, filesystem writes, subprocesses or shell execution. It reads one configured pack at startup and serves an isolated in-memory snapshot. On every request it checks, from file metadata only, that the pack folder is still the one it verified, and stops serving if it is not (no watchers, nothing re-read). Stdout is exclusively MCP protocol output; startup errors go to stderr and exit nonzero. Only these two tools are exposed; no resources, prompts, filesystem or command tools.

Run with npx

The npm package contains only dist/, the pack builder, this README and LICENSE. It ships no pack, so --pack is required:

npx -y @python1/verified-memory-mcp --pack /absolute/path/to/pack

Claude Code: claude mcp add verified-memory -- npx -y @python1/verified-memory-mcp --pack /absolute/path/to/pack. Running through npx does not apply Node's permission mode, because npx starts its own Node process. For the read-only filesystem allowlist described below, install the package and run its dist/index.js with node directly.

Build your own pack

A pack is a directory with exactly four files: records.jsonl, MEMORY.md, README.md and SHA256SUMS, plus an optional PACK.json (creation and expiry dates) and an optional signature, SHA256SUMS.sig (see Signed and dated packs). The easiest way to make one is the bundled offline builder. It reads your messages and the records you want to remember, finds every quote in its message, computes offsets and hashes, writes the pack, and then loads it with the server's own verifier. If any quote is not verbatim in its message, it writes nothing.

From a ChatGPT or Claude export, in your browser

Pack maker (beta): https://python1.github.io/data-lore-hive/. Load your data export, search your chats, select the exact sentences to remember, and download a ready pack. It runs entirely in the browser (no server, and its Content-Security-Policy forbids network requests), uses the same building code as the command-line builder, and checks the pack with the server's own verifier before offering the download. It does not sign packs or add dates (use sign-pack, below), but its Check a signed pack panel runs the same verifier on a signed pack and the public keys you paste. Its source is in web/; to run it from a checkout instead, run npm install && npm run build there and open web/dist/index.html. See web/README.md.

Quick start

  1. Put your source messages in messages.jsonl, one JSON object per line:

    {"id": 3, "date": "2026-03-02T08:20:00Z", "speaker": "Rowan", "text": "Quick thing first: the shed code changed. Also, the café on the corner sells seedlings on Saturdays."}

    | Field | Rule | |---|---| | id | positive integer, unique | | date | UTC ISO 8601 ending in Z or +00:00, e.g. 2026-03-02T08:20:00Z | | speaker | non-empty string | | text | non-empty string; quotes are matched against it exactly |

  2. Put what you want remembered in records.jsonl, one record per line, each citing exact quotes from those messages:

    {"record_id": "M-garden-3", "role": "user", "topic": "Seedlings", "category": "places", "quotes": [{"message_id": 3, "quote": "the café on the corner sells seedlings on Saturdays."}]}

    | Field | Rule | |---|---| | record_id | M- followed by 1–127 letters, digits, _ or -; unique | | role | "user" (the person) or "agent" (the assistant) | | topic, category | non-empty strings of your choice | | quotes | non-empty list of {"message_id", "quote"}; a quote must appear verbatim in that message. If it appears more than once, add "start" (the builder lists the choices) | | statement | optional summary; defaults to the quotes joined by spaces | | speaker | optional; defaults to the cited messages' speaker, required if they differ |

    Unknown fields are refused, so typos fail loudly.

  3. Build into a directory that does not exist yet, then point the server at it:

    npx -y -p @python1/verified-memory-mcp verified-memory-build-pack \
      --messages messages.jsonl --records records.jsonl \
      --out /absolute/path/to/my-pack --title "My memory" --description "Optional text for the pack README"
    npx -y @python1/verified-memory-mcp --pack /absolute/path/to/my-pack

    From a repository checkout, run node tools/build-pack.mjs ... after npm run build. The building rules (verbatim quotes, code-point offsets, hashes, file layout) live in tools/pack-core.mjs, which has no imports so a browser can run the same code. The builder makes no network calls. It never overwrites: it stages in a hidden sibling directory and renames it into place only after verification passes.

examples/input/ holds invented sample input (a fictional community garden, not real people), and examples/pack/ is the pack built from it; the test suite rebuilds it byte for byte. Neither ships in the npm package.

records.jsonl format (what the server verifies)

One JSON object per line, UTF-8, at most 10,000 records, unique record_ids. Every field below is required unless marked optional:

| Field | Rule | |---|---| | record_id | ^M-[A-Za-z0-9_-]{1,127}$ | | role | "user" or "agent" | | speaker, statement, topic, category | non-empty strings | | date_first_utc, date_last_utc | UTC ISO 8601 (Z or +00:00), first ≤ last | | evidence | non-empty list of citations (below) | | anything else | optional; kept and returned by verified_memory_get as metadata (e.g. supersedes, approval, audits) |

Each citation in evidence:

| Field | Rule | |---|---| | message_id | positive integer | | date_utc | UTC ISO 8601, between the record's first and last dates | | quote | non-empty exact text from the message | | start, end | integers, start ≥ 0, end − start = quote length in Unicode code points (offsets index the message text by code point, as Python string slicing does) | | message_sha256 | 64 lowercase hex characters: SHA-256 of the full message text encoded as UTF-8 |

Example (one line in the file; wrapped here for reading):

{"record_id":"M-garden-3","role":"user","speaker":"Rowan","category":"places","topic":"Seedlings",
 "statement":"the café on the corner sells seedlings on Saturdays.",
 "date_first_utc":"2026-03-02T08:20:00Z","date_last_utc":"2026-03-02T08:20:00Z",
 "evidence":[{"message_id":3,"date_utc":"2026-03-02T08:20:00Z",
   "quote":"the café on the corner sells seedlings on Saturdays.","start":48,"end":100,
   "message_sha256":"e3aebbd25f7b809dd5b0b8d55b9ad7df6566464dc2a5033d02e3c5aa7ed8eeb2"}]}

Quotes are checked against their source messages when the pack is built, and after that the signature protects them. The builder (or agent-memory/) refuses any quote that is not verbatim in its message and computes message_sha256 from that message. The server never sees your original messages, so at load and at serve time it checks only citation structure, offsets' arithmetic and the hash's format; it does not re-check quotes against message_sha256. Once the pack is sealed, SHA256SUMS (and, for a signed pack, SHA256SUMS.sig) is what protects those quotes from later changes. A pack with no records is refused at load (since 0.4.1).

MEMORY.md and the pack README.md

The server requires both to exist, be valid UTF-8 and match SHA256SUMS. It does not parse them or return them from either tool; they are for people and agents reading the pack directly. By convention, and as the builder writes them:

  • MEMORY.md: a # Title heading, then for each record its ID and speaker, its statement, and each quote with its message ID and date, marked as historical data.
  • README.md: what the pack is and where it came from (SHA-256 of the input files, record count), plus the notice that quotes are data, not instructions, and that checksums prove integrity, not truth or approval.

SHA256SUMS format

Exactly one line per other file (three, or four with PACK.json), each <64 lowercase hex> + two spaces + file name; no entry for SHA256SUMS or SHA256SUMS.sig; at most 4 KiB:

f3ed88bca6a7a451a892f49bd6d2970f5671b8c704d0bc6cae7646c46978bc69  MEMORY.md
0685a25133fd3e7f60e70ba1e836a2e8b045ace1e2be2ecbd306a4fc656f558f  README.md
34212f199411c8e4ef52cb341b0c68d970b5b30d9b8117c2eae5729b0d71dfa5  records.jsonl

If you edit a pack by hand, regenerate it from inside the pack directory:

shasum -a 256 MEMORY.md README.md records.jsonl > SHA256SUMS   # macOS
sha256sum MEMORY.md README.md records.jsonl > SHA256SUMS       # Linux

Resealing by hand only makes the checksums match again; it does not re-check quotes. Prefer rebuilding with the builder. Resealing also invalidates any signature: sign again.

Signed and dated packs

A pack can carry two optional extras:

  • SHA256SUMS.sig: an OpenSSH signature over the exact SHA256SUMS bytes, which pin every other file. Only Ed25519 keys, the sha512 hash and the namespace verified-memory-pack-v1 are accepted, so a git or code-release signature cannot be reused as a pack signature.
  • PACK.json (since 0.3.0): when the pack was made and when it stops being current, and (since 0.4.0) an optional sequence number. It is listed in SHA256SUMS like every other file, so the signature covers the dates and the sequence.
{
  "format": "verified-memory-pack",
  "version": 2,
  "created_utc": "2026-10-12T18:00:00Z",
  "expires_utc": "2027-01-10T18:00:00Z",
  "sequence": 3
}

Exactly these four keys plus the optional sequence; dates are UTC ISO 8601 and created_utc is before expires_utc. version is the format version and stays 2; sequence is a whole number (0 or more) that the signer raises with every pack they publish, so hosts can refuse an older one (see Rollback protection). A malformed PACK.json refuses to load. The pack maker and the builder never write one, so their packs still load on 0.2.0 hosts; packs with a PACK.json need 0.3.0 or later (older hosts refuse the extra file), and packs with a sequence need 0.4.0 or later (0.3.0 hosts refuse the extra key).

Signing: sign-pack

Sign on a machine you trust with a passphrase-protected key. Since 0.4.1, sign-pack ships in the npm package as the verified-memory-sign-pack command (in a checkout it is tools/sign-pack.mjs). It copies a verified pack to a new folder, writes PACK.json, reseals SHA256SUMS and runs ssh-keygen -Y sign. It never opens the private key itself: ssh-keygen reads it and asks for the passphrase (or uses ssh-agent). Before it moves the result into place, it loads it with the hosts' own verifier in strict mode, trusting only your .pub. It needs Node 22+ and OpenSSH 8.1+.

ssh-keygen -t ed25519 -f ~/.ssh/verified-memory-pack -C "my packs"   # once; set a passphrase
npx -y -p @python1/verified-memory-mcp verified-memory-sign-pack \
  --pack /path/to/pack --key ~/.ssh/verified-memory-pack --expires 90d --sequence 1 --out /path/to/pack-signed

From a repository checkout, run npm ci && npm run build once, then node tools/sign-pack.mjs ... with the same arguments.

--expires is a number of days (90d, at most 1825d) or an exact UTC time (2027-01-31T00:00:00Z). --sequence (optional, since 0.4.0) is written into PACK.json; raise it every time you publish a new pack (1, 2, 3, …). --out must not exist yet. On any failure (wrong passphrase, a pack that does not verify) nothing is written. Signing an already signed pack replaces its dates, sequence and signature. The script prints the key fingerprint, the --trusted-fingerprint line to pin it on hosts, and a trusted-keys line. Keep the private key out of the pack, out of any repository and off shared machines. Share only the .pub file.

Plain ssh-keygen -Y sign -f KEY -n verified-memory-pack-v1 pack/SHA256SUMS still makes a valid signature, but the pack then has no expiry date (freshness: unknown) or sequence, and --strict and --min-sequence refuse it.

Recommended setup: pin the signer

verified-memory-mcp --pack /absolute/path/to/pack \
  --trusted-fingerprint SHA256:…the-fingerprint-sign-pack-printed… \
  --min-sequence 3 --strict

--trusted-fingerprint names the one key allowed to sign your packs, by the fingerprint sign-pack (or ssh-keygen -l -f KEY.pub) prints. Repeat the flag, or separate fingerprints with commas, to allow several. When any is set, a pack signed by any other key is refused, even if that key is in --trusted-keys; an unsigned pack is refused too (a pin implies --require-signature). --trusted-keys then only supplies your name for the pinned key; without it the signer is reported as "pinned".

Why: in red-team round 4, an attacker bot on a shared machine that could rewrite the trusted keys file listed its own key and re-signed a poisoned pack, and a host trusting that file served it as verified. A pin lives in the host's own configuration (the MCP client config or the Claude Desktop setting), not in a file next to the pack, so that attack is refused: pack is signed by SHA256:…, which is not a pinned signer fingerprint. Keep the host configuration writable only by you; nothing here can protect a host whose own configuration an attacker can edit.

Revoking a key: --revoked-keys

--revoked-keys /absolute/path/to/revoked_keys lists fingerprints you no longer trust, one per line, as ssh-keygen -l prints them; # comments and a trailing comment after the fingerprint are allowed:

# laptop lost 2026-10-15
SHA256:…the-old-key-fingerprint… old laptop

A pack signed by a revoked key is always refused, even when the key is pinned or trusted, and even without any signature requirement: pack is signed by SHA256:…, which is revoked. Revocation does not by itself require a signature, so unsigned packs still load unless you also pin or require one. The file is read once at startup, like the trusted keys file; keep it where only you can write.

Rollback protection: --min-sequence

--min-sequence N refuses any pack whose signed PACK.json sequence is missing or lower than N, so an older pack, however validly signed and unexpired, cannot be swapped in for the current one: pack sequence 3 is lower than the minimum 5; refusing a possible rollback to an older pack. You set N, and raise it when you install a newer pack; the server keeps no state file and never writes anything, so it cannot remember the highest sequence it has seen. A sequence counts only when a key you pinned or trusted signed it, so --min-sequence implies --require-signature.

Order of checks

Every refusal gives one specific error, from the first check that fails:

  1. the pack's files, checksums and records (any edit fails here);
  2. the signature, if present, must be valid;
  3. revoked (refused whatever else is set), then pinned (with pins set, only a pinned signer passes), then trusted keys (verified, untrusted or unsigned, and --require-signature refuses all but verified);
  4. sequence against --min-sequence;
  5. expiry under --strict (also judged again on every request).

Stop on change

The server reads and verifies the pack once, at startup. On every tool call it compares the pack folder's file list and each file's size, modification time, change time and identity (device and inode) with what it verified. If anything differs (an edit, an added or removed file, a file or the whole folder replaced), both tools return isError with verified-memory: pack files changed on disk since startup, so the verified snapshot no longer matches them; restart to verify the pack again, and no record data, until you restart. The refusal is latched in memory: removing the added file or putting the folder back does not resume serving; only a restart, which verifies the pack again, does. This is metadata only: no background watchers, no re-reads, no writes. Change time cannot be set back by a normal user, so restoring a file's modification time does not hide an edit; an administrator who can change the system clock or the filesystem itself is outside what this detects.

Choosing whom to trust

Trust is only ever a public key you list. There is no built-in key, network, key server or registry. Put keys in a file, one per line in allowed_signers form, name ssh-ed25519 AAAA… [comment] (blank lines and # comments are allowed):

alice ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA… alice's laptop

The maintainer's pack-signing public key is in keys/ for anyone who chooses to trust packs signed with it. No host trusts it unless you list it.

verified-memory-mcp --pack /absolute/path/to/pack --trusted-keys /absolute/path/to/trusted_keys
verified-memory-mcp --pack /absolute/path/to/pack --trusted-keys /absolute/path/to/trusted_keys --require-signature
verified-memory-mcp --pack /absolute/path/to/pack --trusted-keys /absolute/path/to/trusted_keys --strict

--flag=value, --require-signature=true|false and --strict=true|false forms are accepted too; an empty --trusted-keys=, --trusted-fingerprint=, --revoked-keys= or --min-sequence= means that setting is off. --require-signature keeps its 0.2.0 meaning: a signature by a key you listed is required, and freshness is reported but not enforced, so packs signed with plain ssh-keygen for 0.2.0 keep loading under it. --strict requires a trusted signature and a fresh PACK.json (it implies --require-signature).

What hosts do

Every tool result carries verification.signature and verification.freshness. Freshness is judged against this computer's clock on every call, not only at startup.

| Pack | Default | --require-signature | --strict | |---|---|---|---| | Unsigned (every 0.1 pack) | Starts; signature.status: "unsigned" | Refuses to start | Refuses to start | | Signature present but malformed or invalid | Refuses to start | Refuses to start | Refuses to start | | Valid signature, key not in your file | Starts; untrusted, with the key fingerprint | Refuses to start | Refuses to start | | Valid signature, key in your file | Starts; verified, signer (your name for the key), fingerprint | Starts | Starts if fresh (below) | | No PACK.json | Starts; freshness.status: "unknown" | Starts; unknown | Refuses to start | | PACK.json, not yet expired | Starts; fresh, with created_utc, expires_utc | Starts; fresh | Starts | | PACK.json, expired | Starts; stale, plus a warning on stderr | Starts; stale, plus a warning | Refuses to start; a running server stops serving (tool error) once it expires | | created_utc more than 5 minutes in the future | Starts; not_yet_valid, plus a warning on stderr | Starts; not_yet_valid, plus a warning | Refuses to start | | PACK.json or its checksum tampered with | Refuses to start | Refuses to start | Refuses to start | | Pack folder changes while running | Tools return isError until restart | Same | Same |

With the 0.4.0 options, in any of the modes above:

| Pack | Result | |---|---| | Signed by a key in --revoked-keys | Refuses to start, naming the fingerprint, even if pinned or trusted | | --trusted-fingerprint set; signed by a pinned key | Starts; verified, pinned: true, signer from --trusted-keys or "pinned" | | --trusted-fingerprint set; signed by any other key, even one in --trusted-keys | Refuses to start | | --trusted-fingerprint or --min-sequence set; unsigned | Refuses to start | | --min-sequence N; signed sequence ≥ N | Starts; freshness.sequence reports it | | --min-sequence N; sequence lower than N, or missing | Refuses to start | | A sequence but no --min-sequence | Starts; freshness.sequence reported, nothing enforced |

freshness.authenticated is true only when a key you listed signed the dates; dates in an unsigned or untrusted pack could have been written by anyone. --require-signature or --strict with no pinned fingerprint or trusted key, or a trusted key line that is not a single-name ssh-ed25519 entry, refuses to start. The signer name always comes from your file, never from the pack, and fingerprints match ssh-keygen -l. Anyone can check a pack without this server:

ssh-keygen -Y verify -f trusted_keys -I alice -n verified-memory-pack-v1 -s SHA256SUMS.sig < SHA256SUMS

A signature proves who signed, not that the content is true. It shows that the holder of that key signed these exact bytes. It does not show that the quotes are accurate or complete, or that the key's owner is who you think. "Fresh" means the signer set an expiry that has not passed, not that this is their latest pack: without --min-sequence, an older pack from the same key still loads until it expires. Revocation and the minimum sequence are only as current as your configuration: with no network, a newly revoked key or a newer sequence reaches a host only when you edit its settings. Freshness relies on this computer's clock. Version 0.1.0 refuses signed packs (five files); remove SHA256SUMS.sig to load one there.

Build and run

From a checkout of this repository, using Node 22.0.0 or newer (use the latest patch release of your chosen Node line), in plugins/verified-memory-mcp:

npm ci --ignore-scripts
npm run build
npm test
node --experimental-permission --allow-fs-read="$PWD" "$PWD/dist/index.js"

The last command starts the stdio server, waiting for an MCP client on stdin. It is not an interactive terminal search interface. Connect it through Claude Desktop or another MCP client, then request a pottery search and retrieve M-sample-2. Node's permission mode grants only reads under this project directory; filesystem writes, subprocesses, workers and native addons are denied. The --experimental-permission spelling works at the Node 22.0.0 floor; early Node 22 releases emit an experimental warning on stderr. On Node 22.13+ you can use --permission instead. Node 22 does not enforce network permissions; this server makes no network calls. Node 25.8.0 additionally passes the socket-denial probe. See the Node 22.0 permission documentation and Node 22.13 permission documentation. Use direct node invocation at runtime, not npm/npx or a shell wrapper.

Builds, dependency installation and tests are development operations: they write local output; installing dependencies accesses the npm registry. Runtime does neither. The test harness launches local Node processes and makes isolated temporary copies of the fictional pack; the server itself never launches another process.

Claude Desktop one-click install

Download verified-memory-<version>.mcpb from the latest release.

verified-memory-<version>.mcpb is an MCP Bundle for Claude Desktop on macOS and Windows; it has been tested by hand on macOS and Windows. It runs on Claude Desktop's built-in Node.js, so you need no Node install and no Terminal.

  1. Double-click the .mcpb file. Windows may ask which app opens it: choose Claude. Alternatively, drag the file into Claude Desktop → Settings → Extensions, or use Settings → Extensions → Advanced settings → Install Extension….
  2. Unzip the pack first (Windows: right-click → Extract All). Point the add-on at the folder that contains MEMORY.md, README.md, records.jsonl and SHA256SUMS. The folder picker shows folders only, so the pack folder will look empty; that's normal. You can also paste the folder path.
  3. Make sure the extension is switched on (Settings → Extensions → Enabled) before asking. Example question: "Using verified memory, what did … ?"

The settings form has these fields:

| Setting | Required | Passed as | |---|---|---| | Memory pack folder | yes | --pack=<folder> | | Trusted keys file | no (blank = none) | --trusted-keys=<file> | | Pinned signer fingerprint (recommended for signed packs) | no (blank = none) | --trusted-fingerprint=<SHA256:…[,SHA256:…]> | | Revoked keys file | no (blank = none) | --revoked-keys=<file> | | Minimum pack sequence | no (blank = off) | --min-sequence=<N> | | Require a pinned or trusted signature | no (off) | --require-signature=true\|false | | Strict: require a pinned or trusted signature and an unexpired pack | no (off) | --strict=true\|false |

For a signed pack, paste the fingerprint sign-pack printed into Pinned signer fingerprint; you then need no trusted keys file. Raise Minimum pack sequence when you install a newer pack. Turn on Strict for packs signed with an expiry date (sign-pack always adds one). Every new setting defaults to off, so an add-on upgraded from 0.3.0 behaves as before (apart from stop on change).

Change these later in Settings → Extensions → Verified Memory. The bundle holds the same dist/ as the npm package plus its production dependencies, and nothing else: no pack, keys, signatures or tests. It is not code-signed with mcpb sign, because that would need a private key. It does not use Node's permission mode, because Claude Desktop starts the server itself.

Build it locally with npm ci && npm run build:mcpb (output: build/verified-memory-<version>.mcpb). npm run test:mcpb builds it and runs test/mcpb/. Those tests unpack the bundle with @anthropic-ai/mcpb and start the server with the arguments that its getMcpConfigForManifest produces from sample settings.

Claude Desktop configuration example

Merge this example into your Claude Desktop MCP configuration and restart Claude Desktop after building. All paths below are placeholders, not real user paths. Replace them with absolute paths to the Node executable and this built directory. No configuration is installed or edited automatically.

{
  "mcpServers": {
    "verified-memory": {
      "command": "/absolute/path/to/node",
      "args": [
        "--experimental-permission",
        "--allow-fs-read=/absolute/path/to/hive-public/plugins/verified-memory-mcp",
        "/absolute/path/to/hive-public/plugins/verified-memory-mcp/dist/index.js"
      ]
    }
  }
}

In a repository build, omitting --pack uses the bundled fictional Juniper Vale/Wren sample regardless of the client's working directory. An npm install has no sample and refuses to start without --pack. To use your own pack, append "--pack", "/absolute/path/to/pack" to args, and also add "--allow-fs-read=/absolute/path/to/pack" before the entry-point argument. With --trusted-keys, also allow reading that file the same way. Choose a pack that the connecting client is permitted to read. Relative pack paths and unknown CLI flags are rejected. Tools cannot choose paths. There is no discovery of home directories, other agents, real memory or configuration. There are no watchers or reloads: if the pack folder changes while the server runs, the tools stop answering with an error, even if the change is undone, until you restart to verify the new pack. Add --trusted-fingerprint, --revoked-keys (and its --allow-fs-read) and --min-sequence to args the same way.

Tool contracts

verified_memory_search:

{"type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":512}},"required":["query"],"additionalProperties":false}

Returns matches, matched, verification, and notice. Search requires all normalized Unicode words, at most 32 unique terms, across statement/topic/quotes. Returns at most 10 matches, ordered by term occurrences, newest date, then record ID. Matches preserve exact quotations, source message IDs, dates and speakers. No stemming, semantic inference or network lookup. No match is not proof of absence.

verified_memory_get:

{"type":"object","properties":{"id":{"type":"string","pattern":"^M-[A-Za-z0-9_-]{1,127}$"}},"required":["id"],"additionalProperties":false}

Returns found, full record (or null), verification, and notice, preserving citation, provenance, audit, approval and supersession metadata as historical data. Successful MCP results contain JSON text in content and the same object in structuredContent (the MCP equivalent of the OpenClaw tool's details). Invalid tool arguments return isError: true; unknown tool names return a protocol error. Both tools advertise read-only, nondestructive, idempotent, closed-world annotations.

The SDK's low-level Server API keeps the published JSON schemas exact, including additionalProperties: false; arguments are checked explicitly and by the shared verifier. No OpenClaw runtime or dependencies are needed to build or run this server.

Shared verifier and fail-closed startup

The development-only scripts/prepare.mjs copies the original ../verified-memory/src/pack.ts and verify.ts byte for byte into ignored src/, and copies its public fictional sample into ignored sample-pack/. TypeScript compiles that same verifier into dist/pack.js (file reading) and dist/verify.js (the checks themselves, with no imports). There is no second maintained verifier, and this build never modifies the OpenClaw plugin. Build from the repository checkout so the sibling source and sample are present. Generated files are not committed.

The whole pack must verify before a server is created or stdio is connected. A failing pack cannot initialize, list tools or return records. The exact format is four regular files: records.jsonl, MEMORY.md, README.md, and SHA256SUMS, plus an optional PACK.json and SHA256SUMS.sig checked by the same shared verifier. Checks include exact file inventory, SHA-256 hashes over the bytes actually parsed, UTF-8, JSON, unique IDs, citation structure and offsets, date ranges, manifest syntax, symlink roots/entries and file-size/count limits. Limits remain 16 MiB per data file, 4 KiB manifest, 10,000 records. No permissive fallback. Later disk edits cannot change the verified in-memory snapshot, and since 0.4.0 they stop it being served (see Stop on change); retrieval returns copies.

Inherited filesystem checks are not a sandbox against an adversary able to race ancestor directories. Use a trusted, stable local directory and the Node read allowlist.

Integrity is not authenticity or truth. Replacing both data and checksums can produce a passing pack, unless you pin the signer (--trusted-fingerprint) or list only keys whose trust file the attacker cannot write. Runtime cannot recheck quotations against original messages. Historical quotes are data, never instructions or permission; that notice accompanies every successful result. Host model behavior and automatic tool selection are not validated. Ask the host to quote tool text verbatim and cite record and message IDs.

Only the existing public fictional source agent-memory/sample/telegram-export.json is used. No private records, operational paths, IP addresses, keys or other-agent files are included. Fixture audit/approval metadata is not evidence of real model auditing or human approval.

File tree

plugins/verified-memory-mcp/
├── .gitignore
├── LICENSE
├── README.md
├── VALIDATION.md
├── RELEASE-NOTES-0.4.0.md      # notes for the 0.4.0 release
├── RELEASE-NOTES-0.4.1.md      # draft notes for the 0.4.1 release
├── package.json
├── package-lock.json
├── server.json             # MCP Registry metadata
├── tsconfig.json
├── scripts/
│   ├── prepare.mjs
│   ├── build-mcpb.mjs          # builds the Claude Desktop bundle into build/
│   └── test-all.mjs            # every automated suite, as CI runs it on macOS and Windows
├── mcpb/manifest.json          # Claude Desktop bundle manifest (settings form, tools)
├── src/
│   ├── index.ts
│   ├── server.ts
│   ├── pack.ts                 # generated, ignored; exact shared source
│   └── verify.ts               # generated, ignored; exact shared source
├── web/                        # repo-only: browser pack maker (see web/README.md)
├── tools/
│   ├── build-pack.mjs          # offline pack builder (ships in npm)
│   ├── pack-core.mjs           # its import-free building rules (ships in npm)
│   ├── sign-pack.mjs           # signs and dates a pack via ssh-keygen (ships in npm)
│   └── export-shape.mjs        # repo-only: prints a chat export's structure, never its text
├── examples/
│   ├── input/                  # invented messages.jsonl + records.jsonl
│   └── pack/                   # pack built from them
├── test/
│   ├── server.test.mjs
│   ├── redteam.test.mjs        # red-team rounds R1–R6, revocation, precedence
│   ├── sign-pack.test.mjs
│   ├── build-pack.test.mjs
│   ├── pack-core.test.mjs
│   ├── export-shape.test.mjs
│   └── mcpb/bundle.test.mjs    # run by `npm run test:mcpb` against the built bundle
├── validation/
│   ├── tests.txt               # Node 25.8.0
│   ├── tests-node22-min.txt     # Node 22.0.0
│   └── tests-node22-current.txt # Node 22.23.3
├── sample-pack/                # generated, ignored; four fictional files
├── build/                      # generated, ignored; the .mcpb and its staging folder
└── dist/                       # generated, ignored; JS and declarations

See VALIDATION.md for recorded tests and limitations.

License

MIT — LICENSE. Free functionality, no paid tier.

☕ Support this project: https://buymeacoffee.com/python1