|
37 | 37 | from pydantic import Field |
38 | 38 | from starlette.websockets import WebSocket |
39 | 39 |
|
| 40 | +from nat.builder.function import Function |
40 | 41 | from nat.builder.workflow_builder import WorkflowBuilder |
41 | 42 | from nat.data_models.api_server import ChatRequest |
42 | 43 | from nat.data_models.api_server import ChatResponse |
@@ -1117,97 +1118,101 @@ async def get_mcp_client_tool_list() -> MCPClientToolListResponse: |
1117 | 1118 |
|
1118 | 1119 | # Find MCP client function groups |
1119 | 1120 | for group_name, configured_group in function_groups.items(): |
1120 | | - if configured_group.config.type == "mcp_client": |
1121 | | - from nat.plugins.mcp.client_impl import MCPClientConfig |
| 1121 | + if configured_group.config.type != "mcp_client": |
| 1122 | + continue |
1122 | 1123 |
|
1123 | | - config = configured_group.config |
1124 | | - assert isinstance(config, MCPClientConfig) |
| 1124 | + from nat.plugins.mcp.client_impl import MCPClientConfig |
1125 | 1125 |
|
1126 | | - # Reuse the existing MCP client session stored on the function group instance |
1127 | | - group_instance = configured_group.instance |
1128 | | - client = getattr(group_instance, "_mcp_client", None) |
1129 | | - if client is None: |
1130 | | - raise RuntimeError(f"MCP client not found for group {group_name}") |
| 1126 | + config = configured_group.config |
| 1127 | + assert isinstance(config, MCPClientConfig) |
| 1128 | + |
| 1129 | + # Reuse the existing MCP client session stored on the function group instance |
| 1130 | + group_instance = configured_group.instance |
| 1131 | + |
| 1132 | + client = group_instance.mcp_client |
| 1133 | + if client is None: |
| 1134 | + raise RuntimeError(f"MCP client not found for group {group_name}") |
| 1135 | + |
| 1136 | + try: |
| 1137 | + session_healthy = False |
| 1138 | + server_tools: dict[str, Any] = {} |
1131 | 1139 |
|
1132 | 1140 | try: |
| 1141 | + server_tools = await client.get_tools() |
| 1142 | + session_healthy = True |
| 1143 | + except Exception as e: |
| 1144 | + logger.exception(f"Failed to connect to MCP server {client.server_name}: {e}") |
1133 | 1145 | session_healthy = False |
1134 | | - server_tools: dict[str, Any] = {} |
1135 | | - |
1136 | | - try: |
1137 | | - server_tools = await client.get_tools() |
1138 | | - session_healthy = True |
1139 | | - except Exception as e: |
1140 | | - logger.warning(f"Failed to connect to MCP server {client.server_name}: {e}") |
1141 | | - session_healthy = False |
1142 | | - |
1143 | | - # Get workflow function group configuration (configured client-side tools) |
1144 | | - configured_short_names: set[str] = set() |
1145 | | - configured_full_to_fn: dict[str, Any] = {} |
1146 | | - try: |
1147 | | - accessible_functions = await group_instance.get_accessible_functions() |
1148 | | - configured_full_to_fn = accessible_functions |
1149 | | - configured_short_names = { |
1150 | | - name.split('.', 1)[1] if '.' in name else name |
1151 | | - for name in accessible_functions.keys() |
1152 | | - } |
1153 | | - except Exception as e: |
1154 | | - logger.warning(f"Failed to get accessible functions for group {group_name}: {e}") |
1155 | | - |
1156 | | - # Build alias->original mapping from overrides |
1157 | | - alias_to_original: dict[str, str] = {} |
1158 | | - try: |
1159 | | - overrides = getattr(config, "tool_overrides", None) or {} |
1160 | | - for orig_name, override in overrides.items(): |
1161 | | - alias = getattr(override, "alias", None) |
1162 | | - if alias: |
1163 | | - alias_to_original[alias] = orig_name |
1164 | | - except Exception: |
1165 | | - pass |
1166 | | - |
1167 | | - # Create tool info list (always return configured tools; mark availability) |
1168 | | - tools_info: list[dict[str, Any]] = [] |
1169 | | - available_count = 0 |
1170 | | - for fn_short in sorted(configured_short_names): |
1171 | | - orig_name = alias_to_original.get(fn_short, fn_short) |
1172 | | - available = session_healthy and (orig_name in server_tools) |
1173 | | - if available: |
1174 | | - available_count += 1 |
1175 | | - |
1176 | | - # Prefer the workflow function description (includes overrides) |
1177 | | - full_name = f"{group_name}.{fn_short}" |
1178 | | - wf_fn = configured_full_to_fn.get(full_name) |
1179 | | - description = getattr( |
1180 | | - wf_fn, "description", |
1181 | | - None) or (server_tools[orig_name].description if available else "") |
1182 | | - |
1183 | | - tools_info.append( |
1184 | | - MCPToolInfo(name=fn_short, |
1185 | | - description=description or "", |
1186 | | - server=client.server_name, |
1187 | | - available=available).dict()) |
1188 | | - |
1189 | | - mcp_clients_info.append({ |
1190 | | - "function_group": group_name, |
1191 | | - "server": client.server_name, |
1192 | | - "transport": config.server.transport, |
1193 | | - "session_healthy": session_healthy, |
1194 | | - "tools": tools_info, |
1195 | | - "total_tools": len(configured_short_names), |
1196 | | - "available_tools": available_count |
1197 | | - }) |
1198 | 1146 |
|
| 1147 | + # Get workflow function group configuration (configured client-side tools) |
| 1148 | + configured_short_names: set[str] = set() |
| 1149 | + configured_full_to_fn: dict[str, Function] = {} |
| 1150 | + try: |
| 1151 | + # Pass a no-op filter function to bypass any default filtering that might check |
| 1152 | + # health status, preventing potential infinite recursion during health status checks. |
| 1153 | + async def pass_through_filter(fn): |
| 1154 | + return fn |
| 1155 | + |
| 1156 | + accessible_functions = await group_instance.get_accessible_functions( |
| 1157 | + filter_fn=pass_through_filter) |
| 1158 | + configured_full_to_fn = accessible_functions |
| 1159 | + configured_short_names = {name.split('.', 1)[1] for name in accessible_functions.keys()} |
1199 | 1160 | except Exception as e: |
1200 | | - logger.error(f"Error processing MCP client {group_name}: {e}") |
1201 | | - mcp_clients_info.append({ |
1202 | | - "function_group": group_name, |
1203 | | - "server": "unknown", |
1204 | | - "transport": config.server.transport if config.server else "unknown", |
1205 | | - "session_healthy": False, |
1206 | | - "error": str(e), |
1207 | | - "tools": [], |
1208 | | - "total_tools": 0, |
1209 | | - "workflow_tools": 0 |
1210 | | - }) |
| 1161 | + logger.exception(f"Failed to get accessible functions for group {group_name}: {e}") |
| 1162 | + |
| 1163 | + # Build alias->original mapping from overrides |
| 1164 | + alias_to_original: dict[str, str] = {} |
| 1165 | + try: |
| 1166 | + if config.tool_overrides is not None: |
| 1167 | + for orig_name, override in config.tool_overrides.items(): |
| 1168 | + if override.alias is not None: |
| 1169 | + alias_to_original[override.alias] = orig_name |
| 1170 | + except Exception: |
| 1171 | + pass |
| 1172 | + |
| 1173 | + # Create tool info list (always return configured tools; mark availability) |
| 1174 | + tools_info: list[dict[str, Any]] = [] |
| 1175 | + available_count = 0 |
| 1176 | + for wf_fn, fn_short in zip(configured_full_to_fn.values(), configured_short_names): |
| 1177 | + orig_name = alias_to_original.get(fn_short, fn_short) |
| 1178 | + available = session_healthy and (orig_name in server_tools) |
| 1179 | + if available: |
| 1180 | + available_count += 1 |
| 1181 | + |
| 1182 | + description = (server_tools[orig_name].description |
| 1183 | + if available else None) or wf_fn.description or "" |
| 1184 | + |
| 1185 | + tools_info.append( |
| 1186 | + MCPToolInfo(name=fn_short, |
| 1187 | + description=description or "", |
| 1188 | + server=client.server_name, |
| 1189 | + available=available).model_dump()) |
| 1190 | + |
| 1191 | + # Sort tools_info by name to maintain consistent ordering |
| 1192 | + tools_info.sort(key=lambda x: x['name']) |
| 1193 | + |
| 1194 | + mcp_clients_info.append({ |
| 1195 | + "function_group": group_name, |
| 1196 | + "server": client.server_name, |
| 1197 | + "transport": config.server.transport, |
| 1198 | + "session_healthy": session_healthy, |
| 1199 | + "tools": tools_info, |
| 1200 | + "total_tools": len(configured_short_names), |
| 1201 | + "available_tools": available_count |
| 1202 | + }) |
| 1203 | + |
| 1204 | + except Exception as e: |
| 1205 | + logger.error(f"Error processing MCP client {group_name}: {e}") |
| 1206 | + mcp_clients_info.append({ |
| 1207 | + "function_group": group_name, |
| 1208 | + "server": "unknown", |
| 1209 | + "transport": config.server.transport if config.server else "unknown", |
| 1210 | + "session_healthy": False, |
| 1211 | + "error": str(e), |
| 1212 | + "tools": [], |
| 1213 | + "total_tools": 0, |
| 1214 | + "workflow_tools": 0 |
| 1215 | + }) |
1211 | 1216 |
|
1212 | 1217 | return MCPClientToolListResponse(mcp_clients=mcp_clients_info) |
1213 | 1218 |
|
|
0 commit comments