Skip to content

Improve documentation comments for transport types #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Sources/MCP/Base/Transports/HTTPClientTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
82 changes: 80 additions & 2 deletions Sources/MCP/Base/Transports/NetworkTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 =
Expand All @@ -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 }

Expand Down Expand Up @@ -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
{
Expand All @@ -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 {
Expand All @@ -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
{
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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

Expand Down
63 changes: 58 additions & 5 deletions Sources/MCP/Base/Transports/StdioTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 }

Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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))
Expand Down Expand Up @@ -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
}
Expand Down