@@ -2,6 +2,9 @@ import Foundation
22import SwiftUI
33import MCP
44import McpApps
5+ import os. log
6+
7+ private let logger = Logger ( subsystem: " com.example.BasicHostSwift " , category: " ToolCall " )
58
69/// View model managing MCP server connection and tool execution.
710@MainActor
@@ -22,6 +25,15 @@ class McpHostViewModel: ObservableObject {
2225 }
2326 @Published var activeToolCalls : [ ToolCallInfo ] = [ ]
2427 @Published var errorMessage : String ?
28+ @Published var toastMessage : String ?
29+
30+ func showToast( _ message: String , duration: TimeInterval = 3.0 ) {
31+ toastMessage = message
32+ Task {
33+ try ? await Task . sleep ( nanoseconds: UInt64 ( duration * 1_000_000_000 ) )
34+ await MainActor . run { self . toastMessage = nil }
35+ }
36+ }
2537
2638 /// Known MCP servers (matches examples/servers.json)
2739 static let knownServers = [
@@ -240,7 +252,9 @@ class McpHostViewModel: ObservableObject {
240252 }
241253
242254 func removeToolCall( _ toolCall: ToolCallInfo ) async {
243- await toolCall. teardown ( )
255+ if let error = await toolCall. teardown ( ) {
256+ showToast ( error)
257+ }
244258 activeToolCalls. removeAll { $0. id == toolCall. id }
245259 }
246260}
@@ -465,8 +479,10 @@ class ToolCallInfo: ObservableObject, Identifiable {
465479 ]
466480 }
467481
482+ logger. info ( " Connecting bridge to transport... " )
468483 try await bridge. connect ( transport)
469484 self . appBridge = bridge
485+ logger. info ( " AppBridge is now set and ready " )
470486 }
471487
472488 private func sendToolResult( _ result: ToolResult , to bridge: AppBridge ) async throws {
@@ -486,33 +502,56 @@ class ToolCallInfo: ObservableObject, Identifiable {
486502 }
487503
488504 /// Teardown the app bridge before removing the tool call
489- func teardown( ) async {
505+ /// Returns an error message if teardown failed, nil on success
506+ func teardown( ) async -> String ? {
490507 // Prevent double-tap
491- guard !isTearingDown else { return }
508+ guard !isTearingDown else {
509+ logger. info ( " Teardown already in progress, skipping " )
510+ return nil
511+ }
492512 isTearingDown = true
513+ logger. info ( " Starting teardown, appBridge exists: \( self . appBridge != nil ) " )
514+
515+ var errorMessage : String ?
493516
494517 if let bridge = appBridge {
495- do {
496- // Use a timeout so we don't wait forever if app is unresponsive
497- try await withThrowingTaskGroup ( of: Void . self) { group in
498- group. addTask {
518+ logger. info ( " Sending teardown request... " )
519+
520+ // Race between teardown and timeout
521+ await withTaskGroup ( of: String ? . self) { group in
522+ group. addTask {
523+ do {
524+ logger. info ( " Calling bridge.sendResourceTeardown()... " )
499525 _ = try await bridge. sendResourceTeardown ( )
526+ logger. info ( " Teardown request completed successfully " )
527+ return nil
528+ } catch {
529+ logger. error ( " Teardown request failed: \( String ( describing: error) ) , type: \( type ( of: error) ) " )
530+ return " Teardown failed: app may not have saved data "
500531 }
501- group. addTask {
502- try await Task . sleep ( nanoseconds: 5_000_000_000 ) // 5 second timeout
503- throw CancellationError ( )
504- }
505- // Wait for first to complete (either teardown or timeout)
506- try await group. next ( )
507- group. cancelAll ( )
508532 }
509- } catch {
510- print ( " [Host] Teardown failed or timed out: \( error) " )
533+ group. addTask {
534+ try ? await Task . sleep ( nanoseconds: 5_000_000_000 ) // 5 second timeout
535+ logger. warning ( " Teardown timeout reached after 5 seconds " )
536+ return " Teardown timed out: app may not have saved data "
537+ }
538+ // Wait for first to complete
539+ if let result = await group. next ( ) {
540+ errorMessage = result
541+ }
542+ logger. info ( " Task group completed " )
543+ group. cancelAll ( )
511544 }
545+
546+ logger. info ( " Closing bridge... " )
512547 await bridge. close ( )
548+ logger. info ( " Bridge closed " )
549+ } else {
550+ logger. warning ( " No bridge to teardown (appBridge is nil) " )
513551 }
514552 appBridge = nil
515- // Note: isTearingDown stays true - the card will be removed from the list
553+ logger. info ( " Teardown complete, will remove card from list " )
554+ return errorMessage
516555 }
517556}
518557
0 commit comments