Skip to main content
Glama

Jamgate

CI npm version license: MIT

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

Add to Cursor  •  one-click Claude Desktop bundle → the .mcpb on the latest release

The problem: memory quality, not storage

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

Related MCP server: engramia

The idea

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

What it does — the gate layers

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 before it reaches the store: fragments, pleasantries, placeholder text (test, foo bar), and anything that isn't a claim about you.

Credential refusal

Refuses 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 filter

A 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 filter

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

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 expiry

Volatile state ages out (~2 days) while identity never does, so recall stays current automatically.

Every rejection comes back with a reason the calling agent can act on — the agent is the only party able to correct the call, and a bare "rejected" just teaches it to work around the gate.

Everything is taggable, expirable, and deletable — you always see and control what's remembered.

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:

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:

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

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:

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.

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:

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

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

Zed — add to settings.json under context_servers:

{
  "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 we can't safely round-trip (TOML / YAML) are listed as manual with the one-liner to add yourself.

Agent

Config file

setup

Remote (--remote)

Claude Code

~/.claude.json

✅ auto

Claude Desktop

claude_desktop_config.json

✅ auto

connectors 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/formatting we 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.

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

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.

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, installable into 70+ coding agents with one command:

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.

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.

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.

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

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.

# 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

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

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:

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.

What we read, and what we deliberately don't

  • Curated memory / profile entries only — the list you reviewed and kept in the source app.

  • We never mine 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.

  • We never fetch 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 our guess).

  • type inferred conservativelypreference 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 — we host nothing. A tiny always-on instance with a small persistent disk runs roughly $5–7/month on Railway or Render. 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:

    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 → ConnectorsAdd 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

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

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 & path

Body / query

Returns

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

# /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;
    }
}

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:

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

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.

Honest limits (read this)

  • 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 = one human. Jamgate's memory is of one person, by design. There is no multi-user tenancy, no per-identity isolation or access control. That's a deliberate scope choice, not a missing feature — it keeps the security surface to a single secret and a single store. If several people each want a memory, run one instance per person.

  • Concurrency is single-process. Multiple agents hitting one instance at once is safe (writes are serialized by a lock and re-read before write). This holds for one Jamgate process on one host; it is not a distributed multi-node store.

  • No TLS in the box. If you skip the reverse proxy, you're sending a bearer token in the clear. Don't.

How it compares

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.

Privacy

  • Everything is local. The memory store, the gate, and (if enabled) the embedding model all run on your machine. Jamgate makes no network calls and talks to no cloud AI.

  • The decision log is local too. Every gate decision (saved / duplicate / superseded / conflict / possible_duplicate / rejected, with its reason) is appended to ~/.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.

  • Nothing leaves the machine — no telemetry, no accounts, no keys.

Status

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

  • Gate core — rule pre-filter, exact dedup, time-aware supersession, source-trust conflict guard, over a local flat-file store.

  • Robustness — atomic durable writes, type-based expiry, concurrency-safe locking, automatic schema migration.

  • Intelligence — trusted client provenance, fuzzy recall, optional local embeddings with graceful fallback, auto-subject derivation, local decision log.

  • Remote mode (optional) — self-hosted Streamable HTTP transport with bearer-token auth, so one instance can serve all of your agents (phone, browser, laptop) from one shared memory. stdio stays the default. A plain REST API (/v1/memory) runs on the same port behind the same token, for app backends that don't speak MCP.

  • Namespaces (scopes) (optional) — attach a scope to keep several isolated memories in one instance; the whole gate applies per scope. Omit it for the default single-tenant behaviour.

  • One-click installnpx jamgate setup wires every detected client (Claude Code, Claude Desktop, Cursor, Windsurf, Gemini CLI, VS Code / Copilot, Cline, Roo Code, OpenCode, Zed) in one idempotent, backup-first command, plus a Cursor deeplink and a Claude Desktop .mcpb bundle.

  • Deploy your own (no terminal) — a 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.

  • Bring your memory with youjamgate 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 (413 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.

Development

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

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.

License

MIT

Install Server
A
license - permissive license
A
quality
A
maintenance

Maintenance

Maintainers
Response time
0dRelease cycle
4Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    -
    quality
    D
    maintenance
    Provides a persistent, vendor-neutral memory layer that allows AI tools and agents to share context and knowledge across different platforms while maintaining local data ownership. It enables users to store, recall, and manage structured memories through hybrid semantic search and automated context assembly.
    Last updated
    8
    Apache 2.0
  • F
    license
    -
    quality
    A
    maintenance
    The memory operations platform for production AI agents — eval-weighted recall, multi-evaluator consensus, GDPR Art. 17/20 governance, multi-tenant RBAC, and audit logging built in.
    Last updated
  • A
    license
    A
    quality
    B
    maintenance
    Provides persistent, self-optimizing memory for AI agents, enabling them to remember preferences and context across sessions and share knowledge across multiple agents.
    Last updated
    4
    17
    ISC

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/amirj4m/jamgate'

If you have feedback or need assistance with the MCP directory API, please join our Discord server