Skip to main content
AllMCPs
BrowseBestCategoriesStackCompareToolsGuidesBlog Log in Submit MCP

Stay in the loop

Get new MCP servers and top picks in your inbox.

AllMCPs

The open directory for discovering and installing Model Context Protocol servers.

Explore

  • Browse servers
  • Best MCP servers
  • Categories
  • MCP clients
  • Agent prompts
  • Stack Builder
  • Compare servers
  • Tags index
  • Submit a server
  • Pricing

Learn

  • Guides hub
  • What is MCP?
  • Install guide
  • Troubleshooting
  • Security
  • Blog
  • Blog RSS

Tools

  • All tools
  • Config generator
  • Config validator
  • MCP playground
  • OpenAPI β†’ MCP
  • Badge generator

For agents

  • API docs
  • Trust & traffic
  • llms.txt β†— (opens in a new tab)
  • Catalog JSON β†— (opens in a new tab)
  • Remote MCP β†— (opens in a new tab)

Company

  • About
  • Contact
  • X (@AllMCPs) β†— (opens in a new tab)
  • GitHub β†— (opens in a new tab)
  • Terms
  • Privacy
AllMCPs VerifiedAllMCPs VerifiedFeatured on Nick LaunchesFeatured on Nick LaunchesLaunch Llama NewsletterLaunch Llama NewsletterVerified DR - allmcps.comVerified DR - allmcps.comFeatured on SaaSGrowFeatured on SaaSGrowFeatured on Twelve ToolsFeatured on Twelve ToolsFeatured on Saaspa.geFeatured on Saaspa.geFeatured on Findly.toolsFeatured on Findly.toolsFeatured on Startup FameFeatured on Startup FameFeatured on LaunchKiwiFeatured on LaunchKiwiFeatured on ScrollLaunchFeatured on ScrollLaunchFeatured on DailyPingsFeatured on DailyPingsFazier badgeFazier badgeFeatured on NewTool.siteFeatured on NewTool.siteFeatured on saasfame.comFeatured on saasfame.comDR Checker - Domain RatingDR Checker - Domain RatingListed on Turbo0Listed on Turbo0Launched on LaunchBoard - Product Launch PlatformLaunched on LaunchBoard - Product Launch PlatformList on SimilarlabsList on Similarlabshttps://codetrendy.comhttps://codetrendy.comListed on DevTool.ioFeatured on BuildlistFeatured on BuildlistAllMCPs VerifiedAllMCPs VerifiedFeatured on Nick LaunchesFeatured on Nick LaunchesLaunch Llama NewsletterLaunch Llama NewsletterVerified DR - allmcps.comVerified DR - allmcps.comFeatured on SaaSGrowFeatured on SaaSGrowFeatured on Twelve ToolsFeatured on Twelve ToolsFeatured on Saaspa.geFeatured on Saaspa.geFeatured on Findly.toolsFeatured on Findly.toolsFeatured on Startup FameFeatured on Startup FameFeatured on LaunchKiwiFeatured on LaunchKiwiFeatured on ScrollLaunchFeatured on ScrollLaunchFeatured on DailyPingsFeatured on DailyPingsFazier badgeFazier badgeFeatured on NewTool.siteFeatured on NewTool.siteFeatured on saasfame.comFeatured on saasfame.comDR Checker - Domain RatingDR Checker - Domain RatingListed on Turbo0Listed on Turbo0Launched on LaunchBoard - Product Launch PlatformLaunched on LaunchBoard - Product Launch PlatformList on SimilarlabsList on Similarlabshttps://codetrendy.comhttps://codetrendy.comListed on DevTool.ioFeatured on BuildlistFeatured on Buildlist
Β© 2026 Jackalope Digital LLC. All rights reserved.
  1. Home
  2. 🧠 Knowledge & Memory
  3. Jamgate
J
Health: Not checked yetWe have not completed a health check for this listing yet.Last checked 8/11/2026, 12:25:36 AM

Jamgate

Enrichment pendingWe haven’t run our AI enrichment pass on this listing yet, so the overview, use cases, and FAQ below may be sparse or missing. We work through the catalog over time β€” check back soon.
View Repository

A neutral memory quality-gate MCP server: save_memory, recall_memory, forget_memory over stdio.

Quick Install

Automated & IDE Setup

Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β€” or use 1-click editor setup below.

Add to CursorAdd to VS Code
Manual Client & Custom JSON ConfigExpand JSON β–Ύ

Install Config Generator

Choose your client
claude_desktop_config.json
{
  "mcpServers": {
    "jamgate": {
      "command": "npx",
      "args": [
        "-y",
        "jamgate"
      ]
    }
  }
}

πŸ’‘ Paste into ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows)

Install Directory Badge Claim listing Alternatives🧠 More in Knowledge & Memory

Documentation Overview

Jamgate

CI npm version license: MIT

Every AI tool I use keeps its own memory, so I kept re-introducing myself to all of them. Jamgate is one memory file on my machine that any MCP client can read and write, with a quality gate in front deciding what actually gets written. It runs locally and has one runtime dependency.

I built it for myself and I'm the only person who has used it in anger, which is worth knowing before you read the rest. What it can't do is a section, not a footnote.

One command wires it into every MCP client on your machine:

Terminal
npx jamgate setup

Add to Cursor Β β€’Β  one-click Claude Desktop bundle β†’ the .mcpb on the latest release

Why a gate and not just a store

Sharing memory between agents turns out to be the easy half. I had a working shared store early on and the problem it created was worse than the one it solved: within a week it was full of "jam is on a call", the same fact three times in slightly different words, and a stale preference from a month earlier being handed to an agent as though it were current.

The clearest public example of where this ends up is mem0 issue #4573, where a user audited their own production store of 10,134 entries by hand. The detail I keep coming back to is this one: 808 entries asserting "User prefers Vim". Nobody in that system used Vim. The extraction model hallucinated it once, it got stored, it came back in the next session's recall context, and the pipeline re-extracted it from its own output as though it were a fresh fact.

Read that report carefully before you lean on it, though. It is one person, one agent, 32 days, and a 2-billion-parameter local model did the extraction for the first 20 of those days. The headline "97.8% junk" is dominated by that weak model; in the batch extracted by a frontier model the rate was 89.6%. The issue is now closed, and a mem0 maintainer has since described changes shipped in April 2026 aimed squarely at these problems.

So I don't want to lean on the percentage, and this README used to. The part that survives all of those caveats is structural, and no model upgrade fixes it: if there is nothing between extraction and storage, a hallucination that gets stored once will be re-extracted forever. A better model changes how articulate the junk is. Sharing that memory across every agent you own just distributes it faster.

So Jamgate sits in the write path and decides what gets stored:

Code
                 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  β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     it all piles up, forever                 small, and still true

It runs as an MCP server, so any MCP client (Claude Code, Claude Desktop, Cursor, and seven others) talks to the same memory file on your machine.

Code
Agent β†’ [ Jamgate quality gate ] β†’ local store (~/.jamgate/memory.json)
        save_memory / recall_memory / forget_memory

The gate layers

A memory is kept if it is still true after this session and would change a future answer. Cheapest checks run first:

LayerWhat it does
Rule pre-filterDrops obvious non-durable noise before it reaches the store: fragments, pleasantries, placeholder text (test, foo bar), and anything that isn't a claim about you.
Credential refusalRefuses to store secrets. API keys (sk-…, AKIA…, ghp_…, JWTs, PEM blocks), password assignments, and high-entropy tokens next to credential wording are rejected with a reason β€” and kept out of the decision log too. A git sha or UUID in ordinary prose passes untouched.
Question filterA question asks for memory, it isn't memory. how much is jam's rent? is refused; a rhetorical question inside a longer fact is not.
Transience filterStatements pinned to this instant ("it's raining right now") are refused unless you type them as state, where a short TTL ages them out on their own.
Agent salienceUses the calling agent's own understanding as the main "is this worth remembering?" filter β€” no second LLM call of its own.
Thin classifier (built, and inert on most clients today)For the few saves a rule finds genuinely ambiguous, the gate asks your agent's own model one closed question over MCP sampling, so there is no API key, no vendor and nothing to pay for. It catches two things a rule cannot: text that announces itself as scaffolding (safe to delete, temporary test entry), and a long state save that is really a permanent fact and would quietly expire in two days. It can only tighten a verdict, never loosen one, and it never sees any memory but the one being saved. Sampling is optional in MCP and most clients do not implement it. Claude Code declares roots and elicitation and no sampling, so for most people today this layer never runs at all and the gate is rules-only. See Honest limits.
Exact dedupIdentical facts are never stored twice.
Time-aware supersessionEvery 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 hierarchyA 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.
Related-memory hint (optional)Below the duplicate bar but clearly on the same topic, the memory is stored and the look-alike is named, so the agent can re-save with a shared subject if it was really an update. A hint never retires anything.
Type-based expiryVolatile state ages out (~2 days) while identity never does, so recall stays current automatically. Expiry hides a memory; it never destroys one you asked for β€” compaction skips every user-explicit / user-confirmed record permanently, and jamgate expired lists them with no deletion deadline.
Write-time lifespan check (needs MCP elicitation)If your agent files something you asked it to remember as short-lived state, the gate asks you β€” once, at the moment of saving, showing the memory and the date it would disappear β€” and stores your answer. This exists because it happened to my own store: two facts I confirmed were filed as 2-day state, went dark unannounced, and were four weeks from deletion. Replayed on my real gate log it fires on 3 of 21 save decisions (14.3%) and stays silent on every agent-inferred state note. (That figure was previously given as 17.5%, measured against a gate log that turned out to be 95.5% test fixtures; it is re-derived here against the 25 genuine decisions β€” see D-078.) Claude Code declares elicitation, so this one actually runs. Off with JAMGATE_LIFESPAN_PROMPT=off.

Every rejection comes back with a reason the calling agent can act on. This matters more than it sounds: the agent is the only party in a position to fix the call, and a bare "rejected" just teaches it to retry with slightly different wording until something sticks.

Note what is not in that table: nothing here understands your memory. These are rules, regexes and cosine thresholds. See Honest limits.

Honest limits

Read this before the feature list, not after it. Everything below is measured or observed, and none of it is fixed yet.

Recall often puts the wrong memory first. On my own store β€” 12 real memories, 17 queries I wrote β€” the right memory came back at rank 1 in 10 of 17 cases, and appeared anywhere in the top 5 in 13. Turning on the optional embeddings used to make this worse than leaving them off; that's fixed, but "the answer is in there somewhere" is still an accurate description of recall on a store of any size. This is the weakest part of the project and the thing I'd fix next.

One specific failure inside that has been fixed, because it was worse than "imprecise": a memory past its freshness window could outrank the memory that corrected it. Asking the same question about money six ways, four of six returned the superseded figure first. Freshness is now a tier applied before relevance, so nothing past its window can outrank anything inside one β€” the same six phrasings now return the correct figure six times out of six, with the ordinary top-1 baseline unchanged (14/17 before and after, identical misses). It is not "newest wins": an identity fact never expires, so it is never stale and ranks on relevance exactly as it did. What is not fixed is the general ordering problem above, or contradiction detection across two different subjects β€” two live records can still assert different numbers for the same thing and nothing notices. For scale: Letta measured plain files plus grep at 74.0% on LoCoMo against Mem0's 68.5%, and I have no reason to think Jamgate's retrieval would beat either. See How it compares, where grep gets its own column.

Ranking has produced a wrong answer about money, not just a wrong order. Asked "how much do I still owe on the motorbike", recall returned a 6 August balance at rank 1 and missed the 8 August one entirely β€” both live, both in the store, €250 apart. Across six natural phrasings of that question the stale figure won four. This is the same weakness as the row above, but it is worth stating separately because "the answer is in there somewhere" stops being an acceptable description once the answer is someone's finances. See D-077.

A name written in one script cannot be found by its spelling in the other. Matching is token-based and script-literal, with no transliteration bridge. Measured on my own store, on two names recorded in Latin inside a Persian-speaking user's memory: Rahman and Iraj each return the right record at rank 1 for every natural English phrasing (11 of 12 at rank 1, one at rank 2) β€” and Ψ±Ψ­Ω…Ψ§Ω†, ایرج, Ψ¨Ψ―Ω‡ΫŒ Ψ±Ψ­Ω…Ψ§Ω†, Ψ¨Ψ―Ω‡ΫŒ ایرج return nothing at all, 0 of 4. Cross-script queries only work when the record happens to share tokens in the query's script. For a bilingual user this is data-loss-equivalent: the memory is there, correct, and unreachable in half the languages they actually type. Not fixed β€” see D-075 for the scope.

Nobody outside me has installed it. Many releases, ten supported clients, one user. I've simulated a cold install (fresh HOME, empty npm cache, published package rather than my working copy) and it held up, but simulation is not a stranger on their own machine.

macOS and Windows have never actually been run. Their config paths are unit-tested and CI is Linux-only. If you are on a Mac and jamgate setup writes to the wrong place, you are the first person to find out. Please open an issue.

Embeddings only attach when a memory is saved. Install the optional semantic package today and every memory you saved before that stays invisible to semantic recall until you save it again. There is no reindex command. This is a straightforward gap, not a hard problem, and it isn't done.

"Store-agnostic" is a seam, not a feature. Everything above src/store/ depends on a MemoryStore interface rather than a concrete backend, which is a real design property you can check in the source. But the bundled file store is the only implementation. There is no mem0 adapter, no Graphiti adapter, and no way for you to point Jamgate at your own store today. If a future write-up of mine implies otherwise, this line is the correct one.

The quality judgments are rules and numbers, not understanding. The gate cannot tell that "moved to Berlin last spring" and "no longer lives in Athens" are the same event. It matches subjects, compares cosines against thresholds I set from measurements, and applies regexes. It is genuinely good at the mechanical cases β€” exact duplicates, credentials, recency on a shared subject β€” and blind to anything requiring judgment.

The thin classifier ships in this release and does nothing on most machines. It is built and it is measured: 96.4% accuracy (27 of 28) on a labelled corpus, no real memory refused in any recorded run, and it routes about 30% of decisions. But it works by asking the calling client's own model over MCP sampling, sampling is optional in the protocol, and most clients have not implemented it β€” Claude Code declares roots and elicitation and no sampling. So unless your client samples, this layer never runs and your gate is the rules-only gate described above. I have not seen it change a single real save of my own, because every client I use is in that group. Treat the measurements as evidence the thing works when it is asked, not as evidence it is working for you.

One JSON file, read whole on every operation. At my ~60 records that is free. There is no index and no pagination, so at some size it stops being free. It is now measured rather than guessed at (D-078), with embeddings off, growing a store to 10,000 records:

recordsfileone saveone recall
1000.1 MB5 ms12 ms
1,0000.6 MB19 ms71 ms
5,0003.1 MB52 ms399 ms
10,0006.1 MB110 ms758 ms

Nothing breaks β€” no crash, no corruption, no lock failure; it degrades linearly. Recall hurts first, because it scores every record: perceptible past ~2,000 records and unpleasant past ~6,000. With embeddings on, every save also embeds and the near-duplicate scan compares all vectors, so treat these as the optimistic bound.

Semantic search is English-only. The bundled model is all-MiniLM-L6-v2. On other scripts its similarity degenerates into "is this the same language" (the Greek for bicycle scored 0.62 against an unrelated Greek memory), so non-Latin text is deliberately not embedded at all and falls back to lexical matching, which does work in every script.

A memory is text, and recall puts it into your agent's context. The gate decides whether something is worth keeping, not whether it is safe to act on. If a memory contains instructions, those words come back verbatim on the next recall, in a place the model reads. That is true of every memory system. Jamgate narrows the surface β€” it never scrapes screens, never mines chat logs, refuses credentials, and only writes on an explicit save_memory call β€” but it cannot make text inert. Treat the store as trusted input and look at what goes in; jamgate export prints all of it.

Remote mode has its own set of limits, listed under Remote mode.

Quick start

Jamgate runs locally β€” your memory never leaves your machine. Requires Node.js 20+. No install step: npx fetches and runs it on demand.

Option A β€” npx jamgate setup (recommended)

One command detects the MCP clients installed on your machine (Claude Code, Claude Desktop, Cursor, Windsurf, Gemini CLI, VS Code / Copilot, Cline, Roo Code, OpenCode, Zed) and wires Jamgate into each:

Terminal
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. A plain setup (local stdio) will also never silently overwrite a remote (--remote) wiring β€” it leaves that client as-is and tells you so; pass --force to downgrade it on purpose. Useful flags:

Terminal
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 setup --force                             # overwrite even a remote wiring with local stdio
npx jamgate status                                    # show which clients are wired + where the store lives
npx jamgate --help                                    # every command and environment variable

If setup finds no clients, that is normal on a machine where the client has been installed but never launched β€” a client writes its config on first run. Start it once, then re-run npx jamgate setup.

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.

Option B β€” per-client manual

Prefer to wire it yourself? Each client is a small config change.

Claude Code:

Terminal
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):

config.json
{
  "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):

config.json
{
  "mcpServers": {
    "jamgate": {
      "command": "npx",
      "args": ["jamgate"]
    }
  }
}

Windsurf β€” add the same mcpServers block to ~/.codeium/windsurf/mcp_config.json.

Gemini CLI β€” add the same mcpServers block to ~/.gemini/settings.json.

Cline / Roo Code β€” add the same mcpServers block to the extension's MCP settings file (Cline: .../globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json; Roo: .../globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json), or use each extension's "Configure/Edit MCP Servers" button.

VS Code (Copilot) β€” add to the user mcp.json (Command Palette β†’ MCP: Open User Configuration). VS Code uses a servers key and an explicit type:

config.json
{
  "servers": {
    "jamgate": { "type": "stdio", "command": "npx", "args": ["jamgate"] }
  }
}

OpenCode β€” add to ~/.config/opencode/opencode.json under the mcp key (note the single command array and enabled flag):

config.json
{
  "mcp": {
    "jamgate": { "type": "local", "command": ["npx", "jamgate"], "enabled": true }
  }
}

Zed β€” add to settings.json under context_servers:

config.json
{
  "context_servers": {
    "jamgate": { "command": "npx", "args": ["jamgate"] }
  }
}

Supported agents

jamgate setup auto-wires every agent below whose MCP config it can merge losslessly β€” each entry shape is verified against the vendor's official docs. Agents whose config lives in a non-JSON format I can't safely round-trip (TOML / YAML) are listed as manual with the one-liner to add yourself.

AgentConfig filesetupRemote (--remote)
Claude Code~/.claude.jsonβœ… autoβœ…
Claude Desktopclaude_desktop_config.jsonβœ… autoconnectors UI
Cursor~/.cursor/mcp.jsonβœ… autoβœ…
Windsurf~/.codeium/windsurf/mcp_config.jsonβœ… autoβœ…
Gemini CLI~/.gemini/settings.jsonβœ… autoβœ…
VS Code (Copilot)<Code>/User/mcp.jsonβœ… autoβœ…
Cline.../saoudrizwan.claude-dev/settings/cline_mcp_settings.jsonβœ… autoβœ…
Roo Code.../rooveterinaryinc.roo-cline/settings/mcp_settings.jsonβœ… autoβœ…
OpenCode~/.config/opencode/opencode.jsonβœ… autoβœ…
Zed~/.config/zed/settings.jsonβœ… autoβœ…
Codex CLI~/.codex/config.toml (TOML)manualΒΉβ€”
Goose~/.config/goose/config.yaml (YAML)manualΒΉβ€”
Continue~/.continue/config.yaml (YAML)manualΒΉβ€”

ΒΉ Manual β€” these use TOML/YAML; rather than risk mangling comments or formatting I don't auto-edit them. Add Jamgate by hand: Codex CLI β†’ [mcp_servers.jamgate] with command = "npx" and args = ["jamgate"] in ~/.codex/config.toml; Goose β†’ a stdio extension under extensions: with cmd: npx / args: ["jamgate"]; Continue β†’ an mcpServers: list entry with command: npx / args: [jamgate].

For agents that live in a shared, comment-friendly settings file (Gemini, OpenCode, Zed), setup will skip rather than overwrite a file it can't parse as strict JSON β€” so a //-commented settings.json is never clobbered; add the block by hand in that case.

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. Subjects are lowercase and hyphenated; dots, underscores and spaces fold to hyphens, so editor.theme and editor-theme are one key.
  • recall_memory β€” fetch what's known, relevant to a query (active facts only).
  • forget_memory β€” delete a memory by the id recall_memory printed (the full id, or an unambiguous prefix of 8+ characters).

Volatile memories age out of recall on a TTL, so a fact you saved can stop being returned without being deleted. jamgate expired shows exactly what recall is hiding and when it will be compacted away β€” nothing is modified:

bash
jamgate expired               # what has aged out of recall but is still on disk
jamgate expired --json        # machine-readable, for a script

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.

If you move to a remote instance: retire the local store

The worst failure mode of a memory that lives in two places is that nothing tells you. One client stays wired to the local stdio store, every save there succeeds, and you accumulate a second memory you don't know about. That happened to me: four days, seven memories, found only by a scheduled check.

Two things now make it loud rather than silent:

bash
jamgate status                                  # warns ⚠ SPLIT MEMORY if clients disagree
jamgate retire --to https://your-instance/mcp   # the old store refuses writes from now on

jamgate status compares every wired client and says so when some point at a remote instance while others write locally. jamgate retire marks the store β€” so any client that later gets wired back to it fails with an error naming the real instance instead of quietly writing there. Reads keep working and nothing is deleted, so a retired store is still fully readable; it just stops being a place new memories can land. jamgate retire --status reports the state and changes nothing.

The marker is written twice, deliberately: inside the store file (so it travels when the file is copied) and as a <store>.retired sidecar. The sidecar exists because a Jamgate older than 0.14.0 serializes the store without the in-file field and silently erases it on the first write β€” which is exactly what a stale server did to my own retired store. If the sidecar is there and the in-file marker is gone, jamgate status tells you an old build is still writing and you need to stop the process, not just remove its config entry.

Removing a client's config entry does not kill a server it already started. That is worth knowing because jamgate status reads config files: it will say "no split" while a stale process keeps serving the old store to a live client.

jamgate setup also refuses to add a local store when another client already points at a remote one, so the split cannot be created by accident (--force if you truly want two).

Agent skill: memory-discipline

Wiring in the three tools gives an agent the ability to remember. The memory-discipline skill teaches it the habits β€” recall before answering, save one granular durable fact at a time with a specific reused subject, never send secrets, and treat gate verdicts as answers rather than errors to retry. Its rules are distilled straight from Jamgate's own decision log (D-040…D-045).

It ships in this repo at skills/memory-discipline/SKILL.md as a portable agentskills.io instruction pack. One command installs it for every agent the skills CLI finds on your machine (it wired 17, including Cursor, Copilot and Claude Code, on the machine I tested it on):

Terminal
npx skills add amirj4m/jamgate

The skill is prompt text, not code β€” it is not part of the npm package (the files whitelist ships only dist), so it never bloats the runtime install.

Optional: local semantic search

By default, recall is fuzzy lexical matching (stemming, typo-tolerance, trigrams) β€” fast, deterministic, and dependency-free, but blind to synonyms. It works in any script: Persian, Greek, Cyrillic, Arabic, Hebrew, Chinese, Japanese and Korean all tokenize and recall, and accents fold so cafΓ© and cafe find each other. (Stemming is English-only, and Chinese/Japanese are segmented per character rather than per word β€” good enough to find a term inside a sentence, not a real word segmenter.) To also match on meaning (so "automobile" recalls a memory about your "car"), install the optional embedding backend:

Terminal
npm install @huggingface/transformers

On first use it downloads all-MiniLM-L6-v2 and runs it entirely on your machine β€” no text is ever sent to any cloud AI. Budget about 90 MB for that download: this README used to say "~23 MB, quantized", which was wrong on both counts. Transformers.js fetches the fp32 model by default and the cached directory measures 87 MB on disk, so on a small VPS or a metered connection, plan for the real number. With the model in place, recall blends semantic similarity into the ranking, and a save that means the same as an existing memory comes back as a possible_duplicate for you to confirm. If the package isn't installed, Jamgate runs on fuzzy recall and nothing breaks.

What to expect from it, measured rather than assumed (D-063): the thresholds are set from real cosines on this model over a real store, not from estimates. Two limits are worth knowing before you install it:

  • Embeddings attach when a memory is saved. Memories written before you installed the package have no vector, so they stay on fuzzy recall until they are saved again. There is no backfill command yet.
  • Long memories dilute. The model mean-pools, so a short query against a 500-character memory scores lower than against a one-line fact. Synonym reach is strongest exactly where the README's example is β€” short, single-fact memories.
  • English only. all-MiniLM-L6-v2 is an English model, and on other scripts its "similarity" collapses into "is this the same language" β€” measured, with the Greek for bicycle scoring 0.62 against an unrelated Greek memory. So non-Latin text is deliberately not embedded: those languages stay on fuzzy lexical recall, which works properly in every script. Nothing is lost by installing the package if you write in Persian or Japanese; nothing is gained either.

Namespaces (scopes)

By default Jamgate is single-tenant: one human, one memory. If you need one instance to hold several memories that must not blend β€” a tutor app with separate subjects, or a small group sharing an instance β€” attach an optional scope (an opaque label such as amir/greek) to a memory and to each operation:

  • The gate is per scope. Deduplication, subject supersession, the source-trust conflict guard and the semantic near-duplicate check all compare a new memory only against others in the same scope. Two scopes can hold the same text, the same subject, even contradictory facts, without one affecting the other.
  • Recall and forget are strictly scoped. Recall returns only the requested scope; forget resolves an id only within its scope, so one namespace can never read or delete another's memory β€” even with the exact id.
  • Omitting the scope is the normal case. An absent or empty scope means the single default namespace, which is exactly how Jamgate behaved before namespaces existed. Nothing changes for a single-user setup.

Over MCP, pass scope on save_memory / recall_memory / forget_memory. Over the REST API (below), pass it in the JSON body or as a ?scope= query parameter. Scopes are just case/whitespace-folded labels β€” user/role is a useful convention, not a required format.

Multi-user separation (per-person accounts and auth) is a different thing and is not what a scope provides: whoever holds the JAMGATE_TOKEN can address any scope on that instance. A scope is a namespace within one token-holder's memory.

Configuration

All configuration is via environment variables; every one has a sensible default.

VariableDefaultWhat it does
JAMGATE_STORE~/.jamgate/memory.jsonPath to the memory store file.
JAMGATE_EMBEDDINGSautooff disables the semantic layer even if the model is installed.
JAMGATE_DUP_THRESHOLD0.88Semantic near-duplicate sensitivity (0–1); higher = stricter. Measured against the real model, true rewordings span ~0.76–0.94 and different facts reach ~0.81, so the two overlap β€” 0.88 deliberately favours never refusing a real memory over catching every reword.
JAMGATE_GATE_LOGonoff disables the local decision log.
JAMGATE_CLASSIFIERonoff disables the thin classifier outright β€” no sampling request is ever sent and the gate runs rules-only. It is already inert on any client that doesn't support MCP sampling.
JAMGATE_CLASSIFIER_TIMEOUT_MS8000How long a save may wait for the classifier's answer before giving up and saving as rules-only. A save is never blocked on a model.
JAMGATE_TTL_<TYPE>_DAYSper typeOverride the freshness window for a memory type, e.g. JAMGATE_TTL_PROJECT_DAYS=180.
JAMGATE_HTTPoff1/true enables remote mode (same as the --http flag).
JAMGATE_PORT8420Port for remote mode (same as --port).
JAMGATE_HOST127.0.0.1Interface 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_OAUTHonIn 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.jsonPath to the OAuth state file (registered clients + hashed tokens).

Backup & migration

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.

server.ts
# 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.json on the box (or just place the file at JAMGATE_STORE β€” but import is what merges into an existing server store safely).

Bring your memory with you

If you've been using Claude or ChatGPT for a while, they already know things about you, and starting from an empty file is the annoying part of trying anything new. jamgate import --from <vendor> takes the memory list you copy out of either one and replays it through the same gate a live save goes through, so duplicates and junk don't come along with it.

server.ts
# 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

How to get your export

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:

ProductWhere your memories areWhat to do
ClaudeSettings β†’ 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 β€” which is what the parser expects.
ChatGPTSettings β†’ 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, it reads that too β€” best-effort, looking for entries under memory-ish keys β€” and it accepts the .zip or the extracted folder directly and pick the memory-shaped file out of it.

What it reads, and what it deliberately doesn't

  • βœ… Curated memory / profile entries only β€” the list you reviewed and kept in the source app.
  • ❌ It never mines your conversation logs. 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.
  • ❌ It never fetches anything from a vendor account. You download your own export, yourself. Jamgate reads a local file and nothing else.

What happens to each entry

Every parsed line becomes a memory and goes through the gate, never blind-appended:

  • source 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 a guess by this tool).
  • type inferred conservatively β€” preference or identity only when the wording is obvious; otherwise left untyped. A wrong type is worse than no type.
  • original dates preserved when the line carries one, so time-aware supersession orders your history correctly. Undated entries are stamped at import time.
  • provenance recorded as import:claude.ai / import:chatgpt, so you can always see where a memory came from.
  • the gate decides β€” exact duplicates are skipped, a newer fact about the same subject supersedes the older one, contradictions with more-trusted memories are flagged instead of silently applied, and near-duplicates are surfaced for you.

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.

Deploy your own (no terminal needed)

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):

  • You pay the platform directly. I host nothing. A tiny always-on instance with a small persistent disk was roughly $5–7/month on Railway or Render when I last checked in August 2026, and platform pricing moves β€” check theirs, not mine. That bill is between you and the platform; Jamgate takes no cut and runs no cloud.
  • Your instance, your data. The memory store lives on a disk in your account on your platform. Jamgate never sees it, never proxies it, has no telemetry. A deploy button is convenience, not hosting β€” see DECISIONS D-031.
  • Whoever holds the token holds the memory. The deploy generates a strong bearer token for you. Treat it like a password. There are no per-user accounts (one instance = one person; see Honest limits).

Deploy to Render (works today)

Deploy to Render

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).

Deploy on Railway

Deploy on Railway

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).

Get your URL and token, then connect your devices

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, Gemini CLI, VS Code, Cline, Roo Code, OpenCode, Zed) β€” one command:

    Terminal
    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 β€” e.g. Claude Desktop β€” 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.

Remote mode (self-hosted)

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).

Run it

server.ts
# 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.

Security model

  • Bearer token. One shared secret in 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.
  • TLS is terminated by a reverse proxy, not by Jamgate. Jamgate speaks plain HTTP and binds to 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.
  • Your server, your data. The store is still a flat file on a disk you own. No Jamgate cloud, no third party, no telemetry. "Self-hosted" means exactly that.
  • OAuth without an identity provider. For clients that require OAuth (claude.ai, the Claude app), your instance is its own authorization server β€” your JAMGATE_TOKEN is still the only credential, PKCE is enforced, and issued tokens are stored hashed and revocable. Details in Adding to claude.ai.

REST API (for app backends)

MCP is the right protocol for agents, but an ordinary app backend just wants plain HTTP. In remote mode Jamgate also serves a small REST API on the same port, behind the same bearer token β€” so a mobile app or a script can save and recall without speaking JSON-RPC:

bash
BASE=https://memory.example.com/v1/memory
AUTH="Authorization: Bearer $JAMGATE_TOKEN"

# Save (optionally into a namespace β€” see "Namespaces" above)
curl -sX POST "$BASE" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"text":"the aorist tense expresses a completed action","scope":"amir/greek","type":"project"}'

# Recall within a scope
curl -s "$BASE?query=aorist&scope=amir/greek" -H "$AUTH"

# Forget by id, within a scope
curl -sX DELETE "$BASE/<id>?scope=amir/greek" -H "$AUTH"
Method & pathBody / queryReturns
POST /v1/memory{text, scope?, type?, subject?, source?} (content/memory accepted as aliases of text)201 with {action, memory, …} when a record lands; 200 with the gate's action when it deliberately stores nothing (duplicate/conflict/possible_duplicate/rejected)
GET /v1/memory?query=&scope=&limit=200 with {memories: […]}
DELETE /v1/memory/:id?scope=200 {ok:true,id}, 404 not found, 409 ambiguous prefix

Every REST save goes through the exact same gate as the MCP tool (dedup, supersession, conflict guard, credential refusal), per scope. A missing/wrong token is a 401; a malformed body is a 400. The MCP endpoint (/mcp) and the OAuth flow are unaffected β€” REST is purely additive.

Deploy: systemd + Caddy

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):

ini
# /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
bash
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:

Caddyfile
memory.example.com {
    reverse_proxy 127.0.0.1:8420
}

nginx β€” equivalent, with TLS certs managed by certbot. Note that nginx, unlike Caddy's reverse_proxy, forwards only the paths you name: every Jamgate surface needs its own location, and one you forget returns nginx's own 404 without the request ever reaching Jamgate.

nginx.conf
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;
    }

    # REST API (0.10.0). REQUIRED if you use it β€” without this block `/v1/memory` is a
    # 404 from nginx, not a 401 from Jamgate, and every REST client silently sees "no
    # such endpoint" instead of "you need a token".
    location /v1/ {
        proxy_pass         http://127.0.0.1:8420;
        proxy_http_version 1.1;
        proxy_set_header   Host $host;
        proxy_set_header   Authorization $http_authorization;
    }

    # MCP OAuth (needed for claude.ai and the Claude mobile app).
    location /.well-known/ { proxy_pass http://127.0.0.1:8420; }
    location /authorize    { proxy_pass http://127.0.0.1:8420; }
    location /token        { proxy_pass http://127.0.0.1:8420; }
    location /register     { proxy_pass http://127.0.0.1:8420; }

    # Liveness probe (unauthenticated by design; exposes only status + version).
    location /healthz { proxy_pass http://127.0.0.1:8420/healthz; }
}

Verify each surface actually reaches Jamgate after any proxy change β€” an unauthenticated request must come back 401 from Jamgate, never 404 from the proxy:

Terminal
curl -si https://memory.example.com/v1/memory | head -1   # expect: HTTP/2 401
curl -s  https://memory.example.com/healthz               # expect: {"status":"ok","version":"…"}

Connect your agents

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:

Terminal
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>.

Adding to claude.ai (MCP OAuth)

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:

  1. In claude.ai (or the app): Settings β†’ Connectors β†’ Add custom connector β†’ URL https://your-domain/mcp.
  2. Claude discovers the flow, registers itself, and opens a Jamgate page in your browser.
  3. The page asks for your instance token β€” paste your JAMGATE_TOKEN and submit. That's the only thing it ever asks for, and only once per client.
  4. You're connected. 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):

EndpointSpecPurpose
GET /.well-known/oauth-protected-resourceRFC 9728Tells the client where the authorization server is. A 401 from /mcp also carries a WWW-Authenticate header pointing here.
GET /.well-known/oauth-authorization-serverRFC 8414Advertises the endpoints below; PKCE S256 required.
POST /registerRFC 7591Dynamic client registration β€” the client gets a client_id.
GET/POST /authorizeOAuth 2.1The consent page that asks for your instance token, then issues a single-use, PKCE-bound authorization code.
POST /tokenOAuth 2.1Exchanges 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.

Remote mode limits

These are on top of the general limits above.

  • Whoever holds the token holds the memory. There are no per-user accounts; the token is the authentication. Treat it like a password: strong, secret, rotated on suspicion.
  • One instance is one human. There is no multi-user tenancy, no per-identity isolation, no access control. That was a scope decision, and it keeps the security surface down to one secret and one store, but if three people each want a memory you run three instances.
  • Concurrency is single-process. Several agents hitting one instance at once is safe β€” writes take a lock and re-read before writing. That holds for one process on one host. It is not a distributed store and will not survive being run twice against the same file.
  • No TLS in the box. Skip the reverse proxy and you are sending a bearer token in the clear. Don't.
  • One memory is one fact, up to 32 KB. Bigger saves are refused rather than truncated. If you want a document remembered, save the conclusion.

How it compares

There are no benchmark numbers here. This category has had two of them retracted in public and I am not adding a third from a project with one user.

The column that should worry me most is the last one. Letta benchmarked plain markdown files with grep and semantic search against Mem0 on LoCoMo and the files won β€” 74.0% versus 68.5% with GPT-4o mini (their write-up). That is somebody else's benchmark of somebody else's system, and Jamgate has never been run on LoCoMo, so I cannot tell you where it would land. But the honest reading is that a directory of text files and a twenty-year-old command-line tool is a serious baseline, and any memory product that cannot say why it beats that baseline probably doesn't.

JamgateMem0 / OpenMemoryZep / GraphitiPlain files + grep
Core modelRule-based gate in front of a flat fileLLM-extracted memory layerTemporal knowledge graphA directory of text
Where memory livesA JSON file on your machineHosted platform or self-hosted storeGraph server (self-hosted or cloud)Files on your machine
Gate before writeCore designPartial (dedup/update)PartialNone
DuplicatesExact + optional semanticYesYesPile up forever
Superseding an outdated factBy subject, automaticallyPartialYes, temporallyYou edit the file
Source-trust hierarchyYesNot that I can findNot that I can findNo such concept
Expiry of volatile stateBy type, automaticPartialYesNever
LLM calls of its ownNoneRequiredRequiredNone
Dependencies / infra1 runtime dep, no serverSDK + service/DBGraph DB + serviceZero. You have it already
Retrieval qualityWeak. Fuzzy lexical + optional local embeddings; 10/17 top-1 on my own storeReal vector retrieval and reranking, tuned over many deploymentsGraph traversal plus vector searchBeat Mem0 on LoCoMo (74.0 vs 68.5)
Understands what a memory meansNo. Regexes, subject matching, cosine thresholdsYes β€” LLM extraction is the whole designYes β€” entity and relationship extractionNo
Entity / relationship reasoningNone. Flat records with a subject stringSomeThis is what it is forNone
ScaleMeasured to 10,000 records; recall slows past ~2,000 (758 ms at 10k). One file, read whole, no indexProduction deploymentsProduction deploymentsMillions of lines, fine
Multi-user / teamsNo. One instance, one person, one tokenYesYesWhatever your filesystem does
Language supportLexical recall in any script; semantic is English-onlyMultilingual modelsMultilingual modelsAny bytes at all
SDKsMCP and a small REST APIPython, TS, and morePython, TS, and moreEvery language ever written
MaturityOne developer, one user; see the releasesFunded team, wide adoptionFunded team, wide adoptionOlder than all of us
Best forOne person's cross-agent memory, kept small and current, on their own diskApplication-scale memory with real retrievalRelationship and temporal reasoningAlmost certainly your first thing to try

Read that last column honestly: grep beats Jamgate on retrieval, scale, language support, and every kind of maturity, and it costs nothing because you already have it. What it has no concept of is writing β€” every duplicate, every contradiction and every stale fact stays in your files until you go and edit them yourself. That is the entire bet of this project: that for a memory several agents write to unattended, the write side is where the work is. If you are happy curating the files yourself, curate the files yourself. You will probably get better retrieval than I can give you.

The Jamgate column is checked by the test suite and by the measurements in DECISIONS.md. The other two are read from those projects' public documentation as of August 2026 and describe default behaviour, not the ceiling of what they can be configured to do. If a row is wrong, open an issue and I will fix it β€” including the ones that are unflattering to them.

Short version: if you want the best retrieval, use Mem0. If your memory is really a graph of people and events, use Zep. Jamgate is worth a look if what you want is one small memory of yourself that several agents share, on a disk you own, and you care more about it staying clean than about it being clever.

Privacy

Your memories are never sent anywhere by Jamgate. There is no outbound request carrying your data, no telemetry, no accounts and no keys. Jamgate has no provider of its own to call. The store, the gate and the embedding model all run on your machine.

Two things do touch the network, both of them downloads and neither carrying your text: npx fetching the package from npm, and β€” only if you installed the optional semantic package β€” the embedding model's first-run download from Hugging Face (about 90 MB, then cached). Both stop after install.

The thin classifier is the one place any of your text moves, and only on a client that implements MCP sampling. When it fires, the gate sends one question back down the MCP connection it is already serving, and your agent answers it with the model it was already using. Jamgate still calls nobody. What travels is only the text of the save being judged, never another memory, never the store, never a recall result, and it goes to the model that just wrote that text in the same session, so it cannot disclose anything your client did not already have. On a client without sampling it never happens at all, which today is most of them. If you would rather it never happened anywhere, set JAMGATE_CLASSIFIER=off.

There is also a local decision log. Every gate verdict (saved, duplicate, superseded, conflict, possible_duplicate, rejected), plus the classifier's answer on the decisions where one was consulted, is appended to a size-capped JSONL file that rotates on its own and never leaves the machine. I keep it because it is the corpus the classifier is measured against, and a future local classifier would need real examples to be any good. It lives beside the store β€” gate.log in the same directory as JAMGATE_STORE, or ~/.jamgate/gate.log. JAMGATE_GATE_LOG overrides the path and JAMGATE_GATE_LOG=off turns it off. It holds the memory text, so if that bothers you, turn it off. (It follows the store rather than the home directory so it stays writable under a hardened systemd unit with ProtectHome=true; see D-037.)

Status

I use this daily, it holds my real memory, and it has not lost a record. That is the strongest claim I can make honestly. It is not battle-tested, because there has only ever been one battle.

What works today: the gate itself (rule pre-filter, credential refusal, exact dedup, time-aware supersession, the source-trust conflict guard), atomic durable writes with locking and schema migration, fuzzy recall in any script with optional local embeddings on top, the setup wizard for ten clients, and import --from claude|chatgpt for moving your memory off another product. Optional and less exercised: remote mode over HTTP with a bearer token and MCP OAuth, a small REST API on the same port, and namespaces. Those last three work and are tested, but I am the only person who has ever pointed anything at them β€” the local stdio path is the one that gets used every day.

644 tests on Node 20 and 22, run against a real MCP handshake on both transports. The full history is in CHANGELOG.md, and every non-obvious decision, including the ones I got wrong and reversed, is written up in DECISIONS.md.

The thin classifier for ambiguous cases is now built and measured, but read the row in the table above before you count on it: it is inert on every client that does not implement MCP sampling, which today is nearly all of them.

Next: better recall ranking, a reindex command, and a client that can actually answer a sampling request. MIT, and I am not trying to make money from it.

Development

Terminal
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.

Contributing

The most useful thing you can do right now is install it and tell me what broke, especially on macOS or Windows, which I have never run. After that: recall ranking, which is the part I'm least happy with.

AGENTS.md gets you oriented and RULES.md has the detail. Both are written for an AI agent as much as for a person, since most of this was built with one.

License

MIT

Related MCP Servers

View all in Knowledge & Memory View all alternatives
  • Moxie Docs MCP logoMoxie Docs MCP
    β˜… Featured

    MCP & Agent Skills for Automated Documentation, and codebase conventions + context

    🧠 Knowledge & Memory17 views
    Compare vs Moxie Docs MCP β†’
  • Mcp Server logoMcp Server

    Apple Developer Documentation with Semantic Search, RAG, and AI reranking for MCP clients

    🧠 Knowledge & Memory0 views
    Compare vs Mcp Server β†’
  • Mcp Obsidian logoMcp Obsidian

    Universal AI bridge for Obsidian vaults using MCP. Provides safe read/write access to notes with 11 comprehensive methods for vault operations including search, batch operations, tag management, and frontmatter handling. Works with Claude, ChatGPT, and any MCP-compatible AI assistant.

    🧠 Knowledge & Memory2 views
    Compare vs Mcp Obsidian β†’
  • Mcp logoMcp

    AI memory layer β€” one shared, persistent memory across every AI tool you connect.

    🧠 Knowledge & Memory0 views
    Compare vs Mcp β†’

Frequently Asked Questions about Jamgate

Add the following block to your claude_desktop_config.json under mcpServers: "mcpServers": { "jamgate": { "command": "npx", "args": ["-y", "jamgate"] } }

AllMCPs Directory Badge

Full Badge Customizer

Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.

Badge Style:
Live Dynamic SVG PreviewJamgate AllMCPs Directory Badge
Markdown (GitHub README)
[![AllMCPs](https://allmcps.com/api/badge/jamgate?style=directory)](https://allmcps.com/mcp/jamgate)
HTML Embed
<a href="https://allmcps.com/mcp/jamgate"><img src="https://allmcps.com/api/badge/jamgate?style=directory" alt="Jamgate on AllMCPs" /></a>

Technical Specs & Signals

Category🧠Knowledge & Memory
More technical detailsExpand β–Ύ
TransportSTDIO
RuntimeNode.js
0/4 checks healthy over the last 6h
Views0
Unique ViewsTotal visits recorded for this listing page on AllMCPs.
Installs0
Installs & Copy ActionsTotal times users copied install commands or configuration snippets for this server.
27Quality signal: Emerging Β· 27/100How this signal is calculated β–Ύ
Server availabilityNot measured

Not scored for repo-hosted servers β€” we can't reach the running server, only its GitHub page. Hosted MCP endpoints are health-checked live.

Verified ownership8/20
Documentation & tools11/30
Adoption & activity1/15
Community engagement0/10

A guidance signal from public completeness & health data β€” not a user rating. New listings start lower and rise as they add docs, get verified, and grow adoption. Signals we can't observe for a listing are skipped, not counted against it.

β˜… Spotlight Slot

Feature Your MCP Server

Get maximum visibility for your server across our directory, search results, and detail pages.

Spotlight Your Server

Own this project?

This directory is pre-filled from public sources. Claim via GitHub README, site badge, or DNS TXT to get the verified badge and attach your website.

Free dofollow backlink: after claiming, verify your product site and place a dofollow AllMCPs badge β€” we recheck it stays live.

Claim & get free dofollow

Share & Embed

Add our SVG badge (dark/light directory styles) or embeddable widget to your site.

Explore more

More in 🧠 Knowledge & Memory β†’Best MCP servers for Memory & Knowledge β†’Alternatives to Jamgate β†’Install in Claude DesktopInstall in CursorInstall in VS Code