Skip to main content
Glama
MSIH
by MSIH

LifeContext

(formerly Open Brain Local)

CI

A local-first, self-owned memory layer that any AI tool can plug into — one database, one gateway, running entirely on your own machine. LifeContext stores your notes, contacts, and (soon) emails, photos, and location history as a unified memory your AI assistants can actually recall from: by meaning, by person, by place, and by time.

Origins & lineage

LifeContext began as an independent, local-first implementation of the "Open Brain" concept introduced by Nate B. Jones. Nate's reference implementation, OB1, lives at https://github.com/NateBJones-Projects/OB1.

The project has grown in two stages:

  1. The port — a faithful local re-implementation of the Open Brain idea (one memory store, any AI plugs in via MCP), swapping OB1's cloud stack (Supabase + edge functions) for a fully local one: SQLite + sqlite-vec, a single Node.js server, and Ollama for embeddings. That version (pre-0.2.0) is no longer available: this repository's history was squashed to a single commit to purge personal data that had been committed into test fixtures and doc examples, and the pre-squash history is retained privately.

  2. The evolution — the living codebase on the default branch. Memories became artifacts (events with time, place, and a text representation), backed by an entity graph with contacts as the spine, and hybrid retrieval (vector + keyword, fused and planned by a small local LLM). The roadmap adds pluggable "senses" — email, documents, photos, location — feeding the same stable core.

The project was renamed from Open Brain Local to LifeContext to avoid confusion with OB1's identity as that divergence grew.

Related MCP server: Memory MCP

How this repository is maintained

Day-to-day development happens in a private repository; this one receives reviewed snapshots. So there are no feature branches or pull requests here, and commits arrive in batches rather than continuously — that is the intended shape, not a stalled project.

The reason is specific to what LifeContext is: it is a memory server for its author's real contacts, mail, photos, and location history, and it is developed by dogfooding it against that real data. Several times, real personal values — including a third party's name — reached this public repository through test fixtures and documentation examples written from the live store. A review boundary between development and publication makes that class of mistake recoverable instead of permanent.

Issues and pull requests are welcome. They are triaged into the private repository, so a fix may land in a later snapshot rather than as a direct commit against your report.

Relationship & license

This project is not affiliated with, endorsed by, or officially connected to Nate B. Jones or OB1. It is a clean-room reimplementation of the concept — a single, user-owned knowledge/memory store that multiple AI tools share (for example, over the Model Context Protocol) — and it does not fork or redistribute OB1's source code. Where OB1 targets free-tier cloud services, LifeContext targets a fully local stack: a local database, a local AI gateway, and no SaaS dependency.

"Open Brain" and "OB1" remain the work of their author. The code in this repository is licensed under the MIT License; refer to the OB1 repository for its own license terms.

Concept

Every AI tool keeps its own siloed memory, so each new chat or tool starts from zero. LifeContext flips that around: you own one memory store, and every AI plugs into it — with your data and the gateway staying on your machine.

Quickstart

Runs fully local — no cloud. Requires Node.js 18+ and Ollama.

# 1. Install dependencies (Ollama must be installed and running)
npm install
npm rebuild better-sqlite3        # only if npm skipped its native build

# 2. Bootstrap: pulls the embedding model (+ optional query model) and writes .env with a
#    random LIFECONTEXT_API_KEY. Idempotent — safe to re-run; never overwrites an existing .env.
npm run setup

# 3. Run
npm start                         # REST + MCP on http://localhost:3000

Developing against your data? Use npm run dev — it boots a second server on :3001 against a copy of life-context.db (.dev.db, a consistent online snapshot), so tests never write your live memory. Add -- --fresh to re-copy. A brief overlap — a connector POSTing while a backfill or npm run migrate runs — now waits up to DB_BUSY_TIMEOUT_MS (default 5s) for the lock instead of throwing SQLITE_BUSY instantly. But still never run a second long-lived npm start / node src/server.js against the live DB — two servers sharing the vec/FTS state cause locks the timeout can't paper over. Every instance logs its resolved DB file at boot, so a mis-pointed one is obvious.

npm run setup prints the generated LIFECONTEXT_API_KEY once — save it; it's the x-api-key header for every call. Prefer to do it by hand? The manual equivalent (pull qwen3-embedding:0.6b + qwen2.5:3b, cp .env.example .env, set a key) is in docs/local-llm-setup-guide.md.

Tests & CI. npm test (node --test test/*.test.mjs) runs the full suite — no Ollama needed (the embedder/planner-offline paths degrade gracefully). Supported runtime is Node 22+ (engines in package.json); Node 21+ expands the test/*.test.mjs glob natively, so only an older runtime needs a globbing shell (any Unix shell, or Git Bash on Windows — not cmd.exe/PowerShell). npm run check:boundary asserts connectors never import src/, and npm run test:connectors runs each connector's own suite (skipping any whose deps aren't installed).

The CI workflow has two postures. The checks job always runs on every PR and push to main: the four deterministic gates (hook-layer liveness, connector boundary, schema literal, private-reference sweep) and no tests — it needs no npm ci, so it finishes in seconds. The test job is opt-in, since the suite already runs locally before every PR: label a PR ci:test for one cell (Ubuntu × Node 26) or ci:lts to add Node 24 and 22, or run the workflow manually (Actions → CI → Run workflow) and pick default / lts / full (full adds a Windows × Node 26 cell). Recommended: enable branch protection on main requiring checks — never a test (<os>, <node>) cell, since those are conditional and a required check that never runs would leave an unlabelled PR blocked forever.

Upgrading from an earlier version? Migrate your existing memories into the artifact store once (back up your DB file first — life-context.db by default, or whatever DB_PATH points to — it's idempotent and safe to re-run). It reuses the stored vectors as-is, so it's only valid while the embedding model and VECTOR_DIMENSION are unchanged:

npm run migrate                   # copies memories -> artifacts (type='note'), reusing vectors

Seed the entity graph from your contacts (people become searchable and future emails/photos link to them). Clean them up first — see docs/08-preparing-contacts.md for the pre-clean checklist and why it matters:

npm run import:contacts contacts.vcf

Any embedded contact photo is decoded and preserved to CONTACTS_RAW_DIR (default raw/contacts, override in .env) — the future seed for face-recognition-based photo↔contact linking.

Re-running import:contacts on an edited vCard updates that contact in place (#94): a UID-matched card whose content changed is re-embedded and its searchable text refreshed (new emails/phones/nicknames become additive aliases); an unchanged card is skipped with no work. The imported photo and the entity profile you edit in the contacts UI are left untouched — the UI owns the profile. Summary line, e.g. Contacts import complete: 0 added, 2 updated (0 new entities, 0 photos preserved), 5 skipped, of 7 vCards.

Phone numbers are matched by a canonical key (#129): US/Canada numbers written with a +1 country code (+1 (415) 555-0148) resolve to the same contact as the bare 10-digit form ((415) 555-0148) — punctuation, spacing, and the +1 all normalize away. Contacts imported before this change are re-aliased under the canonical key by npm run backfill:phones (additive and idempotent — back up life-context.db first).

A contact's display name defaults to first + last (#156): a card with a middle name (Amy Margaret Fenwick) is stored and shown as Amy Fenwick, while the full name is kept as a searchable alias — so search/timeline read cleanly without losing resolution by the full name. Contacts imported before this change are shortened by npm run backfill:display-names (idempotent; back up life-context.db first).

Side contact directory (#154). To keep the entity graph curated while still recognizing everyone, load your full contacts export as a lookup-only directory — it creates no entities:

npm run directory:load contacts.vcf        # handle -> name lookup + one card per contact; NO entities created
npm run backfill:directory-proposals       # stage historical unknown handles AND names for review
npm run backfill:directory-attrs           # fill EMPTY profile fields on existing contacts from their card

Browse it and promote who you want (#299). The contacts UI's Directory panel lists the loaded roster ordered by impact — how many artifacts promoting each name would retro-link — so the people you actually have history with come first instead of whoever is alphabetically unlucky. Search by name, phone, or email; promote one row or tick several and promote in bulk. Promoting mints the contact with all of that name's handles (plus the full card profile, above), links its staged history in the same call, and greys the row as done. This is the primary route into the graph and needs no proposal: the directory is your own export, so browsing and clicking is the approval. Same thing over REST: GET /api/v1/directory and POST /api/v1/directory/promote (see docs/09-contacts-ui.md).

Separately and automatically, a handle that misses the curated graph but matches the directory is (a) auto-labeled in search/timeline (Message from Jane Doe (number) — display only, no entity), and (b) staged in the proposed-entities review queue with the name pre-filled. Since #301 that staging also covers person names, not just handles: the photo connector tags a photo with the person from its folder name (alias_type:'name'), and a name the directory knows now stages a proposal too — so backfill:directory-proposals surfaces photographed people, not only phone numbers. An ambiguous name (two different cards share it) stages nothing rather than guessing. Auto-labeling stays handle-only by design — recognizing person names in prose would mean false positives on ordinary words. Approving a proposal promotes it into the curated graph and links its history; rejecting silences it until you reopen it (#300 — a rejected proposal can be flipped back to pending, singly or in bulk, from the contacts UI's Proposed panel; an approved one is terminal). Promotion is always your call — the directory never auto-creates a contact. vCard (.vcf) only for now.

The directory keeps each card's full profile (#304), not just the handle→name pair: addresses, birthday, anniversary, org/title, urls, and nicknames are stored per card (keyed by its vCard UID, else a hash of the card) — the data the parser already produced and previously discarded. Two consequences: a directory entry promoted into the graph arrives with a real profile instead of an empty one, and npm run backfill:directory-attrs enriches contacts you imported before this existed. That backfill only ever writes a field the contact leaves empty — a value you typed always wins, and it never touches the display name, aliases, relationships, or photo. Precedence: user-typed > existing non-empty > directory. Re-loading an export is idempotent and append-only: a new address or url is merged in, while a differing birthday keeps the stored value and records the conflict in ingest_log rather than overwriting it. An existing directory gains cards only on a re-loadnpm run directory:load against the same export, which also links your already-loaded handles to their new cards (back up life-context.db first, as with any backfill).

The staging step (backfill:directory-proposals) also runs from the browser (#162): the Stage from directory button in the contacts UI's Proposed drawer re-runs the same idempotent pass against the loaded directory and refreshes the queue — so you never have to drop to a shell after loading a directory.

Ingest order. There are two tiers, not a five-step chain: Tier 1 — contacts (they seed the entity graph); Tier 2 — everything else (photos, emails, documents, texts), in any order. Tier-2 sources link to entities, never to each other, so nothing among them depends on the rest. Contacts-first is a recommendation, not a hard constraint: an artifact ingested before its contact exists is still stored and fully searchable (by meaning, keyword, time, place) — only the person link is deferred, and it forms automatically when that contact is later imported (see docs/08-preparing-contacts.md).

Real imports are messy. Curate contacts — fix emails/phones/addresses, set a photo, and wire up relationships (spouse/parent/child/worksAt) — in the browser. The web UI is token-only (#169): it is served only when UI_URL_TOKEN is set in .env, and only at a bookmarkable capability URL — http://localhost:3000/<token>/ui/contacts.html (and /<token>/ui/chat.html), token-first, matching the MCP /<token>/mcp URL. The page reads the token from its own path and authorizes with no manual key entry (there is no API-key bar). With UI_URL_TOKEN unset the UI is disabled/ui/* and /<anything>/ui/* all 404 — so a Cloudflare Tunnel can never expose the page without an explicit token (localhost dev needs a token too — the fail-safe default). See docs/09-contacts-ui.md, and docs/07 for the URL-leak tradeoff and the Cloudflare Access recommendation.

Consolidate each day's artifacts into one searchable daily digest (schedule it nightly — see docs/06-consolidation.md):

npm run consolidate               # yesterday; `-- --date=YYYY-MM-DD` or `-- --backfill=N` for history

Smoke test ($KEY = your LIFECONTEXT_API_KEY):

curl -s -X POST localhost:3000/api/remember -H "x-api-key: $KEY" -H "Content-Type: application/json" -d '{"content":"My sister Sarah lives in Austin."}'
curl -s -X POST localhost:3000/api/recall   -H "x-api-key: $KEY" -H "Content-Type: application/json" -d '{"query":"where does my sister live"}'

Recall returns the stored memory with a similarity score. For AI clients, point an MCP-capable tool (Claude Desktop, Cursor, …) at http://<host>:3000/mcp with an x-api-key header to get the memory tools. Clients that can only take a bare URL (some MCP setups, gemini/VS Code extensions) can pass the key as a ?api_key=<key> query param instead (http://<host>:3000/mcp?api_key=$KEY) — the header is preferred; the query param leaks the key into logs and history (see docs/07), and it does not work for the Claude.ai web connector (the MCP spec forbids query-string tokens — web needs header auth or OAuth).

Access logging (#178). Every /api, /mcp, and /ui request appends one metadata line to a daily file logs/access/access-YYYY-MM-DD.log (UTC) — method, redacted path, status, real client IP, latency, surface (api/mcp/ui), and auth outcome, e.g. 2026-07-15T13:40:12Z api POST /api/search 200 842ms ip=203.0.113.7 auth=ok. Secrets are redacted before writing (the ?api_key= query value and any capability path token — /<token>/mcp, /<token>/ui/… — become <redacted>/<token>) and request bodies are never logged (metadata only). Failed-auth probes (401, a wrong capability-token 404, a rate-limit 429) are also surfaced to stderr so brute-force attempts against an internet-exposed instance are visible. On boot the server prunes files older than ACCESS_LOG_RETENTION_DAYS (default 90) and, on Windows, NTFS-compresses the folder so the text logs cost a fraction of the disk. Toggle with ACCESS_LOG_ENABLED / ACCESS_LOG_DIR / ACCESS_LOG_RETENTION_DAYS in .env (see docs/07).

Interfaces

Every endpoint/tool requires the key, sent as the x-api-key header (or Authorization: Bearer, or — for clients that can't set headers — an ?api_key= query param; header preferred, see the caveat above). REST and MCP share one store.

  • RESTPOST /api/remember, POST /api/recall, POST /api/search, POST /api/timeline, POST /api/about_entity, GET /api/artifact/:id

  • Entity curation (/api/v1/entities, core-owned — never via a connector, see docs/03-ob2-design.md §7) — GET /api/v1/entities/duplicates (rank likely-duplicate person entities by shared phone/email + name similarity; also reports dismissed_count), POST /api/v1/entities/merge ({keep_id, absorb_id} — tombstones the absorbed entity, never deletes it, and re-points its aliases/links/relations to the survivor), POST /api/v1/entities/duplicates/dismiss + DELETE …/duplicates/dismissals (#302 — mark a candidate pair "not a duplicate," absolute until an explicit clear-all; list_probable_duplicates hides a dismissed pair), plus the contacts-UI CRUD surface — list/get, create, PATCH fields, add/remove aliases & relationships, and photo upload/download (all under /api/v1/entities, see docs/09-contacts-ui.md). The proposed-entity review queue (the approval gate for suggested entities — a connector/agent suggestion is never minted unapproved; trusted direct creation via POST /api/v1/entities is the separate curation path): GET /api/v1/entities/proposed, POST /api/v1/entities/proposed/:id/approve, POST …/proposed/:id/reject, POST …/proposed/:id/reopen + bulk POST …/proposed/reopen (#300 — rejected → pending, 409 on anything else), POST …/proposed/stage-from-directory (#119/#154), plus GET /api/v1/entities/photos (the face-match photo source, #112). A browser UI over these lives at /<token>/ui/contacts.html (token-only, #169 — see the curation note above).

  • Connector ingest (/api/v1, see docs/04-connector-contract.md) — POST /api/v1/ingest (submit one artifact; upsert on (source, source_id) — 201 create / 200 update, non-destructive issues accepted with a warnings array, 256 KB body cap), POST /api/v1/ingest/batch (submit 1–100 artifacts in one call; 200 with index-aligned per-item results + a summary, per-item isolation — one bad item is reported at its index, never poisons the rest), GET /api/v1/ingest/types (the machine-readable type registry, plus extension_types — the x- types actually observed in the store, with counts, §6). The payload's JSON Schema is published at schemas/ingest.v1.json (generated from the zod schema via npm run schema:ingest) so connector authors can validate offline, without a live server:

    curl -s -X POST localhost:3000/api/v1/ingest -H "x-api-key: $KEY" -H "Content-Type: application/json" -d '{
      "source":"imessage","source_id":"chat.db:msg:88213","type":"message",
      "text_repr":"Text from Sarah Jones: Landed! See you at the gate.",
      "occurred_at":"2026-07-04T18:22:09Z",
      "latitude":30.2672,"longitude":-97.7431,"place_label":"Austin-Bergstrom Intl",
      "entity_hints":[{"alias":"+15550142","alias_type":"phone","role":"sender"}]}'
    
    curl -s -X POST localhost:3000/api/v1/ingest/batch -H "x-api-key: $KEY" -H "Content-Type: application/json" -d '{
      "artifacts":[
        {"source":"photo-exif","source_id":"IMG_0001.jpg","type":"photo","text_repr":"Photo taken 2019-03-04 near Austin, TX"},
        {"source":"photo-exif","source_id":"IMG_0002.jpg","type":"photo","text_repr":"Photo taken 2019-03-04 near Austin, TX"}]}'
  • MCP (Streamable HTTP) — /mcp, tools:

    • store_memory / search_memories — the original note store + recall (unchanged on the wire); store_memory also takes an optional type, "note" (default) or an x- extension marker (e.g. "x-dev-note"), to write dev/personal-isolating memories directly through MCP, and an optional occurred_at (ISO 8601, #436) for when the remembered thing actually happened, as opposed to when it was typed — a malformed value is rejected, not silently dropped; omit it and it stays NULL exactly as before, and timeline still surfaces the memory by falling back to when it was stored (see below). Other registered types are rejected — store_memory only ever writes a freeform manual note (no source_id/extra), so a caller needing a real photo/contact/digest row (or an upsert key) still goes through POST /api/v1/ingest

    • list_types — list the registered type vocabulary plus any x- extension markers observed in the store, so a caller can discover what to pass to store_memory's type or search's types instead of hardcoding a convention

    • search — hybrid semantic + keyword search with optional types / time_range / entities filters, plus near (a place name or {lat, lon}) + radius_km for geo-radius search — surfaces artifacts within the radius by coordinate, catching nearby places the label text doesn't literally name (e.g. near: "San Francisco" finds a Sausalito photo). "Where / last seen" wording is also handled by the query planner: a where … was X last seen query with no place named restricts to geotagged artifacts and orders them most-recent-first, so the top hit is the location the person was last seen (#190) — degrading to a normal relevance search when nothing is geotagged. types also accepts an x- extension marker (e.g. types: ["x-dev-note"]) to read back a store_memory-tagged marker that's otherwise excluded from an untyped search (#244) — the query planner's own type inference stays registered-types-only, so only an explicit filter can name one. Both the REST response and the tool's text output carry a deterministic summary (#353) over the full matched set (not just the returned page) — total count, a per-type breakdown, contiguous date "visits," and top places/people — never LLM prose; the tool text leads with one rendered line, e.g. 55 photos · Ocean City, Maryland · 2 visits: 2019-07-28–30, 2021-06-05. A caller that already knows every filter it needs can pass use_planner: false (default true, #433) to skip the LLM query-planner call entirely — a ~7s-plus win on this box — while still gap-filling types/place from a literal word in the query text (the same deterministic pre-pass, #352); omit it for a natural-language query where you want the planner's inference.

    • timeline — chronological recall over a date range; types accepts an x- extension marker too, same as search. Sorts and range-filters by occurred_at, falling back to ingested_at when occurred_at is NULL (#436) — so a store_memory write without an explicit occurred_at still shows up, on the date it was stored, instead of being silently excluded from every range

    • about_entity — resolve a person/place/org and return their profile, recent linked artifacts, and person↔person relations (spouse, child, parent, …)

    • get_artifact — one artifact's full text, metadata, and entity links by id

    • list_probable_duplicates / merge_entities — surface and merge likely-duplicate contacts (30 years across Google/Yahoo/iPhone rarely dedup perfectly); merge tombstones the absorbed entity rather than deleting it. A pair dismissed as "not a duplicate" via the REST-only POST/DELETE /api/v1/entities/duplicates/{dismiss,dismissals} (#302 — no MCP tool; a human recovery action) stays hidden here too, since both read paths share list_probable_duplicates

    • propose_entity — suggest a NEW entity (person/org/place/event — e.g. a broker or agent you learned about) for review; stages a pending proposal, never mints, so a human approves it (list_proposed_entities / approve_proposed_entity / reject_proposed_entity, or the contacts UI "Proposed" panel). Idempotent — re-proposing the same one is a no-op. REST equivalent: POST /api/v1/entities/proposed. This is the only way an agent adds to the graph, and it can't do so unapproved

    • add_relationship — link two entities that already exist (e.g. a person worksAt an org): from/to are each a name (resolved) or a numeric id, plus a canonical relation_type (worksAt, spouse, manager, …) or free-text raw_label. Directional (from → to); idempotent; ungated (both endpoints already passed approval). If an endpoint doesn't exist yet, propose_entity it and approve first. Ambiguous or unknown names error rather than guess

Reconnect after a tool changes. An MCP client fetches this tool list once, when it connects, and caches it. So a newly added or renamed tool won't appear in a session that was already connected — the agent will correctly report the tool "doesn't exist." Restarting the LifeContext service updates the server but not already-connected clients. To pick up a new tool, reconnect the server in each client session (in Claude Code: /mcp → reconnect lifecontext, or restart the session).

Connectors write; recall is separate

A connector's only job is to submit data via POST /api/v1/ingest (or POST /api/v1/ingest/batch) — see docs/04-connector-contract.md §1.1. Installing a connector does not, by itself, make any AI tool recall from LifeContext. That's a separate integration you configure on the AI tool's side: point an MCP-capable client at /mcp (or use the REST /api/recall//api/search endpoints directly) as described above under Interfaces. A connector neither provides nor configures that wiring for you.

Status

Phase 2.0 (foundation) — working. Every memory is now an artifact (an event with time, place, and a text representation) in a unified store, backed by an entity graph (contacts as the spine) and hybrid retrieval (vector KNN + FTS5 keyword search fused with reciprocal rank fusion, planned by a small LLM). Local store → embed (Ollama) → recall works over both REST and MCP; npm run migrate brings earlier memories forward, npm run import:contacts seeds people.

Feeding the brain is connector-driven: an HTTP ingest contract so anything — a Claude Code hook, an iMessage watcher, a photo-EXIF scan, a document-tree scan — can submit artifacts. The reference connectors live in connectors/ (one self-contained folder each; the HTTP contract is their only coupling to core — npm run check:boundary enforces it). See the connector contract and the roadmap.

Location is resolved server-side: a connector submitting raw latitude/longitude gets a place_label filled in automatically, offline, against a bundled dataset (src/geodata/places.json, ~135k places) derived from GeoNames, licensed CC BY 4.0. US labels store the full state name (Austin, Texas), so a query like "in texas" matches directly. Regenerate it with npm run geocode:build -- <path-to-cities1000.txt> (the -- forwards the path to the script) after downloading GeoNames' cities1000.txt dump (population ≥ 1,000) from https://download.geonames.org/export/dump/cities1000.zip — the raw dump isn't committed, only the derived file and scripts/build-places.js are. Upgrading an existing DB from the old code-form labels (Austin, TX): back up the .db, then run npm run backfill:geo once (idempotent; re-derives place_label from stored coordinates, leaving connector-supplied labels untouched).

Beyond people, the entity graph also has place (#137) and event (#138) kinds — recurring locations and multi-artifact occasions you can pivot recall around (about_entity('Deer Valley'), spend-by-place, "everything from the Tahoe trip"). Both can be minted directly (the /api/v1/entities CRUD surface) or discovered bottom-up from photo GPS/time and staged for approval: npm run places:cluster grids GPS-bearing artifacts into candidate places, npm run events:cluster groups contiguous-day bursts away from home into candidate events — each stages a proposed entity you approve. See docs/03-ob2-design.md §2.2.

Design documents

Doc

What it covers

docs/03-ob2-design.md

The core design: unified artifact schema, entity graph, hybrid retrieval, query planner, consolidation. Its build-phase table (§6) is superseded by the roadmap below.

docs/04-connector-contract.md

The connector contract — the versioned HTTP + JSON ingest API (/api/v1/ingest) that lets any external process, in any language, feed the brain: artifact payloads, entity hints, the event lane, the type registry, and the compatibility promise.

docs/05-roadmap.md

The current roadmap: sequence-ordered milestones with exit tests — ingest API foundations, the first three reference connectors (devsession, imessage, photo-exif), planner hardening, contract v1 freeze, consolidation, and distribution.

docs/06-consolidation.md

Consolidation v1 — npm run consolidate: nightly daily digests (type='digest'), regeneration semantics (input-hash skip, derived-only upsert), timeline/planner digest awareness, scheduling snippets.

docs/07-cloudflare-tunnel-setup.md

Remote access on your own domain — beginner-friendly Cloudflare Tunnel setup so phones, other machines, and cloud agents can reach the server (https://…/api/*, https://…/mcp); no port forwarding.

docs/local-llm-setup-guide.md

Setting up Ollama and the local models (Windows-focused; Linux notes included). Later steps predate the 2.0 layout — see the notes inside.

Contributing

See CONTRIBUTING.md for how issues and pull requests are triaged.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (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
    B
    maintenance
    A local-first memory MCP server that enables storing, searching, and managing personal memories with hybrid keyword and semantic recall, all on-device.
    Last updated
    35
    MIT
  • A
    license
    -
    quality
    C
    maintenance
    A local-first compiled knowledge graph MCP server that provides structured memory for AI agents with full-text search, vector embeddings, and timeline tracking.
    Last updated
    528
    8
    MIT

View all related MCP servers

Related MCP Connectors

  • User-owned memory for AI agents, Copilot, Claude, IDEs, CLIs, and chat apps over remote MCP.

  • Private-by-default, local-first memory/context/task orchestrator for MCP apps and agents.

  • Person-owned, portable AI memory as a remote MCP server, readable and writable by any MCP client.

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/MSIH/life-context'

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