Skip to content

Fix client parsing #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 13, 2025
Merged

Conversation

zats
Copy link
Contributor

@zats zats commented Mar 9, 2025

This addressed issue #7, tldr looks like client had no way of parsing response due to

  1. Response arriving as a Value instead of strong type associated with request
  2. Request casting to <Any> seem to constantly fail for me, introducing type-erased wrapper seem to fix it for me

There is a good chance I am not aware of the original thinking and missing something obvious to make it work 🙂

Original Issue Description:

I will prephrase my question by saying I am not sure at all that any of my findings are accurate.
I think I am seeing consistent failure of the client to handle responses due to 2 issues:

First of all it appears that we are passing response twice under

https://github.com/loopwork-ai/mcp-swift-sdk/blob/main/Sources/MCP/Client/Client.swift#L132-L133

Where instead we should be getting appropriate request from pandingRequests like so

if let response = try? decoder.decode(AnyResponse.self, from: data), let request = pendingRequests[response.id] {
  await handleResponse(response, for: request)
} ... 

Even fixing that locally downcast here https://github.com/loopwork-ai/mcp-swift-sdk/blob/main/Sources/MCP/Client/Client.swift#L329 fails

guard let typedRequest = request as? PendingRequest<Any> else { return }

Not sure if there is a way to tell Swift "don't worry about generic the original value was created with", it seem to consistently fail. Specifically I am debugging initialize sequence and so when I fix passing pending request, $ po request prints MCP.Client.PendingRequest<MCP.Initialize.Result>) , tho guard let typedRequest = request as? PendingRequest<Any> else { return } fails

Any ideas if I am holding it wrong? or we might need to store type-erased pending requests instead of Any?

Sash Zats sash@zats.io added 2 commits March 9, 2025 13:35
Summary: I noticed we are not converting value associated with response to the strong types requests recorded
This change will help to decode the value type later once we support the rest of plumbing (next commit)
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
@stallent
Copy link
Contributor

stallent commented Mar 10, 2025

Just so you don't feel alone... I just now ran into the same thing and trying to discern the best resolution

@stallent
Copy link
Contributor

@zats and can confirm your branch fixes it. Again, no clue if it adheres to matttttttt's intentions but it most certainly unblocks me. Thank you!

@zats
Copy link
Contributor Author

zats commented Mar 11, 2025

ay thanks Stephen, glad it works for you 🙂

@leoshimo
Copy link

Ran into the same issue + patched in a similar way!
I should've checked open issues / PRs first - thanks for the fix.

@stallent
Copy link
Contributor

I no longer think this fix is correct. It appears to fix one thing and break another. Still trying to discern the intention....

@mattt
Copy link
Contributor

mattt commented Mar 13, 2025

Thanks again, @zats, for your help with this. And thanks to you, @stallent @leoshimo, for giving this a go and bearing with us.

@stallent Interesting! I'm also getting my head around these changes. What are you seeing now?

@stallent
Copy link
Contributor

stallent commented Mar 13, 2025

@mattt its related to using client.callTool. The tool is returning a successful response (Which works in the MCP Inspector), but its failing a typecast here during attempting to process the result:

private struct AnyPendingRequest {
private let _resume: (Result<Any, Swift.Error>) -> Void

init<T: Sendable & Decodable>(_ request: PendingRequest<T>) {
      _resume = { result in
          switch result {
          case .success(let value):
            if let typedValue = value as? T {

@stallent
Copy link
Contributor

@mattt best i can tell with my bad eyes.. the spot i pointed to above, the .success(value) is an MCP.Value and in the pending request is expecting MCP.CallTool.Result

@zats
Copy link
Contributor Author

zats commented Mar 13, 2025

hey folks, catching up on he conversation, thanks for digging depper
@mattt did I get it right you will take over the PR or can I contribute anything else?

@mattt
Copy link
Contributor

mattt commented Mar 13, 2025

@zats Yep! I'm very happy to take it from here. Will look closer at what @stallent is seeing. But overall, this is a substantial improvement, and I'm grateful for your contribution.

@mattt
Copy link
Contributor

mattt commented Mar 13, 2025

@stallent I just pushed 043ce65, which expands test coverage to include client-server round-tripping for listing tools and calling a tool. Does that match how you're using it? Or are the tests not exercising this correctly?

@stallent
Copy link
Contributor

@mattt give me a few min to finish lunch and i'll pull and give it a go. and have more deets if its not.

@mattt
Copy link
Contributor

mattt commented Mar 13, 2025

@stallent No rush, I'm about to break for lunch myself. Since this seems to fix the issue reported in #8, I'll go ahead and merge it so we can cut a new patch release. If you see anything amiss, please open a new issue, and I'll get on fixing that ASAP.

@mattt mattt merged commit c63a872 into modelcontextprotocol:main Mar 13, 2025
1 check passed
@zats zats deleted the fix_client_parsing branch March 13, 2025 19:39
devyhan pushed a commit to devyhan/swift-sdk that referenced this pull request Apr 4, 2025
* Allow decoding Decodable type directly from Value

Summary: I noticed we are not converting value associated with response to the strong types requests recorded
This change will help to decode the value type later once we support the rest of plumbing (next commit)

* 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

* Fix warning: 'Cast from Client.AnyPendingRequest to unrelated type Client.PendingRequest<Any> always fails'

* Fix warning: 'Conditional cast from NWError to NWError always succeeds'

* Uncomment code in RoundtripTests

* Formatting

* Reorganize declarations

* Inline JSONDecoder extension

* Expand roundtrip tests

---------

Co-authored-by: Sash Zats sash@zats.io <>
Co-authored-by: Mattt Zmuda <mattt@loopwork.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants