AWS

MCP入門: Pythonコード例で学ぶAI連携

内山浩佑

はじめに

LLM(大規模言語モデル)は、人間のように自然な文章を生成し、複雑な質問に答え、多様なタスクをこなすことができます。しかし、LLM単体では限られた情報しか扱えず、外部のデータやツールと連携することで、より実用的な応用が可能になります。

そこで登場したのが MCP(Model Context Protocol) です。MCPは、LLMを外部システムに接続するためのオープンなプロトコルであり、標準化された方法でデータソースやツールと連携できます。これにより、リアルタイムで最新の情報にアクセスし、より正確な応答を生成できるようになります。

本記事では、MCPの概要を説明し、Pythonを使ったサーバー側とクライアント側の実装例を紹介します。

MCPとは?

MCPは、LLMアプリケーションと外部のデータソースやツールとのシームレスな統合を可能にするオープンなプロトコルです。LLMが様々なデータソースやツールに標準化された方法で接続できるようにすることで、ソフトウェア開発を簡素化し、AIシステムの柔軟性と安全性を向上させながら、開発の効率化を図ることを目的としています。

MCPは、ホストアプリケーションが複数のサーバに接続できるクライアント・サーバアーキテクチャを採用しています。主なコンポーネントは以下のとおりです。

  • ホスト: MCPを介してデータにアクセスするAIツール
  • クライアント: ホストによって作成され、サーバーとの1対1の接続を維持するプロトコルクライアント
  • サーバー: 標準化されたMCPを介して特定の機能を公開するプログラム

MCPの仕組み

MCPの動作は以下のようになります。

  1. ホスト(AIツール)がクライアントを生成
    • ホストは、MCPクライアントを通じてデータソースにアクセスします。
  2. クライアントがサーバーと通信(JSON-RPC)
    • クライアントは、MCPの仕様に基づいてサーバーとの接続を確立し、リクエストを送信します
    • トランスポート層では標準入出力やTCP(over HTTP)などが選択でき、セッション層ではJSON-RPC仕様に基づいています
  3. サーバーがデータや機能を提供
    • サーバーは、MCPのプロトコルに準拠し、要求されたデータや計算結果を提供します
  4. クライアントが応答をホストに返す
    • クライアントは、取得したデータをホストに渡し、AIの応答生成に活用されます

この仕組みにより、MCPを使用することで、LLMがリアルタイムの情報や外部システムと連携しやすくなります。

Pythonの実装

PythonでMCPサーバーとクライアントを実装していきます。

実装する機能

今回は、足し算と掛け算の計算機能と挨拶機能を提供するMCPサーバを作成します。また、AWS Bedrock Claudeとツール連携をするクライアントを作成します。

処理の流れは以下のようなイメージです。

  1. ユーザーが「5足す3はいくつ?」といった質問を入力する
  2. クライアントはこの質問とツール情報をClaudeに送信する
  3. Claudeは質問を処理し、必要に応じてツール呼び出しタグ(<add>5, 3</add>)を含めた応答を返す
  4. クライアントはこのタグを検出し、MCPサーバーの対応するツール(add)を呼び出す
  5. 計算結果(8)をMCPサーバーから受取、応答に組み込む
  6. 最終的な応答がユーザーに表示される

開発環境準備

今回は、バージョン3.12のPythonで動作確認をしています。

以下のコマンドで必要なパッケージをインストールします。

pip install boto3 python-dotenv mcp fastmcp pydantic anyio

クライアントでは、AWS BedrockのAPIを使用するため、AWSの認証情報が必要です。以下の内容で .env ファイルを作成します。

AWS_REGION=ap-northeast-1  # Bedrockが利用可能なリージョン
AWS_ACCESS_KEY_ID=アクセスキー
AWS_SECRET_ACCESS_KEY=シークレットキー
AWS_SESSION_TOKEN=セッショントークン

MCPサーバーの実装

MCPプロトコルに対応したサーバーを実装します。このサーバーは計算機能(足し算・掛け算)と挨拶機能を提供します。今回は FastMCPというライブラリを利用して、MCPサーバーを実装しています。

# simple_mcp.py
from fastmcp import FastMCP
import logging
import sys

# ロガーの設定
logger = logging.getLogger(__name__)
handler = logging.StreamHandler(sys.stderr)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)

# FastMCPサーバーの作成
mcp = FastMCP("Simple MCP Demo")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers together"""
    logger.info(f"add() tool called: a={a}, b={b}")
    result = a + b
    logger.info(f"add() result: {result}")
    return result

@mcp.tool()
def multiply(a: float, b: float) -> float:
    """Multiply two numbers"""
    logger.info(f"multiply() tool called: a={a}, b={b}")
    result = a * b
    logger.info(f"multiply() result: {result}")
    return result

@mcp.resource("server://status")
def get_server_status() -> str:
    """Get the current server status"""
    logger.info("get_server_status() resource called")
    status = {
        "status": "running",
        "version": "1.0.0",
        "uptime": "N/A"
    }
    logger.info(f"get_server_status() result: {status}")
    return str(status)

if __name__ == "__main__":
    # 標準入出力モードで実行
    logger.info("MCPサーバーを標準入出力(stdio)モードで起動します")
    mcp.run(transport="stdio")

このサーバーは、MCPプロトコルに準拠した以下の機能を提供します。

  • add: 2つの整数の足し算
  • multiply: 2つの数値の掛け算
  • greeting://: 名前に対して挨拶を返す
  • server://status: サーバーの状態を返す

クライアントの実装

クライアント側はAWS Bedrock上のClaude AIモデルと連携し、MCPサーバーのツールを使用します。

以下は、AWS Bedrock Claude連携の部分です。

   async def call_bedrock_claude(message: str, tools_info: str, conversation_history: Optional[List[str]] = None) -> str:
       """Call the Claude model through AWS Bedrock"""
       # AWS Bedrockクライアントの初期化
       bedrock = boto3.client(
           service_name='bedrock-runtime',
           region_name=os.getenv('AWS_REGION'),  # .envから環境変数を読み込み
           aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
           aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY'),
           aws_session_token=os.getenv('AWS_SESSION_TOKEN')
       )
       
       # 会話履歴の処理
       if conversation_history is None:
           conversation_history = []
       conversation_text = "\n".join(conversation_history) if conversation_history else ""
       
       # プロンプトの生成 - ツール情報を含める
       prompt = f"\n\nHuman: {tools_info}\n\n"
       if conversation_text:
           prompt += f"Previous conversation:\n{conversation_text}\n\n"
       
       # ユーザーのメッセージとツール使用の指示を追加
       prompt += f"User message: {message}\n\n"
       prompt += "If the user's request involves simple math like addition or multiplication, "
       prompt += "you should use the available tools to perform the calculation accurately.\n"
       
       # ツール使用のためのタグ形式を指定
       prompt += "For addition with two numbers, use: <add>number1, number2</add>\n"
       prompt += "For multiplication with two numbers, use: <multiply>number1, number2</multiply>\n"
       prompt += "For requesting a greeting for a specific name, use: <greeting>name</greeting>\n\n"
       prompt += "Assistant:"
       
       # Claude V2用のリクエスト構造
       request_body = {
           "prompt": prompt,
           "max_tokens_to_sample": 1000,
           "temperature": 0.7,
           "anthropic_version": "bedrock-2023-05-31"
       }
       
       # Bedrockを呼び出してレスポンスを取得
       response = bedrock.invoke_model(
           modelId=MODEL_ID,  # "anthropic.claude-v2:1"
           body=json.dumps(request_body).encode('utf-8'),
           contentType="application/json",
           accept="application/json"
       )
       
       # レスポンスを処理して返却
       response_text = response['body'].read().decode('utf-8')
       response_body = json.loads(response_text)
       completion = response_body.get("completion", "No response content")
       logger.info(f"Received response from model: {completion[:30]}...")
       
       return completion

次の処理をしています。

  • AWS Bedrockと連携してClaude AIモデルを呼び出す
  • 利用可能なツール情報をプロンプトに含める
  • ツールの使用方法をAIに指示する
  • AIの応答を取得して返す

以下は、ツール呼び出しの検出と実行部分です。

   async def handle_tool_calls(session: ClientSession, claude_response: str) -> str:
       """Handle any tool calls in Claude's response and update the response"""
       updated_response = claude_response
       
       # 足し算ツールの呼び出しを検出 (<add>5, 3</add>のようなパターン)
       add_match = re.search(ADD_PATTERN, claude_response, re.IGNORECASE | re.DOTALL)
       if add_match:
           # 数値を抽出
           a = int(add_match.group(1))
           b = int(add_match.group(2))
           logger.info(f"Detected add tool call: {a} + {b}")
           
           # MCPサーバーの「add」ツールを実行
           result = await session.call_tool("add", arguments={"a": a, "b": b})
           
           # 結果をPydanticモデルから辞書に変換してJSON化
           result_dict = model_to_dict(result)
           result_json = json.dumps(result_dict, cls=MCPJSONEncoder)
           result_obj = json.loads(result_json)
           
           # 結果の値を取得
           if isinstance(result_obj, dict) and "result" in result_obj:
               calc_result = result_obj["result"]
           else:
               calc_result = str(result_obj)
           
           # レスポンス内のツール呼び出しを結果で置換
           updated_response = re.sub(
               ADD_PATTERN,  # <add>数値, 数値</add>
               f"{a} + {b} = {calc_result}",  # 「5 + 3 = 8」という形式に変換
               updated_response,
               flags=re.IGNORECASE | re.DOTALL
           )
       
       # 掛け算ツールの呼び出しを検出 (<multiply>数値, 数値</multiply>)
       multiply_match = re.search(MULTIPLY_PATTERN, claude_response, re.IGNORECASE | re.DOTALL)
       if multiply_match:
           # 掛け算の処理も足し算と同様
           # ...
       
       # 挨拶リソースの呼び出しを検出 (<greeting>名前</greeting>)
       greeting_match = re.search(GREETING_PATTERN, claude_response, re.IGNORECASE | re.DOTALL)
       if greeting_match:
           # 挨拶リソースの処理
           # ...
       
       return updated_response

次の処理をしています。

  • AIの応答からツール呼び出しのタグを検出
  • MCPセッション経由で実際にツールを実行
  • 実行結果をAIの応答に反映させる
  • 複数種類のツールに対応(add, multiply)

以下は、MCP対応のセッション管理です。

   async def run():
       """Main function to run the MCP client for Bedrock Claude"""
       logger.info("Starting Bedrock Claude MCP client")
       
       # MCPサーバーパラメータの設定 - simple_mcp.pyをサブプロセスとして起動
       server_params = StdioServerParameters(
           command="python",
           args=["simple_mcp.py"],  # MCPサーバースクリプト
           env=None
       )
       
       try:
           # MCPサーバーとstdio経由で接続
           async with stdio_client(server_params) as (read, write):
               logger.info("Connected to simple_mcp.py server")
               
               # MCPクライアントセッションの確立
               async with ClientSession(
                   read, 
                   write, 
                   sampling_callback=sampling_callback  # AIモデル呼び出しコールバック
               ) as session:
                   # セッションの初期化(MCP handshake)
                   logger.info("Initializing MCP session")
                   await session.initialize()
                   
                   # 利用可能なツールとリソースの情報をサーバーから取得
                   logger.info("Listing available tools and resources")
                   tools_info = await create_tools_info(session)
                   logger.info(f"Tools info for Claude:\n{tools_info}")
                   
                   # 対話ループの開始
                   print("\nType your message to Claude (or 'exit' to quit):")
                   print("You can ask Claude to perform calculations or use available tools.")
                   print("For example: 'What is 5 + 3?' or 'Can you multiply 6 and 7?'")
                   
                   # ユーザー入力を受け付けるループ
                   while True:
                       # ユーザー入力の非同期取得
                       user_input = await asyncio.to_thread(input, "> ")
                       if user_input.lower() == 'exit':
                           break
                       
                       logger.info(f"Received user input: {user_input}")
                       
                       # sampling_callbackを通してClaudeにリクエスト送信
                       result = await sampling_callback(
                           context=RequestContext(...),  # 必要なコンテキスト情報
                           params=types.CreateMessageRequestParams(...)  # リクエストパラメータ
                       )
                       
                       # 応答の表示
                       print("\n===== Claude's response =====")
                       if isinstance(result, types.CreateMessageResult):
                           content = result.content
                           if isinstance(content, types.TextContent):
                               print(content.text)  # テキスト応答を表示
                           else:
                               print("Response is not text content")
                       else:
                           print(f"Error: {result.message}")
                       print("===========================\n")
       
       except Exception as e:
           logger.error(f"Error in run function: {str(e)}")
           raise
       
       logger.info("Bedrock Claude MCP client stopped")

次の処理をしています。

  • MCPサーバー(simple_mcp.py)の起動と接続(今回は標準入出力でやりとり)
  • MCPクライアントセッションの確立と初期化
  • ツール情報の取得と準備
  • ユーザー入力の受け付けとAIへのリクエスト送信
  • AIからの応答の表示
  • 例外処理とクリーンアップ

動作確認

サーバー実装ファイル(simple_mcp.py)とクライアント実装ファイル(client.py)を同じディレクトリに保存し、以下のコマンドでクライアントを実行します。

python client.py

プロンプトが表示されたら、質問を入力します。

> 5足す3はいくつですか?

AIは必要に応じてツールを使って回答します。

INFO - Received response from model:  5足す3は<add>5, 3</add>です。...
INFO - Detected add tool call: 5 + 3
INFO     Processing request of type CallToolRequest                       server.py:534
INFO - add() tool called: a=5, b=3
INFO     add() tool called: a=5, b=3                                   simple_mcp.py:19
INFO - add() result: 8
INFO     add() result: 8                                               simple_mcp.py:21
===== Claude's response =====
   5 + 3 = 8 です。
===========================

実際にMCPの機能が呼び出されて足し算処理が行われていることが、ログから読み取れます。

以上で、MCPサーバーとクライアントの実装が完了しました。今回の全体のソースコードは以下に置いています。

https://gist.github.com/KousukeUchiyama/910e0cb8ceba96574ca61ceab9f79986

まとめ

本記事では、MCPの概要、アーキテクチャ、Pythonでのサーバーサイドとクライアントサイドの実装例を紹介しました。MCPは、LLMアプリケーションと外部システムとの連携を容易にする強力なツールであり、AI開発の重要な役割を担うと考えられます。

参考サイト

  • https://docs.anthropic.com/en/docs/agents-and-tools/mcp
  • https://modelcontextprotocol.io/introduction
  • https://github.com/modelcontextprotocol
  • https://github.com/modelcontextprotocol/python-sdk

AUTHOR
uchiko
uchiko
記事URLをコピーしました