Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions langchain_mcp_adapters/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Agent middleware for LangChain."""

import asyncio
from concurrent.futures import ThreadPoolExecutor
from typing import TYPE_CHECKING

from langchain.agents.middleware import AgentMiddleware

from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_mcp_adapters.sessions import (
StreamableHttpConnection,
)
from langchain_core.tools import BaseTool


def _sync_await(coro):
"""Run an async coroutine from sync code, even if a loop is already running."""
try:
asyncio.get_running_loop()
except RuntimeError:
# No running loop in this thread: just run it.
return asyncio.run(coro)
with ThreadPoolExecutor(max_workers=1) as ex:
return ex.submit(lambda: asyncio.run(coro)).result()


class MCPMiddleware(AgentMiddleware):
"""MCP Middleware to register tools for use in an agent."""

def __init__(self, connections: dict[str, StreamableHttpConnection]) -> None:
"""Initialize the middleware."""
self.server_to_connection = connections
for connection in connections.values():
assert connection["transport"] == "streamable_http"
# Check how it looks if we only support stateless connections
# assert connection["is_stateful"] == False
self._client = MultiServerMCPClient(connections)
self._tools = None

# Do we want to make tools a property? Or support an async method?
@property
def tools(self) -> list[BaseTool]:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Life cycle management is bad

Would want to be able to do some async initialization to get a list of tools from the server.

  1. Requires changing tools into a property
  2. Requires running code on a thread to spawn another event loop

"""No additional tools"""
if self._tools is not None:
return self._tools
self._tools = _sync_await(self._client.get_tools())
return self._tools
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ repository = "https://www.github.com/langchain-ai/langchain-mcp-adapters"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"langchain",
"langchain-core>=0.3.36,<0.4",
"langchain-openai>=0.3.33",
"mcp>=1.9.2",
"typing-extensions>=4.14.0",
]
Expand Down Expand Up @@ -66,6 +68,9 @@ warn_unused_configs = true
disallow_untyped_defs = true
check_untyped_defs = true

[tool.uv.sources]
langchain = { path = "../langchain/libs/langchain_v1" }

[tool.ruff.lint.extend-per-file-ignores]
"tests/**/*.py" = [
# at least this three should be fine in tests:
Expand Down
Loading
Loading