mcp-use 1.2.12__py3-none-any.whl → 1.3.0__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/adapters/base.py +31 -10
- mcp_use/adapters/langchain_adapter.py +112 -2
- mcp_use/agents/mcpagent.py +8 -12
- mcp_use/client.py +18 -9
- mcp_use/config.py +30 -4
- mcp_use/connectors/__init__.py +12 -5
- mcp_use/connectors/base.py +83 -20
- mcp_use/connectors/sandbox.py +291 -0
- mcp_use/connectors/utils.py +13 -0
- mcp_use/session.py +3 -29
- mcp_use/task_managers/base.py +3 -5
- mcp_use/task_managers/sse.py +2 -5
- mcp_use/task_managers/stdio.py +2 -6
- mcp_use/task_managers/websocket.py +25 -25
- mcp_use/types/clientoptions.py +23 -0
- mcp_use/types/sandbox.py +23 -0
- {mcp_use-1.2.12.dist-info → mcp_use-1.3.0.dist-info}/METADATA +176 -33
- {mcp_use-1.2.12.dist-info → mcp_use-1.3.0.dist-info}/RECORD +20 -16
- {mcp_use-1.2.12.dist-info → mcp_use-1.3.0.dist-info}/WHEEL +0 -0
- {mcp_use-1.2.12.dist-info → mcp_use-1.3.0.dist-info}/licenses/LICENSE +0 -0
mcp_use/adapters/base.py
CHANGED
|
@@ -5,7 +5,9 @@ This module provides the abstract base class that all MCP tool adapters should i
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import TypeVar
|
|
9
|
+
|
|
10
|
+
from mcp.types import Prompt, Resource, Tool
|
|
9
11
|
|
|
10
12
|
from ..client import MCPClient
|
|
11
13
|
from ..connectors.base import BaseConnector
|
|
@@ -90,13 +92,13 @@ class BaseAdapter(ABC):
|
|
|
90
92
|
return self._connector_tool_map[connector]
|
|
91
93
|
|
|
92
94
|
# Create tools for this connector
|
|
93
|
-
connector_tools = []
|
|
94
95
|
|
|
95
96
|
# Make sure the connector is initialized and has tools
|
|
96
97
|
success = await self._ensure_connector_initialized(connector)
|
|
97
98
|
if not success:
|
|
98
99
|
return []
|
|
99
100
|
|
|
101
|
+
connector_tools = []
|
|
100
102
|
# Now create tools for each MCP tool
|
|
101
103
|
for tool in connector.tools:
|
|
102
104
|
# Convert the tool and add it to the list if conversion was successful
|
|
@@ -104,6 +106,23 @@ class BaseAdapter(ABC):
|
|
|
104
106
|
if converted_tool:
|
|
105
107
|
connector_tools.append(converted_tool)
|
|
106
108
|
|
|
109
|
+
# Convert resources to tools so that agents can access resource content directly
|
|
110
|
+
resources_list = connector.resources or []
|
|
111
|
+
if resources_list:
|
|
112
|
+
for resource in resources_list:
|
|
113
|
+
converted_resource = self._convert_resource(resource, connector)
|
|
114
|
+
if converted_resource:
|
|
115
|
+
connector_tools.append(converted_resource)
|
|
116
|
+
|
|
117
|
+
# Convert prompts to tools so that agents can retrieve prompt content
|
|
118
|
+
prompts_list = connector.prompts or []
|
|
119
|
+
if prompts_list:
|
|
120
|
+
for prompt in prompts_list:
|
|
121
|
+
converted_prompt = self._convert_prompt(prompt, connector)
|
|
122
|
+
if converted_prompt:
|
|
123
|
+
connector_tools.append(converted_prompt)
|
|
124
|
+
# ------------------------------
|
|
125
|
+
|
|
107
126
|
# Store the tools for this connector
|
|
108
127
|
self._connector_tool_map[connector] = connector_tools
|
|
109
128
|
|
|
@@ -116,16 +135,18 @@ class BaseAdapter(ABC):
|
|
|
116
135
|
return connector_tools
|
|
117
136
|
|
|
118
137
|
@abstractmethod
|
|
119
|
-
def _convert_tool(self, mcp_tool:
|
|
120
|
-
"""Convert an MCP tool to the target framework's tool format.
|
|
138
|
+
def _convert_tool(self, mcp_tool: Tool, connector: BaseConnector) -> T:
|
|
139
|
+
"""Convert an MCP tool to the target framework's tool format."""
|
|
140
|
+
pass
|
|
121
141
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
142
|
+
@abstractmethod
|
|
143
|
+
def _convert_resource(self, mcp_resource: Resource, connector: BaseConnector) -> T:
|
|
144
|
+
"""Convert an MCP resource to the target framework's resource format."""
|
|
145
|
+
pass
|
|
125
146
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
"""
|
|
147
|
+
@abstractmethod
|
|
148
|
+
def _convert_prompt(self, mcp_prompt: Prompt, connector: BaseConnector) -> T:
|
|
149
|
+
"""Convert an MCP prompt to the target framework's prompt format."""
|
|
129
150
|
pass
|
|
130
151
|
|
|
131
152
|
async def _create_tools_from_connectors(self, connectors: list[BaseConnector]) -> list[T]:
|
|
@@ -4,12 +4,21 @@ LangChain adapter for MCP tools.
|
|
|
4
4
|
This module provides utilities to convert MCP tools to LangChain tools.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import re
|
|
7
8
|
from typing import Any, NoReturn
|
|
8
9
|
|
|
9
10
|
from jsonschema_pydantic import jsonschema_to_pydantic
|
|
10
11
|
from langchain_core.tools import BaseTool, ToolException
|
|
11
|
-
from mcp.types import
|
|
12
|
-
|
|
12
|
+
from mcp.types import (
|
|
13
|
+
CallToolResult,
|
|
14
|
+
EmbeddedResource,
|
|
15
|
+
ImageContent,
|
|
16
|
+
Prompt,
|
|
17
|
+
ReadResourceRequestParams,
|
|
18
|
+
Resource,
|
|
19
|
+
TextContent,
|
|
20
|
+
)
|
|
21
|
+
from pydantic import BaseModel, Field, create_model
|
|
13
22
|
|
|
14
23
|
from ..connectors.base import BaseConnector
|
|
15
24
|
from ..logging import logger
|
|
@@ -162,3 +171,104 @@ class LangChainAdapter(BaseAdapter):
|
|
|
162
171
|
raise
|
|
163
172
|
|
|
164
173
|
return McpToLangChainAdapter()
|
|
174
|
+
|
|
175
|
+
def _convert_resource(self, mcp_resource: Resource, connector: BaseConnector) -> BaseTool:
|
|
176
|
+
"""Convert an MCP resource to LangChain's tool format.
|
|
177
|
+
|
|
178
|
+
Each resource becomes an async tool that returns its content when called.
|
|
179
|
+
The tool takes **no** arguments because the resource URI is fixed.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def _sanitize(name: str) -> str:
|
|
183
|
+
return re.sub(r"[^A-Za-z0-9_]+", "_", name).lower().strip("_")
|
|
184
|
+
|
|
185
|
+
class ResourceTool(BaseTool):
|
|
186
|
+
name: str = _sanitize(mcp_resource.name or f"resource_{mcp_resource.uri}")
|
|
187
|
+
description: str = (
|
|
188
|
+
mcp_resource.description
|
|
189
|
+
or f"Return the content of the resource located at URI {mcp_resource.uri}."
|
|
190
|
+
)
|
|
191
|
+
args_schema: type[BaseModel] = ReadResourceRequestParams
|
|
192
|
+
tool_connector: BaseConnector = connector
|
|
193
|
+
handle_tool_error: bool = True
|
|
194
|
+
|
|
195
|
+
def _run(self, **kwargs: Any) -> NoReturn:
|
|
196
|
+
raise NotImplementedError("Resource tools only support async operations")
|
|
197
|
+
|
|
198
|
+
async def _arun(self, **kwargs: Any) -> Any:
|
|
199
|
+
logger.debug(f'Resource tool: "{self.name}" called')
|
|
200
|
+
try:
|
|
201
|
+
result = await self.tool_connector.read_resource(mcp_resource.uri)
|
|
202
|
+
for content in result.contents:
|
|
203
|
+
# Attempt to decode bytes if necessary
|
|
204
|
+
if isinstance(content, bytes):
|
|
205
|
+
content_decoded = content.decode()
|
|
206
|
+
else:
|
|
207
|
+
content_decoded = str(content)
|
|
208
|
+
|
|
209
|
+
return content_decoded
|
|
210
|
+
except Exception as e:
|
|
211
|
+
if self.handle_tool_error:
|
|
212
|
+
return f"Error reading resource: {str(e)}"
|
|
213
|
+
raise
|
|
214
|
+
|
|
215
|
+
return ResourceTool()
|
|
216
|
+
|
|
217
|
+
def _convert_prompt(self, mcp_prompt: Prompt, connector: BaseConnector) -> BaseTool:
|
|
218
|
+
"""Convert an MCP prompt to LangChain's tool format.
|
|
219
|
+
|
|
220
|
+
The resulting tool executes `get_prompt` on the connector with the prompt's name and
|
|
221
|
+
the user-provided arguments (if any). The tool returns the decoded prompt content.
|
|
222
|
+
"""
|
|
223
|
+
prompt_arguments = mcp_prompt.arguments
|
|
224
|
+
|
|
225
|
+
# Sanitize the prompt name to create a valid Python identifier for the model name
|
|
226
|
+
base_model_name = re.sub(r"[^a-zA-Z0-9_]", "_", mcp_prompt.name)
|
|
227
|
+
if not base_model_name or base_model_name[0].isdigit():
|
|
228
|
+
base_model_name = "PromptArgs_" + base_model_name
|
|
229
|
+
dynamic_model_name = f"{base_model_name}_InputSchema"
|
|
230
|
+
|
|
231
|
+
if prompt_arguments:
|
|
232
|
+
field_definitions_for_create: dict[str, Any] = {}
|
|
233
|
+
for arg in prompt_arguments:
|
|
234
|
+
param_type: type = getattr(arg, "type", str)
|
|
235
|
+
if arg.required:
|
|
236
|
+
field_definitions_for_create[arg.name] = (
|
|
237
|
+
param_type,
|
|
238
|
+
Field(description=arg.description),
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
field_definitions_for_create[arg.name] = (
|
|
242
|
+
param_type | None,
|
|
243
|
+
Field(None, description=arg.description),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
InputSchema = create_model(
|
|
247
|
+
dynamic_model_name, **field_definitions_for_create, __base__=BaseModel
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
# Create an empty Pydantic model if there are no arguments
|
|
251
|
+
InputSchema = create_model(dynamic_model_name, __base__=BaseModel)
|
|
252
|
+
|
|
253
|
+
class PromptTool(BaseTool):
|
|
254
|
+
name: str = mcp_prompt.name
|
|
255
|
+
description: str = mcp_prompt.description
|
|
256
|
+
|
|
257
|
+
args_schema: type[BaseModel] = InputSchema
|
|
258
|
+
tool_connector: BaseConnector = connector
|
|
259
|
+
handle_tool_error: bool = True
|
|
260
|
+
|
|
261
|
+
def _run(self, **kwargs: Any) -> NoReturn:
|
|
262
|
+
raise NotImplementedError("Prompt tools only support async operations")
|
|
263
|
+
|
|
264
|
+
async def _arun(self, **kwargs: Any) -> Any:
|
|
265
|
+
logger.debug(f'Prompt tool: "{self.name}" called with args: {kwargs}')
|
|
266
|
+
try:
|
|
267
|
+
result = await self.tool_connector.get_prompt(self.name, kwargs)
|
|
268
|
+
return result.messages
|
|
269
|
+
except Exception as e:
|
|
270
|
+
if self.handle_tool_error:
|
|
271
|
+
return f"Error fetching prompt: {str(e)}"
|
|
272
|
+
raise
|
|
273
|
+
|
|
274
|
+
return PromptTool()
|
mcp_use/agents/mcpagent.py
CHANGED
|
@@ -9,6 +9,7 @@ import logging
|
|
|
9
9
|
from collections.abc import AsyncIterator
|
|
10
10
|
|
|
11
11
|
from langchain.agents import AgentExecutor, create_tool_calling_agent
|
|
12
|
+
from langchain.agents.output_parsers.tools import ToolAgentAction
|
|
12
13
|
from langchain.globals import set_debug
|
|
13
14
|
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
|
|
14
15
|
from langchain.schema import AIMessage, BaseMessage, HumanMessage, SystemMessage
|
|
@@ -21,7 +22,6 @@ from langchain_core.utils.input import get_color_mapping
|
|
|
21
22
|
|
|
22
23
|
from mcp_use.client import MCPClient
|
|
23
24
|
from mcp_use.connectors.base import BaseConnector
|
|
24
|
-
from mcp_use.session import MCPSession
|
|
25
25
|
|
|
26
26
|
from ..adapters.langchain_adapter import LangChainAdapter
|
|
27
27
|
from ..logging import logger
|
|
@@ -102,9 +102,7 @@ class MCPAgent:
|
|
|
102
102
|
|
|
103
103
|
# State tracking
|
|
104
104
|
self._agent_executor: AgentExecutor | None = None
|
|
105
|
-
self._sessions: dict[str, MCPSession] = {}
|
|
106
105
|
self._system_message: SystemMessage | None = None
|
|
107
|
-
self._tools: list[BaseTool] = []
|
|
108
106
|
|
|
109
107
|
async def initialize(self) -> None:
|
|
110
108
|
"""Initialize the MCP client and agent."""
|
|
@@ -346,20 +344,18 @@ class MCPAgent:
|
|
|
346
344
|
history_to_use = (
|
|
347
345
|
external_history if external_history is not None else self._conversation_history
|
|
348
346
|
)
|
|
349
|
-
|
|
350
|
-
m for m in history_to_use if isinstance(m, HumanMessage | AIMessage)
|
|
351
|
-
]
|
|
352
|
-
inputs = {"input": query, "chat_history": langchain_history}
|
|
347
|
+
inputs = {"input": query, "chat_history": history_to_use}
|
|
353
348
|
|
|
354
349
|
# 3. Stream & diff -------------------------------------------------------
|
|
355
|
-
accumulated = ""
|
|
356
350
|
async for event in self._agent_executor.astream_events(inputs):
|
|
351
|
+
if event.get("event") == "on_chain_end":
|
|
352
|
+
output = event["data"]["output"]
|
|
353
|
+
if isinstance(output, list):
|
|
354
|
+
for message in output:
|
|
355
|
+
if not isinstance(message, ToolAgentAction):
|
|
356
|
+
self.add_to_history(message)
|
|
357
357
|
yield event
|
|
358
358
|
|
|
359
|
-
# 4. Persist assistant message ------------------------------------------
|
|
360
|
-
if self.memory_enabled and accumulated:
|
|
361
|
-
self.add_to_history(AIMessage(content=accumulated))
|
|
362
|
-
|
|
363
359
|
# 5. House-keeping -------------------------------------------------------
|
|
364
360
|
if initialised_here and manage_connector:
|
|
365
361
|
await self.close()
|
mcp_use/client.py
CHANGED
|
@@ -12,6 +12,7 @@ from typing import Any
|
|
|
12
12
|
from .config import create_connector_from_config, load_config_file
|
|
13
13
|
from .logging import logger
|
|
14
14
|
from .session import MCPSession
|
|
15
|
+
from .types.clientoptions import ClientOptions
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class MCPClient:
|
|
@@ -24,14 +25,17 @@ class MCPClient:
|
|
|
24
25
|
def __init__(
|
|
25
26
|
self,
|
|
26
27
|
config: str | dict[str, Any] | None = None,
|
|
28
|
+
options: ClientOptions | None = None,
|
|
27
29
|
) -> None:
|
|
28
30
|
"""Initialize a new MCP client.
|
|
29
31
|
|
|
30
32
|
Args:
|
|
31
33
|
config: Either a dict containing configuration or a path to a JSON config file.
|
|
32
34
|
If None, an empty configuration is used.
|
|
35
|
+
options: Configuration options for the client.
|
|
33
36
|
"""
|
|
34
37
|
self.config: dict[str, Any] = {}
|
|
38
|
+
self.options = options or {}
|
|
35
39
|
self.sessions: dict[str, MCPSession] = {}
|
|
36
40
|
self.active_sessions: list[str] = []
|
|
37
41
|
|
|
@@ -43,22 +47,24 @@ class MCPClient:
|
|
|
43
47
|
self.config = config
|
|
44
48
|
|
|
45
49
|
@classmethod
|
|
46
|
-
def from_dict(cls, config: dict[str, Any]) -> "MCPClient":
|
|
50
|
+
def from_dict(cls, config: dict[str, Any], options: ClientOptions | None = None) -> "MCPClient":
|
|
47
51
|
"""Create a MCPClient from a dictionary.
|
|
48
52
|
|
|
49
53
|
Args:
|
|
50
54
|
config: The configuration dictionary.
|
|
55
|
+
options: Optional client configuration options.
|
|
51
56
|
"""
|
|
52
|
-
return cls(config=config)
|
|
57
|
+
return cls(config=config, options=options)
|
|
53
58
|
|
|
54
59
|
@classmethod
|
|
55
|
-
def from_config_file(cls, filepath: str) -> "MCPClient":
|
|
60
|
+
def from_config_file(cls, filepath: str, options: ClientOptions | None = None) -> "MCPClient":
|
|
56
61
|
"""Create a MCPClient from a configuration file.
|
|
57
62
|
|
|
58
63
|
Args:
|
|
59
64
|
filepath: The path to the configuration file.
|
|
65
|
+
options: Optional client configuration options.
|
|
60
66
|
"""
|
|
61
|
-
return cls(config=load_config_file(filepath))
|
|
67
|
+
return cls(config=load_config_file(filepath), options=options)
|
|
62
68
|
|
|
63
69
|
def add_server(
|
|
64
70
|
self,
|
|
@@ -111,6 +117,7 @@ class MCPClient:
|
|
|
111
117
|
|
|
112
118
|
Args:
|
|
113
119
|
server_name: The name of the server to create a session for.
|
|
120
|
+
auto_initialize: Whether to automatically initialize the session.
|
|
114
121
|
|
|
115
122
|
Returns:
|
|
116
123
|
The created MCPSession.
|
|
@@ -128,7 +135,9 @@ class MCPClient:
|
|
|
128
135
|
raise ValueError(f"Server '{server_name}' not found in config")
|
|
129
136
|
|
|
130
137
|
server_config = servers[server_name]
|
|
131
|
-
|
|
138
|
+
|
|
139
|
+
# Create connector with options
|
|
140
|
+
connector = create_connector_from_config(server_config, options=self.options)
|
|
132
141
|
|
|
133
142
|
# Create the session
|
|
134
143
|
session = MCPSession(connector)
|
|
@@ -146,16 +155,16 @@ class MCPClient:
|
|
|
146
155
|
self,
|
|
147
156
|
auto_initialize: bool = True,
|
|
148
157
|
) -> dict[str, MCPSession]:
|
|
149
|
-
"""Create
|
|
158
|
+
"""Create sessions for all configured servers.
|
|
150
159
|
|
|
151
160
|
Args:
|
|
152
|
-
auto_initialize: Whether to automatically initialize the
|
|
161
|
+
auto_initialize: Whether to automatically initialize the sessions.
|
|
153
162
|
|
|
154
163
|
Returns:
|
|
155
|
-
|
|
164
|
+
Dictionary mapping server names to their MCPSession instances.
|
|
156
165
|
|
|
157
166
|
Warns:
|
|
158
|
-
|
|
167
|
+
UserWarning: If no servers are configured.
|
|
159
168
|
"""
|
|
160
169
|
# Get server config
|
|
161
170
|
servers = self.config.get("mcpServers", {})
|
mcp_use/config.py
CHANGED
|
@@ -7,7 +7,15 @@ This module provides functionality to load MCP configuration from JSON files.
|
|
|
7
7
|
import json
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
from .connectors import
|
|
10
|
+
from .connectors import (
|
|
11
|
+
BaseConnector,
|
|
12
|
+
HttpConnector,
|
|
13
|
+
SandboxConnector,
|
|
14
|
+
StdioConnector,
|
|
15
|
+
WebSocketConnector,
|
|
16
|
+
)
|
|
17
|
+
from .connectors.utils import is_stdio_server
|
|
18
|
+
from .types.clientoptions import ClientOptions
|
|
11
19
|
|
|
12
20
|
|
|
13
21
|
def load_config_file(filepath: str) -> dict[str, Any]:
|
|
@@ -23,23 +31,41 @@ def load_config_file(filepath: str) -> dict[str, Any]:
|
|
|
23
31
|
return json.load(f)
|
|
24
32
|
|
|
25
33
|
|
|
26
|
-
def create_connector_from_config(
|
|
34
|
+
def create_connector_from_config(
|
|
35
|
+
server_config: dict[str, Any],
|
|
36
|
+
options: ClientOptions | None = None,
|
|
37
|
+
) -> BaseConnector:
|
|
27
38
|
"""Create a connector based on server configuration.
|
|
28
|
-
|
|
39
|
+
This function can be called with just the server_config parameter:
|
|
40
|
+
create_connector_from_config(server_config)
|
|
29
41
|
Args:
|
|
30
42
|
server_config: The server configuration section
|
|
43
|
+
options: Optional client configuration options including sandboxing preferences.
|
|
44
|
+
If None, default client options will be used.
|
|
31
45
|
|
|
32
46
|
Returns:
|
|
33
47
|
A configured connector instance
|
|
34
48
|
"""
|
|
49
|
+
# Use default options if none provided
|
|
50
|
+
options = options or {"is_sandboxed": False}
|
|
51
|
+
|
|
35
52
|
# Stdio connector (command-based)
|
|
36
|
-
if
|
|
53
|
+
if is_stdio_server(server_config) and not options.get("is_sandboxed", False):
|
|
37
54
|
return StdioConnector(
|
|
38
55
|
command=server_config["command"],
|
|
39
56
|
args=server_config["args"],
|
|
40
57
|
env=server_config.get("env", None),
|
|
41
58
|
)
|
|
42
59
|
|
|
60
|
+
# Sandboxed connector
|
|
61
|
+
elif is_stdio_server(server_config) and options.get("is_sandboxed", False):
|
|
62
|
+
return SandboxConnector(
|
|
63
|
+
command=server_config["command"],
|
|
64
|
+
args=server_config["args"],
|
|
65
|
+
env=server_config.get("env", None),
|
|
66
|
+
e2b_options=options.get("sandbox_options", {}),
|
|
67
|
+
)
|
|
68
|
+
|
|
43
69
|
# HTTP connector
|
|
44
70
|
elif "url" in server_config:
|
|
45
71
|
return HttpConnector(
|
mcp_use/connectors/__init__.py
CHANGED
|
@@ -5,9 +5,16 @@ This module provides interfaces for connecting to MCP implementations
|
|
|
5
5
|
through different transport mechanisms.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .base import BaseConnector
|
|
9
|
-
from .http import HttpConnector
|
|
10
|
-
from .
|
|
11
|
-
from .
|
|
8
|
+
from .base import BaseConnector # noqa: F401
|
|
9
|
+
from .http import HttpConnector # noqa: F401
|
|
10
|
+
from .sandbox import SandboxConnector # noqa: F401
|
|
11
|
+
from .stdio import StdioConnector # noqa: F401
|
|
12
|
+
from .websocket import WebSocketConnector # noqa: F401
|
|
12
13
|
|
|
13
|
-
__all__ = [
|
|
14
|
+
__all__ = [
|
|
15
|
+
"BaseConnector",
|
|
16
|
+
"StdioConnector",
|
|
17
|
+
"HttpConnector",
|
|
18
|
+
"WebSocketConnector",
|
|
19
|
+
"SandboxConnector",
|
|
20
|
+
]
|
mcp_use/connectors/base.py
CHANGED
|
@@ -9,7 +9,8 @@ from abc import ABC, abstractmethod
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from mcp import ClientSession
|
|
12
|
-
from mcp.
|
|
12
|
+
from mcp.shared.exceptions import McpError
|
|
13
|
+
from mcp.types import CallToolResult, GetPromptResult, Prompt, ReadResourceResult, Resource, Tool
|
|
13
14
|
|
|
14
15
|
from ..logging import logger
|
|
15
16
|
from ..task_managers import ConnectionManager
|
|
@@ -26,6 +27,8 @@ class BaseConnector(ABC):
|
|
|
26
27
|
self.client: ClientSession | None = None
|
|
27
28
|
self._connection_manager: ConnectionManager | None = None
|
|
28
29
|
self._tools: list[Tool] | None = None
|
|
30
|
+
self._resources: list[Resource] | None = None
|
|
31
|
+
self._prompts: list[Prompt] | None = None
|
|
29
32
|
self._connected = False
|
|
30
33
|
|
|
31
34
|
@abstractmethod
|
|
@@ -74,6 +77,8 @@ class BaseConnector(ABC):
|
|
|
74
77
|
|
|
75
78
|
# Reset tools
|
|
76
79
|
self._tools = None
|
|
80
|
+
self._resources = None
|
|
81
|
+
self._prompts = None
|
|
77
82
|
|
|
78
83
|
if errors:
|
|
79
84
|
logger.warning(f"Encountered {len(errors)} errors during resource cleanup")
|
|
@@ -88,21 +93,58 @@ class BaseConnector(ABC):
|
|
|
88
93
|
# Initialize the session
|
|
89
94
|
result = await self.client.initialize()
|
|
90
95
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
server_capabilities = result.capabilities
|
|
97
|
+
|
|
98
|
+
if server_capabilities.tools:
|
|
99
|
+
# Get available tools
|
|
100
|
+
tools_result = await self.list_tools()
|
|
101
|
+
self._tools = tools_result or []
|
|
102
|
+
else:
|
|
103
|
+
self._tools = []
|
|
104
|
+
|
|
105
|
+
if server_capabilities.resources:
|
|
106
|
+
# Get available resources
|
|
107
|
+
resources_result = await self.list_resources()
|
|
108
|
+
self._resources = resources_result or []
|
|
109
|
+
else:
|
|
110
|
+
self._resources = []
|
|
111
|
+
|
|
112
|
+
if server_capabilities.prompts:
|
|
113
|
+
# Get available prompts
|
|
114
|
+
prompts_result = await self.list_prompts()
|
|
115
|
+
self._prompts = prompts_result or []
|
|
116
|
+
else:
|
|
117
|
+
self._prompts = []
|
|
118
|
+
|
|
119
|
+
logger.debug(
|
|
120
|
+
f"MCP session initialized with {len(self._tools)} tools, "
|
|
121
|
+
f"{len(self._resources)} resources, "
|
|
122
|
+
f"and {len(self._prompts)} prompts"
|
|
123
|
+
)
|
|
96
124
|
|
|
97
125
|
return result
|
|
98
126
|
|
|
99
127
|
@property
|
|
100
128
|
def tools(self) -> list[Tool]:
|
|
101
129
|
"""Get the list of available tools."""
|
|
102
|
-
if
|
|
130
|
+
if self._tools is None:
|
|
103
131
|
raise RuntimeError("MCP client is not initialized")
|
|
104
132
|
return self._tools
|
|
105
133
|
|
|
134
|
+
@property
|
|
135
|
+
def resources(self) -> list[Resource]:
|
|
136
|
+
"""Get the list of available resources."""
|
|
137
|
+
if self._resources is None:
|
|
138
|
+
raise RuntimeError("MCP client is not initialized")
|
|
139
|
+
return self._resources
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def prompts(self) -> list[Prompt]:
|
|
143
|
+
"""Get the list of available prompts."""
|
|
144
|
+
if self._prompts is None:
|
|
145
|
+
raise RuntimeError("MCP client is not initialized")
|
|
146
|
+
return self._prompts
|
|
147
|
+
|
|
106
148
|
async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
|
|
107
149
|
"""Call an MCP tool with the given arguments."""
|
|
108
150
|
if not self.client:
|
|
@@ -113,43 +155,64 @@ class BaseConnector(ABC):
|
|
|
113
155
|
logger.debug(f"Tool '{name}' called with result: {result}")
|
|
114
156
|
return result
|
|
115
157
|
|
|
116
|
-
async def
|
|
158
|
+
async def list_tools(self) -> list[Tool]:
|
|
159
|
+
"""List all available tools from the MCP implementation."""
|
|
160
|
+
if not self.client:
|
|
161
|
+
raise RuntimeError("MCP client is not connected")
|
|
162
|
+
|
|
163
|
+
logger.debug("Listing tools")
|
|
164
|
+
try:
|
|
165
|
+
result = await self.client.list_tools()
|
|
166
|
+
return result.tools
|
|
167
|
+
except McpError as e:
|
|
168
|
+
logger.error(f"Error listing tools: {e}")
|
|
169
|
+
return []
|
|
170
|
+
|
|
171
|
+
async def list_resources(self) -> list[Resource]:
|
|
117
172
|
"""List all available resources from the MCP implementation."""
|
|
118
173
|
if not self.client:
|
|
119
174
|
raise RuntimeError("MCP client is not connected")
|
|
120
175
|
|
|
121
176
|
logger.debug("Listing resources")
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
177
|
+
try:
|
|
178
|
+
result = await self.client.list_resources()
|
|
179
|
+
return result.resources
|
|
180
|
+
except McpError as e:
|
|
181
|
+
logger.error(f"Error listing resources: {e}")
|
|
182
|
+
return []
|
|
183
|
+
|
|
184
|
+
async def read_resource(self, uri: str) -> ReadResourceResult:
|
|
126
185
|
"""Read a resource by URI."""
|
|
127
186
|
if not self.client:
|
|
128
187
|
raise RuntimeError("MCP client is not connected")
|
|
129
188
|
|
|
130
189
|
logger.debug(f"Reading resource: {uri}")
|
|
131
|
-
|
|
132
|
-
return
|
|
190
|
+
result = await self.client.read_resource(uri)
|
|
191
|
+
return result
|
|
133
192
|
|
|
134
|
-
async def list_prompts(self) -> list[
|
|
193
|
+
async def list_prompts(self) -> list[Prompt]:
|
|
135
194
|
"""List all available prompts from the MCP implementation."""
|
|
136
195
|
if not self.client:
|
|
137
196
|
raise RuntimeError("MCP client is not connected")
|
|
138
197
|
|
|
139
198
|
logger.debug("Listing prompts")
|
|
140
|
-
|
|
141
|
-
|
|
199
|
+
try:
|
|
200
|
+
result = await self.client.list_prompts()
|
|
201
|
+
return result.prompts
|
|
202
|
+
except McpError as e:
|
|
203
|
+
logger.error(f"Error listing prompts: {e}")
|
|
204
|
+
return []
|
|
142
205
|
|
|
143
206
|
async def get_prompt(
|
|
144
207
|
self, name: str, arguments: dict[str, Any] | None = None
|
|
145
|
-
) ->
|
|
208
|
+
) -> GetPromptResult:
|
|
146
209
|
"""Get a prompt by name."""
|
|
147
210
|
if not self.client:
|
|
148
211
|
raise RuntimeError("MCP client is not connected")
|
|
149
212
|
|
|
150
213
|
logger.debug(f"Getting prompt: {name}")
|
|
151
|
-
|
|
152
|
-
return
|
|
214
|
+
result = await self.client.get_prompt(name, arguments)
|
|
215
|
+
return result
|
|
153
216
|
|
|
154
217
|
async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
155
218
|
"""Send a raw request to the MCP implementation."""
|