5
5
6
6
from crewai .tools import BaseTool
7
7
from crewai_tools .adapters .tool_collection import ToolCollection
8
- """
9
- MCPServer for CrewAI.
10
8
11
-
12
- """
13
9
logger = logging .getLogger (__name__ )
14
10
15
11
if TYPE_CHECKING :
29
25
30
26
31
27
class 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.
33
29
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.
36
33
37
34
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)
67
46
"""
68
47
69
48
def __init__ (
70
49
self ,
71
50
serverparams : StdioServerParameters | dict [str , Any ],
72
51
* tool_names : str ,
73
52
):
74
- """Initialize the MCP Server
53
+ """Initialize and start the MCP Server.
75
54
76
55
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 .
79
58
*tool_names: Optional names of tools to filter. If provided, only tools with
80
59
matching names will be available.
81
-
82
60
"""
83
-
84
- super ().__init__ ()
85
61
self ._adapter = None
86
62
self ._tools = None
87
63
self ._tool_names = list (tool_names ) if tool_names else None
88
64
89
65
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
+ )
106
75
107
76
try :
108
77
self ._serverparams = serverparams
109
78
self ._adapter = MCPAdapt (self ._serverparams , CrewAIAdapter ())
110
79
self .start ()
111
-
112
80
except Exception as e :
81
+ logger .exception ("Failed to initialize MCP Adapter during __init__." )
113
82
if self ._adapter is not None :
114
83
try :
115
84
self .stop ()
116
85
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 } " )
118
87
raise RuntimeError (f"Failed to initialize MCP Adapter: { e } " ) from e
119
88
120
89
def start (self ):
121
90
"""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
122
96
self ._tools = self ._adapter .__enter__ ()
123
97
124
98
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
127
112
128
113
@property
129
114
def tools (self ) -> ToolCollection [BaseTool ]:
@@ -133,11 +118,11 @@ def tools(self) -> ToolCollection[BaseTool]:
133
118
ValueError: If the MCP server is not started.
134
119
135
120
Returns:
136
- The CrewAI tools available from the MCP server .
121
+ A ToolCollection of the available CrewAI tools .
137
122
"""
138
123
if self ._tools is None :
139
124
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. "
141
126
)
142
127
143
128
tools_collection = ToolCollection (self ._tools )
@@ -146,12 +131,25 @@ def tools(self) -> ToolCollection[BaseTool]:
146
131
return tools_collection
147
132
148
133
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."""
153
135
return self .tools
154
136
155
137
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