Skip to content

Commit a09a756

Browse files
CopilotOEvortex
andcommitted
Fix client.call() to work with configured tools and provide helpful error messages
Co-authored-by: OEvortex <158988478+OEvortex@users.noreply.github.com>
1 parent d81a50b commit a09a756

File tree

1 file changed

+113
-27
lines changed

1 file changed

+113
-27
lines changed

HelpingAI/client.py

Lines changed: 113 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,10 @@ def _convert_tools_parameter(
509509
if tools is None:
510510
return None
511511

512+
# Cache the tools configuration for direct calling
513+
self._tools_config = tools
514+
self._mcp_manager = None # Clear cached MCP manager
515+
512516
try:
513517
from .tools.compatibility import ensure_openai_format
514518
return ensure_openai_format(tools)
@@ -883,6 +887,33 @@ def __init__(
883887
super().__init__(api_key, organization, base_url, timeout)
884888
self.chat: Chat = Chat(self)
885889
self.models: Models = Models(self)
890+
self._tools_config: Optional[Union[List[Dict[str, Any]], List, str]] = None
891+
self._mcp_manager = None
892+
893+
def configure_tools(self, tools: Optional[Union[List[Dict[str, Any]], List, str]]) -> None:
894+
"""Configure tools for this client instance.
895+
896+
This makes tools available for direct calling via client.call().
897+
898+
Args:
899+
tools: Tools configuration in any supported format:
900+
- List containing MCP server configs, built-in tool names, OpenAI format tools
901+
- String identifier for built-in tools
902+
- None to clear tools configuration
903+
904+
Example:
905+
client.configure_tools([
906+
{'mcpServers': {
907+
'time': {'command': 'uvx', 'args': ['mcp-server-time']},
908+
'fetch': {'command': 'uvx', 'args': ['mcp-server-fetch']}
909+
}},
910+
'code_interpreter',
911+
'web_search'
912+
])
913+
"""
914+
self._tools_config = tools
915+
# Clear cached MCP manager to force reinitialization
916+
self._mcp_manager = None
886917

887918
def call(self, tool_name: str, arguments: Union[Dict[str, Any], str, set]) -> Any:
888919
"""
@@ -933,36 +964,91 @@ def call(self, tool_name: str, arguments: Union[Dict[str, Any], str, set]) -> An
933964
result = fn_tool.call(processed_args)
934965
return result
935966

936-
# If not found, check if it's an MCP tool
967+
# If not found, check if it's an MCP tool using cached configuration
937968
# MCP tools are named with pattern: {server_name}-{tool_name}
969+
if self._tools_config:
970+
try:
971+
# Initialize MCP manager with cached configuration if needed
972+
if not self._mcp_manager:
973+
self._mcp_manager = self._get_mcp_manager_for_tools()
974+
975+
if self._mcp_manager and self._mcp_manager.clients:
976+
# Check if any MCP client has this tool
977+
for client_id, client in self._mcp_manager.clients.items():
978+
if hasattr(client, 'tools'):
979+
for mcp_tool in client.tools:
980+
# Extract server name from client_id (format: {server_name}_{uuid})
981+
server_name = client_id.split('_')[0]
982+
expected_tool_name = f"{server_name}-{mcp_tool.name}"
983+
984+
if expected_tool_name == tool_name:
985+
# Found the MCP tool, create an Fn and call it
986+
fn_tool = self._mcp_manager._create_mcp_tool_fn(
987+
name=tool_name,
988+
client_id=client_id,
989+
mcp_tool_name=mcp_tool.name,
990+
description=mcp_tool.description if hasattr(mcp_tool, 'description') else f"MCP tool: {tool_name}",
991+
parameters=mcp_tool.inputSchema if hasattr(mcp_tool, 'inputSchema') else {'type': 'object', 'properties': {}, 'required': []}
992+
)
993+
result = fn_tool.call(processed_args)
994+
return result
995+
except ImportError:
996+
# MCP package not available, skip MCP tool checking
997+
pass
998+
999+
# If still not found, provide a helpful error message
1000+
error_msg = f"Tool '{tool_name}' not found"
1001+
1002+
# Check if this looks like an MCP tool name pattern
1003+
if '-' in tool_name and self._tools_config:
1004+
error_msg += f". Tool '{tool_name}' appears to be an MCP tool but MCP servers may not be properly initialized. Check that the MCP server is running and accessible."
1005+
elif '-' in tool_name and not self._tools_config:
1006+
error_msg += f". Tool '{tool_name}' appears to be an MCP tool but no tools are configured. Use client.configure_tools() to set up MCP servers."
1007+
elif not self._tools_config:
1008+
error_msg += ". No tools configured - use client.configure_tools() or pass tools to chat.completions.create()"
1009+
else:
1010+
error_msg += " in registry, built-in tools, or configured MCP tools"
1011+
1012+
raise ValueError(error_msg)
1013+
1014+
def _get_mcp_manager_for_tools(self) -> Optional[Any]:
1015+
"""Get or create MCP manager using cached tools configuration.
1016+
1017+
Returns:
1018+
MCPManager instance with tools configured, or None if no MCP config found
1019+
"""
1020+
if not self._tools_config:
1021+
return None
1022+
9381023
try:
939-
mcp_manager = MCPManager()
940-
if mcp_manager.clients:
941-
# Check if any MCP client has this tool
942-
for client_id, client in mcp_manager.clients.items():
943-
if hasattr(client, 'tools'):
944-
for mcp_tool in client.tools:
945-
# Extract server name from client_id (format: {server_name}_{uuid})
946-
server_name = client_id.split('_')[0]
947-
expected_tool_name = f"{server_name}-{mcp_tool.name}"
948-
949-
if expected_tool_name == tool_name:
950-
# Found the MCP tool, create an Fn and call it
951-
fn_tool = mcp_manager._create_mcp_tool_fn(
952-
name=tool_name,
953-
client_id=client_id,
954-
mcp_tool_name=mcp_tool.name,
955-
description=mcp_tool.description if hasattr(mcp_tool, 'description') else f"MCP tool: {tool_name}",
956-
parameters=mcp_tool.inputSchema if hasattr(mcp_tool, 'inputSchema') else {'type': 'object', 'properties': {}, 'required': []}
957-
)
958-
result = fn_tool.call(processed_args)
959-
return result
1024+
from .tools.mcp_manager import MCPManager
1025+
1026+
# Find MCP server configs in the tools configuration
1027+
mcp_configs = []
1028+
if isinstance(self._tools_config, list):
1029+
for item in self._tools_config:
1030+
if isinstance(item, dict) and "mcpServers" in item:
1031+
mcp_configs.append(item)
1032+
1033+
if not mcp_configs:
1034+
return None
1035+
1036+
# Initialize MCP manager with the found configurations
1037+
manager = MCPManager()
1038+
1039+
# Initialize each MCP config (this populates manager.clients)
1040+
for config in mcp_configs:
1041+
try:
1042+
manager.init_config(config) # This returns tools but also populates clients
1043+
except Exception as e:
1044+
# If initialization fails, continue with other configs
1045+
print(f"Warning: Failed to initialize MCP config {config}: {e}")
1046+
continue
1047+
1048+
return manager if manager.clients else None
1049+
9601050
except ImportError:
961-
# MCP package not available, skip MCP tool checking
962-
pass
963-
964-
# If still not found, raise an error
965-
raise ValueError(f"Tool '{tool_name}' not found in registry, built-in tools, or MCP tools")
1051+
return None
9661052

9671053
def _process_arguments(self, arguments: Union[Dict[str, Any], str, set], tool_name: str) -> Dict[str, Any]:
9681054
"""

0 commit comments

Comments
 (0)