Skip to content

Commit b0f6554

Browse files
committed
Store and pass original request when response arrives
Summary: Looks like there were multiple issues with parsing response: 1. It appears that we would mistakenly pass response both for response and request arguments in the private func handleResponse(_ response: Response<AnyMethod>, for request: Any) 2. Later I couldn't figure out how to win against swift type system, so ended up creating a type-erased request to store in the pendingRequests instead of original type under Any (that wouldn't allow to downcast original type later) Test Plan: 1. Standup iMCP client from https://github.com/loopwork-ai/iMCP 2. Create a dummy project setting up MCP client against "/private/var/folders/26/8gncz37x7slbfrr95d9jcr400000gn/T/AppTranslocation/62ADEC4D-3545-4E98-A612-0E6DF52CE525/d/iMCP.app/Contents/MacOS/imcp-server" and call initialize Note response is being parsed correctly and client.initialize() does not hang indefinitely
1 parent b1ccf31 commit b0f6554

File tree

1 file changed

+45
-15
lines changed

1 file changed

+45
-15
lines changed

Sources/MCP/Client/Client.swift

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,44 @@ public actor Client {
8787
/// The task for the message handling loop
8888
private var task: Task<Void, Never>?
8989

90+
91+
private struct TypeMismatchError: Swift.Error {}
92+
93+
private struct AnyPendingRequest {
94+
private let _resume: (Result<Any, Swift.Error>) -> Void
95+
96+
init<T: Sendable & Decodable>(_ request: PendingRequest<T>) {
97+
_resume = { result in
98+
switch result {
99+
case .success(let value):
100+
if let typedValue = value as? T {
101+
request.continuation.resume(returning: typedValue)
102+
} else if let value = value as? Value,
103+
let decoded = try? JSONDecoder().decode(T.self, from: value) {
104+
request.continuation.resume(returning: decoded)
105+
} else {
106+
request.continuation.resume(throwing: TypeMismatchError())
107+
}
108+
case .failure(let error):
109+
request.continuation.resume(throwing: error)
110+
}
111+
}
112+
}
113+
func resume(returning value: Any) {
114+
_resume(.success(value))
115+
}
116+
117+
func resume(throwing error: Swift.Error) {
118+
_resume(.failure(error))
119+
}
120+
}
121+
90122
/// A pending request with a continuation for the result
91123
private struct PendingRequest<T> {
92124
let continuation: CheckedContinuation<T, Swift.Error>
93125
}
94126
/// A dictionary of type-erased pending requests, keyed by request ID
95-
private var pendingRequests: [ID: Any] = [:]
127+
private var pendingRequests: [ID: AnyPendingRequest] = [:]
96128

97129
public init(
98130
name: String,
@@ -129,8 +161,8 @@ public actor Client {
129161

130162
// Attempt to decode string data as AnyResponse or AnyMessage
131163
let decoder = JSONDecoder()
132-
if let response = try? decoder.decode(AnyResponse.self, from: data) {
133-
await handleResponse(response, for: response)
164+
if let response = try? decoder.decode(AnyResponse.self, from: data), let request = pendingRequests[response.id] {
165+
await handleResponse(response, for: request)
134166
} else if let message = try? decoder.decode(AnyMessage.self, from: data) {
135167
await handleMessage(message)
136168
} else {
@@ -220,12 +252,12 @@ public actor Client {
220252
}
221253
}
222254

223-
private func addPendingRequest<T>(
255+
private func addPendingRequest<T: Sendable & Decodable>(
224256
id: ID,
225257
continuation: CheckedContinuation<T, Swift.Error>,
226258
type: T.Type
227259
) {
228-
pendingRequests[id] = PendingRequest(continuation: continuation)
260+
pendingRequests[id] = AnyPendingRequest(PendingRequest(continuation: continuation))
229261
}
230262

231263
private func removePendingRequest(id: ID) {
@@ -320,21 +352,19 @@ public actor Client {
320352

321353
// MARK: -
322354

323-
private func handleResponse(_ response: Response<AnyMethod>, for request: Any) async {
355+
private func handleResponse(_ response: Response<AnyMethod>, for request: AnyPendingRequest) async {
324356
await logger?.debug(
325357
"Processing response",
326358
metadata: ["id": "\(response.id)"])
327359

328360
// We know this cast is safe because we only store PendingRequest values
329-
guard let typedRequest = request as? PendingRequest<Any> else { return }
330-
331-
switch response.result {
332-
case .success(let value):
333-
typedRequest.continuation.resume(returning: value)
334-
case .failure(let error):
335-
typedRequest.continuation.resume(throwing: error)
336-
}
337-
361+
362+
switch response.result {
363+
case .success(let value):
364+
request.resume(returning: value)
365+
case .failure(let error):
366+
request.resume(throwing: error)
367+
}
338368
removePendingRequest(id: response.id)
339369
}
340370

0 commit comments

Comments
 (0)