11
22import Foundation
33import JSONRPC
4+ import MCPInterface
45import OSLog
56
67private let logger = Logger (
@@ -22,37 +23,36 @@ extension JSONRPCSetupError: LocalizedError {
2223 public var errorDescription : String ? {
2324 switch self {
2425 case . missingStandardIO:
25- return " Missing standard IO "
26+ " Missing standard IO "
2627 case . couldNotLocateExecutable( let executable, let error) :
27- return " Could not locate executable \( executable) \( error ?? " " ) " . trimmingCharacters ( in: . whitespaces)
28+ " Could not locate executable \( executable) \( error ?? " " ) " . trimmingCharacters ( in: . whitespaces)
2829 case . standardIOConnectionError( let message) :
29- return " Could not connect to stdio: \( message) " . trimmingCharacters ( in: . whitespaces)
30+ " Could not connect to stdio: \( message) " . trimmingCharacters ( in: . whitespaces)
3031 }
3132 }
3233
3334 public var recoverySuggestion : String ? {
3435 switch self {
3536 case . missingStandardIO:
36- return " Make sure that the Process that is passed as an argument has stdin, stdout and stderr set as a Pipe. "
37+ " Make sure that the Process that is passed as an argument has stdin, stdout and stderr set as a Pipe. "
3738 case . couldNotLocateExecutable:
38- return " Check that the executable is findable given the PATH environment variable. If needed, pass the right environment to the process. "
39+ " Check that the executable is findable given the PATH environment variable. If needed, pass the right environment to the process. "
3940 case . standardIOConnectionError:
40- return nil
41+ nil
4142 }
4243 }
4344}
4445
45- extension DataChannel {
46-
47- // MARK: Public
46+ extension Transport {
4847
48+ /// Creates a new `Transport` by launching the given executable with the specified arguments and attaching to its standard IO.
4949 public static func stdioProcess(
5050 _ executable: String ,
5151 args: [ String ] = [ ] ,
5252 cwd: String ? = nil ,
5353 env: [ String : String ] ? = nil ,
5454 verbose: Bool = false )
55- throws -> DataChannel
55+ throws -> Transport
5656 {
5757 if verbose {
5858 let command = " \( executable) \( args. joined ( separator: " " ) ) "
@@ -103,10 +103,11 @@ extension DataChannel {
103103 return try stdioProcess ( unlaunchedProcess: process, verbose: verbose)
104104 }
105105
106+ /// Creates a new `Transport` by launching the given process and attaching to its standard IO.
106107 public static func stdioProcess(
107108 unlaunchedProcess process: Process ,
108109 verbose: Bool = false )
109- throws -> DataChannel
110+ throws -> Transport
110111 {
111112 guard
112113 let stdin = process. standardInput as? Pipe ,
@@ -119,7 +120,6 @@ extension DataChannel {
119120 // Run the process
120121 var stdoutData = Data ( )
121122 var stderrData = Data ( )
122-
123123 let outStream : AsyncStream < Data >
124124 if verbose {
125125 // As we are both reading stdout here in this function, and want to make the stream readable to the caller,
@@ -131,7 +131,7 @@ extension DataChannel {
131131 }
132132
133133 Task {
134- for await data in stdout. fileHandleForReading. dataStream {
134+ for await data in stdout. fileHandleForReading. dataStream. jsonStream {
135135 stdoutData. append ( data)
136136 outContinuation? . yield ( data)
137137
@@ -150,10 +150,10 @@ extension DataChannel {
150150 }
151151 } else {
152152 // If we are not in verbose mode, we are not reading from stdout internally, so we can just return the stream directly.
153- outStream = stdout. fileHandleForReading. dataStream
153+ outStream = stdout. fileHandleForReading. dataStream. jsonStream
154154 }
155155
156- // Ensures that the process is terminated when the DataChannel is de-referenced.
156+ // Ensures that the process is terminated when the Transport is de-referenced.
157157 let lifetime = Lifetime {
158158 if process. isRunning {
159159 process. terminate ( )
@@ -177,7 +177,7 @@ extension DataChannel {
177177 throw error
178178 }
179179
180- let writeHandler : DataChannel . WriteHandler = { [ lifetime] data in
180+ let writeHandler : Transport . WriteHandler = { [ lifetime] data in
181181 _ = lifetime
182182 if verbose {
183183 logger. log ( " Sending data: \n \( String ( data: data, encoding: . utf8) ?? " nil " ) " )
@@ -188,11 +188,9 @@ extension DataChannel {
188188 stdin. fileHandleForWriting. write ( Data ( " \n " . utf8) )
189189 }
190190
191- return DataChannel ( writeHandler: writeHandler, dataSequence: outStream)
191+ return Transport ( writeHandler: writeHandler, dataSequence: outStream)
192192 }
193193
194- // MARK: Private
195-
196194 /// Finds the full path to the executable using the `which` command.
197195 private static func locate( executable: String , env: [ String : String ] ? = nil ) throws -> String {
198196 let process = Process ( )
@@ -213,10 +211,12 @@ extension DataChannel {
213211 private static func loadZshEnvironment( ) throws -> [ String : String ] {
214212 let process = Process ( )
215213 process. launchPath = " /bin/zsh "
216- process. arguments = [ " -c " , " source ~/.zshrc && printenv " ]
214+ // Those are loaded for interactive login shell by zsh:
215+ // https://www.freecodecamp.org/news/how-do-zsh-configuration-files-work/
216+ process. arguments = [ " -c " , " source ~/.zshenv; source ~/.zprofile; source ~/.zshrc; source ~/.zshrc; printenv " ]
217217 let env = try getProcessStdout ( process: process)
218218
219- if let path = env? . split ( separator: " \n " ) . filter ( { $0. starts ( with: " PATH= " ) } ) . first {
219+ if let path = env? . split ( separator: " \n " ) . filter ( { $0. starts ( with: " PATH= " ) } ) . last {
220220 return [ " PATH " : String ( path. dropFirst ( " PATH= " . count) ) ]
221221 } else {
222222 return ProcessInfo . processInfo. environment
@@ -262,8 +262,6 @@ extension DataChannel {
262262
263263final class Lifetime {
264264
265- // MARK: Lifecycle
266-
267265 init ( onDeinit: @escaping ( ) -> Void ) {
268266 self . onDeinit = onDeinit
269267 }
@@ -272,8 +270,6 @@ final class Lifetime {
272270 onDeinit ( )
273271 }
274272
275- // MARK: Private
276-
277273 private let onDeinit : ( ) -> Void
278274
279275}
0 commit comments