mcp-use 0.0.3__py3-none-any.whl → 0.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mcp-use might be problematic. Click here for more details.

mcp_use/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- mcp_use - A model-agnostic MCP (Multi-Channel Platform) library for LLMs.
2
+ mcp_use - An MCP library for LLMs.
3
3
 
4
4
  This library provides a unified interface for connecting different LLMs
5
5
  to MCP tools through existing LangChain adapters.
@@ -1,8 +1,8 @@
1
1
  """
2
- LangChain agent implementation for MCP tools.
2
+ LangChain agent implementation for MCP tools with customizable system message.
3
3
 
4
4
  This module provides a LangChain agent implementation that can use MCP tools
5
- through a unified interface.
5
+ through a unified interface, with support for customizable system messages.
6
6
  """
7
7
 
8
8
  from typing import Any, NoReturn
@@ -73,8 +73,15 @@ class LangChainAgent:
73
73
  through a unified interface.
74
74
  """
75
75
 
76
+ # Default system message if none is provided
77
+ DEFAULT_SYSTEM_MESSAGE = "You are a helpful AI assistant that can use tools to help users."
78
+
76
79
  def __init__(
77
- self, connector: BaseConnector, llm: BaseLanguageModel, max_steps: int = 5
80
+ self,
81
+ connector: BaseConnector,
82
+ llm: BaseLanguageModel,
83
+ max_steps: int = 5,
84
+ system_message: str | None = None,
78
85
  ) -> None:
79
86
  """Initialize a new LangChain agent.
80
87
 
@@ -82,13 +89,28 @@ class LangChainAgent:
82
89
  connector: The MCP connector to use.
83
90
  llm: The LangChain LLM to use.
84
91
  max_steps: The maximum number of steps to take.
92
+ system_message: Optional custom system message to use.
85
93
  """
86
94
  self.connector = connector
87
95
  self.llm = llm
88
96
  self.max_steps = max_steps
97
+ self.system_message = system_message or self.DEFAULT_SYSTEM_MESSAGE
89
98
  self.tools: list[BaseTool] = []
90
99
  self.agent: AgentExecutor | None = None
91
100
 
101
+ def set_system_message(self, message: str) -> None:
102
+ """Set a new system message and recreate the agent.
103
+
104
+ Args:
105
+ message: The new system message.
106
+ """
107
+ self.system_message = message
108
+
109
+ # Recreate the agent with the new system message if it exists
110
+ if self.agent and self.tools:
111
+ self.agent = self._create_agent()
112
+ logger.info("Agent recreated with new system message")
113
+
92
114
  async def initialize(self) -> None:
93
115
  """Initialize the agent and its tools."""
94
116
  self.tools = await self._create_langchain_tools()
@@ -184,7 +206,7 @@ class LangChainAgent:
184
206
  return langchain_tools
185
207
 
186
208
  def _create_agent(self) -> AgentExecutor:
187
- """Create the LangChain agent.
209
+ """Create the LangChain agent with the configured system message.
188
210
 
189
211
  Returns:
190
212
  An initialized AgentExecutor.
@@ -193,7 +215,7 @@ class LangChainAgent:
193
215
  [
194
216
  (
195
217
  "system",
196
- "You are a helpful AI assistant that can use tools to help users.",
218
+ self.system_message,
197
219
  ),
198
220
  MessagesPlaceholder(variable_name="chat_history"),
199
221
  ("human", "{input}"),
@@ -234,7 +256,25 @@ class LangChainAgent:
234
256
  if chat_history is None:
235
257
  chat_history = []
236
258
 
259
+ # Add a hint to use tools for queries about current information
260
+ enhanced_query = query
261
+ if any(
262
+ keyword in query.lower()
263
+ for keyword in [
264
+ "weather",
265
+ "current",
266
+ "today",
267
+ "now",
268
+ "latest",
269
+ "news",
270
+ "price",
271
+ "stock",
272
+ ]
273
+ ):
274
+ # Just log this, don't modify the query
275
+ logger.info("Query involves current information that may benefit from tool use")
276
+
237
277
  # Invoke with all required variables
238
- result = await self.agent.ainvoke({"input": query, "chat_history": chat_history})
278
+ result = await self.agent.ainvoke({"input": enhanced_query, "chat_history": chat_history})
239
279
 
240
280
  return result["output"]
@@ -1,10 +1,11 @@
1
1
  """
2
- Model-Agnostic MCP: Main integration module.
2
+ MCP: Main integration module with customizable system prompt.
3
3
 
4
4
  This module provides the main MCPAgent class that integrates all components
5
5
  to provide a simple interface for using MCP tools with different LLMs.
6
6
  """
7
7
 
8
+ from langchain.schema import AIMessage, BaseMessage, HumanMessage, SystemMessage
8
9
  from langchain.schema.language_model import BaseLanguageModel
9
10
 
10
11
  from mcp_use.client import MCPClient
@@ -13,15 +14,19 @@ from mcp_use.session import MCPSession
13
14
 
14
15
  from ..logging import logger
15
16
  from .langchain_agent import LangChainAgent
17
+ from .prompts.default import DEFAULT_SYSTEM_PROMPT_TEMPLATE
16
18
 
17
19
 
18
20
  class MCPAgent:
19
21
  """Main class for using MCP tools with various LLM providers.
20
22
 
21
23
  This class provides a unified interface for using MCP tools with different LLM providers
22
- through LangChain's agent framework.
24
+ through LangChain's agent framework, with customizable system prompts and conversation memory.
23
25
  """
24
26
 
27
+ # Default system prompt template to use if none is provided
28
+ DEFAULT_SYSTEM_PROMPT_TEMPLATE = DEFAULT_SYSTEM_PROMPT_TEMPLATE
29
+
25
30
  def __init__(
26
31
  self,
27
32
  llm: BaseLanguageModel,
@@ -30,6 +35,10 @@ class MCPAgent:
30
35
  server_name: str | None = None,
31
36
  max_steps: int = 5,
32
37
  auto_initialize: bool = False,
38
+ memory_enabled: bool = True,
39
+ system_prompt: str | None = None,
40
+ system_prompt_template: str | None = None,
41
+ additional_instructions: str | None = None,
33
42
  ):
34
43
  """Initialize a new MCPAgent instance.
35
44
 
@@ -40,6 +49,10 @@ class MCPAgent:
40
49
  server_name: The name of the server to use if client is provided.
41
50
  max_steps: The maximum number of steps to take.
42
51
  auto_initialize: Whether to automatically initialize the agent when run is called.
52
+ memory_enabled: Whether to maintain conversation history for context.
53
+ system_prompt: Complete system prompt to use (overrides template if provided).
54
+ system_prompt_template: Template for system prompt with {tool_descriptions} placeholder.
55
+ additional_instructions: Extra instructions to append to the system prompt.
43
56
  """
44
57
  self.llm = llm
45
58
  self.client = client
@@ -47,7 +60,14 @@ class MCPAgent:
47
60
  self.server_name = server_name
48
61
  self.max_steps = max_steps
49
62
  self.auto_initialize = auto_initialize
63
+ self.memory_enabled = memory_enabled
50
64
  self._initialized = False
65
+ self._conversation_history: list[BaseMessage] = []
66
+
67
+ # System prompt configuration
68
+ self.system_prompt = system_prompt
69
+ self.system_prompt_template = system_prompt_template or self.DEFAULT_SYSTEM_PROMPT_TEMPLATE
70
+ self.additional_instructions = additional_instructions
51
71
 
52
72
  # Either client or connector must be provided
53
73
  if not client and not connector:
@@ -55,6 +75,7 @@ class MCPAgent:
55
75
 
56
76
  self._agent: LangChainAgent | None = None
57
77
  self._session: MCPSession | None = None
78
+ self._system_message: SystemMessage | None = None
58
79
 
59
80
  async def initialize(self) -> None:
60
81
  """Initialize the MCP client and agent."""
@@ -71,38 +92,104 @@ class MCPAgent:
71
92
  await connector_to_use.connect()
72
93
  await connector_to_use.initialize()
73
94
 
95
+ # Create the system message based on available tools
96
+ await self._create_system_message(connector_to_use)
97
+
74
98
  # Create the agent
75
99
  self._agent = LangChainAgent(
76
- connector=connector_to_use, llm=self.llm, max_steps=self.max_steps
100
+ connector=connector_to_use,
101
+ llm=self.llm,
102
+ max_steps=self.max_steps,
103
+ system_message=(self._system_message.content if self._system_message else None),
77
104
  )
78
105
 
79
106
  # Initialize the agent
80
107
  await self._agent.initialize()
81
108
  self._initialized = True
82
109
 
83
- async def close(self) -> None:
84
- """Close the MCP connection."""
85
- try:
86
- if self._agent:
87
- # Clean up the agent first
88
- self._agent = None
110
+ async def _create_system_message(self, connector: BaseConnector) -> None:
111
+ """Create the system message based on available tools.
89
112
 
90
- # If using client with session, close the session through client
91
- if self.client and self._session:
92
- await self.client.close_session(self.server_name)
93
- # If using direct connector, disconnect
94
- elif self.connector:
95
- await self.connector.disconnect()
113
+ Args:
114
+ connector: The connector with available tools.
115
+ """
116
+ # If a complete system prompt was provided, use it directly
117
+ if self.system_prompt:
118
+ self._system_message = SystemMessage(content=self.system_prompt)
119
+ return
96
120
 
97
- self._initialized = False
98
- except Exception as e:
99
- logger.warning(f"Warning: Error during agent closure: {e}")
100
- # Still try to clean up even if there was an error
101
- self._agent = None
102
- self._initialized = False
121
+ # Otherwise, build the system prompt from the template and tool descriptions
122
+ tools = connector.tools
123
+
124
+ # Generate tool descriptions
125
+ tool_descriptions = []
126
+ for tool in tools:
127
+ description = f"- {tool.name}: {tool.description}"
128
+ tool_descriptions.append(description)
129
+
130
+ # Format the system prompt template with tool descriptions
131
+ system_prompt = self.system_prompt_template.format(
132
+ tool_descriptions="\n".join(tool_descriptions)
133
+ )
134
+
135
+ # Add any additional instructions
136
+ if self.additional_instructions:
137
+ system_prompt += f"\n\n{self.additional_instructions}"
138
+
139
+ # Create the system message
140
+ self._system_message = SystemMessage(content=system_prompt)
141
+ logger.info(f"Created system message with {len(tools)} tool descriptions")
142
+
143
+ def get_conversation_history(self) -> list[BaseMessage]:
144
+ """Get the current conversation history.
145
+
146
+ Returns:
147
+ The list of conversation messages.
148
+ """
149
+ return self._conversation_history
150
+
151
+ def clear_conversation_history(self) -> None:
152
+ """Clear the conversation history."""
153
+ self._conversation_history = []
154
+
155
+ # Re-add the system message if it exists
156
+ if self._system_message:
157
+ self._conversation_history = [self._system_message]
158
+
159
+ def add_to_history(self, message: BaseMessage) -> None:
160
+ """Add a message to the conversation history.
161
+
162
+ Args:
163
+ message: The message to add.
164
+ """
165
+ self._conversation_history.append(message)
166
+
167
+ def get_system_message(self) -> SystemMessage | None:
168
+ """Get the current system message.
169
+
170
+ Returns:
171
+ The current system message, or None if not set.
172
+ """
173
+ return self._system_message
174
+
175
+ def set_system_message(self, message: str) -> None:
176
+ """Set a new system message.
177
+
178
+ Args:
179
+ message: The new system message content.
180
+ """
181
+ self._system_message = SystemMessage(content=message)
182
+
183
+ # Update the agent if initialized
184
+ if self._agent:
185
+ self._agent.set_system_message(message)
103
186
 
104
187
  async def run(
105
- self, query: str, max_steps: int | None = None, manage_connector: bool = True
188
+ self,
189
+ query: str,
190
+ max_steps: int | None = None,
191
+ manage_connector: bool = True,
192
+ external_history: list[BaseMessage] | None = None,
106
193
  ) -> str:
107
194
  """Run a query using the MCP tools.
108
195
 
@@ -116,34 +203,106 @@ class MCPAgent:
116
203
  If True, this method will connect, initialize, and disconnect from
117
204
  the connector automatically. If False, the caller is responsible
118
205
  for managing the connector lifecycle.
206
+ external_history: Optional external history to use instead of the
207
+ internal conversation history.
119
208
 
120
209
  Returns:
121
210
  The result of running the query.
122
211
  """
123
212
  result = ""
213
+ initialized_here = False
214
+
124
215
  try:
125
- if manage_connector:
126
- # Initialize if needed
127
- if not self._initialized or not self._agent:
128
- await self.initialize()
216
+ # Initialize if needed
217
+ if manage_connector and (not self._initialized or not self._agent):
218
+ logger.info("Initializing agent before running query")
219
+ await self.initialize()
220
+ initialized_here = True
221
+ elif not self._initialized and self.auto_initialize:
222
+ logger.info("Auto-initializing agent before running query")
223
+ await self.initialize()
224
+ initialized_here = True
225
+
226
+ # Check if initialization succeeded
227
+ if not self._agent:
228
+ raise RuntimeError("MCP agent failed to initialize")
229
+
230
+ # Add the user query to conversation history if memory is enabled
231
+ if self.memory_enabled:
232
+ self.add_to_history(HumanMessage(content=query))
129
233
 
130
- # Run the query
131
- if not self._agent:
132
- raise RuntimeError("MCP client failed to initialize")
234
+ # Use the provided history or the internal history
235
+ history_to_use = (
236
+ external_history if external_history is not None else self._conversation_history
237
+ )
133
238
 
134
- result = await self._agent.run(query, max_steps)
135
- else:
136
- # Caller is managing connector lifecycle
137
- if not self._initialized and self.auto_initialize:
138
- await self.initialize()
239
+ # Convert messages to format expected by LangChain
240
+ langchain_history = []
241
+ for msg in history_to_use:
242
+ if isinstance(msg, HumanMessage):
243
+ langchain_history.append({"type": "human", "content": msg.content})
244
+ elif isinstance(msg, AIMessage):
245
+ langchain_history.append({"type": "ai", "content": msg.content})
246
+ elif isinstance(msg, SystemMessage) and msg != self._system_message:
247
+ # Include system messages other than the main one
248
+ # which is already included in the agent's prompt
249
+ langchain_history.append({"type": "system", "content": msg.content})
250
+ # Other message types can be handled here if needed
139
251
 
140
- if not self._agent:
141
- raise RuntimeError("MCP client is not initialized")
252
+ # Run the query with the specified max_steps or default
253
+ logger.info(f"Running query with max_steps={max_steps or self.max_steps}")
254
+ result = await self._agent.run(
255
+ query=query,
256
+ max_steps=max_steps,
257
+ chat_history=langchain_history,
258
+ )
142
259
 
143
- result = await self._agent.run(query, max_steps)
260
+ # Add the response to conversation history if memory is enabled
261
+ if self.memory_enabled:
262
+ self.add_to_history(AIMessage(content=result))
144
263
 
145
264
  return result
265
+
266
+ except Exception as e:
267
+ logger.error(f"Error running query: {e}")
268
+ # If we initialized in this method and there was an error,
269
+ # make sure to clean up
270
+ if initialized_here and manage_connector:
271
+ logger.info("Cleaning up resources after initialization error")
272
+ await self.close()
273
+ raise
274
+
146
275
  finally:
147
- # Make sure to clean up the connection if we're managing it
148
- if manage_connector and not self.client:
276
+ # Clean up resources if we're managing the connector and
277
+ # we're not using a client that manages sessions
278
+ if manage_connector and not self.client and not initialized_here:
279
+ logger.info("Closing agent after query completion")
149
280
  await self.close()
281
+
282
+ async def close(self) -> None:
283
+ """Close the MCP connection with improved error handling."""
284
+ try:
285
+ if self._agent:
286
+ # Clean up the agent first
287
+ logger.debug("Cleaning up agent")
288
+ self._agent = None
289
+
290
+ # If using client with session, close the session through client
291
+ if self.client and self._session:
292
+ logger.debug("Closing session through client")
293
+ await self.client.close_session(self.server_name)
294
+ self._session = None
295
+ # If using direct connector, disconnect
296
+ elif self.connector:
297
+ logger.debug("Disconnecting connector")
298
+ await self.connector.disconnect()
299
+
300
+ self._initialized = False
301
+ logger.info("Agent closed successfully")
302
+
303
+ except Exception as e:
304
+ logger.error(f"Error during agent closure: {e}")
305
+ # Still try to clean up references even if there was an error
306
+ self._agent = None
307
+ self._session = None
308
+ self._initialized = False
@@ -0,0 +1,11 @@
1
+ DEFAULT_SYSTEM_PROMPT_TEMPLATE = """You are an assistant with access to these tools:
2
+
3
+ {tool_descriptions}
4
+
5
+ Proactively use these tools to:
6
+ - Find real-time information (weather, news, prices)
7
+ - Perform web searches and extract relevant data
8
+ - Execute multi-step tasks by combining tools
9
+
10
+ You CAN access current information using your tools. Never claim you lack access to real-time data.
11
+ """
mcp_use/client.py CHANGED
@@ -9,6 +9,7 @@ import json
9
9
  from typing import Any
10
10
 
11
11
  from .config import create_connector_from_config, load_config_file
12
+ from .logging import logger
12
13
  from .session import MCPSession
13
14
 
14
15
 
@@ -183,40 +184,55 @@ class MCPClient:
183
184
  Raises:
184
185
  ValueError: If no active session exists or the specified session doesn't exist.
185
186
  """
186
- session = self.get_session(server_name)
187
- await session.disconnect()
188
-
189
- # Remove the session
187
+ # Determine which server to close
190
188
  if server_name is None:
189
+ if self.active_session is None:
190
+ logger.warning("No active session to close")
191
+ return
191
192
  server_name = self.active_session
192
193
 
193
- if server_name in self.sessions:
194
+ # Check if the session exists
195
+ if server_name not in self.sessions:
196
+ logger.warning(f"No session exists for server '{server_name}', nothing to close")
197
+ return
198
+
199
+ # Get the session
200
+ session = self.sessions[server_name]
201
+
202
+ try:
203
+ # Disconnect from the session
204
+ logger.info(f"Closing session for server '{server_name}'")
205
+ await session.disconnect()
206
+ except Exception as e:
207
+ logger.error(f"Error closing session for server '{server_name}': {e}")
208
+ finally:
209
+ # Remove the session regardless of whether disconnect succeeded
194
210
  del self.sessions[server_name]
195
211
 
196
- # If we closed the active session, set active_session to None
197
- if server_name == self.active_session:
198
- self.active_session = None
212
+ # If we closed the active session, set active_session to None
213
+ if server_name == self.active_session:
214
+ self.active_session = None
199
215
 
200
216
  async def close_all_sessions(self) -> None:
201
- """Close all active sessions."""
202
- for server_name in list(self.sessions.keys()):
203
- await self.close_session(server_name)
204
-
205
- async def __aenter__(self) -> "MCPClient":
206
- """Enter the async context manager.
207
-
208
- Creates a session for the first available server if no sessions exist.
209
-
210
- Returns:
211
- The client instance.
212
- """
213
- if not self.sessions and self.config.get("mcpServers"):
214
- await self.create_session()
215
- return self
216
-
217
- async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
218
- """Exit the async context manager.
217
+ """Close all active sessions.
219
218
 
220
- Closes all active sessions.
219
+ This method ensures all sessions are closed even if some fail.
221
220
  """
222
- await self.close_all_sessions()
221
+ # Get a list of all session names first to avoid modification during iteration
222
+ server_names = list(self.sessions.keys())
223
+ errors = []
224
+
225
+ for server_name in server_names:
226
+ try:
227
+ logger.info(f"Closing session for server '{server_name}'")
228
+ await self.close_session(server_name)
229
+ except Exception as e:
230
+ error_msg = f"Failed to close session for server '{server_name}': {e}"
231
+ logger.error(error_msg)
232
+ errors.append(error_msg)
233
+
234
+ # Log summary if there were errors
235
+ if errors:
236
+ logger.error(f"Encountered {len(errors)} errors while closing sessions")
237
+ else:
238
+ logger.info("All sessions closed successfully")