swift-sdk/Sources/MCP/StdioTransport.swift
2025-02-05 11:13:57 -08:00

105 lines
No EOL
3.5 KiB
Swift

import Foundation
/// A transport implementation that communicates over standard input/output
public actor StdioTransport: MCPTransport {
private var isConnected = false
private let processQueue = DispatchQueue(label: "com.mcp.stdio", qos: .userInitiated)
private let process: Process
private let inputPipe = Pipe()
private let outputPipe = Pipe()
private let errorPipe = Pipe()
public init(command: String, arguments: [String] = [], environment: [String: String]? = nil) {
self.process = Process()
self.process.executableURL = URL(fileURLWithPath: command)
self.process.arguments = arguments
if let env = environment {
self.process.environment = env
}
self.process.standardInput = inputPipe
self.process.standardOutput = outputPipe
self.process.standardError = errorPipe
}
public func connect() async throws {
guard !isConnected else { return }
try process.run()
isConnected = true
// Start reading output asynchronously
Task {
await readOutput()
}
}
public func disconnect() async {
guard isConnected else { return }
process.terminate()
isConnected = false
}
public func sendRequest(_ data: Data) async throws -> Data {
guard isConnected else {
throw MCPError.connectionClosed
}
// Write request
try await withCheckedThrowingContinuation { continuation in
processQueue.async {
do {
let message = data + "\n".data(using: .utf8)!
try self.inputPipe.fileHandleForWriting.write(contentsOf: message)
// Read response
if let response = try self.outputPipe.fileHandleForReading.readToEnd() {
continuation.resume(returning: response)
} else {
continuation.resume(throwing: MCPError.invalidResponse)
}
} catch {
continuation.resume(throwing: MCPError.transportError(error))
}
}
}
}
public func sendNotification(_ data: Data) async throws {
guard isConnected else {
throw MCPError.connectionClosed
}
try await withCheckedThrowingContinuation { continuation in
processQueue.async {
do {
let message = data + "\n".data(using: .utf8)!
try self.inputPipe.fileHandleForWriting.write(contentsOf: message)
continuation.resume()
} catch {
continuation.resume(throwing: MCPError.transportError(error))
}
}
}
}
private func readOutput() async {
let handle = outputPipe.fileHandleForReading
while isConnected {
do {
let data = try handle.read(upToCount: 4096)
if let data = data, !data.isEmpty {
// Process received data
// In a real implementation, you would parse the JSON-RPC messages
// and handle them appropriately
print("Received data: \(String(data: data, encoding: .utf8) ?? "")")
}
} catch {
print("Error reading output: \(error)")
break
}
}
}
}