import Logging import struct Foundation.Data #if canImport(System) import System #else @preconcurrency import SystemPackage #endif // Import for specific low-level operations not yet in Swift System #if canImport(Darwin) import Darwin.POSIX #elseif canImport(Glibc) import Glibc #endif #if canImport(Darwin) || canImport(Glibc) /// Standard input/output transport implementation /// /// This transport supports JSON-RPC 2.0 messages, including individual requests, /// notifications, responses, and batches containing multiple requests/notifications. /// /// Messages are delimited by newlines and must not contain embedded newlines. /// Each message must be a complete, valid JSON object or array (for batches). public actor StdioTransport: Transport { private let input: FileDescriptor private let output: FileDescriptor public nonisolated let logger: Logger private var isConnected = false private let messageStream: AsyncThrowingStream private let messageContinuation: AsyncThrowingStream.Continuation public init( input: FileDescriptor = FileDescriptor.standardInput, output: FileDescriptor = FileDescriptor.standardOutput, logger: Logger? = nil ) { self.input = input self.output = output self.logger = logger ?? Logger( label: "mcp.transport.stdio", factory: { _ in SwiftLogNoOpLogHandler() }) // Create message stream var continuation: AsyncThrowingStream.Continuation! self.messageStream = AsyncThrowingStream { continuation = $0 } self.messageContinuation = continuation } public func connect() async throws { guard !isConnected else { return } // Set non-blocking mode try setNonBlocking(fileDescriptor: input) try setNonBlocking(fileDescriptor: output) isConnected = true logger.info("Transport connected successfully") // Start reading loop in background Task { await readLoop() } } private func setNonBlocking(fileDescriptor: FileDescriptor) throws { #if canImport(Darwin) || canImport(Glibc) // Get current flags let flags = fcntl(fileDescriptor.rawValue, F_GETFL) guard flags >= 0 else { throw MCPError.transportError(Errno(rawValue: CInt(errno))) } // Set non-blocking flag let result = fcntl(fileDescriptor.rawValue, F_SETFL, flags | O_NONBLOCK) guard result >= 0 else { throw MCPError.transportError(Errno(rawValue: CInt(errno))) } #else // For platforms where non-blocking operations aren't supported throw MCPError.internalError( "Setting non-blocking mode not supported on this platform") #endif } private func readLoop() async { let bufferSize = 4096 var buffer = [UInt8](repeating: 0, count: bufferSize) var pendingData = Data() while isConnected && !Task.isCancelled { do { let bytesRead = try buffer.withUnsafeMutableBufferPointer { pointer in try input.read(into: UnsafeMutableRawBufferPointer(pointer)) } if bytesRead == 0 { logger.notice("EOF received") break } pendingData.append(Data(buffer[.. 0 { remaining = remaining.dropFirst(written) } } catch let error where MCPError.isResourceTemporarilyUnavailable(error) { try await Task.sleep(for: .milliseconds(10)) continue } catch { throw MCPError.transportError(error) } } } /// Receives messages from the transport. /// /// 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. public func receive() -> AsyncThrowingStream { return messageStream } } #endif