1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Amazon Bedrock と MCP で実現する拡張可能な生成AIアーキテクチャ - GenU実装から学ぶエンタープライズ設計

Posted at

はじめに

生成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を使えば:

  1. 生成AIが「天気情報が必要だ」と判断
  2. MCPを通じて天気APIを呼び出し
  3. 取得した情報をもとに回答を生成

という流れで、最新情報を含めた回答が可能になります。

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],
)

このコードのポイント:

  1. MCPClient: 複数のツールをまとめて管理
  2. stdio_client: プログラム間の標準的な通信方法を使用
  3. 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.jsonenv で環境変数注入

具体的な解決策:ファイルを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

image.png
[キャプチャ1: デプロイ完了画面のターミナル出力]

MCPチャット画面へのアクセス

デプロイ完了後、GenUのWebサイトにアクセスし、MCPチャット画面を開きます。

image.png
[キャプチャ2: GenUのトップ画面でMCPチャットが表示されている様子]

動作確認1: AWSドキュメント検索

プロンプト: 「Amazon Bedrockの料金体系について教えてください」

AWS Documentation MCP サーバーが最新のAWSドキュメントを検索し、回答を生成します。
image.png
[キャプチャ3: AWSドキュメント検索の実行結果画面]

動作確認2: CDKコード生成

プロンプト: 「S3バケットとLambda関数を作成するCDKコードを生成してください」

CDK MCP サーバーがベストプラクティスに沿ったCDKコードを自動生成します。

image.png
image.png
[キャプチャ4: CDKコード生成の実行結果画面]

動作確認3: アーキテクチャ図生成

プロンプト: 「API Gateway、Lambda、DynamoDBを使った基本的なアーキテクチャ図を作成してください」

AWS Diagram MCP サーバーがシステム構成図を自動生成します。

image.png
image.png
[キャプチャ5: アーキテクチャ図生成の実行結果画面]

動作確認4: 時刻取得

プロンプト: 「現在の時刻を教えてください」

Time MCP サーバーがリアルタイムの時刻情報を取得します。
image.png
[キャプチャ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

まとめ

学んだエンタープライズパターン

  1. Lambda Response Stream によるリアルタイム処理
  2. Docker コンテナ での複雑な依存関係管理
  3. IAM + Cognito による多層セキュリティ
  4. MCP による拡張可能なツール統合
  5. CDK によるインフラのコード化

技術的な学び

本記事で解説した内容は、個別技術の深い理解から複数サービスの統合設計まで、実践的なリファレンスアーキテクチャから学べる要素を含んでいます。

参考リンク


1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?