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