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:
parent
bdaa2585c5
commit
daef492821
3 changed files with 159 additions and 7 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue