2025-05-30 21:28:58 +08:00
import json
2025-05-31 23:31:20 +08:00
import httpx
from typing import Optional , Dict , Any , List
2025-05-30 21:28:58 +08:00
from agentpress . tool import Tool , ToolResult , openapi_schema , xml_schema
2025-05-31 23:31:20 +08:00
from agentpress . thread_manager import ThreadManager
2025-05-30 21:28:58 +08:00
class UpdateAgentTool ( Tool ) :
""" Tool for updating agent configuration.
This tool is used by the agent builder to update agent properties
based on user requirements .
"""
2025-05-31 23:31:20 +08:00
def __init__ ( self , thread_manager : ThreadManager , db_connection , agent_id : str ) :
2025-05-30 21:28:58 +08:00
super ( ) . __init__ ( )
2025-05-31 23:31:20 +08:00
self . thread_manager = thread_manager
2025-05-30 21:28:58 +08:00
self . db = db_connection
self . agent_id = agent_id
2025-05-31 23:31:20 +08:00
# Smithery API configuration
self . smithery_api_base_url = " https://registry.smithery.ai "
import os
self . smithery_api_key = os . getenv ( " SMITHERY_API_KEY " )
2025-05-30 21:28:58 +08:00
@openapi_schema ( {
" type " : " function " ,
" function " : {
" name " : " update_agent " ,
" description " : " Update the agent ' s configuration including name, description, system prompt, tools, and MCP servers. Call this whenever the user wants to modify any aspect of the agent. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" name " : {
" type " : " string " ,
" description " : " The name of the agent. Should be descriptive and indicate the agent ' s purpose. "
} ,
" description " : {
" type " : " string " ,
" description " : " A brief description of what the agent does and its capabilities. "
} ,
" system_prompt " : {
" type " : " string " ,
" description " : " The system instructions that define the agent ' s behavior, expertise, and approach. This should be comprehensive and well-structured. "
} ,
" agentpress_tools " : {
" type " : " object " ,
" description " : " Configuration for AgentPress tools. Each key is a tool name, and the value is an object with ' enabled ' (boolean) and ' description ' (string) properties. " ,
" additionalProperties " : {
" type " : " object " ,
" properties " : {
" enabled " : { " type " : " boolean " } ,
" description " : { " type " : " string " }
}
}
} ,
" configured_mcps " : {
" type " : " array " ,
" description " : " List of configured MCP servers for external integrations. " ,
" items " : {
" type " : " object " ,
" properties " : {
" name " : { " type " : " string " } ,
" qualifiedName " : { " type " : " string " } ,
" config " : { " type " : " object " } ,
" enabledTools " : {
" type " : " array " ,
" items " : { " type " : " string " }
}
}
}
} ,
" avatar " : {
" type " : " string " ,
" description " : " Emoji to use as the agent ' s avatar. "
} ,
" avatar_color " : {
" type " : " string " ,
" description " : " Hex color code for the agent ' s avatar background. "
}
} ,
" required " : [ ]
}
}
} )
@xml_schema (
2025-05-31 23:31:20 +08:00
tag_name = " update-agent " ,
2025-05-30 21:28:58 +08:00
mappings = [
{ " param_name " : " name " , " node_type " : " attribute " , " path " : " . " , " required " : False } ,
{ " param_name " : " description " , " node_type " : " element " , " path " : " description " , " required " : False } ,
{ " param_name " : " system_prompt " , " node_type " : " element " , " path " : " system_prompt " , " required " : False } ,
{ " param_name " : " agentpress_tools " , " node_type " : " element " , " path " : " agentpress_tools " , " required " : False } ,
{ " param_name " : " configured_mcps " , " node_type " : " element " , " path " : " configured_mcps " , " required " : False } ,
{ " param_name " : " avatar " , " node_type " : " attribute " , " path " : " . " , " required " : False } ,
{ " param_name " : " avatar_color " , " node_type " : " attribute " , " path " : " . " , " required " : False }
] ,
example = '''
< function_calls >
< invoke name = " update_agent " >
< parameter name = " name " > Research Assistant < / parameter >
< parameter name = " description " > An AI assistant specialized in conducting research and providing comprehensive analysis < / parameter >
< parameter name = " system_prompt " > You are a research assistant with expertise in gathering , analyzing , and synthesizing information . Your approach is thorough and methodical . . . < / parameter >
< parameter name = " agentpress_tools " > { " web_search " : { " enabled " : true , " description " : " Search the web for information " } , " sb_files " : { " enabled " : true , " description " : " Read and write files " } } < / parameter >
< parameter name = " avatar " > 🔬 < / parameter >
< parameter name = " avatar_color " > #4F46E5</parameter>
< / invoke >
< / function_calls >
'''
)
async def update_agent (
self ,
name : Optional [ str ] = None ,
description : Optional [ str ] = None ,
system_prompt : Optional [ str ] = None ,
agentpress_tools : Optional [ Dict [ str , Dict [ str , Any ] ] ] = None ,
configured_mcps : Optional [ list ] = None ,
avatar : Optional [ str ] = None ,
avatar_color : Optional [ str ] = None
) - > ToolResult :
""" Update agent configuration with provided fields.
Args :
name : Agent name
description : Agent description
system_prompt : System instructions for the agent
agentpress_tools : AgentPress tools configuration
configured_mcps : MCP servers configuration
avatar : Emoji avatar
avatar_color : Avatar background color
Returns :
ToolResult with updated agent data or error
"""
try :
client = await self . db . client
update_data = { }
if name is not None :
update_data [ " name " ] = name
if description is not None :
update_data [ " description " ] = description
if system_prompt is not None :
update_data [ " system_prompt " ] = system_prompt
if agentpress_tools is not None :
formatted_tools = { }
for tool_name , tool_config in agentpress_tools . items ( ) :
if isinstance ( tool_config , dict ) :
formatted_tools [ tool_name ] = {
" enabled " : tool_config . get ( " enabled " , False ) ,
" description " : tool_config . get ( " description " , " " )
}
update_data [ " agentpress_tools " ] = formatted_tools
if configured_mcps is not None :
if isinstance ( configured_mcps , str ) :
configured_mcps = json . loads ( configured_mcps )
update_data [ " configured_mcps " ] = configured_mcps
if avatar is not None :
update_data [ " avatar " ] = avatar
if avatar_color is not None :
update_data [ " avatar_color " ] = avatar_color
if not update_data :
return self . fail_response ( " No fields provided to update " )
result = await client . table ( ' agents ' ) . update ( update_data ) . eq ( ' agent_id ' , self . agent_id ) . execute ( )
if not result . data :
return self . fail_response ( " Failed to update agent " )
2025-05-31 23:31:20 +08:00
2025-05-30 21:28:58 +08:00
return self . success_response ( {
" message " : " Agent updated successfully " ,
" updated_fields " : list ( update_data . keys ( ) ) ,
" agent " : result . data [ 0 ]
} )
except Exception as e :
return self . fail_response ( f " Error updating agent: { str ( e ) } " )
@openapi_schema ( {
" type " : " function " ,
" function " : {
" name " : " get_current_agent_config " ,
" description " : " Get the current configuration of the agent being edited. Use this to check what ' s already configured before making updates. " ,
" parameters " : {
" type " : " object " ,
" properties " : { } ,
" required " : [ ]
}
}
} )
@xml_schema (
2025-05-31 23:31:20 +08:00
tag_name = " get-current-agent-config " ,
2025-05-30 21:28:58 +08:00
mappings = [ ] ,
example = '''
< function_calls >
< invoke name = " get_current_agent_config " >
< / invoke >
< / function_calls >
'''
)
async def get_current_agent_config ( self ) - > ToolResult :
""" Get the current agent configuration.
Returns :
ToolResult with current agent configuration
"""
try :
client = await self . db . client
result = await client . table ( ' agents ' ) . select ( ' * ' ) . eq ( ' agent_id ' , self . agent_id ) . execute ( )
if not result . data :
return self . fail_response ( " Agent not found " )
agent = result . data [ 0 ]
config_summary = {
" agent_id " : agent [ " agent_id " ] ,
" name " : agent . get ( " name " , " Untitled Agent " ) ,
" description " : agent . get ( " description " , " No description set " ) ,
" system_prompt " : agent . get ( " system_prompt " , " No system prompt set " ) ,
" avatar " : agent . get ( " avatar " , " 🤖 " ) ,
" avatar_color " : agent . get ( " avatar_color " , " #6B7280 " ) ,
" agentpress_tools " : agent . get ( " agentpress_tools " , { } ) ,
" configured_mcps " : agent . get ( " configured_mcps " , [ ] ) ,
" created_at " : agent . get ( " created_at " ) ,
" updated_at " : agent . get ( " updated_at " )
}
tools_count = len ( [ t for t , cfg in config_summary [ " agentpress_tools " ] . items ( ) if cfg . get ( " enabled " ) ] )
mcps_count = len ( config_summary [ " configured_mcps " ] )
return self . success_response ( {
" summary " : f " Agent ' { config_summary [ ' name ' ] } ' has { tools_count } tools enabled and { mcps_count } MCP servers configured. " ,
" configuration " : config_summary
} )
except Exception as e :
2025-05-31 23:31:20 +08:00
return self . fail_response ( f " Error getting agent configuration: { str ( e ) } " )
@openapi_schema ( {
" type " : " function " ,
" function " : {
" name " : " search_mcp_servers " ,
" description " : " Search for MCP servers from the Smithery registry based on user requirements. Use this when the user wants to add MCP tools to their agent. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" query " : {
" type " : " string " ,
" description " : " Search query for finding relevant MCP servers (e.g., ' linear ' , ' github ' , ' database ' , ' search ' ) "
} ,
" category " : {
" type " : " string " ,
" description " : " Optional category filter " ,
" enum " : [ " AI & Search " , " Development & Version Control " , " Project Management " , " Communication & Collaboration " , " Data & Analytics " , " Cloud & Infrastructure " , " File Storage " , " Marketing & Sales " , " Customer Support " , " Finance " , " Automation & Productivity " , " Utilities " ]
} ,
" limit " : {
" type " : " integer " ,
" description " : " Maximum number of servers to return (default: 10) " ,
" default " : 10
}
} ,
" required " : [ " query " ]
}
}
} )
@xml_schema (
tag_name = " search-mcp-servers " ,
mappings = [
{ " param_name " : " query " , " node_type " : " attribute " , " path " : " . " } ,
{ " param_name " : " category " , " node_type " : " attribute " , " path " : " . " } ,
{ " param_name " : " limit " , " node_type " : " attribute " , " path " : " . " }
] ,
example = '''
< function_calls >
< invoke name = " search_mcp_servers " >
< parameter name = " query " > linear < / parameter >
< parameter name = " limit " > 5 < / parameter >
< / invoke >
< / function_calls >
'''
)
async def search_mcp_servers (
self ,
query : str ,
category : Optional [ str ] = None ,
limit : int = 10
) - > ToolResult :
""" Search for MCP servers based on user requirements.
Args :
query : Search query for finding relevant MCP servers
category : Optional category filter
limit : Maximum number of servers to return
Returns :
ToolResult with matching MCP servers
"""
try :
async with httpx . AsyncClient ( ) as client :
headers = {
" Accept " : " application/json " ,
" User-Agent " : " Suna-MCP-Integration/1.0 "
}
if self . smithery_api_key :
headers [ " Authorization " ] = f " Bearer { self . smithery_api_key } "
params = {
" q " : query ,
" page " : 1 ,
" pageSize " : min ( limit * 2 , 50 ) # Get more results to filter
}
response = await client . get (
f " { self . smithery_api_base_url } /servers " ,
headers = headers ,
params = params ,
timeout = 30.0
)
response . raise_for_status ( )
data = response . json ( )
servers = data . get ( " servers " , [ ] )
# Filter by category if specified
if category :
filtered_servers = [ ]
for server in servers :
server_category = self . _categorize_server ( server )
if server_category == category :
filtered_servers . append ( server )
servers = filtered_servers
# Sort by useCount and limit results
servers = sorted ( servers , key = lambda x : x . get ( " useCount " , 0 ) , reverse = True ) [ : limit ]
# Format results for user-friendly display
formatted_servers = [ ]
for server in servers :
formatted_servers . append ( {
" name " : server . get ( " displayName " , server . get ( " qualifiedName " , " Unknown " ) ) ,
" qualifiedName " : server . get ( " qualifiedName " ) ,
" description " : server . get ( " description " , " No description available " ) ,
" useCount " : server . get ( " useCount " , 0 ) ,
" category " : self . _categorize_server ( server ) ,
" homepage " : server . get ( " homepage " , " " ) ,
" isDeployed " : server . get ( " isDeployed " , False )
} )
if not formatted_servers :
return ToolResult (
success = False ,
output = json . dumps ( [ ] , ensure_ascii = False )
)
return ToolResult (
success = True ,
output = json . dumps ( formatted_servers , ensure_ascii = False )
)
except Exception as e :
return self . fail_response ( f " Error searching MCP servers: { str ( e ) } " )
@openapi_schema ( {
" type " : " function " ,
" function " : {
" name " : " get_mcp_server_tools " ,
" description " : " Get detailed information about a specific MCP server including its available tools. Use this after the user selects a server they want to connect to. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" qualified_name " : {
" type " : " string " ,
" description " : " The qualified name of the MCP server (e.g., ' exa ' , ' @smithery-ai/github ' ) "
}
} ,
" required " : [ " qualified_name " ]
}
}
} )
@xml_schema (
tag_name = " get-mcp-server-tools " ,
mappings = [
{ " param_name " : " qualified_name " , " node_type " : " attribute " , " path " : " . " , " required " : True }
] ,
example = '''
< function_calls >
< invoke name = " get_mcp_server_tools " >
< parameter name = " qualified_name " > exa < / parameter >
< / invoke >
< / function_calls >
'''
)
async def get_mcp_server_tools ( self , qualified_name : str ) - > ToolResult :
""" Get detailed information about a specific MCP server and its tools.
Args :
qualified_name : The qualified name of the MCP server
Returns :
ToolResult with server details and available tools
"""
try :
# First get server metadata from registry
async with httpx . AsyncClient ( ) as client :
headers = {
" Accept " : " application/json " ,
" User-Agent " : " Suna-MCP-Integration/1.0 "
}
if self . smithery_api_key :
headers [ " Authorization " ] = f " Bearer { self . smithery_api_key } "
# URL encode the qualified name if it contains special characters
from urllib . parse import quote
if ' @ ' in qualified_name or ' / ' in qualified_name :
encoded_name = quote ( qualified_name , safe = ' ' )
else :
encoded_name = qualified_name
url = f " { self . smithery_api_base_url } /servers/ { encoded_name } "
response = await client . get (
url ,
headers = headers ,
timeout = 30.0
)
response . raise_for_status ( )
server_data = response . json ( )
# Now connect to the MCP server to get actual tools using ClientSession
try :
# Import MCP components
from mcp import ClientSession
from mcp . client . streamable_http import streamablehttp_client
import base64
import os
# Check if Smithery API key is available
smithery_api_key = os . getenv ( " SMITHERY_API_KEY " )
if not smithery_api_key :
raise ValueError ( " SMITHERY_API_KEY environment variable is not set " )
# Create server URL with empty config for testing
config_json = json . dumps ( { } )
config_b64 = base64 . b64encode ( config_json . encode ( ) ) . decode ( )
server_url = f " https://server.smithery.ai/ { qualified_name } /mcp?config= { config_b64 } &api_key= { smithery_api_key } "
# Connect and get tools
async with streamablehttp_client ( server_url ) as ( read_stream , write_stream , _ ) :
async with ClientSession ( read_stream , write_stream ) as session :
# Initialize the connection
await session . initialize ( )
# List available tools
tools_result = await session . list_tools ( )
tools = tools_result . tools if hasattr ( tools_result , ' tools ' ) else tools_result
# Format tools for user-friendly display
formatted_tools = [ ]
for tool in tools :
tool_info = {
" name " : tool . name ,
" description " : getattr ( tool , ' description ' , ' No description available ' ) ,
}
# Extract parameters from inputSchema if available
if hasattr ( tool , ' inputSchema ' ) and tool . inputSchema :
schema = tool . inputSchema
if isinstance ( schema , dict ) :
tool_info [ " parameters " ] = schema . get ( " properties " , { } )
tool_info [ " required_params " ] = schema . get ( " required " , [ ] )
else :
tool_info [ " parameters " ] = { }
tool_info [ " required_params " ] = [ ]
else :
tool_info [ " parameters " ] = { }
tool_info [ " required_params " ] = [ ]
formatted_tools . append ( tool_info )
# Extract configuration requirements from server metadata
config_requirements = [ ]
security = server_data . get ( " security " , { } )
if security :
for key , value in security . items ( ) :
if isinstance ( value , dict ) :
config_requirements . append ( {
" name " : key ,
" description " : value . get ( " description " , f " Configuration for { key } " ) ,
" required " : value . get ( " required " , False ) ,
" type " : value . get ( " type " , " string " )
} )
server_info = {
" name " : server_data . get ( " displayName " , qualified_name ) ,
" qualifiedName " : qualified_name ,
" description " : server_data . get ( " description " , " No description available " ) ,
" homepage " : server_data . get ( " homepage " , " " ) ,
" iconUrl " : server_data . get ( " iconUrl " , " " ) ,
" isDeployed " : server_data . get ( " isDeployed " , False ) ,
" tools " : formatted_tools ,
" config_requirements " : config_requirements ,
" total_tools " : len ( formatted_tools )
}
return self . success_response ( {
" message " : f " Found { len ( formatted_tools ) } tools for { server_info [ ' name ' ] } " ,
" server " : server_info
} )
except Exception as mcp_error :
# If MCP connection fails, fall back to registry data
tools = server_data . get ( " tools " , [ ] )
formatted_tools = [ ]
for tool in tools :
formatted_tools . append ( {
" name " : tool . get ( " name " , " Unknown " ) ,
" description " : tool . get ( " description " , " No description available " ) ,
" parameters " : tool . get ( " inputSchema " , { } ) . get ( " properties " , { } ) ,
" required_params " : tool . get ( " inputSchema " , { } ) . get ( " required " , [ ] )
} )
config_requirements = [ ]
security = server_data . get ( " security " , { } )
if security :
for key , value in security . items ( ) :
if isinstance ( value , dict ) :
config_requirements . append ( {
" name " : key ,
" description " : value . get ( " description " , f " Configuration for { key } " ) ,
" required " : value . get ( " required " , False ) ,
" type " : value . get ( " type " , " string " )
} )
server_info = {
" name " : server_data . get ( " displayName " , qualified_name ) ,
" qualifiedName " : qualified_name ,
" description " : server_data . get ( " description " , " No description available " ) ,
" homepage " : server_data . get ( " homepage " , " " ) ,
" iconUrl " : server_data . get ( " iconUrl " , " " ) ,
" isDeployed " : server_data . get ( " isDeployed " , False ) ,
" tools " : formatted_tools ,
" config_requirements " : config_requirements ,
" total_tools " : len ( formatted_tools ) ,
" note " : " Tools listed from registry metadata (MCP connection failed - may need configuration) "
}
return self . success_response ( {
" message " : f " Found { len ( formatted_tools ) } tools for { server_info [ ' name ' ] } (from registry) " ,
" server " : server_info
} )
except Exception as e :
return self . fail_response ( f " Error getting MCP server tools: { str ( e ) } " )
@openapi_schema ( {
" type " : " function " ,
" function " : {
" name " : " configure_mcp_server " ,
" description " : " Configure and add an MCP server to the agent with selected tools. Use this after the user has chosen which tools they want from a server. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" qualified_name " : {
" type " : " string " ,
" description " : " The qualified name of the MCP server "
} ,
" display_name " : {
" type " : " string " ,
" description " : " Display name for the server "
} ,
" enabled_tools " : {
" type " : " array " ,
" description " : " List of tool names to enable for this server " ,
" items " : { " type " : " string " }
} ,
" config " : {
" type " : " object " ,
" description " : " Configuration object with API keys and other settings " ,
" additionalProperties " : True
}
} ,
" required " : [ " qualified_name " , " display_name " , " enabled_tools " ]
}
}
} )
@xml_schema (
tag_name = " configure-mcp-server " ,
mappings = [
{ " param_name " : " qualified_name " , " node_type " : " attribute " , " path " : " . " , " required " : True } ,
{ " param_name " : " display_name " , " node_type " : " attribute " , " path " : " . " , " required " : True } ,
{ " param_name " : " enabled_tools " , " node_type " : " element " , " path " : " enabled_tools " , " required " : True } ,
{ " param_name " : " config " , " node_type " : " element " , " path " : " config " , " required " : False }
] ,
example = '''
< function_calls >
< invoke name = " configure_mcp_server " >
< parameter name = " qualified_name " > exa < / parameter >
< parameter name = " display_name " > Exa Search < / parameter >
< parameter name = " enabled_tools " > [ " search " , " find_similar " ] < / parameter >
< parameter name = " config " > { " exaApiKey " : " user-api-key " } < / parameter >
< / invoke >
< / function_calls >
'''
)
async def configure_mcp_server (
self ,
qualified_name : str ,
display_name : str ,
enabled_tools : List [ str ] ,
config : Optional [ Dict [ str , Any ] ] = None
) - > ToolResult :
""" Configure and add an MCP server to the agent.
Args :
qualified_name : The qualified name of the MCP server
display_name : Display name for the server
enabled_tools : List of tool names to enable
config : Configuration object with API keys and settings
Returns :
ToolResult with configuration status
"""
try :
client = await self . db . client
# Get current agent configuration
result = await client . table ( ' agents ' ) . select ( ' configured_mcps ' ) . eq ( ' agent_id ' , self . agent_id ) . execute ( )
if not result . data :
return self . fail_response ( " Agent not found " )
current_mcps = result . data [ 0 ] . get ( ' configured_mcps ' , [ ] )
# Check if server is already configured
existing_server_index = None
for i , mcp in enumerate ( current_mcps ) :
if mcp . get ( ' qualifiedName ' ) == qualified_name :
existing_server_index = i
break
# Create new MCP configuration
new_mcp_config = {
" name " : display_name ,
" qualifiedName " : qualified_name ,
" config " : config or { } ,
" enabledTools " : enabled_tools
}
# Update or add the configuration
if existing_server_index is not None :
current_mcps [ existing_server_index ] = new_mcp_config
action = " updated "
else :
current_mcps . append ( new_mcp_config )
action = " added "
# Save to database
update_result = await client . table ( ' agents ' ) . update ( {
' configured_mcps ' : current_mcps
} ) . eq ( ' agent_id ' , self . agent_id ) . execute ( )
if not update_result . data :
return self . fail_response ( " Failed to save MCP configuration " )
return self . success_response ( {
" message " : f " Successfully { action } MCP server ' { display_name } ' with { len ( enabled_tools ) } tools " ,
" server " : new_mcp_config ,
" total_mcp_servers " : len ( current_mcps ) ,
" action " : action
} )
except Exception as e :
return self . fail_response ( f " Error configuring MCP server: { str ( e ) } " )
@openapi_schema ( {
" type " : " function " ,
" function " : {
" name " : " get_popular_mcp_servers " ,
" description " : " Get a list of popular and recommended MCP servers organized by category. Use this to show users popular options when they want to add MCP tools. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" category " : {
" type " : " string " ,
" description " : " Optional category filter to show only servers from a specific category " ,
" enum " : [ " AI & Search " , " Development & Version Control " , " Project Management " , " Communication & Collaboration " , " Data & Analytics " , " Cloud & Infrastructure " , " File Storage " , " Marketing & Sales " , " Customer Support " , " Finance " , " Automation & Productivity " , " Utilities " ]
}
} ,
" required " : [ ]
}
}
} )
@xml_schema (
tag_name = " get-popular-mcp-servers " ,
mappings = [
{ " param_name " : " category " , " node_type " : " attribute " , " path " : " . " , " required " : False }
] ,
example = '''
< function_calls >
< invoke name = " get_popular_mcp_servers " >
< parameter name = " category " > AI & Search < / parameter >
< / invoke >
< / function_calls >
'''
)
async def get_popular_mcp_servers ( self , category : Optional [ str ] = None ) - > ToolResult :
""" Get popular MCP servers organized by category.
Args :
category : Optional category filter
Returns :
ToolResult with popular MCP servers
"""
try :
async with httpx . AsyncClient ( ) as client :
headers = {
" Accept " : " application/json " ,
" User-Agent " : " Suna-MCP-Integration/1.0 "
}
if self . smithery_api_key :
headers [ " Authorization " ] = f " Bearer { self . smithery_api_key } "
response = await client . get (
f " { self . smithery_api_base_url } /servers " ,
headers = headers ,
params = { " page " : 1 , " pageSize " : 50 } ,
timeout = 30.0
)
response . raise_for_status ( )
data = response . json ( )
servers = data . get ( " servers " , [ ] )
# Categorize servers
categorized = { }
for server in servers :
server_category = self . _categorize_server ( server )
if category and server_category != category :
continue
if server_category not in categorized :
categorized [ server_category ] = [ ]
categorized [ server_category ] . append ( {
" name " : server . get ( " displayName " , server . get ( " qualifiedName " , " Unknown " ) ) ,
" qualifiedName " : server . get ( " qualifiedName " ) ,
" description " : server . get ( " description " , " No description available " ) ,
" useCount " : server . get ( " useCount " , 0 ) ,
" homepage " : server . get ( " homepage " , " " ) ,
" isDeployed " : server . get ( " isDeployed " , False )
} )
# Sort categories and servers within each category
for cat in categorized :
categorized [ cat ] = sorted ( categorized [ cat ] , key = lambda x : x [ " useCount " ] , reverse = True ) [ : 5 ]
return self . success_response ( {
" message " : f " Found popular MCP servers " + ( f " in category ' { category } ' " if category else " " ) ,
" categorized_servers " : categorized ,
" total_categories " : len ( categorized )
} )
except Exception as e :
return self . fail_response ( f " Error getting popular MCP servers: { str ( e ) } " )
def _categorize_server ( self , server : Dict [ str , Any ] ) - > str :
""" Categorize a server based on its qualified name and description. """
qualified_name = server . get ( " qualifiedName " , " " ) . lower ( )
description = server . get ( " description " , " " ) . lower ( )
# Category mappings
category_mappings = {
" AI & Search " : [ " exa " , " perplexity " , " openai " , " anthropic " , " duckduckgo " , " brave " , " google " , " search " ] ,
" Development & Version Control " : [ " github " , " gitlab " , " bitbucket " , " git " ] ,
" Project Management " : [ " linear " , " jira " , " asana " , " notion " , " trello " , " monday " , " clickup " ] ,
" Communication & Collaboration " : [ " slack " , " discord " , " teams " , " zoom " , " telegram " ] ,
" Data & Analytics " : [ " postgres " , " mysql " , " mongodb " , " bigquery " , " snowflake " , " sqlite " , " redis " , " database " ] ,
" Cloud & Infrastructure " : [ " aws " , " gcp " , " azure " , " vercel " , " netlify " , " cloudflare " , " docker " ] ,
" File Storage " : [ " gdrive " , " google-drive " , " dropbox " , " box " , " onedrive " , " s3 " , " drive " ] ,
" Marketing & Sales " : [ " hubspot " , " salesforce " , " mailchimp " , " sendgrid " ] ,
" Customer Support " : [ " zendesk " , " intercom " , " freshdesk " , " helpscout " ] ,
" Finance " : [ " stripe " , " quickbooks " , " xero " , " plaid " ] ,
" Automation & Productivity " : [ " playwright " , " puppeteer " , " selenium " , " desktop-commander " , " sequential-thinking " , " automation " ] ,
" Utilities " : [ " filesystem " , " memory " , " fetch " , " time " , " weather " , " currency " , " file " ]
}
# Check qualified name and description for category keywords
for category , keywords in category_mappings . items ( ) :
for keyword in keywords :
if keyword in qualified_name or keyword in description :
return category
return " Other "
@openapi_schema ( {
" type " : " function " ,
" function " : {
" name " : " test_mcp_server_connection " ,
" description " : " Test connectivity to an MCP server with provided configuration. Use this to validate that a server can be connected to before adding it to the agent. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" qualified_name " : {
" type " : " string " ,
" description " : " The qualified name of the MCP server "
} ,
" config " : {
" type " : " object " ,
" description " : " Configuration object with API keys and other settings " ,
" additionalProperties " : True
}
} ,
" required " : [ " qualified_name " ]
}
}
} )
@xml_schema (
tag_name = " test-mcp-server-connection " ,
mappings = [
{ " param_name " : " qualified_name " , " node_type " : " attribute " , " path " : " . " , " required " : True } ,
{ " param_name " : " config " , " node_type " : " element " , " path " : " config " , " required " : False }
] ,
example = '''
< function_calls >
< invoke name = " test_mcp_server_connection " >
< parameter name = " qualified_name " > exa < / parameter >
< parameter name = " config " > { " exaApiKey " : " user-api-key " } < / parameter >
< / invoke >
< / function_calls >
'''
)
async def test_mcp_server_connection (
self ,
qualified_name : str ,
config : Optional [ Dict [ str , Any ] ] = None
) - > ToolResult :
""" Test connectivity to an MCP server with provided configuration.
Args :
qualified_name : The qualified name of the MCP server
config : Configuration object with API keys and settings
Returns :
ToolResult with connection test results
"""
try :
# Import MCP components
from mcp import ClientSession
from mcp . client . streamable_http import streamablehttp_client
import base64
import os
# Check if Smithery API key is available
smithery_api_key = os . getenv ( " SMITHERY_API_KEY " )
if not smithery_api_key :
return self . fail_response ( " SMITHERY_API_KEY environment variable is not set " )
# Create server URL with provided config
config_json = json . dumps ( config or { } )
config_b64 = base64 . b64encode ( config_json . encode ( ) ) . decode ( )
server_url = f " https://server.smithery.ai/ { qualified_name } /mcp?config= { config_b64 } &api_key= { smithery_api_key } "
# Test connection
async with streamablehttp_client ( server_url ) as ( read_stream , write_stream , _ ) :
async with ClientSession ( read_stream , write_stream ) as session :
# Initialize the connection
await session . initialize ( )
# List available tools to verify connection
tools_result = await session . list_tools ( )
tools = tools_result . tools if hasattr ( tools_result , ' tools ' ) else tools_result
tool_names = [ tool . name for tool in tools ]
return self . success_response ( {
" message " : f " Successfully connected to { qualified_name } " ,
" qualified_name " : qualified_name ,
" connection_status " : " success " ,
" available_tools " : tool_names ,
" total_tools " : len ( tool_names )
} )
except Exception as e :
return self . fail_response ( f " Failed to connect to { qualified_name } : { str ( e ) } " )