diff --git a/backend/agent/run.py b/backend/agent/run.py
index de619748..3c6afb87 100644
--- a/backend/agent/run.py
+++ b/backend/agent/run.py
@@ -158,13 +158,12 @@ async def run_agent(
mcp_info += "You have access to external MCP (Model Context Protocol) server tools through the call_mcp_tool function.\n"
mcp_info += "To use an MCP tool, call it using the standard function calling format:\n"
mcp_info += '\n'
- mcp_info += '\n'
- mcp_info += 'mcp_{server}_{tool}\n'
+ mcp_info += '\n'
+ mcp_info += '{server}_{tool}\n'
mcp_info += '{"argument1": "value1", "argument2": "value2"}\n'
mcp_info += '\n'
mcp_info += '\n'
-
- # List configured MCP servers
+
mcp_info += "\nConfigured MCP servers:\n"
for mcp_config in agent_config['configured_mcps']:
server_name = mcp_config.get('name', 'Unknown')
diff --git a/backend/agent/tools/mcp_tool_wrapper.py b/backend/agent/tools/mcp_tool_wrapper.py
index 5c053a85..b2c02e43 100644
--- a/backend/agent/tools/mcp_tool_wrapper.py
+++ b/backend/agent/tools/mcp_tool_wrapper.py
@@ -2,7 +2,7 @@
MCP Tool Wrapper for AgentPress
This module provides a generic tool wrapper that handles all MCP (Model Context Protocol)
-server tool calls through a single AgentPress tool interface.
+server tool calls through dynamically generated individual function methods.
"""
import json
@@ -14,10 +14,10 @@ from utils.logger import logger
class MCPToolWrapper(Tool):
"""
- A generic tool wrapper that handles all MCP server tool calls.
+ A generic tool wrapper that dynamically creates individual methods for each MCP tool.
- This tool acts as a proxy, forwarding tool calls to the appropriate MCP server
- based on the tool name prefix.
+ This tool creates separate function calls for each MCP tool while routing them all
+ through the same underlying implementation.
"""
def __init__(self, mcp_configs: Optional[List[Dict[str, Any]]] = None):
@@ -31,13 +31,15 @@ class MCPToolWrapper(Tool):
self.mcp_manager = MCPManager()
self.mcp_configs = mcp_configs or []
self._initialized = False
+ self._dynamic_tools = {}
async def _ensure_initialized(self):
- """Ensure MCP connections are initialized."""
+ """Ensure MCP connections are initialized and dynamic tools are created."""
if not self._initialized and self.mcp_configs:
logger.info(f"Initializing MCP connections for {len(self.mcp_configs)} servers")
try:
await self.mcp_manager.connect_all(self.mcp_configs)
+ await self._create_dynamic_tools()
self._initialized = True
except ValueError as e:
if "SMITHERY_API_KEY" in str(e):
@@ -47,55 +49,65 @@ class MCPToolWrapper(Tool):
logger.error("2. Set it as an environment variable: export SMITHERY_API_KEY='your-key-here'")
logger.error("3. Or add it to your .env file: SMITHERY_API_KEY=your-key-here")
raise
+
+ async def _create_dynamic_tools(self):
+ """Create dynamic tool methods for each available MCP tool."""
+ try:
+ available_tools = self.mcp_manager.get_all_tools_openapi()
+
+ for tool_info in available_tools:
+ tool_name = tool_info.get('function', {}).get('name', '')
+ if tool_name:
+ # Create a dynamic method for this tool
+ self._create_dynamic_method(tool_name, tool_info)
+
+ logger.info(f"Created {len(self._dynamic_tools)} dynamic MCP tool methods")
+
+ except Exception as e:
+ logger.error(f"Error creating dynamic MCP tools: {e}")
+
+ def _create_dynamic_method(self, tool_name: str, tool_info: Dict[str, Any]):
+ """Create a dynamic method for a specific MCP tool."""
+
+ async def dynamic_tool_method(arguments: Dict[str, Any]) -> ToolResult:
+ """Dynamically created method for MCP tool."""
+ return await self._execute_mcp_tool(tool_name, arguments)
+
+ # Store the method and its info
+ self._dynamic_tools[tool_name] = {
+ 'method': dynamic_tool_method,
+ 'info': tool_info
+ }
+
+ # Add the method to this instance
+ setattr(self, tool_name.replace('-', '_'), dynamic_tool_method)
+
+ def __getattr__(self, name: str):
+ """Handle calls to dynamically created MCP tool methods."""
+ # Convert method name back to tool name (handle underscore conversion)
+ tool_name = name.replace('_', '-')
+
+ if tool_name in self._dynamic_tools:
+ return self._dynamic_tools[tool_name]['method']
+
+ # If it looks like an MCP tool name, try to find it
+ for existing_tool_name in self._dynamic_tools:
+ if existing_tool_name.replace('-', '_') == name:
+ return self._dynamic_tools[existing_tool_name]['method']
+
+ raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
async def get_available_tools(self) -> List[Dict[str, Any]]:
"""Get all available MCP tools in OpenAPI format."""
await self._ensure_initialized()
return self.mcp_manager.get_all_tools_openapi()
- @openapi_schema({
- "type": "function",
- "function": {
- "name": "call_mcp_tool",
- "description": "Execute a tool from any connected MCP server. This is a generic wrapper that forwards calls to MCP tools. The tool_name should be in the format 'mcp_{server}_{tool}' where {server} is the MCP server's qualified name and {tool} is the specific tool name.",
- "parameters": {
- "type": "object",
- "properties": {
- "tool_name": {
- "type": "string",
- "description": "The full MCP tool name in format 'mcp_{server}_{tool}', e.g., 'mcp_exa_web_search_exa'"
- },
- "arguments": {
- "type": "object",
- "description": "The arguments to pass to the MCP tool, as a JSON object. The required arguments depend on the specific tool being called.",
- "additionalProperties": True
- }
- },
- "required": ["tool_name", "arguments"]
- }
- }
- })
- @xml_schema(
- tag_name="call-mcp-tool",
- mappings=[
- {"param_name": "tool_name", "node_type": "attribute", "path": "."},
- {"param_name": "arguments", "node_type": "content", "path": "."}
- ],
- example='''
-
-
- mcp_exa_web_search_exa
- {"query": "latest developments in AI", "num_results": 10}
-
-
- '''
- )
- async def call_mcp_tool(self, tool_name: str, arguments: Dict[str, Any]) -> ToolResult:
+ async def _execute_mcp_tool(self, tool_name: str, arguments: Dict[str, Any]) -> ToolResult:
"""
- Execute an MCP tool call.
+ Execute an MCP tool call (internal implementation).
Args:
- tool_name: The full MCP tool name (e.g., "mcp_exa_web_search_exa")
+ tool_name: The MCP tool name (e.g., "mcp_exa_web_search_exa")
arguments: The arguments to pass to the tool
Returns:
@@ -107,13 +119,6 @@ class MCPToolWrapper(Tool):
logger.info(f"Executing MCP tool {tool_name} with args: {arguments}")
- # Parse tool name to extract server and tool info
- parts = tool_name.split("_", 2)
- if len(parts) != 3 or parts[0] != "mcp":
- return self.fail_response(f"Invalid MCP tool name format: {tool_name}. Expected format: mcp_{{server}}_{{tool}}")
-
- _, server_name, original_tool_name = parts
-
# Parse arguments if they're provided as a JSON string
if isinstance(arguments, str):
try:
@@ -124,6 +129,11 @@ class MCPToolWrapper(Tool):
# Execute the tool through MCP manager
result = await self.mcp_manager.execute_tool(tool_name, arguments)
+ # Parse tool name to extract server and tool info for metadata
+ parts = tool_name.split("_", 2)
+ server_name = parts[1] if len(parts) > 1 else "unknown"
+ original_tool_name = parts[2] if len(parts) > 2 else tool_name
+
# Enhance the result with metadata for better frontend display
enhanced_result = {
"mcp_metadata": {
@@ -193,6 +203,57 @@ class MCPToolWrapper(Tool):
}
return self.fail_response(json.dumps(error_result, indent=2))
+
+ # Keep the original call_mcp_tool method as a fallback
+ @openapi_schema({
+ "type": "function",
+ "function": {
+ "name": "call_mcp_tool",
+ "description": "Execute a tool from any connected MCP server. This is a fallback wrapper that forwards calls to MCP tools. The tool_name should be in the format 'mcp_{server}_{tool}' where {server} is the MCP server's qualified name and {tool} is the specific tool name.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "tool_name": {
+ "type": "string",
+ "description": "The full MCP tool name in format 'mcp_{server}_{tool}', e.g., 'mcp_exa_web_search_exa'"
+ },
+ "arguments": {
+ "type": "object",
+ "description": "The arguments to pass to the MCP tool, as a JSON object. The required arguments depend on the specific tool being called.",
+ "additionalProperties": True
+ }
+ },
+ "required": ["tool_name", "arguments"]
+ }
+ }
+ })
+ @xml_schema(
+ tag_name="call-mcp-tool",
+ mappings=[
+ {"param_name": "tool_name", "node_type": "attribute", "path": "."},
+ {"param_name": "arguments", "node_type": "content", "path": "."}
+ ],
+ example='''
+
+
+ mcp_exa_web_search_exa
+ {"query": "latest developments in AI", "num_results": 10}
+
+
+ '''
+ )
+ async def call_mcp_tool(self, tool_name: str, arguments: Dict[str, Any]) -> ToolResult:
+ """
+ Execute an MCP tool call (fallback method).
+
+ Args:
+ tool_name: The full MCP tool name (e.g., "mcp_exa_web_search_exa")
+ arguments: The arguments to pass to the tool
+
+ Returns:
+ ToolResult with the tool execution result
+ """
+ return await self._execute_mcp_tool(tool_name, arguments)
async def cleanup(self):
"""Disconnect all MCP servers."""