Releases: stevepridemore/graph-memory
v0.3.0 — curl-pipeable installers + pre-built GHCR image
First release that ships without requiring users to clone the repo or build from source. The new install paths are documented in the README.
Highlights
-
Primary device install (machine running the containers):
curl -fsSL https://raw.githubusercontent.com/stevepridemore/graph-memory/v0.3.0/scripts/install-primary.sh | bash -s v0.3.0PowerShell mirror at
scripts/install-primary.ps1. -
Secondary device install (anything pointing at the primary over Cloudflare Tunnel):
curl -fsSL https://raw.githubusercontent.com/stevepridemore/graph-memory/v0.3.0/scripts/install-secondary.sh | bash -s v0.3.0 <tunnel-host>
PowerShell mirror at
scripts/install-secondary.ps1. -
Developer install unchanged: clone +
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d.
What's in the box
- Pre-built MCP image at
ghcr.io/stevepridemore/graph-memory-mcp:v0.3.0(also tagged:latest). Multi-stage build means no host-sidenpm installneeded. - 12 graph-memory slash commands vendored under
skills/and installed to~/.claude/skills/by the installer. - Container entrypoint auto-seeds the prompts directory and generates a self-signed TLS cert on first run.
- New
.github/workflows/release.ymlpublishes the GHCR image on everyv*tag. scripts/sync-dream-skill.pynow does platform-aware path substitution (--os auto|windows|unix).
Bug fixes
- Slash commands
ingest,ingest-audio, andgraph-dreamwere referencing~/.claude/graph-memory/for the ingest queue, but the actual data dir is~/graph-memory/. Fixed. - MCP server now generates a self-signed cert on first run instead of crashing with
ENOENT: server.crt.
Tested
- Tier 1 local: install dry-run on a WSL clone + multi-stage
docker build+docker compose upagainst the locally-tagged image. Both containers healthy,/healthreturns 200 over HTTPS. - Tier 2 live: anonymous
docker pullfrom GHCR + installer running against the published branch artifacts. Same green result.
🤖 Generated with Claude Code
v0.2.1 — STRIDE threat model fully closed (16/16)
Closes the remaining three STRIDE findings deferred at the v0.2.0 OAuth hardening pass. Internal threat model is now fully resolved (16 of 16 findings).
Included PRs
- #23 —
OAUTH_ALLOWED_EMAILSallowlist (closes S-2). Optional second-layer guard on top of Cloudflare Access at/oauth/authorize. Supports exact emails and*@domainwildcards. Re-validated ongrant_type=refresh_tokenso removing an email invalidates refresh tokens within seconds. - #24 —
readBodysize caps (closes D-3). 64 KB on OAuth routes, 4 MB on/mcp. Two-layer defense (fast reject viaContent-Length, streaming reject via byte counter). Hardcoded named constants. - #25 — Drop confidential-client support (closes I-1). Removes a misadvertised path: the server had been generating and storing plaintext
client_secrets but never verifying them. Discovery metadata now advertises["none"]only;/oauth/registerrejects confidential auth methods. Future-enhancements note added todocs/REMOTE.mdcovering the deferred server-to-server /client_credentialscase.
Compatibility
All real clients (claude.ai web, Claude Desktop, Claude Code) use PKCE + token_endpoint_auth_method: "none", which became mandatory in #18. No on-disk migration required. Production deployed at 182204a and smoke-tested on the wire.
v0.2.0 — OAuth 2.1 security hardening
v0.2.0 — OAuth 2.1 security hardening pass
Closes 12 of 16 findings from an end-to-end STRIDE threat model of the
public deployment at https://your-host.example/mcp.
-
POST /oauth/revoke (RFC 7009) — token revocation with persistent
deny-list at $GRAPH_MEMORY_HOME/oauth/revoked.json (auto-prunes
naturally-expired entries). revocation_endpoint advertised in
authorization-server metadata. -
Every issued access/refresh token now carries a unique jti claim for
individual revocation lookup. -
redirect_uri hostname allowlist on POST /oauth/register
(claude.ai, *.claude.ai, claude.com, *.claude.com, localhost,
127.0.0.1 by default; suffix-match-safe wildcard). Configurable via
the new OAUTH_REDIRECT_URI_HOSTS env var. -
Registration cap via OAUTH_MAX_CLIENTS (default 100; 429 once full).
-
clients.json now written with mode 0o600.
-
Structured OAuth event logging to
$GRAPH_MEMORY_HOME/logs/oauth-events.jsonl: register / register_fail /
authorize_ok / authorize_fail / token_issue / token_refresh /
token_consume_fail / token_pkce_fail / token_refresh_fail /
client_deregistered / revoke_ok / revoke_noop / bearer_verify_fail.
Each event carries client_id, email, jti, redirect_uri_host, reason,
and source_ip as applicable. Best-effort; never throws from the
request path. -
Refresh-token TTL: 90 days → 30 days. Existing tokens keep their
original expiry; new tokens issued after deploy use the shorter TTL. -
PKCE-S256 is now required for any client registered with
token_endpoint_auth_method: "none" (i.e. public clients including the
claude.ai connector). code_challenge_method=plain is rejected.
Discovery metadata advertises ["S256"] only. Existing claude.ai
connectors already send S256 — no flow change. -
Refresh-grant rejects tokens whose client_id is no longer in
clients.json (closes E-4). -
401 response bodies for bearer-verify failures now use a constant
string; verbose jose reasons live in oauth-events.jsonl (closes I-3). -
BearerVerifyError extends TenantAuthError, carrying .reason
separately from the public .message. -
revokeToken signature: Promise →
Promise<{ revoked: boolean; jti?: string }>. -
New module src/shared/oauth-events.ts (logger, OAuthEvent interface,
pickClientIp helper). -
config.ts now honors GRAPH_MEMORY_HOME env var (was hardcoded to
homedir()/graph-memory). -
docker-compose pins GRAPH_MEMORY_HOME=/root/graph-memory in the
container's environment block to override the host-side value
injected via env_file.
| ID | Severity | Status |
|---|---|---|
| S-1 | HIGH | closed |
| T-1 | LOW | closed |
| D-1 | MED | closed |
| E-1 | HIGH | closed |
| E-2 | MED | closed |
| E-3 | HIGH (chain) | closed (subsumed by S-1 fix) |
| E-4 | LOW | closed |
| R-1 | MED | closed |
| I-3 | LOW | closed |
| S-3, T-2, I-2, D-2 | informational | no action needed |
| S-2 | MED | open (deferred) |
| I-1 | LOW | open (deferred — code path currently unused) |
| D-3 | LOW–MED | open (verification step) |
47 OAuth unit tests + 12 OAuth-event-log tests + 46 Neo4j integration
tests + 2 PKCE tests = 100/100 passing against a throwaway Neo4j
side-car. CI runs against a fresh service container per build.
npm audit: 5 vulnerabilities (1 high, 4 moderate) → 0. All in
transitive deps fixable by patch/minor bumps; none reachable from
runtime paths we exercise.
2026-05-09.
v0.1.1 — Decay correctness + test coverage
⚠️ Operational note before upgrading
graph_decay was previously dropping the months component of a normalized duration, so any backdating beyond ~28 days only applied a fraction of expected decay. After this release, decay matches the documented per-type half-lives (Preferences ~693d, Events ~99d, etc.).
First run on an existing graph may show large catch-up decay. Recommended:
graph_export { label: "pre-v0.1.1-decay-fix" } # back up first
graph_decay { dry_run: true } # preview the impact
graph_decay {} # apply when ready
What's new
- Fixed decay-formula bug (#13, commit 45edc8d) —
duration.between().daysreturned only the days component of a normalized year/month/day duration. Switched toduration.inDays().dayswhich forces an all-days representation. - 15 new integration tests covering decay math, bi-temporal queries, contradiction edge cases (#13)
- Test suite is data-safe by construction (#12) — per-tenant isolation plus a startup guard that refuses to run against graphs holding non-test data. Catches the footgun where the test defaults match the production docker-compose Neo4j.
- graph_merge tests added; stale test signatures repaired (#10)
- CI runs typecheck + integration suite against a Neo4j 5.20 service container on every PR (#9, #10, #11)
New internal APIs
Not user-facing MCP tools — useful for ops/maintenance scripts:
Neo4jClient.clearTenant(tenantId)/countNodesOutsideTenantPrefix(prefix)Neo4jClient.setNodeLastSeen(tenantId, id, daysAgo)/setEdgeLastConfirmed(...)Neo4jClient.setEdgeInvalidAt(tenantId, fromId, toId, relation, isoString)— closes a documented-but-unimplemented gap; supersession was in the schema but had no public API before this.
Other
Full changelog: v0.1.0...v0.1.1
v0.1.0
What's Changed
- Add cross-device demo to README by @stevepridemore in #1
- Add graph_merge_suggestions tool by @stevepridemore in #2
- Add Dockerfile.glama for awesome-mcp-servers listing by @stevepridemore in #3
- Add glama.json by @stevepridemore in #4
- Tighten tool descriptions and add Zod defaults for Glama TDQS by @stevepridemore in #5
- Add graph_merge tool, polish graph_contradictions by @stevepridemore in #6
- Address Grok review TODOs: entity_resolved audit event, graph_reembed docs, BACKUP.md by @stevepridemore in #7
- Add Glama score badge to README by @stevepridemore in #8
- Add typecheck CI by @stevepridemore in #9
New Contributors
- @stevepridemore made their first contribution in #1
Full Changelog: https://github.com/stevepridemore/graph-memory/commits/v0.1.0