Improve documentation comments for transport types (#98)

* Add example usage to HTTPClientTransport documentation comments

* Add documentation comments to NetworkTransport

* Improve documentation comments for StdioTransport
This commit is contained in:
Mattt 2025-05-06 06:56:24 -07:00 • committed by GitHub
commit daef492821
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 159 additions and 7 deletions

View file

@ -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

View file

@ -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<Void, Swift.Error>)
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<Void, Swift.Error>
) 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<Void, Swift.Error>)
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<Data, Swift.Error> {
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

View file

@ -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<Data, Swift.Error>
private let messageContinuation: AsyncThrowingStream<Data, Swift.Error>.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<Data, Swift.Error> {
return messageStream
}