brain-mcp
brain-mcp
图标设计致谢:wenmeiZhou,来自 iStock。
一个 MCP 服务器,将基于 Markdown、采用 PARA 结构组织的第二大脑仓库(兼容 Obsidian)以一组读取工具的形式暴露出来,外加一个受限的写入工具(capture,它只会在 00-inbox/ 中创建新笔记)。它以 Docker 容器方式运行,通过 streamable-HTTP 供 Claude Code、opencode 及其他 MCP 客户端使用。
完整行为规范:如果你需要了解某个设计选择背后的“为什么”,请参阅本仓库中的 brain-mcp-spec.md(或你保存该文件的任何位置)。
关于 CONVENTIONS.md 解析器的说明
brain_structure() 会实时读取你仓库中 90-meta/CONVENTIONS.md 里的 type/status/domain 枚举,而不是硬编码它们(参见 src/brain_mcp/vault.py 中的 _extract_enum_values)。它已直接对照一个真实仓库的文件做过校验:该文件在单个 ## Enums 标题下将这三个枚举写成粗体内联标签(**type:** \note`, `project`, ...),而不是让每个枚举各自拥有一个子标题,因此 _extract_enum_values 会先尝试这种形态(限定在标签自身的段落内,这样就不会把下方正文中无关的、被反引号引用的词收进来——例如 “domainis the primary query axis...”),对于以其他方式记录枚举的仓库,则回退到基于标题的启发式方法。如果你以后重构CONVENTIONS.md`'s Enums 部分,请重新检查这个解析器——如果它无法为全部三个字段解析出非空的值列表,服务器会在启动时明确报错,而不是回退到错误的默认值。
uid 格式: 用于校验该解析器的仓库目前在其自身的 CONVENTIONS.md 中存在自相矛盾——frontmatter 示例显示的是 UUIDv4,其下方两行正文说真实格式是 YYYYMMDD-HHmm(同一分钟冲突时附加一个字母),而磁盘上的实际笔记之间又使用了三种不同的方案(一条 inbox 笔记中是 UUIDv4,meta 文档中是 00000000-000N 哨兵值,_index.md 文件中是 YYYYMMDD-000N 顺序计数器)。capture() 生成 UUIDv4——这正是原始规范的要求,与当前代码一致,并且能让 read_note 的短前缀查找(≥8 个字符)保持有意义;而如果针对低熵的、基于日期的 id,同一天的所有内容都共享同一个前缀,短前缀查找就不会有意义了。如果你自己的 CONVENTIONS.md 记录了不同的 uid 方案,那值得在仓库侧进行协调统一——这不是本服务器会自动处理的事情。
Related MCP server: ohmyself
环境要求
仓库的 PARA 结构和 frontmatter 模式,如仓库自身的
90-meta/CONVENTIONS.md所述。Unraid 机器上的 Docker(或 Docker Compose),或本地用于开发的 Python 3.12 +
uv。PATH 中的
ripgrep(已捆绑在容器镜像中;本地开发需单独安装)。
配置
所有配置都通过环境变量进行——没有任何关于特定仓库的信息(路径、名称、令牌)被硬编码,因此同一个镜像可以以独立容器的方式服务任意数量的同级仓库。
变量 | 必填 | 默认值 | 含义 |
| 是 | — | 容器内仓库根的绝对路径 |
| 是 | — | 实例名称,例如 |
| 是 | — | 每个 MCP 请求所需的 Bearer 令牌 |
| 否 |
| 监听端口 |
| 否 |
| 监听地址 |
| 否 |
|
|
运行 Compose 前,请把 .env.example 复制为 .env,并填写 BRAIN_NAME、BRAIN_VAULT_PATH(你的仓库在宿主机上的路径)和 BRAIN_TOKEN(一个随机密钥——openssl rand -hex 32 就很不错)。
本地开发
uv sync --dev # or: python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
uv run pytest # or: .venv/bin/python -m pytest测试完全针对 tests/conftest.py 中构建的合成测试夹具仓库运行——绝不会针对真实数据。
若要在本地针对真实(或临时)仓库目录运行服务器:
export BRAIN_ROOT=/path/to/vault
export BRAIN_NAME=personal
export BRAIN_TOKEN=dev-token
uv run brain-mcp在 Unraid 上运行
cp .env.example .env # fill in BRAIN_NAME, BRAIN_VAULT_PATH, BRAIN_TOKEN
docker compose up -d --build # or: docker compose pull && docker compose up -d
curl http://<unraid-host>:3100/health--build 从当前检出版本进行构建;pull 则改为从 GHCR 拉取同一个预构建镜像(见下文“改为作为 Unraid 应用安装”)——无论哪种方式,都会在本地生成 ghcr.io/sipho102/brain-mcp:latest 标签。
compose 文件将 BRAIN_VAULT_PATH 以只读方式挂载,并在其上仅将 00-inbox/ 以读写方式重新挂载:
volumes:
- ${BRAIN_VAULT_PATH}:/vault:ro
- ${BRAIN_VAULT_PATH}/00-inbox:/vault/00-inbox:rw这是有意为之且至关重要的设计——即使写入路径存在 bug,也无法触碰 inbox 之外的任何内容,无论 Python 代码自以为在做什么。请不要把它简化成单一的读写挂载。
改为作为 Unraid 应用安装
如果你更愿意像管理其他应用一样,从 Unraid 的 Docker 选项卡管理这个容器——用一个表单代替编辑 .env,之后还能用“启动/停止/更新”按钮——unraid/brain-mcp.xml 中就有现成的模板。.github/workflows/publish.yml 会在每次推送到 main 时构建本仓库的镜像并发布到 GHCR(ghcr.io/sipho102/brain-mcp:latest),模板会直接拉取该镜像——完全不需要在 Unraid 机器上进行克隆或构建。
让 Unraid 可以使用这个模板:
推荐——在 Docker 选项卡中点击添加容器(Add Container),然后将本仓库模板的原始 URL 直接粘贴到模板字段中:
https://raw.githubusercontent.com/sipho102/brain-mcp/main/unraid/brain-mcp.xml这种方式不会向 Unraid 的本地模板文件夹写入任何内容,因此不会留下多余文件,日后也不会与之冲突——关于另一种方法,请参阅下面的注意事项。或者先通过 SSH 把它复制到 Unraid 的本地模板文件夹中:
curl -o /boot/config/plugins/dockerMan/templates-user/brain-mcp.xml \ https://raw.githubusercontent.com/sipho102/brain-mcp/main/unraid/brain-mcp.xml之后它会出现在 Docker → 添加容器(Add Container)→ 模板下拉菜单中——但请注意紧接着“添加容器(Add Container)”之后的说明:容器创建后要删除这个文件。
无论采用哪种方式,你都会得到一个表单,用来填写仓库路径、inbox 路径(必须是 <vault path>/00-inbox——模板无法替你推导出来)、实例名称和 Bearer 令牌;其余所有内容都已在“高级视图(advanced view)”下预填了合理的默认值。
如果你使用了上面的本地复制方法,请在容器创建后删除那个种子文件:
rm /boot/config/plugins/dockerMan/templates-user/brain-mcp.xml当你在“添加容器(Add Container)”上点击“应用(Apply)”时,Unraid 会保存第二个文件,里面是你的实际值——my-brain-mcp.xml,与你下载的空白文件并存——而两者声明的是同一个容器名称。当两个模板都声称拥有该名称时,“更新(Update)”最终可能会从那个空白原始模板而不是你保存的模板重新创建容器,从而抹掉 BRAIN_NAME/BRAIN_TOKEN/各项路径,让它无法启动。 一旦 my-brain-mcp.xml 存在(用 ls /boot/config/plugins/dockerMan/templates-user/ 检查),种子文件就已完成了它的使命,不再需要——请删除它,以免产生歧义。之后点击**“更新(Update)”**,便会按预期使用你保存的配置拉取 ghcr.io/sipho102/brain-mcp:latest 上最新的内容。
服务第二个仓库
一个容器服务一个仓库——docker-compose.yml 中刻意没有多仓库服务列表,Unraid 模板中也没有多仓库表单。在上面提到的 Unraid 应用方式下,这只需用同一个模板再次运行添加容器(Add Container),并填入不同的名称/路径/令牌/端口即可。在 Compose 方式下,把这份部署目录(或仅 docker-compose.yml + .env)复制到别处,在该副本的 .env 中填入不同的 BRAIN_NAME、BRAIN_VAULT_PATH、BRAIN_TOKEN 和 PORT,然后也在那里运行 docker compose up -d --build。同一个镜像(brain-mcp:latest),相互独立的容器。
容器用户/权限
容器以非 root 用户身份运行,默认 UID:GID 为 99:100(即 Unraid 的 nobody:users)——如果你的共享目录需要不同的属主,可以在构建时用 .env 中的 BRAIN_UID/BRAIN_GID 覆盖。该用户必须对宿主机共享目录上的 00-inbox/ 具有写权限。
连接客户端
Claude Code
claude mcp add --transport http --scope user brain \
http://<unraid-host>:3100/mcp \
--header "Authorization: Bearer <token>"然后在会话中运行 /mcp,应该会列出全部六个工具。
已知怪癖: Claude Code 反复出现过这样的 bug:通过 --header 设置的请求头在建立会话期间不会被发送,从而产生 401,即便使用相同令牌的 curl 一切正常。如果你遇到了这种情况,请直接把 headers 对象写进 JSON 配置(~/.claude/mcp_servers.json 或相应作用域文件)中:
{
"mcpServers": {
"brain": {
"type": "http",
"url": "http://<unraid-host>:3100/mcp",
"headers": {
"Authorization": "Bearer <token>"
}
}
}
}(在 JSON 配置中,type 也接受 streamable-http 作为 http 的别名。)
opencode
opencode 默认会对远程 MCP 服务器尝试 OAuth 发现,并且除非你显式禁用这一点,否则它会忽略静态 Bearer 令牌:
{
"mcp": {
"brain": {
"type": "remote",
"url": "http://<unraid-host>:3100/mcp",
"oauth": false,
"headers": {
"Authorization": "Bearer <token>"
}
}
}
}如果没有 oauth: false,opencode 将尝试进行 OAuth 握手(并失败),而不是使用该请求头。
完全没有请求头字段的客户端
某些 MCP 客户端界面只接受名称、传输方式和 URL——没有地方可以设置自定义的 Authorization 请求头。对于这些客户端,请改为把令牌放在 URL 中:
http://<unraid-host>:3100/mcp?token=<token>服务器会先检查 Authorization 请求头,然后回退到 ?token= 查询参数,因此上面基于请求头的配置能用的任何地方,这种方式也同样适用。在依赖它之前,有一点值得了解:URL 中的令牌可能出现在比请求头更多的地方——客户端保存的配置、如果 URL 曾被直接打开过则是浏览器历史、如果你曾把它粘贴进终端则是 shell 历史。访问日志在这里不用担心(uvicorn 的访问日志已关闭),但请把 URL 本身视为携带机密,就像对待令牌本身一样。
工具
六个工具,刻意保持精简(工具模式会占用客户端上下文):
brain_structure()— 概览:PARA 文件夹 + 数量、来自CONVENTIONS.md的实时枚举、frontmatter 模式、完整约定文本、笔记数量。请在会话中最先调用它。search_notes(query, domain, type, status, para, tag, limit)— 全文搜索(基于 ripgrep),并支持 frontmatter 过滤。返回元数据外加一段约 200 字符的片段,绝不返回完整正文。read_note(identifier)— 通过仓库相对路径、完整uid或无歧义的uid前缀(≥8 个字符)获取完整笔记。list_notes(para, domain, status, type, limit)— 仅浏览元数据,不进行内容搜索。get_backlinks(identifier)— 链接到这条笔记的其他笔记,并附带上下文行。capture(title, body, domain, tags, source, links)— 唯一的写入操作:在00-inbox/中创建一条新笔记。绝不覆盖已有内容,绝不触碰 inbox 之外的任何内容。
本服务器刻意不做的事
不做语义搜索/嵌入,不提供 00-inbox/ 之外的写访问,不依赖 Obsidian Local REST API(直接读取文件系统),不抓取 paperless-ngx 文档(从 frontmatter 返回文档 ID,供客户端链接到独立的 paperless MCP 服务器),不做任何 git 操作。理由请参阅 brain-mcp-spec.md §2。
Related MCP Connectors
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
Markdown-based note-taking with a hosted MCP server. Your notes serve you and your AI.
OAuth-protected, read-only-by-default MCP server for provenance-labeled QuillCaddie project memory.
Notes and memory assistant: one Markdown vault that Claude, ChatGPT and any MCP client can share.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceExposes an Obsidian notes vault as MCP services, enabling AI assistants to search, read, create, update, and delete notes and folders.15 npm1MIT
- AlicenseNot gradedqualityBmaintenanceExposes a personal markdown-based second brain (Obsidian-style) as an MCP server, enabling agents to search, read, and write notes with privacy controls.MIT
- AlicenseAqualityBmaintenanceEnables MCP clients to read, write, search, and manage notes, canvases, kanban boards, and other Obsidian vault content over a network, without requiring the Obsidian desktop app.33MIT
- AlicenseAqualityBmaintenanceEnables MCP clients to read, search, list, and explore an Obsidian vault through coarse, intent-shaped tools, with optional read-only enforcement and a single write tool.8MIT