From daef49282143f31261aefb6b38d366ae7aae3572 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 6 May 2025 06:56:24 -0700 Subject: [PATCH] Improve documentation comments for transport types (#98) * Add example usage to HTTPClientTransport documentation comments * Add documentation comments to NetworkTransport * Improve documentation comments for StdioTransport --- .../Base/Transports/HTTPClientTransport.swift | 21 +++++ .../Base/Transports/NetworkTransport.swift | 82 ++++++++++++++++++- .../MCP/Base/Transports/StdioTransport.swift | 63 ++++++++++++-- 3 files changed, 159 insertions(+), 7 deletions(-) diff --git a/Sources/MCP/Base/Transports/HTTPClientTransport.swift b/Sources/MCP/Base/Transports/HTTPClientTransport.swift index 678131b..87eb7f4 100644 --- a/Sources/MCP/Base/Transports/HTTPClientTransport.swift +++ b/Sources/MCP/Base/Transports/HTTPClientTransport.swift @@ -26,6 +26,27 @@ import Logging /// - Streaming HTTP with SSE (`streaming=true`): Enables server-to-client push messages /// /// - Important: Server-Sent Events (SSE) functionality is not supported on Linux platforms. +/// +/// ## Example Usage +/// +/// ```swift +/// import MCP +/// +/// // Create a streaming HTTP transport +/// let transport = HTTPClientTransport( +/// endpoint: URL(string: "http://localhost:8080")!, +/// ) +/// +/// // Initialize the client with streaming transport +/// 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 +/// ``` public actor HTTPClientTransport: Transport { /// The server endpoint URL to connect to public let endpoint: URL diff --git a/Sources/MCP/Base/Transports/NetworkTransport.swift b/Sources/MCP/Base/Transports/NetworkTransport.swift index 69f0dd0..5f18a99 100644 --- a/Sources/MCP/Base/Transports/NetworkTransport.swift +++ b/Sources/MCP/Base/Transports/NetworkTransport.swift @@ -5,9 +5,40 @@ import struct Foundation.Data #if canImport(Network) import Network - /// Network connection based transport implementation + /// An implementation of a custom MCP transport using Apple's Network framework. + /// + /// This transport allows MCP clients and servers to communicate over TCP/UDP connections + /// using Apple's Network framework. + /// + /// - Important: This transport is available exclusively on Apple platforms + /// (macOS, iOS, watchOS, tvOS, visionOS) as it depends on the Network framework. + /// + /// ## Example Usage + /// + /// ```swift + /// import MCP + /// import Network + /// + /// // Create a TCP connection to a server + /// let connection = NWConnection( + /// host: NWEndpoint.Host("localhost"), + /// port: NWEndpoint.Port(8080)!, + /// using: .tcp + /// ) + /// + /// // Initialize the transport with the connection + /// let transport = NetworkTransport(connection: connection) + /// + /// // 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 { private let connection: NWConnection + /// Logger instance for transport-related events public nonisolated let logger: Logger private var isConnected = false @@ -17,6 +48,11 @@ import struct Foundation.Data // Track connection state for continuations private var connectionContinuationResumed = false + /// Creates a new NetworkTransport with the specified NWConnection + /// + /// - Parameters: + /// - connection: The NWConnection to use for communication + /// - logger: Optional logger instance for transport events public init(connection: NWConnection, logger: Logger? = nil) { self.connection = connection self.logger = @@ -32,7 +68,12 @@ import struct Foundation.Data self.messageContinuation = continuation } - /// Connects to the network transport + /// Establishes connection with the transport + /// + /// This initiates the NWConnection and waits for it to become ready. + /// Once the connection is established, it starts the message receiving loop. + /// + /// - Throws: Error if the connection fails to establish public func connect() async throws { guard !isConnected else { return } @@ -77,6 +118,9 @@ import struct Foundation.Data } } + /// Handles when the connection reaches the ready state + /// + /// - Parameter continuation: The continuation to resume when connection is ready private func handleConnectionReady(continuation: CheckedContinuation) async { @@ -90,6 +134,11 @@ import struct Foundation.Data } } + /// Handles connection failure + /// + /// - Parameters: + /// - error: The error that caused the connection to fail + /// - continuation: The continuation to resume with the error private func handleConnectionFailed( error: Swift.Error, continuation: CheckedContinuation ) async { @@ -100,6 +149,9 @@ import struct Foundation.Data } } + /// Handles connection cancellation + /// + /// - Parameter continuation: The continuation to resume with cancellation error private func handleConnectionCancelled(continuation: CheckedContinuation) async { @@ -110,6 +162,10 @@ import struct Foundation.Data } } + /// Disconnects from the transport + /// + /// This cancels the NWConnection, finalizes the message stream, + /// and releases associated resources. public func disconnect() async { guard isConnected else { return } isConnected = false @@ -118,6 +174,13 @@ import struct Foundation.Data logger.info("Network transport disconnected") } + /// Sends data through the network connection + /// + /// This sends a JSON-RPC message through the NWConnection, adding a newline + /// delimiter to mark the end of the message. + /// + /// - Parameter message: The JSON-RPC message to send + /// - Throws: MCPError for transport failures or connection issues public func send(_ message: Data) async throws { guard isConnected else { throw MCPError.internalError("Transport not connected") @@ -158,10 +221,21 @@ import struct Foundation.Data } } + /// Receives data in an async sequence + /// + /// This returns an AsyncThrowingStream that emits Data objects representing + /// each JSON-RPC message received from the network connection. + /// + /// - Returns: An AsyncThrowingStream of Data objects public func receive() -> AsyncThrowingStream { return messageStream } + /// Continuous loop to receive and process incoming messages + /// + /// This method runs continuously while the connection is active, + /// receiving data and yielding complete messages to the message stream. + /// Messages are delimited by newline characters. private func receiveLoop() async { var buffer = Data() @@ -204,6 +278,10 @@ import struct Foundation.Data messageContinuation.finish() } + /// Receives a chunk of data from the network connection + /// + /// - Returns: The received data chunk + /// - Throws: Network errors or transport failures private func receiveData() async throws -> Data { var receiveContinuationResumed = false diff --git a/Sources/MCP/Base/Transports/StdioTransport.swift b/Sources/MCP/Base/Transports/StdioTransport.swift index 3808aad..9aed1ac 100644 --- a/Sources/MCP/Base/Transports/StdioTransport.swift +++ b/Sources/MCP/Base/Transports/StdioTransport.swift @@ -16,22 +16,54 @@ import struct Foundation.Data #endif #if canImport(Darwin) || canImport(Glibc) - /// Standard input/output transport implementation + /// An implementation of the MCP stdio transport protocol. /// - /// This transport supports JSON-RPC 2.0 messages, including individual requests, - /// notifications, responses, and batches containing multiple requests/notifications. + /// This transport implements the [stdio transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#stdio) + /// specification from the Model Context Protocol. /// - /// Messages are delimited by newlines and must not contain embedded newlines. - /// Each message must be a complete, valid JSON object or array (for batches). + /// The stdio transport works by: + /// - Reading JSON-RPC messages from standard input + /// - Writing JSON-RPC messages to standard output + /// - Using newline characters as message delimiters + /// - Supporting non-blocking I/O operations + /// + /// This transport is the recommended option for most MCP applications due to its + /// simplicity and broad platform support. + /// + /// - Important: This transport is available on Apple platforms and Linux distributions with glibc + /// (Ubuntu, Debian, Fedora, CentOS, RHEL). + /// + /// ## Example Usage + /// + /// ```swift + /// import MCP + /// + /// // Initialize the client + /// let client = Client(name: "MyApp", version: "1.0.0") + /// + /// // 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 private let output: FileDescriptor + /// Logger instance for transport-related events public nonisolated let logger: Logger private var isConnected = false private let messageStream: AsyncThrowingStream private let messageContinuation: AsyncThrowingStream.Continuation + /// Creates a new stdio transport with the specified file descriptors + /// + /// - Parameters: + /// - input: File descriptor for reading (defaults to standard input) + /// - output: File descriptor for writing (defaults to standard output) + /// - logger: Optional logger instance for transport events public init( input: FileDescriptor = FileDescriptor.standardInput, output: FileDescriptor = FileDescriptor.standardOutput, @@ -51,6 +83,12 @@ import struct Foundation.Data self.messageContinuation = continuation } + /// Establishes connection with the transport + /// + /// This method configures the file descriptors for non-blocking I/O + /// and starts the background message reading loop. + /// + /// - Throws: Error if the file descriptors cannot be configured public func connect() async throws { guard !isConnected else { return } @@ -67,6 +105,10 @@ import struct Foundation.Data } } + /// Configures a file descriptor for non-blocking I/O + /// + /// - Parameter fileDescriptor: The file descriptor to configure + /// - Throws: Error if the operation fails private func setNonBlocking(fileDescriptor: FileDescriptor) throws { #if canImport(Darwin) || canImport(Glibc) // Get current flags @@ -87,6 +129,11 @@ import struct Foundation.Data #endif } + /// Continuous loop that reads and processes incoming messages + /// + /// This method runs in the background while the transport is connected, + /// parsing complete messages delimited by newlines and yielding them + /// to the message stream. private func readLoop() async { let bufferSize = 4096 var buffer = [UInt8](repeating: 0, count: bufferSize) @@ -130,6 +177,9 @@ import struct Foundation.Data messageContinuation.finish() } + /// Disconnects from the transport + /// + /// This stops the message reading loop and releases associated resources. public func disconnect() async { guard isConnected else { return } isConnected = false @@ -144,6 +194,7 @@ import struct Foundation.Data /// according to the JSON-RPC 2.0 specification. /// /// - Parameter message: The message data to send (without a trailing newline) + /// - Throws: Error if the message cannot be sent public func send(_ message: Data) async throws { guard isConnected else { throw MCPError.transportError(Errno(rawValue: ENOTCONN)) @@ -176,6 +227,8 @@ import struct Foundation.Data /// Messages may be individual JSON-RPC requests, notifications, responses, /// or batches containing multiple requests/notifications encoded as JSON arrays. /// Each message is guaranteed to be a complete JSON object or array. + /// + /// - Returns: An AsyncThrowingStream of Data objects representing JSON-RPC messages public func receive() -> AsyncThrowingStream { return messageStream }