diff --git a/api/controllers/console/workspace/tool_providers.py b/api/controllers/console/workspace/tool_providers.py index d8fad83f60..870ad87c6c 100644 --- a/api/controllers/console/workspace/tool_providers.py +++ b/api/controllers/console/workspace/tool_providers.py @@ -1073,7 +1073,8 @@ class ToolMCPListAllApi(Resource): with Session(db.engine) as session, session.begin(): service = MCPToolManageService(session=session) - tools = service.list_providers(tenant_id=tenant_id) + # Skip sensitive data decryption for list view to improve performance + tools = service.list_providers(tenant_id=tenant_id, include_sensitive=False) return [tool.to_dict() for tool in tools] diff --git a/api/core/entities/mcp_provider.py b/api/core/entities/mcp_provider.py index 555896be21..70aeab254d 100644 --- a/api/core/entities/mcp_provider.py +++ b/api/core/entities/mcp_provider.py @@ -150,8 +150,13 @@ class MCPProviderEntity(BaseModel): # If not JSON, assume it's a file path return file_helpers.get_signed_file_url(self.icon) - def to_api_response(self, user_name: str | None = None) -> dict[str, Any]: - """Convert to API response format""" + def to_api_response(self, user_name: str | None = None, include_sensitive: bool = True) -> dict[str, Any]: + """Convert to API response format + + Args: + user_name: User name to display + include_sensitive: If False, skip expensive decryption operations (for list view optimization) + """ response = { "id": self.id, "author": user_name or "Anonymous", @@ -172,14 +177,20 @@ class MCPProviderEntity(BaseModel): "sse_read_timeout": str(self.sse_read_timeout), } - # Add masked headers - response["masked_headers"] = self.masked_headers() + # Skip expensive operations when sensitive data is not needed (e.g., list view) + if not include_sensitive: + response["masked_headers"] = {} + response["is_dynamic_registration"] = True + else: + # Add masked headers + response["masked_headers"] = self.masked_headers() + + # Add authentication info if available + masked_creds = self.masked_credentials() + if masked_creds: + response["authentication"] = masked_creds + response["is_dynamic_registration"] = self.credentials.get("is_dynamic_registration", True) - # Add authentication info if available - masked_creds = self.masked_credentials() - if masked_creds: - response["authentication"] = masked_creds - response["is_dynamic_registration"] = self.credentials.get("is_dynamic_registration", True) return response def retrieve_client_information(self) -> OAuthClientInformation | None: diff --git a/api/core/tools/tool_manager.py b/api/core/tools/tool_manager.py index ff7dcc0e55..82375f87d7 100644 --- a/api/core/tools/tool_manager.py +++ b/api/core/tools/tool_manager.py @@ -726,7 +726,9 @@ class ToolManager: if "mcp" in filters: with Session(db.engine) as session: mcp_service = MCPToolManageService(session=session) - mcp_providers = mcp_service.list_providers(tenant_id=tenant_id, for_list=True) + mcp_providers = mcp_service.list_providers( + tenant_id=tenant_id, for_list=True, include_sensitive=False + ) for mcp_provider in mcp_providers: result_providers[f"mcp_provider.{mcp_provider.name}"] = mcp_provider diff --git a/api/services/tools/mcp_tools_manage_service.py b/api/services/tools/mcp_tools_manage_service.py index c5b0adfdf8..cd14ee34e7 100644 --- a/api/services/tools/mcp_tools_manage_service.py +++ b/api/services/tools/mcp_tools_manage_service.py @@ -252,8 +252,16 @@ class MCPToolManageService: mcp_tool = self.get_provider(provider_id=provider_id, tenant_id=tenant_id) self._session.delete(mcp_tool) - def list_providers(self, *, tenant_id: str, for_list: bool = False) -> list[ToolProviderApiEntity]: - """List all MCP providers for a tenant.""" + def list_providers( + self, *, tenant_id: str, for_list: bool = False, include_sensitive: bool = True + ) -> list[ToolProviderApiEntity]: + """List all MCP providers for a tenant. + + Args: + tenant_id: Tenant ID + for_list: If True, return provider ID; if False, return server identifier + include_sensitive: If False, skip expensive decryption operations (default: True for backward compatibility) + """ from models.account import Account stmt = select(MCPToolProvider).where(MCPToolProvider.tenant_id == tenant_id).order_by(MCPToolProvider.name) @@ -269,7 +277,10 @@ class MCPToolManageService: return [ ToolTransformService.mcp_provider_to_user_provider( - provider, for_list=for_list, user_name=user_name_map.get(provider.user_id) + provider, + for_list=for_list, + user_name=user_name_map.get(provider.user_id), + include_sensitive=include_sensitive, ) for provider in mcp_providers ] diff --git a/api/services/tools/tools_transform_service.py b/api/services/tools/tools_transform_service.py index 931d23e3e0..3b49b0f98a 100644 --- a/api/services/tools/tools_transform_service.py +++ b/api/services/tools/tools_transform_service.py @@ -234,7 +234,10 @@ class ToolTransformService: @staticmethod def mcp_provider_to_user_provider( - db_provider: MCPToolProvider, for_list: bool = False, user_name: str | None = None + db_provider: MCPToolProvider, + for_list: bool = False, + user_name: str | None = None, + include_sensitive: bool = True, ) -> ToolProviderApiEntity: # Use provided user_name to avoid N+1 query, fallback to load_user() if not provided if user_name is None: @@ -244,7 +247,7 @@ class ToolTransformService: # Convert to entity and use its API response method provider_entity = db_provider.to_entity() - response = provider_entity.to_api_response(user_name=user_name) + response = provider_entity.to_api_response(user_name=user_name, include_sensitive=include_sensitive) # Add additional fields specific to the transform response["id"] = db_provider.server_identifier if not for_list else db_provider.id