📁
toolの数が増え始めた`main.py`内のtoolを別のファイルへ分割する際のベストプラクティス
はじめに
MCPを使ったサーバーの開発では、よく@mcp.tool()を使ってツールを定義します。
しかし、ツールの数が増えてくると、すべてをmain.pyのような1つのファイルに記述するのは可読性が下がったり、管理が難しくなります。
この記事では、toolをファイル分割する際のベストプラクティスを実際のコードを通じて紹介します。
環境セットップ
まず必要なライブラリをインストールします。
uv init mcp-add-tools
uv add mcp[cli] langchain langchain-openai langgraph fastmcp langchain-mcp-adapters
次にclient.py(MCP Client + AI Agent)とserver.py(MCP Server)を作成します。
touch client.py
touch server.py
ツールを格納するディレクトリを作成し、各ツールのファイルを作成します。
mkdir tools
touch tools/add.py
touch tools/multiply.py
最後にOpenAIのAPIキーを環境変数に設定します。
export OPENAI_API_KEY=sk-...
ツール設計のベストプラクティス
1. ツールは機能ごとにファイルを分ける
ツールが複雑化したり数が増えたりする場合、機能ごとにファイルを分けることで管理が容易になります。
今回の例では、add.pyとmultiply.pyに計算機能を分割しています。
2. ツールの実装例
add.py
各ツールファイルには、そのツールの実装と必要なメタデータを含めます。
# tools/add.py
def add(a: int, b: int) -> int:
"""
Add two integers.
"""
return a + b
multiply.py
同様に他のツールも個別ファイルで実装します。
# tools/multiply.py
def multiply(a: int, b: int) -> int:
"""
Multiply two integers.
"""
return a * b
3. サーバー側でのツールの登録
サーバーファイルでは、各ツールをインポートしadd_toolで登録します。
# server.py
from fastmcp import FastMCP
from tools import add, multiply
mcp = FastMCP()
# tools
mcp.add_tool(add.add)
mcp.add_tool(multiply.multiply)
# Run server with streamable_http transport
if __name__ == "__main__":
mcp.run(transport="streamable-http")
4. クライアント側でのツールの利用
クライアント(MCP Client + AI Agent)はサーバーからツールを動的に読み込み、必要に応じてtoolを利用します。
# client.py
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
from langgraph.prebuilt import create_react_agent
from langchain_mcp_adapters.tools import load_mcp_tools
async def main():
async with streamablehttp_client("http://localhost:8000/mcp") as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize() # セッションを初期化
tools = await load_mcp_tools(session) # MCP Serverからtoolsを取得
agent = create_react_agent("openai:gpt-4.1-nano", tools) # AIエージェントのセットアップ
response = await agent.ainvoke({"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]}) # メッセージを送信
print(response)
if __name__ == "__main__":
asyncio.run(main())
実行方法
- まずサーバーを起動します:
uv run server.py
- 別のターミナルでクライアントを実行します:
uv run client.py
出力にtoolの呼び出しが表示されれば成功です!
まとめ
ツールの数が増えた場合のベストプラクティスは以下の通りです:
- 機能ごとにファイルを分ける: 関連する機能をまとめて、各ファイルの責務を明確にする
-
適切なディレクトリ構造:
toolsディレクトリの中に機能カテゴリごとにさらにサブディレクトリを作成することも検討する - main.pyでツールをインポートし、
add_toolでツールを登録する
このような構造にすることで、ツールが増えても保守性が高く、チームでの開発がしやすいプロジェクトを維持できます。
このプラクティスはpromptやresourceが増えた場合にも適用可能です。
参考文献
Discussion