diff --git a/gpt_computer_assistant/__init__.py b/gpt_computer_assistant/__init__.py index a4d667aa6..37c0f0402 100644 --- a/gpt_computer_assistant/__init__.py +++ b/gpt_computer_assistant/__init__.py @@ -6,7 +6,7 @@ from .tooler import Tool except: pass -__version__ = '0.24.17' # fmt: skip +__version__ = '0.24.18' # fmt: skip import os diff --git a/gpt_computer_assistant/agent/agent.py b/gpt_computer_assistant/agent/agent.py index 67c62f96b..cc16da0dc 100644 --- a/gpt_computer_assistant/agent/agent.py +++ b/gpt_computer_assistant/agent/agent.py @@ -7,6 +7,7 @@ from ..cu.computer import * from ..teams import * from .agent_tools import get_tools + from ..mcp.tool import mcp_tools except ImportError: from llm import get_model @@ -17,6 +18,7 @@ from cu.computer import * from teams import * from agent.agent_tools import get_tools + from mcp.tool import mcp_tools from langgraph.prebuilt import chat_agent_executor @@ -62,7 +64,7 @@ def get_agent_executor(): pass - tools += [computer_tool] + tools += [computer_tool] + mcp_tools() if ( diff --git a/gpt_computer_assistant/mcp/tool.py b/gpt_computer_assistant/mcp/tool.py new file mode 100644 index 000000000..2404d2154 --- /dev/null +++ b/gpt_computer_assistant/mcp/tool.py @@ -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_ diff --git a/requirements.in b/requirements.in index ea4c6256a..2d551731b 100644 --- a/requirements.in +++ b/requirements.in @@ -47,4 +47,7 @@ langchain-anthropic==0.3.0 -StrEnum==0.4.15 \ No newline at end of file +StrEnum==0.4.15 + + +langchain-mcp==0.1.0a1 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 1820942f4..2dddc0591 100644 --- a/requirements.txt +++ b/requirements.txt @@ -45,4 +45,6 @@ screeninfo==0.8.1 anthropic==0.40.0 langchain-anthropic==0.3.0 -StrEnum==0.4.15 \ No newline at end of file +StrEnum==0.4.15 + +langchain-mcp==0.1.0a1 \ No newline at end of file diff --git a/setup.py b/setup.py index ca639c063..24ab43bcc 100644 --- a/setup.py +++ b/setup.py @@ -20,18 +20,19 @@ setup( name="gpt_computer_assistant", - version="0.24.17", + version="0.24.18", description="""GPT""", long_description="".join(open("README.md", encoding="utf-8").readlines()), long_description_content_type="text/markdown", url="https://github.com/onuratakan/gpt-computer-assistant", - author="Onur Atakan ULUSOY", - author_email="atadogan06@gmail.com", + author="Upsonic", + author_email="onur@upsonic.co", license="MIT", packages=[ "gpt_computer_assistant", "gpt_computer_assistant.agent", "gpt_computer_assistant.cu", + "gpt_computer_assistant.mcp", "gpt_computer_assistant.gui", "gpt_computer_assistant.screen", "gpt_computer_assistant.utils",