99from metorial .exceptions import AuthenticationError , NotFoundError , OAuthRequiredError
1010
1111from .mcp_client import MetorialMcpClient
12- from .mcp_tool import Capability
12+ from .mcp_tool import Capability , ResourceTemplate , Tool
1313
1414if TYPE_CHECKING :
1515 from .mcp_tool_manager import MetorialMcpToolManager
@@ -645,14 +645,60 @@ def _raise_critical_error(self, error: Exception) -> None:
645645 raise error
646646
647647 async def _get_tools_via_direct_mcp (self ) -> list [Capability ]:
648- """Get tools by connecting directly to MCP server, bypassing capabilities API."""
648+ """Get capabilities via direct MCP when the capabilities API is unavailable ."""
649649 _log_info ("Starting direct MCP tool discovery..." )
650650
651+ def _normalize_tool (raw_tool : object ) -> Tool | None :
652+ if isinstance (raw_tool , dict ):
653+ name_value = raw_tool .get ("name" )
654+ description_value = raw_tool .get ("description" )
655+ input_schema_value = raw_tool .get ("inputSchema" )
656+ else :
657+ name_value = getattr (raw_tool , "name" , None )
658+ description_value = getattr (raw_tool , "description" , None )
659+ input_schema_value = getattr (raw_tool , "inputSchema" , None )
660+
661+ if not isinstance (name_value , str ) or not name_value :
662+ return None
663+
664+ normalized_tool : Tool = {"name" : name_value }
665+ if isinstance (description_value , str ):
666+ normalized_tool ["description" ] = description_value
667+ if isinstance (input_schema_value , dict ):
668+ normalized_tool ["inputSchema" ] = input_schema_value
669+ return normalized_tool
670+
671+ def _normalize_resource_template (
672+ raw_template : object ,
673+ ) -> ResourceTemplate | None :
674+ if isinstance (raw_template , dict ):
675+ name_value = raw_template .get ("name" )
676+ description_value = raw_template .get ("description" )
677+ uri_template_value = raw_template .get ("uriTemplate" )
678+ else :
679+ name_value = getattr (raw_template , "name" , None )
680+ description_value = getattr (raw_template , "description" , None )
681+ uri_template_value = getattr (raw_template , "uriTemplate" , None )
682+
683+ if not isinstance (name_value , str ) or not name_value :
684+ return None
685+ if not isinstance (uri_template_value , str ) or not uri_template_value :
686+ return None
687+
688+ normalized_template : ResourceTemplate = {
689+ "name" : name_value ,
690+ "uriTemplate" : uri_template_value ,
691+ }
692+ if isinstance (description_value , str ):
693+ normalized_template ["description" ] = description_value
694+ return normalized_template
695+
651696 capabilities : list [Capability ] = []
652697
653698 for deployment_id in self .server_deployment_ids :
654699 client = None
655- tools = []
700+ tools : list [Tool ] = []
701+ resource_templates : list [ResourceTemplate ] = []
656702
657703 # Step 1: Get client
658704 try :
@@ -666,12 +712,19 @@ async def _get_tools_via_direct_mcp(self) -> list[Capability]:
666712 try :
667713 tools_response = await client .list_tools ()
668714
715+ raw_tools : object
669716 if hasattr (tools_response , "tools" ):
670- tools = tools_response .tools
717+ raw_tools = tools_response .tools
671718 elif isinstance (tools_response , dict ):
672- tools = tools_response .get ("tools" , [])
719+ raw_tools = tools_response .get ("tools" , [])
673720 else :
674- tools = []
721+ raw_tools = []
722+
723+ if isinstance (raw_tools , list ):
724+ for raw_tool in raw_tools :
725+ normalized_tool = _normalize_tool (raw_tool )
726+ if normalized_tool is not None :
727+ tools .append (normalized_tool )
675728
676729 _log_info (f"Direct MCP found { len (tools )} tools for { deployment_id } " )
677730
@@ -683,10 +736,27 @@ async def _get_tools_via_direct_mcp(self) -> list[Capability]:
683736 try :
684737 templates_response = await client .list_resource_templates ()
685738
739+ raw_templates : object
686740 if hasattr (templates_response , "resourceTemplates" ):
687- pass # templates = templates_response.resourceTemplates (unused)
741+ raw_templates = templates_response .resourceTemplates or []
742+ elif hasattr (templates_response , "resource_templates" ):
743+ raw_templates = templates_response .resource_templates or []
688744 elif isinstance (templates_response , dict ):
689- pass # templates = templates_response.get("resourceTemplates", []) (unused)
745+ raw_templates = templates_response .get (
746+ "resourceTemplates"
747+ ) or templates_response .get ("resource_templates" , [])
748+ else :
749+ raw_templates = []
750+
751+ if isinstance (raw_templates , list ):
752+ for raw_template in raw_templates :
753+ normalized_template = _normalize_resource_template (raw_template )
754+ if normalized_template is not None :
755+ resource_templates .append (normalized_template )
756+
757+ _log_info (
758+ f"Direct MCP found { len (resource_templates )} resource templates for { deployment_id } "
759+ )
690760 except Exception as e :
691761 logger .debug (
692762 f"Warning: Failed to get resource templates for { deployment_id } : { e } "
@@ -706,6 +776,21 @@ async def _get_tools_via_direct_mcp(self) -> list[Capability]:
706776 logger .warning (f"Warning: Failed to process tool { tool } : { e } " )
707777 continue
708778
779+ # Step 5: Process resource templates into capabilities
780+ for template in resource_templates :
781+ try :
782+ template_capability : Capability = {
783+ "type" : "resource-template" ,
784+ "resourceTemplate" : template ,
785+ "serverDeployment" : {"id" : deployment_id },
786+ }
787+ capabilities .append (template_capability )
788+ except Exception as e :
789+ logger .warning (
790+ f"Warning: Failed to process resource template { template } : { e } "
791+ )
792+ continue
793+
709794 _log_info (f"Direct MCP discovery completed: { len (capabilities )} total capabilities" )
710795 return capabilities
711796
0 commit comments