Claude Code × MCP で APIキー・シークレットを統合管理する — 1Password / GitHub Secrets / Vault の3パターン実装
Claude Code環境でシークレットをMCPで統合管理する3パターンを実装レベルで解説。1Password op://URI連携、GitHub Codespaces・Actions連携、HashiCorp Vault dynamic secrets。mcp.json設定例とセキュリティリスク評価付き。
エンジニアのゆとです。
1Passwordの設定方法もシークレット管理の設計論も個別に書いてきた。


今回は「MCPプロトコルとしてシークレットを扱うとどうなるか」という角度で書く。ツールの使い方ではなく、「MCPがなぜシークレット管理に向くか」というアーキテクチャの話から始める。
なぜシークレット管理にMCPが向くのか
MCPの正式名称はModel Context Protocol。Anthropicが設計した、AIモデルと外部ツールをつなぐ標準プロトコルだ。
普通「MCPでシークレット管理」と言うと、「MCPサーバーがシークレット管理ツールへのインターフェースを提供する」という意味になる。1Password MCPを例にすると、Claudeが「この変数の値が必要」と判断したとき、1PasswordのVaultから実行時に値を取得してクライアントに渡す。
これがなぜ良いかを整理すると:
設定ファイルへの平文書き込みを排除できるclaude_desktop_config.json にAPIキーを書くと、Dotfilesでバックアップしたとき・バックアップソフトがクラウド同期したとき・Claudeのログに出力されたとき、に漏洩リスクがある。MCPを経由すると、設定ファイルには op://Vault/Item/Field という参照パスだけが残る。
Claudeがシークレットにアクセスするたびに、MCPサーバー側でログを取れる。「誰がいつどのシークレットを読んだか」の監査証跡が作れる。これは .env では不可能だ。
シークレットの実体をMCPサーバー側(VaultやGitHub Secrets)に置いておけば、ローテーション時に参照パスを変える必要がない。op://Personal/Stripe/api_key というパスは変わらず、Vault側の値だけ更新すれば全プロジェクトに即時反映される。
MCPサーバーごとに「どのVault・どのシークレットへのアクセスを許可するか」を設定できる。プロジェクトAのMCP設定ではプロジェクトAのシークレットだけ、プロジェクトBでは別のセット、という分離ができる。
前提: MCP設定ファイルの場所と基本構造
Claude Codeのグローバル設定は ~/.claude/claude_desktop_config.json、プロジェクト固有の設定は .mcp.json(プロジェクトルート)に書く。
// ~/.claude/claude_desktop_config.json (全プロジェクト共通のMCP設定)
{
"mcpServers": {
"1password": {
"command": "op-mcp",
"env": {
"OP_SERVICE_ACCOUNT_TOKEN": "ops_xxxxx"
}
}
}
}
// .mcp.json (プロジェクト固有)
{
"mcpServers": {
"project-secrets": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-custom"],
"env": {}
}
}
}
env ブロックに書いた値はMCPサーバーの起動時に環境変数として渡る。ここに平文でシークレットを書くのが「従来の問題」だ。以下の3パターンはこの env ブロックからシークレットを追い出すアプローチになる。
パターン1: 1Password CLI 連携(op:// URI)
最も完成度が高い方法。1Password CLI(op コマンド)が op:// URI を環境変数に展開する機能を使う。
op run -- コマンドを使うと、起動するコマンドに渡る環境変数の中の op:// URI を、実行時にVaultから取得した値で置換してから起動する。
# 直接起動した場合(値が展開される)
op run -- node server.js
# op:// URIを環境変数に設定しておけば、値をインライン展開してくれる
OPENAI_API_KEY="op://Personal/OpenAI/api_key" op run -- node server.js
Claude Codeとの連携では、MCPサーバーの起動コマンドを op run -- でラップする:
// ~/.claude/claude_desktop_config.json
{
"mcpServers": {
"github": {
"command": "op",
"args": [
"run",
"--no-masking",
"--",
"npx",
"-y",
"@modelcontextprotocol/server-github"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "op://Personal/GitHub-PAT-MCP/token"
}
},
"stripe": {
"command": "op",
"args": [
"run",
"--no-masking",
"--",
"npx",
"-y",
"@modelcontextprotocol/server-stripe"
],
"env": {
"STRIPE_SECRET_KEY": "op://Personal/Stripe-Dev/api_key"
}
}
}
}
op:// URI の構造
op://[Vault名]/[Item名]/[Field名]
例:
op://Personal/OpenAI/api_key
op://Work/AWS-Dev/access_key_id
op://Work/AWS-Dev/secret_access_key
Vault名・Item名・Field名はいずれも1Passwordに登録してあるものと一致させる必要がある。スペースがある場合はダッシュか、URLエンコード(%20)で書く。
Service Account Tokenを使う場合上記は「ローカルにデスクトップアプリがある前提」の方法だ。CI/CDや、デスクトップアプリのない環境では Service Account Token を使う。
1Password の管理コンソールで Service Account を作成し、アクセスするVaultを制限した上でトークンを発行する。
# Service Account Token を使う場合
export OP_SERVICE_ACCOUNT_TOKEN="ops_xxxxxxxxxx"
# op run の動作は同じ
op run -- node server.js
Claude Codeの設定でも同様:
{
"mcpServers": {
"github": {
"command": "op",
"args": ["run", "--", "npx", "-y", "@modelcontextprotocol/server-github"],
"env": {
"OP_SERVICE_ACCOUNT_TOKEN": "ops_xxxxxxxxxx",
"GITHUB_PERSONAL_ACCESS_TOKEN": "op://CI/GitHub-MCP/token"
}
}
}
}
ここで「Service Account Tokenを env に平文で書いたら意味ないのでは?」と思った人は正しい。完全に解決するには、このトークン自体も環境変数(~/.zshenv や ~/.zprofile)で管理して、設定ファイルには $OP_SERVICE_ACCOUNT_TOKEN のように参照だけ書く。
{
"mcpServers": {
"github": {
"command": "op",
"args": ["run", "--", "npx", "-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "op://Personal/GitHub-PAT/token"
}
}
}
}
~/.zprofile に export OP_SERVICE_ACCOUNT_TOKEN="ops_xxxxx" を書いておけば、Claude Code起動時にシェルが環境変数を読み込んで op run が認証される。設定ファイルには何も書かなくて済む。

パターン2: GitHub Secrets 連携
GitHub の Secrets 機能を Claude Code や MCP の文脈で使う方法。「Codespaces での開発」と「Actions でのCI連携」の2サブパターンがある。
2-A: GitHub Codespaces でのシークレット利用Codespaces 上で Claude Code を使う場合、GitHub の「Codespaces secrets」機能でシークレットを設定すると、コンテナ起動時に環境変数として注入される。
# GitHub CLI でCodespacesシークレットを設定
gh secret set OPENAI_API_KEY --app codespaces
# organizationレベルで全リポジトリに適用
gh secret set OPENAI_API_KEY --app codespaces --org myorg
Codespaces 側でのClaudeの設定:
// ~/.claude/claude_desktop_config.json(Codespaces上)
{
"mcpServers": {
"openai-tools": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-openai"],
"env": {
"OPENAI_API_KEY": "${OPENAI_API_KEY}"
}
}
}
}
${OPENAI_API_KEY} は起動シェルの環境変数を参照する(Claude Codeの設定ファイルがこの展開に対応しているかはバージョンによって異なる。対応していない場合はシェルスクリプトでラップする方が確実だ)。
より確実な方法:
# start-mcp.sh
#!/bin/bash
# Codespacesシークレットが環境変数に入っているので、それを使ってMCPを起動
exec npx -y @modelcontextprotocol/server-openai
{
"mcpServers": {
"openai-tools": {
"command": "bash",
"args": ["/workspaces/myproject/.devcontainer/start-mcp.sh"]
}
}
}
2-B: GitHub Actions でのMCP利用
Actions でClaude Code Agentを動かす場合(CI/CD や自動化タスク)、Actionsのシークレットを環境変数に渡す:
# .github/workflows/claude-agent.yml
name: Claude Code Agent Task
on:
workflow_dispatch:
inputs:
task:
description: 'Task to execute'
required: true
jobs:
run-claude:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Claude Code
run: npm install -g @anthropic-ai/claude-code
- name: Configure MCP
run: |
mkdir -p ~/.claude
cat > ~/.claude/claude_desktop_config.json << 'EOF'
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {}
}
}
}
EOF
env:
GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.MCP_GITHUB_TOKEN }}
- name: Run Claude Code Task
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.MCP_GITHUB_TOKEN }}
run: |
claude --print "${{ github.event.inputs.task }}"
ここで注意点がある。ActionsのシークレットはJobステップの env: で受け取るが、そのシークレットが claude_desktop_config.json に書き出されると一時ファイルに平文で残る。Actions環境はジョブ終了後に破棄されるので大きなリスクではないが、ジョブのアーティファクトとして保存しないように注意する。
複数リポジトリでClaude CodeのActionsを使う場合、シークレットをorg単位で管理すると楽になる:
# GitHub CLI でorg secrets を設定
gh secret set ANTHROPIC_API_KEY \
--org myorg \
--repos "repo1,repo2,repo3"
# 全リポジトリに適用
gh secret set ANTHROPIC_API_KEY \
--org myorg \
--visibility all
--repos でアクセス可能なリポジトリを制限できる。全リポジトリに visibility all で共有するのは最小権限の観点からは避けた方がいい。
パターン3: HashiCorp Vault 連携(Dynamic Secrets)
チーム開発・本番環境での運用に最も適したパターン。Vault の「Dynamic Secrets」機能を使うと、MCPサーバーを起動するたびに有効期限付きの一時トークンを発行できる。
なぜDynamic Secretsが強いのか従来の「Static Secrets」(固定値のAPIキー)は、漏洩した場合にローテーションするまでリスクが続く。Dynamic Secretsは「リクエストするたびに新しいトークンを生成し、使い終わったら失効させる」という設計だ。
たとえばAWS IAM Role へのアクセスをVaultで管理すると:
Claudeが「AWSのシークレットが必要」とVaultに要求
→ Vault がAWS IAMに対して一時的なAccess Key/Secret Key を発行
→ 有効期限は1時間
→ 1時間後に自動失効
→ 漏洩しても次のリクエストまでに失効している
Vault MCPサーバーの設定
Vault 用の MCP サーバーは公式のものがまだ少ないため、カスタムで作るか、Vault の CLI をラップする形になる。
Vault CLI をラップするシンプルなMCPサーバー(概念実装):
# vault_mcp_server.py
import subprocess
import json
import sys
def get_secret(path: str, field: str) -> str:
"""VaultからシークレットをJSON形式で取得"""
result = subprocess.run(
["vault", "kv", "get", "-format=json", path],
capture_output=True,
text=True
)
if result.returncode != 0:
raise ValueError(f"Vault lookup failed: {result.stderr}")
data = json.loads(result.stdout)
return data["data"]["data"][field]
def get_dynamic_aws_creds(role: str) -> dict:
"""AWS動的クレデンシャルを取得"""
result = subprocess.run(
["vault", "read", "-format=json", f"aws/creds/{role}"],
capture_output=True,
text=True
)
if result.returncode != 0:
raise ValueError(f"AWS creds lookup failed: {result.stderr}")
data = json.loads(result.stdout)
return {
"access_key": data["data"]["access_key"],
"secret_key": data["data"]["secret_key"],
"lease_id": data["lease_id"],
"lease_duration": data["lease_duration"]
}
claude_desktop_config.json ではこのサーバーを指定:
{
"mcpServers": {
"vault": {
"command": "python3",
"args": ["/Users/yuto/.claude/vault_mcp_server.py"],
"env": {
"VAULT_ADDR": "https://vault.example.com",
"VAULT_TOKEN": "${VAULT_TOKEN}"
}
}
}
}
VAULT_TOKEN はシェルの環境変数から取るか、vault login コマンドで認証済みの状態を使う。
CI/CDではVaultにロールを作って、Role IDとSecret IDで認証する AppRole が一般的:
# Vault設定(管理者が一度だけ実行)
vault auth enable approle
vault policy write claude-code-policy - <<EOF
path "secret/data/claude-code/*" {
capabilities = ["read"]
}
path "aws/creds/claude-code-role" {
capabilities = ["read"]
}
EOF
vault write auth/approle/role/claude-code \
token_policies="claude-code-policy" \
token_ttl="1h" \
token_max_ttl="4h"
# Role ID と Secret ID を取得(CI/CDのシークレットに登録する)
vault read auth/approle/role/claude-code/role-id
vault write -f auth/approle/role/claude-code/secret-id
CI/CDパイプラインでの認証:
# GitHub Actions での Vault 認証
VAULT_TOKEN=$(vault write -field=token auth/approle/login \
role_id="$VAULT_ROLE_ID" \
secret_id="$VAULT_SECRET_ID")
export VAULT_TOKEN
これで取得した VAULT_TOKEN を MCP サーバーに渡せる。
3パターンのセキュリティリスク評価
実運用での選択基準として、リスクと運用コストを整理する。
1Password op:// URI漏洩リスク: 低(設定ファイルに実値が残らない) アクセス制御: Vault単位・Item単位での制限が可能 監査ログ: 1Password Business以上で利用可能 ローテーション: Vault側で変更すれば自動反映 運用コスト: 低(デスクトップアプリが前提)
弱点: デスクトップアプリが起動していない環境では機能しない。Headless環境ではService Accountが必要。
GitHub Secrets漏洩リスク: 中(Actionsのログにマスクされて出力されるが、設定ミスで露出する可能性) アクセス制御: リポジトリ・org単位での制限が可能 監査ログ: GitHub Enterprise では監査ログあり ローテーション: GitHub UI/CLI で更新(自動ローテーションなし) 運用コスト: 低(GitHub前提の環境なら追加コストなし)
弱点: Static Secretsなので漏洩時のリスクが残る。ローテーションを手動で実施する必要がある。
HashiCorp Vault Dynamic Secrets漏洩リスク: 最低(有効期限付きの一時トークン) アクセス制御: ポリシーで詳細なアクセス制御が可能 監査ログ: 全アクセスを記録(Audit Device) ローテーション: 自動(TTLベース) 運用コスト: 高(Vaultのセルフホストまたは HCP Vault の費用)
弱点: 設定・運用の複雑さが高い。個人開発には過剰。
使い分けの指針個人開発・フリーランス → 1Password op:// URI(操作シンプル、コスト最小)
小チーム・GitHubメイン → GitHub Secrets(運用コスト最小)
中〜大規模チーム・本番 → HashiCorp Vault(セキュリティ・監査が要件)
実際に使っている構成の話
僕の環境では1Password + op:// URI のパターンを使っている。理由はシンプルで、すでに1Passwordを使っていてVaultにAPIキーが入っているからだ。op run -- でラップするだけで既存の管理が活きる。
実際のgrep:
# 設定ファイルにAPIキーが含まれていないことの確認
grep -r "sk-" ~/.claude/ 2>/dev/null
grep -r "ghp_" ~/.claude/ 2>/dev/null
grep -r "AKIA" ~/.claude/ 2>/dev/null
全部空だ。op:// のURI参照だけが残っている。
移行前は claude_desktop_config.json に GitHub PAT と Anthropic API キーが平文で書いてあった。バックアップが Dropbox に同期されていたし、Dotfiles リポジトリに差し込みそうになったこともある。
「設定ファイルにシークレットを書かない」という一点だけでも、MCPを使った管理に移行した価値があった。
FAQ
Claude Code MCP のシークレット管理で1Password と GitHub Secrets どっちがおすすめ?
ローカル開発が中心なら 1Password + op:// URI、CI/CD ベースなら GitHub Secrets + Codespaces。両方併用も普通。1Password CLI は op run -- claude のラッパーが手軽で、設定ファイルに op://Vault/Item/field の参照だけ書けば実APIキーは漏れない。GitHub Codespaces 利用なら secrets.MY_KEY を Codespace 環境変数に自動注入。
op:// URI を mcp.json に書くだけで動く?
そのままでは動かない。Claude Code は claude_desktop_config.json / mcp.json の値を literal な文字列として読むため、op:// 解釈はされない。op run -- claude で 1Password CLI ラッパー越しに起動 → op が op:// URI を実値に置換して環境変数として渡す、という流れが必要。claude_desktop_config.json に env: { "MY_KEY": "op://..." } と書いて op run -- claude で起動するパターンが定番。
HashiCorp Vault の Dynamic Secrets は個人開発でも使う価値ある?
正直、個人開発レベルなら オーバースペック。Dynamic Secrets の真価は「TTL付きで自動失効・自動ローテーション」なので、AWS / DB クレデンシャルが頻繁にローテーションされる本番運用で活きる。個人なら 1Password で十分。チーム規模3人以上、SOC2/ISO27001 対応必要、シークレットローテーション義務、のいずれかが当てはまるなら Vault 検討。
MCP サーバーが落ちたらシークレットも止まる?
そう。MCP サーバーが落ちると Claude Code はそのサーバー経由のシークレットを取得できなくなる。逆に言えば、シークレットが MCP に依存する場合、MCP サーバーの可用性が SLA の上限になる。1Password CLI は localファイル直接 に近いので落ちにくい、Vault は ネットワーク依存なので障害時に止まる。重要なシークレットは複数経路(local file + MCP)の冗長化を検討。
Claude Code の .env ファイル直接読みとMCPシークレット管理、どう使い分ける?
.env 直接読みは シンプルだが個人マシン限定(gitignoreされていればOK)。MCP シークレット管理は チーム共有・複数マシン・rotation 対応 が必要なときの定石。一人で固定マシンで開発するだけなら .env で十分。Codespaces や VPS との往復、複数人開発、定期 rotation のどれかが入った瞬間に MCP 化が割に合う。
1Password CLI を導入するコストは?
商用利用なら Teams プラン以上($7.99/user/月)が必要。個人利用は Family プランか個人プランで十分。CLI 自体は brew install 1password-cli で1分。op signin で認証、op run -- claude で起動 → 設定はこれだけ。Touch ID / Watch 経由の認証もサポート。
Claude Code MCP のシークレットを GitHub Actions に渡すには?
Actions の secrets を mcp.json のenv に注入する形が一般的。.github/workflows/*.yml で env: { "ANTHROPIC_API_KEY": "${{ secrets.ANTHROPIC_API_KEY }}" } と渡して、Action 内で claude を起動。secretsはログには *** でマスクされる。Actions の OIDC + Vault 連携も可能だが、setup overhead が大きいので、まず secrets 直接渡しから。
mcp.json と claude_desktop_config.json はどっち使う?
Claude Code 単体運用なら ~/.claude/mcp.json。Claude Desktop(GUI)と Code を両方使うなら、Claude Desktop は ~/Library/Application Support/Claude/claude_desktop_config.json を読むので、両方に同じ MCP 設定を書く or symlink で同期。Claude Code は .claude/mcp.json プロジェクト固有版も読むので、プロジェクト別にMCP切り替えしたいならこっち。
関連記事


