2025/6/8 langchain_mcp_adapters は tool 実行のたびに再接続をする
MCP Integration
langchain-ai/langchain-mcp-adapters
Python 版 LangGraph で Playwright を繋いで遊ぶ時に、ページ遷移 & クリック等がまともに動作しない
No current snapshot available in playwright
adapter の README にあるように
Example above will start a new MCP ClientSession for each tool invocation. If you would like to explicitly start a session for a given server, you can do:
tool 実行のたびにセッションを再作成する
MCP Server 側に状態があるもの、具体的には Playwright のページ遷移 → snapshot 取得 → クリック のようなものは都度接続しなおしているので動かない
動かしたい場合はセッションを作って管理する必要がある
実装例 https://gist.github.com/pokutuna/f1ce8a40b3bfad7c9e93f6d645c22630
1. async with mcp_client.session("playwright") as session: の contextmanager の中で全部やる
session を tool に渡さないといけないのでこのスコープで graph も構築することになる
2. 実行は contextmanager でやるが、configurable に session を渡して実行時に引き回す
graph は都度作らなくて済むが、create_react_agent で済む内容を都度実装する必要がある
単に遊ぶなら 1. が楽、こういうの作っておいておく
code:context.py
@asynccontextmanager
async def agent_session():
async with mcp_client.session("playwright") as session:
tools = await load_mcp_tools(session)
agent = create_react_agent(model=model, tools=tools)
yield agent
都度 Graph 作成のコストが気になる場合は 2 になる
session がない場合は mcp_client.get_tools() / ある場合は load_mcp_tools(session) と呼び分ける
普段は Node として Graph に追加する ToolNode を ToolNode(await load_mpc_tools(session)).ainvoke(...) のように自分で呼ぶ Node を作る
実装観点ならこれでよいのだが、langgraph-cli から使うにはかなりダルい
"agent": "./main5.py:make_graph" のような感じで langgraph.json で、ファイル:グラフの変数名orグラフを作成する関数名 を指定する都合上 session を差し込めない、みんな困ってる
Unable to add persistent session for langgraph dev · Issue 189 · langchain-ai/langchain-mcp-adapters
調べて解決した
asynccontextmanager を指定すれば動く & --allow-blocking で起動する
ちなみに js 版にはセッションの仕組みはなく、invoke を超えて状態が共有される可能性がある
---.icon
カスすぎるが、自分で context manager を脱いで渡す?
うーん最悪 & 絶対 cleanup できてなくて問題が起きる
code:without_contextmanager.py
session_manager: ContextVar = ContextVar("session_manager", default=None)
async def get_session() -> ClientSession:
await cleanup_session()
sm = mcp_client.session("playwright")
session_manager.set(sm)
return await sm.__aenter__()
async def cleanup_session() -> None:
sm = session_manager.get(None)
if sm:
await sm.__aexit__(None, None, None)
session_manager.set(None)
async def make_graph() -> CompiledGraph:
session = await get_session()
tools = await load_mcp_tools(session)
return create_react_agent(model=model, tools=tools)
...で、この langgraph.json を読んでサーバー立てる処理はどこやねんというと
code:langgraph.json
{
"dependencies": [
"."
],
"graphs": {
"agent": "./main5.py:make_graph"
}
}
langgraph-api · PyPI
あれ? リポジトリ自体が GitHub に無い? なぜ? まあ .venv 下のやつを読む...
これだ!! おもしろい! ドキュメントで説明しろ!!! リポジトリ公開しろ!!!!!!!!!!
code:graph.py
async def _generate_graph(value: Any) -> AsyncIteratorAny:
"""Yield a graph object regardless of its type."""
if isinstance(value, Pregel | BaseRemotePregel): # コンパイル済みのグラフ
yield value
elif hasattr(value, "__aenter__") and hasattr(value, "__aexit__"): # asynccontextmanager
async with value as ctx_value:
yield ctx_value
elif hasattr(value, "__enter__") and hasattr(value, "__exit__"): # contextmanager
with value as ctx_value:
yield ctx_value
elif asyncio.iscoroutine(value): # async 関数 (await なしでの呼び出し)
yield await value
else:
yield value # その他全て
なにか自前で特定のパッケージの何かを実行するプログラムを書く時にも便利そうやね
この挙動、どこにも書かずに誰が分かるのか???
ということでこの langgraph.json の関数指定にこういう async context manager を向ければよい
ただ blocking 検出がうまく動いていない? のか --allow-blocking 渡さないと動かない & 単にグラフを渡している時に警告と案内が出るのに対して出なくてただエラーになる
code:make_graph.py
@asynccontextmanager
async def make_graph():
async with mcp_client.session("playwright") as session:
tools = await load_mcp_tools(session)
yield create_react_agent(model=model, tools=tools)
せっかく調べたので Issue に書いておこう
Unable to add persistent session for langgraph dev · Issue 189 · langchain-ai/langchain-mcp-adapters
で、この mcp_client.session("playwright") が複数必要になったらどうなんの?
個別にセッションとツールを対応付けていく必要がある?? 識別用の名前かと思ったら何らかのサーバ名と一致する必要あるのだが → 個別に tool 化 してくっつけて agent に渡せばいい
2025/6/13 コメントした
https://github.com/langchain-ai/langchain-mcp-adapters/issues/189#issuecomment-2968916317
👉️ 任意の数のコンテキストマネージャを作る
#LangGraph #MCP