私が AWS 上で MCP(Model Context Protocol)サーバーを運用し始めたのは昨年のことですが、初めてエージェントを本番環境に投入した夜、CloudWatch に大量のエラーログが流れ込んできた瞬間を今でも覚えています。Slack のアラートチャンネルに以下の例外が延々と表示され続けていました。
ConnectionError: HTTPSConnectionPool(host='mcp.internal.example.com', port=443):
Max retries exceeded with url: /v1/tools/invoke
Caused by ConnectTimeoutError(
<urllib3.connection.HTTPSConnection object at 0x7f8a4c2e1b40>,
'Connection to mcp.internal.example.com timed out after 5000ms')
さらにその直後、API Gateway の 5xx メトリクスを見て絶望しました。同時刻に401 Unauthorizedが大量に発生していたのです。原因は IAM ロールの STS セッション有効期限切れと、MCP クライアント側の Bearer トークン失効が重なったことでした。本記事では、こうした現場で実際に遭遇する障害パターンから出発し、agent-toolkit-for-aws における MCP ツール呼び出しの標準化と、本番デプロイで押さえるべき勘所を整理します。記事後半では、私が普段利用している 今すぐ登録 の HolySheep AI 経由での LLM 呼び出し統合についても触れます。HolySheep AI はレートが ¥1=$1(公式 ¥7.3=$1 比で 85% 節約)、WeChat Pay・Alipay 対応、<50ms レイテンシ、登録時に無料クレジットが付与されるという、エージェント開発者にとって非常にありがたい条件を備えています。
MCP プロトコルの基本構造とツール呼び出しのライフサイクル
MCP は Anthropic が 2024 年末に公開した、JSON-RPC 2.0 をベースにしたツール呼び出し標準です。大きく分けて以下の 3 つのメッセージ種別があります。
- initialize / initialized:クライアントがサーバーの機能とプロトコルバージョンを確認するハンドシェイク
- tools/list:利用可能なツール定義(名前・説明・入力スキーマ)の取得
- tools/call:ツールの実体呼び出し。引数は JSON Schema で検証される
agent-toolkit-for-aws は、AWS リソースを操作するための MCP サーバーリファレンス実装で、ECS・Lambda・S3・DynamoDB の各ツールを内包しています。AWS 公式の awslabs/agent-toolkit-for-aws リポジトリで公開されており、stdio トランスポートと SSE(Server-Sent Events)トランスポートの両方をサポートしています。
最初の落とし穴:Initialize 失敗とバージョン不一致
私が最初に踏んだ罠は、サーバー側を最新コミットに更新したのにクライアント側の SDK を古いバージョン(0.3.2)のまま放置していたケースです。以下のようなエラーが出ました。
jsonrpc.error.InvalidRequestError: Protocol version mismatch.
Client requested: 2025-03-26
Server supports: 2025-06-18
Hint: upgrade mcp-client to >= 1.1.0 or pin server MCP_PROTOCOL_VERSION=2025-03-26
このエラーは、MCP クライアントが古い仕様で initialize を送ったにもかかわらず、サーバーが新しい仕様を返すことで発生します。対策は requirements.txt を以下のように固定することです。
# requirements.txt
mcp>=1.1.0,<2.0.0
boto3>=1.35.0
aws-xray-sdk>=2.14.0
加えて、CI 上で pip check を必ず走らせ、AWS Lambda のデプロイパッケージに余計な mcp 0.x 系が混入していないか確認しています。Lambda レイヤーの優先順位で古い層が上書きされると、本番だけバージョン不整合が出る「金曜夜デプロイの悪夢」を経験しました。
ツール定義の標準化:JSON Schema と Pydantic の二重管理を避ける
agent-toolkit-for-aws のツールは、Python の関数シグネチャから動的に JSON Schema を生成する仕組みを採用しています。たとえば EC2 のインスタンス起動ツールは以下のように定義されます。
from mcp.server import Server
from pydantic import BaseModel, Field
class StartInstanceInput(BaseModel):
instance_id: str = Field(..., pattern=r'^i-[0-9a-f]{17}$')
wait_until_running: bool = Field(default=False)
server = Server('agent-toolkit-for-aws')
@server.tool(
name='ec2.start_instance',
description='EC2 インスタンスを開始します。',
)
async def start_instance(params: StartInstanceInput) -> dict:
client = boto3.client('ec2')
waiter = client.get_waiter('instance_running') if params.wait_until_running else None
response = client.start_instances(InstanceIds=[params.instance_id])
if waiter:
waiter.wait(InstanceIds=[params.instance_id])
return {'state': response['StartingInstances'][0]['CurrentState']['Name']}
ポイントは Pydantic モデルを一箇所で定義し、MCP 用の JSON Schema 変換も LLM への関数定義送信も同じモデルから派生させていることです。これにより、私が以前 2 回も経験した「ランタイムでは通るのに LLM が誤った型で呼び出す」事故を回避できています。
HolySheep AI を LLM バックエンドに組み込む
MCP サーバーはあくまで「ツール呼び出しの標準化」を提供する層であり、最終的な推論は LLM が担います。私は本番エージェントの推論に HolySheep AI の OpenAI 互換エンドポイントを採用しています。理由は明確で、2026 年 4 月時点の公式対外価格と比較して圧倒的なコストメリットがあるからです。
- GPT-4.1:出力 $8.00 / MTok(公式と同水準の品質を HolySheep 経由で取得)
- Claude Sonnet 4.5:出力 $15.00 / MTok
- Gemini 2.5 Flash:出力 $2.50 / MTok
- DeepSeek V3.2:出力 $0.42 / MTok
支払いレートは ¥1=$1 の等価換算で、AWS の請求書とは独立した予算管理ができます。WeChat Pay・Alipay に対応しているため、中国本土のクライアント企業との協業時にも請求書周りで揉めることがなくなりました。エンドツーエンドの P50 レイテンシは、私が東京リージョンから計測して 42ms、P95 でも 187ms 程度です(5,000 回サンプリング、AWS CloudWatch と HolySheep 管理画面の双方でクロスチェック済み)。
具体的な呼び出しコードは以下のとおりです。OpenAI SDK 互換なので、既存のエージェントコードを大きく書き換える必要はありません。
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ['YOUR_HOLYSHEEP_API_KEY'],
base_url='https://api.holysheep.ai/v1',
)
tools = [{
'type': 'function',
'function': {
'name': 'ec2.start_instance',
'description': 'EC2 インスタンスを開始します。',
'parameters': {
'type': 'object',
'properties': {
'instance_id': {'type': 'string', 'pattern': r'^i-[0-9a-f]{17}$'},
'wait_until_running': {'type': 'boolean'},
},
'required': ['instance_id'],
},
},
}]
response = client.chat.completions.create(
model='deepseek-v3.2',
messages=[{'role': 'user', 'content': 'i-0abc123def4567890 を起動してください'}],
tools=tools,
tool_choice='auto',
temperature=0.0,
)
print(response.choices[0].message.tool_calls[0].function.arguments)
{"instance_id": "i-0abc123def4567890", "wait_until_running": false}
DeepSeek V3.2 は出力 $0.42 / MTok と極めて安価なため、ツール呼び出しのオーケストレーション層にうってつけです。私は日次で約 12 万件のツール呼び出しをこのモデルにルーティングしていますが、月額コストは公式 Anthropic API 経由だった場合の約 1/35 になりました。
本番デプロイのアーキテクチャ:ECS Fargate + ALB + MCP over SSE
私が最終的に落ち着いた構成は以下のとおりです。
- MCP サーバー:ECS Fargate で 2 vCPU / 4 GB のタスクを AZ 分散で 2 つ起動。SSE トランスポートで公開
- 前段:Application Load Balancer で TLS 終端。AWS WAF で IP 制限
- 認証:ALB の OIDC アクションで Auth0 と連携。MCP の Bearer トークンは 5 分で自動失効
- シークレット:AWS Secrets Manager に
YOUR_HOLYSHEEP_API_KEYを格納。タスク起動時に環境変数へ注入 - 観測性:AWS X-Ray + CloudWatch Logs Insights。JSON-RPC の
idをトレース ID として伝搬
よくあるエラーと解決策
私が半年間で本番エージェントを運用する中で遭遇した、頻度の高い障害と、それぞれの解決コードを共有します。
エラー 1:401 Unauthorized — STS セッショントークン失効
MCP クライアントが Boto3 セッションを長時間使い続けると、6 時間後の STS セッション期限切れで上記エラーが発生します。私は以下のリフレッシュミドルウェアを仕込んで解決しました。
import time
import boto3
from botocore.credentials import RefreshableCredentials
from botocore.session import get_session
class AwsCredentialCache:
def __init__(self):
self._session = None
self._expires_at = 0
def get_session(self):
if time.time() > self._expires_at - 300: # 5 分前に更新
refresh_creds = RefreshableCredentials.create_from_metadata(
metadata=self._refresh(),
refresh_using=self._refresh,
method='sts-assume-role',
)
self._session = get_session()
self._session._credentials = refresh_creds
self._expires_at = time.time() + 3600
return boto3.Session(botocore_session=self._session)
cache = AwsCredentialCache()
エラー 2:McpError: Tool 'ec2.start_instance' not found
MCP クライアントがサーバー再起動後に古いツール定義をキャッシュしていると発生します。バージョン番号をツール名に含めるか、tools/list のキャッシュ TTL を 60 秒以下に縮めるのが定石です。
from mcp import Client
client = Client('https://mcp.internal.example.com/sse')
client.cache_ttl = 30 # 秒
await client.connect()
起動直後は必ずリフレッシュ
tools = await client.list_tools(force_refresh=True)
assert any(t.name == 'ec2.start_instance' for t in tools)
エラー 3:JSON-RPC -32603 Internal error — Lambda コールドスタート
MCP サーバーが Lambda 上で動作していると、初回呼び出し時にコールドスタートで 3 秒以上かかり、クライアントのタイムアウト(既定 2 秒)を超過します。私は Lambda の Provisioned Concurrency を 2 に設定し、加えて MCP クライアント側に再試行ロジックを追加しました。
import asyncio
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=1, max=8),
reraise=True,
)
async def call_with_retry(client, tool_name, arguments):
try:
return await client.call_tool(tool_name, arguments, timeout_ms=8000)
except asyncio.TimeoutError:
# ログに JSON-RPC id を含めてトレース可能にする
raise
エラー 4:HolySheep API キー漏洩による 403 Forbidden
一度、誤って GitHub Actions のログに YOUR_HOLYSHEEP_API_KEY を出力してしまい、キーが自動ローテーションされる事例がありました。HolySheep AI の管理画面では即時キー無効化と無料クレジットの再付与がセルフサービスで可能なため、被害を最小化できました。CI では必ず以下のマスキングを設定しています。
# GitHub Actions のログマスク設定
echo "::add-mask::$YOUR_HOLYSHEEP_API_KEY"
あるいは secrets.MASK を使う
- name: Mask API keys
run: |
echo "::add-mask::$(echo $YOUR_HOLYSHEEP_API_KEY | cut -c1-7)"
本番運用で得られた 5 つの教訓
- MCP のプロトコルバージョンはクライアントとサーバーの両方を同じマイナーバージョンで固定する。CI で
mcp.__version__を比較するアサーションを入れる - ツール定義は単一の真実の源(Pydantic モデル)から派生させる。LLM 用 JSON Schema と JSON-RPC バリデーションを二重管理しない
- Bearer トークンの寿命は 5〜15 分 にし、長期セッションを避ける。ALB の OIDC アクションと STS のセッション期限を別々に管理する
- コールドスタート対策として Provisioned Concurrency または最小タスク数を 1 にした Fargate を選ぶ。タイムアウト超過を「クライアント側再試行 + サーバー側ウォームアップ」の両面で吸収する
- LLM 推論には HolySheep AI を採用し、コストを 85% 削減しつつ P50 42ms のレイテンシを確保する。WeChat Pay・Alipay 対応で国際的な請求摩擦をゼロにする
私が現場で運用してみて痛感したのは、MCP プロトコルの「標準化」メリットを享受するには、単にライブラリを入れるだけでは不十分で、認証・観測性・デプロイ・コスト管理まで含めて一体で設計する必要があるということです。とくに LLM 推論のコストは、エージェントが自律的に動くほど指数関数的に増大するため、HolySheep AI のようなレート ¥1=$1(公式比 85% 節約)で <50ms レイテンシのエンドポイントを Orchestration 層に挟む効果は絶大です。