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.

@@ -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
- __all__ = ["BaseAdapter", "LangChainAdapter"]
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
+ }
@@ -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
- @telemetry("adapter_create_tools")
38
- async def create_tools(self, client: "MCPClient") -> list[T]:
39
- """Create tools from an MCPClient instance.
41
+ self.tools: list[T] = []
42
+ self.resources: list[T] = []
43
+ self.prompts: list[T] = []
40
44
 
41
- This is the recommended way to create tools from an MCPClient, as it handles
42
- session creation and connector extraction automatically.
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
- client: The MCPClient to extract tools from.
49
+ tool_result: The result object from an MCP operation.
46
50
 
47
51
  Returns:
48
- A list of tools in the target framework's format.
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
- Example:
51
- ```python
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
- client = MCPClient.from_config_file("config.json")
56
- tools = await YourAdapter.create_tools(client)
57
- ```
75
+ Returns:
76
+ The fixed JSON schema.
58
77
  """
59
- # Ensure we have active sessions
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
- # Extract connectors from sessions
68
- connectors = [session.connector for session in sessions.values()]
106
+ async def create_tools(self, client: MCPClient) -> list[T]:
107
+ """Create tools from the MCPClient instance.
69
108
 
70
- # Create tools from connectors
71
- return await self._create_tools_from_connectors(connectors)
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
- # Create tools for this connector
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
- @telemetry("adapter_fix_schema")
40
- def fix_schema(self, schema: dict) -> dict:
41
- """Convert JSON Schema 'type': ['string', 'null'] to 'anyOf' format and fix enum handling.
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
+ }