关于 enpass-mcp
enpass-mcp 是本地 MCP 服务,让 AI 助手访问 Enpass 条目,同时将保险库文件和主密码保留在本机。默认只读,只有明确启用写入后才能创建或删除条目。
主要功能
- 01使用操作系统钥匙串中的密码解锁保险库
- 02列出保险库并搜索条目,不显示密码
- 03按需读取条目字段、密码和 TOTP 代码
- 04列出并解密文件附件
- 05检查 Enpass 文件夹同步状态
- 06默认禁用写入工具
适用场景
- 01按查询查找登录信息或其他 Enpass 条目
- 02按需获取密码和当前双重验证代码
- 03将保险库附件导出到磁盘
- 04检查同步状态或选择性启用保险库修改
MCP 相关
该服务通过 stdio 与 MCP 客户端通信,提供保险库、条目、机密信息、附件和同步状态相关工具。解锁工具从操作系统钥匙串读取主密码,不接受密码参数;写入工具须明确启用。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
enpass-mcp
一个 模型上下文协议(MCP)服务,让 AI 助手能够受控地在本地访问你的 Enpass 密码库:解锁密码库、列出密码库,以及列出和读取条目。也可以创建和删除条目,但默认关闭,需手动启用。
通过 stdio 在本地运行。你的 Enpass 密码库不会离开你的设备,并且你的主密码不会经过模型:它存储在操作系统的密钥链中,由服务直接读取。
为什么安全
- 主密码存储在操作系统的密钥链中(macOS 钥匙串、Windows 凭据管理器、Linux Secret Service),不会存入配置文件或环境变量,也不会作为工具参数传递。
unlock_vault工具刻意不接受密码参数,因此密码绝不会进入模型的上下文或日志。 - 密码库保留在本地。 服务通过 SQLCipher 直接读取加密的
vault.enpassdb文件。不会上传任何内容。 - 读取操作需要明确请求。 列出条目时不会返回密码。只有在你明确索取时,
get_item/get_password才会返回机密信息。 - 除非你另行启用,否则只读。 开箱即用时,服务无法更改任何内容:写入工具甚至不会公布。设置
ENPASS_MCP_ALLOW_WRITES=1即可启用这些工具(参见启用写入)。
按设计,当你索取条目密码时,密码会返回给助手,因此只应将此服务连接到你信任的助手和密码库。
要求
- Node.js 18 或更高版本
- Enpass 6 / 7 / 8 密码库(
vault.enpassdb,SQLCipher 格式) - Linux:用于存储密码的 Secret Service 提供程序(GNOME Keyring 或 KWallet)
原生依赖项(better-sqlite3-multiple-ciphers、@napi-rs/keyring)提供常见平台的预编译二进制文件,因此通常无需编译器。
安装
git clone https://github.com/bitterdev/enpass-mcp.git
cd enpass-mcp
npm install
npm link # optional: makes the `enpass-mcp` command available globally
注册密码库(在终端中执行一次)
这是确保主密码不会被模型获取的安全步骤。请自行运行此命令;密码需要输入到隐藏提示中,并存储在操作系统的密钥链中。
# Find your vault files automatically
enpass-mcp discover
# Register a vault (you will be prompted for the master password)
enpass-mcp add-vault personal --path "/Users/you/Documents/Enpass/Vaults/primary/vault.enpassdb"
enpass-mcp add-vault work --path "/path/to/work/vault.enpassdb"
# With a keyfile
enpass-mcp add-vault personal --path "/path/vault.enpassdb" --keyfile "/path/vault.keyfile"
# Manage
enpass-mcp list-vaults
enpass-mcp test-unlock personal
enpass-mcp remove-vault work
add-vault 会验证密码是否确实能够解锁密码库,然后才保存。
密码库文件通常位于:
| 操作系统 | 常见位置 |
|---|---|
| macOS | ~/Documents/Enpass/Vaults/<vault>/vault.enpassdb |
| Windows | %USERPROFILE%\Documents\Enpass\Vaults\<vault>\vault.enpassdb |
| Linux | ~/Documents/Enpass/Vaults/<vault>/vault.enpassdb |
如果你通过 Dropbox / OneDrive / WebDAV 同步,请将 --path 指向同步后的副本。
将其连接到你的助手
此服务通过 stdio 使用 MCP。将 MCP 客户端指向 enpass-mcp serve。
Claude Desktop(claude_desktop_config.json):
{
"mcpServers": {
"enpass": {
"command": "enpass-mcp",
"args": ["serve"]
}
}
}
如果没有运行 npm link,请改用绝对路径:
{
"mcpServers": {
"enpass": {
"command": "node",
"args": ["/absolute/path/to/enpass-mcp/src/cli.js", "serve"]
}
}
}
Claude Code:
claude mcp add enpass -- enpass-mcp serve
工具
| 工具 | 描述 |
|---|---|
list_vaults | 列出已注册的保险库、对应文件是否存在、是否已存储密码,以及是否已解锁。 |
unlock_vault | 使用 OS 密钥链中的主密码解锁保险库。只接受保险库名称,不接受密码。 |
lock_vault | 锁定保险库,并从内存中清除其派生密钥。 |
list_items | 列出条目(标题、用户名、URL)。绝不返回密码。支持 query、category、folder、limit。 |
get_item | 返回完整条目,包括所有字段值(密码、TOTP 等)及其附件列表。 |
get_password | 返回条目的密码;如果有当前 TOTP 验证码,也一并返回。 |
get_otp | 为条目生成当前 TOTP / 2FA 一次性验证码,并返回该验证码距离轮换还剩的秒数。 |
list_attachments | 列出条目的文件附件(名称、大小、MIME)。 |
export_attachment | 解密附件,将其写入磁盘并返回路径(小文件则以内嵌 base64 形式返回)。 |
sync_status | 列出使用 Enpass 文件夹同步的保险库,以及同步文件夹中的副本是否更新。 |
启用 ENPASS_MCP_ALLOW_WRITES=1 后,会多出三个工具(参见写入(选择启用)):
| 工具 | 描述 |
|---|---|
create_item | 创建条目,包括自定义字段;敏感值会以 Enpass 的方式加密。 |
delete_item | 删除条目或将其移入回收站,并保留 Enpass 用于同步删除操作的墓碑记录。 |
sync_pull | 先备份本地保险库,再从同步文件夹中导入更新的副本。 |
list_items / get_item 适用于 所有 Enpass 条目类型(登录信息、信用卡、安全笔记、身份信息等),而不仅仅是登录信息,并会返回所有字段。
典型的助手工作流:list_vaults → unlock_vault → list_items → get_password。
工作原理
Enpass 将每个保险库存储为标准的 SQLCipher 数据库(vault.enpassdb)。原始加密密钥由主密码(可选与密钥文件组合)和文件开头的 16 字节盐值派生而来:
- PBKDF2-HMAC-SHA512,迭代 100000 次(较旧的保险库)或 320000 次(较新的保险库),取前 32 字节作为 SQLCipher 原始密钥
- 使用
cipher_compatibility4(Enpass 6.8+)或 3(较旧的保险库)打开
服务会自动尝试这些组合,因此适用于不同版本的 Enpass 保险库。派生密钥仅在服务进程运行期间保存在内存中,绝不会写入磁盘或返回给模型。
参考资料:Enpass 安全白皮书, hazcod/enpass-cli。
单个条目的字段加密
Enpass 会在 SQLCipher 加密层之下,使用属于条目而非保险库的密钥,对每个标记为“敏感”的值再加密一次。包含这一层加密的字段,其 itemfield.algo_version = 1:
| 部分 | 位置 | 布局 |
|---|---|---|
| 密钥和 nonce | item.key | 44 字节:32 字节 AES-256 密钥,后接 12 字节 GCM nonce |
| 值 | itemfield.value | ciphertext || 16-byte GCM tag 的十六进制形式 |
| 附加数据 | 条目的 uuid | 去掉连字符,再将十六进制解码为 16 字节原始数据 |
将 AAD 绑定到条目 uuid 后,复制到其他条目中的值便无法使用。Enpass 引入这一层加密时没有重新加密已有条目,因此同一个保险库中的 algo_version 相同字段可能同时包含密文和明文;只有当值符合载荷格式(纯十六进制、长度为整字节且长于标签本身)时,才会将其视为加密值。
如果某个看起来已加密的值未通过身份验证,则会返回 null 并附带 decryptionFailed: true,绝不会返回原始列内容:存储的密文看起来可能像正常字符串,直接返回会让错误的密钥信息被误认为真实值。
双因素验证码(TOTP)
包含一次性密码密钥(Enpass 将其存储为 otpauth:// URI)的条目可以生成实时 2FA 验证码:get_otp 会返回当前的 6 位验证码以及距离验证码轮换的秒数,get_password 则会在密码旁一并返回当前验证码。这让助手可以同时填写密码和 2FA 提示。
附件
Enpass 会加密文件附件。小文件(不超过 1 KB)以内联方式存储在保险库中;较大的文件则存储在保险库旁边的独立 <uuid>.enpassattach SQLCipher 文件中,每个文件都使用单独的密钥加密,该密钥存储在保险库中。export_attachment 可以处理这两种情况:它会解密文件,并默认将文件写入磁盘后返回路径,因此无论文件大小如何,都无需通过模型传输二进制数据。
这种格式有两个容易忽略的细节,在根据真实保险库进行验证之前,这两处的处理都不正确:
attachment.password不是密钥数据块。它以文本形式存储完整的 SQLCipher 原始密钥表达式,即x'<64 hex key chars><32 hex salt chars>',必须原样传入。对其进行十六进制编码会导致 SQLCipher 拒绝该密钥,并使每个外部附件都报错 "file is not a database"。- 存储的载荷通常是 base64 数据 URI(
data:application/pdf;base64,...),而不是文件本身,因此必须先解码。将这些字节直接写入磁盘,会得到一个扩展名为.pdf的文本文件。attachment.size统计的是存储的字节数,因此对于以这种方式封装的文件,该值会大于导出的文件大小。
如果你遇到附件仍无法解密的保险库,请提交 issue,并附上 attachment 表的(非机密)架构。
附件也不会包含在文件夹同步容器中:Enpass 会将 .enpassattach 文件放入其旁边的同步文件夹。sync_pull 会获取本机缺少的文件;发布写入内容时,也会将同步文件夹中缺少的本地文件复制过去。任一方向都不会覆盖或移除已有文件。
写入(选择启用)
写入默认处于关闭状态。密码库是最不应该让工具仅仅因为模型决定这样做就能更改数据的地方,因此服务启动时为只读模式,在你启用之前,甚至不会列出 create_item、delete_item、add_attachment 和 sync_pull:
ENPASS_MCP_ALLOW_WRITES=1
在服务的环境中设置此项(在 MCP 客户端配置中,或在 vaults.json 旁边的 .env 文件中)。其他一切都不变:无论是否启用,读取的工作方式完全相同。
写入时必须关闭 Enpass。 应用会将数据库保存在内存中,并会把自己的缓存副本写回,覆盖在它运行期间进行的任何更改。Enpass 处于打开状态时,所有写入工具都会拒绝运行。
本 README 的早期版本声称无法写入,因为近期版本的密码库(架构版本 6)会在直接插入条目时导致 Enpass 崩溃。崩溃确实存在,但原因判断错了。三条具体规则能让写入正常工作,它们都源于 Enpass 自身写入的数据:
- Enpass 从不存储
NULL。 崩溃是在空指针上调用strlen时发生的EXC_BAD_ACCESS:INSERT中省略的列默认值为NULL,而应用会对其调用strlen。必须显式写入每一列,用空字符串代替NULL。 - 模板的字段集是固定的。 即使大多数字段为空,
login.default始终包含相同的九个字段,顺序相同,字段 uid 也相同。只写入恰好有值的字段,会生成应用无法呈现的条目。 - 每个项目的密钥可重现。
item.key是一个 32 字节的 AES-256 密钥加上一个 12 字节的 GCM nonce,以hex(ciphertext || tag)的形式存储,并将项目 UUID 用作附加认证数据。其中没有任何内容依赖 Enpass 内部机制,因此为每个项目生成新的随机密钥即可。
已使用真实密码库完成端到端验证:写入、读回、解密为原始内容、删除、双向同步,且 Enpass 打开密码库时不会崩溃。
附加文件
add_attachment 接受一个路径,参数列表中绝不能是 base64,并会将原始字节存储在附件自己的加密数据库中。根据与读取相同的格式细节,可以得出两条规则;任何一条出错,都会看起来像文件已损坏,尽管密码库中的每个字节都完好无损:
- 存储原始字节,而不是 data URI。Enpass 应用会原样提供
data:;base64,...形式的载荷,因此导出结果是一个带有.pdf或.jpeg名称的文本文件。add_attachment会拒绝此类输入,而不会存储它。 - 原样传递密钥表达式。它已经是 ASCII 格式的 SQLCipher 原始密钥表达式;再次对其进行十六进制编码会导致“file is not a database”。
数据行会根据密码库的 attachment 表实际包含的列构建,因为不同代密码库的列集有所不同;并且每一列都会获得显式值,确保 Enpass 不会收到 NULL。
回归保护:npm test 会写入包含 0x00 和无效 UTF-8 序列的载荷,将其读回,并断言字节完全相同且以未编码形式存储。
修复以 data URI 形式存储的附件
scripts/repair-data-uri-attachments.mjs 会将此类载荷解码为原始字节。除非提供 --apply,否则只会进行试运行;如果 Enpass 处于打开状态,则拒绝运行;写入前还会检查解码后的载荷是否与文件名所暗示的文件签名相符。
外部附件的字节存储在其自己的 .enpassattach 文件中,而文件夹同步会在同步文件夹中为每个此类文件保留第二份副本。因此,在 Enpass 容器下修复密码库只会修复一台机器。--sync-folders 也会修复共享副本,并且只会写入逐字节解码后与本地机器已有内容相同的载荷。
配置
主密码存储在操作系统的钥匙串中;只有非机密数据(保管库名称和路径)
存储在一个小型的 vaults.json 文件中:
- macOS:
~/Library/Application Support/enpass-mcp/vaults.json - Windows:
%APPDATA%\enpass-mcp\vaults.json - Linux:
~/.config/enpass-mcp/vaults.json
使用 ENPASS_MCP_CONFIG_DIR 覆盖目录。
环境变量:
| 变量 | 作用 |
|---|---|
ENPASS_MCP_ALLOW_WRITES | 设置为 1、true、yes 或 on 可启用写入工具。其他任何值(包括未设置)都会使服务保持只读。 |
ENPASS_MCP_CONFIG_DIR | vaults.json 和可选的 .env 所在目录。 |
ENPASS_MASTER_PASSWORD | 可选的备用主密码,用于没有钥匙串条目的保管库。优先使用钥匙串。 |
ENPASS_MASTER_PASSWORD_<VAULT> | 同上,用于指定的某个保管库。 |
开发
npm test # runs against genuine SQLCipher fixtures in test/fixtures
# Rebuild the fixtures from scratch with real SQLCipher (vault + entries + attachments)
npm install --no-save @journeyapps/sqlcipher
npm run generate-fixtures
CI(GitHub Actions)会从头创建保管库,使用真实的 SQLCipher,填充条目和附件,然后在 Node 18/20/22 上运行完整的只读测试套件。
安全说明和限制
- 任何能够与此 MCP 服务通信的人,都能在保管库解锁后读取其中的所有密码。请仅连接可信的客户端。
- 此服务不实现 Enpass 同步、项目历史记录或移入废纸篓功能。
- 此服务为只读,不会修改保管库。出于安全考虑,不支持创建或编辑条目,因为直接写入数据库会导致近期版本的 Enpass 保管库崩溃(参见为何不支持写入)。
- 这是一个独立项目,与 Enpass 无关,也未得到 Enpass 的认可。
许可证
MIT © Fabian Bitter
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。