11import Foundation
22import AnyCodable
33@testable import SwiftMCP
4- import Logging
5-
6- // Configure logging to use stdout
7- private let logger = Logger ( label: " com.cocoanetics.SwiftMCP.MCPClient " ) { label in
8- StreamLogHandler . standardOutput ( label: label)
9- }
104
115/// A client for communicating with an MCP server over HTTP+SSE
126public actor MCPClient {
@@ -20,21 +14,19 @@ public actor MCPClient {
2014 /// - Parameter endpointURL: The URL of the SSE endpoint (e.g. http://localhost:8080/sse)
2115 public init ( endpointURL: URL ) {
2216 self . endpointURL = endpointURL
23- logger. info ( " Initialized MCPClient with endpoint: \( endpointURL) " )
2417 }
2518
2619 /// Connect to the SSE endpoint and establish a connection
2720 /// - Throws: An error if the connection fails
2821 public func connect( ) async throws {
29- logger. info ( " Connecting to SSE endpoint: \( endpointURL) " )
3022 var request = URLRequest ( url: endpointURL)
3123 request. setValue ( " text/event-stream " , forHTTPHeaderField: " Accept " )
24+ request. timeoutInterval = 10 // Add a 10 second timeout for the initial connection
3225
3326 let ( stream, response) = try await URLSession . shared. bytes ( for: request)
3427 guard let httpResponse = response as? HTTPURLResponse else {
3528 throw MCPError . invalidResponse
3629 }
37- logger. info ( " Connected to SSE endpoint, status: \( httpResponse. statusCode) " )
3830 self . stream = stream
3931
4032 // Create a single message stream for all messages
@@ -47,17 +39,13 @@ public actor MCPClient {
4739 }
4840
4941 // Wait for the initial message containing the messages endpoint URL
50- logger. info ( " Waiting for initial message with endpoint URL " )
51- let initialMessage = try await nextMessage ( timeout: 5 )
52- logger. info ( " Received initial message: \( initialMessage. data) " )
42+ let initialMessage = try await nextMessage ( timeout: 10 ) // Increase timeout to 10 seconds
5343
5444 guard let messagesURL = URL ( string: initialMessage. data) ,
5545 messagesURL. path. contains ( " /messages/ " ) else {
56- logger. error ( " Invalid messages endpoint URL: \( initialMessage. data) " )
5746 throw MCPError . invalidEndpointURL
5847 }
5948 self . messagesURL = messagesURL
60- logger. info ( " Successfully connected with messages URL: \( messagesURL) " )
6149 }
6250
6351 deinit {
@@ -72,11 +60,9 @@ public actor MCPClient {
7260 /// - Throws: An error if the request fails or times out
7361 public func send( _ message: JSONRPCMessage , timeout: TimeInterval = 5 ) async throws -> JSONRPCMessage {
7462 guard let messagesURL = messagesURL else {
75- logger. error ( " Not connected to server " )
7663 throw MCPError . notConnected
7764 }
7865
79- logger. info ( " Sending JSONRPC message to: \( messagesURL) " )
8066 var request = URLRequest ( url: messagesURL)
8167 request. httpMethod = " POST "
8268 request. setValue ( " application/json " , forHTTPHeaderField: " Content-Type " )
@@ -89,14 +75,11 @@ public actor MCPClient {
8975
9076 guard let httpResponse = response as? HTTPURLResponse ,
9177 httpResponse. statusCode == 202 else {
92- logger. error ( " Invalid response from server " )
9378 throw MCPError . invalidResponse
9479 }
9580
96- logger. info ( " Request accepted, waiting for SSE response " )
9781 // Wait for the SSE response
9882 let responseMessage = try await nextMessage ( timeout: timeout)
99- logger. info ( " Received SSE response " )
10083
10184 let jsonData = responseMessage. data. data ( using: . utf8) !
10285 return try JSONDecoder ( ) . decode ( JSONRPCMessage . self, from: jsonData)
@@ -118,7 +101,6 @@ public actor MCPClient {
118101 /// - timeout: Timeout in seconds
119102 /// - Returns: The next SSE message
120103 private func nextMessage( from stream: AsyncStream < SSEMessage > , timeout: TimeInterval ) async throws -> SSEMessage {
121- logger. info ( " Waiting for next message with timeout: \( timeout) s " )
122104 return try await withThrowingTaskGroup ( of: SSEMessage . self) { group in
123105 // Task to read the next message from the stream
124106 group. addTask {
@@ -144,7 +126,6 @@ public actor MCPClient {
144126
145127 private func readMessages( continuation: AsyncStream < SSEMessage > . Continuation ) async {
146128 guard let stream = stream else {
147- logger. error ( " No stream available, finishing continuation " )
148129 continuation. finish ( )
149130 return
150131 }
@@ -153,12 +134,9 @@ public actor MCPClient {
153134 var currentEvent : String ?
154135
155136 for try await line in stream. lines {
156- logger. info ( " Received line: \( line) " )
157-
158137 if line. hasPrefix ( " data: " ) {
159138 // Handle data lines immediately
160139 let data = String ( line. dropFirst ( 6 ) ) // Remove "data: " prefix
161- logger. info ( " Found data line: \( data) " )
162140
163141 // Format the complete SSE message
164142 var messageText = " "
@@ -168,10 +146,7 @@ public actor MCPClient {
168146 messageText += " data: \( data) \n "
169147
170148 if let message = SSEMessage ( messageText) {
171- logger. info ( " Successfully parsed SSE message with data: \( message. data) " )
172149 continuation. yield ( message)
173- } else {
174- logger. error ( " Failed to parse SSE message: \( messageText) " )
175150 }
176151
177152 // Reset for next message
@@ -180,17 +155,12 @@ public actor MCPClient {
180155 // Handle event lines
181156 let event = String ( line. dropFirst ( 7 ) ) // Remove "event: " prefix
182157 currentEvent = event
183- logger. info ( " Found event line: \( event) " )
184- } else if !line. isEmpty {
185- // Skip non-empty lines that aren't SSE format
186- logger. info ( " Skipping line: \( line) " )
187158 }
188159 }
189160 } catch {
190- logger . error ( " Error reading stream: \( error ) " )
161+ // No logging needed for errors
191162 }
192163
193- logger. info ( " Stream ended, finishing continuation " )
194164 continuation. finish ( )
195165 }
196166}
0 commit comments