Skip to content

Commit f3f4cfd

Browse files
committed
Add support for platforms with Musl
Replace os checks with canImport
1 parent 597c260 commit f3f4cfd

File tree

1 file changed

+113
-117
lines changed

1 file changed

+113
-117
lines changed

Sources/MCP/Base/Transports/StdioTransport.swift

Lines changed: 113 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -9,59 +9,61 @@ import struct Foundation.Data
99
#endif
1010

1111
// Import for specific low-level operations not yet in Swift System
12-
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
12+
#if canImport(Darwin)
1313
import Darwin.POSIX
14-
#elseif os(Linux)
14+
#elseif canImport(Glibc)
1515
import Glibc
16+
#elseif canImport(Musl)
17+
import Musl
1618
#endif
1719

18-
/// Standard input/output transport implementation
19-
public actor StdioTransport: Transport {
20-
private let input: FileDescriptor
21-
private let output: FileDescriptor
22-
public nonisolated let logger: Logger
23-
24-
private var isConnected = false
25-
private let messageStream: AsyncStream<Data>
26-
private let messageContinuation: AsyncStream<Data>.Continuation
27-
28-
public init(
29-
input: FileDescriptor = FileDescriptor.standardInput,
30-
output: FileDescriptor = FileDescriptor.standardOutput,
31-
logger: Logger? = nil
32-
) {
33-
self.input = input
34-
self.output = output
35-
self.logger =
36-
logger
37-
?? Logger(
38-
label: "mcp.transport.stdio",
39-
factory: { _ in SwiftLogNoOpLogHandler() })
40-
41-
// Create message stream
42-
var continuation: AsyncStream<Data>.Continuation!
43-
self.messageStream = AsyncStream { continuation = $0 }
44-
self.messageContinuation = continuation
45-
}
20+
#if canImport(Darwin) || canImport(Glibc) || canImport(Musl)
21+
/// Standard input/output transport implementation
22+
public actor StdioTransport: Transport {
23+
private let input: FileDescriptor
24+
private let output: FileDescriptor
25+
public nonisolated let logger: Logger
26+
27+
private var isConnected = false
28+
private let messageStream: AsyncStream<Data>
29+
private let messageContinuation: AsyncStream<Data>.Continuation
30+
31+
public init(
32+
input: FileDescriptor = FileDescriptor.standardInput,
33+
output: FileDescriptor = FileDescriptor.standardOutput,
34+
logger: Logger? = nil
35+
) {
36+
self.input = input
37+
self.output = output
38+
self.logger =
39+
logger
40+
?? Logger(
41+
label: "mcp.transport.stdio",
42+
factory: { _ in SwiftLogNoOpLogHandler() })
43+
44+
// Create message stream
45+
var continuation: AsyncStream<Data>.Continuation!
46+
self.messageStream = AsyncStream { continuation = $0 }
47+
self.messageContinuation = continuation
48+
}
4649

47-
public func connect() async throws {
48-
guard !isConnected else { return }
50+
public func connect() async throws {
51+
guard !isConnected else { return }
4952

50-
// Set non-blocking mode
51-
try setNonBlocking(fileDescriptor: input)
52-
try setNonBlocking(fileDescriptor: output)
53+
// Set non-blocking mode
54+
try setNonBlocking(fileDescriptor: input)
55+
try setNonBlocking(fileDescriptor: output)
5356

54-
isConnected = true
55-
logger.info("Transport connected successfully")
57+
isConnected = true
58+
logger.info("Transport connected successfully")
5659

57-
// Start reading loop in background
58-
Task {
59-
await readLoop()
60+
// Start reading loop in background
61+
Task {
62+
await readLoop()
63+
}
6064
}
61-
}
6265

63-
private func setNonBlocking(fileDescriptor: FileDescriptor) throws {
64-
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(Linux)
66+
private func setNonBlocking(fileDescriptor: FileDescriptor) throws {
6567
// Get current flags
6668
let flags = fcntl(fileDescriptor.rawValue, F_GETFL)
6769
guard flags >= 0 else {
@@ -73,100 +75,94 @@ public actor StdioTransport: Transport {
7375
guard result >= 0 else {
7476
throw MCPError.transportError(Errno(rawValue: CInt(errno)))
7577
}
76-
#else
77-
// For platforms where non-blocking operations aren't supported
78-
throw MCPError.internalError("Setting non-blocking mode not supported on this platform")
79-
#endif
80-
}
78+
}
8179

82-
private func readLoop() async {
83-
let bufferSize = 4096
84-
var buffer = [UInt8](repeating: 0, count: bufferSize)
85-
var pendingData = Data()
80+
private func readLoop() async {
81+
let bufferSize = 4096
82+
var buffer = [UInt8](repeating: 0, count: bufferSize)
83+
var pendingData = Data()
8684

87-
while isConnected && !Task.isCancelled {
88-
do {
89-
let bytesRead = try buffer.withUnsafeMutableBufferPointer { pointer in
90-
try input.read(into: UnsafeMutableRawBufferPointer(pointer))
91-
}
85+
while isConnected && !Task.isCancelled {
86+
do {
87+
let bytesRead = try buffer.withUnsafeMutableBufferPointer { pointer in
88+
try input.read(into: UnsafeMutableRawBufferPointer(pointer))
89+
}
9290

93-
if bytesRead == 0 {
94-
logger.notice("EOF received")
95-
break
96-
}
91+
if bytesRead == 0 {
92+
logger.notice("EOF received")
93+
break
94+
}
9795

98-
pendingData.append(Data(buffer[..<bytesRead]))
96+
pendingData.append(Data(buffer[..<bytesRead]))
9997

100-
// Process complete messages
101-
while let newlineIndex = pendingData.firstIndex(of: UInt8(ascii: "\n")) {
102-
let messageData = pendingData[..<newlineIndex]
103-
pendingData = pendingData[(newlineIndex + 1)...]
98+
// Process complete messages
99+
while let newlineIndex = pendingData.firstIndex(of: UInt8(ascii: "\n")) {
100+
let messageData = pendingData[..<newlineIndex]
101+
pendingData = pendingData[(newlineIndex + 1)...]
104102

105-
if !messageData.isEmpty {
106-
logger.debug("Message received", metadata: ["size": "\(messageData.count)"])
107-
messageContinuation.yield(Data(messageData))
103+
if !messageData.isEmpty {
104+
logger.debug(
105+
"Message received", metadata: ["size": "\(messageData.count)"])
106+
messageContinuation.yield(Data(messageData))
107+
}
108108
}
109+
} catch let error where MCPError.isResourceTemporarilyUnavailable(error) {
110+
try? await Task.sleep(for: .milliseconds(10))
111+
continue
112+
} catch {
113+
if !Task.isCancelled {
114+
logger.error("Read error occurred", metadata: ["error": "\(error)"])
115+
}
116+
break
109117
}
110-
} catch let error where MCPError.isResourceTemporarilyUnavailable(error) {
111-
try? await Task.sleep(for: .milliseconds(10))
112-
continue
113-
} catch {
114-
if !Task.isCancelled {
115-
logger.error("Read error occurred", metadata: ["error": "\(error)"])
116-
}
117-
break
118118
}
119-
}
120119

121-
messageContinuation.finish()
122-
}
120+
messageContinuation.finish()
121+
}
123122

124-
public func disconnect() async {
125-
guard isConnected else { return }
126-
isConnected = false
127-
messageContinuation.finish()
128-
logger.info("Transport disconnected")
129-
}
123+
public func disconnect() async {
124+
guard isConnected else { return }
125+
isConnected = false
126+
messageContinuation.finish()
127+
logger.info("Transport disconnected")
128+
}
130129

131-
public func send(_ message: Data) async throws {
132-
guard isConnected else {
133-
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(Linux)
130+
public func send(_ message: Data) async throws {
131+
guard isConnected else {
134132
throw MCPError.transportError(Errno(rawValue: ENOTCONN))
135-
#else
136-
throw MCPError.internalError("Transport not connected")
137-
#endif
138-
}
133+
}
139134

140-
// Add newline as delimiter
141-
var messageWithNewline = message
142-
messageWithNewline.append(UInt8(ascii: "\n"))
135+
// Add newline as delimiter
136+
var messageWithNewline = message
137+
messageWithNewline.append(UInt8(ascii: "\n"))
143138

144-
var remaining = messageWithNewline
145-
while !remaining.isEmpty {
146-
do {
147-
let written = try remaining.withUnsafeBytes { buffer in
148-
try output.write(UnsafeRawBufferPointer(buffer))
149-
}
150-
if written > 0 {
151-
remaining = remaining.dropFirst(written)
139+
var remaining = messageWithNewline
140+
while !remaining.isEmpty {
141+
do {
142+
let written = try remaining.withUnsafeBytes { buffer in
143+
try output.write(UnsafeRawBufferPointer(buffer))
144+
}
145+
if written > 0 {
146+
remaining = remaining.dropFirst(written)
147+
}
148+
} catch let error where MCPError.isResourceTemporarilyUnavailable(error) {
149+
try await Task.sleep(for: .milliseconds(10))
150+
continue
151+
} catch {
152+
throw MCPError.transportError(error)
152153
}
153-
} catch let error where MCPError.isResourceTemporarilyUnavailable(error) {
154-
try await Task.sleep(for: .milliseconds(10))
155-
continue
156-
} catch {
157-
throw MCPError.transportError(error)
158154
}
159155
}
160-
}
161156

162-
public func receive() -> AsyncThrowingStream<Data, Swift.Error> {
163-
return AsyncThrowingStream { continuation in
164-
Task {
165-
for await message in messageStream {
166-
continuation.yield(message)
157+
public func receive() -> AsyncThrowingStream<Data, Swift.Error> {
158+
return AsyncThrowingStream { continuation in
159+
Task {
160+
for await message in messageStream {
161+
continuation.yield(message)
162+
}
163+
continuation.finish()
167164
}
168-
continuation.finish()
169165
}
170166
}
171167
}
172-
}
168+
#endif

0 commit comments

Comments
 (0)