mcp-use 1.3.13__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.
- mcp_use/agents/adapters/__init__.py +8 -1
- mcp_use/agents/adapters/anthropic.py +93 -0
- mcp_use/agents/adapters/base.py +178 -55
- mcp_use/agents/adapters/google.py +103 -0
- mcp_use/agents/adapters/langchain_adapter.py +6 -22
- mcp_use/agents/adapters/openai.py +111 -0
- mcp_use/agents/mcpagent.py +371 -472
- mcp_use/agents/prompts/system_prompt_builder.py +1 -1
- mcp_use/agents/remote.py +1 -1
- mcp_use/client/auth/oauth_callback.py +3 -2
- mcp_use/logging.py +1 -1
- mcp_use/telemetry/utils.py +1 -1
- {mcp_use-1.3.13.dist-info → mcp_use-1.4.0.dist-info}/METADATA +18 -18
- {mcp_use-1.3.13.dist-info → mcp_use-1.4.0.dist-info}/RECORD +16 -14
- mcp_use/cli.py +0 -581
- {mcp_use-1.3.13.dist-info → mcp_use-1.4.0.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.13.dist-info → mcp_use-1.4.0.dist-info}/entry_points.txt +0 -0
|
@@ -4,7 +4,14 @@ Adapters for converting MCP tools to different frameworks.
|
|
|
4
4
|
This package provides adapters for converting MCP tools to different frameworks.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from .anthropic import AnthropicMCPAdapter
|
|
7
8
|
from .base import BaseAdapter
|
|
8
9
|
from .langchain_adapter import LangChainAdapter
|
|
10
|
+
from .openai import OpenAIMCPAdapter
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
try:
|
|
13
|
+
from .google import GoogleMCPAdapter
|
|
14
|
+
|
|
15
|
+
__all__ = ["BaseAdapter", "LangChainAdapter", "OpenAIMCPAdapter", "AnthropicMCPAdapter", "GoogleMCPAdapter"]
|
|
16
|
+
except ImportError:
|
|
17
|
+
__all__ = ["BaseAdapter", "LangChainAdapter", "OpenAIMCPAdapter", "AnthropicMCPAdapter"]
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
|
|
11
|
+
def _sanitize_for_tool_name(name: str) -> str:
|
|
12
|
+
"""Sanitizes a string to be a valid tool name for Anthropic."""
|
|
13
|
+
# Anthropic tool names can only contain a-z, A-Z, 0-9, and underscores,
|
|
14
|
+
# and must be 64 characters or less.
|
|
15
|
+
return re.sub(r"[^a-zA-Z0-9_]+", "_", name).strip("_")[:64]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AnthropicMCPAdapter(BaseAdapter):
|
|
19
|
+
def __init__(self, disallowed_tools: list[str] | None = None) -> None:
|
|
20
|
+
"""Initialize a new Anthropic adapter.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
disallowed_tools: list of tool names that should not be available.
|
|
24
|
+
"""
|
|
25
|
+
super().__init__(disallowed_tools)
|
|
26
|
+
# This map stores the actual async function to call for each tool.
|
|
27
|
+
self.tool_executors: dict[str, Callable[..., Coroutine[Any, Any, Any]]] = {}
|
|
28
|
+
|
|
29
|
+
self._connector_tool_map: dict[BaseConnector, list[dict[str, Any]]] = {}
|
|
30
|
+
self._connector_resource_map: dict[BaseConnector, list[dict[str, Any]]] = {}
|
|
31
|
+
self._connector_prompt_map: dict[BaseConnector, list[dict[str, Any]]] = {}
|
|
32
|
+
|
|
33
|
+
self.tools: list[dict[str, Any]] = []
|
|
34
|
+
self.resources: list[dict[str, Any]] = []
|
|
35
|
+
self.prompts: list[dict[str, Any]] = []
|
|
36
|
+
|
|
37
|
+
def _convert_tool(self, mcp_tool: Tool, connector: BaseConnector) -> dict[str, Any]:
|
|
38
|
+
"""Convert an MCP tool to the Anthropic tool format."""
|
|
39
|
+
if mcp_tool.name in self.disallowed_tools:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
self.tool_executors[mcp_tool.name] = (
|
|
43
|
+
lambda connector=connector, name=mcp_tool.name, **kwargs: connector.call_tool(name, kwargs)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
fixed_schema = self.fix_schema(mcp_tool.inputSchema)
|
|
47
|
+
return {"name": mcp_tool.name, "description": mcp_tool.description, "input_schema": fixed_schema}
|
|
48
|
+
|
|
49
|
+
def _convert_resource(self, mcp_resource: Resource, connector: BaseConnector) -> dict[str, Any]:
|
|
50
|
+
"""Convert an MCP resource to a readable tool in Anthropic format."""
|
|
51
|
+
tool_name = _sanitize_for_tool_name(f"resource_{mcp_resource.name}")
|
|
52
|
+
|
|
53
|
+
if tool_name in self.disallowed_tools:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
self.tool_executors[tool_name] = (
|
|
57
|
+
lambda connector=connector, uri=mcp_resource.uri, **kwargs: connector.read_resource(uri)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
"name": tool_name,
|
|
62
|
+
"description": mcp_resource.description,
|
|
63
|
+
"input_schema": {"type": "object", "properties": {}},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
def _convert_prompt(self, mcp_prompt: Prompt, connector: BaseConnector) -> dict[str, Any]:
|
|
67
|
+
"""Convert an MCP prompt to a usable tool in Anthropic format."""
|
|
68
|
+
if mcp_prompt.name in self.disallowed_tools:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
self.tool_executors[mcp_prompt.name] = (
|
|
72
|
+
lambda connector=connector, name=mcp_prompt.name, **kwargs: connector.get_prompt(name, kwargs)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
properties = {}
|
|
76
|
+
required_args = []
|
|
77
|
+
if mcp_prompt.arguments:
|
|
78
|
+
for arg in mcp_prompt.arguments:
|
|
79
|
+
prop = {"type": "string"}
|
|
80
|
+
if arg.description:
|
|
81
|
+
prop["description"] = arg.description
|
|
82
|
+
properties[arg.name] = prop
|
|
83
|
+
if arg.required:
|
|
84
|
+
required_args.append(arg.name)
|
|
85
|
+
parameters_schema = {"type": "object", "properties": properties}
|
|
86
|
+
if required_args:
|
|
87
|
+
parameters_schema["required"] = required_args
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
"name": mcp_prompt.name,
|
|
91
|
+
"description": mcp_prompt.description,
|
|
92
|
+
"input_schema": parameters_schema,
|
|
93
|
+
}
|
mcp_use/agents/adapters/base.py
CHANGED
|
@@ -5,7 +5,7 @@ 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 TypeVar
|
|
8
|
+
from typing import Any, TypeVar
|
|
9
9
|
|
|
10
10
|
from mcp.types import Prompt, Resource, Tool
|
|
11
11
|
|
|
@@ -32,43 +32,118 @@ class BaseAdapter(ABC):
|
|
|
32
32
|
disallowed_tools: list of tool names that should not be available.
|
|
33
33
|
"""
|
|
34
34
|
self.disallowed_tools = disallowed_tools or []
|
|
35
|
+
|
|
36
|
+
# Three maps to optimize and cache only what is needed
|
|
35
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]] = {}
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
self.tools: list[T] = []
|
|
42
|
+
self.resources: list[T] = []
|
|
43
|
+
self.prompts: list[T] = []
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
def parse_result(self, tool_result: Any) -> str:
|
|
46
|
+
"""Parse the result from any MCP operation (tool, resource, or prompt) into a string.
|
|
43
47
|
|
|
44
48
|
Args:
|
|
45
|
-
|
|
49
|
+
tool_result: The result object from an MCP operation.
|
|
46
50
|
|
|
47
51
|
Returns:
|
|
48
|
-
A
|
|
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.
|
|
49
71
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
from mcp_use.client import MCPClient
|
|
53
|
-
from mcp_use.adapters import YourAdapter
|
|
72
|
+
Args:
|
|
73
|
+
schema: The JSON schema to fix.
|
|
54
74
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```
|
|
75
|
+
Returns:
|
|
76
|
+
The fixed JSON schema.
|
|
58
77
|
"""
|
|
59
|
-
|
|
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."""
|
|
60
93
|
if not client.active_sessions:
|
|
61
94
|
logger.info("No active sessions found, creating new ones...")
|
|
62
95
|
await client.create_all_sessions()
|
|
63
96
|
|
|
64
|
-
# Get all active sessions
|
|
65
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)
|
|
66
105
|
|
|
67
|
-
|
|
68
|
-
|
|
106
|
+
async def create_tools(self, client: MCPClient) -> list[T]:
|
|
107
|
+
"""Create tools from the MCPClient instance.
|
|
69
108
|
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
72
147
|
|
|
73
148
|
@telemetry("adapter_load_tools")
|
|
74
149
|
async def load_tools_for_connector(self, connector: BaseConnector) -> list[T]:
|
|
@@ -85,38 +160,15 @@ class BaseAdapter(ABC):
|
|
|
85
160
|
logger.debug(f"Returning {len(self._connector_tool_map[connector])} existing tools for connector")
|
|
86
161
|
return self._connector_tool_map[connector]
|
|
87
162
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# Make sure the connector is initialized and has tools
|
|
91
|
-
success = await self._ensure_connector_initialized(connector)
|
|
92
|
-
if not success:
|
|
163
|
+
if not await self._ensure_connector_initialized(connector):
|
|
93
164
|
return []
|
|
94
165
|
|
|
95
166
|
connector_tools = []
|
|
96
|
-
# Now create tools for each MCP tool
|
|
97
167
|
for tool in await connector.list_tools():
|
|
98
|
-
# Convert the tool and add it to the list if conversion was successful
|
|
99
168
|
converted_tool = self._convert_tool(tool, connector)
|
|
100
169
|
if converted_tool:
|
|
101
170
|
connector_tools.append(converted_tool)
|
|
102
171
|
|
|
103
|
-
# Convert resources to tools so that agents can access resource content directly
|
|
104
|
-
resources_list = await connector.list_resources() or []
|
|
105
|
-
if resources_list:
|
|
106
|
-
for resource in resources_list:
|
|
107
|
-
converted_resource = self._convert_resource(resource, connector)
|
|
108
|
-
if converted_resource:
|
|
109
|
-
connector_tools.append(converted_resource)
|
|
110
|
-
|
|
111
|
-
# Convert prompts to tools so that agents can retrieve prompt content
|
|
112
|
-
prompts_list = await connector.list_prompts() or []
|
|
113
|
-
if prompts_list:
|
|
114
|
-
for prompt in prompts_list:
|
|
115
|
-
converted_prompt = self._convert_prompt(prompt, connector)
|
|
116
|
-
if converted_prompt:
|
|
117
|
-
connector_tools.append(converted_prompt)
|
|
118
|
-
# ------------------------------
|
|
119
|
-
|
|
120
172
|
# Store the tools for this connector
|
|
121
173
|
self._connector_tool_map[connector] = connector_tools
|
|
122
174
|
|
|
@@ -128,6 +180,66 @@ class BaseAdapter(ABC):
|
|
|
128
180
|
|
|
129
181
|
return connector_tools
|
|
130
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
|
+
|
|
131
243
|
@abstractmethod
|
|
132
244
|
def _convert_tool(self, mcp_tool: Tool, connector: BaseConnector) -> T:
|
|
133
245
|
"""Convert an MCP tool to the target framework's tool format."""
|
|
@@ -144,24 +256,35 @@ class BaseAdapter(ABC):
|
|
|
144
256
|
pass
|
|
145
257
|
|
|
146
258
|
async def _create_tools_from_connectors(self, connectors: list[BaseConnector]) -> list[T]:
|
|
147
|
-
"""Create tools from MCP tools in all provided connectors.
|
|
148
|
-
|
|
149
|
-
Args:
|
|
150
|
-
connectors: list of MCP connectors to create tools from.
|
|
151
|
-
|
|
152
|
-
Returns:
|
|
153
|
-
A list of tools in the target framework's format.
|
|
154
|
-
"""
|
|
259
|
+
"""Create tools from MCP tools in all provided connectors."""
|
|
155
260
|
tools = []
|
|
156
261
|
for connector in connectors:
|
|
157
|
-
# Create tools for this connector
|
|
158
262
|
connector_tools = await self.load_tools_for_connector(connector)
|
|
159
263
|
tools.extend(connector_tools)
|
|
160
264
|
|
|
161
|
-
# Log available tools for debugging
|
|
162
265
|
logger.debug(f"Available tools: {len(tools)}")
|
|
163
266
|
return tools
|
|
164
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
|
+
|
|
165
288
|
def _check_connector_initialized(self, connector: BaseConnector) -> bool:
|
|
166
289
|
"""Check if a connector is initialized and has tools.
|
|
167
290
|
|
|
@@ -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
|
|
@@ -35,29 +35,12 @@ class LangChainAdapter(BaseAdapter):
|
|
|
35
35
|
"""
|
|
36
36
|
super().__init__(disallowed_tools)
|
|
37
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]] = {}
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
schema: The JSON schema to fix.
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
The fixed JSON schema.
|
|
48
|
-
"""
|
|
49
|
-
if isinstance(schema, dict):
|
|
50
|
-
if "type" in schema and isinstance(schema["type"], list):
|
|
51
|
-
schema["anyOf"] = [{"type": t} for t in schema["type"]]
|
|
52
|
-
del schema["type"] # Remove 'type' and standardize to 'anyOf'
|
|
53
|
-
|
|
54
|
-
# Fix enum handling - ensure enum fields are properly typed as strings
|
|
55
|
-
if "enum" in schema and "type" not in schema:
|
|
56
|
-
schema["type"] = "string"
|
|
57
|
-
|
|
58
|
-
for key, value in schema.items():
|
|
59
|
-
schema[key] = self.fix_schema(value) # Apply recursively
|
|
60
|
-
return schema
|
|
41
|
+
self.tools: list[BaseTool] = []
|
|
42
|
+
self.resources: list[BaseTool] = []
|
|
43
|
+
self.prompts: list[BaseTool] = []
|
|
61
44
|
|
|
62
45
|
@telemetry("adapter_convert_tool")
|
|
63
46
|
def _convert_tool(self, mcp_tool: dict[str, Any], connector: BaseConnector) -> BaseTool:
|
|
@@ -116,6 +99,7 @@ class LangChainAdapter(BaseAdapter):
|
|
|
116
99
|
try:
|
|
117
100
|
tool_result: CallToolResult = await self.tool_connector.call_tool(self.name, kwargs)
|
|
118
101
|
try:
|
|
102
|
+
# Use the helper function to parse the result
|
|
119
103
|
return str(tool_result.content)
|
|
120
104
|
except Exception as e:
|
|
121
105
|
# Log the exception for debugging
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
|
|
11
|
+
def _sanitize_for_tool_name(name: str) -> str:
|
|
12
|
+
"""Sanitizes a string to be a valid tool name for OpenAI."""
|
|
13
|
+
# OpenAI tool names can only contain a-z, A-Z, 0-9, and underscores,
|
|
14
|
+
# and must be 64 characters or less.
|
|
15
|
+
return re.sub(r"[^a-zA-Z0-9_]+", "_", name).strip("_")[:64]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class OpenAIMCPAdapter(BaseAdapter):
|
|
19
|
+
def __init__(self, disallowed_tools: list[str] | None = None) -> None:
|
|
20
|
+
"""Initialize a new OpenAI adapter.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
disallowed_tools: list of tool names that should not be available.
|
|
24
|
+
"""
|
|
25
|
+
super().__init__(disallowed_tools)
|
|
26
|
+
# This map stores the actual async function to call for each tool.
|
|
27
|
+
self.tool_executors: dict[str, Callable[..., Coroutine[Any, Any, Any]]] = {}
|
|
28
|
+
|
|
29
|
+
self._connector_tool_map: dict[BaseConnector, list[dict[str, Any]]] = {}
|
|
30
|
+
self._connector_resource_map: dict[BaseConnector, list[dict[str, Any]]] = {}
|
|
31
|
+
self._connector_prompt_map: dict[BaseConnector, list[dict[str, Any]]] = {}
|
|
32
|
+
|
|
33
|
+
self.tools: list[dict[str, Any]] = []
|
|
34
|
+
self.resources: list[dict[str, Any]] = []
|
|
35
|
+
self.prompts: list[dict[str, Any]] = []
|
|
36
|
+
|
|
37
|
+
def _convert_tool(self, mcp_tool: Tool, connector: BaseConnector) -> dict[str, Any]:
|
|
38
|
+
"""Convert an MCP tool to the OpenAI tool format."""
|
|
39
|
+
if mcp_tool.name in self.disallowed_tools:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
self.tool_executors[mcp_tool.name] = (
|
|
43
|
+
lambda connector=connector, name=mcp_tool.name, **kwargs: connector.call_tool(name, kwargs)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
fixed_schema = self.fix_schema(mcp_tool.inputSchema)
|
|
47
|
+
return {
|
|
48
|
+
"type": "function",
|
|
49
|
+
"function": {
|
|
50
|
+
"name": mcp_tool.name,
|
|
51
|
+
"description": mcp_tool.description,
|
|
52
|
+
"parameters": fixed_schema,
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
def _convert_resource(self, mcp_resource: Resource, connector: BaseConnector) -> dict[str, Any]:
|
|
57
|
+
"""Convert an MCP resource to a readable tool in OpenAI format."""
|
|
58
|
+
# Sanitize the name to be a valid function name for OpenAI
|
|
59
|
+
tool_name = _sanitize_for_tool_name(f"resource_{mcp_resource.name}")
|
|
60
|
+
|
|
61
|
+
if tool_name in self.disallowed_tools:
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
self.tool_executors[tool_name] = (
|
|
65
|
+
lambda connector=connector, uri=mcp_resource.uri, **kwargs: connector.read_resource(uri)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
mcp_resource_desc = mcp_resource.description
|
|
69
|
+
return {
|
|
70
|
+
"type": "function",
|
|
71
|
+
"function": {
|
|
72
|
+
"name": tool_name,
|
|
73
|
+
"description": mcp_resource_desc,
|
|
74
|
+
# They take no arguments
|
|
75
|
+
"parameters": {"type": "object", "properties": {}},
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def _convert_prompt(self, mcp_prompt: Prompt, connector: BaseConnector) -> dict[str, Any]:
|
|
80
|
+
"""Convert an MCP prompt to a usable tool in OpenAI format."""
|
|
81
|
+
if mcp_prompt.name in self.disallowed_tools:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
self.tool_executors[mcp_prompt.name] = (
|
|
85
|
+
lambda connector=connector, name=mcp_prompt.name, **kwargs: connector.get_prompt(name, kwargs)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Preparing JSON schema for prompt arguments
|
|
89
|
+
properties = {}
|
|
90
|
+
required_args = []
|
|
91
|
+
if mcp_prompt.arguments:
|
|
92
|
+
for arg in mcp_prompt.arguments:
|
|
93
|
+
prop = {"type": "string"}
|
|
94
|
+
if arg.description:
|
|
95
|
+
prop["description"] = arg.description
|
|
96
|
+
properties[arg.name] = prop
|
|
97
|
+
if arg.required:
|
|
98
|
+
required_args.append(arg.name)
|
|
99
|
+
|
|
100
|
+
parameters_schema = {"type": "object", "properties": properties}
|
|
101
|
+
if required_args:
|
|
102
|
+
parameters_schema["required"] = required_args
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
"type": "function",
|
|
106
|
+
"function": {
|
|
107
|
+
"name": mcp_prompt.name,
|
|
108
|
+
"description": mcp_prompt.description,
|
|
109
|
+
"parameters": parameters_schema,
|
|
110
|
+
},
|
|
111
|
+
}
|