{
  "markdown": "# enpass-mcp\n\nA [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that gives\nan AI assistant controlled, local access to your [Enpass](https://www.enpass.io)\npassword vaults: unlock a vault, list vaults, and list and read entries. Creating and\ndeleting entries is possible too, but switched off until you enable it.\n\nRuns locally over stdio. Your Enpass vault never leaves your machine, and your\n**master password never passes through the model**: it is stored in your operating\nsystem's keychain and read directly by the server.\n\n## Why this is safe\n\n- **Master passwords live in the OS keychain** (macOS Keychain, Windows Credential\n  Manager, Linux Secret Service), not in config files, not in environment variables,\n  and never as a tool argument. The `unlock_vault` tool deliberately takes **no\n  password parameter**, so the password can never end up in the model's context or in\n  logs.\n- **The vault stays local.** The server reads the encrypted `vault.enpassdb` file\n  directly with SQLCipher. Nothing is uploaded anywhere.\n- **Reads are explicit.** Listing entries never returns passwords. Secrets are only\n  returned by `get_item` / `get_password`, when you explicitly ask for them.\n- **Read-only unless you say otherwise.** Out of the box the server cannot change\n  anything: the writing tools are not even advertised. Set\n  `ENPASS_MCP_ALLOW_WRITES=1` to enable them (see [Writing](#writing-opt-in)).\n\nEntry passwords are, by design, returned to the assistant when you ask for them, so\nonly connect this to an assistant and vaults you trust.\n\n## Requirements\n\n- Node.js 18 or newer\n- An Enpass 6 / 7 / 8 vault (`vault.enpassdb`, SQLCipher format)\n- On Linux: a Secret Service provider (GNOME Keyring or KWallet) for password storage\n\nNative dependencies (`better-sqlite3-multiple-ciphers`, `@napi-rs/keyring`) ship\nprebuilt binaries for common platforms, so no compiler is required in the normal case.\n\n## Install\n\n```bash\ngit clone https://github.com/bitterdev/enpass-mcp.git\ncd enpass-mcp\nnpm install\nnpm link   # optional: makes the `enpass-mcp` command available globally\n```\n\n## Register your vaults (do this once, in a terminal)\n\nThis is the secure step that keeps the master password away from the model. You run it\nyourself; the password is typed into a hidden prompt and stored in the OS keychain.\n\n```bash\n# Find your vault files automatically\nenpass-mcp discover\n\n# Register a vault (you will be prompted for the master password)\nenpass-mcp add-vault personal --path \"/Users/you/Documents/Enpass/Vaults/primary/vault.enpassdb\"\nenpass-mcp add-vault work     --path \"/path/to/work/vault.enpassdb\"\n\n# With a keyfile\nenpass-mcp add-vault personal --path \"/path/vault.enpassdb\" --keyfile \"/path/vault.keyfile\"\n\n# Manage\nenpass-mcp list-vaults\nenpass-mcp test-unlock personal\nenpass-mcp remove-vault work\n```\n\n`add-vault` verifies the password can actually unlock the vault before saving it.\n\nThe vault file is usually found at:\n\n| OS | Typical location |\n| --- | --- |\n| macOS | `~/Documents/Enpass/Vaults/<vault>/vault.enpassdb` |\n| Windows | `%USERPROFILE%\\Documents\\Enpass\\Vaults\\<vault>\\vault.enpassdb` |\n| Linux | `~/Documents/Enpass/Vaults/<vault>/vault.enpassdb` |\n\nIf you sync via Dropbox / OneDrive / WebDAV, point `--path` at the synced copy.\n\n## Connect it to your assistant\n\nThe server speaks MCP over stdio. Point your MCP client at `enpass-mcp serve`.\n\n**Claude Desktop** (`claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"enpass\": {\n      \"command\": \"enpass-mcp\",\n      \"args\": [\"serve\"]\n    }\n  }\n}\n```\n\nIf you did not run `npm link`, use the absolute path instead:\n\n```json\n{\n  \"mcpServers\": {\n    \"enpass\": {\n      \"command\": \"node\",\n      \"args\": [\"/absolute/path/to/enpass-mcp/src/cli.js\", \"serve\"]\n    }\n  }\n}\n```\n\n**Claude Code:**\n\n```bash\nclaude mcp add enpass -- enpass-mcp serve\n```\n\n## Tools\n\n| Tool | Description |\n| --- | --- |\n| `list_vaults` | List registered vaults, whether their file exists, whether a password is stored, and whether they are unlocked. |\n| `unlock_vault` | Unlock a vault using the master password from the OS keychain. Takes only a vault name, never a password. |\n| `lock_vault` | Lock a vault and clear its derived key from memory. |\n| `list_items` | List entries (title, username, URL). Never returns passwords. Supports `query`, `category`, `folder`, `limit`. |\n| `get_item` | Return a full entry including all field values (password, TOTP, etc.) and its attachment list. |\n| `get_password` | Return the password and, if present, the current TOTP code of an entry. |\n| `get_otp` | Generate the current TOTP / 2FA one-time code for an entry, with seconds until it rotates. |\n| `list_attachments` | List an entry's file attachments (name, size, MIME). |\n| `export_attachment` | Decrypt an attachment; writes it to disk and returns the path (or base64 inline for small files). |\n| `sync_status` | List the vaults that use Enpass folder sync and whether the copy in the sync folder is newer. |\n\nWith `ENPASS_MCP_ALLOW_WRITES=1` three more tools appear (see [Writing](#writing-opt-in)):\n\n| Tool | Description |\n| --- | --- |\n| `create_item` | Create an entry, including custom fields; sensitive values are encrypted the way Enpass does it. |\n| `delete_item` | Delete an entry, or move it to the trash, leaving the tombstone Enpass uses so the deletion syncs. |\n| `sync_pull` | Take in a newer copy from the sync folder, after backing up the local vault. |\n\n`list_items` / `get_item` work for **every** Enpass entry type (logins, credit\ncards, secure notes, identities, etc.), not just logins, and return all fields.\n\nA typical assistant flow: `list_vaults` → `unlock_vault` → `list_items` → `get_password`.\n\n## How it works\n\nEnpass stores each vault as a standard SQLCipher database (`vault.enpassdb`). The raw\nencryption key is derived from your master password (optionally combined with a\nkeyfile) and the 16-byte salt at the start of the file:\n\n- PBKDF2-HMAC-SHA512, 100000 iterations (older vaults) or 320000 (newer vaults), the\n  first 32 bytes used as the raw SQLCipher key\n- opened with `cipher_compatibility` 4 (Enpass 6.8+) or 3 (older vaults)\n\nThe server tries these combinations automatically, so it works across Enpass vault\nversions. The derived key is kept in memory only, for the lifetime of the server\nprocess, and is never written to disk or returned to the model.\n\nReferences: [Enpass Security Whitepaper](https://support.enpass.io/docs/security-whitepaper-enpass/vault.html),\n[hazcod/enpass-cli](https://github.com/hazcod/enpass-cli).\n\n## Per-item field encryption\n\nEnpass encrypts every value flagged as \"sensitive\" a second time, underneath\nSQLCipher, with a key that belongs to the entry rather than the vault. Fields\ncarrying that layer have `itemfield.algo_version = 1`:\n\n| Piece | Where | Layout |\n| --- | --- | --- |\n| Key and nonce | `item.key` | 44 bytes: 32-byte AES-256 key, then a 12-byte GCM nonce |\n| Value | `itemfield.value` | hex of `ciphertext \\|\\| 16-byte GCM tag` |\n| Additional data | the entry's uuid | hyphens stripped, hex-decoded to 16 raw bytes |\n\nBinding the AAD to the entry uuid is what makes a value unusable if it is copied into\nanother entry. Enpass did **not** re-encrypt existing entries when it introduced this\nlayer, so one vault mixes ciphertext and plaintext under the same `algo_version`; a\nvalue is treated as encrypted only when it has the shape of a payload (pure hex, whole\nbytes, longer than the tag on its own).\n\nA value that looks encrypted but fails authentication is returned as `null` with\n`decryptionFailed: true`, never as the raw column content: stored ciphertext is a\nplausible-looking string, and handing that back would silently pass off a wrong secret\nas a real one.\n\n## Two-factor codes (TOTP)\n\nEntries with a one-time-password secret (stored by Enpass as an `otpauth://` URI)\ncan produce a live 2FA code: `get_otp` returns the current 6-digit code and the\nseconds until it rotates, and `get_password` includes the current code alongside the\npassword. This lets an assistant fill both the password and the 2FA prompt.\n\n## Attachments\n\nEnpass keeps file attachments encrypted. Small files (up to 1 KB) sit inline in the\nvault; larger files live in separate `<uuid>.enpassattach` SQLCipher files next to the\nvault, each encrypted with its own key stored in the vault. `export_attachment` handles\nboth: it decrypts the file and, by default, writes it to disk and returns the path, so\nit works for files of any size without pushing binary data through the model.\n\nExternal-attachment handling is implemented from Enpass's documented format. If you hit\na vault whose attachments do not decrypt, please open an issue with the (non-secret)\nschema of your `attachment` table.\n\n## Writing (opt-in)\n\nWriting is switched off by default. A password vault is the last place where a tool\nshould be able to change data just because a model decided to, so the server starts\nread-only and does not even list `create_item`, `delete_item` and `sync_pull` until you\nturn them on:\n\n```bash\nENPASS_MCP_ALLOW_WRITES=1\n```\n\nSet it in the server's environment (in your MCP client config, or in the `.env` next to\n`vaults.json`). Nothing else changes: reading works exactly the same either way.\n\n**Enpass must be closed while writing.** The app keeps the database in memory and would\nwrite its own cached copy back over any change made underneath it. Every writing tool\nrefuses to run while Enpass is open.\n\nEarlier versions of this README claimed writing was impossible because recent vaults\n(schema version 6) crash Enpass when entries are inserted directly. The crash was real,\nthe diagnosis was wrong. Three concrete rules make it work, all of them derived from\nwhat Enpass itself writes:\n\n1. **Enpass never stores `NULL`.** The crash is `EXC_BAD_ACCESS` in `strlen` on a null\n   pointer: a column omitted from the `INSERT` defaults to `NULL`, and the app calls\n   `strlen` on it. Every column is written explicitly, empty strings instead of `NULL`.\n2. **A template has a fixed field set.** `login.default` always carries the same nine\n   fields, in the same order, with the same field uids, even when most are empty.\n   Writing only the fields you happen to have a value for produces an entry the app\n   cannot render.\n3. **The per-item key is reproducible.** `item.key` is a 32-byte AES-256 key plus a\n   12-byte GCM nonce, stored as `hex(ciphertext || tag)` with the item UUID as\n   additional authenticated data. Nothing in it is tied to Enpass internals, so a fresh\n   random key per item is fine.\n\nVerified end to end against a real vault: written, read back, decrypted to the original,\ndeleted, synced in both directions, and Enpass opens the vault without crashing.\n\n## Configuration\n\nMaster passwords are in the OS keychain; only non-secret data (vault names and paths)\nis stored in a small `vaults.json`:\n\n- macOS: `~/Library/Application Support/enpass-mcp/vaults.json`\n- Windows: `%APPDATA%\\enpass-mcp\\vaults.json`\n- Linux: `~/.config/enpass-mcp/vaults.json`\n\nOverride the directory with `ENPASS_MCP_CONFIG_DIR`.\n\nEnvironment variables:\n\n| Variable | Effect |\n| --- | --- |\n| `ENPASS_MCP_ALLOW_WRITES` | `1`, `true`, `yes` or `on` enables the writing tools. Anything else, including unset, keeps the server read-only. |\n| `ENPASS_MCP_CONFIG_DIR` | Where `vaults.json` and the optional `.env` live. |\n| `ENPASS_MASTER_PASSWORD` | Optional fallback master password for vaults without a keychain entry. Prefer the keychain. |\n| `ENPASS_MASTER_PASSWORD_<VAULT>` | Same, for one specific vault. |\n\n## Development\n\n```bash\nnpm test                    # runs against genuine SQLCipher fixtures in test/fixtures\n\n# Rebuild the fixtures from scratch with real SQLCipher (vault + entries + attachments)\nnpm install --no-save @journeyapps/sqlcipher\nnpm run generate-fixtures\n```\n\nCI (GitHub Actions) creates a vault from scratch with real SQLCipher, seeds entries and\nattachments, then runs the full read-only test suite on Node 18/20/22.\n\n## Security notes and limitations\n\n- Anyone who can talk to this MCP server can read every password in a vault once it is\n  unlocked. Only connect trusted clients.\n- The server does not implement Enpass sync, item history, or trashing.\n- The server is read-only and never modifies a vault. Creating or editing entries is\n  intentionally not supported, because direct database writes crash recent Enpass\n  vaults (see [Why there is no write support](#why-there-is-no-write-support)).\n- This is an independent project and is not affiliated with or endorsed by Enpass.\n\n## License\n\nMIT © Fabian Bitter\n",
  "bytes": 12597,
  "sha": "62ceb6829e7747001f7b5dcd3dde97d55cbf7f0e64664e737e603c989c88644e",
  "repo_slug": "bitterdev/enpass-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_bitterdev_enpass_mcp_b2f6257b/readme"
}