背景・目的
以前、下記を実施しMCPサーバの挙動を確かめました。今回は、クライアントを作成し理解を進めます。
実践
下記を元に試します。
構成は下記のとおりです。(こちらと同じです。)
環境準備
-
MCPクライアント用のディレクトリを作成します
% uv init mcp-client Initialized project `mcp-client` at `/Users/XXXX/git/mcp-client` % cd mcp-client %
-
venvでPythonの仮想環境を作成します
% uv venv Using CPython 3.12.5 interpreter at: /Users/XXXX/.pyenv/versions/3.12.5/bin/python3.12 Creating virtual environment at: .venv Activate with: source .venv/bin/activate %
-
仮想環境をアクティベートします
% source .venv/bin/activate (mcp-client) %
-
パッケージをインストールします
(mcp-client) % uv add mcp anthropic python-dotenv Resolved 26 packages in 257ms Prepared 4 packages in 221ms Installed 24 packages in 65ms + annotated-types==0.7.0 + anthropic==0.52.1 + anyio==4.9.0 + certifi==2025.4.26 + click==8.2.1 + distro==1.9.0 + h11==0.16.0 + httpcore==1.0.9 + httpx==0.28.1 + httpx-sse==0.4.0 + idna==3.10 + jiter==0.10.0 + mcp==1.9.2 + pydantic==2.11.5 + pydantic-core==2.33.2 + pydantic-settings==2.9.1 + python-dotenv==1.1.0 + python-multipart==0.0.20 + sniffio==1.3.1 + sse-starlette==2.3.6 + starlette==0.47.0 + typing-extensions==4.13.2 + typing-inspection==0.4.1 + uvicorn==0.34.3 (mcp-client) %
-
main.py
を削除します(mcp-client) % ls -l main.py -rw-r--r-- 1 XXXXX XXXXX 88 6 1 21:18 main.py (mcp-client) % cat main.py def main(): print("Hello from mcp-client!") if __name__ == "__main__": main() (mcp-client) % rm main.py (mcp-client) % ls -l main.py ls: main.py: No such file or directory (mcp-client) %
-
client.py
を作成します(mcp-client) % touch client.py (mcp-client) % ls -l client.py -rw-r--r-- 1 XXXXX XXXXX 0 6 1 21:25 client.py (mcp-client) %
APIキーの設定
ClaudeのAPIキー発行
-
Anthropic Consoleにサインインします
-
名前をつけて、「Add」をクリックします。API キーをメモしておきます
APIキーの保存
-
.env
ファイルを作成します(mcp-client) % touch .env
-
APIキーの保存します
ANTHROPIC_API_KEY=<your key here>
-
.gitignore
ファイルに.env
を追加します% echo ".env" >> .gitignore
クライアントの作成
基本的なクライアント構造
-
client.py
に、基本的なクライアントクラスを作成しますimport asyncio from typing import Optional from contextlib import AsyncExitStack from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from anthropic import Anthropic from dotenv import load_dotenv load_dotenv() # load environment variables from .env class MCPClient: def __init__(self): # Initialize session and client objects self.session: Optional[ClientSession] = None self.exit_stack = AsyncExitStack() self.anthropic = Anthropic() # methods will go here
- MCPクライアントの初期化
- self.session:HTTPセッションを管理
- self.exit_stack:複数の非同期コンテキストを安全に管理するためのスタック
- self.anthropic:Anthropicクライアントの初期化
サーバー接続管理
- MCPサーバに接続するメソッドを追加します
async def connect_to_server(self, server_script_path: str): """Connect to an MCP server Args: server_script_path: Path to the server script (.py or .js) """ is_python = server_script_path.endswith('.py') is_js = server_script_path.endswith('.js') if not (is_python or is_js): raise ValueError("Server script must be a .py or .js file") command = "python" if is_python else "node" server_params = StdioServerParameters( command=command, args=[server_script_path], env=None ) stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) self.stdio, self.write = stdio_transport self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write)) await self.session.initialize() # List available tools response = await self.session.list_tools() tools = response.tools print("\nConnected to server with tools:", [tool.name for tool in tools])
- MCPサーバーへの接続を管理する
- is_python、is_js:サーバースクリプトがPython(.py)かJavaScript(.js)かを判定
- self.session:クライアントセッションを作成
- tools = response.tools:サーバーで利用可能なツールの一覧を取得
クエリ処理ロジック
- クエリを処理し、ツール呼び出しを処理するためのコア機能を追加します
async def process_query(self, query: str) -> str: """Process a query using Claude and available tools""" messages = [ { "role": "user", "content": query } ] response = await self.session.list_tools() available_tools = [{ "name": tool.name, "description": tool.description, "input_schema": tool.inputSchema } for tool in response.tools] # Initial Claude API call response = self.anthropic.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=1000, messages=messages, tools=available_tools ) # Process response and handle tool calls final_text = [] assistant_message_content = [] for content in response.content: if content.type == 'text': final_text.append(content.text) assistant_message_content.append(content) elif content.type == 'tool_use': tool_name = content.name tool_args = content.input # Execute tool call result = await self.session.call_tool(tool_name, tool_args) final_text.append(f"[Calling tool {tool_name} with args {tool_args}]") assistant_message_content.append(content) messages.append({ "role": "assistant", "content": assistant_message_content }) messages.append({ "role": "user", "content": [ { "type": "tool_result", "tool_use_id": content.id, "content": result.content } ] }) # Get next response from Claude response = self.anthropic.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=1000, messages=messages, tools=available_tools ) final_text.append(response.content[0].text) return "\n".join(final_text)
- ユーザーのクエリを処理し、Claudeとツールを連携させる
- messages:ユーザーのクエリを初期メッセージとして設定
- response = await self.session.list_tools():サーバーから利用可能なツールの一覧を取得し、各ツールの名前、説明、入力スキーマを収集
- self.anthropic.messages.create:Claudeにクエリを送信
- content in response.content:Claudeの応答を解析
- messages.append:ツールの実行結果を会話履歴に追加
- self.anthropic.messages.create:Claudeの応答を取得するための処理です
インタラクティブチャットインターフェース
- チャットループとクリーンアップ機能を追加します
async def chat_loop(self): """Run an interactive chat loop""" print("\nMCP Client Started!") print("Type your queries or 'quit' to exit.") while True: try: query = input("\nQuery: ").strip() if query.lower() == 'quit': break response = await self.process_query(query) print("\n" + response) except Exception as e: print(f"\nError: {str(e)}") async def cleanup(self): """Clean up resources""" await self.exit_stack.aclose()
- チャットループを実装
- print("\nMCP Client Started!"):初期メッセージ
- uery = input("\nQuery: ").strip():input関数でユーザ入力を待つ
メインエントリーポイント
- メインの実行ロジックを追加します
async def main(): if len(sys.argv) < 2: print("Usage: python client.py <path_to_server_script>") sys.exit(1) client = MCPClient() try: await client.connect_to_server(sys.argv[1]) await client.chat_loop() finally: await client.cleanup() if __name__ == "__main__": import sys asyncio.run(main())
- MCPClientのインスタンスを作成
- サーバーに接続
- チャットループを開始
- 終了時にクリーンアップを実行
クライアントの実行
-
最初にweather.apiを実行します
(weather) % uv run weather.py
-
client.pyを実行します
(mcp-client) % uv run client.py ../weather/weather.py Processing request of type ListToolsRequest Connected to server with tools: ['get_alerts', 'get_forecast'] MCP Client Started! Type your queries or 'quit' to exit. Query:
-
東京の天気を探してみます
Query: 東京の天気は? Processing request of type ListToolsRequest Processing request of type CallToolRequest HTTP Request: GET https://api.weather.gov/points/35.6762,139.6503 "HTTP/1.1 404 Not Found" 申し訳ありませんが、このシステムでは日本の天気予報を直接取得することはできません。利用可能な機能は: 1. アメリカの州の気象警報を取得する機能 2. 緯度と経度を指定して天気予報を取得する機能 東京の天気を知りたい場合は、東京の緯度(約35.6762°N)と経度(約139.6503°E)を使って検索することができます。確認してみましょうか? [Calling tool get_forecast with args {'latitude': 35.6762, 'longitude': 139.6503}] 申し訳ありませんが、このシステムでは東京の位置の天気予報データを取得することができませんでした。このツールはアメリカ国内の天気予報に特化している可能性があります。日本の天気予報については、気象庁のウェブサイトや他の天気予報サービスをご利用いただくことをお勧めします。 Query:
-
ロサンゼルスの天気を調べてみます。表示されました。
Query: ロサンゼルスの天気は? Processing request of type ListToolsRequest Processing request of type CallToolRequest HTTP Request: GET https://api.weather.gov/points/34.0522,-118.2437 "HTTP/1.1 200 OK" HTTP Request: GET https://api.weather.gov/gridpoints/LOX/155,45/forecast "HTTP/1.1 200 OK" ロサンゼルスの天気予報を取得するために、get_forecastを使用しますが、緯度(latitude)と経度(longitude)の情報が必要です。 ロサンゼルスの代表的な座標を使用して天気予報を確認しましょう。ロサンゼルスの中心部の座標はおよそ緯度34.0522°N、経度118.2437°Wです。 [Calling tool get_forecast with args {'latitude': 34.0522, 'longitude': -118.2437}] ロサンゼルスの天気予報をお伝えします: 現在から明日にかけて: - 夜間は気温59°F(約15°C)で、霧が断続的に発生し、曇り空。南西からの弱い風(0-5 mph)。 土曜日: - 日中は気温76°F(約24°C)まで上昇。 - 午前11時までは霧が断続的に発生。 - その後は概ね晴れ。 - 南西の風0-10 mph。 土曜日夜: - 気温は59°F(約15°C)まで下がる。 - 午前5時以降は霧が発生する可能性。 - 部分的に曇り。 日曜日: - 気温は80°F(約27°C)まで上昇。 - 午前中は霧の可能性があるが、その後は概ね晴れ。 - 南西の風0-10 mph。 日曜日夜: - 気温61°F(約16°C)。 - 夜11時以降は霧の可能性。 - 部分的に曇り。 全体的に見ると、週末は穏やかで比較的温暖な天気が続く見込みです。朝晩は霧の発生に注意が必要ですが、日中は晴れて過ごしやすい天気になりそうです。 Query:
APIのCostを確認
- Anthropic Consoleにサインインします
API Keys
Cost
考察
今回、ClaudeのMCP構成をクライアントで構築した。MCPサーバとLLMの挙動がわかりより理解が深まりました。
今後は、複数ツールの連携を確認してみたいと思います。
参考