はじめに
生成AIを業務で活用する際、「ChatGPTだけでは最新情報が取得できない」「社内システムと連携したい」といった課題に直面することがあります。
このような課題を解決する技術の一つが MCP (Model Context Protocol) です。本記事では、AWSの生成AI活用リファレンスアーキテクチャを使って、MCPを実際にどう実装するかを段階的に解説します。
本記事で扱う GenU は AWS が公開している OSS プロジェクトです。
https://github.com/aws-samples/generative-ai-use-cases
GenU のセットアップ手順については、以下のブログ記事をご参照ください。
https://www.fsi.co.jp/blog/12335/
MCP (Model Context Protocol) とは?
基本的な考え方
MCP は、生成AIと外部ツールをつなぐための共通ルールです。
例えば、生成AIに「今日の天気は?」と聞いても、AIは学習データにない最新情報は答えられません。しかし、MCPを使えば:
- 生成AIが「天気情報が必要だ」と判断
- MCPを通じて天気APIを呼び出し
- 取得した情報をもとに回答を生成
という流れで、最新情報を含めた回答が可能になります。
MCPの3つの主要機能
| 機能 | 説明 | 例 |
|---|---|---|
| ツール | AIが実行できる機能 | ファイル作成、API呼び出し |
| リソース | AIが参照できるデータ | ドキュメント、データベース |
| プロンプト | 定型的な指示テンプレート | レポート生成、コードレビュー |
GenU での活用例
GenU では、以下のような実用的なシーンで MCP を活用しています:
- 📚 AWS ドキュメント検索: 最新のAWSサービス情報を参照
- 🏗️ CDK コード生成: インフラコードを自動生成
- 📊 アーキテクチャ図作成: システム構成図を自動描画
- 🎨 画像生成: Amazon Nova Canvas で画像作成
GenU での MCP 実装を理解する
全体の仕組み
GenU では、以下のような流れで MCP が動作します:
ユーザー → GenU → Amazon Bedrock (AI) → MCP サーバー → 外部ツール
↓
結果を統合して回答
使用している技術スタック
GenU では Strands Agents というフレームワークを使って MCP を実装しています。これは、複数の MCP サーバーを簡単に管理できる便利なツールです。
実装のコア部分
# packages/cdk/mcp-api/app.py (抜粋)
from strands.models import BedrockModel
from strands import Agent, tool
from strands.tools.mcp import MCPClient
from mcp import stdio_client, StdioServerParameters
# MCP サーバーの起動
def make_mcp_client(server):
def spawn():
return stdio_client(
StdioServerParameters(
command=server['command'],
args=server['args'],
env={**UV_ENV, **server['env']},
)
)
return MCPClient(spawn)
# エージェントの実行
agent = Agent(
system_prompt=f'{request.systemPrompt}\n{FIXED_SYSTEM_PROMPT}',
model=bedrock_model,
tools=app.mcp_tools + [upload_file_to_s3_and_retrieve_s3_url],
)
このコードのポイント:
- MCPClient: 複数のツールをまとめて管理
- stdio_client: プログラム間の標準的な通信方法を使用
- BedrockModel: Amazon Bedrock の AI モデルと連携
実際に使える AWS 公式ツール
AWS が公式に提供している MCP サーバーは、設定ファイルに追加するだけで使えます:
{
"mcpServers": {
"awslabs.aws-documentation-mcp-server": {
"command": "uvx",
"args": ["awslabs.aws-documentation-mcp-server@latest"]
},
"awslabs.cdk-mcp-server": {
"command": "uvx",
"args": ["awslabs.cdk-mcp-server@latest"]
},
"awslabs.aws-diagram-mcp-server": {
"command": "uvx",
"args": ["awslabs.aws-diagram-mcp-server@latest"]
},
"awslabs.nova-canvas-mcp-server": {
"command": "uvx",
"args": ["awslabs.nova-canvas-mcp-server@latest"]
}
}
}
AWS Lambda で動かす際の工夫
AWS Lambda(サーバーレス環境)で MCP を動かすには、いくつかの制約があります。GenU ではこれらを以下のように解決しています:
| 制約 | 対策 |
|---|---|
| ファイルシステムが読み取り専用 |
/tmp ディレクトリを活用 |
| 生成ファイルをユーザーに渡せない | S3 アップロード用カスタムツールを実装 |
| MCP サーバーの実行環境 |
uvx/npx で実行可能なものに限定 |
| API Key の管理 |
mcp.json の env で環境変数注入 |
具体的な解決策:ファイルをS3にアップロードする
Lambda で作成したファイルをユーザーに渡すため、S3 にアップロードする専用ツールを実装しています:
@tool
def upload_file_to_s3_and_retrieve_s3_url(filepath: str) -> str:
"""Upload the file at /tmp/ws/* and retrieve the s3 path"""
bucket = os.environ['FILE_BUCKET']
region = os.environ['AWS_REGION']
if not filepath.startswith(WORKSPACE_DIR):
raise ValueError(f'{filepath} must be under {WORKSPACE_DIR}')
filename = os.path.basename(filepath)
key = f'mcp/{session_id}/{filename}'
s3 = boto3.client('s3')
s3.upload_file(filepath, bucket, key)
return f'https://{bucket}.s3.{region}.amazonaws.com/{key}'
システム全体のアーキテクチャ
ここからは、より技術的な内容に入ります。GenU がどのように複数の AWS サービスを組み合わせて MCP を実現しているかを見ていきましょう。
アーキテクチャ全体像
インフラをコードで管理する(AWS CDK)
GenU では、AWS CDK を使ってインフラをコードで定義しています。以下は MCP 機能を実現する Lambda 関数の定義です:
// packages/cdk/lib/construct/mcp-api.ts
export class McpApi extends Construct {
public readonly endpoint: string;
constructor(scope: Construct, id: string, props: McpApiProps) {
super(scope, id);
// Docker イメージから Lambda 関数を作成
const mcpFunction = new DockerImageFunction(this, 'McpFunction', {
code: DockerImageCode.fromImageAsset('./mcp-api', {
networkMode: props.isSageMakerStudio
? NetworkMode.custom('sagemaker')
: NetworkMode.DEFAULT,
}),
memorySize: 1024,
ephemeralStorageSize: Size.mebibytes(1024),
timeout: Duration.minutes(15),
architecture: Architecture.X86_64,
environment: {
AWS_LWA_INVOKE_MODE: 'RESPONSE_STREAM',
FILE_BUCKET: props.fileBucket.bucketName,
},
vpc: props.vpc,
securityGroups: props.securityGroups,
});
// Bedrock へのアクセス権限
mcpFunction.role?.addToPrincipalPolicy(
new PolicyStatement({
effect: Effect.ALLOW,
actions: ['bedrock:*'],
resources: ['*'],
})
);
// S3 への書き込み権限
props.fileBucket.grantWrite(mcpFunction);
// Function URL の作成 (Response Stream モード)
const mcpEndpoint = mcpFunction.addFunctionUrl({
authType: FunctionUrlAuthType.AWS_IAM,
cors: {
allowedOrigins: ['*'],
allowedMethods: [HttpMethod.ALL],
allowedHeaders: ['*'],
},
invokeMode: InvokeMode.RESPONSE_STREAM,
});
// Cognito 認証ロールに実行権限を付与
mcpEndpoint.grantInvokeUrl(props.idPool.authenticatedRole);
this.endpoint = `${mcpEndpoint.url}streaming`;
}
}
このコードの重要なポイント:
| 設定 | 目的 | メリット |
|---|---|---|
| Response Stream | リアルタイムで結果を返す | ChatGPT のような逐次表示が可能 |
| Docker Image | 複雑なライブラリをパッケージ化 | Python の依存関係を簡単に管理 |
| IAM 認証 | Cognito でユーザー認証 | セキュアなアクセス制御 |
| VPC 統合 | 閉域ネットワークで実行 | 企業のセキュリティ要件に対応 |
パフォーマンスを最適化する
Lambda の設定値の意味
{
memorySize: 1024, // MCP サーバー起動に必要なメモリ
ephemeralStorageSize: 1024, // uvx のキャッシュ用
timeout: Duration.minutes(15), // 長時間実行タスク対応
architecture: Architecture.X86_64 // uvx の互換性
}
uvx で最新ツールを動的に取得
GenU では uvx というツールを使って、MCP サーバーを動的にインストール・実行しています:
UV_ENV = {
'UV_NO_CACHE': '1',
'UV_PYTHON': '/usr/local/bin/python',
'UV_TOOL_DIR': '/tmp/.uv/tool',
'UV_TOOL_BIN_DIR': '/tmp/.uv/tool/bin',
'UV_PROJECT_ENVIRONMENT': '/tmp/.venv',
}
この方式のメリット:
- 🚀 事前にパッケージを作る必要がない
- 🔄 常に最新バージョンを使える
- 💾 Lambda の容量制限を気にしなくて良い
セキュリティを多層で守る
エンタープライズ環境では、セキュリティが最重要です。GenU では以下のような多層防御を実装しています:
┌─────────────────────────────────────────┐
│ Layer 1: CloudFront + WAF │
│ - IP 制限 │
│ - 地理的制限 │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Layer 2: Cognito Authentication │
│ - SAML 連携 │
│ - MFA │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Layer 3: IAM Policy │
│ - 最小権限の原則 │
│ - リソースベースポリシー │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Layer 4: VPC (Optional) │
│ - Private Subnet │
│ - Security Group │
└─────────────────────────────────────────┘
実際の権限設定
Lambda 関数に付与する権限は、必要最小限に絞っています:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": [
"arn:aws:bedrock:*::foundation-model/*"
]
},
{
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": ["arn:aws:s3:::${FileBucket}/*"]
}
]
}
運用:ログとモニタリング
実行ログの記録
どのツールが呼ばれたかをログに記録することで、トラブルシューティングが容易になります:
async for event in agent.stream_async(request.userPrompt):
if is_message(event):
if is_assistant(event):
text = extract_text(event)
tool_use = extract_tool_use(event)
# ツール実行のログ出力
if tool_use is not None:
yield stream_chunk('', f'```\n{tool_use["name"]}: {tool_use["input"]}\n```\n')
AI モデルの利用状況を追跡
# デプロイ時に出力される CloudWatch Logs グループ
GenerativeAiUseCasesDashboardStack.BedrockLogGroup
監視すべき項目:
- 💰 トークン使用量: コスト管理のため
- ⏱️ レスポンス時間: ユーザー体験の向上
- ⚠️ エラー率: システムの健全性確認
- 🔧 ツール呼び出し頻度: 利用パターンの分析
現在の制約事項
GenU の MCP 実装には、以下の制約があります:
| 項目 | 制約内容 |
|---|---|
| MCP サーバー | uvx/npx 実行可能なもののみ |
| トランスポート | stdio のみサポート |
| マルチモーダル | 未対応 |
| API Key 管理 | 静的設定のみ |
今後の方向性:AgentCore への移行
MCP チャットユースケースは v6 で削除予定です。
今後は AgentCore の利用が推奨されます。
// parameter.ts での AgentCore 設定例
const envs: Record<string, Partial<StackInput>> = {
dev: {
createGenericAgentCoreRuntime: true,
agentCoreRegion: 'us-west-2',
},
};
実装結果
デプロイの実行
MCPを有効化してデプロイを実行しました:
cd generative-ai-use-cases
npm run cdk:deploy -- --require-approval never
MCPチャット画面へのアクセス
デプロイ完了後、GenUのWebサイトにアクセスし、MCPチャット画面を開きます。

[キャプチャ2: GenUのトップ画面でMCPチャットが表示されている様子]
動作確認1: AWSドキュメント検索
プロンプト: 「Amazon Bedrockの料金体系について教えてください」
AWS Documentation MCP サーバーが最新のAWSドキュメントを検索し、回答を生成します。

[キャプチャ3: AWSドキュメント検索の実行結果画面]
動作確認2: CDKコード生成
プロンプト: 「S3バケットとLambda関数を作成するCDKコードを生成してください」
CDK MCP サーバーがベストプラクティスに沿ったCDKコードを自動生成します。
動作確認3: アーキテクチャ図生成
プロンプト: 「API Gateway、Lambda、DynamoDBを使った基本的なアーキテクチャ図を作成してください」
AWS Diagram MCP サーバーがシステム構成図を自動生成します。
動作確認4: 時刻取得
プロンプト: 「現在の時刻を教えてください」
Time MCP サーバーがリアルタイムの時刻情報を取得します。

[キャプチャ6: 時刻取得の実行結果画面]
CloudWatch Logsでの確認
Lambda関数のログから、MCPツールの呼び出しを確認できます:
# Lambda関数のロググループ名を確認(CDKが自動生成するサフィックスが付きます)
aws logs describe-log-groups --log-group-name-prefix /aws/lambda/GenerativeAiUseCasesStack-McpApi --query 'logGroups[0].logGroupName' --output text
# 確認したロググループ名でログを監視(<SUFFIX>は環境ごとに異なります)
aws logs tail /aws/lambda/GenerativeAiUseCasesStack-McpApiMcpFunction<SUFFIX> --follow
注意: Lambda関数名には、CDKがデプロイ時に自動生成するランダムなサフィックス(例: T8lwwcZ2qaaW)が付きます。そのため、上記のコマンドで実際のロググループ名を確認してから使用してください。
実践:独自のツールを追加してみる
ここまでの動作確認を踏まえて、実際にカスタム MCP サーバーを追加する手順を見ていきましょう。
ステップ1: 設定ファイルを編集
{
"mcpServers": {
"time": {
"command": "uvx",
"args": ["mcp-server-time"]
},
"custom-service": {
"command": "uvx",
"args": ["your-mcp-server@latest"],
"env": {
"API_KEY": "your-api-key"
}
}
}
}
ステップ2: デプロイする
# 1. mcp.json を編集
vim packages/cdk/mcp-api/mcp.json
# 2. parameter.ts で MCP を有効化
# mcpEnabled: true
# 3. デプロイ
npm run cdk:deploy
ステップ3: 動作を確認する
# Lambda ログの確認
aws logs tail /aws/lambda/GenerativeAiUseCasesStack-McpFunction --follow
まとめ
学んだエンタープライズパターン
- Lambda Response Stream によるリアルタイム処理
- Docker コンテナ での複雑な依存関係管理
- IAM + Cognito による多層セキュリティ
- MCP による拡張可能なツール統合
- CDK によるインフラのコード化
技術的な学び
本記事で解説した内容は、個別技術の深い理解から複数サービスの統合設計まで、実践的なリファレンスアーキテクチャから学べる要素を含んでいます。




