2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

前編では、ローカルLLM「GPT4All」の特徴・環境構築方法・APIサーバ機能について取り扱いました。本稿では、GPT4AllのAPIサーバ機能を用いたMCPサーバとの連携方法について解説いたします。

MCPサーバを用いた推論の流れ

まず、ClaudeやCursorといった一般的なMCPクライアントがどういった流れでMCPサーバを用いて推論を行っているのか見てみましょう。
MCPサーバの機能(公式ドキュメントでは”Primitive”と表現)の一つである”ツール”を用いた推論の流れは、以下のようになります。
スクリーンショット 2025-12-18 104941.png
※この図はBuild an MCP clientの例を参考に作成しています

しかし、前編でも述べた通りGPT4AllはMCPサーバをネイティブサポートしておらず、GPT4All自身がMCPクライアントとして振る舞うことはできません。GPT4AllがMCPサーバと連携して推論を実施するためには、間を結ぶMCPクライアントを用意する必要があります。
スクリーンショット 2025-12-18 110541.png
このように、MCPのルールに基づいて通信を行う②~⑤について、データの形成やLLMへの指示をMCPクライアントが受け持つことで、GPT4AllでMCPサーバを用いた推論が可能になります。
本稿では上図のような環境構成で実装を進めていきます。

MCPサーバの作成

■筆者の環境
・Windows 11 Enterprise
・GPT4All 3.9.0
・mcp 1.9.1
・requests 2.32.3

まず、連携するMCPサーバを作成します。
今回は与えられた2つの整数を足し算するツールを提供するMCPサーバを作成します。ただし、LLMがMCPサーバを使用して足し算を実行したか分かるように、以下のようにわざと1多い数を返す足し算ツールを定義します。

mcp_server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("add")

@mcp.tool()
def add(a: int, b: int) -> int:
    """2つの整数を加算します。"""
    return a + b + 1

if __name__ == "__main__":
    mcp.run(transport="stdio")

MCPクライアントの作成

次に、MCPの公式ドキュメント(Build an MCP client)を参考に、MCPクライアントを作成します。
ポイントは、LLMにクエリを発行する際、システムプロンプトであらかじめ使用可能なツールとその使い方を説明しておき、ツールを使用する場合はMCPに準ずるJSONの形式で応答を作成するように指示している点です。
そうすることで、MCPに則ってツールの呼び出しを実行でき、ツールの実行結果を踏まえて推論することができます。

async def process_query(self, user_query: str):
    """クエリ処理"""        
    # システムプロンプトでツールの使い方を説明
    system_prompt = f"""あなたは有能なアシスタントです。
    
    以下のツールが利用可能です:
    {json.dumps([{"name": t.name, "description": t.description, "parameters": t.inputSchema} for t in self.available_tools], indent=2, ensure_ascii=False)}

    ユーザーの質問に答える際、必要に応じてツールを使用してください。
    ツールを使用する場合は、以下の形式でJSON形式のみを返してください:
    {{"tool": "ツール名", "arguments": {{"arg1": "value1", "arg2": "value2"}}}}

    ツールを使用しない場合は、通常通り回答してください。
"""

1回目のクエリ応答結果(環境構成図で言うと③)がJSON形式のツール呼び出しか否かで、ツール実行に分岐するかどうかを判断しています。

# GPT4Allにクエリを送信
gpt4all_response = self.query_gpt4all(user_query, system_prompt)
print(f"\n📝 GPT4All応答:\n{gpt4all_response}")
        
# JSON形式のツール呼び出し応答を検出
if "{" in gpt4all_response and "tool" in gpt4all_response:
 # JSON抽出→ツール実行→実行結果を踏まえクエリ再発行
    ・・・

以下がMCPクライアントのコード全文です。

mcp_client.py
import asyncio
import json
import sys
import requests
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

class MCPClientWithGPT4All:
    def __init__(self, server_script: str, gpt4all_url: str = "http://localhost:4891/v1/chat/completions"):
        self.server_script = server_script
        self.gpt4all_url = gpt4all_url
        self.mcp_session = None
        self.available_tools = []
        self.exit_stack = AsyncExitStack()
        
    async def connect_to_mcp_server(self):
        """MCPサーバへの接続を確立"""
        server_params = StdioServerParameters(
            command="python",
            args=[self.server_script],
            env=None
        )
        
        stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
        self.stdio, self.write = stdio_transport
        self.mcp_session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
        
        await self.mcp_session.initialize()
        
        response = await self.mcp_session.list_tools()
        self.available_tools = response.tools
        print(f"✅ MCPサーバーに接続: {len(self.available_tools)} ツールが利用可能")
        for tool in self.available_tools:
            print(f"  - {tool.name}: {tool.description}")
    
    async def cleanup(self):
        """リソースのクリーンアップ"""
        await self.exit_stack.aclose()
        
    def query_gpt4all(self, user_message: str, system_prompt: str = "") -> str:
        # GPT4Allサーバへクエリを送信
        messages = []
        
        if system_prompt:
            messages.append({"role": "system", "content": system_prompt})
        
        messages.append({"role": "user", "content": user_message})
        
        data = {
            "model": "xxx",  # GPT4Allサーバでは無視される
            "messages": messages,
            "max_tokens": 300,
            "temperature": 0.7
        }
        
        response = requests.post(self.gpt4all_url, json=data, timeout=60)
        response.raise_for_status()
        result = response.json()
        return result["choices"][0]["message"]["content"]
    
    async def call_tool(self, tool_name: str, arguments: dict) -> str:
        """MCPツールを呼び出し"""
        if not self.mcp_session:
            raise RuntimeError("MCPサーバに接続されていません")
        
        response = await self.mcp_session.call_tool(tool_name, arguments)
        
        # レスポンスからテキストを抽出
        if response.content:
            return "\n".join([item.text for item in response.content if hasattr(item, 'text')])
        return "結果なし"
    
    async def process_query(self, user_query: str):
        """クエリ処理"""
        
        # システムプロンプトでツールの使い方を説明
        system_prompt = f"""あなたは有能なアシスタントです。
        以下のツールが利用可能です:
        {json.dumps([{"name": t.name, "description": t.description, "parameters": t.inputSchema} for t in self.available_tools], indent=2, ensure_ascii=False)}

        ユーザーの質問に答える際、必要に応じてツールを使用してください。
        ツールを使用する場合は、以下の形式でJSON形式のみを返してください:
        {{"tool": "ツール名", "arguments": {{"arg1": "value1", "arg2": "value2"}}}}

        ツールを使用しない場合は、通常通り回答してください。
        """
        
        # GPT4Allにクエリを送信
        gpt4all_response = self.query_gpt4all(user_query, system_prompt)
        print(f"\n📝 GPT4All応答:\n{gpt4all_response}")
        
        # JSON形式のツール呼び出し応答を検出
        if "{" in gpt4all_response and "tool" in gpt4all_response:
            # JSONを抽出
            json_start = gpt4all_response.find("{")
            json_end = gpt4all_response.rfind("}") + 1
            json_str = gpt4all_response[json_start:json_end]
            tool_call = json.loads(json_str)
            
            if "tool" in tool_call and "arguments" in tool_call:
                tool_name = tool_call["tool"]
                arguments = tool_call["arguments"]
                
                print(f"\n🔧 ツール実行: {tool_name}")
                print(f"   引数: {arguments}")

                # ツールを呼び出し                    
                result = await self.call_tool(tool_name, arguments)
                print(f"\n✅ ツール結果:\n{result}")
                
                # 結果を含めて再度GPT4Allに問い合わせ
                follow_up = f"ツール '{tool_name}' の実行結果: {result}\n\nこれを踏まえて、ユーザーの質問「{user_query}」に答えてください。"
                final_response = self.query_gpt4all(follow_up)
                print(f"\n🎯 最終応答:\n{final_response}")
        else:
            # ツール呼び出しではない通常の応答
            print("✅ 完了")

async def main():    
    if len(sys.argv) < 2:
        print("Usage: python client.py <path_to_server_script>")
        sys.exit(1)
    
    client = MCPClientWithGPT4All(sys.argv[1])
    try:
        await client.connect_to_mcp_server()
        
        # クエリ1: 足し算(MCPツールを使用)
        print("\n" + "=" * 60)
        print("📊 クエリ1")
        print("=" * 60)
        query1 = "25 + 37を計算してください。計算には利用可能なツールを使用してください。"
        await client.process_query(query1)
        
        # クエリ2: 通常の質問(MCPツールを使用しない)
        print("\n\n" + "=" * 60)
        print("📊 クエリ2")
        print("=" * 60)
        query2 = "Pythonとは何ですか?簡潔に説明してください"
        await client.process_query(query2)
                
    finally:
        await client.cleanup()

if __name__ == "__main__":
    asyncio.run(main())

メイン関数の中でクエリを2つ定義しています。クエリ1が足し算のクエリで、利用可能なツールを使うように指示しています。クエリ2は、ツールを使わずに推論可能な内容になっています。

それでは、プログラムを実行してみましょう。このMCPクライアントは、標準入出力 (SIDIO)形式でMCPサーバと通信します。以下のように、コマンドライン引数でMCPサーバを指定してプログラムを実行します。

> python mcp_client.py mcp_server.py
✅ MCPサーバに接続: 1 ツールが利用可能
  - add: 2つの整数を加算します。
============================================================
📊 クエリ1
============================================================
📝 GPT4All応答:
{"tool": "add", "arguments": {"a": 25, "b": 37}}
🔧 ツール実行: add
   引数: {'a': 25, 'b': 37}
✅ ツール結果:
63
🎯 最終応答:
答えは、25 + 37 = 62です。
============================================================
📊 クエリ2
============================================================
📝 GPT4All応答:
Pythonは、オブジェクト指向型のプログラミング言語です。
✅ 完了

まずMCPサーバに接続し、ツールの情報が出力されているのが分かります。
次に、クエリ1の推論を実施しています。クエリ1では1回目の応答でMCPサーバの使い方(ツール名と引数)が返ってきているのが分かります。
その後、ツールの実行結果を踏まえて最終的な推論結果を出力しています。ツールの実行結果が”63”であるにも関わらず、LLMは”25+37=62”と出力しています。これはツールが誤った計算結果を返していることをLLMが理解している、ということですね。
次に、クエリ2の推論を実施しています。クエリ2では、推論にツールを使用する必要がないため、1回目の応答で推論が完了していることが分かります。

まとめ・所感

本稿では、MCPサーバをネイティブサポートしていないGPT4Allで、MCPサーバと連携した推論を実施する方法について取り扱いました。
今回の実装では期待する動作を確認することができましたが、プロンプトを使ってMCPのルールをLLMにインプットするという手法であるため、プロンプトの内容やLLMの精度への依存度が高く、確実性が保証できないのが事実です。また、JSONの抽出ロジックも簡易的なものであり、複数のMCPサーバおよびツールを扱う場合や、ツール以外のMCPサーバ機能を使う場合には、より複雑なロジックで対応する必要があり、デバッグが困難になることが予想されます。
実用システムへの適用には多くの課題がありますが、ローカル環境でコストをかけずにLLMとMCPの統合を学ぶ第一歩として、本稿を活用いただけたら幸いです。

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?