import SwiftUI import Persist import GitHubCopilotService /// Section for a single server's tools struct MCPServerToolsSection: View { let serverTools: MCPServerToolsCollection @Binding var isServerEnabled: Bool var forceExpand: Bool = false @State private var toolEnabledStates: [String: Bool] = [:] @State private var isExpanded: Bool = true private var originalServerName: String { serverTools.name } // Function to check if the MCP config contains unsupported server types private func hasUnsupportedServerType() -> Bool { let mcpConfig = UserDefaults.shared.value(for: \.gitHubCopilotMCPConfig) // Check if config contains a URL field for this server guard !mcpConfig.isEmpty else { return false } do { guard let jsonData = mcpConfig.data(using: .utf8), let jsonObject = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any], let serverConfig = jsonObject[serverTools.name] as? [String: Any], let url = serverConfig["url"] as? String else { return false } return true } catch { return false } } // Get the warning message for unsupported server types private func getUnsupportedServerTypeMessage() -> String { return "SSE/HTTP transport is not yet supported" } var body: some View { VStack(spacing: 0) { DisclosureGroup(isExpanded: $isExpanded) { VStack(spacing: 0) { Divider() .padding(.leading, 32) .padding(.top, 2) .padding(.bottom, 4) ForEach(serverTools.tools, id: \.name) { tool in MCPToolRow( tool: tool, isServerEnabled: isServerEnabled, isToolEnabled: toolBindingFor(tool), onToolToggleChanged: { handleToolToggleChange(tool: tool, isEnabled: $0) } ) } } } label: { // Server name with checkbox Toggle(isOn: Binding( get: { isServerEnabled }, set: { updateAllToolsStatus(enabled: $0) } )) { HStack(spacing: 8) { Text("MCP Server: \(serverTools.name)").fontWeight(.medium) if serverTools.status == .error { if hasUnsupportedServerType() { Badge(text: getUnsupportedServerTypeMessage(), level: .danger, icon: "xmark.circle.fill") } else { let message = extractErrorMessage(serverTools.error?.description ?? "") Badge(text: message, level: .danger, icon: "xmark.circle.fill") } } } } .toggleStyle(.checkbox) .padding(.leading, 4) .disabled(serverTools.status == .error) } .onAppear { initializeToolStates() if forceExpand { isExpanded = true } } .onChange(of: forceExpand) { newForceExpand in if newForceExpand { isExpanded = true } } if !isExpanded { Divider() .padding(.leading, 32) .padding(.top, 2) .padding(.bottom, 4) } } } private func extractErrorMessage(_ description: String) -> String { guard let messageRange = description.range(of: "message:"), let stackRange = description.range(of: "stack:") else { return description } let start = description.index(messageRange.upperBound, offsetBy: 0) let end = description.index(stackRange.lowerBound, offsetBy: 0) return description[start.. Binding { Binding( get: { toolEnabledStates[tool.name] ?? (tool._status == .enabled) }, set: { toolEnabledStates[tool.name] = $0 } ) } private func handleToolToggleChange(tool: MCPTool, isEnabled: Bool) { toolEnabledStates[tool.name] = isEnabled // Update server state based on tool states updateServerState() // Update only this specific tool status updateToolStatus(tool: tool, isEnabled: isEnabled) } private func updateServerState() { // If any tool is enabled, server should be enabled // If all tools are disabled, server should be disabled let allToolsDisabled = serverTools.tools.allSatisfy { tool in !(toolEnabledStates[tool.name] ?? (tool._status == .enabled)) } isServerEnabled = !allToolsDisabled } private func updateToolStatus(tool: MCPTool, isEnabled: Bool) { let serverUpdate = UpdateMCPToolsStatusServerCollection( name: serverTools.name, tools: [UpdatedMCPToolsStatus(name: tool.name, status: isEnabled ? .enabled : .disabled)] ) AppState.shared.updateMCPToolsStatus([serverUpdate]) CopilotMCPToolManager.updateMCPToolsStatus([serverUpdate]) } private func updateAllToolsStatus(enabled: Bool) { isServerEnabled = enabled // Get all tools for this server from the original collection let allServerTools = CopilotMCPToolManagerObservable.shared.availableMCPServerTools .first(where: { $0.name == originalServerName })?.tools ?? serverTools.tools // Update all tool states - includes both visible and filtered-out tools for tool in allServerTools { toolEnabledStates[tool.name] = enabled } // Create status update for all tools let serverUpdate = UpdateMCPToolsStatusServerCollection( name: serverTools.name, tools: allServerTools.map { UpdatedMCPToolsStatus(name: $0.name, status: enabled ? .enabled : .disabled) } ) AppState.shared.updateMCPToolsStatus([serverUpdate]) CopilotMCPToolManager.updateMCPToolsStatus([serverUpdate]) } }