LiteLLMとLangGraphでMCPサーバーのツールをエージェントから利用してみる
はじめに
データ事業本部のkobayashiです。
今回は MCP (Model Context Protocol) をLangGraphエージェントから利用する方法を紹介します。MCPはAnthropicが提唱したLLM向けのツール・リソース提供プロトコルで、2025年に急速に普及しました。サーバー側を実装すればClaude DesktopでもCursorでも自作エージェントでも同じツールが使えるのが利点です。今回はMCPをLangGraphエージェントが消費する側として使います。
MCP の仕組み
- MCPサーバーがツール・リソース・プロンプトを公開
- クライアント(LangGraphエージェントなど)が接続して利用
- トランスポートは
stdio/Streamable HTTP/SSEのいずれか
LangGraphから接続するには langchain-mcp-adapters パッケージを使います。
環境
Python 3.13
litellm 1.83.14
langgraph 1.1.10
langchain-mcp-adapters 0.1.0
mcp 1.5.0
langchain-litellm 0.6.4
$ uv pip install litellm langgraph langchain-litellm langchain-mcp-adapters mcp
サンプルMCPサーバー(FastMCP)
まず最小のMCPサーバーを書きます。fastapi_mcpの記事と同じFastMCPベースで、ローカル環境を確認するための2つのツール(Pythonバージョン取得 / インストール済みパッケージ一覧)を提供します。
"""MCP サーバー側の最小サンプル: 開発者環境の情報を返すツール群。
stdio トランスポートで動く。クライアントから
python sample_mcp_server.py で起動される。
"""
import sys
from importlib.metadata import distributions
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("dev-env-tools")
@mcp.tool()
def get_python_version() -> str:
"""現在実行中の Python のバージョン情報を返します。"""
return sys.version
@mcp.tool()
def list_installed_packages(prefix: str = "") -> list[str]:
"""インストール済み Python パッケージを返します。
prefix を指定すると、その文字列で始まるパッケージのみを返します
(例: prefix="fastapi" で fastapi 系だけに絞り込み)。
"""
packages = sorted(
f"{dist.metadata['Name']}=={dist.version}"
for dist in distributions()
if dist.metadata["Name"]
)
if prefix:
packages = [p for p in packages if p.lower().startswith(prefix.lower())]
return packages
if __name__ == "__main__":
mcp.run(transport="stdio")
このサーバーは単独で実行可能です(python sample_mcp_server.py)。@mcp.tool()のシグネチャと docstring がそのままMCPのスキーマになります。後段の Context7 と組み合わせると 「ローカル環境を確認 → 該当バージョンのドキュメントを引く」 という自然なワークフローが組めるようになります。
LangGraph から MCP ツールを利用
langchain-mcp-adapters の MultiServerMCPClient で MCP ツールをLangChain Toolに自動変換し、create_agentに渡せます。
"""LangGraph エージェントから MCP サーバーのツールを使う。
langchain-mcp-adapters の MultiServerMCPClient で
- ローカル sample_mcp_server.py(stdio)
- リモート Context7 MCP サーバー(streamable_http)
の2系統を束ね、1つのエージェントから利用する。
"""
import asyncio
import sys
from langchain.agents import create_agent
from langchain_litellm import ChatLiteLLM
from langchain_mcp_adapters.client import MultiServerMCPClient
async def main() -> None:
# 複数のMCPサーバーを束ねる
client = MultiServerMCPClient(
{
# ローカルのstdioサーバー
"sample": {
"command": sys.executable,
"args": ["sample_mcp_server.py"],
"transport": "stdio",
},
# リモートの Streamable HTTP サーバー(Context7: ライブラリのドキュメント検索)
"context7": {
"url": "https://mcp.context7.com/mcp",
"transport": "streamable_http",
},
}
)
# MCP サーバーが提供するツール群を取得(自動的に LangChain Tool に変換)
tools = await client.get_tools()
print(f"取得したツール: {[t.name for t in tools]}")
llm = ChatLiteLLM(model="openai/gpt-5-mini")
agent = create_agent(model=llm, tools=tools)
# 「ローカル環境を確認 → そのバージョンに合ったドキュメントを Context7 で引く」
# という自然なワークフローを1つのエージェントで完結させる。
result = await agent.ainvoke(
{
"messages": [
{
"role": "user",
"content": (
"このプロジェクトにインストールされている fastapi のバージョンを確認し、"
"そのバージョンに合わせて StreamingResponse の使い方を Context7 で調べ、"
"ポイントを3行で要約してください。"
),
}
]
}
)
print("\n=== 最終回答 ===")
print(result["messages"][-1].content)
asyncio.run(main())
ポイント:
複数サーバーの統合
MultiServerMCPClientの辞書には、stdio・HTTP・SSE が混在していてOKです。上記の例ではローカルの stdio サーバー(sample) と リモートの Streamable HTTP サーバー(Context7) を同時に登録し、エージェントから一括で利用しています。Context7 はライブラリ・フレームワークのドキュメントを検索する公開MCPサーバーで、認証なしで試せるため動作確認に便利です。
トランスポートの違い
| transport | 用途 |
|---|---|
stdio |
ローカルの子プロセスとして起動。Claude Desktopもこれ |
streamable_http |
HTTP経由(モダンな本番運用向け) |
sse |
レガシーなHTTP+SSE形式(旧仕様、新規採用は非推奨) |
実行結果は以下のようになります(ローカルMCPサーバー側のログが混在する)。
$ python mcp_client.py
INFO Processing request of type ListToolsRequest server.py:727
INFO Processing request of type CallToolRequest server.py:727
INFO Processing request of type ListToolsRequest server.py:727
取得したツール: ['get_python_version', 'list_installed_packages', 'resolve-library-id', 'query-docs']
=== 最終回答 ===
fastapi==0.136.1 がインストールされています。
StreamingResponse は async generator または通常の generator/iterator を content に取り、bytes(または文字列)チャンクを逐次送信でき、media_type / status_code / headers を指定して大きなレスポンスをメモリ効率よく配信します。
ストリーム後のクリーンアップは BackgroundTasks(または StreamingResponse の background 引数)で行い、プロキシ時は hop-by-hop ヘッダを除外、キャンセル対応のために await anyio.sleep(0) 等のチェックポイントを入れるのが推奨されます。
get_tools() でローカル sample 由来の get_python_version / list_installed_packages と、Context7 由来の resolve-library-id / query-docs の 計4ツールが1つのリスト として返り、エージェントはそれらを自律的に組み合わせて使っています。具体的には、まず list_installed_packages(prefix="fastapi") でローカル環境の fastapi バージョン(0.136.1)を特定し、続いて Context7 の resolve-library-id → query-docs で該当ライブラリの StreamingResponse 周辺ドキュメントを取得し、最後に3行に要約しています。create_agent に渡すツールの中身が「ローカル関数」か「リモートMCPサーバー越しの関数」かはエージェント側からは透過的で、ローカルの環境情報とリモートのドキュメントが1つのワークフローに自然に繋がっているのがポイントです。
LiteLLMでのモデル使い分け
| ロール | モデル | 理由 |
|---|---|---|
| ReActエージェント | openai/gpt-5-mini |
tool_use 安定性。MCPツールも結局は LangChain Tool として扱われるため、tool_callsの相性が良いGPTを採用 |
MCP は「ツール提供プロトコル」なので、LiteLLM 側で重要なのは取得したMCPツールに対して安定した tool_calls を発行できるモデルを選ぶことです。ChatLiteLLM(model="openai/gpt-5-mini") を anthropic/claude-sonnet-4-6 に置き換えれば即座に Claude 経由で同じMCPツールを使えるので、本番運用でモデルを切り替える検証も簡単です。
まとめ
LangGraphエージェントから MCP (Model Context Protocol) サーバーのツールを利用する方法を、langchain-mcp-adapters の MultiServerMCPClient で stdio / HTTP のMCPサーバーを束ね、create_agent にそのまま渡す形で紹介しました。
@mcp.tool() のシグネチャと docstring がそのまま MCP のスキーマとして公開され、クライアント側では client.get_tools() で受け取った瞬間に LangChain Tool に変換されるため、エージェント側のコードは「ローカル関数」も「MCPサーバー越しの関数」も区別なく扱えます。組織独自のツールを一度MCPサーバーとして実装しておけば、Claude Code・Cursor・社内エージェントすべてから再利用でき、LiteLLMでモデルだけプロバイダー非依存に切り替える運用と組み合わせると、モデルとツールの両方をプロバイダーに縛られずに構成できます。
最後まで読んでいただきありがとうございました。







