55
66from  crewai .tools  import  BaseTool 
77from  crewai_tools .adapters .tool_collection  import  ToolCollection 
8- """ 
9- MCPServer for CrewAI. 
108
11- 
12- """ 
139logger  =  logging .getLogger (__name__ )
1410
1511if  TYPE_CHECKING :
2925
3026
3127class  MCPServerAdapter :
32-     """Manages the lifecycle of an MCP server and make  its tools available to CrewAI. 
28+     """Manages the lifecycle of an MCP server and makes  its tools available to CrewAI. 
3329
34-     Note: tools can only be accessed after the server has been started with the 
35-         `start()` method. 
30+     This adapter handles starting and stopping the MCP server, converting its 
31+     capabilities into CrewAI tools. It is best used as a context manager (`with` 
32+     statement) to ensure resources are properly cleaned up. 
3633
3734    Attributes: 
38-         tools: The CrewAI tools available from the MCP server. 
39- 
40-     Usage: 
41-         # context manager + stdio 
42-         with MCPServerAdapter(...) as tools: 
43-             # tools is now available 
44- 
45-         # context manager + sse 
46-         with MCPServerAdapter({"url": "http://localhost:8000/sse"}) as tools: 
47-             # tools is now available 
48- 
49-         # context manager with filtered tools 
50-         with MCPServerAdapter(..., "tool1", "tool2") as filtered_tools: 
51-             # only tool1 and tool2 are available 
52- 
53-         # manually stop mcp server 
54-         try: 
55-             mcp_server = MCPServerAdapter(...) 
56-             tools = mcp_server.tools  # all tools 
57- 
58-             # or with filtered tools 
59-             mcp_server = MCPServerAdapter(..., "tool1", "tool2") 
60-             filtered_tools = mcp_server.tools  # only tool1 and tool2 
61-             ... 
62-         finally: 
63-             mcp_server.stop() 
64- 
65-         # Best practice is ensure cleanup is done after use. 
66-         mcp_server.stop() # run after crew().kickoff() 
35+         tools: A ToolCollection of the available CrewAI tools. Accessing this 
36+                before the server is ready will raise a ValueError. 
37+ 
38+     Example: 
39+         # This is a minimal runnable example assuming an MCP server is running. 
40+         # from your_agent_file import your_agent, your_task 
41+         # 
42+         # server_params = {"url": "http://localhost:6006/sse"} 
43+         # with MCPServerAdapter(server_params) as mcp_tools: 
44+         #     your_agent.tools = mcp_tools 
45+         #     result = your_agent.kickoff(your_task) 
6746    """ 
6847
6948    def  __init__ (
7049        self ,
7150        serverparams : StdioServerParameters  |  dict [str , Any ],
7251        * tool_names : str ,
7352    ):
74-         """Initialize the MCP Server 
53+         """Initialize and start  the MCP Server.  
7554
7655        Args: 
77-             serverparams: The parameters for the MCP server it  supports either a 
78-                 `StdioServerParameters` or a `dict` respectively  for STDIO and  SSE. 
56+             serverparams: The parameters for the MCP server. This  supports either a 
57+                 `StdioServerParameters` object for STDIO  or a `dict` for SSE connections . 
7958            *tool_names: Optional names of tools to filter. If provided, only tools with 
8059                matching names will be available. 
81- 
8260        """ 
83- 
84-         super ().__init__ ()
8561        self ._adapter  =  None 
8662        self ._tools  =  None 
8763        self ._tool_names  =  list (tool_names ) if  tool_names  else  None 
8864
8965        if  not  MCP_AVAILABLE :
90-             import  click 
91- 
92-             if  click .confirm (
93-                 "You are missing the 'mcp' package. Would you like to install it?" 
94-             ):
95-                 import  subprocess 
96- 
97-                 try :
98-                     subprocess .run (["uv" , "add" , "mcp crewai-tools[mcp]" ], check = True )
99- 
100-                 except  subprocess .CalledProcessError :
101-                     raise  ImportError ("Failed to install mcp package" )
102-             else :
103-                 raise  ImportError (
104-                     "`mcp` package not found, please run `uv add crewai-tools[mcp]`" 
105-                 )
66+             msg  =  (
67+                 "❌ MCP is not available. The 'mcp' package, a required dependency, " 
68+                 "must be installed for MCPServerAdapter to work." 
69+             )
70+             logger .critical (msg )
71+             raise  ImportError (
72+                 "`mcp` package not found. Please install it with:\n " 
73+                 "  pip install mcp crewai-tools[mcp]" 
74+             )
10675
10776        try :
10877            self ._serverparams  =  serverparams 
10978            self ._adapter  =  MCPAdapt (self ._serverparams , CrewAIAdapter ())
11079            self .start ()
111- 
11280        except  Exception  as  e :
81+             logger .exception ("Failed to initialize MCP Adapter during __init__." )
11382            if  self ._adapter  is  not   None :
11483                try :
11584                    self .stop ()
11685                except  Exception  as  stop_e :
117-                     logger .error (f"Error during stop  cleanup: { stop_e }  " )
86+                     logger .error (f"Error during post-failure  cleanup: { stop_e }  " )
11887            raise  RuntimeError (f"Failed to initialize MCP Adapter: { e }  " ) from  e 
11988
12089    def  start (self ):
12190        """Start the MCP server and initialize the tools.""" 
91+         if  not  self ._adapter :
92+             raise  RuntimeError ("Cannot start MCP server: Adapter is not initialized." )
93+         if  self ._tools :
94+             logger .debug ("MCP server already started." )
95+             return 
12296        self ._tools  =  self ._adapter .__enter__ ()
12397
12498    def  stop (self ):
125-         """Stop the MCP server""" 
126-         self ._adapter .__exit__ (None , None , None )
99+         """Stop the MCP server and release all associated resources. 
100+ 
101+         This method is idempotent; calling it multiple times has no effect. 
102+         """ 
103+         if  not  self ._adapter :
104+             logger .debug ("stop() called but adapter is already stopped." )
105+             return 
106+ 
107+         try :
108+             self ._adapter .__exit__ (None , None , None )
109+         finally :
110+             self ._tools  =  None 
111+             self ._adapter  =  None 
127112
128113    @property  
129114    def  tools (self ) ->  ToolCollection [BaseTool ]:
@@ -133,11 +118,11 @@ def tools(self) -> ToolCollection[BaseTool]:
133118            ValueError: If the MCP server is not started. 
134119
135120        Returns: 
136-             The CrewAI tools available from  the MCP server . 
121+             A ToolCollection of  the available CrewAI tools . 
137122        """ 
138123        if  self ._tools  is  None :
139124            raise  ValueError (
140-                 "MCP server  not started, run `mcp_server.start()` first before accessing `tools` " 
125+                 "MCP tools are  not available. The server may be stopped or initialization failed. " 
141126            )
142127
143128        tools_collection  =  ToolCollection (self ._tools )
@@ -146,12 +131,25 @@ def tools(self) -> ToolCollection[BaseTool]:
146131        return  tools_collection 
147132
148133    def  __enter__ (self ):
149-         """ 
150-         Enter the context manager. Note that `__init__()` already starts the MCP server. 
151-         So tools should already be available. 
152-         """ 
134+         """Enter the context manager, returning the initialized tools.""" 
153135        return  self .tools 
154136
155137    def  __exit__ (self , exc_type , exc_value , traceback ):
156-         """Exit the context manager.""" 
157-         return  self ._adapter .__exit__ (exc_type , exc_value , traceback )
138+         """Exit the context manager, stop the server, and do not suppress exceptions.""" 
139+         self .stop ()
140+         return  False   # Ensures any exceptions that occurred are re-raised. 
141+ 
142+     def  __del__ (self ):
143+         """ 
144+         Finalizer to attempt cleanup if the user forgets to call stop() or use a 
145+         context manager. 
146+ 
147+         Note: This is a fallback and should not be relied upon, as Python does 
148+         not guarantee __del__ will always be called on object destruction. 
149+         """ 
150+         if  self ._adapter :
151+             logger .warning (
152+                 "MCPServerAdapter was not cleanly shut down. Please use a " 
153+                 "context manager (`with` statement) or call .stop() explicitly." 
154+             )
155+             self .stop ()
0 commit comments