Metadata-Version: 2.4
Name: passkey-mcp
Version: 0.4.3
Summary: Cross-platform secrets manager for MCP servers and CLI tools
Author: Nick Sanchez
License-Expression: MIT
Project-URL: Homepage, https://github.com/TheNickSanchez/passkey-mcp
Project-URL: Issues, https://github.com/TheNickSanchez/passkey-mcp/issues
Project-URL: Source, https://github.com/TheNickSanchez/passkey-mcp
Project-URL: PyPI, https://pypi.org/project/passkey-mcp/
Project-URL: Changelog, https://github.com/TheNickSanchez/passkey-mcp/blob/main/CHANGELOG.md
Keywords: keychain,secrets,mcp,cli
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Security
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: keyring>=24.0.0
Requires-Dist: pyperclip>=1.8.0
Requires-Dist: mcp<2.0.0,>=1.0.0
Requires-Dist: questionary>=2.0.0
Requires-Dist: cryptography>=41.0.0
Requires-Dist: argcomplete>=3.0.0
Dynamic: license-file

# Passkey

A local OS-keychain injector for MCP and CLI secrets. It stores named
credential sets in your system keychain (`keyring`, service name `passkey`)
and injects them as environment variables at runtime.

This is **not** a team password manager, a shared vault, or a 1Password
replacement. Alpha, 0.x.

![How passkey injects secrets: mcp.json to passkey run to OS keychain to child env](docs/architecture.png)

No plaintext secrets in config files. No cloud sync. No network. Primary
protection is the OS keychain ACL. Extra sudo/pkexec is **optional and off
by default** (`passkey config require-auth on`) and **never** applies to
`passkey run` (headless MCP). macOS Keychain ACLs are the strong case;
Linux Secret Service and Windows Credential Manager are best-effort by
comparison (Linux also needs a running, unlocked keyring daemon).

## How it works

1. You store named entries (one or more fields per entry) in the OS keychain.
   Values never go in `mcp.json` or in argv. Interactive input uses `getpass`.
2. `passkey init` (opt-in) rewrites existing MCP configs so the editor starts
   `passkey run ENTRY -- <original command>` instead of a command with
   tokens in `env`. **Install does not scan Claude, Cursor, or other tools.**
   First `passkey` with no args is onboarding (`new`), not a wrap.
3. At start, `passkey run` reads the keychain and injects env vars into **that
   child process only**. MCP tools (`passkey_list`, `passkey_fields`,
   `passkey_status`, `passkey_doctor`, `passkey_wrap_server`) return names,
   field names, and status — **never secret values**.
4. `passkey unwrap` restores the original commands. A `.backup` is written
   before `init` and `unwrap`.

Diagram source: [`docs/architecture.mmd`](docs/architecture.mmd). Disclosure
and threat model: [SECURITY.md](SECURITY.md).

## Features

- Store multiple secrets per entry in the system keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service)
- Inject secrets as environment variables for subprocesses
- **Tool-agnostic** — works with Claude, Gemini, VS Code, Cursor, OpenCode, Windsurf, Cline, Zed, and more
- Interactive fuzzy-search entry picker (arrow keys + type-to-filter)
- **Tab completion** for bash, zsh, and fish
- **Generate** cryptographically secure random secrets (configurable length, guaranteed character diversity)
- **Templates** for popular services (GitHub, AWS, Slack, OpenAI, Stripe, Vercel, PostgreSQL, MySQL)
- **Share & receive** entries via encrypted file + passphrase (no key exchange needed)
- **Lifecycle tracking** — `last_rotated` timestamps, `passkey rotate`, `passkey doctor --deep`
- Import from Chrome password exports, existing MCP configs, or passkey backups
- Encrypted bundle export/import for safe machine-to-machine transfer
- Full audit logging with `passkey audit --summary`
- **OS keychain ACL** protection, with optional **Touch ID** / sudo prompt for interactive terminal commands (opt-in, off by default; never on `passkey run`)
- Cross-platform: macOS (strong Keychain ACLs); Linux and Windows are best-effort vs that

## Installation

```bash
pipx install passkey-mcp==0.4.0
```

or:

```bash
uv tool install passkey-mcp
```

Stay on the pinned pipx line for team installs. Unpinned `pip install
passkey-mcp` is not the documented path.

Installing does **not** wrap MCP configs. Wrapping is `passkey init` after
you have entries you want injected.

**This repo / clone:**

```bash
uv run passkey
```

### Requirements

- Python 3.10+
- System keychain access (macOS Keychain, Windows Credential Manager, or a running Linux keyring daemon)

## Quick Start

```bash
# First run — guided onboarding
passkey
#  ┌─────────────────────────────────────────────┐
#  │           Welcome to passkey                 │
#  │  Secrets in your keychain. Not in config.    │
#  └─────────────────────────────────────────────┘
#  Create your first entry now? [Y/n]

# Create an entry
passkey new
# Entry name: github
# Field name: GITHUB_TOKEN
# Value: [hidden input]

# List all entries
passkey list

# Run a command with secrets injected
passkey run github -- python app.py
```

## Shell Completion

Set up tab completion for your shell:

```bash
# Zsh (macOS default)
eval "$(passkey completion --zsh)"

# Bash
eval "$(passkey completion --bash)"

# Fish
passkey completion --fish > ~/.config/fish/completions/passkey.fish
```

After setup, tab completion works everywhere:

```bash
passkey <TAB>         # Show entries + subcommands
passkey hugg<TAB>     # Fuzzy-match: huggingface_key
passkey get <TAB>     # Complete entry names
passkey run <TAB>     # Pick entries to load
```

## Interactive Mode

When you omit an entry name (or type a partial match), passkey shows an interactive picker with fuzzy search:

```bash
$ passkey get
? Select entry to browse: (Use arrow keys, type to filter)
❯ github
  openai
  slack
  myapi_read
  myapi_write

# Type to filter:
$ passkey get my
# Automatically shows: myapi_read, myapi_write

# Single match auto-selects:
$ passkey get github
Copied 'GITHUB_TOKEN' to clipboard (auto-clears in 30s)

# Shortcut: passkey <entry> opens the picker directly
$ passkey github
```

### Fuzzy Matching

- **Prefix**: `my` matches `myapi_read`, `myapi_write`
- **Contains**: `api` matches `myapi_read`, `myapi_write`
- **Sequence**: `gh` matches `github`
- **Case-insensitive**: `GITHUB` matches `github`

## CLI Reference

### Entry Management

| Command | Description |
|---------|-------------|
| `passkey new` | Create a new entry interactively |
| `passkey list` | List all entry names |
| `passkey list --names-only` | Entry names only (one per line, for scripting) |
| `passkey edit [entry]` | Modify an existing entry |
| `passkey delete [entry]` | Delete an entry (exact match required) |
| `passkey info [entry]` | Show entry metadata and field names |
| `passkey clone SOURCE [DEST]` | Clone an entry under a new name |
| `passkey set-field ENTRY FIELD [VALUE]` | Upsert a single field |

### Retrieving Secrets

| Command | Description |
|---------|-------------|
| `passkey get [entry]` | Interactive field picker — select a field to copy |
| `passkey get [entry] --all` | Copy all fields to clipboard |
| `passkey <entry>` | Shortcut: same as `passkey get <entry>` |

### Running Commands

```bash
# Run with secrets from one entry
passkey run github -- python app.py

# Run with secrets from multiple entries (later entries override)
passkey run aws github -- python deploy.py

# Secrets injected as env vars
passkey run myapi -- curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com/v1/data
```

### Tool Integration

Wrapping is explicit. `passkey init` only sees adapter paths that already
exist on disk (Claude, Cursor, VS Code, …). A machine with none of those
configs is a first run, not a failed install — `passkey doctor` stays
quiet about missing clients.

```bash
# Scan configs and migrate plaintext secrets to keychain
passkey init                    # Auto-detect all tools
passkey init --tool claude      # Specific tool
passkey init --tool vscode

# Restore wrapped configs back to inline commands (the way out)
passkey unwrap                  # Auto-detect all tools
passkey unwrap --dry-run        # Preview first

# Add or update credentials for an MCP server
passkey add my-server                            # Interactive field entry
passkey add my-server --tool claude              # Target a specific tool config
passkey add my-server --fields API_KEY TOKEN     # Field names only; values via getpass

# Show security status across all tools
passkey status

# Run diagnostics
passkey doctor

# List MCP servers across tools
passkey servers
```

Supported tools: `claude`, `claude_desktop`, `gemini`, `vscode`, `cursor`, `opencode`, `windsurf`, `cline`, `zed`

### Export and Backup

```bash
# Export all entries to file (owner-only permissions set automatically)
passkey export backup.json

# Export specific entries
passkey export backup.json --entries github openai

# Export metadata only (no secret values)
passkey export backup.json --no-secrets

# Encrypted export (AES-256-GCM + scrypt)
passkey export backup.passkey.enc --encrypt
```

### Import

```bash
# Import from passkey backup
passkey import backup.json

# Preview without importing
passkey import backup.json --dry-run

# Handle duplicates: skip (default), overwrite, merge
passkey import backup.json --mode merge

# Import from encrypted bundle
passkey import backup.passkey.enc --decrypt

# Import from Chrome password export (CSV)
passkey import passwords.csv --filter github.com

# Import from an existing MCP config file
passkey import claude_desktop_config.json
```

### Audit Log

```bash
passkey audit              # View recent operations
passkey audit --limit 50   # Show more entries
passkey audit --clear      # Clear the log
passkey audit --summary    # Aggregate statistics
```

### Verify Entry Health

```bash
# Exit non-zero if any required fields are missing
passkey check github GITHUB_TOKEN GITHUB_USERNAME
```

### Generate Secrets

```bash
# Generate a 32-char random secret (default)
passkey generate

# Generate with custom length
passkey generate --length 64

# Generate without auto-copying to clipboard
passkey generate --no-copy
```

### Credential Templates

```bash
# List built-in templates
passkey template list

# Show a template's fields
passkey template show github

# Apply a template to create a new entry
passkey template apply github

# Save a custom template from an existing entry
passkey template add my-service --from myapi
```

### Share & Receive

```bash
# Share an entry (generates encrypted file + passphrase)
passkey share github --output github-shared.passkey

# Receive a shared entry
passkey receive github-shared.passkey
```

### Lifecycle Management

```bash
# Mark an entry as rotated (updates last_rotated timestamp)
passkey rotate github

# Run expanded diagnostics
passkey doctor --deep
```

## Authentication

The primary protection for your secrets is the **OS keychain's own access
control** — macOS Keychain, Windows Credential Manager, and Linux Secret
Service all gate access per application and may show their own unlock
prompts. Passkey relies on that by default, so wrapped MCP servers can
start headless (no terminal, no password prompt).

If you want an **additional** OS-auth prompt for interactive terminal
commands (`get`, `new`, `edit`, `export`, ...), opt in:

```bash
passkey config require-auth on
```

The tradeoff, explicitly:

- **Off (default)**: Anyone with an unlocked session as your user can read
  secrets via `passkey` — same as any other tool reading your keychain
  (e.g. `security find-generic-password`). MCP servers work everywhere,
  including stock macOS with no tty.
- **On**: Interactive commands first run `sudo -v` (macOS, Touch ID if
  `pam_tid` is configured) or `pkexec` (Linux). This breaks any
  non-interactive use of those commands — `passkey run` is **never**
  gated either way, because MCP servers invoke it headless.
- **Windows**: the setting is a no-op — UAC cannot authenticate the
  current process, so protection comes from Credential Manager ACLs alone.

## Unwrapping (leaving passkey)

`passkey init` rewrites your MCP configs to the wrapped form — but it is
not a one-way door. Restore the original inline commands with:

```bash
passkey unwrap                      # all detected tools
passkey unwrap --tool claude        # one tool
passkey unwrap --server github      # one server
passkey unwrap --restore-secrets    # also write secret values back into the config
passkey unwrap --dry-run            # preview only
```

A `.backup` of each config is written before changes (init does this too),
so you can also restore by hand.

## MCP Config Example

`passkey init` rewrites detected servers from the first form to the second.
`passkey unwrap` goes the other way.

**Before (insecure — secret in plaintext):**
```json
{
  "mcpServers": {
    "github": {
      "command": "python",
      "args": ["-m", "github.server"],
      "env": {
        "GITHUB_TOKEN": "ghp_PLAINTEXT_SECRET_HERE"
      }
    }
  }
}
```

**After (secure — secret loaded from keychain at runtime):**
```json
{
  "mcpServers": {
    "github": {
      "command": "passkey",
      "args": ["run", "github", "--", "python", "-m", "github.server"]
    }
  }
}
```

## MCP Server Mode

Passkey can run as an MCP server itself, letting AI assistants invoke passkey tools via natural language:

```json
{
  "mcpServers": {
    "passkey": {
      "command": "passkey-mcp-server"
    }
  }
}
```

Available tools: `passkey_list`, `passkey_fields`, `passkey_status`, `passkey_doctor`, `passkey_wrap_server`.

AI assistants can **discover** entry names but never see secret values — secrets only flow out via `passkey run`.

## Data Storage

| Platform | Data directory |
|----------|---------------|
| macOS    | `~/Library/Application Support/passkey/` |
| Linux    | `~/.config/passkey/` (respects `$XDG_CONFIG_HOME`) |
| Windows  | `%APPDATA%\passkey\` |

Override with `PASSKEY_DATA_DIR`. Secrets themselves are in the **system
keychain**, not in that directory. On disk you get metadata only: lock file,
audit log, and `entries.json` (names — not values).

## Security

- **Encrypted at rest**: System keychain handles encryption (AES-256-GCM on macOS, DPAPI on Windows)
- **No secrets logged**: Only entry names and operation types appear in the audit log
- **Hidden input**: `getpass` used for all secret entry
- **Clipboard auto-clear**: Copied secrets cleared after 30 seconds
- **Secure export**: Files created with `chmod 600` (owner-only)
- **Encrypted bundles**: AES-256-GCM + scrypt (N=2^20) for portable transfer
- **LLM-safe**: AI assistants can list entries but never read values
- **No network**: no telemetry, no cloud sync; supply chain is the install path

See [SECURITY.md](SECURITY.md) for the disclosure policy and threat model
(private vulnerability reporting; aim to acknowledge within 7 days).

## Troubleshooting

### "Failed to access Keychain" (macOS)
Grant Terminal / Python keychain access in **System Settings → Privacy & Security**.

### Touch ID not working
Ensure `pam_tid.so` is configured in `/etc/pam.d/sudo`:
```bash
sudo sed -i '' '2i\
auth       sufficient     pam_tid.so
' /etc/pam.d/sudo
```

### Linux headless / no keyring daemon
Set a backend explicitly:
```bash
export PYTHON_KEYRING_BACKEND=keyring.backends.SecretService.Keyring
# or for headless environments:
export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring
```

### Entry not found
```bash
passkey list   # check exact names (case-sensitive)
```

### Import shows "skipped (exists)"
```bash
passkey import backup.json --mode merge
```

## Development

Python 3.10+. **`uv` only** — see [CONTRIBUTING.md](CONTRIBUTING.md).

```bash
uv sync
uv run pytest -q
uv run ruff check passkey/ tests/
```

Regenerate the architecture graphic (requires `@mermaid-js/mermaid-cli`):

```bash
mmdc -i docs/architecture.mmd -o docs/architecture.svg -b transparent
mmdc -i docs/architecture.mmd -o docs/architecture.png -b '#ffffff' -s 2
```

## License

MIT — see [LICENSE](LICENSE)
