From a272226759a63d008e2aee5c606a4ca34f5a06d0 Mon Sep 17 00:00:00 2001 From: Adam Van Prooyen Date: Tue, 17 Mar 2026 23:27:53 -0700 Subject: [PATCH 1/2] Fix SIGTRAP crash from continuation misuse in connection handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MCP SDK's NetworkTransport has reconnection logic that creates unstructured tasks holding CheckedContinuation references. For server-side incoming connections, this reconnection path can cause double-resume when connections are cancelled, crashing the app. Disable reconnection and heartbeats for server-accepted connections since they are client features — if a client disconnects, it should reconnect itself. Also make removeConnection idempotent to prevent stop() being called twice on the same connection. Co-Authored-By: Claude Opus 4.6 (1M context) --- App/Controllers/ServerController.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/App/Controllers/ServerController.swift b/App/Controllers/ServerController.swift index 255c2bb6..2f2a9632 100644 --- a/App/Controllers/ServerController.swift +++ b/App/Controllers/ServerController.swift @@ -432,6 +432,8 @@ actor MCPConnectionManager { self.transport = NetworkTransport( connection: connection, logger: nil, + heartbeatConfig: .init(enabled: false), + reconnectionConfig: .disabled, bufferConfig: .unlimited ) @@ -627,6 +629,7 @@ actor ServerNetworkManager { private var connections: [UUID: MCPConnectionManager] = [:] private var connectionTasks: [UUID: Task] = [:] private var pendingConnections: [UUID: String] = [:] + private var removedConnections: Set = [] typealias ConnectionApprovalHandler = @Sendable (UUID, MCP.Client.Info) async -> Bool private var connectionApprovalHandler: ConnectionApprovalHandler? @@ -764,11 +767,20 @@ actor ServerNetworkManager { connections.removeAll() connectionTasks.removeAll() pendingConnections.removeAll() + removedConnections.removeAll() await discoveryManager?.stop() } func removeConnection(_ id: UUID) async { + // Guard against redundant removal — calling stop() on an already-stopped + // connection can trigger a double-resume in the SDK's transport continuation. + guard !removedConnections.contains(id) else { + log.debug("Connection \(id) already removed, skipping") + return + } + removedConnections.insert(id) + log.debug("Removing connection: \(id)") if let connectionManager = connections[id] { From eafdd2145017b4b37b5cccd2d3e84a71c0036157 Mon Sep 17 00:00:00 2001 From: Madison Rickert <3495636+madisonrickert@users.noreply.github.com> Date: Mon, 6 Apr 2026 13:32:34 -0700 Subject: [PATCH 2/2] Bump MCP Swift SDK to 0.12.0 and fix breaking API changes Update swift-sdk dependency from revision pin (106167b) to version-based 0.12.0, which resolves Swift 6 strict concurrency data race errors in NetworkTransport that prevented building. Adapt ServerController to the new Tool.Content labeled enum cases and convert inputSchema from JSONSchema to Value at the MCP.Tool creation boundary. Co-Authored-By: Claude Opus 4.6 (1M context) --- App/Controllers/ServerController.swift | 19 ++++++++------- iMCP.xcodeproj/project.pbxproj | 4 ++-- .../xcshareddata/swiftpm/Package.resolved | 23 +++++++++++++++++-- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/App/Controllers/ServerController.swift b/App/Controllers/ServerController.swift index 2f2a9632..b7c206ea 100644 --- a/App/Controllers/ServerController.swift +++ b/App/Controllers/ServerController.swift @@ -885,7 +885,7 @@ actor ServerNetworkManager { .init( name: tool.name, description: tool.description, - inputSchema: tool.inputSchema, + inputSchema: try Value(tool.inputSchema), annotations: tool.annotations ) ) @@ -901,7 +901,7 @@ actor ServerNetworkManager { await server.withMethodHandler(CallTool.self) { [weak self] params in guard let self = self else { return CallTool.Result( - content: [.text("Server unavailable")], + content: [.text(text: "Server unavailable", annotations: nil, _meta: nil)], isError: true ) } @@ -911,7 +911,7 @@ actor ServerNetworkManager { guard await self.isEnabledState else { log.notice("Tool call rejected: iMCP is disabled") return CallTool.Result( - content: [.text("iMCP is currently disabled. Please enable it to use tools.")], + content: [.text(text: "iMCP is currently disabled. Please enable it to use tools.", annotations: nil, _meta: nil)], isError: true ) } @@ -940,7 +940,9 @@ actor ServerNetworkManager { content: [ .audio( data: data.base64EncodedString(), - mimeType: mimeType + mimeType: mimeType, + annotations: nil, + _meta: nil ) ], isError: false @@ -951,7 +953,8 @@ actor ServerNetworkManager { .image( data: data.base64EncodedString(), mimeType: mimeType, - metadata: nil + annotations: nil, + _meta: nil ) ], isError: false @@ -965,20 +968,20 @@ actor ServerNetworkManager { let data = try encoder.encode(value) let text = String(data: data, encoding: .utf8)! - return CallTool.Result(content: [.text(text)], isError: false) + return CallTool.Result(content: [.text(text: text, annotations: nil, _meta: nil)], isError: false) } } catch { log.error( "Error executing tool \(params.name): \(error.localizedDescription)" ) - return CallTool.Result(content: [.text("Error: \(error)")], isError: true) + return CallTool.Result(content: [.text(text: "Error: \(error)", annotations: nil, _meta: nil)], isError: true) } } } log.error("Tool not found or service not enabled: \(params.name)") return CallTool.Result( - content: [.text("Tool not found or service not enabled: \(params.name)")], + content: [.text(text: "Tool not found or service not enabled: \(params.name)", annotations: nil, _meta: nil)], isError: true ) } diff --git a/iMCP.xcodeproj/project.pbxproj b/iMCP.xcodeproj/project.pbxproj index a7950147..8196627f 100644 --- a/iMCP.xcodeproj/project.pbxproj +++ b/iMCP.xcodeproj/project.pbxproj @@ -631,8 +631,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/modelcontextprotocol/swift-sdk"; requirement = { - kind = revision; - revision = 106167bad12cd8d004b0cbfcec8211c5408794d8; + kind = upToNextMinorVersion; + minimumVersion = 0.12.0; }; }; F8D8C48C2DCE0E6800369E5C /* XCRemoteSwiftPackageReference "JSONSchema" */ = { diff --git a/iMCP.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iMCP.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2b63aef7..c04afe91 100644 --- a/iMCP.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/iMCP.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -4,7 +4,7 @@ { "identity" : "eventsource", "kind" : "remoteSourceControl", - "location" : "https://github.com/loopwork-ai/eventsource.git", + "location" : "https://github.com/mattt/eventsource.git", "state" : { "revision" : "07957602bb99a5355c810187e66e6ce378a1057d", "version" : "1.1.1" @@ -55,6 +55,15 @@ "version" : "1.0.3" } }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", + "version" : "1.3.0" + } + }, { "identity" : "swift-collections", "kind" : "remoteSourceControl", @@ -73,12 +82,22 @@ "version" : "1.6.2" } }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "558f24a4647193b5a0e2104031b71c55d31ff83a", + "version" : "2.97.1" + } + }, { "identity" : "swift-sdk", "kind" : "remoteSourceControl", "location" : "https://github.com/modelcontextprotocol/swift-sdk", "state" : { - "revision" : "106167bad12cd8d004b0cbfcec8211c5408794d8" + "revision" : "6132fd4b5b4217ce4717c4775e4607f5c3120129", + "version" : "0.12.0" } }, {