Initial commit
This commit is contained in:
commit
cc603d35fe
1 changed files with 970 additions and 0 deletions
105
Sources/MCP/StdioTransport.swift
Normal file
105
Sources/MCP/StdioTransport.swift
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue