A personal semantic memory server for AI assistants. Store facts, code snippets, notes, and documents with vector embeddings — then let your AI search them by meaning, not just keywords.
Also watches directories and auto-ingests files (code, PDFs, text) so your memory stays in sync with your projects.
Works as a local stdio MCP server (zero config, Claude Desktop) or as a remote HTTP server so you can access your memory from any machine.
Requirements: Rust 1.75+ — the first build downloads the BGE embedding model (~130 MB).
git clone https://github.com/your-org/sunbeam-memory
cd sunbeam-memory/mcp-server
cargo build --release
# binary at target/release/mcp-serverOr run directly without a permanent binary:
cargo run -- --http 3456The simplest setup: Claude Desktop talks to the server over stdio. No network, no auth.
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"memory": {
"command": "/path/to/mcp-server"
}
}
}The server stores data in the platform data directory by default:
- macOS:
~/Library/Application Support/sunbeam/memory - Linux:
~/.local/share/sunbeam/memory - Windows:
%APPDATA%/sunbeam/memory
Set MCP_MEMORY_BASE_DIR to change it:
{
"mcpServers": {
"memory": {
"command": "/path/to/mcp-server",
"env": {
"MCP_MEMORY_BASE_DIR": "/Users/you/.local/share/sunbeam-memory"
}
}
}
}Run the server on a VPS or home server so you can access your memory from any machine or AI client.
1. Generate a token:
openssl rand -hex 32
# e.g. a3f8c2e1b4d7...2. Start the server with the token:
MCP_AUTH_TOKEN=a3f8c2e1b4d7... cargo run --release -- --http 3456With MCP_AUTH_TOKEN set, the server binds to 0.0.0.0 and requires Authorization: Bearer <token> on every request.
3. Configure Claude Desktop (or any MCP client) to use the remote server:
{
"mcpServers": {
"memory": {
"type": "http",
"url": "http://your-server:3456/mcp",
"headers": {
"Authorization": "Bearer a3f8c2e1b4d7..."
}
}
}
}Tip: Put a reverse proxy (nginx, Caddy) in front with TLS so your token travels over HTTPS.
If you already have an OIDC provider (Keycloak, Auth0, Dex, Kratos+Hydra, etc.), you can use it instead of a raw token. The server fetches the JWKS at startup and validates RS256/ES256 JWTs on every request.
MCP_OIDC_ISSUER=https://auth.example.com \
MCP_OIDC_AUDIENCE=sunbeam-memory \ # optional — leave out to skip aud check
cargo run --release -- --http 3456Your MCP client then gets a token from the provider and passes it as a Bearer token:
{
"mcpServers": {
"memory": {
"type": "http",
"url": "http://your-server:3456/mcp",
"headers": {
"Authorization": "Bearer <access_token>"
}
}
}
}OIDC takes priority over MCP_AUTH_TOKEN if both are set.
| Variable | Default | Description |
|---|---|---|
MCP_MEMORY_BASE_DIR |
platform data dir | Where the SQLite database and model cache are stored |
MCP_AUTH_TOKEN |
(unset) | Simple bearer token for remote hosting. Unset = localhost-only |
MCP_OIDC_ISSUER |
(unset) | OIDC issuer URL. When set, validates JWT bearer tokens via JWKS |
MCP_OIDC_AUDIENCE |
(unset) | Expected aud claim. Leave unset to skip audience validation |
MCP_SESSION_TTL_HOURS |
24 |
How long HTTP sessions stay alive |
Embed and store a piece of text. Returns the fact ID.
content (required) Text to store
namespace (optional) Logical group — e.g. "code", "notes", "docs". Default: "default"
source (optional) smem URN identifying where this came from (see below)
Semantic search — finds content by meaning, not exact words.
query (required) What you're looking for
limit (optional) Max results. Default: 10
namespace (optional) Restrict search to one namespace
Update an existing fact in place. Keeps the same ID, re-embeds the new content.
id (required) Fact ID from store_fact or search_facts
content (required) New text content
source (optional) New smem URN
Delete a fact by ID.
id (required) Fact ID
List facts in a namespace, newest first. Supports date filtering.
namespace (optional) Namespace to list. Default: "default"
limit (optional) Max results. Default: 50
from (optional) Only show facts stored on or after this time (RFC 3339 or Unix timestamp)
to (optional) Only show facts stored on or before this time
Point the server at directories, git repos, or individual files and it will automatically ingest them into searchable memory. PDFs are extracted page-by-page. Binary files are skipped. Git state is tracked so edits on the same branch update facts in place, while new branches create new facts.
Start watching a path. Supports globs (* ? [...]).
path (required) Directory, file, or glob pattern to watch
namespace (optional) Namespace for ingested facts. Default: "default"
type (optional) "dir" | "file" | "glob". Default: inferred from path
Examples:
add_watch_targetwithpath: "/home/me/project/src"add_watch_targetwithpath: "/home/me/docs/**/*.pdf",namespace: "docs"add_watch_targetwithpath: "/home/me/notes/*.{md,txt}",namespace: "notes"
Stop watching a target by ID.
target_id (required) ID returned by add_watch_target
List all active watch targets. No inputs.
Force a full re-scan of a target right now.
target_id (required) ID of the target to sync
Check ingestion progress for a target: files pending, completed, failed, and current file.
target_id (required) ID of the target
Un-mark a fact as stale (e.g. after a file was temporarily deleted).
source_urn (required) The source URN of the fact to restore
Build a valid smem URN from components. Use this before passing source to store_fact.
content_type (required) code | doc | web | data | note | conf
origin (required) git | fs | https | http | db | api | manual
locator (required) Origin-specific path (see describe_urn_schema)
fragment (optional) Line reference: L42 or L10-L30
Parse and validate a smem URN. Returns structured components or an error.
urn (required) The URN to parse, e.g. urn:smem:code:fs:/path/to/file.rs#L10
Returns the full smem URN taxonomy: content types, origins, locator shapes, and examples. No inputs.
List recent ingestion or system errors so you can be informed of failures.
component (optional) Filter to a specific component (e.g. "indexer", "extractor")
limit (optional) Max errors to return. Default: 50
Mark a logged error as resolved so it no longer appears in get_recent_errors.
error_id (required) ID of the error to resolve
Every fact can carry a source URN that records where it came from:
urn:smem:<type>:<origin>:<locator>[#<fragment>]
Types: code doc web data note conf
Origins and locator shapes:
| Origin | Locator | Example |
|---|---|---|
fs |
[hostname:]<absolute-path> |
urn:smem:code:fs:/home/me/project/main.rs#L10-L30 |
git |
<host>/<org>/<repo>/<ref>/<path> |
urn:smem:code:git:github.com/org/repo/main/src/lib.rs |
https |
<host>/<path> |
urn:smem:doc:https:docs.example.com/guide |
db |
<driver>/<host>/<db>/<table>/<pk> |
urn:smem:data:db:postgres/localhost/app/users/42 |
api |
<host>/<path> |
urn:smem:data:api:api.example.com/v1/items/99 |
manual |
<label> |
urn:smem:note:manual:meeting-2026-03-04 |
Use build_source_urn to construct one without memorising the format. Use describe_urn_schema for the full spec.
Facts are stored in a SQLite database (semantic.db) in MCP_MEMORY_BASE_DIR. The embedding model is cached by fastembed on first run.
To back up your memory: copy the semantic.db file. It's self-contained.
Claude / MCP client
│
│ stdio (local) or HTTP POST /mcp (remote)
▼
mcp/server.rs ← JSON-RPC dispatch, tool handlers
│
memory/service.rs ← embed content, business logic
│
semantic/store.rs ← cosine similarity index (usearch)
semantic/db.rs ← SQLite persistence (facts + embeddings + errors)
│
indexer/ ← file watcher, PDF extractor, scanner
Embeddings: BGE-Base-English-v1.5 via fastembed, 768 dimensions, ~130 MB model download on first run.
Vector search: usearch (in-memory, cosine similarity).