The official Swift SDK for Model Context Protocol servers and clients.
by modelcontextprotocolSwift
Last 12 weeks · 0 commits
5 of 6 standards met
Summary When a transport stream finishes (e.g. subprocess death closes the pipe), the Client's message handling loop spins at 100% CPU indefinitely. The root cause is the outer loop in (line 217). When the loop exits normally on stream EOF, the re-enters , gets an instantly-finishing stream, and loops again — consuming a full CPU core. The check at the top of the body only helps if was called externally. On a normal subprocess/transport death, no cancellation is signalled — the stream simply finishes. in this same package does not have this bug — it uses a flat without an outer loop. Fix Remove the wrapper, matching the pattern. The message handling task now: 1. Calls once 2. Iterates the stream with 3. Exits cleanly when the stream finishes (EOF) or an error is thrown The for (which used to re-enter the repeat) is also removed. That EAGAIN-style error is a transient I/O condition handled internally by the transport's read loop — it does not surface through the that returns. Reproduction 1. Start a Client connected via to a subprocess 2. Kill the subprocess (or let it exit) 3. Observe the Client's task consuming 100% CPU via or Activity Monitor The tight loop shows up as: Test plan [x] Existing test suite passes (545 tests in downstream consumer, SDK tests unmodified) [x] Verified CPU drops from 88–99% to 0% after subprocess death [x] still terminates the loop correctly (cancels the task) [x] Normal message handling unaffected (stream stays open while transport is alive)
Motivation and Context Fix #255 - StatelessHTTPServerTransport: notifications/cancelled leaves the original POST hanging (HTTP exchange never completed) Problem A request cancelled via gets no JSON-RPC response (per the cancellation spec's "Not send a response for the cancelled request"), but nothing resumes the transport's HTTP waiter, so the original POST hangs until transport termination — violating the Streamable HTTP requirement that a request POST "MUST either return … or , to return one JSON object." Fix The transport detects , still yields it to the (whose existing cancellation path cancels the handler task and stays wire-silent), then resumes the matching response waiter with a synthesized JSON-RPC error: code , message . Deviating from the cancellation SHOULD is sanctioned by the spec itself: "The sender of the cancellation notification SHOULD ignore any response to the request that arrives afterward." Unknown, already-completed, and malformed cancellations are ignored ("Invalid cancellation notifications SHOULD be ignored"). Cancel-vs-response races are safe via actor isolation — exactly one path resumes the waiter. sits in MCP's designated implementation-specific band , next after this SDK's /. Surveyed precedents: python-sdk always answers cancelled requests (code ), go-sdk's jsonrpc2 always answers calls (generic ), kotlin-sdk completes the POST as 202-no-body (which reads as violating the MUST above for request inputs), typescript-sdk currently has this same hang. No SDK uses LSP's . Tests Five deterministic tests (no fixed sleeps — handler-entry gating via ): completion with + handler-observed cancellation, reason propagation, integer-id round-trip, unknown/completed-id no-op, malformed no-op. Full suite: 556 tests / 41 suites green, no warnings. Non-goals Response-waiter deadline (defense-in-depth for any orphaned exchange) — deliberately a separate follow-up. #254 (waiter collision under duplicate JSON-RPC ids); cancellation targeting under colliding ids inherits that limitation until it lands. How Has This Been Tested? Tested locally AND @sollahiro verified fix addresses a hang in their test suite. Breaking Changes None Types of changes [x] Bug fix (non-breaking change which fixes an issue) [ ] New feature (non-breaking change which adds functionality) [ ] Breaking change (fix or feature that would cause existing functionality to change) [ ] Documentation update Checklist [x] I have read the MCP Documentation [x] My code follows the repository's style guidelines [x] New and existing tests pass locally [x] I have added appropriate error handling [x] I have added or updated documentation as needed 🤖 Generated with Claude Code and Codex
Fixes #262. is typed , but the spec defines it as a map of arbitrary objects. Clients that send nested values there — ChatGPT sends — fail decoding inside , and the server returns JSON-RPC to a valid . The capability map was also unmodeled (ignored on decode). is now ; is added as . Added a decoding test with the exact payload from #262 and an encode/decode round-trip test. Full suite passes (553 tests). Note for reviewers: this changes the public type of . Dictionary literals with string values still compile ( is ); no call sites in the repo needed updating.
Fixes #261. provides the product on Apple platforms only, but gates the import and the SSE code paths on . On Windows the import is compiled without the module being available, so any target depending on fails with . (#261 has the full analysis; the guards drifted when #143 moved the platform logic into a condition.) This keys the guards off availability instead of naming platforms, along the two axes that actually differ: for the import, the SSE listen loop, and — on Apple and Linux this selects exactly the code selected today, because the manifest grants EventSource to all Apple platforms and denies it to Linux. for the vs split and the paired overloads — is a Darwin-Foundation capability, independent of EventSource, so this names the real dependency rather than folding it into the SSE guard. Selection today is likewise identical on Apple and Linux. The two log messages that said "on Linux" now say "on this platform". Windows takes the same graceful-degradation path Linux already takes: JSON request/response works, SSE streaming logs a warning and is skipped. Verified: succeeds for x86_64-unknown-windows-msvc with Swift 6.3.3 (all 113 sources); before the patch it fails at . No selection change on Apple or Linux by construction, since each new condition is equivalent to the old one on those platforms. 🤖 Generated with Claude Code
Repository: modelcontextprotocol/swift-sdk. Description: The official Swift SDK for Model Context Protocol servers and clients. Stars: 1470, Forks: 224. Primary language: Swift. Languages: Swift (99.8%), Shell (0.2%). Topics: mcp, swift. Latest release: 0.12.1 (3mo ago). Open PRs: 33, open issues: 65. Last activity: 3mo ago. Community health: 87%. Top contributors: mattt, adamwulf, yehorsobko-mac, carlpeaslee, movetz, stallent, Overcot, aspitz, localden, phimage and others.