@agentsoft/agent-vault
v0.8.0
Published
MCP server providing secure, access-controlled secret management for agentflow agents
Maintainers
Readme
@agentsoft/agent-vault
MCP server that gives Claude Code / agentflow agents secure, ACL-gated access to secrets stored in Infisical. One Infisical organization, many agent-vault projects (each its own Infisical workspace), many agent identities — each scoped by domain, environment, and role.
Status: v0.8.0, actively iterated. Core flows verified end-to-end against a self-hosted Infisical. Not yet published to npm — install locally via
npm link(see below).
What it does
- Bootstraps Infisical workspaces from the MCP itself (
vault_create_project) — no manual UI clicking to set up a new project's secret store. - Routes secret reads/writes through a four-segment namespace:
{project}/{env}/{domain}/{key}— e.g.ecommerce/dev/backend/STRIPE_KEY. - Enforces a two-layer ACL:
- Namespace ACL — who can access what path, with role-based templates
(
worker,planner,tester,devops,shared-reader,auditor,custom), plus user-defined roles in YAML. - Tool ACL — which MCP tools each agent can even call. Enforced at tool registration time when a default agent is set, at runtime always.
- Namespace ACL — who can access what path, with role-based templates
(
- Every tool call is recorded in an append-only audit log.
- Provides lifecycle commands:
setup,add-agent,remove-agent,roles list/show,detect,health,rotate.
Install
Today — local dev via npm link
git clone <this-repo>
cd agent-vault
pnpm install
pnpm build
npm link # makes `agent-vault` available on your PATHVerify:
agent-vault # → "No vault-config.yaml found — run agent-vault setup"Later — once published
npx @agentsoft/agent-vault setup
# or install globally
npm install -g @agentsoft/agent-vaultQuick start (5 minutes)
1. Run Infisical
Use the bundled compose file, or point at an existing instance:
docker compose up -d # starts Infisical + Postgres + Redis
# open http://localhost:8080, create an account, create an organization2. Provision identities
Interactive wizard (scans for agentflow + user agents, prompts for role assignment):
agent-vault setupOr non-interactive with an agents list:
# agents.yaml
agents:
- { name: devops-executor, domain: devops, role: devops }
- { name: backend-executor, domain: backend, role: worker }
- { name: auditor, domain: shared, role: auditor }VAULT_SETUP_URL=http://localhost:80 \
VAULT_SETUP_TOKEN=<your-admin-jwt> \
VAULT_SETUP_ORG=<your-org-id> \
VAULT_SETUP_ENVS=dev,qa,prod \
agent-vault setup --agents-file ./agents.yamlWrites to ~/.config/agent-vault/:
vault-config.yaml— credentials (gitignore this if you point it elsewhere)vault-acl.yaml— role assignments and ACL rulesvault-state.json— runtime workspace mapping (populated as you create projects)
3. Verify
agent-vault health
# Summary: config=OK, connection=OK, identities=N/N valid4. Add to your project's .mcp.json
{
"mcpServers": {
"agent-vault": { "command": "agent-vault" }
}
}That's the entire per-project footprint. The server auto-discovers
~/.config/agent-vault/vault-config.yaml.
Optionally set a session default identity:
{
"mcpServers": {
"agent-vault": {
"command": "agent-vault",
"env": { "VAULT_DEFAULT_AGENT": "backend-executor" }
}
}
}5. Bootstrap a project and use it
Start a Claude Code session in the project. Then ask:
Create an agent-vault project called
my-app, then save a secret STRIPE_KEY with value sk_test_xxx to my-app/dev/backend, using agent devops-executor.
Claude will call:
vault_create_project({ name: "my-app", agent: "devops-executor" })
vault_set({ namespace: "my-app/dev/backend", key: "STRIPE_KEY", value: "sk_test_xxx", agent: "backend-executor" })A new Infisical workspace is created, folders provisioned, memberships granted, and the secret written.
Concepts
Namespace: {project}/{env}/{domain}/{key}
Every secret lives at a 4-segment path:
- project — routes to an Infisical workspace. Created on demand via
vault_create_project. - env — Infisical environment (
dev,qa,prod). Created automatically inside the workspace when the project is bootstrapped. - domain — folder inside the env. Standard set:
backend,frontend,mobile,devops,qa,shared. - key — the secret name.
Example: ecommerce/dev/backend/STRIPE_KEY →
Infisical workspace ecommerce, env dev, folder /backend, secret STRIPE_KEY.
Roles
Role templates translate to ACL rules + tool allow-lists. Built-ins:
| Role | Namespace access | Tools |
|---|---|---|
| worker | R/W/D/L on {project}/{env}/{domain}; R/L on {project}/{env}/shared | all |
| planner | R/L on own domain + shared | vault_get, vault_list, vault_exists |
| tester | same as worker (for test fixtures) | all |
| devops | override: R/W/D/L/create on {project}/* (cross-env/domain) | all |
| shared-reader | R/L on {project}/{env}/* (all domains) | vault_get, vault_list, vault_exists |
| auditor | L on {project}/* | vault_list, vault_exists (no value reads) |
| custom | none — hand-edit ACL | all |
Assignments in vault-acl.yaml:
domains:
backend: [backend-executor, backend-planner]
devops: [devops-executor]
shared: [auditor, software-architect]
agent_roles:
backend-executor: worker
backend-planner: planner
devops-executor: devops
auditor: auditor
software-architect: shared-readerCustom roles
Define in the same YAML:
roles:
reviewer:
access:
- { namespace: "{project}/*", permissions: [read, list] }
tools: [vault_get, vault_list, vault_exists]
release-captain:
overrides:
- { namespace: "{project}/prod/*", permissions: [read, write, list] }
tools: null # unrestricted
agent_roles:
alice-reviewer: reviewer
bob-captain: release-captainUser-defined roles can override built-ins by using the same name (e.g.
narrowing the stock auditor).
Identity — per-call
Every tool call takes an agent parameter. That identity:
- Determines which namespace ACL rules apply
- Determines which tool-level restrictions apply
- Determines which Infisical credentials authenticate for that call
If you set VAULT_DEFAULT_AGENT=backend-executor in the MCP server env,
calls without agent fall back to that identity. Otherwise agent is
required.
Lazy auth
The server doesn't authenticate to Infisical at startup. First call per agent triggers a Universal Auth login; the token is cached for subsequent calls. Supports multi-agent sessions naturally — dispatching a new agent just means one extra login on its first call.
CLI reference
All commands find config via: VAULT_CONFIG_PATH → ./vault-config.yaml → ~/.config/agent-vault/vault-config.yaml.
agent-vault setup
Scans for agent definitions (./agentflow/agents, ./.claude/agents, ~/.claude/agents,
plus any --scan <dir>), auto-assigns agentflow agents by naming convention,
prompts for user agents. Provisions Infisical identities and writes
config + ACL + empty state.
Flags:
--agents-file <path>— non-interactive; skip detection, use explicit list--no-agentflow— skip the agentflow/ scan--scan <dir>— add a directory to the user-scope scan (repeatable)--project-scope— write files to$PWDinstead of~/.config/agent-vault/
Env-var fallbacks for non-interactive connection prompts:
VAULT_SETUP_URL,VAULT_SETUP_TOKEN,VAULT_SETUP_ORG,VAULT_SETUP_ENVS
agent-vault detect [--scan <dir>]
Read-only. Prints classified agents (agentflow vs user, with auto-assigned
(domain, role) for agentflow). No side effects.
agent-vault add-agent <name> --domain <d> --role <r>
Provisions a new identity in Infisical, appends to vault-acl.yaml and
vault-config.yaml, and grants workspace membership on every existing
project in vault-state.json.
agent-vault remove-agent <name> [--keep-identity]
Removes the identity from every known workspace, deletes it from Infisical
(unless --keep-identity), and strips from ACL + config.
agent-vault roles list
Prints built-in and custom roles with one-line summaries. Shows overrides when a built-in is shadowed by a user-defined role of the same name.
agent-vault roles show <name>
Dumps the role's full definition: access rules, overrides, tool allow-list, and whether it's built-in, custom, or a custom-overriding-built-in.
agent-vault health
Validates config parses, Infisical is reachable, and every identity can complete a Universal Auth login. Exits non-zero if any check fails.
agent-vault rotate <agent-name> / --all
Generates new Universal Auth client secrets for one agent or all agents.
Updates vault-config.yaml in place. Requires INFISICAL_TOKEN env (a
user JWT with admin perms) because machine-identity creds can't rotate
themselves.
MCP tools reference
All secret tools take namespace and key arguments, plus an agent
parameter (optional if VAULT_DEFAULT_AGENT is set, required otherwise).
vault_get({ namespace, key, agent? })
Returns the secret value + metadata, or a not-found message.
vault_set({ namespace, key, value, description?, tags?, agent? })
Creates or updates a secret. description → Infisical secret comment;
tags → categorization.
vault_delete({ namespace, key, agent? })
Deletes the secret.
vault_list({ namespace, agent? })
Returns the list of keys in that folder (no values).
vault_exists({ namespace, key, agent? })
Returns a boolean. Gated by list permission (not read) so auditor-style
roles can check existence without reading values.
vault_create_project({ name, agent? })
Bootstraps a new Infisical workspace. Creates the workspace, provisions
all configured envs, grants every agent identity membership, and creates
{backend, frontend, mobile, devops, qa, shared} folders in each env.
Requires the create permission (typically devops-only).
Configuration
File layout
Default (user-scoped, XDG):
~/.config/agent-vault/
├── vault-config.yaml # adapter settings + credentials
├── vault-acl.yaml # roles + agent_roles + domains
├── vault-state.json # project → workspace UUID map
└── vault-audit.jsonl # append-only audit logRespects $XDG_CONFIG_HOME/agent-vault if set.
Environment variables
| Var | Purpose | Default |
|---|---|---|
| VAULT_CONFIG_PATH | Explicit path to vault-config.yaml | auto-discover |
| VAULT_DEFAULT_AGENT | Session default identity when call omits agent | unset |
| VAULT_ENV | Default env for {env} ACL template resolution | from config |
| VAULT_SETUP_URL | (setup only) skip URL prompt | — |
| VAULT_SETUP_TOKEN | (setup only) skip admin token prompt | — |
| VAULT_SETUP_ORG | (setup only) skip org ID prompt | — |
| VAULT_SETUP_ENVS | (setup only) skip envs prompt | — |
| INFISICAL_TOKEN | (rotate only) admin token for rotation | — |
| VAULT_AGENT_ID | deprecated — falls back to VAULT_DEFAULT_AGENT | — |
vault-config.yaml schema
adapter:
type: infisical
connection:
siteUrl: http://localhost:80
organizationId: <uuid>
environments: [dev, qa, prod]
agentIdentities:
backend-executor:
identityId: <uuid>
clientId: <uuid>
clientSecret: <hex>
# ... one entry per agent
adminIdentity: # used for vault_create_project + add-agent + remove-agent
identityId: <uuid>
clientId: <uuid>
clientSecret: <hex>
defaultEnvironment: dev
aclPath: ./vault-acl.yaml
statePath: ./vault-state.json
auditLogPath: ./vault-audit.jsonl # set to null to disable auditPaths with ./ are resolved relative to the config file's directory.
Audit log
vault-audit.jsonl contains one JSON record per tool call:
{
"ts": "2026-04-16T23:12:59.533Z",
"agent": "backend-executor",
"defaultAgent": null,
"tool": "vault_set",
"namespace": "ecommerce/dev/backend",
"key": "DB_URL",
"permission": "write",
"result": "allowed"
}Fields:
ts— ISO-8601 timestampagent— the identity the call ran as (from theagentparam or default)defaultAgent— whatVAULT_DEFAULT_AGENTwas set to, for contexttool— tool namenamespace,key,permission— what was attemptedresult—"allowed","denied", or"error"reason— only ondenied/error— the message
Append-only, never rotated by the package. Use jq / grep to trace activity.
Architecture
Layers
MCP client (Claude Code session)
│
▼
stdio (JSON-RPC 2.0)
│
▼
┌────────────────────────────────────────┐
│ MCP server (this package) │
│ ┌──────────────────────────────────┐ │
│ │ Tool handlers (tools.ts) │ │
│ │ ├─ resolve agent (per-call) │ │
│ │ ├─ tool-level ACL │ │
│ │ ├─ audit log │ │
│ │ └─ delegate to Vault │ │
│ └──────────────────────────────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ Vault core (core/vault.ts) │ │
│ │ ├─ namespace parse │ │
│ │ ├─ ACL enforcement │ │
│ │ ├─ lazy per-agent auth cache │ │
│ │ └─ state lookup (StateStore) │ │
│ └──────────────────────────────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ Adapter (adapters/infisical.ts) │ │
│ │ ├─ Universal Auth login │ │
│ │ ├─ secret CRUD via SDK │ │
│ │ └─ workspace/folder ops (REST) │ │
│ └──────────────────────────────────┘ │
└────────────────────────────────────────┘
│
▼
Infisical API (@infisical/sdk + REST)Workspace-per-project model
One agent-vault project = one Infisical workspace. vault_create_project:
- Creates workspace (POST
/api/v2/workspace) - Creates each configured environment (POST
/api/v1/workspace/:id/environments) - Grants every agent identity membership as
member - Creates
{backend, frontend, mobile, devops, qa, shared}folders per env - Records
project_name → workspace_idinvault-state.json
State is runtime-mutable; config is immutable (setup + CLI commands edit it, the server never does).
ACL enforcement layers
- MCP tool ACL (at registration, if
VAULT_DEFAULT_AGENTis set): tools the default identity can't use are not registered. Client'stools/listnever sees them. - Tool ACL at runtime (every call): the actual calling identity is checked. A call to a tool the identity isn't allowed → denied with audit log.
- Namespace ACL: checked by
Vault.assertPermissionbefore the adapter is touched. Resolves{self},{domain},{env},{project}templates against the call's identity and namespace. - Backend-level (Infisical): the agent's identity is a workspace member, so Infisical itself authorizes the API call. The narrower agent-vault ACL is the real policy; Infisical is intentionally broad storage.
What's NOT enforced
- In-session identity isolation: when one Claude Code session dispatches
subagents via the
Agenttool, they all share the same MCP process. The per-callagentparam is authoritative but advisory — a subagent that "forgets" to declare its identity falls back to the session'sVAULT_DEFAULT_AGENT, and there's no OS-level boundary between subagent calls. For hard role isolation, run each role in a separate Claude Code session. See commit0949971for the reasoning.
Development
Build + test
pnpm install
pnpm build # tsc → dist/
pnpm test # vitest, ~170 tests
pnpm test:watchProject layout
src/
├── types.ts # Shared types
├── index.ts # Public exports
├── paths.ts # XDG config resolution
├── acl/
│ ├── acl-config.ts # YAML parser
│ ├── acl-engine.ts # Permission resolution + role expansion
│ └── roles.ts # Built-in role templates
├── core/
│ └── vault.ts # Orchestrator + lazy auth + namespace routing
├── adapters/
│ └── infisical.adapter.ts # Infisical backend
├── ports/
│ └── vault-adapter.port.ts # Backend-agnostic adapter interface
├── state/
│ └── state-store.ts # vault-state.json persistence
├── audit/
│ └── audit-logger.ts # JSONL append-only log
├── detection/
│ ├── scanner.ts # Scans .md files for agents
│ └── agentflow-conventions.ts # Name → (domain, role) mapping
├── mcp/
│ ├── server.ts # MCP server factory
│ └── tools.ts # Tool handlers
└── cli/
├── entry.ts # Command dispatch
├── setup.ts # Interactive/non-interactive wizard
├── detect.ts, health.ts, rotate.ts,
├── add-agent.ts, remove-agent.ts, roles.ts
└── admin-ops.ts # Shared admin-API helpersAdding a new backend adapter
Implement VaultAdapter (see src/ports/vault-adapter.port.ts). Methods take
backend-agnostic primitives — workspace identifier, env, folder path, secret
name — so Vault-level code doesn't need changes. Register the adapter type in
src/cli/entry.ts's createAdapter switch and update the setup wizard to
prompt for that backend's connection details.
Contributing
PRs welcome. Conventional commits enforced via commitlint. Husky runs tests pre-commit.
License
MIT.
