アプリケーションサービス部の山本です。 中途社員の研修を担当しています。 が、毎日こちらが学ぶことばかりです。
はじめに
前回の記事:MCP サーバーの API キーを AWS Secrets Manager で管理してみたでは、MCP サーバーの API キーを AWS Secrets Manager で管理する方法を紹介しました。
記事を公開した後、こんな声をいただきました。
- 「Secrets Manager は便利だけど、API キー1つに月 $0.40 はちょっと…」
- 「うちのチームは Parameter Store で統一してるんだけど、同じことできない?」
できます。
本記事では、前回と同じアーキテクチャ(Python ラッパーで API キーを取得 → 環境変数にセット → MCP サーバーを子プロセス起動)を AWS Systems Manager Parameter Store で実現します。Standard パラメータなら 無料 です。
前回記事を読んでいない方でも理解できるよう書いていますが、「なぜ設定ファイルに API キーを書いてはいけないのか」「ラッパー方式の仕組み」については前回記事に詳しいので、併せてご覧ください。
認証の流れ
- AWS マネジメントコンソールにログイン
aws loginをターミナルで実行- 認証

- このタイミングで、kiro を起動すると背後で中継スクリプトが実行され、 MCP サーバーが使えるようになる。

Secrets Manager と Parameter Store の比較
まず、どちらを選ぶべきか整理します。
| 観点 | Secrets Manager | Parameter Store (Standard) |
|---|---|---|
| コスト | $0.40/シークレット/月 + API 呼び出し課金 | 無料(10,000 パラメータまで) |
| 自動ローテーション | ◎ Lambda 連携で自動化可能 | ❌ なし(自前で実装が必要) |
| データ形式 | JSON 文字列(複数キーを1つに格納可) | 単一の値(1パラメータ = 1キー) |
| 暗号化 | デフォルトで暗号化 | SecureString を選べば KMS で暗号化 |
| バージョン管理 | ◎ 自動でバージョニング | ○ 履歴あり(Advanced のみ全履歴保持) |
| スループット | 高い | Standard: 40 TPS(十分) |
| IAM 権限 | secretsmanager:GetSecretValue |
ssm:GetParameter + kms:Decrypt |
選び方の目安:
- 自動ローテーションが必要 → Secrets Manager
- 複数のキーを1つにまとめたい → Secrets Manager
- コストを抑えたい、キーが1つだけ → Parameter Store
- 既に Parameter Store で設定管理している → Parameter Store
MCP サーバーの API キー管理という用途では、ローテーション頻度が低く、キーも1つであることが多いため、Parameter Store で十分なケースが大半です。
この記事で実現すること
flowchart LR
Claude["Claude Desktop\n( MCP 設定ファイルの command に\nrun_mcp.py を指定)"]
Wrapper["run_mcp.py"]
Claude -- "ツール呼び出し・結果受信\n(stdin/stdout)" --> Wrapper
flowchart LR
Wrapper["run_mcp.py"]
SSM["AWS Systems Manager\nParameter Store"]
Wrapper -- "① API キーを取得\n(GetParameter)" --> SSM
flowchart LR
Wrapper["run_mcp.py"]
MCP["Backlog MCP サーバー"]
Wrapper -- "② API キーを環境変数にセット\n③ MCP サーバーを子プロセスとして起動" --> MCP
flowchart LR
MCP["Backlog MCP サーバー"]
Backlog["Backlog API"]
MCP -- "環境変数の API キーで\n認証・リクエスト" --> Backlog
前回記事との違いは ① の取得先が Secrets Manager → Parameter Store に変わっただけ です。ラッパーの構造、stdin/stdout の引き継ぎ、セキュリティ上の考慮事項はすべて同じです。
前提条件
- Python 3.9 以上
- Node.js(MCP サーバーの実行に必要)
- AWS CLI が設定済み(
aws loginまたはaws sso loginで一時トークンを利用) - AWS Systems Manager Parameter Store へのアクセス権限(
ssm:GetParameter) - SecureString を使う場合は追加で
kms:Decrypt権限
必要な IAM ポリシー例
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ssm:GetParameter", "Resource": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/mcp/backlog/api-key" }, { "Effect": "Allow", "Action": "kms:Decrypt", "Resource": "arn:aws:kms:ap-northeast-1:123456789012:key/your-kms-key-id" } ] }
kms:Decrypt は SecureString を使う場合に必要です。デフォルトの aws/ssm キーを使っていれば、通常は明示的な許可なしでも動作します。
手順 1: Parameter Store にパラメータを登録する
AWS マネジメントコンソール、または AWS CLI でパラメータを作成します。SecureString を指定することで、KMS で暗号化されます。
aws ssm put-parameter \ --name "/mcp/backlog/api-key" \ --type "SecureString" \ --value "あなたのBacklog APIキー" \ --region ap-northeast-1
実行結果:
{ "Version": 1, "Tier": "Standard" }
パラメータ名の設計について
Parameter Store ではパス形式の命名が推奨されています。
/mcp/backlog/api-key ← 本記事で使用 /mcp/github/token ← 別の MCP サーバー用 /mcp/slack/bot-token ← さらに別のサービス
この命名規則にしておくと、IAM ポリシーで /mcp/* をまとめて許可したり、aws ssm get-parameters-by-path で一覧取得したりできます。
手順 2: Python venv のセットアップ
前回記事と同じです。
mkdir backlog-mcp-wrapper-ssm cd backlog-mcp-wrapper-ssm # venv 作成 python3 -m venv venv # boto3 をインストール venv/bin/pip install boto3 "botocore[crt]" # macOS / Linux # venv\Scripts\pip install boto3 "botocore[crt]" # Windows
手順 3: ラッパースクリプトの作成
run_mcp.py を作成します。Secrets Manager 版との差分はわずかです。
import os import sys import subprocess import boto3 from botocore.exceptions import ClientError, BotoCoreError # ============================================================ # 設定値(ご自身の環境に合わせて変更してください) # ============================================================ # Parameter Store のパラメータ名 # SecureString で保存することを推奨 PARAMETER_NAME = "/mcp/backlog/api-key" AWS_REGION = "ap-northeast-1" BACKLOG_DOMAIN = "yourspace.backlog.com" # MCP サーバーの起動コマンド MCP_COMMAND = os.environ.get("MCP_COMMAND", "npx") MCP_ARGS = os.environ.get( "MCP_ARGS", "-y,backlog-mcp-server,--enable-toolsets,project,wiki", ).split(",") def get_parameter() -> str: """AWS Systems Manager Parameter Store からパラメータ値を取得して返す。 SecureString の場合は WithDecryption=True で復号する。 """ aws_profile = os.environ.get("AWS_PROFILE", "default") try: session = boto3.session.Session( profile_name=aws_profile, region_name=AWS_REGION, ) client = session.client(service_name="ssm") response = client.get_parameter( Name=PARAMETER_NAME, WithDecryption=True, # SecureString でも平文で取得 ) return response["Parameter"]["Value"] except (ClientError, BotoCoreError) as e: # 【重要】ログは必ず stderr へ。 # stdout に出すと Claude ⇔ MCP の JSON-RPC 通信が壊れる。 print( f"[run_mcp] Parameter Store からの取得に失敗しました " f"(Profile: {aws_profile}, Name: {PARAMETER_NAME})", file=sys.stderr, ) print(f"[run_mcp] エラー詳細: {e}", file=sys.stderr) sys.exit(1) def main() -> None: # 1. Parameter Store から API キーを取得 api_key = get_parameter() # 2. 子プロセス用の環境変数を構築 env = os.environ.copy() env["BACKLOG_API_KEY"] = api_key env["BACKLOG_DOMAIN"] = os.environ.get("BACKLOG_DOMAIN", BACKLOG_DOMAIN) # 3. MCP サーバーを起動 # subprocess.run はデフォルトで親の stdin/stdout を子に引き継ぐため、 # Claude ⇔ MCP の JSON-RPC 通信がそのまま機能する。 cmd = [MCP_COMMAND] + MCP_ARGS print(f"[run_mcp] 起動コマンド: {' '.join(cmd)}", file=sys.stderr) try: result = subprocess.run(cmd, env=env) sys.exit(result.returncode) except FileNotFoundError: print( f"[run_mcp] '{MCP_COMMAND}' コマンドが見つかりません。", file=sys.stderr, ) sys.exit(1) except KeyboardInterrupt: pass if __name__ == "__main__": main()
Secrets Manager 版との差分
変更点は実質 get_parameter() 関数だけです。
| Secrets Manager 版 | Parameter Store 版 | |
|---|---|---|
| import | import json が必要 |
不要 |
| API 呼び出し | client.get_secret_value(SecretId=...) |
client.get_parameter(Name=..., WithDecryption=True) |
| 戻り値の処理 | json.loads(response["SecretString"]) → dict |
response["Parameter"]["Value"] → str |
| 関数の戻り値型 | dict(複数キー対応) |
str(単一の値) |
Secrets Manager は1つのシークレットに JSON で複数キーを格納できるのに対し、Parameter Store は1パラメータ = 1値です。複数の API キーが必要な場合は、パラメータを複数作成して個別に取得します。
手順 4: MCP 設定ファイルの更新
前回記事と同じ構造です。command に venv の Python を、args にスクリプトのパスを指定します。
macOS / Linux
{ "mcpServers": { "backlog-server": { "command": "/path/to/backlog-mcp-wrapper-ssm/venv/bin/python", "args": ["/path/to/backlog-mcp-wrapper-ssm/run_mcp.py"], "env": { "AWS_PROFILE": "my-sso-profile" } } } }
Windows
{ "mcpServers": { "backlog-server": { "command": "C:\\path\\to\\backlog-mcp-wrapper-ssm\\venv\\Scripts\\python.exe", "args": ["C:\\path\\to\\backlog-mcp-wrapper-ssm\\run_mcp.py"], "env": { "AWS_PROFILE": "my-sso-profile" } } } }
動作確認
パラメータ取得テスト
# test_parameter.py import os, boto3 def main(): aws_profile = os.environ.get("AWS_PROFILE", "default") print(f"[テスト] 使用プロファイル: {aws_profile}") session = boto3.session.Session( profile_name=aws_profile, region_name="ap-northeast-1" ) client = session.client(service_name="ssm") parameter_name = "/mcp/backlog/api-key" response = client.get_parameter(Name=parameter_name, WithDecryption=True) value = response["Parameter"]["Value"] param_type = response["Parameter"]["Type"] print(f"[成功] パラメータ名: {parameter_name}") print(f" Type: {param_type}") print(f" Value: {'*' * len(value)} ({len(value)}文字)") if __name__ == "__main__": main()
venv/bin/python test_parameter.py
実行結果:
[テスト] 使用プロファイル: default [成功] パラメータ名: /mcp/backlog/api-key Type: SecureString Value: **************************** (28文字)
Type: SecureString と表示されていれば、KMS で暗号化された状態から正しく復号できています。
E2E テスト(モック MCP サーバーを使用)
前回記事と同じモックサーバーを使います。
// mock_mcp_server.js const readline = require('readline'); const apiKey = process.env.BACKLOG_API_KEY || ''; const domain = process.env.BACKLOG_DOMAIN || ''; // ログは stderr に(stdout は JSON-RPC 専用) process.stderr.write(`[mock-mcp] BACKLOG_API_KEY: ${'*'.repeat(apiKey.length)}\n`); process.stderr.write(`[mock-mcp] BACKLOG_DOMAIN: ${domain}\n`); const rl = readline.createInterface({ input: process.stdin }); rl.on('line', (line) => { const request = JSON.parse(line); const response = { jsonrpc: '2.0', id: request.id, result: { status: 'ok', has_api_key: apiKey.length > 0, // API キーの値自体は絶対にレスポンスに含めない } }; process.stdout.write(JSON.stringify(response) + '\n'); });
# test_e2e.py import os, sys, json, subprocess SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) MOCK_SERVER = os.path.join(SCRIPT_DIR, "mock_mcp_server.js") RUN_MCP = os.path.join(SCRIPT_DIR, "run_mcp.py") VENV_PYTHON = os.path.join(SCRIPT_DIR, "venv", "bin", "python") def test_via_wrapper(): env = os.environ.copy() env["AWS_PROFILE"] = os.environ.get("AWS_PROFILE", "default") env["MCP_COMMAND"] = "node" env["MCP_ARGS"] = MOCK_SERVER request = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "test"}) + "\n" proc = subprocess.run( [VENV_PYTHON, RUN_MCP], input=request, capture_output=True, text=True, timeout=15, env=env, ) if proc.returncode != 0: print(f"❌ 失敗: プロセスが異常終了 (code={proc.returncode})", file=sys.stderr) print(f" stderr: {proc.stderr}", file=sys.stderr) sys.exit(1) response = json.loads(proc.stdout.strip()) assert response["result"]["has_api_key"] is True, "API キーが渡されていません" print("✅ 成功: Parameter Store → 環境変数 → MCP サーバーの連携が正常") if __name__ == "__main__": test_via_wrapper()
venv/bin/python test_e2e.py
実行結果:
✅ 成功: Parameter Store → 環境変数 → MCP サーバーの連携が正常
複数の API キーが必要な場合
Secrets Manager では1つのシークレットに JSON で複数キーを格納できましたが、Parameter Store は1パラメータ = 1値です。複数キーが必要な場合は、パス階層を活用します。
# 複数パラメータを登録 aws ssm put-parameter --name "/mcp/backlog/api-key" --type SecureString --value "..." aws ssm put-parameter --name "/mcp/backlog/domain" --type String --value "yourspace.backlog.com"
スクリプト側では get_parameters_by_path でまとめて取得できます。
def get_parameters_by_path(path_prefix: str) -> dict: """指定パス配下のパラメータをまとめて取得して dict で返す。""" aws_profile = os.environ.get("AWS_PROFILE", "default") session = boto3.session.Session( profile_name=aws_profile, region_name=AWS_REGION, ) client = session.client(service_name="ssm") response = client.get_parameters_by_path( Path=path_prefix, WithDecryption=True, Recursive=True, ) # "/mcp/backlog/api-key" → "api-key" のようにキー名だけ取り出す params = {} for param in response["Parameters"]: key = param["Name"].split("/")[-1] params[key] = param["Value"] return params
この方法なら、Secrets Manager の JSON 格納と同じ感覚で複数キーを扱えます。ただし、IAM ポリシーで ssm:GetParametersByPath の許可が追加で必要です。
Secrets Manager 版からの移行
既に前回記事の Secrets Manager 版を使っている方が Parameter Store 版に移行する手順です。
# 1. Secrets Manager から値を取り出して Parameter Store に登録 API_KEY=$(aws secretsmanager get-secret-value \ --secret-id mcp/backlog-keys \ --query 'SecretString' --output text \ --profile my-sso-profile \ --region ap-northeast-1 \ | python3 -c 'import sys,json; print(json.loads(sys.stdin.read())["BACKLOG_API_KEY"])') aws ssm put-parameter \ --name "/mcp/backlog/api-key" \ --type "SecureString" \ --value "$API_KEY" \ --profile my-sso-profile \ --region ap-northeast-1 # 2. 新しい venv をセットアップ mkdir backlog-mcp-wrapper-ssm && cd backlog-mcp-wrapper-ssm python3 -m venv venv venv/bin/pip install boto3 "botocore[crt]" # 3. run_mcp.py を配置(本記事のコードをコピー) # 4. MCP 設定ファイルのパスを更新 # command: .../backlog-mcp-wrapper/venv/bin/python # → command: .../backlog-mcp-wrapper-ssm/venv/bin/python # 5. 動作確認後、不要になった Secrets Manager のシークレットを削除(任意) # aws secretsmanager delete-secret --secret-id mcp/backlog-keys --force-delete-without-recovery
この方式の限界(前回記事の再掲 + 追記)
前回記事で述べた限界はそのまま当てはまります。
- 環境変数自体のリスクは残る — 最終的に API キーは子プロセスの環境変数に入る
- 守るべきものが移動しただけ — AWS 認証情報が漏洩したら同じ(ただし SSO の一時トークンなのでリスクの性質が異なる)
Parameter Store 固有の注意点を追記します。
SecureString を使わないと意味がない
--type String で登録すると、Parameter Store 上で平文保存されます。API キーには必ず --type SecureString を指定してください。
自動ローテーションがない
Secrets Manager と違い、Parameter Store にはビルトインのローテーション機能がありません。API キーを定期的に更新したい場合は、自前でスクリプトを書くか、Secrets Manager を使ってください。
スループット制限
Standard パラメータは 40 TPS(1秒あたり40リクエスト)の制限があります。MCP サーバーの起動時に1回取得するだけなので、通常は問題になりません。ただし、大量の MCP サーバーを同時起動するような構成では注意が必要です。
まとめ
| 方法 | セキュリティ | コスト | 手軽さ |
|---|---|---|---|
args に直書き |
❌ 危険 | 無料 | ◎ |
env に直書き |
△ ファイルにキーが残る | 無料 | ○ |
| Secrets Manager + ラッパー | ◎ | $0.40/月〜 | △ |
| Parameter Store + ラッパー | ◎ | 無料 | △ |
Parameter Store 版は Secrets Manager 版と同じセキュリティレベルを 無料で 実現できます。自動ローテーションが不要で、キーの数が少ない場合は、こちらの方がシンプルでおすすめです。
前回記事と合わせて、チームの要件に合った方を選んでいただければと思います。
前回記事: MCP サーバーの API キーを AWS Secrets Manager で管理してみた
余談
富士山の上の方でも桜が咲いていました。
