Mycelium V1 — Local Setup Guide (macOS or Linux)

Stand up the self-hosted single-user MCP server on a Mac (Apple Silicon) or a Linux home server. Every value below was verified by running the step against the codebase. Markers: ✅ works today · ⚠️ Tier-2 (needs the ML stack).


1. Prerequisites

Node.js 22+ (required — package.json engines: node >=22)

macOS:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install node@22

Linux (Debian/Ubuntu):

curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
node --version   # must be >= 22

Native build toolchain (required — better-sqlite3 compiles a native addon)

  • macOS: xcode-select --install
  • Linux: sudo apt-get install -y build-essential python3

Python 3.10+ (optional — only for the embed service in step 8)

  • macOS: brew install python@3.12
  • Linux: sudo apt-get install -y python3 python3-venv python3-pip

2. Clone and install

The repo is mycelium.id (with a dot). HTTPS is simplest; use SSH only if you've registered an SSH key with GitHub.

cd ~
git clone https://github.com/Curious-Life/mycelium.id.git
cd mycelium.id
npm install

4 dependencies: @modelcontextprotocol/sdk, better-auth, better-sqlite3, express. A committed .npmrc (legacy-peer-deps=true) resolves the better-authbetter-sqlite3 peer tree — npm install works without flags.

If better-sqlite3 fails to compile → install the build toolchain (step 1).


3. Initialize the database

npm run init-db

Expected output (exact):

init-db: 117 tables in data/mycelium.db (3 migrations: 0001_init.sql, 0002_attachments_local_path.sql, 0003_documents_publish_nonce.sql)

The schema defines 111 tables (migrations/0001_init.sql); the physical count is 117 because SQLite auto-creates internal/FTS shadow tables. 0002 adds a column (attachments.local_path) — no new tables.


4. Set up the two encryption keys

Mycelium uses two independent 256-bit keys (64 hex chars each): USER_MASTER for your personal data, SYSTEM_KEY for infrastructure data. The server reads them at boot from a key source you choose with MYCELIUM_KEY_SOURCE.

npm run set-keys            # generates both keys + stores them in the login Keychain

Then everything runs with MYCELIUM_KEY_SOURCE=keychain (steps 5/7). The keys never touch your shell, env, or config files — boot reads them from the Keychain on demand. Add --show if you also want to copy them into a password manager.

Alternative: 1Password CLI

Store both keys as fields on a 1Password item, then point Mycelium at them (npm run set-keys --show prints the exact op command):

export MYCELIUM_KEY_SOURCE=1password
export MYCELIUM_OP_USER="op://Private/Mycelium/user_master"
export MYCELIUM_OP_SYSTEM="op://Private/Mycelium/system_key"

Alternative: plain env vars (simplest, least secure)

openssl rand -hex 32   # USER_MASTER_KEY
openssl rand -hex 32   # SYSTEM_KEY

⚠️ Whichever source you pick, back BOTH keys up offline and use the SAME pair everywhere. On first boot the server writes data/kcv.json (Key Check Value) locking the DB to these keys; any later boot with a different key → vault stays locked (fail-closed). This is a safety interlock against typos/drift, not a secret. Lose the keys = lose the vault. No recovery.


5. Boot the MCP server (stdio)

# Keychain (recommended on Mac):
MYCELIUM_KEY_SOURCE=keychain npm start

# …or with plain env vars:
USER_MASTER_KEY="<64-char-hex>" SYSTEM_KEY="<64-char-hex>" npm start

Expected output on stderr (exact):

[mycelium] 31 tools registered; 2 deferred (reply, services)
[mycelium] stdio MCP server connected.

This boot creates data/kcv.json if absent. Stop with Ctrl-C — Claude Desktop launches the server itself (step 7).


6. Run the verification suite

npm run verify

15 suites must each print VERDICT: GO (exit 0): foundation, mcp, mindfiles, metrics, rest, search, topology, embed, context, ingest, blob, enqueue, enrich, keysource, oauth. All are Tier-1 — they pass without Ollama/onnxruntime, so a clean machine with no ML stack still goes fully green.


7. Configure Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or ~/.config/Claude/claude_desktop_config.json (Linux):

{
  "mcpServers": {
    "mycelium": {
      "command": "node",
      "args": ["src/index.js"],
      "cwd": "/Users/YOUR_USERNAME/mycelium.id",
      "env": {
        "MYCELIUM_KEY_SOURCE": "keychain"
      }
    }
  }
}

Replace YOUR_USERNAME/cwd. With the Keychain (or 1Password) source, no keys live in this file — the server reads them on demand at launch. (Using plain env vars instead? Put USER_MASTER_KEY/SYSTEM_KEY in the env block — the same pair from step 4.) Restart Claude Desktop; the mycelium tools appear once it connects.


8. Optional: semantic search (⚠️ Tier-2)

The query embedder is now wired into the server — when the embed service is up, the search tool uses 768-dim Nomic vectors; when it's down, it fail-softs to BM25 (keyword) per query. Two processes turn it on:

a) Embed service (Nomic v1.5 ONNX, on :8091):

cd pipeline
python3 -m pip install -r requirements-embed.txt
python3 embed-service.py --serve   # NOTE: --serve is required; first launch downloads the ONNX model (~170MB)
# health check:  curl http://localhost:8091/health   → {"status":"ok","loaded":true,...}

b) Enrichment service (:8095) — embeds + NLP-enriches messages as they're captured (the embed service alone stores nothing):

MYCELIUM_KEY_SOURCE=keychain npm run start:enrich   # or pass USER_MASTER_KEY/SYSTEM_KEY

With both running, new messages get embedded and search retrieves semantically. With neither, search is BM25-only and everything else works. Force BM25-only at any time with MYCELIUM_DISABLE_EMBED=1, or point at a different embed host with MYCELIUM_EMBED_URL.

c) Generate (the mindscape / topology map) — the 5-stage clustering pipeline (pipeline/run-clustering.sh) needs a heavier Python set (faiss, leidenalg, igraph, scikit-learn, umap, scipy, cryptography…). The easiest path installs both the embed and clustering deps at once:

bash pipeline/setup.sh            # venv + embed deps + clustering deps + model warmup
# or just the clustering deps into an existing venv:
pipeline/.venv/bin/python3 -m pip install -r pipeline/requirements.txt

These are heavy native wheels; skip them with PIPELINE_SKIP_CLUSTER_DEPS=1 bash pipeline/setup.sh if you only want search. Without them, Generate fails fast with an actionable message (embedding/search are unaffected). Note: this is the dev/local-checkout workflow; a packaged .app does not yet bundle the pipeline (tracked separately).


9. Optional: HTTP transport (remote access, e.g. via Tailscale)

MYCELIUM_KEY_SOURCE=keychain npm run start:http   # or pass USER_MASTER_KEY/SYSTEM_KEY

Starts the Streamable-HTTP + OAuth 2.1 server instead of stdio. Same boot(), same tool surface, same query embedder wiring.


10. Optional: the web portal UI

Mycelium ships a browser/desktop UI served from the same localhost origin as the API. The canonical UI is the full SvelteKit app (portal-app/); build it once:

npm run portal:install     # one-time: install the portal's deps
npm run portal:build       # builds portal-app/build (a static SPA)
MYCELIUM_KEY_SOURCE=keychain npm run portal   # serves UI + REST at http://127.0.0.1:8787

The server auto-serves the canonical build when present, otherwise the built-in single-file portal. The Tauri Mac app (cargo tauri dev) shows whichever the server serves. To iterate on the UI with hot reload: npm run portal:dev (:5173).

Note: the canonical UI builds + is served today; wiring each screen's data to the local API is in progress (portal-app/README.md, "M2"). Until then some screens render empty — capture/search/library/etc. land first.


Quick reference

Action Command
Install deps npm install
Build the web portal npm run portal:install && npm run portal:build
Start portal UI + REST MYCELIUM_KEY_SOURCE=keychain npm run portal
Init database npm run init-db
Set up keys (Keychain) npm run set-keys
Start (stdio) MYCELIUM_KEY_SOURCE=keychain npm start
Start (HTTP) npm run start:http
Start enrichment (:8095) npm run start:enrich
Run all suites npm run verify
Embed service (:8091) python3 pipeline/embed-service.py --serve
Force BM25-only MYCELIUM_DISABLE_EMBED=1
Generate a key openssl rand -hex 32

Key files

mycelium.id/
├── src/
│   ├── index.js          <- entry point + boot() (stdio / --http / --enrich); wires the query embedder
│   ├── crypto/keys.js     <- two-key unlock + KCV verification (fail-closed)
│   ├── adapter/d1.js      <- the encrypting SQLite/D1 adapter (AES-256-GCM envelope)
│   ├── db/index.js        <- assembles the per-table db namespaces over the adapter
│   ├── mcp.js             <- tool registration (31 tools + 2 deferred)
│   ├── server-http.js     <- Streamable HTTP + OAuth 2.1 transport
│   ├── embed/client.js    <- embed-service (:8091) HTTP client
│   ├── search/embedder.js <- embedder contract + service adapter (createServiceEmbedder)
│   └── tools/             <- 17 tool-domain modules
├── migrations/
│   ├── 0001_init.sql      <- schema (111 CREATE TABLE)
│   └── 0002_attachments_local_path.sql
├── pipeline/
│   ├── embed-service.py   <- Nomic v1.5 ONNX (:8091), L2-normalized vectors
│   └── requirements-embed.txt
├── scripts/
│   ├── init-db.mjs        <- database initializer
│   └── verify-*.mjs       <- the 14 verification suites
├── data/                  <- created at runtime (gitignored): mycelium.db, kcv.json
└── package.json

Troubleshooting

  • "USER_MASTER_KEY and SYSTEM_KEY must be set (64-char hex each)..." — keys not in the environment. Export them, or pass via the Claude Desktop MCP config.
  • "KCV failed — wrong key. Vault stays locked." — the key pair changed after first boot. Restore the original keys, or (destroys data) delete data/kcv.json + data/mycelium.db, re-run npm run init-db, boot with the new keys.
  • better-sqlite3 compile error — install the build toolchain (step 1).
  • Node version error — must be >= 22 (node --version).
  • Search returns only keyword hits — the embed service (:8091) isn't running (or nothing's been embedded yet via :8095). This is expected fail-soft behavior, not an error.
  • No .env loader — the code reads process.env directly. Export keys or pass them via the Claude Desktop MCP config (recommended).