Apidog

オールインワン協働API開発プラットフォーム

API設計

APIドキュメント

APIデバッグ

APIモック

API自動テスト

AWS Lambda上にMCPサーバーをデプロイする方法

中村 拓也

中村 拓也

Updated on 4月 25, 2025

モデルコンテキストプロトコル(MCP)は、Claude、ChatGPTなどの大規模言語モデル(LLM)が外部世界と対話するための標準的な方法として急速に浮上しています。MCPはツールを構造的に定義することで、LLMがアクションを要求したり、リアルタイムデータを取得したり、外部APIと対話したりすることを可能にし、静的なトレーニングデータを超えた動きを実現します。

ただし、これらのMCP "サーバー"(ツールを提供する)の展開は、特に現代のクラウド環境では課題を伴うことがよくあります。多くの初期のMCP実装は、標準入力/出力(stdio)を介したローカル実行またはストリーミングのためのサーバー送信イベント(SSE)のようなプロトコルを使用するように設計されていました。機能的には問題ありませんが、これらのアプローチはしばしば永続的な接続と状態を持つ動作に依存しており、AWS Lambdaのようなスケーラブルで無状態、イベント駆動型のプラットフォームには不向きです。

AWS Lambdaは、オートスケーリング、従量課金制のコスト効率、およびサーバー管理のオーバーヘッドゼロという素晴らしい利点を提供します。このサーバーレス環境で堅牢な生産対応のMCPサーバーをどのように実行できるでしょうか?

MCPEngineにご注目ください。これは、これらの課題に特化して設計されたMCPのオープンソースのPython実装です。MCPEngineは、ストリーミング可能なHTTPをSSEとともにサポートし、AWS Lambdaのリクエスト/レスポンスモデルと完全に互換性があります。また、組み込みの認証サポートやパッケージングの簡素化など、生産展開に必要な基本的な機能を備えています。

この記事では、MCPEngineを活用してAWS Lambda上でMCPサーバーを構築および展開する方法を探ります。無状態ツール、状態管理、認証について解説します。

💡
Cursorでの幻覚を終わらせたいですか?

Apidog-MCP-Serverを使用すると、CursorがAPIドキュメントを直接読み取ることができ、オンライン公開ドキュメントやローカルのOpenAPIファイルのいずれかになります。

API設計をAIの真実の源にすることで、Apidog MCP Serverは、スキーマに基づくコード生成、エンドポイントを通じたインテリジェントな検索、コード変更がAPI契約と完璧に一致することを保証し、最終的には開発ワークフローをスリム化します。

コアコンセプト: MCPEngineとLambda

展開に入る前に、Lambda統合のためのMCPEngineの主要なコンポーネントを理解しましょう。

  1. MCPEngine: ツールを調整し、MCP通信を処理する中心クラス。
  2. @engine.tool()デコレータ: Python関数をMCPツールとして登録します。関数名はツール名になり、そのdocstringはLLMに提供される説明として機能します。
  3. engine.get_lambda_handler(): このメソッドはAWS Lambda互換のハンドラ関数を生成します。このハンドラを公開し、MCPEngineがLambdaのイベントペイロードをMCPリクエストに変換し、レスポンスをフォーマットするのを担います。

シンプルな無状態ツールの構築

基本から始めましょう: Lambdaに展開された無状態ツール。この例は、シンプルな挨拶ツールを提供します。

前提条件:

  • Python 3.8+
  • Lambda、ECR、およびIAMを管理する権限のあるAWSアカウント。
  • ローカルにインストールされたDocker。
  • 設定されたAWS CLI。

1. MCPEngineをインストール:

pip install mcpengine[cli,lambda]

2. アプリケーションを作成(app.py):

# app.py
from mcpengine import MCPEngine, Context

# エンジンを初期化
engine = MCPEngine()

@engine.tool()
def personalized_greeting(name: str) -> str:
    """
    指定された名前に対して親しみのある挨拶を生成します。
    誰かに挨拶するように求められたときは、このツールを使用します。
    """
    # シンプルな無状態ロジック
    return f"こんにちは、{name}さん!サーバーレスMCPの世界へようこそ。"

# Lambdaハンドラ関数を取得
handler = engine.get_lambda_handler()

このコードは、nameを受け取り文字列を返す単一のツールであるpersonalized_greetingを定義しています。handler変数はAWS Lambdaが呼び出すものです。

展開ワークフロー: コードからクラウドへ

MCPEngineアプリケーションをLambdaに展開するには、Dockerでコンテナ化し、それをAmazon Elastic Container Registry(ECR)にプッシュし、Lambda関数を設定します。

1. アプリケーションをDocker化(Dockerfile):

# 公式AWS Lambda Pythonベースイメージを使用
FROM public.ecr.aws/lambda/python:3.12

# コンテナ内の作業ディレクトリを設定
WORKDIR ${LAMBDA_TASK_ROOT}

# まず要求をコピーしてDockerキャッシュを活用
COPY requirements.txt .
# 依存関係をインストール(mcpengineがrequirements.txtにリストされていると仮定)
# または直接インストール: RUN pip install --no-cache-dir mcpengine[cli,lambda]
RUN pip install --no-cache-dir -r requirements.txt

# 残りのアプリケーションコードをコピー
COPY app.py .

# ハンドラ関数を実行するためのコマンドを設定(app.handlerはapp.pyのハンドラを意味します)
CMD ["app.handler"]

(requirements.txtファイルがmcpengine[cli,lambda]をリストしていることを確認するか、RUNコマンドを必要に応じて修正します)。

2. DockerイメージをビルドしてECRにプッシュ:

まず、ECRリポジトリを作成します(<region><repo-name>を置き換えます):

aws ecr create-repository --repository-name <repo-name> --region <region>

出力からAWSアカウントIDとリポジトリURIを確認してください(<account-id>.dkr.ecr.<region>.amazonaws.com/<repo-name>)。

次に、イメージをビルド、タグ付け、プッシュします:

# ECRでDockerを認証する
aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com

# イメージをビルドする(必要に応じてクロスアーキテクチャビルドの場合は--platformを使用)
docker build --platform=linux/amd64 -t <repo-name>:latest .

# ECR用にイメージにタグを付ける
docker tag <repo-name>:latest <account-id>.dkr.ecr.<region>.amazonaws.com/<repo-name>:latest

# イメージをECRにプッシュする
docker push <account-id>.dkr.ecr.<region>.amazonaws.com/<repo-name>:latest

3. Lambda関数を作成および設定:

最初にLambda用にIAM実行ロールが必要です。まだない場合は、基本的なものを作成してください:

# (簡略化 - 必要に応じて信頼ポリシーと権限を調整)
aws iam create-role --role-name lambda-mcp-role --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Service": "lambda.amazonaws.com"},"Action": "sts:AssumeRole"}]}'
aws iam attach-role-policy --role-name lambda-mcp-role --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

続いて、ECRイメージを使用してLambda関数を作成します(プレースホルダを置き換えます):

aws lambda create-function \
  --function-name mcp-greeter-function \
  --package-type Image \
  --code ImageUri=<account-id>.dkr.ecr.<region>.amazonaws.com/<repo-name>:latest \
  --role arn:aws:iam::<account-id>:role/lambda-mcp-role \
  --timeout 30 \
  --memory-size 512 \
  --region <region>

4. 関数URLを介して公開:

API GatewayなしでHTTP経由でLambdaを呼び出せるように、関数URLを作成します:

aws lambda create-function-url-config \
  --function-name mcp-greeter-function \
  --auth-type NONE \
  --region <region>

# 公開アクセスのための権限を追加(認証が必要な場合は調整)
aws lambda add-permission \
  --function-name mcp-greeter-function \
  --statement-id FunctionURLAllowPublicAccess \
  --action lambda:InvokeFunctionUrl \
  --principal '*' \
  --function-url-auth-type NONE \
  --region <region>

create-function-url-configコマンドから返された関数URLに注意してください。これであなたの無状態MCPサーバーが稼働しています!

lifespanコンテキストによる状態管理

Lambdaは無状態ですが、多くのツールは起動時に初期化されたデータベース、接続プール、またはその他のリソースにアクセスする必要があります。MCPEngineは、非同期コンテキストマネージャーを受け入れるlifespan引数でこれに対応しています。

lifespan関数は、Lambdaコンテナが起動するときにそのセットアップコード(yieldの前)を実行し、コンテナがシャットダウンするときにそのテアダウンコード(yieldの後)を実行します。yieldされた値は、ツール関数内でctx(Context)オブジェクトを介して利用可能になります。

シンプルなイベントロガーを構築して、イベントをRDS Postgresデータベースに保存する方法を見てみましょう。

1. app.pyを修正:

# app.py (状態を持つ例)
import os
import psycopg2
from contextlib import asynccontextmanager
from mcpengine import MCPEngine, Context

# DB接続の詳細を環境変数から取得することを前提としています
DB_HOST = os.environ.get("DB_HOST")
DB_USER = os.environ.get("DB_USER")
DB_PASS = os.environ.get("DB_PASS")
DB_NAME = os.environ.get("DB_NAME")

@asynccontextmanager
async def db_connection_manager():
    """データベース接続プールを管理します。"""
    conn = None
    try:
        print("DB接続を確立しています...")
        conn = psycopg2.connect(
            host=DB_HOST,
            user=DB_USER,
            password=DB_PASS,
            dbname=DB_NAME
        )
        # テーブルが存在しない場合は作成(シンプルな例)
        with conn.cursor() as cur:
             cur.execute("""
                CREATE TABLE IF NOT EXISTS events (
                    id SERIAL PRIMARY KEY,
                    event_name TEXT NOT NULL,
                    timestamp TIMESTAMP DEFAULT now()
                );
             """)
        conn.commit()
        print("DB接続が準備完了です。")
        yield {"db_conn": conn} # ctx.db_conn経由で接続を利用可能にする
    finally:
        if conn:
            print("DB接続を閉じています。")
            conn.close()

# lifespanマネージャーでエンジンを初期化
engine = MCPEngine(lifespan=db_connection_manager)

@engine.tool()
def log_event(event_name: str, ctx: Context) -> str:
    """指定された名前でイベントをデータベースに記録します。"""
    try:
        with ctx.db_conn.cursor() as cur:
            cur.execute("INSERT INTO events (event_name) VALUES (%s)", (event_name,))
        ctx.db_conn.commit()
        return f"イベント '{event_name}' が正常に記録されました。"
    except Exception as e:
        # 基本的なエラーハンドリング
        ctx.db_conn.rollback()
        return f"イベントの記録中のエラー: {e}"

@engine.tool()
def get_latest_events(limit: int = 5, ctx: Context) -> list[str]:
    """最新の記録されたイベントをデータベースから取得します。"""
    try:
        with ctx.db_conn.cursor() as cur:
            cur.execute("SELECT event_name, timestamp FROM events ORDER BY timestamp DESC LIMIT %s", (limit,))
            events = [f"[{row[1].strftime('%Y-%m-%d %H:%M:%S')}] {row[0]}" for row in cur.fetchall()]
            return events
    except Exception as e:
        return [f"イベント取得中のエラー: {e}"]

# Lambdaハンドラを取得
handler = engine.get_lambda_handler()

2. 展開に関する考慮事項:

  • データベース: アクセス可能なRDSインスタンス(または他のデータベース)が必要です。
  • ネットワーキング: RDSインスタンスへのアクセスを許可するためにLambda関数のVPC設定を構成します(セキュリティグループ、サブネット)。
  • 環境変数: Lambda関数にDB_HOSTDB_USERDB_PASSDB_NAMEを環境変数として渡します。
  • IAM: 他のAWSサービスにアクセスする場合、Lambda実行ロールが追加の権限を必要とすることがあります(例: データベース資格情報用のSecrets Manager)。

必要に応じてDockerfileを更新し(例: psycopg2-binaryをインストールするため)、イメージをビルドし直し、プッシュし、Lambda関数のコードと設定(環境変数、VPC設定)を更新します。

認証によるツールのセキュリティ

生産ツールには認証が必要です。MCPEngineは、Google、AWS Cognito、Auth0などのOpenID Connect(OIDC)プロバイダーとの統合を行っています。

1. OIDCプロバイダーを設定:
選択したプロバイダー(例: Google Cloud Console)でOAuthクライアントIDをセットアップします。クライアントIDと、フローに応じてクライアントシークレットが必要になる場合があります。

2. 認証のためにapp.pyを更新:

# app.py (認証された例 - スニペット)
import os
# ... 他のインポート ...
from mcpengine import MCPEngine, Context, GoogleIdpConfig # 他のIdpConfigを使用する場合

# ... db_connection_manager ...

# IDPを構成 - Googleを例に使用
# GOOGLE_CLIENT_IDが環境変数として設定されていると仮定
google_config = GoogleIdpConfig(
   client_id=os.environ.get("GOOGLE_CLIENT_ID")
   # 発行者はしばしば推測されるか、明示的に設定されます
)

# lifespanとIDP設定でエンジンを初期化
engine = MCPEngine(
    lifespan=db_connection_manager,
    idp_config=google_config
)

# log_eventツールを保護
@engine.auth() # このデコレータを追加
@engine.tool()
def log_event(event_name: str, ctx: Context) -> str:
    """指定された名前でイベントをデータベースに記録します。認証が必要です。"""
    # 必要に応じて認証されたユーザー情報にアクセスできます: user_email = ctx.user.email
    user_email = ctx.user.email if ctx.user else "不明"
    print(f"認証されたユーザー: {user_email}")
    try:
        # ... (データベースのロジックは同じ) ...
         return f"イベント '{event_name}' が {user_email} によって正常に記録されました。"
    except Exception as e:
        # ... エラーハンドリング ...
        return f"{user_email} によるイベント記録中のエラー: {e}"

# get_latest_eventsは無認証のままで大丈夫ですが、保護することもできます
@engine.tool()
def get_latest_events(limit: int = 5, ctx: Context) -> list[str]:
   # ... (ロジックはそのまま) ...

# Lambdaハンドラを取得
handler = engine.get_lambda_handler()

主な変更点:

  • GoogleIdpConfig(またはプロバイダーに適したもの)をインポートしました。
  • idp_config引数でMCPEngineをインスタンス化しました。
  • 認証が必要な関数(@engine.auth()でマークされた)に対して@engine.tool()@engine.auth()デコレータを追加しました。MCPEngineは、有効なJWTトークンなしのリクエストを自動的に拒否します。
  • 認証されたユーザー情報(JWTのクレームから)はctx.userを介して利用可能です。

3. 展開:

  • 認証に必要な環境変数(例: GOOGLE_CLIENT_ID)をLambda関数に渡します。
  • イメージを再構築/プッシュし、Lambda関数を更新します。

LLMクライアントの接続

MCPサーバーがLambdaに展開され、関数URLが作成されると、互換性のあるクライアントを接続できます。mcpengine proxyを使用することは、Claudeのようなクライアントと橋渡しをする便利な方法です:

mcpengine proxy <あなたの選んだサービス名> <あなたのLambda関数URL> --mode http --claude

認証を使用する場合:

mcpengine proxy <あなたの選んだサービス名> <あなたのLambda関数URL> \
  --mode http \
  --claude \
  --client-id <あなたのGoogleクライアントID> \
  --client-secret <あなたのGoogleクライアントシークレット> # トークン取得フローに必要

このコマンドは、Claudeが接続するローカルプロキシを実行します。プロキシは、その後HTTPを介してあなたのLambda関数URLに要求を転送し、認証フローを設定されている場合は処理します。LLMは、サーバーレスツールを発見し、呼び出すことができます。

結論

AWS Lambda上にMCPサーバーを展開することで、LLM機能の拡張に対して素晴らしいスケーラビリティと運用効率が解き放たれます。従来のMCP実装は無状態の環境では苦労することが多いですが、MCPEngineは堅牢なオープンソースの解決策を提供します。ストリーミング可能なHTTPをサポートし、lifespanを介してコンテキスト管理を提供し、OIDCとの認証の統合をシームレスに行うことで、MCPEngineはサーバーレスMCPを単なる可能性だけでなく、実際の生産利用ケースにとって実用的にします。シンプルな無状態ツールを構築する場合でも、複雑で状態を持つ認証されたアプリケーションを構築する場合でも、MCPEngineとAWS Lambdaを組み合わせることで、次世代のAI駆動交互作用のための強力なプラットフォームが提供されます。

💡
美しいAPIドキュメンテーションを生成する素晴らしいAPIテストツールが欲しいですか?

開発チームが最大限の生産性をもって協力できるための統合されたオールインワンプラットフォームが欲しいですか?

Apidogはすべての要求を満たし、Postmanをより手頃な価格で置き換えます
button