Cairn is an AI notes and project management tool with a built in MCP server
See the code
A calm, local-first workspace for notes, project tracking, and visual idea mapping — with an AI assistant and MCP server built in.
Cairn is a desktop app (Electron + Next.js) that combines markdown notes with a kanban board. It is fully compatible with Obsidian vaults, allowing you to point a workspace directly to any existing Obsidian folder. Notes are saved as plain .md files directly in your vault; project and task data lives in a local SQLite database alongside them. No accounts, no cloud, no backend. An embedded AI assistant and a standalone MCP server let AI agents read and write your workspace directly.
⌘K or ⌘⇧F)⌘/)⌘4): ideas, note/task refs, groups, URLs, AI summaries — connected with labelled edgeswindow.cairn.query() data bridge; inline "Fix with AI" on runtime errors; editable via built-in CodeMirror overlay/compact on demand); context usage ring; works with any OpenAI-compatible endpoint. Runs on the Cordis agent runtime (DeepSeek harness) — see Architecture.![[image.png]]), uploads files to custom attachment folders, resolves local media via sequential-fallback protocol, and merges YAML frontmatter non-destructively.md files, project data in SQLite; no network required![]() | ![]() |
![]() | ![]() |
![]() | |
git clone https://github.com/ddutchie/cairn
cd cairn
npm install
npm run rebuild # build better-sqlite3 native binaries for Electron + system Node
npm run compile # compile Electron main process + bundle MCP server
npm run dev # start Cairn (Next.js + Electron)
npm run build:mac # macOS DMG (arm64 + x64)
npm run build:win # Windows NSIS installer (x64 + arm64)
npm run build:linux # Linux AppImage (x64 + arm64)
npm run build:all # All three platforms
Output goes to dist-app/.
Note: Re-run
npm run rebuildafter any Electron version bump. It builds native binaries for three ABIs (Electron, Node 22/MCP, system Node/vitest) and bundles the MCP server into a self-contained binary viascripts/build-mcp-binary.js.
Configure the AI endpoint in Settings → AI (tabs: Chat, Coding Agents, MCP; no restart needed):
| Setting | Default | Notes |
|---|---|---|
| Base URL | https://api.openai.com | Any OpenAI-compatible endpoint |
| Model | gpt-4o-mini | Any model name the endpoint accepts |
| API Key | (blank) | Not required for local endpoints |
Quick presets — one click to switch between OpenAI, Ollama (localhost:11434), and LM Studio (localhost:1234). Local servers don't need an API key.
[!NOTE] Proxy & Gateway Timeouts (504): If pointing to an API gateway proxy or reverse proxy (e.g., nginx), long synchronous non-stream requests can trigger connection timeouts (returning a
504 Gateway Time-out). All completion requests in Cairn (including chat tool loop and context compaction) stream internally to keep the connection active.
Cairn includes a native coding agent that runs directly inside the app — no external CLI binary required. It is accessible from the Agent view (⌘5) by choosing Cairn Agent in the spawn modal.
The agent runs on the Cordis agent runtime (DeepSeek harness): a shared engine drives the model↔tool loop, session persistence, approvals, subagents, background jobs, and context compaction — Cairn contributes its workspace tools, board-aware system prompt, and renderer bridges. See Architecture.
Unlike a general coding agent, the Cairn Agent is a first-class participant in your project:
ensure_note (idempotent — no duplicates on re-run)Launch the agent in Plan Mode to produce a spec before writing any code. The agent reads your codebase (read-only tools only) and writes a structured PRD note to your project. An Approve Plan button in the chat header then promotes the session to Execute Mode, injecting the full PRD as context for the coding run.
The agent can delegate contained sub-tasks to a fresh subagent via the subagent tool (one-shot) or delegate (continuable background conversations you can message later). A delegated agent runs with its own session history — only its final answer returns to the parent, keeping the parent context lean for long multi-step tasks. The subagent trace renders inline and collapsible in the chat UI.
A small ring in the agent pane header shows how full the model's context window is after each step. Configure the limit for your model in Settings → AI → Coding Agents → Context window (presets: 8k / 32k / 128k / 200k).
When usage reaches 80% the agent automatically compacts older context — the status bar shows "Compacting context…" while this is in flight. Type /compact in the chat input to trigger compaction on demand at any time. If a transient API error occurs, the status bar shows a countdown ("Transient error — retrying (1/3) in 8s…") and the agent retries automatically.
Cairn ships a standalone stdio MCP server as a self-contained binary (dist-mcp/cairn-mcp), built with @yao-pkg/pkg. It connects directly to the same SQLite database as the app — writes are reflected in the UI in real time via WAL polling.
Add to opencode.json in your project root:
{
"mcp": {
"cairn": {
"type": "local",
"command": ["/Applications/Cairn.app/Contents/Resources/app.asar.unpacked/dist-mcp/cairn-mcp"],
"enabled": true
}
}
}
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"cairn": {
"command": "/Applications/Cairn.app/Contents/Resources/app.asar.unpacked/dist-mcp/cairn-mcp"
}
}
}
The exact paths above are generated automatically in Settings → AI → MCP — copy them from there.
The MCP surface is derived from the same schemas the in-app agents use (electron/lib/tool-schemas.ts — everything except chat-only tools such as ask_questions and suggest_connections), across notes, tasks, projects, dashboards, Idea Flow, knowledge graph, tags, and codebase search. The authoritative live list is always in the app under Settings → AI → MCP, which shows every tool with its read/write/delete category — this file does not duplicate that list so it can't rot.
Agent tip: call
get_cairn_contextat the start of a session for all workspace/project/column IDs. Uselist_ready_tasksinstead ofsearch_taskswhen you want to know what work can actually start — it filters out anything blocked by an unresolved dependency. Useupdate_taskwithblockedBy/unblockFromto manage task dependencies.
| Shortcut | Action |
|---|---|
⌘K | Open global search |
⌘⇧F | Global search (any view) / File search in Agent view sidebar |
⌘F | In-context search — find/replace in Note or Agent editor; filter in Notes list, Board, or Knowledge Graph |
⌘N | New note (switches to Notes view) |
⌘/ | Toggle AI chat |
⌘\ | Toggle sidebar |
⌘1 / ⌘2 | Overview / Notes (always) |
⌘3–⌘9 | Walk the visible views in sidebar order (default: Board, Calendar, Flow, Agent, Calendar-all, Graph, Insights, …) — hide views in General settings to compress the range |
⌘S | Save file (Agent editor) |
⌘Z | Undo |
⌘⇧Z / ⌘Y | Redo |
Esc | Close modal / search / filter bar |
Two processes share a single SQLite database (WAL mode):
src/) — React/Next.js. All data flows through IPC via window.electron.*; never touches the DB or filesystem directly.electron/) — Node.js. Owns SQLite, file I/O, the Cordis agent runtime, and PTY sessions for coding agents. IPC handlers are split into per-domain registrars (db-handlers.ts, flow-handlers.ts, chat.ts, session-runtime-handlers.ts, runtime-handlers.ts, etc.) orchestrated by handlers.ts.electron/cordis/) — one shared Cordis context drives chat turns (runChatCordisSession) and coding turns (runCordisCodingLoop): model↔tool loop, session persistence (JSONL), approvals, subagents, background jobs, skills, and auto-compaction via DeepSeek harness plugins. Cairn contributes its workspace tools (cairn-tools.ts), the board-aware coding prompt (lib/coding-session-prompt.ts), and renderer bridges (cairn-plugins.ts). App I/O crosses into the engine only through the HostStore seam (host-store.ts). See docs/architecture-cordis.md.electron/mcp-server.ts) — self-contained binary; connects external agents to the same DB via WAL polling. SQL query helpers are shared with the Electron main process via electron/db/queries.ts (single source of truth).Notes are plain .md files (YAML frontmatter); SQLite is the read/search cache. Writes are atomic (.tmp rename). A chokidar watcher syncs external edits at runtime. notes and task_cards carry a version integer; MCP write tools accept expectedVersion for conflict detection.
For the full architecture reference see CONTRIBUTING.md. AI coding agents should read AGENTS.md for conventions tuned to LLM context windows.
npm test # full gate: licenses + features + compile + unit & integration (Vitest)
npm run test:watch # watch mode
npm run test:coverage # coverage report
npm run test:e2e # E2E smoke tests — headless Chromium, no Electron required
npm run test:e2e:ui # Playwright UI mode
npm run test:e2e:headed # headed for local debugging
Unit/integration tests (electron/**/*.test.ts) cover SQLite queries, file I/O, MCP tools end-to-end, chat executor tool cases, IPC handlers, and MCP↔chat tool parity. The E2E suite runs against the Next.js dev server with a full IPC mock — covers boot, all 8 views, and sidebar content in ~10s.
Run npm run test:e2e before cutting a release to catch renderer crashes unit tests can't reach.
Platform
| Tool | Role |
|---|---|
| Electron | Desktop shell |
| Next.js 16 | UI framework (App Router, static export) |
| TypeScript | Language |
| esbuild | Bundler (Electron main + MCP binary) |
| vitest | Unit & integration test runner |
| Playwright | E2E smoke tests (browser, no Electron required) |
Data & AI
| Tool | Role |
|---|---|
| better-sqlite3 | SQLite (arch-separated native bindings for Electron + MCP + vitest) |
| gray-matter | YAML frontmatter parsing for note files |
| chokidar | File watcher for external .md edits |
| @deepseek-ai/cordis + dsh-* | Agent runtime: model loop, sessions, tools, approvals, subagents, jobs, compaction |
| onnxruntime-node + @huggingface/transformers | Local embeddings for semantic search |
| @modelcontextprotocol/sdk | MCP server |
| Zod | Schema validation |
| nanoid | ID generation |
UI & State
| Tool | Role |
|---|---|
| Tailwind CSS v4 | Styling (CSS custom properties; never raw colour names) |
| Zustand | State management (domain slices: ui, workspace, board, notes, tags, chat, graph, selectors, coding-agents, terminal-sessions) |
| Radix UI | Accessible UI primitives (dialog, dropdown, tooltip, popover, select, context menu) |
| Lucide React | Icons |
| cmdk | Command palette |
| react-day-picker | Date picker |
| date-fns | Date utilities |
| tailwind-merge | Tailwind class merge utility |
Editor & Agent
| Tool | Role |
|---|---|
| CodeMirror 6 | Note editor + Agent file editor (CM6, CSS-hidden-per-tab pattern) |
| @codemirror/search | In-editor find/replace panel (⌘F) |
| node-pty | PTY process spawning (Agent terminal) |
| @xterm/xterm | Terminal emulator (Agent view) |
| @xterm/addon-fit | Terminal auto-resize |
| parse-diff | Git diff parser (Agent diff viewer) |
Visualisation
| Tool | Role |
|---|---|
| @xyflow/react | Node-based canvas (Idea Flow) |
| dnd-kit | Drag and drop (Kanban) |
| D3 v7 | Analytics & graph visualisation (Insights canvases, Radial tree, Force-directed graph) |
| d3-sankey | Sankey pipeline diagram (Insights) |
| @dagrejs/dagre | Graph auto-layout (Idea Flow) |
Markdown
| Tool | Role |
|---|---|
| react-markdown | Markdown preview |
| remark-gfm | GitHub Flavored Markdown |
| remark-breaks | Hard line breaks in markdown |
| remark-math / rehype-katex | Math expression rendering |
| Mermaid | Diagram rendering in notes |
| lowlight | Syntax highlighting in code blocks |
The Settings → About screen in the app shows real installed versions grouped by category (Platform, Data, AI, UI, Editor, Agent, Visualisation) and all open source licenses. These are generated automatically at build time — see below.
Contributions are welcome — bug fixes, features, docs, and tests. See CONTRIBUTING.md for the full guide: dev setup, architecture, coding conventions, and PR checklist.
For security issues please see SECURITY.md rather than filing a public issue.
MIT — see LICENSE.
2,032 commits
TypeScript
97.7%
Cairn is an AI notes and project management tool with a built in MCP server
See the code
A calm, local-first workspace for notes, project tracking, and visual idea mapping — with an AI assistant and MCP server built in.
Cairn is a desktop app (Electron + Next.js) that combines markdown notes with a kanban board. It is fully compatible with Obsidian vaults, allowing you to point a workspace directly to any existing Obsidian folder. Notes are saved as plain .md files directly in your vault; project and task data lives in a local SQLite database alongside them. No accounts, no cloud, no backend. An embedded AI assistant and a standalone MCP server let AI agents read and write your workspace directly.
⌘K or ⌘⇧F)⌘/)⌘4): ideas, note/task refs, groups, URLs, AI summaries — connected with labelled edgeswindow.cairn.query() data bridge; inline "Fix with AI" on runtime errors; editable via built-in CodeMirror overlay/compact on demand); context usage ring; works with any OpenAI-compatible endpoint. Runs on the Cordis agent runtime (DeepSeek harness) — see Architecture.![[image.png]]), uploads files to custom attachment folders, resolves local media via sequential-fallback protocol, and merges YAML frontmatter non-destructively.md files, project data in SQLite; no network required![]() | ![]() |
![]() | ![]() |
![]() | |
git clone https://github.com/ddutchie/cairn
cd cairn
npm install
npm run rebuild # build better-sqlite3 native binaries for Electron + system Node
npm run compile # compile Electron main process + bundle MCP server
npm run dev # start Cairn (Next.js + Electron)
npm run build:mac # macOS DMG (arm64 + x64)
npm run build:win # Windows NSIS installer (x64 + arm64)
npm run build:linux # Linux AppImage (x64 + arm64)
npm run build:all # All three platforms
Output goes to dist-app/.
Note: Re-run
npm run rebuildafter any Electron version bump. It builds native binaries for three ABIs (Electron, Node 22/MCP, system Node/vitest) and bundles the MCP server into a self-contained binary viascripts/build-mcp-binary.js.
Configure the AI endpoint in Settings → AI (tabs: Chat, Coding Agents, MCP; no restart needed):
| Setting | Default | Notes |
|---|---|---|
| Base URL | https://api.openai.com | Any OpenAI-compatible endpoint |
| Model | gpt-4o-mini | Any model name the endpoint accepts |
| API Key | (blank) | Not required for local endpoints |
Quick presets — one click to switch between OpenAI, Ollama (localhost:11434), and LM Studio (localhost:1234). Local servers don't need an API key.
[!NOTE] Proxy & Gateway Timeouts (504): If pointing to an API gateway proxy or reverse proxy (e.g., nginx), long synchronous non-stream requests can trigger connection timeouts (returning a
504 Gateway Time-out). All completion requests in Cairn (including chat tool loop and context compaction) stream internally to keep the connection active.
Cairn includes a native coding agent that runs directly inside the app — no external CLI binary required. It is accessible from the Agent view (⌘5) by choosing Cairn Agent in the spawn modal.
The agent runs on the Cordis agent runtime (DeepSeek harness): a shared engine drives the model↔tool loop, session persistence, approvals, subagents, background jobs, and context compaction — Cairn contributes its workspace tools, board-aware system prompt, and renderer bridges. See Architecture.
Unlike a general coding agent, the Cairn Agent is a first-class participant in your project:
ensure_note (idempotent — no duplicates on re-run)Launch the agent in Plan Mode to produce a spec before writing any code. The agent reads your codebase (read-only tools only) and writes a structured PRD note to your project. An Approve Plan button in the chat header then promotes the session to Execute Mode, injecting the full PRD as context for the coding run.
The agent can delegate contained sub-tasks to a fresh subagent via the subagent tool (one-shot) or delegate (continuable background conversations you can message later). A delegated agent runs with its own session history — only its final answer returns to the parent, keeping the parent context lean for long multi-step tasks. The subagent trace renders inline and collapsible in the chat UI.
A small ring in the agent pane header shows how full the model's context window is after each step. Configure the limit for your model in Settings → AI → Coding Agents → Context window (presets: 8k / 32k / 128k / 200k).
When usage reaches 80% the agent automatically compacts older context — the status bar shows "Compacting context…" while this is in flight. Type /compact in the chat input to trigger compaction on demand at any time. If a transient API error occurs, the status bar shows a countdown ("Transient error — retrying (1/3) in 8s…") and the agent retries automatically.
Cairn ships a standalone stdio MCP server as a self-contained binary (dist-mcp/cairn-mcp), built with @yao-pkg/pkg. It connects directly to the same SQLite database as the app — writes are reflected in the UI in real time via WAL polling.
Add to opencode.json in your project root:
{
"mcp": {
"cairn": {
"type": "local",
"command": ["/Applications/Cairn.app/Contents/Resources/app.asar.unpacked/dist-mcp/cairn-mcp"],
"enabled": true
}
}
}
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"cairn": {
"command": "/Applications/Cairn.app/Contents/Resources/app.asar.unpacked/dist-mcp/cairn-mcp"
}
}
}
The exact paths above are generated automatically in Settings → AI → MCP — copy them from there.
The MCP surface is derived from the same schemas the in-app agents use (electron/lib/tool-schemas.ts — everything except chat-only tools such as ask_questions and suggest_connections), across notes, tasks, projects, dashboards, Idea Flow, knowledge graph, tags, and codebase search. The authoritative live list is always in the app under Settings → AI → MCP, which shows every tool with its read/write/delete category — this file does not duplicate that list so it can't rot.
Agent tip: call
get_cairn_contextat the start of a session for all workspace/project/column IDs. Uselist_ready_tasksinstead ofsearch_taskswhen you want to know what work can actually start — it filters out anything blocked by an unresolved dependency. Useupdate_taskwithblockedBy/unblockFromto manage task dependencies.
| Shortcut | Action |
|---|---|
⌘K | Open global search |
⌘⇧F | Global search (any view) / File search in Agent view sidebar |
⌘F | In-context search — find/replace in Note or Agent editor; filter in Notes list, Board, or Knowledge Graph |
⌘N | New note (switches to Notes view) |
⌘/ | Toggle AI chat |
⌘\ | Toggle sidebar |
⌘1 / ⌘2 | Overview / Notes (always) |
⌘3–⌘9 | Walk the visible views in sidebar order (default: Board, Calendar, Flow, Agent, Calendar-all, Graph, Insights, …) — hide views in General settings to compress the range |
⌘S | Save file (Agent editor) |
⌘Z | Undo |
⌘⇧Z / ⌘Y | Redo |
Esc | Close modal / search / filter bar |
Two processes share a single SQLite database (WAL mode):
src/) — React/Next.js. All data flows through IPC via window.electron.*; never touches the DB or filesystem directly.electron/) — Node.js. Owns SQLite, file I/O, the Cordis agent runtime, and PTY sessions for coding agents. IPC handlers are split into per-domain registrars (db-handlers.ts, flow-handlers.ts, chat.ts, session-runtime-handlers.ts, runtime-handlers.ts, etc.) orchestrated by handlers.ts.electron/cordis/) — one shared Cordis context drives chat turns (runChatCordisSession) and coding turns (runCordisCodingLoop): model↔tool loop, session persistence (JSONL), approvals, subagents, background jobs, skills, and auto-compaction via DeepSeek harness plugins. Cairn contributes its workspace tools (cairn-tools.ts), the board-aware coding prompt (lib/coding-session-prompt.ts), and renderer bridges (cairn-plugins.ts). App I/O crosses into the engine only through the HostStore seam (host-store.ts). See docs/architecture-cordis.md.electron/mcp-server.ts) — self-contained binary; connects external agents to the same DB via WAL polling. SQL query helpers are shared with the Electron main process via electron/db/queries.ts (single source of truth).Notes are plain .md files (YAML frontmatter); SQLite is the read/search cache. Writes are atomic (.tmp rename). A chokidar watcher syncs external edits at runtime. notes and task_cards carry a version integer; MCP write tools accept expectedVersion for conflict detection.
For the full architecture reference see CONTRIBUTING.md. AI coding agents should read AGENTS.md for conventions tuned to LLM context windows.
npm test # full gate: licenses + features + compile + unit & integration (Vitest)
npm run test:watch # watch mode
npm run test:coverage # coverage report
npm run test:e2e # E2E smoke tests — headless Chromium, no Electron required
npm run test:e2e:ui # Playwright UI mode
npm run test:e2e:headed # headed for local debugging
Unit/integration tests (electron/**/*.test.ts) cover SQLite queries, file I/O, MCP tools end-to-end, chat executor tool cases, IPC handlers, and MCP↔chat tool parity. The E2E suite runs against the Next.js dev server with a full IPC mock — covers boot, all 8 views, and sidebar content in ~10s.
Run npm run test:e2e before cutting a release to catch renderer crashes unit tests can't reach.
Platform
| Tool | Role |
|---|---|
| Electron | Desktop shell |
| Next.js 16 | UI framework (App Router, static export) |
| TypeScript | Language |
| esbuild | Bundler (Electron main + MCP binary) |
| vitest | Unit & integration test runner |
| Playwright | E2E smoke tests (browser, no Electron required) |
Data & AI
| Tool | Role |
|---|---|
| better-sqlite3 | SQLite (arch-separated native bindings for Electron + MCP + vitest) |
| gray-matter | YAML frontmatter parsing for note files |
| chokidar | File watcher for external .md edits |
| @deepseek-ai/cordis + dsh-* | Agent runtime: model loop, sessions, tools, approvals, subagents, jobs, compaction |
| onnxruntime-node + @huggingface/transformers | Local embeddings for semantic search |
| @modelcontextprotocol/sdk | MCP server |
| Zod | Schema validation |
| nanoid | ID generation |
UI & State
| Tool | Role |
|---|---|
| Tailwind CSS v4 | Styling (CSS custom properties; never raw colour names) |
| Zustand | State management (domain slices: ui, workspace, board, notes, tags, chat, graph, selectors, coding-agents, terminal-sessions) |
| Radix UI | Accessible UI primitives (dialog, dropdown, tooltip, popover, select, context menu) |
| Lucide React | Icons |
| cmdk | Command palette |
| react-day-picker | Date picker |
| date-fns | Date utilities |
| tailwind-merge | Tailwind class merge utility |
Editor & Agent
| Tool | Role |
|---|---|
| CodeMirror 6 | Note editor + Agent file editor (CM6, CSS-hidden-per-tab pattern) |
| @codemirror/search | In-editor find/replace panel (⌘F) |
| node-pty | PTY process spawning (Agent terminal) |
| @xterm/xterm | Terminal emulator (Agent view) |
| @xterm/addon-fit | Terminal auto-resize |
| parse-diff | Git diff parser (Agent diff viewer) |
Visualisation
| Tool | Role |
|---|---|
| @xyflow/react | Node-based canvas (Idea Flow) |
| dnd-kit | Drag and drop (Kanban) |
| D3 v7 | Analytics & graph visualisation (Insights canvases, Radial tree, Force-directed graph) |
| d3-sankey | Sankey pipeline diagram (Insights) |
| @dagrejs/dagre | Graph auto-layout (Idea Flow) |
Markdown
| Tool | Role |
|---|---|
| react-markdown | Markdown preview |
| remark-gfm | GitHub Flavored Markdown |
| remark-breaks | Hard line breaks in markdown |
| remark-math / rehype-katex | Math expression rendering |
| Mermaid | Diagram rendering in notes |
| lowlight | Syntax highlighting in code blocks |
The Settings → About screen in the app shows real installed versions grouped by category (Platform, Data, AI, UI, Editor, Agent, Visualisation) and all open source licenses. These are generated automatically at build time — see below.
Contributions are welcome — bug fixes, features, docs, and tests. See CONTRIBUTING.md for the full guide: dev setup, architecture, coding conventions, and PR checklist.
For security issues please see SECURITY.md rather than filing a public issue.
MIT — see LICENSE.
2,032 commits
TypeScript
97.7%