
Research
/Security News
Popular npm Packages in the keyv and Cacheable Namespaces Compromised in Active Supply Chain Attack
Popular npm packages keyv and cacheable compromised.
A neutral, cross-agent memory quality gate for AI agents, delivered as an MCP server — a gate, not a store.
A neutral memory quality-gate for AI agents — a gate, not a store. One shared memory of you — who you are, how you're doing, what you're working on — that every AI agent reads from and writes to, kept honest at write time. Local-first, no cloud calls, one dependency.
One command wires Jamgate into every MCP client on your machine:
npx jamgate setup
• one-click Claude Desktop bundle → the
.mcpb on the latest release
You are one person, but every AI you use is a separate island. You design with one, research with another, code with a third — and none of them know what the others know, so you re-explain "what I'm working on" every time.
The tools that try to fix this mostly store everything, so shared memory bloats with junk: one production audit of a leading memory system found 97.8% of its stored entries were junk (source) — duplicates, trivia, one-off chatter, stale states. Sharing memory is the easy part. Keeping the shared memory clean and current is the unsolved part.
Jamgate sits in the write path and decides what deserves to be remembered, before it is stored:
without a gate with Jamgate
┌──────────────────────────────────┐ ┌──────────────────────────────────────┐
│ "remember I'm on a call" │ │ ✗ rejected — not durable │
│ "I use Windows" ← from 6mo ago │ │ ⇄ superseded — "I use Linux" wins │
│ "I use Windows" (again) │ │ ✗ duplicate — already known │
│ "I use Linux" │ │ ✓ saved — durable, changes answers │
│ "my name is Sam" (agent guessed) │ │ ⚠ conflict — lower trust, ask first │
└──────────────────────────────────┘ └──────────────────────────────────────┘
everything piles up, 98% junk small, current, trustworthy
Jamgate is one shared memory of you that every agent plugs into — kept honest by a quality gate. It runs as an MCP server, so any MCP-capable agent (Claude Code, Claude Desktop, Cursor, …) connects to the same memory on your machine. Because it filters at write time, that memory stays small, accurate, and contradiction-free instead of bloating with junk.
Agent → [ Jamgate quality gate ] → local store (~/.jamgate/memory.json)
save_memory / recall_memory / forget_memory
A memory is kept only if it is durable (still true after this session) and would change a future answer. The gate is a hybrid pipeline, cheapest checks first:
| Layer | What it does |
|---|---|
| Rule pre-filter | Drops obvious non-durable noise ("I'm on a call now") before it reaches the store. |
| Agent salience | Uses the calling agent's own understanding as the main "is this worth remembering?" filter — no extra LLM call of our own. |
| Exact dedup | Identical facts are never stored twice. |
| Time-aware supersession | Every memory is a timestamped event; a newer fact retires an older one on the same subject by recency — no contradiction pile-up, and it never throws your own stale words back at you. |
| Trust hierarchy | A lower-trust source (an agent's guess) can't silently overwrite a higher-trust fact (something you said explicitly). The gate refers the conflict back to you instead. |
| Semantic near-dup (optional) | With local embeddings on, a save that means the same as an existing memory returns as a possible_duplicate to confirm, rather than piling up. |
| Type-based expiry | Volatile state ages out (~2 days) while identity never does, so recall stays current automatically. |
Everything is taggable, expirable, and deletable — you always see and control what's remembered.
Jamgate runs locally — your memory never leaves your machine. Requires Node.js 20+.
No install step: npx fetches and runs it on demand.
npx jamgate setup (recommended)One command detects the MCP clients installed on your machine (Claude Code, Claude Desktop, Cursor, Windsurf) and wires Jamgate into each:
npx jamgate setup
It is safe to run: idempotent (running it twice changes nothing), it never touches any
server entry but its own, and it backs up each config file to <file>.jamgate-backup before
writing. Useful flags:
npx jamgate setup --dry-run # show what would change, write nothing
npx jamgate setup --remote https://you/mcp --token … # wire HTTP transport (see Remote mode)
npx jamgate status # show which clients are wired + where the store lives
Restart your client(s) afterwards. On Claude Code, when the claude CLI is present, setup
uses claude mcp add under the hood; otherwise it merges ~/.claude.json directly.
Prefer to wire it yourself? Each client is a small config change.
Claude Code:
claude mcp add jamgate -- npx jamgate
Claude Desktop — one-click: download the .mcpb bundle from the
latest release and open it (Claude
Desktop → Settings → Extensions; the bundle is unsigned, so you may see an "unverified"
prompt). Or add to claude_desktop_config.json (Settings → Developer → Edit Config):
{
"mcpServers": {
"jamgate": {
"command": "npx",
"args": ["jamgate"]
}
}
}
Cursor — click the Add to Cursor badge at the top, or add to ~/.cursor/mcp.json
(or .cursor/mcp.json in a project):
{
"mcpServers": {
"jamgate": {
"command": "npx",
"args": ["jamgate"]
}
}
}
Windsurf — add the same mcpServers block to ~/.codeium/windsurf/mcp_config.json.
Restart the agent. It now has three tools:
save_memory — store a durable fact. The gate rejects junk, drops exact
duplicates, supersedes outdated facts by recency (pass a subject like
operating-system so a newer fact retires the older one — or let the gate derive one),
and refers trust conflicts back to you.recall_memory — fetch what's known, relevant to a query (active facts only).forget_memory — delete a memory by id.Your memory lives in ~/.jamgate/memory.json. Same machine, every agent → one shared
memory. To share one memory across different machines and your phone, see
Remote mode.
By default, recall is fuzzy lexical matching (stemming, typo-tolerance, trigrams) — fast, deterministic, and dependency-free, but blind to synonyms. To also match on meaning (so "automobile" recalls a memory about your "car"), install the optional embedding backend:
npm install @huggingface/transformers
On first use it downloads a small sentence-embedding model (all-MiniLM-L6-v2, ~23 MB,
quantized) and runs it entirely on your machine — no text is ever sent to any cloud
AI. With it enabled, recall blends semantic similarity into the ranking, and a save
that is semantically near-identical to an existing memory comes back as a
possible_duplicate for you to confirm. If the package isn't installed, Jamgate runs
on fuzzy recall — nothing breaks.
All configuration is via environment variables; every one has a sensible default.
| Variable | Default | What it does |
|---|---|---|
JAMGATE_STORE | ~/.jamgate/memory.json | Path to the memory store file. |
JAMGATE_EMBEDDINGS | auto | off disables the semantic layer even if the model is installed. |
JAMGATE_DUP_THRESHOLD | 0.88 | Semantic near-duplicate sensitivity (0–1); higher = stricter. |
JAMGATE_GATE_LOG | on | off disables the local decision log. |
JAMGATE_TTL_<TYPE>_DAYS | per type | Override the freshness window for a memory type, e.g. JAMGATE_TTL_PROJECT_DAYS=180. |
JAMGATE_HTTP | off | 1/true enables remote mode (same as the --http flag). |
JAMGATE_PORT | 8420 | Port for remote mode (same as --port). |
JAMGATE_HOST | 127.0.0.1 | Interface to bind in remote mode. Keep it on localhost behind a reverse proxy. |
JAMGATE_TOKEN | — | Bearer token required in remote mode. The server refuses to start without it. |
JAMGATE_OAUTH | on | In remote mode, serve the MCP OAuth flow so claude.ai / the Claude app can connect. off disables it (static-token-only). |
JAMGATE_OAUTH_STORE | ~/.jamgate/oauth.json | Path to the OAuth state file (registered clients + hashed tokens). |
Your memory is one JSON file (JAMGATE_STORE, default ~/.jamgate/memory.json), so a backup can
be as simple as copying it. But jamgate export / jamgate import do it properly — schema-aware,
and with import passing every record back through the same quality gate so a restore or a
machine-to-machine move can't smuggle in duplicates or overwrite a trusted fact.
# Back up everything (active + superseded history) to a file
jamgate export --output backup.json
# Only the live facts, and pipe it somewhere
jamgate export --active-only > my-memory.json
# Restore / merge into another machine's store (respects JAMGATE_STORE)
jamgate import backup.json
# See exactly what would happen first — nothing is written
jamgate import backup.json --dry-run
Export writes a { schemaVersion, exportedAt, generator, memories } envelope. Without
--output it prints pure JSON to stdout (so it pipes cleanly) and the summary to stderr.
Import accepts that envelope or a bare JSON array. Each active record is replayed through the
gate — exact-duplicate dedup, time-aware supersession, the trust/contradiction guard, and
near-duplicate detection — instead of being blindly appended, and original timestamps and
provenance are preserved (your createdAt is never reset). It prints a per-record report
(imported / duplicates skipped / superseded / conflicts flagged / near-duplicates); conflicts and
near-duplicates are surfaced for you to decide, never silently resolved. The whole import is one
atomic transaction — a malformed file is rejected with a nonzero exit and your store is left
untouched. Records already retired (superseded) in the source are treated as history and skipped,
not re-activated. See DECISIONS D-033.
Moving a local store onto your own server? Export locally, copy the JSON up, then
JAMGATE_STORE=/data/memory.json jamgate import my-memory.jsonon the box (or just place the file atJAMGATE_STORE— butimportis what merges into an existing server store safely).
You've already told another AI product who you are. Starting from zero on a new one is the worst
part of switching. jamgate import --from <vendor> takes the memory list you exported from
Claude or ChatGPT and replays it through the same gate a live save goes through — so you
get day-one memory instead of a cold start, without smuggling in duplicates or junk.
# Claude — a memory list you saved from Settings → Capabilities → "View and edit your memory"
jamgate import --from claude ~/Downloads/claude-memory.md
# ChatGPT — the list copied from Settings → Personalization → Memory → "Manage memories"
jamgate import --from chatgpt ~/Downloads/chatgpt-memory.txt
# Point it at the export .zip or the extracted folder — it finds the memory file inside
jamgate import --from chatgpt ~/Downloads/chatgpt-export.zip
# Always look first. Nothing is written on a dry run.
jamgate import --from claude ~/Downloads/claude-memory.md --dry-run
Honest status, checked July 2026: neither vendor's bulk account data export contains your memory entries. Both keep them in the app's own memory settings, and both document a copy-out-the-text path. So the file you feed Jamgate is a text/markdown list, one memory per line:
| Product | Where your memories are | What to do |
|---|---|---|
| Claude | Settings → Capabilities → "View and edit your memory" | Copy the list (or ask Claude: "Write out your memories of me verbatim, exactly as they appear in your memory") into a .md/.txt file. Anthropic's own memory-transfer format is [date saved, if available] - memory content — exactly what we parse. |
| ChatGPT | Settings → Personalization → Memory → "Manage memories" | Select the list and copy it into a .md/.txt file. A trailing (saved 2026-01-09) is understood. |
Dates are optional. Bullets (-, *, 1.), markdown headings, horizontal rules and code fences
are handled. If a future export does ship structured memory JSON, we'll read that too —
best-effort, looking for entries under memory-ish keys — and we accept the .zip or the extracted
folder directly and pick the memory-shaped file out of it.
conversations.json, chat.html,
message_feedback.json and friends are recognized by name, skipped, and reported as skipped.
Inferring facts about you from raw chat history is exactly the low-consent behavior this project
exists to push back on. If the export contains nothing but chat logs, the import fails with a
message telling you where your memories actually live.Every parsed line becomes a memory and goes through the gate, never blind-appended:
user-confirmed — you curated these in the source product. Not user-explicit
(you didn't dictate them to Jamgate), not agent-inferred (they aren't our guess).preference or identity only when the wording is obvious;
otherwise left untyped. A wrong type is worse than no type.import:claude.ai / import:chatgpt, so you can always see where a
memory came from.Because a hand-pasted file can contain stray prose (a footer, a stray note), every non-empty line
is a candidate. Run --dry-run first — it prints exactly what would land. See
DECISIONS D-035.
Want one shared memory across your phone, browser, and laptop but don't want to run a server? Click a button, log into a hosting platform, and you get your own Jamgate instance with its own URL and token — no terminal, no server knowledge. Same gate, same store as the local install; only the transport is over the network (this is Remote mode, set up for you).
What you should know first (honest version):
The button reads render.yaml straight from this repo: it builds the image from
the Dockerfile, generates a random JAMGATE_TOKEN for you, and attaches a
1 GB persistent disk at /data for your memory. Render provisions a paid starter instance
(a disk needs one). After it goes live, read your token under Environment, and your URL is
the service URL with /mcp appended (e.g. https://jamgate-xxxx.onrender.com/mcp).
The button deploys the published template: it builds the image from the Dockerfile
(pinned via railway.json with the /healthz check), generates a random
JAMGATE_TOKEN for you, and attaches a persistent volume at /data for your memory. After it
goes live, read your token under Variables, and your URL is the service domain with /mcp
appended (e.g. https://jamgate-xxxx.up.railway.app/mcp).
Once the deploy is live you have two things: a URL ending in /mcp and a token (from the
platform's environment/variables tab). Connect each device to the same instance so they share one
memory:
Desktops (Claude Code, Cursor, Windsurf) — one command:
npx jamgate setup --remote https://your-instance/mcp --token <your-token>
This wires every detected client on that machine to your instance (Streamable HTTP clients only; others are skipped with a reason).
Phone (Claude app) and claude.ai in a browser: Settings → Connectors → Add custom
connector → URL https://your-instance/mcp, and provide the bearer token when asked. The same
three tools (save_memory, recall_memory, forget_memory) then work from your phone.
Save on your phone, recall on your laptop — one memory, everywhere. For the full server-owner path (your own VPS, systemd + Caddy), keep reading.
By default Jamgate runs locally over stdio — one process per agent, on your machine, no network. That's the right model for a single computer. But you are one person with agents in several places at once: the Claude app on your phone, claude.ai in a browser, Claude Code on a laptop. stdio can't be their shared brain — each would get its own local process and its own memory.
Remote mode is the answer: run one Jamgate instance on a server you control, put it behind HTTPS, and point every agent at the same URL. Now they share one memory of you — save on your phone, recall on your laptop. It's the same gate and the same store, just reachable over the network. It stays opt-in; stdio remains the default and the local-first promise is unchanged. Whether it's your own memory or a whole team's, the rule is one instance per person (see Honest limits).
# A strong token is REQUIRED — the server refuses to start without one.
export JAMGATE_TOKEN=$(openssl rand -hex 32)
jamgate --http # listens on 127.0.0.1:8420/mcp
# or: jamgate --http --port 9000 (or JAMGATE_HTTP=1 JAMGATE_PORT=9000)
The MCP endpoint is /mcp. Every request must carry Authorization: Bearer <token>; anything
else gets a 401. In remote mode Jamgate also serves the standard MCP OAuth flow (on by
default) so it can be added to claude.ai and the Claude mobile app — see
Adding to claude.ai.
JAMGATE_TOKEN guards every request, compared in
constant time so it can't be recovered from response timing. Generate it with
openssl rand -hex 32, keep it out of shell history, and rotate it by restarting with a new
value.127.0.0.1 by default, so it is never directly exposed. Put caddy or nginx in
front to terminate HTTPS and forward to it locally. A bearer token over plain HTTP on the open
internet is a leaked token — always run it behind TLS.JAMGATE_TOKEN is still the only
credential, PKCE is enforced, and issued tokens are stored hashed and revocable. Details in
Adding to claude.ai.A systemd unit to keep Jamgate running (fill in your user and a real token — ideally load the
token from an EnvironmentFile with 600 permissions rather than inlining it):
# /etc/systemd/system/jamgate.service
[Unit]
Description=Jamgate MCP memory (remote mode)
After=network.target
[Service]
# Load JAMGATE_TOKEN=... (and any JAMGATE_* overrides) from a root-only file:
EnvironmentFile=/etc/jamgate.env
Environment=JAMGATE_HTTP=1
Environment=JAMGATE_PORT=8420
Environment=JAMGATE_STORE=/var/lib/jamgate/memory.json
ExecStart=/usr/bin/npx jamgate
User=jamgate
Restart=on-failure
[Install]
WantedBy=multi-user.target
echo "JAMGATE_TOKEN=$(openssl rand -hex 32)" | sudo tee /etc/jamgate.env >/dev/null
sudo chmod 600 /etc/jamgate.env
sudo systemctl enable --now jamgate
Caddy — automatic HTTPS, two lines of real config:
memory.example.com {
reverse_proxy 127.0.0.1:8420
}
nginx — equivalent, with TLS certs managed by certbot:
server {
listen 443 ssl;
server_name memory.example.com;
ssl_certificate /etc/letsencrypt/live/memory.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/memory.example.com/privkey.pem;
location /mcp {
proxy_pass http://127.0.0.1:8420/mcp;
proxy_http_version 1.1;
proxy_set_header Connection ""; # keep-alive for SSE streaming
proxy_buffering off; # don't buffer the event stream
proxy_read_timeout 3600s;
}
}
Point every agent at https://your-domain/mcp with the token.
Claude app (iOS / Android / desktop) and claude.ai — Settings → Connectors → Add custom
connector → paste the URL https://your-domain/mcp and click through. These clients only speak
the standard MCP OAuth flow, so instead of pasting a token into a config field, a Jamgate
page opens in your browser and asks: "This is your Jamgate instance. Enter your instance token
to authorize this client." Paste your JAMGATE_TOKEN once, and Claude is connected — it
remembers the authorization, so you won't be asked again for that client. Once connected, the
same three tools (save_memory, recall_memory, forget_memory) are available from your phone
and browser. See Adding to claude.ai below for what happens under
the hood.
Claude Code — add it as an HTTP MCP server:
claude mcp add --transport http jamgate https://your-domain/mcp \
--header "Authorization: Bearer <token>"
Any MCP client that speaks Streamable HTTP works the same way: URL https://your-domain/mcp,
header Authorization: Bearer <token>.
claude.ai and the Claude mobile app cannot take a static token in a config field — they only
support the MCP authorization flow
(OAuth 2.1 + PKCE). Jamgate implements that flow itself in remote mode, so no external identity
provider is involved — your instance is the authorization server, and your JAMGATE_TOKEN is
the one credential. It's on by default whenever you run --http (disable with
JAMGATE_OAUTH=off if you only ever use Claude Code with a static header).
What you do:
https://your-domain/mcp.JAMGATE_TOKEN and submit. That's the
only thing it ever asks for, and only once per client.save_memory / recall_memory / forget_memory now work from that client.What happens under the hood (all served by your instance, same origin, no third party):
| Endpoint | Spec | Purpose |
|---|---|---|
GET /.well-known/oauth-protected-resource | RFC 9728 | Tells the client where the authorization server is. A 401 from /mcp also carries a WWW-Authenticate header pointing here. |
GET /.well-known/oauth-authorization-server | RFC 8414 | Advertises the endpoints below; PKCE S256 required. |
POST /register | RFC 7591 | Dynamic client registration — the client gets a client_id. |
GET/POST /authorize | OAuth 2.1 | The consent page that asks for your instance token, then issues a single-use, PKCE-bound authorization code. |
POST /token | OAuth 2.1 | Exchanges the code (+ PKCE verifier) for a long-lived access token (and a refresh token). |
Security: PKCE (S256) is mandatory, redirect URIs are matched exactly (no open redirect),
authorization codes are single-use and expire in ≤60s, and access/refresh tokens are stored
hashed in ~/.jamgate/oauth.json (revoke one by deleting its entry) with the same atomic,
locked writes as the memory store. The /mcp endpoint accepts either an issued OAuth access
token or the static JAMGATE_TOKEN, so existing Claude Code connections keep working
unchanged.
Jamgate is deliberately small and opinionated. It is not trying to be a hosted memory platform or a knowledge graph — it's the write-time quality layer those systems are weakest at, packaged as a drop-in local MCP server.
| Jamgate | Mem0 / OpenMemory | Zep / Graphiti | |
|---|---|---|---|
| Core model | Write-time quality gate over a flat store | LLM-extracted memory layer | Temporal knowledge graph |
| Where memory lives | Local file on your machine | Hosted platform or self-hosted store | Graph server (self-hosted or cloud) |
| Their strength | — | Rich extraction, broad SDKs/integrations, scale | Powerful entity/relationship & temporal modeling |
| Gate before write | ✅ core design | Partial (dedup/update) | Partial |
| Source-trust hierarchy | ✅ | — | — |
| Refers conflicts back to you | ✅ | — | — |
| LLM calls of its own | ❌ none | ✅ required | ✅ required |
| Dependencies / infra | 1 dep, no server | SDK + service/DB | Graph DB + service |
| Best for | Keeping one shared personal memory clean, locally | Full-featured app-scale memory | Complex relational/temporal reasoning |
Mem0, OpenMemory, Zep, and Graphiti are capable systems built for different goals; if you need managed scale or graph reasoning, they're the right tool. Jamgate's bet is that for personal cross-agent memory, the hard part is quality at write time — and that it should be local, free, and one command to install.
~/.jamgate/gate.log, a strictly local, size-capped JSONL file that rotates
automatically and never leaves your machine. It exists to collect real usage data
for a future local quality classifier. Disable it with JAMGATE_GATE_LOG=off.Early but real, and now installable with one command. The MVP core, robustness,
intelligence, and optional remote layers all work today (see CHANGELOG.md
for the full scope):
npx jamgate setup wires every detected client (Claude Code,
Claude Desktop, Cursor, Windsurf) in one idempotent, backup-first command, plus a Cursor
deeplink and a Claude Desktop .mcpb bundle.Dockerfile, a Render blueprint, and a Railway
config so a non-technical user can click a button, log into a platform, and get their own
hosted instance with a generated token and a persistent disk. We host nothing.jamgate import --from claude|chatgpt replays another
product's memory export through the gate, so a new setup starts with day-one memory. Curated
entries only; conversation logs are never mined.Verified end-to-end over the MCP protocol (both stdio and HTTP) and covered by an automated
test suite (226 tests) on Node 20.x and 22.x. Next: a thin classifier for ambiguous cases
(trained on the local decision log) and multi-device sync (see DECISIONS.md).
Goal: impact, not profit — open-source (MIT), built in the open.
npm install
npm run build # compile TypeScript to dist/
npm test # compile and run the test suite (built-in node:test, no extra deps)
CI runs the build and tests on Node 20.x and 22.x for every push and pull request.
This is an impact project. The most valuable contributions are around write-time
quality (selective capture, dedup, contradiction handling, expiry) — the part the whole
field is weakest at. See AGENTS.md to get oriented, then
RULES.md.
FAQs
A neutral, cross-agent memory quality gate for AI agents, delivered as an MCP server — a gate, not a store.
The npm package jamgate receives a total of 44 weekly downloads. As such, jamgate popularity was classified as not popular.
We found that jamgate demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
Popular npm packages keyv and cacheable compromised.

Security News
A misconfiguration gave three Anthropic models internet access, and one, believing it was in a simulation, shipped a credential-stealing package to PyPI.

Security News
/Company News
Socket has joined the new Composer and Packagist sponsorship program as a launch sponsor, supporting the team that keeps PHP's package ecosystem secure.