mcpField
N
Notes Vault

Notes Vault

本地服务
github.com记忆与知识06 天前更新

关于 Notes Vault

Notes Vault 是面向使用 Markdown 笔记库的 MCP 服务,可连接本地目录或 S3 存储。它通过本地索引、Schema 校验和 Claude Code 会话钩子,帮助 Agent 检索、维护笔记并发现其与代码的偏差。

主要功能

  1. 01本地 SQLite 索引与 BM25 搜索
  2. 02按 Schema 校验笔记写入
  3. 03笔记归档与仓库日志追加
  4. 04lint 检查笔记状态与链接
  5. 05Claude Code 会话上下文钩子

适用场景

  1. 01检索已有笔记和历史决策
  2. 02按规则创建或更新 Markdown 笔记
  3. 03开始会话时查看相关系统笔记
  4. 04检查过期笔记及未记录的提交

MCP 相关

作为 MCP 服务,Notes Vault 向客户端提供笔记搜索、读取、写入、整理和检查工具。笔记库存于本地目录或 S3 存储。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"gronare-notes-vault": {
"command": "uvx",
"args": [
"notes-vault-mcp"
],
"env": {
"VAULT_PATH": "<VAULT_PATH>",
"S3_ENDPOINT": "<S3_ENDPOINT>",
"S3_ACCESS_KEY": "<S3_ACCESS_KEY>",
"S3_SECRET_KEY": "<S3_SECRET_KEY>",
"S3_BUCKET": "<S3_BUCKET>",
"S3_PREFIX": "<S3_PREFIX>",
"S3_REGION": "<S3_REGION>",
"VAULT_CACHE_DIR": "<VAULT_CACHE_DIR>",
"VAULT_SCHEMA": "<VAULT_SCHEMA>",
"VAULT_TOKEN": "<VAULT_TOKEN>",
"VAULT_STOP_HOOK": "<VAULT_STOP_HOOK>"
}
}
}
}
启动命令uvx notes-vault-mcp
需要配置的环境变量
VAULT_PATHDirectory holding the vault. Set this or the four S3_* variables.
S3_ENDPOINTS3 or MinIO endpoint URL holding the vault
S3_ACCESS_KEY密钥Access key for the vault bucket
S3_SECRET_KEY密钥Secret key for the vault bucket
S3_BUCKETBucket holding the vault
S3_PREFIXKey prefix inside the bucket
S3_REGIONRegion for the vault bucket
VAULT_CACHE_DIRWhere the SQLite index lives
VAULT_SCHEMALocal schema file overriding the one in the vault
VAULT_TOKEN密钥Bearer token required by the streamable HTTP transport
VAULT_STOP_HOOKSet to off to disable the stop hook

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypinotes-vault-mcp0.8.0

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

notes-vault-mcp

用于 Markdown 笔记库的 MCP 服务——也就是 Obsidian 保存笔记的那种形式:由带有 YAML frontmatter 的 .md 文件组成的文件夹。笔记库可以位于本地目录或 S3 存储桶中(也支持 MinIO),该服务为 Agent 提供一种低成本、带索引的读取和写入方式。

这样做的目的是让 Agent 能通过一次调用回答“关于这件事,我们已经知道些什么?”,并在笔记库与代码不一致时得到提醒。因此,该服务不只会读写文件:

  • 本地 SQLite 索引。 每次调用工具都会刷新索引,只获取版本发生变化的笔记。搜索时不会下载整个笔记库。
  • 支持 BM25 排序的全文搜索,结合文件夹权重、时间衰减和状态因子,因此,仍在使用的系统笔记会比两年前归档、但包含相同词语的计划排名更高。
  • 一个模式。 frontmatter 规范存放在笔记库的 .vault/schema.yml 中:有哪些文件夹及其用途、哪些字段是必填的、哪些状态和类型有效、哪些文件夹必须关联一个 area。写入内容会根据该规范进行验证,不符合规范时将被拒绝。
  • 生命周期管理。 close 会归档已完成的笔记并标记其状态;log_append 会为每个仓库的每个会话写入一条带日期的记录;lint 会报告发现的所有不一致。
  • 适用于 Claude Code 的会话钩子:session-start 会向 Agent 提供它即将操作的仓库对应的系统笔记,以及每条笔记上次更新后产生的提交;如果会话结束时有提交未记录或笔记已过时,stop 会拒绝结束会话。

瑞典语和英语笔记都适用:索引会折叠变音符号,并对笔记库中的所有语言进行词干提取,因此搜索 äppelpressar 会找到 äppelpressen,搜索 pressing 会找到 presses;模式还包含同义词列表,因此搜索 bokning 会找到 booking。

安装

作为 Claude Code 插件
claude plugin marketplace add https://github.com/gronare/claude-plugins
claude plugin install vault@gronare

插件会询问笔记库设置,并将其作为 CLAUDE_PLUGIN_OPTION_* 环境变量传入;该服务会像读取原始变量名一样读取这些变量。

直接从 PyPI 安装为 MCP 服务
claude mcp add vault -s user \
  -e VAULT_PATH=$HOME/vault \
  -- uvx notes-vault-mcp

或者使用 S3 / MinIO:

claude mcp add vault -s user \
  -e S3_ENDPOINT=https://minio.example.com \
  -e S3_ACCESS_KEY=... \
  -e S3_SECRET_KEY=... \
  -e S3_BUCKET=vault \
  -- uvx notes-vault-mcp
作为容器
claude mcp add vault -s user -- \
  docker run --rm -i \
  -e S3_ENDPOINT -e S3_ACCESS_KEY -e S3_SECRET_KEY -e S3_BUCKET \
  ghcr.io/gronare/notes-vault-mcp:latest

配置

每个变量也会从 CLAUDE_PLUGIN_OPTION_<NAME> 读取,这是 Claude Code 插件传递用户配置的方式。如果两者都已设置,则优先使用原始变量名。

变量必填条件含义
VAULT_PATH使用本地笔记库时必填存放笔记库的目录。选择本地后端。
S3_ENDPOINT使用 S3 笔记库时必填端点 URL,例如 https://minio.example.com。
S3_ACCESS_KEY使用 S3 笔记库时必填访问密钥。
S3_SECRET_KEY使用 S3 笔记库时必填秘密密钥。
S3_BUCKET使用 S3 笔记库时必填存放笔记库的存储桶。
S3_PREFIX否存储桶内的键前缀。
S3_REGION否区域,默认为 us-east-1。
VAULT_CACHE_DIR否索引的存放位置,默认为 ~/.cache/notes-vault-mcp。
VAULT_SCHEMA否模式文件的本地路径,用于覆盖笔记库中的模式。
VAULT_TOKEN使用 --auth bearer 时必填用于 --transport http 局域网模式的静态 bearer 令牌。
VAULT_ALLOWED_HOSTS否服务器会响应的额外 Host 请求头值,以空格或逗号分隔。
VAULT_STOP_HOOK否设置为 off 可禁用停止钩子。

设置 VAULT_PATH 或四个 S3_* 变量。两者都未设置时,服务会退出并输出一行说明。

首次运行

uvx notes-vault-mcp init

init 会向笔记库写入内容,且如果未使用 --force,不会覆盖任何已有内容:

  • .vault/schema.yml — frontmatter 规范,从内置默认值复制而来,供你编辑。
  • Areas.base、Open tasks.base、Resources.base、Backlog.base、Log.base — 基于相同结构创建的 Obsidian Bases 视图。

随后,它会将一段 CLAUDE.md 代码片段输出到 stdout:Agent 在对话中需要遵循的工作流规则。

架构

.vault/schema.yml 会与内置默认配置进行深度合并,因此只需包含不同之处。 默认配置定义了五个文件夹:

文件夹类型权重作用
Areas/system3.0每个系统一篇持续更新的笔记。仅记录当前状态。图谱的枢纽。
Resources/reference2.0陷阱、操作指南,以及附有原因说明的决策。
Projects/task1.0跨会话的未完成工作。使用 close 关闭。
Log/log1.0每个代码仓库一篇只追加内容的日志笔记。
Archive/archive0.3历史记录。仅在请求时搜索。

以及笔记的约定:

frontmatter:
  required: [title, date, updated, tags, status]
  optional: [kind, area, summary, path, superseded_by, priority, source, kelpie]
  area_required_in: [Projects, Resources, Log]
  status_values: [draft, active, complete, superseded]
  kind_values: [system, task, trap, howto, decision, reference, log]

每种类型都附有一行规则,说明何时笔记属于该类型;服务会将这些规则写入指令,因此每个客户端选择类型的方式都相同:

kinds:
  trap: "a gotcha that bit once and will bite again; nothing to do, nothing was chosen"
  decision: "a choice and its reasons; something was chosen, alternatives were dropped"
  howto: "steps that are known to work; nothing chosen, nothing open"

知识库可以通过添加条目(research: "reading notes on a topic; no task, no decision")来新增类型,通过重新表述规则来改写规则,并将类型设为 null 来删除类型。即使只有 kind_values 而没有规则,也仍然可用。

path 用于将笔记与代码关联:它是以逗号分隔的目录列表(~ 会按原样保留,同时也会以展开后的形式建立索引)。context 和会话钩子会据此进行匹配。

每个代码仓库对应一篇仓库日志笔记,其文件名和行格式都由架构设置决定:

log:
  folder: Log
  file_format: "{repo}-log.md"
  entry_format: "- [{date}] {line} | commits: {commits} | {area}"

file_format 接受一个 {repo} 占位符;默认后缀用于避免日志与枢纽笔记重名:如果知识库中同时存在 Areas/myapp.md 和 Log/myapp.md,Obsidian 就无法解析 [[myapp]]。所有构建日志路径的地方都会读取此设置——log_append、context 中的日志尾部内容、停止钩子对未记录提交的检查、changelog 和 lint——因此更改此设置会一次性调整所有相关路径。更改设置时,请将现有文件重命名为匹配的名称。

language 是知识库的主要语言,other_languages 则是其余语言,使用 ISO 代码表示(language: sv、other_languages: [en]);搜索会对 Snowball 支持的每种语言进行词干提取,更改这些设置会重建索引。没有匹配项时,回复会告知客户端知识库使用哪些语言编写,以便客户端翻译搜索词并再次搜索;部分匹配结果也会用一行文字说明相同信息。仅通过词干匹配的结果会乘以 stemmed_weight(默认为 0.5,低于正文的 1.0),因此精确形式会排在前面。

还可以配置:标签词汇表及是否强制执行、搜索会扩展的同义词组、stale_after_days,以及搜索权重。

工具

每次调用都会先刷新索引,最多每 20 秒刷新一次。

工具成本功能
search低对索引进行全文搜索。搜索文件名主体、标题、摘要、标签和正文,并支持同义词、前缀、词干提取、带引号的短语和变音符号折叠。查询内容若与某条笔记的文件名主体完全一致(或为 [[stem]]),则会将该笔记置于首位,即使它来自归档。查询中的每个词都必须匹配;若没有笔记包含所有词,则会返回匹配词数最多的笔记,并在标题中说明结果不完整。仅输入提交 SHA 会找到提及该 SHA 的笔记。隐藏归档和已取代的笔记,并说明隐藏的数量。
context低会话开始时调用:一次性返回涵盖某路径的系统笔记、开放任务、stale_after_days 天以上的开放笔记分流列表(含每条笔记的三种处理方式)、待办事项、参考笔记和仓库日志末尾内容。
list_files低仅返回路径。
read_file中读取一条笔记,前缀为 etag: <version>。已取代的笔记会附带警告提示。
obsidian_access写入仅限 --auth forwarded。生成 Obsidian Remotely Save 插件通过 WebDAV 使用的个人令牌,并仅显示一次;新令牌会撤销之前的令牌。
lint中读取每条笔记并报告不一致之处。
init写入初始化一个知识库:在根目录写入 .vault/schema.yml 和 Obsidian Bases 视图,并保留已有内容;force 会覆盖。插件的 /vault:init 会调用此工具。
write_file写入根据架构验证内容,不符合要求时拒绝写入。写入 updated 时间戳,并补上 date。传入 expected_etag 可进行条件写入,传入 "*" 则用于创建必须尚不存在的笔记。在 S3 上,条件会随 PUT 请求一同发送(If-Match、If-None-Match: *);若服务拒绝条件 PUT,则会先执行 HEAD 再执行 PUT,并且只记录一次该情况。只会写入 .md 笔记,且不会写入点号开头的文件夹;架构和 .base 视图由 init 创建,read_file 仍可提供这些文件。
append_file写入追加内容并更新 updated;笔记必须存在且仍符合架构。
set_status写入更改笔记状态而不重写笔记:设为 backlog 并指定优先级会将其搁置,设为 active 则会重新启用。
close写入将状态设为已完成(或在提供 merged_into 时,设为已取代并填写 superseded_by),并将笔记移入归档。
log_append写入在仓库日志中追加一条带日期的记录,并注明由此产生的提交。日志不存在时会创建日志。
move_file写入移动或重命名笔记;笔记在目标位置必须符合架构。
delete_file写入永久删除。优先使用 close。

search 过滤条件:folder、status、tag、kind、area、path_prefix、since、 include_archive、include_superseded、limit。

lint 报告内容

broken_frontmatter、missing_required(按字段分别报告)、missing_area、unknown_tags(仅在词汇表严格时)、unresolved_links、orphans(没有入链 wikilink;忽略日志和归档)、stale_active、archive_status_mismatch、duplicate_stems、superseded_target_missing。使用 --park-stale 时,超过 stale_after_days 天未更新的开放任务笔记会在生成报告前被设为 backlog(优先级 low,来源 veckolint <date>),并列在 parked 下,因此 active 始终表示在该时间窗口内有过更新;如需重新启用,使用 set_status(path, "active")。若笔记包含一条声明(kelpie 键),且该声明是在最近 14 天内创建或刷新的,则会保留其当前状态;时间从 since 和笔记 updated 两者中较晚的日期起算。

uvx notes-vault-mcp lint
uvx notes-vault-mcp lint --write "Log/lint-$(date +%F).md"
uvx notes-vault-mcp lint --write Log/lint.md --park-stale

Hooks

两个 Claude Code hook 都会从 stdin 读取 hook JSON,并且无论发生什么都会以 0 退出。

session-start 会打印工作目录的上下文包,然后针对它返回的每条系统备注,列出自该备注上次更新以来触及其 path 的提交。在 Agent 采信备注之前,这能回答“这条备注还准确吗?”。

stop 会阻止未记录工作的会话结束:列出过去 24 小时内的提交中,其 sha 未出现在仓库日志里的提交;以及会话在其记录中通过 vault 工具调用读取或写入的、超过架构 stale_after_days 天数仍未关闭的任务备注。它会返回 {"decision": "block", "reason": ...};如果 vault 已保持最新,则不返回任何内容。会话未触及的过期备注留给下一次会话处理:context 会在 triage 下列出这些备注,以及逐条处理它们的三种方式(关闭、添加带日期的行,或搁置到待办事项列表),而不是每次停止时都处理。将 VAULT_STOP_HOOK=off 设为静默该 hook;例如,在会话设置的 env 中,运行 Agent 会话并自行写入仓库日志的工具就可以这样做。

{
  "hooks": {
    "SessionStart": [
      { "hooks": [{ "type": "command", "command": "uvx notes-vault-mcp hook session-start" }] }
    ],
    "Stop": [
      { "hooks": [{ "type": "command", "command": "uvx notes-vault-mcp hook stop" }] }
    ]
  }
}

其他命令

notes-vault-mcp serve --transport stdio          # the default
notes-vault-mcp sync --rebuild                   # drop the index and read every note again
notes-vault-mcp search "bokning" --limit 5       # the same ranking, from a shell
notes-vault-mcp changelog myapp 2026-08 --repo-path ~/projects/myapp
notes-vault-mcp changelog myapp 2026-08 --repo-path ~/projects/myapp --write
notes-vault-mcp changelog --all                  # this month (and last month during its first week)

changelog 会打印日志行、按日期分组的 git 提交,以及日期处于该时间段内的仓库备注。使用 --write 时,它会将这些内容保存为时间段页面 Log/<repo>-<period>.md,放在标记 和 之间;标记上方的文字(由 Agent 在月末撰写的摘要)会保持不变,页面的状态则随日历变化。--all 会对本机上 session-start hook 见过的每个仓库执行此操作;stop hook 每天会运行一次,因此无需 cron,页面也能保持最新。

待办事项列表

notes-vault-mcp backlog --area myapp --priority high

待办事项是状态为 status: backlog 的任务备注,包含 area、单行 summary、可选的 priority(urgent、high、medium、low)和可选的 source(说明是谁在何时提出,或提供 sha)。backlog_add 会在对话中有事项被推迟时立即为其创建一条记录;backlog 会按优先级再按时间列出这些事项,并可按 area 或 family 筛选;除了未完成任务外,context 还会显示与当前仓库相关的待办事项。要着手处理某个事项,只需将其状态设为 active;完成后则执行 close。无论待办事项备注有多旧,Lint 都不会处理它们;但如果某条 complete 备注从未关闭,则会发出提示。init 会在其他 Obsidian bases 旁写入 Backlog.base。

任务和日志命令

适用于驱动编码 Agent 并在固定节点(开始、推送、完成、重置)自行记录事实,而不依赖 Agent 调用 MCP 工具的工具。每条命令都会在 stdout 上打印一个 JSON 对象并以 0 退出;失败时以非零状态退出,并在 stderr 上打印一行错误信息。所有写入都通过与 MCP 工具相同的路径:架构验证、updated 时间戳和索引。

notes-vault-mcp task open --name "Fix login" --cwd ~/wt/wt1.myapp [--branch fix-login]
# {"path": "Projects/myapp-fix-login.md", "created": true, "repo": "myapp", "area": "myapp"}
notes-vault-mcp task open --note Projects/myapp-idea.md --cwd ~/wt/wt1.myapp
# {"path": "Projects/myapp-idea.md", "created": false, "repo": "myapp", "area": "myapp"}
notes-vault-mcp task check Projects/myapp-fix-login.md --need problem,solution,decisions
# {"missing": ["solution", "decisions"]}
notes-vault-mcp task close Projects/myapp-fix-login.md        # {"path": "Archive/myapp-fix-login.md"}
notes-vault-mcp task backlog Projects/myapp-fix-login.md --priority medium   # {"path": "Projects/…"}
notes-vault-mcp task claim Projects/myapp-idea.md --machine mac --task fix-login --repo myapp [--force]
# {"path": "Projects/myapp-idea.md", "claimed": true, "claim": {"machine": "mac", "task": "fix-login", "repo": "myapp", "since": "2026-10-23T09:12:00Z"}, "replaced": null}
notes-vault-mcp task release Projects/myapp-idea.md [--machine mac]
# {"path": "Projects/myapp-idea.md", "released": true, "claim": null}
notes-vault-mcp task release --machine mac --task fix-login
# {"paths": ["Projects/myapp-renamed.md"], "released": true}
notes-vault-mcp log append --cwd ~/wt/wt1.myapp --line "Login fixed" --commit abc1234 --commit def5678
# {"path": "Log/myapp-log.md"}
notes-vault-mcp log path --repo myapp                         # {"path": "Log/myapp-log.md"}
notes-vault-mcp status
# {"configured": true, "version": "0.5.1", "backend": "s3", "writable": null}
notes-vault-mcp status --probe
# {"configured": true, "version": "0.5.1", "backend": "s3", "writable": true}

仓库名取自主检出目录的名称,即使从 worktree 执行也是如此;区域则是会话开始钩子为该目录加载的系统备注,如果没有匹配的备注,则回退为仓库名(输出中无匹配备注时 "area": null)。task open 会写入一条任务备注,以任务名作为占位标题,状态为 active,path 指向主检出目录,source: branch <name>,各节标题下的内容为空;文件名主干由仓库名和任务名组成,并确保在整个知识库中唯一。--note 则认领一条现有任务备注:状态设为 active,其他内容不变。--branch 默认为 --cwd 中检出的分支。

task claim 会在任务备注的 kelpie frontmatter 键中记录哪台机器正在处理哪个任务(machine、task、repo、UTC 时间的 since),这样同一知识库中的第二台机器就能看到该备注已被占用。它只修改该键,并基于读取到的版本执行条件写入;如果期间备注发生变化,则会重新读取。若备注由另一台机器持有,则保持原样,并返回 "claimed": false 以及该机器的认领信息;--force 会接管,并将旧认领信息作为 replaced 返回。同一台机器再次认领同一任务时,会保留 since。task release 会移除此键;指定 --machine 时,如果认领属于另一台机器,就会保留该认领并返回 "released": false。如果未提供路径,--machine 和 --task 会在该任务当前所在位置的所有备注中清除其认领信息,即使备注已移动或重命名;"paths": [] 表示备注已不存在,其认领信息也随之消失。close 和将状态改为 backlog 也会移除认领信息;write_file 则会保留已存储的认领信息,不受文件内容影响,因此只有这些命令会更改认领信息。请在架构的 optional 中列出 kelpie,并在服务端说明中告知客户端不得编辑此键。

task check 使用规范键 problem、solution、decisions、tests 和 scope。如果某个节的标题下、下一个同级或更高级标题前有任何文本,就视为该节已填写。标题由架构按语言设置,并根据知识库的 language 选择:

task:
  sections:
    en: { problem: Problem, solution: Solution, decisions: Decisions, tests: Test decisions, scope: Out of scope }
    sv: { problem: Problem, solution: Lösning, decisions: Beslut, tests: Testbeslut, scope: Utanför ramen }

status 不会失败:未配置知识库时,它会打印 "configured": false, "backend": null 并以 0 退出。此命令只读,并将 writable 留为 null;--probe 会通过写入并删除 .vault/write-probe 来检测可写性。

这些命令会优先从环境变量读取存储设置。如果环境中未指定知识库,则会改用 Claude Code 插件保存的用户配置:$CLAUDE_CONFIG_DIR/settings.json(默认为 ~/.claude/settings.json)中的 pluginConfigs["vault@<marketplace>"].options,并将 vault_path、s3_endpoint、s3_bucket、s3_access_key、s3_secret_key、s3_prefix 和 s3_region 映射到对应变量。Claude Code 保存在该文件之外的值必须通过环境变量提供。

HTTP 传输

VAULT_TOKEN=$(openssl rand -hex 32) notes-vault-mcp serve --transport http --host 0.0.0.0 --port 8765

在 /mcp 上使用 Streamable HTTP,支持四种身份验证方式:--auth bearer(默认)、--auth oidc、--auth builtin 和 --auth forwarded。

bearer 是局域网模式。每个请求都必须携带 Authorization: Bearer $VAULT_TOKEN;否则会在到达服务之前收到 401。此模式下必须设置 VAULT_TOKEN,否则命令将拒绝启动。这是一个拥有完整访问权限的单一令牌,claude.ai 无法使用它。

Host 标头的处理取决于服务的监听地址。绑定到 localhost 时,只响应 localhost、127.0.0.1 和 [::1],其他请求都会收到 421;绑定到其他地址时,则响应所有 Host。如果要在保留此检查的同时使用自定义域名、Kubernetes Service 或局域网主机名,请将其列入 VAULT_ALLOWED_HOSTS,或设置 VAULT_PUBLIC_URL:这样会响应该名称和 localhost,其他请求则会收到 421。

oidc 和 builtin 使用 OAuth,这是 Claude 连接器所需的方式。请参阅远程访问:claude.ai、Claude Desktop 和移动端。

forwarded 适用于位于已完成 OAuth 验证的代理之后的服务:它信任经过签名的身份标头,并为每个 subject 提供一个保险库。请参阅代理之后:每人一个保险库。

远程访问:claude.ai、Claude Desktop 和移动端

使用 --auth builtin 或 --auth oidc 通过 HTTPS 运行服务,claude.ai 就可以将其添加为自定义连接器。在网页上连接一次后,同一连接器也会出现在 Claude Desktop 和移动应用中。

一个服务实例提供一个保险库。

前提条件
  • 一个保险库:具有专用密钥的兼容 S3 存储桶,或主机上的文件夹。
  • Docker,或主机上的 uv。
  • 一个指向该机器的域名,且服务前方配置了 TLS。deploy/ 下的示例使用 Caddy,它会自动获取证书。
  • 对于 --auth oidc:一个已在运行的 OpenID Connect 提供商。
两种模式
--auth builtin--auth oidc
登录者一位所有者,使用此服务保存的密码登录提供商允许的任何人
客户端注册动态注册,无需在 claude.ai 中配置提供商支持时动态注册,否则需将客户端 ID 和密钥粘贴到 claude.ai
只读客户端登录页面上的两个复选框群组成员资格
磁盘上的状态VAULT_AUTH_DIR 下的 auth.sqlite无
环境
变量模式含义
VAULT_PUBLIC_URLoidc, builtin必填。客户端访问的 https 地址;如果服务不在根路径,还需包含路径。无默认值:未设置时服务会拒绝启动,并且拒绝任何不是 https:// 的地址(本地试用时可使用 http://localhost)。
VAULT_AUTH_DIRbuiltinauth.sqlite 的存放位置——其中包含所有者的密码哈希、已注册的客户端和经过哈希处理的令牌。默认值为 ~/.cache/notes-vault-mcp/auth。
VAULT_OIDC_ISSUERoidc必填。提供方的颁发者 URL。
VAULT_OIDC_AUDIENCEoidc提供方在 aud 中填写的客户端 ID。必填;缺少此值或面向其他受众的令牌都会被拒绝。
VAULT_OIDC_READ_GROUPoidc授予 vault:read 权限的组。默认值为 vault。
VAULT_OIDC_WRITE_GROUPoidc授予 vault:read 和 vault:write 权限的组。默认值为 vault-writers。
VAULT_OIDC_SCOPESoidc资源元数据公布的范围,以便客户端向提供方请求其支持的范围。默认值为 openid profile email groups。如果访问令牌不包含 groups,服务会请求提供方的 userinfo 端点。
VAULT_TOKENbearer静态令牌。
VAULT_ALLOWED_HOSTSall服务额外响应的 Host 标头值,以空格或逗号分隔,可填写 Service 名称或与公网主机名并列的局域网主机名。
VAULT_CACHE_DIRall索引的存放位置。默认值为 ~/.cache/notes-vault-mcp。
S3_ENDPOINT、S3_BUCKET、S3_ACCESS_KEY、S3_SECRET_KEYall保管库配置,除非 VAULT_PATH 指定了本地文件夹。

MCP 端点为 VAULT_PUBLIC_URL + /mcp。将此 URL 粘贴到 claude.ai 即可。

权限范围
  • vault:read — search、read_file、list_files、context、lint、backlog。
  • vault:write — write_file、append_file、move_file、delete_file、close、log_append、 backlog_add。

使用只读令牌调用写入工具时,会返回错误,说明该令牌只能读取保管库,而不会只写入一部分或静默地什么也不做。

从零开始使用内置登录
cd deploy/caddy-builtin
cp .env.example .env
$EDITOR .env
docker compose up -d
docker compose exec vault notes-vault-mcp owner set-password

然后,在 claude.ai 中依次选择:Settings → Connectors → Add custom connector → 输入 /mcp URL,例如 https://vault.example.com/mcp。无需其他操作:服务会自行注册客户端。 浏览器会打开服务的登录页面,要求输入所有者密码,并显示两个复选框:读取和写入。取消勾选“写入”,即可创建只读连接器。

查看已连接的客户端,以及如何断开连接:

docker compose exec vault notes-vault-mcp tokens list
docker compose exec vault notes-vault-mcp tokens revoke <client_id>

密码哈希和令牌存放在 vault-data 卷中的 auth.sqlite 文件里。请备份该卷;如果丢失,每个客户端都必须重新连接。

从零开始使用 OIDC
cd deploy/caddy-oidc
cp .env.example .env
$EDITOR .env
docker compose up -d

在此配置中,服务是资源服务器:它会验证提供方的访问令牌,且永远不会接触密码。写入组中的用户拥有 vault:read 和 vault:write 权限,读取组中的用户仅拥有 vault:read 权限,两者皆不属于的用户会被拒绝。

以相同方式在 claude.ai 中添加连接器。如果提供方支持动态客户端注册,配置到此就完成了。如果不支持(Pocket ID、Authentik),请先在提供方注册客户端,然后在连接器的 Advanced 设置中粘贴客户端 ID 和密钥;在提供方中允许的重定向 URI,应为 claude.ai 在该对话框中显示的 URI。

Pocket ID

claude.ai 会将 MCP 资源(/mcp URL)作为 OAuth resource 参数发送,而 Pocket ID 只接受已知为 API 的资源。因此:

  1. Settings → APIs → Add API:设置名称和资源标识符,准确填写 VAULT_PUBLIC_URL + /mcp。添加两个权限:键为 vault:read 和 vault:write,名称填写家人将在授权页面上看到的内容。
  2. Settings → User Groups:创建 vault(读取)和 vault-writers(写入),并将用户添加到相应组中。
  3. Settings → OIDC Clients → Add:名称为 claude.ai,回调 URL 为 https://claude.ai/api/mcp/auth_callback,启用 PKCE,关闭 public client,允许的用户组设为 vault。在 API access 下,授予该 API 的两项权限(用户委托)。复制客户端 ID 和密钥。
  4. 在服务器上:VAULT_OIDC_ISSUER 是 Pocket ID 的基础 URL,VAULT_OIDC_AUDIENCE 是第 1 步中的资源标识符(Pocket ID 会将其放入 aud),VAULT_OIDC_SCOPES 为 openid profile email groups vault:read vault:write。
  5. 在 claude.ai 中,在 Advanced settings 下使用客户端 ID 和密钥添加连接器。

用户所属的组决定令牌可以执行哪些操作;只有在提供方完全没有报告组信息时(无论是在令牌中还是通过 userinfo),客户端权限才会起作用。

Kubernetes

deploy/k8s/ 中包含原始清单:一个将 PVC 挂载到 /data 的 Deployment、一个 Service、一个 Ingress 和一个 Secret 示例。

cp deploy/k8s/secret.example.yaml secret.yaml
$EDITOR secret.yaml
$EDITOR deploy/k8s/ingress.yaml
kubectl apply -f secret.yaml -f deploy/k8s/deployment.yaml -f deploy/k8s/service.yaml -f deploy/k8s/ingress.yaml

Deployment 使用 --auth builtin;如需使用 OIDC,请更改 --auth 参数,并在 Secret 中填写 VAULT_OIDC_* 键。就绪探针会访问 /.well-known/oauth-protected-resource/mcp,这是两种模式下唯一一个无需令牌即可响应的路由;如果 VAULT_PUBLIC_URL 包含路径,探针 URL 中也要包含该路径。Pod 启动后设置所有者密码:

kubectl exec -it deploy/notes-vault-mcp -- notes-vault-mcp owner set-password

填写 ingressClassName,并将 secretName 指向 TLS 证书——可以是现有密钥,也可以是 cert-manager 签发的证书。

通过代理:每人一个保险库

--auth forwarded 适用于这样的组织:组织已在 MCP 服务前运行 OAuth 授权服务器,并在标头中将调用者身份作为签名令牌转发(Cloudflare Access 使用的模式是 Cf-Access-Jwt-Assertion)。服务会用代理的公钥验证该令牌,从中获取主体,并提供该主体自己的保险库:在同一个存储桶中为每个人设置一个前缀(或在 VAULT_PATH 下为每个人设置一个文件夹),每个保险库都有自己的索引、自己的 .vault/schema.yml 和一条欢迎说明,这些内容都会在首次请求时创建。无需手动配置任何内容;在代理处授予访问权限即可完成整个入门流程。

notes-vault-mcp serve --transport http --host 0.0.0.0 --port 8765 --auth forwarded

与代理的约定:没有有效身份的请求会收到 403,且不带 WWW-Authenticate 挑战(在这里公布授权服务器会让客户端绕过代理);传输是无状态的,使用普通 JSON 响应,因此缓冲代理无需 Mcp-Session-Id;不会检查 Host 标头;/up 无需令牌即可响应 ok,供就绪探针使用。此模式下不会提供 OAuth 元数据路由;这些路由由代理负责。

变量含义
VAULT_PUBLIC_URL必填。客户端访问服务的地址;其主机名是令牌必须携带的 aud,其路径(如有)会作为所有路由的前缀。
VAULT_IDENTITY_PUBLIC_KEY必填。代理的 PEM 公钥(RS256 或 ES256),可以是原始格式,也可以是单行 base64。
VAULT_IDENTITY_ISSUER必填。代理令牌中的 iss。
VAULT_IDENTITY_HEADER携带令牌的标头。默认为 X-Forwarded-Identity。
VAULT_IDENTITY_TYP设置后,令牌的 typ 必须匹配。
VAULT_IDENTITY_WRITE_CLAIMS授予 vault:read 和 vault:write 权限的授权名称(位于令牌的 entitlements 列表或 scope 中)。默认为 vault:write。
VAULT_IDENTITY_READ_CLAIMS仅授予 vault:read 权限的授权名称。默认为 vault:read。不具备这两类授权的令牌会收到 403 insufficient_scope。
VAULT_SUBJECT_PREFIX保险库在存储桶或文件夹中的存放位置:<prefix>/<sub>/。默认为 users。
VAULT_AUTH_DIRpersonal.sqlite 存放个人令牌哈希值的位置。
VAULT_CACHE_DIR每个主体的 SQLite 索引都存放在此处;请为其配置一个卷。

令牌必须包含 iss、aud、sub 和 exp;若存在 email 和 name,则会使用它们。不是普通标识符的主体会先经过哈希处理,再用于命名文件夹。

通过同一服务使用 Obsidian

每个保险库也会通过 VAULT_PUBLIC_URL/dav/ 提供 WebDAV 服务,供无法使用 OAuth 的 Obsidian Remotely Save 插件使用。用户让 Claude 运行 obsidian_access;服务会生成一个个人令牌,只返回一次,并且只存储该令牌的哈希值。Remotely Save 使用 /dav/ 地址、任意用户名(通常使用邮箱)以及作为密码的令牌。再次运行 obsidian_access 会轮换令牌。代理必须直接转发 /dav/*,并完整保留客户端的 Authorization 标头以及 WebDAV 方法和标头(PROPFIND、MKCOL、MOVE、COPY、Depth、Destination、Overwrite)。首次请求时写入的欢迎说明会包含这些操作指引。

开发

uv sync
uv run pytest
uv run ruff check .

测试套件使用 tests/fixtures/vault/ 下的样例保险库和由 moto 模拟的 S3 存储桶运行,不会访问真实存储桶。

许可证

MIT。请参阅 LICENSE。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。