This document provides comprehensive API documentation for the Symaira Vault Model Context Protocol (MCP) server, enabling agent developers to integrate with Symaira Vault securely.
- Overview
- Authentication
- Transport Modes
- Tool Reference
- Error Handling
- Rate Limiting
- Agent Configuration
- Field Redaction with redactFields
- Examples
Symaira Vault exposes a Model Context Protocol (MCP) server that allows AI agents to securely read and optionally write password vault entries. The MCP server supports both stdio (standard input/output) and HTTP transports.
- Structured API: Type-safe tool invocations with JSON schemas
- Per-Agent Access Control: Each agent can have different permissions
- Bearer Authentication: HTTP mode uses token-based auth
- Audit Logging: All operations are logged for security monitoring
- Metadata Support: Version tracking for credential caching
| Tool | Description | Write Operation |
|---|---|---|
health |
Check server health status | No |
get_auth_status |
Check Symaira Vault unlock auth status | No |
set_auth_method |
Change unlock auth method | Config write |
list_entries |
List all vault entries | No |
get_entry |
Retrieve entry contents | No |
get_entry_metadata |
Get entry metadata without sensitive data | No |
find_entries |
Search entries by path | No |
search |
Search vault entries by query (OpenAI Company Knowledge format) | No |
fetch |
Fetch vault entry by path/id (OpenAI Company Knowledge format) | No |
generate_password |
Generate secure passwords | No |
generate_totp |
Generate TOTP codes | No |
copy_to_clipboard |
Copy entry password to system clipboard (auto-clears) | No |
autotype |
Type entry field as keyboard input into focused app | No |
set_entry_field |
Store or update a field | Yes |
run_command |
Execute command with secret env injection | Yes |
delete_entry |
Delete an entry | Yes |
symvault_delete |
Deprecated alias for delete_entry | Yes |
secure_input |
Prompt user for sensitive data via TTY or native GUI dialog | Yes |
request_credential |
Agent-initiated: native dialog for a missing credential, stored without exposure | Yes |
prepare_payment |
Validate a payment entry, show approval prompt, autotype card/bank fields — card values never returned | No |
HTTP mode requires bearer token authentication. The token is auto-generated on first server start.
<vault>/mcp-token
Authorization: Bearer <token>
X-Symaira-Agent: <agent-profile-name>
curl -H "Authorization: Bearer $(cat ~/.symvault/mcp-token)" \
-H "X-Symaira-Agent: claude-code" \
-H "Content-Type: application/json" \
-X POST \
-d '{"tool": "list_entries", "arguments": {}}' \
http://127.0.0.1:8080/mcpStdio mode does not use HTTP authentication. The agent is identified via the --agent flag:
symvault serve --stdio --agent claude-codeThe agent name must match a profile in the vault configuration.
Symaira Vault implements OAuth 2.1 with PKCE and Dynamic Client Registration (DCR). This allows MCP clients that support DCR (such as opencode) to automatically register with the Symaira Vault MCP server and obtain scoped tokens without manual token management.
- Discovery: The client fetches
/.well-known/oauth-authorization-serverto find the authorize, token, and registration endpoints. - Registration: The client sends a
POST /oauth/registerwith its redirect URIs. The server returns aclient_id. - Authorization: The client redirects the user to
/mcp/oauth/authorizewith PKCE challenge. The server prompts for user consent via TTY and issues a short-lived authorization code. - Token Exchange: The client exchanges the authorization code for an access token + refresh token at
/mcp/oauth/token. - Refresh: When the access token expires, the client uses the refresh token to obtain a new pair without user interaction.
| Endpoint | Method | Status | Description |
|---|---|---|---|
/.well-known/oauth-protected-resource |
GET | 200 | Protected resource metadata (RFC 9728) |
/.well-known/oauth-authorization-server |
GET | 200 | Authorization server metadata (RFC 8414) |
/oauth/register |
POST | 201 | Dynamic client registration (RFC 7591) |
/mcp/oauth/authorize |
GET | 302/400 | Authorization request (RFC 6749 §4.1.1) |
/mcp/oauth/token |
POST | 200/400 | Token exchange and refresh (RFC 6749 §4.1.3, §6) |
GET /.well-known/oauth-protected-resource
HTTP/1.1 200 OK
Content-Type: application/json
{
"resource": "http://127.0.0.1:8080/mcp",
"bearer_methods_supported": ["header"],
"resource_name": "Symaira Vault MCP Server"
}GET /.well-known/oauth-authorization-server
HTTP/1.1 200 OK
Content-Type: application/json
{
"issuer": "http://127.0.0.1:8080",
"authorization_endpoint": "http://127.0.0.1:8080/mcp/oauth/authorize",
"token_endpoint": "http://127.0.0.1:8080/mcp/oauth/token",
"registration_endpoint": "http://127.0.0.1:8080/oauth/register",
"response_types_supported": ["code"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"],
"grant_types_supported": ["authorization_code", "refresh_token"]
}POST /oauth/register
Content-Type: application/json
{
"redirect_uris": ["http://127.0.0.1:4321/callback"]
}Response (201 Created):
{
"client_id": "a1b2c3d4e5f6a7b8",
"client_id_issued_at": 1700000000,
"client_secret_expires_at": 0,
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"redirect_uris": ["http://127.0.0.1:4321/callback"]
}Client registrations are persistent across server restarts (stored in <vault-dir>/mcp-oauth-clients.json).
POST /mcp/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTH_CODE&
code_verifier=VERIFIERResponse (200 OK):
{
"access_token": "a1b2c3d4...",
"token_type": "Bearer",
"expires_in": 86400,
"refresh_token": "e5f6a7b8..."
}POST /mcp/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&
refresh_token=REFRESH_TOKENResponse (200 OK):
{
"access_token": "new_access_token...",
"token_type": "Bearer",
"expires_in": 86400,
"refresh_token": "new_refresh_token..."
}Token rotation: each refresh invalidates the previous access token and refresh token (single-use pattern).
OAuth token TTLs are configurable via config.yaml:
mcp:
oauth:
access_token_ttl: 1h # default: 24h
refresh_token_ttl: 720h # default: 720h (30 days)opencode can use either OAuth with DCR or static bearer token auth.
OAuth with DCR (recommended):
{
"mcpServers": {
"symvault": {
"type": "remote",
"url": "http://127.0.0.1:8080/mcp",
"oauth": true
}
}
}With this configuration, opencode mcp auth symvault will:
- Discover the authorization server metadata
- Register a client via DCR
- Walk through the PKCE authorization flow (user consent via TTY)
- Receive scoped access+refresh tokens
- Automatically refresh tokens when they expire
Bearer token auth (legacy):
{
"mcpServers": {
"symvault": {
"type": "remote",
"url": "http://127.0.0.1:8080/mcp",
"headers": {
"Authorization": "Bearer YOUR_SYMVAULT_TOKEN",
"X-Symaira-Agent": "opencode"
},
"oauth": false
}
}
}Best for: Local AI agents with direct process communication.
Advantages:
- No network exposure
- Simpler configuration
- Lower latency
- No token management
Start the server:
symvault serve --stdio --agent <profile-name>Generate configuration:
symvault agent install <agent-name> --config-onlyBest for: Remote agents, multiple clients, or service integration.
Advantages:
- Network accessible
- Multiple concurrent clients
- Compatible with HTTP-based tools
Start the server:
symvault serve --port 8080 --agent <profile-name>Generate configuration (token redacted by default):
symvault agent install <agent-name> --http --config-onlyInclude token in output:
symvault agent token <agent-name> new --tools list_entries,get_entry --expires 24hCheck the MCP server health status.
Request:
{
"tool": "health",
"arguments": {}
}Response:
{
"status": "healthy",
"timestamp": "2026-04-21T10:30:00Z",
"version": "1.0.0"
}HTTP Endpoint: GET /health (no authentication required)
Return the configured unlock method, Touch ID availability, and session cache backend.
Request:
{
"tool": "get_auth_status",
"arguments": {}
}Set the unlock method to passphrase or touchid. The calling agent profile
must set canManageConfig: true. MCP never accepts a passphrase from the agent;
Touch ID setup requires an already active Symaira Vault session.
Request:
{
"tool": "set_auth_method",
"arguments": {
"method": "touchid"
}
}List all password entries in the vault.
Request:
{
"tool": "list_entries",
"arguments": {}
}Response:
{
"entries": [
{
"path": "github",
"modified": "2026-01-15T14:32:00Z"
},
{
"path": "work/aws",
"modified": "2026-02-20T09:15:00Z"
}
]
}Notes:
- Returns paths relative to vault root
- Does not include entry contents
- Sorted by path
Retrieve the contents of a password entry.
Request:
{
"tool": "get_entry",
"arguments": {
"path": "github",
"include_metadata": false
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Entry path (e.g., "github" or "work/aws") |
include_metadata |
boolean | No | Include creation/update metadata |
Response (without metadata):
{
"path": "github",
"data": {
"password": "mysecretpassword",
"username": "myuser",
"url": "https://github.com"
}
}Response (with metadata):
{
"path": "github",
"data": {
"password": "mysecretpassword",
"username": "myuser",
"url": "https://github.com"
},
"meta": {
"created": "2026-01-15T14:32:00Z",
"updated": "2026-04-21T09:45:00Z",
"version": 5
}
}Errors:
not_found: Entry does not existaccess_denied: Agent profile restricts access to this pathvault_locked: Vault is locked, run symvault unlock.
Get entry metadata without retrieving sensitive data. Useful for cache validation.
Request:
{
"tool": "get_entry_metadata",
"arguments": {
"path": "api/kimi-key"
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Entry path |
Response:
{
"path": "api/kimi-key",
"exists": true,
"created": "2026-01-15T14:32:00Z",
"updated": "2026-04-21T09:45:00Z",
"version": 5
}Use Case: Credential Cache Validation
Agents can compare the version field with their cached version to determine if credentials need refresh:
// Check if cached credentials are stale
{
"tool": "get_entry_metadata",
"arguments": {
"path": "api/kimi-key"
}
}Search for entries by path substring.
Request:
{
"tool": "find_entries",
"arguments": {
"query": "aws"
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Search string to match against entry paths |
Response:
{
"entries": [
{
"path": "work/aws",
"modified": "2026-02-20T09:15:00Z"
},
{
"path": "work/aws-staging",
"modified": "2026-03-10T11:20:00Z"
}
]
}Search vault entries by query. Returns results in OpenAI Company Knowledge compatible format with both structured and text content.
Request:
{
"tool": "search",
"arguments": {
"query": "aws"
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Search query to match against entry paths and field values |
Response:
The response includes both structuredContent and content fields simultaneously:
{
"structuredContent": {
"results": [
{
"id": "work/aws",
"title": "aws",
"url": "symvault://entry/work/aws"
},
{
"id": "work/aws-staging",
"title": "aws-staging",
"url": "symvault://entry/work/aws-staging"
}
]
},
"content": [
{
"type": "text",
"text": "[{\"id\":\"work/aws\",\"title\":\"aws\",\"url\":\"symvault://entry/work/aws\"},{\"id\":\"work/aws-staging\",\"title\":\"aws-staging\",\"url\":\"symvault://entry/work/aws-staging\"}]"
}
]
}Notes:
- Uses the same search engine as
find_entries - Each result includes
id(entry path),title(entry name), andurl(vault URL) - Scope filtering respects the agent's
allowedPathsconfiguration - Read-only operation (no write permissions required)
Fetch a vault entry by path/id. Returns full entry content with metadata and values in OpenAI Company Knowledge compatible format.
Request:
{
"tool": "fetch",
"arguments": {
"id": "work/aws"
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Entry path or id to fetch (e.g., "github" or "work/aws") |
Response:
The response includes both structuredContent and content fields simultaneously:
{
"structuredContent": {
"id": "work/aws",
"title": "aws",
"url": "symvault://entry/work/aws",
"metadata": {
"created": "2026-01-15T14:32:00Z",
"updated": "2026-04-21T09:45:00Z",
"version": 5,
"type": "password"
},
"values": {
"password": "...",
"username": "admin"
}
},
"content": [
{
"type": "text",
"text": "{\"id\":\"work/aws\",\"title\":\"aws\",\"url\":\"symvault://entry/work/aws\",\"metadata\":{\"created\":\"2026-01-15T14:32:00Z\",\"updated\":\"2026-04-21T09:45:00Z\",\"version\":5,\"type\":\"password\"},\"values\":{\"password\":\"...\",\"username\":\"admin\"}}"
}
]
}Notes:
- The
metadatafield always containscreated,updated,version, andtype - The
valuesfield is only included when the agent's profile has value access (standard/admin tier orcanReadValues: true) - Scope filtering respects the agent's
allowedPathsconfiguration - Read-only operation (no write permissions required)
Errors:
access_denied: Entry path is outside the agent's allowed scopenot_found: Entry does not exist
Generate a cryptographically secure password.
Request:
{
"tool": "generate_password",
"arguments": {
"length": 20,
"include_symbols": true
}
}Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
length |
integer | No | 16 | Password length (8-128) |
symbols |
boolean | No | true | Include special characters |
Response:
{
"password": "xK9#mP2$vL7@nQ4!aB8&"
}Generate a Time-based One-Time Password (TOTP) code from a stored TOTP secret.
Request:
{
"tool": "generate_totp",
"arguments": {
"path": "github"
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Entry path containing TOTP secret |
Response:
{
"code": "123456",
"expires_at": "2026-04-21T10:35:00Z",
"period": 30
}Security Note: This tool only returns the generated code, not the underlying TOTP secret. Use redactFields in agent configuration to prevent access to raw secrets while still allowing code generation.
Copy a vault entry's password field to the system clipboard without exposing the value to the agent. The clipboard auto-clears after 30 seconds.
Request:
{
"tool": "copy_to_clipboard",
"arguments": {
"path": "github"
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Entry path whose password field to copy |
Response:
{
"success": true,
"path": "github",
"clears_at": "2026-05-05T15:30:00Z"
}Notes:
- Requires
canUseClipboard: truein agent profile (separate fromcanWrite) - The password value is never exposed in the MCP response
- Clipboard is automatically cleared after 30 seconds
- Only copies the
passwordfield of the entry
Errors:
clipboard_denied: Agent profile hascanUseClipboard: falsenot_found: Entry does not exist
Type a vault entry's field value as keyboard input into the currently focused application without exposing the value to the agent.
Request:
{
"tool": "autotype",
"arguments": {
"path": "github",
"field": "password"
}
}Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
path |
string | Yes | - | Entry path |
field |
string | No | password |
Field name to type (e.g., password, username) |
Response:
{
"success": true,
"path": "github",
"field": "password"
}Notes:
- Requires
canUseAutotype: truein agent profile (separate fromcanWrite) - The field value is never exposed in the MCP response
- Types into the currently focused application window
- Cross-platform: macOS, Linux (via xdotool), Windows (via AutoIt)
- Falls back gracefully on unsupported platforms
Errors:
autotype_denied: Agent profile hascanUseAutotype: falsenot_found: Entry or field does not exist
Prompt the user for sensitive data via an interactive TTY and store it without exposing the value to the agent. Only available in stdio mode with a TTY.
Request:
{
"tool": "secure_input",
"arguments": {
"path": "new-service",
"field": "password",
"description": "Enter the password for new-service"
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Entry path to store the value |
field |
string | Yes | Field name to store the value under |
description |
string | No | Optional description shown to the user in the prompt |
Response:
{
"success": true,
"path": "new-service",
"field": "password"
}Notes:
- Available whenever any secure-input backend is reachable: an interactive TTY
(stdio mode), or a native GUI dialog (macOS
osascript, Linuxzenity/kdialog, WindowsGet-Credential). SetSYMVAULT_SECUREUI=tty|gui|noneto override the auto-detected backend. - The agent never sees the value being stored
- Requires
canWrite: truein agent profile - Triggers automatic git commit (if enabled)
Agent-initiated counterpart to secure_input. Use this when, during a task, the
agent discovers an expected vault entry is missing. The user gets a native
input dialog with the agent's stated reason; the value is stored at the
requested path and never returned to the agent.
Request:
{
"tool": "request_credential",
"arguments": {
"path": "github/api-token",
"field": "token",
"reason": "Needed to push to main on the symvault repo"
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Vault path to store the new credential |
field |
string | Yes | Field name (e.g. token, password, api_key) |
reason |
string | Yes | Short reason shown verbatim in the dialog |
Response:
{
"success": true,
"path": "github/api-token",
"field": "token"
}Notes:
- Same backend rules as
secure_input(TTY or native GUI;SYMVAULT_SECUREUIoverride applies) reasonis shown to the user — agents should write it as a clear, human-readable sentence- Recommended call site: after
find_entries/get_entryreturns nothing for an expected path, instead of asking the user for the secret in chat
Validate a payment entry, show a native approval prompt with merchant/amount/currency details, and on user approval autotype the card or bank account fields into the focused checkout window. Card number, CVC, and IBAN values are never returned in the tool response.
Request:
{
"tool": "prepare_payment",
"arguments": {
"entry_path": "payments/mycard",
"merchant": "shop.example",
"amount": "75.00",
"currency": "EUR"
}
}Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
entry_path |
string | Yes | - | Vault path of the payment entry |
merchant |
string | Yes | - | Merchant name or origin (e.g. shop.example) |
amount |
string | Yes | - | Payment amount (e.g. 75.00) |
currency |
string | Yes | - | Currency code (e.g. EUR, USD) |
description |
string | No | - | Optional description shown in the approval prompt |
Response:
{
"success": true
}Security guarantees:
- Card number, CVC, and IBAN values are never returned in the MCP response
- The user sees a native approval prompt (e.g. "Allow payment of EUR 75.00 to shop.example?") before any autotyping occurs
- Approval mode must not be
deny; the tool requires user interaction - Risk level: Critical — cannot be remembered across sessions
- Requires
canUseAutotype: truein agent profile
Autotype field order:
- Card entries:
card_number→expiry_month→expiry_year→cvc - Bank account entries:
iban
Notes:
- Entry must have
type: "payment"in its secret metadata - Payment subtype (
cardorbank_account) is read from the entry'ssubtypefield or the requestsubtypeargument - Same autotype backend as
autotypetool (macOS, Linux, Windows) - Falls back gracefully on unsupported platforms
Errors:
denied: User declined the approval promptnot_a_payment_entry: Entry type is notpaymentnot_found: Entry does not existoutside_allowed_scope: Path is outside the agent's allowed scopepayment.policy_denied: Payment policy check failed (merchant not allowed, currency mismatch, or amount limit exceeded)
Payment policies provide declarative guardrails on prepare_payment requests. When an agent profile references a policy, the enforcer checks merchant allowlists, per-transaction limits, per-day limits, and currency requirements before the native approval prompt is shown.
Define policies under paymentPolicies in config.yaml:
paymentPolicies:
shopping-limited:
instrument: "payments/visa" # vault entry path of the payment instrument
allowed_merchants:
- "amazon.de"
- "otto.de"
- "mediamarkt.de"
max_amount:
per_transaction: "75.00"
per_day: "150.00"
currency: "EUR"Link a policy to an agent profile:
agents:
hermes:
allowedPaths: ["*"]
canWrite: true
approvalMode: none
paymentPolicy: "shopping-limited" # reference the policy by name| Field | Type | Required | Description |
|---|---|---|---|
instrument |
string | Yes | Vault entry path of the payment instrument (card/bank account) |
allowed_merchants |
array | No | Allowlist of merchant names (case-insensitive exact match) |
max_amount.per_transaction |
string | No | Maximum single-transaction amount (decimal string, e.g. "75.00") |
max_amount.per_day |
string | No | Maximum total per calendar day (decimal string, e.g. "150.00") |
currency |
string | Yes when limits set | Required ISO-4217 currency code (e.g. "EUR", "USD") |
- The agent calls
prepare_paymentwithentry_path,merchant,amount,currency. - If the agent has a
paymentPolicy, the enforcer checks:- Merchant allowlist: if
allowed_merchantsis non-empty, the merchant must match (case-insensitive). - Currency: must match the policy's
currencywhen limits are set. - Per-transaction:
amountmust not exceedper_transaction. - Per-day:
amount+ today's accumulated total must not exceedper_day.
- Merchant allowlist: if
- If any check fails, the request is rejected with a
payment.policy_deniedaudit event. The native approval prompt is never shown. - If all checks pass, the native approval prompt is shown as usual. On approval, the daily total is incremented.
Per-day totals are stored on disk at $XDG_DATA_HOME/symaira-vault/payment-state/<vault-hash>/<policy>/daily-totals.json. Entries older than today are automatically expired on load and save. Totals survive daemon restarts.
| Reason | Description |
|---|---|
merchant_not_allowed |
Merchant is not in the policy's allowed_merchants list |
currency_mismatch |
Requested currency does not match the policy's required currency |
over_per_transaction |
Amount exceeds the per-transaction limit |
over_per_day |
Amount + today's total would exceed the per-day limit |
Store or update a single field in an entry. Requires write permission.
Request:
{
"tool": "set_entry_field",
"arguments": {
"path": "new-service",
"field": "password",
"value": "secure-password-here"
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Entry path |
field |
string | Yes | Field name (e.g., "password", "username", "api_key") |
value |
string | Yes | Field value |
Response:
{
"success": true,
"path": "new-service",
"field": "password",
"version": 6
}Notes:
- Creates entry if it doesn't exist
- Updates existing field or adds new one
- Triggers automatic git commit (if enabled)
- Requires
canWrite: truein agent profile
Errors:
access_denied: Agent profile hascanWrite: falseapproval_required: Agent profile hasapprovalMode: prompt(degrades to deny in MCP)
Execute a command on the host with secrets injected as environment variables.
Request:
{
"tool": "run_command",
"arguments": {
"command": ["curl", "-H", "Authorization: Bearer $API_KEY", "https://api.github.com/user"],
"env": {
"API_KEY": "github.api_key"
},
"working_dir": "/tmp",
"timeout": 30
}
}Some consumers need a file path, not an environment variable — e.g. a
certificate a tool opens by path. files materializes each referenced secret
into an ephemeral, 0600, owner-only file for the lifetime of the command,
exposed as $SYMVAULT_FILE_<name>; the file is shredded and removed once the
command finishes, whether it succeeds, fails, or times out. A plain string
value is treated as raw text (same convention as env); use the
{"ref": ..., "encoding": "base64"} form to decode base64-encoded binary
content (e.g. a PKCS#12 certificate) before it is written to disk:
{
"tool": "run_command",
"arguments": {
"command": ["ericctl", "send", "--cert", "$SYMVAULT_FILE_CERT"],
"files": {
"CERT": {"ref": "elster/org-zertifikat.pfx", "encoding": "base64"}
},
"env": {
"ELSTER_PIN": "elster/org-zertifikat.pin"
}
}
}Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
command |
array | Yes | - | Command and arguments as strings |
env |
object | No | {} |
Map of env var names to secret refs (e.g. {"API_KEY": "github.api_key"}) |
files |
object | No | {} |
Map of names to secret refs (string) or {"ref", "encoding"} objects, materialized as ephemeral $SYMVAULT_FILE_<name> files |
working_dir |
string | No | current dir | Working directory for the command |
timeout |
number | No | 30 | Timeout in seconds |
Response:
{
"exit_code": 0,
"stdout": "...",
"stderr": "",
"duration_ms": 245
}Notes:
- Requires
canRunCommands: truein agent profile (separate fromcanWrite) - Each secret ref (
envandfiles) is scope-checked individually - Secret values are never exposed in the MCP response or audit logs — audit logs record
filesrefs, never their content - Output is capped at 100KB per stream to prevent context bloat
- Timeout kills the process with exit code
-1, and anyfilesare still removed
Errors:
run_denied: Agent profile hascanRunCommands: falsescope_denied: Secret ref path is outside agent's allowed scopeapproval_required: Agent profile requires approval
Delete a password entry. Requires write permission.
Request:
{
"tool": "delete_entry",
"arguments": {
"path": "old-service"
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Entry path to delete |
Response:
{
"success": true,
"path": "old-service",
"deleted_at": "2026-04-21T10:30:00Z"
}Notes:
- Permanent deletion (no recycle bin)
- Triggers automatic git commit (if enabled)
- Requires
canWrite: truein agent profile
Deprecated: Use delete_entry instead. This is a legacy alias maintained for backward compatibility.
Symaira Vault advertises the MCP prompts capability. In MCP clients that surface
prompts as slash commands (Claude Code, OpenCode, Hermes, …) four guided
credential workflows become available once the server is connected. The server
implements the standard prompts/list and prompts/get JSON-RPC methods.
Returns all available prompts with their argument schemas.
Request:
{"jsonrpc": "2.0", "id": 1, "method": "prompts/list"}Response:
{
"prompts": [
{
"name": "add-credential",
"description": "Guided workflow to add a new credential …",
"arguments": [
{"name": "service_name", "description": "…", "required": false},
{"name": "path", "description": "…", "required": false}
]
},
...
]
}Renders the prompt body for a given prompt and argument map. The MCP client injects the returned messages into the conversation.
Request:
{
"jsonrpc": "2.0", "id": 2,
"method": "prompts/get",
"params": {
"name": "add-credential",
"arguments": {"service_name": "GitHub"}
}
}Response:
{
"description": "Guided workflow to add a new credential …",
"messages": [
{"role": "user", "content": {"type": "text", "text": "Add a new credential …"}}
]
}| Name | Required args | Description |
|---|---|---|
add-credential |
– | Walks the agent through adding a vault entry. Sensitive fields routed through request_credential. Optional args: service_name, path. |
rotate-credential |
path |
Generates a new password, stores it, reminds the user to update the remote service. Optional: length (default 32). |
find-and-use |
query |
Searches the vault and suggests the right consumption tool (copy_to_clipboard, autotype, execute_with_secret). Optional: task. |
share-credential |
path, to_agent |
Creates a share grant and explains the human-approval flow. Optional: ttl (default 1h), secret_field. |
In Claude Code the prompts appear as /mcp__symvault__add-credential (and
similar). The displayed argument form is generated automatically from each
prompt's argument schema.
{
"error": {
"code": "error_code",
"message": "Human-readable error description",
"details": {}
}
}| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
not_found |
404 | Entry or resource not found | Verify the path exists with list_entries |
access_denied |
403 | Agent not authorized for this operation | Check agent profile in config.yaml |
vault_locked |
403 | Vault is locked | Run symvault unlock |
invalid_request |
400 | Malformed request | Check JSON syntax and parameters |
missing_parameter |
400 | Required parameter missing | Include all required fields |
invalid_parameter |
400 | Parameter value invalid | Check parameter constraints |
write_denied |
403 | Agent cannot write | Set canWrite: true in profile |
run_denied |
403 | Agent cannot execute commands | Set canRunCommands: true in profile |
approval_required |
403 | Operation requires approval | approvalMode: prompt degrades to deny in MCP |
rate_limited |
429 | Too many requests | Wait and retry |
internal_error |
500 | Server error | Check server logs, restart server |
not_implemented |
501 | Tool not available | Verify Symaira Vault version |
{
"error": {
"code": "vault_locked",
"message": "Vault is locked. Please run symvault unlock. first.",
"details": {}
}
}Resolution:
symvault unlock
# Enter passphrase{
"error": {
"code": "access_denied",
"message": "Agent 'readonly-agent' is not allowed to access path 'work/aws'",
"details": {
"agent": "readonly-agent",
"path": "work/aws",
"allowed_paths": ["personal/*"]
}
}
}Resolution: Update agent profile allowedPaths to include the path pattern.
{
"error": {
"code": "write_denied",
"message": "Agent 'claude-code' does not have write permission",
"details": {}
}
}Resolution: Set canWrite: true in the agent profile.
Symaira Vault implements rate limiting to prevent abuse and ensure fair resource usage.
| Operation Type | Limit | Window |
|---|---|---|
| Read operations | 100 | 60 seconds |
| Write operations | 20 | 60 seconds |
| Password generation | 50 | 60 seconds |
| Health checks | Unlimited | - |
When rate limit is exceeded:
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Retry after 30 seconds.",
"details": {
"retry_after": 30
}
}
}- Cache credentials: Don't fetch the same entry repeatedly
- Use metadata for cache validation: Check
versionbefore fetching full entry - Batch operations: Minimize individual tool calls
- Handle rate limits gracefully: Implement exponential backoff
Agent profiles are defined in the vault configuration file (~/.symvault/config.yaml).
~/.symvault/config.yaml
agents:
<profile-name>:
allowedPaths: ["*"] # Path patterns agent can access
canWrite: false # Whether agent can create/update/delete entries
canRunCommands: false # Whether agent can execute commands with secrets
canUseClipboard: false # Whether agent can copy passwords to clipboard
canUseAutotype: false # Whether agent can type passwords via autotype
approvalMode: "none" # Approval behavior: none | deny | prompt
redactFields: [] # Fields to redact from responses| Field | Type | Default | Description |
|---|---|---|---|
allowedPaths |
array | ["*"] |
Path patterns the agent can access. Use * for all paths, or prefixes like ["work/", "personal/"] |
canWrite |
boolean | false |
Whether the agent can modify vault entries |
canRunCommands |
boolean | false |
Whether the agent can execute commands with secret env injection via run_command |
canUseClipboard |
boolean | false |
Whether the agent can copy passwords to clipboard via copy_to_clipboard |
canUseAutotype |
boolean | false |
Whether the agent can type passwords via keyboard input through autotype |
approvalMode |
string | "none" |
Write approval behavior: none (allow), deny (reject), prompt (degrades to deny in MCP) |
redactFields |
array | [] |
Field names to redact from get_entry responses (e.g., ["totp.secret"] shows [REDACTED]) |
exposePaymentValues |
boolean | false |
When true, payment entries (type: payment) expose sensitive fields (card_number, cvc, iban). Disabled by default — these fields are always redacted for agents unless this flag is set |
Symaira Vault includes several pre-configured profiles:
| Profile | allowedPaths |
canWrite |
canRunCommands |
canUseClipboard |
canUseAutotype |
Use Case |
|---|---|---|---|---|---|---|
default |
["*"] |
false |
false |
false |
false |
Read-only access to all entries |
claude-code |
["*"] |
true |
false |
false |
false |
Full vault access for Claude Code |
codex |
["*"] |
false |
false |
false |
false |
Read-only access for Codex |
hermes |
["*"] |
true |
false |
false |
false |
Full vault access for Hermes |
openclaw |
["*"] |
true |
false |
false |
false |
Full vault access for OpenClaw |
opencode |
["*"] |
false |
false |
false |
false |
Read-only access for OpenCode |
agents:
# Read-only agent for production secrets only
prod-reader:
allowedPaths: ["production/*"]
canWrite: false
approvalMode: "deny"
# Write agent for development secrets
dev-writer:
allowedPaths: ["development/*", "staging/*"]
canWrite: true
approvalMode: "none"
# TOTP-only agent (cannot read TOTP secrets)
totp-agent:
allowedPaths: ["*"]
canWrite: false
redactFields: ["totp.secret"]
# Clipboard agent (can copy but not read passwords)
clipboard-agent:
allowedPaths: ["*"]
canWrite: false
canUseClipboard: true
# Full automation agent (run commands + autotype)
automation-agent:
allowedPaths: ["*"]
canWrite: false
canRunCommands: true
canUseAutotype: true| Pattern | Matches |
|---|---|
* |
All paths |
work/* |
All entries under work/ |
work/aws |
Exact path work/aws |
api/* |
All entries under api/ |
["work/*", "personal/*"] |
Both work and personal directories |
The redactFields agent profile setting controls which entry fields are hidden from get_entry responses. Redacted fields appear as [REDACTED] instead of their actual values.
Entries with secret_meta.type = "payment" have their sensitive fields automatically redacted in all agent responses, independent of the profile's redactFields configuration:
| Sensitive Field | Description |
|---|---|
card_number |
Full card number (e.g., 4111111111111111) |
cvc |
Card verification code |
iban |
International bank account number |
Non-sensitive payment fields (cardholder, expiry_month, expiry_year, bic, subtype) are always returned.
To opt out and expose raw payment values, set exposePaymentValues: true on the agent profile. This requires the agent to also have canReadValues: true or value-tool access.
Redaction is applied only to get_entry responses. It does not affect:
list_entries,find_entries, orget_entry_metadata(these never return field values)generate_totp(reads the secret directly from the vault)generate_password(creates new values)- Write operations such as
set_entry_fieldorsecure_input
redactFields is an array of field name patterns. Patterns are matched against the fully-qualified field path using dot notation for nested maps.
| Pattern | Matches | Example |
|---|---|---|
"password" |
Exact top-level field | password |
"totp.secret" |
Exact nested field | totp.secret |
"*" |
All fields | everything |
"totp.*" |
All fields under the totp map |
totp.secret, totp.issuer, totp.algorithm |
The most common use case is preventing agents from reading raw TOTP secrets while still allowing them to generate codes:
agents:
totp-only:
allowedPaths: ["*"]
canWrite: false
redactFields: ["totp.secret"]With this profile:
get_entry githubreturns all fields, buttotp.secretshows[REDACTED]generate_totp githubstill works normally and returns the current TOTP code- The agent can use TOTP-based authentication without ever seeing the underlying seed
agents:
# Redact all TOTP-related fields
totp-redacted:
allowedPaths: ["*"]
canWrite: false
redactFields: ["totp.*"]
# Redact password and TOTP secret
limited-reader:
allowedPaths: ["*"]
canWrite: false
redactFields: ["password", "totp.secret", "api_key"]
# Redact everything (metadata-only access)
metadata-only:
allowedPaths: ["*"]
canWrite: false
redactFields: ["*"]Profile:
agents:
readonly-agent:
allowedPaths: ["*"]
canWrite: false
redactFields: ["totp.secret"]Request:
{
"tool": "get_entry",
"arguments": {
"path": "github"
}
}Response:
{
"path": "github",
"data": {
"password": "mysecretpassword",
"username": "myuser",
"url": "https://github.com",
"totp": {
"secret": "[REDACTED]",
"issuer": "GitHub",
"algorithm": "SHA1"
}
}
}Note that generate_totp github continues to work because it reads the TOTP secret directly from the encrypted vault entry, bypassing the get_entry redaction layer.
// 1. List entries
{
"tool": "list_entries",
"arguments": {}
}
// 2. Get entry metadata for cache check
{
"tool": "get_entry_metadata",
"arguments": {
"path": "api/service-key"
}
}
// 3. Get full entry (if cache miss)
{
"tool": "get_entry",
"arguments": {
"path": "api/service-key",
"include_metadata": true
}
}
// 4. Update field (requires write permission)
{
"tool": "set_entry_field",
"arguments": {
"path": "api/service-key",
"field": "api_key",
"value": "new-api-key-value"
}
}# Set variables
TOKEN=$(cat ~/.symvault/mcp-token)
AGENT="claude-code"
BASE_URL="http://127.0.0.1:8080"
# Health check
curl -s "$BASE_URL/health" | jq .
# List entries
curl -s -X POST "$BASE_URL/mcp" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Symaira-Agent: $AGENT" \
-H "Content-Type: application/json" \
-d '{"tool": "list_entries", "arguments": {}}' | jq .
# Get entry
curl -s -X POST "$BASE_URL/mcp" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Symaira-Agent: $AGENT" \
-H "Content-Type: application/json" \
-d '{"tool": "get_entry", "arguments": {"path": "github"}}' | jq .
# Generate password
curl -s -X POST "$BASE_URL/mcp" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Symaira-Agent: $AGENT" \
-H "Content-Type: application/json" \
-d '{"tool": "generate_password", "arguments": {"length": 20}}' | jq .# Start the server
symvault serve --stdio --agent claude-code
# Send MCP request (via stdin)
echo '{"tool": "list_entries", "arguments": {}}' | symvault serve --stdio --agent claude-code
# Or with a proper MCP client
# The MCP client handles the JSON-RPC framing// Pseudocode for credential caching with Symaira Vault
async function getCredential(path) {
const cached = cache.get(path);
// Check if cached version is stale
const metadata = await mcp.call('get_entry_metadata', { path });
if (!cached || cached.version !== metadata.version) {
// Fetch fresh credential
const entry = await mcp.call('get_entry', {
path,
include_metadata: true
});
cache.set(path, {
data: entry.data,
version: entry.meta.version
});
return entry.data;
}
return cached.data;
}- Agent Integration Guide - Detailed setup for specific agents
- Troubleshooting - Common issues and solutions
- Runbook - Operational procedures and incident response
- README - General usage and installation
-
Token Security: The
mcp-tokenfile contains authentication credentials. Protect it like a password. -
Network Binding: HTTP mode binds to
127.0.0.1by default. Do not expose to public networks without additional security. -
Agent Isolation: Use separate agent profiles for different security contexts.
-
Audit Logging: All MCP operations are logged. Monitor logs for unauthorized access attempts.
-
Write Permissions: Grant write permissions sparingly. Prefer read-only agents when possible.
-
Credential Rotation: If an agent may have logged credentials to chat logs or terminals, rotate those credentials immediately.
| Version | Changes |
|---|---|
| 2.5.0 | Added search and fetch tools for OpenAI Company Knowledge compatibility; added structuredContent field support in tool responses |
| 2.2.0 | Added copy_to_clipboard, autotype, and run_command tools; added canUseClipboard and canUseAutotype agent permissions; added scoped token management |
| 2.0.0 | Added run_command tool, canRunCommands permission, and HTTP MCP authentication |
| 1.0.0 | Initial MCP API documentation |
Maintainer: Symaira Vault Team
Repository: https://github.com/danieljustus/symaira-vault
Security: Report security issues via GitHub Security Advisories