diff --git a/App/Controllers/ServerController.swift b/App/Controllers/ServerController.swift index 255c2bb6..b7c206ea 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] { @@ -873,7 +885,7 @@ actor ServerNetworkManager { .init( name: tool.name, description: tool.description, - inputSchema: tool.inputSchema, + inputSchema: try Value(tool.inputSchema), annotations: tool.annotations ) ) @@ -889,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 ) } @@ -899,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 ) } @@ -928,7 +940,9 @@ actor ServerNetworkManager { content: [ .audio( data: data.base64EncodedString(), - mimeType: mimeType + mimeType: mimeType, + annotations: nil, + _meta: nil ) ], isError: false @@ -939,7 +953,8 @@ actor ServerNetworkManager { .image( data: data.base64EncodedString(), mimeType: mimeType, - metadata: nil + annotations: nil, + _meta: nil ) ], isError: false @@ -953,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" } }, {