-
Notifications
You must be signed in to change notification settings - Fork 495
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add MCP tools integration and update requirements
- Loading branch information
1 parent
b3b3c24
commit 944abfd
Showing
5 changed files
with
256 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
import asyncio | ||
import pathlib | ||
import time | ||
from typing import List, Any, Dict | ||
|
||
from mcp import ClientSession, StdioServerParameters | ||
from mcp.client.stdio import stdio_client | ||
from langchain_mcp import MCPToolkit | ||
from langchain_core.tools import BaseTool | ||
|
||
|
||
|
||
|
||
from typing import Any, Dict, List | ||
from langchain_core.tools import BaseTool | ||
from pydantic import Field, PrivateAttr | ||
|
||
|
||
|
||
|
||
|
||
class MCPToolWrapper(BaseTool): | ||
"""A wrapper for an individual tool managed by the SyncInvocationManager.""" | ||
_manager: Any = PrivateAttr() | ||
_tool: Any = PrivateAttr() | ||
|
||
def __init__(self, tool: BaseTool, manager: "SyncInvocationManager"): | ||
super().__init__(name=tool.name, description=tool.description) | ||
self.name = tool.name | ||
self.description = tool.description | ||
self._manager = manager | ||
self._tool = tool | ||
|
||
def _run(self, **kwargs: Any) -> Any: | ||
"""Run the tool synchronously using the SyncInvocationManager.""" | ||
|
||
|
||
|
||
try: | ||
from ..gpt_computer_assistant import the_main_window | ||
except ImportError: | ||
from gpt_computer_assistant import the_main_window | ||
|
||
|
||
|
||
|
||
|
||
function_name = "Tool: " + self.name | ||
the_main_window.active_border_animation(function_name) | ||
try: | ||
result = self._manager.invoke_tool_sync(self._tool, kwargs) | ||
except Exception as e: | ||
time.sleep(1) | ||
the_main_window.deactive_border_animation(function_name) | ||
return e | ||
time.sleep(1) | ||
the_main_window.deactive_border_animation(function_name) | ||
|
||
return result | ||
|
||
async def _arun(self, **kwargs: Any) -> Any: | ||
"""Asynchronous run (if needed), wraps the synchronous call.""" | ||
return self._run(**kwargs) | ||
|
||
|
||
class MCPToolManager: | ||
"""Manages tools provided by the SyncInvocationManager and converts them into LangChain tools.""" | ||
|
||
def __init__(self, manager: "SyncInvocationManager"): | ||
self.manager = manager | ||
self.tools: List[BaseTool] = [] | ||
|
||
def load_tools(self) -> List[BaseTool]: | ||
"""Load tools from SyncInvocationManager and wrap them in LangChain-compatible structure.""" | ||
raw_tools = self.manager.get_tools_sync() | ||
|
||
self.tools = [MCPToolWrapper(tool, self.manager) for tool in raw_tools] | ||
return self.tools | ||
|
||
|
||
class SyncInvocationManager: | ||
def __init__(self, command: str, args: list[str], env: dict[str, str] | None = None): | ||
self.loop = asyncio.new_event_loop() | ||
self.server_params = StdioServerParameters( | ||
command=command, | ||
args=args, | ||
env=env, | ||
|
||
) | ||
self.client_ctx = None | ||
self.client = None | ||
self.session_ctx = None | ||
self.session = None | ||
self.toolkit = None | ||
self._task = None # Add this line | ||
|
||
|
||
async def _start_async(self): | ||
# Manually enter the stdio_client context | ||
self.client_ctx = stdio_client(self.server_params) | ||
self.client = await self.client_ctx.__aenter__() | ||
read, write = self.client | ||
|
||
# Manually enter the ClientSession context | ||
self.session_ctx = ClientSession(read, write) | ||
self.session = await self.session_ctx.__aenter__() | ||
|
||
self.toolkit = MCPToolkit(session=self.session) | ||
await self.toolkit.initialize() | ||
|
||
def get_tools_sync(self) -> List[BaseTool]: | ||
# Now that session is open, just return tools directly | ||
return self.toolkit.get_tools() | ||
|
||
def invoke_tool_sync(self, tool: BaseTool, input_data: Dict[str, Any]) -> Any: | ||
return self.loop.run_until_complete(tool.ainvoke(input_data)) | ||
|
||
def start(self): | ||
asyncio.set_event_loop(self.loop) | ||
self._task = self.loop.create_task(self._start_async()) | ||
self.loop.run_until_complete(self._task) | ||
|
||
def stop(self): | ||
if self._task and not self._task.done(): | ||
cleanup_task = self.loop.create_task(self._stop_async()) | ||
self.loop.run_until_complete(cleanup_task) | ||
self.loop.close() | ||
|
||
async def _stop_async(self): | ||
# Exit contexts in the same task and loop they were entered | ||
if self.session_ctx: | ||
await self.session_ctx.__aexit__(None, None, None) | ||
if self.client_ctx: | ||
await self.client_ctx.__aexit__(None, None, None) | ||
|
||
|
||
|
||
|
||
def file_system_tool(): | ||
print(""" | ||
This is file_system_tool | ||
""") | ||
|
||
|
||
manager = SyncInvocationManager(command="npx", args=["-y", "@modelcontextprotocol/server-filesystem", str(pathlib.Path(__file__).parent.parent)]) | ||
manager.start() | ||
tool_manager = MCPToolManager(manager) | ||
tools = tool_manager.load_tools() | ||
print(tools) | ||
return tools | ||
|
||
|
||
def memory_tool(): | ||
|
||
print(""" | ||
This is memory_tool | ||
""") | ||
|
||
|
||
manager = SyncInvocationManager(command="npx", args=["-y", "@modelcontextprotocol/server-memory"]) | ||
manager.start() | ||
tool_manager = MCPToolManager(manager) | ||
tools = tool_manager.load_tools() | ||
print(tools) | ||
return tools | ||
|
||
|
||
def playwright(): | ||
|
||
print(""" | ||
This is playwright | ||
""") | ||
|
||
manager = SyncInvocationManager(command="npx", args=["-y", "@executeautomation/playwright-mcp-server"]) | ||
manager.start() | ||
tool_manager = MCPToolManager(manager) | ||
tools = tool_manager.load_tools() | ||
print(tools) | ||
return tools | ||
|
||
|
||
def youtube_transcript(): | ||
|
||
print(""" | ||
This is youtube_transcript | ||
""") | ||
|
||
manager = SyncInvocationManager(command="npx", args=["-y", "@kimtaeyoon83/mcp-server-youtube-transcript"]) | ||
manager.start() | ||
tool_manager = MCPToolManager(manager) | ||
tools = tool_manager.load_tools() | ||
print(tools) | ||
return tools | ||
|
||
def fetch(): | ||
|
||
print(""" | ||
This is fetch | ||
""") | ||
|
||
manager = SyncInvocationManager(command="uvx", args=["mcp-server-fetch"]) | ||
manager.start() | ||
tool_manager = MCPToolManager(manager) | ||
tools = tool_manager.load_tools() | ||
print(tools) | ||
return tools | ||
|
||
|
||
|
||
|
||
def websearch(): | ||
|
||
print(""" | ||
This is websearch | ||
""") | ||
|
||
|
||
manager = SyncInvocationManager(command="npx", args=["-y", "@executeautomation/playwright-mcp-server"]) | ||
manager.start() | ||
tool_manager = MCPToolManager(manager) | ||
tools = tool_manager.load_tools() | ||
print(tools) | ||
return tools | ||
|
||
|
||
|
||
|
||
the_tools_ = None | ||
def mcp_tools(): | ||
global the_tools_ | ||
if the_tools_ is None: | ||
the_tools_ = file_system_tool() + memory_tool() + playwright() + youtube_transcript() + fetch() + websearch() | ||
return the_tools_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,4 +47,7 @@ langchain-anthropic==0.3.0 | |
|
||
|
||
|
||
StrEnum==0.4.15 | ||
StrEnum==0.4.15 | ||
|
||
|
||
langchain-mcp==0.1.0a1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters