mcp-use 1.3.12__py3-none-any.whl → 1.4.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.

Files changed (108) hide show
  1. mcp_use/__init__.py +1 -1
  2. mcp_use/adapters/.deprecated +0 -0
  3. mcp_use/adapters/__init__.py +18 -7
  4. mcp_use/adapters/base.py +12 -185
  5. mcp_use/adapters/langchain_adapter.py +12 -219
  6. mcp_use/agents/adapters/__init__.py +17 -0
  7. mcp_use/agents/adapters/anthropic.py +93 -0
  8. mcp_use/agents/adapters/base.py +316 -0
  9. mcp_use/agents/adapters/google.py +103 -0
  10. mcp_use/agents/adapters/langchain_adapter.py +212 -0
  11. mcp_use/agents/adapters/openai.py +111 -0
  12. mcp_use/agents/base.py +1 -1
  13. mcp_use/agents/managers/__init__.py +19 -0
  14. mcp_use/agents/managers/base.py +36 -0
  15. mcp_use/agents/managers/server_manager.py +131 -0
  16. mcp_use/agents/managers/tools/__init__.py +15 -0
  17. mcp_use/agents/managers/tools/base_tool.py +19 -0
  18. mcp_use/agents/managers/tools/connect_server.py +69 -0
  19. mcp_use/agents/managers/tools/disconnect_server.py +43 -0
  20. mcp_use/agents/managers/tools/get_active_server.py +29 -0
  21. mcp_use/agents/managers/tools/list_servers_tool.py +53 -0
  22. mcp_use/agents/managers/tools/search_tools.py +328 -0
  23. mcp_use/agents/mcpagent.py +386 -485
  24. mcp_use/agents/prompts/system_prompt_builder.py +1 -1
  25. mcp_use/agents/remote.py +15 -2
  26. mcp_use/auth/.deprecated +0 -0
  27. mcp_use/auth/__init__.py +19 -4
  28. mcp_use/auth/bearer.py +11 -12
  29. mcp_use/auth/oauth.py +11 -620
  30. mcp_use/auth/oauth_callback.py +16 -207
  31. mcp_use/client/__init__.py +1 -0
  32. mcp_use/client/auth/__init__.py +6 -0
  33. mcp_use/client/auth/bearer.py +23 -0
  34. mcp_use/client/auth/oauth.py +629 -0
  35. mcp_use/client/auth/oauth_callback.py +215 -0
  36. mcp_use/client/client.py +356 -0
  37. mcp_use/client/config.py +106 -0
  38. mcp_use/client/connectors/__init__.py +20 -0
  39. mcp_use/client/connectors/base.py +470 -0
  40. mcp_use/client/connectors/http.py +304 -0
  41. mcp_use/client/connectors/sandbox.py +332 -0
  42. mcp_use/client/connectors/stdio.py +109 -0
  43. mcp_use/client/connectors/utils.py +13 -0
  44. mcp_use/client/connectors/websocket.py +257 -0
  45. mcp_use/client/exceptions.py +31 -0
  46. mcp_use/client/middleware/__init__.py +50 -0
  47. mcp_use/client/middleware/logging.py +31 -0
  48. mcp_use/client/middleware/metrics.py +314 -0
  49. mcp_use/client/middleware/middleware.py +266 -0
  50. mcp_use/client/session.py +162 -0
  51. mcp_use/client/task_managers/__init__.py +20 -0
  52. mcp_use/client/task_managers/base.py +145 -0
  53. mcp_use/client/task_managers/sse.py +84 -0
  54. mcp_use/client/task_managers/stdio.py +69 -0
  55. mcp_use/client/task_managers/streamable_http.py +86 -0
  56. mcp_use/client/task_managers/websocket.py +68 -0
  57. mcp_use/client.py +12 -344
  58. mcp_use/config.py +20 -97
  59. mcp_use/connectors/.deprecated +0 -0
  60. mcp_use/connectors/__init__.py +46 -20
  61. mcp_use/connectors/base.py +12 -455
  62. mcp_use/connectors/http.py +13 -300
  63. mcp_use/connectors/sandbox.py +13 -306
  64. mcp_use/connectors/stdio.py +13 -104
  65. mcp_use/connectors/utils.py +15 -8
  66. mcp_use/connectors/websocket.py +13 -252
  67. mcp_use/exceptions.py +33 -18
  68. mcp_use/logging.py +1 -1
  69. mcp_use/managers/.deprecated +0 -0
  70. mcp_use/managers/__init__.py +56 -17
  71. mcp_use/managers/base.py +13 -31
  72. mcp_use/managers/server_manager.py +13 -119
  73. mcp_use/managers/tools/__init__.py +45 -15
  74. mcp_use/managers/tools/base_tool.py +5 -16
  75. mcp_use/managers/tools/connect_server.py +5 -67
  76. mcp_use/managers/tools/disconnect_server.py +5 -41
  77. mcp_use/managers/tools/get_active_server.py +5 -26
  78. mcp_use/managers/tools/list_servers_tool.py +5 -51
  79. mcp_use/managers/tools/search_tools.py +17 -321
  80. mcp_use/middleware/.deprecated +0 -0
  81. mcp_use/middleware/__init__.py +89 -50
  82. mcp_use/middleware/logging.py +14 -26
  83. mcp_use/middleware/metrics.py +30 -303
  84. mcp_use/middleware/middleware.py +39 -246
  85. mcp_use/session.py +13 -149
  86. mcp_use/task_managers/.deprecated +0 -0
  87. mcp_use/task_managers/__init__.py +48 -20
  88. mcp_use/task_managers/base.py +13 -140
  89. mcp_use/task_managers/sse.py +13 -79
  90. mcp_use/task_managers/stdio.py +13 -64
  91. mcp_use/task_managers/streamable_http.py +15 -81
  92. mcp_use/task_managers/websocket.py +13 -63
  93. mcp_use/telemetry/events.py +58 -0
  94. mcp_use/telemetry/telemetry.py +71 -1
  95. mcp_use/telemetry/utils.py +1 -1
  96. mcp_use/types/.deprecated +0 -0
  97. mcp_use/types/sandbox.py +13 -18
  98. {mcp_use-1.3.12.dist-info → mcp_use-1.4.0.dist-info}/METADATA +68 -43
  99. mcp_use-1.4.0.dist-info/RECORD +111 -0
  100. mcp_use/cli.py +0 -581
  101. mcp_use-1.3.12.dist-info/RECORD +0 -64
  102. mcp_use-1.3.12.dist-info/licenses/LICENSE +0 -21
  103. /mcp_use/{observability → agents/observability}/__init__.py +0 -0
  104. /mcp_use/{observability → agents/observability}/callbacks_manager.py +0 -0
  105. /mcp_use/{observability → agents/observability}/laminar.py +0 -0
  106. /mcp_use/{observability → agents/observability}/langfuse.py +0 -0
  107. {mcp_use-1.3.12.dist-info → mcp_use-1.4.0.dist-info}/WHEEL +0 -0
  108. {mcp_use-1.3.12.dist-info → mcp_use-1.4.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,316 @@
1
+ """
2
+ Base adapter interface for MCP tools.
3
+
4
+ This module provides the abstract base class that all MCP tool adapters should inherit from.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any, TypeVar
9
+
10
+ from mcp.types import Prompt, Resource, Tool
11
+
12
+ from mcp_use.client.client import MCPClient
13
+ from mcp_use.client.connectors.base import BaseConnector
14
+ from mcp_use.logging import logger
15
+ from mcp_use.telemetry.telemetry import telemetry
16
+
17
+ # Generic type for the tools created by the adapter
18
+ T = TypeVar("T")
19
+
20
+
21
+ class BaseAdapter(ABC):
22
+ """Abstract base class for converting MCP tools to other framework formats.
23
+
24
+ This class defines the common interface that all adapter implementations
25
+ should follow to ensure consistency across different frameworks.
26
+ """
27
+
28
+ def __init__(self, disallowed_tools: list[str] | None = None) -> None:
29
+ """Initialize a new adapter.
30
+
31
+ Args:
32
+ disallowed_tools: list of tool names that should not be available.
33
+ """
34
+ self.disallowed_tools = disallowed_tools or []
35
+
36
+ # Three maps to optimize and cache only what is needed
37
+ self._connector_tool_map: dict[BaseConnector, list[T]] = {}
38
+ self._connector_resource_map: dict[BaseConnector, list[T]] = {}
39
+ self._connector_prompt_map: dict[BaseConnector, list[T]] = {}
40
+
41
+ self.tools: list[T] = []
42
+ self.resources: list[T] = []
43
+ self.prompts: list[T] = []
44
+
45
+ def parse_result(self, tool_result: Any) -> str:
46
+ """Parse the result from any MCP operation (tool, resource, or prompt) into a string.
47
+
48
+ Args:
49
+ tool_result: The result object from an MCP operation.
50
+
51
+ Returns:
52
+ A string representation of the result content.
53
+ """
54
+ if getattr(tool_result, "isError", False):
55
+ # Handle errors first
56
+ error_content = tool_result.content or "Unknown error"
57
+ return f"Error: {error_content}"
58
+ elif hasattr(tool_result, "contents"): # For Resources (ReadResourceResult)
59
+ return "\n".join(c.decode() if isinstance(c, bytes) else str(c) for c in tool_result.contents)
60
+ elif hasattr(tool_result, "messages"): # For Prompts (GetPromptResult)
61
+ return "\n".join(str(s) for s in tool_result.messages)
62
+ elif hasattr(tool_result, "content"): # For Tools (CallToolResult)
63
+ return str(tool_result.content)
64
+ else:
65
+ # Fallback for unexpected types
66
+ return str(tool_result)
67
+
68
+ @telemetry("adapter_fix_schema")
69
+ def fix_schema(self, schema: dict) -> dict:
70
+ """Convert JSON Schema 'type': ['string', 'null'] to 'anyOf' format and fix enum handling.
71
+
72
+ Args:
73
+ schema: The JSON schema to fix.
74
+
75
+ Returns:
76
+ The fixed JSON schema.
77
+ """
78
+ if isinstance(schema, dict):
79
+ if "type" in schema and isinstance(schema["type"], list):
80
+ schema["anyOf"] = [{"type": t} for t in schema["type"]]
81
+ del schema["type"] # Remove 'type' and standardize to 'anyOf'
82
+
83
+ # Fix enum handling - ensure enum fields are properly typed as strings
84
+ if "enum" in schema and "type" not in schema:
85
+ schema["type"] = "string"
86
+
87
+ for key, value in schema.items():
88
+ schema[key] = self.fix_schema(value) # Apply recursively
89
+ return schema
90
+
91
+ async def _get_connectors(self, client: MCPClient) -> list[BaseConnector]:
92
+ """Get all connectors from the client, creating sessions if needed."""
93
+ if not client.active_sessions:
94
+ logger.info("No active sessions found, creating new ones...")
95
+ await client.create_all_sessions()
96
+
97
+ sessions = client.get_all_active_sessions()
98
+ return [session.connector for session in sessions.values()]
99
+
100
+ async def create_all(self, client: MCPClient) -> None:
101
+ """Create tools, resources, and prompts from an MCPClient instance."""
102
+ await self.create_tools(client)
103
+ await self.create_resources(client)
104
+ await self.create_prompts(client)
105
+
106
+ async def create_tools(self, client: MCPClient) -> list[T]:
107
+ """Create tools from the MCPClient instance.
108
+
109
+ This handles session creation and connector extraction automatically.
110
+ The created tools are stored in `self.tools`.
111
+
112
+ Returns:
113
+ A list of tools in the target framework's format.
114
+ """
115
+ connectors = await self._get_connectors(client)
116
+ self.tools = await self._create_tools_from_connectors(connectors)
117
+
118
+ return self.tools
119
+
120
+ async def create_resources(self, client: MCPClient) -> list[T]:
121
+ """Create resources from the MCPClient instance.
122
+
123
+ This handles session creation and connector extraction automatically.
124
+ The created resources are stored in `self.resources`.
125
+
126
+ Returns:
127
+ A list of resources in the target framework's format.
128
+ """
129
+ connectors = await self._get_connectors(client)
130
+ self.resources = await self._create_resources_from_connectors(connectors)
131
+
132
+ return self.resources
133
+
134
+ async def create_prompts(self, client: MCPClient) -> list[T]:
135
+ """Create prompts from the MCPClient instance.
136
+
137
+ This handles session creation and connector extraction automatically.
138
+ The created prompts are stored in `self.prompts`.
139
+
140
+ Returns:
141
+ A list of prompts in the target framework's format.
142
+ """
143
+ connectors = await self._get_connectors(client)
144
+ self.prompts = await self._create_prompts_from_connectors(connectors)
145
+
146
+ return self.prompts
147
+
148
+ @telemetry("adapter_load_tools")
149
+ async def load_tools_for_connector(self, connector: BaseConnector) -> list[T]:
150
+ """Dynamically load tools for a specific connector.
151
+
152
+ Args:
153
+ connector: The connector to load tools for.
154
+
155
+ Returns:
156
+ The list of tools that were loaded in the target framework's format.
157
+ """
158
+ # Check if we already have tools for this connector
159
+ if connector in self._connector_tool_map:
160
+ logger.debug(f"Returning {len(self._connector_tool_map[connector])} existing tools for connector")
161
+ return self._connector_tool_map[connector]
162
+
163
+ if not await self._ensure_connector_initialized(connector):
164
+ return []
165
+
166
+ connector_tools = []
167
+ for tool in await connector.list_tools():
168
+ converted_tool = self._convert_tool(tool, connector)
169
+ if converted_tool:
170
+ connector_tools.append(converted_tool)
171
+
172
+ # Store the tools for this connector
173
+ self._connector_tool_map[connector] = connector_tools
174
+
175
+ # Log available tools for debugging
176
+ logger.debug(
177
+ f"Loaded {len(connector_tools)} new tools for connector: "
178
+ f"{[getattr(tool, 'name', str(tool)) for tool in connector_tools]}"
179
+ )
180
+
181
+ return connector_tools
182
+
183
+ @telemetry("adapter_load_resources")
184
+ async def load_resources_for_connector(self, connector: BaseConnector):
185
+ """Dynamically load resources for a specific connector.
186
+
187
+ Args:
188
+ connector: The connector to load resources for.
189
+
190
+ Returns:
191
+ The list of resources that were loaded in the target framework's format.
192
+ """
193
+ if connector in self._connector_resource_map:
194
+ logger.debug(f"Returning {len(self._connector_resource_map[connector])} existing resources for connector")
195
+ return self._connector_resource_map[connector]
196
+
197
+ if not await self._ensure_connector_initialized(connector):
198
+ return []
199
+
200
+ connector_resources = []
201
+ for resource in await connector.list_resources() or []:
202
+ converted_resource = self._convert_resource(resource, connector)
203
+ if converted_resource:
204
+ connector_resources.append(converted_resource)
205
+
206
+ self._connector_resource_map[connector] = connector_resources
207
+ logger.debug(
208
+ f"Loaded {len(connector_resources)} new resources for connector: "
209
+ f"{[getattr(r, 'name', str(r)) for r in connector_resources]}"
210
+ )
211
+ return connector_resources
212
+
213
+ @telemetry("adapter_load_prompts")
214
+ async def load_prompts_for_connector(self, connector: BaseConnector) -> list[T]:
215
+ """Dynamically load prompts for a specific connector.
216
+
217
+ Args:
218
+ connector: The connector to load prompts for.
219
+
220
+ Returns:
221
+ The list of prompts that were loaded in the target framework's format.
222
+ """
223
+ if connector in self._connector_prompt_map:
224
+ logger.debug(f"Returning {len(self._connector_prompt_map[connector])} existing prompts for connector")
225
+ return self._connector_prompt_map[connector]
226
+
227
+ if not await self._ensure_connector_initialized(connector):
228
+ return []
229
+
230
+ connector_prompts = []
231
+ for prompt in await connector.list_prompts() or []:
232
+ converted_prompt = self._convert_prompt(prompt, connector)
233
+ if converted_prompt:
234
+ connector_prompts.append(converted_prompt)
235
+
236
+ self._connector_prompt_map[connector] = connector_prompts
237
+ logger.debug(
238
+ f"Loaded {len(connector_prompts)} new prompts for connector: "
239
+ f"{[getattr(p, 'name', str(p)) for p in connector_prompts]}"
240
+ )
241
+ return connector_prompts
242
+
243
+ @abstractmethod
244
+ def _convert_tool(self, mcp_tool: Tool, connector: BaseConnector) -> T:
245
+ """Convert an MCP tool to the target framework's tool format."""
246
+ pass
247
+
248
+ @abstractmethod
249
+ def _convert_resource(self, mcp_resource: Resource, connector: BaseConnector) -> T:
250
+ """Convert an MCP resource to the target framework's resource format."""
251
+ pass
252
+
253
+ @abstractmethod
254
+ def _convert_prompt(self, mcp_prompt: Prompt, connector: BaseConnector) -> T:
255
+ """Convert an MCP prompt to the target framework's prompt format."""
256
+ pass
257
+
258
+ async def _create_tools_from_connectors(self, connectors: list[BaseConnector]) -> list[T]:
259
+ """Create tools from MCP tools in all provided connectors."""
260
+ tools = []
261
+ for connector in connectors:
262
+ connector_tools = await self.load_tools_for_connector(connector)
263
+ tools.extend(connector_tools)
264
+
265
+ logger.debug(f"Available tools: {len(tools)}")
266
+ return tools
267
+
268
+ async def _create_resources_from_connectors(self, connectors: list[BaseConnector]) -> list[T]:
269
+ """Create resources from MCP resources in all provided connectors."""
270
+ resources = []
271
+ for connector in connectors:
272
+ connector_resources = await self.load_resources_for_connector(connector)
273
+ resources.extend(connector_resources)
274
+
275
+ logger.debug(f"Available resources: {len(resources)}")
276
+ return resources
277
+
278
+ async def _create_prompts_from_connectors(self, connectors: list[BaseConnector]) -> list[T]:
279
+ """Create prompts from MCP prompts in all provided connectors."""
280
+ prompts = []
281
+ for connector in connectors:
282
+ connector_prompts = await self.load_prompts_for_connector(connector)
283
+ prompts.extend(connector_prompts)
284
+
285
+ logger.debug(f"Available prompts: {len(prompts)}")
286
+ return prompts
287
+
288
+ def _check_connector_initialized(self, connector: BaseConnector) -> bool:
289
+ """Check if a connector is initialized and has tools.
290
+
291
+ Args:
292
+ connector: The connector to check.
293
+
294
+ Returns:
295
+ True if the connector is initialized and has tools, False otherwise.
296
+ """
297
+ return hasattr(connector, "tools") and connector.tools
298
+
299
+ async def _ensure_connector_initialized(self, connector: BaseConnector) -> bool:
300
+ """Ensure a connector is initialized.
301
+
302
+ Args:
303
+ connector: The connector to initialize.
304
+
305
+ Returns:
306
+ True if initialization succeeded, False otherwise.
307
+ """
308
+ if not self._check_connector_initialized(connector):
309
+ logger.debug("Connector doesn't have tools, initializing it")
310
+ try:
311
+ await connector.initialize()
312
+ return True
313
+ except Exception as e:
314
+ logger.error(f"Error initializing connector: {e}")
315
+ return False
316
+ return True
@@ -0,0 +1,103 @@
1
+ import re
2
+ from collections.abc import Callable, Coroutine
3
+ from typing import Any
4
+
5
+ from mcp.types import Prompt, Resource, Tool
6
+
7
+ from mcp_use.agents.adapters.base import BaseAdapter
8
+ from mcp_use.client.connectors.base import BaseConnector
9
+
10
+ try:
11
+ from google.genai import types
12
+ except ImportError as e:
13
+ raise ImportError(
14
+ "google-genai is required for GoogleMCPAdapter. Install it with: uv pip install google-genai"
15
+ ) from e
16
+
17
+
18
+ def _sanitize_for_tool_name(name: str) -> str:
19
+ """Sanitizes a string to be a valid tool name for Google."""
20
+ # Google tool names can only contain a-z, A-Z, 0-9, and underscores,
21
+ # and must be 64 characters or less.
22
+ return re.sub(r"[^a-zA-Z0-9_]+", "_", name).strip("_")[:64]
23
+
24
+
25
+ class GoogleMCPAdapter(BaseAdapter):
26
+ def __init__(self, disallowed_tools: list[str] | None = None) -> None:
27
+ """Initialize a new Google adapter.
28
+
29
+ Args:
30
+ disallowed_tools: list of tool names that should not be available.
31
+ """
32
+ super().__init__(disallowed_tools)
33
+ # This map stores the actual async function to call for each tool.
34
+ self.tool_executors: dict[str, Callable[..., Coroutine[Any, Any, Any]]] = {}
35
+
36
+ self._connector_tool_map: dict[BaseConnector, list[types.FunctionDeclaration]] = {}
37
+ self._connector_resource_map: dict[BaseConnector, list[types.FunctionDeclaration]] = {}
38
+ self._connector_prompt_map: dict[BaseConnector, list[types.FunctionDeclaration]] = {}
39
+
40
+ self.tools: list[types.FunctionDeclaration] = []
41
+ self.resources: list[types.FunctionDeclaration] = []
42
+ self.prompts: list[types.FunctionDeclaration] = []
43
+
44
+ def _convert_tool(self, mcp_tool: Tool, connector: BaseConnector) -> types.FunctionDeclaration:
45
+ """Convert an MCP tool to the Google tool format."""
46
+ if mcp_tool.name in self.disallowed_tools:
47
+ return None
48
+
49
+ self.tool_executors[mcp_tool.name] = (
50
+ lambda connector=connector, name=mcp_tool.name, **kwargs: connector.call_tool(name, kwargs)
51
+ )
52
+
53
+ fixed_schema = self.fix_schema(mcp_tool.inputSchema)
54
+ function_declaration = types.FunctionDeclaration(
55
+ name=mcp_tool.name, description=mcp_tool.description, parameters_json_schema=fixed_schema
56
+ )
57
+ return function_declaration
58
+
59
+ def _convert_resource(self, mcp_resource: Resource, connector: BaseConnector) -> types.FunctionDeclaration:
60
+ """Convert an MCP resource to a readable tool in Google format."""
61
+ tool_name = _sanitize_for_tool_name(f"resource_{mcp_resource.name}")
62
+
63
+ if tool_name in self.disallowed_tools:
64
+ return None
65
+
66
+ self.tool_executors[tool_name] = (
67
+ lambda connector=connector, uri=mcp_resource.uri, **kwargs: connector.read_resource(uri)
68
+ )
69
+
70
+ function_declaration = types.FunctionDeclaration(
71
+ name=tool_name,
72
+ description=mcp_resource.description,
73
+ parameters_json_schema={"input_schema": {"type": "object", "properties": {}}},
74
+ )
75
+ return function_declaration
76
+
77
+ def _convert_prompt(self, mcp_prompt: Prompt, connector: BaseConnector) -> types.FunctionDeclaration:
78
+ """Convert an MCP prompt to a usable tool in Google format."""
79
+ if mcp_prompt.name in self.disallowed_tools:
80
+ return None
81
+
82
+ self.tool_executors[mcp_prompt.name] = (
83
+ lambda connector=connector, name=mcp_prompt.name, **kwargs: connector.get_prompt(name, kwargs)
84
+ )
85
+
86
+ properties = {}
87
+ required_args = []
88
+ if mcp_prompt.arguments:
89
+ for arg in mcp_prompt.arguments:
90
+ prop = {"type": "string"}
91
+ if arg.description:
92
+ prop["description"] = arg.description
93
+ properties[arg.name] = prop
94
+ if arg.required:
95
+ required_args.append(arg.name)
96
+ parameters_schema = {"type": "object", "properties": properties}
97
+ if required_args:
98
+ parameters_schema["required"] = required_args
99
+
100
+ function_declaration = types.FunctionDeclaration(
101
+ name=mcp_prompt.name, description=mcp_prompt.description, parameters_json_schema=parameters_schema
102
+ )
103
+ return function_declaration
@@ -0,0 +1,212 @@
1
+ """
2
+ LangChain adapter for MCP tools.
3
+
4
+ This module provides utilities to convert MCP tools to LangChain tools.
5
+ """
6
+
7
+ import re
8
+ from typing import Any, NoReturn
9
+
10
+ from jsonschema_pydantic import jsonschema_to_pydantic
11
+ from langchain_core.tools import BaseTool
12
+ from mcp.types import (
13
+ CallToolResult,
14
+ Prompt,
15
+ ReadResourceRequestParams,
16
+ Resource,
17
+ )
18
+ from pydantic import BaseModel, Field, create_model
19
+
20
+ from mcp_use.agents.adapters.base import BaseAdapter
21
+ from mcp_use.client.connectors.base import BaseConnector
22
+ from mcp_use.errors.error_formatting import format_error
23
+ from mcp_use.logging import logger
24
+ from mcp_use.telemetry.telemetry import telemetry
25
+
26
+
27
+ class LangChainAdapter(BaseAdapter):
28
+ """Adapter for converting MCP tools to LangChain tools."""
29
+
30
+ def __init__(self, disallowed_tools: list[str] | None = None) -> None:
31
+ """Initialize a new LangChain adapter.
32
+
33
+ Args:
34
+ disallowed_tools: list of tool names that should not be available.
35
+ """
36
+ super().__init__(disallowed_tools)
37
+ self._connector_tool_map: dict[BaseConnector, list[BaseTool]] = {}
38
+ self._connector_resource_map: dict[BaseConnector, list[BaseTool]] = {}
39
+ self._connector_prompt_map: dict[BaseConnector, list[BaseTool]] = {}
40
+
41
+ self.tools: list[BaseTool] = []
42
+ self.resources: list[BaseTool] = []
43
+ self.prompts: list[BaseTool] = []
44
+
45
+ @telemetry("adapter_convert_tool")
46
+ def _convert_tool(self, mcp_tool: dict[str, Any], connector: BaseConnector) -> BaseTool:
47
+ """Convert an MCP tool to LangChain's tool format.
48
+
49
+ Args:
50
+ mcp_tool: The MCP tool to convert.
51
+ connector: The connector that provides this tool.
52
+
53
+ Returns:
54
+ A LangChain BaseTool.
55
+ """
56
+ # Skip disallowed tools
57
+ if mcp_tool.name in self.disallowed_tools:
58
+ return None
59
+
60
+ # This is a dynamic class creation, we need to work with the self reference
61
+ adapter_self = self
62
+
63
+ class McpToLangChainAdapter(BaseTool):
64
+ name: str = mcp_tool.name or "NO NAME"
65
+ description: str = mcp_tool.description or ""
66
+ # Convert JSON schema to Pydantic model for argument validation
67
+ args_schema: type[BaseModel] = jsonschema_to_pydantic(
68
+ adapter_self.fix_schema(mcp_tool.inputSchema) # Apply schema conversion
69
+ )
70
+ tool_connector: BaseConnector = connector # Renamed variable to avoid name conflict
71
+ handle_tool_error: bool = True
72
+
73
+ def __repr__(self) -> str:
74
+ return f"MCP tool: {self.name}: {self.description}"
75
+
76
+ def _run(self, **kwargs: Any) -> NoReturn:
77
+ """Synchronous run method that always raises an error.
78
+
79
+ Raises:
80
+ NotImplementedError: Always raises this error because MCP tools
81
+ only support async operations.
82
+ """
83
+ raise NotImplementedError("MCP tools only support async operations")
84
+
85
+ async def _arun(self, **kwargs: Any) -> str | dict:
86
+ """Asynchronously execute the tool with given arguments.
87
+
88
+ Args:
89
+ kwargs: The arguments to pass to the tool.
90
+
91
+ Returns:
92
+ The result of the tool execution.
93
+
94
+ Raises:
95
+ ToolException: If tool execution fails.
96
+ """
97
+ logger.debug(f'MCP tool: "{self.name}" received input: {kwargs}')
98
+
99
+ try:
100
+ tool_result: CallToolResult = await self.tool_connector.call_tool(self.name, kwargs)
101
+ try:
102
+ # Use the helper function to parse the result
103
+ return str(tool_result.content)
104
+ except Exception as e:
105
+ # Log the exception for debugging
106
+ logger.error(f"Error parsing tool result: {e}")
107
+ return format_error(e, tool=self.name, tool_content=tool_result.content)
108
+
109
+ except Exception as e:
110
+ if self.handle_tool_error:
111
+ return format_error(e, tool=self.name) # Format the error to make LLM understand it
112
+ raise
113
+
114
+ return McpToLangChainAdapter()
115
+
116
+ def _convert_resource(self, mcp_resource: Resource, connector: BaseConnector) -> BaseTool:
117
+ """Convert an MCP resource to LangChain's tool format.
118
+
119
+ Each resource becomes an async tool that returns its content when called.
120
+ The tool takes **no** arguments because the resource URI is fixed.
121
+ """
122
+
123
+ def _sanitize(name: str) -> str:
124
+ return re.sub(r"[^A-Za-z0-9_]+", "_", name).lower().strip("_")
125
+
126
+ class ResourceTool(BaseTool):
127
+ name: str = _sanitize(mcp_resource.name or f"resource_{mcp_resource.uri}")
128
+ description: str = (
129
+ mcp_resource.description or f"Return the content of the resource located at URI {mcp_resource.uri}."
130
+ )
131
+ args_schema: type[BaseModel] = ReadResourceRequestParams
132
+ tool_connector: BaseConnector = connector
133
+ handle_tool_error: bool = True
134
+
135
+ def _run(self, **kwargs: Any) -> NoReturn:
136
+ raise NotImplementedError("Resource tools only support async operations")
137
+
138
+ async def _arun(self, **kwargs: Any) -> Any:
139
+ logger.debug(f'Resource tool: "{self.name}" called')
140
+ try:
141
+ result = await self.tool_connector.read_resource(mcp_resource.uri)
142
+ for content in result.contents:
143
+ # Attempt to decode bytes if necessary
144
+ if isinstance(content, bytes):
145
+ content_decoded = content.decode()
146
+ else:
147
+ content_decoded = str(content)
148
+
149
+ return content_decoded
150
+ except Exception as e:
151
+ if self.handle_tool_error:
152
+ return format_error(e, tool=self.name) # Format the error to make LLM understand it
153
+ raise
154
+
155
+ return ResourceTool()
156
+
157
+ def _convert_prompt(self, mcp_prompt: Prompt, connector: BaseConnector) -> BaseTool:
158
+ """Convert an MCP prompt to LangChain's tool format.
159
+
160
+ The resulting tool executes `get_prompt` on the connector with the prompt's name and
161
+ the user-provided arguments (if any). The tool returns the decoded prompt content.
162
+ """
163
+ prompt_arguments = mcp_prompt.arguments
164
+
165
+ # Sanitize the prompt name to create a valid Python identifier for the model name
166
+ base_model_name = re.sub(r"[^a-zA-Z0-9_]", "_", mcp_prompt.name)
167
+ if not base_model_name or base_model_name[0].isdigit():
168
+ base_model_name = "PromptArgs_" + base_model_name
169
+ dynamic_model_name = f"{base_model_name}_InputSchema"
170
+
171
+ if prompt_arguments:
172
+ field_definitions_for_create: dict[str, Any] = {}
173
+ for arg in prompt_arguments:
174
+ param_type: type = getattr(arg, "type", str)
175
+ if arg.required:
176
+ field_definitions_for_create[arg.name] = (
177
+ param_type,
178
+ Field(description=arg.description),
179
+ )
180
+ else:
181
+ field_definitions_for_create[arg.name] = (
182
+ param_type | None,
183
+ Field(None, description=arg.description),
184
+ )
185
+
186
+ InputSchema = create_model(dynamic_model_name, **field_definitions_for_create, __base__=BaseModel)
187
+ else:
188
+ # Create an empty Pydantic model if there are no arguments
189
+ InputSchema = create_model(dynamic_model_name, __base__=BaseModel)
190
+
191
+ class PromptTool(BaseTool):
192
+ name: str = mcp_prompt.name
193
+ description: str = mcp_prompt.description
194
+
195
+ args_schema: type[BaseModel] = InputSchema
196
+ tool_connector: BaseConnector = connector
197
+ handle_tool_error: bool = True
198
+
199
+ def _run(self, **kwargs: Any) -> NoReturn:
200
+ raise NotImplementedError("Prompt tools only support async operations")
201
+
202
+ async def _arun(self, **kwargs: Any) -> Any:
203
+ logger.debug(f'Prompt tool: "{self.name}" called with args: {kwargs}')
204
+ try:
205
+ result = await self.tool_connector.get_prompt(self.name, kwargs)
206
+ return result.messages
207
+ except Exception as e:
208
+ if self.handle_tool_error:
209
+ return format_error(e, tool=self.name) # Format the error to make LLM understand it
210
+ raise
211
+
212
+ return PromptTool()