メインコンテンツへスキップ
見出し画像

FastMCP での MCPサーバ と MCPクライアント の構築を試す

    「FastMCP」での「MCPサーバ」と「MCPクライアント」の構築を試したので、まとめました。

    1. FastMCP

    「FastMCP」は、「MCPサーバ」や「MCPクライアント」をシンプルかつ直感的に構築できるパッケージです。

    「MCP」の実装には、サーバのセットアップ、プロトコルハンドラ、コンテンツタイプ、エラー管理など、多くの定型処理が必要です。「FastMCP」は複雑なプロトコルとサーバ管理をすべて処理するため、開発者はツールの構築に集中できます。

    「FastMCP」の目標は次のとおりです。

    ・高速
    高水準インターフェースによりコードが少なくなり、開発が高速化
    ・シンプル
    最小限の定型文で MCP サーバを構築
    ・Pythonic
    Python開発者にとって自然に感じられる
    ・完全
    コアMCP仕様の完全な実装を提供することを目指している

    「FastMCP v1」は、 MCPサーバを公開するための最も一般的なボイラープレートの抽象化に重点を置いており、現在では公式「MCP Python SDK」に含まれています。「FastMCP v2」では、その基盤を拡張し、柔軟なクライアント、プロキシとコンポジション、デプロイメントなど、主にサーバインタラクションの簡素化に重点を置いた新しい機能を導入しています。

    2. MCP

    「MCP」 (Model Context Protocol) を使用すると、LLMアプリケーションにデータと機能を安全かつ標準化された方法で公開するサーバを構築できます。これは「AI用のUSB-Cポート」とも呼ばれ、LLMを利用可能なリソースに接続するための統一された方法を提供します。APIと考えると分かりやすいかもしれませんが、LLMとのやり取りに特化して設計されています。

    「MCPサーバ」は以下のことが可能です。

    ・Resource
    データを公開します。GETエンドポイントのようなものです。LLMのコンテキストに情報をロードするために使用します。
    ・Tool
    関数を提供します。POSTエンドポイントのようなものです。コードを実行したりするために使用します。
    ・Prompt
    LLMインタラクションの再利用可能なテンプレートを通じてインタラクションパターンを定義します。

    3. FastMCPでのMCPサーバの構築

    FastMCPでのMCPサーバの構築手順は、次のとおりです。

    (1) uvのインストール。

    (2) プロジェクトの作成。

    uv init -p 3.10 letter_counter
    cd letter_counter

    (3) パッケージのインストール。

    uv add fastmcp

    (4) コードを作成。
    単語やフレーズの文字数を数えるシンプルなMCPサーバを作成します。

    ・letter_counter.py

    from fastmcp import FastMCP
    
    mcp = FastMCP("Letter Counter")
    
    @mcp.tool()
    def letter_counter(word: str, letter: str) -> int:
        """
        単語の中に文字が何回現れるかを数える。
    
        Args:
            word: 分析する単語またはフレーズ
            letter: 出現回数を数える文字
    
        Returns:
            単語中にその文字が現れる回数
        """
        return word.lower().count(letter.lower())
    
    if __name__ == "__main__":
        mcp.run(transport="stdio")

    4. CursorでのMCPサーバの利用

    CursorでのMCPサーバの利用手順は、次のとおりです。

    (1) 「Cursor」をインストールして起動。

    (2) メニュー「Cursor → 基本設定 → Cursor Settings → MCP」で「Add new global MCP server」をクリックし、「mcp.json」を以下のように編集。
    --directoryには自分のプロジェクトの絶対パスを指定してください。

    {
      "mcpServers": {
        "letter-counter": {
          "command": "uv",
          "args": [
            "--directory",
            "/Users/npaka/Documents/letter_counter",
            "run",
            "letter_counter.py"
          ]
        }
      }
    }
    画像

    (3) 「AI Pane」で「Strawberryに含まれるrの数は?」と質問。
    gpt-4oでも3つと答えることができることを確認します。

    画像

    5. FastMCPでMCPクライアントの構築

    (1) コードの作成。

    ・letter_counter_client.py

    import asyncio
    from fastmcp import Client
    from fastmcp.client.transports import PythonStdioTransport
    
    async def main():
        # サーバースクリプトのパス
        server_script = "/Users/npaka/Documents/letter_counter/letter_counter.py"
    
        # PythonStdioTransportを使用してサーバを起動
        transport = PythonStdioTransport(script_path=server_script)
    
        # クライアントを作成し、サーバと通信
        async with Client(transport) as client:
            # 利用可能なツールを取得
            tools = await client.list_tools()
            print("ツール:" ,tools)
    
            # ツールの呼び出し
            result = await client.call_tool("letter_counter", {"word": "Strawberry", "letter": "r"})
            print("ツール呼び出し結果:", result)
    
    if __name__ == "__main__":
        asyncio.run(main())

    (2) コードの実行。

    uv run letter_counter_client.py
    ツール: [
      Tool(
        name='letter_counter',
        description='''
        単語の中に文字が何回現れるかを数える。
    
        Args:
            word: 分析する単語またはフレーズ
            letter: 出現回数を数える文字
    
        Returns:
            単語中にその文字が現れる回数
        ''',
        inputSchema={
          'properties': {
            'word': {'title': 'Word', 'type': 'string'},
            'letter': {'title': 'Letter', 'type': 'string'}
          },
          'required': ['word', 'letter'],
          'title': 'letter_counterArguments',
          'type': 'object'
        },
        annotations=None
      )
    ]
    
    ツール呼び出し結果: [
      TextContent(
        type='text',
        text='3',
        annotations=None
      )
    ]

    6. FastMCPでのMCPホストの構築

    OpenAIのFunction Callingで、MCPクライアントを呼び出してみます。

    (1) 環境変数「OPENAI_API_KEY」に「OpenAI APIキー」を設定。

    (2) コードの作成

    ・letter_counter_host.py

    import asyncio
    import json
    import os
    from openai import OpenAI
    from fastmcp import Client
    from fastmcp.client.transports import PythonStdioTransport
    
    # OpenAIのクライアントの準備
    openai_api_key = os.getenv("OPENAI_API_KEY")
    client = OpenAI(api_key=openai_api_key)
    
    # サーバースクリプトのパス
    SERVER_SCRIPT = "/Users/npaka/Documents/letter_counter/letter_counter.py"
    
    # MCPサーバからツールスキーマを取得
    async def get_tools():
        transport = PythonStdioTransport(script_path=SERVER_SCRIPT)
        async with Client(transport) as client:
            tools = await client.list_tools()
            return tools
    
    # MCPサーバのツールを呼び出す
    async def call_tool(tool_name, tool_args):
        transport = PythonStdioTransport(script_path=SERVER_SCRIPT)
        async with Client(transport) as client:
            result = await client.call_tool(tool_name, tool_args)
            return result
    
    def main():
        # メッセージリストの準備
        messages = [
            {"role": "user", "content": "Strawberryに含まれるrの数は?"}
        ]    
    
        # ツールの準備
        tools = [
            {
                "type": "function",
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.inputSchema
            }
            for tool in asyncio.run(get_tools())
        ]
        print("ツール:", tools)
    
        # 推論の実行
        response = client.responses.create(
            model="gpt-4o",
            input=messages,
            tools=tools
        )
    
        if response.output[0].type == "function_call":
            # 関数呼び出し
            tool_call = response.output[0]
            tool_name = tool_call.name
            tool_args = json.loads(tool_call.arguments)
            result = asyncio.run(call_tool(tool_name, tool_args))
            print("関数呼び出し結果:", result)
    
    
            # メッセージリストの準備
            messages.append(tool_call)
            messages.append({
                "type": "function_call_output",
                "call_id": tool_call.call_id,
                "output": str(result)
            })
    
            # 推論の実行
            response2 = client.responses.create(
                model="gpt-4o",
                input=messages,
                tools=tools,
            )
            print("応答:", response2.output_text)        
        else:
            print("関数呼び出しが見つかりませんでした。")
    
    if __name__ == "__main__":
        main()

    (3) コードの実行。

    uv run letter_counter_host.py
    ツール: [
      {
        'type': 'function',
        'name': 'letter_counter',
        'description': '''
        単語の中に文字が何回現れるかを数える。
    
        Args:
            word: 分析する単語またはフレーズ
            letter: 出現回数を数える文字
    
        Returns:
            単語中にその文字が現れる回数
        ''',
        'parameters': {
          'properties': {
            'word': {'title': 'Word', 'type': 'string'},
            'letter': {'title': 'Letter', 'type': 'string'}
          },
          'required': ['word', 'letter'],
          'title': 'letter_counterArguments',
          'type': 'object'
        }
      }
    ]
    
    関数呼び出し結果: [
        TextContent(type='text', text='3', annotations=None)
    ]
    
    応答: 「Strawberry」には「r」が3回含まれています

    次回



     
     

    npaka

     
     
    プログラマー。iPhone / Android / Unity / ROS / AI / AR / VR / RasPi / ロボット / ガジェット。年2冊ペースで技術書を執筆。アニソン / カラオケ / ギター / 猫 twitter : @npaka123

    あなたへのおすすめ