cortex
Self-hosted MCP server exposing a brain's tool registry (vault search, vault access, and connector tools) to MCP clients like Claude Code, scoped to the box owner.
README
<div align="center"> <img src="docs/assets/lockup-horizontal.svg" width="300" alt="Unchained Labs"> </div>
cortex
A self-hosted brain for a household or a team: a dashboard where people chat with each other and with an agent that has read their shared notes — on your own model, on your own machine.
<div align="center"> <a href="https://unchained-labs.github.io/cortex/#film"> <img src="docs/assets/cortex-demo.gif" width="720" alt="cortex demo — ask the agent, watch the tool call stream, get a cited answer"> </a> <br><sub>Real product on film — dashboard, agent, and streaming are the shipped code; only the model is scripted (<code>docs/promo/</code> rebuilds it). <a href="https://unchained-labs.github.io/cortex/#film">The full one-minute film →</a></sub> </div>
Status: alpha. The API and config surface are settling; expect breaking
changes between minor versions. The index and checkpoint formats are
disposable caches — deleting .cortex/ loses conversations, never notes.
pip install cortxai
cortex setup # wizard: brain dir, model endpoint, admin account
cortex serve --host 0.0.0.0 # dashboard on :8642
Or bash install.sh (pipx/uv/venv autodetect), or docker compose up after
the one-time cortex setup /brain documented in docker-compose.yml.
What it does not do: cortex hosts no model — you bring an endpoint: Ollama, vLLM, LM Studio, a LiteLLM proxy, OpenRouter, or the Anthropic API. Vector search is exact cosine in-process, right for personal- and team-sized brains, wrong for millions of chunks. Vault edits are last-writer-wins with conflict detection (a 409 and a banner), not git-grade merging. The calendar connector expands no recurrence rules yet.
The dashboard
- Today — the default view and the reason to open it: what is on today, a few open tasks you can tick straight from the list, what changed, and anything you wrote on this date in earlier years. Computed without the model, so it answers instantly and works on a brain with no model configured at all. It is deliberately bounded — a handful of tasks and then "that is everything for today", never a growing pile of everything you have not done.
- Capture — press c anywhere. One line, Enter, and it lands in
today's daily note. Also
cortex note "..."from a terminal, and the agent can do it for you. Filing is optional; search does not care which note a line is in. - Search — hybrid full-text and vector search over everything you can read, with / from anywhere.
- Chat — private threads with the agent. It searches before it answers,
streams its tool calls (⚙
search_brain… ✓ 33ms), and cites files by path; clicking a citation opens it in the vault view. - Channels — peer chat for the people on the brain. Mention
@cortexand the agent answers in-channel, reading only the shared vault — never anyone's personal vault. - Vault — shared and personal vaults, edited in the browser with
Obsidian-flavored rendering:
[[wikilinks]],![[embeds]],> [!note]callouts, frontmatter, task checkboxes that write through,#tags. Ctrl-S saves; a concurrent edit gets a conflict banner, not a silent clobber. - Import — bring an existing Obsidian vault as a zip upload, a git URL,
or a server path.
.obsidian/,.git/and non-vault file types are skipped. - Admin — accounts (
admin/member), index and model health, and a way to re-index without a terminal.
The agent can write, narrowly: it can add a line to today's note, tick a task by exact path and line, and save a web page as markdown. There is no general "write any file" tool — on a vault with no version control, the narrowness is the safety property.
Accounts are username + password (scrypt), sessions are HttpOnly cookies. Each user sees the shared vault, their own vault, and connector sources — search, grep, and the agent are scoped per request, filtered inside the query rather than trimmed after it.
The agent stack
LangGraph's ReAct agent over LangChain chat models, with conversation state
in an AsyncSqliteSaver checkpoint per thread:
providers:
local:
kind: openai # Ollama, vLLM, LM Studio — one wire
base_url: "http://localhost:11434/v1"
chat_model: qwen3
embed_model: nomic-embed-text
router:
kind: openrouter # cloud aggregator, OpenAI wire
api_key_env: OPENROUTER_API_KEY
chat_model: anthropic/claude-sonnet-5
claude:
kind: anthropic # direct Anthropic Messages API
api_key_env: ANTHROPIC_API_KEY
chat_model: claude-sonnet-5
roles:
chat: router
embed: local
A LiteLLM proxy is kind: litellm with its base_url — its routing and
fallback policy stays in the proxy, so cortex carries no LiteLLM SDK.
Endpoints are classified by network facts: private, loopback, CGNAT and
Tailscale addresses are trusted; anything public gets a plain warning that
your notes will leave the network.
Retrieval is hybrid: SQLite FTS5 and vector cosine ranked separately, fused with reciprocal rank fusion, nudged by recency — the design from Cerebras' knowledge base. The index rebuilds from scratch when the chunk schema or embedding model changes, because silently mixing vector spaces is corruption. No embedding endpoint means full-text search that says so, not fake vector scores.
Four ways to extend it
| Extension | Contract | Runs |
|---|---|---|
| Tool plugin | plugins/*.py exposing register(registry), or a package with a cortex.tools entry point |
agent time |
| MCP server | mcp_servers: block (stdio or streamable HTTP), attached via langchain-mcp-adapters |
agent time |
| Skill | skills/<name>/SKILL.md (agentskills.io), loaded lazily via use_skill |
on demand |
| Connector | connectors/*.py exposing sync(out_dir, settings) — distill, don't dump |
cortex connectors run |
A broken extension is reported and isolated, never fatal. Registration is not authorization: a tool that touches something sensitive keeps its own checks inside the callable.
Manage them from the dashboard. The admin-only Extend panel lists
every plugin, skill, connector and MCP server with what it provides, its
load error if it has one, and an enable toggle that never edits your source
file. You can write a plugin or connector in the browser: it is loaded
before it is saved, so code that will not import is refused with the
loader's own message instead of silently breaking the next turn, and a
successful save rebuilds the agent so the new tool is live without a
restart. Connectors get a settings box and a "Run now" button; MCP servers
get a form. Servers defined in cortex.yaml show up read-only — the file
stays the owner of what it declares.
Saving a plugin or connector runs that code on the server as the cortex
user. That is the same trust level as configuring a stdio MCP server, and it
is why the panel is admin-only. From the terminal, cortex ext list,
cortex ext disable plugin <name>, and cortex ext delete do the same
management without the browser.
Cortex is also an MCP server —
claude mcp add home-brain -- cortex mcp --brain ~/brain gives Claude Code,
Cursor, or Hermes the same tool registry, at box-owner scope.
Layout of a brain
~/brain/
├── cortex.yaml # providers, roles, mcp servers, connectors
├── vaults/shared/ # everyone's notes
├── vaults/<user>/ # each user's private vault
├── sources/ # connector output
├── skills/ plugins/ connectors/
└── .cortex/ # index, checkpoints, usage.jsonl — disposable cache
Back it up by copying the folder. Home brain, company brain, club brain:
three folders, three cortex serve processes.
cortex note "the boiler service is due in March" # capture, from anywhere
cortex today # what is on
cortex clip https://example.com/recipe # save a page as markdown
cortex demo # example notes for an empty brain
cortex service install # keep it running across reboots
An empty brain cannot help you, so cortex setup offers to import an
existing vault, indexes what it finds, and cortex demo seeds a few
obviously-fake example notes you can delete in one command.
Observability
Every model and tool call appends JSONL to .cortex/usage.jsonl with
prompt_tokens/completion_tokens when the endpoint reports them — absent
counts stay absent rather than becoming zeros, which is what
preflight expects for
calibration. Telemetry never makes a call fail.
Development
uv venv --python 3.12 && uv pip install -e '.[dev]'
.venv/bin/pytest # 102 tests
.venv/bin/ruff check src tests
cd web && npm install && npm run dev # SPA dev server, proxies to :8642
The frontend contract lives in docs/product-spec.md; cutting a release is RELEASING.md.
Docs: unchained-labs.github.io/cortex · Brand: Unchained-Labs/branding · License: MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。