@@ -34,6 +34,7 @@ public final actor MCPServerProxy: Sendable {
3434 private var isDisconnecting = false
3535
3636 public private( set) var endpointURL : URL ?
37+ public private( set) var sessionID : String ?
3738 private var streamTask : Task < Void , Error > ?
3839
3940 public private( set) var serverName : String ?
@@ -62,6 +63,8 @@ public final actor MCPServerProxy: Sendable {
6263 public func connect( ) async throws {
6364 isDisconnecting = false
6465 streamFailure = nil
66+ endpointURL = nil
67+ sessionID = nil
6568
6669 switch config {
6770 case . stdio( let stdioConfig) :
@@ -96,17 +99,9 @@ public final actor MCPServerProxy: Sendable {
9699 let session = URLSession ( configuration: sessionConfig)
97100 var request = URLRequest ( url: sseConfig. url)
98101 request. httpMethod = " GET "
102+ applyConfiguredSSEHeaders ( & request, sseConfig: sseConfig)
99103 request. setValue ( " text/event-stream " , forHTTPHeaderField: " Accept " )
100104
101- for (key, value) in sseConfig. headers {
102- request. setValue ( value, forHTTPHeaderField: key)
103- }
104-
105- // Add Authorization header from meta if present
106- if let accessToken = meta [ " accessToken " ] ? . value as? String {
107- request. setValue ( " Bearer \( accessToken) " , forHTTPHeaderField: " Authorization " )
108- }
109-
110105 let ( asyncBytes, response) = try await session. bytes ( for: request)
111106
112107 if let response = response as? HTTPURLResponse , response. statusCode != 200 {
@@ -116,10 +111,14 @@ public final actor MCPServerProxy: Sendable {
116111 throw MCPServerProxyError . communicationError ( " HTTP error \( response. statusCode) : \( String ( data: data, encoding: . utf8) ?? " Unknown error " ) " )
117112 }
118113
119- if let response = response as? HTTPURLResponse ,
120- let sessionId = response. value ( forHTTPHeaderField: " Mcp-Session-Id " ) ,
121- let endpoint = messageEndpointURL ( baseURL: sseConfig. url, sessionId: sessionId) {
122- endpointURL = endpoint
114+ if let response = response as? HTTPURLResponse {
115+ sessionID = response. value ( forHTTPHeaderField: " Mcp-Session-Id " )
116+ if isStreamableMCPURL ( sseConfig. url) {
117+ endpointURL = sseConfig. url
118+ } else if let sessionID,
119+ let endpoint = messageEndpointURL ( baseURL: sseConfig. url, sessionId: sessionID) {
120+ endpointURL = endpoint
121+ }
123122 }
124123
125124 streamTask = Task {
@@ -175,6 +174,8 @@ public final actor MCPServerProxy: Sendable {
175174
176175 streamTask? . cancel ( )
177176 streamTask = nil
177+ endpointURL = nil
178+ sessionID = nil
178179
179180 switch config {
180181 case . stdio, . stdioHandles, . tcp:
@@ -342,7 +343,7 @@ public final actor MCPServerProxy: Sendable {
342343 }
343344 }
344345
345- case . sse:
346+ case . sse( let sseConfig ) :
346347 guard let endpointURL = endpointURL else {
347348 throw MCPServerProxyError . communicationError ( " Not connected to server " )
348349 }
@@ -356,6 +357,7 @@ public final actor MCPServerProxy: Sendable {
356357 var urlRequest = URLRequest ( url: endpointURL)
357358 urlRequest. httpMethod = " POST "
358359 urlRequest. setValue ( " application/json " , forHTTPHeaderField: " Content-Type " )
360+ configureSSEPOSTRequest ( & urlRequest, sseConfig: sseConfig)
359361 let encoder = JSONEncoder ( )
360362 encoder. outputFormatting = [ . sortedKeys]
361363 let data = try encoder. encode ( message)
@@ -371,13 +373,29 @@ public final actor MCPServerProxy: Sendable {
371373
372374 Task {
373375 do {
374- let ( _, response) = try await session. data ( for: urlRequest)
375- if let httpResponse = response as? HTTPURLResponse ,
376- ![ 200 , 202 ] . contains ( httpResponse. statusCode) {
376+ let ( responseData, response) = try await session. data ( for: urlRequest)
377+ guard let httpResponse = response as? HTTPURLResponse else {
378+ throw MCPServerProxyError . communicationError ( " Invalid HTTP response " )
379+ }
380+
381+ switch httpResponse. statusCode {
382+ case 200 :
383+ guard let responseMessage = try responseMessage ( for: messageId, from: responseData) else {
384+ let responseBody = String ( data: responseData, encoding: . utf8) ? . trimmingCharacters ( in: . whitespacesAndNewlines) ?? " "
385+ let details = responseBody. isEmpty ? " " : " : \( responseBody) "
386+ throw MCPServerProxyError . communicationError ( " HTTP 200 did not include JSON-RPC response for request \( messageId. stringValue) \( details) " )
387+ }
388+
377389 if responseTasks [ messageId] != nil {
378390 responseTasks. removeValue ( forKey: messageId)
379- continuation. resume ( throwing : MCPServerProxyError . communicationError ( " HTTP error \( httpResponse . statusCode ) " ) )
391+ continuation. resume ( returning : responseMessage )
380392 }
393+ case 202 :
394+ break // response will arrive over SSE
395+ default :
396+ let responseBody = String ( data: responseData, encoding: . utf8) ? . trimmingCharacters ( in: . whitespacesAndNewlines) ?? " "
397+ let details = responseBody. isEmpty ? " " : " : \( responseBody) "
398+ throw MCPServerProxyError . communicationError ( " HTTP error \( httpResponse. statusCode) \( details) " )
381399 }
382400 } catch {
383401 if responseTasks [ messageId] != nil {
@@ -560,12 +578,14 @@ public final actor MCPServerProxy: Sendable {
560578
561579 private func handlePingRequest( _ request: JSONRPCMessage . JSONRPCRequestData ) async {
562580 guard let endpointURL = endpointURL else { return }
581+ guard case . sse( let sseConfig) = config else { return }
563582 let response = JSONRPCMessage . response ( id: request. id, result: [ : ] )
564583 let sessionConfig = URLSessionConfiguration . default
565584 let session = URLSession ( configuration: sessionConfig)
566585 var urlRequest = URLRequest ( url: endpointURL)
567586 urlRequest. httpMethod = " POST "
568587 urlRequest. setValue ( " application/json " , forHTTPHeaderField: " Content-Type " )
588+ configureSSEPOSTRequest ( & urlRequest, sseConfig: sseConfig)
569589 let encoder = JSONEncoder ( )
570590 let data = try ? encoder. encode ( response)
571591 urlRequest. httpBody = data
@@ -767,6 +787,52 @@ public final actor MCPServerProxy: Sendable {
767787 return " \( rounded) % "
768788 }
769789
790+ private func isStreamableMCPURL( _ url: URL ) -> Bool {
791+ guard let components = URLComponents ( url: url, resolvingAgainstBaseURL: false ) else {
792+ return false
793+ }
794+ return components. path. hasPrefix ( " /mcp " )
795+ }
796+
797+ private func applyConfiguredSSEHeaders( _ request: inout URLRequest , sseConfig: MCPServerSseConfig ) {
798+ for (key, value) in sseConfig. headers {
799+ request. setValue ( value, forHTTPHeaderField: key)
800+ }
801+
802+ // Allow token in metadata to override auth header from config.
803+ if let accessToken = meta [ " accessToken " ] ? . value as? String {
804+ request. setValue ( " Bearer \( accessToken) " , forHTTPHeaderField: " Authorization " )
805+ }
806+ }
807+
808+ private func configureSSEPOSTRequest( _ request: inout URLRequest , sseConfig: MCPServerSseConfig ) {
809+ applyConfiguredSSEHeaders ( & request, sseConfig: sseConfig)
810+ request. setValue ( " application/json, text/event-stream " , forHTTPHeaderField: " Accept " )
811+
812+ if let sessionID {
813+ request. setValue ( sessionID, forHTTPHeaderField: " Mcp-Session-Id " )
814+ }
815+ }
816+
817+ private func responseMessage( for requestID: JSONRPCID , from data: Data ) throws -> JSONRPCMessage ? {
818+ guard !data. isEmpty else {
819+ return nil
820+ }
821+
822+ let messages = try JSONRPCMessage . decodeMessages ( from: data)
823+ return messages. first { message in
824+ guard message. id == requestID else {
825+ return false
826+ }
827+ switch message {
828+ case . response, . errorResponse:
829+ return true
830+ case . request, . notification:
831+ return false
832+ }
833+ }
834+ }
835+
770836 private func messageEndpointURL( baseURL: URL , sessionId: String ) -> URL ? {
771837 guard var components = URLComponents ( url: baseURL, resolvingAgainstBaseURL: false ) else {
772838 return nil
0 commit comments