Are you the author? Sign in to claim
Encrypted-at-rest credential vault with pluggable DB backends and an MCP server for agents.
An encrypted-at-rest credential vault with an MCP server for agents.
AES-256-GCM secrets, a pluggable database backend, and a read-only-by-default
MCP server — so agents can look up credentials without plaintext ever touching disk.
Secrets sprawl across .env files, shell history, and plaintext columns — and agents have no safe, structured way to ask for them. CryptoFort seals every secret with authenticated encryption, keeps the key out of the database entirely, and hands agents a narrow MCP interface that returns metadata by default and plaintext only on an explicit get.
Encrypted at restEvery secret is sealed with AES-256-GCM. The master key lives only in the environment, so a database dump is inert on its own. |
Agent-nativeA built-in MCP server exposes |
Backend-agnosticThe same vault runs on Supabase, SQLite, or any Postgres. Switch backends with a single environment variable. |
npm install cryptofort
# plus the driver for your backend:
npm install @supabase/supabase-js # or: better-sqlite3 | postgres
CryptoFort is also published to GitHub Packages as @bradley-t-t/cryptofort. Point the @bradley-t-t scope at the GitHub registry and authenticate with a token that has read:packages — GitHub Packages requires auth even for public packages:
@bradley-t-t:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
npm install @bradley-t-t/cryptofort
import { Vault, Crypto, SqliteAdapter } from 'cryptofort';
const adapter = new SqliteAdapter('vault.db');
await adapter.init();
const vault = new Vault({
adapter,
crypto: new Crypto({ key: process.env.CRYPTOFORT_MASTER_KEY! }),
});
await vault.put({
name: 'stripe-secret-key',
secret: 'sk_live_…',
provider: 'stripe',
tags: ['payments'],
});
await vault.search('stripe'); // metadata only — never the secret
await vault.get('stripe-secret-key'); // the decrypted secret
Generate a master key (base64, 32 bytes):
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
…or from the library with import { generateKey } from 'cryptofort'.
Point any MCP client at the cryptofort-mcp binary:
{
"mcpServers": {
"cryptofort": {
"command": "cryptofort-mcp",
"env": {
"CRYPTOFORT_ADAPTER": "supabase",
"SUPABASE_URL": "https://<ref>.supabase.co",
"SUPABASE_SERVICE_ROLE_KEY": "<service-role-key>",
"CRYPTOFORT_MASTER_KEY": "<base64-32-bytes>"
}
}
}
}
The server is read-only by default. Add "args": ["--allow-write"] to expose credential_put.
| Tool | Access | Description |
|---|---|---|
credential_search | read | Search by name, description, provider, or tag. Returns metadata only. |
credential_get | read | Decrypt and return a single secret by exact name. |
credential_list | read | List credential metadata in a namespace, optionally filtered by tag. |
credential_put | write | Create or update a credential. Requires --allow-write. |
| Variable | Required | Purpose |
|---|---|---|
CRYPTOFORT_MASTER_KEY | always | Base64, 32-byte AES-256 key. Never written to the database. |
CRYPTOFORT_ADAPTER | — | supabase (default), sqlite, or postgres. |
CRYPTOFORT_KEY_ID | — | Key identifier for rotation. Defaults to default. |
SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY | Supabase | Connection for the Supabase adapter. |
CRYPTOFORT_SUPABASE_DB_URL | — | Direct Postgres URL, used only to auto-create the schema. |
CRYPTOFORT_POSTGRES_URL | Postgres | Connection string for the Postgres adapter. |
CRYPTOFORT_SQLITE_PATH | — | SQLite file path. Defaults to cryptofort.db. |
| Backend | Driver | Best for |
|---|---|---|
| Supabase | @supabase/supabase-js | Hosted, shared across agents, service-role access. |
| Postgres | postgres | Dropping the vault into existing Postgres infrastructure. |
| SQLite | better-sqlite3 | Local, single-process, zero-infrastructure use. |
flowchart TD
A["Agent / MCP client"] -->|"stdio"| M["cryptofort-mcp — read-only by default"]
App["Your app"] --> V["Vault"]
M --> V
V --> C["Crypto — AES-256-GCM"]
C -->|"master key from env, never stored"| K["CRYPTOFORT_MASTER_KEY"]
V --> AD["Adapter"]
AD --> S[("Supabase")]
AD --> P[("Postgres")]
AD --> Q[("SQLite")]
name, description, provider, and tags stay plaintext, so search and listing work without ever decrypting.CRYPTOFORT_MASTER_KEY; a stolen dump reveals nothing without it.--allow-write, so an agent can look secrets up but cannot quietly rewrite the vault.CryptoFort creates its schema automatically on first connect — one table, one ciphertext column, the rest plaintext metadata for search. There is no migration to run by hand.
adapter.init() issues create table if not exists (plus indexes), so pointing CryptoFort at an empty database is enough.init() probes for the table and, when it is missing, creates it through a direct Postgres connection given in CRYPTOFORT_SUPABASE_DB_URL. If the table already exists the probe is a no-op; if it is missing and no DB URL is set, init() fails with a clear message instead of silently.The canonical column definitions live in src/adapters/schema.ts.
npm install
npm run build # bundle with tsup
npm test # run the vitest suite
| Script | Does |
|---|---|
npm run build | Bundle ESM, CJS, and types with tsup. |
npm test | Run the Vitest suite. |
npm run typecheck | tsc --noEmit. |
npm run lint | Lint with ESLint. |
npm run format | Check formatting with Prettier. |
Backend drivers are optional peer dependencies — install only the one you use.
Released under the MIT License.
Secrets sealed at rest — handed to agents, never spilled.
Run Claude Code as an MCP server so any agent can delegate coding tasks to it
Browser automation using accessibility snapshots instead of screenshots
Google's universal MCP server supporting PostgreSQL, MySQL, MongoDB, Redis, and 10+ databases
Official GitHub integration for repos, issues, PRs, and CI/CD workflows