Storing API keys for Claude Code providers in KeePassXC

4 min read Original article ↗

A growing number of providers expose Anthropic-compatible API endpoints for Claude Code:

They all work the same way: set ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN, and Claude Code talks to them instead of Anthropic. Their docs typically suggest putting the key straight into ~/.claude/settings.json:

json

{
  "env": {
    "ANTHROPIC_BASE_URL": "https://api.minimax.io/anthropic",
    "ANTHROPIC_AUTH_TOKEN": "<MINIMAX_API_KEY>"
  }
}

That, unfortunately, involves saving a plaintext secret on disk. Plaintext secrets anywhere near Claude Code are risky – it has been known to read .env files and leak their contents into session transcripts and tool-result logs under ~/.claude/, and settings.json itself is no safer. A popular workaround is a ~/.secrets file sourced from your shell profile, but that still means the key is stored in plaintext, just somewhere else.

One nice way of going around this that I found useful was to use keepassxc-proxy-getpw, which talks to a running KeePassXC instance over its browser integration protocol and returns the password for a matching entry. We can then wrap it in a fish function and make sure the key never touches disk:

fish

function claude-minimax \
    --wraps='claude' \
    --description 'Run Claude Code against MiniMax M2.7 API'
        ANTHROPIC_BASE_URL="https://api.minimax.io/anthropic" \
        ANTHROPIC_AUTH_TOKEN=(keepassxc-proxy-getpw \
            "https://api.minimax.io" \
            --filter name="MiniMax API Key") \
        ANTHROPIC_MODEL="MiniMax-M2.7" \
        CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC="1" \
        API_TIMEOUT_MS="3000000" \
        claude $argv
end

There are two optional (but relevant!) env variables added to the “alias function” above, that it might make sense to keep there in general. CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 stops Claude Code from phoning home to Anthropic for telemetry and update checks, which you may not want to do for a third-party provider anyway. API_TIMEOUT_MS bumps the request timeout to avoid cuts on longer responses.

The --wraps='claude' flag gives the function the same tab completions as claude itself.

This approach is very general – here is the same thing for Z.AI:

fish

function claude-zai \
    --wraps='claude' \
    --description 'Run Claude Code against Z.AI GLM-4.7'
        ANTHROPIC_BASE_URL="https://api.z.ai/api/anthropic" \
        ANTHROPIC_AUTH_TOKEN=(keepassxc-proxy-getpw \
            "https://api.z.ai" \
            --filter name="Z.AI API Key") \
        ANTHROPIC_MODEL="GLM-4.7" \
        CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC="1" \
        API_TIMEOUT_MS="3000000" \
        claude $argv
end

The env vars are set inline as prefixes to claude, so they only exist for that process – they won’t show up in env or leak into the rest of your shell session. And since the key is fetched at invocation time, there’s nothing to update when you rotate it in KeePassXC.

In bash/zsh the equivalent uses $() instead of fish’s () for command substitution. A function (rather than an alias) keeps the _key variable scoped:

bash

claude-minimax() {
    local _key
    _key=$(keepassxc-proxy-getpw "https://api.minimax.io" \
        --filter name="MiniMax API Key") &&
    ANTHROPIC_AUTH_TOKEN=$_key \
    ANTHROPIC_BASE_URL="https://api.minimax.io/anthropic" \
    ANTHROPIC_MODEL="MiniMax-M2.7" \
    CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC="1" \
    API_TIMEOUT_MS="3000000" \
    claude "$@"
}

The same pattern works for any provider that uses ANTHROPIC_BASE_URL – just swap the URL, model name, and KeePassXC entry. Alorse/cc-compatible-models maintains a living list of compatible providers if you want to see what else is out there.

Loading provider-specific MCP servers#

Some providers ship their own MCP servers – MiniMax, for example, has minimax-coding-plan-mcp. You probably only want these active when you’re actually using that provider, not polluting every Claude Code session.

Claude Code’s --mcp-config flag loads MCP servers from a standalone JSON file, and --strict-mcp-config ensures only those servers are used (ignoring project .mcp.json, global settings, etc.). Put the config in its own file:

json

// ~/.claude/minimax.mcp.json
{
  "mcpServers": {
    "MiniMax": {
      "command": "uvx",
      "args": ["minimax-coding-plan-mcp", "-y"],
      "env": {
        "MINIMAX_API_KEY": "${ANTHROPIC_AUTH_TOKEN}",
        "MINIMAX_API_HOST": "https://api.minimax.io"
      }
    }
  }
}

Note that MINIMAX_API_KEY references ${ANTHROPIC_AUTH_TOKEN} – MCP server env values inherit from the parent process, so the key fetched by keepassxc-proxy-getpw flows through without a second lookup. We can then wire it into the fish function:

fish

claude --strict-mcp-config \
    --mcp-config ~/.claude/minimax.mcp.json $argv

This way the MiniMax MCP server only starts when you run claude-minimax, and it is also the only MCP server that Claude uses in this particular session.

The same approach works for providers with HTTP-based MCP servers. Z.AI, for example, offers web search, web reader, and vision servers. These use headers for authentication instead of env, but ${VAR} interpolation works there too – I tested it:

json

// ~/.claude/zai.mcp.json
{
  "mcpServers": {
    "web-search-prime": {
      "type": "http",
      "url": "https://api.z.ai/api/mcp/web_search_prime/mcp",
      "headers": {
        "Authorization": "Bearer ${ANTHROPIC_AUTH_TOKEN}"
      }
    },
    "web-reader": {
      "type": "http",
      "url": "https://api.z.ai/api/mcp/web_reader/mcp",
      "headers": {
        "Authorization": "Bearer ${ANTHROPIC_AUTH_TOKEN}"
      }
    },
    "zai-vision": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@z_ai/mcp-server@latest"],
      "env": {
        "Z_AI_API_KEY": "${ANTHROPIC_AUTH_TOKEN}",
        "Z_AI_MODE": "ZAI"
      }
    }
  }
}

The HTTP servers pick up ${ANTHROPIC_AUTH_TOKEN} via headers, and the stdio vision server picks it up via env – both from the same keepassxc-fetched token set by the wrapper function.

Other password managers#

And if you don’t use KeePassXC, the same idea applies with other password manager CLIs: op read for 1Password, bw get password for Bitwarden, or pass show for pass.