Skip to main content
Glama

Your AI agent forgets everything between sessions. Kybase fixes that: a self-hosted Markdown knowledge base you browse and edit in the browser, that any MCP-speaking agent — Claude, Cursor, Windsurf — uses as persistent memory over MCP. The agent writes notes as you work, links them with [[wikilinks]], and finds them again next session — no re-onboarding, no lost decisions.

Everything runs on your machine via Docker: PostgreSQL for notes, pgvector + Ollama for embeddings. No SaaS, no accounts, private by default (see Switching Embedding Providers for the trade-off if you opt into a cloud embedding provider).

Why Kybase? · Quick Start · Environment variables · Connect an MCP Client · Stack · Switching Embedding Providers · Export & Import · Sharing · Backups · Upgrading · Local development · License

Why Kybase?

Giving an agent persistent memory usually means assembling it yourself: a notes app, an MCP bridge, an embedding pipeline, and sync between them. Kybase is that whole stack as one docker compose up:

  • MCP-native — 17 tools (search_notes, get_note with optional wikilink resolution, get_graph, get_backlinks, append_to_note, replace_in_note, indexing_status, CRUD for notes/folders) over Streamable HTTP, with instructions that teach the agent to interlink notes properly

  • Local semantic search — pgvector + Ollama embeddings, private by default; hybrid RRF fusion with bilingual full-text search and chunked, excerpt-based results

  • Agent-friendly graph — explicit wikilink edges plus semantic edges computed from embedding similarity, so the agent discovers related notes that were never linked

  • A real notes app, not a black box — web editor with backlinks, graph view with a similarity slider, workspace focus mode; renaming a note rewrites its wikilinks everywhere

  • Zero external services — app, Postgres+pgvector, and Ollama in one compose file; single-secret auth, revocable per-client OAuth tokens for MCP

Related MCP server: Cairn MCP Server

Quick Start (Docker)

git clone https://github.com/Kyrzin/kybase.git
cd kybase
cp .env.example .env
# edit .env: set KYBASE_SECRET (openssl rand -hex 32)
docker compose pull && docker compose up -d

This pulls the prebuilt multi-arch image (ghcr.io/kyrzin/kybase, linux/amd64 + linux/arm64) from GitHub Packages, tagged latest. To build from source instead, run docker compose up -d --build.

Open http://localhost:3000 and log in with your KYBASE_SECRET.

That's it. On startup the app applies db/migrations/*.sql automatically (tracked in the schema_migrations table) and Ollama downloads the embedding model (embeddinggemma, ~620 MB, one time). Change the host port with KYBASE_PORT in .env.

NOTE

Notes and text search work immediately. Semantic search and semantic graph edges activate once Ollama finishes pulling the model and notes get indexed (automatic, in the background).

Environment variables

Everything below goes in .env (copied from .env.example). KYBASE_SECRET and POSTGRES_PASSWORD are required — the rest have working defaults.

Variable

Default

Notes

KYBASE_SECRET

(required)

UI login password and MCP/API bearer token. Generate with openssl rand -hex 32.

KYBASE_PORT

3000

Host port the app is exposed on.

POSTGRES_PASSWORD

(required)

Postgres is only reachable inside the compose network, but docker compose up refuses to start without one — no silent weak default. Generate with openssl rand -hex 16.

KYBASE_TAG

latest

Prebuilt image tag from ghcr.io/kyrzin/kybase. latest always tracks the newest release tag; pin to a specific version (e.g. v1.3.0) if you want upgrades to be a deliberate step.

EMBEDDING_PROVIDER

ollama

ollama, google, or openai — see Switching Embedding Providers.

OLLAMA_URL

http://ollama:11434

Point at an external Ollama instance instead of the bundled container.

OLLAMA_MODEL

embeddinggemma

Or nomic-embed-text.

GOOGLE_API_KEY / GOOGLE_MODEL

(empty) / text-embedding-004

Only used when EMBEDDING_PROVIDER=google.

OPENAI_API_KEY

(empty)

Only used when EMBEDDING_PROVIDER=openai.

DATABASE_URL isn't something you set for the Docker path — compose derives it from POSTGRES_PASSWORD automatically. It's only relevant for local development running the app directly on the host.

Connect an MCP Client

The app exposes a Streamable HTTP MCP endpoint at /api/mcp. Any MCP client that speaks Streamable HTTP can connect — not just Claude.

Claude Code — add to .mcp.json (or claude mcp add):

{
  "mcpServers": {
    "kybase": {
      "type": "http",
      "url": "https://your-domain/api/mcp",
      "headers": {
        "Authorization": "Bearer <KYBASE_SECRET>"
      }
    }
  }
}

Claude Desktop — same JSON shape, in claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json, Windows: %APPDATA%\Claude\claude_desktop_config.json).

claude.ai — Settings → Connectors → Add custom connector, same URL (requires the instance to be reachable over HTTPS). Each client gets its own revocable OAuth token — see Settings → Connected clients in the web UI.

Cursor — add to .cursor/mcp.json (project) or ~/.cursor/mcp.json (global):

{
  "mcpServers": {
    "kybase": {
      "url": "https://your-domain/api/mcp",
      "headers": {
        "Authorization": "Bearer <KYBASE_SECRET>"
      }
    }
  }
}

Windsurf — add to ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "kybase": {
      "serverUrl": "https://your-domain/api/mcp",
      "headers": {
        "Authorization": "Bearer <KYBASE_SECRET>"
      }
    }
  }
}

MCP tools (17)

Tool

Category

What it does for the agent

search_notes

Search

Hybrid RRF search (pgvector + bilingual FTS) with calibrated relevance/confidence per hit

get_note

Read

Fetch a note by id or fuzzy title; windowed for large notes, with a heading outline. resolve_links:true also resolves every [[wikilink]] inside it, one level deep, in the same round-trip

list_notes

Read

Newest-first listing, filterable by folder/tag/updated date

list_tags

Read

All tags in use with counts, so the agent reuses existing tags instead of coining duplicates

list_folders

Read

Flat folder list for reconstructing the tree

get_backlinks

Graph

Notes that link to a given note via [[wikilinks]]

get_graph

Graph

The knowledge graph — wikilink edges plus semantic edges — scoped by folder or by hop count from a root note

create_note

Write

Create a note; embedding is generated automatically in the background

update_note

Write

Update fields; supports expected_updated_at to refuse a stale overwrite instead of silently clobbering a concurrent edit

append_to_note

Write

Insert text at a note/section boundary (at: note/section start or end) without resending the rest — safe under concurrent writers (row-locked)

replace_in_note

Write

Find-and-replace exact text; refuses unless the match count equals expected_count, so a loose find can't silently rewrite more than intended

delete_note

Write

Soft-delete; recoverable with restore_note before it ages out of the trash

restore_note

Write

Undo delete_note

create_folder

Organize

Create a folder, optionally nested

update_folder

Organize

Rename or move a folder; refuses a move that would create a cycle

delete_folder

Organize

Delete a folder and its subtree; every note inside is soft-deleted along with it

indexing_status

Diagnostics

How many notes are embedded vs. still pending, to tell "still indexing" from "done"

The server ships with MCP instructions that teach the agent to search before writing and to add [[wikilinks]] to related notes — so the knowledge graph grows as the agent uses it, instead of accumulating orphan notes.

Stack

Layer

Tech

Frontend

Next.js App Router, React 19

Database

PostgreSQL 16 + pgvector (direct pg connection)

Embeddings

Ollama embeddinggemma (default, multilingual) / Google / OpenAI

Search

RRF hybrid: pgvector HNSW cosine + bilingual FTS

MCP

@modelcontextprotocol/sdk Streamable HTTP

Auth

KYBASE_SECRET env var + revocable per-client OAuth tokens (MCP)


Switching Embedding Providers

You can switch the embedding provider (between local Ollama, Google, or OpenAI) and trigger re-indexing directly in the browser:

  1. Open the settings modal in the web UI.

  2. Select your provider, add the API key if needed, and click Save & Apply (switching the provider automatically re-indexes every note).

  3. Reindex only catches notes that were never embedded. After anything else that changes how embeddings are computed (e.g. an update to the embedding logic itself), use Reindex all to force-recompute every note.

All supported providers use 768-dimensional embeddings, so switching does not require any database schema changes.

IMPORTANT

Ollama keeps everything on your machine — no note content leaves it. Google and OpenAI are convenience options: picking either sends your notes' full text to that provider's API to compute the embedding.

Local model choice. The default local model is embeddinggemma (Google, multilingual) — for multilingual vaults (e.g. Russian/German) set the Ollama model to embeddinggemma; it separates relevant from irrelevant notes far better than English-centric models. nomic-embed-text is a smaller, English-leaning alternative. The semantic-similarity threshold adapts to the model automatically (see getMinSimilarity in lib/embeddings.ts), so no manual tuning is needed when you switch.

TIP

Already run Ollama? On a host that already has an Ollama instance (e.g. a GPU one), skip the bundled CPU container: set OLLAMA_URL in .env to your instance (pull OLLAMA_MODEL there first) and start with the override file — docker compose -f docker-compose.yml -f docker-compose.external-ollama.yml up -d.

TIP

CLI alternative. If you prefer using the terminal, you can trigger re-indexing by calling the admin endpoint — only pending notes by default, add ?mode=all to the URL to force every note instead:

docker compose exec kybase node -e "
  fetch('http://localhost:3000/api/admin/reindex', {
    method: 'POST',
    headers: { Authorization: 'Bearer <KYBASE_SECRET>' }
  }).then(r => r.json()).then(console.log)
"

Export & Import

Your notes are never locked in. Settings → Export .zip downloads the whole vault as plain markdown files with frontmatter (title, tags, created/ updated dates), folders as directories — readable by any editor, Obsidian included. Import .zip merges a vault back; notes whose titles already exist are skipped. A new note's creation date is restored from the file; its "last updated" timestamp is set to the moment it lands back in the vault rather than carried over — that field tracks when this server last changed the row, so a re-imported note showing up as recently touched is correct, not a bug. Imported notes are re-embedded automatically in the background.

The same via API:

curl -H "Authorization: Bearer <KYBASE_SECRET>" -o vault.zip \
  http://localhost:3000/api/export

# mode=skip (default) keeps existing notes; mode=overwrite replaces them
curl -X POST -H "Authorization: Bearer <KYBASE_SECRET>" \
  --data-binary @vault.zip \
  "http://localhost:3000/api/import?mode=skip"

Sharing notes

The Share button on a note creates a public read-only link (/share/<token>) — rendered markdown, no login, wikilinks shown as plain text so nothing else in your vault is reachable. The threat model in one sentence: the link is the access — revoke links you no longer need (Settings → Active share links shows everything that is currently public).


Backups

Everything lives in one Postgres volume — a nightly pg_dump is one line. Full recipe including cron and restore: docs/backup.md.


Upgrading

# prebuilt image
docker compose pull && docker compose up -d

# or rebuild from source
git pull && docker compose up -d --build

Migrations apply automatically on startup. Details: docs/upgrading.md.


Local development

# Postgres only (app runs on the host)
docker compose up -d db
cp .env.example .env.local
# in .env.local: set KYBASE_SECRET and uncomment DATABASE_URL
npm install
npm run dev                  # http://localhost:3000

npm run build     # Production build check
npx tsc --noEmit  # Type check

License

AGPL-3.0 — free to use, modify, and self-host. If you run a modified version as a network service, you must make its source available to your users under the same license.

For a commercial license (e.g. embedding Kybase in a closed-source product or service), contact the author.

Copyright © Denis Kurzin (https://github.com/Kyrzin)

A
license - permissive license
Not graded
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
4wRelease cycle
2Releases (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
    Not graded
    quality
    A
    maintenance
    A self-hosted Markdown knowledge base and Agent Harness with an MCP server that enables AI agents to read and write notes, providing persistent memory and a shared workspace for multi-agent collaboration.
    1
    MIT
  • A
    license
    Not graded
    quality
    F
    maintenance
    A self-hosted persistent memory platform for AI agents and humans offering tools for memory storage, search, beliefs, work management, and code intelligence via MCP.
    7
    GPL 3.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    Self-hosted personal knowledge base with semantic search, enabling AI agents to capture, search, and manage thoughts using PostgreSQL with pgvector.
    24
    ISC

View all related MCP servers

Related MCP Connectors

  • Persistent memory and knowledge management for AI agents with semantic search and 50+ tools.

  • Persistent memory and knowledge graphs for AI agents. Hybrid search, context checkpoints, and more.

  • Shared, governed long-term memory for AI agents across tools and sessions via MCP and REST.

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/Kyrzin/kybase'

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