From de70570840dd74e0b39555070b4cc493e40f5faa Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 6 May 2025 09:45:23 -0700 Subject: [PATCH 1/9] Update Client to automatically call initialize in connect --- README.md | 11 +++------ Sources/MCP/Client/Client.swift | 6 ++++- Tests/MCPTests/ClientTests.swift | 42 +++++++++++++++++++------------- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 03fc9e20..aaa3e657 100644 --- a/README.md +++ b/README.md @@ -52,12 +52,9 @@ import MCP // Initialize the client let client = Client(name: "MyApp", version: "1.0.0") -// Create a transport and connect +// Create a transport and connect - initialization happens automatically let transport = StdioTransport() -try await client.connect(transport: transport) - -// Initialize the connection -let result = try await client.initialize() +let result = try await client.connect(transport: transport) // Check server capabilities if result.capabilities.tools != nil { @@ -74,7 +71,7 @@ For local subprocess communication: ```swift // Create a stdio transport (simplest option) let transport = StdioTransport() -try await client.connect(transport: transport) +let result = try await client.connect(transport: transport) ``` #### HTTP Transport @@ -87,7 +84,7 @@ let transport = HTTPClientTransport( endpoint: URL(string: "http://localhost:8080")!, streaming: true // Enable Server-Sent Events for real-time updates ) -try await client.connect(transport: transport) +let result = try await client.connect(transport: transport) ``` ### Tools diff --git a/Sources/MCP/Client/Client.swift b/Sources/MCP/Client/Client.swift index 58f380de..ebf1c73f 100644 --- a/Sources/MCP/Client/Client.swift +++ b/Sources/MCP/Client/Client.swift @@ -168,7 +168,7 @@ public actor Client { } /// Connect to the server using the given transport - public func connect(transport: any Transport) async throws { + public func connect(transport: any Transport) async throws -> Initialize.Result { self.connection = transport try await self.connection?.connect() @@ -217,6 +217,10 @@ public actor Client { } while true await self.logger?.info("Client message handling loop task is terminating.") } + + // Automatically initialize after connecting + let result = try await initialize() + return result } /// Disconnect the client and cancel all pending requests diff --git a/Tests/MCPTests/ClientTests.swift b/Tests/MCPTests/ClientTests.swift index 9e649250..5363b92e 100644 --- a/Tests/MCPTests/ClientTests.swift +++ b/Tests/MCPTests/ClientTests.swift @@ -11,8 +11,9 @@ struct ClientTests { let client = Client(name: "TestClient", version: "1.0") #expect(await transport.isConnected == false) - try await client.connect(transport: transport) + let result = try await client.connect(transport: transport) #expect(await transport.isConnected == true) + #expect(result.protocolVersion == Version.latest) await client.disconnect() #expect(await transport.isConnected == false) } @@ -25,25 +26,32 @@ struct ClientTests { let transport = MockTransport() let client = Client(name: "TestClient", version: "1.0") - try await client.connect(transport: transport) - // Small delay to ensure message loop is started - try await Task.sleep(for: .milliseconds(10)) - - // Create a task for initialize that we'll cancel - let initTask = Task { - try await client.initialize() - } + // Queue a response for the initialize request + try await Task.sleep(for: .milliseconds(10)) // Wait for request to be sent - // Give it a moment to send the request - try await Task.sleep(for: .milliseconds(10)) + if let lastMessage = await transport.sentMessages.last, + let data = lastMessage.data(using: .utf8), + let request = try? JSONDecoder().decode(Request.self, from: data) + { + // Create a valid initialize response + let response = Initialize.response( + id: request.id, + result: .init( + protocolVersion: Version.latest, + capabilities: .init(), + serverInfo: .init(name: "TestServer", version: "1.0"), + instructions: nil + ) + ) - #expect(await transport.sentMessages.count == 1) - #expect(await transport.sentMessages.first?.contains(Initialize.name) == true) - #expect(await transport.sentMessages.first?.contains(client.name) == true) - #expect(await transport.sentMessages.first?.contains(client.version) == true) + try await transport.queue(response: response) - // Cancel the initialize task - initTask.cancel() + // Now complete the connect call which will automatically initialize + let result = try await client.connect(transport: transport) + #expect(result.protocolVersion == Version.latest) + #expect(result.serverInfo.name == "TestServer") + #expect(result.serverInfo.version == "1.0") + } // Disconnect client to clean up message loop and give time for continuation cleanup await client.disconnect() From 977b5158586b96b2561221474febf4473208754f Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 6 May 2025 09:51:14 -0700 Subject: [PATCH 2/9] Make Client.connect(transport:) return value discardable --- README.md | 11 ++++++++--- Sources/MCP/Client/Client.swift | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index aaa3e657..f6a9c3c9 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,11 @@ if result.capabilities.tools != nil { } ``` +> [!NOTE] +> The `Client.connect(transport:)` method returns the initialization result. +> This return value is discardable, +> so you can ignore it if you don't need to check server capabilities. + ### Transport Options for Clients #### Stdio Transport @@ -71,7 +76,7 @@ For local subprocess communication: ```swift // Create a stdio transport (simplest option) let transport = StdioTransport() -let result = try await client.connect(transport: transport) +try await client.connect(transport: transport) ``` #### HTTP Transport @@ -84,7 +89,7 @@ let transport = HTTPClientTransport( endpoint: URL(string: "http://localhost:8080")!, streaming: true // Enable Server-Sent Events for real-time updates ) -let result = try await client.connect(transport: transport) +try await client.connect(transport: transport) ``` ### Tools @@ -275,7 +280,7 @@ Handle common client errors: ```swift do { - let result = try await client.initialize() + try await client.connect() // Success } catch let error as MCPError { print("MCP Error: \(error.localizedDescription)") diff --git a/Sources/MCP/Client/Client.swift b/Sources/MCP/Client/Client.swift index ebf1c73f..36f97bb2 100644 --- a/Sources/MCP/Client/Client.swift +++ b/Sources/MCP/Client/Client.swift @@ -168,6 +168,7 @@ public actor Client { } /// Connect to the server using the given transport + @discardableResult public func connect(transport: any Transport) async throws -> Initialize.Result { self.connection = transport try await self.connection?.connect() From c200f2ef4e88110c02521c42a3e671638caba657 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 6 May 2025 10:04:33 -0700 Subject: [PATCH 3/9] Fix example usage in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6a9c3c9..7c778f41 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ Handle common client errors: ```swift do { - try await client.connect() + try await client.connect(transport: transport) // Success } catch let error as MCPError { print("MCP Error: \(error.localizedDescription)") From c243e6b6186fc51d1f4ee21e40264c170d57897d Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 6 May 2025 10:04:56 -0700 Subject: [PATCH 4/9] Update example code in transports to remove initialize call --- Sources/MCP/Base/Transports/HTTPClientTransport.swift | 3 --- Sources/MCP/Base/Transports/NetworkTransport.swift | 3 --- Sources/MCP/Base/Transports/StdioTransport.swift | 3 --- 3 files changed, 9 deletions(-) diff --git a/Sources/MCP/Base/Transports/HTTPClientTransport.swift b/Sources/MCP/Base/Transports/HTTPClientTransport.swift index 87eb7f44..f9a73a71 100644 --- a/Sources/MCP/Base/Transports/HTTPClientTransport.swift +++ b/Sources/MCP/Base/Transports/HTTPClientTransport.swift @@ -41,9 +41,6 @@ import Logging /// let client = Client(name: "MyApp", version: "1.0.0") /// try await client.connect(transport: transport) /// -/// // Initialize the connection -/// let result = try await client.initialize() -/// /// // The transport will automatically handle SSE events /// // and deliver them through the client's notification handlers /// ``` diff --git a/Sources/MCP/Base/Transports/NetworkTransport.swift b/Sources/MCP/Base/Transports/NetworkTransport.swift index ecc6081d..cd3a7b22 100644 --- a/Sources/MCP/Base/Transports/NetworkTransport.swift +++ b/Sources/MCP/Base/Transports/NetworkTransport.swift @@ -51,9 +51,6 @@ import Logging /// // Use the transport with an MCP client /// let client = Client(name: "MyApp", version: "1.0.0") /// try await client.connect(transport: transport) - /// - /// // Initialize the connection - /// let result = try await client.initialize() /// ``` public actor NetworkTransport: Transport { /// Represents a heartbeat message for connection health monitoring. diff --git a/Sources/MCP/Base/Transports/StdioTransport.swift b/Sources/MCP/Base/Transports/StdioTransport.swift index 9aed1acb..84a98c66 100644 --- a/Sources/MCP/Base/Transports/StdioTransport.swift +++ b/Sources/MCP/Base/Transports/StdioTransport.swift @@ -44,9 +44,6 @@ import struct Foundation.Data /// // Create a transport and connect /// let transport = StdioTransport() /// try await client.connect(transport: transport) - /// - /// // Initialize the connection - /// let result = try await client.initialize() /// ``` public actor StdioTransport: Transport { private let input: FileDescriptor From 54cf16b9292c570203dc21c8bbc70b0058d07f58 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 6 May 2025 10:05:04 -0700 Subject: [PATCH 5/9] Deprecate initialize method --- Sources/MCP/Client/Client.swift | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Sources/MCP/Client/Client.swift b/Sources/MCP/Client/Client.swift index 36f97bb2..60318358 100644 --- a/Sources/MCP/Client/Client.swift +++ b/Sources/MCP/Client/Client.swift @@ -220,8 +220,7 @@ public actor Client { } // Automatically initialize after connecting - let result = try await initialize() - return result + return try await _initialize() } /// Disconnect the client and cancel all pending requests @@ -485,7 +484,23 @@ public actor Client { // MARK: - Lifecycle + /// Initialize the connection with the server. + /// + /// - Important: This method is deprecated. Initialization now happens automatically + /// when calling `connect(transport:)`. You should use that method instead. + /// + /// - Returns: The server's initialization response containing capabilities and server info + @available( + *, deprecated, + message: + "Initialization now happens automatically during connect. Use connect(transport:) instead." + ) public func initialize() async throws -> Initialize.Result { + return try await _initialize() + } + + /// Internal initialization implementation + private func _initialize() async throws -> Initialize.Result { let request = Initialize.request( .init( protocolVersion: Version.latest, From 297bf6351c1ee04cb5aedd492d23ed1ed9bbe02d Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 6 May 2025 10:05:11 -0700 Subject: [PATCH 6/9] Update tests --- Tests/MCPTests/ClientTests.swift | 251 ++++++++++++++---- Tests/MCPTests/HTTPClientTransportTests.swift | 5 +- Tests/MCPTests/RoundtripTests.swift | 3 +- 3 files changed, 208 insertions(+), 51 deletions(-) diff --git a/Tests/MCPTests/ClientTests.swift b/Tests/MCPTests/ClientTests.swift index 5363b92e..12d3dfd9 100644 --- a/Tests/MCPTests/ClientTests.swift +++ b/Tests/MCPTests/ClientTests.swift @@ -11,18 +11,40 @@ struct ClientTests { let client = Client(name: "TestClient", version: "1.0") #expect(await transport.isConnected == false) + + // Set up a task to handle the initialize response + let initTask = Task { + try await Task.sleep(for: .milliseconds(10)) + if let lastMessage = await transport.sentMessages.last, + let data = lastMessage.data(using: .utf8), + let request = try? JSONDecoder().decode(Request.self, from: data) + { + let response = Initialize.response( + id: request.id, + result: .init( + protocolVersion: Version.latest, + capabilities: .init(), + serverInfo: .init(name: "TestServer", version: "1.0"), + instructions: nil + ) + ) + try await transport.queue(response: response) + } + } + let result = try await client.connect(transport: transport) #expect(await transport.isConnected == true) #expect(result.protocolVersion == Version.latest) await client.disconnect() #expect(await transport.isConnected == false) + initTask.cancel() } @Test( - "Initialize request", + "Ping request", .timeLimit(.minutes(1)) ) - func testClientInitialize() async throws { + func testClientPing() async throws { let transport = MockTransport() let client = Client(name: "TestClient", version: "1.0") @@ -51,38 +73,24 @@ struct ClientTests { #expect(result.protocolVersion == Version.latest) #expect(result.serverInfo.name == "TestServer") #expect(result.serverInfo.version == "1.0") - } - // Disconnect client to clean up message loop and give time for continuation cleanup - await client.disconnect() - try await Task.sleep(for: .milliseconds(50)) - } - - @Test( - "Ping request", - .timeLimit(.minutes(1)) - ) - func testClientPing() async throws { - let transport = MockTransport() - let client = Client(name: "TestClient", version: "1.0") + // Small delay to ensure message loop is started + try await Task.sleep(for: .milliseconds(10)) - try await client.connect(transport: transport) - // Small delay to ensure message loop is started - try await Task.sleep(for: .milliseconds(10)) + // Create a task for the ping + let pingTask = Task { + try await client.ping() + } - // Create a task for the ping that we'll cancel - let pingTask = Task { - try await client.ping() - } + // Give it a moment to send the request + try await Task.sleep(for: .milliseconds(10)) - // Give it a moment to send the request - try await Task.sleep(for: .milliseconds(10)) + #expect(await transport.sentMessages.count == 2) // Initialize + Ping + #expect(await transport.sentMessages.last?.contains(Ping.name) == true) - #expect(await transport.sentMessages.count == 1) - #expect(await transport.sentMessages.first?.contains(Ping.name) == true) - - // Cancel the ping task - pingTask.cancel() + // Cancel the ping task + pingTask.cancel() + } // Disconnect client to clean up message loop and give time for continuation cleanup await client.disconnect() @@ -112,10 +120,35 @@ struct ClientTests { @Test("Send failure handling") func testClientSendFailure() async throws { let transport = MockTransport() - await transport.setFailSend(true) let client = Client(name: "TestClient", version: "1.0") + // Set up a task to handle the initialize response + let initTask = Task { + try await Task.sleep(for: .milliseconds(10)) + if let lastMessage = await transport.sentMessages.last, + let data = lastMessage.data(using: .utf8), + let request = try? JSONDecoder().decode(Request.self, from: data) + { + let response = Initialize.response( + id: request.id, + result: .init( + protocolVersion: Version.latest, + capabilities: .init(), + serverInfo: .init(name: "TestServer", version: "1.0"), + instructions: nil + ) + ) + try await transport.queue(response: response) + } + } + + // Connect first without failure try await client.connect(transport: transport) + try await Task.sleep(for: .milliseconds(10)) + initTask.cancel() + + // Now set the transport to fail sends + await transport.setFailSend(true) do { try await client.ping() @@ -124,11 +157,13 @@ struct ClientTests { if case MCPError.transportError = error { #expect(Bool(true)) } else { - #expect(Bool(false), "Expected transport error") + #expect(Bool(false), "Expected transport error, got \(error)") } } catch { #expect(Bool(false), "Expected MCP.Error") } + + await client.disconnect() } @Test("Strict configuration - capabilities check") @@ -137,6 +172,26 @@ struct ClientTests { let config = Client.Configuration.strict let client = Client(name: "TestClient", version: "1.0", configuration: config) + // Set up a task to handle the initialize response + let initTask = Task { + try await Task.sleep(for: .milliseconds(10)) + if let lastMessage = await transport.sentMessages.last, + let data = lastMessage.data(using: .utf8), + let request = try? JSONDecoder().decode(Request.self, from: data) + { + let response = Initialize.response( + id: request.id, + result: .init( + protocolVersion: Version.latest, + capabilities: .init(), + serverInfo: .init(name: "TestServer", version: "1.0"), + instructions: nil + ) + ) + try await transport.queue(response: response) + } + } + try await client.connect(transport: transport) // Create a task for listPrompts @@ -160,6 +215,7 @@ struct ClientTests { // Cancel the task if it's still running promptsTask.cancel() + initTask.cancel() // Disconnect client await client.disconnect() @@ -172,8 +228,31 @@ struct ClientTests { let config = Client.Configuration.default let client = Client(name: "TestClient", version: "1.0", configuration: config) + // Set up a task to handle the initialize response + let initTask = Task { + try await Task.sleep(for: .milliseconds(10)) + if let lastMessage = await transport.sentMessages.last, + let data = lastMessage.data(using: .utf8), + let request = try? JSONDecoder().decode(Request.self, from: data) + { + let response = Initialize.response( + id: request.id, + result: .init( + protocolVersion: Version.latest, + capabilities: .init(), + serverInfo: .init(name: "TestServer", version: "1.0"), + instructions: nil + ) + ) + try await transport.queue(response: response) + } + } + try await client.connect(transport: transport) + // Make sure init task is complete + initTask.cancel() + // Wait a bit for any setup to complete try await Task.sleep(for: .milliseconds(10)) @@ -240,8 +319,29 @@ struct ClientTests { let transport = MockTransport() let client = Client(name: "TestClient", version: "1.0") + // Set up a task to handle the initialize response + let initTask = Task { + try await Task.sleep(for: .milliseconds(10)) + if let lastMessage = await transport.sentMessages.last, + let data = lastMessage.data(using: .utf8), + let request = try? JSONDecoder().decode(Request.self, from: data) + { + let response = Initialize.response( + id: request.id, + result: .init( + protocolVersion: Version.latest, + capabilities: .init(), + serverInfo: .init(name: "TestServer", version: "1.0"), + instructions: nil + ) + ) + try await transport.queue(response: response) + } + } + try await client.connect(transport: transport) try await Task.sleep(for: .milliseconds(10)) // Allow connection tasks + initTask.cancel() let request1 = Ping.request() let request2 = Ping.request() @@ -253,11 +353,11 @@ struct ClientTests { resultTask2 = try await batch.addRequest(request2) } - // Check if one batch message was sent + // Check if batch message was sent (after initialize and initialized notification) let sentMessages = await transport.sentMessages - #expect(sentMessages.count == 1) + #expect(sentMessages.count == 3) // Initialize request + Initialized notification + Batch - guard let batchData = sentMessages.first?.data(using: .utf8) else { + guard let batchData = sentMessages.last?.data(using: .utf8) else { #expect(Bool(false), "Failed to get batch data") return } @@ -299,8 +399,29 @@ struct ClientTests { let transport = MockTransport() let client = Client(name: "TestClient", version: "1.0") + // Set up a task to handle the initialize response + let initTask = Task { + try await Task.sleep(for: .milliseconds(10)) + if let lastMessage = await transport.sentMessages.last, + let data = lastMessage.data(using: .utf8), + let request = try? JSONDecoder().decode(Request.self, from: data) + { + let response = Initialize.response( + id: request.id, + result: .init( + protocolVersion: Version.latest, + capabilities: .init(), + serverInfo: .init(name: "TestServer", version: "1.0"), + instructions: nil + ) + ) + try await transport.queue(response: response) + } + } + try await client.connect(transport: transport) try await Task.sleep(for: .milliseconds(10)) + initTask.cancel() let request1 = Ping.request() // Success let request2 = Ping.request() // Error @@ -312,8 +433,8 @@ struct ClientTests { resultTasks.append(try await batch.addRequest(request2)) } - // Check if one batch message was sent - #expect(await transport.sentMessages.count == 1) + // Check if batch message was sent (after initialize and initialized notification) + #expect(await transport.sentMessages.count == 3) // Initialize request + Initialized notification + Batch // Prepare batch response (success for 1, error for 2) let response1 = Response(id: request1.id, result: .init()) @@ -358,16 +479,37 @@ struct ClientTests { let transport = MockTransport() let client = Client(name: "TestClient", version: "1.0") + // Set up a task to handle the initialize response + let initTask = Task { + try await Task.sleep(for: .milliseconds(10)) + if let lastMessage = await transport.sentMessages.last, + let data = lastMessage.data(using: .utf8), + let request = try? JSONDecoder().decode(Request.self, from: data) + { + let response = Initialize.response( + id: request.id, + result: .init( + protocolVersion: Version.latest, + capabilities: .init(), + serverInfo: .init(name: "TestServer", version: "1.0"), + instructions: nil + ) + ) + try await transport.queue(response: response) + } + } + try await client.connect(transport: transport) try await Task.sleep(for: .milliseconds(10)) + initTask.cancel() // Call withBatch but don't add any requests try await client.withBatch { _ in // No requests added } - // Check that no messages were sent - #expect(await transport.sentMessages.isEmpty) + // Check that only initialize message and initialized notification were sent + #expect(await transport.sentMessages.count == 2) // Initialize request + Initialized notification await client.disconnect() } @@ -377,17 +519,38 @@ struct ClientTests { let transport = MockTransport() let client = Client(name: "TestClient", version: "1.0") + // Set up a task to handle the initialize response + let initTask = Task { + try await Task.sleep(for: .milliseconds(10)) + if let lastMessage = await transport.sentMessages.last, + let data = lastMessage.data(using: .utf8), + let request = try? JSONDecoder().decode(Request.self, from: data) + { + let response = Initialize.response( + id: request.id, + result: .init( + protocolVersion: Version.latest, + capabilities: .init(), + serverInfo: .init(name: "TestServer", version: "1.0"), + instructions: nil + ) + ) + try await transport.queue(response: response) + } + } + try await client.connect(transport: transport) try await Task.sleep(for: .milliseconds(10)) + initTask.cancel() // Create a test notification let notification = InitializedNotification.message() try await client.notify(notification) - // Verify notification was sent - #expect(await transport.sentMessages.count == 1) + // Verify notification was sent (in addition to initialize and initialized notification) + #expect(await transport.sentMessages.count == 3) // Initialize request + Initialized notification + Custom notification - if let sentMessage = await transport.sentMessages.first, + if let sentMessage = await transport.sentMessages.last, let data = sentMessage.data(using: .utf8) { @@ -412,9 +575,6 @@ struct ClientTests { let transport = MockTransport() let client = Client(name: "TestClient", version: "1.0") - try await client.connect(transport: transport) - try await Task.sleep(for: .milliseconds(10)) - // Create a task for initialize let initTask = Task { // Queue a response for the initialize request @@ -439,7 +599,8 @@ struct ClientTests { try await transport.queue(response: response) // Now complete the initialize call - _ = try await client.initialize() + try await client.connect(transport: transport) + try await Task.sleep(for: .milliseconds(10)) // Verify that two messages were sent: initialize request and initialized notification #expect(await transport.sentMessages.count == 2) diff --git a/Tests/MCPTests/HTTPClientTransportTests.swift b/Tests/MCPTests/HTTPClientTransportTests.swift index c2149bb7..9056a1bf 100644 --- a/Tests/MCPTests/HTTPClientTransportTests.swift +++ b/Tests/MCPTests/HTTPClientTransportTests.swift @@ -653,11 +653,8 @@ import Testing } } - // Execute the complete flow - try await client.connect(transport: transport) - // Step 1: Initialize client - let initResult = try await client.initialize() + let initResult = try await client.connect(transport: transport) #expect(initResult.protocolVersion == Version.latest) #expect(initResult.capabilities.tools != nil) diff --git a/Tests/MCPTests/RoundtripTests.swift b/Tests/MCPTests/RoundtripTests.swift index 11978401..5cd3f865 100644 --- a/Tests/MCPTests/RoundtripTests.swift +++ b/Tests/MCPTests/RoundtripTests.swift @@ -99,10 +99,9 @@ struct RoundtripTests { let client = Client(name: "TestClient", version: "1.0") try await server.start(transport: serverTransport) - try await client.connect(transport: clientTransport) let initTask = Task { - let result = try await client.initialize() + let result = try await client.connect(transport: clientTransport) #expect(result.serverInfo.name == "TestServer") #expect(result.serverInfo.version == "1.0.0") From bbf22dc7ae63ec115fdac2ba8ca99d502290d5db Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 26 May 2025 05:19:50 -0700 Subject: [PATCH 7/9] Update README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7c778f41..2f31f8ff 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ import MCP // Initialize the client let client = Client(name: "MyApp", version: "1.0.0") -// Create a transport and connect - initialization happens automatically +// Create a transport and connect let transport = StdioTransport() let result = try await client.connect(transport: transport) @@ -404,7 +404,7 @@ The server component allows your application to host model capabilities and resp ```swift import MCP -// Initialize the server with capabilities +// Create a server with given capabilities let server = Server( name: "MyModelServer", version: "1.0.0", From 568ae9fcdbd60e904233fe2f1a21e2d7c6acd5d4 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 26 May 2025 06:01:43 -0700 Subject: [PATCH 8/9] Fix sampling tests --- Tests/MCPTests/SamplingTests.swift | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/Tests/MCPTests/SamplingTests.swift b/Tests/MCPTests/SamplingTests.swift index f10964e5..6ad4aa83 100644 --- a/Tests/MCPTests/SamplingTests.swift +++ b/Tests/MCPTests/SamplingTests.swift @@ -380,30 +380,6 @@ struct SamplingIntegrationTests { try await server.start(transport: serverTransport) try await client.connect(transport: clientTransport) - // Test initialization and capability negotiation - let initTask = Task { - let result = try await client.initialize() - - #expect(result.serverInfo.name == "SamplingTestServer") - #expect(result.serverInfo.version == "1.0.0") - #expect( - result.capabilities.sampling != nil, "Server should advertise sampling capability") - #expect(result.protocolVersion == Version.latest) - } - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await Task.sleep(for: .seconds(1)) - initTask.cancel() - throw CancellationError() - } - group.addTask { - try await initTask.value - } - try await group.next() - group.cancelAll() - } - await server.stop() await client.disconnect() try? clientToServerRead.close() From e6619d4c2f0ccf6dc6e0dfad2b9231d1dfc5978b Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 26 May 2025 06:01:55 -0700 Subject: [PATCH 9/9] Fix race condition tests --- Tests/MCPTests/ClientTests.swift | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Tests/MCPTests/ClientTests.swift b/Tests/MCPTests/ClientTests.swift index 12d3dfd9..6fcc87a4 100644 --- a/Tests/MCPTests/ClientTests.swift +++ b/Tests/MCPTests/ClientTests.swift @@ -654,8 +654,29 @@ struct ClientTests { let transport = MockTransport() let client = Client(name: "TestClient", version: "1.0") + // Set up a task to handle the initialize response + let initTask = Task { + try await Task.sleep(for: .milliseconds(10)) + if let lastMessage = await transport.sentMessages.last, + let data = lastMessage.data(using: .utf8), + let request = try? JSONDecoder().decode(Request.self, from: data) + { + let response = Initialize.response( + id: request.id, + result: .init( + protocolVersion: Version.latest, + capabilities: .init(), + serverInfo: .init(name: "TestServer", version: "1.0"), + instructions: nil + ) + ) + try await transport.queue(response: response) + } + } + try await client.connect(transport: transport) try await Task.sleep(for: .milliseconds(10)) + initTask.cancel() // Set up the transport to fail sends from the start await transport.setFailSend(true) @@ -694,8 +715,29 @@ struct ClientTests { let transport = MockTransport() let client = Client(name: "TestClient", version: "1.0") + // Set up a task to handle the initialize response + let initTask = Task { + try await Task.sleep(for: .milliseconds(10)) + if let lastMessage = await transport.sentMessages.last, + let data = lastMessage.data(using: .utf8), + let request = try? JSONDecoder().decode(Request.self, from: data) + { + let response = Initialize.response( + id: request.id, + result: .init( + protocolVersion: Version.latest, + capabilities: .init(), + serverInfo: .init(name: "TestServer", version: "1.0"), + instructions: nil + ) + ) + try await transport.queue(response: response) + } + } + try await client.connect(transport: transport) try await Task.sleep(for: .milliseconds(10)) + initTask.cancel() // Create a ping request to get the ID let request = Ping.request()