LangChainでMCP (Model Context Protocol)を使ってみよう #langchain #mcp #python

はじめに
2024年11月末、生成AIチャットClaudeで著名なAnthropicが、Model Context Protocol (MCP)のオープンソース化を発表しました。その後、2025年3月から4月にかけて急速に注目を集めています。
事前知識としてOpenAIのFunction callingを見ておきましょう。簡単に言うとGPTなどOpenAIのLLMモデルと外部サービスを連携する仕組みです。過去記事「LangGraphのTool callingでOpenAI APIのFunction callingを試してみよう」もご覧ください。
MCPは、この「LLMモデルと外部サービスを連携する仕組み」を統一しようという試みです。 OpenAIでもMCPを採用し、MCPがデファクトスタンダードになる期待が膨らんでいるようです。
本稿ではLangChainのMCP Adaptersを利用して、MCPのデモンストレーションをしてみます。なお、MCPの詳細なアーキテクチャなどは本稿では触れません。
前提条件
次の環境で実施してみます。
- Python 3.13.3
- LangChain 0.3.14
- LangChain MCP Adapters 0.0.9
- LangChain OpenAI 0.3.14
LLMモデルは Azure OpenAI を通して使います。
シングルサーバの例
MCPサーバ
本家クイックスタートのコードをそのまま使います。
# math_server.py from mcp.server.fastmcp import FastMCP mcp = FastMCP("Math") @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers""" return a + b @mcp.tool() def multiply(a: int, b: int) -> int: """Multiply two numbers""" return a * b if __name__ == "__main__": mcp.run(transport="stdio")
足し算とかけ算を行うMCPサーバです。
「MCPサーバ」という名前なので勘違いしやすいですが、このサンプルでは、どこかリモートに「サーバ」を立てて起動しておくのではなく、MCPクライアントと同一マシン上で実行することになります。 transport="stdio"
とあるように、MCPクライアント・サーバ間は標準入出力で通信を行います。
MCPクライアント
本家クイックスタートのコードは次のシンタックスエラーが出て動作しません。
File "/home/dai/langgraph/client.py", line 17 async with stdio_client(server_params) as (read, write): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SyntaxError: 'async with' outside async function
SyntaxError: 'async with' outside async functionというバグレポートが出されているのですがなぜか却下されており、別途 ready-to-use examples が提供されているのでそちらを参考に、コードを作成します。Azure OpenAIに接続するように修正しています。
import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from langchain_mcp_adapters.tools import load_mcp_tools from langgraph.prebuilt import create_react_agent from langchain_openai import AzureChatOpenAI import os from dotenv import load_dotenv load_dotenv() model = AzureChatOpenAI( azure_deployment = os.getenv("AZURE_OPENAI_MODEL_NAME"), api_version = os.getenv("AZURE_OPENAI_API_VERSION"), temperature = 0.95, max_tokens = 1024, ) server_params = StdioServerParameters( command="python", args=[ os.path.join( os.path.dirname(os.path.abspath(__file__)), "math_server.py", ), ], ) async def run_agent(): async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: # Initialize the connection await session.initialize() # Get tools tools = await load_mcp_tools(session) # Create and run the agent agent = create_react_agent(model, tools) agent_response = await agent.ainvoke({"messages": "what's (3 + 5) x 12?"}) return agent_response if __name__ == "__main__": result = asyncio.run(run_agent()) print(result) print(result["messages"][-1].content)
ここではLangGraphのcreate_react_agentを使って、起動したMCPサーバに what's (3 + 5) x 12?
メッセージを送り、受け取ったメッセージを表示するという形になっています。
実行例
MCPクライアントを実行すると次のようになります。
[04/23/25 16:55:29] INFO Processing request of type server.py:534 ListToolsRequest [04/23/25 16:55:31] INFO Processing request of type server.py:534 CallToolRequest INFO Processing request of type server.py:534 CallToolRequest (((中略))) The result of (3 + 5) x 12 is 96.
正しい結果が得られました。内部でMCPサーバを起動し、そちらに処理を行わせたようです。
マルチサーバの例
2つのMCPサーバとやりとりできるようにします。
MCPサーバ
1つ目のMCPサーバは、シングルサーバの足し算・かけ算サーバをそのまま用います。
2つ目のMCPサーバは、指定の場所の天気を返す「ふりをする」サーバです。本家クイックスタートのコードそのままです。
# weather_server.py from typing import List from mcp.server.fastmcp import FastMCP mcp = FastMCP("Weather") @mcp.tool() async def get_weather(location: str) -> str: """Get weather for location.""" return "It's always sunny in New York" if __name__ == "__main__": mcp.run(transport="sse")
こちらを実行すると、HTTPサーバとして起動して待ち受け状態になります。
(langgraph) % ./weather_server.py INFO: Started server process [842369] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
transport="sse"
はMCPクライアント・サーバ間の通信をSSE (Server-Sent Events) で行うことを意味しています。これならば標準入出力と異なり、リモートにサーバを立てておくことができそうですが、サンプルでは認証などを行っていないためローカル起動に留めておくべきでしょう。
MCPクライアント
本家クイックスタートのコードは、やはり次のシンタックスエラーが出て動作しません。意図的に不完全なコードなのでしょうか?
(langgraph) % python client.py File "/home/dai/langgraph/client.py", line 7 async with MultiServerMCPClient( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SyntaxError: 'async with' outside async function
やはり別途 ready-to-use examples が提供されているので、Azure OpenAIに接続するように修正してコードを作成します。
from dotenv import load_dotenv load_dotenv() import asyncio import os from langchain_mcp_adapters.client import MultiServerMCPClient from langgraph.prebuilt import create_react_agent from langchain_openai import AzureChatOpenAI from dotenv import load_dotenv load_dotenv() model = AzureChatOpenAI( azure_deployment = os.getenv("AZURE_OPENAI_MODEL_NAME"), api_version = os.getenv("AZURE_OPENAI_API_VERSION"), temperature = 0.95, max_tokens = 1024, ) async def run_agent(): async with MultiServerMCPClient( { "math": { "command": "python", "args": [ os.path.join( os.path.dirname(os.path.abspath(__file__)), "math_server.py", ), ], "transport": "stdio", }, "weather": { # make sure you start your weather server on port 8000 "url": f"http://localhost:8000/sse", "transport": "sse", } } ) as client: agent = create_react_agent(model, client.get_tools()) math_response = await agent.ainvoke({"messages": "what's (3 + 5) x 12?"}) print(math_response["messages"][-1].content) weather_response = await agent.ainvoke({"messages": "what is the weather in nyc?"}) print(weather_response["messages"][-1].content) if __name__ == "__main__": asyncio.run(run_agent())
ここではLangGraphのcreate_react_agentを使って、起動したMCPサーバに what's (3 + 5) x 12?
メッセージと what is the weather in nyc?
メッセージを送り、受け取ったメッセージを表示するという形になっています。
実行例
MCPクライアントを実行すると次のようになります。
[04/24/25 12:25:00] INFO Processing request of type server.py:534 ListToolsRequest [04/24/25 12:25:02] INFO Processing request of type server.py:534 CallToolRequest [04/24/25 12:25:03] INFO Processing request of type server.py:534 CallToolRequest The result of \((3 + 5) \times 12\) is 96. The weather in New York City is sunny. Enjoy the sunshine!
まず内部で足し算とかけ算を行うMCPサーバを起動し、そちらに処理を行わせて正しい計算結果を導き出しました。
指定の場所の天気を返す「ふりをする」サーバの出力を見てみましょう。
INFO: Started server process [842369] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) INFO: 127.0.0.1:41918 - "GET /sse HTTP/1.1" 200 OK INFO: 127.0.0.1:41930 - "POST /messages/?session_id=23c3c99f32db4797ac856b1e55290426 HTTP/1.1" 202 Accepted INFO: 127.0.0.1:41930 - "POST /messages/?session_id=23c3c99f32db4797ac856b1e55290426 HTTP/1.1" 202 Accepted INFO: 127.0.0.1:41930 - "POST /messages/?session_id=23c3c99f32db4797ac856b1e55290426 HTTP/1.1" 202 Accepted [04/24/25 12:25:00] INFO Processing request of type server.py:534 ListToolsRequest INFO: 127.0.0.1:41930 - "POST /messages/?session_id=23c3c99f32db4797ac856b1e55290426 HTTP/1.1" 202 Accepted [04/24/25 12:25:05] INFO Processing request of type server.py:534 CallToolRequest
GETやPOSTによってアクセスがあったことがわかり、Processing request of type ListToolsRequestとCallToolRequestでMCPサーバで処理を行ったことがわかります。こちらで「NYCの天気」を返したと思われます。
まとめ
本稿ではLangChainのMCP Adaptersのサンプルスクリプトを実行することで、MCP (Model Context Protocol)のさわりを見てみました。実際の処理を行うMCPサーバの作り込みは当然ながら必要であるものの、MCPクライアントとなる呼び出し部分はかなり簡単に作成できるようです。
MCPサーバは既に多くが用意されており、すぐにでも実用的に使えそうです。ただしその手軽さ・柔軟さゆえにセキュリティ面での心配があります。まだ登場したばかりで十分に成熟してないが故に、採用には慎重さが必要となるでしょう。
引き続き、MCPがどのように進化していくかを追っていきたいと思います。